Retire the superseded Custom Local API, and send queued bytes verbatim - #2175
Merged
Conversation
karlitschek
force-pushed
the
debt/retire-local-api
branch
2 times, most recently
from
September 14, 2026 06:24
327b95b to
567e812
Compare
Nineteen of LocalController's thirty-one routes had no caller anywhere in
src/ and had been documented as deprecated for a release: the eight
/api/v1/stream/* and /account/{username}/stream timelines, the two
/local/v1/post reads, the like pair, /current/info, /current/followers and
/following, /account/summary, /global/actor/info and /header, and
/local/v1/search. They go, and with them the whole query layer only they
used -- the nine StreamService::getStream*() methods and the seven
StreamRequest::getTimeline*_dep() / getTimelineLiked() / getTimelineTag()
queries, which were also the last timeline reads on the wide SELECT
DISTINCT -- plus six constructor dependencies LocalController no longer
touches. docs/API.md keeps the table of what replaced each route, as the
release note. The twelve routes the frontend does call stay.
Delivery also stops re-encoding what it sends. bodyFromQueue() returned
json_encode(json_decode($activity)), which defeated ForwardService on
purpose: it queues a third party's getSource() verbatim so their Linked
Data signature still verifies on arrival, and re-encoding is exactly what
breaks such a signature. What this instance writes itself is encoded once
at queue time, so nothing it sends changes; the digest covers the same
bytes. ActivityServiceTest now asserts the body byte for byte.
Signed-off-by: Frank Karlitschek <frank@nextcloud.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Frank Karlitschek <frank@nextcloud.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
karlitschek
force-pushed
the
debt/retire-local-api
branch
from
September 14, 2026 06:52
567e812 to
297dcb2
Compare
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.
Item 11 of the fresh look, first two of its three parts (the browser-test layer follows as its own PR).
Retired: the superseded half of the Custom Local API
Technical-Debt.md item 2 called this "a decision rather than a task". Decided: 19 of
LocalController's 31 routes had no caller anywhere insrc/(the doc said 18 —/api/v1/account/{username}/streamwas listed as still used and is not) and had been documented as deprecated for a release. They are removed:/api/v1/stream/{home,notifications,direct,timeline,federated,liked},/api/v1/stream/tag/{hashtag}/,/api/v1/account/{username}/streamGET /local/v1/post,GET /local/v1/post/replies,POST/DELETE /api/v1/post/like/api/v1/current/{info,followers,following},PUT /api/v1/account/summary/api/v1/global/actor/{info,header},GET /local/v1/searchWith them goes the whole query layer only they used: the nine
StreamService::getStream*()methods and the sevenStreamRequest::getTimeline*_dep()/getTimelineLiked()/getTimelineTag()queries — which were also the last timeline reads on the wideSELECT DISTINCT(see #2174) — and six constructor dependenciesLocalControllerno longer touched (SearchService,BoostService,LikeService,MiscService,ActorService,ActivityService).LocalControllergoes from 1,178 to 734 lines. The twelve routes the frontend does call stay (banner uploads,global/accountandglobal/tagssearches, profile fields, avatar,POST/DELETE /api/v1/post,/api/v1/current/follow).Release note:
docs/API.mdkeeps the table of removed routes with the Mastodon route to use instead, under "Removed". A caller of one gets a 404.Delivery sends the stored bytes
Technical-Debt.md item 1.
ActivityService::bodyFromQueue()returnedjson_encode(json_decode($queue->getActivity(), true), JSON_UNESCAPED_SLASHES). That defeatedForwardServiceon purpose: it queues a third party'sgetSource()verbatim so their Linked Data signature still verifies on arrival, and re-encoding (key order, escaping) is exactly what breaks such a signature. It returns the queued string now. What this instance writes itself is encoded once, with unescaped slashes, at queue time (generateRequestQueue()), so nothing it sends changes; the digest is computed over the same bytes.ActivityServiceTestasserts the wire body byte for byte instead of "the same JSON".Verification
_depdelegation rows went with them; the threeknownActor()guard tests were re-pointed at the avatar route, the one public route left on it). psalm no errors, php-cs-fixer clean.DocumentationTestenforces that every path indocs/API.mdis a route and vice versa — it is what caught the last stray mention.Version 0.19.39. Technical-Debt.md: both items moved to "what has been done", the remaining list renumbered.
🤖 Generated with Claude Code