export const PORT = Number(Deno.env.get("GERBEUR_PORT")) || 8000; export const SMTPS_URL = Deno.env.get("GERBEUR_SMTPS_URL")?.trim() || ""; export const FROM_EMAIL = Deno.env.get("GERBEUR_FROM_EMAIL")?.trim() || ""; const DEFAULT_WELCOME_EMAIL_BODY = `# Welcome to {{site_name}}! Hi **{{username}}**, Your account has been created successfully. Welcome aboard!`; export const WELCOME_EMAIL_BODY = Deno.env.get("GERBEUR_WELCOME_EMAIL_BODY")?.trim() || DEFAULT_WELCOME_EMAIL_BODY; export const JWT_SECRET = Deno.env.get("GERBEUR_JWT_SECRET")?.trim() || ""; // GERBEUR_LISTEN_HOST controls the network interface Oak binds to. // Defaults to 0.0.0.0 so Docker port-forwarding works out of the box. // Set to 127.0.0.1 to restrict to loopback only. export const LISTEN_HOST = Deno.env.get("GERBEUR_LISTEN_HOST") || "0.0.0.0"; // Public-facing URL of the server — used for CORS, WebSocket origin checks, // email links, and OG image URLs. Behind a reverse proxy, set this to the // externally-visible URL (e.g. https://example.com). No trailing slash. // Defaults to http://localhost:PORT for local development. export const PUBLIC_URL = Deno.env.get("GERBEUR_PUBLIC_URL")?.trim().replace(/\/$/, "") || `http://localhost:${PORT}`; // In single-container deployments the API serves the frontend, so FRONTEND_URL // equals PUBLIC_URL. Override with GERBEUR_FRONTEND_URL when running the // frontend on a separate host/port (e.g. Vite dev server or a CDN origin). export const FRONTEND_URL = Deno.env.get("GERBEUR_FRONTEND_URL")?.trim() || PUBLIC_URL; export const DB_PATH = "api/sql/gerbeur.db"; // Upload/files export const UPLOADS_DIR = "api/uploads"; export const DUMPS_DIR = `${UPLOADS_DIR}/dumps`; export const AVATARS_DIR = `${UPLOADS_DIR}/avatars`; export const PLAYLIST_IMAGES_DIR = `${UPLOADS_DIR}/playlist-images`; export const ATTACHMENTS_DIR = `${UPLOADS_DIR}/attachments`; export const THUMBNAILS_DIR = `${UPLOADS_DIR}/thumbnails`; export const MAX_IMAGE_SIZE_BYTES = 5 * 1024 * 1024; // 5 MB export const ALLOWED_IMAGE_MIMES = new Set([ "image/jpeg", "image/png", "image/gif", "image/webp", ]); export const DUMP_MAX_FILE_SIZE_BYTES = 50 * 1024 * 1024; // 50 MB export const DUMP_ALLOWED_MIME_PREFIXES = [ "text/", "image/", "video/", "audio/", ]; export const DUMP_ALLOWED_MIME_TYPES = new Set([ "application/pdf", "application/json", "application/zip", "application/x-zip-compressed", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/vnd.openxmlformats-officedocument.presentationml.presentation", "application/msword", "application/vnd.ms-excel", "application/vnd.ms-powerpoint", ]); // Pagination export const PAGINATION_DEFAULT_LIMIT = 20; export const PAGINATION_MAX_LIMIT = 100; // Startup housekeeping export const ORPHANED_ATTACHMENTS_RETENTION_HOURS = 1; export const UNUSED_INVITES_RETENTION_DAYS = 7; // Validation constraints export const VALIDATION = { USERNAME_MIN: 1, USERNAME_MAX: 32, PASSWORD_MIN: 8, PASSWORD_MAX: 128, DUMP_TITLE_MAX: 200, DUMP_COMMENT_MAX: 5000, PLAYLIST_TITLE_MAX: 100, PLAYLIST_DESCRIPTION_MAX: 2000, COMMENT_BODY_MAX: 5000, USER_DESCRIPTION_MAX: 2000, } as const; // SEO/OG export const OG_SITE_NAME = Deno.env.get("GERBEUR_SITE_NAME") || "gerbeur"; const rawOrigins = Deno.env.get("GERBEUR_ALLOWED_ORIGINS") ?? ""; export const ALLOWED_ORIGINS: string[] = Array.from( new Set([ PUBLIC_URL, // FRONTEND_URL is auto-included so the separate-frontend case only needs // GERBEUR_FRONTEND_URL — no need to repeat it in GERBEUR_ALLOWED_ORIGINS. FRONTEND_URL, ...rawOrigins.split(",").map((o) => o.trim()).filter(Boolean), ]), );