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:
@@ -1,69 +1,70 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { getDb } from '../config/database.js';
|
||||
import { neo4jDriver } from '../config/neo4j.js';
|
||||
import { textbooks, chapters, knowledgePoints } from './textbooks.schema.js';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { db } from "../config/database.js";
|
||||
import { textbooks, type Textbook } from "./textbooks.schema.js";
|
||||
import {
|
||||
NotFoundError,
|
||||
ValidationError,
|
||||
} from "../shared/errors/application-error.js";
|
||||
|
||||
export interface CreateTextbookInput {
|
||||
title: string;
|
||||
subjectId: string;
|
||||
gradeId: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
export interface UpdateTextbookInput {
|
||||
title?: string;
|
||||
subjectId?: string;
|
||||
gradeId?: string;
|
||||
version?: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class TextbooksService {
|
||||
async create(data: { title: string; subjectId: string; gradeId: string; version: string }) {
|
||||
const id = uuidv4();
|
||||
const db = getDb();
|
||||
await db.insert(textbooks).values({ id, ...data });
|
||||
return { id, ...data };
|
||||
async create(input: CreateTextbookInput): Promise<{ id: string }> {
|
||||
if (!input.title || !input.subjectId || !input.gradeId || !input.version) {
|
||||
throw new ValidationError(
|
||||
"title, subjectId, gradeId, version are required",
|
||||
);
|
||||
}
|
||||
|
||||
const id = randomUUID();
|
||||
await db.insert(textbooks).values({
|
||||
id,
|
||||
title: input.title,
|
||||
subjectId: input.subjectId,
|
||||
gradeId: input.gradeId,
|
||||
version: input.version,
|
||||
});
|
||||
return { id };
|
||||
}
|
||||
|
||||
async list() {
|
||||
const db = getDb();
|
||||
async list(): Promise<Textbook[]> {
|
||||
return db.select().from(textbooks);
|
||||
}
|
||||
|
||||
async getById(id: string) {
|
||||
const db = getDb();
|
||||
const [result] = await db.select().from(textbooks).where(eq(textbooks.id, id));
|
||||
async getById(id: string): Promise<Textbook> {
|
||||
const [result] = await db
|
||||
.select()
|
||||
.from(textbooks)
|
||||
.where(eq(textbooks.id, id))
|
||||
.limit(1);
|
||||
if (!result) {
|
||||
throw new NotFoundError("Textbook", id);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// 知识图谱:在 Neo4j 中创建知识点节点和关系
|
||||
async createKnowledgeGraph(knowledgePointId: string, title: string, prerequisiteIds: string[]): Promise<void> {
|
||||
const session = neo4jDriver.session();
|
||||
try {
|
||||
await session.executeWrite((tx) =>
|
||||
tx.run(
|
||||
'MERGE (kp:KnowledgePoint {id: $id, title: $title})',
|
||||
{ id: knowledgePointId, title }
|
||||
)
|
||||
);
|
||||
|
||||
for (const prereqId of prerequisiteIds) {
|
||||
await session.executeWrite((tx) =>
|
||||
tx.run(
|
||||
`MATCH (prereq:KnowledgePoint {id: $prereqId}), (kp:KnowledgePoint {id: $kpId})
|
||||
MERGE (prereq)-[:PREREQUISITE_OF]->(kp)`,
|
||||
{ prereqId, kpId: knowledgePointId }
|
||||
)
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
await session.close();
|
||||
}
|
||||
async update(id: string, data: UpdateTextbookInput): Promise<void> {
|
||||
await this.getById(id);
|
||||
await db.update(textbooks).set(data).where(eq(textbooks.id, id));
|
||||
}
|
||||
|
||||
// 查询知识图谱(前置知识点)
|
||||
async getPrerequisites(knowledgePointId: string): Promise<unknown[]> {
|
||||
const session = neo4jDriver.session();
|
||||
try {
|
||||
const result = await session.executeRead((tx) =>
|
||||
tx.run(
|
||||
`MATCH (kp:KnowledgePoint {id: $id})<-[:PREREQUISITE_OF*1..5]-(prereq)
|
||||
RETURN prereq.id as id, prereq.title as title`,
|
||||
{ id: knowledgePointId }
|
||||
)
|
||||
);
|
||||
return result.records.map((r) => ({ id: r.get('id'), title: r.get('title') }));
|
||||
} finally {
|
||||
await session.close();
|
||||
}
|
||||
async delete(id: string): Promise<void> {
|
||||
await this.getById(id);
|
||||
await db.delete(textbooks).where(eq(textbooks.id, id));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user