Skip to content

feat(storage): R2 cloud files and registered-user data sync - #16

Open
daum110715 wants to merge 69 commits into
LemonStudio-hub:mainfrom
daum110715:pr/cloud-r2-files
Open

daum110715 wants to merge 69 commits into
LemonStudio-hub:mainfrom
daum110715:pr/cloud-r2-files

Conversation

@daum110715

Copy link
Copy Markdown
Collaborator

What

Enable cloud file storage on R2 for registered users (replaces 410 GONE stubs).

Worker

  • /files/upload|list|quota|get|rename|delete backed by SCP_FILES (R2)
  • /api/sync/data GET/PUT + /api/sync/quota for full JSON sync blob
  • requiredRegisteredUser() — guests get 403; banned registered get 403
  • 512MB quota per user prefix users/{userId}/…
  • wrangler: SCP_FILES R2 binding (prod + dev)

App

  • File manager dual-write: local VFS always, R2 when authStore.canUseCloudSync
  • Sync from Cloud toolbar action (download R2 → local VFS)
  • useCloudQuota composable (quota API helper)
  • i18n: fm.syncCloud

Depends on

#15 (email auth / guest vs registered / JWT accountType).
This branch is stacked on #15 so CI stays green; after #15 merges, rebase to shrink the diff to storage-only.

Ops

  1. Create R2 buckets (e.g. scp-os-files, scp-os-files-dev)
  2. Ensure wrangler SCP_FILES bindings point at them
  3. Registered users only (email login from feat(auth): guest login, email registration/login, verification codes #15)

Out of scope

  • Settings UI for quota display wiring
  • Admin panel changes
  • Notification auto-create side effects

Verification

  • pnpm typecheck ✅
  • worker tests 145 ✅
  • app tests 1161 ✅

daum110715 and others added 30 commits May 17, 2026 22:29
Desktop icons loaded from .desktop files displayed hardcoded English
names (Terminal, Files, Chat, etc.) even in zh-CN locale. Added
getAppLabel() to map known tool types to i18n keys before rendering,
so system apps now show localized names while user files/folders keep
their original labels.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Change default .desktop file names in filesystem.ts from English to
  Chinese (终端, 文件, 聊天, 仪表盘, 反馈, 文档, 设置, 编辑器)
- Add label translation in loadDesktopApps() to migrate legacy English
  names from existing IndexedDB data
- Keep getAppLabel() as render-time fallback for any missed cases

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Backend: add PUT /chat/rooms/:id for room creators to update name/description/public
- Backend: add DELETE /chat/rooms/:id for room creators to delete rooms
- Frontend: saveRoomSettings() now calls API before updating local state, with rollback on failure
- Frontend: deleteRoom() now calls API before removing from local state, with error handling

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Format 47 frontend files with Prettier to fix CI format:check
- Add CI verification rule to CONTRIBUTING.md
- Add PROJECT_GUIDE.md architecture reference
- Add UNIMPLEMENTED_FEATURES report (CSV + Excel)
- Remove obsolete PROJECT_SUMMARY.md

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Replace 410 GONE stubs with full R2 CRUD for file uploads, listing,
  quota, download, rename, and delete
- Wire FileManagerWindow.vue to upload files to cloud after local save
- Auto-create notifications on feedback comments and votes
- Respect user notification preferences (feedback_comment, feedback_upvote,
  feedback_downvote)
- Skip self-notifications

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Fix notification unread count by adding total/unreadCount to backend
  /notifications response (notificationStore.ts expected result.total)
- Fix GOI admin fields mismatch: name/acronym → title/link to match scp_goi
  table schema
- Rewrite ChatRoomDO to persist WebSocket messages to D1 database and
  broadcast by room; handle chat_message, switch_room, heartbeat, rename
- Add cloud sync button to FileManagerWindow.vue; download cloud files to
  local virtual filesystem via GET /files and GET /files/:key

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
0004_chat_messages.sql already added room_id column in a later commit.
This caused fresh local databases to fail when applying 0005.
Comment out the redundant ALTER TABLE and keep the index creation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- MobileFileManager: enable R2 cloud upload with dual-write (local + cloud),
  add syncFromCloud with binary-safe download, sanitizeFileName, and
  ensureDirectory for nested paths
- PerformanceOptimizer: implement real score calculation in createOptimizationPlan
  based on issue severity and recommended strategies; fix validateImplementation
  to use localStorage-persisted step tracking instead of always returning false
- SettingsWindow/MobileSettings: include IndexedDB storage in storageUsed stats
  via async calculateStorageUsed covering all 8 object stores
- indexedDB: extend getStorageSize to all stores, filter availableStores before
  transaction to avoid NotFoundError on old DB versions, use TextEncoder for
  accurate byte counts

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add convertToCsv() with RFC 4180 escaping and UTF-8 BOM
- Add format parameter validation (json/csv only)
- Fix frontend empty CSV download blocking
- Add HTTP error handling in adminApi exportUsers
- Pass CI + multi-agent review + 2 rounds real testing

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add action validation (delete/update_status/move_category)
- Add parameter validation for status and category
- Gracefully handle tables without support via no-such-column catch
- Frontend: dynamic batchActions based on activeTab
- Frontend: prompt for status/category input in handleBatchAction
- Add admin i18n translations for new batch operations
- Pass CI + 2 rounds real testing

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Define import schemas per table (required fields, unique fields)
- Add field validation (required, illegal names)
- Add unique conflict detection via pre-check query
- Use D1 batch() for atomic insert of valid records
- Return detailed import results (success/fail counts + error details)
- Frontend: adapt handleImport to show partial success and errors
- Add i18n translations for import feedback
- Pass CI + 2 rounds real testing

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Uncomment error detection logic for PerformanceResourceTiming
- Use type assertion for responseStatus (supported in modern browsers)
- Change errorCount from const to let to allow increment
- Gracefully handles browsers without responseStatus support
- Pass CI + real testing

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add reactive watch on authStore.userId in PerformanceDashboard
- Calls apiService.setUserId() when userId becomes available or changes
- Fixes case where userId loads asynchronously after mount
- Pass CI + real testing

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add optional silent param to loadFeedbacks() to avoid loading flicker
- Call loadFeedbacks(true) after successful vote and comment
- Keeps existing optimistic update for immediate UI feedback
- Pass CI + real testing

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add shared useCloudQuota composable with caching and login check
- Show cloud quota (used/max + percent) and file count in PC/Mobile settings
- Add i18n translations for cloud storage quota
- Include real-run test scripts for quota logic

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Backend: add /docs/goi endpoint with pagination and search
- Frontend: add DocType support in useDocsReader (scp/goi/tales/hubs)
- UI: add doc type tabs in PCDocsWindow and MobileDocs
- Fix missing CSS variables (--docs-bg-hover, --docs-accent-soft)
- Fix MobileDocs theme toggle double-switch bug
- Add error feedback when non-SCP item has no URL
- Include real-run test scripts for GOI API and docType mapping

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add edit_message and delete_message handlers to ChatRoomDO with
  ownership verification and room_id scoping
- Extend useChatWebSocket with editMessage/deleteMessage methods
  and onMessageEdited/onMessageDeleted callbacks
- Add context menu (PC) and action sheet (mobile) for edit/delete UI
- Add edit mode to input bar with cancel support
- Add edited indicator to message bubbles
- Add i18n translations for new chat actions (en + zh-CN)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Update PROJECT_GUIDE.md with latest API routes (chat edit/delete, GOI, R2 storage)
- Update README.md with 10 GUI tools and new features
- Fix Prettier formatting issues in 8 files
- Add chat message edited/deleted migration

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
0004_chat_messages.sql already contains the edited column,
0015 was causing D1 deployment failure due to duplicate column.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Remove .env.development and .env.production from git tracking
- These files should be in .gitignore to prevent secret leaks

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…e body to prevent vertical squeezing and overlapping text
…ormanceScore to ensure AAA text contrast and readability
… green branding colors in Light Mode, keeping only label text contrast corrections
…rough

Multi-window support:
- ToolRegistry: add seeded window-id generator to avoid Date.now() collisions
  when opening the same tool twice in the same tick; default new windows to
  non-fullscreen so multiple instances are usable side-by-side.
- windowManager: extract blurWindowsExcept() helper; restore windows with a
  fresh zIndex and re-establish a single focused window after IndexedDB hydrate
  so a refresh leaves the taskbar in a consistent state.
- PCTaskbar: show window-count badge per app, mark the focused window with an
  inset accent ring, and cycle through visible windows on repeat click
  (restoring minimized ones).
- DesktopScreen: derive activeTools from windowManager instead of a separate
  ref; add a 250ms launch throttle to prevent accidental double-open from
  rapid clicks / double-clicks.
- MobileApp: drop the desktop-only "one window per tool" guard; only enforce
  reuse when the tool is declared singleton. Pass existing window ids through
  to openTool so generated ids stay unique.
- main.ts: pass the pinia instance explicitly to useTerminalStore() to avoid
  relying on install order.

Theme fixes:
- DesktopScreen / HomeScreen: scope :global(.light) icon/label overrides with
  :not(.claude) so the Claude theme is not overwritten by light-mode styles
  when both classes are present on <html>.
- DesktopScreen / HomeScreen: replace hard-coded grey backgrounds on Claude
  icon labels with the design-system CSS variables.
- CustomAccentPicker: fix the :global() selector — the panel/input rules were
  using :global(.light) .x (which scopes only .light), but the templates need
  the whole compound selector scoped, so move the inner class inside the
  :global(...) parens.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
CI was failing on `prettier --check`. One file (HomeScreen.vue) picked up
new hex colors from the previous commit; the other eight had pre-existing
formatting drift on this branch that predated the multi-window work and
was never caught because earlier commits didn't run the check.

All changes are mechanical: lowercase hex literals, attribute wrapping,
trailing commas. No behavior change.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
daum110715 and others added 16 commits May 31, 2026 13:41
- Root garbage: '1', 'ecret list', fix-light-theme.cjs, scan-remaining.cjs
- Feature tracking spreadsheets (UNIMPLEMENTED_FEATURES.csv/xlsx)
- scripts/ directory (17 one-off test/verify scripts from May 18)
- memory/half_done_features.md (all 3 batches completed)
- .deepseek/instructions.md (auto-generated, not project code)
- .vscode/extensions.json, 启动开发服务器.bat (locally deleted)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Delete 4 unused refactored composables (673 lines of dead code)
- Replace axios in scraper.ts with native fetch + AbortController timeout;
  ApiResponseError mirrors axios error.response, TypeError/DOMException
  mirrors error.request — same retry and error-reporting behavior
- Remove axios from package.json dependencies
- Fix PCStartMenu.vue onItemClick(item: any) → proper union type,
  remove file-level eslint-disable comment

Type-check: 0 errors. Tests: 384/384 passed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Delete MobileFeedbackWindow.vue (1180 lines, never registered/imported)
- Fix TalesListPanel API_BASE: remove optional chaining and wrong fallback URL
- Add uploadToCloud() and syncCloudFiles() to useFileManagerOps:
  uses authStore + config from composable, syncCloudFiles now handles
  binary/text content-type detection and directory creation properly
- FileManagerWindow + MobileFileManager: replace 3 copies of duplicated
  cloud upload loop and 2 copies of syncFromCloud with composable calls
- Remove now-unused authStore/config/cloudSyncing from both components

Type-check: 0 errors. Tests: 384/384 passed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extract shared nickname validation (nicknameValidator.ts) eliminating
  4x duplicated regex/length checks across login screens and store
- Add accountType ('guest'|'registered') to auth store; guests may share
  display names, only registered accounts occupy a nickname slot
- Fix logout to call clearAuthToken(), add 401 auto-retry in authFetch
- New DB migration 0015 adds account_type, is_banned, ban_reason,
  banned_at to users table; worker syncs chat nickname on register
- Settings: add Account section (guest badge, inline rename, logout)
  to both PC and mobile settings panels
- PC settings font-size: replace expand/collapse iOS slider with a
  desktop-native control (−/slider/+ with min-max labels, live preview)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Deploy job now uses `if: secrets.CLOUDFLARE_API_TOKEN != ''` so it
gracefully skips instead of failing when credentials are absent.
Notify-failure is also scoped to actual deploy failures only.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Job-level `if: secrets.* != ''` is not supported by GitHub Actions
(causes workflow parse failure with 0 jobs). Instead, check the env
var inside the deploy step and exit 0 gracefully when unconfigured.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Empty commit to verify the Cloudflare-secret skip logic in
deploy-worker.yml without modifying worker source files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a comment to app.ts to trigger the deploy-worker workflow and
confirm the Cloudflare-secret absence is handled gracefully.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resolved 13 text conflicts and 1 auto-merge divergence against upstream's
English-comment refactor + terminal split + test additions, while keeping
the local app platform & cloud-sync work:

- useTerminal.ts: kept local commandRegistry integration (upstream moved
  command logic into useTerminalEmulator which still uses AVAILABLE_COMMANDS).
- useChatWebSocket.ts: restored upstream dev-mode WS branch.
- fileService.ts / scraper.ts / commands/index.ts / commandAutocomplete.ts:
  merged local functional code with upstream English comments.
- README.md: replaced non-existent Admin row with App Manager (local app
  platform UI).
- PROJECT_SUMMARY.md: honored local deletion (superseded by PROJECT_GUIDE.md).
- Dropped 8 upstream worker tests that exercise the removed admin feature.
- Aligned ToolRegistry.test isFullscreen assertion to local default (false).

typecheck green; tests green (app 1203, worker 48).

Co-Authored-By: Claude <noreply@anthropic.com>
coverage/ is in .gitignore but two png files were tracked before the rule was added. Untrack them so the ignore rule takes effect.

Co-Authored-By: Claude <noreply@anthropic.com>
@LemonStudio-hub

Copy link
Copy Markdown
Owner

🔍 Code Review — Request Changes

Summary

Replaces 410 GONE stubs with full R2-backed cloud file storage for registered users, adds JSON sync blob endpoint, and email/password auth. Large diff (~173KB) because stacked on PR #15.

Critical Issues

1. cloudUsage() capped at 1000 objects — quota silently wrong for >1000 files

  • bucket.list({ prefix, limit: 1000 }) without pagination
  • Users can massively exceed 512MB limit
  • Fix: Paginate (loop while truncated is true) or maintain D1 counter

2. PUT /files/:key rename does not check quota

  • Re-uploads object body without quota validation
  • No check if destination key already exists — silent data loss
  • Fix: Validate quota before rename; check for existing destination

3. No tests for /files/* or /api/sync/* routes

  • Zero test files for file upload, list, download, rename, delete, quota enforcement, sync
  • Fix: Add tests for all endpoints, especially security-sensitive operations

Warning Issues

4. Manual Access-Control-Allow-Origin: * in sync.ts — conflicts with CORS middleware; remove it
5. Upload path strips only one leading / — //etc/passwd passes through; use path.replace(/^\/+/, '')
6. syncFromCloud relies on Content-Type for text/binary sniffing — R2 metadata may be inaccurate
7. PUT /files/:key reads entire body into memory — use streaming for large files
8. Race condition on quota check — two concurrent uploads could both pass
9. cloudUsage called on every upload — O(n) list operation; use D1 counter instead
10. Duplicate CLOUD_QUOTA_BYTES and cloudUsage — extract to shared module

Recommendation: Request changes — fix quota pagination (exploit vector), add route tests, fix rename handler.

daum110715 added a commit to daum110715/scp-os that referenced this pull request Jul 15, 2026
…te tests

- Paginate R2 list for usage and file listing via shared r2-usage module
- Rename checks destination exists (409) and verifies quota
- Content PUT prefers streaming body when Content-Length is set
- Remove manual Access-Control-Allow-Origin from sync GET
- Add route tests for files quota/list/rename/delete and sync get/put
@daum110715

Copy link
Copy Markdown
Collaborator Author

Addressed review feedback

Critical

  1. Quota pagination — listAllObjects / cloudUsage paginate while truncated (shared r2-usage.ts).
  2. Rename handler — rejects existing destination with 409; quota checked before rename.
  3. Route tests — __tests__/routes-files-sync.test.ts covers quota, list, rename (ok + conflict), delete, sync get/put, over-quota.

Warnings

  1. Removed manual Access-Control-Allow-Origin: * from sync GET (global CORS middleware).
  2. Path normalization already strips all leading / and .. segments.
  3. Content update uses request stream when Content-Length is present.
  4. Deduplicated CLOUD_QUOTA_BYTES + cloudUsage.

Residual

Ready for re-review.

@daum110715

Copy link
Copy Markdown
Collaborator Author

Status

  • CI: Lint / Test / Build all green on latest tip (Security Scan skip is expected).
  • Review feedback addressed on this tip; ready for re-review / merge when convenient.

No further code changes planned on this PR unless requested.

Add registered accounts with email verification (Resend), guest sessions,
and password auth while keeping the admin panel intact.

- Worker: /api/auth/{guest,register,login,send-code,email-domains,token}
- Migrations 0015-0017: account_type, email/password, verification codes
- App: login screens + authStore for guest/registered flows
- PBKDF2 hashPassword; guest-only JWT refresh; allowed email domains

Requires KEY_RESEND (or RESEND_API_KEY) and EMAIL_FROM for send-code.
Replace /files 410 stubs with R2 CRUD (upload/list/quota/get/rename/delete)
for registered accounts, add /api/sync data+quota, and dual-write from the
file manager with optional sync-from-cloud.

Depends on email auth (LemonStudio-hub#15) for accountType + requiredRegisteredUser.
Ops: create R2 bucket bound as SCP_FILES (see wrangler.toml).
…te tests

- Paginate R2 list for usage and file listing via shared r2-usage module
- Rename checks destination exists (409) and verifies quota
- Content PUT prefers streaming body when Content-Length is set
- Remove manual Access-Control-Allow-Origin from sync GET
- Add route tests for files quota/list/rename/delete and sync get/put

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants