Why
Follow-up from the epic #922 and
from review of need4deed-org/fe#955 (volunteer
self-registration, closes need4deed-org/fe#954).
POST /user (src/server/routes/user.ts, ~lines 293-350) only looks up an
existing Person when the request body explicitly passes person.id.
When person.id is omitted it unconditionally does new Person(personData)
— even when a Person with that exact email already exists, e.g. via an
existing Volunteer row created through the legacy public form.
This directly undermines the point of this epic: after we invite today's
Volunteer backlog to link up with real accounts (#920 / #921), anyone who
instead self-registers straight through /register/volunteer (fe#955) can
still create a brand-new, disconnected Person — splitting one human across
an orphaned Volunteer profile and a separate fresh account, with nothing
surfacing the collision to them or to a coordinator.
Scope
- When
POST /user's body omits person.id, look up an existing Person
by the account email (case-insensitively) before falling through to
new Person(personData).
- If found, link the new
User to that existing Person — mirror the
person.id branch's "backfill the email only if the person had none,
never overwrite" semantics — instead of creating a duplicate.
- Decide the collision-handling behavior during TODO review: if the matched
Person already has a User (of any role), does registration get
rejected (409, mirroring the existing email-uniqueness ConflictError)
or is a second User role allowed against the same Person? Default to
the conservative option — reject and point the person at login/support —
unless there's a concrete case for allowing multiple roles per person.
- Applies to every self-registration entry point that hits
POST /user
without a person.id (today: agent registration, and now volunteer
registration from fe#955) — fix once, in the shared route, not per-flow.
Acceptance criteria
Why
Follow-up from the epic #922 and
from review of need4deed-org/fe#955 (volunteer
self-registration, closes need4deed-org/fe#954).
POST /user(src/server/routes/user.ts, ~lines 293-350) only looks up anexisting
Personwhen the request body explicitly passesperson.id.When
person.idis omitted it unconditionally doesnew Person(personData)— even when a
Personwith that exact email already exists, e.g. via anexisting
Volunteerrow created through the legacy public form.This directly undermines the point of this epic: after we invite today's
Volunteerbacklog to link up with real accounts (#920 / #921), anyone whoinstead self-registers straight through
/register/volunteer(fe#955) canstill create a brand-new, disconnected
Person— splitting one human acrossan orphaned
Volunteerprofile and a separate fresh account, with nothingsurfacing the collision to them or to a coordinator.
Scope
POST /user's body omitsperson.id, look up an existingPersonby the account email (case-insensitively) before falling through to
new Person(personData).Userto that existingPerson— mirror theperson.idbranch's "backfill the email only if the person had none,never overwrite" semantics — instead of creating a duplicate.
Personalready has aUser(of any role), does registration getrejected (409, mirroring the existing email-uniqueness
ConflictError)or is a second
Userrole allowed against the samePerson? Default tothe conservative option — reject and point the person at login/support —
unless there's a concrete case for allowing multiple roles per person.
POST /userwithout a
person.id(today: agent registration, and now volunteerregistration from fe#955) — fix once, in the shared route, not per-flow.
Acceptance criteria
Person(e.g. froman existing
Volunteerrow) links to thatPersoninstead ofcreating a duplicate
Personalready has aUser(anyrole) is explicit and tested, not accidental
emails