Compare commits

...

4 Commits

12 changed files with 99 additions and 11 deletions

View File

@ -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<LatestSnapshotType[]>`
SELECT *
FROM latest_video_snapshot
WHERE views >= ${minViews}
AND views <= ${maxViews}
ORDER BY views DESC
LIMIT 5000
`;
}

View File

@ -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",

View File

@ -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<BlankEnv, "/videos", BlankInput>;
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<string> = {
code: "INVALID_QUERY_PARAMS",
message: "Invalid query parameters",
errors: ["Must provide one of these query parameters: min_views, max_views"]
};
return c.json<ErrorResponse<string>>(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<string> = {
code: "INVALID_QUERY_PARAMS",
message: "Invalid query parameters",
errors: e.errors
};
return c.json<ErrorResponse<string>>(response, 400);
} else {
const response: ErrorResponse<unknown> = {
code: "UNKNOWN_ERROR",
message: "Unhandled error",
errors: [e]
};
return c.json<ErrorResponse<unknown>>(response, 500);
}
}
});

View File

@ -0,0 +1 @@
export * from "./GET.ts";

View File

@ -15,4 +15,4 @@ configureRoutes(app);
await startServer(app);
export const VERSION = "0.5.3";
export const VERSION = "0.6.0";

View File

@ -6,6 +6,7 @@ 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";
import { loginHandler } from "@/routes/login/session/POST";
import { logoutHandler } from "@/routes/session";
@ -13,6 +14,8 @@ 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);

View File

@ -4,20 +4,20 @@ import type { Psql } from "@core/db/psql.d.ts";
export async function getVideosNearMilestone(sql: Psql) {
const queryResult = await sql<LatestSnapshotType[]>`
SELECT ls.*
SELECT ls.*
FROM latest_video_snapshot ls
RIGHT JOIN songs ON songs.aid = ls.aid
RIGHT JOIN songs ON songs.aid = ls.aid
WHERE
(views >= 50000 AND views < 100000) OR
(views >= 900000 AND views < 1000000) OR
(views >= 9900000 AND views < 10000000)
(views >= CEIL(views::float/1000000::float)*1000000-100000 AND views < CEIL(views::float/1000000::float)*1000000)
UNION
SELECT ls.*
FROM latest_video_snapshot ls
WHERE
(views >= 90000 AND views < 100000) OR
(views >= 900000 AND views < 1000000) OR
(views >= 9900000 AND views < 10000000)
(views >= CEIL(views::float/1000000::float)*1000000-100000 AND views < CEIL(views::float/1000000::float)*1000000)
`;
return queryResult.map((row) => {
return {

View File

@ -84,5 +84,5 @@ export const snapshotTickWorker = async (_job: Job) => {
export const closetMilestone = (views: number) => {
if (views < 100000) return 100000;
if (views < 1000000) return 1000000;
return 10000000;
return Math.ceil(views / 1000000) * 1000000;
};

View File

@ -1,5 +1,5 @@
import { Job } from "bullmq";
import { scheduleSnapshot, setSnapshotStatus, snapshotScheduleExists } from "db/snapshotSchedule.ts";
import { getLatestSnapshot, scheduleSnapshot, setSnapshotStatus, snapshotScheduleExists } from "db/snapshotSchedule.ts";
import logger from "@core/log/logger.ts";
import { HOUR, MINUTE, SECOND } from "@core/const/time.ts";
import { getBiliVideoStatus, setBiliVideoStatus } from "../../db/bilibili_metadata.ts";
@ -8,6 +8,7 @@ import { getSongsPublihsedAt } from "db/songs.ts";
import { getAdjustedShortTermETA } from "mq/scheduling.ts";
import { NetSchedulerError } from "@core/net/delegate.ts";
import { sql } from "@core/db/dbNew.ts";
import { closetMilestone } from "./snapshotTick.ts";
const snapshotTypeToTaskMap: { [key: string]: string } = {
milestone: "snapshotMilestoneVideo",
@ -21,6 +22,7 @@ export const snapshotVideoWorker = async (job: Job): Promise<void> => {
const type = job.data.type;
const task = snapshotTypeToTaskMap[type] ?? "snapshotVideo";
const retryInterval = type === "milestone" ? 5 * SECOND : 2 * MINUTE;
const latestSnapshot = await getLatestSnapshot(sql, aid);
try {
const exists = await snapshotScheduleExists(sql, id);
if (!exists) {
@ -71,6 +73,10 @@ export const snapshotVideoWorker = async (job: Job): Promise<void> => {
await scheduleSnapshot(sql, aid, type, Date.now() + intervalMins * MINUTE, true);
}
if (type !== "milestone") return;
const alreadyAchievedMilestone = stat.views > closetMilestone(latestSnapshot.views);
if (alreadyAchievedMilestone) {
return;
}
const eta = await getAdjustedShortTermETA(sql, aid);
if (eta > 144) {
const etaHoursString = eta.toFixed(2) + " hrs";

View File

@ -1,6 +1,6 @@
{
"name": "crawler",
"version": "1.2.26",
"version": "1.3.0",
"scripts": {
"test": "bun --env-file=.env.test run vitest",
"worker:main": "bun run ./src/worker.ts",

View File

@ -14,7 +14,7 @@ export const ErrorDialog: React.FC<ErrorDialogProps> = ({ children, closeDialog,
<>
<DialogHeadline>{errorCode ? t(errorCode) : "错误"}</DialogHeadline>
<DialogSupportingText>{children}</DialogSupportingText>
<DialogButtonGroup>
<DialogButtonGroup close={closeDialog}>
<DialogButton onClick={closeDialog}></DialogButton>
</DialogButtonGroup>
</>

View File

@ -1,6 +1,6 @@
{
"name": "next",
"version": "2.9.0",
"version": "2.9.1",
"private": true,
"scripts": {
"dev": "next dev --turbopack -p 7400",