ref: dir structure of backend package
This commit is contained in:
parent
bf00918c00
commit
01f5e57864
@ -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,
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -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,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
24
packages/backend/routes/ping/index.ts
Normal file
24
packages/backend/routes/ping/index.ts
Normal 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
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
@ -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
|
|
||||||
});
|
|
||||||
});
|
|
1
packages/backend/routes/user/index.ts
Normal file
1
packages/backend/routes/user/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./register.ts";
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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) {
|
@ -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({
|
2
packages/backend/routes/video/index.ts
Normal file
2
packages/backend/routes/video/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./[id]/info";
|
||||||
|
export * from "./[id]/snapshots";
|
@ -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";
|
||||||
|
|
||||||
|
16
packages/backend/src/routing.ts
Normal file
16
packages/backend/src/routing.ts
Normal 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);
|
||||||
|
}
|
2
packages/backend/src/schema.d.ts
vendored
2
packages/backend/src/schema.d.ts
vendored
@ -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[];
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user