Files
gerbeur/api/config.ts
khannurien d62e4a9883
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 47s
Build and deploy docs / build-and-push (push) Successful in 26s
v3: the backend is documented for deno doc, one way in every file, and the docs build and publish themselves — comments were in three shapes: file headers as // lines, as an untagged /** */ (which deno doc drops, only an @module block counts) or missing; 43 exports described by a // deno doc can't see, two JSDoc blocks sitting on the wrong declaration and one duplicated; endpoints commented in 5 of 18 route files, three ways, on router.get() calls no doc tool reads. The rule, applied to every non-test module of api/, migrations included: each opens with a /** … @module */ block; every export has JSDoc, and so does every field of an exported interface; a comment describing a declaration is /** */, // only notes code; functions open with a verb, everything else with a noun phrase; parameters are named in the prose, {@link} only for symbols in scope; whatever throws to its caller says so with @throws (for an APIException, the statuses and when); a router's JSDoc lists its endpoints, method and path, who may call it, what it does. deno doc --lint goes from 528 errors to none in api/ (shared/ keeps its 385; it is next). The lint also wanted explicit types on config.ts's settings and a few return types, JsonLdResult exported, and the routers typed Router. deno task doc builds the HTML reference into docs/ from every module of api/ and shared/ but the tests, deno task doc:lint lints it; the index page is the module doc of a root mod.ts (deno.json's exports), which introduces the backend, shared/ and the frontend to come and links every module, and mod.test.ts fails when one is missing. The docs workflow builds it on every push to main that touches them and publishes it to the gh-pages branch, like publish-assistant's. Two defects found along the way are fixed: a private playlist's cover was served to anyone who had its id or slug, and now needs its owner's media token, as private dump files do (playlistImageUrl adds it for private playlists only, so public cover URLs stay the same for everyone); og.ts and static.ts each filled index.html's placeholders, and now share lib/index-html.ts, so the OG middleware picks up a rebuilt index.html instead of keeping the first it read. The README states the documentation rule. 205 backend and 628 frontend tests pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 21:07:40 +00:00

177 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),
]),
);