Handle watch status on entries

This commit is contained in:
Zoe Roux 2025-04-06 18:25:41 +02:00
parent 11e1c59698
commit c3abd7c61b
No known key found for this signature in database
2 changed files with 45 additions and 10 deletions

View File

@ -1,11 +1,13 @@
import { type SQL, and, desc, eq, isNotNull, ne, sql } from "drizzle-orm"; import { type SQL, and, desc, eq, isNotNull, ne, sql } from "drizzle-orm";
import { Elysia, t } from "elysia"; import { Elysia, t } from "elysia";
import { auth } from "~/auth";
import { db } from "~/db"; import { db } from "~/db";
import { import {
entries, entries,
entryTranslations, entryTranslations,
entryVideoJoin, entryVideoJoin,
history, history,
profiles,
shows, shows,
videos, videos,
} from "~/db/schema"; } from "~/db/schema";
@ -124,7 +126,7 @@ export const entryVideosQ = db
.leftJoin(videos, eq(videos.pk, entryVideoJoin.videoPk)) .leftJoin(videos, eq(videos.pk, entryVideoJoin.videoPk))
.as("videos"); .as("videos");
export const getEntryProgressQ = (userId: number) => export const getEntryProgressQ = (userId: string) =>
db db
.selectDistinctOn([history.entryPk], { .selectDistinctOn([history.entryPk], {
percent: history.percent, percent: history.percent,
@ -133,11 +135,23 @@ export const getEntryProgressQ = (userId: number) =>
videoId: videos.id, videoId: videos.id,
}) })
.from(history) .from(history)
.where(eq(history.profilePk, userId))
.leftJoin(videos, eq(history.videoPk, videos.pk)) .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)) .orderBy(history.entryPk, desc(history.playedDate))
.as("progress"); .as("progress");
export const mapProgress = (
progressQ: ReturnType<typeof getEntryProgressQ>,
) => {
const { time, percent, videoId } = getColumns(progressQ);
return {
time: coalesce(time, sql`0`),
percent: coalesce(percent, sql`0`),
videoId,
};
};
async function getEntries({ async function getEntries({
after, after,
limit, limit,
@ -153,7 +167,7 @@ async function getEntries({
sort: Sort; sort: Sort;
filter: SQL | undefined; filter: SQL | undefined;
languages: string[]; languages: string[];
userId: number; userId: string;
}): Promise<(Entry | Extra | UnknownEntry)[]> { }): Promise<(Entry | Extra | UnknownEntry)[]> {
const transQ = db const transQ = db
.selectDistinctOn([entryTranslations.pk]) .selectDistinctOn([entryTranslations.pk])
@ -181,7 +195,7 @@ async function getEntries({
...entryCol, ...entryCol,
...transCol, ...transCol,
videos: entryVideosQ.videos, videos: entryVideosQ.videos,
progress: getColumns(entryProgressQ), progress: mapProgress(entryProgressQ),
// specials don't have an `episodeNumber` but a `number` field. // specials don't have an `episodeNumber` but a `number` field.
number: episodeNumber, number: episodeNumber,
@ -230,6 +244,7 @@ export const entriesH = new Elysia({ tags: ["series"] })
...models, ...models,
entry: t.Union([models.episode, models.movie_entry, models.special]), entry: t.Union([models.episode, models.movie_entry, models.special]),
})) }))
.use(auth)
.get( .get(
"/series/:id/entries", "/series/:id/entries",
async ({ async ({
@ -237,6 +252,7 @@ export const entriesH = new Elysia({ tags: ["series"] })
query: { limit, after, query, sort, filter }, query: { limit, after, query, sort, filter },
headers: { "accept-language": languages }, headers: { "accept-language": languages },
request: { url }, request: { url },
jwt: { sub },
error, error,
}) => { }) => {
const [serie] = await db const [serie] = await db
@ -270,6 +286,7 @@ export const entriesH = new Elysia({ tags: ["series"] })
filter, filter,
), ),
languages: langs, languages: langs,
userId: sub,
})) as Entry[]; })) as Entry[];
return createPage(items, { url, sort, limit }); return createPage(items, { url, sort, limit });
@ -316,6 +333,7 @@ export const entriesH = new Elysia({ tags: ["series"] })
params: { id }, params: { id },
query: { limit, after, query, sort, filter }, query: { limit, after, query, sort, filter },
request: { url }, request: { url },
jwt: { sub },
error, error,
}) => { }) => {
const [serie] = await db const [serie] = await db
@ -347,6 +365,7 @@ export const entriesH = new Elysia({ tags: ["series"] })
filter, filter,
), ),
languages: ["extra"], languages: ["extra"],
userId: sub,
})) as Extra[]; })) as Extra[];
return createPage(items, { url, sort, limit }); return createPage(items, { url, sort, limit });
@ -386,6 +405,7 @@ export const entriesH = new Elysia({ tags: ["series"] })
async ({ async ({
query: { limit, after, query, sort, filter }, query: { limit, after, query, sort, filter },
request: { url }, request: { url },
jwt: { sub },
}) => { }) => {
const items = (await getEntries({ const items = (await getEntries({
limit, limit,
@ -394,6 +414,7 @@ export const entriesH = new Elysia({ tags: ["series"] })
sort: sort, sort: sort,
filter: and(eq(entries.kind, "unknown"), filter), filter: and(eq(entries.kind, "unknown"), filter),
languages: ["extra"], languages: ["extra"],
userId: sub,
})) as UnknownEntry[]; })) as UnknownEntry[];
return createPage(items, { url, sort, limit }); return createPage(items, { url, sort, limit });
@ -421,7 +442,11 @@ export const entriesH = new Elysia({ tags: ["series"] })
) )
.get( .get(
"/news", "/news",
async ({ query: { limit, after, query, filter }, request: { url } }) => { async ({
query: { limit, after, query, filter },
request: { url },
jwt: { sub },
}) => {
const sort = newsSort; const sort = newsSort;
const items = (await getEntries({ const items = (await getEntries({
limit, limit,
@ -435,6 +460,7 @@ export const entriesH = new Elysia({ tags: ["series"] })
filter, filter,
), ),
languages: ["extra"], languages: ["extra"],
userId: sub,
})) as Entry[]; })) as Entry[];
return createPage(items, { url, sort, limit }); return createPage(items, { url, sort, limit });

View File

@ -1,6 +1,6 @@
import { import {
Column,
type ColumnsSelection, type ColumnsSelection,
InferColumnsDataTypes,
type SQL, type SQL,
type SQLWrapper, type SQLWrapper,
type Subquery, type Subquery,
@ -13,7 +13,7 @@ import {
} from "drizzle-orm"; } from "drizzle-orm";
import type { CasingCache } from "drizzle-orm/casing"; import type { CasingCache } from "drizzle-orm/casing";
import type { AnyMySqlSelect } from "drizzle-orm/mysql-core"; 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 { AnySQLiteSelect } from "drizzle-orm/sqlite-core";
import type { WithSubquery } from "drizzle-orm/subquery"; import type { WithSubquery } from "drizzle-orm/subquery";
import { db } from "./index"; import { db } from "./index";
@ -95,7 +95,7 @@ export function values(items: Record<string, unknown>[]) {
}; };
} }
export const coalesce = <T>(val: SQL<T>, def: SQLWrapper) => { export const coalesce = <T>(val: SQL<T> | Column, def: SQL<T>) => {
return sql<T>`coalesce(${val}, ${def})`; return sql<T>`coalesce(${val}, ${def})`;
}; };
@ -109,10 +109,19 @@ export const jsonbAgg = <T>(val: SQL<T>) => {
return sql<T[]>`jsonb_agg(${val})`; return sql<T[]>`jsonb_agg(${val})`;
}; };
export const jsonbBuildObject = <T>(select: Record<string, SQLWrapper>) => { type JsonFields = {
[k: string]:
| SelectedFieldsFlat[string]
| Table
| SelectedFieldsFlat
| JsonFields;
};
export const jsonbBuildObject = <T>(select: JsonFields) => {
const query = sql.join( const query = sql.join(
Object.entries(select).flatMap(([k, v]) => { 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<any>(v as JsonFields)];
}), }),
sql.raw(", "), sql.raw(", "),
); );