You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Part of the epic: #922. Depends on #920 (invite
issuance + accept-invite endpoint) being merged first.
This is a one-off backfill, not a recurring cron job and not a persisted
coordinator-facing feature — it clears today's backlog of already-registered
volunteers who have no login. (Explicitly decided: no POST /volunteer/:id/invite
endpoint, no dashboard button — just a script.)
Scope
One-off script — check the existing conventions for one-off data scripts
in this repo first (dev/db-update/, src/data/seeds/populate/) and follow
whichever pattern already fits, rather than inventing a new location/runner.
Query all Volunteer rows with relations: ["person", "person.users"]
(same join src/services/jobs/scan-stale-pending.ts already uses).
Eligible = has a person, person.email is set, and person.users is
empty (no linked User at all, regardless of role — don't create a second
account for someone who already has one).
Email-collision case: the eligibility check above only confirms this Person has no linked User — it does not rule out a User already
existing with that same email under a different, unrelated Person
(this can happen via the gap tracked in fix: POST /user must link to an existing Person by email instead of creating a duplicate #923). Placeholder-User
creation will then fail on the User.email uniqueness constraint. Catch
this specific case and log/report it distinctly (e.g. "skipped: email
already in use by another account") rather than lumping it into a generic
"failed" count — it means this Volunteer row needs manual reconciliation
and won't resolve itself on a re-run.
Isolate errors per-row (matching the Promise.allSettled + per-item
try/catch pattern in scan-stale-pending.ts) so one bad row doesn't abort
the whole run — log and continue.
Log a summary at the end: total volunteers scanned, already-linked
(skipped), invited, email-collision (needs reconciliation), failed.
Re-running the script must be safe — since eligibility is "no linked
User", already-invited volunteers are naturally skipped on a second run
(their placeholder User row already exists).
Acceptance criteria
Dry-run mode (or at minimum: tested against a full DB dump/staging
copy) with output reviewed before any real invite emails go out
Summary counts (scanned / skipped / invited / email-collision / failed)
are logged, with email-collisions reported distinctly from other
failures
Why
Part of the epic: #922. Depends on #920 (invite
issuance + accept-invite endpoint) being merged first.
This is a one-off backfill, not a recurring cron job and not a persisted
coordinator-facing feature — it clears today's backlog of already-registered
volunteers who have no login. (Explicitly decided: no
POST /volunteer/:id/inviteendpoint, no dashboard button — just a script.)
Scope
in this repo first (
dev/db-update/,src/data/seeds/populate/) and followwhichever pattern already fits, rather than inventing a new location/runner.
Volunteerrows withrelations: ["person", "person.users"](same join
src/services/jobs/scan-stale-pending.tsalready uses).person,person.emailis set, andperson.usersisempty (no linked
Userat all, regardless of role — don't create a secondaccount for someone who already has one).
User(perfeat: invite flow — mint invite tokens for volunteers, accept-invite endpoint #920 design) and call
fastify.notify.sendVolunteerInvite(...).Personhas no linkedUser— it does not rule out aUseralreadyexisting with that same email under a different, unrelated
Person(this can happen via the gap tracked in
fix: POST /user must link to an existing Person by email instead of creating a duplicate #923). Placeholder-
Usercreation will then fail on the
User.emailuniqueness constraint. Catchthis specific case and log/report it distinctly (e.g. "skipped: email
already in use by another account") rather than lumping it into a generic
"failed" count — it means this
Volunteerrow needs manual reconciliationand won't resolve itself on a re-run.
Promise.allSettled+ per-itemtry/catch pattern in
scan-stale-pending.ts) so one bad row doesn't abortthe whole run — log and continue.
(skipped), invited, email-collision (needs reconciliation), failed.
User", already-invited volunteers are naturally skipped on a second run
(their placeholder
Userrow already exists).Acceptance criteria
copy) with output reviewed before any real invite emails go out
are logged, with email-collisions reported distinctly from other
failures