Files
gerbeur/api/config.ts
khannurien 42e0356d21
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 50s
Build and Deploy Static Documentation Site / build-and-push (push) Successful in 21s
v3: the docs follow deno doc's grain — the index page was a hand-kept list of some 250 links into deno doc's output layout (./api/main.ts/index.html), policed by mod.test.ts; deno doc has no module index of its own beyond all_symbols.html, which lists every module by path with its summary. mod.ts is now a prose overview of the three parts and their layers, linking that list once, and mod.test.ts is gone. Re-exporting everything from mod.ts, as deno doc's tutorial does for a library, was tried and doesn't fit: export * drops the 18 routers (default exports) and makes 17 names ambiguous, and deno 2.9 renders export * as namespaces with the root's module doc and nested ones empty, losing every module doc. A router's page showed one line: its endpoint list sat on the default symbol, whose summary was cut at the colon introducing it; the list is now the module doc of all 18 routers, and the router's JSDoc says so. 22 summaries ended in a colon (the routers, BandcampPlayer, migration 0016, useDumpListSync, usePlaylistListSync) and read cut short wherever deno doc shows a first paragraph alone; they end in a full stop, doc-lint.ts now fails on a colon summary, and the README states the rule. No behaviour changes; 204 backend tests pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 19:28:24 +00:00

178 lines
6.4 KiB
TypeScript

/**
* The server's settings: those read from `GERBEUR_*` environment variables,
* with their defaults, and the fixed limits the backend applies.
*
* @module
*/
/** The port the server listens on (`GERBEUR_PORT`, default 8000). */
export const PORT: number = Number(Deno.env.get("GERBEUR_PORT")) || 8000;
/**
* The SMTP server mail is sent through, as an `smtps://` URL
* (`GERBEUR_SMTPS_URL`). Empty disables email.
*/
export const SMTPS_URL: string = Deno.env.get("GERBEUR_SMTPS_URL")?.trim() ||
"";
/** The sender address of outgoing mail (`GERBEUR_FROM_EMAIL`). */
export const FROM_EMAIL: string = 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!`;
/**
* The Markdown body of the welcome email (`GERBEUR_WELCOME_EMAIL_BODY`), with
* `{{site_name}}` and `{{username}}` placeholders.
*/
export const WELCOME_EMAIL_BODY: string =
Deno.env.get("GERBEUR_WELCOME_EMAIL_BODY")?.trim() ||
DEFAULT_WELCOME_EMAIL_BODY;
/** The key tokens are signed with (`GERBEUR_JWT_SECRET`). Required. */
export const JWT_SECRET: string = Deno.env.get("GERBEUR_JWT_SECRET")?.trim() ||
"";
/**
* The network interface the server binds to (`GERBEUR_LISTEN_HOST`). Defaults
* to 0.0.0.0 so Docker port-forwarding works out of the box; 127.0.0.1
* restricts it to loopback.
*/
export const LISTEN_HOST: string = Deno.env.get("GERBEUR_LISTEN_HOST") ||
"0.0.0.0";
/**
* The server's public-facing URL (`GERBEUR_PUBLIC_URL`), without a trailing
* slash — used for CORS, WebSocket origin checks, email links, and OG image
* URLs. Behind a reverse proxy, set it to the externally-visible URL (e.g.
* https://example.com). Defaults to http://localhost:PORT for local
* development.
*/
export const PUBLIC_URL: string =
Deno.env.get("GERBEUR_PUBLIC_URL")?.trim().replace(/\/$/, "") ||
`http://localhost:${PORT}`;
/**
* The frontend's URL (`GERBEUR_FRONTEND_URL`). In single-container deployments
* the API serves the frontend, so it defaults to {@link PUBLIC_URL}; set it
* when the frontend runs on a separate host or port (e.g. Vite dev server or a
* CDN origin).
*/
export const FRONTEND_URL: string =
Deno.env.get("GERBEUR_FRONTEND_URL")?.trim() || PUBLIC_URL;
/**
* The SQLite database file (`GERBEUR_DB_PATH`). Overridable so the test suite
* can run against a throwaway database (see api/test/setup.ts).
*/
export const DB_PATH: string = Deno.env.get("GERBEUR_DB_PATH") ||
"api/sql/gerbeur.db";
// Upload/files
/**
* The directory uploads are stored under (`GERBEUR_UPLOADS_DIR`), one
* subdirectory per kind.
*/
export const UPLOADS_DIR: string = Deno.env.get("GERBEUR_UPLOADS_DIR") ||
"api/uploads";
/** Where file dumps are stored. */
export const DUMPS_DIR = `${UPLOADS_DIR}/dumps`;
/** Where avatars are stored. */
export const AVATARS_DIR = `${UPLOADS_DIR}/avatars`;
/** Where playlist cover images are stored. */
export const PLAYLIST_IMAGES_DIR = `${UPLOADS_DIR}/playlist-images`;
/** Where images pasted into chat, comments and descriptions are stored. */
export const ATTACHMENTS_DIR = `${UPLOADS_DIR}/attachments`;
/** Where custom dump thumbnails are stored. */
export const THUMBNAILS_DIR = `${UPLOADS_DIR}/thumbnails`;
/** The largest image upload accepted: 5 MB. */
export const MAX_IMAGE_SIZE_BYTES = 5 * 1024 * 1024;
/** The image types accepted for avatars, covers, thumbnails and attachments. */
export const ALLOWED_IMAGE_MIMES: Set<string> = new Set([
"image/jpeg",
"image/png",
"image/gif",
"image/webp",
]);
/** The MIME type prefixes a file dump may have. */
export const DUMP_ALLOWED_MIME_PREFIXES: string[] = [
"text/",
"image/",
"video/",
"audio/",
];
/** The exact MIME types a file dump may have, beyond the prefixes. */
export const DUMP_ALLOWED_MIME_TYPES: Set<string> = 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
/** The page size when a request names none. */
export const PAGINATION_DEFAULT_LIMIT = 20;
/** The largest page size a request may ask for. */
export const PAGINATION_MAX_LIMIT = 100;
// Startup housekeeping
/** How long an attachment nothing links to is kept before startup purges it. */
export const ORPHANED_ATTACHMENTS_RETENTION_HOURS = 1;
/** How long an unused invite is kept before startup purges it. */
export const UNUSED_INVITES_RETENTION_DAYS = 7;
// SEO/OG
/** The site's name (`GERBEUR_SITE_NAME`), in page titles, OG tags and mail. */
export const OG_SITE_NAME: string = Deno.env.get("GERBEUR_SITE_NAME") ||
"gerbeur";
/**
* The emoji the favicon set and app header logo are generated from
* (`GERBEUR_SITE_EMOJI`). Applied at runtime (server startup and index.html
* injection), so changing it only needs a restart — no rebuild.
*/
export const SITE_EMOJI: string = Deno.env.get("GERBEUR_SITE_EMOJI") || "🚚";
/**
* A Bandcamp playback path.
*
* - `"embed"` — the bandcamp.com iframe (cannot autoplay)
* - `"native"` — resolve the page's mp3 streams and play them in the app's own
* player, which autoplays and supports albums as a queue
*/
export type BandcampPlayer = "embed" | "native";
/**
* The Bandcamp playback path the frontend uses (`GERBEUR_BANDCAMP_PLAYER`).
* Anything unrecognised falls back to `"embed"`, so a typo degrades safely.
*/
export const BANDCAMP_PLAYER: BandcampPlayer =
Deno.env.get("GERBEUR_BANDCAMP_PLAYER")?.trim() === "native"
? "native"
: "embed";
/**
* The background color of generated icons and the manifest. Mirrors the
* hard-coded theme-color in index.html.
*/
export const THEME_COLOR = "#111827";
const rawOrigins = Deno.env.get("GERBEUR_ALLOWED_ORIGINS") ?? "";
/**
* The origins CORS and the WebSocket accept: {@link PUBLIC_URL},
* {@link FRONTEND_URL} and the comma-separated `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),
]),
);