非常好,我们将基于 Bun 运行环境为 LightweightTodo Pro 项目的后端 API 开发生成详细的 TypeScript 代码。Bun 是一个现代的 JavaScript 运行时,它提供了更快的性能和更好的开发体验。我们将使用 Bun 的内置特性和兼容的库来实现这个项目。

首先,让我们设置项目结构:

src/
  models/
    Todo.ts
    User.ts
    Tag.ts
  controllers/
    TodoController.ts
  services/
    TodoService.ts
  repositories/
    TodoRepository.ts
  middleware/
    auth.ts
  routes/
    todoRoutes.ts
  utils/
    validation.ts
  app.ts
package.json
tsconfig.json

现在,让我们逐个实现这些文件:

  1. src/models/Todo.ts:
import { User } from './User';
import { Tag } from './Tag';
 
export enum TodoStatus {
  PENDING = 'PENDING',
  IN_PROGRESS = 'IN_PROGRESS',
  COMPLETED = 'COMPLETED'
}
 
export enum Priority {
  LOW = 'LOW',
  MEDIUM = 'MEDIUM',
  HIGH = 'HIGH'
}
 
export interface Todo {
  id: string;
  title: string;
  description: string;
  status: TodoStatus;
  priority: Priority;
  dueDate: Date;
  userId: string;
  parentId?: string;
  tags: string[];
  createdAt: Date;
  updatedAt: Date;
}
 
export interface CreateTodoDTO {
  title: string;
  description: string;
  status: TodoStatus;
  priority: Priority;
  dueDate: Date;
  parentId?: string;
  tags?: string[];
}
 
export interface UpdateTodoDTO {
  title?: string;
  description?: string;
  status?: TodoStatus;
  priority?: Priority;
  dueDate?: Date;
  parentId?: string;
  tags?: string[];
}
  1. src/models/User.ts:
export interface User {
  id: string;
  username: string;
  email: string;
  password: string;
}
  1. src/models/Tag.ts:
export interface Tag {
  id: string;
  name: string;
  userId: string;
}
  1. src/repositories/TodoRepository.ts:
import { Database } from 'bun:sqlite';
import { Todo, TodoStatus, Priority, CreateTodoDTO, UpdateTodoDTO } from '../models/Todo';
 
export class TodoRepository {
  private db: Database;
 
  constructor() {
    this.db = new Database('todos.sqlite');
    this.initDatabase();
  }
 
  private initDatabase() {
    this.db.run(`
      CREATE TABLE IF NOT EXISTS todos (
        id TEXT PRIMARY KEY,
        title TEXT NOT NULL,
        description TEXT,
        status TEXT NOT NULL,
        priority TEXT NOT NULL,
        dueDate TEXT,
        userId TEXT NOT NULL,
        parentId TEXT,
        tags TEXT,
        createdAt TEXT NOT NULL,
        updatedAt TEXT NOT NULL
      )
    `);
  }
 
  async create(todo: CreateTodoDTO, userId: string): Promise<Todo> {
    const id = crypto.randomUUID();
    const now = new Date().toISOString();
    const query = `
      INSERT INTO todos (id, title, description, status, priority, dueDate, userId, parentId, tags, createdAt, updatedAt)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    `;
    this.db.run(query, [
      id,
      todo.title,
      todo.description,
      todo.status,
      todo.priority,
      todo.dueDate.toISOString(),
      userId,
      todo.parentId || null,
      JSON.stringify(todo.tags || []),
      now,
      now
    ]);
 
    return this.findById(id);
  }
 
  async findById(id: string): Promise<Todo | null> {
    const query = 'SELECT * FROM todos WHERE id = ?';
    const result = this.db.query(query).get(id) as any;
    return result ? this.mapRowToTodo(result) : null;
  }
 
  async findByUser(userId: string): Promise<Todo[]> {
    const query = 'SELECT * FROM todos WHERE userId = ?';
    const results = this.db.query(query).all(userId) as any[];
    return results.map(this.mapRowToTodo);
  }
 
  async update(id: string, updates: UpdateTodoDTO): Promise<Todo | null> {
    const todo = await this.findById(id);
    if (!todo) return null;
 
    const updatedTodo = { ...todo, ...updates, updatedAt: new Date() };
    const query = `
      UPDATE todos
      SET title = ?, description = ?, status = ?, priority = ?, dueDate = ?, parentId = ?, tags = ?, updatedAt = ?
      WHERE id = ?
    `;
    this.db.run(query, [
      updatedTodo.title,
      updatedTodo.description,
      updatedTodo.status,
      updatedTodo.priority,
      updatedTodo.dueDate.toISOString(),
      updatedTodo.parentId || null,
      JSON.stringify(updatedTodo.tags),
      updatedTodo.updatedAt.toISOString(),
      id
    ]);
 
    return this.findById(id);
  }
 
  async delete(id: string): Promise<boolean> {
    const query = 'DELETE FROM todos WHERE id = ?';
    const result = this.db.run(query, [id]);
    return result.changes > 0;
  }
 
  async findByStatus(userId: string, status: TodoStatus): Promise<Todo[]> {
    const query = 'SELECT * FROM todos WHERE userId = ? AND status = ?';
    const results = this.db.query(query).all(userId, status) as any[];
    return results.map(this.mapRowToTodo);
  }
 
  async findByPriority(userId: string, priority: Priority): Promise<Todo[]> {
    const query = 'SELECT * FROM todos WHERE userId = ? AND priority = ?';
    const results = this.db.query(query).all(userId, priority) as any[];
    return results.map(this.mapRowToTodo);
  }
 
  private mapRowToTodo(row: any): Todo {
    return {
      id: row.id,
      title: row.title,
      description: row.description,
      status: row.status as TodoStatus,
      priority: row.priority as Priority,
      dueDate: new Date(row.dueDate),
      userId: row.userId,
      parentId: row.parentId,
      tags: JSON.parse(row.tags),
      createdAt: new Date(row.createdAt),
      updatedAt: new Date(row.updatedAt)
    };
  }
}
  1. src/services/TodoService.ts:
import { Todo, TodoStatus, Priority, CreateTodoDTO, UpdateTodoDTO } from '../models/Todo';
import { TodoRepository } from '../repositories/TodoRepository';
 
export class TodoService {
  constructor(private todoRepository: TodoRepository) {}
 
  async createTodo(todo: CreateTodoDTO, userId: string): Promise<Todo> {
    return this.todoRepository.create(todo, userId);
  }
 
  async getTodo(id: string, userId: string): Promise<Todo | null> {
    const todo = await this.todoRepository.findById(id);
    return todo && todo.userId === userId ? todo : null;
  }
 
  async updateTodo(id: string, updates: UpdateTodoDTO, userId: string): Promise<Todo | null> {
    const todo = await this.getTodo(id, userId);
    if (!todo) return null;
    return this.todoRepository.update(id, updates);
  }
 
  async deleteTodo(id: string, userId: string): Promise<boolean> {
    const todo = await this.getTodo(id, userId);
    if (!todo) return false;
    return this.todoRepository.delete(id);
  }
 
  async getTodosByUser(userId: string): Promise<Todo[]> {
    return this.todoRepository.findByUser(userId);
  }
 
  async getTodosByStatus(userId: string, status: TodoStatus): Promise<Todo[]> {
    return this.todoRepository.findByStatus(userId, status);
  }
 
  async getTodosByPriority(userId: string, priority: Priority): Promise<Todo[]> {
    return this.todoRepository.findByPriority(userId, priority);
  }
}
  1. src/controllers/TodoController.ts:
import { Context } from 'elysia';
import { TodoService } from '../services/TodoService';
import { CreateTodoDTO, UpdateTodoDTO, TodoStatus, Priority } from '../models/Todo';
 
export class TodoController {
  constructor(private todoService: TodoService) {}
 
  async createTodo(c: Context) {
    const todo = await this.todoService.createTodo(c.body as CreateTodoDTO, c.user.id);
    return c.json(todo, 201);
  }
 
  async getTodo(c: Context) {
    const todo = await this.todoService.getTodo(c.params.id, c.user.id);
    if (todo) {
      return c.json(todo);
    }
    return c.json({ message: 'Todo not found' }, 404);
  }
 
  async updateTodo(c: Context) {
    const todo = await this.todoService.updateTodo(c.params.id, c.body as UpdateTodoDTO, c.user.id);
    if (todo) {
      return c.json(todo);
    }
    return c.json({ message: 'Todo not found' }, 404);
  }
 
  async deleteTodo(c: Context) {
    const success = await this.todoService.deleteTodo(c.params.id, c.user.id);
    if (success) {
      return c.json({ message: 'Todo deleted successfully' });
    }
    return c.json({ message: 'Todo not found' }, 404);
  }
 
  async getTodosByUser(c: Context) {
    const todos = await this.todoService.getTodosByUser(c.user.id);
    return c.json(todos);
  }
 
  async getTodosByStatus(c: Context) {
    const status = c.query.status as TodoStatus;
    const todos = await this.todoService.getTodosByStatus(c.user.id, status);
    return c.json(todos);
  }
 
  async getTodosByPriority(c: Context) {
    const priority = c.query.priority as Priority;
    const todos = await this.todoService.getTodosByPriority(c.user.id, priority);
    return c.json(todos);
  }
}
  1. src/middleware/auth.ts:
import { Context, Next } from 'elysia';
import jwt from 'jsonwebtoken';
 
export const authMiddleware = async (c: Context, next: Next) => {
  const token = c.headers['authorization']?.split(' ')[1];
 
  if (!token) {
    return c.json({ message: 'No token, authorization denied' }, 401);
  }
 
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    c.user = decoded as any;
    await next();
  } catch (err) {
    return c.json({ message: 'Token is not valid' }, 401);
  }
};
  1. src/routes/todoRoutes.ts:
import { Elysia } from 'elysia';
import { TodoController } from '../controllers/TodoController';
import { TodoService } from '../services/TodoService';
import { TodoRepository } from '../repositories/TodoRepository';
import { authMiddleware } from '../middleware/auth';
 
const todoRepository = new TodoRepository();
const todoService = new TodoService(todoRepository);
const todoController = new TodoController(todoService);
 
export const todoRouter = new Elysia()
  .use(authMiddleware)
  .post('/todos', (c) => todoController.createTodo(c))
  .get('/todos/:id', (c) => todoController.getTodo(c))
  .put('/todos/:id', (c) => todoController.updateTodo(c))
  .delete('/todos/:id', (c) => todoController.deleteTodo(c))
  .get('/todos', (c) => todoController.getTodosByUser(c))
  .get('/todos/status', (c) => todoController.getTodosByStatus(c))
  .get('/todos/priority', (c) => todoController.getTodosByPriority(c));
  1. src/utils/validation.ts:
import { z } from 'zod';
import { TodoStatus, Priority } from '../models/Todo';
 
export const createTodoSchema = z.object({
  title: z.string().min(1).max(100),
  description: z.string().max(500).optional(),
  status: z.nativeEnum(TodoStatus),
  priority: z.nativeEnum(Priority),
  dueDate: z.coerce.date(),
  parentId: z.string().optional(),
  tags: z.array(z.string()).optional()
});
 
export const updateTodoSchema = createTodoSchema.partial();
  1. src/app.ts:
import { Elysia } from 'elysia';
import { todoRouter } from './routes/todoRoutes';
 
const app = new Elysia()
  .use(todoRouter)
  .listen(3000);
 
console.log(`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`);
  1. package.json:
{
  "name": "lightweight-todo-pro",
  "version": "1.0.0",
  "scripts": {
    "start": "bun run src/app.ts",
    "dev": "bun --watch run src/app.ts"
  },
  "dependencies": {
    "elysia": "latest",
    "jsonwebtoken": "^9.0.0",
    "zod": "^3.21.4"
  },
  "devDependencies": {
    "bun-types": "latest"
  }
}
  1. tsconfig.json:
{
  "compilerOptions": {
    "lib": ["ESNext"],
    "module": "esnext",
    "target": "esnext",
    "moduleResolution": "bundler",
    "moduleDetection": "force",
    "allowImportingTsExtensions": true,
    "noEmit": true,
    "composite": true,
    "strict": true,
    "downlevelIteration": true,
    "skipLibCheck": true,
    "jsx": "react-jsx",
    "allowSyntheticDefaultImports": true,
    "forceConsistentCasingInFileNames": true,
    "allowJs": true,
    "types": [
      "bun-types"
    ]
  }
}

这个实现涵盖了主要的 Todo 操作,包括创建、读取、更新、删除,以及按状态和优先级筛选等功能。它使用了 Bun 的内置 SQLite 数据库,Elysia 框架来处理 HTTP 请求,以及 Zod 进行