diff --git a/packages/backend/middleware/captcha.ts b/packages/backend/middleware/captcha.ts index 7f9e55e..60b0d40 100644 --- a/packages/backend/middleware/captcha.ts +++ b/packages/backend/middleware/captcha.ts @@ -4,7 +4,7 @@ import { SlidingWindow } from "@core/mq/slidingWindow.ts"; import { getCaptchaConfigMaxDuration, getCurrentCaptchaDifficulty } from "@/lib/auth/captchaDifficulty.ts"; import { sqlCred } from "@core/db/dbNew.ts"; import { redis } from "@core/db/redis.ts"; -import { verify } from 'hono/jwt'; +import { verify } from "hono/jwt"; import { JwtTokenInvalid, JwtTokenExpired } from "hono/utils/jwt/types"; import { getJWTsecret } from "@/lib/auth/getJWTsecret.ts"; import { lockManager } from "@core/mq/lockManager.ts"; @@ -23,7 +23,8 @@ export const captchaMiddleware = async (c: Context, next: Next) => { if (!authHeader) { const response: ErrorResponse = { message: "'Authorization' header is missing.", - code: "UNAUTHORIZED" + code: "UNAUTHORIZED", + errors: [] }; return c.json(response, 401); } @@ -32,7 +33,8 @@ export const captchaMiddleware = async (c: Context, next: Next) => { if (!authIsBearer || authHeader.length < 8) { const response: ErrorResponse = { message: "'Authorization' header is invalid.", - code: "INVALID_HEADER" + code: "INVALID_HEADER", + errors: [] }; return c.json(response, 400); } @@ -60,47 +62,48 @@ export const captchaMiddleware = async (c: Context, next: Next) => { if (consumed) { const response: ErrorResponse = { message: "Token has already been used.", - code: "INVALID_CREDENTIALS" + code: "INVALID_CREDENTIALS", + errors: [] }; return c.json(response, 401); } if (difficulty < requiredDifficulty) { const response: ErrorResponse = { message: "Token too weak.", - code: "UNAUTHORIZED" + code: "UNAUTHORIZED", + errors: [] }; return c.json(response, 401); } const EXPIRE_FIVE_MINUTES = 300; await lockManager.acquireLock(tokenID, EXPIRE_FIVE_MINUTES); - } - catch (e) { + } catch (e) { if (e instanceof JwtTokenInvalid) { const response: ErrorResponse = { message: "Failed to verify the token.", - code: "INVALID_CREDENTIALS" + code: "INVALID_CREDENTIALS", + errors: [] }; return c.json(response, 400); - } - else if (e instanceof JwtTokenExpired) { + } else if (e instanceof JwtTokenExpired) { const response: ErrorResponse = { message: "Token expired.", - code: "INVALID_CREDENTIALS" + code: "INVALID_CREDENTIALS", + errors: [] }; return c.json(response, 400); - } - else if (e instanceof ValidationError) { + } else if (e instanceof ValidationError) { const response: ErrorResponse = { code: "INVALID_QUERY_PARAMS", message: "Invalid query parameters", errors: e.errors }; return c.json(response, 400); - } - else { + } else { const response: ErrorResponse = { message: "Unknown error.", - code: "UNKNOWN_ERROR" + code: "UNKNOWN_ERROR", + errors: [] }; return c.json(response, 500); } @@ -114,4 +117,4 @@ export const captchaMiddleware = async (c: Context, next: Next) => { await window.event(`captcha-${identifierWithIP}`); await next(); -}; \ No newline at end of file +}; diff --git a/packages/backend/routes/captcha/[id]/result/GET.ts b/packages/backend/routes/captcha/[id]/result/GET.ts index 19a4c5a..0c9c2e4 100644 --- a/packages/backend/routes/captcha/[id]/result/GET.ts +++ b/packages/backend/routes/captcha/[id]/result/GET.ts @@ -34,7 +34,8 @@ export const verifyChallengeHandler = createHandlers( if (!ans) { const response: ErrorResponse = { message: "Missing required query parameter: ans", - code: "INVALID_QUERY_PARAMS" + code: "INVALID_QUERY_PARAMS", + errors: [] }; return c.json(response, 400); } @@ -46,26 +47,30 @@ export const verifyChallengeHandler = createHandlers( code: "ENTITY_NOT_FOUND", i18n: { key: "backend.error.captcha_not_found" - } + }, + errors: [] }; return c.json(response, 401); } else if (data.error && res.status === 400) { const response: ErrorResponse = { message: data.error, - code: "INVALID_QUERY_PARAMS" + code: "INVALID_QUERY_PARAMS", + errors: [] }; return c.json(response, 400); } else if (data.error) { const response: ErrorResponse = { message: data.error, - code: "UNKNOWN_ERROR" + code: "UNKNOWN_ERROR", + errors: [] }; return c.json(response, 500); } if (!data.success) { const response: ErrorResponse = { message: "Incorrect answer", - code: "INVALID_CREDENTIALS" + code: "INVALID_CREDENTIALS", + errors: [] }; return c.json(response, 401); } diff --git a/packages/backend/routes/user/register.ts b/packages/backend/routes/user/POST.ts similarity index 100% rename from packages/backend/routes/user/register.ts rename to packages/backend/routes/user/POST.ts diff --git a/packages/backend/routes/user/index.ts b/packages/backend/routes/user/index.ts index d9f7bf7..2cf5602 100644 --- a/packages/backend/routes/user/index.ts +++ b/packages/backend/routes/user/index.ts @@ -1 +1,2 @@ -export * from "./register.ts"; +export * from "./POST.ts"; +export * from "./session/[id]/GET.ts"; diff --git a/packages/backend/routes/user/session/[id]/GET.ts b/packages/backend/routes/user/session/[id]/GET.ts new file mode 100644 index 0000000..7ed42c0 --- /dev/null +++ b/packages/backend/routes/user/session/[id]/GET.ts @@ -0,0 +1,32 @@ +import { Context } from "hono"; +import { Bindings, BlankEnv } from "hono/types"; +import { ErrorResponse } from "src/schema"; +import { createHandlers } from "src/utils.ts"; +import { sqlCred } from "@core/db/dbNew"; +import { DatabaseUserType } from "@core/db/schema"; + +export const getUserByLoginSessionHandler = createHandlers( + async (c: Context) => { + const id = c.req.param("id"); + const users = await sqlCred` + SELECT u.* + FROM users u + JOIN login_sessions ls ON u.id = ls.uid + WHERE ls.id = ${id}; + `; + if (users.length === 0) { + const response: ErrorResponse = { + message: "Cannot find user", + code: "ENTITY_NOT_FOUND", + errors: [] + }; + return c.json(response, 404); + } + const user = users[0]; + return c.json({ + username: user.username, + nickname: user.nickname, + role: user.role + }); + } +); diff --git a/packages/backend/src/routing.ts b/packages/backend/src/routing.ts index d5a7aa2..ee3886b 100644 --- a/packages/backend/src/routing.ts +++ b/packages/backend/src/routing.ts @@ -1,23 +1,23 @@ import { rootHandler } from "routes"; import { pingHandler } from "routes/ping"; -import { registerHandler } from "routes/user"; +import { getUserByLoginSessionHandler, registerHandler } from "routes/user"; import { videoInfoHandler, getSnapshotsHanlder } from "routes/video"; import { Hono } from "hono"; import { Variables } from "hono/types"; import { createCaptchaSessionHandler, verifyChallengeHandler } from "routes/captcha"; -import { getCaptchaDifficultyHandler } from "../routes/captcha/difficulty/GET.ts"; +import { getCaptchaDifficultyHandler } from "routes/captcha/difficulty/GET.ts"; 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); + app.post("/user", ...registerHandler); + app.get("/user/session/:id", ...getUserByLoginSessionHandler); + app.post("/captcha/session", ...createCaptchaSessionHandler); app.get("/captcha/:id/result", ...verifyChallengeHandler); - - app.get("/captcha/difficulty", ...getCaptchaDifficultyHandler) + app.get("/captcha/difficulty", ...getCaptchaDifficultyHandler); } diff --git a/packages/backend/src/schema.d.ts b/packages/backend/src/schema.d.ts index a1a1220..e753764 100644 --- a/packages/backend/src/schema.d.ts +++ b/packages/backend/src/schema.d.ts @@ -43,6 +43,12 @@ export interface SignUpResponse { token: string; } +export interface UserResponse { + username: string; + nickname: string | null; + role: string; +} + export type CaptchaVerificationRawResponse = { token: string; } diff --git a/packages/core/db/schema.d.ts b/packages/core/db/schema.d.ts index f10c8bc..35173d3 100644 --- a/packages/core/db/schema.d.ts +++ b/packages/core/db/schema.d.ts @@ -53,3 +53,12 @@ export interface SnapshotScheduleType { finished_at?: string; status: string; } + +export interface DatabaseUserType { + id: number; + username: string; + nickname: string | null; + password: string; + unq_id: string; + role: string; +} diff --git a/packages/core/db/videoSnapshot.ts b/packages/core/db/videoSnapshot.ts deleted file mode 100644 index eeabd58..0000000 --- a/packages/core/db/videoSnapshot.ts +++ /dev/null @@ -1,62 +0,0 @@ -import type { Client } from "https://deno.land/x/postgres@v0.19.3/mod.ts"; -import type { VideoSnapshotType } from "./schema.d.ts"; - -export async function getVideoSnapshots( - client: Client, - aid: number, - limit: number, - pageOrOffset: number, - reverse: boolean, - mode: "page" | "offset" = "page", -) { - const offset = mode === "page" ? (pageOrOffset - 1) * limit : pageOrOffset; - const queryDesc: string = ` - SELECT * - FROM video_snapshot - WHERE aid = $1 - ORDER BY created_at DESC - LIMIT $2 - OFFSET $3 - `; - const queryAsc: string = ` - SELECT * - FROM video_snapshot - WHERE aid = $1 - ORDER BY created_at - LIMIT $2 OFFSET $3 - `; - const query = reverse ? queryAsc : queryDesc; - const queryResult = await client.queryObject(query, [aid, limit, offset]); - return queryResult.rows; -} - -export async function getVideoSnapshotsByBV( - client: Client, - bv: string, - limit: number, - pageOrOffset: number, - reverse: boolean, - mode: "page" | "offset" = "page", -) { - const offset = mode === "page" ? (pageOrOffset - 1) * limit : pageOrOffset; - const queryAsc = ` - SELECT vs.* - FROM video_snapshot vs - JOIN bilibili_metadata bm ON vs.aid = bm.aid - WHERE bm.bvid = $1 - ORDER BY vs.created_at - LIMIT $2 - OFFSET $3 - `; - const queryDesc: string = ` - SELECT * - FROM video_snapshot vs - JOIN bilibili_metadata bm ON vs.aid = bm.aid - WHERE bm.bvid = $1 - ORDER BY vs.created_at DESC - LIMIT $2 OFFSET $3 - `; - const query = reverse ? queryAsc : queryDesc; - const queryResult = await client.queryObject(query, [bv, limit, offset]); - return queryResult.rows; -} diff --git a/packages/next/app/[locale]/fonts/MiSans/MiSans.css b/packages/next/app/[locale]/fonts/MiSans/MiSans.css index 1d42290..b717f5b 100644 --- a/packages/next/app/[locale]/fonts/MiSans/MiSans.css +++ b/packages/next/app/[locale]/fonts/MiSans/MiSans.css @@ -2,7 +2,7 @@ font-family: "MiSans VF"; font-style: normal; font-weight: 150 700; - font-display: swap; + font-display: optional; src: url("MiSans VF.woff2") format("woff2"); } diff --git a/packages/next/app/[locale]/page.tsx b/packages/next/app/[locale]/page.tsx index a7b466b..1e7a705 100644 --- a/packages/next/app/[locale]/page.tsx +++ b/packages/next/app/[locale]/page.tsx @@ -1,9 +1,21 @@ import { Header } from "@/components/shell/Header"; +import { fetcher } from "@/lib/net"; +import { UserResponse } from "@backend/src/schema"; +import { cookies } from "next/headers"; -export default function Home() { +export default async function Home() { + const cookieStore = await cookies(); + const sessionID = cookieStore.get("session_id"); + let user: undefined | UserResponse = undefined; + if (sessionID) { + try { + user = await fetcher(`${process.env.BACKEND_URL}/user/session/${sessionID.value}`); + } finally { + } + } return ( <> -
+

正在施工中……

在搜索栏输入BV号或AV号,可以查询目前数据库收集到的信息~

diff --git a/packages/next/app/[locale]/signup/request.tsx b/packages/next/app/[locale]/signup/request.tsx index 468d035..fd177ae 100644 --- a/packages/next/app/[locale]/signup/request.tsx +++ b/packages/next/app/[locale]/signup/request.tsx @@ -1,7 +1,7 @@ import { Dispatch, JSX, SetStateAction } from "react"; import { ApiRequestError, fetcher } from "@/lib/net"; import type { CaptchaVerificationRawResponse, ErrorResponse, SignUpResponse } from "@backend/src/schema"; -import Link from "next/link"; +import { Link } from "@/i18n/navigation"; import { LocalizedMessage } from "./SignUpForm"; import { ErrorDialog } from "./ErrorDialog"; import { string, object, ValidationError } from "yup"; diff --git a/packages/next/components/shell/Header.tsx b/packages/next/components/shell/Header.tsx index 18c8465..1e40b54 100644 --- a/packages/next/components/shell/Header.tsx +++ b/packages/next/components/shell/Header.tsx @@ -16,8 +16,13 @@ import { InfoIcon } from "@/components/icons/InfoIcon"; import { HomeIcon } from "@/components/icons/HomeIcon"; import { TextButton } from "@/components/ui/Buttons/TextButton"; import { Link } from "@/i18n/navigation"; +import { UserResponse } from "@backend/src/schema"; -export const HeaderDestop = () => { +interface HeaderProps { + user?: UserResponse; +} + +export const HeaderDestop = ({ user }: HeaderProps) => { return (
@@ -37,14 +42,19 @@ export const HeaderDestop = () => { className="inline-flex relative gap-6 h-full lg:right-12 text-xl font-medium items-center w-[15rem] min-w-[8rem] mr-4 lg:mr-0 lg:w-[305px] justify-end" > - 注册 + {user ? ( + {user.nickname || user.username} + ) : ( + 注册 + )} + 关于
); }; -export const HeaderMobile = () => { +export const HeaderMobile = ({ user }: HeaderProps) => { const [showDrawer, setShowDrawer] = useState(false); const [showsearchBox, setShowsearchBox] = useState(false); @@ -125,11 +135,11 @@ export const HeaderMobile = () => { ); }; -export const Header = () => { +export const Header = (props: HeaderProps) => { return ( <> - - + + ); };