diff --git a/prisma/hyperdrive.prisma b/prisma/hyperdrive.prisma index 6d4fbb1..833421f 100644 --- a/prisma/hyperdrive.prisma +++ b/prisma/hyperdrive.prisma @@ -250,8 +250,7 @@ model sr_platform_bind { } model sr_bookmark_share { - id Int @id @default(autoincrement()) - uuid String @unique @default(dbgenerated("gen_random_uuid()")) + id Int @id @default(autoincrement()) share_code String @default("") user_id Int @default(0) @@ -264,8 +263,8 @@ model sr_bookmark_share { is_enable Boolean @default(true) created_at DateTime @default(now()) - @@unique([share_code]) @@unique([bookmark_id, user_id]) + @@index([share_code]) } model sr_bookmark_import { diff --git a/prisma/migrations/20260608030507_bookmark_share_index_sharecode/migration.sql b/prisma/migrations/20260608030507_bookmark_share_index_sharecode/migration.sql new file mode 100644 index 0000000..d6ce088 --- /dev/null +++ b/prisma/migrations/20260608030507_bookmark_share_index_sharecode/migration.sql @@ -0,0 +1,17 @@ +/* + Warnings: + + - You are about to drop the column `uuid` on the `sr_bookmark_share` table. All the data in the column will be lost. + +*/ +-- DropIndex +DROP INDEX "sr_bookmark_share_share_code_key"; + +-- DropIndex +DROP INDEX "sr_bookmark_share_uuid_key"; + +-- AlterTable +ALTER TABLE "sr_bookmark_share" DROP COLUMN "uuid"; + +-- CreateIndex +CREATE INDEX "sr_bookmark_share_share_code_idx" ON "sr_bookmark_share"("share_code"); diff --git a/src/domain/bookmark.ts b/src/domain/bookmark.ts index e5b25b1..54715ce 100644 --- a/src/domain/bookmark.ts +++ b/src/domain/bookmark.ts @@ -789,6 +789,10 @@ export class BookmarkService { return await this.bookmarkRepo.getUserBookmarkByUuidWithDetail(uuid) } + public async getBookmarkShareByBookmarkId(bmId: number, userId: number) { + return await this.bookmarkRepo.getBookmarkShareByBookmarkId(bmId, userId) + } + public async getUserBookmarkWithDetail(userId: number, bmId: number) { return (await this.bookmarkRepo.getUserBookmarkWithDetail(bmId, userId)) ?? null } @@ -908,19 +912,17 @@ export class BookmarkService { public async getBookmarkTitleContent( ctx: ContextManager, - bmId?: number, - shareCode?: string, - cbId?: number, - title?: string, - content?: string + params: { bmId?: number; shareCode?: string; cbId?: number; title?: string; content?: string; bmUId?: string } ): Promise<{ title: string; content: string; bmId: number }> { - if (!bmId && !shareCode && !cbId && !title && !content) throw ErrorParam() + const { bmId, shareCode, cbId, title, content, bmUId } = params - if (!bmId && !shareCode && !cbId && title && content) { + const emptyId = !bmId && !shareCode && !cbId && !bmUId + if (emptyId && !title && !content) throw ErrorParam() + if (emptyId && title && content) { return { title, content, bmId: 0 } } - const bookmarkId = await this.getBookmarkId(ctx, { bmId, shareCode, cbId }) + const bookmarkId = await this.getBookmarkId(ctx, { bmId, shareCode, cbId, bmUId }) if (!bookmarkId || bookmarkId < 1) throw ErrorParam() const bookmark = await this.getBookmarkById(bookmarkId) diff --git a/src/domain/orchestrator/share.ts b/src/domain/orchestrator/share.ts index 289317b..817cc6f 100644 --- a/src/domain/orchestrator/share.ts +++ b/src/domain/orchestrator/share.ts @@ -113,4 +113,18 @@ export class ShareOrchestrator { if (!userBm) return { mark_list: [], user_list: [] } return await this.markService.getBookmarkMarkList(ctx, { id: userBm.id, isShowMarks: share.show_comment && share.show_line }) } + + public async getBookmarkShareMarkListByUid(ctx: ContextManager, bmUId: string): Promise { + const empty = { mark_list: [], user_list: [] } + const ub = await this.bookmarkService.getUserBookmarkByUuidWithDetail(bmUId) + if (!ub) return empty + + if (ub.user_id !== ctx.getUserId()) { + const share = await this.bookmarkService.getBookmarkShareByBookmarkId(ub.bookmark_id, ub.user_id) + const allowed = !share || (share.is_enable && share.allow_comment && share.allow_line) + if (!allowed) return empty + } + + return await this.markService.getBookmarkMarkList(ctx, { id: ub.id, isShowMarks: true }) + } } diff --git a/src/domain/share.ts b/src/domain/share.ts index 77b09ce..d7e25dd 100644 --- a/src/domain/share.ts +++ b/src/domain/share.ts @@ -16,7 +16,8 @@ export interface updateBookmarkShareReq { show_comment_line: boolean show_userinfo: boolean allow_action: boolean - bookmark_id: number + bookmark_id?: number + bookmark_uid?: string } export interface getBookmarkByShareResp { @@ -51,13 +52,21 @@ export interface getBookmarkByShareResp { export class ShareService { constructor(@inject(BookmarkRepo) private bookmarkRepo: BookmarkRepo) {} - public async checkBookmarkShareExists(ctx: ContextManager, bmId: number): Promise { - const userId = ctx.getUserId() - - bmId = ctx.hashIds.decodeId(bmId) - if (bmId < 1) throw ErrorParam() + public async checkBookmarkShareExists(ctx: ContextManager, params: { bmId?: number; bmUId?: string }): Promise { + let bmId: number + let ownerUserId: number + if (params.bmUId) { + const ub = await this.bookmarkRepo.getUserBookmarkByUuid(params.bmUId) + if (!ub) throw ErrorParam() + bmId = ub.bookmark_id + ownerUserId = ub.user_id + } else { + bmId = ctx.hashIds.decodeId(params.bmId ?? 0) + if (bmId < 1) throw ErrorParam() + ownerUserId = ctx.getUserId() + } - const res = await this.bookmarkRepo.getBookmarkShareByBookmarkId(bmId, userId) + const res = await this.bookmarkRepo.getBookmarkShareByBookmarkId(bmId, ownerUserId) const isEnable = res && res.is_enable return { allow_action: isEnable ? res.allow_comment : false, @@ -78,8 +87,16 @@ export class ShareService { public async updateBookmarkShare(ctx: ContextManager, req: updateBookmarkShareReq): Promise { const userId = ctx.getUserId() - const bmId = ctx.hashIds.decodeId(req.bookmark_id) - if (bmId < 1 || !bmId) throw ErrorParam() + let bmId: number + const viaUid = !!req.bookmark_uid + if (req.bookmark_uid) { + const ub = await this.bookmarkRepo.getUserBookmarkByUId(req.bookmark_uid, userId) + if (!ub) throw BookmarkNotFoundError() + bmId = ub.bookmark_id + } else { + bmId = ctx.hashIds.decodeId(req.bookmark_id ?? 0) + if (bmId < 1 || !bmId) throw ErrorParam() + } const bookmark = await this.bookmarkRepo.getUserBookmark(bmId, userId) if (!bookmark) throw BookmarkNotFoundError() @@ -93,14 +110,18 @@ export class ShareService { res = await this.bookmarkRepo.updateBookmarkShare(bmId, userId, req.show_comment_line, req.show_userinfo, req.allow_action) } catch (err) { console.log(`update bookmark share failed: ${err}`) - return BookmarkNotFoundError() + // 必须抛出:之前 return 的错误对象会被丢弃,导致 res 为 undefined,下方取 res.allow_comment 抛 500 + throw BookmarkNotFoundError() } } const createShare = async () => { for (let i = 0; i < 3; i++) { - const timeCode = ctx.hashIds.generateTimeCode() - const hash = (await hashMD5(`${bmId}-${userId}-${Date.now()}`)).slice(0, 7) - const code = `${timeCode}${hash}` + let code = '' + if (!viaUid) { + const timeCode = ctx.hashIds.generateTimeCode() + const hash = (await hashMD5(`${bmId}-${userId}-${Date.now()}`)).slice(0, 7) + code = `${timeCode}${hash}` + } const shareRes = await this.bookmarkRepo.createBookmarkShare(code, userId, bmId, req.show_comment_line, req.show_userinfo, req.allow_action) if (!shareRes) throw ServerError() res = shareRes diff --git a/src/handler/http/aigcController.ts b/src/handler/http/aigcController.ts index 1333948..e7afc8e 100644 --- a/src/handler/http/aigcController.ts +++ b/src/handler/http/aigcController.ts @@ -12,6 +12,7 @@ import { convertToGeminiContent } from '../../utils/conversation' type SummaryRequest = { bm_id?: number + bookmark_uid?: string share_code?: string cb_id?: number collection_code: string @@ -21,6 +22,7 @@ type SummaryRequest = { type CompletionsRequest = { bm_id?: number + bookmark_uid?: string share_code?: string cb_id?: number collection_code?: string @@ -52,11 +54,12 @@ export class AigcController { ctx.set('country', request.cf?.country || '') ctx.set('continent', request.cf?.continent || '') - if (!req.force && req.bm_id) { + if (!req.force && (req.bm_id || req.bookmark_uid)) { const summary = await this.bookmarkService.getUserBookmarkSummary(ctx, { bmId: req.bm_id, shareCode: req.share_code, - cbId: req.cb_id + cbId: req.cb_id, + bmUId: req.bookmark_uid }) if (summary) { @@ -66,7 +69,14 @@ export class AigcController { } } - const { title, content, bmId } = await this.bookmarkService.getBookmarkTitleContent(ctx, req.bm_id, req.share_code, req.cb_id, 'no title', req.raw_content) + const { title, content, bmId } = await this.bookmarkService.getBookmarkTitleContent(ctx, { + bmId: req.bm_id, + shareCode: req.share_code, + cbId: req.cb_id, + title: 'no title', + content: req.raw_content, + bmUId: req.bookmark_uid + }) ctx.execution.waitUntil( aiSvc.bookmarkSummary(ctx, content, writable, async result => { bmId > 0 && (await this.bookmarkService.saveSummary(ctx, bmId, result.provider, result.response, result.model)) @@ -85,7 +95,14 @@ export class AigcController { const [user, { title, content, bmId }] = await Promise.all([ this.userService.getUserInfo(ctx), - this.bookmarkService.getBookmarkTitleContent(ctx, req.bm_id, req.share_code, req.cb_id, req.title, req.raw_content) + this.bookmarkService.getBookmarkTitleContent(ctx, { + bmId: req.bm_id, + shareCode: req.share_code, + cbId: req.cb_id, + title: req.title, + content: req.raw_content, + bmUId: req.bookmark_uid + }) ]) // 设置上下文 diff --git a/src/handler/http/shareController.ts b/src/handler/http/shareController.ts index ee8bdb1..2afcf01 100644 --- a/src/handler/http/shareController.ts +++ b/src/handler/http/shareController.ts @@ -66,10 +66,10 @@ export class ShareController { */ @Get('/exists') public async existsShare(ctx: ContextManager, request: Request) { - const req = await RequestUtils.query<{ bookmark_id: number }>(request) - if (!req) return Failed(ErrorParam()) + const req = await RequestUtils.query<{ bookmark_id?: number; bookmark_uid?: string }>(request) + if (!req || (!req.bookmark_id && !req.bookmark_uid)) return Failed(ErrorParam()) - const res = await this.shareService.checkBookmarkShareExists(ctx, req.bookmark_id) + const res = await this.shareService.checkBookmarkShareExists(ctx, { bmId: req.bookmark_id, bmUId: req.bookmark_uid }) return Successed(res) } @@ -78,10 +78,12 @@ export class ShareController { */ @Get('/mark_list') public async getMarkList(ctx: ContextManager, request: Request) { - const req = await RequestUtils.query<{ share_code: string }>(request) - if (!req) return Failed(ErrorParam()) + const req = await RequestUtils.query<{ share_code?: string; bookmark_uid?: string }>(request) + if (!req || (!req.share_code && !req.bookmark_uid)) return Failed(ErrorParam()) - const markList = await this.shareOrchestrator.getBookmarkShareMarkList(ctx, req.share_code) + const markList = req.bookmark_uid + ? await this.shareOrchestrator.getBookmarkShareMarkListByUid(ctx, req.bookmark_uid) + : await this.shareOrchestrator.getBookmarkShareMarkList(ctx, req.share_code!) return Successed(markList) } } diff --git a/src/infra/repository/dbBookmark.ts b/src/infra/repository/dbBookmark.ts index d6ab81d..70eb382 100644 --- a/src/infra/repository/dbBookmark.ts +++ b/src/infra/repository/dbBookmark.ts @@ -401,7 +401,8 @@ export class BookmarkRepo { }) } catch (err) { console.log(`create bookmark share failed: ${err}`) - return CreateBookmarkShareUniqueFail() + // 失败要抛出,让上层走错误响应;之前 return 错误对象会被 createShare 当成 share 行,导致返回畸形 200 + throw CreateBookmarkShareUniqueFail() } }