fix(scripts): sequence-token race + comment/import cleanup (Codex ffec234 follow-up)
Codex review of ffec234 flagged:
1. **MEDIUM — loadScriptCount in-flight race on workspace switch.**
Previous implementation used `if (get().scriptCountLoading) return`
to dedupe. That meant switching workspaces WHILE a fetch was in
flight dropped the new fetch entirely; the stale response from the
previous workspace then overwrote state, leaving the dashboard
showing workspace A's total while the user is on workspace B.
Fix: drop the dedupe-via-flag, use a module-level
`_scriptCountSeq` counter. Every call increments, captures the
seq at start, and the response/finally block only mutates state
when `_scriptCountSeq === seq` — stale responses are silently
dropped. Rapid workspace switches each get their own fetch; only
the latest response wins.
2. **LOW — scriptCount scope comment was misleading.**
"范围与 list_scripts(parent_path=\"\") 对齐" is wrong: the
count includes all descendant depths, not just root-level.
Behaviour is correct for the dashboard's "全部脚本" intent but
the comment would mislead the next maintainer. Rewritten to
explicitly call out that the count is the UNION across all
parent_path depths, with the rationale for each design choice.
3. **LOW — local `from backend.scripts import _escape_like_pattern`
inside `resources.py` keyword-search block.**
The "avoid cycle" justification was false: scripts.py and
resources.py don't import each other at module level. Moved to
the top-of-file import block.
Skipped:
- get_workspace_tree `like_prefix` not run through the escape helper
(user_id is a 26-char Crockford ULID so no `_`/`%` can appear, but
the invariant is not documented at the call site). Pre-existing
pattern; out of scope for this round.
Verified: pytest 65 passed; pnpm typecheck clean.
This commit is contained in:
@@ -28,6 +28,7 @@ from backend.dependencies import (
|
||||
database_session,
|
||||
request_context,
|
||||
)
|
||||
from backend.scripts import _escape_like_pattern
|
||||
from backend.schemas import (
|
||||
CompleteResourceUploadRequest,
|
||||
CreateResourceUploadRequest,
|
||||
@@ -402,7 +403,6 @@ async def list_resources(
|
||||
if keyword:
|
||||
# Escape LIKE metacharacters so a search like "100%" or "my_file"
|
||||
# doesn't act as a wildcard. The outer "%...%" wildcards stay raw.
|
||||
from backend.scripts import _escape_like_pattern # local import: avoid cycle
|
||||
escaped = _escape_like_pattern(keyword.strip())
|
||||
statement = statement.where(
|
||||
DataResources.resource_name.like(f"%{escaped}%", escape="\\")
|
||||
|
||||
@@ -1138,11 +1138,18 @@ async def list_scripts(
|
||||
# 避免被 listScripts 的懒加载语义污染。该路由必须在 /scripts/{script_id} 之前声明
|
||||
# ——FastAPI 按声明顺序匹配,否则 `count` 会被当作 script_id 命中 get_script。
|
||||
#
|
||||
# 范围与 list_scripts(parent_path="") 对齐:INNER JOIN 到 StorageObjects 以排除
|
||||
# 孤儿脚本(其 current_object_id 没有 joinable row),按用户子树
|
||||
# (workspace/{user_id}/) 过滤。否则:
|
||||
# - 含孤儿 → 数字虚高
|
||||
# - workspace 范围 → 多成员工作区里 dashboard 会显示用户看不见的脚本
|
||||
# Scope: equals the UNION of list_scripts across every parent_path
|
||||
# within the user's subtree, NOT just the root-level call. We
|
||||
# intentionally include deeper descendants because the dashboard's
|
||||
# "全部脚本" / "工作副本" counts reflect the whole workspace, not
|
||||
# only the top level.
|
||||
#
|
||||
# Implementation choices and why:
|
||||
# - INNER JOIN to StorageObjects so orphans (current_object_id has no
|
||||
# joinable row) are excluded.
|
||||
# - User subtree filter so multi-member workspaces don't show counts
|
||||
# the requester can't see.
|
||||
# - No NOT-LIKE filter because the count wants descendants too.
|
||||
@router.get("/api/v1/scripts/count")
|
||||
async def count_scripts(
|
||||
context: RequestContext = Depends(request_context),
|
||||
|
||||
Reference in New Issue
Block a user