mirror of
https://github.com/Dvorinka/Primora.git
synced 2026-10-29 00:01:20 +00:00
feat: phase 4 integrations — connectors, rybbit analytics, webhooks
- core.integrations (migration 00005) with AES-256-GCM-encrypted credentials (PRIMORA_ENCRYPTION_KEY, hex/base64, dev fallback); has_credentials serialization only — secrets never leave the server - /projects/:id/integrations list/create/delete/test; health check hits the stored base URL with a 5s timeout and records status - Rybbit connector: GET …/integrations/:id/analytics proxies /api/organizations + /api/sites/* server-side into a normalized shape - core.webhooks + core.webhook_deliveries; CRUD, deliveries, test, and POST …/deploy-markers. Events: issue.created (new fingerprint on ingest), deploy.marker, webhook.test. X-Primora-Signature HMAC-SHA256 over the raw body; in-process dispatcher, 3 attempts, quadratic backoff, 10s timeout, pending-scan on boot - HTTPS enforced for public targets; HTTP allowed for loopback/private/ self-hosted sinks - Integrations page (connectors + webhooks tabs, inline analytics, delivery log, secret-shown-once), demo-mode stubs - 12 new primora_* MCP tools - workspace 0.4.0 Authored By: TDvorak <[email protected]>
This commit is contained in:
1 parent
8cfddf9184
commit
e25eb92339
82 files changed
+5710
-61
No files matched your search
@@ -89,6 +89,7 @@ func (s *PlatformService) IngestEvents(ctx context.Context, actor *models.Actor,
|
||||
projectID := *actor.ProjectID
|
||||
|
||||
var inserted []TelemetryEvent
|
||||
var newIssues []TelemetryEvent
|
||||
err := s.repo.WithTx(ctx, func(q *db.Queries) error {
|
||||
compCache := map[string]*uuid.UUID{}
|
||||
for _, ev := range events {
|
||||
@@ -136,6 +137,16 @@ func (s *PlatformService) IngestEvents(ctx context.Context, actor *models.Actor,
|
||||
if len(payload) == 0 {
|
||||
payload = json.RawMessage(`{}`)
|
||||
}
|
||||
newFingerprint := false
|
||||
if ev.Type == "error" && fp != "" {
|
||||
seen, err := q.HasFingerprintSeen(ctx, db.HasFingerprintSeenParams{
|
||||
ProjectID: projectID,
|
||||
Fingerprint: fp,
|
||||
})
|
||||
if err == nil && !seen {
|
||||
newFingerprint = true
|
||||
}
|
||||
}
|
||||
var cid pgtype.UUID
|
||||
if compID != nil {
|
||||
cid = pgtype.UUID{Bytes: *compID, Valid: true}
|
||||
@@ -168,6 +179,9 @@ func (s *PlatformService) IngestEvents(ctx context.Context, actor *models.Actor,
|
||||
out.ComponentName = ev.Component
|
||||
}
|
||||
inserted = append(inserted, out)
|
||||
if newFingerprint {
|
||||
newIssues = append(newIssues, out)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@@ -178,6 +192,18 @@ func (s *PlatformService) IngestEvents(ctx context.Context, actor *models.Actor,
|
||||
for _, ev := range inserted {
|
||||
s.hub.Broadcast(ev.ProjectID.String(), ev)
|
||||
}
|
||||
// A fresh error fingerprint is a new issue group — notify webhooks.
|
||||
for _, issue := range newIssues {
|
||||
s.dispatchEvent(ctx, projectID, WebhookEventIssueCreated, map[string]any{
|
||||
"fingerprint": issue.Fingerprint,
|
||||
"message": issue.Message,
|
||||
"severity": issue.Severity,
|
||||
"component_id": issue.ComponentID,
|
||||
"component_name": issue.ComponentName,
|
||||
"event_id": issue.ID,
|
||||
"first_seen": issue.TS,
|
||||
})
|
||||
}
|
||||
return len(inserted), nil
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user