3.7 KiB
3.7 KiB
11 — Integrations: Incoming Webhooks, Outgoing Webhooks, Export
Incoming webhooks (GitHub & Gitea)
- Endpoint
POST /webhooks/incoming/{provider}(github|gitea), outside/api/v1, unauthenticated but HMAC-verified - Signature:
X-Hub-Signature-256: sha256=<hmac>(both providers); the per-project secret is configured on the incoming side of project settings; verification is constant-time; invalid → 401, receipt recorded withsignature_valid=false, body NOT processed - Project routing: match the payload's repository URL (clone/html URL, normalized like
git_repository_url) against projects; no match → receipt stored withproject_id NULL, statusignored - Idempotency:
UNIQUE(source, delivery_id)(GitHubX-GitHub-Delivery, GiteaX-Gitea-Delivery); duplicate delivery → 200, no reprocessing - Receipts: every request stored raw (
payloadtext) with event_type, status (pending → processed | failed | ignored),failure_reason - v1 automation — PR merged closes issues:
- On
pull_requestevent withaction=closed+merged=true: scan PR title + body forclose[sd]? #(\d+)/fix(e[sd])? #(\d+)/resolve[sd]? #(\d+)(case-insensitive) - Each
#Nresolving to an open issue in the routed project → set statusdoneas system user (id 1) withvia_webhook=true; emits normalissue.status_changed(so notifications/SSE/search/outgoing webhooks all fire); comment-like audit trail shows the PR URL in the event payload - Issues already closed → skipped, noted in receipt
- On
- Replay:
POST /projects/{id}/webhook_receipts/{rid}/replayre-runs processing on the stored payload (owner only) - Push events: recorded as receipts,
ignoredin v1 (future: commit-message automation)
Outgoing webhooks
- Per-project registrations (owner-managed):
url,secret,active,event_types[](empty = all) - Delivery payload (JSON):
{event: "issue.status_changed", timestamp, project: {id, title}, actor: {id, username}, resource: {type, id, issue_num?, title?}, changes: {field: {prev, new}}} - Signing:
X-SoloPM-Signature: sha256=<hex hmac-sha256(secret, raw body)>+X-SoloPM-Event+X-SoloPM-Delivery(delivery id) headers - Delivery via
webhook_deliveryjobs: 10s timeout, any 2xx = success; failure → queue backoff (30s·2^n, 8 attempts) → delivery rowfailed - Delivery log per webhook: status, response code, truncated response body, attempts, timestamps; visible in project settings
POST …/webhooks/{whId}/test— sends a{event: "webhook.test"}payload immediately, records a delivery row- Never deliver to internal targets: resolve the URL host and reject private/loopback ranges (SSRF guard), both at registration and at delivery time
Export
GET /projects/{id}/export?format=json— streaming downloadsolopm-{project}-{date}.json:- Full project dump: project, members (user refs by username/email), labels, epics, issues (with sub-issue/blocker/epic relations by
project_issue_id), comments, links, wiki pages, attachment metadata (files not embedded; paths listed for manual copy) - Versioned envelope
{solopm_export: 1, exported_at, data: {...}}— designed to be re-importable later (import itself is out of scope v1, 17-decisions.md)
- Full project dump: project, members (user refs by username/email), labels, epics, issues (with sub-issue/blocker/epic relations by
GET /projects/{id}/export?format=csv— issues as CSV:number,title,status,priority,estimate,assignee,epic,labels,parent,start_date,target_date,completed_at,created_at,updated_at(RFC 4180 quoting)- Both owner-or-member accessible, streamed (no buffering whole dump in memory), audit-logged as
project.exported(03-domain-events.md)