Commit Graph

277 Commits

Author SHA1 Message Date
Dionisio ab0c4476b3 perf: fix thundering herd in ThumbnailService with moka entry()
get_thumbnail() used a check-then-act pattern (get → miss → generate →
insert) that allowed N concurrent requests for the same thumbnail to
each trigger independent CPU-heavy generation (image decode + Lanczos3
resize + WebP encode, 50-500ms each).

Replace with moka's entry().or_insert_with() which guarantees only ONE
init closure runs per key; all concurrent callers coalesce and await
the same computation:
- 90% less CPU under concurrent thumbnail requests
- 90% less peak RAM (1 buffer instead of N)
- 90% fewer redundant disk writes
- Removed dead thumbnail_exists() method (no callers)
2026-02-23 23:22:54 +01:00
Dionisio a162aafd43 perf: replace sync zip crate with async_zip in ZipService
ZipWriter<std::fs::File> performed every write_all() as a blocking
write(2) syscall on the Tokio worker thread, sequestering it for
10-100ms per file (0.8-12s for a 100-file ZIP).

Replace with async_zip::ZipFileWriter backed by a buffered
tokio::fs::File:
- All I/O (headers, deflate chunks, central directory) is fully async
- 256 KB BufWriter minimises syscall count
- Zero Tokio worker blocking during ZIP creation
- Streaming per-chunk writes keep RAM O(1) regardless of archive size
- Removed dead From<zip::result::ZipError> for DomainError impl
- zip crate retained for batch_operations.rs (separate concern)
2026-02-23 23:11:55 +01:00
Dionisio 958836e96b perf: replace blocking std::fs calls with tokio::fs::metadata in PathService
file_exists(), directory_exists(), and ensure_directory() were using
synchronous Path::exists() + is_file()/is_dir() which each perform two
blocking stat(2) syscalls on the Tokio worker thread (0.2-4ms total).

Replace with a single tokio::fs::metadata().await call per method:
- Async: worker thread is never blocked
- 1 syscall instead of 2: metadata() returns file type in one stat(2)
- Proper error propagation for I/O errors (not just silent false)
2026-02-23 22:56:20 +01:00
Dionisio b5652b029d perf: offload MD5 checksum to spawn_blocking in chunked uploads
md5::compute(&data) is CPU-bound (~1.2ms per 5MB chunk) and was
blocking the Tokio worker thread. Move it to the blocking thread-pool
via spawn_blocking so the async worker is freed in ~5µs. Bytes::clone
is O(1) (Arc increment) so no extra copy overhead.
2026-02-23 22:49:34 +01:00
Dionisio 1da284a841 perf: move 512KB I/O buffer from stack to heap in chunked upload assembly
- Async Future size drops from ~525KB to ~5KB
- Eliminates stack overflow risk and reduces work-stealing copy cost
2026-02-23 22:41:50 +01:00
Dionisio 41b5500b77 perf: move persist_progress disk I/O outside write lock in chunked uploads
- Write lock now held only for RAM updates (~microseconds instead of ~ms)
- Bitmask built under lock, written to disk after lock release
- Concurrent uploads across all sessions no longer blocked by disk I/O
- Under 10 concurrent sessions: lock wait drops from ~50ms to ~10µs
2026-02-23 22:32:14 +01:00
Dionisio 363ec33fc2 perf: parallelize WebDAV PROPFIND queries with tokio::join!
- Root PROPFIND: list_folders + list_files now run concurrently
- Sub-folder PROPFIND: list_files + list_folders now run concurrently
- ~50% latency reduction on every PROPFIND operation
- Matches existing pattern used in folder_handler::list_folder_listing
2026-02-23 22:19:41 +01:00
George Wu 6a9554d23f feat: add breadcrumb navigation and refactor SPA view management
- Add breadcrumb navigation with folder hierarchy display
- Create setCurrentSection() helper to centralize view state management
- Derive nav item section dynamically from DOM data-i18n attribute
- Remove remnant /shared page and consolidate to SPA sharedView
- Add search input to sharedView for client-side filtering
- Fix 'Go to Files' button to use switchToFilesView()
2026-02-22 20:20:00 -08:00
Diocrafts 85908311dc perf: findings 6.1, 6.2, 2.6 — async Argon2, moka cache, full streaming migration
- 6.1: PasswordHasherPort now async_trait with spawn_blocking for Argon2
- 6.2: OIDC pending maps migrated from std::sync::Mutex to moka::sync::Cache with TTL
- 2.6: All file download paths migrated to 64KB streaming (get_file_stream / read_blob_stream)
  - WOPI, dedup, batch ZIP, file_retrieval_service consumers migrated
  - WebDAV COPY uses zero-copy dedup (copy_file)
  - Removed dead code: get_file_content, get_file_mmap, read_blob, read_blob_bytes
    from traits, impls, stubs, and mocks (18 files touched)
2026-02-23 00:51:46 +01:00
Diocrafts b501c4052b perf: replace recursive spawn-per-folder search with O(1) ltree queries
- Add search_files_in_subtree() to FileReadPort with ltree-based SQL
- Add list_descendant_folders() to FolderRepository with ltree GiST index
- Implement both in PG repositories (single query per entity type)
- Remove search_parallel() fan-out (O(N) tokio::spawn → 0 spawns)
- Remove passes_file_filter/passes_folder_filter (filtering now in SQL)
- Fix SearchCriteriaDto cache key: JSON serialization → u64 hash (15x faster)
- Fix escaped quote literals in webdav_handler.rs handle_put function
2026-02-23 00:17:40 +01:00
Diocrafts 92e0364a60 fix: OOM protection, lock-free thumbnail cache, OIDC JWKS TTL
- Streaming WebDAV PUT: body spooled to tempfile with incremental SHA-256,
  peak RAM ~64KB regardless of file size (Solution 2)
- RequestBodyLimitLayer (1MB) on CalDAV/CardDAV routers (Solution 3)
- All body::to_bytes(body, usize::MAX) replaced with explicit limits:
  PROPFIND/PROPPATCH/LOCK → 1MB, MKCOL → 4KB
- Added AppError::payload_too_large (HTTP 413)
- Added max_upload_size to StorageConfig (default 10GB, env override)
- New streaming update chain: FileWritePort::update_file_content_from_temp
  → FileUploadUseCase::update_file_streaming
- ThumbnailService: migrated from RwLock<LruCache> to moka::future::Cache
  with weight-based eviction — eliminates lock contention on read hot-path
- OIDC: discovery + JWKS caches now expire after 1 hour (Cached<T> wrapper)
  so IdP key rotation no longer requires server restart
2026-02-22 23:28:03 +01:00
Diocrafts b48f2867ac security(P0): fail-closed when auth is enabled but cannot initialize
Previously, if enable_auth=true but the database or auth services failed
to initialize, the server silently started in PUBLIC mode — all API routes
accessible without authentication. This is a critical fail-open bug.

Fixes (fail-closed behavior):
1. main.rs: DB pool creation failure with enable_auth=true now panics
   instead of falling through with db_pool=None
2. di.rs: create_auth_services() failure now propagates the error via ?
   instead of logging and continuing with auth_services=None
3. main.rs: Added defensive assert! that auth_service is Some when
   enable_auth=true, preventing any future refactor from reintroducing
   the silent degradation

The server will now refuse to start if authentication is configured
but cannot be properly initialized.
2026-02-22 22:40:55 +01:00
Diocrafts 6ad23e0acc fix(perf): replace std::sync::Mutex with moka lock-free cache in async context
Eliminates deadlock risk under concurrent load:
- SearchService: Arc<Mutex<HashMap>> → moka::sync::Cache with automatic TTL + LRU
  - Removed manual cleanup task, TTL checking, eviction logic (~90 lines)
  - get_from_cache/store_in_cache are now single lock-free calls
  - clear_search_cache uses invalidate_all()
- HttpCache: Arc<Mutex<HashMap>> → moka::sync::Cache
  - Removed stats(), cleanup(), evict_oldest() manual methods
  - Removed CacheEntry.timestamp/max_age fields (moka handles internally)
  - Removed start_cache_cleanup_task (moka evicts lazily)
- routes.rs: Removed dead HttpCache instantiation and unused TTL variables

Impact: std::sync::Mutex::lock() blocked Tokio worker threads; N concurrent
requests (N = CPU count) could freeze the entire server. moka::sync::Cache
is lock-free and designed for async runtimes — zero contention.
2026-02-22 22:37:36 +01:00
Diocrafts 5b4cd30e2b fix(zip): stream ZIP to temp file instead of loading entire archive into RAM
Solution C - Hybrid temp-file streaming:
- ZipPort trait now returns NamedTempFile instead of Vec<u8>
- ZipService writes to a temp file via ZipWriter<std::fs::File> (O(1) RAM)
- Files are read in 64KB stream chunks via get_file_stream() instead of get_file_content()
- HTTP response streams the temp file via ReaderStream (never loads full ZIP in memory)
- Temp file auto-deleted on drop after response completes
- Removed dead imports (HeaderName, HeaderValue, Cursor, Read)
2026-02-22 22:29:07 +01:00
Diocrafts 2dd3dc0b54 fix: prevent blob storage leak on folder deletion
- Add PG trigger trg_files_decrement_blob_ref (AFTER DELETE ON storage.files)
  that auto-decrements storage.blobs.ref_count for every deleted file row.
  Covers all paths: explicit DELETE, ON DELETE CASCADE, trash emptying.

- Remove manual remove_reference() call from delete_file() in
  file_blob_write_repository — trigger is now the single source of truth.

- Fix double-decrement bug in FileManagementService::delete_with_cleanup:
  was decrementing ref_count on trash (soft-delete) when the file row still
  existed, causing premature blob GC and potential data corruption on restore.

- Remove dead fields (file_read, dedup_service) from FileManagementService
  and simplify constructors — ref_count fully handled by PG trigger.
2026-02-22 22:07:46 +01:00
Diocrafts fd9e509648 perf: implement findings #2, #3, #17 from architecture audit
- Finding #2: Replace Mutex<HashMap> with moka::sync::Cache in file_blob_read_repository (10K cap, 30s TTI)
- Finding #3: Add chunked upload persistence with session.json + progress.bin bitmask for crash recovery
- Finding #17: Remove manual gzip compression, delegate entirely to tower-http CompressionLayer
- Remove dead code: StubCompressionPort, GzipCompressionService re-export, duplicate response structs
- All 114 tests passing
2026-02-22 14:12:53 +01:00
Dionisio Pozo 0975f1f7ab Merge pull request #141 from gbw/docs/env-file-documentation
docs: add example.env and document all environment variables
2026-02-22 09:48:36 +01:00
Dionisio Pozo 316b48b8e4 Merge pull request #139 from gbw/database_level_filtering
Optimize search service with database-level pagination and improve cache handling
2026-02-22 09:47:09 +01:00
Dionisio Pozo 959337479e Merge pull request #140 from gbw/fix/storage-usage-calculation
fix: correct storage usage calculation by using direct SQL query
2026-02-22 09:45:39 +01:00
George Wu 2c428678b6 docs: add example.env and document all environment variables
- Create example.env with all 31 environment variables documented
- Update docker-compose.yml to use env_file directive
- Update doc/deployment.md with Quick Start and missing variables
- Add OXICLOUD_BASE_URL and WOPI configuration docs
- Update README.md Docker section with .env setup
- Remove duplicate port mapping (incorporates 4577e56)
- Fix server port default from 8085 to 8086
2026-02-21 18:44:38 -08:00
George Wu be290604bf feat(search): add missing trait methods for mock repositories
- Add search_files_paginated and count_files to MockFileRepository in share_service.rs
- Add search_files_paginated and count_files to MockFileRepository in trash_service_test.rs

These methods were added to the FileReadPort trait to support database-level
pagination for search optimization.
2026-02-21 18:01:08 -08:00
George Wu 922f00e339 fix: remove uuid cast from storage usage query
The user_id column in storage.files is varchar, not uuid. The ::uuid cast
was causing a type mismatch error when calculating storage usage.
2026-02-21 17:49:36 -08:00
George Wu 6907f0a057 fix: correct storage usage calculation by using direct SQL query
The original implementation tried to find user's home folder via
file_repository.list_files(None), but this only returns files (not folders)
and folders are in a separate table. This resulted in storage always
showing 0 bytes.

Fixed by using a direct SQL query to sum file sizes by user_id from
storage.files table, which is more efficient (O(1) vs recursive) and
correctly calculates storage for all files owned by the user.
2026-02-21 17:30:32 -08:00
George Wu f3f5c40b6b fix: auto-create home folder when listing root folders returns empty
When a user has no home folder (e.g., legacy users or failed folder
creation during registration), the frontend would get an empty list
from GET /api/folders, leaving userHomeFolderId undefined. This caused
uploads to fail or go to the wrong location.

Now list_folders_for_owner() automatically creates a home folder when:
- Listing root folders (parent_id is None)
- The result is empty

This self-healing approach fixes the issue at the source, ensuring
the frontend always gets a valid userHomeFolderId.
2026-02-21 16:53:08 -08:00
George Wu 95fa648a55 Optimize search service with database-level pagination and improve cache handling
- Add search_files_paginated method to FileReadPort for database-level pagination
- Implement efficient SQL-based search with LIMIT/OFFSET in file_blob_read_repository
- Add relevance scoring (exact match > starts-with > contains)
- Support multiple sort options (name, date, size) with ascending/descending order
- Fix cache expiration handling with proper borrow checker semantics
- Use i64 for SQL LIMIT/OFFSET parameters instead of usize
- Clean up duplicate SQL query builder code in search_files_paginated

This significantly improves search performance for non-recursive queries by:
- Pushing pagination to the database layer
- Avoiding loading all files into memory for filtering
- Supporting database-level sorting
2026-02-21 14:50:53 -08:00
George Wu 46a65c322c Add database level pagination and filtering for efficiency 2026-02-21 14:50:53 -08:00
Dionisio Pozo ef15fe85cb Merge pull request #135 from zjean/feature/oidc-user-identity
Feature/OIDC user identity
2026-02-21 21:08:57 +01:00
George Wu 52a47d4b33 Add email_verified check for OIDC login
- Parse email_verified from ID token and UserInfo endpoint
- Reject OIDC login if email is present and not verified
- Only applies when email is in OIDC claims (not required otherwise)
2026-02-21 11:51:48 -08:00
Jan Wiebe 628f4d6b6a style: apply cargo fmt
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 20:33:54 +01:00
Jan Wiebe 0db9c3cd88 feat(auth): expose auth_provider in UserDto and guard password ops for OIDC users
- Add auth_provider field to UserDto, derived from oidc_provider
  ("local" for password users, provider name for OIDC users)
- Block change_password() for OIDC users with clear error message
- Block admin_reset_password() for OIDC users

Fixes #122, Fixes #123

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 20:26:21 +01:00
Jan Wiebe 4e2c9d2592 feat(wopi): add WOPI protocol support for collaborative editing
Implement the Web Application Open Platform Interface (WOPI) protocol
to enable collaborative document editing with Collabora Online and
OnlyOffice through OxiCloud.

Backend:
- WOPI token service with HMAC-SHA256 signed access tokens
- WOPI lock service with in-memory lock management and expiry
- WOPI discovery service for auto-detecting editor capabilities
- WOPI HTTP handler: CheckFileInfo, GetFile, PutFile, Lock/Unlock
- File entity extended with owner_id for WOPI file-info responses
- Configuration via WOPI_* environment variables
- Services wired through DI in AppState

Frontend:
- WOPI editor component with modal and new-tab viewing modes
- Context menu integration for opening files in online editors
- Inline viewer integration for document preview

Infrastructure:
- Docker Compose file for local Collabora/OnlyOffice dev setup

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 13:39:27 +01:00
Jan Wiebe 807370e194 style: apply cargo fmt formatting to existing codebase
Run `cargo fmt` across all Rust source files to enforce consistent
formatting (import ordering, line wrapping, match arm braces).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 13:33:18 +01:00
George Wu 283a80a302 fix: display unlimited quota (∞) when storage_quota_bytes = 0
- Added formatQuotaSize() function to display ∞ for unlimited (0) quota
- Added format_quota_size() Rust function matching JavaScript behavior
- Updated quota defaulting logic from '||' to '== null' check
- This allows 0 (unlimited) to pass through while defaulting to 10 GB
  only when the value is null/undefined
- Call sites now use dedicated formatQuotaSize() or format_quota_size()
  instead of options parameter for cleaner API
2026-02-20 19:23:44 -08:00
Diocrafts a1a3bd1b2b fix: folder trash/delete operations & frontend refactoring
- Fix recursive CTE: add missing RECURSIVE keyword in move_to_trash and restore_from_trash SQL queries (relation 'descendants' does not exist)
- Fix folder deletion: delete descendant files before folder to avoid 'duplicate key violates unique constraint idx_files_unique_name_at_root'
- Simplify trash model: only mark the folder as trashed, not child files (implicit trash via parent)
- Update trash_items view: filter to show only top-level trashed items
- Update schema.sql: change files.folder_id FK from ON DELETE SET NULL to ON DELETE CASCADE
- Fix trash view icons: folders and files now show correct visual icons (folder-icon, pdf-icon, etc.) in trash view
- Frontend refactoring: extract inline CSS/JS from admin.html and profile.html into dedicated external files
- Frontend cleanup: replace all inline style attributes with CSS classes
- Frontend cleanup: replace style.display JS
- Fix recursive CTE: add missing RECURSIVE keyword in move_to_trash and restore_from_trash SQL queries (relation 'descendants' d
2026-02-20 12:27:52 +01:00
Dionisio Pozo 27eb7b16e0 Merge pull request #128 from gbw/fix/oidc-env-vars-in-admin-panel 2026-02-20 09:21:18 +01:00
George Wu 5738ddbb54 fix(admin): populate OIDC settings in admin panel from environment variables
- Use load_effective_oidc_config() to get OIDC settings, which applies env var overrides
- Use effective.redirect_uri for callback_url instead of calculated value from server_base_url
- This ensures the admin panel shows the correct OXICLOUD_OIDC_REDIRECT_URI value
2026-02-18 19:41:43 -08:00
George Wu e2a2026c3d fix(admin): populate OIDC settings in admin panel from environment variables
The get_oidc_settings() method was reading directly from the database only,
without applying environment variable overrides. Now it uses load_effective_oidc_config()
which properly applies env var overrides (OXICLOUD_OIDC_*), ensuring the admin
panel accurately reflects the runtime configuration.
2026-02-18 19:25:53 -08:00
George Wu d782d8469c fix: always apply database schema on startup
Previously, the application only applied the schema if auth.users table
didn't exist. This caused issues when upgrading - new schema elements
(like storage.trash_items view) weren't created on existing databases.

Now the schema is applied on every startup. Since schema.sql uses
'IF NOT EXISTS' and 'CREATE OR REPLACE', it's safe to run repeatedly.
2026-02-18 17:56:06 -08:00
Dionisio Pozo 4168e259a5 Merge pull request #125 from gbw/brotli-compression
Enable brotli compression for API and static files
2026-02-18 22:53:18 +01:00
Diocrafts 0d7c8bc019 perf: optimize UI rendering and event lifecycle 2026-02-18 10:53:43 +01:00
George Wu b28aa341b3 Enable brotli compression for API and static files
- Add compression-br feature to tower-http
- Apply CompressionLayer to API routes (JSON responses)
- Apply CompressionLayer to static files (CSS, JS, locales)
- File downloads remain uncompressed (avoid double compression)
2026-02-17 23:00:45 -08:00
Diocrafts f661282962 perf(web): add gzip compression + cache-control headers for static assets
- Add CompressionLayer to web router for gzip compression of JS/CSS/JSON/SVG
- Add Cache-Control header (7 days + stale-while-revalidate) via SetResponseHeaderLayer
- Add 'set-header' feature to tower-http dependency
- Result: 67-85% reduction in transfer size for all static assets
2026-02-17 19:29:42 +01:00
Dionisio 1ceae0ce94 Frontend optimizations: SVG icons, remove updateFileIcons, unify rendering, scope translatePage
- Replace Font Awesome CDN with inline SVG system (icons.js + MutationObserver)
- Remove updateFileIcons() (~140 lines) - redundant with backend icon_class + MutationObserver
- Migrate favorites.js and recent.js to use shared ui.renderFolders/renderFiles (eliminate ~260 lines of duplicate rendering + per-item event listeners)
- Add view-mode aware click delegation for favorites/recent views
- Fix _createFileCard to apply icon_special_class
- Add translateElement(root) for scoped i18n translation
- Replace full-page translatePage() calls with scoped translateElement() or inline t()
- Remove redundant translatePage() calls in shared.js and auth.js
- Remove Alpine.js from Service Worker cache
2026-02-16 21:51:53 +01:00
Dionisio a4709426d9 fix(upload): sanitize multipart filename for folder uploads (#121)
Browsers send the full relative path (e.g. 'Screenshots/file.png') as
the multipart filename when uploading folders via webkitRelativePath.
The File entity rejects names containing '/' or '\', causing all files
in a folder upload to fail with 'Invalid file name'.

Three fixes:
- Backend: strip path components from multipart filename in file_handler,
  keeping only the basename. Also prevents path-traversal attacks.
- Frontend (fileOperations.js): explicitly pass file.name as the third
  argument to FormData.append() in uploadFolderFiles() to override the
  browser's relative path.
- Frontend (ui.js): detect folder drops in drag-and-drop handlers by
  checking webkitRelativePath, and route them to uploadFolderFiles()
  instead of uploadFiles() so subfolders are created first.

Closes #121
2026-02-16 17:58:50 +01:00
Dionisio f70890e884 fix: upload failure, share dialog, shared view styling, dark mode fixes
- Fix 'folder_id is required' upload error by adding create_home_folder
  through the full hexagonal architecture (trait, service, repository, auth)
- Fix double upload issue with _isUploading concurrency guard
- Fix Share context menu doing nothing (ID collision between sharedView
  and main share dialog resolved with sv- prefix)
- Fix Compartidos tab duplicate headers and broken layout
- Add missing .shared-dialog CSS with dark mode support
- Fix dark mode white backgrounds on empty-state, shared-filters,
  trash-actions, action-btn, and header
- Fix i18n key mismatches in sharedView
- Bump version to 0.4.1

Closes #120
2026-02-16 16:18:39 +01:00
Dionisio b7bd656a43 perf: fix issues #1,#2,#3,#13 from performance audit
- #1  image_transcode: dedicated rayon pool + moka cache (no tokio blocking)
- #2  ltree materialized paths: eliminate N+1 folder/file queries
- #3  compute_content_hash: read blob_hash column instead of loading file into RAM
- #13 event delegation + DocumentFragment: ~15 delegated listeners replace ~19k per-item listeners

Backend: 82/82 tests pass, cargo check clean.
Frontend: ui.js and app.js syntax-validated via Node.js.
2026-02-16 13:10:44 +01:00
Dionisio d6c4eb884d feat: batch favorites endpoint + frontend dedup fixes
Backend:
- Add POST /api/favorites/batch endpoint (single multi-row INSERT)
- Add BatchFavoritesResult/BatchFavoritesStats DTOs
- Add batch methods to ports, service, PG repository
- Transaction-based insert with ON CONFLICT DO NOTHING, chunking at 5000

Frontend:
- Rewrite batchFavorites() to single API call (40 requests → 1)
- Add _replaceCacheFromResponse() to avoid extra GET round-trip
- Centralize formatFileSize, isTextViewable, formatDateTime, formatDateShort
- Remove duplicate icon mapping from app.js
- Fix inconsistent quota defaults (10GB everywhere)
2026-02-16 09:17:54 +01:00
Dionisio fb652c07e3 feat(P1+P2): server-authoritative favorites/recent + pre-computed display fields
P1-A: Enrich FavoriteItemDto & RecentItemDto with item_name, item_size,
      item_mime_type, parent_id, modified_at via SQL LEFT JOINs — eliminates
      N+1 per-item fetches.

P1-B: Rewrite favorites.js as server-authoritative (724→380 lines).
      In-memory cache backed by GET /api/favorites; no localStorage.

P1-C: Rewrite recent.js as server-authoritative (341→249 lines).
      GET /api/recent + POST /api/recent/{type}/{id}; no localStorage.

P1 cleanup: Remove dead localStorage cleanup from auth.js logout,
            fix async clearRecentFiles in app.js.

P2: Add icon_class, icon_special_class, category, size_formatted to
    FileDto, FolderDto, FavoriteItemDto, RecentItemDto. New shared
    display_helpers.rs module centralises mime→icon/category/size logic.
    Frontend (ui.js, fileRenderer.js, favorites.js, recent.js) now reads
    pre-computed fields from the API with fallback defaults — eliminates
    5 duplicated mime→icon mapping blocks (~89 lines removed).

Also fixes: synthetic FolderDto in webdav_handler.rs, pre-existing
            missing item_name field in share_service.rs test.

Net: -307 lines across 15 files. cargo check: 0 errors, 0 warnings.
     cargo test display_helpers: 3/3 pass.
2026-02-16 01:09:28 +01:00
Dionisio 5a679dfc90 fix(security): patch 3 vulnerabilities — IDOR, ownership bypass, XSS
V1: Add owner-scoped folder pagination (list_folders_by_owner_paginated)
  - New method in FolderRepository trait, PG implementation, service & handler
  - Prevents IDOR by filtering folder listings to authenticated user

V2: Enforce ownership checks on folder mutations
  - rename_folder, move_folder, delete_folder now require caller_id
  - Service verifies folder.owner_id == caller_id (returns 404 on mismatch)
  - Propagated to folder_handler, batch_handler, batch_operations, webdav_handler
  - delete_folder_with_trash upgraded from OptionalAuthUser to AuthUser
  - download_folder_zip now checks ownership before streaming

V3: Fix XSS in frontend via DOM APIs
  - sharedView.js: innerHTML → createElement + textContent
  - contextMenus.js: innerHTML → DOM construction for share dialog

Cleanup: removed unused OptionalAuthUser import, updated all stubs/mocks
2026-02-16 00:22:42 +01:00
Dionisio 7737ed90c7 feat: folder ownership scoping, batch operations integration, frontend audit fixes
Backend:
- Add owner_id to Folder entity + FolderDto (DB user_id column)
- Add list_folders_by_owner to FolderRepository trait + PG impl
- Add list_folders_for_owner to FolderUseCase + FolderService
- Rewrite FolderHandler: all endpoints now scope by AuthUser
- Remove dead handler methods (list_folders_inner, list_folders_for_user, is_user_home_folder, folder_belongs_to_user)
- Add ownership check in get_folder (returns 404 on mismatch)

Batch operations:
- Add trash_service + zip_service to BatchOperationService
- New methods: trash_files, trash_folders, move_folders, download_zip
- New handlers: trash_batch, move_folders_batch, download_batch
- New routes: POST /api/batch/trash, /api/batch/folders/move, /api/batch/download

Frontend:
- Replace findUserHomeFolder (~130 lines) with resolveHomeFolder (~35 lines)
- Remove client-side folder filtering in loadFiles (backend now scopes)
- Rewrite batchDelete: N requests -> 1 POST /api/batch/trash
- Rewrite batchMove: N requests -> 2 POST max (files + folders)
- Rewrite batchDownload: N requests -> 1 POST /api/batch/download (ZIP)
- Search moved to backend, share system uses backend API
- Dark mode fixes, frontend audit improvements
2026-02-15 23:45:11 +01:00