mirror of
				https://github.com/zoriya/Kyoo.git
				synced 2025-11-03 19:17:16 -05:00 
			
		
		
		
	Make movie get work
This commit is contained in:
		
							parent
							
								
									9e1afca9ec
								
							
						
					
					
						commit
						eea0f688a0
					
				@ -11,6 +11,7 @@ export const base = new Elysia({ name: "base" })
 | 
				
			|||||||
			} as KError;
 | 
								} as KError;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if (code === "INTERNAL_SERVER_ERROR") {
 | 
							if (code === "INTERNAL_SERVER_ERROR") {
 | 
				
			||||||
 | 
								console.error(error);
 | 
				
			||||||
			return {
 | 
								return {
 | 
				
			||||||
				status: 500,
 | 
									status: 500,
 | 
				
			||||||
				message: error.message,
 | 
									message: error.message,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,57 +1,45 @@
 | 
				
			|||||||
 | 
					import { and, eq, sql } from "drizzle-orm";
 | 
				
			||||||
import { Elysia, t } from "elysia";
 | 
					import { Elysia, t } from "elysia";
 | 
				
			||||||
import { Movie, MovieTranslation } from "../models/movie";
 | 
					import { KError } from "~/models/error";
 | 
				
			||||||
 | 
					import { isUuid, processLanguages } from "~/models/utils";
 | 
				
			||||||
 | 
					import { comment } 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 { eq, and, sql, or } from "drizzle-orm";
 | 
					 | 
				
			||||||
import { getColumns } from "../db/schema/utils";
 | 
					import { getColumns } from "../db/schema/utils";
 | 
				
			||||||
import { bubble } from "../models/examples";
 | 
					import { bubble } from "../models/examples";
 | 
				
			||||||
import { comment } from "~/utils";
 | 
					import { Movie, MovieTranslation } from "../models/movie";
 | 
				
			||||||
import { processLanguages } from "~/models/utils";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const translations = db
 | 
					// drizzle is bugged and doesn't allow js arrays to be used in raw sql.
 | 
				
			||||||
	.selectDistinctOn([showTranslations.pk])
 | 
					export function sqlarr(array: unknown[]) {
 | 
				
			||||||
	.from(showTranslations)
 | 
						return `{${array.map((item) => `"${item}"`).join(",")}}`;
 | 
				
			||||||
	// .where(
 | 
					}
 | 
				
			||||||
		// or(
 | 
					
 | 
				
			||||||
			// eq(showTranslations.language, sql`any(${sql.placeholder("langs")})`),
 | 
					const getTranslationQuery = (languages: string[]) => {
 | 
				
			||||||
			// eq(showTranslations.language, shows.originalLanguage),
 | 
						const fallback = languages.includes("*");
 | 
				
			||||||
		// ),
 | 
						const query = db
 | 
				
			||||||
	// )
 | 
							.selectDistinctOn([showTranslations.pk])
 | 
				
			||||||
	.orderBy(
 | 
							.from(showTranslations)
 | 
				
			||||||
		showTranslations.pk,
 | 
							.where(
 | 
				
			||||||
		sql`array_position(${sql.placeholder("langs")}, ${showTranslations.language})`,
 | 
								fallback
 | 
				
			||||||
	)
 | 
									? undefined
 | 
				
			||||||
	.as("t");
 | 
									: eq(showTranslations.language, sql`any(${sqlarr(languages)})`),
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
							.orderBy(
 | 
				
			||||||
 | 
								showTranslations.pk,
 | 
				
			||||||
 | 
								sql`array_position(${sqlarr(languages)}, ${showTranslations.language})`,
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
							.as("t");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const { pk, ...col } = getColumns(query);
 | 
				
			||||||
 | 
						return [query, col] as const;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { pk: _, kind, startAir, endAir, ...moviesCol } = getColumns(shows);
 | 
					const { pk: _, kind, startAir, endAir, ...moviesCol } = getColumns(shows);
 | 
				
			||||||
const { pk, language, ...translationsCol } = getColumns(translations);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const findMovie = db
 | 
					 | 
				
			||||||
	.select({
 | 
					 | 
				
			||||||
		...moviesCol,
 | 
					 | 
				
			||||||
		...translationsCol,
 | 
					 | 
				
			||||||
		airDate: startAir,
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
	.from(shows)
 | 
					 | 
				
			||||||
	.innerJoin(translations, eq(shows.pk, translations.pk))
 | 
					 | 
				
			||||||
	.where(
 | 
					 | 
				
			||||||
		and(
 | 
					 | 
				
			||||||
			eq(shows.kind, "movie"),
 | 
					 | 
				
			||||||
			// or(
 | 
					 | 
				
			||||||
			// 	eq(shows.id, sql.placeholder("id")),
 | 
					 | 
				
			||||||
				eq(shows.slug, sql.placeholder("id")),
 | 
					 | 
				
			||||||
			// ),
 | 
					 | 
				
			||||||
		),
 | 
					 | 
				
			||||||
	)
 | 
					 | 
				
			||||||
	// .orderBy()
 | 
					 | 
				
			||||||
	.limit(1)
 | 
					 | 
				
			||||||
	.prepare("findMovie");
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] })
 | 
					export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] })
 | 
				
			||||||
	.model({
 | 
						.model({
 | 
				
			||||||
		movie: Movie,
 | 
							movie: Movie,
 | 
				
			||||||
		"movie-translation": MovieTranslation,
 | 
							"movie-translation": MovieTranslation,
 | 
				
			||||||
		error: t.Object({}),
 | 
					 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	.guard({
 | 
						.guard({
 | 
				
			||||||
		params: t.Object({
 | 
							params: t.Object({
 | 
				
			||||||
@ -61,7 +49,7 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] })
 | 
				
			|||||||
			}),
 | 
								}),
 | 
				
			||||||
		}),
 | 
							}),
 | 
				
			||||||
		headers: t.Object({
 | 
							headers: t.Object({
 | 
				
			||||||
			"Accept-Language": t.String({
 | 
								"accept-language": t.String({
 | 
				
			||||||
				default: "*",
 | 
									default: "*",
 | 
				
			||||||
				examples: "en-us, ja;q=0.5",
 | 
									examples: "en-us, ja;q=0.5",
 | 
				
			||||||
				description: comment`
 | 
									description: comment`
 | 
				
			||||||
@ -71,26 +59,66 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] })
 | 
				
			|||||||
				`,
 | 
									`,
 | 
				
			||||||
			}),
 | 
								}),
 | 
				
			||||||
		}),
 | 
							}),
 | 
				
			||||||
		response: { 200: "movie", 404: "error" },
 | 
							response: {
 | 
				
			||||||
 | 
								200: "movie",
 | 
				
			||||||
 | 
								404: {
 | 
				
			||||||
 | 
									...KError,
 | 
				
			||||||
 | 
									description: "No movie found with the given id or slug.",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								422: {
 | 
				
			||||||
 | 
									...KError,
 | 
				
			||||||
 | 
									description: comment`
 | 
				
			||||||
 | 
										The Accept-Language header can't be satisfied (all languages listed are
 | 
				
			||||||
 | 
										unavailable). Try with another languages or add * to the list of languages
 | 
				
			||||||
 | 
										to fallback to any language.
 | 
				
			||||||
 | 
									`,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	.get(
 | 
						.get(
 | 
				
			||||||
		"/:id",
 | 
							"/:id",
 | 
				
			||||||
		async ({
 | 
							async ({
 | 
				
			||||||
			params: { id },
 | 
								params: { id },
 | 
				
			||||||
			headers: { "Accept-Language": languages },
 | 
								headers: { "accept-language": languages },
 | 
				
			||||||
			error,
 | 
								error,
 | 
				
			||||||
 | 
								set,
 | 
				
			||||||
		}) => {
 | 
							}) => {
 | 
				
			||||||
			const langs = processLanguages(languages);
 | 
								const langs = processLanguages(languages);
 | 
				
			||||||
		console.log(langs);
 | 
								const [transQ, transCol] = getTranslationQuery(langs);
 | 
				
			||||||
			console.log(findMovie.getQuery());
 | 
					
 | 
				
			||||||
			const ret = await findMovie.execute({ id, langs });
 | 
								const idFilter = isUuid(id) ? eq(shows.id, id) : eq(shows.slug, id);
 | 
				
			||||||
			console.log(ret);
 | 
					
 | 
				
			||||||
			if (ret.length !== 1) return error(404, {});
 | 
								const [ret] = await db
 | 
				
			||||||
			return ret[0];
 | 
									.select({
 | 
				
			||||||
 | 
										...moviesCol,
 | 
				
			||||||
 | 
										...transCol,
 | 
				
			||||||
 | 
										airDate: startAir,
 | 
				
			||||||
 | 
									})
 | 
				
			||||||
 | 
									.from(shows)
 | 
				
			||||||
 | 
									.leftJoin(transQ, eq(shows.pk, transQ.pk))
 | 
				
			||||||
 | 
									.where(and(eq(shows.kind, "movie"), idFilter))
 | 
				
			||||||
 | 
									.limit(1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (!ret) {
 | 
				
			||||||
 | 
									return error(404, {
 | 
				
			||||||
 | 
										status: 404,
 | 
				
			||||||
 | 
										message: "Movie not found",
 | 
				
			||||||
 | 
										details: undefined,
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if (!ret.language) {
 | 
				
			||||||
 | 
									return error(422, {
 | 
				
			||||||
 | 
										status: 422,
 | 
				
			||||||
 | 
										message: "Accept-Language header could not be satisfied.",
 | 
				
			||||||
 | 
										details: undefined,
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								set.headers["content-language"] = ret.language;
 | 
				
			||||||
 | 
								return ret;
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			detail: {
 | 
								detail: {
 | 
				
			||||||
				description: "Get a movie by id or slug",
 | 
									description: "Get a movie by id or slug",
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	)
 | 
						);
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,5 @@
 | 
				
			|||||||
import { FormatRegistry } from "@sinclair/typebox";
 | 
					import { FormatRegistry } from "@sinclair/typebox";
 | 
				
			||||||
 | 
					import { TypeCompiler } from "@sinclair/typebox/compiler";
 | 
				
			||||||
import { t } from "elysia";
 | 
					import { t } from "elysia";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const slugPattern = "^[a-z0-9-]+$";
 | 
					export const slugPattern = "^[a-z0-9-]+$";
 | 
				
			||||||
@ -11,3 +12,6 @@ export const Resource = t.Object({
 | 
				
			|||||||
	id: t.String({ format: "uuid" }),
 | 
						id: t.String({ format: "uuid" }),
 | 
				
			||||||
	slug: t.String({ format: "slug" }),
 | 
						slug: t.String({ format: "slug" }),
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const checker = TypeCompiler.Compile(t.String({ format: "uuid" }));
 | 
				
			||||||
 | 
					export const isUuid = (id: string) => checker.Check(id);
 | 
				
			||||||
 | 
				
			|||||||
@ -22,6 +22,8 @@ const getMovie = async (id: string, langs: string) => {
 | 
				
			|||||||
	return [resp, body] as const;
 | 
						return [resp, body] as const;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let bubbleId = "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function expectStatus(resp: Response, body: object) {
 | 
					function expectStatus(resp: Response, body: object) {
 | 
				
			||||||
	const matcher = expect({ ...body, status: resp.status });
 | 
						const matcher = expect({ ...body, status: resp.status });
 | 
				
			||||||
	return {
 | 
						return {
 | 
				
			||||||
@ -41,12 +43,50 @@ describe("Get movie", () => {
 | 
				
			|||||||
			name: bubble.translations.en.name,
 | 
								name: bubble.translations.en.name,
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
						it("Retrive by id", async () => {
 | 
				
			||||||
 | 
							const [resp, body] = await getMovie(bubbleId, "en");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							expectStatus(resp, body).toBe(200);
 | 
				
			||||||
 | 
							expect(body).toMatchObject({
 | 
				
			||||||
 | 
								id: bubbleId,
 | 
				
			||||||
 | 
								slug: bubble.slug,
 | 
				
			||||||
 | 
								name: bubble.translations.en.name,
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
						it("Get non available translation", async () => {
 | 
				
			||||||
 | 
							const [resp, body] = await getMovie(bubble.slug, "fr");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							expectStatus(resp, body).toBe(422);
 | 
				
			||||||
 | 
							expect(body).toMatchObject({
 | 
				
			||||||
 | 
								status: 422,
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
						it("Get first available language", async () => {
 | 
				
			||||||
 | 
							const [resp, body] = await getMovie(bubble.slug, "fr,en");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							expectStatus(resp, body).toBe(200);
 | 
				
			||||||
 | 
							expect(body).toMatchObject({
 | 
				
			||||||
 | 
								slug: bubble.slug,
 | 
				
			||||||
 | 
								name: bubble.translations.en.name,
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
							expect(resp.headers.get("Content-Language")).toBe("en");
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
						it("Use language fallback", async () => {
 | 
				
			||||||
 | 
							const [resp, body] = await getMovie(bubble.slug, "fr,ja,*");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							expectStatus(resp, body).toBe(200);
 | 
				
			||||||
 | 
							expect(body).toMatchObject({
 | 
				
			||||||
 | 
								slug: bubble.slug,
 | 
				
			||||||
 | 
								name: bubble.translations.en.name,
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
							expect(resp.headers.get("Content-Language")).toBe("en");
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
beforeAll(async () => {
 | 
					beforeAll(async () => {
 | 
				
			||||||
	const ret = await seedMovie(bubble);
 | 
						const ret = await seedMovie(bubble);
 | 
				
			||||||
	console.log("seed bubble", ret);
 | 
						bubbleId = ret.id;
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
afterAll(async () => {
 | 
					afterAll(async () => {
 | 
				
			||||||
	// await db.delete(shows).where(eq(shows.slug, bubble.slug));
 | 
						await db.delete(shows).where(eq(shows.slug, bubble.slug));
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user