Skip to content

feat(#539): accept agent.id on PATCH /opportunity/{id} to re-link agent#540

Merged
arturasmckwcz merged 2 commits into
developfrom
539-accept-agent-reference-agentid-on-patch-opportunityid
May 19, 2026
Merged

feat(#539): accept agent.id on PATCH /opportunity/{id} to re-link agent#540
arturasmckwcz merged 2 commits into
developfrom
539-accept-agent-reference-agentid-on-patch-opportunityid

Conversation

@arturasmckwcz
Copy link
Copy Markdown
Collaborator

Closes #539.

Summary

  • PATCH /opportunity/{id} now accepts { agent: { id: <existing_id> } } as an explicit re-link reference; the opportunity's agentId is updated to point at the existing agent without mutating the agent's name/address/district.
  • The legacy { name, address, district } shape still works for in-place edits (recommendation per the issue: deprecate once the frontend cuts over).
  • When agent.id is present, it wins — other fields on agent are ignored.
  • Swagger (/swagger/json) now reflects id on ApiVolunteerOpportunityPatch.agent.

Implementation

  • src/server/schema/sdk-types.json — added id: integer to ApiVolunteerOpportunityPatch.agent.
  • src/services/dto/parser-opportunity-patch-data.ts — new local types OpportunityAgentPatchBody / OpportunityPatchBody (the SDK type is stale on agent shape); parser surfaces agentLinkId and skips the in-place agent patch when an id is sent.
  • src/server/routes/opportunity/opportunity.routes.ts — when agentLinkId is set, verify the agent exists (400 if not) and update opportunity.agentId; otherwise fall back to the existing in-place agent patch.
  • src/test/services/dto/parser-opportunity-patch-data.test.ts — 3 new parser tests (relink, legacy in-place edit, id-wins).

Test plan

  • yarn typecheck
  • yarn test:run (161/161 pass)
  • Manual: PATCH /opportunity/<id> with { "agent": { "id": <other_agent_id> } } → 200; subsequent GET shows the new agent inlined; the previously-linked agent record is untouched.
  • Manual: PATCH /opportunity/<id> with { "agent": { "id": 99999999 } } → 400 Agent (id:99999999) not found.
  • Manual: PATCH /opportunity/<id> with the legacy { "agent": { "name": "..." } } shape still patches the title in place.
  • Confirm Swagger UI exposes the new id field on the agent body.

🤖 Generated with Claude Code

Resolve agent.id to an existing agent and update opportunity.agentId
instead of mutating the agent record. Legacy { name, address, district }
shape still works for in-place edits; id wins when both are sent.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@arturasmckwcz arturasmckwcz linked an issue May 19, 2026 that may be closed by this pull request
Copy link
Copy Markdown
Collaborator

@nadavosa nadavosa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall: looks good to merge. Logic is correct, backward-compatible, and well-tested.


One real issue — address and district silently ignored in in-place edit:

The parser only maps agentBody.name → title. The address and district fields exist in OpportunityAgentPatchBody but are never applied to the Agent patch:

```ts
agent: agentBody && agentLinkId === undefined
? ({ title: agentBody.name } as Partial) // address, district dropped
: {},
```

This was the same before this PR, so it is pre-existing — but since we are touching this code it is worth calling out. A coordinator editing the RAC address via the form today would see their change silently lost. Worth a follow-up issue if not fixing here.


Minor observations:

  • { agentId: agentLinkId } as Partial<Opportunity> — the cast is needed because TypeORM's partial entity type does not expose FK columns directly. Fine as-is, just worth a comment.
  • The OpportunityStatusType enum reformatting in sdk-types.json is cosmetic and harmless.
  • The local OpportunityPatchBody workaround for the stale SDK type is acceptable short-term — should go into a future SDK update.
  • All 3 parser test cases are exactly the right ones to have (relink, legacy, id-wins).

Verdict: Approve with the note that the silent address/district drop should be tracked in a follow-up issue. The relink path is correct and production-safe.

opportunity.id,
);
if (!success) {
throw new Error("Relinking opportunity agent failed.");
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

throwing new Error returns 500 which is semantically wrong, isn't it? 400 would be more appropriate. wouldn't use custom BadRequestError be more to the point? or maybe NotFoundError?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e020286: missing referenced agent now throws NotFoundError (404), and a failed relink update throws BadRequestError (400). No more generic 500.

fastify.patch<{
Params: ParamsId;
Body: ApiOpportunityPatch;
Body: OpportunityPatchBody;
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand that JSON schema used by fastify is the main contract, but SDK lifts shared structures to transpilation phase which is worth bothering IMHO

let's do ApiOpportunityPatch refactoring instead!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed and done. SDK 0.0.86 adds agent.id (and deprecates name/address/district) on ApiOpportunityPatch. Bumped the dependency, removed the local OpportunityPatchBody workaround, and the route now types its body as ApiOpportunityPatch again.

opportunity: opportunityObj,
contact,
agent,
agentLinkId,
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why move agent id outside agent object? wouldn't agent.id do the thing?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. Removed the hoisted agentLinkId from the parser; the route now reads request.body.agent?.id directly. The parser just suppresses the in-place agent patch when an id is present.

- bump need4deed-sdk to 0.0.86 (agent.id now in ApiOpportunityPatch)
- drop local OpportunityPatchBody workaround; route uses ApiOpportunityPatch
- read agent.id directly instead of hoisting agentLinkId out of the parser
- return NotFoundError when the referenced agent is missing and
  BadRequestError on relink failure (was a generic 500)
- inline the agent id on GET /opportunity/:id so the picker can preselect

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@arturasmckwcz
Copy link
Copy Markdown
Collaborator Author

Thanks for the review @nadavosa. The address/district silent-drop is tracked as a follow-up in #542 (predates this PR; the SDK now marks those fields @deprecated). The three inline comments are addressed in e020286 — replied on each thread.

Copy link
Copy Markdown
Contributor

@need4deed need4deed left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved — review comments addressed, tests/typecheck/lint green. Follow-up tracked in #542.

@arturasmckwcz arturasmckwcz merged commit 7fba78e into develop May 19, 2026
@arturasmckwcz arturasmckwcz deleted the 539-accept-agent-reference-agentid-on-patch-opportunityid branch May 19, 2026 22:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Accept agent reference (agent.id) on PATCH /opportunity/{id}

3 participants