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:
tao.chen
2026-08-21 10:57:01 +08:00
parent 9233d99237
commit 79650c61ed
6 changed files with 153 additions and 1 deletions
+17
View File
@@ -296,6 +296,22 @@ export async function listScripts(
return apiRequest<ScriptItem[]>(`/api/v1/scripts${query}`, {}, workspaceId);
}
export async function countScripts(
workspaceId: string,
): Promise<number> {
// Backend route /api/v1/scripts/count must be declared BEFORE the
// /scripts/{script_id} route on the server side. Returns
// { data: { total: number } } — the dashboard's single source of
// truth for "total active scripts in workspace", independent of the
// lazy-loaded scripts[] in the workspace store.
const envelope = await apiRequest<{ total: number }>(
"/api/v1/scripts/count",
{},
workspaceId,
);
return envelope.total;
}
function initialContent(scriptType: ScriptType): string {
if (scriptType === "python") {
return [
@@ -1466,6 +1482,7 @@ export type WorkspaceBoundApi = {
listScripts: (
parentPath?: Parameters<typeof listScripts>[1],
) => Promise<ScriptItem[]>;
countScripts: () => Promise<number>;
listResources: (
opts?: Parameters<typeof listResources>[1],
) => Promise<ResourceItem[]>;