Replace String with Arc<str> for etag and content_type fields in the
content cache. String::clone() allocates and copies the full string on
every cache hit (O(n)), while Arc<str>::clone() is O(1) — just an atomic
ref-count increment.
This eliminates 2 heap allocations per cache hit on the hottest download
path. At 1000 req/s that is 2000 fewer alloc/dealloc cycles per second.
Changed files:
- cache_ports.rs: trait signatures String → Arc<str>
- file_content_cache.rs: CacheEntry fields, get/put methods, tests
- stubs.rs: StubContentCachePort signatures
- file_retrieval_service.rs: caller creates Arc<str> before put()
Replace BFS traversal that issued 2 SQL queries per folder (list_files +
list_folders) with 2 total queries using PostgreSQL ltree <@ operator:
1. list_subtree_folders: single GiST-indexed scan for all folders
2. list_files_in_subtree: single GiST-indexed join for all files
Files are grouped by folder_id in a HashMap, then iterated in directory
order (folders pre-sorted by path from SQL).
Changes across 4 architecture layers:
- Domain: FolderRepository::list_subtree_folders (default impl)
- Application ports: FolderUseCase, FileRetrievalUseCase, FileReadPort
- Application services: FolderService, FileRetrievalService passthroughs
- Infrastructure: PG implementations + ZipService rewrite
Query count: O(N) → O(1). Latency for 100-folder tree: ~200 round-trips → 3.
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)
- 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)
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>
- 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
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
Remove legacy abstractions that are no longer used after the
100% blob storage model migration (#113):
- IdMappingPort trait from application/ports/outbound.rs
- storage_mediator.rs module (StorageMediator trait + impls)
- StorageMediator impl from PathService
- write_behind_cache.rs (FS-based, incompatible with blob model)
The WriteBehindCachePort trait and Optional fields in services
are preserved for potential future blob-compatible caching.
-968 lines of dead code removed. Build clean, RC=0.
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.
get_authorize_url() was synchronous and fell back to constructing
{issuer}/authorize when the discovery cache was empty. This produced
incorrect URLs for providers like Keycloak whose authorization
endpoint is {issuer}/protocol/openid-connect/auth.
Made get_authorize_url() async so it can call get_discovery() to
fetch the real authorization_endpoint from .well-known/openid-configuration
before the first redirect. The discovery document is cached after the
initial fetch.
Security fixes for OIDC authentication flow:
1. CSRF state validation (High): State nonce is now stored server-side
and validated on callback (single-use, 600s TTL)
2. PKCE S256 (Medium): code_challenge/code_verifier pair generated per
RFC 9126, sent in authorize URL and token exchange
3. Nonce in ID token (Medium): Random nonce included in authorize URL,
verified against ID token claims to prevent token replay
4. Secure token delivery (Medium): Tokens no longer in URL fragments.
One-time exchange code redirected to frontend, tokens retrieved via
POST /api/auth/oidc/exchange endpoint (60s TTL, single-use)
5. Registration guard (Low): POST /api/auth/register returns 403 when
disable_password_login is active in OIDC-only mode
- Remove Serialize/Deserialize from File, Folder, Session, User, Contact entities
- Create contact_persistence_dto.rs for JSONB persistence in infrastructure layer
- Update contact_pg_repository to use persistence DTOs
- Fix dependency on zip crate (downgrade from 7.2.0 to 2.1.0)
- Fix unused variable warnings in main.rs
- Move PathService import from domain to infrastructure
- Add missing fields to CoreServices and RepositoryServices
- Create proper service initialization in main.rs
Clean Architecture improvements:
- Domain layer no longer depends on serde framework
- Persistence concerns isolated to infrastructure layer
- TokenClaims in auth_service.rs is only exception (required for JWT)