Commit Graph

64 Commits

Author SHA1 Message Date
Edouard Vanbelle dfb082fdf4 refactor(server): file_management_service: move all method without owner check into private, add folder_ports 2026-05-20 15:39:53 +02:00
Dionisio Pozo 1e34c5f3f8 Merge pull request #330 from EdouardVanbelle/fix/thumbnail-on-update
bugfix/thumbnails on update
2026-05-14 00:38:03 +02:00
Edouard Vanbelle 78cb37b311 feat: check thumbnail cleanup on files deletion + correct ref counter 2026-05-13 15:55:38 +02:00
abnvle d15d7b8f8e feat(share): public folder browsing API + range support + zip
Five new public endpoints under /api/s/{token}/...:
  GET /contents
  GET /contents/{folder_id}
  GET /file/{file_id}
  GET /zip
  GET /zip/{folder_id}

All honour the unlock cookie from /verify, so password-protected
folder shares work end-to-end.

Folder/file IDs are validated against the share subtree via a single
ltree containment query (O(log N) on the existing GiST index).
Out-of-scope IDs return 404.

download_shared_file refactored to a Range/304/206/416-aware
serve_share_file helper, shared with the new /file/{file_id}
endpoint. content_disposition extracted from FileHandler so RFC 5987
formatting is identical across auth and share download paths.
2026-05-05 22:41:55 +02:00
Edouard Vanbelle d0c6bd6a73 feat(openapi): upgrade code to gnerate openapi on all path 2026-05-04 12:02:33 +02:00
DioCrafts 30fd3cc488 Fix thumbnail generation for CDC blob storage 2026-04-26 11:55:05 +02:00
Edouard Vanbelle 62a7713af5 refactor(server): apply fmt + lint recos 2026-04-14 19:05:01 +02:00
Diocrafts 2dde4da5cf feat: thumbnail dedup — store thumbnails by blob_hash instead of file_id (#233)
Thumbnails are now keyed by blob_hash on disk so identical files share
a single set of thumbnails (icon/preview/large). For 4000 duplicate
files with the same content, this reduces thumbnail storage from 12,000
files to just 3.

Changes:
- get_thumbnail_path() keys by blob_hash instead of file_id
- get_thumbnail(), get_cached_thumbnail(), generate_all_sizes_background()
  accept blob_hash parameter for disk dedup
- generate_all_sizes_background() fast path: if blob-hash thumbnails
  already exist on disk, skip image processing entirely and just
  populate moka cache for the new file_id
- delete_thumbnails() only invalidates moka cache (shared disk
  thumbnails must not be deleted when one file is removed)
- delete_blob_thumbnails() added for GC; garbage_collect() now cleans
  up orphaned thumbnail files alongside blob files
- External thumbnails (video frames) stored as ext-{file_id}.jpg
  since they are client-generated and not dedup-able
- ThumbnailPort trait updated with blob_hash parameters
- All handler call sites updated (file_handler, preview_handler)
- Tests updated for new signatures
2026-04-12 00:50:10 +02:00
Andrey Tkachenko da066f47fa Music Player & Playlist Manager 2026-04-08 15:14:03 +03:00
Edouard Vanbelle badf35f08f chore: remove all executable attributes on non bash files 2026-04-01 23:14:42 +02:00
iltumio bf7e030cd6 feat: add OpenAPI spec generation with utoipa and justfile
- Add utoipa v5 dependency with ToSchema derives on all REST API DTOs
- Annotate free-function handlers with #[utoipa::path] (trash, share, favorites, recent)
- Create ApiDoc struct with OpenApi derive registering 37 schemas across 7 tags
- Add generate-openapi binary outputting resources/gen/openapi.json
- Serve OpenAPI spec at GET /api/openapi.json (public, no auth)
- Add justfile with common dev commands (build, test, lint, check, openapi, db)
2026-03-29 18:49:10 +02:00
zjean 18518bedaf fix: resolve clippy warnings and rustfmt issues for CI compliance
Fix all clippy lints (collapsible if, clone on Copy, needless borrow,
redundant bindings, unused params) and apply rustfmt across the codebase.
Update test mocks to match Uuid-based trait signatures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 14:34:07 +01:00
Diocrafts b8638f5131 perf(thumbs): switch thumbnail encoding from WebP to JPEG q=80
Replace all 3 ImageFormat::WebP encode sites with JpegEncoder q=80.
Update fast-path to detect JPEG SOI instead of RIFF/WEBP magic.
Change file extension .webp -> .jpg, Content-Type headers, and
browser toBlob. Remove unused ImageFormat import and stale comments.
The webp feature stays for DECODING uploaded WebP images.
2026-03-07 20:37:19 +01:00
Diocrafts fc5f101e61 perf(thumbnails): cache-first + ETag 304 eliminates DB queries on GET
Solution A — Cache-first path:
- get_thumbnail now tries moka (RAM) → disk BEFORE any DB query
- Ownership was verified at creation time; UUIDv4 prevents enumeration
- 146-photo timeline: 0 SQL queries instead of 146 per page load

Solution C — ETag / If-None-Match short-circuit:
- Deterministic ETag 'thumb-{id}-{size}' on every response
- If browser sends matching If-None-Match → 304 with zero I/O
- Cache-Control: immutable prevents even conditional requests

Additional improvements:
- Remove moka TTL (was 600s); thumbnails are immutable, weight-only eviction
- generate_all_sizes_background populates moka after disk write;
  first GET after upload served from RAM with zero disk I/O
- DB path only taken on cache miss for images needing generation
2026-03-07 19:48:35 +01:00
Diocrafts 2aeb97383c fix: instant video thumbnails on tab switch + throttle decodes
Backend:
- 204 response: add Cache-Control: no-store so browser never caches
  'no thumbnail yet' — next GET after PUT upload gets the WebP

Frontend:
- _videoThumbCache (Map): persists fileId → URL across re-renders,
  so switching tabs reuses cached URLs instantly (no re-decode)
- Render: videos with cached URL skip the 204/error/decode cycle
- After PUT succeeds: swap blob URL → server ?v=1 URL so blob is GC'd
- Concurrency throttle: max 3 simultaneous video decodes to avoid
  overwhelming network + CPU when gallery has many videos
- Decode queue: pending videos processed as slots free up
2026-03-07 19:06:37 +01:00
Diocrafts db93b48149 feat: client-side video thumbnail generation (no ffmpeg)
Backend:
- get_thumbnail: for non-images, serve cached thumb or 204 (not 400)
- store_external_thumbnail: validate + re-encode to WebP + persist
- get_cached_thumbnail: memory → disk lookup without generation
- PUT /api/files/{id}/thumbnail/{size} endpoint for client uploads
- ThumbnailPort trait: add get_cached_thumbnail + store_external_thumbnail

Frontend (photos.js):
- On <img> error for video tiles, use <video> + <canvas> to extract
  a frame at 25% duration using browser's native codec
- Show frame immediately via blob URL
- Fire-and-forget PUT to server for permanent caching
- Subsequent visits serve cached WebP instantly (no re-extraction)

Zero server-side dependencies — CPU distributed across clients.
2026-03-07 18:55:44 +01:00
Diocrafts 06ed0455ce perf: migrate all user/session/auth IDs from VARCHAR(36) to native UUID
- Schema: all ~15 VARCHAR(36) columns → UUID with DEFAULT gen_random_uuid()
- Domain entities: User, Session, DeviceCode, AppPassword, Share → id: Uuid
- DTOs: CurrentUser.id → Uuid (API boundary DTOs keep String for JSON)
- Auth middleware: parse JWT claims.sub (String) → Uuid at boundary
- All repository traits, port traits, service impls updated end-to-end
- Handlers: pass Uuid by value (Copy, 16 bytes) instead of String refs
- Settings chain: updated_by column → Uuid (was text, caused setup crash)
- Removed ~650 lines of String↔Uuid conversion boilerplate
- Eliminates per-request heap allocations for ID cloning
- 16-byte binary comparison vs 36-byte string comparison in all queries
- Native UUID indexing in PostgreSQL (btree on 16 bytes vs 36-char text)

85 files changed, 1090 insertions(+), 1739 deletions(-)
2026-03-07 14:59:32 +01:00
Dionisio 69d25d46d6 perf: eliminate redundant PG round-trip for blob_hash after image upload
upload_file_inner now returns (FileDto, blob_hash) tuple, propagating
the BLAKE3 hash computed during the hash-on-write spool directly to
upload_file_with_thumbnails. This removes the unnecessary
get_blob_hash() SQL query that was fetching a value already in memory.

Saves ~0.5-2ms + 1 PG pool connection per image upload.
2026-03-07 00:10:56 +01:00
Jared Wolff 5b5a9173bc fix(auth): resolve race condition causing files not to load on initial visit
The cached-user-data path in checkAuthentication() fired resolveHomeFolder()
and loadFiles() concurrently with refreshUserData() using non-blocking .then()
chains. When the session cookie was expired, the folder/file API calls received
401 errors before the session could be refreshed. Now awaits session validation
before loading files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 13:01:08 -05:00
Dionisio 9aa35aa0ea quick fix 2026-03-06 13:18:36 +01:00
Jared Wolff c53a0602ec fix: resolve clippy warnings (collapsible_if, manual_clamp)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 17:39:34 -05:00
Jared Wolff 6a84a5c44e fix(photos): fix SQL type mismatch, pagination panic, missing icons, and add day/month/year grouping
- Fix photos endpoint 500: remove ::uuid cast on user_id WHERE clause (VARCHAR column)
- Fix pagination underflow panic when total_pages is 0
- Add missing 'images' and 'play' icons to SVG icon registry
- Add day/month/year grouping toggle with localStorage persistence
- Improve grid spacing and group header styling per mode
- Add i18n translations for grouping labels (all 9 locales)
2026-03-05 17:32:28 -05:00
Jared Wolff 69fe3a8b07 feat(photos): add EXIF metadata extraction and storage
Extract EXIF orientation, GPS coordinates, camera info, and timestamps
from uploaded images using kamadak-exif. Store metadata in a new
file_metadata PG table. Apply EXIF orientation to thumbnail generation
so images display correctly. Add /api/files/{id}/metadata endpoint.
2026-03-05 17:32:28 -05:00
Dionisio f2d35ca792 feat: auto-persist JWT secret, remove setup token requirement
- JWT secret auto-generates and persists to <STORAGE_PATH>/.jwt_secret
- Remove setup token: first admin setup is open until system initialized
- Fix schema.sql: move CREATE EXTENSION pg_trgm/ltree to top
- Update login UI and auth.js to remove setup token fields
2026-03-05 22:12:53 +01:00
zjean 190527edfb style: apply rustfmt formatting to fix CI
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 21:28:51 +01:00
zjean 54eedf5483 feat(nextcloud): add Nextcloud-compatible API layer
Implement a complete Nextcloud client compatibility layer so that
Nextcloud desktop/mobile sync clients can connect to OxiCloud.

Key additions:
- Login Flow v2 (device auth) with OIDC bridge support
- WebDAV handler compatible with Nextcloud clients (PROPFIND, GET,
  PUT, DELETE, MKCOL, MOVE, COPY, HEAD, PROPPATCH)
- OCS API endpoints (user info, capabilities, notifications stubs,
  sharees, unified search)
- Basic Auth middleware with app password verification, account
  lockout integration, and blake3-keyed auth cache
- App password management: create, list, revoke via both native
  API (JWT-authenticated profile page) and Nextcloud OCS endpoints
- Nextcloud file ID mapping (oc:fileid) with persistent DB storage
- Chunked upload support (Nextcloud v2 chunking protocol)
- Trashbin WebDAV interface
- Avatar (SVG placeholder) and preview (redirect) handlers
- User profile page with app password management UI
- URL user validation on all DAV routes (403 on mismatch)
- Database schema for app_passwords and nextcloud_object_ids tables

All services are behind a `nextcloud.enabled` config flag and
cleanly separated under src/interfaces/nextcloud/.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 20:46:07 +01:00
Jared Wolff cf6d87d805 fix(thumbnails): resolve blob path instead of logical path for thumbnail generation
Since the blob storage migration (3c7c16f), thumbnail generation failed
with "No such file or directory" because the handler constructed logical
file paths that don't exist on disk. Resolve the actual blob path via
get_blob_hash() + dedup_service.blob_path() in both upload and get
thumbnail handlers. Add regression tests.
2026-03-05 13:43:06 -05:00
Dionisio 33cfb0faef fix: security audit — patch vulnerabilities V-02 through V-16
- V-02: XSS via innerHTML in profile.js — wrap err.message in escapeHtml()
- V-03: IDOR upload to other users' folders — add folder ownership check
- V-04: IDOR create folders in other users' trees — add parent ownership check
- V-06: Content-Disposition header injection — RFC 5987 percent-encoding
- V-08: WebDAV MOVE/COPY destination without ownership — add assert_owner checks
- V-09: .gitignore missing cert/key patterns — add *.pem, *.key, *.p12, etc.
- V-11: Username accepts XSS payloads — restrict to [a-zA-Z0-9._-]
- V-12: Minimal email validation — reject forbidden chars, require domain dot
- V-13: admin_reset_password doesn't invalidate sessions — revoke all sessions
- V-14: Rate limiting bypassable via X-Forwarded-For — gate behind OXICLOUD_TRUST_PROXY_HEADERS
- V-15: Cookie Secure flag off by default — default to true (safe-by-default)
- V-16: LIKE wildcard injection in searches — add like_escape() helper across 9 sites
2026-03-05 14:52:11 +01:00
Dionisio b503e08384 security: fix vulnerabilities 1-7 from security audit
- Fix #1: Share handler IDOR - enforce owner check on share operations
- Fix #2: list_files_query IDOR - bind folder queries to authenticated user
- Fix #3: Dedup handler IDOR - restrict dedup operations to file owner
- Fix #4: Trash handler OptionalAuthUser - require full AuthUser
- Fix #5: Error info leakage - sanitize 500 error responses
- Fix #6: Chunked upload IDOR - bind upload sessions to user_id,
  add verify_session_owner() check on all session operations
- Fix #7: CSP unsafe-inline removal - migrate all inline scripts,
  styles and event handlers to external files, tighten CSP to
  script-src 'self'; style-src 'self'

New files:
  - static/js/core/theme-init.js (render-blocking theme init)
  - static/js/core/sw-register.js (service worker registration)
  - static/css/views/device-verify.css (extracted inline styles)
  - static/js/views/device-verify/device-verify.js (extracted inline script)
2026-03-05 13:15:34 +01:00
Dionisio fdbb2bf60a fix(security): patch critical IDOR & auth vulnerabilities
- Fix logout no-op: extract refresh token from cookie/body (auth_handler)
- Secure all 12 WebDAV handlers with AuthUser + resolve_path_for_user
- Secure all 7 batch handlers with caller_id ownership checks
- Add _owned variants: copy_file_owned, delete_file_owned, get_file_stream_owned, get_folder_owned
- Secure list_files_query: add AuthUser, SQL-level user_id filter, tenant-isolated ETag
- Remove deprecated unscoped resolve_path() and exists() from PathResolverService
- Remove dead list_files handler (unmounted, no auth)
- Add list_files_for_owner (SQL) and list_files_owned across trait chain
2026-03-05 10:30:39 +01:00
Diocrafts ee86c3a128 fix: resolve all clippy warnings and convert integration_tests to custom cfg
- Add type aliases (FileRow, FolderRow, FolderRowPaginated, FolderRowOptUser) to reduce type complexity
- Simplify redundant closures in app_password_handler and webdav_handler
- Remove needless borrow in auth_handler
- Collapse nested if/let chains in login_lockout, webdav_lock, auth, rate_limit
- Box LockEntry in acquire() Err variant to fix large enum variant warning
- Rename DeviceCodeStatus::from_str to parse to avoid should_implement_trait lint
- Add #[allow(clippy::too_many_arguments)] and #[allow(clippy::result_unit_err)] where appropriate
- Convert integration_tests from cargo feature to custom cfg attribute
- Add check-cfg lint config in Cargo.toml for integration_tests cfg
2026-03-04 23:55:08 +01:00
Dionisio 4a60fdc984 fix(security): IDOR protection for file operations
Adds ownership verification at repository, service, and handler layers
for download, rename, move, and delete file operations.

- Repository: get_file_for_owner() with AND user_id= SQL filter
- Service: _owned() methods with verify_owner() fail-closed guard
- Handlers: require AuthUser, delegate to _owned() methods
- Tests: 10 IDOR protection tests (all passing)
- Cleanup: remove dead OptionalUserId import, gate broken pre-existing
  test modules behind integration_tests feature flag
2026-03-04 17:18:39 +01:00
Claude 78c983136f perf: use Arc<str> for repetitive DTO fields to eliminate clone allocations
Replace String with Arc<str> for fields that contain repeated static
values (mime_type, icon_class, icon_special_class, category) in
FileDto, FolderDto, and OptimizedFileContent.

These fields are computed from ~40 static lookup tables and cloned
on every request. With Arc<str>, clone becomes O(1) atomic increment
instead of O(n) heap allocation — saving thousands of allocations/s
under load.

Fields kept as String: id, name, path, folder_id, owner_id
(unique per item, rarely cloned).

Zero API impact — serde serializes Arc<str> identically to String.

https://claude.ai/code/session_01EbAFEfyJNLRmJHmmYDX3Tt
2026-03-03 15:55:15 +00:00
Claude 1b49135ca9 perf: replace dyn trait objects with concrete types to eliminate vtable overhead
Remove async-trait dependency and use native Rust async fn in traits.
Replace Arc<dyn Trait> with Arc<ConcreteType> throughout the codebase
to enable monomorphization and eliminate dynamic dispatch overhead.

Key changes:
- Remove write-behind cache (no implementation existed)
- Fix should_transcode static method call
- Use ContactStorageAdapter directly instead of dyn AddressBookUseCase
- Clean up unused trait imports across services and DI

https://claude.ai/code/session_01EbAFEfyJNLRmJHmmYDX3Tt
2026-03-03 15:36:42 +00:00
Dionisio 641b6853ad perf: add socket2 TCP_NODELAY + socket tuning for low-latency responses
- Replace basic TcpListener::bind with socket2 tuned socket
- TCP_NODELAY: disable Nagle's algorithm (-5 to 40ms latency on small responses)
- SO_REUSEADDR: port available immediately after server restart
- SO_REUSEPORT: ready for multi-worker scaling (Linux)
- TCP_KEEPALIVE: detect dead connections within 60s/10s interval
- listen(2048): high backlog for WebDAV connection bursts
- Eliminate redundant create_dir_all calls from upload hot path
2026-03-02 00:12:33 +01:00
Dionisio e2fb29ea60 perf: replace SHA-256 with BLAKE3 + add mimalloc global allocator
- Replace SHA-256 with BLAKE3 (~5x faster) for content-addressable hashing
  in dedup_service, file_handler, file_upload_service, chunked_upload_service
- Add mimalloc as global allocator for 10-30% throughput improvement
- sha2 crate retained only for PKCE (OAuth2 standard requirement)
- BLAKE3 produces 64-char hex hashes (same format), no DB schema changes needed
2026-03-01 21:47:39 +01:00
Dionisio 5a1959bf23 perf: eliminate Vec<u8> buffer paths — all uploads now stream to disk
Issue #4 (HIGH): save_file(Vec<u8>) and update_file_content(Vec<u8>)
accepted up to 10 MB of contiguous memory per request. While the main
upload paths already used streaming, the WebDAV compat methods
(create_file, update_file) and the empty-file handler still used the
buffered path, creating a .to_vec() copy.

Changes:
- FileWritePort trait: remove save_file(Vec<u8>) and
  update_file_content(Vec<u8>) — only streaming variants remain
- FileUploadUseCase trait: remove upload_file(Vec<u8>)
- file_upload_service.rs: create_file() and update_file() now spool
  &[u8] to NamedTempFile + Sha256::digest, then delegate to streaming
  path (save_file_from_temp / update_file_streaming)
- file_handler.rs: empty file uploads use upload_file_streaming with
- FileBlobWriteRepository: remove save_file and update_file_content impls
- StubFileWritePort, StubFileUploadUseCase, MockFileRepository: remove
  corresponding dead method impls

Impact: impossible to accidentally use a buffered upload path. All
content goes through streaming with ~256 KB peak RAM. -166 LOC.
2026-02-25 23:41:16 +01:00
Dionisio 97cf6402e2 fix: resolve all clippy warnings and enforce cargo fmt
- display_helpers: convert module doc-comments to regular comments,
  merge identical text/markdown + text/ branches
- search_service: replace needless range loops with slice-based pagination
- folder_repository, folder_db_repository: collapse nested if statements
- favorites_pg_repository: remove unnecessary borrow on generic arg
- file_blob_read_repository: collapse 6 nested if-let blocks
- file_blob_write_repository: collapse nested if for dedup ref decrement
- chunked_upload_service: use div_ceil(), collapse 2 nested if blocks
- folder_handler: collapse nested if-let for owner check
- webdav_handler: replace 7x io::Error::new(ErrorKind::Other, ..) with
  io::Error::other(..)
- cargo fmt applied to all files

Passes: cargo clippy --all-targets --all-features -- -D warnings
2026-02-25 10:28:34 +01:00
Dionisio 7786871d6c feat: P1 — audio/video modal player + MIME detection via magic bytes (infer)
Backend:
- Add infer crate for magic-byte MIME detection (<1μs per file)
- New src/common/mime_detect.rs: refine_content_type() with priority
  magic bytes > extension > client Content-Type
- Inject MIME refinement in file upload handler (after spool to temp)
- Inject MIME refinement in chunked upload handler (after assembly)

Frontend:
- Extend isViewableFile() to include audio/* and video/*
- Add createMediaViewer() to InlineViewer with <audio>/<video> controls
- Blob URL pattern for authenticated streaming playback
- Graceful fallback for unsupported codecs (error message + download)
- CSS: video player, audio wrapper with animated icon, responsive
2026-02-24 23:15:10 +01:00
Dionisio 28966ce28e optimize folder search: SQL-level filtering, user isolation, no in-memory filter; batch cascade trigger 2026-02-24 17:15:36 +01:00
Dionisio 71c2cb5edb perf: Arc<AppState>, streaming PROPFIND, spawn_blocking SHA-256
- Issue #4: Wrap AppState in Arc — eliminates 42 Arc::clone + 16 String::clone per request
- Issue #2: Reject Depth:infinity with 403 + streaming XML with paginated DB queries
- Issue #5: Move chunked upload assembly (SHA-256 hash-on-write) to spawn_blocking
- Remove ~270 lines dead code from di.rs (unused builders, Default impl, stubs)
- Clean up unused tokio imports in chunked_upload_service.rs
2026-02-24 15:11:56 +01:00
Dionisio cba34056dc perf: remove HTTP cache middleware, add service-level ETags
- Delete cache.rs middleware that buffered entire response bodies (up to 10MB)
  in RAM on every cache miss, defeating streaming and causing memory spikes
- Also buffered non-GET responses unnecessarily via response_map_body()
- Add lightweight ETag support (based on max modified_at + count) to:
  - FolderHandler::list_folder_listing (combined folder+files endpoint)
  - FileHandler::list_files_query (file listing endpoint)
- Both support If-None-Match / 304 Not Modified without any body buffering
- File downloads already had ETag/304 support at handler level
- Service-level caches (FileContentCache, SearchService, ThumbnailService)
  remain unchanged — they handle caching without HTTP body materialization
2026-02-24 09:52:22 +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 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 0be7ef8c0b style: fix clippy collapsible_if + cargo fmt 2026-02-15 18:04:32 +01:00
Dionisio 1ed20f425f perf: Phase 4+5 optimizations — uploads 10x, downloads 2x, concurrent 2x. moka cache, 512KB buffers, remove sync_all, hash-on-write, preloaded queries, bench.sh v3, gitignore storage/. 500MB upload 12.6s->1.3s (392MB/s). RSS 69-113MB, 0 swap. 2026-02-15 17:56:47 +01:00
Dionisio 3f60765d9a fix(#104): enforce storage quota on uploads & fix usage tracking
Backend:
- Add QuotaExceeded error kind mapped to HTTP 507 Insufficient Storage
- Add check_storage_quota() and get_user_storage_info() to StorageUsagePort
- Enforce quota in upload_file_with_cache, upload_file_with_thumbnails (AuthUser extractor)
- Enforce quota in chunked upload create_upload handler
- Add update_user_storage_usage_by_username() for username-based lookup
- Fix extract_username_from_path() to handle subfolders (take first segment only)
- Fix maybe_update_storage_usage() to use username-based lookup instead of passing
  username to get_user_by_id (which always failed silently)

Frontend:
- Parse and display quota error messages on upload failure (507 / QuotaExceeded)
- Stop remaining uploads when quota is exceeded
- Call refreshUserData() after uploads to update storage usage display
- Bump service worker cache to v9
2026-02-14 10:34:07 +01:00
Dionisio 4c98c5a657 style: apply cargo fmt to entire codebase
Standardize code formatting across all 173 Rust source files
using rustfmt. No functional changes - purely cosmetic.

This establishes a consistent code style baseline for the
project going forward.
2026-02-14 01:29:34 +01:00
Dionisio 67137a3ef2 refactor: apply clippy auto-fixes (162 warnings resolved)
- Fix needless borrows and references
- Collapse nested if statements
- Replace manual strip_prefix with str::strip_prefix()
- Remove redundant closures in map/unwrap_or_else
- Use Iterator::next_back() instead of rev().next()
- Simplify map_or patterns
- Use std::io::Error::other() instead of new(ErrorKind::Other, ..)
- Use div_ceil() instead of manual ceiling division
- Consolidate format! string arguments
- Various other idiomatic Rust improvements

39 files changed, 220 insertions(+), 320 deletions(-)
2026-02-14 01:26:02 +01:00
Dionisio 5c53d94c0c feat: fix favorites display and add star indicator on favorited items
- Add ?metadata=true support to GET /api/files/{id} to return JSON metadata
  instead of binary content (was the root cause of favorites not loading)
- Fix favorites loadFileDetails to use metadata endpoint with auth headers
- Add star icon on favorited files/folders in grid view (top-left corner)
- Add star icon on favorited files/folders in list view (next to name)
- Refresh file view when toggling favorites so star appears/disappears
- Add CSS styles for .favorite-star and .favorite-star-inline
2026-02-13 09:32:16 +01:00