Run seeds only once (#475)
## 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>
This commit is contained in:
parent
c381b86b5e
commit
f8a3cc4c47
6 changed files with 78 additions and 37 deletions
|
|
@ -6,8 +6,8 @@ defmodule Mv.Release do
|
|||
## Tasks
|
||||
|
||||
- `migrate/0` - Runs all pending Ecto migrations.
|
||||
- `run_seeds/0` - Runs bootstrap seeds (fee types, custom fields, roles, settings).
|
||||
In production, set `RUN_DEV_SEEDS=true` to also run dev seeds (members, groups, sample data).
|
||||
- `bootstrap_seeds_applied?/0` - Returns whether bootstrap was already applied (admin user exists). Used to skip re-running seeds.
|
||||
- `run_seeds/0` - If bootstrap already applied, skips; otherwise runs bootstrap seeds (fee types, custom fields, roles, settings). Set `FORCE_SEEDS=true` to re-run seeds even when already applied. In production, set `RUN_DEV_SEEDS=true` to also run dev seeds (members, groups, sample data).
|
||||
- `seed_admin/0` - Ensures an admin user exists from ENV (ADMIN_EMAIL, ADMIN_PASSWORD
|
||||
or ADMIN_PASSWORD_FILE). Idempotent; can be run on every deployment or via shell
|
||||
to update the admin password without redeploying.
|
||||
|
|
@ -19,6 +19,7 @@ defmodule Mv.Release do
|
|||
alias Mv.Authorization.Role
|
||||
|
||||
require Ash.Query
|
||||
require Logger
|
||||
|
||||
def migrate do
|
||||
load_app()
|
||||
|
|
@ -28,13 +29,37 @@ defmodule Mv.Release do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns whether bootstrap seeds have already been applied (admin user exists).
|
||||
|
||||
We check for the admin user (from ADMIN_EMAIL or default), not the Admin role,
|
||||
because migrations may create the Admin role for the system actor. Only seeds
|
||||
create the admin (login) user. Used to skip re-running seeds on subsequent starts.
|
||||
Call only when the application is already started.
|
||||
"""
|
||||
def bootstrap_seeds_applied? do
|
||||
admin_email = get_env("ADMIN_EMAIL", "admin@localhost")
|
||||
|
||||
case User
|
||||
|> Ash.Query.filter(email == ^admin_email)
|
||||
|> Ash.read_one(authorize?: false, domain: Mv.Accounts) do
|
||||
{:ok, %User{}} -> true
|
||||
_ -> false
|
||||
end
|
||||
rescue
|
||||
e ->
|
||||
Logger.warning("Could not check seed status (#{inspect(e)}), assuming not applied.")
|
||||
false
|
||||
end
|
||||
|
||||
@doc """
|
||||
Runs seed scripts so the database has required bootstrap data (and optionally dev data).
|
||||
|
||||
- Always runs bootstrap seeds (fee types, custom fields, roles, system user, settings).
|
||||
- If `RUN_DEV_SEEDS` env is set to `"true"`, also runs dev seeds (members, groups, sample data).
|
||||
- Skips if bootstrap was already applied (admin user exists); set `FORCE_SEEDS=true` to override and re-run.
|
||||
- If `RUN_DEV_SEEDS` env is set to `"true"`, also runs dev seeds (members, groups, sample data)
|
||||
when bootstrap is run.
|
||||
|
||||
Uses paths from the application's priv dir so it works in releases (no Mix). Idempotent.
|
||||
Uses paths from the application's priv dir so it works in releases (no Mix).
|
||||
"""
|
||||
def run_seeds do
|
||||
case Application.ensure_all_started(@app) do
|
||||
|
|
@ -42,23 +67,27 @@ defmodule Mv.Release do
|
|||
{:error, {app, reason}} -> raise "Failed to start #{inspect(app)}: #{inspect(reason)}"
|
||||
end
|
||||
|
||||
priv = :code.priv_dir(@app)
|
||||
bootstrap_path = Path.join(priv, "repo/seeds_bootstrap.exs")
|
||||
dev_path = Path.join(priv, "repo/seeds_dev.exs")
|
||||
if bootstrap_seeds_applied?() and System.get_env("FORCE_SEEDS") != "true" do
|
||||
IO.puts("Seeds already applied. Skipping. (Set FORCE_SEEDS=true to override)")
|
||||
else
|
||||
priv = :code.priv_dir(@app)
|
||||
bootstrap_path = Path.join(priv, "repo/seeds_bootstrap.exs")
|
||||
dev_path = Path.join(priv, "repo/seeds_dev.exs")
|
||||
|
||||
prev = Code.compiler_options()
|
||||
Code.compiler_options(ignore_module_conflict: true)
|
||||
prev = Code.compiler_options()
|
||||
Code.compiler_options(ignore_module_conflict: true)
|
||||
|
||||
try do
|
||||
Code.eval_file(bootstrap_path)
|
||||
IO.puts("✅ Bootstrap seeds completed.")
|
||||
try do
|
||||
Code.eval_file(bootstrap_path)
|
||||
IO.puts("✅ Bootstrap seeds completed.")
|
||||
|
||||
if System.get_env("RUN_DEV_SEEDS") == "true" do
|
||||
Code.eval_file(dev_path)
|
||||
IO.puts("✅ Dev seeds completed.")
|
||||
if System.get_env("RUN_DEV_SEEDS") == "true" do
|
||||
Code.eval_file(dev_path)
|
||||
IO.puts("✅ Dev seeds completed.")
|
||||
end
|
||||
after
|
||||
Code.compiler_options(prev)
|
||||
end
|
||||
after
|
||||
Code.compiler_options(prev)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue