feat(poracleng): move the human, location and place proxy onto /api/v2 - #829
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Merge last. Moves
PoracleHumanProxyonto/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 deletedIHumanService.UpdateAsyncandHumanRepository.UpdateAsyncoutright.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, behindIPlaceUpdateCapabilityService.The gap I closed after the swarm reported
Get, add and update moved; delete did not. It was still POSTing v1's
/locations/{label}/deletewhile 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 serialisesstore.ReferencingRule, which carries no json tags at all, so Go emits{"Type":"raid","UID":424}. The old code calledToString()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 asraid 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 deadisAdminbranches 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/placeswas still answering the old list shape while GET and PUT answered the new one, which blankedcanEditand 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}updatesactive_hoursonly and cannot rename, and v2'sactive_hoursschema is broken — verified live,day: 0returns 200 whileday: 7returns 422expected 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.