[FR] Add POST /api/connections/{id}/sync to trigger a connection sync over the public API
Problem
Operators have a growing surface for inspecting connection state over the public API: GET /api/connections (PR #1517) lists connections with sync state, and GET /api/connections/{id} (PR #1519) returns one connection with recent jobs and in-flight count. But once an operator sees "the GitHub connection has been broken for 6 hours", there is no public API to force a re-sync. The only path today is to open the web UI and click "Sync now" — which means scripted recovery (on-call runbooks, Grafana→Slack→script hooks) is impossible.
Background
The backend worker already has a public method for this: ConnectionManager.createJobs([connection]) in packages/backend/src/connectionManager.ts:129, exposed internally via the express route POST /api/sync-connection in packages/backend/src/api.ts:59. The web app already calls it from a server action (syncConnection in packages/web/src/features/workerApi/actions.ts:14) that's OWNER-gated and used by the React UI. The gap is purely the public-API surface on the web app: no route.ts for it.
Current behavior
There is no way for an external client (a script, a CI job, a Slack bot) to force a connection sync using only the public API. The internal worker endpoint is reachable only on the worker's network port, not the public web port.
Expected behavior
POST /api/connections/{id}/sync returns the new job id for an OWNER in the org. Like the list and detail endpoints, it is org-scoped (cross-org access returns 404, not 403). The response mirrors the existing workerApi/actions.ts → syncConnection server action's contract: { jobId: string }. The status code is 202 Accepted because the job is enqueued, not completed.
Proposed solution
Add a new route file at packages/web/src/app/api/(server)/connections/[id]/sync/route.ts that:
- Validates the path param
id as a positive integer (400 on bad id).
- Loads the connection with
where: { id, orgId: org.id } (404 on missing or cross-org).
- Reuses the existing
syncConnection server action from workerApi/actions.ts so the public route and the UI share one implementation. The action already handles OWNER-gating and the worker call.
- Returns 202 with
{ jobId } on success. Errors from the worker bubble up as 500 (already handled by sew).
- Adds a
POST /api/connections/{id}/sync path to the public OpenAPI spec and a Trigger connection sync entry to the System API Reference group.
- Adds a one-sentence CHANGELOG entry under
[Unreleased] → Added.
Alternatives considered
- Add a 409 Conflict if a sync is already in-flight. The backend's
createJobs does not dedupe (each call creates a new ConnectionSyncJob row), so the conflict check would have to live in the web app, and the operator would lose the "force a re-sync even if one is running" use case. Operators can already see in-flight count via GET /api/connections/{id}, so they can decide whether to wait. Skipped.
- Move the existing server action to an API route entirely. Would break the UI's form submit. The action is the right primitive; the API route should call it.
- Add a bulk
POST /api/connections/sync-all endpoint. Useful but larger scope; punted to a future PR.
Scope
One route file (~50 lines), one action-reuse import, one OpenAPI registration, one docs entry, one CHANGELOG line. Stacks on the list/detail endpoints (PRs #1517, #1519). Reuses connectionSummarySchema.omit/extend patterns from those PRs.
Acceptance criteria
Backward compatibility
Purely additive. The existing workerApi/actions.ts → syncConnection server action is unchanged.
Risks
- No new auth model. Reuses the existing OWNER gate from the server action.
- No rate limiting on the public API. An OWNER could trigger many syncs in a loop. Mitigation: the backend's
createJobs does not dedupe, but the BullMQ queue + worker concurrency cap provides back-pressure. A future PR could add a rate limit.
[FR] Add POST /api/connections/{id}/sync to trigger a connection sync over the public API
Problem
Operators have a growing surface for inspecting connection state over the public API:
GET /api/connections(PR #1517) lists connections with sync state, andGET /api/connections/{id}(PR #1519) returns one connection with recent jobs and in-flight count. But once an operator sees "the GitHub connection has been broken for 6 hours", there is no public API to force a re-sync. The only path today is to open the web UI and click "Sync now" — which means scripted recovery (on-call runbooks, Grafana→Slack→script hooks) is impossible.Background
The backend worker already has a public method for this:
ConnectionManager.createJobs([connection])inpackages/backend/src/connectionManager.ts:129, exposed internally via the express routePOST /api/sync-connectioninpackages/backend/src/api.ts:59. The web app already calls it from a server action (syncConnectioninpackages/web/src/features/workerApi/actions.ts:14) that's OWNER-gated and used by the React UI. The gap is purely the public-API surface on the web app: noroute.tsfor it.Current behavior
There is no way for an external client (a script, a CI job, a Slack bot) to force a connection sync using only the public API. The internal worker endpoint is reachable only on the worker's network port, not the public web port.
Expected behavior
POST /api/connections/{id}/syncreturns the new job id for an OWNER in the org. Like the list and detail endpoints, it is org-scoped (cross-org access returns 404, not 403). The response mirrors the existingworkerApi/actions.ts → syncConnectionserver action's contract:{ jobId: string }. The status code is 202 Accepted because the job is enqueued, not completed.Proposed solution
Add a new route file at
packages/web/src/app/api/(server)/connections/[id]/sync/route.tsthat:idas a positive integer (400 on bad id).where: { id, orgId: org.id }(404 on missing or cross-org).syncConnectionserver action fromworkerApi/actions.tsso the public route and the UI share one implementation. The action already handles OWNER-gating and the worker call.{ jobId }on success. Errors from the worker bubble up as 500 (already handled bysew).POST /api/connections/{id}/syncpath to the public OpenAPI spec and aTrigger connection syncentry to the System API Reference group.[Unreleased] → Added.Alternatives considered
createJobsdoes not dedupe (each call creates a newConnectionSyncJobrow), so the conflict check would have to live in the web app, and the operator would lose the "force a re-sync even if one is running" use case. Operators can already see in-flight count viaGET /api/connections/{id}, so they can decide whether to wait. Skipped.POST /api/connections/sync-allendpoint. Useful but larger scope; punted to a future PR.Scope
One route file (~50 lines), one action-reuse import, one OpenAPI registration, one docs entry, one CHANGELOG line. Stacks on the list/detail endpoints (PRs #1517, #1519). Reuses
connectionSummarySchema.omit/extendpatterns from those PRs.Acceptance criteria
POST /api/connections/{id}/syncreturns 202 with{ jobId }for an OWNER.idis not a positive integer.Backward compatibility
Purely additive. The existing
workerApi/actions.ts → syncConnectionserver action is unchanged.Risks
createJobsdoes not dedupe, but the BullMQ queue + worker concurrency cap provides back-pressure. A future PR could add a rate limit.