From 8011c3f05bb8b815021fe03a268200cd44d40145 Mon Sep 17 00:00:00 2001 From: YaoJunchang Date: Mon, 20 Apr 2026 22:01:18 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=E2=9C=A8=20add=20metadata=20handling=20and?= =?UTF-8?q?=20new=20methods=20for=20comment=20marks=20by=20UID?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/infra/repository/dbMark.ts | 68 +++++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/src/infra/repository/dbMark.ts b/src/infra/repository/dbMark.ts index 692b554..6f72da3 100644 --- a/src/infra/repository/dbMark.ts +++ b/src/infra/repository/dbMark.ts @@ -4,6 +4,7 @@ import { PRISIMA_CLIENT, PRISIMA_HYPERDRIVE_CLIENT } from '../../const/symbol' import type { LazyInstance } from '../../decorators/lazy' import { PrismaClient as HyperdrivePrismaClient } from '@prisma/hyperdrive-client' import { PrismaClient } from '@prisma/client' +import { JsonValue } from '@prisma/client/runtime/client' export enum markType { LINE = 1, @@ -63,6 +64,8 @@ export interface markPOWithId { is_deleted: boolean created_at: Date updated_at: Date + uuid: string + metadata: JsonValue // { parent_id?: string; root_id?: string } } export interface markDetailPO { @@ -77,6 +80,9 @@ export interface markDetailPO { is_deleted: boolean parent_id: number root_id: number + uid: string + parent_uid?: string + root_uid?: string } @injectable() @@ -116,8 +122,10 @@ export class MarkRepo { } }) ).map(item => { + const metadata = item.metadata as { parent_id?: string; root_id?: string } return { id: item.id, + uid: item.uuid, user_id: item.is_deleted ? 0 : item.user_id, user_bookmark_id: item.bookmark_id, type: item.type, @@ -128,6 +136,8 @@ export class MarkRepo { is_deleted: item.is_deleted, parent_id: item.parent_id, root_id: item.root_id, + parent_uid: metadata?.parent_id ?? undefined, + root_uid: metadata?.root_id ?? undefined, approx_source: JSON.parse(item.approx_source.length > 0 ? item.approx_source : '{}') } }) @@ -136,8 +146,12 @@ export class MarkRepo { async get(id: number): Promise { const res = await this.prismaPg().sr_bookmark_comment.findFirst({ where: { id } }) if (!res) return null + + const metadata = res.metadata as { parent_id?: string; root_id?: string } + return { id: res.id, + uid: res.uuid, user_id: res.is_deleted ? 0 : res.user_id, user_bookmark_id: res.bookmark_id, type: res.type, @@ -147,7 +161,33 @@ export class MarkRepo { updated_at: res.updated_at, is_deleted: res.is_deleted, parent_id: res.parent_id, - root_id: res.root_id + root_id: res.root_id, + parent_uid: metadata?.parent_id ?? undefined, + root_uid: metadata?.root_id ?? undefined + } + } + + async getByUid(uid: string): Promise { + const res = await this.prismaPg().sr_bookmark_comment.findFirst({ where: { uuid: uid } }) + if (!res) return null + + const metadata = res.metadata as { parent_id?: string; root_id?: string } + + return { + id: res.id, + uid: res.uuid, + user_id: res.is_deleted ? 0 : res.user_id, + user_bookmark_id: res.bookmark_id, + type: res.type, + source: JSON.parse(res.source), + comment: res.is_deleted ? '' : res.comment, + created_at: res.created_at, + updated_at: res.updated_at, + is_deleted: res.is_deleted, + parent_id: res.parent_id, + root_id: res.root_id, + parent_uid: metadata?.parent_id ?? undefined, + root_uid: metadata?.root_id ?? undefined } } @@ -155,10 +195,20 @@ export class MarkRepo { return await this.prismaPg().sr_bookmark_comment.delete({ where: { id } }) } + async delByUid(uid: string) { + return await this.prismaPg().sr_bookmark_comment.delete({ where: { uuid: uid } }) + } + async deleteByRootId(bookmarkId: number, rootId: number) { return await this.prismaPg().sr_bookmark_comment.deleteMany({ where: { bookmark_id: bookmarkId, root_id: rootId } }) } + async deleteByRootUid(bookmarkId: number, rootUid: string) { + const rootMark = await this.getByUid(rootUid) + if (!rootMark) return + return await this.prismaPg().sr_bookmark_comment.deleteMany({ where: { bookmark_id: bookmarkId, root_id: rootMark.id } }) + } + async existsCommentMarkChild(bookmarkId: number, rootId: number) { const res = await this.prismaPg().sr_bookmark_comment.count({ where: { @@ -173,14 +223,30 @@ export class MarkRepo { return Number(res || 0) } + async existsCommentMarkChildByRootUid(bookmarkId: number, rootUid: string) { + const rootMark = await this.getByUid(rootUid) + if (!rootMark) return 0 + return await this.existsCommentMarkChild(bookmarkId, rootMark.id) + } + async updateCommentMarkDeleted(id: number) { return await this.prismaPg().sr_bookmark_comment.update({ where: { id }, data: { is_deleted: true, updated_at: new Date() } }) } + async updateCommentMarkDeletedByUid(uid: string) { + return await this.prismaPg().sr_bookmark_comment.update({ where: { uuid: uid }, data: { is_deleted: true, updated_at: new Date() } }) + } + async updateCommentRootId(id: number, rootId: number) { return await this.prismaPg().sr_bookmark_comment.update({ where: { id }, data: { root_id: rootId, updated_at: new Date() } }) } + async updateCommentRootUid(uid: string, rootUid: string) { + const rootMark = await this.getByUid(rootUid) + if (!rootMark) return + return await this.prismaPg().sr_bookmark_comment.update({ where: { uuid: uid }, data: { root_id: rootMark.id, updated_at: new Date() } }) + } + async deleteByBookmarkId(bookmarkId: number) { return await this.prismaPg().sr_bookmark_comment.deleteMany({ where: { bookmark_id: bookmarkId } }) } From 76b41d1ae20d1a2a982238abc8f1a2f1881b740e Mon Sep 17 00:00:00 2001 From: YaoJunchang Date: Tue, 21 Apr 2026 10:01:17 +0800 Subject: [PATCH 2/5] =?UTF-8?q?=E2=9C=A8=20enhance=20mark=20and=20bookmark?= =?UTF-8?q?=20handling=20with=20uid=20support=20and=20refactor=20mark=20se?= =?UTF-8?q?rvice=20methods?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/domain/mark.ts | 90 ++++++++++++++++++++++------- src/domain/orchestrator/bookmark.ts | 6 +- src/domain/orchestrator/share.ts | 4 +- src/handler/http/markController.ts | 17 ++++-- 4 files changed, 85 insertions(+), 32 deletions(-) diff --git a/src/domain/mark.ts b/src/domain/mark.ts index 85d526a..261b643 100644 --- a/src/domain/mark.ts +++ b/src/domain/mark.ts @@ -8,8 +8,6 @@ import { ServerError, ShareActionNotAllowedError, ShareCodeNotFoundError, - ShareCollectionNotAllowedError, - ShareCollectionNotFoundError, ShareDisabledError } from '../const/err' import { markDetailPO, MarkRepo, markType } from '../infra/repository/dbMark' @@ -20,6 +18,8 @@ import { UserRepo } from '../infra/repository/dbUser' export interface markResponse { id: number root_id: number + uid: string + root_uid?: string } export interface markPathItem { @@ -33,6 +33,7 @@ export interface markRequest { source: markPathItem[] select_content: markSelectContent[] parent_id: number + parent_uid?: string comment?: string bm_id?: number bookmark_uid?: string @@ -57,6 +58,11 @@ export interface markSelectContent { src: string } +export interface markIdParams { + id?: number + uid?: string +} + export interface markInfo { id: number user_id: number @@ -87,6 +93,9 @@ export interface markCommentItem { source_type: 'share' | 'bookmark' source_id: string approx_source?: markApproxSource + uid: string + parent_uid?: string + root_uid?: string } @injectable() @@ -110,10 +119,10 @@ export class MarkService { return res } - assertMarkBookmark = async (ctx: ContextManager, bmId: number, parentId: number) => { - if (parentId < 1) throw ErrorParam() + assertMarkBookmark = async (ctx: ContextManager, bmId: number, params: markIdParams) => { + if (!params.uid && (!params.id || params.id < 1)) throw ErrorParam() - const mark = await this.markRepo.get(parentId) + const mark = params.uid ? await this.markRepo.getByUid(params.uid) : await this.markRepo.get(params.id!) if (!mark) throw ErrorParam() if (mark.user_bookmark_id !== bmId) throw ShareActionNotAllowedError() if (mark.is_deleted) throw ShareActionNotAllowedError() @@ -168,7 +177,7 @@ export class MarkService { bmId = res.bookmark_id } - if (data.bm_id) { + if (data.bm_id || data.bookmark_uid) { await bookmarkHandle() } else if (data.share_code) { await shareHandle() @@ -195,11 +204,12 @@ export class MarkService { let parentId = 0 let replyComment: markDetailPO | undefined = undefined if (data.type === markType.REPLY) { - parentId = ctx.hashIds.decodeId(data.parent_id) - if (parentId < 1) throw ErrorParam() - const res = await this.assertMarkBookmark(ctx, userBookmark.id, parentId) + const parentParams: markIdParams = data.parent_uid ? { uid: data.parent_uid } : { id: ctx.hashIds.decodeId(data.parent_id) } + if (!parentParams.uid && (!parentParams.id || parentParams.id < 1)) throw ErrorParam() + const res = await this.assertMarkBookmark(ctx, userBookmark.id, parentParams) replyComment = res rootId = res.root_id + parentId = res.id data.source = [] } @@ -215,7 +225,7 @@ export class MarkService { sourceId = `${data.collection_code!}/${cbId}` } else { sourceType = 'bookmark' - sourceId = ctx.hashIds.decodeId(data.bm_id || 0) + sourceId = data.bm_id ? ctx.hashIds.decodeId(data.bm_id) : userBookmark.bookmark_id } // 创建实体 @@ -234,6 +244,7 @@ export class MarkService { root_id: rootId, approx_source: data.approx_source }) + if (!res) throw ServerError() // 如果是父级评论,多update一次追加root_id,后续delete的时候不需要重新查询 @@ -242,10 +253,13 @@ export class MarkService { } ctx.execution.waitUntil(callback()) + const metadata = res.metadata as { parent_id?: string; root_id?: string } return { response: { id: ctx.hashIds.encodeId(res.id), - root_id: ctx.hashIds.encodeId(res.root_id > 0 ? res.root_id : res.id) + root_id: ctx.hashIds.encodeId(res.root_id > 0 ? res.root_id : res.id), + uid: res.uuid, + root_uid: metadata?.root_id ?? undefined }, mark: res, userBookmark, @@ -253,10 +267,12 @@ export class MarkService { } } - public async deleteMark(ctx: ContextManager, markId: number): Promise { + public async deleteMark(ctx: ContextManager, params: markIdParams): Promise { + if (!params.uid && !params.id) throw ErrorParam() const userId = ctx.getUserId() + const useUid = !!params.uid - const mark = await this.markRepo.get(markId) + const mark = useUid ? await this.markRepo.getByUid(params.uid!) : await this.markRepo.get(params.id!) if (!mark || mark.is_deleted) throw ErrorParam() if (mark.user_id !== userId) { // 非本人则去校验文章所有权 @@ -267,19 +283,34 @@ export class MarkService { // 如果root_id的评论底下有任意子评论,则软删除当前评论 if ([markType.COMMENT, markType.ORIGIN_COMMENT, markType.REPLY].includes(mark.type)) { - const res = await this.markRepo.existsCommentMarkChild(mark.user_bookmark_id, mark.root_id) + const res = + useUid && mark.root_uid + ? await this.markRepo.existsCommentMarkChildByRootUid(mark.user_bookmark_id, mark.root_uid) + : await this.markRepo.existsCommentMarkChild(mark.user_bookmark_id, mark.root_id) // 如果有子评论,把评论标记为删除 if (res && res > 1) { - await this.markRepo.updateCommentMarkDeleted(markId) + if (useUid) { + await this.markRepo.updateCommentMarkDeletedByUid(params.uid!) + } else { + await this.markRepo.updateCommentMarkDeleted(params.id!) + } return 'ok' } } // 硬删除评论 try { if ([markType.COMMENT, markType.ORIGIN_COMMENT, markType.REPLY].includes(mark.type)) { - await this.markRepo.deleteByRootId(mark.user_bookmark_id, mark.root_id) + if (useUid && mark.root_uid) { + await this.markRepo.deleteByRootUid(mark.user_bookmark_id, mark.root_uid) + } else { + await this.markRepo.deleteByRootId(mark.user_bookmark_id, mark.root_id) + } } else if ([markType.LINE, markType.ORIGIN_LINE].includes(mark.type)) { - await this.markRepo.del(markId) + if (useUid) { + await this.markRepo.delByUid(params.uid!) + } else { + await this.markRepo.del(params.id!) + } } } catch (e) { console.error(`delete mark failed: ${e}`) @@ -289,12 +320,20 @@ export class MarkService { return 'ok' } - public async getBookmarkMarkList(ctx: ContextManager, userBmId: number, isShowMarks: boolean) { + public async getBookmarkMarkList(ctx: ContextManager, params: markIdParams & { isShowMarks: boolean }) { const defaultResult = { mark_list: [], user_list: [] } - if (!isShowMarks) return defaultResult + if (!params.isShowMarks) return defaultResult const markRepo = this.markRepo const userRepo = this.userRepo + let userBmId = params.id + if (params.uid && !userBmId) { + const ub = await this.bookmarkRepo.getUserBookmarkByUuid(params.uid) + if (!ub) return defaultResult + userBmId = ub.id + } + if (!userBmId) return defaultResult + const marks = await markRepo.list(userBmId) if (marks.length === 0) return defaultResult @@ -310,7 +349,10 @@ export class MarkService { root_id: ctx.hashIds.encodeId(m.root_id), created_at: m.created_at, is_deleted: m.is_deleted, - approx_source: m.approx_source + approx_source: m.approx_source, + uid: m.uid, + parent_uid: m.parent_uid, + root_uid: m.root_uid } }) const userMap: Record = { @@ -388,8 +430,12 @@ export class MarkService { const [collectionCode, cbId] = mark.source_id.split('/') sourceId = `${collectionCode}/${ctx.hashIds.encodeId(parseInt(cbId))}` } + + const metadata = mark.metadata as { parent_id?: string; root_id?: string } + res.push({ id: ctx.hashIds.encodeId(mark.id), + uid: mark.uuid, type: markTypeMap[mark.type as markType], content: JSON.parse(mark.content) as markSelectContent[], created_at: mark.created_at, @@ -400,7 +446,9 @@ export class MarkService { comment: mark.comment, source_type: mark.source_type as 'share' | 'bookmark', source_id: sourceId, - approx_source: JSON.parse(mark.approx_source) + approx_source: JSON.parse(mark.approx_source), + parent_uid: metadata?.parent_id ?? undefined, + root_uid: metadata?.root_id ?? undefined }) } catch (e) { console.log(`get mark list failed: ${e}, content: ${mark.content}`) diff --git a/src/domain/orchestrator/bookmark.ts b/src/domain/orchestrator/bookmark.ts index 59a8a6a..78fe8ef 100644 --- a/src/domain/orchestrator/bookmark.ts +++ b/src/domain/orchestrator/bookmark.ts @@ -19,7 +19,7 @@ export class BookmarkOrchestrator { if (!res || !res.bookmark) throw BookmarkNotFoundError() if (res.bookmark.private_user > 0 && res.bookmark.private_user !== userId) throw BookmarkNotFoundError() - const marksResult = await this.markService.getBookmarkMarkList(ctx, res.id, true) + const marksResult = await this.markService.getBookmarkMarkList(ctx, { id: res.id, isShowMarks: true }) return marksResult } @@ -29,7 +29,7 @@ export class BookmarkOrchestrator { if (res.bookmark.private_user > 0 && res.bookmark.private_user !== ctx.getUserId()) throw BookmarkNotFoundError() const [marksResult, overviewResult, tagsResult] = await Promise.allSettled([ - this.markService.getBookmarkMarkList(ctx, res.id, true), + this.markService.getBookmarkMarkList(ctx, { id: res.id, isShowMarks: true }), this.bookmarkService.getUserBookmarkOverview(ctx.getUserId(), bmId), this.tagService.getBookmarkTags(ctx, ctx.getUserId(), bmId) ]) @@ -59,7 +59,7 @@ export class BookmarkOrchestrator { const [contentResult, marksResult, tagsResult, overviewResult] = await Promise.allSettled([ this.bookmarkService.getBookmarkContent(res.bookmark.content_key), - this.markService.getBookmarkMarkList(ctx, res.id, true), + this.markService.getBookmarkMarkList(ctx, { id: res.id, isShowMarks: true }), this.tagService.getBookmarkTags(ctx, userId, bmId), this.bookmarkService.getUserBookmarkOverview(userId, bmId) ]) diff --git a/src/domain/orchestrator/share.ts b/src/domain/orchestrator/share.ts index 9a4fae5..289317b 100644 --- a/src/domain/orchestrator/share.ts +++ b/src/domain/orchestrator/share.ts @@ -47,7 +47,7 @@ export class ShareOrchestrator { const [userInfo, bookmark, marks] = await Promise.all([ this.userService.getUserBriefInfo(share.show_userinfo, share.user_id), this.bookmarkService.getBookmarkById(share.bookmark_id), - this.markService.getBookmarkMarkList(ctx, userBm.id, share.show_comment && share.show_line) + this.markService.getBookmarkMarkList(ctx, { id: userBm.id, isShowMarks: share.show_comment && share.show_line }) ]) if (!bookmark) throw BookmarkNotFoundError() @@ -111,6 +111,6 @@ export class ShareOrchestrator { const userBm = await this.bookmarkService.getUserBookmark(share.bookmark_id, share.user_id) if (!userBm) return { mark_list: [], user_list: [] } - return await this.markService.getBookmarkMarkList(ctx, userBm.id, share.show_comment && share.show_line) + return await this.markService.getBookmarkMarkList(ctx, { id: userBm.id, isShowMarks: share.show_comment && share.show_line }) } } diff --git a/src/handler/http/markController.ts b/src/handler/http/markController.ts index 8ddf70c..d2993a1 100644 --- a/src/handler/http/markController.ts +++ b/src/handler/http/markController.ts @@ -23,7 +23,7 @@ export class MarkController { @Post('/create') public async createMark(ctx: ContextManager, request: Request) { const req = await RequestUtils.json(request) - if (!req || !req.source || (!req.bm_id && !req.share_code && !req.collection_code && !req.cb_id)) { + if (!req || !req.source || (!req.bm_id && !req.bookmark_uid && !req.share_code && !req.collection_code && !req.cb_id)) { return Failed(ErrorParam()) } @@ -34,14 +34,16 @@ export class MarkController { const sourceType = typeof req.source if (req.type === markType.LINE && (req.comment || sourceType !== 'object')) return Failed(ErrorMarkTypeError()) if (req.type === markType.COMMENT && (!req.comment || req.comment.length < 1 || sourceType !== 'object')) return Failed(ErrorMarkTypeError()) - if (req.type === markType.REPLY && (!req.comment || req.comment.length < 1)) return Failed(ErrorMarkTypeError()) + if (req.type === markType.REPLY && (!req.comment || req.comment.length < 1 || (!req.parent_id && !req.parent_uid))) return Failed(ErrorMarkTypeError()) if ([markType.ORIGIN_COMMENT, markType.ORIGIN_LINE].includes(req.type) && !req.approx_source) return Failed(ErrorMarkTypeError()) const createResult = await this.markOrchestrator.createMark(ctx, req) return Successed({ mark_id: createResult.id, - root_id: createResult.root_id + root_id: createResult.root_id, + mark_uid: createResult.uid, + root_uid: createResult.root_uid }) } @@ -50,11 +52,14 @@ export class MarkController { */ @Post('/delete') public async deleteMark(ctx: ContextManager, request: Request) { - const req = await RequestUtils.json<{ mark_id: number }>(request) - if (!req || !req.mark_id) { + const req = await RequestUtils.json<{ mark_id?: number; mark_uid?: string }>(request) + if (!req || (!req.mark_id && !req.mark_uid)) { return Failed(ErrorParam()) } - const deleteResult = await this.markService.deleteMark(ctx, ctx.hashIds.decodeId(req.mark_id)) + const deleteResult = await this.markService.deleteMark(ctx, { + id: req.mark_id ? ctx.hashIds.decodeId(req.mark_id) : undefined, + uid: req.mark_uid + }) return Successed(deleteResult) } From 565bdf513a7a9a5af0385156d9009a15acd493be Mon Sep 17 00:00:00 2001 From: YaoJunchang Date: Wed, 22 Apr 2026 15:15:12 +0800 Subject: [PATCH 3/5] =?UTF-8?q?=E2=9C=A8=20refactor=20mark=20handling=20to?= =?UTF-8?q?=20replace=20'uid'=20with=20'uuid'=20for=20consistency=20across?= =?UTF-8?q?=20interfaces=20and=20services?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/domain/mark.ts | 14 +++++++------- src/handler/http/markController.ts | 2 +- src/infra/repository/dbMark.ts | 18 +++++++++--------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/domain/mark.ts b/src/domain/mark.ts index 261b643..c8be347 100644 --- a/src/domain/mark.ts +++ b/src/domain/mark.ts @@ -18,7 +18,7 @@ import { UserRepo } from '../infra/repository/dbUser' export interface markResponse { id: number root_id: number - uid: string + uuid: string root_uid?: string } @@ -93,7 +93,7 @@ export interface markCommentItem { source_type: 'share' | 'bookmark' source_id: string approx_source?: markApproxSource - uid: string + uuid: string parent_uid?: string root_uid?: string } @@ -122,7 +122,7 @@ export class MarkService { assertMarkBookmark = async (ctx: ContextManager, bmId: number, params: markIdParams) => { if (!params.uid && (!params.id || params.id < 1)) throw ErrorParam() - const mark = params.uid ? await this.markRepo.getByUid(params.uid) : await this.markRepo.get(params.id!) + const mark = params.uid ? await this.markRepo.getByUuid(params.uid) : await this.markRepo.get(params.id!) if (!mark) throw ErrorParam() if (mark.user_bookmark_id !== bmId) throw ShareActionNotAllowedError() if (mark.is_deleted) throw ShareActionNotAllowedError() @@ -258,7 +258,7 @@ export class MarkService { response: { id: ctx.hashIds.encodeId(res.id), root_id: ctx.hashIds.encodeId(res.root_id > 0 ? res.root_id : res.id), - uid: res.uuid, + uuid: res.uuid, root_uid: metadata?.root_id ?? undefined }, mark: res, @@ -272,7 +272,7 @@ export class MarkService { const userId = ctx.getUserId() const useUid = !!params.uid - const mark = useUid ? await this.markRepo.getByUid(params.uid!) : await this.markRepo.get(params.id!) + const mark = useUid ? await this.markRepo.getByUuid(params.uid!) : await this.markRepo.get(params.id!) if (!mark || mark.is_deleted) throw ErrorParam() if (mark.user_id !== userId) { // 非本人则去校验文章所有权 @@ -350,7 +350,7 @@ export class MarkService { created_at: m.created_at, is_deleted: m.is_deleted, approx_source: m.approx_source, - uid: m.uid, + uuid: m.uuid, parent_uid: m.parent_uid, root_uid: m.root_uid } @@ -435,7 +435,7 @@ export class MarkService { res.push({ id: ctx.hashIds.encodeId(mark.id), - uid: mark.uuid, + uuid: mark.uuid, type: markTypeMap[mark.type as markType], content: JSON.parse(mark.content) as markSelectContent[], created_at: mark.created_at, diff --git a/src/handler/http/markController.ts b/src/handler/http/markController.ts index d2993a1..6984e73 100644 --- a/src/handler/http/markController.ts +++ b/src/handler/http/markController.ts @@ -42,7 +42,7 @@ export class MarkController { return Successed({ mark_id: createResult.id, root_id: createResult.root_id, - mark_uid: createResult.uid, + mark_uid: createResult.uuid, root_uid: createResult.root_uid }) } diff --git a/src/infra/repository/dbMark.ts b/src/infra/repository/dbMark.ts index 6f72da3..75bb5d8 100644 --- a/src/infra/repository/dbMark.ts +++ b/src/infra/repository/dbMark.ts @@ -80,7 +80,7 @@ export interface markDetailPO { is_deleted: boolean parent_id: number root_id: number - uid: string + uuid: string parent_uid?: string root_uid?: string } @@ -125,7 +125,7 @@ export class MarkRepo { const metadata = item.metadata as { parent_id?: string; root_id?: string } return { id: item.id, - uid: item.uuid, + uuid: item.uuid, user_id: item.is_deleted ? 0 : item.user_id, user_bookmark_id: item.bookmark_id, type: item.type, @@ -151,7 +151,7 @@ export class MarkRepo { return { id: res.id, - uid: res.uuid, + uuid: res.uuid, user_id: res.is_deleted ? 0 : res.user_id, user_bookmark_id: res.bookmark_id, type: res.type, @@ -167,15 +167,15 @@ export class MarkRepo { } } - async getByUid(uid: string): Promise { - const res = await this.prismaPg().sr_bookmark_comment.findFirst({ where: { uuid: uid } }) + async getByUuid(uuid: string): Promise { + const res = await this.prismaPg().sr_bookmark_comment.findFirst({ where: { uuid } }) if (!res) return null const metadata = res.metadata as { parent_id?: string; root_id?: string } return { id: res.id, - uid: res.uuid, + uuid: res.uuid, user_id: res.is_deleted ? 0 : res.user_id, user_bookmark_id: res.bookmark_id, type: res.type, @@ -204,7 +204,7 @@ export class MarkRepo { } async deleteByRootUid(bookmarkId: number, rootUid: string) { - const rootMark = await this.getByUid(rootUid) + const rootMark = await this.getByUuid(rootUid) if (!rootMark) return return await this.prismaPg().sr_bookmark_comment.deleteMany({ where: { bookmark_id: bookmarkId, root_id: rootMark.id } }) } @@ -224,7 +224,7 @@ export class MarkRepo { } async existsCommentMarkChildByRootUid(bookmarkId: number, rootUid: string) { - const rootMark = await this.getByUid(rootUid) + const rootMark = await this.getByUuid(rootUid) if (!rootMark) return 0 return await this.existsCommentMarkChild(bookmarkId, rootMark.id) } @@ -242,7 +242,7 @@ export class MarkRepo { } async updateCommentRootUid(uid: string, rootUid: string) { - const rootMark = await this.getByUid(rootUid) + const rootMark = await this.getByUuid(rootUid) if (!rootMark) return return await this.prismaPg().sr_bookmark_comment.update({ where: { uuid: uid }, data: { root_id: rootMark.id, updated_at: new Date() } }) } From bc24f3c5d1887f81547aafb2604d3ec3abf78b88 Mon Sep 17 00:00:00 2001 From: YaoJunchang Date: Wed, 22 Apr 2026 16:20:41 +0800 Subject: [PATCH 4/5] =?UTF-8?q?=E2=9C=A8=20refactor=20mark=20ID=20handling?= =?UTF-8?q?=20in=20MarkService=20and=20MarkController=20for=20improved=20c?= =?UTF-8?q?larity=20and=20consistency?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/domain/mark.ts | 107 +++++++++++++++-------------- src/handler/http/markController.ts | 5 +- 2 files changed, 57 insertions(+), 55 deletions(-) diff --git a/src/domain/mark.ts b/src/domain/mark.ts index c8be347..69acada 100644 --- a/src/domain/mark.ts +++ b/src/domain/mark.ts @@ -58,10 +58,7 @@ export interface markSelectContent { src: string } -export interface markIdParams { - id?: number - uid?: string -} +export type markIdParams = { uid: string } | { id: number } export interface markInfo { id: number @@ -119,11 +116,14 @@ export class MarkService { return res } - assertMarkBookmark = async (ctx: ContextManager, bmId: number, params: markIdParams) => { - if (!params.uid && (!params.id || params.id < 1)) throw ErrorParam() - - const mark = params.uid ? await this.markRepo.getByUuid(params.uid) : await this.markRepo.get(params.id!) + protected resolveMark = async (params: markIdParams) => { + const mark = 'uid' in params ? await this.markRepo.getByUuid(params.uid) : await this.markRepo.get(params.id) if (!mark) throw ErrorParam() + return mark + } + + assertMarkBookmark = async (ctx: ContextManager, bmId: number, params: markIdParams) => { + const mark = await this.resolveMark(params) if (mark.user_bookmark_id !== bmId) throw ShareActionNotAllowedError() if (mark.is_deleted) throw ShareActionNotAllowedError() return mark @@ -205,7 +205,6 @@ export class MarkService { let replyComment: markDetailPO | undefined = undefined if (data.type === markType.REPLY) { const parentParams: markIdParams = data.parent_uid ? { uid: data.parent_uid } : { id: ctx.hashIds.decodeId(data.parent_id) } - if (!parentParams.uid && (!parentParams.id || parentParams.id < 1)) throw ErrorParam() const res = await this.assertMarkBookmark(ctx, userBookmark.id, parentParams) replyComment = res rootId = res.root_id @@ -268,49 +267,32 @@ export class MarkService { } public async deleteMark(ctx: ContextManager, params: markIdParams): Promise { - if (!params.uid && !params.id) throw ErrorParam() - const userId = ctx.getUserId() - const useUid = !!params.uid + const mark = await this.resolveMark(params) + if (mark.is_deleted) throw ErrorParam() - const mark = useUid ? await this.markRepo.getByUuid(params.uid!) : await this.markRepo.get(params.id!) - if (!mark || mark.is_deleted) throw ErrorParam() + const userId = ctx.getUserId() if (mark.user_id !== userId) { - // 非本人则去校验文章所有权 const ubm = await this.bookmarkRepo.getUserBookmarkById(mark.user_bookmark_id) - if (!ubm) throw ShareActionNotAllowedError() - if (ubm.user_id !== userId) throw ShareActionNotAllowedError() + if (!ubm || ubm.user_id !== userId) throw ShareActionNotAllowedError() } - // 如果root_id的评论底下有任意子评论,则软删除当前评论 - if ([markType.COMMENT, markType.ORIGIN_COMMENT, markType.REPLY].includes(mark.type)) { - const res = - useUid && mark.root_uid - ? await this.markRepo.existsCommentMarkChildByRootUid(mark.user_bookmark_id, mark.root_uid) - : await this.markRepo.existsCommentMarkChild(mark.user_bookmark_id, mark.root_id) - // 如果有子评论,把评论标记为删除 - if (res && res > 1) { - if (useUid) { - await this.markRepo.updateCommentMarkDeletedByUid(params.uid!) - } else { - await this.markRepo.updateCommentMarkDeleted(params.id!) - } + const isComment = [markType.COMMENT, markType.ORIGIN_COMMENT, markType.REPLY].includes(mark.type) + + if (isComment) { + const childCount = mark.root_uid + ? await this.markRepo.existsCommentMarkChildByRootUid(mark.user_bookmark_id, mark.root_uid) + : await this.markRepo.existsCommentMarkChild(mark.user_bookmark_id, mark.root_id) + if (childCount && childCount > 1) { + await this.softDeleteMark(mark) return 'ok' } } - // 硬删除评论 + try { - if ([markType.COMMENT, markType.ORIGIN_COMMENT, markType.REPLY].includes(mark.type)) { - if (useUid && mark.root_uid) { - await this.markRepo.deleteByRootUid(mark.user_bookmark_id, mark.root_uid) - } else { - await this.markRepo.deleteByRootId(mark.user_bookmark_id, mark.root_id) - } - } else if ([markType.LINE, markType.ORIGIN_LINE].includes(mark.type)) { - if (useUid) { - await this.markRepo.delByUid(params.uid!) - } else { - await this.markRepo.del(params.id!) - } + if (isComment) { + await this.hardDeleteComment(mark) + } else { + await this.hardDeleteLine(mark) } } catch (e) { console.error(`delete mark failed: ${e}`) @@ -320,24 +302,48 @@ export class MarkService { return 'ok' } + private async softDeleteMark(mark: markDetailPO) { + if (mark.uuid) { + await this.markRepo.updateCommentMarkDeletedByUid(mark.uuid) + } else { + await this.markRepo.updateCommentMarkDeleted(mark.id) + } + } + + private async hardDeleteComment(mark: markDetailPO) { + if (mark.root_uid) { + await this.markRepo.deleteByRootUid(mark.user_bookmark_id, mark.root_uid) + } else { + await this.markRepo.deleteByRootId(mark.user_bookmark_id, mark.root_id) + } + } + + private async hardDeleteLine(mark: markDetailPO) { + if (mark.uuid) { + await this.markRepo.delByUid(mark.uuid) + } else { + await this.markRepo.del(mark.id) + } + } + public async getBookmarkMarkList(ctx: ContextManager, params: markIdParams & { isShowMarks: boolean }) { const defaultResult = { mark_list: [], user_list: [] } if (!params.isShowMarks) return defaultResult - const markRepo = this.markRepo - const userRepo = this.userRepo - let userBmId = params.id - if (params.uid && !userBmId) { + let userBmId: number | undefined + if ('uid' in params) { const ub = await this.bookmarkRepo.getUserBookmarkByUuid(params.uid) if (!ub) return defaultResult userBmId = ub.id + } else { + userBmId = params.id } if (!userBmId) return defaultResult - const marks = await markRepo.list(userBmId) + const marks = await this.markRepo.list(userBmId) if (marks.length === 0) return defaultResult - const users = await userRepo.getUserInfoList(marks.map(m => m.user_id)) + const users = await this.userRepo.getUserInfoList(marks.map(m => m.user_id)) const markList: markInfo[] = marks.map(m => { return { id: ctx.hashIds.encodeId(m.id), @@ -377,7 +383,6 @@ export class MarkService { } public async getMarkList(ctx: ContextManager, page: number, size: number): Promise { - const markRepo = this.markRepo const markTypeMap: Record = { [markType.COMMENT]: 'comment', [markType.LINE]: 'mark', @@ -385,7 +390,7 @@ export class MarkService { [markType.ORIGIN_LINE]: 'mark', [markType.ORIGIN_COMMENT]: 'comment' } - const marks = await markRepo.listUserMark(ctx.getUserId(), page, size) + const marks = await this.markRepo.listUserMark(ctx.getUserId(), page, size) if (marks.length === 0) return [] const bookmarkIdList: number[] = [] diff --git a/src/handler/http/markController.ts b/src/handler/http/markController.ts index 6984e73..b5ea9de 100644 --- a/src/handler/http/markController.ts +++ b/src/handler/http/markController.ts @@ -56,10 +56,7 @@ export class MarkController { if (!req || (!req.mark_id && !req.mark_uid)) { return Failed(ErrorParam()) } - const deleteResult = await this.markService.deleteMark(ctx, { - id: req.mark_id ? ctx.hashIds.decodeId(req.mark_id) : undefined, - uid: req.mark_uid - }) + const deleteResult = await this.markService.deleteMark(ctx, req.mark_uid ? { uid: req.mark_uid } : { id: ctx.hashIds.decodeId(req.mark_id!) }) return Successed(deleteResult) } From dc67633cc88905ec9e8e4bef87eb08825c50b341 Mon Sep 17 00:00:00 2001 From: YaoJunchang Date: Wed, 22 Apr 2026 17:05:50 +0800 Subject: [PATCH 5/5] =?UTF-8?q?=E2=9C=A8=20refactor=20mark=20ID=20handling?= =?UTF-8?q?=20to=20replace=20'uid'=20with=20'uuid'=20and=20enhance=20mark?= =?UTF-8?q?=20metadata=20structure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/domain/mark.ts | 27 ++++++++++++++++++++------- src/handler/http/markController.ts | 10 ++++++++-- src/infra/repository/dbMark.ts | 8 ++++---- 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/domain/mark.ts b/src/domain/mark.ts index 69acada..263c02d 100644 --- a/src/domain/mark.ts +++ b/src/domain/mark.ts @@ -58,7 +58,15 @@ export interface markSelectContent { src: string } -export type markIdParams = { uid: string } | { id: number } +export type markIdParams = { uuid: string } | { id: number } + +export interface markMetadata { + root_id?: string | null + parent_id?: string | null + user_id?: string + source_id?: string + bookmark_id?: string +} export interface markInfo { id: number @@ -117,7 +125,12 @@ export class MarkService { } protected resolveMark = async (params: markIdParams) => { - const mark = 'uid' in params ? await this.markRepo.getByUuid(params.uid) : await this.markRepo.get(params.id) + if ('uuid' in params) { + const mark = await this.markRepo.getByUuid(params.uuid) + if (!mark) throw ErrorParam() + return mark + } + const mark = await this.markRepo.get(params.id) if (!mark) throw ErrorParam() return mark } @@ -204,7 +217,7 @@ export class MarkService { let parentId = 0 let replyComment: markDetailPO | undefined = undefined if (data.type === markType.REPLY) { - const parentParams: markIdParams = data.parent_uid ? { uid: data.parent_uid } : { id: ctx.hashIds.decodeId(data.parent_id) } + const parentParams: markIdParams = data.parent_uid ? { uuid: data.parent_uid } : { id: ctx.hashIds.decodeId(data.parent_id) } const res = await this.assertMarkBookmark(ctx, userBookmark.id, parentParams) replyComment = res rootId = res.root_id @@ -252,7 +265,7 @@ export class MarkService { } ctx.execution.waitUntil(callback()) - const metadata = res.metadata as { parent_id?: string; root_id?: string } + const metadata = res.metadata as markMetadata return { response: { id: ctx.hashIds.encodeId(res.id), @@ -331,8 +344,8 @@ export class MarkService { if (!params.isShowMarks) return defaultResult let userBmId: number | undefined - if ('uid' in params) { - const ub = await this.bookmarkRepo.getUserBookmarkByUuid(params.uid) + if ('uuid' in params) { + const ub = await this.bookmarkRepo.getUserBookmarkByUuid(params.uuid) if (!ub) return defaultResult userBmId = ub.id } else { @@ -436,7 +449,7 @@ export class MarkService { sourceId = `${collectionCode}/${ctx.hashIds.encodeId(parseInt(cbId))}` } - const metadata = mark.metadata as { parent_id?: string; root_id?: string } + const metadata = mark.metadata as markMetadata res.push({ id: ctx.hashIds.encodeId(mark.id), diff --git a/src/handler/http/markController.ts b/src/handler/http/markController.ts index b5ea9de..aa1e01d 100644 --- a/src/handler/http/markController.ts +++ b/src/handler/http/markController.ts @@ -1,6 +1,6 @@ import { ErrorMarkTypeError, ErrorParam } from '../../const/err' import { markType } from '../../infra/repository/dbMark' -import { markRequest } from '../../domain/mark' +import { markRequest, markIdParams } from '../../domain/mark' import { RequestUtils } from '../../utils/requestUtils' import { Failed, Successed } from '../../utils/responseUtils' import { Controller } from '../../decorators/controller' @@ -56,7 +56,13 @@ export class MarkController { if (!req || (!req.mark_id && !req.mark_uid)) { return Failed(ErrorParam()) } - const deleteResult = await this.markService.deleteMark(ctx, req.mark_uid ? { uid: req.mark_uid } : { id: ctx.hashIds.decodeId(req.mark_id!) }) + let params: markIdParams + if (req.mark_uid) { + params = { uuid: req.mark_uid } + } else { + params = { id: ctx.hashIds.decodeId(req.mark_id!) } + } + const deleteResult = await this.markService.deleteMark(ctx, params) return Successed(deleteResult) } diff --git a/src/infra/repository/dbMark.ts b/src/infra/repository/dbMark.ts index 75bb5d8..205777f 100644 --- a/src/infra/repository/dbMark.ts +++ b/src/infra/repository/dbMark.ts @@ -1,4 +1,4 @@ -import { markSelectContent } from '../../domain/mark' +import { markSelectContent, markMetadata } from '../../domain/mark' import { inject, injectable } from '../../decorators/di' import { PRISIMA_CLIENT, PRISIMA_HYPERDRIVE_CLIENT } from '../../const/symbol' import type { LazyInstance } from '../../decorators/lazy' @@ -122,7 +122,7 @@ export class MarkRepo { } }) ).map(item => { - const metadata = item.metadata as { parent_id?: string; root_id?: string } + const metadata = item.metadata as markMetadata return { id: item.id, uuid: item.uuid, @@ -147,7 +147,7 @@ export class MarkRepo { const res = await this.prismaPg().sr_bookmark_comment.findFirst({ where: { id } }) if (!res) return null - const metadata = res.metadata as { parent_id?: string; root_id?: string } + const metadata = res.metadata as markMetadata return { id: res.id, @@ -171,7 +171,7 @@ export class MarkRepo { const res = await this.prismaPg().sr_bookmark_comment.findFirst({ where: { uuid } }) if (!res) return null - const metadata = res.metadata as { parent_id?: string; root_id?: string } + const metadata = res.metadata as markMetadata return { id: res.id,