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:
Tomas Dvorak committed 2026-09-16 18:47:31 +02:00
1 parent 8cfddf9184
commit e25eb92339
82 files changed
+5710 -61

No files matched your search

@@ -0,0 +1,178 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// source: integrations.sql
package db
import (
"context"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgtype"
)
const createIntegration = `-- name: CreateIntegration :one
INSERT INTO core.integrations (
project_id,
type,
name,
base_url,
config,
credentials,
created_by_user_id
) VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING id, project_id, type, name, base_url, config, credentials, status, last_health_at, created_by_user_id, created_at, updated_at
`
type CreateIntegrationParams struct {
ProjectID uuid.UUID `json:"project_id"`
Type string `json:"type"`
Name string `json:"name"`
BaseUrl string `json:"base_url"`
Config []byte `json:"config"`
Credentials []byte `json:"credentials"`
CreatedByUserID pgtype.UUID `json:"created_by_user_id"`
}
func (q *Queries) CreateIntegration(ctx context.Context, arg CreateIntegrationParams) (CoreIntegration, error) {
row := q.db.QueryRow(ctx, createIntegration,
arg.ProjectID,
arg.Type,
arg.Name,
arg.BaseUrl,
arg.Config,
arg.Credentials,
arg.CreatedByUserID,
)
var i CoreIntegration
err := row.Scan(
&i.ID,
&i.ProjectID,
&i.Type,
&i.Name,
&i.BaseUrl,
&i.Config,
&i.Credentials,
&i.Status,
&i.LastHealthAt,
&i.CreatedByUserID,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const deleteIntegration = `-- name: DeleteIntegration :one
DELETE FROM core.integrations
WHERE id = $1
AND project_id = $2
RETURNING id, project_id, type, name, base_url, config, credentials, status, last_health_at, created_by_user_id, created_at, updated_at
`
type DeleteIntegrationParams struct {
ID uuid.UUID `json:"id"`
ProjectID uuid.UUID `json:"project_id"`
}
func (q *Queries) DeleteIntegration(ctx context.Context, arg DeleteIntegrationParams) (CoreIntegration, error) {
row := q.db.QueryRow(ctx, deleteIntegration, arg.ID, arg.ProjectID)
var i CoreIntegration
err := row.Scan(
&i.ID,
&i.ProjectID,
&i.Type,
&i.Name,
&i.BaseUrl,
&i.Config,
&i.Credentials,
&i.Status,
&i.LastHealthAt,
&i.CreatedByUserID,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const getIntegrationByID = `-- name: GetIntegrationByID :one
SELECT id, project_id, type, name, base_url, config, credentials, status, last_health_at, created_by_user_id, created_at, updated_at FROM core.integrations
WHERE id = $1
`
func (q *Queries) GetIntegrationByID(ctx context.Context, id uuid.UUID) (CoreIntegration, error) {
row := q.db.QueryRow(ctx, getIntegrationByID, id)
var i CoreIntegration
err := row.Scan(
&i.ID,
&i.ProjectID,
&i.Type,
&i.Name,
&i.BaseUrl,
&i.Config,
&i.Credentials,
&i.Status,
&i.LastHealthAt,
&i.CreatedByUserID,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const listIntegrations = `-- name: ListIntegrations :many
SELECT id, project_id, type, name, base_url, config, credentials, status, last_health_at, created_by_user_id, created_at, updated_at FROM core.integrations
WHERE project_id = $1
ORDER BY name ASC
`
func (q *Queries) ListIntegrations(ctx context.Context, projectID uuid.UUID) ([]CoreIntegration, error) {
rows, err := q.db.Query(ctx, listIntegrations, projectID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []CoreIntegration
for rows.Next() {
var i CoreIntegration
if err := rows.Scan(
&i.ID,
&i.ProjectID,
&i.Type,
&i.Name,
&i.BaseUrl,
&i.Config,
&i.Credentials,
&i.Status,
&i.LastHealthAt,
&i.CreatedByUserID,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const updateIntegrationHealth = `-- name: UpdateIntegrationHealth :exec
UPDATE core.integrations
SET status = $2,
last_health_at = NOW(),
updated_at = NOW()
WHERE id = $1
`
type UpdateIntegrationHealthParams struct {
ID uuid.UUID `json:"id"`
Status string `json:"status"`
}
func (q *Queries) UpdateIntegrationHealth(ctx context.Context, arg UpdateIntegrationHealthParams) error {
_, err := q.db.Exec(ctx, updateIntegrationHealth, arg.ID, arg.Status)
return err
}
@@ -242,6 +242,21 @@ type CoreEvent struct {
Ts pgtype.Timestamptz `json:"ts"`
}
type CoreIntegration struct {
ID uuid.UUID `json:"id"`
ProjectID uuid.UUID `json:"project_id"`
Type string `json:"type"`
Name string `json:"name"`
BaseUrl string `json:"base_url"`
Config []byte `json:"config"`
Credentials []byte `json:"credentials"`
Status string `json:"status"`
LastHealthAt pgtype.Timestamptz `json:"last_health_at"`
CreatedByUserID pgtype.UUID `json:"created_by_user_id"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
}
type CoreOrganization struct {
ID uuid.UUID `json:"id"`
Slug string `json:"slug"`
@@ -298,3 +313,29 @@ type CoreUser struct {
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
LastSeenAt pgtype.Timestamptz `json:"last_seen_at"`
}
type CoreWebhook struct {
ID uuid.UUID `json:"id"`
ProjectID uuid.UUID `json:"project_id"`
Url string `json:"url"`
Secret []byte `json:"secret"`
Events []string `json:"events"`
Enabled bool `json:"enabled"`
CreatedByUserID pgtype.UUID `json:"created_by_user_id"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
}
type CoreWebhookDelivery struct {
ID uuid.UUID `json:"id"`
WebhookID uuid.UUID `json:"webhook_id"`
EventType string `json:"event_type"`
Payload []byte `json:"payload"`
Status string `json:"status"`
Attempts int32 `json:"attempts"`
LastStatusCode *int32 `json:"last_status_code"`
LastError string `json:"last_error"`
DeliveredAt pgtype.Timestamptz `json:"delivered_at"`
NextRetryAt pgtype.Timestamptz `json:"next_retry_at"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
}
@@ -28,18 +28,22 @@ type Querier interface {
CreateCollection(ctx context.Context, arg CreateCollectionParams) (CoreCollection, error)
CreateDBConnection(ctx context.Context, arg CreateDBConnectionParams) (CoreDbConnection, error)
CreateDocument(ctx context.Context, arg CreateDocumentParams) (CoreDocument, error)
CreateIntegration(ctx context.Context, arg CreateIntegrationParams) (CoreIntegration, error)
CreateInvitation(ctx context.Context, arg CreateInvitationParams) (CoreProjectInvitation, error)
CreateOrganization(ctx context.Context, arg CreateOrganizationParams) (CoreOrganization, error)
CreateProject(ctx context.Context, arg CreateProjectParams) (CoreProject, error)
CreateWebhook(ctx context.Context, arg CreateWebhookParams) (CoreWebhook, error)
DeleteBucketByID(ctx context.Context, id uuid.UUID) (CoreBucket, error)
DeleteBucketObjectByKey(ctx context.Context, arg DeleteBucketObjectByKeyParams) (CoreBucketObject, error)
DeleteCollection(ctx context.Context, arg DeleteCollectionParams) error
DeleteComponent(ctx context.Context, arg DeleteComponentParams) error
DeleteDBConnection(ctx context.Context, arg DeleteDBConnectionParams) (CoreDbConnection, error)
DeleteDocument(ctx context.Context, arg DeleteDocumentParams) error
DeleteIntegration(ctx context.Context, arg DeleteIntegrationParams) (CoreIntegration, error)
DeleteOrganizationByID(ctx context.Context, id uuid.UUID) (CoreOrganization, error)
DeletePendingInvitationByIDForOrganization(ctx context.Context, arg DeletePendingInvitationByIDForOrganizationParams) (CoreProjectInvitation, error)
DeleteProjectByID(ctx context.Context, id uuid.UUID) (CoreProject, error)
DeleteWebhook(ctx context.Context, arg DeleteWebhookParams) (CoreWebhook, error)
EventTotals(ctx context.Context, arg EventTotalsParams) (EventTotalsRow, error)
EventTypeSeries(ctx context.Context, arg EventTypeSeriesParams) ([]EventTypeSeriesRow, error)
GetAPIKeyByIDForProject(ctx context.Context, arg GetAPIKeyByIDForProjectParams) (CoreApiKey, error)
@@ -52,6 +56,7 @@ type Querier interface {
GetDBConnectionByID(ctx context.Context, id uuid.UUID) (CoreDbConnection, error)
GetDBConnectionByName(ctx context.Context, arg GetDBConnectionByNameParams) (CoreDbConnection, error)
GetDocumentByID(ctx context.Context, arg GetDocumentByIDParams) (CoreDocument, error)
GetIntegrationByID(ctx context.Context, id uuid.UUID) (CoreIntegration, error)
GetInvitationByIDForOrganization(ctx context.Context, arg GetInvitationByIDForOrganizationParams) (CoreProjectInvitation, error)
GetInvitationByTokenHash(ctx context.Context, tokenHash string) (CoreProjectInvitation, error)
GetOrganizationMembership(ctx context.Context, arg GetOrganizationMembershipParams) (GetOrganizationMembershipRow, error)
@@ -60,7 +65,11 @@ type Querier interface {
GetProjectOverview(ctx context.Context, id uuid.UUID) (GetProjectOverviewRow, error)
GetUserByAuthSubject(ctx context.Context, authSubject string) (CoreUser, error)
GetUserByID(ctx context.Context, id uuid.UUID) (CoreUser, error)
GetWebhookByID(ctx context.Context, id uuid.UUID) (CoreWebhook, error)
GetWebhookDeliveryByID(ctx context.Context, id uuid.UUID) (CoreWebhookDelivery, error)
HasFingerprintSeen(ctx context.Context, arg HasFingerprintSeenParams) (bool, error)
InsertEvent(ctx context.Context, arg InsertEventParams) (CoreEvent, error)
InsertWebhookDelivery(ctx context.Context, arg InsertWebhookDeliveryParams) (CoreWebhookDelivery, error)
ListAPIKeysForProject(ctx context.Context, projectID uuid.UUID) ([]CoreApiKey, error)
ListAuditLogsForProject(ctx context.Context, arg ListAuditLogsForProjectParams) ([]CoreAuditLog, error)
ListBucketObjects(ctx context.Context, arg ListBucketObjectsParams) ([]CoreBucketObject, error)
@@ -72,12 +81,18 @@ type Querier interface {
ListDocuments(ctx context.Context, arg ListDocumentsParams) ([]CoreDocument, error)
ListErrorGroups(ctx context.Context, arg ListErrorGroupsParams) ([]ListErrorGroupsRow, error)
ListEvents(ctx context.Context, arg ListEventsParams) ([]ListEventsRow, error)
ListIntegrations(ctx context.Context, projectID uuid.UUID) ([]CoreIntegration, error)
ListInvitationsForOrganization(ctx context.Context, organizationID uuid.UUID) ([]ListInvitationsForOrganizationRow, error)
ListOrganizationMembers(ctx context.Context, organizationID uuid.UUID) ([]ListOrganizationMembersRow, error)
ListOrganizationsForUser(ctx context.Context, userID uuid.UUID) ([]ListOrganizationsForUserRow, error)
ListPendingWebhookDeliveries(ctx context.Context, limit int32) ([]CoreWebhookDelivery, error)
ListProjectMembers(ctx context.Context, projectID uuid.UUID) ([]ListProjectMembersRow, error)
ListProjectsForOrganization(ctx context.Context, arg ListProjectsForOrganizationParams) ([]ListProjectsForOrganizationRow, error)
ListWebhookDeliveries(ctx context.Context, arg ListWebhookDeliveriesParams) ([]CoreWebhookDelivery, error)
ListWebhooks(ctx context.Context, projectID uuid.UUID) ([]CoreWebhook, error)
ListWebhooksForEvent(ctx context.Context, arg ListWebhooksForEventParams) ([]CoreWebhook, error)
MarkInvitationAccepted(ctx context.Context, id uuid.UUID) (CoreProjectInvitation, error)
MarkWebhookDelivery(ctx context.Context, arg MarkWebhookDeliveryParams) error
MetricNames(ctx context.Context, projectID uuid.UUID) ([]string, error)
MetricSeries(ctx context.Context, arg MetricSeriesParams) ([]MetricSeriesRow, error)
MoveBucketObject(ctx context.Context, arg MoveBucketObjectParams) (CoreBucketObject, error)
@@ -89,10 +104,12 @@ type Querier interface {
UpdateBucketByID(ctx context.Context, arg UpdateBucketByIDParams) (CoreBucket, error)
UpdateCollection(ctx context.Context, arg UpdateCollectionParams) (CoreCollection, error)
UpdateDocument(ctx context.Context, arg UpdateDocumentParams) (CoreDocument, error)
UpdateIntegrationHealth(ctx context.Context, arg UpdateIntegrationHealthParams) error
UpdateOrganizationByID(ctx context.Context, arg UpdateOrganizationByIDParams) (CoreOrganization, error)
UpdateOrganizationMemberRole(ctx context.Context, arg UpdateOrganizationMemberRoleParams) (CoreOrganizationMember, error)
UpdateProjectByID(ctx context.Context, arg UpdateProjectByIDParams) (CoreProject, error)
UpdateProjectMemberRole(ctx context.Context, arg UpdateProjectMemberRoleParams) (CoreProjectMember, error)
UpdateWebhook(ctx context.Context, arg UpdateWebhookParams) (CoreWebhook, error)
UpsertComponent(ctx context.Context, arg UpsertComponentParams) (CoreComponent, error)
UpsertUser(ctx context.Context, arg UpsertUserParams) (CoreUser, error)
}
@@ -173,6 +173,25 @@ func (q *Queries) GetComponentByName(ctx context.Context, arg GetComponentByName
return i, err
}
const hasFingerprintSeen = `-- name: HasFingerprintSeen :one
SELECT EXISTS(
SELECT 1 FROM core.events
WHERE project_id = $1 AND fingerprint = $2 AND type = 'error'
) AS seen
`
type HasFingerprintSeenParams struct {
ProjectID uuid.UUID `json:"project_id"`
Fingerprint string `json:"fingerprint"`
}
func (q *Queries) HasFingerprintSeen(ctx context.Context, arg HasFingerprintSeenParams) (bool, error) {
row := q.db.QueryRow(ctx, hasFingerprintSeen, arg.ProjectID, arg.Fingerprint)
var seen bool
err := row.Scan(&seen)
return seen, err
}
const insertEvent = `-- name: InsertEvent :one
INSERT INTO core.events (project_id, component_id, type, severity, message, payload, fingerprint, ts)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
@@ -0,0 +1,409 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// source: webhooks.sql
package db
import (
"context"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgtype"
)
const createWebhook = `-- name: CreateWebhook :one
INSERT INTO core.webhooks (
project_id,
url,
secret,
events,
enabled,
created_by_user_id
) VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id, project_id, url, secret, events, enabled, created_by_user_id, created_at, updated_at
`
type CreateWebhookParams struct {
ProjectID uuid.UUID `json:"project_id"`
Url string `json:"url"`
Secret []byte `json:"secret"`
Events []string `json:"events"`
Enabled bool `json:"enabled"`
CreatedByUserID pgtype.UUID `json:"created_by_user_id"`
}
func (q *Queries) CreateWebhook(ctx context.Context, arg CreateWebhookParams) (CoreWebhook, error) {
row := q.db.QueryRow(ctx, createWebhook,
arg.ProjectID,
arg.Url,
arg.Secret,
arg.Events,
arg.Enabled,
arg.CreatedByUserID,
)
var i CoreWebhook
err := row.Scan(
&i.ID,
&i.ProjectID,
&i.Url,
&i.Secret,
&i.Events,
&i.Enabled,
&i.CreatedByUserID,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const deleteWebhook = `-- name: DeleteWebhook :one
DELETE FROM core.webhooks
WHERE id = $1
AND project_id = $2
RETURNING id, project_id, url, secret, events, enabled, created_by_user_id, created_at, updated_at
`
type DeleteWebhookParams struct {
ID uuid.UUID `json:"id"`
ProjectID uuid.UUID `json:"project_id"`
}
func (q *Queries) DeleteWebhook(ctx context.Context, arg DeleteWebhookParams) (CoreWebhook, error) {
row := q.db.QueryRow(ctx, deleteWebhook, arg.ID, arg.ProjectID)
var i CoreWebhook
err := row.Scan(
&i.ID,
&i.ProjectID,
&i.Url,
&i.Secret,
&i.Events,
&i.Enabled,
&i.CreatedByUserID,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const getWebhookByID = `-- name: GetWebhookByID :one
SELECT id, project_id, url, secret, events, enabled, created_by_user_id, created_at, updated_at FROM core.webhooks
WHERE id = $1
`
func (q *Queries) GetWebhookByID(ctx context.Context, id uuid.UUID) (CoreWebhook, error) {
row := q.db.QueryRow(ctx, getWebhookByID, id)
var i CoreWebhook
err := row.Scan(
&i.ID,
&i.ProjectID,
&i.Url,
&i.Secret,
&i.Events,
&i.Enabled,
&i.CreatedByUserID,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const getWebhookDeliveryByID = `-- name: GetWebhookDeliveryByID :one
SELECT id, webhook_id, event_type, payload, status, attempts, last_status_code, last_error, delivered_at, next_retry_at, created_at FROM core.webhook_deliveries
WHERE id = $1
`
func (q *Queries) GetWebhookDeliveryByID(ctx context.Context, id uuid.UUID) (CoreWebhookDelivery, error) {
row := q.db.QueryRow(ctx, getWebhookDeliveryByID, id)
var i CoreWebhookDelivery
err := row.Scan(
&i.ID,
&i.WebhookID,
&i.EventType,
&i.Payload,
&i.Status,
&i.Attempts,
&i.LastStatusCode,
&i.LastError,
&i.DeliveredAt,
&i.NextRetryAt,
&i.CreatedAt,
)
return i, err
}
const insertWebhookDelivery = `-- name: InsertWebhookDelivery :one
INSERT INTO core.webhook_deliveries (
webhook_id,
event_type,
payload,
next_retry_at
) VALUES ($1, $2, $3, NOW())
RETURNING id, webhook_id, event_type, payload, status, attempts, last_status_code, last_error, delivered_at, next_retry_at, created_at
`
type InsertWebhookDeliveryParams struct {
WebhookID uuid.UUID `json:"webhook_id"`
EventType string `json:"event_type"`
Payload []byte `json:"payload"`
}
func (q *Queries) InsertWebhookDelivery(ctx context.Context, arg InsertWebhookDeliveryParams) (CoreWebhookDelivery, error) {
row := q.db.QueryRow(ctx, insertWebhookDelivery, arg.WebhookID, arg.EventType, arg.Payload)
var i CoreWebhookDelivery
err := row.Scan(
&i.ID,
&i.WebhookID,
&i.EventType,
&i.Payload,
&i.Status,
&i.Attempts,
&i.LastStatusCode,
&i.LastError,
&i.DeliveredAt,
&i.NextRetryAt,
&i.CreatedAt,
)
return i, err
}
const listPendingWebhookDeliveries = `-- name: ListPendingWebhookDeliveries :many
SELECT id, webhook_id, event_type, payload, status, attempts, last_status_code, last_error, delivered_at, next_retry_at, created_at FROM core.webhook_deliveries
WHERE status = 'pending'
AND (next_retry_at IS NULL OR next_retry_at <= NOW())
ORDER BY created_at
LIMIT $1
`
func (q *Queries) ListPendingWebhookDeliveries(ctx context.Context, limit int32) ([]CoreWebhookDelivery, error) {
rows, err := q.db.Query(ctx, listPendingWebhookDeliveries, limit)
if err != nil {
return nil, err
}
defer rows.Close()
var items []CoreWebhookDelivery
for rows.Next() {
var i CoreWebhookDelivery
if err := rows.Scan(
&i.ID,
&i.WebhookID,
&i.EventType,
&i.Payload,
&i.Status,
&i.Attempts,
&i.LastStatusCode,
&i.LastError,
&i.DeliveredAt,
&i.NextRetryAt,
&i.CreatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listWebhookDeliveries = `-- name: ListWebhookDeliveries :many
SELECT id, webhook_id, event_type, payload, status, attempts, last_status_code, last_error, delivered_at, next_retry_at, created_at FROM core.webhook_deliveries
WHERE webhook_id = $1
ORDER BY created_at DESC
LIMIT $2
`
type ListWebhookDeliveriesParams struct {
WebhookID uuid.UUID `json:"webhook_id"`
Limit int32 `json:"limit"`
}
func (q *Queries) ListWebhookDeliveries(ctx context.Context, arg ListWebhookDeliveriesParams) ([]CoreWebhookDelivery, error) {
rows, err := q.db.Query(ctx, listWebhookDeliveries, arg.WebhookID, arg.Limit)
if err != nil {
return nil, err
}
defer rows.Close()
var items []CoreWebhookDelivery
for rows.Next() {
var i CoreWebhookDelivery
if err := rows.Scan(
&i.ID,
&i.WebhookID,
&i.EventType,
&i.Payload,
&i.Status,
&i.Attempts,
&i.LastStatusCode,
&i.LastError,
&i.DeliveredAt,
&i.NextRetryAt,
&i.CreatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listWebhooks = `-- name: ListWebhooks :many
SELECT id, project_id, url, secret, events, enabled, created_by_user_id, created_at, updated_at FROM core.webhooks
WHERE project_id = $1
ORDER BY created_at ASC
`
func (q *Queries) ListWebhooks(ctx context.Context, projectID uuid.UUID) ([]CoreWebhook, error) {
rows, err := q.db.Query(ctx, listWebhooks, projectID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []CoreWebhook
for rows.Next() {
var i CoreWebhook
if err := rows.Scan(
&i.ID,
&i.ProjectID,
&i.Url,
&i.Secret,
&i.Events,
&i.Enabled,
&i.CreatedByUserID,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listWebhooksForEvent = `-- name: ListWebhooksForEvent :many
SELECT id, project_id, url, secret, events, enabled, created_by_user_id, created_at, updated_at FROM core.webhooks
WHERE project_id = $1
AND enabled
AND (cardinality(events) = 0 OR $2::text = ANY(events))
`
type ListWebhooksForEventParams struct {
ProjectID uuid.UUID `json:"project_id"`
Column2 string `json:"column_2"`
}
func (q *Queries) ListWebhooksForEvent(ctx context.Context, arg ListWebhooksForEventParams) ([]CoreWebhook, error) {
rows, err := q.db.Query(ctx, listWebhooksForEvent, arg.ProjectID, arg.Column2)
if err != nil {
return nil, err
}
defer rows.Close()
var items []CoreWebhook
for rows.Next() {
var i CoreWebhook
if err := rows.Scan(
&i.ID,
&i.ProjectID,
&i.Url,
&i.Secret,
&i.Events,
&i.Enabled,
&i.CreatedByUserID,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const markWebhookDelivery = `-- name: MarkWebhookDelivery :exec
UPDATE core.webhook_deliveries
SET status = $2,
attempts = attempts + 1,
last_status_code = $3,
last_error = $4,
delivered_at = $5,
next_retry_at = $6
WHERE id = $1
`
type MarkWebhookDeliveryParams struct {
ID uuid.UUID `json:"id"`
Status string `json:"status"`
LastStatusCode *int32 `json:"last_status_code"`
LastError string `json:"last_error"`
DeliveredAt pgtype.Timestamptz `json:"delivered_at"`
NextRetryAt pgtype.Timestamptz `json:"next_retry_at"`
}
func (q *Queries) MarkWebhookDelivery(ctx context.Context, arg MarkWebhookDeliveryParams) error {
_, err := q.db.Exec(ctx, markWebhookDelivery,
arg.ID,
arg.Status,
arg.LastStatusCode,
arg.LastError,
arg.DeliveredAt,
arg.NextRetryAt,
)
return err
}
const updateWebhook = `-- name: UpdateWebhook :one
UPDATE core.webhooks
SET url = $3,
events = $4,
enabled = $5,
secret = CASE WHEN $6::boolean THEN $7 ELSE secret END,
updated_at = NOW()
WHERE id = $1
AND project_id = $2
RETURNING id, project_id, url, secret, events, enabled, created_by_user_id, created_at, updated_at
`
type UpdateWebhookParams struct {
ID uuid.UUID `json:"id"`
ProjectID uuid.UUID `json:"project_id"`
Url string `json:"url"`
Events []string `json:"events"`
Enabled bool `json:"enabled"`
Column6 bool `json:"column_6"`
Secret []byte `json:"secret"`
}
func (q *Queries) UpdateWebhook(ctx context.Context, arg UpdateWebhookParams) (CoreWebhook, error) {
row := q.db.QueryRow(ctx, updateWebhook,
arg.ID,
arg.ProjectID,
arg.Url,
arg.Events,
arg.Enabled,
arg.Column6,
arg.Secret,
)
var i CoreWebhook
err := row.Scan(
&i.ID,
&i.ProjectID,
&i.Url,
&i.Secret,
&i.Events,
&i.Enabled,
&i.CreatedByUserID,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}