When a username like 'christianlouis.de' (containing a dot) was submitted
on the signup page, FastAPI returned a 422 with detail as an array of
Pydantic validation error objects. The JS code assigned that array directly
to `this.error`, causing Alpine.js x-text to render '[object Object]'.
Two fixes applied in signup.html:
1. Client-side validation: check username length and pattern in submit()
before the API call, with clear human-readable error messages.
2. Server error handling: detect when data.detail is an Array and extract
each entry's .msg field, joining them into a readable string.
Also adds a regression test to confirm the 422 response format for an
invalid username (with dot) includes a list detail with msg fields.
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
Address code review feedback:
- Extract normalizeFileUri to mobile/src/utils/normalizeUri.ts
- Import shared function in ShareContext and UploadScreen
- Move os import to top of test file
- Update test docstring to reflect new behavior
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
- Move duplicate check before task enqueue in ui_upload endpoint
- Clean up temp file and return status "duplicate" for exact duplicates
- Add URI-level dedup guard in mobile UploadScreen to prevent repeated uploads
- Improve ShareContext URI normalization (collapse slashes, decode percent-encoding)
- Guard +not-found.tsx effect against re-firing for the same pathname
- Update mobile UploadResponse type and handlers for duplicate status
- Update web frontend upload.js to show duplicate status
- Update API and Configuration docs
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
Merge origin/main into branch, resolving conflict in app/database.py.
Combined improvements from both branches:
- Keep pool_pre_ping=True and structured variable approach from feature branch
- Add explicit QueuePool import and poolclass assignment from main
Override require_upload_rate_limit with a no-op in the test client
fixture so that upload-heavy test suites (test_file_upload.py) are not
rejected with 429 Too Many Requests when Redis is available in CI.
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
Introduces a Redis-backed sliding-window rate limiter for upload
endpoints (/api/ui-upload, /api/process-url) that:
- Enforces per-user limits (default: 20 uploads / 60 s)
- Dynamically reduces limits under system stress (queue depth, CPU load)
- Returns 429 with Retry-After header when exceeded
- Fails open when Redis is unavailable
- Works with the existing client-side adaptive back-off
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
SQLite engines now use NullPool instead of QueuePool, eliminating the
"QueuePool limit of size 5 overflow 10 reached" TimeoutError under
concurrent load. PostgreSQL/MySQL engines use a configurable QueuePool
with sensible defaults (pool_size=10, max_overflow=20) exposed via
DB_POOL_SIZE, DB_MAX_OVERFLOW, DB_POOL_TIMEOUT, DB_POOL_RECYCLE env
vars. pool_pre_ping is enabled on all backends.
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
The QR login page countdown timer compared the server's UTC expiration
timestamp against the client's local clock, causing the QR code to appear
immediately expired when the client clock was ahead of the server.
Changes:
- Add ttl_seconds field to CreateChallengeResponse (seconds until expiry)
- Frontend countdown now uses relative elapsed time since response was
received, eliminating clock-skew issues
- Mobile app: replace alert-only QR button with actual camera-based
QR code scanner using expo-camera
- Add QRScannerScreen with barcode scanning, permission handling, and
scan area overlay
- Update camera permission description to mention QR code scanning
- Add tests for ttl_seconds computation
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
- Add path traversal guard in reimport file copy loop
- Improve error log message context for table wipe failures
- Use conditional role=alert/status on result banner for accessibility
- Make test assertions more specific (exact status codes)
- Rename ambiguous view test
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
- 21 tests covering unit, integration, API, and view layers
- Update ConfigurationGuide.md with System Reset section
- Update API.md with system reset endpoint docs
- All tests pass, ruff clean
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
Merge origin/main into copilot/add-sharepoint-integration.
All four conflicts were resolved by keeping both the SharePoint
additions (from this branch) and the iCloud additions (from main):
- app/models.py: added both SHAREPOINT and ICLOUD to IntegrationType
- app/tasks/send_to_all.py: added both to service_map and services list
- app/tasks/upload_to_user_integration.py: kept both upload handlers
- frontend/templates/files.html: added both filter options
No database migration conflicts — SharePoint does not require schema changes.
The per-user notification functions (notify_user_document_processed /
notify_user_document_failed) were defined but never called from the
document processing pipeline.
- Call notify_user_document_processed in finalize_document_storage
when owner_id is available (creates in-app + email/webhook notifications)
- Add _dispatch_user_failure_notification helper to celery_app.py that
extracts file_id from failed task args and dispatches
notify_user_document_failed for document pipeline tasks
- Add comprehensive tests for both success and failure notification paths
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
The tests were patching `app.api.url_upload.requests.get` but the module
uses `httpx.AsyncClient`. Updated 4 tests across 2 files to use the
correct `httpx.AsyncClient.stream` mock pattern with `AsyncMock`,
matching the existing working tests in test_url_upload.py.
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
get_current_owner_id() only checked the session for authenticated users.
When the mobile app sends Authorization: Bearer <token>, there is no
session cookie, so the Depends(_get_owner_id) dependency raised HTTP 401
before the @require_login wrapper could resolve the Bearer token.
The function now checks three sources in order:
1. Session user dict (existing behavior)
2. request.state.api_token_user (cached by require_login or prior call)
3. Direct Bearer token resolution via _resolve_bearer_user (new)
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
Added a new test file `tests/test_api_saved_searches.py` containing a comprehensive test suite for the CRUD operations provided in `app/api/saved_searches.py`. The suite validates happy paths, error conditions (like missing filters, name limits, duplicates), and user isolation using an in-memory SQLite database.
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
Replaces the synchronous `requests.post` and `requests.get` calls in
`app/api/onedrive.py:test_onedrive_token` with an asynchronous
`httpx.AsyncClient` implementation. This unblocks the FastAPI event loop
when this endpoint is hit.
Fixed unused requests import in `app/api/onedrive.py` and sorted imports in the testing files updated previously to adhere to the repository formatting (`ruff check --fix`). Tests in `test_api_onedrive_extended.py` were also migrated to use AsyncMock properly.
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>