mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-08-29 01:35:39 -04:00
Remove unnecessary await/async from sync bun:sqlite db calls
drizzle-orm/bun-sqlite is fully synchronous — all queries return values directly, not promises. Remove await from all db calls, drop async from functions that no longer need it, simplify Promise.all patterns that wrapped sync operations, and fix setSetting() which was missing .run() (previously masked by await triggering execution via thenable). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+12
-12
@@ -58,8 +58,8 @@ function delay(ms: number) {
|
||||
return new Promise((r) => setTimeout(r, ms));
|
||||
}
|
||||
|
||||
async function getLibraryTitleIds(): Promise<string[]> {
|
||||
const rows = await db
|
||||
function getLibraryTitleIds(): string[] {
|
||||
const rows = db
|
||||
.select({ titleId: userTitleStatus.titleId })
|
||||
.from(userTitleStatus)
|
||||
.groupBy(userTitleStatus.titleId)
|
||||
@@ -69,13 +69,13 @@ async function getLibraryTitleIds(): Promise<string[]> {
|
||||
|
||||
// Refresh titles where lastFetchedAt is stale
|
||||
async function nightlyRefreshLibrary() {
|
||||
const libraryIds = await getLibraryTitleIds();
|
||||
const libraryIds = getLibraryTitleIds();
|
||||
const libraryStale = new Date(Date.now() - 7 * DAY);
|
||||
const nonLibraryStale = new Date(Date.now() - 30 * DAY);
|
||||
|
||||
// Library titles: 7 days
|
||||
for (const titleId of libraryIds) {
|
||||
const t = await db
|
||||
const t = db
|
||||
.select()
|
||||
.from(titles)
|
||||
.where(
|
||||
@@ -89,7 +89,7 @@ async function nightlyRefreshLibrary() {
|
||||
}
|
||||
|
||||
// Non-library titles: 30 days
|
||||
const nonLibrary = await db
|
||||
const nonLibrary = db
|
||||
.select()
|
||||
.from(titles)
|
||||
.where(
|
||||
@@ -111,12 +111,12 @@ async function nightlyRefreshLibrary() {
|
||||
|
||||
// Refresh availability for library titles where stale
|
||||
async function refreshAvailabilityJob() {
|
||||
const libraryIds = await getLibraryTitleIds();
|
||||
const libraryIds = getLibraryTitleIds();
|
||||
const stale = new Date(Date.now() - DAY);
|
||||
|
||||
for (const titleId of libraryIds) {
|
||||
// Check if any offer is stale
|
||||
const offer = await db
|
||||
const offer = db
|
||||
.select()
|
||||
.from(availabilityOffers)
|
||||
.where(
|
||||
@@ -128,7 +128,7 @@ async function refreshAvailabilityJob() {
|
||||
.get();
|
||||
|
||||
// Also handle titles with no offers yet
|
||||
const anyOffer = await db
|
||||
const anyOffer = db
|
||||
.select()
|
||||
.from(availabilityOffers)
|
||||
.where(eq(availabilityOffers.titleId, titleId))
|
||||
@@ -143,7 +143,7 @@ async function refreshAvailabilityJob() {
|
||||
|
||||
// Refresh recommendations for recently active titles
|
||||
async function refreshRecommendationsJob() {
|
||||
const libraryIds = await getLibraryTitleIds();
|
||||
const libraryIds = getLibraryTitleIds();
|
||||
|
||||
for (const titleId of libraryIds) {
|
||||
await refreshRecommendations(titleId);
|
||||
@@ -156,7 +156,7 @@ async function refreshTvChildrenJob() {
|
||||
const returningStatuses = ["Returning Series", "In Production"];
|
||||
const stale = new Date(Date.now() - 7 * DAY);
|
||||
|
||||
const tvShows = await db
|
||||
const tvShows = db
|
||||
.select()
|
||||
.from(titles)
|
||||
.where(
|
||||
@@ -170,7 +170,7 @@ async function refreshTvChildrenJob() {
|
||||
|
||||
for (const show of tvShows) {
|
||||
// Check if seasons are stale
|
||||
const staleSeason = await db
|
||||
const staleSeason = db
|
||||
.select()
|
||||
.from(seasons)
|
||||
.where(
|
||||
@@ -190,7 +190,7 @@ async function refreshTvChildrenJob() {
|
||||
async function cacheImagesJob() {
|
||||
if (!imageCacheEnabled()) return;
|
||||
|
||||
const libraryIds = await getLibraryTitleIds();
|
||||
const libraryIds = getLibraryTitleIds();
|
||||
|
||||
for (const titleId of libraryIds) {
|
||||
try {
|
||||
|
||||
@@ -4,11 +4,7 @@ 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();
|
||||
const title = db.select().from(titles).where(eq(titles.id, titleId)).get();
|
||||
if (!title) return;
|
||||
|
||||
const data = await getWatchProviders(title.tmdbId, title.type);
|
||||
@@ -19,8 +15,7 @@ export async function refreshAvailability(titleId: string) {
|
||||
const offerTypes = ["flatrate", "rent", "buy", "free", "ads"] as const;
|
||||
|
||||
// Delete existing offers for this title+region
|
||||
await db
|
||||
.delete(availabilityOffers)
|
||||
db.delete(availabilityOffers)
|
||||
.where(
|
||||
and(
|
||||
eq(availabilityOffers.titleId, titleId),
|
||||
@@ -34,8 +29,7 @@ export async function refreshAvailability(titleId: string) {
|
||||
if (!providers) continue;
|
||||
|
||||
for (const p of providers) {
|
||||
await db
|
||||
.insert(availabilityOffers)
|
||||
db.insert(availabilityOffers)
|
||||
.values({
|
||||
titleId,
|
||||
region: "US",
|
||||
@@ -52,7 +46,7 @@ export async function refreshAvailability(titleId: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAvailability(titleId: string) {
|
||||
export function getAvailability(titleId: string) {
|
||||
return db
|
||||
.select()
|
||||
.from(availabilityOffers)
|
||||
|
||||
@@ -51,8 +51,7 @@ export async function extractAndStoreColors(
|
||||
lightMuted: palette.LightMuted?.hex ?? null,
|
||||
};
|
||||
|
||||
await db
|
||||
.update(titles)
|
||||
db.update(titles)
|
||||
.set({ colorPalette: JSON.stringify(colors) })
|
||||
.where(eq(titles.id, titleId))
|
||||
.run();
|
||||
|
||||
+59
-80
@@ -20,7 +20,7 @@ export interface DashboardStats {
|
||||
completed: number;
|
||||
}
|
||||
|
||||
export async function getUserStats(userId: string): Promise<DashboardStats> {
|
||||
export function getUserStats(userId: string): DashboardStats {
|
||||
const now = new Date();
|
||||
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
const dayOfWeek = now.getDay();
|
||||
@@ -28,7 +28,7 @@ export async function getUserStats(userId: string): Promise<DashboardStats> {
|
||||
weekStart.setDate(now.getDate() - (dayOfWeek === 0 ? 6 : dayOfWeek - 1));
|
||||
weekStart.setHours(0, 0, 0, 0);
|
||||
|
||||
const [moviesThisMonth] = await db
|
||||
const [moviesThisMonth] = db
|
||||
.select({ count: sql<number>`count(*)` })
|
||||
.from(userMovieWatches)
|
||||
.where(
|
||||
@@ -39,7 +39,7 @@ export async function getUserStats(userId: string): Promise<DashboardStats> {
|
||||
)
|
||||
.all();
|
||||
|
||||
const [episodesThisWeek] = await db
|
||||
const [episodesThisWeek] = db
|
||||
.select({ count: sql<number>`count(*)` })
|
||||
.from(userEpisodeWatches)
|
||||
.where(
|
||||
@@ -50,13 +50,13 @@ export async function getUserStats(userId: string): Promise<DashboardStats> {
|
||||
)
|
||||
.all();
|
||||
|
||||
const [librarySizeRow] = await db
|
||||
const [librarySizeRow] = db
|
||||
.select({ count: sql<number>`count(*)` })
|
||||
.from(userTitleStatus)
|
||||
.where(eq(userTitleStatus.userId, userId))
|
||||
.all();
|
||||
|
||||
const [completedCount] = await db
|
||||
const [completedCount] = db
|
||||
.select({ count: sql<number>`count(*)` })
|
||||
.from(userTitleStatus)
|
||||
.where(
|
||||
@@ -96,11 +96,11 @@ export interface ContinueWatchingItem {
|
||||
watchedEpisodes: number;
|
||||
}
|
||||
|
||||
export async function getContinueWatchingFeed(
|
||||
export function getContinueWatchingFeed(
|
||||
userId: string,
|
||||
): Promise<ContinueWatchingItem[]> {
|
||||
): ContinueWatchingItem[] {
|
||||
// Get in-progress TV shows
|
||||
const inProgress = await db
|
||||
const inProgress = db
|
||||
.select({
|
||||
titleId: userTitleStatus.titleId,
|
||||
updatedAt: userTitleStatus.updatedAt,
|
||||
@@ -118,7 +118,7 @@ export async function getContinueWatchingFeed(
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
|
||||
for (const row of inProgress) {
|
||||
const title = await db
|
||||
const title = db
|
||||
.select()
|
||||
.from(titles)
|
||||
.where(and(eq(titles.id, row.titleId), eq(titles.type, "tv")))
|
||||
@@ -126,7 +126,7 @@ export async function getContinueWatchingFeed(
|
||||
if (!title) continue;
|
||||
|
||||
// Get all seasons for this title, ordered
|
||||
const titleSeasons = await db
|
||||
const titleSeasons = db
|
||||
.select()
|
||||
.from(seasons)
|
||||
.where(eq(seasons.titleId, title.id))
|
||||
@@ -141,7 +141,7 @@ export async function getContinueWatchingFeed(
|
||||
|
||||
// Get most recent watch for this show
|
||||
for (const s of titleSeasons) {
|
||||
const eps = await db
|
||||
const eps = db
|
||||
.select()
|
||||
.from(episodes)
|
||||
.where(eq(episodes.seasonId, s.id))
|
||||
@@ -151,7 +151,7 @@ export async function getContinueWatchingFeed(
|
||||
totalEpisodes += eps.length;
|
||||
|
||||
for (const ep of eps) {
|
||||
const watch = await db
|
||||
const watch = db
|
||||
.select()
|
||||
.from(userEpisodeWatches)
|
||||
.where(
|
||||
@@ -210,10 +210,10 @@ export async function getContinueWatchingFeed(
|
||||
}
|
||||
|
||||
// biome-ignore lint/correctness/noUnusedFunctionParameters: days reserved for future date filtering
|
||||
export async function getNewAvailableFeed(userId: string, days = 14) {
|
||||
export function getNewAvailableFeed(userId: string, days = 14) {
|
||||
// Get titles the user has in any status that have availability offers
|
||||
// and recent release/air dates
|
||||
const results = await db
|
||||
const results = db
|
||||
.select({
|
||||
titleId: titles.id,
|
||||
title: titles.title,
|
||||
@@ -243,52 +243,46 @@ export async function getNewAvailableFeed(userId: string, days = 14) {
|
||||
return results;
|
||||
}
|
||||
|
||||
export async function getRecommendationsFeed(userId: string) {
|
||||
export function getRecommendationsFeed(userId: string) {
|
||||
// Get recommendations from user's highly-rated or completed titles
|
||||
const userCompletedOrRated = (
|
||||
await db
|
||||
.select({ titleId: userTitleStatus.titleId })
|
||||
.from(userTitleStatus)
|
||||
.where(
|
||||
and(
|
||||
eq(userTitleStatus.userId, userId),
|
||||
eq(userTitleStatus.status, "completed"),
|
||||
),
|
||||
)
|
||||
.all()
|
||||
).map((r) => r.titleId);
|
||||
const userCompletedOrRated = db
|
||||
.select({ titleId: userTitleStatus.titleId })
|
||||
.from(userTitleStatus)
|
||||
.where(
|
||||
and(
|
||||
eq(userTitleStatus.userId, userId),
|
||||
eq(userTitleStatus.status, "completed"),
|
||||
),
|
||||
)
|
||||
.all()
|
||||
.map((r) => r.titleId);
|
||||
|
||||
const ratedIds = (
|
||||
await db
|
||||
.select({ titleId: userRatings.titleId })
|
||||
.from(userRatings)
|
||||
.where(
|
||||
and(
|
||||
eq(userRatings.userId, userId),
|
||||
sql`${userRatings.ratingStars} >= 4`,
|
||||
),
|
||||
)
|
||||
.all()
|
||||
).map((r) => r.titleId);
|
||||
const ratedIds = db
|
||||
.select({ titleId: userRatings.titleId })
|
||||
.from(userRatings)
|
||||
.where(
|
||||
and(eq(userRatings.userId, userId), sql`${userRatings.ratingStars} >= 4`),
|
||||
)
|
||||
.all()
|
||||
.map((r) => r.titleId);
|
||||
|
||||
const sourceIds = [...new Set([...userCompletedOrRated, ...ratedIds])];
|
||||
if (sourceIds.length === 0) return [];
|
||||
|
||||
// Get all tracked title IDs to exclude
|
||||
const trackedIds = new Set(
|
||||
(
|
||||
await db
|
||||
.select({ titleId: userTitleStatus.titleId })
|
||||
.from(userTitleStatus)
|
||||
.where(eq(userTitleStatus.userId, userId))
|
||||
.all()
|
||||
).map((r) => r.titleId),
|
||||
db
|
||||
.select({ titleId: userTitleStatus.titleId })
|
||||
.from(userTitleStatus)
|
||||
.where(eq(userTitleStatus.userId, userId))
|
||||
.all()
|
||||
.map((r) => r.titleId),
|
||||
);
|
||||
|
||||
const recs: Map<string, { titleId: string; score: number }> = new Map();
|
||||
|
||||
for (const sourceId of sourceIds) {
|
||||
const recRows = await db
|
||||
const recRows = db
|
||||
.select({
|
||||
recommendedTitleId: titleRecommendations.recommendedTitleId,
|
||||
rank: titleRecommendations.rank,
|
||||
@@ -316,29 +310,16 @@ export async function getRecommendationsFeed(userId: string) {
|
||||
.sort((a, b) => b.score - a.score)
|
||||
.slice(0, 20);
|
||||
|
||||
return (
|
||||
await Promise.all(
|
||||
sorted.map(async (r) => {
|
||||
const title = await db
|
||||
.select()
|
||||
.from(titles)
|
||||
.where(eq(titles.id, r.titleId))
|
||||
.get();
|
||||
return title;
|
||||
}),
|
||||
)
|
||||
).filter(Boolean);
|
||||
return sorted
|
||||
.map((r) => db.select().from(titles).where(eq(titles.id, r.titleId)).get())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
export async function getRecommendationsForTitle(titleId: string) {
|
||||
const title = await db
|
||||
.select()
|
||||
.from(titles)
|
||||
.where(eq(titles.id, titleId))
|
||||
.get();
|
||||
export function getRecommendationsForTitle(titleId: string) {
|
||||
const title = db.select().from(titles).where(eq(titles.id, titleId)).get();
|
||||
if (!title) return [];
|
||||
|
||||
const recs = await db
|
||||
const recs = db
|
||||
.select({
|
||||
recommendedTitleId: titleRecommendations.recommendedTitleId,
|
||||
source: titleRecommendations.source,
|
||||
@@ -349,20 +330,18 @@ export async function getRecommendationsForTitle(titleId: string) {
|
||||
.orderBy(titleRecommendations.rank)
|
||||
.all();
|
||||
|
||||
const results = (
|
||||
await Promise.all(
|
||||
recs.map(async (rec) => {
|
||||
const recTitle = await db
|
||||
.select()
|
||||
.from(titles)
|
||||
.where(eq(titles.id, rec.recommendedTitleId))
|
||||
.get();
|
||||
return recTitle
|
||||
? { ...recTitle, source: rec.source, rank: rec.rank }
|
||||
: null;
|
||||
}),
|
||||
)
|
||||
).filter((r): r is NonNullable<typeof r> => r !== null);
|
||||
const results = recs
|
||||
.map((rec) => {
|
||||
const recTitle = db
|
||||
.select()
|
||||
.from(titles)
|
||||
.where(eq(titles.id, rec.recommendedTitleId))
|
||||
.get();
|
||||
return recTitle
|
||||
? { ...recTitle, source: rec.source, rank: rec.rank }
|
||||
: null;
|
||||
})
|
||||
.filter((r): r is NonNullable<typeof r> => r !== null);
|
||||
|
||||
return results.map((r) => ({
|
||||
id: r.id,
|
||||
|
||||
@@ -121,11 +121,7 @@ export async function fetchAndMaybeCache(
|
||||
}
|
||||
|
||||
export async function cacheImagesForTitle(titleId: string) {
|
||||
const title = await db
|
||||
.select()
|
||||
.from(titles)
|
||||
.where(eq(titles.id, titleId))
|
||||
.get();
|
||||
const title = db.select().from(titles).where(eq(titles.id, titleId)).get();
|
||||
if (!title) return;
|
||||
|
||||
const tasks: Promise<unknown>[] = [];
|
||||
@@ -145,7 +141,7 @@ export async function cacheImagesForTitle(titleId: string) {
|
||||
|
||||
// Season posters
|
||||
if (title.type === "tv") {
|
||||
const allSeasons = await db
|
||||
const allSeasons = db
|
||||
.select()
|
||||
.from(seasons)
|
||||
.where(eq(seasons.titleId, titleId))
|
||||
@@ -164,14 +160,14 @@ export async function cacheImagesForTitle(titleId: string) {
|
||||
}
|
||||
|
||||
export async function cacheEpisodeStills(titleId: string) {
|
||||
const allSeasons = await db
|
||||
const allSeasons = db
|
||||
.select()
|
||||
.from(seasons)
|
||||
.where(eq(seasons.titleId, titleId))
|
||||
.all();
|
||||
|
||||
for (const s of allSeasons) {
|
||||
const eps = await db
|
||||
const eps = db
|
||||
.select()
|
||||
.from(episodes)
|
||||
.where(eq(episodes.seasonId, s.id))
|
||||
@@ -191,7 +187,7 @@ export async function cacheEpisodeStills(titleId: string) {
|
||||
}
|
||||
|
||||
export async function cacheProviderLogos(titleId: string) {
|
||||
const offers = await db
|
||||
const offers = db
|
||||
.select()
|
||||
.from(availabilityOffers)
|
||||
.where(eq(availabilityOffers.titleId, titleId))
|
||||
|
||||
+55
-85
@@ -35,7 +35,7 @@ export async function importTitle(
|
||||
options?: { awaitEnrichment?: boolean },
|
||||
) {
|
||||
const awaitEnrichment = options?.awaitEnrichment ?? false;
|
||||
const existing = await db
|
||||
const existing = db
|
||||
.select()
|
||||
.from(titles)
|
||||
.where(eq(titles.tmdbId, tmdbId))
|
||||
@@ -45,18 +45,15 @@ export async function importTitle(
|
||||
// They may be missing if a prior fetch failed or the title was created
|
||||
// as a shell by the recommendations system (lastFetchedAt: null).
|
||||
if (existing.type === "tv") {
|
||||
const seasonCount = (
|
||||
await db
|
||||
.select()
|
||||
.from(seasons)
|
||||
.where(eq(seasons.titleId, existing.id))
|
||||
.all()
|
||||
).length;
|
||||
const seasonCount = db
|
||||
.select()
|
||||
.from(seasons)
|
||||
.where(eq(seasons.titleId, existing.id))
|
||||
.all().length;
|
||||
if (seasonCount === 0) {
|
||||
const show = await getTvDetails(tmdbId);
|
||||
if (!existing.lastFetchedAt) {
|
||||
await db
|
||||
.update(titles)
|
||||
db.update(titles)
|
||||
.set({
|
||||
overview: show.overview,
|
||||
posterPath: show.poster_path,
|
||||
@@ -93,7 +90,7 @@ export async function importTitle(
|
||||
|
||||
if (type === "movie") {
|
||||
const movie = await getMovieDetails(tmdbId);
|
||||
const row = await db
|
||||
const row = db
|
||||
.insert(titles)
|
||||
.values({
|
||||
tmdbId: movie.id,
|
||||
@@ -126,7 +123,7 @@ export async function importTitle(
|
||||
}
|
||||
|
||||
const show = await getTvDetails(tmdbId);
|
||||
const row = await db
|
||||
const row = db
|
||||
.insert(titles)
|
||||
.values({
|
||||
tmdbId: show.id,
|
||||
@@ -164,19 +161,14 @@ export async function importTitle(
|
||||
}
|
||||
|
||||
export async function refreshTitle(titleId: string) {
|
||||
const title = await db
|
||||
.select()
|
||||
.from(titles)
|
||||
.where(eq(titles.id, titleId))
|
||||
.get();
|
||||
const title = db.select().from(titles).where(eq(titles.id, titleId)).get();
|
||||
if (!title) return null;
|
||||
|
||||
const now = new Date();
|
||||
|
||||
if (title.type === "movie") {
|
||||
const movie = await getMovieDetails(title.tmdbId);
|
||||
await db
|
||||
.update(titles)
|
||||
db.update(titles)
|
||||
.set({
|
||||
title: movie.title,
|
||||
originalTitle: movie.original_title,
|
||||
@@ -194,8 +186,7 @@ export async function refreshTitle(titleId: string) {
|
||||
.run();
|
||||
} else {
|
||||
const show = await getTvDetails(title.tmdbId);
|
||||
await db
|
||||
.update(titles)
|
||||
db.update(titles)
|
||||
.set({
|
||||
title: show.name,
|
||||
originalTitle: show.original_name,
|
||||
@@ -214,11 +205,7 @@ export async function refreshTitle(titleId: string) {
|
||||
await refreshTvChildren(titleId, title.tmdbId, show.number_of_seasons);
|
||||
}
|
||||
|
||||
const updated = await db
|
||||
.select()
|
||||
.from(titles)
|
||||
.where(eq(titles.id, titleId))
|
||||
.get();
|
||||
const updated = db.select().from(titles).where(eq(titles.id, titleId)).get();
|
||||
if (updated) {
|
||||
extractAndStoreColors(updated.id, updated.posterPath).catch(() => {});
|
||||
if (imageCacheEnabled()) {
|
||||
@@ -243,7 +230,7 @@ export async function refreshTvChildren(
|
||||
try {
|
||||
const seasonData = await getTvSeasonDetails(tmdbId, sn);
|
||||
|
||||
const seasonRow = await db
|
||||
const seasonRow = db
|
||||
.insert(seasons)
|
||||
.values({
|
||||
titleId,
|
||||
@@ -268,8 +255,7 @@ export async function refreshTvChildren(
|
||||
.get();
|
||||
|
||||
for (const ep of seasonData.episodes) {
|
||||
await db
|
||||
.insert(episodes)
|
||||
db.insert(episodes)
|
||||
.values({
|
||||
seasonId: seasonRow.id,
|
||||
episodeNumber: ep.episode_number,
|
||||
@@ -300,11 +286,7 @@ export async function refreshTvChildren(
|
||||
}
|
||||
|
||||
export async function refreshRecommendations(titleId: string) {
|
||||
const title = await db
|
||||
.select()
|
||||
.from(titles)
|
||||
.where(eq(titles.id, titleId))
|
||||
.get();
|
||||
const title = db.select().from(titles).where(eq(titles.id, titleId)).get();
|
||||
if (!title) return;
|
||||
|
||||
const now = new Date();
|
||||
@@ -322,7 +304,7 @@ export async function refreshRecommendations(titleId: string) {
|
||||
if (type !== "movie" && type !== "tv") continue;
|
||||
|
||||
// Minimal upsert of the recommended title
|
||||
const existing = await db
|
||||
const existing = db
|
||||
.select()
|
||||
.from(titles)
|
||||
.where(eq(titles.tmdbId, r.id))
|
||||
@@ -331,7 +313,7 @@ export async function refreshRecommendations(titleId: string) {
|
||||
if (existing) {
|
||||
recTitleId = existing.id;
|
||||
} else {
|
||||
const row = await db
|
||||
const row = db
|
||||
.insert(titles)
|
||||
.values({
|
||||
tmdbId: r.id,
|
||||
@@ -352,7 +334,7 @@ export async function refreshRecommendations(titleId: string) {
|
||||
.returning()
|
||||
.get();
|
||||
if (!row) {
|
||||
const found = await db
|
||||
const found = db
|
||||
.select()
|
||||
.from(titles)
|
||||
.where(eq(titles.tmdbId, r.id))
|
||||
@@ -364,8 +346,7 @@ export async function refreshRecommendations(titleId: string) {
|
||||
}
|
||||
}
|
||||
|
||||
await db
|
||||
.insert(titleRecommendations)
|
||||
db.insert(titleRecommendations)
|
||||
.values({
|
||||
titleId,
|
||||
recommendedTitleId: recTitleId,
|
||||
@@ -390,7 +371,7 @@ export async function refreshRecommendations(titleId: string) {
|
||||
const type = r.media_type ?? title.type;
|
||||
if (type !== "movie" && type !== "tv") continue;
|
||||
|
||||
const existing = await db
|
||||
const existing = db
|
||||
.select()
|
||||
.from(titles)
|
||||
.where(eq(titles.tmdbId, r.id))
|
||||
@@ -399,7 +380,7 @@ export async function refreshRecommendations(titleId: string) {
|
||||
if (existing) {
|
||||
recTitleId = existing.id;
|
||||
} else {
|
||||
const row = await db
|
||||
const row = db
|
||||
.insert(titles)
|
||||
.values({
|
||||
tmdbId: r.id,
|
||||
@@ -420,7 +401,7 @@ export async function refreshRecommendations(titleId: string) {
|
||||
.returning()
|
||||
.get();
|
||||
if (!row) {
|
||||
const found = await db
|
||||
const found = db
|
||||
.select()
|
||||
.from(titles)
|
||||
.where(eq(titles.tmdbId, r.id))
|
||||
@@ -432,8 +413,7 @@ export async function refreshRecommendations(titleId: string) {
|
||||
}
|
||||
}
|
||||
|
||||
await db
|
||||
.insert(titleRecommendations)
|
||||
db.insert(titleRecommendations)
|
||||
.values({
|
||||
titleId,
|
||||
recommendedTitleId: recTitleId,
|
||||
@@ -458,15 +438,14 @@ export async function getTitleWithChildren(id: string): Promise<{
|
||||
seasons: Season[];
|
||||
availability: AvailabilityOffer[];
|
||||
} | null> {
|
||||
let title = await db.select().from(titles).where(eq(titles.id, id)).get();
|
||||
let title = db.select().from(titles).where(eq(titles.id, id)).get();
|
||||
if (!title) return null;
|
||||
|
||||
// If this is a shell TV title, fetch full details now
|
||||
if (title.type === "tv" && !title.lastFetchedAt) {
|
||||
try {
|
||||
const show = await getTvDetails(title.tmdbId);
|
||||
await db
|
||||
.update(titles)
|
||||
db.update(titles)
|
||||
.set({
|
||||
overview: show.overview,
|
||||
posterPath: show.poster_path,
|
||||
@@ -477,9 +456,7 @@ export async function getTitleWithChildren(id: string): Promise<{
|
||||
.where(eq(titles.id, id))
|
||||
.run();
|
||||
await refreshTvChildren(id, title.tmdbId, show.number_of_seasons);
|
||||
title =
|
||||
(await db.select().from(titles).where(eq(titles.id, id)).get()) ??
|
||||
title;
|
||||
title = db.select().from(titles).where(eq(titles.id, id)).get() ?? title;
|
||||
} catch {
|
||||
// Continue with whatever data we have
|
||||
}
|
||||
@@ -489,8 +466,7 @@ export async function getTitleWithChildren(id: string): Promise<{
|
||||
if (title.type === "movie" && !title.lastFetchedAt) {
|
||||
try {
|
||||
const movie = await getMovieDetails(title.tmdbId);
|
||||
await db
|
||||
.update(titles)
|
||||
db.update(titles)
|
||||
.set({
|
||||
title: movie.title,
|
||||
originalTitle: movie.original_title,
|
||||
@@ -506,9 +482,7 @@ export async function getTitleWithChildren(id: string): Promise<{
|
||||
})
|
||||
.where(eq(titles.id, id))
|
||||
.run();
|
||||
title =
|
||||
(await db.select().from(titles).where(eq(titles.id, id)).get()) ??
|
||||
title;
|
||||
title = db.select().from(titles).where(eq(titles.id, id)).get() ?? title;
|
||||
} catch {
|
||||
// Continue with whatever data we have
|
||||
}
|
||||
@@ -517,26 +491,24 @@ export async function getTitleWithChildren(id: string): Promise<{
|
||||
let titleSeasons: Season[] = [];
|
||||
|
||||
if (title.type === "tv") {
|
||||
const seasonRows = await db
|
||||
const seasonRows = db
|
||||
.select()
|
||||
.from(seasons)
|
||||
.where(eq(seasons.titleId, title.id))
|
||||
.orderBy(seasons.seasonNumber)
|
||||
.all();
|
||||
|
||||
titleSeasons = await Promise.all(
|
||||
seasonRows.map(async (s) => ({
|
||||
id: s.id,
|
||||
seasonNumber: s.seasonNumber,
|
||||
name: s.name,
|
||||
episodes: (
|
||||
await db
|
||||
.select()
|
||||
.from(episodes)
|
||||
.where(eq(episodes.seasonId, s.id))
|
||||
.orderBy(episodes.episodeNumber)
|
||||
.all()
|
||||
).map(
|
||||
titleSeasons = seasonRows.map((s) => ({
|
||||
id: s.id,
|
||||
seasonNumber: s.seasonNumber,
|
||||
name: s.name,
|
||||
episodes: db
|
||||
.select()
|
||||
.from(episodes)
|
||||
.where(eq(episodes.seasonId, s.id))
|
||||
.orderBy(episodes.episodeNumber)
|
||||
.all()
|
||||
.map(
|
||||
(ep): Episode => ({
|
||||
id: ep.id,
|
||||
episodeNumber: ep.episodeNumber,
|
||||
@@ -547,24 +519,22 @@ export async function getTitleWithChildren(id: string): Promise<{
|
||||
runtimeMinutes: ep.runtimeMinutes,
|
||||
}),
|
||||
),
|
||||
})),
|
||||
);
|
||||
}));
|
||||
}
|
||||
|
||||
const availability = (
|
||||
await db
|
||||
.select()
|
||||
.from(availabilityOffers)
|
||||
.where(eq(availabilityOffers.titleId, title.id))
|
||||
.all()
|
||||
).map(
|
||||
(a): AvailabilityOffer => ({
|
||||
providerId: a.providerId,
|
||||
providerName: a.providerName,
|
||||
logoPath: tmdbImageUrl(a.logoPath, "w92"),
|
||||
offerType: a.offerType,
|
||||
}),
|
||||
);
|
||||
const availability = db
|
||||
.select()
|
||||
.from(availabilityOffers)
|
||||
.where(eq(availabilityOffers.titleId, title.id))
|
||||
.all()
|
||||
.map(
|
||||
(a): AvailabilityOffer => ({
|
||||
providerId: a.providerId,
|
||||
providerName: a.providerName,
|
||||
logoPath: tmdbImageUrl(a.logoPath, "w92"),
|
||||
offerType: a.offerType,
|
||||
}),
|
||||
);
|
||||
|
||||
// Lazy color extraction — await so the palette is available for theming
|
||||
let palette = parseColorPalette(title.colorPalette);
|
||||
|
||||
+11
-11
@@ -2,8 +2,8 @@ import { count, eq } from "drizzle-orm";
|
||||
import { db } from "@/lib/db/client";
|
||||
import { appSettings, user } from "@/lib/db/schema";
|
||||
|
||||
export async function getSetting(key: string): Promise<string | null> {
|
||||
const row = await db
|
||||
export function getSetting(key: string): string | null {
|
||||
const row = db
|
||||
.select()
|
||||
.from(appSettings)
|
||||
.where(eq(appSettings.key, key))
|
||||
@@ -11,22 +11,22 @@ export async function getSetting(key: string): Promise<string | null> {
|
||||
return row?.value ?? null;
|
||||
}
|
||||
|
||||
export async function setSetting(key: string, value: string): Promise<void> {
|
||||
await db
|
||||
.insert(appSettings)
|
||||
export function setSetting(key: string, value: string): void {
|
||||
db.insert(appSettings)
|
||||
.values({ key, value })
|
||||
.onConflictDoUpdate({ target: appSettings.key, set: { value } });
|
||||
.onConflictDoUpdate({ target: appSettings.key, set: { value } })
|
||||
.run();
|
||||
}
|
||||
|
||||
export async function getUserCount(): Promise<number> {
|
||||
const result = await db.select({ count: count() }).from(user).get();
|
||||
export function getUserCount(): number {
|
||||
const result = db.select({ count: count() }).from(user).get();
|
||||
return result?.count ?? 0;
|
||||
}
|
||||
|
||||
export async function isRegistrationOpen(): Promise<boolean> {
|
||||
const userCount = await getUserCount();
|
||||
export function isRegistrationOpen(): boolean {
|
||||
const userCount = getUserCount();
|
||||
if (userCount === 0) return true;
|
||||
|
||||
const setting = await getSetting("registrationOpen");
|
||||
const setting = getSetting("registrationOpen");
|
||||
return setting === "true";
|
||||
}
|
||||
|
||||
+49
-70
@@ -10,7 +10,7 @@ import {
|
||||
userTitleStatus,
|
||||
} from "@/lib/db/schema";
|
||||
|
||||
export async function setTitleStatus(
|
||||
export function setTitleStatus(
|
||||
userId: string,
|
||||
titleId: string,
|
||||
status: "watchlist" | "in_progress" | "completed",
|
||||
@@ -18,8 +18,7 @@ export async function setTitleStatus(
|
||||
source: "manual" | "import" | "plex" | "jellyfin" = "manual",
|
||||
) {
|
||||
const now = new Date();
|
||||
await db
|
||||
.insert(userTitleStatus)
|
||||
db.insert(userTitleStatus)
|
||||
.values({ userId, titleId, status, addedAt: now, updatedAt: now })
|
||||
.onConflictDoUpdate({
|
||||
target: [userTitleStatus.userId, userTitleStatus.titleId],
|
||||
@@ -28,9 +27,8 @@ export async function setTitleStatus(
|
||||
.run();
|
||||
}
|
||||
|
||||
export async function removeTitleStatus(userId: string, titleId: string) {
|
||||
await db
|
||||
.delete(userTitleStatus)
|
||||
export function removeTitleStatus(userId: string, titleId: string) {
|
||||
db.delete(userTitleStatus)
|
||||
.where(
|
||||
and(
|
||||
eq(userTitleStatus.userId, userId),
|
||||
@@ -40,19 +38,18 @@ export async function removeTitleStatus(userId: string, titleId: string) {
|
||||
.run();
|
||||
}
|
||||
|
||||
export async function logMovieWatch(
|
||||
export function logMovieWatch(
|
||||
userId: string,
|
||||
titleId: string,
|
||||
source: "manual" | "import" | "plex" | "jellyfin" = "manual",
|
||||
) {
|
||||
const now = new Date();
|
||||
await db
|
||||
.insert(userMovieWatches)
|
||||
db.insert(userMovieWatches)
|
||||
.values({ userId, titleId, watchedAt: now, source })
|
||||
.run();
|
||||
|
||||
// Auto-set status to completed
|
||||
const existing = await db
|
||||
const existing = db
|
||||
.select()
|
||||
.from(userTitleStatus)
|
||||
.where(
|
||||
@@ -64,31 +61,26 @@ export async function logMovieWatch(
|
||||
.get();
|
||||
|
||||
if (!existing) {
|
||||
await setTitleStatus(userId, titleId, "completed", source);
|
||||
setTitleStatus(userId, titleId, "completed", source);
|
||||
} else if (existing.status !== "completed") {
|
||||
await setTitleStatus(userId, titleId, "completed", source);
|
||||
setTitleStatus(userId, titleId, "completed", source);
|
||||
}
|
||||
}
|
||||
|
||||
export async function logEpisodeWatch(
|
||||
export function logEpisodeWatch(
|
||||
userId: string,
|
||||
episodeId: string,
|
||||
source: "manual" | "import" | "plex" | "jellyfin" = "manual",
|
||||
) {
|
||||
const now = new Date();
|
||||
await db
|
||||
.insert(userEpisodeWatches)
|
||||
db.insert(userEpisodeWatches)
|
||||
.values({ userId, episodeId, watchedAt: now, source })
|
||||
.run();
|
||||
|
||||
// Find the title for this episode
|
||||
const ep = await db
|
||||
.select()
|
||||
.from(episodes)
|
||||
.where(eq(episodes.id, episodeId))
|
||||
.get();
|
||||
const ep = db.select().from(episodes).where(eq(episodes.id, episodeId)).get();
|
||||
if (!ep) return;
|
||||
const season = await db
|
||||
const season = db
|
||||
.select()
|
||||
.from(seasons)
|
||||
.where(eq(seasons.id, ep.seasonId))
|
||||
@@ -97,7 +89,7 @@ export async function logEpisodeWatch(
|
||||
const titleId = season.titleId;
|
||||
|
||||
// Auto-set status to in_progress if not set
|
||||
const existing = await db
|
||||
const existing = db
|
||||
.select()
|
||||
.from(userTitleStatus)
|
||||
.where(
|
||||
@@ -109,41 +101,37 @@ export async function logEpisodeWatch(
|
||||
.get();
|
||||
|
||||
if (!existing) {
|
||||
await setTitleStatus(userId, titleId, "in_progress", source);
|
||||
setTitleStatus(userId, titleId, "in_progress", source);
|
||||
}
|
||||
|
||||
// Check if all episodes are watched -> auto-complete
|
||||
await checkAllEpisodesWatched(userId, titleId);
|
||||
checkAllEpisodesWatched(userId, titleId);
|
||||
}
|
||||
|
||||
export async function markAllEpisodesWatched(
|
||||
export function markAllEpisodesWatched(
|
||||
userId: string,
|
||||
titleId: string,
|
||||
source: "manual" | "import" | "plex" | "jellyfin" = "manual",
|
||||
) {
|
||||
const title = await db
|
||||
.select()
|
||||
.from(titles)
|
||||
.where(eq(titles.id, titleId))
|
||||
.get();
|
||||
const title = db.select().from(titles).where(eq(titles.id, titleId)).get();
|
||||
if (!title || title.type !== "tv") return;
|
||||
|
||||
const now = new Date();
|
||||
const allSeasons = await db
|
||||
const allSeasons = db
|
||||
.select()
|
||||
.from(seasons)
|
||||
.where(eq(seasons.titleId, titleId))
|
||||
.all();
|
||||
|
||||
for (const s of allSeasons) {
|
||||
const eps = await db
|
||||
const eps = db
|
||||
.select()
|
||||
.from(episodes)
|
||||
.where(eq(episodes.seasonId, s.id))
|
||||
.all();
|
||||
|
||||
for (const ep of eps) {
|
||||
const existing = await db
|
||||
const existing = db
|
||||
.select()
|
||||
.from(userEpisodeWatches)
|
||||
.where(
|
||||
@@ -154,8 +142,7 @@ export async function markAllEpisodesWatched(
|
||||
)
|
||||
.get();
|
||||
if (!existing) {
|
||||
await db
|
||||
.insert(userEpisodeWatches)
|
||||
db.insert(userEpisodeWatches)
|
||||
.values({
|
||||
userId,
|
||||
episodeId: ep.id,
|
||||
@@ -167,11 +154,11 @@ export async function markAllEpisodesWatched(
|
||||
}
|
||||
}
|
||||
|
||||
await setTitleStatus(userId, titleId, "completed", source);
|
||||
setTitleStatus(userId, titleId, "completed", source);
|
||||
}
|
||||
|
||||
async function checkAllEpisodesWatched(userId: string, titleId: string) {
|
||||
const allSeasons = await db
|
||||
function checkAllEpisodesWatched(userId: string, titleId: string) {
|
||||
const allSeasons = db
|
||||
.select()
|
||||
.from(seasons)
|
||||
.where(eq(seasons.titleId, titleId))
|
||||
@@ -181,7 +168,7 @@ async function checkAllEpisodesWatched(userId: string, titleId: string) {
|
||||
let watchedEpisodes = 0;
|
||||
|
||||
for (const s of allSeasons) {
|
||||
const eps = await db
|
||||
const eps = db
|
||||
.select()
|
||||
.from(episodes)
|
||||
.where(eq(episodes.seasonId, s.id))
|
||||
@@ -189,7 +176,7 @@ async function checkAllEpisodesWatched(userId: string, titleId: string) {
|
||||
totalEpisodes += eps.length;
|
||||
|
||||
for (const ep of eps) {
|
||||
const watch = await db
|
||||
const watch = db
|
||||
.select()
|
||||
.from(userEpisodeWatches)
|
||||
.where(
|
||||
@@ -204,13 +191,12 @@ async function checkAllEpisodesWatched(userId: string, titleId: string) {
|
||||
}
|
||||
|
||||
if (totalEpisodes > 0 && watchedEpisodes >= totalEpisodes) {
|
||||
await setTitleStatus(userId, titleId, "completed");
|
||||
setTitleStatus(userId, titleId, "completed");
|
||||
}
|
||||
}
|
||||
|
||||
export async function unwatchEpisode(userId: string, episodeId: string) {
|
||||
await db
|
||||
.delete(userEpisodeWatches)
|
||||
export function unwatchEpisode(userId: string, episodeId: string) {
|
||||
db.delete(userEpisodeWatches)
|
||||
.where(
|
||||
and(
|
||||
eq(userEpisodeWatches.userId, userId),
|
||||
@@ -220,20 +206,16 @@ export async function unwatchEpisode(userId: string, episodeId: string) {
|
||||
.run();
|
||||
|
||||
// Find parent title and downgrade from completed to in_progress
|
||||
const ep = await db
|
||||
.select()
|
||||
.from(episodes)
|
||||
.where(eq(episodes.id, episodeId))
|
||||
.get();
|
||||
const ep = db.select().from(episodes).where(eq(episodes.id, episodeId)).get();
|
||||
if (!ep) return;
|
||||
const season = await db
|
||||
const season = db
|
||||
.select()
|
||||
.from(seasons)
|
||||
.where(eq(seasons.id, ep.seasonId))
|
||||
.get();
|
||||
if (!season) return;
|
||||
|
||||
const existing = await db
|
||||
const existing = db
|
||||
.select()
|
||||
.from(userTitleStatus)
|
||||
.where(
|
||||
@@ -245,12 +227,12 @@ export async function unwatchEpisode(userId: string, episodeId: string) {
|
||||
.get();
|
||||
|
||||
if (existing?.status === "completed") {
|
||||
await setTitleStatus(userId, season.titleId, "in_progress");
|
||||
setTitleStatus(userId, season.titleId, "in_progress");
|
||||
}
|
||||
}
|
||||
|
||||
export async function unwatchSeason(userId: string, seasonId: string) {
|
||||
const seasonEps = await db
|
||||
export function unwatchSeason(userId: string, seasonId: string) {
|
||||
const seasonEps = db
|
||||
.select()
|
||||
.from(episodes)
|
||||
.where(eq(episodes.seasonId, seasonId))
|
||||
@@ -258,8 +240,7 @@ export async function unwatchSeason(userId: string, seasonId: string) {
|
||||
|
||||
const epIds = seasonEps.map((ep) => ep.id);
|
||||
if (epIds.length > 0) {
|
||||
await db
|
||||
.delete(userEpisodeWatches)
|
||||
db.delete(userEpisodeWatches)
|
||||
.where(
|
||||
and(
|
||||
eq(userEpisodeWatches.userId, userId),
|
||||
@@ -270,14 +251,14 @@ export async function unwatchSeason(userId: string, seasonId: string) {
|
||||
}
|
||||
|
||||
// Find parent title and downgrade from completed to in_progress
|
||||
const season = await db
|
||||
const season = db
|
||||
.select()
|
||||
.from(seasons)
|
||||
.where(eq(seasons.id, seasonId))
|
||||
.get();
|
||||
if (!season) return;
|
||||
|
||||
const existing = await db
|
||||
const existing = db
|
||||
.select()
|
||||
.from(userTitleStatus)
|
||||
.where(
|
||||
@@ -289,27 +270,25 @@ export async function unwatchSeason(userId: string, seasonId: string) {
|
||||
.get();
|
||||
|
||||
if (existing?.status === "completed") {
|
||||
await setTitleStatus(userId, season.titleId, "in_progress");
|
||||
setTitleStatus(userId, season.titleId, "in_progress");
|
||||
}
|
||||
}
|
||||
|
||||
export async function rateTitleStars(
|
||||
export function rateTitleStars(
|
||||
userId: string,
|
||||
titleId: string,
|
||||
ratingStars: number,
|
||||
) {
|
||||
const now = new Date();
|
||||
if (ratingStars === 0) {
|
||||
await db
|
||||
.delete(userRatings)
|
||||
db.delete(userRatings)
|
||||
.where(
|
||||
and(eq(userRatings.userId, userId), eq(userRatings.titleId, titleId)),
|
||||
)
|
||||
.run();
|
||||
return;
|
||||
}
|
||||
await db
|
||||
.insert(userRatings)
|
||||
db.insert(userRatings)
|
||||
.values({ userId, titleId, ratingStars, ratedAt: now })
|
||||
.onConflictDoUpdate({
|
||||
target: [userRatings.userId, userRatings.titleId],
|
||||
@@ -318,8 +297,8 @@ export async function rateTitleStars(
|
||||
.run();
|
||||
}
|
||||
|
||||
export async function getUserTitleInfo(userId: string, titleId: string) {
|
||||
const status = await db
|
||||
export function getUserTitleInfo(userId: string, titleId: string) {
|
||||
const status = db
|
||||
.select()
|
||||
.from(userTitleStatus)
|
||||
.where(
|
||||
@@ -330,7 +309,7 @@ export async function getUserTitleInfo(userId: string, titleId: string) {
|
||||
)
|
||||
.get();
|
||||
|
||||
const rating = await db
|
||||
const rating = db
|
||||
.select()
|
||||
.from(userRatings)
|
||||
.where(
|
||||
@@ -339,7 +318,7 @@ export async function getUserTitleInfo(userId: string, titleId: string) {
|
||||
.get();
|
||||
|
||||
// Get watched episode IDs for this title
|
||||
const titleSeasons = await db
|
||||
const titleSeasons = db
|
||||
.select()
|
||||
.from(seasons)
|
||||
.where(eq(seasons.titleId, titleId))
|
||||
@@ -347,13 +326,13 @@ export async function getUserTitleInfo(userId: string, titleId: string) {
|
||||
|
||||
const watchedEpisodeIds: string[] = [];
|
||||
for (const s of titleSeasons) {
|
||||
const eps = await db
|
||||
const eps = db
|
||||
.select()
|
||||
.from(episodes)
|
||||
.where(eq(episodes.seasonId, s.id))
|
||||
.all();
|
||||
for (const ep of eps) {
|
||||
const watch = await db
|
||||
const watch = db
|
||||
.select()
|
||||
.from(userEpisodeWatches)
|
||||
.where(
|
||||
|
||||
+29
-43
@@ -197,12 +197,9 @@ async function resolveEpisode(event: WebhookEvent): Promise<{
|
||||
|
||||
// ─── Deduplication ──────────────────────────────────────────────────
|
||||
|
||||
async function isDuplicateMovieWatch(
|
||||
userId: string,
|
||||
titleId: string,
|
||||
): Promise<boolean> {
|
||||
function isDuplicateMovieWatch(userId: string, titleId: string): boolean {
|
||||
const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
|
||||
const recent = await db
|
||||
const recent = db
|
||||
.select()
|
||||
.from(userMovieWatches)
|
||||
.where(
|
||||
@@ -216,12 +213,9 @@ async function isDuplicateMovieWatch(
|
||||
return !!recent;
|
||||
}
|
||||
|
||||
async function isDuplicateEpisodeWatch(
|
||||
userId: string,
|
||||
episodeId: string,
|
||||
): Promise<boolean> {
|
||||
function isDuplicateEpisodeWatch(userId: string, episodeId: string): boolean {
|
||||
const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
|
||||
const recent = await db
|
||||
const recent = db
|
||||
.select()
|
||||
.from(userEpisodeWatches)
|
||||
.where(
|
||||
@@ -237,14 +231,13 @@ async function isDuplicateEpisodeWatch(
|
||||
|
||||
// ─── Event Logging ──────────────────────────────────────────────────
|
||||
|
||||
async function logEvent(
|
||||
function logEvent(
|
||||
connectionId: string,
|
||||
event: WebhookEvent | null,
|
||||
status: "success" | "ignored" | "error",
|
||||
errorMessage?: string,
|
||||
) {
|
||||
await db
|
||||
.insert(webhookEventLog)
|
||||
db.insert(webhookEventLog)
|
||||
.values({
|
||||
connectionId,
|
||||
eventType: event?.provider === "plex" ? "media.scrobble" : "PlaybackStop",
|
||||
@@ -256,8 +249,7 @@ async function logEvent(
|
||||
})
|
||||
.run();
|
||||
|
||||
await db
|
||||
.update(webhookConnections)
|
||||
db.update(webhookConnections)
|
||||
.set({ lastEventAt: new Date() })
|
||||
.where(eq(webhookConnections.id, connectionId))
|
||||
.run();
|
||||
@@ -275,7 +267,7 @@ export async function processWebhook(
|
||||
if (event.mediaType === "movie") {
|
||||
const tmdbId = await resolveMovieTmdbId(event);
|
||||
if (!tmdbId) {
|
||||
await logEvent(
|
||||
logEvent(
|
||||
connectionId,
|
||||
event,
|
||||
"error",
|
||||
@@ -286,12 +278,12 @@ export async function processWebhook(
|
||||
|
||||
const title = await importTitle(tmdbId, "movie");
|
||||
if (!title) {
|
||||
await logEvent(connectionId, event, "error", "Failed to import movie");
|
||||
logEvent(connectionId, event, "error", "Failed to import movie");
|
||||
return { status: "error", message: "Failed to import movie" };
|
||||
}
|
||||
|
||||
if (await isDuplicateMovieWatch(userId, title.id)) {
|
||||
await logEvent(
|
||||
if (isDuplicateMovieWatch(userId, title.id)) {
|
||||
logEvent(
|
||||
connectionId,
|
||||
event,
|
||||
"ignored",
|
||||
@@ -300,36 +292,26 @@ export async function processWebhook(
|
||||
return { status: "ignored", message: "Duplicate watch" };
|
||||
}
|
||||
|
||||
await logMovieWatch(userId, title.id, provider);
|
||||
await logEvent(connectionId, event, "success");
|
||||
logMovieWatch(userId, title.id, provider);
|
||||
logEvent(connectionId, event, "success");
|
||||
return { status: "success", message: `Logged watch for ${event.title}` };
|
||||
}
|
||||
|
||||
if (event.mediaType === "episode") {
|
||||
const resolved = await resolveEpisode(event);
|
||||
if (!resolved) {
|
||||
await logEvent(
|
||||
connectionId,
|
||||
event,
|
||||
"error",
|
||||
"Could not resolve episode",
|
||||
);
|
||||
logEvent(connectionId, event, "error", "Could not resolve episode");
|
||||
return { status: "error", message: "Could not resolve episode" };
|
||||
}
|
||||
|
||||
const title = await importTitle(resolved.showTmdbId, "tv");
|
||||
if (!title) {
|
||||
await logEvent(
|
||||
connectionId,
|
||||
event,
|
||||
"error",
|
||||
"Failed to import TV show",
|
||||
);
|
||||
logEvent(connectionId, event, "error", "Failed to import TV show");
|
||||
return { status: "error", message: "Failed to import TV show" };
|
||||
}
|
||||
|
||||
// Find the episode in our DB
|
||||
const season = await db
|
||||
const season = db
|
||||
.select()
|
||||
.from(seasons)
|
||||
.where(
|
||||
@@ -341,7 +323,7 @@ export async function processWebhook(
|
||||
.get();
|
||||
|
||||
if (!season) {
|
||||
await logEvent(
|
||||
logEvent(
|
||||
connectionId,
|
||||
event,
|
||||
"error",
|
||||
@@ -353,7 +335,7 @@ export async function processWebhook(
|
||||
};
|
||||
}
|
||||
|
||||
const episode = await db
|
||||
const episode = db
|
||||
.select()
|
||||
.from(episodes)
|
||||
.where(
|
||||
@@ -365,7 +347,7 @@ export async function processWebhook(
|
||||
.get();
|
||||
|
||||
if (!episode) {
|
||||
await logEvent(
|
||||
logEvent(
|
||||
connectionId,
|
||||
event,
|
||||
"error",
|
||||
@@ -377,8 +359,8 @@ export async function processWebhook(
|
||||
};
|
||||
}
|
||||
|
||||
if (await isDuplicateEpisodeWatch(userId, episode.id)) {
|
||||
await logEvent(
|
||||
if (isDuplicateEpisodeWatch(userId, episode.id)) {
|
||||
logEvent(
|
||||
connectionId,
|
||||
event,
|
||||
"ignored",
|
||||
@@ -387,12 +369,12 @@ export async function processWebhook(
|
||||
return { status: "ignored", message: "Duplicate watch" };
|
||||
}
|
||||
|
||||
await logEpisodeWatch(userId, episode.id, provider);
|
||||
await logEvent(connectionId, event, "success");
|
||||
logEpisodeWatch(userId, episode.id, provider);
|
||||
logEvent(connectionId, event, "success");
|
||||
return { status: "success", message: `Logged watch for ${event.title}` };
|
||||
}
|
||||
|
||||
await logEvent(
|
||||
logEvent(
|
||||
connectionId,
|
||||
event,
|
||||
"ignored",
|
||||
@@ -401,7 +383,11 @@ export async function processWebhook(
|
||||
return { status: "ignored", message: "Unsupported media type" };
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Unknown error";
|
||||
await logEvent(connectionId, event, "error", message).catch(() => {});
|
||||
try {
|
||||
logEvent(connectionId, event, "error", message);
|
||||
} catch {
|
||||
// best-effort
|
||||
}
|
||||
return { status: "error", message };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user