Extract shared constants into lib/constants.ts

- Add `lib/constants.ts` exporting `DATA_DIR`, `DATABASE_URL`, `CACHE_DIR`,
  `BACKUP_DIR`, `AVATAR_DIR`, `TMDB_API_BASE_URL`, and `TMDB_IMAGE_BASE_URL`
- Replace inline `process.env` derivations in `db/client`, `backup`,
  `image-cache`, `system-health`, `tmdb/client`, `actions/settings`,
  and `api/avatars` with imports from the new module
This commit is contained in:
2026-03-08 11:06:29 -04:00
parent 84c4356a9f
commit 17fe19e85b
8 changed files with 35 additions and 38 deletions
+1 -3
View File
@@ -1,8 +1,6 @@
import path from "node:path"; import path from "node:path";
import { type NextRequest, NextResponse } from "next/server"; import { type NextRequest, NextResponse } from "next/server";
import { AVATAR_DIR } from "@/lib/constants";
const DATA_DIR = process.env.DATA_DIR || "./data";
const AVATAR_DIR = path.join(DATA_DIR, "avatars");
const IMMUTABLE_CACHE = "public, max-age=31536000, immutable"; const IMMUTABLE_CACHE = "public, max-age=31536000, immutable";
+1 -2
View File
@@ -7,6 +7,7 @@ import { headers } from "next/headers";
import { z } from "zod"; import { z } from "zod";
import { auth } from "@/lib/auth/server"; import { auth } from "@/lib/auth/server";
import { requireAdmin, requireSession } from "@/lib/auth/session"; import { requireAdmin, requireSession } from "@/lib/auth/session";
import { AVATAR_DIR } from "@/lib/constants";
import { type BackupFrequency, rescheduleBackup, triggerJob } from "@/lib/cron"; import { type BackupFrequency, rescheduleBackup, triggerJob } from "@/lib/cron";
import { db } from "@/lib/db/client"; import { db } from "@/lib/db/client";
import { integrations } from "@/lib/db/schema"; import { integrations } from "@/lib/db/schema";
@@ -268,8 +269,6 @@ export async function getUpdateCheckAction(): Promise<UpdateCheckResult | null>
// --- Avatar actions --- // --- Avatar actions ---
const DATA_DIR = process.env.DATA_DIR || "./data";
const AVATAR_DIR = path.join(DATA_DIR, "avatars");
const MAX_AVATAR_SIZE = 2 * 1024 * 1024; // 2MB const MAX_AVATAR_SIZE = 2 * 1024 * 1024; // 2MB
const ALLOWED_AVATAR_TYPES = new Set([ const ALLOWED_AVATAR_TYPES = new Set([
"image/jpeg", "image/jpeg",
+20
View File
@@ -0,0 +1,20 @@
import path from "node:path";
export const DATA_DIR = process.env.DATA_DIR || "./data";
export const DATABASE_URL =
process.env.DATABASE_URL || path.join(DATA_DIR, "sqlite.db");
export const CACHE_DIR = process.env.CACHE_DIR
? path.join(process.env.CACHE_DIR, "images")
: path.join(DATA_DIR, "images");
export const BACKUP_DIR = path.join(DATA_DIR, "backups");
export const AVATAR_DIR = path.join(DATA_DIR, "avatars");
export const TMDB_API_BASE_URL =
process.env.TMDB_API_BASE_URL || "https://api.themoviedb.org/3";
export const TMDB_IMAGE_BASE_URL =
process.env.TMDB_IMAGE_BASE_URL || "https://image.tmdb.org/t/p";
+1 -5
View File
@@ -1,7 +1,7 @@
import { Database } from "bun:sqlite"; import { Database } from "bun:sqlite";
import path from "node:path";
import type { Logger } from "drizzle-orm"; import type { Logger } from "drizzle-orm";
import { drizzle } from "drizzle-orm/bun-sqlite"; import { drizzle } from "drizzle-orm/bun-sqlite";
import { DATABASE_URL } from "@/lib/constants";
import { createLogger } from "@/lib/logger"; import { createLogger } from "@/lib/logger";
import * as schema from "./schema"; import * as schema from "./schema";
@@ -29,10 +29,6 @@ const globalForDb = globalThis as unknown as {
_client: Database | undefined; _client: Database | undefined;
}; };
const DATABASE_URL =
process.env.DATABASE_URL ||
path.join(process.env.DATA_DIR || "./data", "sqlite.db");
function getClient() { function getClient() {
if (!globalForDb._client) { if (!globalForDb._client) {
globalForDb._client = new Database(DATABASE_URL); globalForDb._client = new Database(DATABASE_URL);
+1 -5
View File
@@ -4,17 +4,13 @@ import { mkdir, readdir } from "node:fs/promises";
import path from "node:path"; import path from "node:path";
import { format } from "date-fns"; import { format } from "date-fns";
import { sql } from "drizzle-orm"; import { sql } from "drizzle-orm";
import { BACKUP_DIR, DATABASE_URL } from "@/lib/constants";
import { closeDatabase, db } from "@/lib/db/client"; import { closeDatabase, db } from "@/lib/db/client";
import { runMigrations } from "@/lib/db/migrate"; import { runMigrations } from "@/lib/db/migrate";
import { createLogger } from "@/lib/logger"; import { createLogger } from "@/lib/logger";
const log = createLogger("backup"); const log = createLogger("backup");
const DATA_DIR = process.env.DATA_DIR || "./data";
const DATABASE_URL =
process.env.DATABASE_URL || path.join(DATA_DIR, "sqlite.db");
const BACKUP_DIR = path.join(DATA_DIR, "backups");
const MANUAL_PATTERN = /^sofa-manual-\d{4}-\d{2}-\d{2}-\d{6}(?:\d{3})?\.db$/; const MANUAL_PATTERN = /^sofa-manual-\d{4}-\d{2}-\d{2}-\d{6}(?:\d{3})?\.db$/;
const SCHEDULED_PATTERN = const SCHEDULED_PATTERN =
/^sofa-scheduled-\d{4}-\d{2}-\d{2}-\d{6}(?:\d{3})?\.db$/; /^sofa-scheduled-\d{4}-\d{2}-\d{2}-\d{6}(?:\d{3})?\.db$/;
+3 -10
View File
@@ -1,6 +1,7 @@
import { mkdir, rename } from "node:fs/promises"; import { mkdir, rename } from "node:fs/promises";
import path from "node:path"; import path from "node:path";
import { eq, inArray } from "drizzle-orm"; import { eq, inArray } from "drizzle-orm";
import { CACHE_DIR, TMDB_IMAGE_BASE_URL } from "@/lib/constants";
import { db } from "@/lib/db/client"; import { db } from "@/lib/db/client";
import { import {
availabilityOffers, availabilityOffers,
@@ -29,14 +30,6 @@ const CATEGORY_SIZES: Record<ImageCategory, string> = {
profiles: "w185", profiles: "w185",
}; };
const IMAGE_BASE_URL =
process.env.TMDB_IMAGE_BASE_URL || "https://image.tmdb.org/t/p";
const DATA_DIR = process.env.DATA_DIR || "./data";
const CACHE_DIR = process.env.CACHE_DIR
? path.join(process.env.CACHE_DIR, "images")
: path.join(DATA_DIR, "images");
export function imageCacheEnabled(): boolean { export function imageCacheEnabled(): boolean {
return process.env.IMAGE_CACHE_ENABLED !== "false"; return process.env.IMAGE_CACHE_ENABLED !== "false";
} }
@@ -76,7 +69,7 @@ export async function downloadAndCacheImage(
category: ImageCategory, category: ImageCategory,
): Promise<Buffer | null> { ): Promise<Buffer | null> {
const size = CATEGORY_SIZES[category]; const size = CATEGORY_SIZES[category];
const url = `${IMAGE_BASE_URL}/${size}${tmdbPath}`; const url = `${TMDB_IMAGE_BASE_URL}/${size}${tmdbPath}`;
const res = await fetch(url); const res = await fetch(url);
if (!res.ok) { if (!res.ok) {
@@ -121,7 +114,7 @@ export async function fetchAndMaybeCache(
// Fetch from TMDB // Fetch from TMDB
const size = CATEGORY_SIZES[category]; const size = CATEGORY_SIZES[category];
const url = `${IMAGE_BASE_URL}/${size}${tmdbPath}`; const url = `${TMDB_IMAGE_BASE_URL}/${size}${tmdbPath}`;
const res = await fetch(url); const res = await fetch(url);
if (!res.ok) { if (!res.ok) {
log.warn(`Fetch failed: ${url} -> ${res.status}`); log.warn(`Fetch failed: ${url} -> ${res.status}`);
+6 -9
View File
@@ -1,6 +1,12 @@
import { access, constants, readdir } from "node:fs/promises"; import { access, constants, readdir } from "node:fs/promises";
import path from "node:path"; import path from "node:path";
import { count, desc, eq } from "drizzle-orm"; import { count, desc, eq } from "drizzle-orm";
import {
CACHE_DIR,
DATA_DIR,
DATABASE_URL,
TMDB_API_BASE_URL,
} from "@/lib/constants";
import { db } from "@/lib/db/client"; import { db } from "@/lib/db/client";
import { cronRuns, episodes, titles, user } from "@/lib/db/schema"; import { cronRuns, episodes, titles, user } from "@/lib/db/schema";
import { listBackups } from "@/lib/services/backup"; import { listBackups } from "@/lib/services/backup";
@@ -8,15 +14,6 @@ import { imageCacheEnabled } from "@/lib/services/image-cache";
import { getSetting } from "@/lib/services/settings"; import { getSetting } from "@/lib/services/settings";
import { isUpdateCheckEnabled } from "@/lib/services/update-check"; import { isUpdateCheckEnabled } from "@/lib/services/update-check";
const DATA_DIR = process.env.DATA_DIR || "./data";
const DATABASE_URL =
process.env.DATABASE_URL || path.join(DATA_DIR, "sqlite.db");
const CACHE_DIR = process.env.CACHE_DIR
? path.join(process.env.CACHE_DIR, "images")
: path.join(DATA_DIR, "images");
const TMDB_API_BASE_URL =
process.env.TMDB_API_BASE_URL || "https://api.themoviedb.org/3";
export interface SystemHealthData { export interface SystemHealthData {
database: { database: {
dbSizeBytes: number; dbSizeBytes: number;
+2 -4
View File
@@ -1,3 +1,4 @@
import { TMDB_API_BASE_URL } from "@/lib/constants";
import { createLogger } from "@/lib/logger"; import { createLogger } from "@/lib/logger";
import type { import type {
TmdbExternalIds, TmdbExternalIds,
@@ -18,9 +19,6 @@ import type {
} from "./types"; } from "./types";
const log = createLogger("tmdb"); const log = createLogger("tmdb");
const BASE_URL =
process.env.TMDB_API_BASE_URL || "https://api.themoviedb.org/3";
function getApiKey() { function getApiKey() {
const key = process.env.TMDB_API_READ_ACCESS_TOKEN; const key = process.env.TMDB_API_READ_ACCESS_TOKEN;
if (!key) throw new Error("TMDB_API_READ_ACCESS_TOKEN is not set"); if (!key) throw new Error("TMDB_API_READ_ACCESS_TOKEN is not set");
@@ -32,7 +30,7 @@ async function tmdbFetch<T>(
params?: Record<string, string>, params?: Record<string, string>,
fetchOptions?: RequestInit, fetchOptions?: RequestInit,
): Promise<T> { ): Promise<T> {
const url = new URL(`${BASE_URL}${path}`); const url = new URL(`${TMDB_API_BASE_URL}${path}`);
if (params) { if (params) {
for (const [k, v] of Object.entries(params)) { for (const [k, v] of Object.entries(params)) {
url.searchParams.set(k, v); url.searchParams.set(k, v);