mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-07-14 18:15:56 -04:00
Migrate from Node.js built-ins to Bun-native APIs (#1)
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
"use server";
|
||||
|
||||
import crypto from "node:crypto";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { headers } from "next/headers";
|
||||
import { auth } from "@/lib/auth/server";
|
||||
@@ -56,7 +55,9 @@ export async function saveWebhookConnection(
|
||||
};
|
||||
}
|
||||
|
||||
const token = crypto.randomBytes(32).toString("hex");
|
||||
const token = Buffer.from(
|
||||
crypto.getRandomValues(new Uint8Array(32)),
|
||||
).toString("hex");
|
||||
const now = new Date();
|
||||
|
||||
const connection = db
|
||||
@@ -103,7 +104,9 @@ export async function regenerateWebhookToken(provider: "plex" | "jellyfin") {
|
||||
throw new Error("Invalid provider");
|
||||
}
|
||||
|
||||
const newToken = crypto.randomBytes(32).toString("hex");
|
||||
const newToken = Buffer.from(
|
||||
crypto.getRandomValues(new Uint8Array(32)),
|
||||
).toString("hex");
|
||||
|
||||
const connection = db
|
||||
.update(webhookConnections)
|
||||
|
||||
@@ -19,17 +19,17 @@ async function getAdminSession() {
|
||||
|
||||
export async function createBackupAction(): Promise<BackupInfo> {
|
||||
await getAdminSession();
|
||||
return createBackup();
|
||||
return await createBackup();
|
||||
}
|
||||
|
||||
export async function listBackupsAction(): Promise<BackupInfo[]> {
|
||||
await getAdminSession();
|
||||
return listBackups();
|
||||
return await listBackups();
|
||||
}
|
||||
|
||||
export async function deleteBackupAction(filename: string): Promise<void> {
|
||||
await getAdminSession();
|
||||
deleteBackup(filename);
|
||||
await deleteBackup(filename);
|
||||
}
|
||||
|
||||
export async function setScheduledBackupAction(
|
||||
|
||||
@@ -57,7 +57,7 @@ export default async function SettingsPage() {
|
||||
? getSetting("registrationOpen") === "true"
|
||||
: false;
|
||||
|
||||
const backups = isAdmin ? listBackups() : [];
|
||||
const backups = isAdmin ? await listBackups() : [];
|
||||
const scheduledBackupsEnabled = isAdmin
|
||||
? getSetting("scheduledBackups") === "true"
|
||||
: false;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { statSync } from "node:fs";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { headers } from "next/headers";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
@@ -26,20 +24,20 @@ export async function GET(
|
||||
return NextResponse.json({ error: "Invalid filename" }, { status: 400 });
|
||||
}
|
||||
|
||||
const backupPath = getBackupPath(safe);
|
||||
const backupPath = await getBackupPath(safe);
|
||||
if (!backupPath) {
|
||||
return NextResponse.json({ error: "Not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
const buffer = await readFile(backupPath);
|
||||
const stat = statSync(backupPath);
|
||||
const file = Bun.file(backupPath);
|
||||
const buffer = await file.arrayBuffer();
|
||||
|
||||
return new NextResponse(new Uint8Array(buffer), {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "application/x-sqlite3",
|
||||
"Content-Disposition": `attachment; filename="${safe}"`,
|
||||
"Content-Length": String(stat.size),
|
||||
"Content-Length": String(file.size),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ export async function POST(req: Request) {
|
||||
const buffer = Buffer.from(await file.arrayBuffer());
|
||||
|
||||
try {
|
||||
restoreFromBackup(buffer);
|
||||
await restoreFromBackup(buffer);
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : "Restore failed";
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
"sonner": "2.0.7",
|
||||
"tailwind-merge": "3.5.0",
|
||||
"tw-animate-css": "1.4.0",
|
||||
"uuid": "13.0.0",
|
||||
"vaul": "1.1.2",
|
||||
"zod": "4.3.6",
|
||||
},
|
||||
@@ -1475,7 +1474,7 @@
|
||||
|
||||
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
||||
|
||||
"uuid": ["uuid@13.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w=="],
|
||||
"uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
|
||||
|
||||
"valibot": ["valibot@1.2.0", "", { "peerDependencies": { "typescript": ">=5" }, "optionalPeers": ["typescript"] }, "sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg=="],
|
||||
|
||||
@@ -1525,8 +1524,6 @@
|
||||
|
||||
"@azure/identity/open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="],
|
||||
|
||||
"@azure/msal-node/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
|
||||
|
||||
"@dotenvx/dotenvx/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="],
|
||||
|
||||
"@dotenvx/dotenvx/execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="],
|
||||
|
||||
+2
-2
@@ -12,12 +12,12 @@ export async function register() {
|
||||
"@/lib/services/image-cache"
|
||||
);
|
||||
if (imageCacheEnabled()) {
|
||||
ensureImageDirs();
|
||||
await ensureImageDirs();
|
||||
}
|
||||
|
||||
// Ensure backup directory exists
|
||||
const { ensureBackupDir } = await import("@/lib/services/backup");
|
||||
ensureBackupDir();
|
||||
await ensureBackupDir();
|
||||
|
||||
// Run database migrations on startup
|
||||
const { runMigrations } = await import("@/lib/db/migrate");
|
||||
|
||||
+1
-2
@@ -2,7 +2,6 @@ import { betterAuth } from "better-auth";
|
||||
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
||||
import { APIError, createAuthMiddleware } from "better-auth/api";
|
||||
import { admin, genericOAuth } from "better-auth/plugins";
|
||||
import { v4 as uuid } from "uuid";
|
||||
import {
|
||||
isOidcAutoRegisterEnabled,
|
||||
isOidcConfigured,
|
||||
@@ -64,7 +63,7 @@ export const auth = betterAuth({
|
||||
plugins: [admin(), ...oidcPlugin],
|
||||
advanced: {
|
||||
database: {
|
||||
generateId: () => uuid(),
|
||||
generateId: () => Bun.randomUUIDv7(),
|
||||
},
|
||||
},
|
||||
hooks: {
|
||||
|
||||
+3
-3
@@ -220,12 +220,12 @@ async function scheduledBackupJob() {
|
||||
return;
|
||||
}
|
||||
|
||||
ensureBackupDir();
|
||||
createBackup();
|
||||
await ensureBackupDir();
|
||||
await createBackup();
|
||||
|
||||
const maxStr = getSetting("maxBackupRetention");
|
||||
const max = maxStr ? Number.parseInt(maxStr, 10) : 7;
|
||||
pruneBackups(max);
|
||||
await pruneBackups(max);
|
||||
}
|
||||
|
||||
export function startJobs() {
|
||||
|
||||
+1
-2
@@ -6,13 +6,12 @@ import {
|
||||
text,
|
||||
uniqueIndex,
|
||||
} from "drizzle-orm/sqlite-core";
|
||||
import { v4 as uuid } from "uuid";
|
||||
|
||||
// Helper for UUID primary keys
|
||||
const uuidPk = () =>
|
||||
text("id")
|
||||
.primaryKey()
|
||||
.$defaultFn(() => uuid());
|
||||
.$defaultFn(() => Bun.randomUUIDv7());
|
||||
|
||||
// ─── Better Auth tables ──────────────────────────────────────────────
|
||||
|
||||
|
||||
+50
-56
@@ -1,14 +1,5 @@
|
||||
import { Database } from "bun:sqlite";
|
||||
import {
|
||||
copyFileSync,
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
readdirSync,
|
||||
statSync,
|
||||
unlinkSync,
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { mkdir, readdir } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { format } from "date-fns";
|
||||
import { sql } from "drizzle-orm";
|
||||
@@ -31,10 +22,8 @@ export interface BackupInfo {
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export function ensureBackupDir() {
|
||||
if (!existsSync(BACKUP_DIR)) {
|
||||
mkdirSync(BACKUP_DIR, { recursive: true });
|
||||
}
|
||||
export async function ensureBackupDir() {
|
||||
await mkdir(BACKUP_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
function isValidBackupFilename(filename: string): boolean {
|
||||
@@ -46,8 +35,10 @@ function isValidBackupFilename(filename: string): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
export function createBackup(prefix = "sofa-backup"): BackupInfo {
|
||||
ensureBackupDir();
|
||||
export async function createBackup(
|
||||
prefix = "sofa-backup",
|
||||
): Promise<BackupInfo> {
|
||||
await ensureBackupDir();
|
||||
|
||||
const timestamp = format(new Date(), "yyyy-MM-dd-HHmmss");
|
||||
const filename = `${prefix}-${timestamp}.db`;
|
||||
@@ -56,74 +47,74 @@ export function createBackup(prefix = "sofa-backup"): BackupInfo {
|
||||
// VACUUM INTO atomically creates a clean, self-contained copy (safe for WAL mode)
|
||||
db.run(sql.raw(`VACUUM INTO '${dest.replace(/'/g, "''")}'`));
|
||||
|
||||
const stat = statSync(dest);
|
||||
log.info(`Created backup: ${filename} (${stat.size} bytes)`);
|
||||
const s = await Bun.file(dest).stat();
|
||||
log.info(`Created backup: ${filename} (${s.size} bytes)`);
|
||||
|
||||
return {
|
||||
filename,
|
||||
sizeBytes: stat.size,
|
||||
createdAt: stat.mtime.toISOString(),
|
||||
sizeBytes: s.size,
|
||||
createdAt: s.mtime.toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
export function listBackups(): BackupInfo[] {
|
||||
ensureBackupDir();
|
||||
export async function listBackups(): Promise<BackupInfo[]> {
|
||||
await ensureBackupDir();
|
||||
|
||||
const files = readdirSync(BACKUP_DIR).filter(
|
||||
const files = (await readdir(BACKUP_DIR)).filter(
|
||||
(f) => BACKUP_PATTERN.test(f) || PRE_RESTORE_PATTERN.test(f),
|
||||
);
|
||||
|
||||
return files
|
||||
.map((filename) => {
|
||||
const stat = statSync(path.join(BACKUP_DIR, filename));
|
||||
return {
|
||||
filename,
|
||||
sizeBytes: stat.size,
|
||||
createdAt: stat.mtime.toISOString(),
|
||||
};
|
||||
})
|
||||
.sort(
|
||||
(a, b) =>
|
||||
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(),
|
||||
);
|
||||
const results: BackupInfo[] = [];
|
||||
for (const filename of files) {
|
||||
const s = await Bun.file(path.join(BACKUP_DIR, filename)).stat();
|
||||
results.push({
|
||||
filename,
|
||||
sizeBytes: s.size,
|
||||
createdAt: s.mtime.toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
return results.sort(
|
||||
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(),
|
||||
);
|
||||
}
|
||||
|
||||
export function deleteBackup(filename: string): void {
|
||||
export async function deleteBackup(filename: string): Promise<void> {
|
||||
if (!isValidBackupFilename(filename)) {
|
||||
throw new Error("Invalid backup filename");
|
||||
}
|
||||
|
||||
const filePath = path.join(BACKUP_DIR, filename);
|
||||
if (!existsSync(filePath)) {
|
||||
if (!(await Bun.file(filePath).exists())) {
|
||||
throw new Error("Backup not found");
|
||||
}
|
||||
|
||||
unlinkSync(filePath);
|
||||
await Bun.file(filePath).delete();
|
||||
log.info(`Deleted backup: ${filename}`);
|
||||
}
|
||||
|
||||
export function getBackupPath(filename: string): string | null {
|
||||
export async function getBackupPath(filename: string): Promise<string | null> {
|
||||
if (!isValidBackupFilename(filename)) return null;
|
||||
|
||||
const filePath = path.join(BACKUP_DIR, filename);
|
||||
return existsSync(filePath) ? filePath : null;
|
||||
return (await Bun.file(filePath).exists()) ? filePath : null;
|
||||
}
|
||||
|
||||
export async function readBackupFile(filename: string): Promise<Buffer | null> {
|
||||
const filePath = getBackupPath(filename);
|
||||
const filePath = await getBackupPath(filename);
|
||||
if (!filePath) return null;
|
||||
return readFile(filePath);
|
||||
return Buffer.from(await Bun.file(filePath).arrayBuffer());
|
||||
}
|
||||
|
||||
export function restoreFromBackup(buffer: Buffer): void {
|
||||
ensureBackupDir();
|
||||
export async function restoreFromBackup(buffer: Buffer): Promise<void> {
|
||||
await ensureBackupDir();
|
||||
|
||||
const timestamp = format(new Date(), "yyyy-MM-dd-HHmmss");
|
||||
const tempPath = path.join(BACKUP_DIR, `_restore-temp-${timestamp}.db`);
|
||||
|
||||
try {
|
||||
// Write uploaded file to temp location
|
||||
writeFileSync(tempPath, buffer);
|
||||
await Bun.write(tempPath, buffer);
|
||||
|
||||
// Validate it's a real SQLite database
|
||||
const testDb = new Database(tempPath, { readonly: true });
|
||||
@@ -150,34 +141,37 @@ export function restoreFromBackup(buffer: Buffer): void {
|
||||
|
||||
// Create safety backup before replacing
|
||||
log.info("Creating pre-restore safety backup...");
|
||||
createBackup("pre-restore");
|
||||
await createBackup("pre-restore");
|
||||
|
||||
// Replace the database
|
||||
log.info("Replacing database...");
|
||||
closeDatabase();
|
||||
copyFileSync(tempPath, DATABASE_URL);
|
||||
await Bun.write(DATABASE_URL, Bun.file(tempPath));
|
||||
|
||||
// Clean up WAL/SHM files from the old database
|
||||
const walPath = `${DATABASE_URL}-wal`;
|
||||
const shmPath = `${DATABASE_URL}-shm`;
|
||||
if (existsSync(walPath)) unlinkSync(walPath);
|
||||
if (existsSync(shmPath)) unlinkSync(shmPath);
|
||||
const walFile = Bun.file(`${DATABASE_URL}-wal`);
|
||||
const shmFile = Bun.file(`${DATABASE_URL}-shm`);
|
||||
if (await walFile.exists()) await walFile.delete();
|
||||
if (await shmFile.exists()) await shmFile.delete();
|
||||
|
||||
log.info("Database restored successfully");
|
||||
} finally {
|
||||
// Clean up temp file
|
||||
if (existsSync(tempPath)) unlinkSync(tempPath);
|
||||
const tempFile = Bun.file(tempPath);
|
||||
if (await tempFile.exists()) await tempFile.delete();
|
||||
}
|
||||
}
|
||||
|
||||
export function pruneBackups(maxKeep: number): void {
|
||||
const backups = listBackups().filter((b) => BACKUP_PATTERN.test(b.filename));
|
||||
export async function pruneBackups(maxKeep: number): Promise<void> {
|
||||
const backups = (await listBackups()).filter((b) =>
|
||||
BACKUP_PATTERN.test(b.filename),
|
||||
);
|
||||
|
||||
if (backups.length <= maxKeep) return;
|
||||
|
||||
const toDelete = backups.slice(maxKeep);
|
||||
for (const backup of toDelete) {
|
||||
deleteBackup(backup.filename);
|
||||
await deleteBackup(backup.filename);
|
||||
}
|
||||
|
||||
log.info(`Pruned ${toDelete.length} old backup(s), kept ${maxKeep}`);
|
||||
|
||||
@@ -32,7 +32,7 @@ export async function extractAndStoreColors(
|
||||
let source: string;
|
||||
const filename = path.basename(posterPath);
|
||||
if (imageCacheEnabled()) {
|
||||
if (!isImageCached("posters", filename)) {
|
||||
if (!(await isImageCached("posters", filename))) {
|
||||
await downloadAndCacheImage(posterPath, "posters");
|
||||
}
|
||||
source = getLocalImagePath("posters", filename);
|
||||
|
||||
@@ -63,6 +63,13 @@ export interface HistoryBucket {
|
||||
count: number;
|
||||
}
|
||||
|
||||
function toLocalDateBucket(date: Date): string {
|
||||
const year = date.getFullYear();
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
||||
const day = date.getDate().toString().padStart(2, "0");
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
|
||||
export function getWatchHistory(
|
||||
userId: string,
|
||||
table: "movies" | "episodes",
|
||||
@@ -89,7 +96,7 @@ export function getWatchHistory(
|
||||
buckets = Array.from({ length: 7 }, (_, i) => {
|
||||
const d = new Date(now);
|
||||
d.setDate(now.getDate() - 6 + i);
|
||||
return d.toISOString().slice(0, 10);
|
||||
return toLocalDateBucket(d);
|
||||
});
|
||||
break;
|
||||
}
|
||||
@@ -98,7 +105,7 @@ export function getWatchHistory(
|
||||
buckets = Array.from({ length: 30 }, (_, i) => {
|
||||
const d = new Date(now);
|
||||
d.setDate(now.getDate() - 29 + i);
|
||||
return d.toISOString().slice(0, 10);
|
||||
return toLocalDateBucket(d);
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
+16
-19
@@ -1,5 +1,4 @@
|
||||
import { existsSync, mkdirSync } from "node:fs";
|
||||
import { readFile, rename, writeFile } from "node:fs/promises";
|
||||
import { mkdir, rename } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { db } from "@/lib/db/client";
|
||||
@@ -26,20 +25,17 @@ export function imageCacheEnabled(): boolean {
|
||||
return process.env.IMAGE_CACHE_ENABLED !== "false";
|
||||
}
|
||||
|
||||
export function ensureImageDirs() {
|
||||
export async function ensureImageDirs() {
|
||||
for (const category of Object.keys(CATEGORY_SIZES)) {
|
||||
const dir = path.join(CACHE_DIR, category);
|
||||
if (!existsSync(dir)) {
|
||||
mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
await mkdir(path.join(CACHE_DIR, category), { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
export function isImageCached(
|
||||
export async function isImageCached(
|
||||
category: ImageCategory,
|
||||
filename: string,
|
||||
): boolean {
|
||||
return existsSync(getLocalImagePath(category, filename));
|
||||
): Promise<boolean> {
|
||||
return Bun.file(getLocalImagePath(category, filename)).exists();
|
||||
}
|
||||
|
||||
export function getLocalImagePath(
|
||||
@@ -54,8 +50,9 @@ export async function readCachedImage(
|
||||
filename: string,
|
||||
): Promise<Buffer | null> {
|
||||
const filePath = getLocalImagePath(category, filename);
|
||||
if (!existsSync(filePath)) return null;
|
||||
return readFile(filePath);
|
||||
const file = Bun.file(filePath);
|
||||
if (!(await file.exists())) return null;
|
||||
return Buffer.from(await file.arrayBuffer());
|
||||
}
|
||||
|
||||
export async function downloadAndCacheImage(
|
||||
@@ -74,7 +71,7 @@ export async function downloadAndCacheImage(
|
||||
const tmpPath = `${finalPath}.tmp.${Date.now()}`;
|
||||
|
||||
try {
|
||||
await writeFile(tmpPath, buffer);
|
||||
await Bun.write(tmpPath, buffer);
|
||||
await rename(tmpPath, finalPath);
|
||||
} catch {
|
||||
// Best-effort cleanup
|
||||
@@ -113,7 +110,7 @@ export async function fetchAndMaybeCache(
|
||||
// Fire-and-forget save to disk
|
||||
const finalPath = getLocalImagePath(category, filename);
|
||||
const tmpPath = `${finalPath}.tmp.${Date.now()}`;
|
||||
writeFile(tmpPath, buffer)
|
||||
Bun.write(tmpPath, buffer)
|
||||
.then(() => rename(tmpPath, finalPath))
|
||||
.catch(() => {});
|
||||
|
||||
@@ -128,13 +125,13 @@ export async function cacheImagesForTitle(titleId: string) {
|
||||
|
||||
if (
|
||||
title.posterPath &&
|
||||
!isImageCached("posters", path.basename(title.posterPath))
|
||||
!(await isImageCached("posters", path.basename(title.posterPath)))
|
||||
) {
|
||||
tasks.push(downloadAndCacheImage(title.posterPath, "posters"));
|
||||
}
|
||||
if (
|
||||
title.backdropPath &&
|
||||
!isImageCached("backdrops", path.basename(title.backdropPath))
|
||||
!(await isImageCached("backdrops", path.basename(title.backdropPath)))
|
||||
) {
|
||||
tasks.push(downloadAndCacheImage(title.backdropPath, "backdrops"));
|
||||
}
|
||||
@@ -149,7 +146,7 @@ export async function cacheImagesForTitle(titleId: string) {
|
||||
for (const s of allSeasons) {
|
||||
if (
|
||||
s.posterPath &&
|
||||
!isImageCached("posters", path.basename(s.posterPath))
|
||||
!(await isImageCached("posters", path.basename(s.posterPath)))
|
||||
) {
|
||||
tasks.push(downloadAndCacheImage(s.posterPath, "posters"));
|
||||
}
|
||||
@@ -177,7 +174,7 @@ export async function cacheEpisodeStills(titleId: string) {
|
||||
for (const ep of eps) {
|
||||
if (
|
||||
ep.stillPath &&
|
||||
!isImageCached("stills", path.basename(ep.stillPath))
|
||||
!(await isImageCached("stills", path.basename(ep.stillPath)))
|
||||
) {
|
||||
tasks.push(downloadAndCacheImage(ep.stillPath, "stills"));
|
||||
}
|
||||
@@ -198,7 +195,7 @@ export async function cacheProviderLogos(titleId: string) {
|
||||
for (const offer of offers) {
|
||||
if (offer.logoPath) {
|
||||
const basename = path.basename(offer.logoPath);
|
||||
if (!seen.has(basename) && !isImageCached("logos", basename)) {
|
||||
if (!seen.has(basename) && !(await isImageCached("logos", basename))) {
|
||||
seen.add(basename);
|
||||
tasks.push(downloadAndCacheImage(offer.logoPath, "logos"));
|
||||
}
|
||||
|
||||
@@ -40,7 +40,6 @@
|
||||
"sonner": "2.0.7",
|
||||
"tailwind-merge": "3.5.0",
|
||||
"tw-animate-css": "1.4.0",
|
||||
"uuid": "13.0.0",
|
||||
"vaul": "1.1.2",
|
||||
"zod": "4.3.6"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user