ref: dir structure of backend package

This commit is contained in:
alikia2x (寒寒) 2025-05-10 00:20:20 +08:00
parent bf00918c00
commit 01f5e57864
Signed by: alikia2x
GPG Key ID: 56209E0CCD8420C6
23 changed files with 103 additions and 101 deletions

View File

@ -35,7 +35,7 @@ export const postgresCredConfigNpm = {
database: databaseNameCred, database: databaseNameCred,
username: databaseUser, username: databaseUser,
password: databasePassword password: databasePassword
} };
export const postgresConfigCred = { export const postgresConfigCred = {
hostname: databaseHost, hostname: databaseHost,

View File

@ -2,4 +2,4 @@ import postgres from "postgres";
import { postgresConfigNpm, postgresCredConfigNpm } from "./config"; import { postgresConfigNpm, postgresCredConfigNpm } from "./config";
export const sql = postgres(postgresConfigNpm); export const sql = postgres(postgresConfigNpm);
export const sqlCred = postgres(postgresCredConfigNpm) export const sqlCred = postgres(postgresCredConfigNpm);

View File

@ -8,7 +8,7 @@ import { logger } from "./logger.ts";
import { timing } from "hono/timing"; import { timing } from "hono/timing";
import { contentType } from "./contentType.ts"; import { contentType } from "./contentType.ts";
export function configureMiddleWares(app: Hono<{Variables: Variables }>) { export function configureMiddleWares(app: Hono<{ Variables: Variables }>) {
app.use("*", contentType); app.use("*", contentType);
app.use(timing()); app.use(timing());
app.use("*", preetifyResponse); app.use("*", preetifyResponse);

View File

@ -77,7 +77,7 @@ const defaultFormatter = (params) => {
`${methodColor} ${params.method.padEnd(6)}${reset} ${params.path}` `${methodColor} ${params.method.padEnd(6)}${reset} ${params.path}`
); );
}; };
type Ctx = Context type Ctx = Context;
export const logger = (config) => { export const logger = (config) => {
const { formatter = defaultFormatter, output = console, skipPaths = [], skip = null } = config; const { formatter = defaultFormatter, output = console, skipPaths = [], skip = null } = config;

View File

@ -1,17 +1,29 @@
import { rootHandler } from "./root/root.ts"; import { getSingerForBirthday, pickSinger, pickSpecialSinger, type Singer } from "lib/const/singers.ts";
import { pingHandler } from "./ping.ts"; import { VERSION } from "src/main.ts";
import { getSnapshotsHanlder } from "./snapshots.ts"; import { createHandlers } from "src/utils.ts";
import { registerHandler } from "./user.ts";
import { videoInfoHandler } from "db/videoInfo.ts";
import { Hono } from "hono";
import { Variables } from "hono/types";
export function configureRoutes(app: Hono<{Variables: Variables }>) { export const rootHandler = createHandlers((c) => {
app.get("/", ...rootHandler); let singer: Singer | Singer[];
app.all("/ping", ...pingHandler); const shouldShowSpecialSinger = Math.random() < 0.016;
if (getSingerForBirthday().length !== 0) {
app.get("/video/:id/snapshots", ...getSnapshotsHanlder); singer = JSON.parse(JSON.stringify(getSingerForBirthday())) as Singer[];
app.post("/user", ...registerHandler); for (const s of singer) {
delete s.birthday;
app.get("/video/:id/info", ...videoInfoHandler); s.message = `${s.name}生日快乐~`;
} }
} else if (shouldShowSpecialSinger) {
singer = pickSpecialSinger();
} else {
singer = pickSinger();
}
return c.json({
project: {
name: "中V档案馆",
motto: "一起唱吧,心中的歌!"
},
status: 200,
version: VERSION,
time: Date.now(),
singer: singer
});
});

View File

@ -1,24 +0,0 @@
import { getClientIP } from "middleware/logger.ts";
import { createHandlers } from "../src/utils.ts";
import { VERSION } from "../src/main.ts";
export const pingHandler = createHandlers(async (c) => {
const requestHeaders = c.req.raw.headers;
return c.json({
"message": "pong",
"request": {
"headers": requestHeaders,
"ip": getClientIP(c),
"mode": c.req.raw.mode,
"method": c.req.method,
"query": new URL(c.req.url).searchParams,
"body": await c.req.text(),
"url": c.req.raw.url
},
"response": {
"time": new Date().getTime(),
"status": 200,
"version": VERSION,
}
});
});

View File

@ -0,0 +1,24 @@
import { getClientIP } from "middleware/logger.ts";
import { createHandlers } from "src/utils.ts";
import { VERSION } from "src/main.ts";
export const pingHandler = createHandlers(async (c) => {
const requestHeaders = c.req.raw.headers;
return c.json({
message: "pong",
request: {
headers: requestHeaders,
ip: getClientIP(c),
mode: c.req.raw.mode,
method: c.req.method,
query: new URL(c.req.url).searchParams,
body: await c.req.text(),
url: c.req.raw.url
},
response: {
time: new Date().getTime(),
status: 200,
version: VERSION
}
});
});

View File

@ -1,29 +0,0 @@
import { getSingerForBirthday, pickSinger, pickSpecialSinger, type Singer } from "./singers.ts";
import { VERSION } from "../../src/main.ts";
import { createHandlers } from "../../src/utils.ts";
export const rootHandler = createHandlers((c) => {
let singer: Singer | Singer[];
const shouldShowSpecialSinger = Math.random() < 0.016;
if (getSingerForBirthday().length !== 0) {
singer = JSON.parse(JSON.stringify(getSingerForBirthday())) as Singer[];
for (const s of singer) {
delete s.birthday;
s.message = `${s.name}生日快乐~`;
}
} else if (shouldShowSpecialSinger) {
singer = pickSpecialSinger();
} else {
singer = pickSinger();
}
return c.json({
project: {
name: "中V档案馆",
motto: "一起唱吧,心中的歌!"
},
status: 200,
version: VERSION,
time: Date.now(),
singer: singer
});
});

View File

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

View File

@ -44,7 +44,7 @@ export const registerHandler = createHandlers(async (c: ContextType) => {
const response: StatusResponse = { const response: StatusResponse = {
message: `User '${username}' registered successfully.` message: `User '${username}' registered successfully.`
} };
return c.json<StatusResponse>(response, 201); return c.json<StatusResponse>(response, 201);
} catch (e) { } catch (e) {
@ -53,21 +53,21 @@ export const registerHandler = createHandlers(async (c: ContextType) => {
message: "Invalid registration data.", message: "Invalid registration data.",
errors: e.errors, errors: e.errors,
code: "INVALID_PAYLOAD" code: "INVALID_PAYLOAD"
} };
return c.json<ErrorResponse<string>>(response, 400); return c.json<ErrorResponse<string>>(response, 400);
} else if (e instanceof SyntaxError) { } else if (e instanceof SyntaxError) {
const response: ErrorResponse<string> = { const response: ErrorResponse<string> = {
message: "Invalid JSON payload.", message: "Invalid JSON payload.",
errors: [e.message], errors: [e.message],
code: "INVALID_FORMAT" code: "INVALID_FORMAT"
} };
return c.json<ErrorResponse<string>>(response, 400); return c.json<ErrorResponse<string>>(response, 400);
} else { } else {
const response: ErrorResponse<string> = { const response: ErrorResponse<string> = {
message: "Invalid JSON payload.", message: "Invalid JSON payload.",
errors: [(e as Error).message], errors: [(e as Error).message],
code: "UNKNOWN_ERR" code: "UNKNOWN_ERR"
} };
return c.json<ErrorResponse<string>>(response, 500); return c.json<ErrorResponse<string>>(response, 500);
} }
} }

View File

@ -1,15 +1,15 @@
import logger from "@core/log/logger.ts"; import logger from "@core/log/logger.ts";
import { redis } from "@core/db/redis.ts"; import { redis } from "@core/db/redis.ts";
import { sql } from "./db.ts"; import { sql } from "../../../db/db.ts";
import { number, ValidationError } from "yup"; import { number, ValidationError } from "yup";
import { createHandlers } from "../src/utils.ts"; import { createHandlers } from "../../../src/utils.ts";
import { getVideoInfo, getVideoInfoByBV } from "@core/net/getVideoInfo.ts"; import { getVideoInfo, getVideoInfoByBV } from "@core/net/getVideoInfo.ts";
import { idSchema } from "../routes/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 { 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";
import { startTime, endTime } from 'hono/timing' import { startTime, endTime } from "hono/timing";
const CACHE_EXPIRATION_SECONDS = 60; const CACHE_EXPIRATION_SECONDS = 60;
@ -34,7 +34,7 @@ async function insertVideoSnapshot(data: VideoInfoData) {
} }
export const videoInfoHandler = createHandlers(async (c: ContextType) => { export const videoInfoHandler = createHandlers(async (c: ContextType) => {
startTime(c, 'parse', 'Parse the request'); startTime(c, "parse", "Parse the request");
try { try {
const id = await idSchema.validate(c.req.param("id")); const id = await idSchema.validate(c.req.param("id"));
let videoId: string | number = id as string; let videoId: string | number = id as string;
@ -45,33 +45,33 @@ export const videoInfoHandler = createHandlers(async (c: ContextType) => {
} }
const cacheKey = `cvsa:videoInfo:${videoId}`; const cacheKey = `cvsa:videoInfo:${videoId}`;
endTime(c, 'parse'); endTime(c, "parse");
startTime(c, 'cache', 'Check for cached data'); startTime(c, "cache", "Check for cached data");
const cachedData = await redis.get(cacheKey); const cachedData = await redis.get(cacheKey);
endTime(c, 'cache'); endTime(c, "cache");
if (cachedData) { if (cachedData) {
return c.json(JSON.parse(cachedData)); return c.json(JSON.parse(cachedData));
} }
startTime(c, 'net', 'Fetch data'); startTime(c, "net", "Fetch data");
let result: VideoInfoData | number; let result: VideoInfoData | number;
if (typeof videoId === "number") { if (typeof videoId === "number") {
result = await getVideoInfo(videoId, "getVideoInfo"); result = await getVideoInfo(videoId, "getVideoInfo");
} else { } else {
result = await getVideoInfoByBV(videoId, "getVideoInfo"); result = await getVideoInfoByBV(videoId, "getVideoInfo");
} }
endTime(c, 'net'); endTime(c, "net");
if (typeof result === "number") { if (typeof result === "number") {
return c.json({ message: "Error fetching video info", code: result }, 500); return c.json({ message: "Error fetching video info", code: result }, 500);
} }
startTime(c, 'db', 'Write data to database'); startTime(c, "db", "Write data to database");
await redis.setex(cacheKey, CACHE_EXPIRATION_SECONDS, JSON.stringify(result)); await redis.setex(cacheKey, CACHE_EXPIRATION_SECONDS, JSON.stringify(result));
await insertVideoSnapshot(result); await insertVideoSnapshot(result);
endTime(c, 'db'); endTime(c, "db");
return c.json(result); return c.json(result);
} catch (e) { } catch (e) {
if (e instanceof ValidationError) { if (e instanceof ValidationError) {

View File

@ -1,10 +1,10 @@
import type { Context } from "hono"; import type { Context } from "hono";
import { createHandlers } from "../src/utils.ts"; import { createHandlers } from "src/utils.ts";
import type { BlankEnv, BlankInput } from "hono/types"; import type { BlankEnv, BlankInput } from "hono/types";
import { getVideoSnapshots, getVideoSnapshotsByBV } from "../db/videoSnapshot.ts"; import { getVideoSnapshots, getVideoSnapshotsByBV } from "db/snapshots.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 "../src/schema"; import { ErrorResponse } from "src/schema";
import { startTime, endTime } from "hono/timing"; import { startTime, endTime } from "hono/timing";
const SnapshotQueryParamsSchema = object({ const SnapshotQueryParamsSchema = object({

View File

@ -0,0 +1,2 @@
export * from "./[id]/info";
export * from "./[id]/snapshots";

View File

@ -1,7 +1,7 @@
import { Hono } from "hono"; import { Hono } from "hono";
import type { TimingVariables } from "hono/timing"; import type { TimingVariables } from "hono/timing";
import { startServer } from "./startServer.ts"; import { startServer } from "./startServer.ts";
import { configureRoutes } from "routes"; import { configureRoutes } from "./routing.ts";
import { configureMiddleWares } from "middleware"; import { configureMiddleWares } from "middleware";
import { notFoundRoute } from "routes/404.ts"; import { notFoundRoute } from "routes/404.ts";

View File

@ -0,0 +1,16 @@
import { rootHandler } from "routes";
import { pingHandler } from "routes/ping";
import { registerHandler } from "routes/user";
import { videoInfoHandler, getSnapshotsHanlder } from "routes/video";
import { Hono } from "hono";
import { Variables } from "hono/types";
export function configureRoutes(app: Hono<{ Variables: Variables }>) {
app.get("/", ...rootHandler);
app.all("/ping", ...pingHandler);
app.get("/video/:id/snapshots", ...getSnapshotsHanlder);
app.post("/user", ...registerHandler);
app.get("/video/:id/info", ...videoInfoHandler);
}

View File

@ -1,7 +1,7 @@
type ErrorCode = "INVALID_QUERY_PARAMS" | "UNKNOWN_ERR" | "INVALID_PAYLOAD" | "INVALID_FORMAT" | "BODY_TOO_LARGE"; type ErrorCode = "INVALID_QUERY_PARAMS" | "UNKNOWN_ERR" | "INVALID_PAYLOAD" | "INVALID_FORMAT" | "BODY_TOO_LARGE";
export interface ErrorResponse<E> { export interface ErrorResponse<E> {
code: ErrorCode code: ErrorCode;
message: string; message: string;
errors: E[]; errors: E[];
} }

View File

@ -32,7 +32,7 @@ function logStartup(hostname: string, port: number, wasAutoIncremented: boolean,
console.log("\nPress Ctrl+C to quit."); console.log("\nPress Ctrl+C to quit.");
} }
export async function startServer(app: Hono<{Variables: Variables }>) { export async function startServer(app: Hono<{ Variables: Variables }>) {
const NODE_ENV = process.env.NODE_ENV || "production"; const NODE_ENV = process.env.NODE_ENV || "production";
const HOST = process.env.HOST ?? (NODE_ENV === "development" ? "0.0.0.0" : "127.0.0.1"); const HOST = process.env.HOST ?? (NODE_ENV === "development" ? "0.0.0.0" : "127.0.0.1");
const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : undefined; const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : undefined;