feat(orgs): per-organization source selection (#407)

Organizations can pin the source they mirror from when more than one source is connected.

- New nullable organizations.source_id column (migration 0020_org_source), backfilled only when an organization's repositories agree on exactly one source.
- NULL means every source; a pin whose source was deleted falls back to the same. A valid pin scopes the org mirror job, the repository count breakdowns and org deletion to that source.
- POST /api/sync/organization persists the pin, PATCH /api/organizations/:id sets or clears it, the bulk import stamps discovered orgs and clears the pin when a second source lists the same org.
- UI, only with more than one source connected: a Source picker in the Add Organization dialog, a per-card inline source editor with an Every source option, and per-org View on source links.
- The org mirror repository query now filters by user.
- Shared provider icon map in src/lib/source-providers/icons.ts.
This commit is contained in:
TomRoyls authored and GitHub committed 2026-09-08 19:13:32 +05:30
1 parent 3a2f8b34ab
commit a74b7e54ed
26 files changed
+4042 -43

No files matched your search

+8
View File
@@ -117,6 +117,14 @@ Starts mirroring in the background and returns straight away. Poll the list endp
`role` is the account's role in that organization (`member`, `admin`, `owner` or `billing_manager`). `409` means it is already tracked; `force: true` refreshes it. Then `POST /api/job/mirror-org` with `{ "organizationIds": ["..."] }` mirrors its repositories.
When several sources are connected, an optional `sourceId` picks which one the organization imports and mirrors from (the first connected source is the default):
```json
{ "org": "my-org", "role": "member", "sourceId": "…" }
```
`PATCH /api/organizations/:id` with `{ "sourceId": "…" }` re-pins an existing organization to another source, and `{ "sourceId": null }` clears the pin so the organization follows every source's repositories of the same name again. A pin whose source was removed falls back the same way.
### Reconcile the destination
`POST /api/cleanup/reconcile`