Skip to content

feat(poracleng): move the human, location and place proxy onto /api/v2 - #829

Merged
hokiepokedad2 merged 6 commits into
developfrom
feat/v2humans
Aug 24, 2026
Merged

feat(poracleng): move the human, location and place proxy onto /api/v2#829
hokiepokedad2 merged 6 commits into
developfrom
feat/v2humans

Conversation

@hokiepokedad2

Copy link
Copy Markdown
Contributor

Merge last. Moves PoracleHumanProxy onto /api/v2, each call with a v1 fallback so nothing on 5.1.0 loses a feature.

Migrated

Get human, enable/disable, admin-disable, set-location, check-location, saved-locations list, switch-profile, GetAdminRolesAsync/v2/humans/{id}/admin-roles, and the language write → POST /v2/humans/{id}/language — which deleted IHumanService.UpdateAsync and HumanRepository.UpdateAsync outright.

Saved places: get, add, update and delete all on v2.

A capability users did not have before

PUT /v2/humans/{id}/locations/{label} has no v1 equivalent — v1 forced delete-and-re-add, which is refused while an alarm references the place. So a saved place an alarm points at simply could not be moved. It can now, behind IPlaceUpdateCapabilityService.

The gap I closed after the swarm reported

Get, add and update moved; delete did not. It was still POSTing v1's /locations/{label}/delete while its three siblings had moved — the sibling-miss pattern, in the one branch whose whole job was this surface.

The conflict body was also unreadable. Both versions answer 409 with referencing_rules, and they disagree about case: v2 tags its fields ({"type":"raid","uid":424}) while v1 serialises store.ReferencingRule, which carries no json tags at all, so Go emits {"Type":"raid","UID":424}. The old code called ToString() on each element, putting raw JSON into the list either way.

Nothing broke, because the only consumer counts entries — but the component's own spec mocked readable strings (['pokemon 7','raid 9']) the server has never produced, so the test described a world that did not exist. Entries now render as raid 424, reading both casings since v1 remains the fallback. The two tests naming a real server shape were watched failing against the previous code.

Two fixes found while in here

UserRoleResolver's degraded-caching hole closed, and its two dead isAdmin branches deleted after confirming on the wire that they never fire on either version. CLAUDE.md records four separate defects caused by that resolver's surfaces disagreeing, so this was handled carefully.

POST /api/location/places was still answering the old list shape while GET and PUT answered the new one, which blanked canEdit and removed the edit pencil from every card until reload.

What is still on v1, and why

Profile create/update/delete/copy stay on v1: v2's PATCH /profiles/{n} updates active_hours only and cannot rename, and v2's active_hours schema is broken — verified live, day: 0 returns 200 while day: 7 returns 422 expected number <= 6, against a scheduler that uses ISO Monday 1 to Sunday 7. Through v2 a Sunday schedule cannot be expressed. The official migration guide documents the wrong numbering, so this is worth reporting upstream.

Verification

Backend 2489, frontend 1402, production build clean, exercised live against both 5.1.0 and 5.2.1.

Nine operations now prefer PoracleNG's /api/v2/humans surface and keep their v1
path as a fallback, so a self-hoster on 5.1.0 sends byte-identical requests and
loses nothing. There is no version floor.

Three of them are more than a rename. The notification language stops being a
direct write to humans.language and goes through POST .../language, which also
reloads PoracleNG's in-memory state -- so a language change now takes effect on
the next alert instead of at the next restart. Saving a place with a label you
already have becomes a real 409 instead of a refusal buried inside a 200, and a
label too long for the column is refused before it is sent rather than coming
back as "database error". PUT /locations/{label} is new capability with no v1
equivalent: moving a place an alarm points at was previously impossible.

IHumanService.UpdateAsync, IHumanRepository.UpdateAsync and CreateAsync are
gone. Nothing in PoracleWeb.NET writes a humans row directly any more.
IPoracleHumanProxy.CheckLocationAsync went with them -- it had no callers.
…rs nothing"

GetAdminRolesAsync answered null for every non-2xx without throwing, so a 500 or
a 503 from PoracleNG reached UserRoleResolver as a confident "no delegated
webhooks" -- Resolved:true, cached for the full minute. That is the #656/#667
failure on the one source their fix did not cover, and it is the fifth way this
path has broken. A 404 now means PoracleNG has no such human; anything else
non-2xx throws, and the resolver reports the answer as unresolved.

The read moved to IPoracleHumanProxy, where it belongs and where it picks up the
/api/v2 route and the v1 fallback for free, and its id is now URL-encoded.

Both isAdmin branches are deleted. Neither has ever fired: both API versions
build the body from the same adminRolesResult, whose fields are channels,
webhooks and users, and v2's schema is additionalProperties:false, so an isAdmin
cannot appear even by accident. Admin status still comes from the configured ids
and Poracle's own config.

IPoracleApiProxy.GetAreasAsync goes with it -- zero callers, v1 path.
…s losing case

Moving a saved place was impossible in any way: there is no v1 update route, and
the delete answers 409 while any alarm still references the label -- so moving
the place you live meant repointing every alarm, deleting, re-adding and
repointing back. PUT /v2/humans/{id}/locations/{label} does it in one call, with
the label untouched so every alarm follows.

It is the only operation in this migration with no older equivalent, so it is
gated rather than degraded: IPlaceUpdateCapabilityService, same shape as
MuteCapabilityService, 5.2.0, failing closed. On an older server the pencil is
absent with no @else and no tooltip naming a version at someone who cannot act
on it -- the delete-and-re-add flow is still right there.

Also fixed, and independent of the migration: Poracle lowercases the language it
stores, on both API versions and from the bot's own !language command, so
humans.language reads back "pt-br" where the picker's list says "pt-BR". The
comparison was exact, so it dropped the value and quietly fell back to the
server default. It ignores case now and stores the list's own spelling back.
…vers

The SPA replaces its whole places signal from whatever POST /api/location/places
answers, and that action was still returning the bare place list. Adding a place
therefore cleared canEdit on the client and took the new edit control off every
card until the next reload -- the exact hazard already commented on the PUT, one
path along. It now answers PlacesWithCapabilityAsync like the GET and the PUT do,
with a controller test and a jest test, both watched red first.

Also restores the CR that the mute-capability registration line lost, and deletes
HumanRepository.EnsureNotNullDefaults and its cached reflection, dead since
CreateAsync and UpdateAsync were removed.
…cking it

Get, add and update all moved to /api/v2/humans/{id}/locations. Delete did not --
it was still POSTing v1's /locations/{label}/delete while its three siblings had
moved. This is the shape CLAUDE.md warns about, in the one branch whose whole job
was this surface.

The conflict body was also unreadable. Both versions answer 409 with a
referencing_rules array and they disagree about case: v2 tags its fields
({"type":"raid","uid":424}) while v1 serialises store.ReferencingRule, which carries
no json tags at all, so Go's default marshalling emits {"Type":"raid","UID":424}.
The old code called ToString() on each element, which put the raw JSON object into
the list either way.

Nothing broke, because the only consumer counts the entries -- but the component's
own spec mocks readable strings the server had never produced, so the test was
describing a world that did not exist. Entries now render as "raid 424", reading
both casings, since v1 remains the fallback.

Three tests. The two naming a real server shape were watched failing against the
previous code; the third pins that an entry in neither shape is passed through
rather than dropped.
@hokiepokedad2
hokiepokedad2 merged commit 2790a18 into develop Aug 24, 2026
6 checks passed
@hokiepokedad2
hokiepokedad2 deleted the feat/v2humans branch August 24, 2026 22:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant