chore: migrate readThreads and push_test DDP callers to REST
Part of the DDP → REST migration (ARCH-2166). Removes two more client callers off DDP: readThreads and push_test.
readThreads → POST /v1/chat.readThread
GET /v1/chat.getThreadMessages (#40998) is a plain read — a GET must not mutate — so the read side effect the DDP getThreadMessages used to perform server-side had to be called explicitly. That left readThreads with two call sites and no REST target.
POST /v1/subscriptions.read cannot absorb it: it takes { rid, readThreads? } and marks the whole room, with no way to address a single thread.
So this adds the missing per-thread route, with the same checks the method did (Threads_enabled, message exists, room access) and the same effects (beforeReadMessages callback, readThread). Both call sites — ThreadChat and useThreadMessagesQuery — move over.
Follows the chat.pinMessage pattern in the same file: local schema + ajv.compile, type exposed through ExtractRoutesFromAPI, no manual entry in rest-typings (which would collide with the interface declaration).
push_test → POST /v1/push.test
The "Send a test push to my user" admin setting stored the DDP method name, which MethodActionInput calls through useMethod. POST /v1/push.test already existed with the same permission (test-push-notifications), the same 1 req/s rate limit and the same Push_enable guard, so the setting only needed to hold { method, path } — ActionSettingInput then routes it through EndpointActionInput, exactly like the ABAC health check already does.
One catch: ActionInputBase renders the outcome straight from the response (t(data.message, { sprintf: data.params })), and the endpoint returned only { tokensCount } — the toast would have resolved t(undefined). The endpoint now also returns the message translation key and its params, matching the DDP return value, so the admin still sees how many devices the test reached. tokensCount is unchanged for existing consumers.
Deprecation
Both DDP methods keep their registration with methodDeprecationLogger entries pointing at the new routes, to be removed with the rest in 9.0.0.
Tests
New API test block for chat.readThread covering:
- 400 when
Threads_enabledis off - 400 for a
tmidthat matches no message - 400 when
tmidis missing - the actual effect: a reply from a second user puts the thread in the caller's
tunread, and the call clears it
push.test already had API coverage for the unauthenticated and push-disabled paths; the success path needs a configured gateway, so the response change is exercised through the admin setting rather than a new API test.
Steps to test
- Open a thread with unread replies (have another user reply while you are following it) → the thread badge clears, and
POST /v1/chat.readThreadshows in the network tab instead of amethod.callforreadThreads. - Admin → Push → enable push, click Send a test push to my user → the toast still reports the device count, and the request goes to
/v1/push.test. - With
Threads_enabledoff,POST /v1/chat.readThreadanswers 400error-not-allowed.
Remaining after this PR
37 methods still called from the UI. Next cheapest group is the remaining admin action settings (8 methods), which need thin POST endpoints over functions that already exist.
Related: ARCH-2166 · #40998 (thread pagination, which created the readThreads gap) · #41589 / ARCH-2297 (401/403 semantics, unrelated to this PR but relevant to the migration as a whole)
Task: ARCH-2298
Summary by CodeRabbit
- New Features
- Added
POST /v1/chat.readThreadto mark a single thread as read (accepts{ tmid }) and clear its unread state for the caller. - Updated the admin “send a test push” action to use
POST /v1/push.test.
- Added
- Improvements
- Added request validation, room access checks, and read-message callbacks for thread-read requests.
POST /v1/push.testnow returns a localizedmessageplusparams(withtokensCount).
- Deprecations
- Legacy
readThreadsandpush_testnow emit migration warnings and remain available until 9.0.0.
- Legacy
- Tests
- Added end-to-end coverage for
POST /v1/chat.readThread.
- Added end-to-end coverage for