mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
Add progress status in every entry
This commit is contained in:
parent
781a6a8196
commit
6ecaec2dee
@ -1,10 +1,11 @@
|
||||
import { type SQL, and, 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 { db } from "~/db";
|
||||
import {
|
||||
entries,
|
||||
entryTranslations,
|
||||
entryVideoJoin,
|
||||
history,
|
||||
shows,
|
||||
videos,
|
||||
} from "~/db/schema";
|
||||
@ -39,7 +40,7 @@ import {
|
||||
processLanguages,
|
||||
sortToSql,
|
||||
} from "~/models/utils";
|
||||
import { desc } from "~/models/utils/descriptions";
|
||||
import { desc as description } from "~/models/utils/descriptions";
|
||||
import type { EmbeddedVideo } from "~/models/video";
|
||||
|
||||
const entryFilters: FilterDef = {
|
||||
@ -149,6 +150,18 @@ async function getEntries({
|
||||
.leftJoin(videos, eq(videos.pk, entryVideoJoin.videoPk))
|
||||
.as("videos");
|
||||
|
||||
const progressQ = db
|
||||
.selectDistinctOn([history.entryPk], {
|
||||
percent: history.percent,
|
||||
time: history.time,
|
||||
entryPk: history.entryPk,
|
||||
videoId: videos.id,
|
||||
})
|
||||
.from(history)
|
||||
.leftJoin(videos, eq(history.videoPk, videos.pk))
|
||||
.orderBy(history.entryPk, desc(history.playedDate))
|
||||
.as("progress");
|
||||
|
||||
const {
|
||||
kind,
|
||||
externalId,
|
||||
@ -163,6 +176,9 @@ async function getEntries({
|
||||
...entryCol,
|
||||
...transCol,
|
||||
videos: videosQ.videos,
|
||||
progress: {
|
||||
...getColumns(progressQ),
|
||||
},
|
||||
// specials don't have an `episodeNumber` but a `number` field.
|
||||
number: episodeNumber,
|
||||
|
||||
@ -181,6 +197,7 @@ async function getEntries({
|
||||
.from(entries)
|
||||
.innerJoin(transQ, eq(entries.pk, transQ.pk))
|
||||
.leftJoinLateral(videosQ, sql`true`)
|
||||
.leftJoin(progressQ, eq(entries.pk, progressQ.entryPk))
|
||||
.where(
|
||||
and(
|
||||
filter,
|
||||
@ -265,14 +282,14 @@ export const entriesH = new Elysia({ tags: ["series"] })
|
||||
query: t.Object({
|
||||
sort: entrySort,
|
||||
filter: t.Optional(Filter({ def: entryFilters })),
|
||||
query: t.Optional(t.String({ description: desc.query })),
|
||||
query: t.Optional(t.String({ description: description.query })),
|
||||
limit: t.Integer({
|
||||
minimum: 1,
|
||||
maximum: 250,
|
||||
default: 50,
|
||||
description: "Max page size.",
|
||||
}),
|
||||
after: t.Optional(t.String({ description: desc.after })),
|
||||
after: t.Optional(t.String({ description: description.after })),
|
||||
}),
|
||||
headers: t.Object(
|
||||
{
|
||||
@ -342,14 +359,14 @@ export const entriesH = new Elysia({ tags: ["series"] })
|
||||
query: t.Object({
|
||||
sort: extraSort,
|
||||
filter: t.Optional(Filter({ def: extraFilters })),
|
||||
query: t.Optional(t.String({ description: desc.query })),
|
||||
query: t.Optional(t.String({ description: description.query })),
|
||||
limit: t.Integer({
|
||||
minimum: 1,
|
||||
maximum: 250,
|
||||
default: 50,
|
||||
description: "Max page size.",
|
||||
}),
|
||||
after: t.Optional(t.String({ description: desc.after })),
|
||||
after: t.Optional(t.String({ description: description.after })),
|
||||
}),
|
||||
response: {
|
||||
200: Page(Extra),
|
||||
@ -383,14 +400,14 @@ export const entriesH = new Elysia({ tags: ["series"] })
|
||||
query: t.Object({
|
||||
sort: extraSort,
|
||||
filter: t.Optional(Filter({ def: unknownFilters })),
|
||||
query: t.Optional(t.String({ description: desc.query })),
|
||||
query: t.Optional(t.String({ description: description.query })),
|
||||
limit: t.Integer({
|
||||
minimum: 1,
|
||||
maximum: 250,
|
||||
default: 50,
|
||||
description: "Max page size.",
|
||||
}),
|
||||
after: t.Optional(t.String({ description: desc.after })),
|
||||
after: t.Optional(t.String({ description: description.after })),
|
||||
}),
|
||||
response: {
|
||||
200: Page(UnknownEntry),
|
||||
@ -423,14 +440,14 @@ export const entriesH = new Elysia({ tags: ["series"] })
|
||||
detail: { description: "Get new movies/episodes added recently." },
|
||||
query: t.Object({
|
||||
filter: t.Optional(Filter({ def: entryFilters })),
|
||||
query: t.Optional(t.String({ description: desc.query })),
|
||||
query: t.Optional(t.String({ description: description.query })),
|
||||
limit: t.Integer({
|
||||
minimum: 1,
|
||||
maximum: 250,
|
||||
default: 50,
|
||||
description: "Max page size.",
|
||||
}),
|
||||
after: t.Optional(t.String({ description: desc.after })),
|
||||
after: t.Optional(t.String({ description: description.after })),
|
||||
}),
|
||||
response: {
|
||||
200: Page(Entry),
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { index, integer, jsonb, timestamp } from "drizzle-orm/pg-core";
|
||||
import type { Progress } from "~/models/watchlist";
|
||||
import { sql } from "drizzle-orm";
|
||||
import { check, index, integer, jsonb, timestamp } from "drizzle-orm/pg-core";
|
||||
import { entries } from "./entries";
|
||||
import { profiles } from "./profiles";
|
||||
import { schema } from "./utils";
|
||||
@ -18,8 +18,13 @@ export const history = schema.table(
|
||||
videoPk: integer()
|
||||
.notNull()
|
||||
.references(() => videos.pk, { onDelete: "set null" }),
|
||||
progress: jsonb().$type<Progress>(),
|
||||
percent: integer().notNull().default(0),
|
||||
time: integer(),
|
||||
playedDate: timestamp({ mode: "string" }).notNull().defaultNow(),
|
||||
},
|
||||
(t) => [index("history_play_date").on(t.playedDate.desc())],
|
||||
(t) => [
|
||||
index("history_play_date").on(t.playedDate.desc()),
|
||||
|
||||
check("percent_valid", sql`${t.percent} between 0 and 100`),
|
||||
],
|
||||
);
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
} from "../utils";
|
||||
import { EmbeddedVideo } from "../video";
|
||||
import { BaseEntry, EntryTranslation } from "./base-entry";
|
||||
import { Progress } from "../watchlist";
|
||||
|
||||
export const BaseEpisode = t.Intersect([
|
||||
t.Object({
|
||||
@ -27,7 +28,8 @@ export const Episode = t.Intersect([
|
||||
EntryTranslation(),
|
||||
BaseEpisode,
|
||||
t.Object({
|
||||
videos: t.Optional(t.Array(EmbeddedVideo)),
|
||||
videos: t.Array(EmbeddedVideo),
|
||||
progress: Progress,
|
||||
}),
|
||||
DbMetadata,
|
||||
]);
|
||||
|
@ -4,6 +4,7 @@ import { madeInAbyss, registerExamples } from "../examples";
|
||||
import { DbMetadata, SeedImage } from "../utils";
|
||||
import { Resource } from "../utils/resource";
|
||||
import { BaseEntry } from "./base-entry";
|
||||
import { Progress } from "../watchlist";
|
||||
|
||||
export const ExtraType = t.UnionEnum([
|
||||
"other",
|
||||
@ -31,7 +32,14 @@ export const BaseExtra = t.Intersect(
|
||||
},
|
||||
);
|
||||
|
||||
export const Extra = t.Intersect([Resource(), BaseExtra, DbMetadata]);
|
||||
export const Extra = t.Intersect([
|
||||
Resource(),
|
||||
BaseExtra,
|
||||
t.Object({
|
||||
progress: t.Omit(Progress, ["videoId"]),
|
||||
}),
|
||||
DbMetadata,
|
||||
]);
|
||||
export type Extra = Prettify<typeof Extra.static>;
|
||||
|
||||
export const SeedExtra = t.Intersect([
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
} from "../utils";
|
||||
import { EmbeddedVideo } from "../video";
|
||||
import { BaseEntry, EntryTranslation } from "./base-entry";
|
||||
import { Progress } from "../watchlist";
|
||||
|
||||
export const BaseMovieEntry = t.Intersect(
|
||||
[
|
||||
@ -46,6 +47,7 @@ export const MovieEntry = t.Intersect([
|
||||
BaseMovieEntry,
|
||||
t.Object({
|
||||
videos: t.Optional(t.Array(EmbeddedVideo)),
|
||||
progress: Progress,
|
||||
}),
|
||||
DbMetadata,
|
||||
]);
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
} from "../utils";
|
||||
import { EmbeddedVideo } from "../video";
|
||||
import { BaseEntry, EntryTranslation } from "./base-entry";
|
||||
import { Progress } from "../watchlist";
|
||||
|
||||
export const BaseSpecial = t.Intersect(
|
||||
[
|
||||
@ -38,6 +39,7 @@ export const Special = t.Intersect([
|
||||
BaseSpecial,
|
||||
t.Object({
|
||||
videos: t.Optional(t.Array(EmbeddedVideo)),
|
||||
progress: Progress,
|
||||
}),
|
||||
DbMetadata,
|
||||
]);
|
||||
|
@ -2,6 +2,7 @@ import { t } from "elysia";
|
||||
import { type Prettify, comment } from "~/utils";
|
||||
import { bubbleImages, registerExamples, youtubeExample } from "../examples";
|
||||
import { DbMetadata, Resource } from "../utils";
|
||||
import { Progress } from "../watchlist";
|
||||
import { BaseEntry, EntryTranslation } from "./base-entry";
|
||||
|
||||
export const BaseUnknownEntry = t.Intersect(
|
||||
@ -27,6 +28,9 @@ export const UnknownEntry = t.Intersect([
|
||||
Resource(),
|
||||
UnknownEntryTranslation,
|
||||
BaseUnknownEntry,
|
||||
t.Object({
|
||||
progress: t.Omit(Progress, ["videoId"]),
|
||||
}),
|
||||
DbMetadata,
|
||||
]);
|
||||
export type UnknownEntry = Prettify<typeof UnknownEntry.static>;
|
||||
|
@ -1,10 +1,29 @@
|
||||
import { t } from "elysia";
|
||||
import { comment } from "~/utils";
|
||||
|
||||
export const Progress = t.Object({
|
||||
percent: t.Integer({ minimum: 0, maximum: 100 }),
|
||||
time: t.Number({
|
||||
minimum: 0,
|
||||
description: "When this episode was stopped (in seconds since the start",
|
||||
}),
|
||||
time: t.Nullable(
|
||||
t.Integer({
|
||||
minimum: 0,
|
||||
description: comment`
|
||||
When this episode was stopped (in seconds since the start).
|
||||
This value is null if the entry was never watched or is finished.
|
||||
`,
|
||||
}),
|
||||
),
|
||||
videoId: t.Nullable(
|
||||
t.String({
|
||||
format: "uuid",
|
||||
description: comment`
|
||||
Id of the video the user watched.
|
||||
This can be used to resume playback in the correct video file
|
||||
without asking the user what video to play.
|
||||
|
||||
This will be null if the user did not watch the entry or
|
||||
if the video was deleted since.
|
||||
`,
|
||||
}),
|
||||
),
|
||||
});
|
||||
export type Progress = typeof Progress.static;
|
||||
|
Loading…
x
Reference in New Issue
Block a user