diff --git a/packages/backend/db/latestSnapshots.ts b/packages/backend/db/latestSnapshots.ts new file mode 100644 index 0000000..1d9d79b --- /dev/null +++ b/packages/backend/db/latestSnapshots.ts @@ -0,0 +1,13 @@ +import { sql } from "@core/db/dbNew"; +import type { LatestSnapshotType } from "@core/db/schema.d.ts"; + +export async function getVideosInViewsRange(minViews: number, maxViews: number) { + return sql` + SELECT * + FROM latest_video_snapshot + WHERE views >= ${minViews} + AND views <= ${maxViews} + ORDER BY views DESC + LIMIT 5000 + `; +} diff --git a/packages/backend/middleware/index.ts b/packages/backend/middleware/index.ts index 0cd2a9d..f476d7f 100644 --- a/packages/backend/middleware/index.ts +++ b/packages/backend/middleware/index.ts @@ -1,4 +1,4 @@ -import { Context, Hono } from "hono"; +import { Hono } from "hono"; import { Variables } from "hono/types"; import { bodyLimitForPing } from "./bodyLimits.ts"; import { pingHandler } from "routes/ping"; diff --git a/packages/backend/package.json b/packages/backend/package.json index a368376..581cdf4 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,7 +1,7 @@ { "name": "@cvsa/backend", "private": false, - "version": "0.5.3", + "version": "0.6.0", "scripts": { "format": "prettier --write .", "dev": "NODE_ENV=development bun run --hot src/main.ts", diff --git a/packages/backend/routes/videos/GET.ts b/packages/backend/routes/videos/GET.ts new file mode 100644 index 0000000..65ba5e8 --- /dev/null +++ b/packages/backend/routes/videos/GET.ts @@ -0,0 +1,65 @@ +import type { Context } from "hono"; +import { createHandlers } from "src/utils.ts"; +import type { BlankEnv, BlankInput } from "hono/types"; +import { number, object, ValidationError } from "yup"; +import { ErrorResponse } from "src/schema"; +import { startTime, endTime } from "hono/timing"; +import { getVideosInViewsRange } from "@/db/latestSnapshots"; + +const SnapshotQueryParamsSchema = object({ + min_views: number().integer().optional().positive(), + max_views: number().integer().optional().positive() +}); + +type ContextType = Context; + +export const getVideosHanlder = createHandlers(async (c: ContextType) => { + startTime(c, "parse", "Parse the request"); + try { + const queryParams = await SnapshotQueryParamsSchema.validate(c.req.query()); + const { min_views, max_views } = queryParams; + + if (!min_views && !max_views) { + const response: ErrorResponse = { + code: "INVALID_QUERY_PARAMS", + message: "Invalid query parameters", + errors: ["Must provide one of these query parameters: min_views, max_views"] + }; + return c.json>(response, 400); + } + + endTime(c, "parse"); + + startTime(c, "db", "Query the database"); + + const minViews = min_views ? min_views : 0; + const maxViews = max_views ? max_views : 2147483647; + + const result = await getVideosInViewsRange(minViews, maxViews); + + endTime(c, "db"); + + const rows = result.map((row) => ({ + ...row, + aid: Number(row.aid) + })); + + return c.json(rows); + } catch (e: unknown) { + if (e instanceof ValidationError) { + const response: ErrorResponse = { + code: "INVALID_QUERY_PARAMS", + message: "Invalid query parameters", + errors: e.errors + }; + return c.json>(response, 400); + } else { + const response: ErrorResponse = { + code: "UNKNOWN_ERROR", + message: "Unhandled error", + errors: [e] + }; + return c.json>(response, 500); + } + } +}); diff --git a/packages/backend/routes/videos/index.ts b/packages/backend/routes/videos/index.ts new file mode 100644 index 0000000..e4e6226 --- /dev/null +++ b/packages/backend/routes/videos/index.ts @@ -0,0 +1 @@ +export * from "./GET.ts"; diff --git a/packages/backend/src/main.ts b/packages/backend/src/main.ts index 08dbe4b..dc71d03 100644 --- a/packages/backend/src/main.ts +++ b/packages/backend/src/main.ts @@ -15,4 +15,4 @@ configureRoutes(app); await startServer(app); -export const VERSION = "0.5.3"; +export const VERSION = "0.6.0"; diff --git a/packages/backend/src/routing.ts b/packages/backend/src/routing.ts index ee3886b..96d8cc1 100644 --- a/packages/backend/src/routing.ts +++ b/packages/backend/src/routing.ts @@ -6,11 +6,14 @@ import { Hono } from "hono"; import { Variables } from "hono/types"; import { createCaptchaSessionHandler, verifyChallengeHandler } from "routes/captcha"; import { getCaptchaDifficultyHandler } from "routes/captcha/difficulty/GET.ts"; +import { getVideosHanlder } from "@/routes/videos"; export function configureRoutes(app: Hono<{ Variables: Variables }>) { app.get("/", ...rootHandler); app.all("/ping", ...pingHandler); + app.get("/videos", ...getVideosHanlder); + app.get("/video/:id/snapshots", ...getSnapshotsHanlder); app.get("/video/:id/info", ...videoInfoHandler);