From c3abd7c61b0c3e0e22342ad2c945b748b04e2cb7 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 6 Apr 2025 18:25:41 +0200 Subject: [PATCH] Handle watch status on entries --- api/src/controllers/entries.ts | 36 +++++++++++++++++++++++++++++----- api/src/db/utils.ts | 19 +++++++++++++----- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/api/src/controllers/entries.ts b/api/src/controllers/entries.ts index 7d2e5eb7..1fb84b8a 100644 --- a/api/src/controllers/entries.ts +++ b/api/src/controllers/entries.ts @@ -1,11 +1,13 @@ import { type SQL, and, desc, eq, isNotNull, ne, sql } from "drizzle-orm"; import { Elysia, t } from "elysia"; +import { auth } from "~/auth"; import { db } from "~/db"; import { entries, entryTranslations, entryVideoJoin, history, + profiles, shows, videos, } from "~/db/schema"; @@ -124,7 +126,7 @@ export const entryVideosQ = db .leftJoin(videos, eq(videos.pk, entryVideoJoin.videoPk)) .as("videos"); -export const getEntryProgressQ = (userId: number) => +export const getEntryProgressQ = (userId: string) => db .selectDistinctOn([history.entryPk], { percent: history.percent, @@ -133,11 +135,23 @@ export const getEntryProgressQ = (userId: number) => videoId: videos.id, }) .from(history) - .where(eq(history.profilePk, userId)) .leftJoin(videos, eq(history.videoPk, videos.pk)) + .leftJoin(profiles, eq(history.profilePk, profiles.pk)) + .where(eq(profiles.id, userId)) .orderBy(history.entryPk, desc(history.playedDate)) .as("progress"); +export const mapProgress = ( + progressQ: ReturnType, +) => { + const { time, percent, videoId } = getColumns(progressQ); + return { + time: coalesce(time, sql`0`), + percent: coalesce(percent, sql`0`), + videoId, + }; +}; + async function getEntries({ after, limit, @@ -153,7 +167,7 @@ async function getEntries({ sort: Sort; filter: SQL | undefined; languages: string[]; - userId: number; + userId: string; }): Promise<(Entry | Extra | UnknownEntry)[]> { const transQ = db .selectDistinctOn([entryTranslations.pk]) @@ -181,7 +195,7 @@ async function getEntries({ ...entryCol, ...transCol, videos: entryVideosQ.videos, - progress: getColumns(entryProgressQ), + progress: mapProgress(entryProgressQ), // specials don't have an `episodeNumber` but a `number` field. number: episodeNumber, @@ -230,6 +244,7 @@ export const entriesH = new Elysia({ tags: ["series"] }) ...models, entry: t.Union([models.episode, models.movie_entry, models.special]), })) + .use(auth) .get( "/series/:id/entries", async ({ @@ -237,6 +252,7 @@ export const entriesH = new Elysia({ tags: ["series"] }) query: { limit, after, query, sort, filter }, headers: { "accept-language": languages }, request: { url }, + jwt: { sub }, error, }) => { const [serie] = await db @@ -270,6 +286,7 @@ export const entriesH = new Elysia({ tags: ["series"] }) filter, ), languages: langs, + userId: sub, })) as Entry[]; return createPage(items, { url, sort, limit }); @@ -316,6 +333,7 @@ export const entriesH = new Elysia({ tags: ["series"] }) params: { id }, query: { limit, after, query, sort, filter }, request: { url }, + jwt: { sub }, error, }) => { const [serie] = await db @@ -347,6 +365,7 @@ export const entriesH = new Elysia({ tags: ["series"] }) filter, ), languages: ["extra"], + userId: sub, })) as Extra[]; return createPage(items, { url, sort, limit }); @@ -386,6 +405,7 @@ export const entriesH = new Elysia({ tags: ["series"] }) async ({ query: { limit, after, query, sort, filter }, request: { url }, + jwt: { sub }, }) => { const items = (await getEntries({ limit, @@ -394,6 +414,7 @@ export const entriesH = new Elysia({ tags: ["series"] }) sort: sort, filter: and(eq(entries.kind, "unknown"), filter), languages: ["extra"], + userId: sub, })) as UnknownEntry[]; return createPage(items, { url, sort, limit }); @@ -421,7 +442,11 @@ export const entriesH = new Elysia({ tags: ["series"] }) ) .get( "/news", - async ({ query: { limit, after, query, filter }, request: { url } }) => { + async ({ + query: { limit, after, query, filter }, + request: { url }, + jwt: { sub }, + }) => { const sort = newsSort; const items = (await getEntries({ limit, @@ -435,6 +460,7 @@ export const entriesH = new Elysia({ tags: ["series"] }) filter, ), languages: ["extra"], + userId: sub, })) as Entry[]; return createPage(items, { url, sort, limit }); diff --git a/api/src/db/utils.ts b/api/src/db/utils.ts index baa3658c..2f1e6689 100644 --- a/api/src/db/utils.ts +++ b/api/src/db/utils.ts @@ -1,6 +1,6 @@ import { + Column, type ColumnsSelection, - InferColumnsDataTypes, type SQL, type SQLWrapper, type Subquery, @@ -13,7 +13,7 @@ import { } from "drizzle-orm"; import type { CasingCache } from "drizzle-orm/casing"; import type { AnyMySqlSelect } from "drizzle-orm/mysql-core"; -import type { AnyPgSelect } from "drizzle-orm/pg-core"; +import type { AnyPgSelect, SelectedFieldsFlat } from "drizzle-orm/pg-core"; import type { AnySQLiteSelect } from "drizzle-orm/sqlite-core"; import type { WithSubquery } from "drizzle-orm/subquery"; import { db } from "./index"; @@ -95,7 +95,7 @@ export function values(items: Record[]) { }; } -export const coalesce = (val: SQL, def: SQLWrapper) => { +export const coalesce = (val: SQL | Column, def: SQL) => { return sql`coalesce(${val}, ${def})`; }; @@ -109,10 +109,19 @@ export const jsonbAgg = (val: SQL) => { return sql`jsonb_agg(${val})`; }; -export const jsonbBuildObject = (select: Record) => { +type JsonFields = { + [k: string]: + | SelectedFieldsFlat[string] + | Table + | SelectedFieldsFlat + | JsonFields; +}; +export const jsonbBuildObject = (select: JsonFields) => { const query = sql.join( Object.entries(select).flatMap(([k, v]) => { - return [sql.raw(`'${k}'`), v]; + if (v.getSQL) return [sql.raw(`'${k}'`), v]; + // nested object (getSql is present in all SqlWrappers) + return [sql.raw(`'${k}'`), jsonbBuildObject(v as JsonFields)]; }), sql.raw(", "), );