feat: full site build — Project/Melody schema (Option A), admin CRUD, public sections, uploads, email+SMTP, internal analytics, legal pages, docs
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const melodyStatusSchema = z.enum(["DRAFT", "PUBLISHED", "ARCHIVED"]);
|
||||
const audioPath = z.string().trim().regex(/^\/api\/uploads\/audios\/[^/]+$/, "Use an uploaded audio path.");
|
||||
const imagePath = z.string().trim().regex(/^\/api\/uploads\/images\/[^/]+$/, "Use an uploaded image path.");
|
||||
|
||||
export const melodySchema = z.object({
|
||||
slug: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, "Slug is required.")
|
||||
.max(100, "Slug must be 100 characters or fewer.")
|
||||
.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "Use lowercase letters, numbers, and hyphens only."),
|
||||
titleAr: z.string().trim().min(1, "Arabic title is required.").max(160),
|
||||
titleEn: z.string().trim().min(1, "English title is required.").max(160),
|
||||
descAr: z.string().trim().max(4000).optional(),
|
||||
descEn: z.string().trim().max(4000).optional(),
|
||||
audioFile: audioPath,
|
||||
coverImage: z.union([z.literal(""), imagePath]).optional(),
|
||||
durationSec: z.number().int().min(0).max(86400).nullable(),
|
||||
isDownloadable: z.boolean(),
|
||||
status: melodyStatusSchema,
|
||||
isFeatured: z.boolean(),
|
||||
sortOrder: z.number().int().min(0).max(9999),
|
||||
categoryId: z.string().cuid("Choose a valid melody category."),
|
||||
});
|
||||
|
||||
export const melodyIdSchema = z.string().cuid("Invalid melody id.");
|
||||
export type MelodyInput = z.infer<typeof melodySchema>;
|
||||
Reference in New Issue
Block a user