mirror of
https://github.com/Since-3/since3-portal.git
synced 2026-10-12 21:41:16 +00:00
101 lines
3.6 KiB
TypeScript
101 lines
3.6 KiB
TypeScript
import * as z from "zod";
|
||
|
||
export const MAX_FILE_BYTES = 50 * 1024 * 1024;
|
||
export const MAX_FILES_PER_UPLOAD = 20;
|
||
export const DOCUMENTS_BUCKET = "client-documents";
|
||
|
||
export type Folder = {
|
||
id: string;
|
||
name: string;
|
||
documentCount: number;
|
||
};
|
||
|
||
export type DocumentItem = {
|
||
id: string;
|
||
name: string;
|
||
mimeType: string | null;
|
||
sizeBytes: number | null;
|
||
createdAt: string;
|
||
thumbnailUrl: string | null;
|
||
};
|
||
|
||
export type FileKind = "image" | "pdf" | "text" | "spreadsheet" | "presentation" | "document" | "archive" | "video" | "audio" | "other";
|
||
|
||
const EXTENSION_KINDS: Record<string, FileKind> = {
|
||
png: "image", jpg: "image", jpeg: "image", gif: "image", webp: "image", svg: "image",
|
||
pdf: "pdf",
|
||
txt: "text", md: "text", csv: "spreadsheet", json: "text",
|
||
xls: "spreadsheet", xlsx: "spreadsheet", ods: "spreadsheet",
|
||
ppt: "presentation", pptx: "presentation", odp: "presentation",
|
||
doc: "document", docx: "document", odt: "document", rtf: "document",
|
||
zip: "archive", rar: "archive", "7z": "archive", gz: "archive",
|
||
mp4: "video", mov: "video", webm: "video",
|
||
mp3: "audio", wav: "audio", m4a: "audio",
|
||
};
|
||
|
||
export function fileKind(name: string, mimeType: string | null): FileKind {
|
||
const extension = name.split(".").pop()?.toLowerCase() ?? "";
|
||
if (EXTENSION_KINDS[extension]) return EXTENSION_KINDS[extension];
|
||
if (mimeType?.startsWith("image/")) return "image";
|
||
if (mimeType?.startsWith("video/")) return "video";
|
||
if (mimeType?.startsWith("audio/")) return "audio";
|
||
if (mimeType === "application/pdf") return "pdf";
|
||
if (mimeType?.startsWith("text/")) return "text";
|
||
return "other";
|
||
}
|
||
|
||
export type PreviewKind = "image" | "pdf" | "text" | "video" | "audio";
|
||
|
||
// Vorschau richtet sich nach dem Content-Type, mit dem der Storage die Datei ausliefert – nicht nach der Endung.
|
||
// So landet z. B. ein als ".pdf" benanntes HTML nie im Iframe. SVG (kann Skripte enthalten) nur als Download.
|
||
const PREVIEW_TYPES: Record<string, PreviewKind> = {
|
||
"image/png": "image",
|
||
"image/jpeg": "image",
|
||
"image/gif": "image",
|
||
"image/webp": "image",
|
||
"application/pdf": "pdf",
|
||
"text/plain": "text",
|
||
"text/markdown": "text",
|
||
"text/csv": "text",
|
||
"application/json": "text",
|
||
"video/mp4": "video",
|
||
"video/webm": "video",
|
||
"video/quicktime": "video",
|
||
"audio/mpeg": "audio",
|
||
"audio/wav": "audio",
|
||
"audio/mp4": "audio",
|
||
"audio/x-m4a": "audio",
|
||
};
|
||
|
||
export function previewKind(mimeType: string | null): PreviewKind | null {
|
||
if (!mimeType) return null;
|
||
return PREVIEW_TYPES[mimeType.split(";")[0].trim().toLowerCase()] ?? null;
|
||
}
|
||
|
||
export function formatFileSize(bytes: number | null) {
|
||
if (bytes === null) return "";
|
||
if (bytes < 1024) return `${bytes} B`;
|
||
const units = ["KB", "MB", "GB"];
|
||
let value = bytes / 1024;
|
||
let unit = 0;
|
||
while (value >= 1024 && unit < units.length - 1) {
|
||
value /= 1024;
|
||
unit++;
|
||
}
|
||
return `${value.toLocaleString("de-DE", { maximumFractionDigits: value < 10 ? 1 : 0 })} ${units[unit]}`;
|
||
}
|
||
|
||
export const UploadFileSchema = z.object({
|
||
name: z.string().trim().min(1).max(255),
|
||
size: z.number().int().positive({ error: "Leere Dateien können nicht hochgeladen werden." }).max(MAX_FILE_BYTES, {
|
||
error: "Dateien dürfen maximal 50 MB groß sein.",
|
||
}),
|
||
type: z.string().max(255),
|
||
});
|
||
|
||
export const FolderNameSchema = z
|
||
.string()
|
||
.trim()
|
||
.min(1, { error: "Name ist erforderlich." })
|
||
.max(100, { error: "Name ist zu lang." })
|
||
.refine((value) => !/[\\/]/.test(value), { error: "Name darf keine Schrägstriche enthalten." });
|