mirror of
https://github.com/Dvorinka/Primora.git
synced 2026-10-28 07:41:23 +00:00
feat: phase 7 automation & realtime — scheduled jobs, domain events, SSE
Scheduled jobs (migration 00009): cron expressions + @every descriptors, in-process scheduler (15s scan, missed-run collapse), signed HTTP POST delivery (X-Primora-Signature), AES-256-GCM sealed secrets returned once, run history with status/duration/http-code, manual execution, retention pruning. Viewer reads, admin/developer writes. Domain events: publishEvent fans document/object/issue/deploy/job mutations to realtime subscribers and matching webhooks through one path. Webhook event filter extended to job.run, document.*, object.*. Realtime: GET /projects/:id/realtime/stream SSE with 25s keepalives and ?token=/?api_key= auth for EventSource clients. Automation dashboard page: Schedules tab (CRUD, run history, manual run, secret-once banner) + Live events tab (live badge, pause/clear). Demo mode parity. primora jobs:* CLI group, six primora_*_job* MCP tools, AutomationService in the generated client. Fixes folded in: gzip middleware buffered SSE flushes (broke EventSource on both streams — */stream now bypasses compression); event payloads double-marshaled json.RawMessage as base64; compose hardcoded localhost trusted origins so non-default ports broke sign-in; frontend auth base URL baked an absolute localhost instead of resolving /auth on origin. Verified live: stack up on :8085, signed delivery HMAC-checked at sink, @every 30s schedule firing, run history rendering, document.created landing in the live events feed. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
4b7fdf35ad
commit
9e9a3d5f98
49 files changed
+3555
-32
No files matched your search
@@ -14,6 +14,9 @@ tags:
|
||||
- name: Collections
|
||||
- name: Databases
|
||||
- name: Telemetry
|
||||
- name: Integrations
|
||||
- name: Webhooks
|
||||
- name: Automation
|
||||
paths:
|
||||
/health/liveness:
|
||||
get:
|
||||
@@ -1597,6 +1600,162 @@ paths:
|
||||
$ref: "#/components/schemas/DeployMarker"
|
||||
"401":
|
||||
$ref: "#/components/responses/ErrorResponse"
|
||||
/projects/{projectID}/jobs:
|
||||
get:
|
||||
tags: [Automation]
|
||||
operationId: listScheduledJobs
|
||||
security:
|
||||
- bearerAuth: []
|
||||
- apiKeyAuth: []
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/ProjectID"
|
||||
responses:
|
||||
"200":
|
||||
description: Scheduled jobs for the project
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ScheduledJobListResponse"
|
||||
"401":
|
||||
$ref: "#/components/responses/ErrorResponse"
|
||||
post:
|
||||
tags: [Automation]
|
||||
operationId: createScheduledJob
|
||||
summary: Runs a signed POST to the job URL on a cron schedule (standard 5-field cron or @every/@hourly descriptors). Payload is signed X-Primora-Signature like webhook deliveries; an empty secret generates one returned once. HTTPS required for public targets, HTTP allowed for private/self-hosted.
|
||||
security:
|
||||
- bearerAuth: []
|
||||
- apiKeyAuth: []
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/ProjectID"
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/CreateScheduledJobRequest"
|
||||
responses:
|
||||
"201":
|
||||
description: Job created
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ScheduledJobCreateResponse"
|
||||
"400":
|
||||
$ref: "#/components/responses/ErrorResponse"
|
||||
"401":
|
||||
$ref: "#/components/responses/ErrorResponse"
|
||||
"409":
|
||||
$ref: "#/components/responses/ErrorResponse"
|
||||
/projects/{projectID}/jobs/{jobID}:
|
||||
patch:
|
||||
tags: [Automation]
|
||||
operationId: updateScheduledJob
|
||||
security:
|
||||
- bearerAuth: []
|
||||
- apiKeyAuth: []
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/ProjectID"
|
||||
- $ref: "#/components/parameters/JobID"
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/UpdateScheduledJobRequest"
|
||||
responses:
|
||||
"200":
|
||||
description: Job updated
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ScheduledJobCreateResponse"
|
||||
"404":
|
||||
$ref: "#/components/responses/ErrorResponse"
|
||||
delete:
|
||||
tags: [Automation]
|
||||
operationId: deleteScheduledJob
|
||||
security:
|
||||
- bearerAuth: []
|
||||
- apiKeyAuth: []
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/ProjectID"
|
||||
- $ref: "#/components/parameters/JobID"
|
||||
responses:
|
||||
"204":
|
||||
description: Job deleted
|
||||
"404":
|
||||
$ref: "#/components/responses/ErrorResponse"
|
||||
/projects/{projectID}/jobs/{jobID}/runs:
|
||||
get:
|
||||
tags: [Automation]
|
||||
operationId: listScheduledJobRuns
|
||||
summary: Recent executions, newest first. History is capped at 200 runs per job.
|
||||
security:
|
||||
- bearerAuth: []
|
||||
- apiKeyAuth: []
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/ProjectID"
|
||||
- $ref: "#/components/parameters/JobID"
|
||||
- name: limit
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 200
|
||||
default: 50
|
||||
responses:
|
||||
"200":
|
||||
description: Job run history
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ScheduledJobRunListResponse"
|
||||
"404":
|
||||
$ref: "#/components/responses/ErrorResponse"
|
||||
/projects/{projectID}/jobs/{jobID}/run:
|
||||
post:
|
||||
tags: [Automation]
|
||||
operationId: runScheduledJob
|
||||
summary: Triggers an out-of-schedule manual run. Returns the run row in "running" state.
|
||||
security:
|
||||
- bearerAuth: []
|
||||
- apiKeyAuth: []
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/ProjectID"
|
||||
- $ref: "#/components/parameters/JobID"
|
||||
responses:
|
||||
"202":
|
||||
description: Manual run queued
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ScheduledJobRun"
|
||||
"404":
|
||||
$ref: "#/components/responses/ErrorResponse"
|
||||
/projects/{projectID}/realtime/stream:
|
||||
get:
|
||||
tags: [Automation]
|
||||
operationId: streamRealtimeEvents
|
||||
summary: Server-sent events stream of project domain events — document/object mutations, new issues, deploy markers, job runs. EventSource clients pass `?api_key=` or `?token=` since headers cannot be set.
|
||||
security:
|
||||
- bearerAuth: []
|
||||
- apiKeyAuth: []
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/ProjectID"
|
||||
- name: api_key
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: token
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: text/event-stream of {type, occurred_at, data} messages
|
||||
"401":
|
||||
$ref: "#/components/responses/ErrorResponse"
|
||||
components:
|
||||
securitySchemes:
|
||||
bearerAuth:
|
||||
@@ -1730,6 +1889,13 @@ components:
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
JobID:
|
||||
name: jobID
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
DatabaseQuery:
|
||||
name: database
|
||||
in: query
|
||||
@@ -3106,7 +3272,7 @@ components:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: Event filter (issue.created, deploy.marker, webhook.test). Empty means all events.
|
||||
description: Event filter (issue.created, deploy.marker, webhook.test, job.run, document.*, object.*). Empty means all events.
|
||||
enabled:
|
||||
type: boolean
|
||||
has_secret:
|
||||
@@ -3128,7 +3294,7 @@ components:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enum: [issue.created, deploy.marker, webhook.test]
|
||||
enum: [issue.created, deploy.marker, webhook.test, job.run, document.created, document.updated, document.deleted, object.created, object.updated, object.deleted]
|
||||
enabled:
|
||||
type: boolean
|
||||
required: [url]
|
||||
@@ -3145,7 +3311,7 @@ components:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enum: [issue.created, deploy.marker, webhook.test]
|
||||
enum: [issue.created, deploy.marker, webhook.test, job.run, document.created, document.updated, document.deleted, object.created, object.updated, object.deleted]
|
||||
enabled:
|
||||
type: boolean
|
||||
WebhookCreateResponse:
|
||||
@@ -3234,3 +3400,137 @@ components:
|
||||
type: string
|
||||
format: date-time
|
||||
required: [recorded_at]
|
||||
ScheduledJob:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
project_id:
|
||||
type: string
|
||||
format: uuid
|
||||
name:
|
||||
type: string
|
||||
schedule:
|
||||
type: string
|
||||
description: Cron expression (5-field) or descriptor (@hourly, @every 30m).
|
||||
url:
|
||||
type: string
|
||||
payload:
|
||||
type: object
|
||||
description: Static JSON payload merged into every delivery's data field.
|
||||
enabled:
|
||||
type: boolean
|
||||
has_secret:
|
||||
type: boolean
|
||||
last_run_at:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
last_status:
|
||||
type: string
|
||||
enum: [success, failed]
|
||||
nullable: true
|
||||
next_run_at:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
required: [id, project_id, name, schedule, url, payload, enabled, has_secret]
|
||||
CreateScheduledJobRequest:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
minLength: 2
|
||||
schedule:
|
||||
type: string
|
||||
description: 'Cron expression, e.g. "*/15 * * * *", or descriptor like "@every 1h".'
|
||||
url:
|
||||
type: string
|
||||
format: uri
|
||||
secret:
|
||||
type: string
|
||||
description: HMAC key for X-Primora-Signature. Generated when omitted.
|
||||
payload:
|
||||
type: object
|
||||
description: Static JSON payload delivered as data on every run.
|
||||
enabled:
|
||||
type: boolean
|
||||
default: true
|
||||
required: [name, schedule, url]
|
||||
UpdateScheduledJobRequest:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
minLength: 2
|
||||
schedule:
|
||||
type: string
|
||||
url:
|
||||
type: string
|
||||
format: uri
|
||||
secret:
|
||||
type: string
|
||||
description: Replace the signing secret. Omit to keep the current one.
|
||||
payload:
|
||||
type: object
|
||||
enabled:
|
||||
type: boolean
|
||||
ScheduledJobCreateResponse:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/ScheduledJob"
|
||||
- type: object
|
||||
properties:
|
||||
secret:
|
||||
type: string
|
||||
description: Plaintext secret — present only when Primora generated it. Store it; it cannot be recovered.
|
||||
ScheduledJobListResponse:
|
||||
type: object
|
||||
properties:
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/ScheduledJob"
|
||||
required: [items]
|
||||
ScheduledJobRun:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
job_id:
|
||||
type: string
|
||||
format: uuid
|
||||
status:
|
||||
type: string
|
||||
enum: [running, success, failed]
|
||||
triggered_by:
|
||||
type: string
|
||||
enum: [schedule, manual]
|
||||
status_code:
|
||||
type: integer
|
||||
nullable: true
|
||||
error:
|
||||
type: string
|
||||
duration_ms:
|
||||
type: integer
|
||||
nullable: true
|
||||
started_at:
|
||||
type: string
|
||||
format: date-time
|
||||
finished_at:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
required: [id, job_id, status, triggered_by]
|
||||
ScheduledJobRunListResponse:
|
||||
type: object
|
||||
properties:
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/ScheduledJobRun"
|
||||
required: [items]
|
||||
Reference in new issue
Block a user