feat(scripts): GET /api/v1/scripts/count + DashboardRoute wiring
After #34 the workspace store only holds the root-level scripts plus whatever subfolders the user has expanded. DashboardRoute's "全部脚本"/"工作副本" counts derived from scripts.length therefore underreport the workspace total until the user navigates to /scripts and expands every folder. Fix: separate count endpoint + dedicated store field, mounted independently. Backend — backend/src/backend/scripts.py - New endpoint GET /api/v1/scripts/count. - Route declared BEFORE /api/v1/scripts/{script_id}/... so FastAPI's declaration-order matching does not interpret "count" as a script_id. - Returns { data: { total: number }, meta: {} }; SQL is a single COUNT(*) on scripts filtered by workspace_id + status='active'. Frontend — services/api.ts + context/AuthContext.tsx - countScripts(workspaceId) client; WorkspaceBoundApi gains the field; AuthContext binding forwards workspaceId. Frontend — state/scriptWorkspaceStore.ts - scriptCount: number | null, scriptCountLoading: boolean. - loadScriptCount() action: idempotent (no-op while in-flight), silent on failure (dashboard tolerates a stale count). - Initial state and reset() clear both fields. Frontend — features/platform/DashboardRoute.tsx - Subscribes to scriptCount; calls loadScriptCount() on mount. - Falls back to scripts.length until the count resolves so the dashboard never blanks. Tests — backend/tests/test_count_scripts.py (new) - 3 unit tests: scalar result handling, NULL coercion, route callable. Verified: pytest 55 passed (52 + 3 new); pnpm typecheck clean.
This commit is contained in:
@@ -1114,6 +1114,29 @@ async def list_scripts(
|
||||
}
|
||||
|
||||
|
||||
# 工作区内 active 脚本总数。DashboardRoute 等不需要列表但需要计数的场景使用,
|
||||
# 避免被 listScripts 的懒加载语义污染。该路由必须在 /scripts/{script_id} 之前声明
|
||||
# ——FastAPI 按声明顺序匹配,否则 `count` 会被当作 script_id 命中 get_script。
|
||||
@router.get("/api/v1/scripts/count")
|
||||
async def count_scripts(
|
||||
context: RequestContext = Depends(request_context),
|
||||
session: AsyncSession = Depends(database_session),
|
||||
) -> dict[str, Any]:
|
||||
total = await session.scalar(
|
||||
select(func.count())
|
||||
.select_from(Scripts)
|
||||
.where(
|
||||
Scripts.workspace_id == context.workspace.workspace_id,
|
||||
Scripts.status == "active",
|
||||
)
|
||||
)
|
||||
return {
|
||||
"request_id": context.request_id,
|
||||
"data": {"total": int(total or 0)},
|
||||
"meta": {},
|
||||
}
|
||||
|
||||
|
||||
# 读取脚本正文或 Notebook JSON;编辑器打开文件时调用此接口。
|
||||
@router.get("/api/v1/scripts/{script_id}/content")
|
||||
async def get_script_content(
|
||||
|
||||
Reference in New Issue
Block a user