diff --git a/.idea/bun.xml b/.idea/bun.xml
new file mode 100644
index 0000000..56b40f0
--- /dev/null
+++ b/.idea/bun.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..78b0220
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..79ee123
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..8ca546d
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/deno.xml b/.idea/deno.xml
index decfd9a..7d42ca2 100644
--- a/.idea/deno.xml
+++ b/.idea/deno.xml
@@ -2,5 +2,6 @@
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index 3aa665f..35a47b4 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -31,5 +31,6 @@
+
\ No newline at end of file
diff --git a/deno.json b/deno.json
index 9104dd2..2ade864 100644
--- a/deno.json
+++ b/deno.json
@@ -1,6 +1,6 @@
{
"lock": false,
- "workspace": ["./packages/crawler", "./packages/frontend", "./packages/backend", "./packages/core"],
+ "workspace": ["./packages/crawler", "./packages/core"],
"nodeModulesDir": "auto",
"tasks": {
"crawler": "deno task --filter 'crawler' all",
diff --git a/packages/backend/.prettierrc b/packages/backend/.prettierrc
new file mode 100644
index 0000000..0961adb
--- /dev/null
+++ b/packages/backend/.prettierrc
@@ -0,0 +1,8 @@
+{
+ "useTabs": true,
+ "tabWidth": 4,
+ "trailingComma": "none",
+ "singleQuote": false,
+ "printWidth": 120,
+ "endOfLine": "lf"
+}
diff --git a/packages/backend/bun.lock b/packages/backend/bun.lock
new file mode 100644
index 0000000..0dd6c14
--- /dev/null
+++ b/packages/backend/bun.lock
@@ -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=="],
+ }
+}
diff --git a/packages/backend/database.ts b/packages/backend/database.ts
deleted file mode 100644
index fdd67ec..0000000
--- a/packages/backend/database.ts
+++ /dev/null
@@ -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;
- }
-}
diff --git a/packages/backend/db/config.ts b/packages/backend/db/config.ts
new file mode 100644
index 0000000..d6cb437
--- /dev/null
+++ b/packages/backend/db/config.ts
@@ -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
+};
diff --git a/packages/backend/db/db.ts b/packages/backend/db/db.ts
new file mode 100644
index 0000000..5839327
--- /dev/null
+++ b/packages/backend/db/db.ts
@@ -0,0 +1,6 @@
+import postgres from "postgres";
+import { postgresConfigNpm } from "./config";
+
+const sql = postgres(postgresConfigNpm);
+
+export default sql;
diff --git a/packages/backend/db/videoSnapshot.ts b/packages/backend/db/videoSnapshot.ts
new file mode 100644
index 0000000..6c5ab48
--- /dev/null
+++ b/packages/backend/db/videoSnapshot.ts
@@ -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`
+ SELECT *
+ FROM video_snapshot
+ WHERE aid = ${aid}
+ ORDER BY created_at
+ LIMIT ${limit} OFFSET ${offset}
+ `;
+ } else {
+ return sql`
+ 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`
+ 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`
+ 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}
+ `;
+ }
+}
diff --git a/packages/backend/deno.json b/packages/backend/deno.json
deleted file mode 100644
index 14ce789..0000000
--- a/packages/backend/deno.json
+++ /dev/null
@@ -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"
-}
diff --git a/packages/backend/main.ts b/packages/backend/main.ts
index 5cbf42f..72d0c49 100644
--- a/packages/backend/main.ts
+++ b/packages/backend/main.ts
@@ -1,26 +1,39 @@
import { Hono } from "hono";
-import { dbCredMiddleware, dbMiddleware } from "./database.ts";
import { rootHandler } from "./root.ts";
import { getSnapshotsHanlder } from "./snapshots.ts";
import { registerHandler } from "./register.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();
-app.use("/video/*", dbMiddleware);
-app.use("/user", dbCredMiddleware);
+// const limiter = rateLimiter({
+// windowMs: 60 * MINUTE,
+// limit: 5,
+// standardHeaders: "draft-6",
+// keyGenerator: (c) => {
+// const info = getConnInfo(c as unknown as Context);
+// if (!info.remote || !info.remote.address) {
+// return crypto.randomUUID()
+// }
+// return info.remote.address;
+// },
+// });
+// app.use("/user", limiter);
app.get("/", ...rootHandler);
+app.get("/ping", ...pingHandler);
app.get("/video/:id/snapshots", ...getSnapshotsHanlder);
app.post("/user", ...registerHandler);
app.get("/video/:id/info", ...videoInfoHandler);
-const fetch = app.fetch;
-
-export default {
- fetch,
-} satisfies Deno.ServeDefaultExport;
+export default app
export const VERSION = "0.4.2";
diff --git a/packages/backend/package.json b/packages/backend/package.json
new file mode 100644
index 0000000..82c42e9
--- /dev/null
+++ b/packages/backend/package.json
@@ -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"
+ }
+}
diff --git a/packages/backend/ping.ts b/packages/backend/ping.ts
new file mode 100644
index 0000000..112ffbc
--- /dev/null
+++ b/packages/backend/ping.ts
@@ -0,0 +1,7 @@
+import { createHandlers } from "./utils.ts";
+
+export const pingHandler = createHandlers(async (c) => {
+ return c.json({
+ "message": "pong"
+ });
+});
\ No newline at end of file
diff --git a/packages/backend/register.ts b/packages/backend/register.ts
index 7964668..cc12324 100644
--- a/packages/backend/register.ts
+++ b/packages/backend/register.ts
@@ -3,63 +3,72 @@ import Argon2id from "@rabbit-company/argon2id";
import { object, string, ValidationError } from "yup";
import type { Context } from "hono";
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({
username: string().trim().required("Username is required").max(50, "Username cannot exceed 50 characters"),
password: string().required("Password is required"),
- nickname: string().optional(),
+ nickname: string().optional()
});
type ContextType = Context;
-export const userExists = async (username: string, client: Client) => {
- const query = `
- SELECT * FROM users WHERE username = $1
- `;
- const result = await client.queryObject(query, [username]);
- return result.rows.length > 0;
+export const userExists = async (username: string) => {
+ const result = await sql`
+ SELECT 1
+ FROM users
+ WHERE username = ${username}
+ `;
+ return result.length > 0;
};
export const registerHandler = createHandlers(async (c: ContextType) => {
- const client = c.get("dbCred");
-
try {
const body = await RegistrationBodySchema.validate(await c.req.json());
const { username, password, nickname } = body;
- if (await userExists(username, client)) {
- return c.json({
- message: `User "${username}" already exists.`,
- }, 400);
+ if (await userExists(username)) {
+ const response: StatusResponse = {
+ message: `User "${username}" already exists.`
+ };
+ return c.json(response, 400);
}
const hash = await Argon2id.hashEncoded(password);
- const query = `
- INSERT INTO users (username, password, nickname) VALUES ($1, $2, $3)
- `;
- await client.queryObject(query, [username, hash, nickname || null]);
+ await sql`
+ INSERT INTO users (username, password, nickname)
+ VALUES (${username}, ${hash}, ${nickname ? nickname : null})
+ `;
- return c.json({
- message: `User "${username}" registered successfully.`,
- }, 201);
+ const response: StatusResponse = {
+ message: `User "${username}" registered successfully.`
+ }
+
+ return c.json(response, 201);
} catch (e) {
if (e instanceof ValidationError) {
- return c.json({
+ const response: ErrorResponse = {
message: "Invalid registration data.",
errors: e.errors,
- }, 400);
+ code: "INVALID_PAYLOAD"
+ }
+ return c.json>(response, 400);
} else if (e instanceof SyntaxError) {
- return c.json({
- message: "Invalid JSON in request body.",
- }, 400);
+ const response: ErrorResponse = {
+ message: "Invalid JSON payload.",
+ errors: [e.message],
+ code: "INVALID_FORMAT"
+ }
+ return c.json>(response, 400);
} else {
- console.error("Registration error:", e);
- return c.json({
- message: "An unexpected error occurred during registration.",
- error: (e as Error).message,
- }, 500);
+ const response: ErrorResponse = {
+ message: "Invalid JSON payload.",
+ errors: [(e as Error).message],
+ code: "UNKNOWN_ERR"
+ }
+ return c.json>(response, 500);
}
}
});
diff --git a/packages/backend/root.ts b/packages/backend/root.ts
index aa2a1e9..f5c549e 100644
--- a/packages/backend/root.ts
+++ b/packages/backend/root.ts
@@ -17,13 +17,13 @@ export const rootHandler = createHandlers((c) => {
singer = pickSinger();
}
return c.json({
- "project": {
- "name": "中V档案馆",
- "motto": "一起唱吧,心中的歌!",
+ project: {
+ name: "中V档案馆",
+ motto: "一起唱吧,心中的歌!"
},
- "status": 200,
- "version": VERSION,
- "time": Date.now(),
- "singer": singer,
+ status: 200,
+ version: VERSION,
+ time: Date.now(),
+ singer: singer
});
});
diff --git a/packages/backend/schema.d.ts b/packages/backend/schema.d.ts
new file mode 100644
index 0000000..f6194f6
--- /dev/null
+++ b/packages/backend/schema.d.ts
@@ -0,0 +1,11 @@
+type ErrorCode = "INVALID_QUERY_PARAMS" | "UNKNOWN_ERR" | "INVALID_PAYLOAD" | "INVALID_FORMAT";
+
+export interface ErrorResponse {
+ code: ErrorCode
+ message: string;
+ errors: E[];
+}
+
+export interface StatusResponse {
+ message: string;
+}
\ No newline at end of file
diff --git a/packages/backend/singers.ts b/packages/backend/singers.ts
index a24abb9..063cc12 100644
--- a/packages/backend/singers.ts
+++ b/packages/backend/singers.ts
@@ -1,69 +1,69 @@
export const singers = [
{
- "name": "洛天依",
- "color": "#66CCFF",
- "birthday": "0712",
+ name: "洛天依",
+ color: "#66CCFF",
+ birthday: "0712"
},
{
- "name": "言和",
- "color": "#00FFCC",
- "birthday": "0711",
+ name: "言和",
+ color: "#00FFCC",
+ birthday: "0711"
},
{
- "name": "乐正绫",
- "color": "#EE0000",
- "birthday": "0412",
+ name: "乐正绫",
+ color: "#EE0000",
+ birthday: "0412"
},
{
- "name": "乐正龙牙",
- "color": "#006666",
- "birthday": "1002",
+ name: "乐正龙牙",
+ color: "#006666",
+ birthday: "1002"
},
{
- "name": "徵羽摩柯",
- "color": "#0080FF",
- "birthday": "1210",
+ name: "徵羽摩柯",
+ color: "#0080FF",
+ birthday: "1210"
},
{
- "name": "墨清弦",
- "color": "#FFFF00",
- "birthday": "0520",
+ name: "墨清弦",
+ color: "#FFFF00",
+ birthday: "0520"
},
{
- "name": "星尘",
- "color": "#9999FF",
- "birthday": "0812",
+ name: "星尘",
+ color: "#9999FF",
+ birthday: "0812"
},
{
- "name": "心华",
- "color": "#EE82EE",
- "birthday": "0210",
+ name: "心华",
+ color: "#EE82EE",
+ birthday: "0210"
},
{
- "name": "海伊",
- "color": "#3399FF",
- "birthday": "0722",
+ name: "海伊",
+ color: "#3399FF",
+ birthday: "0722"
},
{
- "name": "苍穹",
- "color": "#8BC0B5",
- "birthday": "0520",
+ name: "苍穹",
+ color: "#8BC0B5",
+ birthday: "0520"
},
{
- "name": "赤羽",
- "color": "#FF4004",
- "birthday": "1126",
+ name: "赤羽",
+ color: "#FF4004",
+ birthday: "1126"
},
{
- "name": "诗岸",
- "color": "#F6BE72",
- "birthday": "0119",
+ name: "诗岸",
+ color: "#F6BE72",
+ birthday: "0119"
},
{
- "name": "牧心",
- "color": "#2A2859",
- "birthday": "0807",
- },
+ name: "牧心",
+ color: "#2A2859",
+ birthday: "0807"
+ }
];
export interface Singer {
@@ -75,13 +75,13 @@ export interface Singer {
export const specialSingers = [
{
- "name": "雅音宫羽",
- "message": "你是我最真模样,从来不曾遗忘。",
+ name: "雅音宫羽",
+ message: "你是我最真模样,从来不曾遗忘。"
},
{
- "name": "初音未来",
- "message": "初始之音,响彻未来!",
- },
+ name: "初音未来",
+ message: "初始之音,响彻未来!"
+ }
];
export const pickSinger = () => {
diff --git a/packages/backend/snapshots.ts b/packages/backend/snapshots.ts
index 7d191d5..784ff22 100644
--- a/packages/backend/snapshots.ts
+++ b/packages/backend/snapshots.ts
@@ -1,22 +1,23 @@
import type { Context } from "hono";
import { createHandlers } from "./utils.ts";
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 { boolean, mixed, number, object, ValidationError } from "yup";
+import { ErrorResponse } from "./schema";
const SnapshotQueryParamsSchema = object({
ps: number().integer().optional().positive(),
pn: number().integer().optional().positive(),
offset: number().integer().optional().positive(),
- reverse: boolean().optional(),
+ reverse: boolean().optional()
});
export const idSchema = mixed().test(
"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',
async (value) => {
- if (value && await number().integer().isValid(value)) {
+ if (value && (await number().integer().isValid(value))) {
const v = parseInt(value as string);
return Number.isInteger(v) && v > 0;
}
@@ -34,13 +35,11 @@ export const idSchema = mixed().test(
}
return false;
- },
+ }
);
type ContextType = Context;
export const getSnapshotsHanlder = createHandlers(async (c: ContextType) => {
- const client = c.get("db");
-
try {
const idParam = await idSchema.validate(c.req.param("id"));
let videoId: string | number = idParam as string;
@@ -71,22 +70,32 @@ export const getSnapshotsHanlder = createHandlers(async (c: ContextType) => {
let result: VideoSnapshotType[];
if (typeof videoId === "number") {
- result = await getVideoSnapshots(client, videoId, limit, pageOrOffset, reverse, mode);
+ result = await getVideoSnapshots(videoId, limit, pageOrOffset, reverse, mode);
} else {
- result = await getVideoSnapshotsByBV(client, videoId, limit, pageOrOffset, reverse, mode);
+ result = await getVideoSnapshotsByBV(videoId, limit, pageOrOffset, reverse, mode);
}
const rows = result.map((row) => ({
...row,
- aid: Number(row.aid),
+ aid: Number(row.aid)
}));
return c.json(rows);
- } catch (e) {
+ } catch (e: unknown) {
if (e instanceof ValidationError) {
- return c.json({ message: "Invalid query parameters", errors: e.errors }, 400);
+ const response: ErrorResponse = {
+ code: "INVALID_QUERY_PARAMS",
+ message: "Invalid query parameters",
+ errors: e.errors,
+ }
+ return c.json>(response, 400);
} else {
- return c.json({ message: "Unhandled error", error: e }, 500);
+ const response: ErrorResponse = {
+ code: "UNKNOWN_ERR",
+ message: "Unhandled error",
+ errors: [e]
+ }
+ return c.json>(response, 500);
}
}
});
diff --git a/packages/backend/tsconfig.json b/packages/backend/tsconfig.json
new file mode 100644
index 0000000..6cbd96f
--- /dev/null
+++ b/packages/backend/tsconfig.json
@@ -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
+ }
+}
diff --git a/packages/backend/videoInfo.ts b/packages/backend/videoInfo.ts
index fd8bf89..2583807 100644
--- a/packages/backend/videoInfo.ts
+++ b/packages/backend/videoInfo.ts
@@ -1,12 +1,12 @@
-import logger from "log/logger.ts";
+import logger from "@core/log/logger.ts";
import { Redis } from "ioredis";
+import { sql } from "db/db.ts";
import { number, ValidationError } from "yup";
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 { NetSchedulerError } from "@core/net/delegate.ts";
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 { VideoInfoData } from "@core/net/bilibili.d.ts";
@@ -15,7 +15,7 @@ const CACHE_EXPIRATION_SECONDS = 60;
type ContextType = Context;
-async function insertVideoSnapshot(client: Client, data: VideoInfoData) {
+async function insertVideoSnapshot(data: VideoInfoData) {
const views = data.stat.view;
const danmakus = data.stat.danmaku;
const replies = data.stat.reply;
@@ -25,16 +25,11 @@ async function insertVideoSnapshot(client: Client, data: VideoInfoData) {
const favorites = data.stat.favorite;
const aid = data.aid;
- const query: string = `
+ await sql`
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");
}
diff --git a/packages/core/bun.lock b/packages/core/bun.lock
new file mode 100644
index 0000000..ed464c5
--- /dev/null
+++ b/packages/core/bun.lock
@@ -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=="],
+ }
+}
diff --git a/packages/core/const/time.ts b/packages/core/const/time.ts
new file mode 100644
index 0000000..f735c2f
--- /dev/null
+++ b/packages/core/const/time.ts
@@ -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;
\ No newline at end of file
diff --git a/packages/core/db/pgConfig.ts b/packages/core/db/pgConfig.ts
index cebba63..fcc0a13 100644
--- a/packages/core/db/pgConfig.ts
+++ b/packages/core/db/pgConfig.ts
@@ -21,6 +21,14 @@ export const postgresConfig = {
password: databasePassword,
};
+export const postgresConfigNpm = {
+ host: databaseHost,
+ port: parseInt(databasePort),
+ database: databaseName,
+ username: databaseUser,
+ password: databasePassword,
+}
+
export const postgresConfigCred = {
hostname: databaseHost,
port: parseInt(databasePort),
diff --git a/packages/core/deno.json b/packages/core/deno.json
index 36044b0..350449e 100644
--- a/packages/core/deno.json
+++ b/packages/core/deno.json
@@ -7,6 +7,8 @@
"db/": "./db/",
"$std/": "https://deno.land/std@0.216.0/",
"mq/": "./mq/",
- "chalk": "npm:chalk"
+ "chalk": "npm:chalk",
+ "winston": "npm:winston",
+ "logform": "npm:logform"
}
}
diff --git a/packages/core/log/logger.ts b/packages/core/log/logger.ts
index 02d8948..3d0fc64 100644
--- a/packages/core/log/logger.ts
+++ b/packages/core/log/logger.ts
@@ -1,5 +1,5 @@
-import winston, { format, transports } from "npm:winston";
-import type { TransformableInfo } from "npm:logform";
+import winston, { format, transports } from "winston";
+import type { TransformableInfo } from "logform";
import chalk from "chalk";
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 warnLogPath = Deno.env.get("LOG_WARN") ?? "logs/warn.log";
-const errorLogPath = Deno.env.get("LOG_ERROR") ?? "logs/error.log";
+const sillyLogPath = process.env["LOG_VERBOSE"] ?? "logs/verbose.log";
+const warnLogPath = process.env["LOG_WARN"] ?? "logs/warn.log";
+const errorLogPath = process.env["LOG_ERROR"] ?? "logs/error.log";
const winstonLogger = winston.createLogger({
levels: winston.config.npm.levels,
diff --git a/packages/core/mq/rateLimiter.ts b/packages/core/mq/rateLimiter.ts
index 74359a8..ac42748 100644
--- a/packages/core/mq/rateLimiter.ts
+++ b/packages/core/mq/rateLimiter.ts
@@ -1,4 +1,4 @@
-import { SlidingWindow } from "./slidingWindow.ts";
+import type { SlidingWindow } from "./slidingWindow.ts";
export interface RateLimiterConfig {
window: SlidingWindow;
diff --git a/packages/core/mq/slidingWindow.ts b/packages/core/mq/slidingWindow.ts
index 499528f..457303c 100644
--- a/packages/core/mq/slidingWindow.ts
+++ b/packages/core/mq/slidingWindow.ts
@@ -1,4 +1,4 @@
-import { Redis } from "ioredis";
+import type { Redis } from "ioredis";
export class SlidingWindow {
private redis: Redis;
diff --git a/packages/crawler/net/getVideoInfo.ts b/packages/core/net/getVideoInfo.ts
similarity index 94%
rename from packages/crawler/net/getVideoInfo.ts
rename to packages/core/net/getVideoInfo.ts
index ea87918..e13f599 100644
--- a/packages/crawler/net/getVideoInfo.ts
+++ b/packages/core/net/getVideoInfo.ts
@@ -1,6 +1,6 @@
import networkDelegate from "@core/net/delegate.ts";
-import { VideoInfoData, VideoInfoResponse } from "@core/net/bilibili.d.ts";
-import logger from "log/logger.ts";
+import type { VideoInfoData, VideoInfoResponse } from "@core/net/bilibili.d.ts";
+import logger from "@core/log/logger.ts";
/*
* Fetch video metadata from bilibili API
diff --git a/packages/core/package.json b/packages/core/package.json
new file mode 100644
index 0000000..89931f3
--- /dev/null
+++ b/packages/core/package.json
@@ -0,0 +1,7 @@
+{
+ "dependencies": {
+ "chalk": "^5.4.1",
+ "logform": "^2.7.0",
+ "winston": "^3.17.0"
+ }
+}
\ No newline at end of file
diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json
new file mode 100644
index 0000000..27dc7bc
--- /dev/null
+++ b/packages/core/tsconfig.json
@@ -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
+ }
+}
diff --git a/packages/frontend/bun.lock b/packages/frontend/bun.lock
index 8cceee4..380653d 100644
--- a/packages/frontend/bun.lock
+++ b/packages/frontend/bun.lock
@@ -6,7 +6,6 @@
"dependencies": {
"@astrojs/node": "^9.1.3",
"@astrojs/svelte": "^7.0.9",
- "@astrojs/tailwind": "^6.0.2",
"@tailwindcss/vite": "^4.1.4",
"argon2id": "^1.0.1",
"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/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=="],
"@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=="],
- "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="],
-
"locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="],
"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-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=="],
"postgres-array": ["postgres-array@3.0.4", "", {}, "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ=="],
diff --git a/packages/frontend/deno.json b/packages/frontend/deno.json
index 675c374..390c95d 100644
--- a/packages/frontend/deno.json
+++ b/packages/frontend/deno.json
@@ -1,8 +1,4 @@
{
"name": "@cvsa/frontend",
- "tasks": {
- "preview": "bun run astro preview",
- "build": "bun run astro build"
- },
"exports": "./main.ts"
}
diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index 3016d5f..5d57ced 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -11,7 +11,6 @@
"dependencies": {
"@astrojs/node": "^9.1.3",
"@astrojs/svelte": "^7.0.9",
- "@astrojs/tailwind": "^6.0.2",
"@tailwindcss/vite": "^4.1.4",
"argon2id": "^1.0.1",
"astro": "^5.5.5",
diff --git a/packages/frontend/src/pages/song/[id]/info.astro b/packages/frontend/src/pages/song/[id]/info.astro
index 7413a88..535425a 100644
--- a/packages/frontend/src/pages/song/[id]/info.astro
+++ b/packages/frontend/src/pages/song/[id]/info.astro
@@ -51,7 +51,8 @@ async function getVideoSnapshots(aid: number) {
}
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) {
return null;
}