Files
clawmates/migrations/0034_repos.sql
T
Omar Sobh 6d087bf537 repos: backend — schema, /api/repos routes + GitHub sync provider
Migration 0034: two tables. repo_connections carries the workspace's
per-provider config (owner, base_url, label, last_synced_at,
last_sync_error) and points at an app_connections row for the PAT.
repos is the per-connection cache with (connection_id, external_id)
unique so upsert is idempotent across re-syncs. Cascading deletes clean
up cleanly on connection removal.

cm-secrets grows a FetchAuthorized op — GET with the stored PAT injected
as bearer, returns status + JSON body without ever exposing the
credential to cm-api. This is the least-privilege door for read-only
provider APIs (list repos), distinct from the InvokeHttp path that still
requires a single-use approval grant for outbound writes.

cm-api::routes::repos wires:
- POST /api/repos/connections (broker store_secret + insert both rows +
  initial sync + mark_synced)
- GET /api/repos/connections
- DELETE /api/repos/connections/:id
- POST /api/repos/connections/:id/sync
- GET /api/repos (500 cap, newest provider_updated first)
- GET /api/repos/:id (full detail incl. clone_url + html_url)

GitHub provider inline for v1 — paginated pull of /orgs/:owner/repos
(when owner set) or /user/repos (when absent), 100/page, capped at 20
pages (~2k repos) to keep first-sync latency bounded. Non-2xx surface
back to the caller as sync_error; parse failures are best-effort per
repo (skipped, logged, don't abort the batch).

Gitea + GitLab providers land in a follow-up — mostly URL swap +
response-shape adapter.
2026-07-07 14:52:47 -07:00

64 lines
2.9 KiB
SQL

-- Repos: workspace-scoped cache of the repositories pulled from each
-- connected provider (GitHub / Gitea / GitLab). One row per (connection,
-- external_id). The provider PAT itself never lives here — it's already
-- in the secret broker via app_connections.secret_ref (see 0007).
--
-- Sync flow:
-- POST /api/repos/connections/:id/sync → provider API list → upsert.
-- Removed-upstream rows stay for now (v1); a future flag can prune them.
--
-- repo_connections wraps an app_connections row with the repo-specific
-- config: which owner (org/user) to pull from, provider base URL for
-- self-hosted Gitea/GitLab instances, and last-sync bookkeeping.
CREATE TABLE repo_connections (
id UUID PRIMARY KEY,
workspace_id UUID NOT NULL REFERENCES workspaces (id) ON DELETE CASCADE,
app_connection_id UUID NOT NULL REFERENCES app_connections (id) ON DELETE CASCADE,
provider TEXT NOT NULL, -- 'github' | 'gitea' | 'gitlab'
-- Optional owner (org or user). When NULL, the provider fetches "all
-- repos the token can see" (e.g. GitHub /user/repos).
owner TEXT,
-- Optional base URL for self-hosted providers (e.g. https://git.redclaw.dev).
base_url TEXT,
label TEXT NOT NULL DEFAULT '',
last_synced_at TIMESTAMPTZ,
last_sync_error TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX repo_connections_workspace_idx
ON repo_connections (workspace_id, created_at DESC);
CREATE TABLE repos (
id UUID PRIMARY KEY,
workspace_id UUID NOT NULL REFERENCES workspaces (id) ON DELETE CASCADE,
connection_id UUID NOT NULL REFERENCES repo_connections (id) ON DELETE CASCADE,
provider TEXT NOT NULL, -- 'github' | 'gitea' | 'gitlab'
-- Provider-side stable id (numeric on GitHub, integer on Gitea/GitLab).
-- Stored as TEXT so we don't care which flavor the provider uses.
external_id TEXT NOT NULL,
owner TEXT NOT NULL, -- org or user login
name TEXT NOT NULL, -- repo slug
description TEXT,
default_branch TEXT,
clone_url TEXT,
html_url TEXT,
private BOOLEAN NOT NULL DEFAULT false,
stars INTEGER NOT NULL DEFAULT 0,
forks INTEGER NOT NULL DEFAULT 0,
-- Provider's own `updated_at` (last push / edit), NOT the sync time.
provider_updated_at TIMESTAMPTZ,
last_synced_at TIMESTAMPTZ NOT NULL DEFAULT now(),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (connection_id, external_id)
);
CREATE INDEX repos_workspace_idx
ON repos (workspace_id, provider_updated_at DESC NULLS LAST);
-- Fast per-connection list (sidebar grouping).
CREATE INDEX repos_connection_idx
ON repos (connection_id, name);