22//
33// Endpoints:
44//
5- // GET /api/v1/messages — list messages (query: status, limit)
6- // GET /api/v1/messages/{id} — get a single message by ID
7- // GET /api/v1/channels — list channels
8- // POST /api/v1/channels — create channel
9- // GET /api/v1/channels/{id} — get channel by ID
10- // PUT /api/v1/channels/{id} — update channel
11- // DELETE /api/v1/channels/{id} — delete channel
12- // GET /api/v1/sources — list sources
13- // POST /api/v1/sources — create source
14- // GET /api/v1/sources/{id} — get source by ID
15- // PUT /api/v1/sources/{id} — update source
16- // DELETE /api/v1/sources/{id} — delete source
17- // GET /healthz — liveness probe
18- // GET /readyz — readiness probe (checks store)
5+ // GET /api/v1/messages — list messages (query: status, limit)
6+ // GET /api/v1/messages/{id} — get a single message by ID
7+ // GET /api/v1/channels — list channels
8+ // POST /api/v1/channels — create channel
9+ // GET /api/v1/channels/{id} — get channel by ID
10+ // PUT /api/v1/channels/{id} — update channel
11+ // DELETE /api/v1/channels/{id} — delete channel
12+ // GET /api/v1/sources — list sources
13+ // POST /api/v1/sources — create source
14+ // GET /api/v1/sources/{id} — get source by ID
15+ // PUT /api/v1/sources/{id} — update source
16+ // DELETE /api/v1/sources/{id} — delete source
17+ // GET /api/v1/pipelines — list pipelines
18+ // POST /api/v1/pipelines — create pipeline
19+ // GET /api/v1/pipelines/{id} — get pipeline by ID
20+ // PUT /api/v1/pipelines/{id} — update pipeline
21+ // DELETE /api/v1/pipelines/{id} — delete pipeline
22+ // GET /healthz — liveness probe
23+ // GET /readyz — readiness probe (checks store)
1924//
2025// All responses are JSON. Error bodies have the shape {"error":"reason"}.
2126// The server uses Go 1.22 enhanced ServeMux patterns, so unknown methods
@@ -61,16 +66,17 @@ type MessageResponse struct {
6166// Server is an http.Handler that exposes the message store and channel
6267// registry as a REST API.
6368type Server struct {
64- store store.Store
65- channels * channel.Registry
66- sources * channel.SourceRegistry
67- auditDir string
68- mux * http.ServeMux
69+ store store.Store
70+ channels * channel.Registry
71+ sources * channel.SourceRegistry
72+ pipelines * channel.PipelineRegistry
73+ auditDir string
74+ mux * http.ServeMux
6975}
7076
71- // New creates a Server backed by st, reg and sourceReg , and registers all routes.
72- func New (st store.Store , reg * channel.Registry , sourceReg * channel.SourceRegistry ) * Server {
73- s := & Server {store : st , channels : reg , sources : sourceReg }
77+ // New creates a Server backed by st, reg, sourceReg and pipelineReg , and registers all routes.
78+ func New (st store.Store , reg * channel.Registry , sourceReg * channel.SourceRegistry , pipelineReg * channel. PipelineRegistry ) * Server {
79+ s := & Server {store : st , channels : reg , sources : sourceReg , pipelines : pipelineReg }
7480 s .mux = http .NewServeMux ()
7581 // message routes
7682 s .mux .HandleFunc ("GET /api/v1/messages" , s .listMessages )
@@ -87,6 +93,12 @@ func New(st store.Store, reg *channel.Registry, sourceReg *channel.SourceRegistr
8793 s .mux .HandleFunc ("GET /api/v1/sources/{id}" , s .getSource )
8894 s .mux .HandleFunc ("PUT /api/v1/sources/{id}" , s .updateSource )
8995 s .mux .HandleFunc ("DELETE /api/v1/sources/{id}" , s .deleteSource )
96+ // pipeline routes
97+ s .mux .HandleFunc ("GET /api/v1/pipelines" , s .listPipelines )
98+ s .mux .HandleFunc ("POST /api/v1/pipelines" , s .createPipeline )
99+ s .mux .HandleFunc ("GET /api/v1/pipelines/{id}" , s .getPipeline )
100+ s .mux .HandleFunc ("PUT /api/v1/pipelines/{id}" , s .updatePipeline )
101+ s .mux .HandleFunc ("DELETE /api/v1/pipelines/{id}" , s .deletePipeline )
90102 // audit + reports routes
91103 s .mux .HandleFunc ("GET /api/v1/audit" , s .listAudit )
92104 s .mux .HandleFunc ("GET /api/v1/reports" , s .getReports )
@@ -358,6 +370,94 @@ func (s *Server) deleteSource(w http.ResponseWriter, r *http.Request) {
358370 w .WriteHeader (http .StatusNoContent )
359371}
360372
373+ // ---- pipeline handlers -----------------------------------------------------
374+
375+ // listPipelines handles GET /api/v1/pipelines
376+ func (s * Server ) listPipelines (w http.ResponseWriter , r * http.Request ) {
377+ writeJSON (w , http .StatusOK , s .pipelines .List ())
378+ }
379+
380+ // createPipeline handles POST /api/v1/pipelines
381+ func (s * Server ) createPipeline (w http.ResponseWriter , r * http.Request ) {
382+ var p channel.Pipeline
383+ if err := json .NewDecoder (r .Body ).Decode (& p ); err != nil {
384+ writeError (w , http .StatusBadRequest , "invalid JSON body" )
385+ return
386+ }
387+ if p .ID == "" {
388+ writeError (w , http .StatusBadRequest , "id is required" )
389+ return
390+ }
391+ if p .Name == "" {
392+ writeError (w , http .StatusBadRequest , "name is required" )
393+ return
394+ }
395+ if err := s .pipelines .Add (p ); err != nil {
396+ if errors .Is (err , channel .ErrDuplicateID ) {
397+ writeError (w , http .StatusConflict , "pipeline ID already exists" )
398+ return
399+ }
400+ writeError (w , http .StatusInternalServerError , "failed to create pipeline" )
401+ return
402+ }
403+ got , _ := s .pipelines .Get (p .ID )
404+ writeJSON (w , http .StatusCreated , got )
405+ }
406+
407+ // getPipeline handles GET /api/v1/pipelines/{id}
408+ func (s * Server ) getPipeline (w http.ResponseWriter , r * http.Request ) {
409+ id := r .PathValue ("id" )
410+ p , err := s .pipelines .Get (id )
411+ if err != nil {
412+ if errors .Is (err , channel .ErrNotFound ) {
413+ writeError (w , http .StatusNotFound , "pipeline not found" )
414+ return
415+ }
416+ writeError (w , http .StatusInternalServerError , "failed to retrieve pipeline" )
417+ return
418+ }
419+ writeJSON (w , http .StatusOK , p )
420+ }
421+
422+ // updatePipeline handles PUT /api/v1/pipelines/{id}
423+ func (s * Server ) updatePipeline (w http.ResponseWriter , r * http.Request ) {
424+ id := r .PathValue ("id" )
425+ var p channel.Pipeline
426+ if err := json .NewDecoder (r .Body ).Decode (& p ); err != nil {
427+ writeError (w , http .StatusBadRequest , "invalid JSON body" )
428+ return
429+ }
430+ p .ID = id // path wins over body
431+ if p .Name == "" {
432+ writeError (w , http .StatusBadRequest , "name is required" )
433+ return
434+ }
435+ if err := s .pipelines .Update (p ); err != nil {
436+ if errors .Is (err , channel .ErrNotFound ) {
437+ writeError (w , http .StatusNotFound , "pipeline not found" )
438+ return
439+ }
440+ writeError (w , http .StatusInternalServerError , "failed to update pipeline" )
441+ return
442+ }
443+ got , _ := s .pipelines .Get (id )
444+ writeJSON (w , http .StatusOK , got )
445+ }
446+
447+ // deletePipeline handles DELETE /api/v1/pipelines/{id}
448+ func (s * Server ) deletePipeline (w http.ResponseWriter , r * http.Request ) {
449+ id := r .PathValue ("id" )
450+ if err := s .pipelines .Delete (id ); err != nil {
451+ if errors .Is (err , channel .ErrNotFound ) {
452+ writeError (w , http .StatusNotFound , "pipeline not found" )
453+ return
454+ }
455+ writeError (w , http .StatusInternalServerError , "failed to delete pipeline" )
456+ return
457+ }
458+ w .WriteHeader (http .StatusNoContent )
459+ }
460+
361461// listAudit handles GET /api/v1/audit
362462//
363463// Query parameters:
0 commit comments