ref: switch to bun & postgres.js for backend
This commit is contained in:
parent
a31f702499
commit
e72b3008d1
6
.idea/bun.xml
Normal file
6
.idea/bun.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="BunSettings">
|
||||||
|
<option name="bunPath" value="$USER_HOME$/.bun/bin/bun" />
|
||||||
|
</component>
|
||||||
|
</project>
|
55
.idea/codeStyles/Project.xml
Normal file
55
.idea/codeStyles/Project.xml
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<code_scheme name="Project" version="173">
|
||||||
|
<option name="LINE_SEPARATOR" value=" " />
|
||||||
|
<HTMLCodeStyleSettings>
|
||||||
|
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
|
||||||
|
</HTMLCodeStyleSettings>
|
||||||
|
<JSCodeStyleSettings version="0">
|
||||||
|
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
||||||
|
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
||||||
|
<option name="FORCE_QUOTE_STYlE" value="true" />
|
||||||
|
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
|
||||||
|
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
||||||
|
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
||||||
|
</JSCodeStyleSettings>
|
||||||
|
<TypeScriptCodeStyleSettings version="0">
|
||||||
|
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
||||||
|
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
||||||
|
<option name="FORCE_QUOTE_STYlE" value="true" />
|
||||||
|
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
|
||||||
|
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
||||||
|
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
||||||
|
</TypeScriptCodeStyleSettings>
|
||||||
|
<VueCodeStyleSettings>
|
||||||
|
<option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
|
||||||
|
<option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
|
||||||
|
</VueCodeStyleSettings>
|
||||||
|
<codeStyleSettings language="HTML">
|
||||||
|
<option name="SOFT_MARGINS" value="120" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="JavaScript">
|
||||||
|
<option name="SOFT_MARGINS" value="120" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="TypeScript">
|
||||||
|
<option name="SOFT_MARGINS" value="120" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="Vue">
|
||||||
|
<option name="SOFT_MARGINS" value="120" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="4" />
|
||||||
|
<option name="TAB_SIZE" value="4" />
|
||||||
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
</code_scheme>
|
||||||
|
</component>
|
5
.idea/codeStyles/codeStyleConfig.xml
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<state>
|
||||||
|
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||||
|
</state>
|
||||||
|
</component>
|
6
.idea/compiler.xml
Normal file
6
.idea/compiler.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="TypeScriptCompiler">
|
||||||
|
<option name="useTypesFromServer" value="true" />
|
||||||
|
</component>
|
||||||
|
</project>
|
@ -2,5 +2,6 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="DenoSettings">
|
<component name="DenoSettings">
|
||||||
<option name="denoInit" value="{ "enable": true, "lint": true, "unstable": true, "importMap": "import_map.json", "config": "deno.json", "fmt": { "useTabs": true, "lineWidth": 120, "indentWidth": 4, "semiColons": true, "proseWrap": "always" } }" />
|
<option name="denoInit" value="{ "enable": true, "lint": true, "unstable": true, "importMap": "import_map.json", "config": "deno.json", "fmt": { "useTabs": true, "lineWidth": 120, "indentWidth": 4, "semiColons": true, "proseWrap": "always" } }" />
|
||||||
|
<option name="useDenoValue" value="DISABLE" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
@ -31,5 +31,6 @@
|
|||||||
<option name="processLiterals" value="true" />
|
<option name="processLiterals" value="true" />
|
||||||
<option name="processComments" value="true" />
|
<option name="processComments" value="true" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
|
<inspection_tool class="TsLint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
</profile>
|
</profile>
|
||||||
</component>
|
</component>
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"lock": false,
|
"lock": false,
|
||||||
"workspace": ["./packages/crawler", "./packages/frontend", "./packages/backend", "./packages/core"],
|
"workspace": ["./packages/crawler", "./packages/core"],
|
||||||
"nodeModulesDir": "auto",
|
"nodeModulesDir": "auto",
|
||||||
"tasks": {
|
"tasks": {
|
||||||
"crawler": "deno task --filter 'crawler' all",
|
"crawler": "deno task --filter 'crawler' all",
|
||||||
|
8
packages/backend/.prettierrc
Normal file
8
packages/backend/.prettierrc
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"useTabs": true,
|
||||||
|
"tabWidth": 4,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"singleQuote": false,
|
||||||
|
"printWidth": 120,
|
||||||
|
"endOfLine": "lf"
|
||||||
|
}
|
66
packages/backend/bun.lock
Normal file
66
packages/backend/bun.lock
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
{
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"workspaces": {
|
||||||
|
"": {
|
||||||
|
"dependencies": {
|
||||||
|
"@rabbit-company/argon2id": "^2.1.0",
|
||||||
|
"hono": "^4.7.8",
|
||||||
|
"hono-rate-limiter": "^0.4.2",
|
||||||
|
"ioredis": "^5.6.1",
|
||||||
|
"postgres": "^3.4.5",
|
||||||
|
"yup": "^1.6.1",
|
||||||
|
"zod": "^3.24.3",
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"prettier": "^3.5.3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"packages": {
|
||||||
|
"@ioredis/commands": ["@ioredis/commands@1.2.0", "", {}, "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg=="],
|
||||||
|
|
||||||
|
"@rabbit-company/argon2id": ["@rabbit-company/argon2id@2.1.0", "", { "peerDependencies": { "typescript": "^5.6.2" } }, "sha512-X/kt89qjmS9+Zh+DYCGcWeTwHa4C8vY8T3EnSma+vWj7spMzAYX4F8vmGUkny9hygpTOeC/yXwAUdJAfJ52H+w=="],
|
||||||
|
|
||||||
|
"cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="],
|
||||||
|
|
||||||
|
"debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
|
||||||
|
|
||||||
|
"denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="],
|
||||||
|
|
||||||
|
"hono": ["hono@4.7.8", "", {}, "sha512-PCibtFdxa7/Ldud9yddl1G81GjYaeMYYTq4ywSaNsYbB1Lug4mwtOMJf2WXykL0pntYwmpRJeOI3NmoDgD+Jxw=="],
|
||||||
|
|
||||||
|
"hono-rate-limiter": ["hono-rate-limiter@0.4.2", "", { "peerDependencies": { "hono": "^4.1.1" } }, "sha512-AAtFqgADyrmbDijcRTT/HJfwqfvhalya2Zo+MgfdrMPas3zSMD8SU03cv+ZsYwRU1swv7zgVt0shwN059yzhjw=="],
|
||||||
|
|
||||||
|
"ioredis": ["ioredis@5.6.1", "", { "dependencies": { "@ioredis/commands": "^1.1.1", "cluster-key-slot": "^1.1.0", "debug": "^4.3.4", "denque": "^2.1.0", "lodash.defaults": "^4.2.0", "lodash.isarguments": "^3.1.0", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0", "standard-as-callback": "^2.1.0" } }, "sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA=="],
|
||||||
|
|
||||||
|
"lodash.defaults": ["lodash.defaults@4.2.0", "", {}, "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="],
|
||||||
|
|
||||||
|
"lodash.isarguments": ["lodash.isarguments@3.1.0", "", {}, "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg=="],
|
||||||
|
|
||||||
|
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||||
|
|
||||||
|
"postgres": ["postgres@3.4.5", "", {}, "sha512-cDWgoah1Gez9rN3H4165peY9qfpEo+SA61oQv65O3cRUE1pOEoJWwddwcqKE8XZYjbblOJlYDlLV4h67HrEVDg=="],
|
||||||
|
|
||||||
|
"prettier": ["prettier@3.5.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="],
|
||||||
|
|
||||||
|
"property-expr": ["property-expr@2.0.6", "", {}, "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA=="],
|
||||||
|
|
||||||
|
"redis-errors": ["redis-errors@1.2.0", "", {}, "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w=="],
|
||||||
|
|
||||||
|
"redis-parser": ["redis-parser@3.0.0", "", { "dependencies": { "redis-errors": "^1.0.0" } }, "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A=="],
|
||||||
|
|
||||||
|
"standard-as-callback": ["standard-as-callback@2.1.0", "", {}, "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="],
|
||||||
|
|
||||||
|
"tiny-case": ["tiny-case@1.0.3", "", {}, "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q=="],
|
||||||
|
|
||||||
|
"toposort": ["toposort@2.0.2", "", {}, "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg=="],
|
||||||
|
|
||||||
|
"type-fest": ["type-fest@2.19.0", "", {}, "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA=="],
|
||||||
|
|
||||||
|
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
|
||||||
|
|
||||||
|
"yup": ["yup@1.6.1", "", { "dependencies": { "property-expr": "^2.0.5", "tiny-case": "^1.0.3", "toposort": "^2.0.2", "type-fest": "^2.19.0" } }, "sha512-JED8pB50qbA4FOkDol0bYF/p60qSEDQqBD0/qeIrUCG1KbPBIQ776fCUNb9ldbPcSTxA69g/47XTo4TqWiuXOA=="],
|
||||||
|
|
||||||
|
"zod": ["zod@3.24.3", "", {}, "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg=="],
|
||||||
|
}
|
||||||
|
}
|
@ -1,30 +0,0 @@
|
|||||||
import { type Client, Pool } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
|
|
||||||
import { postgresConfig, postgresConfigCred } from "@core/db/pgConfig.ts";
|
|
||||||
import { createMiddleware } from "hono/factory";
|
|
||||||
|
|
||||||
const pool = new Pool(postgresConfig, 4);
|
|
||||||
const poolCred = new Pool(postgresConfigCred, 2);
|
|
||||||
|
|
||||||
export const db = pool;
|
|
||||||
export const dbCred = poolCred;
|
|
||||||
|
|
||||||
export const dbMiddleware = createMiddleware(async (c, next) => {
|
|
||||||
const connection = await pool.connect();
|
|
||||||
c.set("db", connection);
|
|
||||||
await next();
|
|
||||||
connection.release();
|
|
||||||
});
|
|
||||||
|
|
||||||
export const dbCredMiddleware = createMiddleware(async (c, next) => {
|
|
||||||
const connection = await poolCred.connect();
|
|
||||||
c.set("dbCred", connection);
|
|
||||||
await next();
|
|
||||||
connection.release();
|
|
||||||
});
|
|
||||||
|
|
||||||
declare module "hono" {
|
|
||||||
interface ContextVariableMap {
|
|
||||||
db: Client;
|
|
||||||
dbCred: Client;
|
|
||||||
}
|
|
||||||
}
|
|
38
packages/backend/db/config.ts
Normal file
38
packages/backend/db/config.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
const requiredEnvVars = ["DB_HOST", "DB_NAME", "DB_USER", "DB_PASSWORD", "DB_PORT", "DB_NAME_CRED"];
|
||||||
|
|
||||||
|
const unsetVars = requiredEnvVars.filter((key) => process.env[key] === undefined);
|
||||||
|
|
||||||
|
if (unsetVars.length > 0) {
|
||||||
|
throw new Error(`Missing required environment variables: ${unsetVars.join(", ")}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const databaseHost = process.env["DB_HOST"]!;
|
||||||
|
const databaseName = process.env["DB_NAME"];
|
||||||
|
const databaseNameCred = process.env["DB_NAME_CRED"]!;
|
||||||
|
const databaseUser = process.env["DB_USER"]!;
|
||||||
|
const databasePassword = process.env["DB_PASSWORD"]!;
|
||||||
|
const databasePort = process.env["DB_PORT"]!;
|
||||||
|
|
||||||
|
export const postgresConfig = {
|
||||||
|
hostname: databaseHost,
|
||||||
|
port: parseInt(databasePort),
|
||||||
|
database: databaseName,
|
||||||
|
user: databaseUser,
|
||||||
|
password: databasePassword
|
||||||
|
};
|
||||||
|
|
||||||
|
export const postgresConfigNpm = {
|
||||||
|
host: databaseHost,
|
||||||
|
port: parseInt(databasePort),
|
||||||
|
database: databaseName,
|
||||||
|
username: databaseUser,
|
||||||
|
password: databasePassword
|
||||||
|
};
|
||||||
|
|
||||||
|
export const postgresConfigCred = {
|
||||||
|
hostname: databaseHost,
|
||||||
|
port: parseInt(databasePort),
|
||||||
|
database: databaseNameCred,
|
||||||
|
user: databaseUser,
|
||||||
|
password: databasePassword
|
||||||
|
};
|
6
packages/backend/db/db.ts
Normal file
6
packages/backend/db/db.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import postgres from "postgres";
|
||||||
|
import { postgresConfigNpm } from "./config";
|
||||||
|
|
||||||
|
const sql = postgres(postgresConfigNpm);
|
||||||
|
|
||||||
|
export default sql;
|
58
packages/backend/db/videoSnapshot.ts
Normal file
58
packages/backend/db/videoSnapshot.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import sql from "./db";
|
||||||
|
import type { VideoSnapshotType } from "@core/db/schema.d.ts";
|
||||||
|
|
||||||
|
export async function getVideoSnapshots(
|
||||||
|
aid: number,
|
||||||
|
limit: number,
|
||||||
|
pageOrOffset: number,
|
||||||
|
reverse: boolean,
|
||||||
|
mode: "page" | "offset" = "page"
|
||||||
|
) {
|
||||||
|
const offset = mode === "page" ? (pageOrOffset - 1) * limit : pageOrOffset;
|
||||||
|
if (reverse) {
|
||||||
|
return sql<VideoSnapshotType[]>`
|
||||||
|
SELECT *
|
||||||
|
FROM video_snapshot
|
||||||
|
WHERE aid = ${aid}
|
||||||
|
ORDER BY created_at
|
||||||
|
LIMIT ${limit} OFFSET ${offset}
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
return sql<VideoSnapshotType[]>`
|
||||||
|
SELECT *
|
||||||
|
FROM video_snapshot
|
||||||
|
WHERE aid = ${aid}
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
LIMIT ${limit} OFFSET ${offset}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getVideoSnapshotsByBV(
|
||||||
|
bv: string,
|
||||||
|
limit: number,
|
||||||
|
pageOrOffset: number,
|
||||||
|
reverse: boolean,
|
||||||
|
mode: "page" | "offset" = "page"
|
||||||
|
) {
|
||||||
|
const offset = mode === "page" ? (pageOrOffset - 1) * limit : pageOrOffset;
|
||||||
|
if (reverse) {
|
||||||
|
return sql<VideoSnapshotType[]>`
|
||||||
|
SELECT vs.*
|
||||||
|
FROM video_snapshot vs
|
||||||
|
JOIN bilibili_metadata bm ON vs.aid = bm.aid
|
||||||
|
WHERE bm.bvid = ${bv}
|
||||||
|
ORDER BY vs.created_at
|
||||||
|
LIMIT ${limit} OFFSET ${offset}
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
return sql<VideoSnapshotType[]>`
|
||||||
|
SELECT vs.*
|
||||||
|
FROM video_snapshot vs
|
||||||
|
JOIN bilibili_metadata bm ON vs.aid = bm.aid
|
||||||
|
WHERE bm.bvid = ${bv}
|
||||||
|
ORDER BY vs.created_at DESC
|
||||||
|
LIMIT ${limit} OFFSET ${offset}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@cvsa/backend",
|
|
||||||
"imports": {
|
|
||||||
"@rabbit-company/argon2id": "jsr:@rabbit-company/argon2id@^2.1.0",
|
|
||||||
"hono": "jsr:@hono/hono@^4.7.5",
|
|
||||||
"zod": "npm:zod",
|
|
||||||
"yup": "npm:yup",
|
|
||||||
"@core/": "../core/",
|
|
||||||
"log/": "../core/log/",
|
|
||||||
"@crawler/net/videoInfo": "../crawler/net/getVideoInfo.ts",
|
|
||||||
"ioredis": "npm:ioredis"
|
|
||||||
},
|
|
||||||
"tasks": {
|
|
||||||
"dev": "deno serve --env-file=.env --allow-env --allow-net --allow-read --allow-write --allow-run --watch main.ts",
|
|
||||||
"start": "deno serve --env-file=.env --allow-env --allow-net --allow-read --allow-write --allow-run --host 127.0.0.1 main.ts"
|
|
||||||
},
|
|
||||||
"compilerOptions": {
|
|
||||||
"jsx": "precompile",
|
|
||||||
"jsxImportSource": "hono/jsx"
|
|
||||||
},
|
|
||||||
"exports": "./main.ts"
|
|
||||||
}
|
|
@ -1,26 +1,39 @@
|
|||||||
import { Hono } from "hono";
|
import { Hono } from "hono";
|
||||||
import { dbCredMiddleware, dbMiddleware } from "./database.ts";
|
|
||||||
import { rootHandler } from "./root.ts";
|
import { rootHandler } from "./root.ts";
|
||||||
import { getSnapshotsHanlder } from "./snapshots.ts";
|
import { getSnapshotsHanlder } from "./snapshots.ts";
|
||||||
import { registerHandler } from "./register.ts";
|
import { registerHandler } from "./register.ts";
|
||||||
import { videoInfoHandler } from "./videoInfo.ts";
|
import { videoInfoHandler } from "./videoInfo.ts";
|
||||||
|
import { pingHandler } from "./ping.ts";
|
||||||
|
// import { getConnInfo } from "hono/deno";
|
||||||
|
//import { rateLimiter } from "hono-rate-limiter";
|
||||||
|
// import { MINUTE } from "https://deno.land/std@0.216.0/datetime/constants.ts";
|
||||||
|
// import type { Context } from "hono";
|
||||||
|
// import type { BlankEnv } from "hono/types";
|
||||||
|
|
||||||
export const app = new Hono();
|
export const app = new Hono();
|
||||||
|
|
||||||
app.use("/video/*", dbMiddleware);
|
// const limiter = rateLimiter<BlankEnv, "/user", {}>({
|
||||||
app.use("/user", dbCredMiddleware);
|
// windowMs: 60 * MINUTE,
|
||||||
|
// limit: 5,
|
||||||
|
// standardHeaders: "draft-6",
|
||||||
|
// keyGenerator: (c) => {
|
||||||
|
// const info = getConnInfo(c as unknown as Context<BlankEnv, "/user", {}>);
|
||||||
|
// if (!info.remote || !info.remote.address) {
|
||||||
|
// return crypto.randomUUID()
|
||||||
|
// }
|
||||||
|
// return info.remote.address;
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// app.use("/user", limiter);
|
||||||
|
|
||||||
app.get("/", ...rootHandler);
|
app.get("/", ...rootHandler);
|
||||||
|
app.get("/ping", ...pingHandler);
|
||||||
|
|
||||||
app.get("/video/:id/snapshots", ...getSnapshotsHanlder);
|
app.get("/video/:id/snapshots", ...getSnapshotsHanlder);
|
||||||
app.post("/user", ...registerHandler);
|
app.post("/user", ...registerHandler);
|
||||||
|
|
||||||
app.get("/video/:id/info", ...videoInfoHandler);
|
app.get("/video/:id/info", ...videoInfoHandler);
|
||||||
|
|
||||||
const fetch = app.fetch;
|
export default app
|
||||||
|
|
||||||
export default {
|
|
||||||
fetch,
|
|
||||||
} satisfies Deno.ServeDefaultExport;
|
|
||||||
|
|
||||||
export const VERSION = "0.4.2";
|
export const VERSION = "0.4.2";
|
||||||
|
19
packages/backend/package.json
Normal file
19
packages/backend/package.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"format": "prettier --write .",
|
||||||
|
"dev": "bun run --hot main.ts"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@rabbit-company/argon2id": "^2.1.0",
|
||||||
|
"hono": "^4.7.8",
|
||||||
|
"hono-rate-limiter": "^0.4.2",
|
||||||
|
"ioredis": "^5.6.1",
|
||||||
|
"postgres": "^3.4.5",
|
||||||
|
"yup": "^1.6.1",
|
||||||
|
"zod": "^3.24.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"prettier": "^3.5.3",
|
||||||
|
"@types/bun": "latest"
|
||||||
|
}
|
||||||
|
}
|
7
packages/backend/ping.ts
Normal file
7
packages/backend/ping.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { createHandlers } from "./utils.ts";
|
||||||
|
|
||||||
|
export const pingHandler = createHandlers(async (c) => {
|
||||||
|
return c.json({
|
||||||
|
"message": "pong"
|
||||||
|
});
|
||||||
|
});
|
@ -3,63 +3,72 @@ import Argon2id from "@rabbit-company/argon2id";
|
|||||||
import { object, string, ValidationError } from "yup";
|
import { object, string, ValidationError } from "yup";
|
||||||
import type { Context } from "hono";
|
import type { Context } from "hono";
|
||||||
import type { Bindings, BlankEnv, BlankInput } from "hono/types";
|
import type { Bindings, BlankEnv, BlankInput } from "hono/types";
|
||||||
import type { Client } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
|
import sql from "./db/db.ts";
|
||||||
|
import { ErrorResponse, StatusResponse } from "./schema";
|
||||||
|
|
||||||
const RegistrationBodySchema = object({
|
const RegistrationBodySchema = object({
|
||||||
username: string().trim().required("Username is required").max(50, "Username cannot exceed 50 characters"),
|
username: string().trim().required("Username is required").max(50, "Username cannot exceed 50 characters"),
|
||||||
password: string().required("Password is required"),
|
password: string().required("Password is required"),
|
||||||
nickname: string().optional(),
|
nickname: string().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
type ContextType = Context<BlankEnv & { Bindings: Bindings }, "/user", BlankInput>;
|
type ContextType = Context<BlankEnv & { Bindings: Bindings }, "/user", BlankInput>;
|
||||||
|
|
||||||
export const userExists = async (username: string, client: Client) => {
|
export const userExists = async (username: string) => {
|
||||||
const query = `
|
const result = await sql`
|
||||||
SELECT * FROM users WHERE username = $1
|
SELECT 1
|
||||||
|
FROM users
|
||||||
|
WHERE username = ${username}
|
||||||
`;
|
`;
|
||||||
const result = await client.queryObject(query, [username]);
|
return result.length > 0;
|
||||||
return result.rows.length > 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const registerHandler = createHandlers(async (c: ContextType) => {
|
export const registerHandler = createHandlers(async (c: ContextType) => {
|
||||||
const client = c.get("dbCred");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const body = await RegistrationBodySchema.validate(await c.req.json());
|
const body = await RegistrationBodySchema.validate(await c.req.json());
|
||||||
const { username, password, nickname } = body;
|
const { username, password, nickname } = body;
|
||||||
|
|
||||||
if (await userExists(username, client)) {
|
if (await userExists(username)) {
|
||||||
return c.json({
|
const response: StatusResponse = {
|
||||||
message: `User "${username}" already exists.`,
|
message: `User "${username}" already exists.`
|
||||||
}, 400);
|
};
|
||||||
|
return c.json<StatusResponse>(response, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
const hash = await Argon2id.hashEncoded(password);
|
const hash = await Argon2id.hashEncoded(password);
|
||||||
|
|
||||||
const query = `
|
await sql`
|
||||||
INSERT INTO users (username, password, nickname) VALUES ($1, $2, $3)
|
INSERT INTO users (username, password, nickname)
|
||||||
|
VALUES (${username}, ${hash}, ${nickname ? nickname : null})
|
||||||
`;
|
`;
|
||||||
await client.queryObject(query, [username, hash, nickname || null]);
|
|
||||||
|
|
||||||
return c.json({
|
const response: StatusResponse = {
|
||||||
message: `User "${username}" registered successfully.`,
|
message: `User "${username}" registered successfully.`
|
||||||
}, 201);
|
}
|
||||||
|
|
||||||
|
return c.json<StatusResponse>(response, 201);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof ValidationError) {
|
if (e instanceof ValidationError) {
|
||||||
return c.json({
|
const response: ErrorResponse<string> = {
|
||||||
message: "Invalid registration data.",
|
message: "Invalid registration data.",
|
||||||
errors: e.errors,
|
errors: e.errors,
|
||||||
}, 400);
|
code: "INVALID_PAYLOAD"
|
||||||
|
}
|
||||||
|
return c.json<ErrorResponse<string>>(response, 400);
|
||||||
} else if (e instanceof SyntaxError) {
|
} else if (e instanceof SyntaxError) {
|
||||||
return c.json({
|
const response: ErrorResponse<string> = {
|
||||||
message: "Invalid JSON in request body.",
|
message: "Invalid JSON payload.",
|
||||||
}, 400);
|
errors: [e.message],
|
||||||
|
code: "INVALID_FORMAT"
|
||||||
|
}
|
||||||
|
return c.json<ErrorResponse<string>>(response, 400);
|
||||||
} else {
|
} else {
|
||||||
console.error("Registration error:", e);
|
const response: ErrorResponse<string> = {
|
||||||
return c.json({
|
message: "Invalid JSON payload.",
|
||||||
message: "An unexpected error occurred during registration.",
|
errors: [(e as Error).message],
|
||||||
error: (e as Error).message,
|
code: "UNKNOWN_ERR"
|
||||||
}, 500);
|
}
|
||||||
|
return c.json<ErrorResponse<string>>(response, 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -17,13 +17,13 @@ export const rootHandler = createHandlers((c) => {
|
|||||||
singer = pickSinger();
|
singer = pickSinger();
|
||||||
}
|
}
|
||||||
return c.json({
|
return c.json({
|
||||||
"project": {
|
project: {
|
||||||
"name": "中V档案馆",
|
name: "中V档案馆",
|
||||||
"motto": "一起唱吧,心中的歌!",
|
motto: "一起唱吧,心中的歌!"
|
||||||
},
|
},
|
||||||
"status": 200,
|
status: 200,
|
||||||
"version": VERSION,
|
version: VERSION,
|
||||||
"time": Date.now(),
|
time: Date.now(),
|
||||||
"singer": singer,
|
singer: singer
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
11
packages/backend/schema.d.ts
vendored
Normal file
11
packages/backend/schema.d.ts
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
type ErrorCode = "INVALID_QUERY_PARAMS" | "UNKNOWN_ERR" | "INVALID_PAYLOAD" | "INVALID_FORMAT";
|
||||||
|
|
||||||
|
export interface ErrorResponse<E> {
|
||||||
|
code: ErrorCode
|
||||||
|
message: string;
|
||||||
|
errors: E[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StatusResponse {
|
||||||
|
message: string;
|
||||||
|
}
|
@ -1,69 +1,69 @@
|
|||||||
export const singers = [
|
export const singers = [
|
||||||
{
|
{
|
||||||
"name": "洛天依",
|
name: "洛天依",
|
||||||
"color": "#66CCFF",
|
color: "#66CCFF",
|
||||||
"birthday": "0712",
|
birthday: "0712"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "言和",
|
name: "言和",
|
||||||
"color": "#00FFCC",
|
color: "#00FFCC",
|
||||||
"birthday": "0711",
|
birthday: "0711"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "乐正绫",
|
name: "乐正绫",
|
||||||
"color": "#EE0000",
|
color: "#EE0000",
|
||||||
"birthday": "0412",
|
birthday: "0412"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "乐正龙牙",
|
name: "乐正龙牙",
|
||||||
"color": "#006666",
|
color: "#006666",
|
||||||
"birthday": "1002",
|
birthday: "1002"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "徵羽摩柯",
|
name: "徵羽摩柯",
|
||||||
"color": "#0080FF",
|
color: "#0080FF",
|
||||||
"birthday": "1210",
|
birthday: "1210"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "墨清弦",
|
name: "墨清弦",
|
||||||
"color": "#FFFF00",
|
color: "#FFFF00",
|
||||||
"birthday": "0520",
|
birthday: "0520"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "星尘",
|
name: "星尘",
|
||||||
"color": "#9999FF",
|
color: "#9999FF",
|
||||||
"birthday": "0812",
|
birthday: "0812"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "心华",
|
name: "心华",
|
||||||
"color": "#EE82EE",
|
color: "#EE82EE",
|
||||||
"birthday": "0210",
|
birthday: "0210"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "海伊",
|
name: "海伊",
|
||||||
"color": "#3399FF",
|
color: "#3399FF",
|
||||||
"birthday": "0722",
|
birthday: "0722"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "苍穹",
|
name: "苍穹",
|
||||||
"color": "#8BC0B5",
|
color: "#8BC0B5",
|
||||||
"birthday": "0520",
|
birthday: "0520"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "赤羽",
|
name: "赤羽",
|
||||||
"color": "#FF4004",
|
color: "#FF4004",
|
||||||
"birthday": "1126",
|
birthday: "1126"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "诗岸",
|
name: "诗岸",
|
||||||
"color": "#F6BE72",
|
color: "#F6BE72",
|
||||||
"birthday": "0119",
|
birthday: "0119"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "牧心",
|
name: "牧心",
|
||||||
"color": "#2A2859",
|
color: "#2A2859",
|
||||||
"birthday": "0807",
|
birthday: "0807"
|
||||||
},
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
export interface Singer {
|
export interface Singer {
|
||||||
@ -75,13 +75,13 @@ export interface Singer {
|
|||||||
|
|
||||||
export const specialSingers = [
|
export const specialSingers = [
|
||||||
{
|
{
|
||||||
"name": "雅音宫羽",
|
name: "雅音宫羽",
|
||||||
"message": "你是我最真模样,从来不曾遗忘。",
|
message: "你是我最真模样,从来不曾遗忘。"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "初音未来",
|
name: "初音未来",
|
||||||
"message": "初始之音,响彻未来!",
|
message: "初始之音,响彻未来!"
|
||||||
},
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
export const pickSinger = () => {
|
export const pickSinger = () => {
|
||||||
|
@ -1,22 +1,23 @@
|
|||||||
import type { Context } from "hono";
|
import type { Context } from "hono";
|
||||||
import { createHandlers } from "./utils.ts";
|
import { createHandlers } from "./utils.ts";
|
||||||
import type { BlankEnv, BlankInput } from "hono/types";
|
import type { BlankEnv, BlankInput } from "hono/types";
|
||||||
import { getVideoSnapshots, getVideoSnapshotsByBV } from "@core/db/videoSnapshot.ts";
|
import { getVideoSnapshots, getVideoSnapshotsByBV } from "./db/videoSnapshot.ts";
|
||||||
import type { VideoSnapshotType } from "@core/db/schema.d.ts";
|
import type { VideoSnapshotType } from "@core/db/schema.d.ts";
|
||||||
import { boolean, mixed, number, object, ValidationError } from "yup";
|
import { boolean, mixed, number, object, ValidationError } from "yup";
|
||||||
|
import { ErrorResponse } from "./schema";
|
||||||
|
|
||||||
const SnapshotQueryParamsSchema = object({
|
const SnapshotQueryParamsSchema = object({
|
||||||
ps: number().integer().optional().positive(),
|
ps: number().integer().optional().positive(),
|
||||||
pn: number().integer().optional().positive(),
|
pn: number().integer().optional().positive(),
|
||||||
offset: number().integer().optional().positive(),
|
offset: number().integer().optional().positive(),
|
||||||
reverse: boolean().optional(),
|
reverse: boolean().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export const idSchema = mixed().test(
|
export const idSchema = mixed().test(
|
||||||
"is-valid-id",
|
"is-valid-id",
|
||||||
'id must be a string starting with "av" followed by digits, or "BV" followed by 10 alphanumeric characters, or a positive integer',
|
'id must be a string starting with "av" followed by digits, or "BV" followed by 10 alphanumeric characters, or a positive integer',
|
||||||
async (value) => {
|
async (value) => {
|
||||||
if (value && await number().integer().isValid(value)) {
|
if (value && (await number().integer().isValid(value))) {
|
||||||
const v = parseInt(value as string);
|
const v = parseInt(value as string);
|
||||||
return Number.isInteger(v) && v > 0;
|
return Number.isInteger(v) && v > 0;
|
||||||
}
|
}
|
||||||
@ -34,13 +35,11 @@ export const idSchema = mixed().test(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
type ContextType = Context<BlankEnv, "/video/:id/snapshots", BlankInput>;
|
type ContextType = Context<BlankEnv, "/video/:id/snapshots", BlankInput>;
|
||||||
export const getSnapshotsHanlder = createHandlers(async (c: ContextType) => {
|
export const getSnapshotsHanlder = createHandlers(async (c: ContextType) => {
|
||||||
const client = c.get("db");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const idParam = await idSchema.validate(c.req.param("id"));
|
const idParam = await idSchema.validate(c.req.param("id"));
|
||||||
let videoId: string | number = idParam as string;
|
let videoId: string | number = idParam as string;
|
||||||
@ -71,22 +70,32 @@ export const getSnapshotsHanlder = createHandlers(async (c: ContextType) => {
|
|||||||
let result: VideoSnapshotType[];
|
let result: VideoSnapshotType[];
|
||||||
|
|
||||||
if (typeof videoId === "number") {
|
if (typeof videoId === "number") {
|
||||||
result = await getVideoSnapshots(client, videoId, limit, pageOrOffset, reverse, mode);
|
result = await getVideoSnapshots(videoId, limit, pageOrOffset, reverse, mode);
|
||||||
} else {
|
} else {
|
||||||
result = await getVideoSnapshotsByBV(client, videoId, limit, pageOrOffset, reverse, mode);
|
result = await getVideoSnapshotsByBV(videoId, limit, pageOrOffset, reverse, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
const rows = result.map((row) => ({
|
const rows = result.map((row) => ({
|
||||||
...row,
|
...row,
|
||||||
aid: Number(row.aid),
|
aid: Number(row.aid)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return c.json(rows);
|
return c.json(rows);
|
||||||
} catch (e) {
|
} catch (e: unknown) {
|
||||||
if (e instanceof ValidationError) {
|
if (e instanceof ValidationError) {
|
||||||
return c.json({ message: "Invalid query parameters", errors: e.errors }, 400);
|
const response: ErrorResponse<string> = {
|
||||||
|
code: "INVALID_QUERY_PARAMS",
|
||||||
|
message: "Invalid query parameters",
|
||||||
|
errors: e.errors,
|
||||||
|
}
|
||||||
|
return c.json<ErrorResponse<string>>(response, 400);
|
||||||
} else {
|
} else {
|
||||||
return c.json({ message: "Unhandled error", error: e }, 500);
|
const response: ErrorResponse<unknown> = {
|
||||||
|
code: "UNKNOWN_ERR",
|
||||||
|
message: "Unhandled error",
|
||||||
|
errors: [e]
|
||||||
|
}
|
||||||
|
return c.json<ErrorResponse<unknown>>(response, 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
19
packages/backend/tsconfig.json
Normal file
19
packages/backend/tsconfig.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"include": ["**/*.ts"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"paths": {
|
||||||
|
"@core/*": ["../core/*"],
|
||||||
|
"@crawler/*": ["../crawler/*"]
|
||||||
|
},
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"noEmit": true
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,12 @@
|
|||||||
import logger from "log/logger.ts";
|
import logger from "@core/log/logger.ts";
|
||||||
import { Redis } from "ioredis";
|
import { Redis } from "ioredis";
|
||||||
|
import { sql } from "db/db.ts";
|
||||||
import { number, ValidationError } from "yup";
|
import { number, ValidationError } from "yup";
|
||||||
import { createHandlers } from "./utils.ts";
|
import { createHandlers } from "./utils.ts";
|
||||||
import { getVideoInfo, getVideoInfoByBV } from "@crawler/net/videoInfo";
|
import { getVideoInfo, getVideoInfoByBV } from "@core/net/getVideoInfo";
|
||||||
import { idSchema } from "./snapshots.ts";
|
import { idSchema } from "./snapshots.ts";
|
||||||
import { NetSchedulerError } from "@core/net/delegate.ts";
|
import { NetSchedulerError } from "@core/net/delegate.ts";
|
||||||
import type { Context } from "hono";
|
import type { Context } from "hono";
|
||||||
import type { Client } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
|
|
||||||
import type { BlankEnv, BlankInput } from "hono/types";
|
import type { BlankEnv, BlankInput } from "hono/types";
|
||||||
import type { VideoInfoData } from "@core/net/bilibili.d.ts";
|
import type { VideoInfoData } from "@core/net/bilibili.d.ts";
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ const CACHE_EXPIRATION_SECONDS = 60;
|
|||||||
|
|
||||||
type ContextType = Context<BlankEnv, "/video/:id/info", BlankInput>;
|
type ContextType = Context<BlankEnv, "/video/:id/info", BlankInput>;
|
||||||
|
|
||||||
async function insertVideoSnapshot(client: Client, data: VideoInfoData) {
|
async function insertVideoSnapshot(data: VideoInfoData) {
|
||||||
const views = data.stat.view;
|
const views = data.stat.view;
|
||||||
const danmakus = data.stat.danmaku;
|
const danmakus = data.stat.danmaku;
|
||||||
const replies = data.stat.reply;
|
const replies = data.stat.reply;
|
||||||
@ -25,16 +25,11 @@ async function insertVideoSnapshot(client: Client, data: VideoInfoData) {
|
|||||||
const favorites = data.stat.favorite;
|
const favorites = data.stat.favorite;
|
||||||
const aid = data.aid;
|
const aid = data.aid;
|
||||||
|
|
||||||
const query: string = `
|
await sql`
|
||||||
INSERT INTO video_snapshot (aid, views, danmakus, replies, likes, coins, shares, favorites)
|
INSERT INTO video_snapshot (aid, views, danmakus, replies, likes, coins, shares, favorites)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
VALUES (${aid}, ${views}, ${danmakus}, ${replies}, ${likes}, ${coins}, ${shares}, ${favorites})
|
||||||
`;
|
`;
|
||||||
|
|
||||||
await client.queryObject(
|
|
||||||
query,
|
|
||||||
[aid, views, danmakus, replies, likes, coins, shares, favorites],
|
|
||||||
);
|
|
||||||
|
|
||||||
logger.log(`Inserted into snapshot for video ${aid} by videoInfo API.`, "api", "fn:insertVideoSnapshot");
|
logger.log(`Inserted into snapshot for video ${aid} by videoInfo API.`, "api", "fn:insertVideoSnapshot");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
75
packages/core/bun.lock
Normal file
75
packages/core/bun.lock
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
{
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"workspaces": {
|
||||||
|
"": {
|
||||||
|
"dependencies": {
|
||||||
|
"chalk": "^5.4.1",
|
||||||
|
"logform": "^2.7.0",
|
||||||
|
"winston": "^3.17.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"packages": {
|
||||||
|
"@colors/colors": ["@colors/colors@1.6.0", "", {}, "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA=="],
|
||||||
|
|
||||||
|
"@dabh/diagnostics": ["@dabh/diagnostics@2.0.3", "", { "dependencies": { "colorspace": "1.1.x", "enabled": "2.0.x", "kuler": "^2.0.0" } }, "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA=="],
|
||||||
|
|
||||||
|
"@types/triple-beam": ["@types/triple-beam@1.3.5", "", {}, "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw=="],
|
||||||
|
|
||||||
|
"async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="],
|
||||||
|
|
||||||
|
"chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="],
|
||||||
|
|
||||||
|
"color": ["color@3.2.1", "", { "dependencies": { "color-convert": "^1.9.3", "color-string": "^1.6.0" } }, "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA=="],
|
||||||
|
|
||||||
|
"color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
|
||||||
|
|
||||||
|
"color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
|
||||||
|
|
||||||
|
"color-string": ["color-string@1.9.1", "", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="],
|
||||||
|
|
||||||
|
"colorspace": ["colorspace@1.1.4", "", { "dependencies": { "color": "^3.1.3", "text-hex": "1.0.x" } }, "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w=="],
|
||||||
|
|
||||||
|
"enabled": ["enabled@2.0.0", "", {}, "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="],
|
||||||
|
|
||||||
|
"fecha": ["fecha@4.2.3", "", {}, "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw=="],
|
||||||
|
|
||||||
|
"fn.name": ["fn.name@1.1.0", "", {}, "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="],
|
||||||
|
|
||||||
|
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||||
|
|
||||||
|
"is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="],
|
||||||
|
|
||||||
|
"is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
|
||||||
|
|
||||||
|
"kuler": ["kuler@2.0.0", "", {}, "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A=="],
|
||||||
|
|
||||||
|
"logform": ["logform@2.7.0", "", { "dependencies": { "@colors/colors": "1.6.0", "@types/triple-beam": "^1.3.2", "fecha": "^4.2.0", "ms": "^2.1.1", "safe-stable-stringify": "^2.3.1", "triple-beam": "^1.3.0" } }, "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ=="],
|
||||||
|
|
||||||
|
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||||
|
|
||||||
|
"one-time": ["one-time@1.0.0", "", { "dependencies": { "fn.name": "1.x.x" } }, "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g=="],
|
||||||
|
|
||||||
|
"readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
|
||||||
|
|
||||||
|
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
||||||
|
|
||||||
|
"safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="],
|
||||||
|
|
||||||
|
"simple-swizzle": ["simple-swizzle@0.2.2", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg=="],
|
||||||
|
|
||||||
|
"stack-trace": ["stack-trace@0.0.10", "", {}, "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg=="],
|
||||||
|
|
||||||
|
"string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
|
||||||
|
|
||||||
|
"text-hex": ["text-hex@1.0.0", "", {}, "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg=="],
|
||||||
|
|
||||||
|
"triple-beam": ["triple-beam@1.4.1", "", {}, "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg=="],
|
||||||
|
|
||||||
|
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
||||||
|
|
||||||
|
"winston": ["winston@3.17.0", "", { "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.2", "async": "^3.2.3", "is-stream": "^2.0.0", "logform": "^2.7.0", "one-time": "^1.0.0", "readable-stream": "^3.4.0", "safe-stable-stringify": "^2.3.1", "stack-trace": "0.0.x", "triple-beam": "^1.3.0", "winston-transport": "^4.9.0" } }, "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw=="],
|
||||||
|
|
||||||
|
"winston-transport": ["winston-transport@4.9.0", "", { "dependencies": { "logform": "^2.7.0", "readable-stream": "^3.6.2", "triple-beam": "^1.3.0" } }, "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A=="],
|
||||||
|
}
|
||||||
|
}
|
5
packages/core/const/time.ts
Normal file
5
packages/core/const/time.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export const SECOND = 1000;
|
||||||
|
export const MINUTE = 60 * SECOND;
|
||||||
|
export const HOUR = 60 * MINUTE;
|
||||||
|
export const DAY = 24 * HOUR;
|
||||||
|
export const WEEK = 7 * DAY;
|
@ -21,6 +21,14 @@ export const postgresConfig = {
|
|||||||
password: databasePassword,
|
password: databasePassword,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const postgresConfigNpm = {
|
||||||
|
host: databaseHost,
|
||||||
|
port: parseInt(databasePort),
|
||||||
|
database: databaseName,
|
||||||
|
username: databaseUser,
|
||||||
|
password: databasePassword,
|
||||||
|
}
|
||||||
|
|
||||||
export const postgresConfigCred = {
|
export const postgresConfigCred = {
|
||||||
hostname: databaseHost,
|
hostname: databaseHost,
|
||||||
port: parseInt(databasePort),
|
port: parseInt(databasePort),
|
||||||
|
@ -7,6 +7,8 @@
|
|||||||
"db/": "./db/",
|
"db/": "./db/",
|
||||||
"$std/": "https://deno.land/std@0.216.0/",
|
"$std/": "https://deno.land/std@0.216.0/",
|
||||||
"mq/": "./mq/",
|
"mq/": "./mq/",
|
||||||
"chalk": "npm:chalk"
|
"chalk": "npm:chalk",
|
||||||
|
"winston": "npm:winston",
|
||||||
|
"logform": "npm:logform"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import winston, { format, transports } from "npm:winston";
|
import winston, { format, transports } from "winston";
|
||||||
import type { TransformableInfo } from "npm:logform";
|
import type { TransformableInfo } from "logform";
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
|
|
||||||
const customFormat = format.printf((info: TransformableInfo) => {
|
const customFormat = format.printf((info: TransformableInfo) => {
|
||||||
@ -52,9 +52,9 @@ const createTransport = (level: string, filename: string) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const sillyLogPath = Deno.env.get("LOG_VERBOSE") ?? "logs/verbose.log";
|
const sillyLogPath = process.env["LOG_VERBOSE"] ?? "logs/verbose.log";
|
||||||
const warnLogPath = Deno.env.get("LOG_WARN") ?? "logs/warn.log";
|
const warnLogPath = process.env["LOG_WARN"] ?? "logs/warn.log";
|
||||||
const errorLogPath = Deno.env.get("LOG_ERROR") ?? "logs/error.log";
|
const errorLogPath = process.env["LOG_ERROR"] ?? "logs/error.log";
|
||||||
|
|
||||||
const winstonLogger = winston.createLogger({
|
const winstonLogger = winston.createLogger({
|
||||||
levels: winston.config.npm.levels,
|
levels: winston.config.npm.levels,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { SlidingWindow } from "./slidingWindow.ts";
|
import type { SlidingWindow } from "./slidingWindow.ts";
|
||||||
|
|
||||||
export interface RateLimiterConfig {
|
export interface RateLimiterConfig {
|
||||||
window: SlidingWindow;
|
window: SlidingWindow;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Redis } from "ioredis";
|
import type { Redis } from "ioredis";
|
||||||
|
|
||||||
export class SlidingWindow {
|
export class SlidingWindow {
|
||||||
private redis: Redis;
|
private redis: Redis;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import networkDelegate from "@core/net/delegate.ts";
|
import networkDelegate from "@core/net/delegate.ts";
|
||||||
import { VideoInfoData, VideoInfoResponse } from "@core/net/bilibili.d.ts";
|
import type { VideoInfoData, VideoInfoResponse } from "@core/net/bilibili.d.ts";
|
||||||
import logger from "log/logger.ts";
|
import logger from "@core/log/logger.ts";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Fetch video metadata from bilibili API
|
* Fetch video metadata from bilibili API
|
7
packages/core/package.json
Normal file
7
packages/core/package.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"chalk": "^5.4.1",
|
||||||
|
"logform": "^2.7.0",
|
||||||
|
"winston": "^3.17.0"
|
||||||
|
}
|
||||||
|
}
|
18
packages/core/tsconfig.json
Normal file
18
packages/core/tsconfig.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"include": ["**/*.ts"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"paths": {
|
||||||
|
"@core/*": ["../core/*"]
|
||||||
|
},
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"noEmit": true
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,6 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/node": "^9.1.3",
|
"@astrojs/node": "^9.1.3",
|
||||||
"@astrojs/svelte": "^7.0.9",
|
"@astrojs/svelte": "^7.0.9",
|
||||||
"@astrojs/tailwind": "^6.0.2",
|
|
||||||
"@tailwindcss/vite": "^4.1.4",
|
"@tailwindcss/vite": "^4.1.4",
|
||||||
"argon2id": "^1.0.1",
|
"argon2id": "^1.0.1",
|
||||||
"astro": "^5.5.5",
|
"astro": "^5.5.5",
|
||||||
@ -39,8 +38,6 @@
|
|||||||
|
|
||||||
"@astrojs/svelte": ["@astrojs/svelte@7.0.9", "", { "dependencies": { "@sveltejs/vite-plugin-svelte": "^5.0.3", "svelte2tsx": "^0.7.35", "vite": "^6.2.4" }, "peerDependencies": { "astro": "^5.0.0", "svelte": "^5.1.16", "typescript": "^5.3.3" } }, "sha512-EpJfDh7eelYEj/zSwgSHdqJCx6YjiZmpVDEiNjxhnrBwM6Ll7hjllTrNQyfnv7KgJwaVo2SOSz6d1MwV52/T/w=="],
|
"@astrojs/svelte": ["@astrojs/svelte@7.0.9", "", { "dependencies": { "@sveltejs/vite-plugin-svelte": "^5.0.3", "svelte2tsx": "^0.7.35", "vite": "^6.2.4" }, "peerDependencies": { "astro": "^5.0.0", "svelte": "^5.1.16", "typescript": "^5.3.3" } }, "sha512-EpJfDh7eelYEj/zSwgSHdqJCx6YjiZmpVDEiNjxhnrBwM6Ll7hjllTrNQyfnv7KgJwaVo2SOSz6d1MwV52/T/w=="],
|
||||||
|
|
||||||
"@astrojs/tailwind": ["@astrojs/tailwind@6.0.2", "", { "dependencies": { "autoprefixer": "^10.4.21", "postcss": "^8.5.3", "postcss-load-config": "^4.0.2" }, "peerDependencies": { "astro": "^3.0.0 || ^4.0.0 || ^5.0.0", "tailwindcss": "^3.0.24" } }, "sha512-j3mhLNeugZq6A8dMNXVarUa8K6X9AW+QHU9u3lKNrPLMHhOQ0S7VeWhHwEeJFpEK1BTKEUY1U78VQv2gN6hNGg=="],
|
|
||||||
|
|
||||||
"@astrojs/telemetry": ["@astrojs/telemetry@3.2.0", "", { "dependencies": { "ci-info": "^4.1.0", "debug": "^4.3.7", "dlv": "^1.1.3", "dset": "^3.1.4", "is-docker": "^3.0.0", "is-wsl": "^3.1.0", "which-pm-runs": "^1.1.0" } }, "sha512-wxhSKRfKugLwLlr4OFfcqovk+LIFtKwLyGPqMsv+9/ibqqnW3Gv7tBhtKEb0gAyUAC4G9BTVQeQahqnQAhd6IQ=="],
|
"@astrojs/telemetry": ["@astrojs/telemetry@3.2.0", "", { "dependencies": { "ci-info": "^4.1.0", "debug": "^4.3.7", "dlv": "^1.1.3", "dset": "^3.1.4", "is-docker": "^3.0.0", "is-wsl": "^3.1.0", "which-pm-runs": "^1.1.0" } }, "sha512-wxhSKRfKugLwLlr4OFfcqovk+LIFtKwLyGPqMsv+9/ibqqnW3Gv7tBhtKEb0gAyUAC4G9BTVQeQahqnQAhd6IQ=="],
|
||||||
|
|
||||||
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.25.9", "", {}, "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA=="],
|
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.25.9", "", {}, "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA=="],
|
||||||
@ -543,8 +540,6 @@
|
|||||||
|
|
||||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.29.2", "", { "os": "win32", "cpu": "x64" }, "sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA=="],
|
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.29.2", "", { "os": "win32", "cpu": "x64" }, "sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA=="],
|
||||||
|
|
||||||
"lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="],
|
|
||||||
|
|
||||||
"locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="],
|
"locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="],
|
||||||
|
|
||||||
"longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="],
|
"longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="],
|
||||||
@ -719,8 +714,6 @@
|
|||||||
|
|
||||||
"postcss": ["postcss@8.5.3", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A=="],
|
"postcss": ["postcss@8.5.3", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A=="],
|
||||||
|
|
||||||
"postcss-load-config": ["postcss-load-config@4.0.2", "", { "dependencies": { "lilconfig": "^3.0.0", "yaml": "^2.3.4" }, "peerDependencies": { "postcss": ">=8.0.9", "ts-node": ">=9.0.0" }, "optionalPeers": ["postcss", "ts-node"] }, "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ=="],
|
|
||||||
|
|
||||||
"postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="],
|
"postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="],
|
||||||
|
|
||||||
"postgres-array": ["postgres-array@3.0.4", "", {}, "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ=="],
|
"postgres-array": ["postgres-array@3.0.4", "", {}, "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ=="],
|
||||||
|
@ -1,8 +1,4 @@
|
|||||||
{
|
{
|
||||||
"name": "@cvsa/frontend",
|
"name": "@cvsa/frontend",
|
||||||
"tasks": {
|
|
||||||
"preview": "bun run astro preview",
|
|
||||||
"build": "bun run astro build"
|
|
||||||
},
|
|
||||||
"exports": "./main.ts"
|
"exports": "./main.ts"
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/node": "^9.1.3",
|
"@astrojs/node": "^9.1.3",
|
||||||
"@astrojs/svelte": "^7.0.9",
|
"@astrojs/svelte": "^7.0.9",
|
||||||
"@astrojs/tailwind": "^6.0.2",
|
|
||||||
"@tailwindcss/vite": "^4.1.4",
|
"@tailwindcss/vite": "^4.1.4",
|
||||||
"argon2id": "^1.0.1",
|
"argon2id": "^1.0.1",
|
||||||
"astro": "^5.5.5",
|
"astro": "^5.5.5",
|
||||||
|
@ -51,7 +51,8 @@ async function getVideoSnapshots(aid: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getAidFromBV(bv: string) {
|
async function getAidFromBV(bv: string) {
|
||||||
const res = await client.query("SELECT aid FROM bilibili_metadata WHERE bvid = $1", [bv]);
|
const res = await client.query("SELECT aid FROM bilibili_metadata WHERE bvid = $1" +
|
||||||
|
"", [bv]);
|
||||||
if (res.rows.length <= 0) {
|
if (res.rows.length <= 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user