mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-08-29 03:55:38 -04:00
Docker self-hosting support: - Dockerfile (multi-stage Alpine build with tini init) - docker-compose.yml with named volume for SQLite persistence - /api/health endpoint for container health checks - Auto-migration on startup via drizzle-orm/libsql/migrator - Graceful shutdown (SIGTERM stops scheduler, closes DB) - Next.js standalone output mode for minimal image size Database driver migration (better-sqlite3 → @libsql/client): - Eliminates native C++ compilation, enabling Alpine Docker images - All DB queries converted from sync to async across services and routes - DATABASE_URL now uses libsql file: prefix format - drizzle.config.ts dialect changed to turso for libsql support - Initial migration files generated in drizzle/ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
62 lines
1.5 KiB
TypeScript
62 lines
1.5 KiB
TypeScript
import { and, eq } from "drizzle-orm";
|
|
import { db } from "@/lib/db/client";
|
|
import { availabilityOffers, titles } from "@/lib/db/schema";
|
|
import { getWatchProviders } from "@/lib/tmdb/client";
|
|
|
|
export async function refreshAvailability(titleId: string) {
|
|
const title = await db
|
|
.select()
|
|
.from(titles)
|
|
.where(eq(titles.id, titleId))
|
|
.get();
|
|
if (!title) return;
|
|
|
|
const data = await getWatchProviders(title.tmdbId, title.type);
|
|
const us = data.results?.US;
|
|
if (!us) return;
|
|
|
|
const now = new Date();
|
|
const offerTypes = ["flatrate", "rent", "buy", "free", "ads"] as const;
|
|
|
|
// Delete existing offers for this title+region
|
|
await db
|
|
.delete(availabilityOffers)
|
|
.where(
|
|
and(
|
|
eq(availabilityOffers.titleId, titleId),
|
|
eq(availabilityOffers.region, "US"),
|
|
),
|
|
)
|
|
.run();
|
|
|
|
for (const offerType of offerTypes) {
|
|
const providers = us[offerType];
|
|
if (!providers) continue;
|
|
|
|
for (const p of providers) {
|
|
await db
|
|
.insert(availabilityOffers)
|
|
.values({
|
|
titleId,
|
|
region: "US",
|
|
providerId: p.provider_id,
|
|
providerName: p.provider_name,
|
|
logoPath: p.logo_path,
|
|
offerType,
|
|
link: us.link ?? null,
|
|
lastFetchedAt: now,
|
|
})
|
|
.onConflictDoNothing()
|
|
.run();
|
|
}
|
|
}
|
|
}
|
|
|
|
export async function getAvailability(titleId: string) {
|
|
return db
|
|
.select()
|
|
.from(availabilityOffers)
|
|
.where(eq(availabilityOffers.titleId, titleId))
|
|
.all();
|
|
}
|