mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-08-29 02:45:39 -04:00
- Add `openapi-fetch` dependency; generate `lib/tmdb/schema.d.ts` from TMDB's OpenAPI spec and delete `lib/tmdb/types.ts` - Rewrite `lib/tmdb/client.ts` to use the typed fetch client against the generated schema, exporting `TmdbMovieDetails`, `TmdbTvDetails`, `TmdbVideo`, and `TmdbGenre` directly - Fix null-safety across services (`credits`, `availability`, `metadata`, `person`, `webhooks`) and call sites (explore page, search route, explore actions) to handle optional fields produced by the stricter generated types - Update `metadata.ts` and `metadata.test.ts` to import shared types from `@/lib/tmdb/client` instead of the removed `types.ts` - Exclude `lib/tmdb/schema.d.ts` from Biome linting
71 lines
2.0 KiB
TypeScript
71 lines
2.0 KiB
TypeScript
import { and, eq } from "drizzle-orm";
|
|
import { db } from "@/lib/db/client";
|
|
import { availabilityOffers, titles } from "@/lib/db/schema";
|
|
import { createLogger } from "@/lib/logger";
|
|
import { getWatchProviders } from "@/lib/tmdb/client";
|
|
|
|
const log = createLogger("availability");
|
|
|
|
export async function refreshAvailability(titleId: string) {
|
|
const title = 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) {
|
|
log.debug(`No US providers for title ${titleId}`);
|
|
return;
|
|
}
|
|
|
|
const now = new Date();
|
|
const offerTypes = ["flatrate", "rent", "buy", "free", "ads"] as const;
|
|
|
|
// Collect all offer rows, then batch insert in a single transaction
|
|
const allOfferRows: (typeof availabilityOffers.$inferInsert)[] = [];
|
|
for (const offerType of offerTypes) {
|
|
const providers = us[offerType];
|
|
if (!providers) continue;
|
|
for (const p of providers) {
|
|
allOfferRows.push({
|
|
titleId,
|
|
region: "US",
|
|
providerId: p.provider_id,
|
|
providerName: p.provider_name ?? "",
|
|
logoPath: p.logo_path ?? "",
|
|
offerType,
|
|
link: us.link ?? null,
|
|
lastFetchedAt: now,
|
|
});
|
|
}
|
|
}
|
|
|
|
db.transaction((tx) => {
|
|
tx.delete(availabilityOffers)
|
|
.where(
|
|
and(
|
|
eq(availabilityOffers.titleId, titleId),
|
|
eq(availabilityOffers.region, "US"),
|
|
),
|
|
)
|
|
.run();
|
|
|
|
if (allOfferRows.length > 0) {
|
|
tx.insert(availabilityOffers)
|
|
.values(allOfferRows)
|
|
.onConflictDoNothing()
|
|
.run();
|
|
}
|
|
});
|
|
|
|
const total = offerTypes.reduce((n, t) => n + (us[t]?.length ?? 0), 0);
|
|
log.debug(`Refreshed availability for title ${titleId}: ${total} offers`);
|
|
}
|
|
|
|
export function getAvailability(titleId: string) {
|
|
return db
|
|
.select()
|
|
.from(availabilityOffers)
|
|
.where(eq(availabilityOffers.titleId, titleId))
|
|
.all();
|
|
}
|