Keep form-based invitation screens open on account creation failures so users can see errors without replaying the welcome animation.
Centralize join form template context, preserve theming across invite routes, and add regression coverage for validation and E2E flows.
When multiple users accept the same unlimited invite code, the user lookup
after a successful join used `filter_by(code=...).first()`, which always
returned the first user who ever used that code on the server — not the
user who just signed up. Subsequent users were never inserted into
invitation_users, so the home page card only ever showed the first acceptee.
Fix: look up the newly created user by username (unique per server) so each
acceptance is correctly attributed.
Add a regression test that asserts all users appear in invite.users after
multiple acceptances of an unlimited invite.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Commit 73c29458f renamed library_id/limit to _library_id/_limit to
suppress ARG002 warnings, but the widget caller passes them as keyword
arguments (limit=6), causing a silent TypeError that returns empty data.
Closes#1207
- Fix 55 test failures caused by missing request contexts and incorrect
session_transaction() usage across 8 test files
- Fix ruff import sorting errors and unused imports
- Fix 122 type errors: rename method override parameters to match base
classes, add None guards for fetchone()/datetime, widen dict type
annotations, add type: ignore for SQLAlchemy stub limitations
- Add [tool.ty.rules] config to suppress unsupported-base warnings
- Fix _ variable shadowing gettext in wizard routes
- Add noqa: ARG002 for unused method arguments required by base class
- Fix SUBTREE→BASE scope in _fetch_user_attributes (security)
- Remove is_ldap_user threading from all 8 media client interfaces;
set flag after join via workflow-level User.query.update instead
- Use find_user_dn (search-based) instead of build_user_dn (concatenation)
in delete_user and reset_user_password for robustness
- Wrap all user-facing strings in _() for i18n in ldap_auth.py
- Fix username fallback from hardcoded "wizarr" to unique per-user
- Return failure from _update_existing_user when password update fails
- Replace dynamic Tailwind classes with explicit conditionals in alert
- Add stale group cleanup and upsert in sync_ldap_groups
- Remove fake @ldap.local email fallback in user_sync
Jellyfin and Emby collectors were recording RunTimeTicks (total file
runtime) as the session duration, causing severe overestimation when
users stopped partway through long titles.
- JellyfinCollector._emit_session_event: on session_end, use position_ms
(last known playback position) as duration_ms; fall back to
raw_duration_ms only when position_ms is unavailable or zero.
- EmbyCollector._emit_session_event: identical fix (shared API shape).
- JellyfinHistoricalImporter._process_item: for partially-watched items
(UserData.Played == false) use PlaybackPositionTicks; keep RunTimeTicks
only for fully-played items where position has already been reset.
- historical_duration_source metadata field now accurately reflects
which source was used (runtime_ticks, playback_position_ticks, or
runtime_ticks_fallback).
Plex was already correct via elapsed-time tracking in SessionManager.
- Change EmbyClient.libraries() to return {Id: Name} instead of {Guid: Name}
so Library.external_id stores the value Emby's EnabledFolders policy field
expects, matching what JellyfinClient._set_specific_folders() resolves.
- Change EmbyClient.scan_libraries() to return {Name: Id} instead of
{Name: Guid} for the same reason.
- Override _set_specific_folders() in EmbyClient with a mapping that includes
{Name: Id}, {Id: Id}, and {Guid: Id} entries, providing backwards
compatibility for existing Library records that still hold a Guid.
- In both EmbyClient._set_specific_folders() and the inherited
JellyfinClient._set_specific_folders(), when the input names list is
non-empty but all lookups fail (folder_ids ends up empty), log a structured
WARNING via structlog and set EnableAllFolders: False with an empty
EnabledFolders list — restricting access to nothing — rather than silently
falling back to EnableAllFolders: True which would grant full access.
- Replace inline debug logging.info() calls with structlog structured events.
When a user who already has a Plex share uses a new invite link, the
"already sharing" error from the Plex API is now handled gracefully by
calling update_user_libraries() and update_user_permissions() instead of
propagating the error to the end-user. The route layer also switches to
a generic, translatable user-facing error message backed by structlog for
admin-visible diagnostics.
Security:
- Fix XSS in HTMX responses by replacing f-string HTML with Jinja partials
- Fix TLS verify_cert not being applied (add Tls config to ldap3 Server)
- Fix password_hash NOT NULL conflict for LDAP-only admin accounts
- Fix should_create_ldap_user defaulting to True for existing invitations
Correctness:
- Fix DN reconstruction fragility using escape_rdn for special characters
- Fix connection leaks with try/finally in all LDAPClient methods
- Fix sync interval logic (check FLASK_ENV not WIZARR_ENABLE_SCHEDULER)
- Only register LDAP sync job when LDAP is actually configured
- Make find_user_dn and service_connection public API methods
Cleanup:
- Squash 5 incremental LDAP migrations into single 20251226_add_ldap_support
- Remove duplicate imports/queries in auth routes
- Replace f-string logging with %s formatting
- Extract inline HTML to Jinja template partials
When server rows exist but have no per-server expiry, the global
invitation.expires was ignored, causing expired invitations to be
treated as valid.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This fixes a long-standing issue where scanning a library would cause an
unintended cascade delete of the invite_library table and would reset
the associated invite's permissions.
Introduces Telegram as a notification agent, including backend logic, form fields, and database columns for bot token and chat ID. Updates creation and editing flows, notification service, and UI to support Telegram integration. Includes a migration to add the new fields to the Notification model.
- Added a preview link for bundles in the wizard settings page. closes#1048
- Enhanced the wizard steps template to handle bundle preview mode.
- Updated JavaScript to generate URLs for bundle preview and runtime modes.
- Improved test coverage for password reset token generation by asserting token creation.
- A new API endpoint for requesting a password reset token, which can be accessed either through an API key or an authenticated session.
- A public route for handling the password reset process using the generated token.
- Updates media client to allow password resets.
This fixes a critical issue where Gunicorn workers would fail to start
after upgrading to v2025.11.0, causing containers to show as unhealthy
with only the uv wrapper process running and no actual workers.
Root Cause:
-----------
In v2025.11.0, library scanning and session recovery were added to the
create_app() function, which runs during EVERY app creation including:
1. During 'flask db upgrade' (migrations)
2. During Gunicorn master when_ready() hook
3. During each Gunicorn worker spawn
The migration 20251103_properly_fix_foreign_keys recreates 4 database
tables with CASCADE foreign keys using raw SQL. This holds exclusive
database locks during table recreation.
When library scanning and session recovery try to query these tables
during migration, they hit database locks, creating a race condition
that causes workers to timeout and crash during startup.
Fix:
----
- Skip library scanning during migrations (FLASK_SKIP_SCHEDULER=true)
- Skip activity monitoring/session recovery during migrations
- Make Gunicorn log level configurable (GUNICORN_LOG_LEVEL env var)
- Add worker lifecycle hooks for better crash debugging
- Increase healthcheck start period from 10s to 60s
- Increase Gunicorn worker timeout from 30s to 120s
Testing:
--------
- Verified app starts successfully with FLASK_SKIP_SCHEDULER=true
- Verified library scanning runs normally without the flag
- Confirmed 0.38s startup during migrations vs 1.61s normal startup
Closes#976