diff --git a/api/package.json b/api/package.json index feffaf22..465af2a8 100644 --- a/api/package.json +++ b/api/package.json @@ -20,5 +20,6 @@ "@types/pg": "^8.11.10", "bun-types": "^1.1.42" }, - "module": "src/index.js" + "module": "src/index.js", + "packageManager": "yarn@1.22.21+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72" } diff --git a/api/src/controllers/movies.ts b/api/src/controllers/movies.ts index 21683381..30c5494b 100644 --- a/api/src/controllers/movies.ts +++ b/api/src/controllers/movies.ts @@ -157,7 +157,7 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] }) .get( "", async ({ - query: { limit, after, sort, filter }, + query: { limit, after, sort, filter, random }, headers: { "accept-language": languages }, request: { url }, }) => { @@ -177,7 +177,12 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] }) .innerJoin(transQ, eq(shows.pk, transQ.pk)) .where(and(filter, keysetPaginate({ table: shows, after, sort }))) .orderBy( - ...sort.map((x) => (x.desc ? sql`${shows[x.key]} desc nulls last` : shows[x.key])), + ...(random !== undefined + ? [sql`md5(${random} || ${shows.pk} )`] + : []), + ...sort.map((x) => + x.desc ? sql`${shows[x.key]} desc nulls last` : shows[x.key], + ), shows.pk, ) .limit(limit); @@ -193,6 +198,12 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] }) default: ["slug"], description: "How to sort the query", }), + random: t.Optional( + t.Integer({ + minimum: 0, + description: "Seed to shuffle items", + }), + ), filter: t.Optional(Filter({ def: movieFilters })), limit: t.Integer({ minimum: 1, diff --git a/api/src/models/utils/sort.ts b/api/src/models/utils/sort.ts index 650ffb79..e452688a 100644 --- a/api/src/models/utils/sort.ts +++ b/api/src/models/utils/sort.ts @@ -1,4 +1,4 @@ -import { t } from "elysia"; +import { t, TSchema } from "elysia"; export type Sort< T extends string[], diff --git a/api/tests/movies/get-all-movies.test.ts b/api/tests/movies/get-all-movies.test.ts index 09adc457..76da7e14 100644 --- a/api/tests/movies/get-all-movies.test.ts +++ b/api/tests/movies/get-all-movies.test.ts @@ -7,6 +7,7 @@ import { bubble } from "~/models/examples"; import { dune1984 } from "~/models/examples/dune-1984"; import { dune } from "~/models/examples/dune-2021"; import { getMovies, movieApp } from "./movies-helper"; +import { Movie } from "~/models/movie"; beforeAll(async () => { await db.delete(shows); @@ -120,4 +121,46 @@ describe("Get all movies", () => { next: null, }); }); + + describe("Random sort", () => { + it("No limit, compare order with same seeds", async () => { + // First query + let [resp1, body1] = await getMovies({ + random: 100, + }); + expectStatus(resp1, body1).toBe(200); + const items1: Movie[] = body1.items; + const items1Ids = items1.map(({ id }) => id); + + // Second query + let [resp2, body2] = await getMovies({ + random: 100, + }); + expectStatus(resp2, body2).toBe(200); + const items2: Movie[] = body2.items; + const items2Ids = items2.map(({ id }) => id); + + expect(items1Ids).toEqual(items2Ids); + }); + it("No limit, compare order with different seeds", async () => { + // First query + let [resp1, body1] = await getMovies({ + random: 100, + }); + expectStatus(resp1, body1).toBe(200); + const items1: Movie[] = body1.items; + const items1Ids = items1.map(({ id }) => id); + + // Second query + let [resp2, body2] = await getMovies({ + random: 1, + }); + expectStatus(resp2, body2).toBe(200); + const items2: Movie[] = body2.items; + const items2Ids = items2.map(({ id }) => id); + + console.log(items1Ids, items2Ids); + expect(items1Ids).not.toEqual(items2Ids); + }); + }); }); diff --git a/api/tests/movies/movies-helper.ts b/api/tests/movies/movies-helper.ts index 9d160b15..825da29e 100644 --- a/api/tests/movies/movies-helper.ts +++ b/api/tests/movies/movies-helper.ts @@ -30,6 +30,7 @@ export const getMovies = async ({ limit?: number; after?: string; sort?: string | string[]; + random?: number; langs?: string; }) => { const resp = await movieApp.handle(