diff --git a/apps/server/package.json b/apps/server/package.json index f587549..98c60c4 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -55,6 +55,7 @@ "@tanstack/react-router-devtools": "catalog:", "@types/bun": "catalog:", "@vitejs/plugin-react": "catalog:", + "babel-plugin-react-compiler": "^1.0.0", "drizzle-kit": "catalog:", "nitro": "catalog:", "tailwindcss": "catalog:", diff --git a/apps/server/src/modules/bookmarks/contract.ts b/apps/server/src/modules/bookmarks/contract.ts index d326498..4307659 100644 --- a/apps/server/src/modules/bookmarks/contract.ts +++ b/apps/server/src/modules/bookmarks/contract.ts @@ -1,16 +1,16 @@ import { oc } from '@orpc/contract' import { createInsertSchema, createSelectSchema, createUpdateSchema } from 'drizzle-orm/zod' import { z } from 'zod' -import { bookmarkTable, categoryTable } from '@/modules/bookmarks/schema' +import * as schema from '@/modules/bookmarks/schema' import { generatedFieldKeys } from '@/server/db/fields' -const categorySelect = createSelectSchema(categoryTable) -const categoryInsert = createInsertSchema(categoryTable).omit(generatedFieldKeys).omit({ userId: true }) -const categoryUpdate = createUpdateSchema(categoryTable).omit(generatedFieldKeys).omit({ userId: true }) +const categorySelect = createSelectSchema(schema.category) +const categoryInsert = createInsertSchema(schema.category).omit(generatedFieldKeys).omit({ userId: true }) +const categoryUpdate = createUpdateSchema(schema.category).omit(generatedFieldKeys).omit({ userId: true }) -const bookmarkSelect = createSelectSchema(bookmarkTable) -const bookmarkInsert = createInsertSchema(bookmarkTable).omit(generatedFieldKeys).omit({ userId: true }) -const bookmarkUpdate = createUpdateSchema(bookmarkTable).omit(generatedFieldKeys).omit({ userId: true }) +const bookmarkSelect = createSelectSchema(schema.bookmark) +const bookmarkInsert = createInsertSchema(schema.bookmark).omit(generatedFieldKeys).omit({ userId: true }) +const bookmarkUpdate = createUpdateSchema(schema.bookmark).omit(generatedFieldKeys).omit({ userId: true }) export const category = { list: oc.input(z.void()).output(z.array(categorySelect.extend({ bookmarks: z.array(bookmarkSelect) }))), diff --git a/apps/server/src/modules/bookmarks/router.ts b/apps/server/src/modules/bookmarks/router.ts index 079f275..ecaa21e 100644 --- a/apps/server/src/modules/bookmarks/router.ts +++ b/apps/server/src/modules/bookmarks/router.ts @@ -1,15 +1,15 @@ import { ORPCError } from '@orpc/server' import { and, eq } from 'drizzle-orm' -import { bookmarkTable, categoryTable } from '@/modules/bookmarks/schema' -import { authMiddleware, db } from '@/server/api/middlewares' +import * as schema from '@/modules/bookmarks/schema' +import { authMiddleware, dbMiddleware } from '@/server/api/middlewares' import { os } from '@/server/api/server' export const category = { list: os.bookmarks.category.list - .use(db) + .use(dbMiddleware) .use(authMiddleware) .handler(async ({ context }) => { - return await context.db.query.categoryTable.findMany({ + return await context.db.query.category.findMany({ where: { userId: context.user.id }, orderBy: { orderId: 'asc' }, with: { @@ -21,11 +21,11 @@ export const category = { }), create: os.bookmarks.category.create - .use(db) + .use(dbMiddleware) .use(authMiddleware) .handler(async ({ context, input }) => { const [created] = await context.db - .insert(categoryTable) + .insert(schema.category) .values({ ...input, userId: context.user.id }) .returning() if (!created) throw new ORPCError('INTERNAL_SERVER_ERROR', { message: 'Failed to create category' }) @@ -33,39 +33,39 @@ export const category = { }), update: os.bookmarks.category.update - .use(db) + .use(dbMiddleware) .use(authMiddleware) .handler(async ({ context, input }) => { const [updated] = await context.db - .update(categoryTable) + .update(schema.category) .set(input.data) - .where(and(eq(categoryTable.id, input.id), eq(categoryTable.userId, context.user.id))) + .where(and(eq(schema.category.id, input.id), eq(schema.category.userId, context.user.id))) .returning() if (!updated) throw new ORPCError('NOT_FOUND') return updated }), remove: os.bookmarks.category.remove - .use(db) + .use(dbMiddleware) .use(authMiddleware) .handler(async ({ context, input }) => { const [deleted] = await context.db - .delete(categoryTable) - .where(and(eq(categoryTable.id, input.id), eq(categoryTable.userId, context.user.id))) - .returning({ id: categoryTable.id }) + .delete(schema.category) + .where(and(eq(schema.category.id, input.id), eq(schema.category.userId, context.user.id))) + .returning({ id: schema.category.id }) if (!deleted) throw new ORPCError('NOT_FOUND') }), reorder: os.bookmarks.category.reorder - .use(db) + .use(dbMiddleware) .use(authMiddleware) .handler(async ({ context, input }) => { await context.db.transaction(async (tx) => { for (const item of input) { await tx - .update(categoryTable) + .update(schema.category) .set({ orderId: item.orderId }) - .where(and(eq(categoryTable.id, item.id), eq(categoryTable.userId, context.user.id))) + .where(and(eq(schema.category.id, item.id), eq(schema.category.userId, context.user.id))) } }) }), @@ -73,11 +73,11 @@ export const category = { export const bookmark = { create: os.bookmarks.bookmark.create - .use(db) + .use(dbMiddleware) .use(authMiddleware) .handler(async ({ context, input }) => { const [created] = await context.db - .insert(bookmarkTable) + .insert(schema.bookmark) .values({ ...input, userId: context.user.id }) .returning() if (!created) throw new ORPCError('INTERNAL_SERVER_ERROR', { message: 'Failed to create bookmark' }) @@ -85,39 +85,39 @@ export const bookmark = { }), update: os.bookmarks.bookmark.update - .use(db) + .use(dbMiddleware) .use(authMiddleware) .handler(async ({ context, input }) => { const [updated] = await context.db - .update(bookmarkTable) + .update(schema.bookmark) .set(input.data) - .where(and(eq(bookmarkTable.id, input.id), eq(bookmarkTable.userId, context.user.id))) + .where(and(eq(schema.bookmark.id, input.id), eq(schema.bookmark.userId, context.user.id))) .returning() if (!updated) throw new ORPCError('NOT_FOUND') return updated }), remove: os.bookmarks.bookmark.remove - .use(db) + .use(dbMiddleware) .use(authMiddleware) .handler(async ({ context, input }) => { const [deleted] = await context.db - .delete(bookmarkTable) - .where(and(eq(bookmarkTable.id, input.id), eq(bookmarkTable.userId, context.user.id))) - .returning({ id: bookmarkTable.id }) + .delete(schema.bookmark) + .where(and(eq(schema.bookmark.id, input.id), eq(schema.bookmark.userId, context.user.id))) + .returning({ id: schema.bookmark.id }) if (!deleted) throw new ORPCError('NOT_FOUND') }), reorder: os.bookmarks.bookmark.reorder - .use(db) + .use(dbMiddleware) .use(authMiddleware) .handler(async ({ context, input }) => { await context.db.transaction(async (tx) => { for (const item of input) { await tx - .update(bookmarkTable) + .update(schema.bookmark) .set({ orderId: item.orderId }) - .where(and(eq(bookmarkTable.id, item.id), eq(bookmarkTable.userId, context.user.id))) + .where(and(eq(schema.bookmark.id, item.id), eq(schema.bookmark.userId, context.user.id))) } }) }), diff --git a/apps/server/src/modules/bookmarks/schema.ts b/apps/server/src/modules/bookmarks/schema.ts index 127ea71..e8824a7 100644 --- a/apps/server/src/modules/bookmarks/schema.ts +++ b/apps/server/src/modules/bookmarks/schema.ts @@ -1,8 +1,8 @@ import { boolean, integer, pgTable, text, uuid } from 'drizzle-orm/pg-core' -import { userTable } from '../../server/auth/schema' +import { user } from '../../server/auth/schema' import { generatedFields } from '../../server/db/fields' -export const categoryTable = pgTable('category', { +export const category = pgTable('category', { ...generatedFields, name: text('name').notNull(), isPinned: boolean('is_pinned').notNull().default(false), @@ -10,20 +10,20 @@ export const categoryTable = pgTable('category', { orderId: integer('order_id').notNull().default(0), userId: text('user_id') .notNull() - .references(() => userTable.id, { onDelete: 'cascade' }), + .references(() => user.id, { onDelete: 'cascade' }), }) -export const bookmarkTable = pgTable('bookmark', { +export const bookmark = pgTable('bookmark', { ...generatedFields, name: text('name').notNull(), url: text('url').notNull(), icon: text('icon'), categoryId: uuid('category_id') .notNull() - .references(() => categoryTable.id, { onDelete: 'cascade' }), + .references(() => category.id, { onDelete: 'cascade' }), isPublic: boolean('is_public').notNull().default(true), orderId: integer('order_id').notNull().default(0), userId: text('user_id') .notNull() - .references(() => userTable.id, { onDelete: 'cascade' }), + .references(() => user.id, { onDelete: 'cascade' }), }) diff --git a/apps/server/src/server/api/middlewares/db.middleware.ts b/apps/server/src/server/api/middlewares/db.middleware.ts index d57afd2..6d0373a 100644 --- a/apps/server/src/server/api/middlewares/db.middleware.ts +++ b/apps/server/src/server/api/middlewares/db.middleware.ts @@ -1,11 +1,11 @@ import { os } from '@/server/api/server' -import { getDB } from '@/server/db' +import { db } from '@/server/db' -export const db = os.middleware(async ({ context, next }) => { +export const dbMiddleware = os.middleware(async ({ context, next }) => { return next({ context: { ...context, - db: getDB(), + db, }, }) }) diff --git a/apps/server/src/server/auth/index.ts b/apps/server/src/server/auth/index.ts index 6920fa1..2f31444 100644 --- a/apps/server/src/server/auth/index.ts +++ b/apps/server/src/server/auth/index.ts @@ -3,12 +3,12 @@ import { drizzleAdapter } from 'better-auth/adapters/drizzle' import { tanstackStartCookies } from 'better-auth/tanstack-start' import { env } from '@/env' import * as authSchema from '@/server/auth/schema' -import { getDB } from '@/server/db' +import { db } from '@/server/db' export const auth = betterAuth({ baseURL: env.BETTER_AUTH_URL, secret: env.BETTER_AUTH_SECRET, - database: drizzleAdapter(getDB(), { + database: drizzleAdapter(db, { provider: 'pg', schema: authSchema, }), diff --git a/apps/server/src/server/auth/schema.ts b/apps/server/src/server/auth/schema.ts index 06a1636..eea45f9 100644 --- a/apps/server/src/server/auth/schema.ts +++ b/apps/server/src/server/auth/schema.ts @@ -7,7 +7,7 @@ import { boolean, pgTable, text, timestamp } from 'drizzle-orm/pg-core' * 不使用项目的 generatedFields(UUID v7)。 */ -export const userTable = pgTable('user', { +export const user = pgTable('user', { id: text('id').primaryKey(), name: text('name').notNull(), email: text('email').notNull().unique(), @@ -20,7 +20,7 @@ export const userTable = pgTable('user', { .$onUpdateFn(() => new Date()), }) -export const sessionTable = pgTable('session', { +export const session = pgTable('session', { id: text('id').primaryKey(), expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(), token: text('token').notNull().unique(), @@ -33,16 +33,16 @@ export const sessionTable = pgTable('session', { userAgent: text('user_agent'), userId: text('user_id') .notNull() - .references(() => userTable.id, { onDelete: 'cascade' }), + .references(() => user.id, { onDelete: 'cascade' }), }) -export const accountTable = pgTable('account', { +export const account = pgTable('account', { id: text('id').primaryKey(), accountId: text('account_id').notNull(), providerId: text('provider_id').notNull(), userId: text('user_id') .notNull() - .references(() => userTable.id, { onDelete: 'cascade' }), + .references(() => user.id, { onDelete: 'cascade' }), accessToken: text('access_token'), refreshToken: text('refresh_token'), idToken: text('id_token'), @@ -57,7 +57,7 @@ export const accountTable = pgTable('account', { .$onUpdateFn(() => new Date()), }) -export const verificationTable = pgTable('verification', { +export const verification = pgTable('verification', { id: text('id').primaryKey(), identifier: text('identifier').notNull(), value: text('value').notNull(), diff --git a/apps/server/src/server/db/index.ts b/apps/server/src/server/db/index.ts index e7fd5f0..bbf193c 100644 --- a/apps/server/src/server/db/index.ts +++ b/apps/server/src/server/db/index.ts @@ -2,23 +2,9 @@ import { drizzle } from 'drizzle-orm/postgres-js' import { env } from '@/env' import { relations } from '@/server/db/relations' -export const createDB = () => - drizzle({ - connection: env.DATABASE_URL, - relations, - }) +export const db = drizzle({ + connection: env.DATABASE_URL, + relations, +}) -export type DB = ReturnType - -export const getDB = (() => { - let db: DB | null = null - - return (singleton = true): DB => { - if (!singleton) { - return createDB() - } - - db ??= createDB() - return db - } -})() +export type DB = typeof db diff --git a/apps/server/src/server/db/relations.ts b/apps/server/src/server/db/relations.ts index ba27439..f8ad204 100644 --- a/apps/server/src/server/db/relations.ts +++ b/apps/server/src/server/db/relations.ts @@ -2,25 +2,25 @@ import { defineRelations } from 'drizzle-orm' import * as schema from './schema' export const relations = defineRelations(schema, (r) => ({ - userTable: { - categories: r.many.categoryTable(), - bookmarks: r.many.bookmarkTable(), + user: { + categories: r.many.category(), + bookmarks: r.many.bookmark(), }, - categoryTable: { - user: r.one.userTable({ - from: r.categoryTable.userId, - to: r.userTable.id, + category: { + user: r.one.user({ + from: r.category.userId, + to: r.user.id, }), - bookmarks: r.many.bookmarkTable(), + bookmarks: r.many.bookmark(), }, - bookmarkTable: { - user: r.one.userTable({ - from: r.bookmarkTable.userId, - to: r.userTable.id, + bookmark: { + user: r.one.user({ + from: r.bookmark.userId, + to: r.user.id, }), - category: r.one.categoryTable({ - from: r.bookmarkTable.categoryId, - to: r.categoryTable.id, + category: r.one.category({ + from: r.bookmark.categoryId, + to: r.category.id, }), }, })) diff --git a/bun.lock b/bun.lock index ee0d974..6fbcff5 100644 --- a/bun.lock +++ b/bun.lock @@ -46,6 +46,7 @@ "@tanstack/react-router-devtools": "catalog:", "@types/bun": "catalog:", "@vitejs/plugin-react": "catalog:", + "babel-plugin-react-compiler": "^1.0.0", "drizzle-kit": "catalog:", "nitro": "catalog:", "tailwindcss": "catalog:", @@ -537,6 +538,8 @@ "babel-dead-code-elimination": ["babel-dead-code-elimination@1.0.12", "", { "dependencies": { "@babel/core": "^7.23.7", "@babel/parser": "^7.23.6", "@babel/traverse": "^7.23.7", "@babel/types": "^7.23.6" } }, "sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig=="], + "babel-plugin-react-compiler": ["babel-plugin-react-compiler@1.0.0", "", { "dependencies": { "@babel/types": "^7.26.0" } }, "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw=="], + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], "baseline-browser-mapping": ["baseline-browser-mapping@2.10.12", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-qyq26DxfY4awP2gIRXhhLWfwzwI+N5Nxk6iQi8EFizIaWIjqicQTE4sLnZZVdeKPRcVNoJOkkpfzoIYuvCKaIQ=="],