feat(content): 修复服务并添加chapters/knowledge-points/questions模块
- database.ts 导出db常量替代getDb()函数 - env.ts JWT_SECRET/ES_URL/NEO4J_URL改optional加DEV_MODE - neo4j.ts driver惰性创建+try/catch+connectionTimeout:3000 - health/lifecycle改用Drizzle原生查询 - textbooks.schema修复integer到int+导出NewTextbook类型 - 新建chapters/knowledge-points/questions三模块CRUD - knowledge-points含Neo4j前置依赖图非阻塞查询 - content-init.sql创建4张表 端到端验证: textbooks/chapters/knowledge-points/questions全CRUD通过
This commit is contained in:
61
services/content/src/chapters/chapters.controller.ts
Normal file
61
services/content/src/chapters/chapters.controller.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
Put,
|
||||
} from "@nestjs/common";
|
||||
import {
|
||||
ChaptersService,
|
||||
type CreateChapterInput,
|
||||
type UpdateChapterInput,
|
||||
} from "./chapters.service.js";
|
||||
import type { Chapter } from "./chapters.schema.js";
|
||||
|
||||
@Controller("chapters")
|
||||
export class ChaptersController {
|
||||
constructor(private readonly service: ChaptersService) {}
|
||||
|
||||
@Post()
|
||||
async create(
|
||||
@Body() body: CreateChapterInput,
|
||||
): Promise<{ success: true; data: { id: string } }> {
|
||||
const result = await this.service.createChapter(body);
|
||||
return { success: true, data: result };
|
||||
}
|
||||
|
||||
@Get("textbook/:textbookId")
|
||||
async listByTextbook(
|
||||
@Param("textbookId") textbookId: string,
|
||||
): Promise<{ success: true; data: Chapter[] }> {
|
||||
const data = await this.service.listChaptersByTextbook(textbookId);
|
||||
return { success: true, data };
|
||||
}
|
||||
|
||||
@Get(":id")
|
||||
async getById(
|
||||
@Param("id") id: string,
|
||||
): Promise<{ success: true; data: Chapter }> {
|
||||
const data = await this.service.getChapter(id);
|
||||
return { success: true, data };
|
||||
}
|
||||
|
||||
@Put(":id")
|
||||
async update(
|
||||
@Param("id") id: string,
|
||||
@Body() body: UpdateChapterInput,
|
||||
): Promise<{ success: true; data: { success: true } }> {
|
||||
await this.service.updateChapter(id, body);
|
||||
return { success: true, data: { success: true } };
|
||||
}
|
||||
|
||||
@Delete(":id")
|
||||
async remove(
|
||||
@Param("id") id: string,
|
||||
): Promise<{ success: true; data: { success: true } }> {
|
||||
await this.service.deleteChapter(id);
|
||||
return { success: true, data: { success: true } };
|
||||
}
|
||||
}
|
||||
10
services/content/src/chapters/chapters.module.ts
Normal file
10
services/content/src/chapters/chapters.module.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { ChaptersController } from "./chapters.controller.js";
|
||||
import { ChaptersService } from "./chapters.service.js";
|
||||
|
||||
@Module({
|
||||
controllers: [ChaptersController],
|
||||
providers: [ChaptersService],
|
||||
exports: [ChaptersService],
|
||||
})
|
||||
export class ChaptersModule {}
|
||||
35
services/content/src/chapters/chapters.repository.ts
Normal file
35
services/content/src/chapters/chapters.repository.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import { db } from "../config/database.js";
|
||||
import { chapters, type Chapter, type NewChapter } from "./chapters.schema.js";
|
||||
|
||||
export class ChaptersRepository {
|
||||
async findById(id: string): Promise<Chapter | undefined> {
|
||||
const [result] = await db
|
||||
.select()
|
||||
.from(chapters)
|
||||
.where(eq(chapters.id, id))
|
||||
.limit(1);
|
||||
return result;
|
||||
}
|
||||
|
||||
async findByTextbookId(textbookId: string): Promise<Chapter[]> {
|
||||
return db
|
||||
.select()
|
||||
.from(chapters)
|
||||
.where(eq(chapters.textbookId, textbookId));
|
||||
}
|
||||
|
||||
async create(data: NewChapter): Promise<void> {
|
||||
await db.insert(chapters).values(data);
|
||||
}
|
||||
|
||||
async update(id: string, data: Partial<NewChapter>): Promise<void> {
|
||||
await db.update(chapters).set(data).where(eq(chapters.id, id));
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<void> {
|
||||
await db.delete(chapters).where(eq(chapters.id, id));
|
||||
}
|
||||
}
|
||||
|
||||
export const chaptersRepository = new ChaptersRepository();
|
||||
5
services/content/src/chapters/chapters.schema.ts
Normal file
5
services/content/src/chapters/chapters.schema.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export {
|
||||
chapters,
|
||||
type Chapter,
|
||||
type NewChapter,
|
||||
} from "../textbooks/textbooks.schema.js";
|
||||
62
services/content/src/chapters/chapters.service.ts
Normal file
62
services/content/src/chapters/chapters.service.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { chaptersRepository } from "./chapters.repository.js";
|
||||
import type { Chapter } from "./chapters.schema.js";
|
||||
import {
|
||||
NotFoundError,
|
||||
ValidationError,
|
||||
} from "../shared/errors/application-error.js";
|
||||
|
||||
export interface CreateChapterInput {
|
||||
textbookId: string;
|
||||
title: string;
|
||||
order: number;
|
||||
parentId?: string;
|
||||
}
|
||||
|
||||
export interface UpdateChapterInput {
|
||||
title?: string;
|
||||
order?: number;
|
||||
parentId?: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ChaptersService {
|
||||
async createChapter(input: CreateChapterInput): Promise<{ id: string }> {
|
||||
if (!input.textbookId || !input.title || input.order === undefined) {
|
||||
throw new ValidationError("textbookId, title, order are required");
|
||||
}
|
||||
|
||||
const id = randomUUID();
|
||||
await chaptersRepository.create({
|
||||
id,
|
||||
textbookId: input.textbookId,
|
||||
title: input.title,
|
||||
order: input.order,
|
||||
parentId: input.parentId,
|
||||
});
|
||||
return { id };
|
||||
}
|
||||
|
||||
async getChapter(id: string): Promise<Chapter> {
|
||||
const chapter = await chaptersRepository.findById(id);
|
||||
if (!chapter) {
|
||||
throw new NotFoundError("Chapter", id);
|
||||
}
|
||||
return chapter;
|
||||
}
|
||||
|
||||
async listChaptersByTextbook(textbookId: string): Promise<Chapter[]> {
|
||||
return chaptersRepository.findByTextbookId(textbookId);
|
||||
}
|
||||
|
||||
async updateChapter(id: string, data: UpdateChapterInput): Promise<void> {
|
||||
await this.getChapter(id);
|
||||
await chaptersRepository.update(id, data);
|
||||
}
|
||||
|
||||
async deleteChapter(id: string): Promise<void> {
|
||||
await this.getChapter(id);
|
||||
await chaptersRepository.delete(id);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user