当然, 我很乐意为您详细展开第 2 部分”数据库设计和 ORM 设置”的内容。这个部分我们将分为 2 天来完成, 每天的任务都会非常具体。

第 2 部分: 数据库设计和 ORM 设置 (2天)

Day 1: 数据模型设计和 Drizzle ORM 配置

  1. 设置 Drizzle ORM 配置文件 创建文件 drizzle.config.ts:

    import type { Config } from 'drizzle-kit';
    import { config } from './src/config';
     
    export default {
      schema: './src/models/*',
      out: './drizzle',
      driver: 'turso',
      dbCredentials: {
        url: config.databaseUrl,
      },
    } satisfies Config;
  2. 创建用户模型 在 src/models/user.ts 中:

    import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
     
    export const users = sqliteTable('users', {
      id: text('id').primaryKey(),
      username: text('username').notNull().unique(),
      email: text('email').notNull().unique(),
      passwordHash: text('password_hash').notNull(),
      createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(sql`CURRENT_TIMESTAMP`),
      updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull().default(sql`CURRENT_TIMESTAMP`),
    });
     
    export type User = typeof users.$inferSelect;
    export type NewUser = typeof users.$inferInsert;
  3. 创建 Todo 模型 在 src/models/todo.ts 中:

    import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
    import { users } from './user';
     
    export const todos = sqliteTable('todos', {
      id: text('id').primaryKey(),
      title: text('title').notNull(),
      description: text('description'),
      completed: integer('completed', { mode: 'boolean' }).notNull().default(false),
      priority: integer('priority').notNull().default(1),
      dueDate: integer('due_date', { mode: 'timestamp' }),
      userId: text('user_id').notNull().references(() => users.id),
      createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(sql`CURRENT_TIMESTAMP`),
      updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull().default(sql`CURRENT_TIMESTAMP`),
    });
     
    export type Todo = typeof todos.$inferSelect;
    export type NewTodo = typeof todos.$inferInsert;
  4. 创建标签模型 在 src/models/tag.ts 中:

    import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
    import { users } from './user';
     
    export const tags = sqliteTable('tags', {
      id: text('id').primaryKey(),
      name: text('name').notNull(),
      userId: text('user_id').notNull().references(() => users.id),
      createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(sql`CURRENT_TIMESTAMP`),
    });
     
    export const todoTags = sqliteTable('todo_tags', {
      id: text('id').primaryKey(),
      todoId: text('todo_id').notNull().references(() => todos.id),
      tagId: text('tag_id').notNull().references(() => tags.id),
    });
     
    export type Tag = typeof tags.$inferSelect;
    export type NewTag = typeof tags.$inferInsert;
  5. 设置数据库关系 创建 src/models/index.ts:

    export * from './user';
    export * from './todo';
    export * from './tag';
     
    import { relations } from 'drizzle-orm';
    import { users, todos, tags, todoTags } from './';
     
    export const usersRelations = relations(users, ({ many }) => ({
      todos: many(todos),
      tags: many(tags),
    }));
     
    export const todosRelations = relations(todos, ({ one, many }) => ({
      user: one(users, { fields: [todos.userId], references: [users.id] }),
      tags: many(todoTags),
    }));
     
    export const tagsRelations = relations(tags, ({ one, many }) => ({
      user: one(users, { fields: [tags.userId], references: [users.id] }),
      Todos: many (todoTags),
    }));
     
    Export const todoTagsRelations = relations (todoTags, ({ one }) => ({
      todo: one(todos, { fields: [todoTags.todoId], references: [todos.id] }),
      tag: one(tags, { fields: [todoTags.tagId], references: [tags.id] }),
    }));

Day 2: 数据库连接和迁移

  1. 设置 Turso 数据库连接 更新 src/db.ts:

    import { createClient } from '@libsql/client';
    import { drizzle } from 'drizzle-orm/libsql';
    import { config } from './config';
    import * as schema from './models';
     
    const client = createClient({
      url: config.databaseUrl,
      authToken: config.databaseAuthToken,
    });
     
    export const db = drizzle(client, { schema });
  2. 创建数据库迁移脚本 在 package.json 中添加新的脚本:

    {
      "scripts": {
        "db:generate": "drizzle-kit generate:sqlite",
        "db:push": "drizzle-kit push:sqlite",
        "db:studio": "drizzle-kit studio"
      }
    }
  3. 生成初始迁移 运行命令:

    bun run db:generate
    

    这将在 drizzle 目录下创建迁移文件。

  4. 应用迁移到数据库 运行命令:

    bun run db:push
    
  5. 创建数据库种子脚本 创建文件 scripts/seed.ts:

    import { db } from '../src/db';
    import { users, todos, tags, todoTags } from '../src/models';
    import { v4 as uuidv4 } from 'uuid';
    import * as bcrypt from 'bcrypt';
     
    async function seed() {
      // 创建示例用户
      const userId = uuidv4();
      await db.insert(users).values({
        id: userId,
        username: 'testuser',
        email: 'test@example.com',
        passwordHash: await bcrypt.hash('password123', 10),
      });
     
      // 创建示例Todo
      const todoId = uuidv4();
      await db.insert(todos).values({
        id: todoId,
        title: 'Complete project',
        description: 'Finish the LightweightTodo Pro project',
        userId: userId,
        priority: 2,
        dueDate: new Date('2023-12-31'),
      });
     
      // 创建示例标签
      const tagId = uuidv4();
      await db.insert(tags).values({
        id: tagId,
        name: 'Work',
        userId: userId,
      });
     
      // 关联Todo和标签
      await db.insert(todoTags).values({
        id: uuidv4(),
        todoId: todoId,
        tagId: tagId,
      });
     
      console.log('Seed data inserted successfully');
    }
     
    seed().catch(console.error);
  6. 添加种子脚本到 package.json:

    {
      "scripts": {
        "db:seed": "bun run scripts/seed.ts"
      }
    }
  7. 运行种子脚本:

    bun run db:seed
    
  8. 创建数据库工具函数 在 src/utils/db.ts 中:

    import { db } from '../db';
    import { SQL } from 'drizzle-orm';
     
    export async function executeTransaction<T>(callback: () => Promise<T>): Promise<T> {
      return db.transaction(async (tx) => {
        return await callback();
      });
    }
     
    export async function executeQuery<T>(query: SQL): Promise<T[]> {
      Return db.Select (). From (query);
    }

这个详细的计划涵盖了数据库设计、ORM 配置、迁移管理和初始数据填充。它为您的 LightweightTodo Pro 项目建立了一个强大的数据层基础。主要亮点包括:

  1. 使用 Drizzle ORM 定义了清晰的数据模型和关系。
  2. 设置了与 Turso 数据库的连接。
  3. 创建了数据库迁移和种子脚本,便于管理数据库结构和初始数据。
  4. 提供了事务和查询的工具函数,简化了数据库操作。

这个设置为接下来的后端 API 开发和前端集成奠定了坚实的基础。在下一部分中,我们将基于这个数据层开始构建 API 端点和业务逻辑。