Skip to content

chore: one-off script — invite existing volunteers without a user account #921

Description

@arturasmckwcz

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/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).
  • For each eligible volunteer: create the placeholder User (per
    feat: invite flow — mint invite tokens for volunteers, accept-invite endpoint #920 design) and call
    fastify.notify.sendVolunteerInvite(...).
  • 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
  • Re-running the script sends no duplicate invites

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    choreChore tasks, housekeeping, etc.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions