Commit Graph

467 Commits

Author SHA1 Message Date
Dionisio Pozo 628707e884 Merge pull request #228 from BillionClaw/clawoss/fix/thumbnail-memory-optimization
fix(thumbnails): drop encoded image data after decoding to reduce memory
2026-03-18 09:39:20 +01:00
BillionClaw 9794bba975 fix(thumbnails): drop encoded image data after decoding to reduce memory usage
Explicitly drop the encoded image buffer after decoding and extracting
EXIF orientation data. This reduces peak memory consumption during
thumbnail generation by the size of the original file.

The encoded data is no longer needed once the image is decoded into
a DynamicImage, but it was being held in memory until the end of the
spawn_blocking scope.

Fixes excessive memory consumption in thumbnail generation.
2026-03-18 11:51:39 +08:00
BillionClaw 5cf0266d24 fix(auth): cap admin initial quota to available disk space
The admin_create_user method was using hardcoded quota values (100GB for
admin, 1GB for user) instead of the capped_quota method that checks
available disk space. This could result in setting a quota higher than
the actual available disk space.

Fixes #92
2026-03-18 11:06:39 +08:00
Dionisio Pozo 4b62499b8f Merge pull request #225 from BillionClaw/clawoss/fix/copy-error-messages
fix: improve error messages for file/folder already exists
2026-03-17 10:07:41 +01:00
BillionClaw aebd51788b fix: improve error messages for file/folder already exists 2026-03-17 17:05:52 +08:00
Dionisio Pozo 775abab8e1 Merge pull request #224 from BillionClaw/clawoss/fix/file-move-folder-ownership
fix(file): verify target folder ownership on move
2026-03-17 09:57:28 +01:00
BillionClaw 30d6e53fca fix(file): verify target folder ownership on move
When moving a file to a folder, verify that the caller owns the target
folder. Without this check, a file could be moved to another user's
folder, causing the file to "disappear" from the original user's view
since file listings filter by user_id.

- Add folder_repo to FileManagementService
- Add verify_target_folder_owner() method
- Call it in move_file_owned() before moving
2026-03-17 16:53:55 +08:00
Dionisio Pozo 79902bf727 Merge pull request #223 from BillionClaw/clawoss/fix/shared-link-404
fix(share): correct shared link URL to include /api prefix
2026-03-17 09:50:15 +01:00
BillionClaw 852db02cd0 fix(share): correct shared link URL to include /api prefix
The shared link URL was generated as {base_url}/s/{token} but the
API endpoint is actually at /api/s/{token}. This caused 404 errors
when users accessed shared links.

Fixes #101
2026-03-17 16:48:11 +08:00
BillionClaw 3c48aa1e6f fix(webdav): preserve correct status codes for file rename/move failures
When file or folder rename/move operations failed in WebDAV handlers,
all errors were incorrectly converted to HTTP 500 Internal Server Error
using AppError::internal_error(). This masked specific error types:

- AlreadyExists errors should return 409 CONFLICT
- NotFound errors should return 404 NOT FOUND
- AccessDenied errors should return 403 FORBIDDEN

Changed error handling to use AppError::from() which preserves the
original DomainError type and maps to appropriate HTTP status codes.

Fixes handling of incorrect status code for file rename failure.
2026-03-17 16:29:21 +08:00
Dionisio Pozo d1699357d8 Merge pull request #221 from BillionClaw/clawoss/fix/trash-view-rendering
fix(trash): add missing display fields to TrashedItemDto
2026-03-17 09:12:28 +01:00
BillionClaw 32c5630a5f fix(trash): add missing display fields to TrashedItemDto
Add category, icon_class, and icon_special_class fields to TrashedItemDto
to fix trash view rendering issues. The frontend expects these fields to
properly display file type information, icons, and deletion dates.

- category: Human-readable file category (e.g., "Image", "Document")
- icon_class: FontAwesome icon class for the file type
- icon_special_class: Special CSS class for icon styling

Fixes #107
2026-03-17 16:08:12 +08:00
BillionClaw ec09136836 fix(webdav): enforce storage quota on WebDAV PUT uploads
Adds storage quota checking to the WebDAV PUT handler, which was missing
while present in other upload handlers (regular upload and chunked upload).

The quota check happens after the file is spooled to a temp file (so we
know the exact size) but before it's moved to permanent storage. If the
quota is exceeded, the temp file is cleaned up and a 507 Insufficient
Storage error is returned.

Fixes #104
2026-03-17 16:04:20 +08:00
BillionClaw 665220dd0b fix(files): batch folder deletion fails
Add debug logging to diagnose batch trash failures for folders.
When batch trash operations fail, the specific error was not being
logged, making it difficult to diagnose issues like #124.

- Add tracing::debug! logs for failed file and folder trash operations
  in batch_operations.rs
- Add unit tests for batch operation result handling to verify
  correct success/failure counting

Fixes #124
2026-03-17 11:21:16 +08:00
BillionClaw 2daee68d20 fix(auth): resolve CSP blocking and session refresh loop
Fix two issues causing login loop after successful admin setup:

1. CSP blocking inline styles: The frontend JavaScript dynamically sets
   inline styles (e.g., element.style.display = 'none') for UI state
   management. The CSP header only allowed 'self' for style-src, blocking
   these dynamic styles. Added 'unsafe-inline' to style-src directive.

2. Session refresh 401 errors: The cookie Secure flag defaulted to true
   when OXICLOUD_BASE_URL was not set, causing cookies to not be sent
   over HTTP in Docker deployments. Changed the default to false when
   the base URL is not explicitly set to HTTPS, with clear logging to
   guide users to set OXICLOUD_COOKIE_SECURE=true for production.

Fixes #203
2026-03-17 06:14:02 +08:00
BillionClaw 0cbbb6b7ac fix: ARMv7 32-bit compilation overflow
The constant 10 * 1024 * 1024 * 1024 (10 GB) overflows on 32-bit systems
where usize is 32-bit (max ~4GB). This caused compilation failures on
ARMv7 architecture.

Fix by using architecture-appropriate limits:
- 64-bit: 10 GB (unchanged)
- 32-bit: 1 GB (safe maximum for 32-bit usize)

Fixes #206
2026-03-17 04:39:18 +08:00
BillionClaw 7645792d5a fix(calendar): change owner_id from String to Uuid type
The database schema defines owner_id as UUID, but the Calendar entity
stored it as String. This caused a type mismatch error when creating
calendars via CalDAV clients like DAVx:

  column "owner_id" is of type uuid but expression is of type text

Changes:
- Change Calendar.owner_id from String to Uuid
- Update new() and with_id() to accept Uuid
- Update owner_id() getter to return &Uuid
- Update belongs_to() to accept &Uuid
- Update calendar_storage_adapter to pass Uuid directly
- Update tests to use Uuid::new_v4()

Fixes #200
2026-03-17 02:58:34 +08:00
Jared Wolff 1fcd02a519 Fix Nextcloud sync conflict by using content-hash ETags
The Nextcloud Android client compares ETags before and after upload to
verify its write landed. OxiCloud was returning the stable file UUID as
the ETag, which never changed on content updates, causing false
SYNC_CONFLICT errors on every upload.

Five fixes applied:
1. Thread blob_hash (SHA-256) through File entity, FileDto, all read/write
   queries, and all WebDAV/PROPFIND responses as the ETag — changes on
   every content update, no DB migration needed.
2. Honor X-OC-Mtime header: parse the client-supplied mtime and use it
   for updated_at via COALESCE(to_timestamp($n), NOW()).
3. Disable phantom checksum capability (preferredUploadType/supportedTypes)
   that the server never actually implemented, stopping retry loops.
4. Add nc:creation_time and nc:upload_time to PROPFIND responses.
5. Return oc-etag header in chunked upload MOVE (assemble) responses.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 14:14:24 -04:00
Julian Merkle 85663ef050 calendar.rs: Allow RGBA colors
The android app "DAVx" creates calendars with RGBA color codes.

fixes #199
2026-03-14 15:48:11 +01:00
Jared Wolff 036390a242 feat: add SQL migration system using sqlx::migrate!()
Replace manual schema.sql application with sqlx's built-in migration
system. Migrations are embedded at compile time and tracked in the
_sqlx_migrations table. Pending migrations run automatically on startup.

- Move db/schema.sql → migrations/20260307000000_initial_schema.sql
- Remove apply_schema() and split_sql_statements() from db.rs
- Add run_migrations() using sqlx::migrate!() macro
- Remove docker-compose schema.sql mount (app handles it now)
- Enable sqlx "migrate" feature in Cargo.toml

Future schema changes: add a new timestamped .sql in migrations/.

Closes #190

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 15:30:52 -04: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 df336da679 feat(frontend): i18n expansion, admin/profile i18n, grid/list view fix, empty state
- Add 5 new locales (hi, ar, ru, ja, ko) — now 14 total
- Admin panel: 117 i18n keys, confirm modal, animated tabs, no inline handlers
- Profile page: 58 i18n keys with data-i18n attributes
- Fix i18n safeT() shadowing bug and translationsLoaded timing
- Fix grid/list view: list header no longer shows in grid mode on login
- Fix classList.toggle hidden sync for view switching across all nav functions
- Revert .hidden important that broke login page rendering
- Add files empty state (no_files + empty_hint) with translations
- Fix language selector dropdown scroll and styling
- Fix admin panel scroll with sticky tabs
2026-03-09 00:08:34 +01:00
Diocrafts f409a9edd7 perf(frontend): add build.rs asset pipeline with oxc + lightningcss
- Bundle 31 JS files → single app.{hash}.js (oxc minifier)
- Bundle 36 CSS files → single app.{hash}.css (lightningcss)
- Resolve CSS @import chains at build time
- Inline theme-init.js to eliminate render-blocking script
- Minify all individual JS/CSS/JSON assets in static-dist/
- Auto-update Service Worker cache manifest with bundle hashes
- FNV hash-based cache-busting filenames

Results: 88 → 12 requests, 640 kB → 96.5 kB transferred (-85%)

Build modes:
- Debug: copies HTML to OUT_DIR, serves original static/
- Release: generates static-dist/ with processed assets

Also:
- Update Dockerfile to include build.rs in cacher stage
- Serve static-dist/ in release Docker builds
- Remove host static/ bind mount from docker-compose
- Switch include_str!() to OUT_DIR for all HTML pages
2026-03-08 13:10:38 +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 05108d3e12 perf: denormalize media_sort_date + pre-scale video thumbs
Schema (media_sort_date denormalization):
- Add media_sort_date column to storage.files with DEFAULT created_at
- Add trigger sync_media_sort_date: when file_metadata is upserted,
  copies COALESCE(captured_at, created_at) into files.media_sort_date
- Add partial index idx_files_media_timeline on (user_id, media_sort_date DESC)
  WHERE NOT is_trashed AND media type -- enables Index Scan + Limit (no Sort)
- copy_folder_tree now copies media_sort_date for copied files
- Remove dead idx_file_metadata_captured (no longer needed)

Query optimization (list_media_files):
- Rewrite to use fi.media_sort_date instead of COALESCE(fm.captured_at,...)
- Eliminates LEFT JOIN file_metadata -- one fewer table touch
- Plan: Limit to Index Scan O(LIMIT) instead of Sort O(N)

Video thumbnail pre-scaling (client + server):
- JS: pre-scale canvas to max 400px before toBlob -- 22x less RAM, 15x less BW
- Rust: fast-path in store_external_thumbnail -- if payload is already
  WebP with dims within max_dim, store as-is (zero decode, zero encode)
2026-03-07 20:12:31 +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 661c9cb688 fix: delete thumbnails when files are permanently deleted
- Inject ThumbnailService into TrashService and FileManagementService
- Call delete_thumbnails() after permanent file deletion in:
  - TrashService::delete_permanently (single item)
  - TrashService::empty_trash (bulk: collects file IDs first)
  - FileManagementService::delete_file
  - FileManagementService::delete_with_cleanup (fallback path)
- All thumbnail cleanup is best-effort (warn on failure, never blocks)
- Prevents orphaned thumbnail files from accumulating on disk
2026-03-07 19:15:36 +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 03c051f9d8 fix: add blob: to CSP frame-src and media-src for PDF/video viewing
- frame-src: add 'blob:' (wildcard * only matches network schemes)
- media-src: new directive with 'self' blob: (was falling back to default-src)
- Fixes: PDFs blocked in iframe, videos blocked by media-src fallback
2026-03-07 18:17:35 +01:00
Diocrafts 6b02c10942 perf: complete storage-layer UUID migration — eliminate UUID↔String round-trips
- Domain: Folder.owner_id + File.owner_id → Option<Uuid> (was Option<String>)
- Traits: folder_repository signatures accept Uuid directly (was &str)
- Infra: remove all fi.user_id::text / fo.user_id::text SQL casts in repos
- Infra: remove $N::uuid bind casts (columns are native UUID now)
- Services: eliminate to_string() bridge variables in folder_service,
  search_service, trash_service
- DTOs: boundary conversion to String only at API edge (file_dto, folder_dto)
- Ports: default impls compare Option<Uuid> directly
- Stubs: updated signatures to match trait

13 files changed across domain/application/infrastructure layers.
Compiles clean (cargo check passes).
2026-03-07 18:05:52 +01:00
Diocrafts cc98c5b0da fix: add ::uuid/::text SQL casts in storage repos for UUID columns
- folder_db_repository: cast user_id to ::text in SELECT, ::uuid in INSERT/WHERE
- file_blob_write_repository: cast ::uuid in INSERT for user_id column
- file_blob_read_repository: bind Uuid natively instead of .to_string()

Fixes runtime errors:
  - column user_id is of type uuid but expression is of type text
  - operator does not exist: uuid = text
2026-03-07 15:17:15 +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
Diocrafts 9f08460027 perf: OnceLock for env var, Arc<CurrentUser> in auth, pre-compute query lowercase
- rate_limit: cache OXICLOUD_TRUST_PROXY_HEADERS in OnceLock<bool> to avoid
  syscall on every request (~500ns → ~1ns)
- auth middleware: insert Arc<CurrentUser> instead of bare CurrentUser;
  all 5 extractors now clone Arc (~1ns) instead of 4 Strings (~60-100ns)
- search_service: pre-compute query.to_lowercase() once before loops,
  eliminating N redundant heap allocations per search
2026-03-07 11:23:56 +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
Dionisio 42b242204c perf: enable HTTP compression (gzip + Brotli) with smart predicate
- Apply CompressionLayer globally with content-type filtering
- Compress: JSON, XML, HTML, CSS, JS (60-80% bandwidth savings)
- Skip: images, video, audio, PDF, ZIP, gzip, tar, octet-stream
- Min threshold 256 bytes to avoid CPU waste on tiny responses
- Compatible with future reverse proxy (Content-Encoding passthrough)
2026-03-06 22:59:48 +01:00
Dionisio fc78dcd35d perf: eliminate Vec<char> allocation in split_sql_statements
- Replace sql.chars().collect::<Vec<char>>() with direct byte-level
  iteration over sql.as_bytes()
- Saves ~140 KB heap allocation for 35 KB schema (4× input size)
- Remove unused _start variable
- SQL is ASCII-safe so byte comparison is sufficient for all delimiters
2026-03-06 22:44:04 +01:00
Dionisio 55936699ce perf: zero-alloc File → FileDto via into_parts() ownership transfer
- Add FileParts struct and File::into_parts() to yield owned fields
- Rewrite From<File> for FileDto to move id, name, path, folder_id,
  owner_id by ownership instead of .to_string() copies
- Eliminates ~5 heap allocations per file (5000 saved per 1000-file listing)
2026-03-06 22:37:31 +01:00
Dionisio d4a7cb0414 perf: replace blocking Path::exists() with async try_exists() in dedup hot path
- store_bytes: blob_path.exists() → fs::try_exists(&blob_path).await
- store_from_file: blob_path.exists() → fs::try_exists(&blob_path).await (2 sites)
- Prevents synchronous stat() syscalls from blocking Tokio worker threads
  during concurrent uploads
2026-03-06 22:27:43 +01:00
Dionisio 3d4156673c perf: use blake3 mmap_rayon for file hashing — zero heap allocation
- Replace std::fs::read() + update_rayon() with update_mmap_rayon()
  for file hashing, eliminating full-file heap allocation (500MB file
  no longer needs 500MB of RAM to hash)
- Enable blake3 'mmap' feature in Cargo.toml
- Lower hash_bytes rayon threshold from 10MB to 128KB
- Remove dead constants HASH_BLOCK_SIZE and RAYON_HASH_THRESHOLD
2026-03-06 22:14:43 +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 4617c1c1df fix(ui): fix list view column spacing, rubber-band selection, and clippy warnings
Add column-gap to list header/items, prevent rubber-band selection from
swallowing clicks on list headers and file items, preserve rubber-band
selection after mouseup, and extract type aliases to resolve clippy
type_complexity warnings.
2026-03-05 21:36:14 -05:00
Jared Wolff d8eecbd9ca fix(nc): enable Nextcloud Android app connectivity and uploads
- Add /remote.php/dav discovery endpoint for Android app server detection
- Add /index.php/204 connectivity check endpoint (returns 204 No Content)
- Redirect login flow to nc:// deep link for mobile credential delivery
- Support GET/HEAD on folders (NC clients use as existence checks)
- Recursive MKCOL to create missing parent directories
- Fix single-file PROPFIND returning empty multistatus response
- Strip instance suffix from preview fileId (e.g. "00000326ocnca")
- Add recommendations stub endpoint
2026-03-05 20:42:54 -05: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 53e4f5afe6 feat(photos): add Photos timeline view with lightbox and infinite scroll
Backend: new GET /api/photos endpoint with cursor-based pagination that
queries image/video files sorted by EXIF captured_at (falling back to
created_at), joining file_metadata for sort dates.

Frontend: dense photo grid grouped by day with lazy-loaded thumbnails,
IntersectionObserver infinite scroll, multi-select with batch
download/delete, and a full-screen lightbox with prev/next navigation,
EXIF metadata display, and download/favorite/delete toolbar.

Includes navigation wiring, CSS (with dark theme), and i18n translations
for 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
Jared Wolff cea7665a43 fix(storage): handle cross-device rename and MKCOL on existing folders
- dedup_service: fall back to copy+delete when rename() fails with
  EXDEV (os error 18), which occurs when temp and blob dirs are on
  different filesystems
- NC webdav_handler: return 405 instead of 500 when MKCOL targets an
  existing folder (RFC 4918 §9.3.1)
2026-03-05 17:32:21 -05:00
Jared Wolff f730f576e7 fix(webdav): translate paths for all operations and fix MKCOL recursive creation
- Add resolve_webdav_path() helper that prepends user's home folder
  prefix to raw WebDAV paths before routing to handlers
- Rewrite handle_mkcol to walk path segments, creating folders with
  proper parent_id chain instead of relying on path-only lookup
- Fix double path stripping in update_file_streaming where
  get_parent_folder_id received an already-stripped parent path
  instead of the full file path it expects

Fixes #165
2026-03-05 17:20:07 -05:00