mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
v5 api: Random query parameter becomes sort value
This commit is contained in:
parent
0e230114a7
commit
2afccaa813
@ -20,6 +20,5 @@
|
||||
"@types/pg": "^8.11.10",
|
||||
"bun-types": "^1.1.42"
|
||||
},
|
||||
"module": "src/index.js",
|
||||
"packageManager": "yarn@1.22.21+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72"
|
||||
"module": "src/index.js"
|
||||
}
|
||||
|
@ -157,13 +157,12 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] })
|
||||
.get(
|
||||
"",
|
||||
async ({
|
||||
query: { limit, after, sort, filter, random },
|
||||
query: { limit, after, sort, filter },
|
||||
headers: { "accept-language": languages },
|
||||
request: { url },
|
||||
}) => {
|
||||
const langs = processLanguages(languages);
|
||||
const [transQ, transCol] = getTranslationQuery(langs, true);
|
||||
|
||||
// TODO: Add sql indexes on sort keys
|
||||
|
||||
const items = await db
|
||||
@ -177,10 +176,14 @@ 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(
|
||||
...(random !== undefined
|
||||
? [sql`md5(${random} || ${shows.pk} )`]
|
||||
...(sort.random !== undefined
|
||||
? [
|
||||
sort.random.desc
|
||||
? sql`md5(${sort.random.seed} || ${shows.pk}) desc`
|
||||
: sql`md5(${sort.random.seed} || ${shows.pk})`,
|
||||
]
|
||||
: []),
|
||||
...sort.map((x) =>
|
||||
...sort.sort.map((x) =>
|
||||
x.desc ? sql`${shows[x.key]} desc nulls last` : shows[x.key],
|
||||
),
|
||||
shows.pk,
|
||||
@ -193,17 +196,10 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] })
|
||||
detail: { description: "Get all movies" },
|
||||
query: t.Object({
|
||||
sort: Sort(["slug", "rating", "airDate", "createdAt", "nextRefresh"], {
|
||||
// TODO: Add random
|
||||
remap: { airDate: "startAir" },
|
||||
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,
|
||||
|
@ -24,7 +24,7 @@ export const keysetPaginate = <
|
||||
sort,
|
||||
after,
|
||||
}: {
|
||||
table: Table<"pk" | Sort<T, Remap>[number]["key"]>;
|
||||
table: Table<"pk" | Sort<T, Remap>["sort"][number]["key"]>;
|
||||
after: string | undefined;
|
||||
sort: Sort<T, Remap>;
|
||||
}) => {
|
||||
@ -39,7 +39,7 @@ export const keysetPaginate = <
|
||||
// PERF: See https://use-the-index-luke.com/sql/partial-results/fetch-next-page#sb-equivalent-logic
|
||||
let where = undefined;
|
||||
let previous = undefined;
|
||||
for (const [i, by] of [...sort, pkSort].entries()) {
|
||||
for (const [i, by] of [...sort.sort, pkSort].entries()) {
|
||||
const cmp = by.desc ? lt : gt;
|
||||
where = or(
|
||||
where,
|
||||
@ -62,7 +62,7 @@ export const keysetPaginate = <
|
||||
|
||||
export const generateAfter = (cursor: any, sort: Sort<any, any>) => {
|
||||
const ret = [
|
||||
...sort.map((by) => cursor[by.remmapedKey ?? by.key]),
|
||||
...sort.sort.map((by) => cursor[by.remmapedKey ?? by.key]),
|
||||
cursor.pk,
|
||||
];
|
||||
return Buffer.from(JSON.stringify(ret), "utf-8").toString("base64url");
|
||||
|
@ -1,13 +1,16 @@
|
||||
import { t, TSchema } from "elysia";
|
||||
import { t } from "elysia";
|
||||
|
||||
export type Sort<
|
||||
T extends string[],
|
||||
Remap extends Partial<Record<T[number], string>>,
|
||||
> = {
|
||||
key: Exclude<T[number], keyof Remap> | NonNullable<Remap[keyof Remap]>;
|
||||
remmapedKey?: keyof Remap;
|
||||
desc: boolean;
|
||||
}[];
|
||||
sort: {
|
||||
key: Exclude<T[number], keyof Remap> | NonNullable<Remap[keyof Remap]>;
|
||||
remmapedKey?: keyof Remap;
|
||||
desc: boolean;
|
||||
}[];
|
||||
random?: { desc: boolean; seed: number };
|
||||
};
|
||||
|
||||
export type NonEmptyArray<T> = [T, ...T[]];
|
||||
|
||||
@ -29,9 +32,15 @@ export const Sort = <
|
||||
t
|
||||
.Transform(
|
||||
t.Array(
|
||||
t.UnionEnum([
|
||||
...values,
|
||||
...values.map((x: T[number]) => `-${x}` as const),
|
||||
t.Union([
|
||||
t.UnionEnum([
|
||||
...values,
|
||||
...values.map((x: T[number]) => `-${x}` as const),
|
||||
]),
|
||||
t.Union([
|
||||
t.TemplateLiteral("random:${number}"),
|
||||
t.TemplateLiteral("-random:${number}"),
|
||||
]),
|
||||
]),
|
||||
{
|
||||
// TODO: support explode: true (allow sort=slug,-createdAt). needs a pr to elysia
|
||||
@ -42,12 +51,39 @@ export const Sort = <
|
||||
),
|
||||
)
|
||||
.Decode((sort): Sort<T, Remap> => {
|
||||
return sort.map((x) => {
|
||||
const sortItems: Sort<T, Remap>["sort"] = [];
|
||||
let random: Sort<T, Remap>["random"] = undefined;
|
||||
for (const x of sort) {
|
||||
const desc = x[0] === "-";
|
||||
const key = (desc ? x.substring(1) : x) as T[number];
|
||||
if (key in remap) return { key: remap[key]!, remmapedKey: key, desc };
|
||||
return { key: key as Exclude<typeof key, keyof Remap>, desc };
|
||||
});
|
||||
if (key == "random") {
|
||||
random = {
|
||||
seed: Math.floor(Math.random() * Number.MAX_SAFE_INTEGER),
|
||||
desc,
|
||||
};
|
||||
continue;
|
||||
} else if (key.startsWith("random:")) {
|
||||
const strSeed = key.replace("random:", "");
|
||||
random = {
|
||||
seed: parseInt(strSeed),
|
||||
desc,
|
||||
};
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key in remap) {
|
||||
sortItems.push({ key: remap[key]!, remmapedKey: key, desc });
|
||||
} else {
|
||||
sortItems.push({
|
||||
key: key as Exclude<typeof key, keyof Remap>,
|
||||
desc,
|
||||
});
|
||||
}
|
||||
}
|
||||
return {
|
||||
sort: sortItems,
|
||||
random,
|
||||
};
|
||||
})
|
||||
.Encode(() => {
|
||||
throw new Error("Encode not supported for sort");
|
||||
|
@ -126,7 +126,7 @@ describe("Get all movies", () => {
|
||||
it("No limit, compare order with same seeds", async () => {
|
||||
// First query
|
||||
let [resp1, body1] = await getMovies({
|
||||
random: 100,
|
||||
sort: "random:100",
|
||||
});
|
||||
expectStatus(resp1, body1).toBe(200);
|
||||
const items1: Movie[] = body1.items;
|
||||
@ -134,7 +134,7 @@ describe("Get all movies", () => {
|
||||
|
||||
// Second query
|
||||
let [resp2, body2] = await getMovies({
|
||||
random: 100,
|
||||
sort: "random:100",
|
||||
});
|
||||
expectStatus(resp2, body2).toBe(200);
|
||||
const items2: Movie[] = body2.items;
|
||||
@ -145,7 +145,7 @@ describe("Get all movies", () => {
|
||||
it("No limit, compare order with different seeds", async () => {
|
||||
// First query
|
||||
let [resp1, body1] = await getMovies({
|
||||
random: 100,
|
||||
sort: "random:100",
|
||||
});
|
||||
expectStatus(resp1, body1).toBe(200);
|
||||
const items1: Movie[] = body1.items;
|
||||
@ -153,14 +153,42 @@ describe("Get all movies", () => {
|
||||
|
||||
// Second query
|
||||
let [resp2, body2] = await getMovies({
|
||||
random: 1,
|
||||
sort: "random:5",
|
||||
});
|
||||
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);
|
||||
});
|
||||
|
||||
it("Limit 1, pages 1 and 2 ", async () => {
|
||||
// First query fetches all
|
||||
// use the result to know what is expected
|
||||
let [resp, body] = await getMovies({
|
||||
sort: "random:1234",
|
||||
});
|
||||
expectStatus(resp, body).toBe(200);
|
||||
let items: Movie[] = body.items;
|
||||
const expectedIds = items.map(({ id }) => id);
|
||||
|
||||
// Get First Page
|
||||
[resp, body] = await getMovies({
|
||||
sort: "random:1234",
|
||||
limit: 1,
|
||||
});
|
||||
expectStatus(resp, body).toBe(200);
|
||||
items = body.items;
|
||||
expect(items.length).toBe(1);
|
||||
expect(items[0].id).toBe(expectedIds[0]);
|
||||
// Get Second Page
|
||||
resp = await movieApp.handle(new Request(body.next));
|
||||
body = await resp.json();
|
||||
|
||||
expectStatus(resp, body).toBe(200);
|
||||
items = body.items;
|
||||
expect(items.length).toBe(1);
|
||||
expect(items[0].id).toBe(expectedIds[1]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -30,7 +30,6 @@ export const getMovies = async ({
|
||||
limit?: number;
|
||||
after?: string;
|
||||
sort?: string | string[];
|
||||
random?: number;
|
||||
langs?: string;
|
||||
}) => {
|
||||
const resp = await movieApp.handle(
|
||||
|
Loading…
x
Reference in New Issue
Block a user