Fix color extraction and image cache setup in development

Move `ensureImageDirs` outside the production-only block so the local
image cache directories are created in dev mode too. Update
`extractAndStoreColors` to download the poster if it isn't cached yet
instead of falling back to a direct TMDB CDN URL, keeping color
extraction consistent with the server-side image routing approach. Add
`/data` to `.gitignore` to avoid committing the local cache directory.
This commit is contained in:
2026-03-02 13:49:57 -05:00
parent 8eb1007612
commit 2f0fe02d26
6 changed files with 22 additions and 18 deletions
+2 -2
View File
@@ -1,5 +1,5 @@
# SQLite database URL (Docker: file:/data/sqlite.db, local dev: file:sqlite.db) # SQLite database URL (Docker: file:/data/sqlite.db, local dev: file:sqlite.db)
DATABASE_URL=file:sqlite.db DATABASE_URL=file:./data/sqlite.db
# TMDB API Read Access Token — get one at https://www.themoviedb.org/settings/api # TMDB API Read Access Token — get one at https://www.themoviedb.org/settings/api
TMDB_API_READ_ACCESS_TOKEN=your_tmdb_api_read_access_token_here TMDB_API_READ_ACCESS_TOKEN=your_tmdb_api_read_access_token_here
@@ -16,5 +16,5 @@ BETTER_AUTH_URL=http://localhost:3000
# Image caching — downloads TMDB images to local disk for faster serving # Image caching — downloads TMDB images to local disk for faster serving
# Set to "false" to disable and use TMDB CDN directly (default: enabled) # Set to "false" to disable and use TMDB CDN directly (default: enabled)
IMAGE_CACHE_DIR=./data/images
# IMAGE_CACHE_ENABLED=false # IMAGE_CACHE_ENABLED=false
# IMAGE_CACHE_DIR=/data/images
+1
View File
@@ -19,6 +19,7 @@
# production # production
/build /build
/data
# misc # misc
.DS_Store .DS_Store
+1 -1
View File
@@ -5,6 +5,6 @@ export default defineConfig({
schema: "./lib/db/schema.ts", schema: "./lib/db/schema.ts",
out: "./drizzle", out: "./drizzle",
dbCredentials: { dbCredentials: {
url: process.env.DATABASE_URL ?? "file:sqlite.db", url: process.env.DATABASE_URL ?? "file:./data/sqlite.db",
}, },
}); });
+6 -6
View File
@@ -4,12 +4,7 @@ export async function onRequestError() {
export async function register() { export async function register() {
if (process.env.NEXT_RUNTIME === "nodejs") { if (process.env.NEXT_RUNTIME === "nodejs") {
// Run database migrations on startup // Ensure image cache directories exist (all environments)
if (process.env.NODE_ENV === "production") {
const { runMigrations } = await import("@/lib/db/migrate");
await runMigrations();
// Ensure image cache directories exist
const { ensureImageDirs, imageCacheEnabled } = await import( const { ensureImageDirs, imageCacheEnabled } = await import(
"@/lib/services/image-cache" "@/lib/services/image-cache"
); );
@@ -17,6 +12,11 @@ export async function register() {
ensureImageDirs(); ensureImageDirs();
} }
// Run database migrations on startup
if (process.env.NODE_ENV === "production") {
const { runMigrations } = await import("@/lib/db/migrate");
await runMigrations();
const { initJobs } = await import("@/lib/jobs/init"); const { initJobs } = await import("@/lib/jobs/init");
initJobs(); initJobs();
+1 -1
View File
@@ -9,7 +9,7 @@ const globalForDb = globalThis as unknown as {
if (!globalForDb._client) { if (!globalForDb._client) {
globalForDb._client = createClient({ globalForDb._client = createClient({
url: process.env.DATABASE_URL ?? "file:sqlite.db", url: process.env.DATABASE_URL ?? "file:./data/sqlite.db",
}); });
globalForDb._client.execute("PRAGMA journal_mode = WAL"); globalForDb._client.execute("PRAGMA journal_mode = WAL");
globalForDb._client.execute("PRAGMA foreign_keys = ON"); globalForDb._client.execute("PRAGMA foreign_keys = ON");
+9 -6
View File
@@ -4,11 +4,11 @@ import { Vibrant } from "node-vibrant/node";
import { db } from "@/lib/db/client"; import { db } from "@/lib/db/client";
import { titles } from "@/lib/db/schema"; import { titles } from "@/lib/db/schema";
import { import {
downloadAndCacheImage,
getLocalImagePath, getLocalImagePath,
imageCacheEnabled, imageCacheEnabled,
isImageCached, isImageCached,
} from "@/lib/services/image-cache"; } from "@/lib/services/image-cache";
import { tmdbImageUrl } from "@/lib/tmdb/image";
export interface ColorPalette { export interface ColorPalette {
vibrant: string | null; vibrant: string | null;
@@ -25,15 +25,18 @@ export async function extractAndStoreColors(
): Promise<ColorPalette | null> { ): Promise<ColorPalette | null> {
if (!posterPath) return null; if (!posterPath) return null;
// Prefer local file when image cache is active // Use local cached image, downloading first if needed
let source: string; let source: string;
const filename = path.basename(posterPath); const filename = path.basename(posterPath);
if (imageCacheEnabled() && isImageCached("posters", filename)) { if (imageCacheEnabled()) {
if (!isImageCached("posters", filename)) {
await downloadAndCacheImage(posterPath, "posters");
}
source = getLocalImagePath("posters", filename); source = getLocalImagePath("posters", filename);
} else { } else {
const url = tmdbImageUrl(posterPath, "w300"); const baseUrl =
if (!url) return null; process.env.TMDB_IMAGE_BASE_URL || "https://image.tmdb.org/t/p";
source = url; source = `${baseUrl}/w300${posterPath}`;
} }
try { try {