fix(api): KB 架構審查修復 I3-I5
Some checks failed
E2E Health Check / e2e-health (push) Successful in 17s
CD Pipeline / build-and-deploy (push) Has been cancelled

- I3: Service 層加 IKnowledgeRepository Protocol 型別標注
- I4: search 方法加入 tags JSONB 搜尋 (cast→String→ilike)
- I5: get_categories 獨立方法,不再繞道 list_entries(limit=0)

首席架構師審查 87/100 → 全部 Important issues 已修復

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
OG T
2026-04-02 09:05:54 +08:00
parent 48a0bc66f7
commit d2bad44173
3 changed files with 20 additions and 11 deletions

View File

@@ -71,8 +71,8 @@ async def search_entries(
async def get_categories() -> list[dict]:
"""取得分類樹 (含各類數量)"""
service = get_knowledge_service()
result = await service.list_entries(limit=0)
return [cat.model_dump() for cat in result.categories]
cats = await service.get_categories()
return [cat.model_dump() for cat in cats]
@router.get("/{entry_id}", response_model=KnowledgeEntry)

View File

@@ -12,7 +12,7 @@ Knowledge Base Phase 1: CRUD + 搜尋
"""
import structlog
from sqlalchemy import func, or_, select, update
from sqlalchemy import String, func, or_, select, update
from sqlalchemy.ext.asyncio import AsyncSession
from src.db.models import KnowledgeEntryRecord
@@ -154,7 +154,7 @@ class KnowledgeDBRepository:
return [(row.category, row.cnt) for row in result.all()]
async def search(self, query: str, limit: int = 20) -> list[KnowledgeEntry]:
"""關鍵字搜尋 (title + content)"""
"""關鍵字搜尋 (title + content + tags)"""
like_q = f"%{query}%"
result = await self.db.execute(
select(KnowledgeEntryRecord)
@@ -163,6 +163,7 @@ class KnowledgeDBRepository:
or_(
KnowledgeEntryRecord.title.ilike(like_q),
KnowledgeEntryRecord.content.ilike(like_q),
KnowledgeEntryRecord.tags.cast(String).ilike(like_q),
),
)
.order_by(KnowledgeEntryRecord.view_count.desc())

View File

@@ -24,6 +24,7 @@ from src.models.knowledge import (
KnowledgeEntryUpdate,
KnowledgeListResponse,
)
from src.repositories.interfaces import IKnowledgeRepository
from src.repositories.knowledge_repository import KnowledgeDBRepository
logger = structlog.get_logger(__name__)
@@ -49,7 +50,7 @@ class KnowledgeService:
async def create_entry(self, data: KnowledgeEntryCreate) -> KnowledgeEntry:
"""建立知識條目"""
async with get_db_context() as db:
repo = KnowledgeDBRepository(db)
repo: IKnowledgeRepository = KnowledgeDBRepository(db)
entry = await repo.create(data)
logger.info(
"knowledge_entry_created",
@@ -62,7 +63,7 @@ class KnowledgeService:
async def get_entry(self, entry_id: str) -> KnowledgeEntry | None:
"""取得知識條目 (view_count +1)"""
async with get_db_context() as db:
repo = KnowledgeDBRepository(db)
repo: IKnowledgeRepository = KnowledgeDBRepository(db)
entry = await repo.get_by_id(entry_id)
if entry:
await repo.increment_view_count(entry_id)
@@ -75,7 +76,7 @@ class KnowledgeService:
"""更新知識條目"""
update_data = data.model_dump(exclude_none=True)
async with get_db_context() as db:
repo = KnowledgeDBRepository(db)
repo: IKnowledgeRepository = KnowledgeDBRepository(db)
if not update_data:
return await repo.get_by_id(entry_id)
return await repo.update(entry_id, update_data)
@@ -83,7 +84,7 @@ class KnowledgeService:
async def approve_entry(self, entry_id: str) -> KnowledgeEntry | None:
"""審核通過 (draft/review → approved)"""
async with get_db_context() as db:
repo = KnowledgeDBRepository(db)
repo: IKnowledgeRepository = KnowledgeDBRepository(db)
entry = await repo.get_by_id(entry_id)
if not entry:
return None
@@ -94,7 +95,7 @@ class KnowledgeService:
async def archive_entry(self, entry_id: str) -> bool:
"""封存 (軟刪除)"""
async with get_db_context() as db:
repo = KnowledgeDBRepository(db)
repo: IKnowledgeRepository = KnowledgeDBRepository(db)
return await repo.delete(entry_id)
async def list_entries(
@@ -109,7 +110,7 @@ class KnowledgeService:
) -> KnowledgeListResponse:
"""列出知識條目 + 分類統計"""
async with get_db_context() as db:
repo = KnowledgeDBRepository(db)
repo: IKnowledgeRepository = KnowledgeDBRepository(db)
items, total = await repo.list_entries(
category=category,
entry_type=entry_type,
@@ -127,8 +128,15 @@ class KnowledgeService:
items=items, total=total, categories=categories
)
async def get_categories(self) -> list[CategoryCount]:
"""取得分類統計(直接呼叫 repo不走 list_entries"""
async with get_db_context() as db:
repo: IKnowledgeRepository = KnowledgeDBRepository(db)
categories_raw = await repo.get_categories()
return [CategoryCount(category=cat, count=cnt) for cat, cnt in categories_raw]
async def search(self, query: str, limit: int = 20) -> list[KnowledgeEntry]:
"""關鍵字搜尋"""
async with get_db_context() as db:
repo = KnowledgeDBRepository(db)
repo: IKnowledgeRepository = KnowledgeDBRepository(db)
return await repo.search(query, limit)