## Description of the implemented changes
The changes were:
- [ ] Bugfixing
- [x] New Feature
- [ ] Breaking Change
- [x] Refactoring
**Seeds run only on first startup.** On every application start (e.g. `just run`, Docker entrypoint), seed scripts are still invoked, but they exit immediately when the admin user already exists. This avoids duplicate seed data (e.g. join requests), keeps startup fast after the first run, and works the same in dev and production.
## What has been changed?
- **`lib/mv/release.ex`**
- Added `bootstrap_seeds_applied?/0`: returns whether the admin user (from `ADMIN_EMAIL` or default `admin@localhost`) exists. We check the admin *user*, not the Admin *role*, so we do not skip when only migrations have run (migrations can create the Admin role for the system actor).
- `run_seeds/0`: if `bootstrap_seeds_applied?()` is true, prints “Seeds already applied (admin user exists). Skipping.” and returns without running bootstrap or dev seeds; otherwise unchanged behaviour.
- Module docs updated for the new function and the skip behaviour.
- **`priv/repo/seeds.exs`**
- Ensures the app is started (`Application.ensure_all_started(:mv)`).
- If `Mv.Release.bootstrap_seeds_applied?()` is true, prints the same skip message and does not run bootstrap or dev seeds; otherwise runs as before (bootstrap + dev seeds in dev/test).
- Comment at the top updated to describe the skip behaviour.
- **Documentation**
- `CODE_GUIDELINES.md` §1.2.1: seeds run on every start but exit early when already applied; mentions `bootstrap_seeds_applied?/0`.
- `docs/admin-bootstrap-and-oidc-role-sync.md`: run_seeds skips when admin user exists; description of `run_seeds/0` updated.
- `CHANGELOG.md` [Unreleased]: new “Seeds run only when needed” entry under Changed.
## Definition of Done
### Code Quality
- [x] No new technical depths
- [x] Linting passed
- [x] Documentation is added where needed
### Accessibility
- [x] New elements are properly defined with html-tags *(no new UI)*
- [x] Colour contrast follows WCAG criteria *(no new UI)*
- [x] Aria labels are added when needed *(no new UI)*
- [x] Everything is accessible by keyboard *(no new UI)*
- [x] Tab-Order is comprehensible *(no new UI)*
- [x] All interactive elements have a visible focus *(no new UI)*
### Testing
- [x] Tests for new code are written *(existing seeds and release tests cover behaviour; idempotency test still passes when second run skips)*
- [x] All tests pass
- [x] axe-core dev tools show no critical or major issues *(no UI changes)*
## Additional Notes
- **Review focus:** Logic in `Mv.Release` and `priv/repo/seeds.exs`; the “already applied” check is a single DB read for the admin user. On failure (e.g. DB down), `bootstrap_seeds_applied?/0` returns `false`, so seeds run (safe for first deploy).
- **Suggested check:** Run `mix test test/seeds_test.exs test/mv/release_test.exs` to confirm seeds and release behaviour.
Reviewed-on: #475
Co-authored-by: Simon <s.thiessen@local-it.org>
Co-committed-by: Simon <s.thiessen@local-it.org>
## Description of the implemented changes
The changes were:
- [x] Bugfixing
- [x] New Feature
- [ ] Breaking Change
- [x] Refactoring
**OIDC-only mode improvements and UX tweaks (success toasts, unauthenticated redirect).**
## What has been changed?
### OIDC-only mode (new feature)
- **Admin settings:** "Only OIDC sign-in" is an immediate toggle at the top of the OIDC section (no save button). Enabling it also turns off "Allow direct registration". When OIDC-only is on, the registration checkbox is disabled and shows a tooltip (DaisyUI `<.tooltip>`).
- **Backend:** Password sign-in is forbidden via Ash policy (`OidcOnlyActive` check). Password registration is blocked via validation `OidcOnlyBlocksPasswordRegistration`. New plug `OidcOnlySignInRedirect`: when OIDC-only and OIDC are configured, GET `/sign-in` redirects to the OIDC flow; GET `/auth/user/password/sign_in_with_token` is rejected with redirect + flash. `AuthController.success/4` also rejects password sign-in when OIDC-only.
- **Tests:** GlobalSettingsLive (OIDC-only UI), AuthController (redirect and password sign-in rejection), User authentication (register_with_password blocked when OIDC-only).
### UX / behaviour (no new feature flag)
- **Success toasts:** Success flash messages auto-dismiss after 5 seconds via JS hook `FlashAutoDismiss` and optional `auto_clear_ms` on `<.flash>` (used for success in root layout and `flash_group`).
- **Unauthenticated users:** Redirect to sign-in without the "You don't have permission to access this page" flash; that message is only shown to logged-in users who lack access. Logic in `LiveHelpers` and `CheckPagePermission` plug; test updated accordingly.
### Other
- Layouts: comment about unprocessed join-request count no longer uses "TODO" (Credo).
- Gettext: German translation for "Home" (Startseite); POT/PO kept in sync.
- CHANGELOG: Unreleased section updated with the above.
## Definition of Done
### Code Quality
- [x] No new technical depths
- [x] Linting passed
- [x] Documentation is added where needed (module docs, comments where non-obvious)
### Accessibility
- [x] New elements are properly defined with html-tags (labels, aria-label on checkboxes)
- [x] Colour contrast follows WCAG criteria (unchanged)
- [x] Aria labels are added when needed (e.g. oidc-only and registration checkboxes)
- [x] Everything is accessible by keyboard (toggles and buttons unchanged)
- [x] Tab-Order is comprehensible
- [x] All interactive elements have a visible focus (existing patterns)
### Testing
- [x] Tests for new code are written (OIDC-only UI, auth controller, user auth; SMTP config builder and mailer)
- [x] All tests pass
- [ ] axe-core dev tools show no critical or major issues (not re-run for this PR; suggest spot-check on settings and sign-in)
## Additional Notes
- **OIDC-only:** When the `OIDC_ONLY` env var is set, the toggle is read-only and shows "(From OIDC_ONLY)". When OIDC is not configured, the toggle is disabled.
- **Invalidation:** Enabling OIDC-only sets `registration_enabled: false` in one update; disabling OIDC-only only updates `oidc_only` (registration left as-is).
- **Review focus:** Plug order in router (OidcOnlySignInRedirect), policy/validation order in User, and that all OIDC-only paths (form, plug, controller) stay consistent.
Reviewed-on: #474
Co-authored-by: Simon <s.thiessen@local-it.org>
Co-committed-by: Simon <s.thiessen@local-it.org>
- find_contact_by_email uses GET with filter[isExternal]=true and filter[email]
- vereinfacht_required_member_fields is now empty (API accepts minimal payload)
delete_cycles returns first error for debugging. Advisory lock key
derived from member id (first 8 bytes of UUID hex) to reduce
phash2 collision risk; fallback to phash2 on invalid UUID.
- Add oidc_* attributes to Setting, migration and Config helpers
- Secrets and OidcRoleSyncConfig read from Config (ENV overrides DB)
- GlobalSettingsLive: OIDC section with disabled fields when ENV set
- OIDC role sync tests use DataCase for DB access
- Rename AshAuthentication strategy from :oidc :rauthy to :oidc :oidc;
generated actions are now register_with_oidc / sign_in_with_oidc.
- Update config keys (:rauthy → :oidc) in dev.exs and runtime.exs.
- Update default_redirect_uri to /auth/user/oidc/callback everywhere.
- Rename Mv.Accounts helper functions accordingly.
- Update Mv.Secrets, AuthController, link_oidc_account_live and all tests.
- Update docker-compose.prod.yml, .env.example, README and docs.
IMPORTANT: OIDC providers must be updated to use the new redirect URI
/auth/user/oidc/callback instead of /auth/user/rauthy/callback.
Append membership_fee_type to column list when it is visible but
membership_fee_start_date was not in the selection (MemberExport,
export_column_order, build_export_member_fields_list).
Use Ash related-field sort (membership_fee_type.name) instead of
membership_fee_type_id so column order is alphabetical. Load
membership_fee_type when sorting by it even if column is hidden.
In-memory re-sort (Build) uses loaded fee type name.
- Receipt attrs: allowlist only (no String.to_atom on API input / DoS)
- find_contact_by_email: paginate through all pages (page[size]=100)
- Extract helpers to satisfy Credo max nesting depth