Repository navigation
refactor(tag): 标签改为文章内联管理 + create_post 支持标签名 - #66
Conversation
- 标签 CRUD 从独立管理页改为文章创建/编辑时内联输入(tag names) - create_post/update_post 工具支持 tags 字段(传标签名,不存在自动创建) - 删除独立的 tags 管理页面和路由(前端+后端) - shared schemas 补充 createPostSchema 的 tags 字段 - post.service 支持标签名自动创建/关联 - tag.repository 增加 findOrCreateBatch 批量创建标签 - post-edit 页面重构标签选择为内联输入
📝 WalkthroughWalkthroughChanges标签名称化工作流
运行时依赖配置
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Editor
participant TagsAPI
participant PostService
participant TagRepository
participant PostRepository
Editor->>TagsAPI: 读取标签列表
Editor->>PostService: 提交 tags 名称数组
PostService->>TagRepository: 查找或创建标签
TagRepository-->>PostService: 返回 TagRow[]
PostService->>PostRepository: 使用 tagIds 保存文章关联
PostRepository-->>Editor: 返回文章结果
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (3)
package.json (1)
20-23: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win运行时依赖应声明在实际使用的 workspace 包中,而非 monorepo 根目录。
docx和image-size作为运行时依赖放在根package.json中,会使依赖边界模糊——不清楚哪个 workspace 包(很可能是apps/api)实际使用它们。建议将其移至实际 import 这些库的 workspace 包的package.json,根目录仅保留devDependencies和 workspace 配置。♻️ 建议的调整
将
docx和image-size从根package.json移至实际使用的 workspace 包(如apps/api/package.json)的dependencies中:- "dependencies": { - "docx": "^9.7.1", - "image-size": "^2.0.2" - }然后在
apps/api/package.json(或其他实际 import 的包)中添加:"dependencies": { "docx": "^9.7.1", "image-size": "^2.0.2" }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@package.json` around lines 20 - 23, 将根 package.json dependencies 中的 docx 和 image-size 移除,并在实际 import 它们的 workspace 包 package.json 中声明相同版本的运行时依赖;确认依赖归属与使用代码所在 workspace 一致,根目录仅保留 workspace 配置及开发依赖。apps/web/src/pages/admin/post-edit.page.tsx (1)
228-275: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value标签删除按钮缺少
aria-label。屏幕阅读器会朗读 "标签名 ×",语义不够明确。建议添加
aria-label以提升可访问性。♿ 建议添加 aria-label
<button key={name} type="button" + aria-label={`移除标签 ${name}`} onClick={() => {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/src/pages/admin/post-edit.page.tsx` around lines 228 - 275, 在标签删除按钮中补充明确的 aria-label,定位 currentTags.map 渲染的 button,并将当前标签名包含在删除语义中;保持现有点击删除逻辑不变。apps/api/src/ai/tools.ts (1)
180-182: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value标签解析逻辑与
post.service.ts重复。
normalizeTagNames→findOrCreateMany→map(t => t.id)的三步逻辑在post.service.ts(lines 177-179) 和此处完全重复。若后续标签解析规则变更,需同步修改两处。考虑提取为共享辅助函数(如resolveTagIds(names): Promise<number[] | undefined>)。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/api/src/ai/tools.ts` around lines 180 - 182, Extract the duplicated tag-resolution flow from the current tool logic and post.service.ts into a shared helper such as resolveTagIds(names), preserving the undefined result for absent tags and the existing normalized findOrCreateMany-to-ID behavior. Update both callers to use the shared helper so future tag-resolution changes require only one implementation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/api/src/ai/tools.ts`:
- Around line 180-183: 将 tags 处理与 updateWithTags 统一放入同一个 db.transaction
回调中:在事务回调内调用 tagRepository.findOrCreateMany 时传入 tx,并将其结果映射为 tagIds,再调用
postRepository.updateWithTags 使用同一 tx;无 tags 时保持 undefined。
In `@apps/api/src/modules/tag/tag.repository.ts`:
- Around line 79-93: Update the tag creation branch in the names loop to catch
unique-constraint failures from create, then query by lowercased name and reuse
the existing tag. Preserve normal successful creation and only suppress errors
when the conflicting tag is found; rethrow other failures so the surrounding
transaction behavior remains intact.
In `@package.json`:
- Around line 21-22: Replace the vulnerable image-size dependency in
package.json with a maintained alternative that provides the required
image-dimension functionality, and update the corresponding import and usage
symbols throughout the codebase while preserving existing behavior for untrusted
images.
---
Nitpick comments:
In `@apps/api/src/ai/tools.ts`:
- Around line 180-182: Extract the duplicated tag-resolution flow from the
current tool logic and post.service.ts into a shared helper such as
resolveTagIds(names), preserving the undefined result for absent tags and the
existing normalized findOrCreateMany-to-ID behavior. Update both callers to use
the shared helper so future tag-resolution changes require only one
implementation.
In `@apps/web/src/pages/admin/post-edit.page.tsx`:
- Around line 228-275: 在标签删除按钮中补充明确的 aria-label,定位 currentTags.map 渲染的
button,并将当前标签名包含在删除语义中;保持现有点击删除逻辑不变。
In `@package.json`:
- Around line 20-23: 将根 package.json dependencies 中的 docx 和 image-size 移除,并在实际
import 它们的 workspace 包 package.json 中声明相同版本的运行时依赖;确认依赖归属与使用代码所在 workspace
一致,根目录仅保留 workspace 配置及开发依赖。
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 639e623e-4f4f-4ac8-85cb-aeddf84eda2d
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (15)
apps/api/src/ai/runtime.tsapps/api/src/ai/tools.tsapps/api/src/modules/post/post.service.tsapps/api/src/modules/tag/tag.repository.tsapps/api/src/modules/tag/tag.schema.tsapps/api/src/modules/tag/tag.service.tsapps/api/src/routes/tags.routes.tsapps/web/src/api/tags.api.tsapps/web/src/app/router.tsxapps/web/src/components/layout/admin-sidebar.tsxapps/web/src/features/tag/use-tags.tsapps/web/src/pages/admin/post-edit.page.tsxapps/web/src/pages/admin/tags.page.tsxpackage.jsonpackages/shared/src/schemas.ts
💤 Files with no reviewable changes (2)
- apps/web/src/pages/admin/tags.page.tsx
- apps/web/src/app/router.tsx
| const tagIds = tags !== undefined | ||
| ? (await tagRepository.findOrCreateMany(normalizeTagNames(tags))).map((t) => t.id) | ||
| : undefined | ||
| const updated = await db.transaction(() => postRepository.updateWithTags(id, data, tagIds)) |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 检查 tagRepository.findOrCreateMany 是否使用内部事务
ast-grep outline apps/api/src/modules/tag/tag.repository.ts --match findOrCreateMany --view expandedRepository: BqLee-AI/AgentBlog
Length of output: 213
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- locate tag repository files ---'
git ls-files | rg 'apps/api/src/modules/tag/.*repository.*|apps/api/src/ai/tools.ts|post.service.ts|tag' || true
echo
echo '--- search for findOrCreateMany and tagRepository references ---'
rg -n "findOrCreateMany|tagRepository|updateWithTags|update_post|normalizeTagNames" apps/api/src -g '!**/*.map' || true
echo
echo '--- outline candidate tag repository file(s) if present ---'
for f in $(git ls-files | rg 'apps/api/src/modules/tag/.*repository.*'); do
echo "### $f"
ast-grep outline "$f" --view expanded || true
doneRepository: BqLee-AI/AgentBlog
Length of output: 3690
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- apps/api/src/modules/tag/tag.repository.ts ---'
cat -n apps/api/src/modules/tag/tag.repository.ts | sed -n '1,220p'
echo
echo '--- apps/api/src/modules/post/post.service.ts ---'
cat -n apps/api/src/modules/post/post.service.ts | sed -n '150,220p'
echo
echo '--- apps/api/src/modules/post/post.repository.ts (updateWithTags) ---'
cat -n apps/api/src/modules/post/post.repository.ts | sed -n '220,290p'Repository: BqLee-AI/AgentBlog
Length of output: 7871
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- db client ---'
cat -n apps/api/src/db/client.ts | sed -n '1,220p'
echo
echo '--- search for tx-aware repository patterns ---'
rg -n "\btx\b|db\.transaction\(" apps/api/src/modules apps/api/src/db apps/api/src/ai -g '!**/*.map' || true
echo
echo '--- any repository methods that accept tx parameter ---'
rg -n "async .*tx|tx:\s*.*Transaction|Transaction<|Database|DB" apps/api/src/modules -g '!**/*.map' || trueRepository: BqLee-AI/AgentBlog
Length of output: 3040
🌐 Web query:
Drizzle ORM bun-sqlite db.transaction callback global db vs tx object transaction methods repository uses db inside transaction
💡 Result:
In Drizzle ORM, when using the transaction API, you must use the transaction object (typically named tx) provided within the callback [1]. Using the global db object inside a transaction callback will not execute those operations within the transaction scope; instead, they will execute outside the transaction, potentially leading to race conditions or data inconsistencies [1][2]. The tx object mirrors the API of your main db instance, supporting the same query methods (e.g., select, insert, update, delete) and relational queries [1]. For repository patterns, it is a recommended best practice to design your repository methods to accept an optional transaction object (e.g., tx: MyDb | MyTransaction) [2]. This allows your business logic—typically housed in a service layer—to control the transaction boundary [2]. By passing the tx object into these repository methods when inside a transaction callback, you ensure that the repository's operations are correctly included within that transaction [1][2]. Example of the recommended pattern: // Repository layer class UserRepository { constructor(private db: MyDatabaseType) {} async createUser(data: UserInsert, tx?: MyTransactionType) { const client = tx || this.db; return await client.insert(users).values(data).returning; } } // Service layer async function performBusinessLogic(data: UserInsert) { return await db.transaction(async (tx) => { // Pass 'tx' to ensure operation is part of the transaction await userRepo.createUser(data, tx); await otherRepo.doSomethingElse(data, tx); }); } If you do not pass the tx object into your repository methods, and those methods instead rely exclusively on the global db instance, they will run outside the transaction scope [1][2].
Citations:
- 1: https://drizzle-team-drizzle-orm.mintlify.app/core/transactions
- 2: https://www.paulserban.eu/blog/post/drizzle-orm-best-practices-principles-patterns-and-real-world-case-studies/
🌐 Web query:
Drizzle ORM transaction callback use tx object not global db docs bun sqlite
💡 Result:
When using transactions in Drizzle ORM with Bun SQLite, you should use the transaction object (commonly named tx) provided by the transaction callback to ensure your operations are part of the same transaction [1][2][3]. The transaction callback receives a tx object that mirrors the API of your global db instance [1][2]. By using tx instead of the global db instance inside your transaction block, you guarantee that all queries executed within that function share the same connection and transaction context [2][3]. Example usage with Bun SQLite: import { drizzle } from 'drizzle-orm/bun-sqlite'; import { Database } from 'bun:sqlite'; import { users } from './schema'; const sqlite = new Database('sqlite.db'); const db = drizzle(sqlite); await db.transaction(async (tx) => { // Use 'tx' for all operations within the transaction await tx.insert(users).values({ name: 'Alice' }).run; await tx.update(users).set({ active: true }).where(...).run; }); Key points: - Atomicity: The tx object ensures that if any operation inside the callback fails or throws an error, the entire transaction is automatically rolled back [2]. - Nested Transactions: Drizzle supports nested transactions (savepoints) by calling db.transaction or tx.transaction inside the callback [3]. - Global vs. Transactional Object: While some users may attempt to use the global db instance inside a transaction, the tx object is the intended way to maintain the correct transactional scope [1][2][3]. Using the global db might bypass the transaction context or lead to unexpected behavior depending on the driver and configuration [4].
Citations:
- 1: https://drizzle-team-drizzle-orm.mintlify.app/core/transactions
- 2: https://drizzle-team-drizzle-orm.mintlify.app/api/core/transactions
- 3: https://github.com/drizzle-team/drizzle-orm/blob/HEAD/drizzle-orm/src/sqlite-core/README.md
- 4: Support prepared statements in transactions drizzle-team/drizzle-orm#613
findOrCreateMany 也要纳入同一事务 tagRepository.findOrCreateMany 现在直接用全局 db,所以这里只把 updateWithTags 包进 db.transaction 还不够;新建标签仍可能在失败后残留。把 tx 传进 findOrCreateMany,并用同一个事务执行。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/api/src/ai/tools.ts` around lines 180 - 183, 将 tags 处理与 updateWithTags
统一放入同一个 db.transaction 回调中:在事务回调内调用 tagRepository.findOrCreateMany 时传入
tx,并将其结果映射为 tagIds,再调用 postRepository.updateWithTags 使用同一 tx;无 tags 时保持
undefined。
| for (const name of names) { | ||
| const key = name.toLowerCase() | ||
| if (seen.has(key)) continue | ||
| seen.add(key) | ||
| const hit = existingByLower.get(key) | ||
| if (hit) { | ||
| result.push(hit) | ||
| } else { | ||
| // 不存在则创建(UNIQUE(name) 兜底并发,catch 后回查) | ||
| const created = await this.create({ name, slug: generateSlug(name) }) | ||
| if (created) { | ||
| existingByLower.set(key, created) | ||
| result.push(created) | ||
| } | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 查看 create 实现与 better-sqlite3 驱动配置,确认冲突时的抛错行为
fd -t f 'client.ts' apps/api/src/db --exec cat -n {}
rg -nP 'better-sqlite3|drizzle' apps/api/src/db -C2Repository: BqLee-AI/AgentBlog
Length of output: 4169
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- tag.repository.ts ---'
cat -n apps/api/src/modules/tag/tag.repository.ts | sed -n '1,220p'
echo '--- schema.ts (tags) ---'
rg -n 'export const tags|unique|slug|name' apps/api/src/db/schema.ts -A6 -B4
echo '--- repository create usages ---'
rg -n 'async create\\(|create\\(' apps/api/src/modules/tag -A6 -B4Repository: BqLee-AI/AgentBlog
Length of output: 6793
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- tag.repository.ts around findOrCreateMany/create ---'
cat -n apps/api/src/modules/tag/tag.repository.ts | sed -n '1,180p'
echo '--- tag schema ---'
cat -n apps/api/src/db/schema.ts | sed -n '1,220p'Repository: BqLee-AI/AgentBlog
Length of output: 9524
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- findOrCreateMany call sites ---'
rg -n 'findOrCreateMany\(' apps/api/src -A6 -B6
echo '--- tagRepository call sites ---'
rg -n 'tagRepository\.' apps/api/src -A4 -B4
echo '--- transaction usages near post service ---'
rg -n 'db\.transaction|transaction\(' apps/api/src/modules/post -A8 -B8Repository: BqLee-AI/AgentBlog
Length of output: 14917
并发兜底要补回查
apps/api/src/modules/tag/tag.repository.ts:87-92 这里还没有实现注释里说的“catch 后回查”。tag.name/tag.slug 都有 UNIQUE 约束,并发创建同名标签时会直接抛错,回滚外层 post 事务。这里应在唯一冲突后按 lower(name) 回查并复用已存在的标签。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/api/src/modules/tag/tag.repository.ts` around lines 79 - 93, Update the
tag creation branch in the names loop to catch unique-constraint failures from
create, then query by lowercased name and reuse the existing tag. Preserve
normal successful creation and only suppress errors when the conflicting tag is
found; rethrow other failures so the surrounding transaction behavior remains
intact.
| "docx": "^9.7.1", | ||
| "image-size": "^2.0.2" |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
What is the latest stable version of the npm packages "docx" and "image-size"?
💡 Result:
As of July 12, 2026, the latest stable versions of the requested npm packages are as follows: - docx: 9.7.1 (published May 27, 2026) [1][2][3]. - image-size: 2.0.2 [4]. Please note that the image-size repository has been marked as archived [5][6].
Citations:
- 1: https://registry.npmjs.org/docx
- 2: https://www.npmjs.com/package/docx
- 3: https://npmx.dev/package/docx/v/9.7.1
- 4: deps(deps): bump image-size from 1.2.1 to 2.0.2 TurboDocx/html-to-docx#79
- 5: chore(deps): move from image-size to probe-image-size TurboDocx/html-to-docx#207
- 6: TurboDocx/html-to-docx@58d8c2d
🌐 Web query:
docx 9.7.1 security advisory npm vulnerability image-size 2.0.2 security advisory npm vulnerability
💡 Result:
As of July 12, 2026, the security status of the requested packages is as follows: image-size The image-size package versions up to and including 2.0.2 are associated with multiple Denial of Service (DoS) vulnerabilities [1][2]. Specifically, versions through 2.0.2 contain vulnerabilities that allow remote attackers to cause an infinite loop in the JXL, HEIF, and ICNS image parsers, which can permanently block the Node.js event loop and hang the application [1][2]. These vulnerabilities are tracked under identifiers such as CVE-2025-71319, CVE-2025-71329, and CVE-2025-71330 [1][3][2][4]. Users are advised to verify if later versions have been released that address these issues, as the referenced advisories identify 2.0.2 as an affected version [1][2]. docx The docx package version 9.7.1 does not have a formal CVE-based security advisory linked to it in major vulnerability databases [5]. However, automated security analysis tools have flagged this version for high-risk behaviors [6][7]. Security research platforms, such as Shoulder, have noted concerns regarding install-time payloads, potential dynamic code execution, and dependency-related flags in recent versions of the docx package [6][7]. Users are encouraged to exercise caution and perform manual reviews or use dependency-analysis tools when integrating this package, as these flags indicate potentially anomalous or suspicious supply-chain characteristics rather than a confirmed exploit [6][8].
Citations:
- 1: https://nvd.nist.gov/vuln/detail/cve-2025-71329
- 2: https://nvd.nist.gov/vuln/detail/cve-2025-71330
- 3: GHSA-m5qc-5hw7-8vg7
- 4: https://security.snyk.io/vuln/SNYK-JS-IMAGESIZE-9634164
- 5: https://security.snyk.io/package/npm/docx
- 6: https://shoulder.dev/npm/docx/9.7.1
- 7: https://shoulder.dev/npm/docx/9.7.0
- 8: https://socket.dev/npm/package/docx
image-size 仍有已知 DoS 风险,建议替换
docx@^9.7.1 没有明显的公开 CVE 问题;image-size@^2.0.2 仍受已知 DoS 漏洞影响,若会处理不受信任图片,建议改用维护中的替代方案或加严格隔离。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@package.json` around lines 21 - 22, Replace the vulnerable image-size
dependency in package.json with a maintained alternative that provides the
required image-dimension functionality, and update the corresponding import and
usage symbols throughout the codebase while preserving existing behavior for
untrusted images.
关联
docs/design/06-文章模块.md§九(标签管理)、docs/design/02-目录结构与分层规范.md背景
原设计中标签(Tag)有独立的管理页面和 CRUD 接口(admin+ 创建/删除),文章创建时通过 tagIds 关联。实际使用中发现:标签的创建和使用场景几乎完全在文章编辑时发生,独立管理页是过度设计,增加了前端复杂度和用户操作步骤。
本 PR 将标签改为文章创建/编辑时内联输入(传标签名),不存在自动创建,简化了全链路。
改动
后端 (apps/api)
modules/tag/tag.repository.ts:新增findOrCreateBatch(names)批量按名查找或创建标签modules/tag/tag.service.ts:精简为 list + findOrCreateBatch,移除独立的 create/deletemodules/post/post.service.ts:create/update 支持 tags(标签名数组),内部调 findOrCreateBatch 自动创建并关联ai/tools.ts:create_post/update_post 工具的 tagIds 改为 tags(标签名列表),MCP Agent 可直接传标签名ai/runtime.ts:在线 Agent 工具同步更新routes/tags.routes.ts:保留 GET /(公开列表),移除 POST/DELETE(admin+ CRUD)shared (packages/shared)
schemas.ts:createPostSchema 的 tagIds 改为 tags(z.array(z.string()))前端 (apps/web)
pages/admin/tags.page.tsx(独立标签管理页)pages/admin/post-edit.page.tsx:标签选择改为内联输入(输入标签名,逗号分隔)app/router.tsx:移除标签管理路由components/layout/admin-sidebar.tsx:移除标签管理菜单项api/tags.api.ts+features/tag/use-tags.ts:精简为仅 list 查询依赖
package.json/bun.lock:更新相关依赖验证
风险与未验证项
Review Notes
findOrCreateBatch在并发下可能有 UNIQUE 约束冲突(两个请求同时创建同名标签),当前用 catch + retry 模式处理。Summary by CodeRabbit
新功能
功能调整