mirror of
				https://github.com/zoriya/Kyoo.git
				synced 2025-11-03 19:17:16 -05:00 
			
		
		
		
	Create /news (#839)
This commit is contained in:
		
						commit
						458eb2c387
					
				
							
								
								
									
										1
									
								
								api/drizzle/0015_news.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								api/drizzle/0015_news.sql
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					ALTER TABLE "kyoo"."entries" ADD COLUMN "available_since" timestamp with time zone;
 | 
				
			||||||
							
								
								
									
										1493
									
								
								api/drizzle/meta/0015_snapshot.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1493
									
								
								api/drizzle/meta/0015_snapshot.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -106,6 +106,13 @@
 | 
				
			|||||||
			"when": 1741601145901,
 | 
								"when": 1741601145901,
 | 
				
			||||||
			"tag": "0014_staff",
 | 
								"tag": "0014_staff",
 | 
				
			||||||
			"breakpoints": true
 | 
								"breakpoints": true
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								"idx": 15,
 | 
				
			||||||
 | 
								"version": "7",
 | 
				
			||||||
 | 
								"when": 1741623934941,
 | 
				
			||||||
 | 
								"tag": "0015_news",
 | 
				
			||||||
 | 
								"breakpoints": true
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	]
 | 
						]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
import { type SQL, and, eq, ne, sql } from "drizzle-orm";
 | 
					import { type SQL, and, eq, isNotNull, ne, sql } from "drizzle-orm";
 | 
				
			||||||
import { Elysia, t } from "elysia";
 | 
					import { Elysia, t } from "elysia";
 | 
				
			||||||
import { db } from "~/db";
 | 
					import { db } from "~/db";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
@ -93,6 +93,19 @@ const extraSort = Sort(
 | 
				
			|||||||
	},
 | 
						},
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const newsSort: Sort = {
 | 
				
			||||||
 | 
						tablePk: entries.pk,
 | 
				
			||||||
 | 
						sort: [
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								sql: entries.availableSince,
 | 
				
			||||||
 | 
								// in the news query we already filter nulls out
 | 
				
			||||||
 | 
								isNullable: false,
 | 
				
			||||||
 | 
								accessor: (x) => x.availableSince,
 | 
				
			||||||
 | 
								desc: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function getEntries({
 | 
					async function getEntries({
 | 
				
			||||||
	after,
 | 
						after,
 | 
				
			||||||
	limit,
 | 
						limit,
 | 
				
			||||||
@ -304,7 +317,7 @@ export const entriesH = new Elysia({ tags: ["series"] })
 | 
				
			|||||||
				limit,
 | 
									limit,
 | 
				
			||||||
				after,
 | 
									after,
 | 
				
			||||||
				query,
 | 
									query,
 | 
				
			||||||
				sort: sort as any,
 | 
									sort: sort,
 | 
				
			||||||
				filter: and(
 | 
									filter: and(
 | 
				
			||||||
					eq(entries.showPk, serie.pk),
 | 
										eq(entries.showPk, serie.pk),
 | 
				
			||||||
					eq(entries.kind, "extra"),
 | 
										eq(entries.kind, "extra"),
 | 
				
			||||||
@ -355,7 +368,7 @@ export const entriesH = new Elysia({ tags: ["series"] })
 | 
				
			|||||||
				limit,
 | 
									limit,
 | 
				
			||||||
				after,
 | 
									after,
 | 
				
			||||||
				query,
 | 
									query,
 | 
				
			||||||
				sort: sort as any,
 | 
									sort: sort,
 | 
				
			||||||
				filter: and(eq(entries.kind, "unknown"), filter),
 | 
									filter: and(eq(entries.kind, "unknown"), filter),
 | 
				
			||||||
				languages: ["extra"],
 | 
									languages: ["extra"],
 | 
				
			||||||
			})) as UnknownEntry[];
 | 
								})) as UnknownEntry[];
 | 
				
			||||||
@ -382,4 +395,44 @@ export const entriesH = new Elysia({ tags: ["series"] })
 | 
				
			|||||||
			},
 | 
								},
 | 
				
			||||||
			tags: ["videos"],
 | 
								tags: ["videos"],
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						.get(
 | 
				
			||||||
 | 
							"/news",
 | 
				
			||||||
 | 
							async ({ query: { limit, after, query, filter }, request: { url } }) => {
 | 
				
			||||||
 | 
								const sort = newsSort;
 | 
				
			||||||
 | 
								const items = (await getEntries({
 | 
				
			||||||
 | 
									limit,
 | 
				
			||||||
 | 
									after,
 | 
				
			||||||
 | 
									query,
 | 
				
			||||||
 | 
									sort,
 | 
				
			||||||
 | 
									filter: and(
 | 
				
			||||||
 | 
										isNotNull(entries.availableSince),
 | 
				
			||||||
 | 
										ne(entries.kind, "unknown"),
 | 
				
			||||||
 | 
										ne(entries.kind, "extra"),
 | 
				
			||||||
 | 
										filter,
 | 
				
			||||||
 | 
									),
 | 
				
			||||||
 | 
									languages: ["extra"],
 | 
				
			||||||
 | 
								})) as Entry[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return createPage(items, { url, sort, limit });
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								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 })),
 | 
				
			||||||
 | 
									limit: t.Integer({
 | 
				
			||||||
 | 
										minimum: 1,
 | 
				
			||||||
 | 
										maximum: 250,
 | 
				
			||||||
 | 
										default: 50,
 | 
				
			||||||
 | 
										description: "Max page size.",
 | 
				
			||||||
 | 
									}),
 | 
				
			||||||
 | 
									after: t.Optional(t.String({ description: desc.after })),
 | 
				
			||||||
 | 
								}),
 | 
				
			||||||
 | 
								response: {
 | 
				
			||||||
 | 
									200: Page(Entry),
 | 
				
			||||||
 | 
									422: KError,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								tags: ["videos"],
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
import { type Column, type SQL, eq, sql } from "drizzle-orm";
 | 
					import { type Column, type SQL, and, eq, isNull, sql } from "drizzle-orm";
 | 
				
			||||||
import { db } from "~/db";
 | 
					import { db } from "~/db";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
	entries,
 | 
						entries,
 | 
				
			||||||
@ -6,10 +6,11 @@ import {
 | 
				
			|||||||
	entryVideoJoin,
 | 
						entryVideoJoin,
 | 
				
			||||||
	videos,
 | 
						videos,
 | 
				
			||||||
} from "~/db/schema";
 | 
					} from "~/db/schema";
 | 
				
			||||||
import { conflictUpdateAllExcept, values } from "~/db/utils";
 | 
					import { conflictUpdateAllExcept, sqlarr, values } from "~/db/utils";
 | 
				
			||||||
import type { SeedEntry as SEntry, SeedExtra as SExtra } from "~/models/entry";
 | 
					import type { SeedEntry as SEntry, SeedExtra as SExtra } from "~/models/entry";
 | 
				
			||||||
import { processOptImage } from "../images";
 | 
					import { processOptImage } from "../images";
 | 
				
			||||||
import { guessNextRefresh } from "../refresh";
 | 
					import { guessNextRefresh } from "../refresh";
 | 
				
			||||||
 | 
					import { updateAvailableCount } from "./shows";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type SeedEntry = SEntry & {
 | 
					type SeedEntry = SEntry & {
 | 
				
			||||||
	video?: undefined;
 | 
						video?: undefined;
 | 
				
			||||||
@ -41,8 +42,9 @@ const generateSlug = (
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const insertEntries = async (
 | 
					export const insertEntries = async (
 | 
				
			||||||
	show: { pk: number; slug: string },
 | 
						show: { pk: number; slug: string; kind: "movie" | "serie" | "collection" },
 | 
				
			||||||
	items: (SeedEntry | SeedExtra)[],
 | 
						items: (SeedEntry | SeedExtra)[],
 | 
				
			||||||
 | 
						onlyExtras = false,
 | 
				
			||||||
) => {
 | 
					) => {
 | 
				
			||||||
	if (!items) return [];
 | 
						if (!items) return [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -135,10 +137,15 @@ export const insertEntries = async (
 | 
				
			|||||||
		}));
 | 
							}));
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (vids.length === 0)
 | 
						if (vids.length === 0) {
 | 
				
			||||||
 | 
							// we have not added videos but we need to update the `entriesCount`
 | 
				
			||||||
 | 
							if (show.kind === "serie" && !onlyExtras)
 | 
				
			||||||
 | 
								await updateAvailableCount(db, [show.pk], true);
 | 
				
			||||||
		return retEntries.map((x) => ({ id: x.id, slug: x.slug, videos: [] }));
 | 
							return retEntries.map((x) => ({ id: x.id, slug: x.slug, videos: [] }));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const retVideos = await db
 | 
						const retVideos = await db.transaction(async (tx) => {
 | 
				
			||||||
 | 
							const ret = await tx
 | 
				
			||||||
			.insert(entryVideoJoin)
 | 
								.insert(entryVideoJoin)
 | 
				
			||||||
			.select(
 | 
								.select(
 | 
				
			||||||
				db
 | 
									db
 | 
				
			||||||
@ -159,6 +166,22 @@ export const insertEntries = async (
 | 
				
			|||||||
				entryPk: entryVideoJoin.entryPk,
 | 
									entryPk: entryVideoJoin.entryPk,
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (!onlyExtras)
 | 
				
			||||||
 | 
								await updateAvailableCount(tx, [show.pk], show.kind === "serie");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const entriesPk = [...new Set(vids.map((x) => x.entryPk))];
 | 
				
			||||||
 | 
							await tx
 | 
				
			||||||
 | 
								.update(entries)
 | 
				
			||||||
 | 
								.set({ availableSince: sql`now()` })
 | 
				
			||||||
 | 
								.where(
 | 
				
			||||||
 | 
									and(
 | 
				
			||||||
 | 
										eq(entries.pk, sql`any(${sqlarr(entriesPk)})`),
 | 
				
			||||||
 | 
										isNull(entries.availableSince),
 | 
				
			||||||
 | 
									),
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
							return ret;
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return retEntries.map((entry) => ({
 | 
						return retEntries.map((entry) => ({
 | 
				
			||||||
		id: entry.id,
 | 
							id: entry.id,
 | 
				
			||||||
		slug: entry.slug,
 | 
							slug: entry.slug,
 | 
				
			||||||
 | 
				
			|||||||
@ -60,6 +60,7 @@ async function insertBaseShow(
 | 
				
			|||||||
			})
 | 
								})
 | 
				
			||||||
			.returning({
 | 
								.returning({
 | 
				
			||||||
				pk: shows.pk,
 | 
									pk: shows.pk,
 | 
				
			||||||
 | 
									kind: shows.kind,
 | 
				
			||||||
				id: shows.id,
 | 
									id: shows.id,
 | 
				
			||||||
				slug: shows.slug,
 | 
									slug: shows.slug,
 | 
				
			||||||
				// https://stackoverflow.com/questions/39058213/differentiate-inserted-and-updated-rows-in-upsert-using-system-columns/39204667#39204667
 | 
									// https://stackoverflow.com/questions/39058213/differentiate-inserted-and-updated-rows-in-upsert-using-system-columns/39204667#39204667
 | 
				
			||||||
@ -81,13 +82,14 @@ async function insertBaseShow(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// if at this point ret is still undefined, we could not reconciliate.
 | 
						// if at this point ret is still undefined, we could not reconciliate.
 | 
				
			||||||
	// simply bail and let the caller handle this.
 | 
						// simply bail and let the caller handle this.
 | 
				
			||||||
	const [{ pk, id }] = await db
 | 
						const [{ pk, id, kind }] = await db
 | 
				
			||||||
		.select({ pk: shows.pk, id: shows.id })
 | 
							.select({ pk: shows.pk, id: shows.id, kind: shows.kind })
 | 
				
			||||||
		.from(shows)
 | 
							.from(shows)
 | 
				
			||||||
		.where(eq(shows.slug, show.slug))
 | 
							.where(eq(shows.slug, show.slug))
 | 
				
			||||||
		.limit(1);
 | 
							.limit(1);
 | 
				
			||||||
	return {
 | 
						return {
 | 
				
			||||||
		status: 409 as const,
 | 
							status: 409 as const,
 | 
				
			||||||
 | 
							kind,
 | 
				
			||||||
		pk,
 | 
							pk,
 | 
				
			||||||
		id,
 | 
							id,
 | 
				
			||||||
		slug: show.slug,
 | 
							slug: show.slug,
 | 
				
			||||||
@ -95,10 +97,11 @@ async function insertBaseShow(
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function updateAvailableCount(
 | 
					export async function updateAvailableCount(
 | 
				
			||||||
 | 
						tx: typeof db | Parameters<Parameters<typeof db.transaction>[0]>[0],
 | 
				
			||||||
	showPks: number[],
 | 
						showPks: number[],
 | 
				
			||||||
	updateEntryCount = true,
 | 
						updateEntryCount = true,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
	return await db
 | 
						return await tx
 | 
				
			||||||
		.update(shows)
 | 
							.update(shows)
 | 
				
			||||||
		.set({
 | 
							.set({
 | 
				
			||||||
			availableCount: sql`${db
 | 
								availableCount: sql`${db
 | 
				
			||||||
 | 
				
			|||||||
@ -105,7 +105,6 @@ export const seedMovie = async (
 | 
				
			|||||||
			videos,
 | 
								videos,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	]);
 | 
						]);
 | 
				
			||||||
	await updateAvailableCount([show.pk], false);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const retStudios = await insertStudios(studios, show.pk);
 | 
						const retStudios = await insertStudios(studios, show.pk);
 | 
				
			||||||
	const retStaff = await insertStaff(staff, show.pk);
 | 
						const retStaff = await insertStaff(staff, show.pk);
 | 
				
			||||||
 | 
				
			|||||||
@ -131,8 +131,8 @@ export const seedSerie = async (
 | 
				
			|||||||
	const retExtras = await insertEntries(
 | 
						const retExtras = await insertEntries(
 | 
				
			||||||
		show,
 | 
							show,
 | 
				
			||||||
		(extras ?? []).map((x) => ({ ...x, kind: "extra", extraKind: x.kind })),
 | 
							(extras ?? []).map((x) => ({ ...x, kind: "extra", extraKind: x.kind })),
 | 
				
			||||||
 | 
							true,
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
	await updateAvailableCount([show.pk]);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const retStudios = await insertStudios(studios, show.pk);
 | 
						const retStudios = await insertStudios(studios, show.pk);
 | 
				
			||||||
	const retStaff = await insertStaff(staff, show.pk);
 | 
						const retStaff = await insertStaff(staff, show.pk);
 | 
				
			||||||
 | 
				
			|||||||
@ -1,11 +1,15 @@
 | 
				
			|||||||
import { and, eq, inArray, sql } from "drizzle-orm";
 | 
					import { and, eq, exists, inArray, not, sql } from "drizzle-orm";
 | 
				
			||||||
 | 
					import { alias } from "drizzle-orm/pg-core";
 | 
				
			||||||
import { Elysia, t } from "elysia";
 | 
					import { Elysia, t } from "elysia";
 | 
				
			||||||
import { db } from "~/db";
 | 
					import { db } from "~/db";
 | 
				
			||||||
import { entries, entryVideoJoin, shows, videos } from "~/db/schema";
 | 
					import { entries, entryVideoJoin, shows, videos } from "~/db/schema";
 | 
				
			||||||
 | 
					import { sqlarr } from "~/db/utils";
 | 
				
			||||||
import { bubbleVideo } from "~/models/examples";
 | 
					import { bubbleVideo } from "~/models/examples";
 | 
				
			||||||
 | 
					import { Page } from "~/models/utils";
 | 
				
			||||||
import { SeedVideo, Video } from "~/models/video";
 | 
					import { SeedVideo, Video } from "~/models/video";
 | 
				
			||||||
import { comment } from "~/utils";
 | 
					import { comment } from "~/utils";
 | 
				
			||||||
import { computeVideoSlug } from "./seed/insert/entries";
 | 
					import { computeVideoSlug } from "./seed/insert/entries";
 | 
				
			||||||
 | 
					import { updateAvailableCount } from "./seed/insert/shows";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const CreatedVideo = t.Object({
 | 
					const CreatedVideo = t.Object({
 | 
				
			||||||
	id: t.String({ format: "uuid" }),
 | 
						id: t.String({ format: "uuid" }),
 | 
				
			||||||
@ -23,9 +27,6 @@ export const videosH = new Elysia({ prefix: "/videos", tags: ["videos"] })
 | 
				
			|||||||
		"created-videos": t.Array(CreatedVideo),
 | 
							"created-videos": t.Array(CreatedVideo),
 | 
				
			||||||
		error: t.Object({}),
 | 
							error: t.Object({}),
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	.get("/:id", () => "hello" as unknown as Video, {
 | 
					 | 
				
			||||||
		response: { 200: "video" },
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
	.post(
 | 
						.post(
 | 
				
			||||||
		"",
 | 
							"",
 | 
				
			||||||
		async ({ body, error }) => {
 | 
							async ({ body, error }) => {
 | 
				
			||||||
@ -115,8 +116,6 @@ export const videosH = new Elysia({ prefix: "/videos", tags: ["videos"] })
 | 
				
			|||||||
			// return error(201, ret as any);
 | 
								// return error(201, ret as any);
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			body: t.Array(SeedVideo),
 | 
					 | 
				
			||||||
			response: { 201: t.Array(CreatedVideo) },
 | 
					 | 
				
			||||||
			detail: {
 | 
								detail: {
 | 
				
			||||||
				description: comment`
 | 
									description: comment`
 | 
				
			||||||
					Create videos in bulk.
 | 
										Create videos in bulk.
 | 
				
			||||||
@ -126,5 +125,67 @@ export const videosH = new Elysia({ prefix: "/videos", tags: ["videos"] })
 | 
				
			|||||||
					movie or entry.
 | 
										movie or entry.
 | 
				
			||||||
				`,
 | 
									`,
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
								body: t.Array(SeedVideo),
 | 
				
			||||||
 | 
								response: { 201: t.Array(CreatedVideo) },
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						.delete(
 | 
				
			||||||
 | 
							"",
 | 
				
			||||||
 | 
							async ({ body }) => {
 | 
				
			||||||
 | 
								await db.transaction(async (tx) => {
 | 
				
			||||||
 | 
									const vids = tx.$with("vids").as(
 | 
				
			||||||
 | 
										tx
 | 
				
			||||||
 | 
											.delete(videos)
 | 
				
			||||||
 | 
											.where(eq(videos.path, sql`any(${body})`))
 | 
				
			||||||
 | 
											.returning({ pk: videos.pk }),
 | 
				
			||||||
 | 
									);
 | 
				
			||||||
 | 
									const evj = alias(entryVideoJoin, "evj");
 | 
				
			||||||
 | 
									const delEntries = tx.$with("del_entries").as(
 | 
				
			||||||
 | 
										tx
 | 
				
			||||||
 | 
											.with(vids)
 | 
				
			||||||
 | 
											.select({ entry: entryVideoJoin.entryPk })
 | 
				
			||||||
 | 
											.from(entryVideoJoin)
 | 
				
			||||||
 | 
											.where(
 | 
				
			||||||
 | 
												and(
 | 
				
			||||||
 | 
													inArray(entryVideoJoin.videoPk, tx.select().from(vids)),
 | 
				
			||||||
 | 
													not(
 | 
				
			||||||
 | 
														exists(
 | 
				
			||||||
 | 
															tx
 | 
				
			||||||
 | 
																.select()
 | 
				
			||||||
 | 
																.from(evj)
 | 
				
			||||||
 | 
																.where(
 | 
				
			||||||
 | 
																	and(
 | 
				
			||||||
 | 
																		eq(evj.entryPk, entryVideoJoin.entryPk),
 | 
				
			||||||
 | 
																		not(inArray(evj.videoPk, db.select().from(vids))),
 | 
				
			||||||
 | 
																	),
 | 
				
			||||||
 | 
																),
 | 
				
			||||||
 | 
														),
 | 
				
			||||||
 | 
													),
 | 
				
			||||||
 | 
												),
 | 
				
			||||||
 | 
											),
 | 
				
			||||||
 | 
									);
 | 
				
			||||||
 | 
									const delShows = await tx
 | 
				
			||||||
 | 
										.with(delEntries)
 | 
				
			||||||
 | 
										.update(entries)
 | 
				
			||||||
 | 
										.set({ availableSince: null })
 | 
				
			||||||
 | 
										.where(inArray(entries.pk, db.select().from(delEntries)))
 | 
				
			||||||
 | 
										.returning({ show: entries.showPk });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									await updateAvailableCount(
 | 
				
			||||||
 | 
										tx,
 | 
				
			||||||
 | 
										delShows.map((x) => x.show),
 | 
				
			||||||
 | 
										false,
 | 
				
			||||||
 | 
									);
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								detail: { description: "Delete videos in bulk." },
 | 
				
			||||||
 | 
								body: t.Array(
 | 
				
			||||||
 | 
									t.String({
 | 
				
			||||||
 | 
										description: "Path of the video to delete",
 | 
				
			||||||
 | 
										examples: [bubbleVideo.path],
 | 
				
			||||||
 | 
									}),
 | 
				
			||||||
 | 
								),
 | 
				
			||||||
 | 
								response: { 204: t.Void() },
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
 | 
				
			|||||||
@ -74,6 +74,7 @@ export const entries = schema.table(
 | 
				
			|||||||
		updatedAt: timestamp({ withTimezone: true, mode: "string" })
 | 
							updatedAt: timestamp({ withTimezone: true, mode: "string" })
 | 
				
			||||||
			.notNull()
 | 
								.notNull()
 | 
				
			||||||
			.$onUpdate(() => sql`now()`),
 | 
								.$onUpdate(() => sql`now()`),
 | 
				
			||||||
 | 
							availableSince: timestamp({ withTimezone: true, mode: "string" }),
 | 
				
			||||||
		nextRefresh: timestamp({ withTimezone: true, mode: "string" }).notNull(),
 | 
							nextRefresh: timestamp({ withTimezone: true, mode: "string" }).notNull(),
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	(t) => [
 | 
						(t) => [
 | 
				
			||||||
 | 
				
			|||||||
@ -129,3 +129,28 @@ export const getUnknowns = async (opts: {
 | 
				
			|||||||
	const body = await resp.json();
 | 
						const body = await resp.json();
 | 
				
			||||||
	return [resp, body] as const;
 | 
						return [resp, body] as const;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getNews = async ({
 | 
				
			||||||
 | 
						langs,
 | 
				
			||||||
 | 
						...opts
 | 
				
			||||||
 | 
					}: {
 | 
				
			||||||
 | 
						filter?: string;
 | 
				
			||||||
 | 
						limit?: number;
 | 
				
			||||||
 | 
						after?: string;
 | 
				
			||||||
 | 
						query?: string;
 | 
				
			||||||
 | 
						langs?: string;
 | 
				
			||||||
 | 
						preferOriginal?: boolean;
 | 
				
			||||||
 | 
					}) => {
 | 
				
			||||||
 | 
						const resp = await app.handle(
 | 
				
			||||||
 | 
							new Request(buildUrl("news", opts), {
 | 
				
			||||||
 | 
								method: "GET",
 | 
				
			||||||
 | 
								headers: langs
 | 
				
			||||||
 | 
									? {
 | 
				
			||||||
 | 
											"Accept-Language": langs,
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									: {},
 | 
				
			||||||
 | 
							}),
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
						const body = await resp.json();
 | 
				
			||||||
 | 
						return [resp, body] as const;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,11 @@
 | 
				
			|||||||
import { beforeAll, describe, expect, it } from "bun:test";
 | 
					import { beforeAll, describe, expect, it } from "bun:test";
 | 
				
			||||||
import { createSerie, createVideo, getEntries, getExtras } from "tests/helpers";
 | 
					import {
 | 
				
			||||||
 | 
						createSerie,
 | 
				
			||||||
 | 
						createVideo,
 | 
				
			||||||
 | 
						getEntries,
 | 
				
			||||||
 | 
						getExtras,
 | 
				
			||||||
 | 
						getNews,
 | 
				
			||||||
 | 
					} from "tests/helpers";
 | 
				
			||||||
import { expectStatus } from "tests/utils";
 | 
					import { expectStatus } from "tests/utils";
 | 
				
			||||||
import { db } from "~/db";
 | 
					import { db } from "~/db";
 | 
				
			||||||
import { shows, videos } from "~/db/schema";
 | 
					import { shows, videos } from "~/db/schema";
 | 
				
			||||||
@ -48,6 +54,21 @@ describe("Get entries", () => {
 | 
				
			|||||||
			part: madeInAbyssVideo.part,
 | 
								part: madeInAbyssVideo.part,
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
						it("Get new videos", async () => {
 | 
				
			||||||
 | 
							const [resp, body] = await getNews({ langs: "en" });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							expectStatus(resp, body).toBe(200);
 | 
				
			||||||
 | 
							expect(body.items).toBeArrayOfSize(1);
 | 
				
			||||||
 | 
							expect(body.items[0].slug).toBe("made-in-abyss-s1e13");
 | 
				
			||||||
 | 
							expect(body.items[0].videos).toBeArrayOfSize(1);
 | 
				
			||||||
 | 
							expect(body.items[0].videos[0]).toMatchObject({
 | 
				
			||||||
 | 
								path: madeInAbyssVideo.path,
 | 
				
			||||||
 | 
								slug: madeInAbyssVideo.slug,
 | 
				
			||||||
 | 
								version: madeInAbyssVideo.version,
 | 
				
			||||||
 | 
								rendering: madeInAbyssVideo.rendering,
 | 
				
			||||||
 | 
								part: madeInAbyssVideo.part,
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe("Get extra", () => {
 | 
					describe("Get extra", () => {
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user