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>
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.
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
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
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.
- 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
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.
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>
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.
- 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
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.
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.
- 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)
- 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
The Nextcloud integration added duplicate /api/auth/app-passwords
handlers that only accepted Bearer tokens, breaking cookie-authenticated
browser sessions (profile page). Remove the duplicates and mount the
original app_password_handler routes which use CurrentUser from the auth
middleware, supporting all auth methods (cookie, Bearer, Basic).
- 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
Update share_service test impl to match upstream trait changes
(requester_id params, verify_shared_link_password returns ShareDto).
Fix map_or, collapsible_if, dead_code, too_many_arguments warnings.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Allow async_fn_in_trait lint crate-wide (internal project, 413 warnings)
- Add integration_tests feature to Cargo.toml to fix unexpected cfg warnings
- Collapse nested if statements into single conditions (13 locations)
- Add type_complexity allows on pg repository functions (12 locations)
- Fix dead code warnings in test modules with allow attributes
- Fix E0599 by gating new_stub() for integration_tests feature
- Add result_unit_err and result_large_err allows where appropriate
- Apply rustfmt formatting
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Nextcloud app uploads sent application/octet-stream as Content-Type,
causing images to not be recognized. Now both WebDAV PUT and chunked
upload paths call refine_content_type() which detects via magic bytes,
then extension, then falls back to the client header. Also fixes
update_file() which previously hardcoded application/octet-stream.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
admin_guard() only checked Authorization: Bearer header, ignoring the
oxicloud_access HttpOnly cookie used by browser sessions. All admin
endpoints (dashboard, OIDC settings, etc.) returned 401 for logged-in
users. Fall back to cookie_auth::extract_cookie_value() when no Bearer
token is present.
The protected auth routes (/me, /change-password, /logout) were merged
with public routes in auth_handler.rs but never had auth middleware
applied in main.rs — so the CurrentUserId extractor always failed with
401. Split auth_routes() into auth_public_routes() and
auth_protected_routes(), applying auth + CSRF middleware to the latter.
Also added credentials: 'same-origin' to all 13 fetch calls in admin.js
so the browser sends HttpOnly auth cookies with requests.
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.
1. Share password bypass (HIGH): enforce password check in get_shared_link_by_token,
verify_shared_link_password now returns ShareDto only on correct password.
2. WebDAV MOVE ownership (MEDIUM): add assert_owner on destination parent folder
for file moves in both PathResolver and legacy branches.
3. Path traversal defense-in-depth (LOW): add reject_path_traversal() to WebDAV,
CalDAV, and CardDAV handlers rejecting '..' segments at HTTP boundary.
4. Setup race condition (LOW): atomic INSERT ... ON CONFLICT DO NOTHING in
try_claim_initialization prevents duplicate admin creation.
- 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
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
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
- Rate limit login (5/min), register (3/hr), refresh (10/min) per IP
- Account lockout after 5 consecutive failed logins (15 min cooldown)
- Fix stored XSS in admin panel (escapeHtml on all user-controlled data)
- All limits configurable via OXICLOUD_RATE_LIMIT_* / OXICLOUD_LOCKOUT_* env vars
- Zero new dependencies (uses existing moka crate for in-memory caches)
- Includes unit tests for lockout service
Replace push_str(&format!(...)) pattern in generate_full_calendar_ical,
generate_event_ical, and generate_vevent with direct write!() into a
pre-sized String buffer.
Before: ~5N+1 heap allocations for N events (temporary Strings created
by format!(), copied into the main buffer, then dropped).
After: 1 allocation (the initial String::with_capacity). All write!()
calls format directly into the destination buffer with zero intermediate
Strings.
Replace the double-query pattern (get_folder_by_path + get_file_by_path)
across PROPFIND, HEAD, DELETE, MOVE, and COPY handlers with a single
UNION ALL query via PathResolverService.
PG Append node short-circuits on LIMIT 1: if the folder branch matches,
the file branch is never executed. Cuts WebDAV path resolution from
2 round-trips to 1 per request.
Also adds an exists() method using EXISTS subqueries for the Overwrite
header checks in MOVE/COPY (avoids constructing full DTOs).
Legacy double-query fallback retained when PathResolver is unavailable.
The redirect middleware was a leftover from the custom HTTP server → Axum
migration. It executed on every single request (including static files),
allocating a String from the URI path and performing 3 starts_with() checks,
but never actually redirected anything — just debug logging.
- Remove middleware application from main.rs
- Remove pub mod redirect from middleware/mod.rs
- Delete redirect.rs (121 lines of dead code)
Saves ~80-170ns of overhead per request (String alloc + comparisons +
Tower layer dispatch).
Root cause: XML namespace prefixes (e.g. "D", "C") were not being
resolved to their actual namespace URIs (e.g. "DAV:",
"urn:ietf:params:xml:ns:caldav") during PROPFIND parsing. This caused
all property match arms to fall through to the catch-all, producing
empty XML elements.
Changes:
- Add namespace-aware XML parsing (collect_ns_decls + resolve_name) to
WebDavAdapter, used by all DAV protocol parsers (WebDAV, CalDAV,
CardDAV)
- Add /.well-known/caldav -> /caldav/ redirect (RFC 6764)
- Add root /caldav/ PROPFIND response with current-user-principal and
calendar-home-set discovery properties
- Add /caldav/principals/{username}/ PROPFIND handler (was 500 error)
- Add /caldav/{username}/ user calendar home handler (calendar-home-set
target)
- Respect Depth header at root: depth 0 returns only root entry, depth
1+ includes calendar children
- Fix pre-existing TRANSCODE_POOL_THREADS test compilation error
- Add 8 new tests covering namespace resolution, discovery properties,
and principal responses
https://claude.ai/code/session_01T49VBJSimgo28APxbucHzq