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:
2026-08-05 20:53:40 +02:00
parent d87f3033c6
commit c26f41511f
122 changed files with 15068 additions and 41 deletions
+56
View File
@@ -0,0 +1,56 @@
import { z } from "zod";
export const uploadKindSchema = z.enum(["image", "audio"]);
export type UploadKind = z.infer<typeof uploadKindSchema>;
export const uploadRequestSchema = z.object({
kind: uploadKindSchema,
fileName: z.string().trim().min(1).max(255),
mimeType: z.string().trim().min(1).max(120),
size: z.number().int().positive(),
});
export const uploadedFileSchema = z.object({
kind: uploadKindSchema,
url: z.string().startsWith("/api/uploads/"),
fileName: z.string().min(1),
mimeType: z.string().min(1),
size: z.number().int().positive(),
});
export type UploadRequest = z.infer<typeof uploadRequestSchema>;
export type UploadedFileMetadata = z.infer<typeof uploadedFileSchema>;
export const UPLOAD_RULES: Record<
UploadKind,
{
maxBytes: number;
accept: string;
mimeTypes: readonly string[];
}
> = {
image: {
maxBytes: 10 * 1024 * 1024,
accept: "image/jpeg,image/png,image/webp",
mimeTypes: ["image/jpeg", "image/png", "image/webp"],
},
audio: {
maxBytes: 50 * 1024 * 1024,
accept: "audio/mpeg,audio/wav,audio/x-wav,audio/ogg,audio/webm,audio/mp4,audio/aac,audio/flac",
mimeTypes: [
"audio/mpeg",
"audio/wav",
"audio/x-wav",
"audio/ogg",
"audio/webm",
"audio/mp4",
"audio/aac",
"audio/flac",
],
},
};
export function isAllowedUploadMimeType(kind: UploadKind, mimeType: string) {
return UPLOAD_RULES[kind].mimeTypes.includes(mimeType.toLowerCase());
}