@mindbase/express-knowledge 1.0.4 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,271 @@
1
+ import { knowledgeTag, knowledgeTagRelation } from "../orm/Tag.schema";
2
+ import { knowledge } from "../orm/Knowledge.schema";
3
+ import { eq, and, sql, desc, or, inArray } from "drizzle-orm";
4
+ import type { InsertTag } from "../types";
5
+
6
+ let _db: any;
7
+
8
+ /**
9
+ * 初始化 Service 的数据库实例
10
+ */
11
+ export function initTagService(db: any) {
12
+ _db = db;
13
+ }
14
+
15
+ function getDB() {
16
+ if (!_db) throw new Error("TagService not initialized with DB");
17
+ return _db;
18
+ }
19
+
20
+ // ==================== 标签管理 ====================
21
+
22
+ /**
23
+ * 创建标签
24
+ */
25
+ export async function createTag(data: InsertTag & { userId?: number }) {
26
+ const db = getDB();
27
+ const now = new Date();
28
+
29
+ const result = await db
30
+ .insert(knowledgeTag)
31
+ .values({
32
+ name: data.name,
33
+ color: data.color,
34
+ userId: data.userId,
35
+ created: now,
36
+ })
37
+ .execute();
38
+
39
+ const lastId = result.lastInsertRowid as number;
40
+ return getTagById(lastId);
41
+ }
42
+
43
+ /**
44
+ * 获取标签详情
45
+ */
46
+ export async function getTagById(id: number) {
47
+ const db = getDB();
48
+ const [tag] = await db.select().from(knowledgeTag).where(eq(knowledgeTag.id, id)).limit(1);
49
+
50
+ return tag || null;
51
+ }
52
+
53
+ /**
54
+ * 获取标签列表
55
+ */
56
+ export async function listTags(userId?: number) {
57
+ const db = getDB();
58
+
59
+ let conditions = [];
60
+ if (userId !== undefined) {
61
+ conditions.push(eq(knowledgeTag.userId, userId));
62
+ }
63
+
64
+ return await db
65
+ .select()
66
+ .from(knowledgeTag)
67
+ .where(conditions.length > 0 ? and(...conditions) : undefined)
68
+ .orderBy(knowledgeTag.name)
69
+ .execute();
70
+ }
71
+
72
+ /**
73
+ * 更新标签
74
+ */
75
+ export async function updateTag(id: number, data: Partial<InsertTag>, userId: number) {
76
+ const db = getDB();
77
+
78
+ // 检查权限
79
+ const [tag] = await db
80
+ .select()
81
+ .from(knowledgeTag)
82
+ .where(and(eq(knowledgeTag.id, id), eq(knowledgeTag.userId, userId)))
83
+ .limit(1);
84
+
85
+ if (!tag) {
86
+ throw new Error("标签不存在或无权操作");
87
+ }
88
+
89
+ await db
90
+ .update(knowledgeTag)
91
+ .set({
92
+ ...(data.name !== undefined && { name: data.name }),
93
+ ...(data.color !== undefined && { color: data.color }),
94
+ })
95
+ .where(eq(knowledgeTag.id, id))
96
+ .execute();
97
+
98
+ return getTagById(id);
99
+ }
100
+
101
+ /**
102
+ * 删除标签
103
+ */
104
+ export async function deleteTag(id: number, userId: number) {
105
+ const db = getDB();
106
+
107
+ // 检查权限
108
+ const [tag] = await db
109
+ .select()
110
+ .from(knowledgeTag)
111
+ .where(and(eq(knowledgeTag.id, id), eq(knowledgeTag.userId, userId)))
112
+ .limit(1);
113
+
114
+ if (!tag) {
115
+ throw new Error("标签不存在或无权操作");
116
+ }
117
+
118
+ // 删除标签关联
119
+ await db.delete(knowledgeTagRelation).where(eq(knowledgeTagRelation.tagId, id)).execute();
120
+
121
+ // 删除标签
122
+ await db.delete(knowledgeTag).where(eq(knowledgeTag.id, id)).execute();
123
+
124
+ return { success: true };
125
+ }
126
+
127
+ // ==================== 标签关联 ====================
128
+
129
+ /**
130
+ * 为知识库内容添加标签
131
+ */
132
+ export async function addTagToKnowledge(knowledgeId: number, tagId: number, userId: number) {
133
+ const db = getDB();
134
+
135
+ // 检查标签是否存在
136
+ const [tag] = await db.select().from(knowledgeTag).where(eq(knowledgeTag.id, tagId)).limit(1);
137
+
138
+ if (!tag) {
139
+ throw new Error("标签不存在");
140
+ }
141
+
142
+ // 检查是否已存在关联
143
+ const [existing] = await db
144
+ .select()
145
+ .from(knowledgeTagRelation)
146
+ .where(and(eq(knowledgeTagRelation.knowledgeId, knowledgeId), eq(knowledgeTagRelation.tagId, tagId)))
147
+ .limit(1);
148
+
149
+ if (existing) {
150
+ return { success: true, message: "标签已存在" };
151
+ }
152
+
153
+ // 创建关联
154
+ await db
155
+ .insert(knowledgeTagRelation)
156
+ .values({
157
+ knowledgeId,
158
+ tagId,
159
+ })
160
+ .execute();
161
+
162
+ return { success: true };
163
+ }
164
+
165
+ /**
166
+ * 移除知识库内容的标签
167
+ */
168
+ export async function removeTagFromKnowledge(knowledgeId: number, tagId: number) {
169
+ const db = getDB();
170
+
171
+ await db
172
+ .delete(knowledgeTagRelation)
173
+ .where(and(eq(knowledgeTagRelation.knowledgeId, knowledgeId), eq(knowledgeTagRelation.tagId, tagId)))
174
+ .execute();
175
+
176
+ return { success: true };
177
+ }
178
+
179
+ /**
180
+ * 获取知识库内容的标签列表
181
+ */
182
+ export async function getKnowledgeTags(knowledgeId: number) {
183
+ const db = getDB();
184
+
185
+ return await db
186
+ .select({
187
+ id: knowledgeTag.id,
188
+ name: knowledgeTag.name,
189
+ color: knowledgeTag.color,
190
+ })
191
+ .from(knowledgeTagRelation)
192
+ .innerJoin(knowledgeTag, eq(knowledgeTagRelation.tagId, knowledgeTag.id))
193
+ .where(eq(knowledgeTagRelation.knowledgeId, knowledgeId))
194
+ .execute();
195
+ }
196
+
197
+ /**
198
+ * 按标签查询知识库内容
199
+ */
200
+ export async function getKnowledgeByTag(
201
+ tagId: number,
202
+ options: {
203
+ pageIndex?: number;
204
+ pageSize?: number;
205
+ safe?: number[];
206
+ userId?: number;
207
+ isAdmin?: boolean;
208
+ }
209
+ ) {
210
+ const db = getDB();
211
+ const { pageIndex = 1, pageSize = 10, safe = [0], userId, isAdmin } = options;
212
+
213
+ // 获取带有该标签的知识库 ID 列表
214
+ const relations = await db.select().from(knowledgeTagRelation).where(eq(knowledgeTagRelation.tagId, tagId)).execute();
215
+
216
+ if (relations.length === 0) {
217
+ return { list: [], total: 0, pageIndex, pageSize };
218
+ }
219
+
220
+ const knowledgeIds = relations.map((r) => r.knowledgeId);
221
+
222
+ let conditions = [eq(knowledge.isDelete, 0), inArray(knowledge.id, knowledgeIds)];
223
+
224
+ // 权限筛选
225
+ const safeConditions = [];
226
+ for (const s of safe) {
227
+ switch (s) {
228
+ case 0: // 公开
229
+ safeConditions.push(eq(knowledge.safe, s));
230
+ break;
231
+ case 1: // 登录共享
232
+ if (userId) {
233
+ safeConditions.push(eq(knowledge.safe, s));
234
+ }
235
+ break;
236
+ case 2: // 个人私密
237
+ if (userId) {
238
+ safeConditions.push(and(eq(knowledge.safe, s), isAdmin ? sql`1=1` : eq(knowledge.userId, userId)));
239
+ }
240
+ break;
241
+ }
242
+ }
243
+
244
+ if (safeConditions.length > 0) {
245
+ conditions.push(or(...safeConditions));
246
+ }
247
+
248
+ const offset = (pageIndex - 1) * pageSize;
249
+
250
+ const list = await db
251
+ .select()
252
+ .from(knowledge)
253
+ .where(and(...conditions))
254
+ .limit(pageSize)
255
+ .offset(offset)
256
+ .orderBy(desc(knowledge.created))
257
+ .execute();
258
+
259
+ const [totalResult] = await db
260
+ .select({ count: sql<number>`count(*)` })
261
+ .from(knowledge)
262
+ .where(and(...conditions))
263
+ .execute();
264
+
265
+ return {
266
+ list,
267
+ total: totalResult.count,
268
+ pageIndex,
269
+ pageSize,
270
+ };
271
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist"
5
+ },
6
+ "include": ["**/*.ts"]
7
+ }
package/types/index.ts ADDED
@@ -0,0 +1,85 @@
1
+ import type { knowledge } from "../orm/Knowledge.schema";
2
+ import type { knowledgeTag } from "../orm/Tag.schema";
3
+
4
+ // ==================== 枚举类型 ====================
5
+
6
+ /**
7
+ * 安全级别
8
+ */
9
+ export enum KnowledgeSafe {
10
+ PUB = 0, // 公开(所有人可见)
11
+ SHARED = 1, // 登录共享(登录用户可见)
12
+ PRIVATE = 2, // 个人私密(仅创建者可见)
13
+ }
14
+
15
+ /**
16
+ * 节点类型
17
+ */
18
+ export enum KnowledgeType {
19
+ FOLDER = 0, // 文件夹
20
+ NOTE = 1, // 笔记
21
+ BOOK = 2, // 书籍
22
+ }
23
+
24
+ // ==================== 接口类型 ====================
25
+
26
+ /**
27
+ * 知识库基础信息
28
+ */
29
+ export interface Knowledge {
30
+ id: number;
31
+ name: string;
32
+ path: string;
33
+ level: number;
34
+ type: KnowledgeType;
35
+ safe: KnowledgeSafe;
36
+ userId: number;
37
+ // 书籍特有字段
38
+ coverImage?: string | null;
39
+ author?: string | null;
40
+ isbn?: string | null;
41
+ description?: string | null;
42
+ created: Date;
43
+ updated: Date;
44
+ isDelete: number;
45
+ isTop: number;
46
+ content?: string | null;
47
+ contentText?: string | null;
48
+ viewType: number;
49
+ }
50
+
51
+ /**
52
+ * 树节点(带子节点)
53
+ */
54
+ export interface KnowledgeTreeNode extends Knowledge {
55
+ children?: KnowledgeTreeNode[];
56
+ }
57
+
58
+ /**
59
+ * 标签
60
+ */
61
+ export interface KnowledgeTag {
62
+ id: number;
63
+ name: string;
64
+ color?: string | null;
65
+ userId?: number | null;
66
+ created?: Date;
67
+ }
68
+
69
+ /**
70
+ * 标签关联
71
+ */
72
+ export interface KnowledgeTagRelation {
73
+ knowledgeId: number;
74
+ tagId: number;
75
+ }
76
+
77
+ /**
78
+ * 创建知识库输入
79
+ */
80
+ export type InsertKnowledge = Partial<Omit<typeof knowledge.$inferInsert, "id">>;
81
+
82
+ /**
83
+ * 插入标签输入
84
+ */
85
+ export type InsertTag = typeof knowledgeTag.$inferInsert;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * 物化路径工具函数
3
+ */
4
+
5
+ /**
6
+ * 构建新节点路径
7
+ * @param parentPath 父节点路径
8
+ * @param id 新节点ID
9
+ * @returns 新节点路径
10
+ */
11
+ export function buildPath(parentPath: string, id: number): string {
12
+ return `${parentPath}${id}/`;
13
+ }
14
+
15
+ /**
16
+ * 从路径计算层级
17
+ * @param path 节点路径
18
+ * @returns 层级深度(根节点为0)
19
+ */
20
+ export function getLevel(path: string): number {
21
+ // path 格式: "/1/3/7/" -> 分割后 ['', '1', '3', '7', '']
22
+ // 减2是因为前后各有一个空字符串
23
+ const parts = path.split("/").filter(Boolean);
24
+ return parts.length - 1;
25
+ }
26
+
27
+ /**
28
+ * 验证路径是否有效(防止循环引用)
29
+ * @param newPath 新路径
30
+ * @param oldPath 旧路径
31
+ * @returns 是否有效
32
+ */
33
+ export function validatePath(newPath: string, oldPath: string): boolean {
34
+ // 不能将节点移动到自己的后代节点下
35
+ // 如果新父节点的 path 以旧节点的 path 开头,说明是后代
36
+ return !newPath.startsWith(oldPath);
37
+ }
38
+
39
+ /**
40
+ * 检查是否为后代节点
41
+ * @param ancestorPath 祖先节点路径
42
+ * @param descendantPath 后代节点路径
43
+ * @returns 是否为后代
44
+ */
45
+ export function isDescendant(ancestorPath: string, descendantPath: string): boolean {
46
+ return descendantPath.startsWith(ancestorPath) && ancestorPath !== descendantPath;
47
+ }
@@ -0,0 +1,57 @@
1
+ import type { KnowledgeTreeNode, Knowledge } from "../types";
2
+
3
+ /**
4
+ * 将扁平数组转换为树形结构
5
+ * @param nodes 扁平节点数组
6
+ * @param parentId 父节点ID(可选,用于构建子树)
7
+ * @returns 树形结构
8
+ */
9
+ export function buildTree(
10
+ nodes: Knowledge[],
11
+ parentId: number | null = null
12
+ ): KnowledgeTreeNode[] {
13
+ // 按 isTop 降序、created 降序排序
14
+ const sortedNodes = [...nodes].sort((a, b) => {
15
+ if (a.isTop !== b.isTop) {
16
+ return b.isTop - a.isTop;
17
+ }
18
+ return b.created.getTime() - a.created.getTime();
19
+ });
20
+
21
+ // 如果指定了 parentId,先找到该节点
22
+ if (parentId !== null) {
23
+ const parent = sortedNodes.find(n => n.id === parentId);
24
+ if (!parent) return [];
25
+
26
+ // 查找该节点的直接子节点
27
+ const children = sortedNodes.filter(n =>
28
+ n.path.startsWith(parent.path) &&
29
+ n.level === parent.level + 1
30
+ );
31
+
32
+ return children.map(child => buildNodeTree(child, sortedNodes));
33
+ }
34
+
35
+ // 否则返回根节点(level=0)的树
36
+ const rootNodes = sortedNodes.filter(n => n.level === 0);
37
+ return rootNodes.map(node => buildNodeTree(node, sortedNodes));
38
+ }
39
+
40
+ /**
41
+ * 递归构建节点树
42
+ */
43
+ function buildNodeTree(node: Knowledge, allNodes: Knowledge[]): KnowledgeTreeNode {
44
+ const treeNode: KnowledgeTreeNode = { ...node };
45
+
46
+ // 查找直接子节点
47
+ const children = allNodes.filter(n =>
48
+ n.path.startsWith(node.path) &&
49
+ n.level === node.level + 1
50
+ );
51
+
52
+ if (children.length > 0) {
53
+ treeNode.children = children.map(child => buildNodeTree(child, allNodes));
54
+ }
55
+
56
+ return treeNode;
57
+ }
@@ -0,0 +1,13 @@
1
+ // Zod 验证 Schema 已在 orm/Knowledge.schema.ts 中定义
2
+ // 此文件导出所有知识库相关的 Schema
3
+ export {
4
+ createKnowledgeSchema,
5
+ batchCreateKnowledgeSchema,
6
+ batchDetailKnowledgeSchema,
7
+ updateKnowledgeSchema,
8
+ listKnowledgeSchema,
9
+ searchKnowledgeSchema,
10
+ moveKnowledgeSchema,
11
+ updateIsTopSchema,
12
+ updateSafeSchema,
13
+ } from "../orm/Knowledge.schema";
@@ -0,0 +1,7 @@
1
+ // Zod 验证 Schema 已在 orm/Tag.schema.ts 中定义
2
+ // 此文件导出所有标签相关的 Schema
3
+ export {
4
+ createTagSchema,
5
+ updateTagSchema,
6
+ addTagRelationSchema,
7
+ } from "../orm/Tag.schema";
@@ -1,27 +0,0 @@
1
- import {
2
- batchCreateKnowledgeSchema,
3
- batchDetailKnowledgeSchema,
4
- createKnowledgeSchema,
5
- knowledge,
6
- listKnowledgeSchema,
7
- moveKnowledgeSchema,
8
- searchKnowledgeSchema,
9
- updateIsTopSchema,
10
- updateKnowledgeSchema,
11
- updateSafeSchema
12
- } from "./chunk-2G44ILZL.mjs";
13
- import "./chunk-GWIBTASJ.mjs";
14
- import "./chunk-VHBDNZOQ.mjs";
15
- export {
16
- batchCreateKnowledgeSchema,
17
- batchDetailKnowledgeSchema,
18
- createKnowledgeSchema,
19
- knowledge,
20
- listKnowledgeSchema,
21
- moveKnowledgeSchema,
22
- searchKnowledgeSchema,
23
- updateIsTopSchema,
24
- updateKnowledgeSchema,
25
- updateSafeSchema
26
- };
27
- //# sourceMappingURL=Knowledge.schema-BHFPDLIO.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}