mirror of
				https://github.com/zoriya/Kyoo.git
				synced 2025-11-03 19:17:16 -05:00 
			
		
		
		
	Add get /movies & sort api
This commit is contained in:
		
							parent
							
								
									a4853cb186
								
							
						
					
					
						commit
						587dc4f970
					
				@ -1,13 +1,14 @@
 | 
				
			|||||||
import { and, eq, sql } from "drizzle-orm";
 | 
					import { and, desc, eq, sql } from "drizzle-orm";
 | 
				
			||||||
import { Elysia, t } from "elysia";
 | 
					import { Elysia, t } from "elysia";
 | 
				
			||||||
import { KError } from "~/models/error";
 | 
					import { KError } from "~/models/error";
 | 
				
			||||||
import { isUuid, processLanguages } from "~/models/utils";
 | 
					import { isUuid, processLanguages } from "~/models/utils";
 | 
				
			||||||
import { comment } from "~/utils";
 | 
					import { comment, RemovePrefix } from "~/utils";
 | 
				
			||||||
import { db } from "../db";
 | 
					import { db } from "../db";
 | 
				
			||||||
import { shows, showTranslations } from "../db/schema/shows";
 | 
					import { shows, showTranslations } from "../db/schema/shows";
 | 
				
			||||||
import { getColumns } from "../db/schema/utils";
 | 
					import { getColumns } from "../db/schema/utils";
 | 
				
			||||||
import { bubble } from "../models/examples";
 | 
					import { bubble } from "../models/examples";
 | 
				
			||||||
import { Movie, type MovieStatus, MovieTranslation } from "../models/movie";
 | 
					import { Movie, type MovieStatus, MovieTranslation } from "../models/movie";
 | 
				
			||||||
 | 
					import { Page } from "~/models/utils/page";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// drizzle is bugged and doesn't allow js arrays to be used in raw sql.
 | 
					// drizzle is bugged and doesn't allow js arrays to be used in raw sql.
 | 
				
			||||||
export function sqlarr(array: unknown[]) {
 | 
					export function sqlarr(array: unknown[]) {
 | 
				
			||||||
@ -130,4 +131,102 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] })
 | 
				
			|||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						.get(
 | 
				
			||||||
 | 
							"",
 | 
				
			||||||
 | 
							async ({
 | 
				
			||||||
 | 
								query: { limit, after, sort },
 | 
				
			||||||
 | 
								headers: { "accept-language": languages },
 | 
				
			||||||
 | 
							}) => {
 | 
				
			||||||
 | 
								const langs = processLanguages(languages);
 | 
				
			||||||
 | 
								const [transQ, transCol] = getTranslationQuery(langs);
 | 
				
			||||||
 | 
								const order = sort.map((x) => {
 | 
				
			||||||
 | 
									const desc = x[0] === "-";
 | 
				
			||||||
 | 
									const key = (desc ? x.substring(1) : x) as RemovePrefix<typeof x, "-">;
 | 
				
			||||||
 | 
									if (key === "airDate") return { key: "startAir" as const, desc };
 | 
				
			||||||
 | 
									return { key, desc };
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const items = await db
 | 
				
			||||||
 | 
									.select({
 | 
				
			||||||
 | 
										...moviesCol,
 | 
				
			||||||
 | 
										...transCol,
 | 
				
			||||||
 | 
										status: sql<MovieStatus>`${moviesCol.status}`,
 | 
				
			||||||
 | 
										airDate: startAir,
 | 
				
			||||||
 | 
									})
 | 
				
			||||||
 | 
									.from(shows)
 | 
				
			||||||
 | 
									.innerJoin(transQ, eq(shows.pk, transQ.pk))
 | 
				
			||||||
 | 
									.orderBy(
 | 
				
			||||||
 | 
										...order.map((x) => (x.desc ? desc(shows[x.key]) : shows[x.key])),
 | 
				
			||||||
 | 
										shows.pk,
 | 
				
			||||||
 | 
									)
 | 
				
			||||||
 | 
									.limit(limit);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return { items, next: "", prev: "", this: "" };
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								detail: { description: "Get all movies" },
 | 
				
			||||||
 | 
								query: t.Object({
 | 
				
			||||||
 | 
									sort: t.Array(
 | 
				
			||||||
 | 
										t.UnionEnum([
 | 
				
			||||||
 | 
											"slug",
 | 
				
			||||||
 | 
											"-slug",
 | 
				
			||||||
 | 
											"rating",
 | 
				
			||||||
 | 
											"-rating",
 | 
				
			||||||
 | 
											"airDate",
 | 
				
			||||||
 | 
											"-airDate",
 | 
				
			||||||
 | 
											"createdAt",
 | 
				
			||||||
 | 
											"-createdAt",
 | 
				
			||||||
 | 
											"nextRefresh",
 | 
				
			||||||
 | 
											"-nextRefresh",
 | 
				
			||||||
 | 
										]),
 | 
				
			||||||
 | 
										// TODO: support explode: true (allow sort=slug,-createdAt). needs a pr to elysia
 | 
				
			||||||
 | 
										{ explode: false, default: ["slug"] },
 | 
				
			||||||
 | 
									),
 | 
				
			||||||
 | 
									limit: t.Integer({
 | 
				
			||||||
 | 
										minimum: 1,
 | 
				
			||||||
 | 
										maximum: 250,
 | 
				
			||||||
 | 
										default: 50,
 | 
				
			||||||
 | 
										description: "Max page size.",
 | 
				
			||||||
 | 
									}),
 | 
				
			||||||
 | 
									after: t.Optional(
 | 
				
			||||||
 | 
										t.String({
 | 
				
			||||||
 | 
											format: "uuid",
 | 
				
			||||||
 | 
											description: comment`
 | 
				
			||||||
 | 
												Id of the cursor in the pagination.
 | 
				
			||||||
 | 
												You can ignore this and only use the prev/next field in the response.
 | 
				
			||||||
 | 
											`,
 | 
				
			||||||
 | 
										}),
 | 
				
			||||||
 | 
									),
 | 
				
			||||||
 | 
								}),
 | 
				
			||||||
 | 
								headers: t.Object({
 | 
				
			||||||
 | 
									"accept-language": t.String({
 | 
				
			||||||
 | 
										default: "*",
 | 
				
			||||||
 | 
										example: "en-us, ja;q=0.5",
 | 
				
			||||||
 | 
										description: comment`
 | 
				
			||||||
 | 
											List of languages you want the data in.
 | 
				
			||||||
 | 
											This follows the Accept-Language offical specification
 | 
				
			||||||
 | 
											(https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											In this request, * is always implied (if no language could satisfy the request, kyoo will use any language available).
 | 
				
			||||||
 | 
										`,
 | 
				
			||||||
 | 
									}),
 | 
				
			||||||
 | 
								}),
 | 
				
			||||||
 | 
								// response: {
 | 
				
			||||||
 | 
								// 	200: Page(Movie, {
 | 
				
			||||||
 | 
								// 		description: "Paginated list of movies that match filters.",
 | 
				
			||||||
 | 
								// 	}),
 | 
				
			||||||
 | 
								// 	422: {
 | 
				
			||||||
 | 
								// 		...KError,
 | 
				
			||||||
 | 
								// 		description: "Invalid query parameters.",
 | 
				
			||||||
 | 
								// 		examples: [
 | 
				
			||||||
 | 
								// 			{
 | 
				
			||||||
 | 
								// 				status: 422,
 | 
				
			||||||
 | 
								// 				message: "Accept-Language header could not be satisfied.",
 | 
				
			||||||
 | 
								// 				details: undefined,
 | 
				
			||||||
 | 
								// 			},
 | 
				
			||||||
 | 
								// 		],
 | 
				
			||||||
 | 
								// 	},
 | 
				
			||||||
 | 
								// },
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										13
									
								
								api/src/models/utils/page.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								api/src/models/utils/page.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					import type { ObjectOptions } from "@sinclair/typebox";
 | 
				
			||||||
 | 
					import { t, type TSchema } from "elysia";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const Page = <T extends TSchema>(schema: T, options?: ObjectOptions) =>
 | 
				
			||||||
 | 
						t.Object(
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								items: t.Array(schema),
 | 
				
			||||||
 | 
								this: t.String({ format: "uri" }),
 | 
				
			||||||
 | 
								prev: t.String({ format: "uri" }),
 | 
				
			||||||
 | 
								next: t.String({ format: "uri" }),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							options,
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
@ -1,3 +1,8 @@
 | 
				
			|||||||
// remove indent in multi-line comments
 | 
					// remove indent in multi-line comments
 | 
				
			||||||
export const comment = (str: TemplateStringsArray, ...values: any[]) =>
 | 
					export const comment = (str: TemplateStringsArray, ...values: any[]) =>
 | 
				
			||||||
	str.reduce((acc, str, i) => `${acc}${str}${values[i]}`).replace(/^\s+/gm, "");
 | 
						str.reduce((acc, str, i) => `${acc}${str}${values[i]}`).replace(/^\s+/gm, "");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type RemovePrefix<
 | 
				
			||||||
 | 
						T extends string,
 | 
				
			||||||
 | 
						Prefix extends string,
 | 
				
			||||||
 | 
					> = T extends `${Prefix}${infer Ret}` ? Ret : T;
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user