From c55cfb36fc4f878e44e05ca2f1129d0fd13d112d Mon Sep 17 00:00:00 2001 From: alikia2x Date: Wed, 31 Dec 2025 21:03:27 +0800 Subject: [PATCH] ref: format with biome --- ecosystem.config.mjs | 54 ++++----- packages/backend/lib/auth.ts | 16 +-- packages/backend/lib/bilibiliID.ts | 4 +- packages/backend/lib/schema.ts | 62 +++++----- packages/backend/lib/singers.ts | 72 +++++------ packages/backend/middlewares/auth.ts | 12 +- packages/backend/middlewares/openapi.ts | 2 +- packages/backend/middlewares/timing.ts | 2 +- packages/backend/routes/auth/login.ts | 30 ++--- packages/backend/routes/auth/logout.ts | 14 +-- packages/backend/routes/auth/user.ts | 4 +- packages/backend/routes/ping/index.ts | 20 +-- packages/backend/routes/root/index.ts | 40 +++--- packages/backend/routes/search/index.ts | 74 ++++++------ packages/backend/routes/song/add.ts | 60 ++++----- packages/backend/routes/song/delete.ts | 24 ++-- packages/backend/routes/song/milestone.ts | 34 +++--- packages/backend/routes/video/eta.ts | 26 ++-- packages/backend/routes/video/label.ts | 14 +-- packages/backend/routes/video/metadata.ts | 26 ++-- packages/backend/routes/video/snapshots.ts | 40 +++--- packages/backend/src/mq.ts | 4 +- packages/backend/src/onAfterHandle.ts | 2 +- packages/backend/src/schema.ts | 2 +- packages/cf-worker/src/index.ts | 23 ++-- packages/cf-worker/worker-configuration.d.ts | 14 +-- packages/core/db/pgConfigNew.ts | 12 +- packages/core/db/redis.ts | 2 +- packages/core/drizzle.config.ts | 4 +- packages/core/drizzle/drizzle-cred.config.ts | 4 +- packages/core/drizzle/drizzle-main.config.ts | 4 +- packages/core/log/index.ts | 38 +++--- packages/core/mq/multipleRateLimiter.ts | 4 +- packages/core/net/services.ts | 8 +- packages/crawler/db/bilibili_metadata.ts | 2 +- packages/crawler/ml/api_manager.ts | 14 +-- packages/crawler/mq/exec/archiveSnapshots.ts | 2 +- packages/crawler/mq/exec/getLatestVideos.ts | 3 +- packages/crawler/mq/exec/getVideoInfo.ts | 30 ++--- packages/crawler/mq/exec/snapshotTick.ts | 6 +- packages/crawler/mq/exec/snapshotVideo.ts | 2 +- packages/crawler/mq/task/getVideoStats.ts | 26 ++-- packages/crawler/mq/task/queueLatestVideo.ts | 11 +- packages/crawler/src/filterWorker.ts | 2 +- packages/crawler/src/worker.ts | 6 +- packages/palette/src/App.tsx | 2 +- packages/palette/src/colorTokens.ts | 32 ++--- .../palette/src/components/ColorBlock.tsx | 2 +- .../palette/src/components/Picker/Handle.tsx | 2 +- .../palette/src/components/Picker/Slider.tsx | 4 +- .../src/components/Picker/useOklchCanvas.tsx | 4 +- .../palette/src/components/Picker/utils.ts | 6 +- .../app/components/ui/button.tsx | 26 ++-- .../temp_frontend/app/components/ui/chart.tsx | 8 +- .../app/components/ui/sonner.tsx | 2 +- .../app/routes/home/Milestone.tsx | 2 +- packages/temp_frontend/app/routes/login.tsx | 2 +- .../app/routes/song/[id]/info/columns.tsx | 20 +-- .../app/routes/song/[id]/info/data-table.tsx | 4 +- .../app/routes/song/[id]/info/index.tsx | 2 +- .../app/routes/song/[id]/info/lib.ts | 24 ++-- .../routes/song/[id]/info/snapshotsView.tsx | 24 ++-- .../app/routes/song/[id]/info/views-chart.tsx | 16 +-- packages/tracker/app/admin/users.tsx | 14 +-- .../app/components/project/ProjectDialog.tsx | 2 +- .../app/components/project/UserSearch.tsx | 2 +- .../tracker/app/components/task/TaskForm.tsx | 10 +- packages/tracker/app/components/ui/badge.tsx | 10 +- packages/tracker/app/components/ui/button.tsx | 30 ++--- .../tracker/app/components/ui/calendar.tsx | 114 +++++++++--------- .../tracker/app/components/ui/sidebar.tsx | 26 ++-- packages/tracker/app/components/ui/sonner.tsx | 12 +- packages/tracker/app/projects/newProject.tsx | 14 +-- packages/tracker/app/projects/projectPage.tsx | 22 ++-- .../tracker/app/projects/projectPageAction.ts | 34 +++--- packages/tracker/app/projects/settings.tsx | 12 +- packages/tracker/app/root.tsx | 8 +- packages/tracker/app/setup/setup.tsx | 2 +- packages/tracker/app/user/profile.tsx | 8 +- packages/tracker/drizzle.config.ts | 6 +- packages/tracker/lib/auth.ts | 10 +- packages/tracker/lib/db/schema.ts | 56 ++++----- src/aliyun-fc.mjs | 10 +- src/fullSnapshot.ts | 2 +- 84 files changed, 721 insertions(+), 720 deletions(-) diff --git a/ecosystem.config.mjs b/ecosystem.config.mjs index 4a91c9b..f81cfa2 100644 --- a/ecosystem.config.mjs +++ b/ecosystem.config.mjs @@ -2,52 +2,52 @@ import "dotenv/config"; export const apps = [ { + cwd: "./packages/crawler", + interpreter: "bun", name: "crawler-jobadder", script: "src/jobAdder.wrapper.ts", - cwd: "./packages/crawler", - interpreter: "bun", }, { + cwd: "./packages/crawler", + env: { + LOG_ERR: "logs/error.log", + LOG_VERBOSE: "logs/verbose.log", + LOG_WARN: "logs/warn.log", + }, + interpreter: "bun", name: "crawler-worker", script: "src/worker.ts", - cwd: "./packages/crawler", - interpreter: "bun", - env: { - LOG_VERBOSE: "logs/verbose.log", - LOG_WARN: "logs/warn.log", - LOG_ERR: "logs/error.log", - }, }, { + cwd: "./packages/crawler", + env: { + LOG_ERR: "logs/error.log", + LOG_VERBOSE: "logs/verbose.log", + LOG_WARN: "logs/warn.log", + }, + interpreter: "bun", name: "crawler-filter", script: "src/filterWorker.wrapper.ts", - cwd: "./packages/crawler", - interpreter: "bun", - env: { - LOG_VERBOSE: "logs/verbose.log", - LOG_WARN: "logs/warn.log", - LOG_ERR: "logs/error.log", - }, }, { + cwd: "./ml/api", + env: { + LOG_ERR: "logs/error.log", + LOG_VERBOSE: "logs/verbose.log", + LOG_WARN: "logs/warn.log", + PYTHONPATH: "./ml/api:./ml/filter", + }, + interpreter: process.env.PYTHON_INTERPRETER || "python3", name: "ml-api", script: "start.py", - cwd: "./ml/api", - interpreter: process.env.PYTHON_INTERPRETER || "python3", - env: { - PYTHONPATH: "./ml/api:./ml/filter", - LOG_VERBOSE: "logs/verbose.log", - LOG_WARN: "logs/warn.log", - LOG_ERR: "logs/error.log", - }, }, { - name: "cvsa-be", - script: "src/index.ts", cwd: "./packages/backend", - interpreter: "bun", env: { NODE_ENV: "production", }, + interpreter: "bun", + name: "cvsa-be", + script: "src/index.ts", }, ]; diff --git a/packages/backend/lib/auth.ts b/packages/backend/lib/auth.ts index 750e157..2584a6c 100644 --- a/packages/backend/lib/auth.ts +++ b/packages/backend/lib/auth.ts @@ -31,12 +31,12 @@ export async function verifyUser( } return { + createdAt: foundUser.createdAt, id: foundUser.id, - username: foundUser.username, nickname: foundUser.nickname, role: foundUser.role, unqId: foundUser.unqId, - createdAt: foundUser.createdAt, + username: foundUser.username, }; } @@ -52,12 +52,12 @@ export async function createSession( try { await db.insert(loginSessionsInCredentials).values({ - id: sessionId, - uid: userId, - ipAddress, - userAgent, - lastUsedAt: new Date().toISOString(), expireAt: expireAt.toISOString(), + id: sessionId, + ipAddress, + lastUsedAt: new Date().toISOString(), + uid: userId, + userAgent, }); } catch (error) { logger.error(error as Error); @@ -107,8 +107,8 @@ export async function validateSession( .where(eq(loginSessionsInCredentials.id, sessionId)); return { - user: users[0], session: session, + user: users[0], }; } diff --git a/packages/backend/lib/bilibiliID.ts b/packages/backend/lib/bilibiliID.ts index 1898931..5be58ad 100644 --- a/packages/backend/lib/bilibiliID.ts +++ b/packages/backend/lib/bilibiliID.ts @@ -38,13 +38,13 @@ const avSchema = z.string().regex(AV_REGEX); export function detectBiliID(id: string) { if (bvSchema.safeParse(id).success) { return { - type: "bv" as const, id: id as `BV1${string}`, + type: "bv" as const, }; } else if (avSchema.safeParse(id).success) { return { - type: "av" as const, id: id as `av${string}`, + type: "av" as const, }; } return null; diff --git a/packages/backend/lib/schema.ts b/packages/backend/lib/schema.ts index ca4de6f..7b58fd8 100644 --- a/packages/backend/lib/schema.ts +++ b/packages/backend/lib/schema.ts @@ -2,69 +2,69 @@ import { z } from "zod"; const videoStatsSchema = z.object({ aid: z.number(), - view: z.number(), - danmaku: z.number(), - reply: z.number(), - favorite: z.number(), coin: z.number(), - share: z.number(), - now_rank: z.number(), + danmaku: z.number(), + favorite: z.number(), his_rank: z.number(), like: z.number(), + now_rank: z.number(), + reply: z.number(), + share: z.number(), + view: z.number(), }); export const BiliAPIVideoMetadataSchema = z.object({ - bvid: z.string(), aid: z.number(), + bvid: z.string(), copyright: z.number(), - pic: z.string(), - title: z.string(), - pubdate: z.number(), ctime: z.number(), desc: z.string(), desc_v2: z.string(), - tname: z.string(), - tid: z.number(), - tid_v2: z.number(), - tname_v2: z.string(), - state: z.number(), duration: z.number(), owner: z.object({ + face: z.string(), mid: z.number(), name: z.string(), - face: z.string(), }), + pic: z.string(), + pubdate: z.number(), stat: videoStatsSchema, + state: z.number(), + tid: z.number(), + tid_v2: z.number(), + title: z.string(), + tname: z.string(), + tname_v2: z.string(), }); export const BiliVideoSchema = z.object({ - duration: z.number().nullable(), - id: z.number(), aid: z.number(), - publishedAt: z.string().nullable(), + bvid: z.string().nullable(), + coverUrl: z.string().nullable(), createdAt: z.string().nullable(), description: z.string().nullable(), - bvid: z.string().nullable(), - uid: z.number().nullable(), + duration: z.number().nullable(), + id: z.number(), + publishedAt: z.string().nullable(), + status: z.number(), tags: z.string().nullable(), title: z.string().nullable(), - status: z.number(), - coverUrl: z.string().nullable(), + uid: z.number().nullable(), }); export type BiliVideoType = z.infer; export const SongSchema = z.object({ - duration: z.number().nullable(), - name: z.string().nullable(), - id: z.number(), aid: z.number().nullable(), + createdAt: z.string(), + deleted: z.boolean(), + duration: z.number().nullable(), + id: z.number(), + image: z.string().nullable(), + name: z.string().nullable(), + neteaseId: z.number().nullable(), + producer: z.string().nullable(), publishedAt: z.string().nullable(), type: z.number().nullable(), - neteaseId: z.number().nullable(), - createdAt: z.string(), updatedAt: z.string(), - deleted: z.boolean(), - image: z.string().nullable(), - producer: z.string().nullable(), }); diff --git a/packages/backend/lib/singers.ts b/packages/backend/lib/singers.ts index 10518a9..1421a82 100644 --- a/packages/backend/lib/singers.ts +++ b/packages/backend/lib/singers.ts @@ -7,100 +7,100 @@ export interface Singer { export const singers: Singer[] = [ { - name: "洛天依", - color: "#66CCFF", birthday: "0712", + color: "#66CCFF", + name: "洛天依", }, { - name: "言和", - color: "#00FFCC", birthday: "0711", + color: "#00FFCC", + name: "言和", }, { - name: "乐正绫", - color: "#EE0000", birthday: "0412", + color: "#EE0000", + name: "乐正绫", }, { - name: "乐正龙牙", - color: "#006666", birthday: "1002", + color: "#006666", + name: "乐正龙牙", }, { - name: "徵羽摩柯", - color: "#0080FF", birthday: "1210", + color: "#0080FF", + name: "徵羽摩柯", }, { - name: "墨清弦", + birthday: "0520", color: "#FFFF00", - birthday: "0520", + name: "墨清弦", }, { - name: "星尘", - color: "#9999FF", birthday: "0812", + color: "#9999FF", + name: "星尘", }, { - name: "永夜Minus", - color: "#613c8a", birthday: "1208", + color: "#613c8a", + name: "永夜Minus", }, { - name: "心华", - color: "#EE82EE", birthday: "0210", + color: "#EE82EE", + name: "心华", }, { - name: "海伊", - color: "#3399FF", birthday: "0722", + color: "#3399FF", + name: "海伊", }, { - name: "苍穹", - color: "#8BC0B5", birthday: "0520", + color: "#8BC0B5", + name: "苍穹", }, { - name: "赤羽", - color: "#FF4004", birthday: "1126", + color: "#FF4004", + name: "赤羽", }, { - name: "诗岸", - color: "#F6BE72", birthday: "0119", + color: "#F6BE72", + name: "诗岸", }, { - name: "牧心", - color: "#2A2859", birthday: "0807", + color: "#2A2859", + name: "牧心", }, { - name: "起礼", + birthday: "0713", color: "#FF0099", - birthday: "0713", + name: "起礼", }, { - name: "起复", + birthday: "0713", color: "#99FF00", - birthday: "0713", + name: "起复", }, { - name: "夏语遥", - color: "#34CCCC", birthday: "1110", + color: "#34CCCC", + name: "夏语遥", }, ]; export const specialSingers = [ { - name: "雅音宫羽", message: "你是我最真模样,从来不曾遗忘。", + name: "雅音宫羽", }, { - name: "初音未来", message: "初始之音,响彻未来!", + name: "初音未来", }, ]; diff --git a/packages/backend/middlewares/auth.ts b/packages/backend/middlewares/auth.ts index 9828d5c..becff6a 100644 --- a/packages/backend/middlewares/auth.ts +++ b/packages/backend/middlewares/auth.ts @@ -40,9 +40,9 @@ export const requireAuth = new Elysia({ name: "require-auth" }) if (!sessionId) { set.status = 401; return { - user: null, - session: null, isAuthenticated: false, + session: null, + user: null, }; } @@ -52,17 +52,17 @@ export const requireAuth = new Elysia({ name: "require-auth" }) if (!validationResult) { set.status = 401; return { - user: null, - session: null, isAuthenticated: false, + session: null, + user: null, }; } // Session is valid, return user and session context return { - user: validationResult.user, - session: validationResult.session, isAuthenticated: true, + session: validationResult.session, + user: validationResult.user, }; }) .onBeforeHandle({ as: "scoped" }, ({ user, status }) => { diff --git a/packages/backend/middlewares/openapi.ts b/packages/backend/middlewares/openapi.ts index 2112d18..5000c9f 100644 --- a/packages/backend/middlewares/openapi.ts +++ b/packages/backend/middlewares/openapi.ts @@ -14,8 +14,8 @@ export const openAPIMiddleware = openapi({ }, references: fromTypes(), scalar: { - theme: "kepler", hideClientButton: true, hideDarkModeToggle: true, + theme: "kepler", }, }); diff --git a/packages/backend/middlewares/timing.ts b/packages/backend/middlewares/timing.ts index 1144fdb..20475ef 100644 --- a/packages/backend/middlewares/timing.ts +++ b/packages/backend/middlewares/timing.ts @@ -13,8 +13,8 @@ class TimeLogger { getCompletedDurations() { return Array.from(this.durations.entries()).map(([name, duration]) => ({ - name, duration, + name, })); } diff --git a/packages/backend/routes/auth/login.ts b/packages/backend/routes/auth/login.ts index b00bb1b..9d2913e 100644 --- a/packages/backend/routes/auth/login.ts +++ b/packages/backend/routes/auth/login.ts @@ -25,41 +25,41 @@ export const loginHandler = new Elysia({ prefix: "/auth" }).use(ip()).post( return { message: "You are logged in.", + sessionID: sessionId, user: { id: user.id, - username: user.username, nickname: user.nickname, role: user.role, + username: user.username, }, - sessionID: sessionId, }; }, { + body: t.Object({ + password: t.String(), + username: t.String(), + }), + detail: { + description: + "This endpoint authenticates users by verifying their credentials and creates a new session. \ + Upon successful authentication, it returns user information and sets a secure HTTP-only cookie \ + for session management. The session includes IP address and user agent tracking for security purposes.", + summary: "User login", + }, response: { 200: t.Object({ message: t.String(), + sessionID: t.String(), user: t.Object({ id: t.Integer(), - username: t.String(), nickname: t.Optional(t.String()), role: t.String(), + username: t.String(), }), - sessionID: t.String(), }), 401: t.Object({ message: t.String(), }), }, - body: t.Object({ - username: t.String(), - password: t.String(), - }), - detail: { - summary: "User login", - description: - "This endpoint authenticates users by verifying their credentials and creates a new session. \ - Upon successful authentication, it returns user information and sets a secure HTTP-only cookie \ - for session management. The session includes IP address and user agent tracking for security purposes.", - }, } ); diff --git a/packages/backend/routes/auth/logout.ts b/packages/backend/routes/auth/logout.ts index 814879c..4dce445 100644 --- a/packages/backend/routes/auth/logout.ts +++ b/packages/backend/routes/auth/logout.ts @@ -18,6 +18,13 @@ export const logoutHandler = new Elysia({ prefix: "/auth" }).use(requireAuth).de return { message: "Successfully logged out." }; }, { + detail: { + description: + "This endpoint logs out the current user by deactivating their session and removing the session cookie. \ + It requires an active session cookie to be present in the request. After successful logout, the session \ + is invalidated and cannot be used again.", + summary: "Logout current session", + }, response: { 200: t.Object({ message: t.String(), @@ -26,12 +33,5 @@ export const logoutHandler = new Elysia({ prefix: "/auth" }).use(requireAuth).de message: t.String(), }), }, - detail: { - summary: "Logout current session", - description: - "This endpoint logs out the current user by deactivating their session and removing the session cookie. \ - It requires an active session cookie to be present in the request. After successful logout, the session \ - is invalidated and cannot be used again.", - }, } ); diff --git a/packages/backend/routes/auth/user.ts b/packages/backend/routes/auth/user.ts index d1e0837..716d41d 100644 --- a/packages/backend/routes/auth/user.ts +++ b/packages/backend/routes/auth/user.ts @@ -9,18 +9,18 @@ export const getCurrentUserHandler = new Elysia().use(requireAuth).get( } return { id: user.id, - username: user.username, nickname: user.nickname, role: user.role, + username: user.username, }; }, { response: { 200: t.Object({ id: t.Integer(), - username: t.String(), nickname: t.Union([t.String(), t.Null()]), role: t.String(), + username: t.String(), }), 401: t.Object({ message: t.String(), diff --git a/packages/backend/routes/ping/index.ts b/packages/backend/routes/ping/index.ts index 191c7fa..c5f8643 100644 --- a/packages/backend/routes/ping/index.ts +++ b/packages/backend/routes/ping/index.ts @@ -8,42 +8,42 @@ export const pingHandler = new Elysia({ prefix: "/ping" }).use(ip()).get( return { message: "pong", request: { + body: body, headers: headers, ip: ip, method: request.method, - body: body, url: request.url, }, response: { - time: Date.now(), status: 200, + time: Date.now(), version: VERSION, }, }; }, { + body: t.Optional(t.String()), + detail: { + description: + "This endpoint returns a 'pong' message along with comprehensive information about the incoming request and the server's current status, including request headers, IP address, and server version. It's useful for monitoring API availability and debugging.", + summary: "Send a ping", + }, response: { 200: t.Object({ message: t.String(), request: t.Object({ + body: t.Optional(t.Union([t.String(), t.Null()])), headers: t.Any(), ip: t.Optional(t.String()), method: t.String(), - body: t.Optional(t.Union([t.String(), t.Null()])), url: t.String(), }), response: t.Object({ - time: t.Number(), status: t.Number(), + time: t.Number(), version: t.String(), }), }), }, - body: t.Optional(t.String()), - detail: { - summary: "Send a ping", - description: - "This endpoint returns a 'pong' message along with comprehensive information about the incoming request and the server's current status, including request headers, IP address, and server version. It's useful for monitoring API availability and debugging.", - }, } ); diff --git a/packages/backend/routes/root/index.ts b/packages/backend/routes/root/index.ts index 4d25daf..2949d45 100644 --- a/packages/backend/routes/root/index.ts +++ b/packages/backend/routes/root/index.ts @@ -8,10 +8,10 @@ import { VERSION } from "@backend/src"; import { Elysia, t } from "elysia"; const SingerObj = t.Object({ - name: t.String(), - color: t.Optional(t.String()), birthday: t.Optional(t.String()), + color: t.Optional(t.String()), message: t.Optional(t.String()), + name: t.String(), }); export const rootHandler = new Elysia().get( @@ -32,35 +32,35 @@ export const rootHandler = new Elysia().get( } return { project: { - name: "中V档案馆", mascot: "知夏", + name: "中V档案馆", quote: "星河知海夏生光", }, - status: 200, - version: VERSION, - time: Date.now(), singer: singer, + status: 200, + time: Date.now(), + version: VERSION, }; }, { - response: { - 200: t.Object({ - project: t.Object({ - name: t.String(), - mascot: t.String(), - quote: t.String(), - }), - status: t.Number(), - version: t.String(), - time: t.Number(), - singer: t.Union([SingerObj, t.Array(SingerObj)]), - }), - }, detail: { - summary: "Root route", description: "The root path. It returns a JSON object containing a random virtual singer, \ backend version, current server time and other miscellaneous information.", + summary: "Root route", + }, + response: { + 200: t.Object({ + project: t.Object({ + mascot: t.String(), + name: t.String(), + quote: t.String(), + }), + singer: t.Union([SingerObj, t.Array(SingerObj)]), + status: t.Number(), + time: t.Number(), + version: t.String(), + }), }, } ); diff --git a/packages/backend/routes/search/index.ts b/packages/backend/routes/search/index.ts index 9224e60..84e18cb 100644 --- a/packages/backend/routes/search/index.ts +++ b/packages/backend/routes/search/index.ts @@ -23,11 +23,11 @@ const getSongSearchResult = async (searchQuery: string) => { const lengthRatio = searchQuery.length / song.songs.name.length; const viewsLog = Math.log10(song.latest_video_snapshot.views + 1); return { - type: "song" as "song", data: song, - occurrences, - viewsLog, lengthRatio, + occurrences, + type: "song" as "song", + viewsLog, }; }) .filter((d) => d !== null); @@ -52,9 +52,9 @@ const getSongSearchResult = async (searchQuery: string) => { normalizedOccurrences * 0.3 + result.lengthRatio * 0.5 + normalizedViewsLog * 0.2; return { - type: result.type, data: result.data.songs, rank: Math.min(Math.max(rank, 0), 1), // Ensure rank is between 0 and 1 + type: result.type, }; }); @@ -70,9 +70,9 @@ const getDBVideoSearchResult = async (searchQuery: string) => { .innerJoin(latestVideoSnapshot, eq(bilibiliMetadata.aid, latestVideoSnapshot.aid)) .where(eq(bilibiliMetadata.aid, aid)); return results.map((video) => ({ - type: "bili-video-db" as "bili-video-db", data: { views: video.latest_video_snapshot.views, ...video.bilibili_metadata }, rank: 1, // Exact match + type: "bili-video-db" as "bili-video-db", })); }; @@ -92,9 +92,9 @@ const getVideoSearchResult = async (searchQuery: string) => { } return [ { - type: "bili-video" as "bili-video", data: data, rank: 0.99, // Exact match + type: "bili-video" as "bili-video", }, ]; }; @@ -123,43 +123,43 @@ export const searchHandler = new Elysia({ prefix: "/search" }).get( }; }, { - response: { - 200: z.object({ - elapsedMs: z.number(), - data: z.array( - z.union([ - z.object({ - type: z.literal("song"), - data: SongSchema, - rank: z.number(), - }), - z.object({ - type: z.literal("bili-video-db"), - data: BiliVideoDataSchema, - rank: z.number(), - }), - z.object({ - type: z.literal("bili-video"), - data: BiliAPIVideoMetadataSchema, - rank: z.number(), - }), - ]) - ), - }), - 404: z.object({ - message: z.string(), - }), - }, - query: z.object({ - query: z.string(), - }), detail: { - summary: "Search songs and videos", description: "This endpoint performs a comprehensive search across songs and videos in the database. \ It searches for songs by name and videos by bilibili ID (av/BV format). The results are ranked \ by relevance using a weighted algorithm that considers search term frequency, title length, \ and view count. Returns search results with performance timing information.", + summary: "Search songs and videos", + }, + query: z.object({ + query: z.string(), + }), + response: { + 200: z.object({ + data: z.array( + z.union([ + z.object({ + data: SongSchema, + rank: z.number(), + type: z.literal("song"), + }), + z.object({ + data: BiliVideoDataSchema, + rank: z.number(), + type: z.literal("bili-video-db"), + }), + z.object({ + data: BiliAPIVideoMetadataSchema, + rank: z.number(), + type: z.literal("bili-video"), + }), + ]) + ), + elapsedMs: z.number(), + }), + 404: z.object({ + message: z.string(), + }), }, } ); diff --git a/packages/backend/routes/song/add.ts b/packages/backend/routes/song/add.ts index fef6e43..75569dd 100644 --- a/packages/backend/routes/song/add.ts +++ b/packages/backend/routes/song/add.ts @@ -40,15 +40,26 @@ export const addSongHandler = new Elysia() }); } return status(201, { - message: "Successfully created import session.", jobID: job.id, + message: "Successfully created import session.", }); }, { + body: t.Object({ + id: t.String(), + }), + detail: { + description: + "This endpoint allows authenticated users to import a song from bilibili by providing a video ID. \ + The video ID can be in av or BV format. The system validates the ID format, checks if the video already \ + exists in the database, and if not, creates a background job to fetch video metadata and add it to the songs collection. \ + Returns the job ID for tracking the import progress.", + summary: "Import song from bilibili", + }, response: { 201: t.Object({ - message: t.String(), jobID: t.String(), + message: t.String(), }), 400: t.Object({ message: t.String(), @@ -60,17 +71,6 @@ export const addSongHandler = new Elysia() message: t.String(), }), }, - body: t.Object({ - id: t.String(), - }), - detail: { - summary: "Import song from bilibili", - description: - "This endpoint allows authenticated users to import a song from bilibili by providing a video ID. \ - The video ID can be in av or BV format. The system validates the ID format, checks if the video already \ - exists in the database, and if not, creates a background job to fetch video metadata and add it to the songs collection. \ - Returns the job ID for tracking the import progress.", - }, } ) .get( @@ -80,10 +80,10 @@ export const addSongHandler = new Elysia() if (parseInt(jobID) === -1) { return { id: jobID, - state: "completed", result: { message: "Video already exists in the songs table.", }, + state: "completed", }; } const job = await LatestVideosQueue.getJob(jobID); @@ -94,33 +94,33 @@ export const addSongHandler = new Elysia() } const state = await job.getState(); return { - id: job.id!, - state, - result: job.returnvalue, failedReason: job.failedReason, + id: job.id!, + result: job.returnvalue, + state, }; }, { + detail: { + description: + "This endpoint retrieves the current status of a song import job. It returns the job state \ + (completed, failed, active, etc.), the result if completed, and any failure reason if the job failed. \ + Use this endpoint to monitor the progress of song imports initiated through the import endpoint.", + summary: "Check import job status", + }, + params: t.Object({ + id: t.String(), + }), response: { 200: t.Object({ - id: t.String(), - state: t.String(), - result: t.Optional(t.Any()), failedReason: t.Optional(t.String()), + id: t.String(), + result: t.Optional(t.Any()), + state: t.String(), }), 404: t.Object({ message: t.String(), }), }, - params: t.Object({ - id: t.String(), - }), - detail: { - summary: "Check import job status", - description: - "This endpoint retrieves the current status of a song import job. It returns the job state \ - (completed, failed, active, etc.), the result if completed, and any failure reason if the job failed. \ - Use this endpoint to monitor the progress of song imports initiated through the import endpoint.", - }, } ); diff --git a/packages/backend/routes/song/delete.ts b/packages/backend/routes/song/delete.ts index 3b1d9af..b8e3d8c 100644 --- a/packages/backend/routes/song/delete.ts +++ b/packages/backend/routes/song/delete.ts @@ -9,16 +9,26 @@ export const deleteSongHandler = new Elysia({ prefix: "/song" }).use(requireAuth const id = Number(params.id); await db.update(songs).set({ deleted: true }).where(eq(songs.id, id)); await db.insert(history).values({ - objectId: id, - changeType: "del-song", changedBy: user!.unqId, + changeType: "del-song", data: null, + objectId: id, }); return { message: `Successfully deleted song ${id}.`, }; }, { + detail: { + description: + "This endpoint allows authenticated users to soft-delete a song from the database. \ + The song is marked as deleted rather than being permanently removed, preserving data integrity. \ + The deletion is logged in the history table for audit purposes. Requires authentication and appropriate permissions.", + summary: "Delete song", + }, + params: t.Object({ + id: t.String(), + }), response: { 200: t.Object({ message: t.String(), @@ -30,15 +40,5 @@ export const deleteSongHandler = new Elysia({ prefix: "/song" }).use(requireAuth message: t.String(), }), }, - params: t.Object({ - id: t.String(), - }), - detail: { - summary: "Delete song", - description: - "This endpoint allows authenticated users to soft-delete a song from the database. \ - The song is marked as deleted rather than being permanently removed, preserving data integrity. \ - The deletion is logged in the history table for audit purposes. Requires authentication and appropriate permissions.", - }, } ); diff --git a/packages/backend/routes/song/milestone.ts b/packages/backend/routes/song/milestone.ts index 36f649f..923f314 100644 --- a/packages/backend/routes/song/milestone.ts +++ b/packages/backend/routes/song/milestone.ts @@ -31,37 +31,37 @@ export const closeMileStoneHandler = new Elysia({ prefix: "/songs" }).use(server return q.limit(limit || 20).offset(offset || 0); }, { + detail: { + description: + "This endpoint retrieves songs that are approaching significant view count milestones. \ + It supports three milestone types: 'dendou' (0-100k views), 'densetsu' (100k-1M views), and 'shinwa' (1M-10M views). \ + For each type, it returns videos that are within the specified view range and have an estimated time to reach \ + the next milestone below the threshold. Results are ordered by estimated time to milestone.", + summary: "Get songs close to milestones", + }, + params: t.Object({ + type: t.String({ enum: ["dendou", "densetsu", "shinwa"] }), + }), + query: t.Object({ + limit: t.Optional(t.Number()), + offset: t.Optional(t.Number()), + }), response: { 200: z.array( z.object({ + bilibili_metadata: BiliVideoSchema, eta: z.object({ aid: z.number(), + currentViews: z.number(), eta: z.number(), speed: z.number(), - currentViews: z.number(), updatedAt: z.string(), }), - bilibili_metadata: BiliVideoSchema, }) ), 404: t.Object({ message: t.String(), }), }, - params: t.Object({ - type: t.String({ enum: ["dendou", "densetsu", "shinwa"] }), - }), - query: t.Object({ - offset: t.Optional(t.Number()), - limit: t.Optional(t.Number()), - }), - detail: { - summary: "Get songs close to milestones", - description: - "This endpoint retrieves songs that are approaching significant view count milestones. \ - It supports three milestone types: 'dendou' (0-100k views), 'densetsu' (100k-1M views), and 'shinwa' (1M-10M views). \ - For each type, it returns videos that are within the specified view range and have an estimated time to reach \ - the next milestone below the threshold. Results are ordered by estimated time to milestone.", - }, } ); diff --git a/packages/backend/routes/video/eta.ts b/packages/backend/routes/video/eta.ts index bab5c24..8b79830 100644 --- a/packages/backend/routes/video/eta.ts +++ b/packages/backend/routes/video/eta.ts @@ -25,19 +25,30 @@ export const songEtaHandler = new Elysia({ prefix: "/video" }).get( return { aid: data[0].aid, eta: data[0].eta, - views: data[0].currentViews, speed: data[0].speed, updatedAt: data[0].updatedAt, + views: data[0].currentViews, }; }, { + detail: { + description: + "This endpoint retrieves the estimated time to reach the next milestone for a given video. \ + It accepts video IDs in av or BV format and returns the current view count, estimated time to \ + reach the next milestone (in hours), view growth speed, and last update timestamp. Useful for \ + tracking video growth and milestone predictions.", + summary: "Get video milestone ETA", + }, + headers: t.Object({ + Authorization: t.Optional(t.String()), + }), response: { 200: t.Object({ aid: t.Number(), eta: t.Number(), - views: t.Number(), speed: t.Number(), updatedAt: t.String(), + views: t.Number(), }), 400: t.Object({ code: t.String(), @@ -48,16 +59,5 @@ export const songEtaHandler = new Elysia({ prefix: "/video" }).get( message: t.String(), }), }, - headers: t.Object({ - Authorization: t.Optional(t.String()), - }), - detail: { - summary: "Get video milestone ETA", - description: - "This endpoint retrieves the estimated time to reach the next milestone for a given video. \ - It accepts video IDs in av or BV format and returns the current view count, estimated time to \ - reach the next milestone (in hours), view growth speed, and last update timestamp. Useful for \ - tracking video growth and milestone predictions.", - }, } ); diff --git a/packages/backend/routes/video/label.ts b/packages/backend/routes/video/label.ts index 3376351..63e9dc3 100644 --- a/packages/backend/routes/video/label.ts +++ b/packages/backend/routes/video/label.ts @@ -11,12 +11,12 @@ const videoSchema = BiliVideoSchema.omit({ publishedAt: true }) .omit({ createdAt: true }) .omit({ coverUrl: true }) .extend({ - views: z.number(), - username: z.string(), - uid: z.number(), - published_at: z.string(), - createdAt: z.string(), cover_url: z.string(), + createdAt: z.string(), + published_at: z.string(), + uid: z.number(), + username: z.string(), + views: z.number(), }); export const getUnlabelledVideos = new Elysia({ prefix: "/videos" }).use(requireAuth).get( @@ -75,9 +75,9 @@ export const postVideoLabel = new Elysia({ prefix: "/video" }).use(requireAuth). if (!aid) { return status(400, { code: "MALFORMED_SLOT", + errors: [], message: "We cannot parse the video ID, or we currently do not support this format.", - errors: [], }); } @@ -90,8 +90,8 @@ export const postVideoLabel = new Elysia({ prefix: "/video" }).use(requireAuth). if (video.length === 0) { return status(400, { code: "VIDEO_NOT_FOUND", - message: "Video not found", errors: [], + message: "Video not found", }); } diff --git a/packages/backend/routes/video/metadata.ts b/packages/backend/routes/video/metadata.ts index bc1189b..ab59a02 100644 --- a/packages/backend/routes/video/metadata.ts +++ b/packages/backend/routes/video/metadata.ts @@ -34,13 +34,13 @@ async function insertVideoSnapshot(data: VideoInfoData) { await db.insert(videoSnapshot).values({ aid, - views, - danmakus, - replies, - likes, coins, - shares, + danmakus, favorites, + likes, + replies, + shares, + views, }); snapshotCounter.add(1); } @@ -54,9 +54,9 @@ export const getVideoMetadataHandler = new Elysia({ prefix: "/video" }).get( if (!aid) { return c.status(400, { code: "MALFORMED_SLOT", + errors: [], message: "We cannot parse the video ID, or we currently do not support this format.", - errors: [], }); } @@ -70,8 +70,8 @@ export const getVideoMetadataHandler = new Elysia({ prefix: "/video" }).get( if (typeof r === "number") { return c.status(500, { code: "THIRD_PARTY_ERROR", - message: `Got status code ${r} from bilibili API.`, errors: [], + message: `Got status code ${r} from bilibili API.`, }); } @@ -83,18 +83,18 @@ export const getVideoMetadataHandler = new Elysia({ prefix: "/video" }).get( return data; }, { - response: { - 200: BiliAPIVideoMetadataSchema, - 400: ErrorResponseSchema, - 500: ErrorResponseSchema, - }, detail: { - summary: "Get video metadata", description: "This endpoint retrieves comprehensive metadata for a bilibili video. It accepts video IDs in av or BV format \ and returns detailed information including title, description, uploader, statistics (views, likes, coins, etc.), \ and publication date. The data is cached for 60 seconds to reduce API calls. If the video is not in cache, \ it fetches fresh data from bilibili API and stores a snapshot in the database.", + summary: "Get video metadata", + }, + response: { + 200: BiliAPIVideoMetadataSchema, + 400: ErrorResponseSchema, + 500: ErrorResponseSchema, }, } ); diff --git a/packages/backend/routes/video/snapshots.ts b/packages/backend/routes/video/snapshots.ts index 49e3199..1cdeaad 100644 --- a/packages/backend/routes/video/snapshots.ts +++ b/packages/backend/routes/video/snapshots.ts @@ -15,9 +15,9 @@ export const getVideoSnapshotsHandler = new Elysia({ prefix: "/video" }).get( if (!aid) { return c.status(400, { code: "MALFORMED_SLOT", + errors: [], message: "We cannot parse the video ID, or we currently do not support this format.", - errors: [], }); } @@ -36,31 +36,31 @@ export const getVideoSnapshotsHandler = new Elysia({ prefix: "/video" }).get( return data; }, { - response: { - 200: z.array( - z.object({ - id: z.number(), - createdAt: z.string(), - views: z.number(), - coins: z.number().nullable(), - likes: z.number().nullable(), - favorites: z.number().nullable(), - shares: z.number().nullable(), - danmakus: z.number().nullable(), - aid: z.number(), - replies: z.number().nullable(), - }) - ), - 400: ErrorResponseSchema, - 500: ErrorResponseSchema, - }, detail: { - summary: "Get video snapshots", description: "This endpoint retrieves historical view count snapshots for a bilibili video. It accepts video IDs in av or BV format \ and returns a chronological list of snapshots showing how the video's statistics (views, likes, coins, favorites, etc.) \ have changed over time. If no snapshots exist for the video, it automatically queues a snapshot job to collect initial data. \ Results are ordered by creation date in descending order.", + summary: "Get video snapshots", + }, + response: { + 200: z.array( + z.object({ + aid: z.number(), + coins: z.number().nullable(), + createdAt: z.string(), + danmakus: z.number().nullable(), + favorites: z.number().nullable(), + id: z.number(), + likes: z.number().nullable(), + replies: z.number().nullable(), + shares: z.number().nullable(), + views: z.number(), + }) + ), + 400: ErrorResponseSchema, + 500: ErrorResponseSchema, }, } ); diff --git a/packages/backend/src/mq.ts b/packages/backend/src/mq.ts index 4e2e124..1f9318d 100644 --- a/packages/backend/src/mq.ts +++ b/packages/backend/src/mq.ts @@ -12,10 +12,10 @@ queueEvents.on( "addSong", async ({ uid, songID }: { uid: string; songID: number }) => { await db.insert(history).values({ - objectId: songID, - changeType: "add-song", changedBy: uid, + changeType: "add-song", data: null, + objectId: songID, }); } ); diff --git a/packages/backend/src/onAfterHandle.ts b/packages/backend/src/onAfterHandle.ts index 82accc5..a5ca123 100644 --- a/packages/backend/src/onAfterHandle.ts +++ b/packages/backend/src/onAfterHandle.ts @@ -24,10 +24,10 @@ export const onAfterHandler = new Elysia().onAfterHandle( ? JSON.stringify(realResponse.response, null, 2) : JSON.stringify(realResponse.response); return new Response(encoder.encode(text), { - status: realResponse.code as any, headers: { "Content-Type": "application/json; charset=utf-8", }, + status: realResponse.code as any, }); } const text = isBrowser diff --git a/packages/backend/src/schema.ts b/packages/backend/src/schema.ts index d5bcd99..16e8eea 100644 --- a/packages/backend/src/schema.ts +++ b/packages/backend/src/schema.ts @@ -28,7 +28,6 @@ function generateErrorCodeRegex(strings: string[]): string { export const ErrorResponseSchema = t.Object({ code: t.String({ pattern: generateErrorCodeRegex(errorCodes) }), - message: t.String(), errors: t.Array(t.String()), i18n: t.Optional( t.Object({ @@ -36,4 +35,5 @@ export const ErrorResponseSchema = t.Object({ values: t.Optional(t.Record(t.String(), t.Union([t.String(), t.Number(), t.Date()]))), }) ), + message: t.String(), }); diff --git a/packages/cf-worker/src/index.ts b/packages/cf-worker/src/index.ts index 24f9e3e..b166e4f 100644 --- a/packages/cf-worker/src/index.ts +++ b/packages/cf-worker/src/index.ts @@ -166,15 +166,18 @@ async function handleFetch( } function createJsonResponse(data: ProxyResponseData, requestId: string): Response { - return new Response(JSON.stringify({ - ...data, - requestId, - }), { - headers: { - "Access-Control-Allow-Origin": "*", - "Content-Type": "application/json", - }, - }); + return new Response( + JSON.stringify({ + ...data, + requestId, + }), + { + headers: { + "Access-Control-Allow-Origin": "*", + "Content-Type": "application/json", + }, + } + ); } function createErrorResponse(message: string, status: number, requestId: string): Response { @@ -182,8 +185,8 @@ function createErrorResponse(message: string, status: number, requestId: string) JSON.stringify({ data: "", error: message, - time: Date.now(), requestId, + time: Date.now(), }), { headers: { diff --git a/packages/cf-worker/worker-configuration.d.ts b/packages/cf-worker/worker-configuration.d.ts index 7d3a817..ca0e93d 100644 --- a/packages/cf-worker/worker-configuration.d.ts +++ b/packages/cf-worker/worker-configuration.d.ts @@ -5,7 +5,7 @@ declare namespace Cloudflare { interface GlobalProps { mainModule: typeof import("./src/index"); } - interface Env {} + type Env = {}; } interface Env extends Cloudflare.Env {} @@ -464,7 +464,7 @@ declare const performance: Performance; declare const Cloudflare: Cloudflare; declare const origin: string; declare const navigator: Navigator; -interface TestController {} +type TestController = {}; interface ExecutionContext { waitUntil(promise: Promise): void; passThroughOnException(): void; @@ -590,7 +590,7 @@ type DurableObjectLocationHint = interface DurableObjectNamespaceGetDurableObjectOptions { locationHint?: DurableObjectLocationHint; } -interface DurableObjectClass<_T extends Rpc.DurableObjectBranded | undefined = undefined> {} +type DurableObjectClass<_T extends Rpc.DurableObjectBranded | undefined = undefined> = {}; interface DurableObjectState { waitUntil(promise: Promise): void; readonly exports: Cloudflare.Exports; @@ -2933,7 +2933,7 @@ interface TraceItem { interface TraceItemAlarmEventInfo { readonly scheduledTime: Date; } -interface TraceItemCustomEventInfo {} +type TraceItemCustomEventInfo = {}; interface TraceItemScheduledEventInfo { readonly scheduledTime: number; readonly cron: string; @@ -10460,7 +10460,7 @@ declare abstract class D1PreparedStatement { // but this will ensure type checking on older versions still passes. // TypeScript's interface merging will ensure our empty interface is effectively // ignored when `Disposable` is included in the standard lib. -interface Disposable {} +type Disposable = {}; /** * An email message that can be sent from a Worker. */ @@ -11166,7 +11166,7 @@ declare namespace Cloudflare { // will merge all declarations. // // You can use `wrangler types` to generate the `Env` type automatically. - interface Env {} + type Env = {}; // Project-specific parameters used to inform types. // // This interface is, again, intended to be declared in project-specific files, and then that @@ -11185,7 +11185,7 @@ declare namespace Cloudflare { // } // // You can use `wrangler types` to generate `GlobalProps` automatically. - interface GlobalProps {} + type GlobalProps = {}; // Evaluates to the type of a property in GlobalProps, defaulting to `Default` if it is not // present. type GlobalProp = K extends keyof GlobalProps diff --git a/packages/core/db/pgConfigNew.ts b/packages/core/db/pgConfigNew.ts index 66de89e..796998f 100644 --- a/packages/core/db/pgConfigNew.ts +++ b/packages/core/db/pgConfigNew.ts @@ -18,17 +18,17 @@ const databasePassword = getEnvVar("DB_PASSWORD")!; const databasePort = getEnvVar("DB_PORT")!; export const postgresConfig = { - host: databaseHost, - port: parseInt(databasePort), database: databaseName, - username: databaseUser, + host: databaseHost, password: databasePassword, + port: parseInt(databasePort), + username: databaseUser, }; export const postgresConfigCred = { - hostname: databaseHost, - port: parseInt(databasePort), database: databaseNameCred, - user: databaseUser, + hostname: databaseHost, password: databasePassword, + port: parseInt(databasePort), + user: databaseUser, }; diff --git a/packages/core/db/redis.ts b/packages/core/db/redis.ts index 2b80e68..f974b6c 100644 --- a/packages/core/db/redis.ts +++ b/packages/core/db/redis.ts @@ -4,7 +4,7 @@ const host = process.env.REDIS_HOST || "localhost"; const port = parseInt(process.env.REDIS_PORT) || 6379; export const redis = new Redis({ - port: port, host: host, maxRetriesPerRequest: null, + port: port, }); diff --git a/packages/core/drizzle.config.ts b/packages/core/drizzle.config.ts index 34270c2..113b0ff 100644 --- a/packages/core/drizzle.config.ts +++ b/packages/core/drizzle.config.ts @@ -2,10 +2,10 @@ import "dotenv/config"; import { defineConfig } from "drizzle-kit"; export default defineConfig({ - out: "./drizzle/main", - dialect: "postgresql", dbCredentials: { url: process.env.DATABASE_URL_MAIN!, }, + dialect: "postgresql", + out: "./drizzle/main", schemaFilter: ["public", "credentials", "internal"], }); diff --git a/packages/core/drizzle/drizzle-cred.config.ts b/packages/core/drizzle/drizzle-cred.config.ts index 3f78afc..34851ce 100644 --- a/packages/core/drizzle/drizzle-cred.config.ts +++ b/packages/core/drizzle/drizzle-cred.config.ts @@ -1,9 +1,9 @@ import { defineConfig } from "drizzle-kit"; export default defineConfig({ - out: "./cred", - dialect: "postgresql", dbCredentials: { url: process.env.DATABASE_URL_CRED!, }, + dialect: "postgresql", + out: "./cred", }); diff --git a/packages/core/drizzle/drizzle-main.config.ts b/packages/core/drizzle/drizzle-main.config.ts index 4244b81..a876c59 100644 --- a/packages/core/drizzle/drizzle-main.config.ts +++ b/packages/core/drizzle/drizzle-main.config.ts @@ -6,9 +6,9 @@ if (!process.env.DATABASE_URL_MAIN) { } export default defineConfig({ - out: "./drizzle/main", - dialect: "postgresql", dbCredentials: { url: process.env.DATABASE_URL_MAIN, }, + dialect: "postgresql", + out: "./drizzle/main", }); diff --git a/packages/core/log/index.ts b/packages/core/log/index.ts index 048a755..6f7ae59 100644 --- a/packages/core/log/index.ts +++ b/packages/core/log/index.ts @@ -43,12 +43,12 @@ const createTransport = (level: string, filename: string) => { return value; } return new transports.File({ - level, filename, + format: format.combine(timestampFormat, format.json({ replacer })), + level, + maxFiles, maxsize, tailable, - maxFiles, - format: format.combine(timestampFormat, format.json({ replacer })), }); }; @@ -60,13 +60,13 @@ const winstonLogger = winston.createLogger({ levels: winston.config.npm.levels, transports: [ new transports.Console({ - level: "debug", format: format.combine( format.timestamp({ format: "YYYY-MM-DD HH:mm:ss.SSSZZ" }), format.colorize(), format.errors({ stack: true }), customFormat ), + level: "debug", }), createTransport("silly", sillyLogPath), createTransport("warn", warnLogPath), @@ -75,28 +75,28 @@ const winstonLogger = winston.createLogger({ }); const logger = { - silly: (message: string, service?: string, codePath?: string) => { - winstonLogger.silly(message, { service, codePath }); - }, - verbose: (message: string, service?: string, codePath?: string) => { - winstonLogger.verbose(message, { service, codePath }); - }, - log: (message: string, service?: string, codePath?: string) => { - winstonLogger.info(message, { service, codePath }); - }, debug: (message: string, service?: string, codePath?: string) => { - winstonLogger.debug(message, { service, codePath }); - }, - warn: (message: string, service?: string, codePath?: string) => { - winstonLogger.warn(message, { service, codePath }); + winstonLogger.debug(message, { codePath, service }); }, error: (error: string | Error, service?: string, codePath?: string) => { if (error instanceof Error) { - winstonLogger.error(error.message, { service, error: error, codePath }); + winstonLogger.error(error.message, { codePath, error: error, service }); } else { - winstonLogger.error(error, { service, codePath }); + winstonLogger.error(error, { codePath, service }); } }, + log: (message: string, service?: string, codePath?: string) => { + winstonLogger.info(message, { codePath, service }); + }, + silly: (message: string, service?: string, codePath?: string) => { + winstonLogger.silly(message, { codePath, service }); + }, + verbose: (message: string, service?: string, codePath?: string) => { + winstonLogger.verbose(message, { codePath, service }); + }, + warn: (message: string, service?: string, codePath?: string) => { + winstonLogger.warn(message, { codePath, service }); + }, }; export default logger; diff --git a/packages/core/mq/multipleRateLimiter.ts b/packages/core/mq/multipleRateLimiter.ts index 19e232b..8494866 100644 --- a/packages/core/mq/multipleRateLimiter.ts +++ b/packages/core/mq/multipleRateLimiter.ts @@ -40,9 +40,9 @@ export class MultipleRateLimiter { const { duration, max } = this.configs[i]; const { allowed } = await this.limiter.allow(`cvsa:${this.name}_${i}`, { burst: max, - ratePerPeriod: max, - period: duration, cost: 1, + period: duration, + ratePerPeriod: max, }); if (!allowed && shouldThrow) { throw new RateLimiterError("Rate limit exceeded"); diff --git a/packages/core/net/services.ts b/packages/core/net/services.ts index b097015..848489a 100644 --- a/packages/core/net/services.ts +++ b/packages/core/net/services.ts @@ -17,14 +17,14 @@ export class BilibiliService { const stats = metadata.data.data.stat; return { aid, - createdAt: new Date(metadata.time).toISOString(), - views: stats.view, - likes: stats.like, coins: stats.coin, + createdAt: new Date(metadata.time).toISOString(), + danmakus: stats.danmaku, favorites: stats.favorite, + likes: stats.like, replies: stats.reply, shares: stats.share, - danmakus: stats.danmaku, + views: stats.view, }; } } diff --git a/packages/crawler/db/bilibili_metadata.ts b/packages/crawler/db/bilibili_metadata.ts index 057b918..d30f45d 100644 --- a/packages/crawler/db/bilibili_metadata.ts +++ b/packages/crawler/db/bilibili_metadata.ts @@ -73,9 +73,9 @@ export async function getVideoInfoFromAllData(aid: number) { const row = rows[0]; return { - title: row.title, description: row.description, tags: row.tags, + title: row.title, }; } diff --git a/packages/crawler/ml/api_manager.ts b/packages/crawler/ml/api_manager.ts index 4029981..430deca 100644 --- a/packages/crawler/ml/api_manager.ts +++ b/packages/crawler/ml/api_manager.ts @@ -31,10 +31,10 @@ export class APIManager { public async healthCheck(): Promise { try { const response = await fetch(`${this.baseUrl}/health`, { - method: "GET", headers: { "Content-Type": "application/json", }, + method: "GET", signal: AbortSignal.timeout(this.timeout), }); @@ -57,19 +57,19 @@ export class APIManager { aid?: number ): Promise { const request: ClassificationRequest = { - title: title.trim() || "untitled", + aid: aid, description: description.trim() || "N/A", tags: tags.trim() || "empty", - aid: aid, + title: title.trim() || "untitled", }; try { const response = await fetch(`${this.baseUrl}/classify`, { - method: "POST", + body: JSON.stringify(request), headers: { "Content-Type": "application/json", }, - body: JSON.stringify(request), + method: "POST", signal: AbortSignal.timeout(this.timeout), }); @@ -98,11 +98,11 @@ export class APIManager { ): Promise> { try { const response = await fetch(`${this.baseUrl}/classify_batch`, { - method: "POST", + body: JSON.stringify(requests), headers: { "Content-Type": "application/json", }, - body: JSON.stringify(requests), + method: "POST", signal: AbortSignal.timeout(this.timeout * 2), // Longer timeout for batch }); diff --git a/packages/crawler/mq/exec/archiveSnapshots.ts b/packages/crawler/mq/exec/archiveSnapshots.ts index 4a4104c..cabd6bc 100644 --- a/packages/crawler/mq/exec/archiveSnapshots.ts +++ b/packages/crawler/mq/exec/archiveSnapshots.ts @@ -61,8 +61,8 @@ export const archiveSnapshotsWorker = async (_job: Job) => { const now = Date.now(); const targetTime = getRandomTimeInNextWeek(); const interval = intervalToDuration({ - start: new Date(), end: new Date(targetTime), + start: new Date(), }); const formatted = formatDuration(interval, { format: ["days", "hours"] }); diff --git a/packages/crawler/mq/exec/getLatestVideos.ts b/packages/crawler/mq/exec/getLatestVideos.ts index 028917c..ffb82ab 100644 --- a/packages/crawler/mq/exec/getLatestVideos.ts +++ b/packages/crawler/mq/exec/getLatestVideos.ts @@ -1,7 +1,6 @@ -import { sql } from "@core/db/dbNew"; import type { Job } from "bullmq"; import { queueLatestVideos } from "mq/task/queueLatestVideo"; export const getLatestVideosWorker = async (_job: Job): Promise => { - await queueLatestVideos(sql); + await queueLatestVideos(); }; diff --git a/packages/crawler/mq/exec/getVideoInfo.ts b/packages/crawler/mq/exec/getVideoInfo.ts index 4bce811..1edcd89 100644 --- a/packages/crawler/mq/exec/getVideoInfo.ts +++ b/packages/crawler/mq/exec/getVideoInfo.ts @@ -22,8 +22,8 @@ interface AddSongEventPayload { const publishAddsongEvent = async (songID: number, uid: string) => latestVideosEventsProducer.publishEvent({ eventName: "addSong", - uid: uid, songID: songID, + uid: uid, }); export const getVideoInfoWorker = async (job: Job): Promise => { @@ -56,34 +56,34 @@ export const getVideoInfoWorker = async (job: Job): Promise await insertIntoMetadata({ aid, bvid: data.View.bvid, + coverUrl: data.View.pic, description: data.View.desc, - uid: uid, + duration: data.View.duration, + publishedAt: new Date(data.View.pubdate * 1000).toISOString(), tags: data.Tags.filter((tag) => !["old_channel", "topic"].indexOf(tag.tag_type)) .map((tag) => tag.tag_name) .join(","), title: data.View.title, - publishedAt: new Date(data.View.pubdate * 1000).toISOString(), - duration: data.View.duration, - coverUrl: data.View.pic, + uid: uid, }); const userExists = await userExistsInBiliUsers(aid); if (!userExists) { await db.insert(bilibiliUser).values({ - uid, - username: data.View.owner.name, + avatar: data.View.owner.face, desc: data.Card.card.sign, fans: data.Card.follower, - avatar: data.View.owner.face, + uid, + username: data.View.owner.name, }); } else { await db .update(bilibiliUser) .set({ - username: data.View.owner.name, + avatar: data.View.owner.face, desc: data.Card.card.sign, fans: data.Card.follower, - avatar: data.View.owner.face, + username: data.View.owner.name, }) .where(eq(bilibiliUser.uid, uid)); } @@ -92,13 +92,13 @@ export const getVideoInfoWorker = async (job: Job): Promise await db.insert(videoSnapshot).values({ aid, - views: stat.view, - danmakus: stat.danmaku, - replies: stat.reply, - likes: stat.like, coins: stat.coin, - shares: stat.share, + danmakus: stat.danmaku, favorites: stat.favorite, + likes: stat.like, + replies: stat.reply, + shares: stat.share, + views: stat.view, }); snapshotCounter.add(1); diff --git a/packages/crawler/mq/exec/snapshotTick.ts b/packages/crawler/mq/exec/snapshotTick.ts index 154dda5..601938a 100644 --- a/packages/crawler/mq/exec/snapshotTick.ts +++ b/packages/crawler/mq/exec/snapshotTick.ts @@ -32,12 +32,12 @@ export const bulkSnapshotTickWorker = async (_job: Job) => { const schedulesData = group.map((schedule) => { return { aid: Number(schedule.aid), - id: Number(schedule.id), - type: schedule.type, created_at: schedule.created_at, - started_at: schedule.started_at, finished_at: schedule.finished_at, + id: Number(schedule.id), + started_at: schedule.started_at, status: schedule.status, + type: schedule.type, }; }); await SnapshotQueue.add( diff --git a/packages/crawler/mq/exec/snapshotVideo.ts b/packages/crawler/mq/exec/snapshotVideo.ts index a321e06..5234d04 100644 --- a/packages/crawler/mq/exec/snapshotVideo.ts +++ b/packages/crawler/mq/exec/snapshotVideo.ts @@ -17,8 +17,8 @@ import { closetMilestone } from "./snapshotTick"; const snapshotTypeToTaskMap = { milestone: "snapshotMilestoneVideo", - normal: "snapshotVideo", new: "snapshotMilestoneVideo", + normal: "snapshotVideo", } as const; export const snapshotVideoWorker = async (job: Job): Promise => { diff --git a/packages/crawler/mq/task/getVideoStats.ts b/packages/crawler/mq/task/getVideoStats.ts index ccf57a4..7454361 100644 --- a/packages/crawler/mq/task/getVideoStats.ts +++ b/packages/crawler/mq/task/getVideoStats.ts @@ -44,28 +44,28 @@ export async function takeVideoSnapshot( const favorites = data.stat.favorite; await insertVideoSnapshot({ - createdAt: new Date(time).toISOString(), - views, - coins, - likes, - favorites, - shares, - danmakus, - replies, aid, + coins, + createdAt: new Date(time).toISOString(), + danmakus, + favorites, + likes, + replies, + shares, + views, }); logger.log(`Taken snapshot for video ${aid}.`, "net", "fn:insertVideoSnapshot"); return { aid, - views, - danmakus, - replies, - likes, coins, - shares, + danmakus, favorites, + likes, + replies, + shares, time, + views, }; } diff --git a/packages/crawler/mq/task/queueLatestVideo.ts b/packages/crawler/mq/task/queueLatestVideo.ts index 804a673..4f2a9f0 100644 --- a/packages/crawler/mq/task/queueLatestVideo.ts +++ b/packages/crawler/mq/task/queueLatestVideo.ts @@ -1,4 +1,3 @@ -import type { Psql } from "@core/db/psql.d"; import { SECOND } from "@core/lib"; import logger from "@core/log"; import { videoExistsInAllData } from "db/bilibili_metadata"; @@ -6,14 +5,14 @@ import { LatestVideosQueue } from "mq/index"; import { getLatestVideoAids } from "net/getLatestVideoAids"; import { sleep } from "utils/sleep"; -export async function queueLatestVideos(sql: Psql): Promise { +export async function queueLatestVideos(): Promise { let page = 1; let i = 0; const videosFound = new Set(); while (true) { - const pageSize = page == 1 ? 10 : 30; + const pageSize = page === 1 ? 10 : 30; const aids = await getLatestVideoAids(page, pageSize); - if (aids.length == 0) { + if (aids.length === 0) { logger.verbose("No more videos found", "net", "fn:insertLatestVideos()"); break; } @@ -28,12 +27,12 @@ export async function queueLatestVideos(sql: Psql): Promise { "getVideoInfo", { aid }, { - delay, attempts: 100, backoff: { - type: "fixed", delay: SECOND * 5, + type: "fixed", }, + delay, } ); videosFound.add(aid); diff --git a/packages/crawler/src/filterWorker.ts b/packages/crawler/src/filterWorker.ts index 8c05fbb..aece1fd 100644 --- a/packages/crawler/src/filterWorker.ts +++ b/packages/crawler/src/filterWorker.ts @@ -26,7 +26,7 @@ const filterWorker = new Worker( break; } }, - { connection: redis as ConnectionOptions, concurrency: 2, removeOnComplete: { count: 1000 } } + { concurrency: 2, connection: redis as ConnectionOptions, removeOnComplete: { count: 1000 } } ); process.on("SIGINT", () => shutdown("SIGINT", filterWorker)); diff --git a/packages/crawler/src/worker.ts b/packages/crawler/src/worker.ts index 1113ada..0123579 100644 --- a/packages/crawler/src/worker.ts +++ b/packages/crawler/src/worker.ts @@ -58,8 +58,8 @@ const latestVideoWorker = new Worker( } }, { - connection: redis as ConnectionOptions, concurrency: 6, + connection: redis as ConnectionOptions, removeOnComplete: { count: 1440 }, removeOnFail: { count: 0 }, } @@ -100,7 +100,7 @@ const snapshotWorker = new Worker( break; } }, - { connection: redis as ConnectionOptions, concurrency: 50, removeOnComplete: { count: 2000 } } + { concurrency: 50, connection: redis as ConnectionOptions, removeOnComplete: { count: 2000 } } ); snapshotWorker.on("error", (err) => { @@ -118,7 +118,7 @@ const miscWorker = new Worker( break; } }, - { connection: redis as ConnectionOptions, concurrency: 5, removeOnComplete: { count: 1000 } } + { concurrency: 5, connection: redis as ConnectionOptions, removeOnComplete: { count: 1000 } } ); miscWorker.on("error", (err) => { diff --git a/packages/palette/src/App.tsx b/packages/palette/src/App.tsx index 3dd6576..9197a39 100644 --- a/packages/palette/src/App.tsx +++ b/packages/palette/src/App.tsx @@ -11,7 +11,7 @@ import { Switch } from "./Switch"; import { useTheme } from "./ThemeContext"; import { i18nProvider } from "./utils"; -const defaultColor: Oklch = { mode: "oklch", h: 29.2339, c: 0.244572, l: 0.596005 }; +const defaultColor: Oklch = { c: 0.244572, h: 29.2339, l: 0.596005, mode: "oklch" }; const colorAtom = atomWithStorage("selectedColor", defaultColor); const p3Atom = atomWithStorage("showP3", false); diff --git a/packages/palette/src/colorTokens.ts b/packages/palette/src/colorTokens.ts index 986914d..93168e7 100644 --- a/packages/palette/src/colorTokens.ts +++ b/packages/palette/src/colorTokens.ts @@ -5,19 +5,6 @@ export type ThemeMode = "light" | "dark"; export const buildColorTokens = (base: Oklch) => { return { - light: { - background: getAdjustedColor(base, 0.98, 0.01), - "bg-elevated-1": getAdjustedColor(base, 1, 0.008), - "body-text": getAdjustedColor(base, 0.1, 0.01), - "border-var-1": getAdjustedColor(base, 0.845, 0.004), - "border-var-2": getAdjustedColor(base, 0.8, 0.007), - "border-var-3": getAdjustedColor(base, 0.755, 0.01), - primary: getAdjustedColor(base, 0.48, 0.08), - "on-primary": getAdjustedColor(base, 0.999, 0.001), - "on-bg-var-2": getAdjustedColor(base, 0.398, 0.0234), - error: { mode: "oklch", l: 0.506, c: 0.192, h: 27.7 } as Oklch, - "on-error": getAdjustedColor(base, 0.99, 0.01), - }, dark: { background: getAdjustedColor(base, 0.15, 0.002), "bg-elevated-1": getAdjustedColor(base, 0.2, 0.004), @@ -25,11 +12,24 @@ export const buildColorTokens = (base: Oklch) => { "border-var-1": getAdjustedColor(base, 0.3, 0.004), "border-var-2": getAdjustedColor(base, 0.4, 0.007), "border-var-3": getAdjustedColor(base, 0.5, 0.01), - primary: getAdjustedColor(base, 0.84, 0.1), - "on-primary": getAdjustedColor(base, 0.3, 0.08), + error: { c: 0.223, h: 27.8, l: 0.65, mode: "oklch" } as Oklch, "on-bg-var-2": getAdjustedColor(base, 0.83, 0.028), - error: { mode: "oklch", l: 0.65, c: 0.223, h: 27.8 } as Oklch, "on-error": getAdjustedColor(base, 0.9, 0.01), + "on-primary": getAdjustedColor(base, 0.3, 0.08), + primary: getAdjustedColor(base, 0.84, 0.1), + }, + light: { + background: getAdjustedColor(base, 0.98, 0.01), + "bg-elevated-1": getAdjustedColor(base, 1, 0.008), + "body-text": getAdjustedColor(base, 0.1, 0.01), + "border-var-1": getAdjustedColor(base, 0.845, 0.004), + "border-var-2": getAdjustedColor(base, 0.8, 0.007), + "border-var-3": getAdjustedColor(base, 0.755, 0.01), + error: { c: 0.192, h: 27.7, l: 0.506, mode: "oklch" } as Oklch, + "on-bg-var-2": getAdjustedColor(base, 0.398, 0.0234), + "on-error": getAdjustedColor(base, 0.99, 0.01), + "on-primary": getAdjustedColor(base, 0.999, 0.001), + primary: getAdjustedColor(base, 0.48, 0.08), }, }; }; diff --git a/packages/palette/src/components/ColorBlock.tsx b/packages/palette/src/components/ColorBlock.tsx index 66b423b..30994f1 100644 --- a/packages/palette/src/components/ColorBlock.tsx +++ b/packages/palette/src/components/ColorBlock.tsx @@ -81,7 +81,7 @@ export const ColorBlock = ({ baseColor, text, l, c, h }: ColorBlockProps) => { animate={{ opacity: 1, width: 22 }} transition={{ opacity: { duration: 0.2, ease: "backOut" }, - width: { type: "spring", bounce: 0.2, duration: 0.5 }, + width: { bounce: 0.2, duration: 0.5, type: "spring" }, }} > diff --git a/packages/palette/src/components/Picker/Handle.tsx b/packages/palette/src/components/Picker/Handle.tsx index 5a4aeff..8900b2e 100644 --- a/packages/palette/src/components/Picker/Handle.tsx +++ b/packages/palette/src/components/Picker/Handle.tsx @@ -84,8 +84,8 @@ export const Handle = ({ shadow-[0px_0px_7px_2px_rgba(0,0,0,0.35)] cursor-grab active:cursor-grabbing touch-none select-none" style={{ - left: `${pos}%`, backgroundColor: `oklch(${color.l} ${color.c} ${color.h})`, + left: `${pos}%`, transform: "translateY(-50%) translateX(-50%) rotate(45deg)", }} onMouseDown={handleMouseDown} diff --git a/packages/palette/src/components/Picker/Slider.tsx b/packages/palette/src/components/Picker/Slider.tsx index 333240d..2d5d3b6 100644 --- a/packages/palette/src/components/Picker/Slider.tsx +++ b/packages/palette/src/components/Picker/Slider.tsx @@ -24,10 +24,10 @@ export const Slider = ({ useP3, channel, color, onChange, i18nProvider }: Slider const canvasRef = useRef(null); useOklchCanvas({ - channel: channel, - max: maxValue[channel], canvasRef: canvasRef, + channel: channel, color, + max: maxValue[channel], useP3, }); diff --git a/packages/palette/src/components/Picker/useOklchCanvas.tsx b/packages/palette/src/components/Picker/useOklchCanvas.tsx index ef6f25e..14f28dc 100644 --- a/packages/palette/src/components/Picker/useOklchCanvas.tsx +++ b/packages/palette/src/components/Picker/useOklchCanvas.tsx @@ -44,10 +44,10 @@ export function useOklchCanvas({ useP3, channel, max, canvasRef, color }: UseOkl try { const testColor = oklch({ - mode: "oklch", - l: channel === "l" ? value : color.l, c: channel === "c" ? value : color.c, h: channel === "h" ? value : color.h, + l: channel === "l" ? value : color.l, + mode: "oklch", }); if (testColor && inGamut(colorGamut)(testColor)) { diff --git a/packages/palette/src/components/Picker/utils.ts b/packages/palette/src/components/Picker/utils.ts index 8863ec9..3ee5010 100644 --- a/packages/palette/src/components/Picker/utils.ts +++ b/packages/palette/src/components/Picker/utils.ts @@ -7,22 +7,22 @@ export const round = (value: number, precision: number) => { export const roundOklch = (oklch: Oklch) => { return { ...oklch, - l: round(oklch.l, precision.l), c: round(oklch.c, precision.c), h: round(oklch.h!, precision.h), + l: round(oklch.l, precision.l), }; }; export const precision = { - l: 4, c: 4, h: 2, + l: 4, }; export const maxValue = { - l: 1, c: 0.37, h: 360, + l: 1, }; /** diff --git a/packages/temp_frontend/app/components/ui/button.tsx b/packages/temp_frontend/app/components/ui/button.tsx index 3559a13..9956c0f 100644 --- a/packages/temp_frontend/app/components/ui/button.tsx +++ b/packages/temp_frontend/app/components/ui/button.tsx @@ -7,27 +7,27 @@ import { cn } from "@/lib//utils"; const buttonVariants = cva( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", { + defaultVariants: { + size: "default", + variant: "default", + }, variants: { + size: { + default: "h-9 px-4 py-2 has-[>svg]:px-3", + icon: "size-9", + lg: "h-10 rounded-md px-6 has-[>svg]:px-4", + sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", + }, variant: { default: "bg-primary text-primary-foreground hover:bg-primary/90", destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", + link: "text-primary underline-offset-4 hover:underline", outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", - ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", - link: "text-primary underline-offset-4 hover:underline", }, - size: { - default: "h-9 px-4 py-2 has-[>svg]:px-3", - sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", - lg: "h-10 rounded-md px-6 has-[>svg]:px-4", - icon: "size-9", - }, - }, - defaultVariants: { - variant: "default", - size: "default", }, } ); @@ -47,7 +47,7 @@ function Button({ return ( ); diff --git a/packages/temp_frontend/app/components/ui/chart.tsx b/packages/temp_frontend/app/components/ui/chart.tsx index 20682dd..5ea1647 100644 --- a/packages/temp_frontend/app/components/ui/chart.tsx +++ b/packages/temp_frontend/app/components/ui/chart.tsx @@ -4,7 +4,7 @@ import * as RechartsPrimitive from "recharts"; import { cn } from "@/lib//utils"; // Format: { THEME_NAME: CSS_SELECTOR } -const THEMES = { light: "", dark: ".dark" } as const; +const THEMES = { dark: ".dark", light: "" } as const; export type ChartConfig = { [k in string]: { @@ -192,11 +192,11 @@ function ChartTooltipContent({ "shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)", { "h-2.5 w-2.5": indicator === "dot", - "w-1": indicator === "line", - "w-0 border-[1.5px] border-dashed bg-transparent": - indicator === "dashed", "my-0.5": nestLabel && indicator === "dashed", + "w-0 border-[1.5px] border-dashed bg-transparent": + indicator === "dashed", + "w-1": indicator === "line", } )} style={ diff --git a/packages/temp_frontend/app/components/ui/sonner.tsx b/packages/temp_frontend/app/components/ui/sonner.tsx index aa65f1c..56efc1e 100644 --- a/packages/temp_frontend/app/components/ui/sonner.tsx +++ b/packages/temp_frontend/app/components/ui/sonner.tsx @@ -12,8 +12,8 @@ const Toaster = ({ ...props }: ToasterProps) => { style={ { "--normal-bg": "var(--popover)", - "--normal-text": "var(--popover-foreground)", "--normal-border": "var(--border)", + "--normal-text": "var(--popover-foreground)", } as React.CSSProperties } {...props} diff --git a/packages/temp_frontend/app/routes/home/Milestone.tsx b/packages/temp_frontend/app/routes/home/Milestone.tsx index e921537..b5a52c3 100644 --- a/packages/temp_frontend/app/routes/home/Milestone.tsx +++ b/packages/temp_frontend/app/routes/home/Milestone.tsx @@ -54,8 +54,8 @@ export const MilestoneVideos: React.FC = () => { try { const { data, error } = await app.songs["close-milestone"]({ type }).get({ query: { - offset: currentOffset, limit: 20, + offset: currentOffset, }, }); diff --git a/packages/temp_frontend/app/routes/login.tsx b/packages/temp_frontend/app/routes/login.tsx index ff93ad8..e2b49d1 100644 --- a/packages/temp_frontend/app/routes/login.tsx +++ b/packages/temp_frontend/app/routes/login.tsx @@ -12,8 +12,8 @@ const app = treaty(import.meta.env.VITE_API_URL!); export default function Login() { const navigate = useNavigate(); const [formData, setFormData] = useState({ - username: "", password: "", + username: "", }); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(""); diff --git a/packages/temp_frontend/app/routes/song/[id]/info/columns.tsx b/packages/temp_frontend/app/routes/song/[id]/info/columns.tsx index 4862ccc..344e058 100644 --- a/packages/temp_frontend/app/routes/song/[id]/info/columns.tsx +++ b/packages/temp_frontend/app/routes/song/[id]/info/columns.tsx @@ -18,6 +18,10 @@ export type Snapshot = { export const columns: ColumnDef[] = [ { accessorKey: "createdAt", + cell: ({ row }) => { + const createdAt = row.getValue("createdAt") as string; + return
{formatDateTime(new Date(createdAt))}
; + }, header: ({ column }) => { return ( ); }, - cell: ({ row }) => { - const createdAt = row.getValue("createdAt") as string; - return
{formatDateTime(new Date(createdAt))}
; - }, }, { accessorKey: "views", - header: "播放", cell: ({ row }) => { const views = row.getValue("views") as number; return
{views.toLocaleString()}
; }, + header: "播放", }, { accessorKey: "likes", - header: "点赞", cell: ({ row }) => { const likes = row.getValue("likes") as number; return
{likes.toLocaleString()}
; }, + header: "点赞", }, { accessorKey: "favorites", - header: "收藏", cell: ({ row }) => { const favorites = row.getValue("favorites") as number; return
{favorites.toLocaleString()}
; }, + header: "收藏", }, { accessorKey: "coins", - header: "硬币", cell: ({ row }) => { const coins = row.getValue("coins") as number; return
{coins.toLocaleString()}
; }, + header: "硬币", }, { accessorKey: "danmakus", - header: "弹幕", cell: ({ row }) => { const danmakus = row.getValue("danmakus") as number; return
{danmakus.toLocaleString()}
; }, + header: "弹幕", }, { accessorKey: "shares", - header: "转发", cell: ({ row }) => { const shares = row.getValue("shares") as number; return
{shares.toLocaleString()}
; }, + header: "转发", }, ]; diff --git a/packages/temp_frontend/app/routes/song/[id]/info/data-table.tsx b/packages/temp_frontend/app/routes/song/[id]/info/data-table.tsx index 8af4d1e..d4499c8 100644 --- a/packages/temp_frontend/app/routes/song/[id]/info/data-table.tsx +++ b/packages/temp_frontend/app/routes/song/[id]/info/data-table.tsx @@ -30,12 +30,12 @@ export function DataTable({ columns, data }: DataTableProps([]); const table = useReactTable({ - data, columns, + data, getCoreRowModel: getCoreRowModel(), getPaginationRowModel: getPaginationRowModel(), - onSortingChange: setSorting, getSortedRowModel: getSortedRowModel(), + onSortingChange: setSorting, state: { sorting, }, diff --git a/packages/temp_frontend/app/routes/song/[id]/info/index.tsx b/packages/temp_frontend/app/routes/song/[id]/info/index.tsx index a99c6d0..84c7e85 100644 --- a/packages/temp_frontend/app/routes/song/[id]/info/index.tsx +++ b/packages/temp_frontend/app/routes/song/[id]/info/index.tsx @@ -55,7 +55,7 @@ export function formatHours(hours: number): string { export function addHoursToNow(hours: number): string { const d = new Date(); d.setSeconds(d.getSeconds() + hours * 3600); - return formatDateTime(d, true) + return formatDateTime(d, true); } export default function SongInfo({ loaderData }: Route.ComponentProps) { diff --git a/packages/temp_frontend/app/routes/song/[id]/info/lib.ts b/packages/temp_frontend/app/routes/song/[id]/info/lib.ts index f185c2a..cd13e63 100644 --- a/packages/temp_frontend/app/routes/song/[id]/info/lib.ts +++ b/packages/temp_frontend/app/routes/song/[id]/info/lib.ts @@ -140,25 +140,25 @@ export const processSnapshots = ( }; const createSnapshotData = (timestamp: number, snapshot: any) => ({ - createdAt: new Date(timestamp).toISOString(), - views: snapshot.views, - likes: snapshot.likes || 0, - favorites: snapshot.favorites || 0, coins: snapshot.coins || 0, + createdAt: new Date(timestamp).toISOString(), danmakus: snapshot.danmakus || 0, + favorites: snapshot.favorites || 0, + likes: snapshot.likes || 0, + views: snapshot.views, }); const createInterpolatedSnapshot = (timestamp: number, prev: any, next: any, ratio: number) => ({ - createdAt: new Date(timestamp).toISOString(), - views: Math.round(prev.views + (next.views - prev.views) * ratio), - likes: Math.round((prev.likes || 0) + ((next.likes || 0) - (prev.likes || 0)) * ratio), - favorites: Math.round( - (prev.favorites || 0) + ((next.favorites || 0) - (prev.favorites || 0)) * ratio - ), coins: Math.round((prev.coins || 0) + ((next.coins || 0) - (prev.coins || 0)) * ratio), + createdAt: new Date(timestamp).toISOString(), danmakus: Math.round( (prev.danmakus || 0) + ((next.danmakus || 0) - (prev.danmakus || 0)) * ratio ), + favorites: Math.round( + (prev.favorites || 0) + ((next.favorites || 0) - (prev.favorites || 0)) * ratio + ), + likes: Math.round((prev.likes || 0) + ((next.likes || 0) - (prev.likes || 0)) * ratio), + views: Math.round(prev.views + (next.views - prev.views) * ratio), }); export const detectMilestoneAchievements = ( @@ -198,9 +198,9 @@ export const detectMilestoneAchievements = ( const milestoneTime = new Date(prevTime + ratio * timeDiff); const achievement: MilestoneAchievement = { + achievedAt: milestoneTime.toISOString(), milestone, milestoneName, - achievedAt: milestoneTime.toISOString(), views: milestone, }; @@ -217,9 +217,9 @@ export const detectMilestoneAchievements = ( const exactSnapshot = prevSnapshot.views === milestone ? prevSnapshot : currentSnapshot; const achievement: MilestoneAchievement = { + achievedAt: exactSnapshot.createdAt, milestone, milestoneName, - achievedAt: exactSnapshot.createdAt, views: milestone, }; diff --git a/packages/temp_frontend/app/routes/song/[id]/info/snapshotsView.tsx b/packages/temp_frontend/app/routes/song/[id]/info/snapshotsView.tsx index 2afbebd..ef9f67c 100644 --- a/packages/temp_frontend/app/routes/song/[id]/info/snapshotsView.tsx +++ b/packages/temp_frontend/app/routes/song/[id]/info/snapshotsView.tsx @@ -29,13 +29,13 @@ const StatsTable = ({ snapshots }: { snapshots: Snapshots | null }) => { } const tableData: Snapshot[] = snapshots.map((snapshot) => ({ - createdAt: snapshot.createdAt, - views: snapshot.views, - likes: snapshot.likes || 0, - favorites: snapshot.favorites || 0, coins: snapshot.coins || 0, + createdAt: snapshot.createdAt, danmakus: snapshot.danmakus || 0, + favorites: snapshot.favorites || 0, + likes: snapshot.likes || 0, shares: snapshot.shares || 0, + views: snapshot.views, })); return ; @@ -85,16 +85,16 @@ export const SnapshotsView = ({ if (!snapshots) return null; return [ { - id: 0, - createdAt: publishedAt, - views: 0, - coins: 0, - likes: 0, - favorites: 0, - shares: 0, - danmakus: 0, aid: 0, + coins: 0, + createdAt: publishedAt, + danmakus: 0, + favorites: 0, + id: 0, + likes: 0, replies: 0, + shares: 0, + views: 0, }, ...snapshots, ] diff --git a/packages/temp_frontend/app/routes/song/[id]/info/views-chart.tsx b/packages/temp_frontend/app/routes/song/[id]/info/views-chart.tsx index c92ead2..139a909 100644 --- a/packages/temp_frontend/app/routes/song/[id]/info/views-chart.tsx +++ b/packages/temp_frontend/app/routes/song/[id]/info/views-chart.tsx @@ -11,23 +11,23 @@ import { } from "@/components/ui/chart"; const chartConfigLight = { - views: { - label: "播放", - color: "#111417", - }, likes: { label: "点赞", }, + views: { + color: "#111417", + label: "播放", + }, } satisfies ChartConfig; const chartConfigDark = { - views: { - label: "播放", - color: "#EEEEF0", - }, likes: { label: "点赞", }, + views: { + color: "#EEEEF0", + label: "播放", + }, } satisfies ChartConfig; interface ChartData { diff --git a/packages/tracker/app/admin/users.tsx b/packages/tracker/app/admin/users.tsx index 00619a8..e61ce5d 100644 --- a/packages/tracker/app/admin/users.tsx +++ b/packages/tracker/app/admin/users.tsx @@ -35,7 +35,7 @@ import type { Route } from "./+types/users"; export function meta({}: Route.MetaArgs) { return [ { title: "User Management - Admin" }, - { name: "description", content: "Manage users and permissions" }, + { content: "Manage users and permissions", name: "description" }, ]; } @@ -48,7 +48,7 @@ export async function loader({ request }: Route.LoaderArgs) { // Fetch all users const allUsers = await db.select().from(users).orderBy(users.createdAt); - return { users: allUsers, currentUser: user }; + return { currentUser: user, users: allUsers }; } export async function action({ request }: Route.ActionArgs) { @@ -119,12 +119,12 @@ export async function action({ request }: Route.ActionArgs) { const hashedPassword = await hashPassword(password); await db.insert(users).values({ - id: await generateId(6), - username, - password: hashedPassword, - isAdmin, createdAt: new Date(), + id: await generateId(6), + isAdmin, + password: hashedPassword, updatedAt: new Date(), + username, }); return { success: true }; @@ -155,8 +155,8 @@ export async function action({ request }: Route.ActionArgs) { } const updateData: any = { - username, isAdmin, + username, }; // Only update password if provided diff --git a/packages/tracker/app/components/project/ProjectDialog.tsx b/packages/tracker/app/components/project/ProjectDialog.tsx index bf9fc0c..9a71d5b 100644 --- a/packages/tracker/app/components/project/ProjectDialog.tsx +++ b/packages/tracker/app/components/project/ProjectDialog.tsx @@ -41,9 +41,9 @@ export function ProjectDialog({ const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); onSubmit({ - name, description, isPublic, + name, }); onOpenChange(false); // Reset form diff --git a/packages/tracker/app/components/project/UserSearch.tsx b/packages/tracker/app/components/project/UserSearch.tsx index 04dc4f1..7f15d39 100644 --- a/packages/tracker/app/components/project/UserSearch.tsx +++ b/packages/tracker/app/components/project/UserSearch.tsx @@ -40,8 +40,8 @@ export function UserSearchModal({ try { const response = await fetch(`/project/${projectId}/settings`, { - method: "POST", body: formData, + method: "POST", }); if (response.ok) { diff --git a/packages/tracker/app/components/task/TaskForm.tsx b/packages/tracker/app/components/task/TaskForm.tsx index 1868f60..163ed8f 100644 --- a/packages/tracker/app/components/task/TaskForm.tsx +++ b/packages/tracker/app/components/task/TaskForm.tsx @@ -54,11 +54,11 @@ export function TaskForm({ const [isSubmitting, setIsSubmitting] = useState(false); const currentColumn = columns.find((col) => col.id === columnId); - const priorityLabel = { low: "Low", medium: "Medium", high: "High" }[priority]; + const priorityLabel = { high: "High", low: "Low", medium: "Medium" }[priority]; const priorityColor = { + high: "bg-red-100 text-red-800", low: "bg-green-100 text-green-800", medium: "bg-yellow-100 text-yellow-800", - high: "bg-red-100 text-red-800", }[priority]; const handleSubmit = async (e: React.FormEvent) => { @@ -71,11 +71,11 @@ export function TaskForm({ setIsSubmitting(true); try { await onSubmit({ - title: title.trim(), - description: description.trim(), columnId, - priority, + description: description.trim(), dueDate, + priority, + title: title.trim(), }); } finally { setIsSubmitting(false); diff --git a/packages/tracker/app/components/ui/badge.tsx b/packages/tracker/app/components/ui/badge.tsx index 5798168..12f0c69 100644 --- a/packages/tracker/app/components/ui/badge.tsx +++ b/packages/tracker/app/components/ui/badge.tsx @@ -7,20 +7,20 @@ import { cn } from "@/lib/utils"; const badgeVariants = cva( "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden", { + defaultVariants: { + variant: "default", + }, variants: { variant: { default: "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90", - secondary: - "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", destructive: "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", outline: "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", + secondary: + "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", }, }, - defaultVariants: { - variant: "default", - }, } ); diff --git a/packages/tracker/app/components/ui/button.tsx b/packages/tracker/app/components/ui/button.tsx index 3579d96..38fd105 100644 --- a/packages/tracker/app/components/ui/button.tsx +++ b/packages/tracker/app/components/ui/button.tsx @@ -7,29 +7,29 @@ import { cn } from "@/lib/utils"; const buttonVariants = cva( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", { + defaultVariants: { + size: "default", + variant: "default", + }, variants: { + size: { + default: "h-9 px-4 py-2 has-[>svg]:px-3", + icon: "size-9", + "icon-lg": "size-10", + "icon-sm": "size-8", + lg: "h-10 rounded-md px-6 has-[>svg]:px-4", + sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", + }, variant: { default: "bg-primary text-primary-foreground hover:bg-primary/90", destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", + link: "text-primary underline-offset-4 hover:underline", outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", - ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", - link: "text-primary underline-offset-4 hover:underline", }, - size: { - default: "h-9 px-4 py-2 has-[>svg]:px-3", - sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", - lg: "h-10 rounded-md px-6 has-[>svg]:px-4", - icon: "size-9", - "icon-sm": "size-8", - "icon-lg": "size-10", - }, - }, - defaultVariants: { - variant: "default", - size: "default", }, } ); @@ -49,7 +49,7 @@ function Button({ return ( ); diff --git a/packages/tracker/app/components/ui/calendar.tsx b/packages/tracker/app/components/ui/calendar.tsx index fc7916d..a176554 100644 --- a/packages/tracker/app/components/ui/calendar.tsx +++ b/packages/tracker/app/components/ui/calendar.tsx @@ -33,36 +33,16 @@ function Calendar({ ...formatters, }} classNames={{ - root: cn("w-fit", defaultClassNames.root), - months: cn("flex gap-4 flex-col md:flex-row relative", defaultClassNames.months), - month: cn("flex flex-col w-full gap-4", defaultClassNames.month), - nav: cn( - "flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between", - defaultClassNames.nav + button_next: cn( + buttonVariants({ variant: buttonVariant }), + "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none", + defaultClassNames.button_next ), button_previous: cn( buttonVariants({ variant: buttonVariant }), "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none", defaultClassNames.button_previous ), - button_next: cn( - buttonVariants({ variant: buttonVariant }), - "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none", - defaultClassNames.button_next - ), - month_caption: cn( - "flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)", - defaultClassNames.month_caption - ), - dropdowns: cn( - "w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5", - defaultClassNames.dropdowns - ), - dropdown_root: cn( - "relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md", - defaultClassNames.dropdown_root - ), - dropdown: cn("absolute bg-popover inset-0 opacity-0", defaultClassNames.dropdown), caption_label: cn( "select-none font-medium", captionLayout === "label" @@ -70,51 +50,61 @@ function Calendar({ : "rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5", defaultClassNames.caption_label ), - table: "w-full border-collapse", - weekdays: cn("flex", defaultClassNames.weekdays), - weekday: cn( - "text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none", - defaultClassNames.weekday - ), - week: cn("flex w-full mt-2", defaultClassNames.week), - week_number_header: cn( - "select-none w-(--cell-size)", - defaultClassNames.week_number_header - ), - week_number: cn( - "text-[0.8rem] select-none text-muted-foreground", - defaultClassNames.week_number - ), day: cn( "relative w-full h-full p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none", defaultClassNames.day ), - range_start: cn("rounded-l-md bg-accent", defaultClassNames.range_start), - range_middle: cn("rounded-none", defaultClassNames.range_middle), - range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end), - today: cn( - "bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none", - defaultClassNames.today + disabled: cn("text-muted-foreground opacity-50", defaultClassNames.disabled), + dropdown: cn("absolute bg-popover inset-0 opacity-0", defaultClassNames.dropdown), + dropdown_root: cn( + "relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md", + defaultClassNames.dropdown_root + ), + dropdowns: cn( + "w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5", + defaultClassNames.dropdowns + ), + hidden: cn("invisible", defaultClassNames.hidden), + month: cn("flex flex-col w-full gap-4", defaultClassNames.month), + month_caption: cn( + "flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)", + defaultClassNames.month_caption + ), + months: cn("flex gap-4 flex-col md:flex-row relative", defaultClassNames.months), + nav: cn( + "flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between", + defaultClassNames.nav ), outside: cn( "text-muted-foreground aria-selected:text-muted-foreground", defaultClassNames.outside ), - disabled: cn("text-muted-foreground opacity-50", defaultClassNames.disabled), - hidden: cn("invisible", defaultClassNames.hidden), + range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end), + range_middle: cn("rounded-none", defaultClassNames.range_middle), + range_start: cn("rounded-l-md bg-accent", defaultClassNames.range_start), + root: cn("w-fit", defaultClassNames.root), + table: "w-full border-collapse", + today: cn( + "bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none", + defaultClassNames.today + ), + week: cn("flex w-full mt-2", defaultClassNames.week), + week_number: cn( + "text-[0.8rem] select-none text-muted-foreground", + defaultClassNames.week_number + ), + week_number_header: cn( + "select-none w-(--cell-size)", + defaultClassNames.week_number_header + ), + weekday: cn( + "text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none", + defaultClassNames.weekday + ), + weekdays: cn("flex", defaultClassNames.weekdays), ...classNames, }} components={{ - Root: ({ className, rootRef, ...props }) => { - return ( -
- ); - }, Chevron: ({ className, orientation, ...props }) => { if (orientation === "left") { return ; @@ -127,6 +117,16 @@ function Calendar({ return ; }, DayButton: CalendarDayButton, + Root: ({ className, rootRef, ...props }) => { + return ( +
+ ); + }, WeekNumber: ({ children, ...props }) => { return ( diff --git a/packages/tracker/app/components/ui/sidebar.tsx b/packages/tracker/app/components/ui/sidebar.tsx index c7d7508..be7a2d7 100644 --- a/packages/tracker/app/components/ui/sidebar.tsx +++ b/packages/tracker/app/components/ui/sidebar.tsx @@ -104,12 +104,12 @@ function SidebarProvider({ const contextValue = React.useMemo( () => ({ - state, - open, - setOpen, isMobile, + open, openMobile, + setOpen, setOpenMobile, + state, toggleSidebar, }), [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar] @@ -452,21 +452,21 @@ function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) { const sidebarMenuButtonVariants = cva( "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0", { + defaultVariants: { + size: "default", + variant: "default", + }, variants: { + size: { + default: "h-8 text-sm", + lg: "h-12 text-sm group-data-[collapsible=icon]:p-0!", + sm: "h-7 text-xs", + }, variant: { default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground", outline: "bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]", }, - size: { - default: "h-8 text-sm", - sm: "h-7 text-xs", - lg: "h-12 text-sm group-data-[collapsible=icon]:p-0!", - }, - }, - defaultVariants: { - variant: "default", - size: "default", }, } ); @@ -493,7 +493,7 @@ function SidebarMenuButton({ data-sidebar="menu-button" data-size={size} data-active={isActive} - className={cn(sidebarMenuButtonVariants({ variant, size }), className)} + className={cn(sidebarMenuButtonVariants({ size, variant }), className)} {...props} /> ); diff --git a/packages/tracker/app/components/ui/sonner.tsx b/packages/tracker/app/components/ui/sonner.tsx index f204e9d..bde9a88 100644 --- a/packages/tracker/app/components/ui/sonner.tsx +++ b/packages/tracker/app/components/ui/sonner.tsx @@ -18,18 +18,18 @@ const Toaster = ({ ...props }: ToasterProps) => { theme={theme as ToasterProps["theme"]} className="toaster group" icons={{ - success: , - info: , - warning: , error: , + info: , loading: , + success: , + warning: , }} style={ { - "--normal-bg": "var(--popover)", - "--normal-text": "var(--popover-foreground)", - "--normal-border": "var(--border)", "--border-radius": "var(--radius)", + "--normal-bg": "var(--popover)", + "--normal-border": "var(--border)", + "--normal-text": "var(--popover-foreground)", } as React.CSSProperties } {...props} diff --git a/packages/tracker/app/projects/newProject.tsx b/packages/tracker/app/projects/newProject.tsx index b63ffd5..bf1a739 100644 --- a/packages/tracker/app/projects/newProject.tsx +++ b/packages/tracker/app/projects/newProject.tsx @@ -14,7 +14,7 @@ import type { Route } from "./+types/newProject"; export function meta({}: Route.MetaArgs) { return [ { title: "Create New Project" }, - { name: "description", content: "Create a new project for task management" }, + { content: "Create a new project for task management", name: "description" }, ]; } @@ -38,11 +38,11 @@ export async function action({ request }: Route.ActionArgs) { // Create the project await db.insert(projects).values({ - id: projectId, - ownerId: user.id, - name, - description, createdAt: now, + description, + id: projectId, + name, + ownerId: user.id, updatedAt: now, }); @@ -55,11 +55,11 @@ export async function action({ request }: Route.ActionArgs) { for (const column of defaultColumns) { await db.insert(columns).values({ + createdAt: now, id: await generateId(6), - projectId, name: column.name, position: column.position, - createdAt: now, + projectId, updatedAt: now, }); } diff --git a/packages/tracker/app/projects/projectPage.tsx b/packages/tracker/app/projects/projectPage.tsx index 5bdd962..b4bd515 100644 --- a/packages/tracker/app/projects/projectPage.tsx +++ b/packages/tracker/app/projects/projectPage.tsx @@ -17,7 +17,7 @@ import { projectPageAction } from "./projectPageAction"; export function meta({ loaderData }: Route.MetaArgs) { return [ { title: `${loaderData.project.name} - FramSpor` }, - { name: "description", content: `Manage tasks for ${loaderData.project.name}` }, + { content: `Manage tasks for ${loaderData.project.name}`, name: "description" }, ]; } @@ -77,10 +77,10 @@ export async function loader({ params, request }: Route.LoaderArgs) { ); return { - project, - columns: columnsWithTasks, - user, canEdit, + columns: columnsWithTasks, + project, + user, }; } @@ -144,8 +144,8 @@ export default function ProjectBoard({ loaderData }: Route.ComponentProps) { formData.append("name", data.name); const response = await fetch(`/project/${project.id}`, { - method: "POST", body: formData, + method: "POST", }); revalidator.revalidate(); @@ -157,8 +157,8 @@ export default function ProjectBoard({ loaderData }: Route.ComponentProps) { formData.append("columnId", columnId); const response = await fetch(`/project/${project.id}`, { - method: "POST", body: formData, + method: "POST", }); if (response.ok) { @@ -179,8 +179,8 @@ export default function ProjectBoard({ loaderData }: Route.ComponentProps) { formData.append("isPublic", data.isPublic ? "true" : "false"); const response = await fetch(`/project/${project.id}`, { - method: "POST", body: formData, + method: "POST", }); if (response.ok) { @@ -194,8 +194,8 @@ export default function ProjectBoard({ loaderData }: Route.ComponentProps) { formData.append("intent", "deleteProject"); const response = await fetch(`/project/${project.id}`, { - method: "POST", body: formData, + method: "POST", }); if (response.ok) { @@ -229,8 +229,8 @@ export default function ProjectBoard({ loaderData }: Route.ComponentProps) { } const response = await fetch(`/project/${project.id}`, { - method: "POST", body: formData, + method: "POST", }); revalidator.revalidate(); @@ -242,8 +242,8 @@ export default function ProjectBoard({ loaderData }: Route.ComponentProps) { formData.append("taskId", editingTask.id); const response = await fetch(`/project/${project.id}`, { - method: "POST", body: formData, + method: "POST", }); revalidator.revalidate(); @@ -279,8 +279,8 @@ export default function ProjectBoard({ loaderData }: Route.ComponentProps) { onSubmit={handleProjectSubmit} onDelete={handleDeleteProject} initialData={{ - name: project.name, description: project.description || "", + name: project.name, }} isEditing={true} /> diff --git a/packages/tracker/app/projects/projectPageAction.ts b/packages/tracker/app/projects/projectPageAction.ts index 9bf001b..247700d 100644 --- a/packages/tracker/app/projects/projectPageAction.ts +++ b/packages/tracker/app/projects/projectPageAction.ts @@ -67,14 +67,14 @@ export const projectPageAction = async ({ request, params }: Route.ActionArgs) = const taskId = await generateId(7); await db.insert(tasks).values({ - id: taskId, - projectId: projectId, columnId: columnId, - title: title, - description: description, - priority: priority, - dueDate: dueDate ? new Date(dueDate) : null, createdAt: new Date(), + description: description, + dueDate: dueDate ? new Date(dueDate) : null, + id: taskId, + priority: priority, + projectId: projectId, + title: title, updatedAt: new Date(), }); @@ -96,11 +96,11 @@ export const projectPageAction = async ({ request, params }: Route.ActionArgs) = await db .update(tasks) .set({ - title: title, - description: description, columnId: columnId, - priority: priority, + description: description, dueDate: dueDate ? new Date(dueDate) : null, + priority: priority, + title: title, updatedAt: new Date(), }) .where(eq(tasks.id, taskId)); @@ -142,15 +142,15 @@ export const projectPageAction = async ({ request, params }: Route.ActionArgs) = : 0; await db.insert(columns).values({ + createdAt: new Date(), id: columnId, - projectId: projectId, name: name, position: newPosition, - createdAt: new Date(), + projectId: projectId, updatedAt: new Date(), }); - return { success: true, columnId }; + return { columnId, success: true }; } if (intent === "updateColumn") { @@ -171,7 +171,7 @@ export const projectPageAction = async ({ request, params }: Route.ActionArgs) = }) .where(eq(columns.id, columnId)); - return { success: true, columnId }; + return { columnId, success: true }; } if (intent === "deleteColumn") { @@ -190,7 +190,7 @@ export const projectPageAction = async ({ request, params }: Route.ActionArgs) = await db.delete(columns).where(eq(columns.id, columnId)); - return { success: true, columnId }; + return { columnId, success: true }; } if (intent === "reorderColumns") { @@ -223,10 +223,10 @@ export const projectPageAction = async ({ request, params }: Route.ActionArgs) = await db .update(projects) .set({ - name: name, description: description, - updatedAt: new Date(), isPublic: isPublic, + name: name, + updatedAt: new Date(), }) .where(eq(projects.id, projectId)); @@ -248,7 +248,7 @@ export const projectPageAction = async ({ request, params }: Route.ActionArgs) = await db.delete(projects).where(eq(projects.id, projectId)); - return { success: true, redirect: "/" }; + return { redirect: "/", success: true }; } return { error: "Unknown action" }; diff --git a/packages/tracker/app/projects/settings.tsx b/packages/tracker/app/projects/settings.tsx index afbca67..c79c33f 100644 --- a/packages/tracker/app/projects/settings.tsx +++ b/packages/tracker/app/projects/settings.tsx @@ -43,7 +43,7 @@ import type { Route } from "./+types/settings"; export function meta({}: Route.MetaArgs) { return [ { title: "Project Settings" }, - { name: "description", content: "Manage project settings and permissions" }, + { content: "Manage project settings and permissions", name: "description" }, ]; } @@ -85,7 +85,7 @@ export async function loader({ request, params }: Route.LoaderArgs) { .where(and(eq(projects.id, projectId), eq(projects.ownerId, user.id))) .get(); - return { project, allUsers, currentPermissions, currentUser: user, isOwner }; + return { allUsers, currentPermissions, currentUser: user, isOwner, project }; } export async function action({ request, params }: Route.ActionArgs) { @@ -127,9 +127,9 @@ export async function action({ request, params }: Route.ActionArgs) { await db .update(projects) .set({ - name, description, isPublic, + name, updatedAt: new Date(), }) .where(eq(projects.id, projectId)); @@ -172,11 +172,11 @@ export async function action({ request, params }: Route.ActionArgs) { } await db.insert(projectPermissions).values({ + canEdit: canEditPermission, + createdAt: new Date(), id: crypto.randomUUID(), projectId, userId, - canEdit: canEditPermission, - createdAt: new Date(), }); return redirect(`/project/${projectId}`); @@ -333,8 +333,8 @@ export function UsersManagement({ fetch( `/project/${project.id}/settings`, { - method: "POST", body: formData, + method: "POST", } ); }} diff --git a/packages/tracker/app/root.tsx b/packages/tracker/app/root.tsx index b25a50d..8213061 100644 --- a/packages/tracker/app/root.tsx +++ b/packages/tracker/app/root.tsx @@ -12,15 +12,15 @@ import "./app.css"; import { Toaster } from "@/components/ui/sonner"; export const links: Route.LinksFunction = () => [ - { rel: "preconnect", href: "https://fonts.googleapis.com" }, + { href: "https://fonts.googleapis.com", rel: "preconnect" }, { - rel: "preconnect", - href: "https://fonts.gstatic.com", crossOrigin: "anonymous", + href: "https://fonts.gstatic.com", + rel: "preconnect", }, { - rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap", + rel: "stylesheet", }, ]; diff --git a/packages/tracker/app/setup/setup.tsx b/packages/tracker/app/setup/setup.tsx index f20489f..8ad76f1 100644 --- a/packages/tracker/app/setup/setup.tsx +++ b/packages/tracker/app/setup/setup.tsx @@ -12,7 +12,7 @@ import type { Route } from "./+types/setup"; export function meta({}: Route.MetaArgs) { return [ { title: "Initial Setup" }, - { name: "description", content: "Create initial admin user" }, + { content: "Create initial admin user", name: "description" }, ]; } diff --git a/packages/tracker/app/user/profile.tsx b/packages/tracker/app/user/profile.tsx index e90d2c7..a00770e 100644 --- a/packages/tracker/app/user/profile.tsx +++ b/packages/tracker/app/user/profile.tsx @@ -16,7 +16,7 @@ import type { Route } from "./+types/profile"; export function meta({}: Route.MetaArgs) { return [ { title: "User Profile" }, - { name: "description", content: "Manage your account settings" }, + { content: "Manage your account settings", name: "description" }, ]; } @@ -67,7 +67,7 @@ export async function action({ request }: Route.ActionArgs) { .set({ password: hashedNewPassword, updatedAt: new Date() }) .where(eq(users.id, user.id)); - return { success: true, message: "Password updated successfully" }; + return { message: "Password updated successfully", success: true }; } if (intent === "changeUsername") { @@ -88,14 +88,14 @@ export async function action({ request }: Route.ActionArgs) { await db .update(users) - .set({ username: newUsername, updatedAt: new Date() }) + .set({ updatedAt: new Date(), username: newUsername }) .where(eq(users.id, user.id)); const updatedUser = await db.select().from(users).where(eq(users.id, user.id)).get(); return { - success: true, message: "Username updated successfully", + success: true, updatedUser, }; } diff --git a/packages/tracker/drizzle.config.ts b/packages/tracker/drizzle.config.ts index e2fceca..b936fcf 100644 --- a/packages/tracker/drizzle.config.ts +++ b/packages/tracker/drizzle.config.ts @@ -1,10 +1,10 @@ import { defineConfig } from "drizzle-kit"; export default defineConfig({ - out: "./drizzle", - schema: "./lib/db/schema.ts", - dialect: "sqlite", dbCredentials: { url: process.env.DB_FILE_NAME!, }, + dialect: "sqlite", + out: "./drizzle", + schema: "./lib/db/schema.ts", }); diff --git a/packages/tracker/lib/auth.ts b/packages/tracker/lib/auth.ts index 5d1a49c..0ce9302 100644 --- a/packages/tracker/lib/auth.ts +++ b/packages/tracker/lib/auth.ts @@ -24,11 +24,11 @@ export async function createUser(username: string, password: string) { const hashedPassword = await Argon2id.hashEncoded(password); await db.insert(users).values({ - id: userId, - username, - password: hashedPassword, createdAt: now, + id: userId, + password: hashedPassword, updatedAt: now, + username, }); return userId; @@ -52,10 +52,10 @@ export async function createSession(userId: string) { const expiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000); // 30 days await db.insert(sessions).values({ + createdAt: new Date(), + expiresAt, id: sessionId, userId, - expiresAt, - createdAt: new Date(), }); return sessionId; diff --git a/packages/tracker/lib/db/schema.ts b/packages/tracker/lib/db/schema.ts index ae8ed07..3aa684f 100644 --- a/packages/tracker/lib/db/schema.ts +++ b/packages/tracker/lib/db/schema.ts @@ -3,26 +3,28 @@ import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; // Users table export const users = sqliteTable("users", { - id: text("id").primaryKey(), - username: text("username").notNull(), - password: text("password_hash").notNull(), - isAdmin: integer("is_admin", { mode: "boolean" }).default(false), createdAt: integer("created_at", { mode: "timestamp" }).notNull(), + id: text("id").primaryKey(), + isAdmin: integer("is_admin", { mode: "boolean" }).default(false), + password: text("password_hash").notNull(), updatedAt: integer("updated_at", { mode: "timestamp" }).notNull(), + username: text("username").notNull(), }); // Sessions table for authentication export const sessions = sqliteTable("sessions", { + createdAt: integer("created_at", { mode: "timestamp" }).notNull(), + expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(), id: text("id").primaryKey(), userId: text("user_id") .notNull() .references(() => users.id, { onDelete: "cascade" }), - expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(), - createdAt: integer("created_at", { mode: "timestamp" }).notNull(), }); // Project permissions table export const projectPermissions = sqliteTable("project_permissions", { + canEdit: integer("can_edit", { mode: "boolean" }).default(false), + createdAt: integer("created_at", { mode: "timestamp" }).notNull(), id: text("id").primaryKey(), projectId: text("project_id") .notNull() @@ -30,49 +32,47 @@ export const projectPermissions = sqliteTable("project_permissions", { userId: text("user_id") .notNull() .references(() => users.id, { onDelete: "cascade" }), - canEdit: integer("can_edit", { mode: "boolean" }).default(false), - createdAt: integer("created_at", { mode: "timestamp" }).notNull(), }); // Projects table export const projects = sqliteTable("projects", { + createdAt: integer("created_at", { mode: "timestamp" }).notNull(), + description: text("description"), id: text("id").primaryKey(), + isPublic: integer("is_public", { mode: "boolean" }).default(false), + name: text("name").notNull(), ownerId: text("owner_id") .notNull() .references(() => users.id, { onDelete: "cascade" }), - name: text("name").notNull(), - description: text("description"), - isPublic: integer("is_public", { mode: "boolean" }).default(false), - createdAt: integer("created_at", { mode: "timestamp" }).notNull(), updatedAt: integer("updated_at", { mode: "timestamp" }).notNull(), }); // Columns table for Kanban board export const columns = sqliteTable("columns", { + createdAt: integer("created_at", { mode: "timestamp" }).notNull(), id: text("id").primaryKey(), + name: text("name").notNull(), + position: integer("position").notNull(), projectId: text("project_id") .notNull() .references(() => projects.id, { onDelete: "cascade" }), - name: text("name").notNull(), - position: integer("position").notNull(), - createdAt: integer("created_at", { mode: "timestamp" }).notNull(), updatedAt: integer("updated_at", { mode: "timestamp" }).notNull(), }); // Tasks table export const tasks = sqliteTable("tasks", { - id: text("id").primaryKey(), - projectId: text("project_id") - .notNull() - .references(() => projects.id, { onDelete: "cascade" }), columnId: text("column_id") .notNull() .references(() => columns.id, { onDelete: "cascade" }), - title: text("title").notNull(), - description: text("description"), - priority: text("priority", { enum: ["low", "medium", "high"] }).default("medium"), - dueDate: integer("due_date", { mode: "timestamp" }), createdAt: integer("created_at", { mode: "timestamp" }).notNull(), + description: text("description"), + dueDate: integer("due_date", { mode: "timestamp" }), + id: text("id").primaryKey(), + priority: text("priority", { enum: ["low", "medium", "high"] }).default("medium"), + projectId: text("project_id") + .notNull() + .references(() => projects.id, { onDelete: "cascade" }), + title: text("title").notNull(), updatedAt: integer("updated_at", { mode: "timestamp" }).notNull(), }); @@ -91,13 +91,13 @@ export const sessionsRelations = relations(sessions, ({ one }) => ({ })); export const projectsRelations = relations(projects, ({ one, many }) => ({ + columns: many(columns), owner: one(users, { fields: [projects.ownerId], references: [users.id], relationName: "owner", }), permissions: many(projectPermissions), - columns: many(columns), tasks: many(tasks), })); @@ -121,14 +121,14 @@ export const columnsRelations = relations(columns, ({ one, many }) => ({ })); export const tasksRelations = relations(tasks, ({ one }) => ({ - project: one(projects, { - fields: [tasks.projectId], - references: [projects.id], - }), column: one(columns, { fields: [tasks.columnId], references: [columns.id], }), + project: one(projects, { + fields: [tasks.projectId], + references: [projects.id], + }), })); // Types diff --git a/src/aliyun-fc.mjs b/src/aliyun-fc.mjs index 97b96e2..25d3aa0 100644 --- a/src/aliyun-fc.mjs +++ b/src/aliyun-fc.mjs @@ -35,8 +35,8 @@ export const handler = async (event, _context) => { const randomUserAgent = userAgents[Math.floor(Math.random() * userAgents.length)]; const response = await fetch(eventObj.url, { headers: { - "User-Agent": randomUserAgent, Referer: refererUrl, + "User-Agent": randomUserAgent, }, }); statusCode = response.status; @@ -51,19 +51,19 @@ export const handler = async (event, _context) => { const randomUserAgent = userAgents[Math.floor(Math.random() * userAgents.length)]; const response = await fetch(url, { headers: { - "User-Agent": randomUserAgent, Referer: refererUrl, + "User-Agent": randomUserAgent, }, }); const responseBody = await response.text(); return { - statusCode: response.status, body: responseBody, + statusCode: response.status, }; } catch (error) { return { - statusCode: 500, body: `Error fetching URL: ${error.message}`, + statusCode: 500, }; } }); @@ -73,7 +73,7 @@ export const handler = async (event, _context) => { } return { - statusCode: statusCode, body: JSON.stringify(body), + statusCode: statusCode, }; }; diff --git a/src/fullSnapshot.ts b/src/fullSnapshot.ts index af79a67..4af600d 100644 --- a/src/fullSnapshot.ts +++ b/src/fullSnapshot.ts @@ -10,8 +10,8 @@ const quit = (reason?: string) => { }; const args = arg({ - "--db": String, "--aids": String, + "--db": String, }); const dbPath = args["--db"];