feat(shared): zod şemaları, Türkçe arama ve TL/tarih biçimleri
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
import { z } from "zod";
|
||||
import { tr } from "../../i18n/tr";
|
||||
|
||||
/** Giriş formu: kullanıcı adı veya e-posta + şifre. Biçim kontrolü sunucuda yapılır. */
|
||||
export const loginSchema = z.object({
|
||||
identifier: z.string().trim().min(1, tr.auth.identifierRequired),
|
||||
password: z.string().min(1, tr.auth.passwordRequired),
|
||||
});
|
||||
|
||||
export type LoginInput = z.infer<typeof loginSchema>;
|
||||
@@ -0,0 +1,36 @@
|
||||
import { z } from "zod";
|
||||
import { tr } from "../../i18n/tr";
|
||||
|
||||
const v = tr.validation;
|
||||
|
||||
/** Sistem rolleri (Better Auth admin plugin). */
|
||||
export const SYSTEM_ROLES = ["admin", "user"] as const;
|
||||
export const systemRoleSchema = z.enum(SYSTEM_ROLES);
|
||||
export type SystemRole = z.infer<typeof systemRoleSchema>;
|
||||
|
||||
export const USERNAME_PATTERN = /^[a-z0-9._]{3,30}$/;
|
||||
|
||||
export const nameSchema = z.string().trim().min(2, v.nameMin).max(80, v.nameMax);
|
||||
|
||||
/** Kullanıcı adı küçük harfe çevrilir; Türkçe büyük İ/I sorununu önlemek için tr-TR kullanılır. */
|
||||
export const usernameSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.transform((s) => s.toLocaleLowerCase("tr-TR"))
|
||||
.pipe(z.string().regex(USERNAME_PATTERN, v.usernameFormat));
|
||||
|
||||
export const emailSchema = z.string().trim().toLowerCase().pipe(z.email(v.email));
|
||||
|
||||
/** Boş bırakılabilen e-posta: "" → undefined. */
|
||||
export const optionalEmailSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.transform((s) => (s === "" ? undefined : s.toLowerCase()))
|
||||
.pipe(z.email(v.email).optional());
|
||||
|
||||
export const passwordSchema = z.string().min(8, v.passwordMin).max(128, v.passwordMax);
|
||||
|
||||
/** Girilen değer e-posta mı (giriş sırasında e-posta/kullanıcı adı yöntemini seçmek için)? */
|
||||
export function looksLikeEmail(value: string): boolean {
|
||||
return value.includes("@");
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { loginSchema } from "./auth";
|
||||
import { looksLikeEmail, usernameSchema } from "./common";
|
||||
import { banUserSchema, userCreateSchema, userUpdateSchema } from "./user";
|
||||
|
||||
const base = {
|
||||
name: "Ayşe Yılmaz",
|
||||
username: "ayse",
|
||||
email: "",
|
||||
role: "user",
|
||||
password: "gizli-1234",
|
||||
};
|
||||
|
||||
describe("usernameSchema", () => {
|
||||
it("küçük harfe çevirir ve boşlukları kırpar (Türkçe İ dahil)", () => {
|
||||
expect(usernameSchema.parse(" AYSE.K ")).toBe("ayse.k");
|
||||
expect(usernameSchema.parse("Ali_2")).toBe("ali_2");
|
||||
});
|
||||
|
||||
it("Türkçe karakter, boşluk ve kısa değerleri reddeder", () => {
|
||||
for (const bad of ["ayşe", "ali veli", "ab", "a".repeat(31), "ali-veli"]) {
|
||||
expect(usernameSchema.safeParse(bad).success, bad).toBe(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("userCreateSchema", () => {
|
||||
it("boş e-postayı undefined yapar", () => {
|
||||
const out = userCreateSchema.parse(base);
|
||||
expect(out.email).toBeUndefined();
|
||||
expect(out.name).toBe("Ayşe Yılmaz");
|
||||
});
|
||||
|
||||
it("e-postayı küçük harfe çevirir ve doğrular", () => {
|
||||
expect(userCreateSchema.parse({ ...base, email: " Ayse@Ornek.COM " }).email).toBe(
|
||||
"ayse@ornek.com",
|
||||
);
|
||||
expect(userCreateSchema.safeParse({ ...base, email: "gecersiz" }).success).toBe(false);
|
||||
});
|
||||
|
||||
it("kısa şifreyi ve geçersiz rolü reddeder", () => {
|
||||
expect(userCreateSchema.safeParse({ ...base, password: "1234567" }).success).toBe(false);
|
||||
expect(userCreateSchema.safeParse({ ...base, role: "owner" }).success).toBe(false);
|
||||
});
|
||||
|
||||
it("güncelleme şemasında şifre yoktur", () => {
|
||||
const { password: _password, ...rest } = base;
|
||||
expect(userUpdateSchema.parse(rest)).not.toHaveProperty("password");
|
||||
});
|
||||
});
|
||||
|
||||
describe("banUserSchema", () => {
|
||||
it("boş nedeni undefined yapar, uzun nedeni reddeder", () => {
|
||||
expect(banUserSchema.parse({ reason: " " }).reason).toBeUndefined();
|
||||
expect(banUserSchema.safeParse({ reason: "x".repeat(201) }).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("loginSchema", () => {
|
||||
it("boş alanları Türkçe mesajla reddeder", () => {
|
||||
const r = loginSchema.safeParse({ identifier: " ", password: "" });
|
||||
expect(r.success).toBe(false);
|
||||
expect(r.error?.issues.map((i) => i.message)).toEqual([
|
||||
"Kullanıcı adı veya e-posta girin",
|
||||
"Şifre girin",
|
||||
]);
|
||||
});
|
||||
|
||||
it("e-posta / kullanıcı adı ayrımı", () => {
|
||||
expect(looksLikeEmail("ayse@ornek.com")).toBe(true);
|
||||
expect(looksLikeEmail("ayse")).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import { z } from "zod";
|
||||
import { tr } from "../../i18n/tr";
|
||||
import {
|
||||
nameSchema,
|
||||
optionalEmailSchema,
|
||||
passwordSchema,
|
||||
systemRoleSchema,
|
||||
usernameSchema,
|
||||
} from "./common";
|
||||
|
||||
/** Admin: yeni kullanıcı. E-posta isteğe bağlı (çocuklar vb. için kullanıcı adıyla giriş). */
|
||||
export const userCreateSchema = z.object({
|
||||
name: nameSchema,
|
||||
username: usernameSchema,
|
||||
email: optionalEmailSchema,
|
||||
role: systemRoleSchema,
|
||||
password: passwordSchema,
|
||||
});
|
||||
|
||||
/** Admin: kullanıcı bilgisi güncelleme (şifre ayrı akışta). */
|
||||
export const userUpdateSchema = userCreateSchema.omit({ password: true });
|
||||
|
||||
export const setPasswordSchema = z.object({ password: passwordSchema });
|
||||
|
||||
export const banUserSchema = z.object({
|
||||
reason: z
|
||||
.string()
|
||||
.trim()
|
||||
.max(200, tr.validation.banReasonMax)
|
||||
.transform((s) => (s === "" ? undefined : s))
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export type UserCreateInput = z.input<typeof userCreateSchema>;
|
||||
export type UserCreate = z.output<typeof userCreateSchema>;
|
||||
export type UserUpdateInput = z.input<typeof userUpdateSchema>;
|
||||
export type UserUpdate = z.output<typeof userUpdateSchema>;
|
||||
export type SetPasswordInput = z.input<typeof setPasswordSchema>;
|
||||
export type BanUserInput = z.input<typeof banUserSchema>;
|
||||
Reference in New Issue
Block a user