ea2f92921b14115158e113a2f5fcbf7b2d292d8f
5
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
256f369661 |
fix(scripts): workspace-wide parent_path listing + private visibility on reads
1. 同 workspace 互相可见(排除 private):
- list_scripts / count_scripts 已 workspace-wide + visibility 过滤,但
单条读取(get/content/latest-version/versions)不校验 visibility,非
owner 猜 id 即可读他人 private 脚本。新增 script_can_view(与 data
resources 的 can_view 对称)并在 get_script_row / latest_version 强制,
private 对非 owner 返回 404。
2. parent_path 为空默认拉根路径文件:
- 物理存储为 workspace/{user_id}/...,根 prefix 原来是 workspace/ +
NOT LIKE workspace/%/%,所有文件都在两层被整体排除,list_scripts("")
恒空。改为 workspace/%/,配合 LIKE workspace/%/% AND NOT LIKE
workspace/%/%/% 返回各 owner 根级文件。
3. 非空 parent_path 跨 owner 查询:
- 原来 workspace/foo/ 永远匹配不到 workspace/{uid}/foo/...,子目录
懒加载返回空,其他用户目录点击无内容。改为 workspace/%/foo/(owner
段通配,与 list_resources 一致),_ / % 仍按字面转义。
测试:更新前缀契约断言,新增 SQLite 行为测试(跨 owner 根/子目录、转义)
与 script_can_view / get_script 权限测试,133 passed。
|
||
|
|
ecf3b2e9c5 |
refactor(backend): split into api/ schemas/ services/ clients/ layers
4-phase restructuring of the previously flat backend/ package. Each
phase lands as a single squash commit so future bisects stay readable
per phase if needed.
## Phase 1 — move + shim (location-only, zero behavior change)
* git mv 14 files into api/ schemas/ services/ clients/ subpackages
(history preserved via RM/R renames)
* New files: api/{admin,auth,dependencies,jupyter,platform,resources,
scripts,storage}.py + api/schedules/{schedules,runs}.py
* New files: schemas/{auth,common,jupyter,platform,resources,
schedules,scripts}.py
* New files: clients/{rclone,runtime,scheduler}.py
* Old paths kept as 1-line `from backend.<new> import *` shims so
tests/main.py/importers kept working untouched
* schemas/__init__.py now re-exports from backend.schemas.<domain>
## Phase 2 — APIRouter prefix consolidation
* Every APIRouter() now carries its prefix (e.g. prefix="/api/v1/auth")
and decorators are stripped of the redundant path prefix
* URL paths exposed to the frontend are byte-identical to before
* Affected: api/{auth,jupyter,admin,platform,resources,scripts,
storage}.py + api/schedules/{schedules,runs}.py
## Phase 3 — first service-layer extraction
* backend.services.schedules.validate_dag moved out of api/
(pure DAG validator, no Request/BackgroundTasks/DB)
* api/schedules/schedules.py now re-exports the symbol so existing
4 callsites keep working unchanged
* Added backend/tests/test_validate_dag.py: 8 unit tests covering
DAG_EMPTY, linear chain, diamond, cycle, self-edge, duplicate
edge, orphan edge, multi-root ordering
## Phase 4 — delete shims + unify test imports
* Removed 14 flat shim files + schemas/__init__.py
* Migrated 5 test files (32 import sites) to new paths:
backend.scripts.* → backend.api.scripts.*
backend.resources.* → backend.api.resources.*
backend.jupyter.* → backend.api.jupyter.*
backend.runtime_client.* → backend.clients.runtime.*
backend.schemas.UpdateScriptRequest → backend.schemas.scripts.*
* audit.py kept at backend.audit (main.py references it; not a
shim, real code)
## Final structure
backend/src/backend/
main.py, audit.py, __init__.py
api/ (10 files: routes + 2 subpackage)
schemas/ (7 files: Pydantic contracts)
services/ (storage + schedules)
clients/ (rclone, runtime, scheduler)
## Verification
* uv run python -m compileall backend/src backend/tests — clean
* uv run --package backend pytest backend/tests -q — 122 passed
(114 → 114 → 122 → 122 across phases)
* grep -r 'from backend\.\(scripts\|resources\|...\)' backend/ — 0 hits
* git blame --follow still traces file origins through the renames
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
|
||
|
|
57f21f2017 |
feat(scripts/resources): align list_scripts visibility with list_resources
|
||
|
|
23ed028f25 |
fix(scripts): actually escape LIKE pattern literals + scope count endpoint
Codex review of #36 + #37 surfaced that my prior `escape="\\"` only declared the escape character — the pattern literals themselves still contained unescaped `_` and `%`, so `parent_path="foo_bar"` continued to match `fooXbar/...`, `foo2bar/...`, etc. My earlier ESCAPE-clause assertions were tautological: they verified the SQL rendered the ESCAPE keyword without ever checking that the pattern was actually escaped. The tests passed; the leak persisted. Fix in three layers: 1. Real escape: `backend/src/backend/scripts.py` gains `_escape_like_pattern(value)` that escapes `\` → `\\`, `%` → `\%`, `_` → `\_` (in that order — the escape char MUST be escaped first). `_build_list_scripts_descendant_prefix` now returns the escaped prefix. `list_workspace_directories` and `delete_workspace_directory` also escape their server-built prefixes. `count_scripts` escapes the user subtree prefix. 2. Same bug elsewhere: `backend/src/backend/resources.py:404` had the identical `DataResources.resource_name.like(f"%{keyword}%")` pattern; a search for "100%" would match everything. Now escaped too. 3. Count endpoint scope: `count_scripts` was workspace-wide and skipped the StorageObjects JOIN. Now INNER JOINs StorageObjects (drops orphans whose current_object_id is dangling) and filters by `workspace/{user_id}/` subtree so the result matches what `list_scripts(parent_path="")` would return. Multi-member workspaces no longer over-report, and orphan rows no longer inflate the count. Frontend: `DashboardRoute` is not keyed by workspace/user (only ScriptsPage is), so without a workspace_id dep the previous workspace's count persisted across navigation. useEffect now depends on `currentWorkspace?.workspace_id`; `loadScriptCount` clears the count to null at the start of the fetch so the dashboard doesn't flash a stale number. Tests — backend/tests/test_list_scripts_parent_path.py - Rewritten with three layers of coverage: * Pure helper tests for `_escape_like_pattern` (7 cases including backslash-escape-first ordering). * SQL-contract tests asserting the COMPILED PATTERN contains the escaped form (lowercased to neutralise SQLAlchemy keyword casing). * BEHAVIORAL tests on SQLite in-memory with the same LIKE semantics — proves the fix actually prevents the wildcard leak. Includes a negative test (without escape, siblings DO match) so the fixture is verified to exercise the bug. Tests — backend/tests/test_count_scripts.py - Updated to assert the JOIN + user-scope filter. New test verifies two different users in the same workspace get different subtrees. Verified: - pytest backend/tests: 65 passed (43 baseline + 12 list_scripts + 4 count + 6 helper/SQLite behavioral) - pnpm typecheck: clean - Raw SQL on MySQL (live DB) confirms `LIKE 'workspace/.../foo\_bar/%%' ESCAPE '\\'`. |
||
|
|
c6ac886133 |
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. |