From 900cd28a04fcd1825dfbca1a96fb4ab3bebbb5a4 Mon Sep 17 00:00:00 2001 From: imbytecat Date: Mon, 26 Jan 2026 15:24:33 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E7=A7=BB=E9=99=A4=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E4=B8=8E=E5=BE=85=E5=8A=9E=E4=BA=8B=E9=A1=B9=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E5=8A=9F=E8=83=BD=EF=BC=8C=E9=87=8D=E6=9E=84=E8=AE=B8?= =?UTF-8?q?=E5=8F=AF=E8=AF=81=E7=AE=A1=E7=90=86=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除设备信息表和待办事项表 - 添加许可证激活记录表的数据库模式快照,包含指纹唯一约束和时间戳字段。 - 添加新的迁移版本记录以支持最新数据库结构变更 - 移除 todo 相关操作的默认缓存失效配置,将 experimental_defaults 设置为空对象。 - 删除设备初始化逻辑,移除硬件指纹获取及数据库初始化相关功能。 - 将主页组件从待办事项列表替换为 License 管理系统欢迎界面,并添加设备指纹和 License 激活两个功能入口链接。 - 删除设备相关接口契约定义及验证规则 - 移除未使用的设备和待办事项合约导入,并更新合约导出对象。 - 删除待办事项接口契约文件中定义的输入输出验证 schema 及其相关路由契约 - 移除设备初始化逻辑,仅保留许可证激活初始化。 - 删除设备信息获取和许可证设置的API路由逻辑 - 移除未使用的路由模块导入并清理路由注册列表 - 删除待办事项相关的API路由定义及其实现逻辑 - 删除设备信息表的数据库模式定义。 - 移除设备信息表的导出,仅保留许可证激活表的导出。 - 删除待办事项数据表的定义及其相关字段配置 --- apps/server/drizzle/0002_dizzy_kingpin.sql | 2 + apps/server/drizzle/meta/0002_snapshot.json | 76 ++++++ apps/server/drizzle/meta/_journal.json | 7 + apps/server/src/client/query-client.ts | 26 +- apps/server/src/lib/device-init.ts | 61 ----- apps/server/src/routes/index.tsx | 228 +++--------------- .../server/api/contracts/device.contract.ts | 15 -- apps/server/src/server/api/contracts/index.ts | 4 - .../src/server/api/contracts/todo.contract.ts | 43 ---- .../server/api/middlewares/db.middleware.ts | 2 - .../src/server/api/routers/device.router.ts | 54 ----- apps/server/src/server/api/routers/index.ts | 4 - .../src/server/api/routers/todo.router.ts | 49 ---- .../src/server/db/schema/device-info.ts | 14 -- apps/server/src/server/db/schema/index.ts | 2 - apps/server/src/server/db/schema/todo.ts | 8 - 16 files changed, 119 insertions(+), 476 deletions(-) create mode 100644 apps/server/drizzle/0002_dizzy_kingpin.sql create mode 100644 apps/server/drizzle/meta/0002_snapshot.json delete mode 100644 apps/server/src/lib/device-init.ts delete mode 100644 apps/server/src/server/api/contracts/device.contract.ts delete mode 100644 apps/server/src/server/api/contracts/todo.contract.ts delete mode 100644 apps/server/src/server/api/routers/device.router.ts delete mode 100644 apps/server/src/server/api/routers/todo.router.ts delete mode 100644 apps/server/src/server/db/schema/device-info.ts delete mode 100644 apps/server/src/server/db/schema/todo.ts diff --git a/apps/server/drizzle/0002_dizzy_kingpin.sql b/apps/server/drizzle/0002_dizzy_kingpin.sql new file mode 100644 index 0000000..789f210 --- /dev/null +++ b/apps/server/drizzle/0002_dizzy_kingpin.sql @@ -0,0 +1,2 @@ +DROP TABLE `device_info`;--> statement-breakpoint +DROP TABLE `todo`; \ No newline at end of file diff --git a/apps/server/drizzle/meta/0002_snapshot.json b/apps/server/drizzle/meta/0002_snapshot.json new file mode 100644 index 0000000..d62e5b6 --- /dev/null +++ b/apps/server/drizzle/meta/0002_snapshot.json @@ -0,0 +1,76 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "2bdd10cc-8e14-4843-931a-37b1d00f21a6", + "prevId": "14bad572-1fc5-489d-90f2-7560a7cad1f4", + "tables": { + "license_activation": { + "name": "license_activation", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "license": { + "name": "license", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "license_activated_at": { + "name": "license_activated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "license_activation_fingerprint_unique": { + "name": "license_activation_fingerprint_unique", + "columns": ["fingerprint"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} diff --git a/apps/server/drizzle/meta/_journal.json b/apps/server/drizzle/meta/_journal.json index 172e64c..1a7cc47 100644 --- a/apps/server/drizzle/meta/_journal.json +++ b/apps/server/drizzle/meta/_journal.json @@ -15,6 +15,13 @@ "when": 1769409970060, "tag": "0001_watery_mongu", "breakpoints": true + }, + { + "idx": 2, + "version": "6", + "when": 1769412006512, + "tag": "0002_dizzy_kingpin", + "breakpoints": true } ] } diff --git a/apps/server/src/client/query-client.ts b/apps/server/src/client/query-client.ts index 56c4976..0478f13 100644 --- a/apps/server/src/client/query-client.ts +++ b/apps/server/src/client/query-client.ts @@ -2,29 +2,5 @@ import { createTanstackQueryUtils } from '@orpc/tanstack-query' import { orpc as orpcClient } from './orpc.client' export const orpc = createTanstackQueryUtils(orpcClient, { - experimental_defaults: { - todo: { - create: { - mutationOptions: { - onSuccess: (_, __, ___, ctx) => { - ctx.client.invalidateQueries({ queryKey: orpc.todo.list.key() }) - }, - }, - }, - update: { - mutationOptions: { - onSuccess: (_, __, ___, ctx) => { - ctx.client.invalidateQueries({ queryKey: orpc.todo.list.key() }) - }, - }, - }, - remove: { - mutationOptions: { - onSuccess: (_, __, ___, ctx) => { - ctx.client.invalidateQueries({ queryKey: orpc.todo.list.key() }) - }, - }, - }, - }, - }, + experimental_defaults: {}, }) diff --git a/apps/server/src/lib/device-init.ts b/apps/server/src/lib/device-init.ts deleted file mode 100644 index bfd8e66..0000000 --- a/apps/server/src/lib/device-init.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { getHardwareFingerprint } from '@/lib/fingerprint' -import { getDB } from '@/server/db' -import { deviceInfoTable } from '@/server/db/schema' - -let initPromise: Promise | null = null - -/** - * 确保设备信息已在数据库中初始化 - * 使用单例模式防止并发重复初始化 - */ -export async function ensureDeviceInitialized(): Promise { - if (initPromise) { - return initPromise - } - - initPromise = (async () => { - try { - const db = getDB() - - // 获取硬件指纹 - let result: { fingerprint: string; quality: 'strong' | 'medium' | 'weak' } - try { - result = await getHardwareFingerprint({ - cacheTtlMs: 10 * 60 * 1000, - includePrimaryDisk: true, - }) - } catch (error) { - console.error('Failed to get hardware fingerprint:', error) - // 回退逻辑 - result = { - fingerprint: `unknown-${Date.now()}`, - quality: 'weak' as const, - } - } - - // 使用 UPSERT 逻辑更新或插入设备信息 - await db - .insert(deviceInfoTable) - .values({ - fingerprint: result.fingerprint, - fingerprintQuality: result.quality, - license: null, - licenseActivatedAt: null, - }) - .onConflictDoUpdate({ - target: deviceInfoTable.fingerprint, - set: { - fingerprintQuality: result.quality, - updatedAt: new Date(), - }, - }) - } catch (error) { - console.error('Failed to initialize device info:', error) - // 重置 promise 以允许重试 - initPromise = null - throw error - } - })() - - return initPromise -} diff --git a/apps/server/src/routes/index.tsx b/apps/server/src/routes/index.tsx index 61b7eaa..609c5d1 100644 --- a/apps/server/src/routes/index.tsx +++ b/apps/server/src/routes/index.tsx @@ -1,206 +1,44 @@ -import { useMutation, useSuspenseQuery } from '@tanstack/react-query' -import { createFileRoute } from '@tanstack/react-router' -import type { ChangeEventHandler, FormEventHandler } from 'react' -import { useState } from 'react' -import { orpc } from '@/client/query-client' +import { createFileRoute, Link } from '@tanstack/react-router' export const Route = createFileRoute('/')({ - component: Todos, - loader: async ({ context }) => { - await context.queryClient.ensureQueryData(orpc.todo.list.queryOptions()) - }, + component: Home, }) -function Todos() { - const [newTodoTitle, setNewTodoTitle] = useState('') - - const listQuery = useSuspenseQuery(orpc.todo.list.queryOptions()) - const createMutation = useMutation(orpc.todo.create.mutationOptions()) - const updateMutation = useMutation(orpc.todo.update.mutationOptions()) - const deleteMutation = useMutation(orpc.todo.remove.mutationOptions()) - - const handleCreateTodo: FormEventHandler = (e) => { - e.preventDefault() - if (newTodoTitle.trim()) { - createMutation.mutate({ title: newTodoTitle.trim() }) - setNewTodoTitle('') - } - } - - const handleInputChange: ChangeEventHandler = (e) => { - setNewTodoTitle(e.target.value) - } - - const handleToggleTodo = (id: string, currentCompleted: boolean) => { - updateMutation.mutate({ - id, - data: { completed: !currentCompleted }, - }) - } - - const handleDeleteTodo = (id: string) => { - deleteMutation.mutate({ id }) - } - - const todos = listQuery.data - const completedCount = todos.filter((todo) => todo.completed).length - const totalCount = todos.length - const progress = totalCount > 0 ? (completedCount / totalCount) * 100 : 0 - +function Home() { return (
-
- {/* Header */} -
-
-

- 我的待办 -

-

保持专注,逐个击破

-
-
-
- {completedCount} - /{totalCount} -
-
- 已完成 -
-
-
+
+

+ License 管理系统 +

+

+ 欢迎使用基于 TanStack Start + ORPC 的 License 管理系统 +

- {/* Add Todo Form */} -
-
- - -
-
+
+ +

+ 设备指纹 +

+

+ 查询当前设备的硬件指纹信息,用于 License 绑定 +

+ - {/* Progress Bar (Only visible when there are tasks) */} - {totalCount > 0 && ( -
-
-
- )} - - {/* Todo List */} -
- {todos.length === 0 ? ( -
-
- -
-

没有待办事项

-

- 输入上方内容添加您的第一个任务 -

-
- ) : ( - todos.map((todo) => ( -
- - -
-

- {todo.title} -

-
- -
- - {new Date(todo.createdAt).toLocaleDateString('zh-CN')} - - -
-
- )) - )} + +

+ License 激活 +

+

+ 管理和激活当前设备的 License 授权状态 +

+
diff --git a/apps/server/src/server/api/contracts/device.contract.ts b/apps/server/src/server/api/contracts/device.contract.ts deleted file mode 100644 index eb5360c..0000000 --- a/apps/server/src/server/api/contracts/device.contract.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { oc } from '@orpc/contract' -import { z } from 'zod' - -const deviceInfoSchema = z.object({ - fingerprint: z.string(), - quality: z.enum(['strong', 'medium', 'weak']), - license: z.string().nullable(), - licenseActivatedAt: z.number().nullable(), -}) - -export const getInfo = oc.input(z.void()).output(deviceInfoSchema) - -export const setLicense = oc - .input(z.object({ license: z.string().min(1) })) - .output(z.object({ success: z.boolean() })) diff --git a/apps/server/src/server/api/contracts/index.ts b/apps/server/src/server/api/contracts/index.ts index 7f5c61d..e38d951 100644 --- a/apps/server/src/server/api/contracts/index.ts +++ b/apps/server/src/server/api/contracts/index.ts @@ -1,12 +1,8 @@ -import * as device from './device.contract' import * as fingerprint from './fingerprint.contract' import * as license from './license.contract' -import * as todo from './todo.contract' export const contract = { - device, fingerprint, - todo, license, } diff --git a/apps/server/src/server/api/contracts/todo.contract.ts b/apps/server/src/server/api/contracts/todo.contract.ts deleted file mode 100644 index 4f483bd..0000000 --- a/apps/server/src/server/api/contracts/todo.contract.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { oc } from '@orpc/contract' -import { - createInsertSchema, - createSelectSchema, - createUpdateSchema, -} from 'drizzle-zod' -import { z } from 'zod' -import { todoTable } from '@/server/db/schema' - -const selectSchema = createSelectSchema(todoTable) - -const insertSchema = createInsertSchema(todoTable).omit({ - id: true, - createdAt: true, - updatedAt: true, -}) - -const updateSchema = createUpdateSchema(todoTable).omit({ - id: true, - createdAt: true, - updatedAt: true, -}) - -export const list = oc.input(z.void()).output(z.array(selectSchema)) - -export const create = oc.input(insertSchema).output(selectSchema) - -export const update = oc - .input( - z.object({ - id: z.uuid(), - data: updateSchema, - }), - ) - .output(selectSchema) - -export const remove = oc - .input( - z.object({ - id: z.uuid(), - }), - ) - .output(z.void()) diff --git a/apps/server/src/server/api/middlewares/db.middleware.ts b/apps/server/src/server/api/middlewares/db.middleware.ts index 093b019..cf4d2d9 100644 --- a/apps/server/src/server/api/middlewares/db.middleware.ts +++ b/apps/server/src/server/api/middlewares/db.middleware.ts @@ -1,10 +1,8 @@ import { os } from '@orpc/server' -import { ensureDeviceInitialized } from '@/lib/device-init' import { ensureLicenseActivationInitialized } from '@/lib/license-init' import { getDB } from '@/server/db' export const dbProvider = os.middleware(async ({ context, next }) => { - await ensureDeviceInitialized() await ensureLicenseActivationInitialized() return next({ context: { diff --git a/apps/server/src/server/api/routers/device.router.ts b/apps/server/src/server/api/routers/device.router.ts deleted file mode 100644 index 56aee67..0000000 --- a/apps/server/src/server/api/routers/device.router.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { ORPCError } from '@orpc/server' -import { eq } from 'drizzle-orm' -import { ensureDeviceInitialized } from '@/lib/device-init' -import { deviceInfoTable } from '@/server/db/schema' -import { db } from '../middlewares' -import { os } from '../server' - -export const getInfo = os.device.getInfo - .use(db) - .handler(async ({ context }) => { - // 再次确保初始化(竞态条件兜底) - await ensureDeviceInitialized() - - const info = await context.db.query.deviceInfoTable.findFirst() - - if (!info) { - // 理论上不应该发生,因为 ensureDeviceInitialized 已经调用 - throw new ORPCError('NOT_FOUND', { - message: 'Device info not found after initialization', - }) - } - - return { - fingerprint: info.fingerprint, - quality: info.fingerprintQuality, - license: info.license, - licenseActivatedAt: info.licenseActivatedAt?.getTime() ?? null, - } - }) - -export const setLicense = os.device.setLicense - .use(db) - .handler(async ({ context, input }) => { - await ensureDeviceInitialized() - - const info = await context.db.query.deviceInfoTable.findFirst() - - if (!info) { - throw new ORPCError('NOT_FOUND', { - message: 'Device info not found', - }) - } - - await context.db - .update(deviceInfoTable) - .set({ - license: input.license, - licenseActivatedAt: new Date(), - updatedAt: new Date(), - }) - .where(eq(deviceInfoTable.id, info.id)) - - return { success: true } - }) diff --git a/apps/server/src/server/api/routers/index.ts b/apps/server/src/server/api/routers/index.ts index 6e33bd1..58041da 100644 --- a/apps/server/src/server/api/routers/index.ts +++ b/apps/server/src/server/api/routers/index.ts @@ -1,12 +1,8 @@ import { os } from '../server' -import * as device from './device.router' import * as fingerprint from './fingerprint.router' import * as license from './license.router' -import * as todo from './todo.router' export const router = os.router({ - device, fingerprint, - todo, license, }) diff --git a/apps/server/src/server/api/routers/todo.router.ts b/apps/server/src/server/api/routers/todo.router.ts deleted file mode 100644 index e176dea..0000000 --- a/apps/server/src/server/api/routers/todo.router.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { ORPCError } from '@orpc/server' -import { eq } from 'drizzle-orm' -import { todoTable } from '@/server/db/schema' -import { db } from '../middlewares' -import { os } from '../server' - -export const list = os.todo.list.use(db).handler(async ({ context }) => { - const todos = await context.db.query.todoTable.findMany({ - orderBy: (todos, { desc }) => [desc(todos.createdAt)], - }) - return todos -}) - -export const create = os.todo.create - .use(db) - .handler(async ({ context, input }) => { - const [newTodo] = await context.db - .insert(todoTable) - .values(input) - .returning() - - if (!newTodo) { - throw new ORPCError('NOT_FOUND') - } - - return newTodo - }) - -export const update = os.todo.update - .use(db) - .handler(async ({ context, input }) => { - const [updatedTodo] = await context.db - .update(todoTable) - .set(input.data) - .where(eq(todoTable.id, input.id)) - .returning() - - if (!updatedTodo) { - throw new ORPCError('NOT_FOUND') - } - - return updatedTodo - }) - -export const remove = os.todo.remove - .use(db) - .handler(async ({ context, input }) => { - await context.db.delete(todoTable).where(eq(todoTable.id, input.id)) - }) diff --git a/apps/server/src/server/db/schema/device-info.ts b/apps/server/src/server/db/schema/device-info.ts deleted file mode 100644 index b8872c3..0000000 --- a/apps/server/src/server/db/schema/device-info.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core' -import { generatedFields } from './utils/field' - -export const deviceInfoTable = sqliteTable('device_info', { - ...generatedFields, - fingerprint: text('fingerprint').notNull().unique(), - fingerprintQuality: text('fingerprint_quality', { - enum: ['strong', 'medium', 'weak'], - }).notNull(), - license: text('license'), - licenseActivatedAt: integer('license_activated_at', { - mode: 'timestamp_ms', - }), -}) diff --git a/apps/server/src/server/db/schema/index.ts b/apps/server/src/server/db/schema/index.ts index ddebe30..cc99460 100644 --- a/apps/server/src/server/db/schema/index.ts +++ b/apps/server/src/server/db/schema/index.ts @@ -1,3 +1 @@ -export * from './device-info' export * from './license-activation' -export * from './todo' diff --git a/apps/server/src/server/db/schema/todo.ts b/apps/server/src/server/db/schema/todo.ts deleted file mode 100644 index 5b09544..0000000 --- a/apps/server/src/server/db/schema/todo.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core' -import { generatedFields } from './utils/field' - -export const todoTable = sqliteTable('todo', { - ...generatedFields, - title: text('title').notNull(), - completed: integer('completed', { mode: 'boolean' }).notNull().default(false), -})