add: complete docker config
This commit is contained in:
parent
784939074a
commit
43b52dee0b
3
.gitignore
vendored
3
.gitignore
vendored
@ -91,5 +91,6 @@ model/
|
||||
*.db
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
db/
|
||||
data/
|
||||
|
||||
docker-compose.yml
|
30
Dockerfile.frontend
Normal file
30
Dockerfile.frontend
Normal file
@ -0,0 +1,30 @@
|
||||
FROM oven/bun AS bun-builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./packages/core ./core
|
||||
|
||||
COPY ./packages/frontend/package.json ./packages/frontend/bun.lock ./packages/frontend/tsconfig.json ./packages/frontend/astro.config.mjs ./frontend/
|
||||
|
||||
WORKDIR frontend
|
||||
|
||||
RUN bun install
|
||||
|
||||
COPY ./packages/frontend/ .
|
||||
|
||||
RUN bun run build
|
||||
|
||||
FROM node:lts-alpine
|
||||
|
||||
WORKDIR /app/frontend
|
||||
|
||||
COPY --from=bun-builder /app/frontend/dist ./dist
|
||||
COPY --from=bun-builder /app/frontend/node_modules ./node_modules
|
||||
COPY --from=bun-builder /app/frontend/package.json ./package.json
|
||||
|
||||
ENV HOST=0.0.0.0
|
||||
ENV PORT=4321
|
||||
|
||||
EXPOSE 4321
|
||||
|
||||
CMD ["node", "dist/server/entry.mjs"]
|
10
package.json
Normal file
10
package.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "cvsa",
|
||||
"version": "2.13.22",
|
||||
"private": false,
|
||||
"type": "module",
|
||||
"workspaces": [
|
||||
"packages/frontend",
|
||||
"packages/core"
|
||||
]
|
||||
}
|
@ -1,6 +1,4 @@
|
||||
import postgres from "postgres";
|
||||
import { postgresConfigNpm } from "./config";
|
||||
|
||||
const sql = postgres(postgresConfigNpm);
|
||||
|
||||
export default sql;
|
||||
export const sql = postgres(postgresConfigNpm);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import sql from "./db";
|
||||
import { sql } from "./db";
|
||||
import type { VideoSnapshotType } from "@core/db/schema.d.ts";
|
||||
|
||||
export async function getVideoSnapshots(
|
||||
|
@ -3,7 +3,7 @@ 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 sql from "./db/db.ts";
|
||||
import { sql } from "./db/db.ts";
|
||||
import { ErrorResponse, StatusResponse } from "./schema";
|
||||
|
||||
const RegistrationBodySchema = object({
|
||||
|
@ -34,7 +34,6 @@ async function insertVideoSnapshot(data: VideoInfoData) {
|
||||
}
|
||||
|
||||
export const videoInfoHandler = createHandlers(async (c: ContextType) => {
|
||||
const client = c.get("db");
|
||||
try {
|
||||
const id = await idSchema.validate(c.req.param("id"));
|
||||
let videoId: string | number = id as string;
|
||||
@ -64,7 +63,7 @@ export const videoInfoHandler = createHandlers(async (c: ContextType) => {
|
||||
|
||||
await redis.setex(cacheKey, CACHE_EXPIRATION_SECONDS, JSON.stringify(result));
|
||||
|
||||
await insertVideoSnapshot(client, result);
|
||||
await insertVideoSnapshot(result);
|
||||
|
||||
return c.json(result);
|
||||
} catch (e) {
|
||||
|
@ -4,9 +4,13 @@
|
||||
"": {
|
||||
"dependencies": {
|
||||
"chalk": "^5.4.1",
|
||||
"ioredis": "^5.6.1",
|
||||
"logform": "^2.7.0",
|
||||
"winston": "^3.17.0",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ioredis": "^5.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
@ -14,12 +18,18 @@
|
||||
|
||||
"@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=="],
|
||||
|
||||
"@ioredis/commands": ["@ioredis/commands@1.2.0", "", {}, "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg=="],
|
||||
|
||||
"@types/ioredis": ["@types/ioredis@5.0.0", "", { "dependencies": { "ioredis": "*" } }, "sha512-zJbJ3FVE17CNl5KXzdeSPtdltc4tMT3TzC6fxQS0sQngkbFZ6h+0uTafsRqu+eSLIugf6Yb0Ea0SUuRr42Nk9g=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
"cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="],
|
||||
|
||||
"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=="],
|
||||
@ -30,6 +40,10 @@
|
||||
|
||||
"colorspace": ["colorspace@1.1.4", "", { "dependencies": { "color": "^3.1.3", "text-hex": "1.0.x" } }, "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"enabled": ["enabled@2.0.0", "", {}, "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="],
|
||||
|
||||
"fecha": ["fecha@4.2.3", "", {}, "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw=="],
|
||||
@ -38,12 +52,18 @@
|
||||
|
||||
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"lodash.defaults": ["lodash.defaults@4.2.0", "", {}, "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="],
|
||||
|
||||
"lodash.isarguments": ["lodash.isarguments@3.1.0", "", {}, "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg=="],
|
||||
|
||||
"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=="],
|
||||
@ -52,6 +72,10 @@
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
@ -60,6 +84,8 @@
|
||||
|
||||
"stack-trace": ["stack-trace@0.0.10", "", {}, "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg=="],
|
||||
|
||||
"standard-as-callback": ["standard-as-callback@2.1.0", "", {}, "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="],
|
||||
|
||||
"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=="],
|
||||
|
4
packages/core/db/dbNew.ts
Normal file
4
packages/core/db/dbNew.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import postgres from "postgres";
|
||||
import { postgresConfigNpm } from "./pgConfigNew";
|
||||
|
||||
export const sql = postgres(postgresConfigNpm);
|
38
packages/core/db/pgConfigNew.ts
Normal file
38
packages/core/db/pgConfigNew.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
|
||||
};
|
@ -10,6 +10,8 @@
|
||||
"chalk": "npm:chalk",
|
||||
"winston": "npm:winston",
|
||||
"logform": "npm:logform",
|
||||
"@core/": "./"
|
||||
"@core/": "./",
|
||||
"child_process": "node:child_process",
|
||||
"util": "node:util"
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,12 @@ import logger from "log/logger.ts";
|
||||
import { RateLimiter, type RateLimiterConfig } from "mq/rateLimiter.ts";
|
||||
import { SlidingWindow } from "mq/slidingWindow.ts";
|
||||
import { redis } from "db/redis.ts";
|
||||
import Redis from "ioredis";
|
||||
import { SECOND } from "$std/datetime/constants.ts";
|
||||
import { ReplyError } from "ioredis";
|
||||
import { SECOND } from "../const/time.ts";
|
||||
import { execFile } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
|
||||
const execAsync = promisify(execFile);
|
||||
|
||||
interface Proxy {
|
||||
type: string;
|
||||
@ -99,7 +103,7 @@ class NetworkDelegate {
|
||||
await this.providerLimiters[providerLimiterId]?.trigger();
|
||||
} catch (e) {
|
||||
const error = e as Error;
|
||||
if (e instanceof Redis.ReplyError) {
|
||||
if (e instanceof ReplyError) {
|
||||
logger.error(error, "redis");
|
||||
}
|
||||
logger.warn(`Unhandled error: ${error.message}`, "mq", "proxyRequest");
|
||||
@ -209,7 +213,7 @@ class NetworkDelegate {
|
||||
return providerAvailable && proxyAvailable;
|
||||
} catch (e) {
|
||||
const error = e as Error;
|
||||
if (e instanceof Redis.ReplyError) {
|
||||
if (e instanceof ReplyError) {
|
||||
logger.error(error, "redis");
|
||||
return false;
|
||||
}
|
||||
@ -239,8 +243,7 @@ class NetworkDelegate {
|
||||
private async alicloudFcRequest<R>(url: string, region: string): Promise<R> {
|
||||
try {
|
||||
const decoder = new TextDecoder();
|
||||
const output = await new Deno.Command("aliyun", {
|
||||
args: [
|
||||
const output = await execAsync("aliyun", [
|
||||
"fc",
|
||||
"POST",
|
||||
`/2023-03-30/functions/proxy-${region}/invocations`,
|
||||
@ -258,9 +261,8 @@ class NetworkDelegate {
|
||||
"10",
|
||||
"--profile",
|
||||
`CVSA-${region}`,
|
||||
],
|
||||
}).output();
|
||||
const out = decoder.decode(output.stdout);
|
||||
])
|
||||
const out = output.stdout;
|
||||
const rawData = JSON.parse(out);
|
||||
if (rawData.statusCode !== 200) {
|
||||
// noinspection ExceptionCaughtLocallyJS
|
||||
|
@ -1,7 +1,11 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"chalk": "^5.4.1",
|
||||
"ioredis": "^5.6.1",
|
||||
"logform": "^2.7.0",
|
||||
"winston": "^3.17.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ioredis": "^5.0.0"
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"skipLibCheck": true,
|
||||
"paths": {
|
||||
"@core/*": ["../core/*"]
|
||||
"@core/*": ["./*"]
|
||||
},
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
|
@ -1,27 +0,0 @@
|
||||
FROM oven/bun AS bun-builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json bun.lock tsconfig.json astro.config.mjs ./
|
||||
|
||||
RUN bun install
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN bun run build
|
||||
|
||||
FROM node:lts-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=bun-builder /app/dist ./dist
|
||||
COPY --from=bun-builder /app/node_modules ./node_modules
|
||||
COPY --from=bun-builder /app/package.json ./package.json
|
||||
|
||||
|
||||
ENV HOST=0.0.0.0
|
||||
ENV PORT=4321
|
||||
|
||||
EXPOSE 4321
|
||||
|
||||
CMD ["node", "dist/server/entry.mjs"]
|
8
packages/frontend/src/db/bilibili_metadata/aidExists.ts
Normal file
8
packages/frontend/src/db/bilibili_metadata/aidExists.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { sql } from "@core/db/dbNew";
|
||||
|
||||
export async function aidExists(aid: number) {
|
||||
const res = await sql`
|
||||
SELECT 1 FROM bilibili_metadata WHERE aid = ${aid}
|
||||
`;
|
||||
return res.length > 0;
|
||||
}
|
15
packages/frontend/src/db/bilibili_metadata/getAidFromBV.ts
Normal file
15
packages/frontend/src/db/bilibili_metadata/getAidFromBV.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { sql } from "@core/db/dbNew";
|
||||
|
||||
export async function getAidFromBV(bv: string) {
|
||||
const res = await sql`
|
||||
SELECT aid FROM bilibili_metadata WHERE bvid = ${bv}
|
||||
`
|
||||
if (res.length <= 0) {
|
||||
return null;
|
||||
}
|
||||
const row = res[0];
|
||||
if (row && row.aid) {
|
||||
return Number(row.aid);
|
||||
}
|
||||
return null;
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
import { sql } from "@core/db/dbNew";
|
||||
|
||||
export async function getVideoMetadata(aid: number) {
|
||||
const res = await sql`
|
||||
SELECT * FROM bilibili_metadata WHERE aid = ${aid}
|
||||
`;
|
||||
if (res.length <= 0) {
|
||||
return null;
|
||||
}
|
||||
const row = res[0];
|
||||
if (row) {
|
||||
return row;
|
||||
}
|
||||
return {};
|
||||
}
|
11
packages/frontend/src/db/snapshots/getAllSnapshots.ts
Normal file
11
packages/frontend/src/db/snapshots/getAllSnapshots.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { sql } from "@core/db/dbNew";
|
||||
|
||||
export async function getAllSnapshots(aid: number) {
|
||||
const res = await sql`
|
||||
SELECT * FROM video_snapshot WHERE aid = ${aid} ORDER BY created_at DESC
|
||||
`;
|
||||
if (res.length <= 0) {
|
||||
return null;
|
||||
}
|
||||
return res;
|
||||
}
|
@ -1,16 +1,19 @@
|
||||
---
|
||||
import Layout from "@layouts/Layout.astro";
|
||||
import TitleBar from "@components/TitleBar.astro";
|
||||
import pg from "pg";
|
||||
import { format } from 'date-fns';
|
||||
import { zhCN } from 'date-fns/locale';
|
||||
import { format } from "date-fns";
|
||||
import { zhCN } from "date-fns/locale";
|
||||
import MetadataRow from "@components/InfoPage/MetadataRow.astro";
|
||||
import { getAllSnapshots } from "src/db/snapshots/getAllSnapshots";
|
||||
import { getAidFromBV } from "src/db/bilibili_metadata/getAidFromBV";
|
||||
import { getVideoMetadata } from "src/db/bilibili_metadata/getVideoMetadata";
|
||||
import { aidExists as idExists } from "src/db/bilibili_metadata/aidExists";
|
||||
|
||||
const databaseHost = import.meta.env.DB_HOST
|
||||
const databaseName = import.meta.env.DB_NAME
|
||||
const databaseUser = import.meta.env.DB_USER
|
||||
const databasePassword = import.meta.env.DB_PASSWORD
|
||||
const databasePort = import.meta.env.DB_PORT
|
||||
const databaseHost = import.meta.env.DB_HOST;
|
||||
const databaseName = import.meta.env.DB_NAME;
|
||||
const databaseUser = import.meta.env.DB_USER;
|
||||
const databasePassword = import.meta.env.DB_PASSWORD;
|
||||
const databasePort = import.meta.env.DB_PORT;
|
||||
|
||||
const postgresConfig = {
|
||||
hostname: databaseHost,
|
||||
@ -20,54 +23,9 @@ const postgresConfig = {
|
||||
password: databasePassword,
|
||||
};
|
||||
|
||||
console.log(postgresConfig)
|
||||
console.log(postgresConfig);
|
||||
|
||||
// 路由参数
|
||||
const { id } = Astro.params;
|
||||
const { Client } = pg;
|
||||
const client = new Client(postgresConfig);
|
||||
await client.connect();
|
||||
|
||||
// 数据库查询函数
|
||||
async function getVideoMetadata(aid: number) {
|
||||
const res = await client.query("SELECT * FROM bilibili_metadata WHERE aid = $1", [aid]);
|
||||
if (res.rows.length <= 0) {
|
||||
return null;
|
||||
}
|
||||
const row = res.rows[0];
|
||||
if (row) {
|
||||
return row;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
async function getVideoSnapshots(aid: number) {
|
||||
const res = await client.query("SELECT * FROM video_snapshot WHERE aid = $1 ORDER BY created_at DESC", [
|
||||
aid,
|
||||
]);
|
||||
if (res.rows.length <= 0) {
|
||||
return null;
|
||||
}
|
||||
return res.rows;
|
||||
}
|
||||
|
||||
async function getAidFromBV(bv: string) {
|
||||
const res = await client.query("SELECT aid FROM bilibili_metadata WHERE bvid = $1" +
|
||||
"", [bv]);
|
||||
if (res.rows.length <= 0) {
|
||||
return null;
|
||||
}
|
||||
const row = res.rows[0];
|
||||
if (row && row.aid) {
|
||||
return Number(row.aid);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function idExists(aid: number) {
|
||||
const res = await client.query("SELECT COUNT(*) FROM bilibili_metadata WHERE aid = $1", [aid]);
|
||||
return res.rows[0].count > 0;
|
||||
}
|
||||
|
||||
async function getVideoAid(id: string) {
|
||||
if (id.startsWith("av")) {
|
||||
@ -78,27 +36,22 @@ async function getVideoAid(id: string) {
|
||||
return parseInt(id);
|
||||
}
|
||||
|
||||
// 获取数据
|
||||
if (!id) {
|
||||
Astro.response.status = 404;
|
||||
client.end();
|
||||
return new Response(null, { status: 404 });
|
||||
}
|
||||
const aid = await getVideoAid(id);
|
||||
if (!aid || isNaN(aid)) {
|
||||
Astro.response.status = 404;
|
||||
client.end();
|
||||
return new Response(null, { status: 404 });
|
||||
}
|
||||
const aidExists = await idExists(aid);
|
||||
if (!aidExists) {
|
||||
Astro.response.status = 404;
|
||||
client.end();
|
||||
return new Response(null, { status: 404 });
|
||||
}
|
||||
const videoInfo = await getVideoMetadata(aid);
|
||||
const snapshots = await getVideoSnapshots(aid);
|
||||
client.end();
|
||||
const snapshots = await getAllSnapshots(aid);
|
||||
|
||||
interface Snapshot {
|
||||
created_at: Date;
|
||||
@ -117,7 +70,9 @@ interface Snapshot {
|
||||
<TitleBar />
|
||||
<main class="flex flex-col items-center min-h-screen gap-8 mt-10 md:mt-6 relative z-0 overflow-x-auto pb-8">
|
||||
<div class="w-full lg:max-w-4xl lg:mx-auto lg:p-6">
|
||||
<h1 class="text-2xl font-medium ml-2 mb-4">视频信息: <a href={`https://www.bilibili.com/video/av${aid}`} class="underline ">av{aid}</a></h1>
|
||||
<h1 class="text-2xl font-medium ml-2 mb-4">
|
||||
视频信息: <a href={`https://www.bilibili.com/video/av${aid}`} class="underline">av{aid}</a>
|
||||
</h1>
|
||||
|
||||
<div class="mb-6">
|
||||
<h2 class="px-2 mb-2 text-xl font-medium">基本信息</h2>
|
||||
@ -131,9 +86,19 @@ interface Snapshot {
|
||||
<MetadataRow title="描述" description={videoInfo?.description} />
|
||||
<MetadataRow title="UID" description={videoInfo?.uid} />
|
||||
<MetadataRow title="标签" description={videoInfo?.tags} />
|
||||
<MetadataRow title="发布时间" description={format(new Date(videoInfo?.published_at), 'yyyy-MM-dd HH:mm:ss', { locale: zhCN })}/>
|
||||
<MetadataRow
|
||||
title="发布时间"
|
||||
description={format(new Date(videoInfo?.published_at), "yyyy-MM-dd HH:mm:ss", {
|
||||
locale: zhCN,
|
||||
})}
|
||||
/>
|
||||
<MetadataRow title="时长 (秒)" description={videoInfo?.duration} />
|
||||
<MetadataRow title="创建时间" description={format(new Date(videoInfo?.created_at), 'yyyy-MM-dd HH:mm:ss', { locale: zhCN })}/>
|
||||
<MetadataRow
|
||||
title="创建时间"
|
||||
description={format(new Date(videoInfo?.created_at), "yyyy-MM-dd HH:mm:ss", {
|
||||
locale: zhCN,
|
||||
})}
|
||||
/>
|
||||
<MetadataRow title="封面" description={videoInfo?.cover_url} />
|
||||
</tbody>
|
||||
</table>
|
||||
@ -142,7 +107,8 @@ interface Snapshot {
|
||||
|
||||
<div>
|
||||
<h2 class="px-2 mb-2 text-xl font-medium">播放量历史数据</h2>
|
||||
{snapshots && snapshots.length > 0 ? (
|
||||
{
|
||||
snapshots && snapshots.length > 0 ? (
|
||||
<div class="overflow-x-auto px-2">
|
||||
<table class="table-auto w-full">
|
||||
<thead>
|
||||
@ -160,7 +126,11 @@ interface Snapshot {
|
||||
<tbody>
|
||||
{snapshots.map((snapshot: Snapshot) => (
|
||||
<tr>
|
||||
<td class="border dark:border-zinc-500 px-4 py-2">{format(new Date(snapshot.created_at), 'yyyy-MM-dd HH:mm:ss', { locale: zhCN })}</td>
|
||||
<td class="border dark:border-zinc-500 px-4 py-2">
|
||||
{format(new Date(snapshot.created_at), "yyyy-MM-dd HH:mm:ss", {
|
||||
locale: zhCN,
|
||||
})}
|
||||
</td>
|
||||
<td class="border dark:border-zinc-500 px-4 py-2">{snapshot.views}</td>
|
||||
<td class="border dark:border-zinc-500 px-4 py-2">{snapshot.coins}</td>
|
||||
<td class="border dark:border-zinc-500 px-4 py-2">{snapshot.likes}</td>
|
||||
@ -175,7 +145,8 @@ interface Snapshot {
|
||||
</div>
|
||||
) : (
|
||||
<p>暂无历史数据。</p>
|
||||
)}
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
@ -11,6 +11,7 @@
|
||||
"@assets/*": ["src/assets/*"],
|
||||
"@styles": ["src/styles/*"],
|
||||
"@core/*": ["../core/*"]
|
||||
}
|
||||
},
|
||||
"verbatimModuleSyntax": true
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user