Skip to content

Commit 7cadd5b

Browse files
committed
feat(api): split public and internal routes
1 parent 0a12572 commit 7cadd5b

File tree

5 files changed

+73
-24
lines changed

5 files changed

+73
-24
lines changed

README.md

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ What exists today:
3232
- gRPC health checks via `grpc.health.v1.Health/Check`
3333
- PostgreSQL-backed raw check execution storage
3434
- persisted current check state
35+
- persisted check state event history
3536
- migration CLI via `cmd/pulse-migrate`
3637
- early REST API via `cmd/pulse-api`
3738

@@ -79,23 +80,33 @@ CONFIG_DIR=./examples go run ./cmd/pulse
7980
Run API with:
8081

8182
```bash
82-
API_LISTEN_ADDR=:8080 CONFIG_DIR=./examples go run ./cmd/pulse-api
83+
API_LISTEN_ADDR=:8080 INTERNAL_API_ENABLED=true CONFIG_DIR=./examples go run ./cmd/pulse-api
8384
```
8485

85-
Implemented API endpoints:
86+
The API process has separate internal and public route groups.
87+
Both are disabled by default and must be enabled explicitly:
8688

87-
- `GET /v1/services`
88-
- `GET /v1/services/{serviceId}/checks/state`
89-
- `GET /v1/services/{serviceId}/checks/{checkId}/executions`
90-
- `GET /v1/services/{serviceId}/checks/{checkId}/timeline`
91-
- `GET /v1/services/{serviceId}/checks/{checkId}/buckets`
89+
- `INTERNAL_API_ENABLED=true` enables monitoring/admin-oriented endpoints
90+
- `PUBLIC_API_ENABLED=true` enables public status endpoints
91+
92+
Implemented internal API endpoints:
93+
94+
- `GET /internal/v1/services`
95+
- `GET /internal/v1/services/{serviceId}/checks/state`
96+
- `GET /internal/v1/services/{serviceId}/checks/{checkId}/executions`
97+
- `GET /internal/v1/services/{serviceId}/checks/{checkId}/timeline`
98+
- `GET /internal/v1/services/{serviceId}/checks/{checkId}/buckets`
99+
100+
Placeholder public API endpoints:
101+
102+
- `GET /public/v1/status`
92103

93104
## Notes
94105

95106
A few implementation details are intentionally narrow at this stage:
96107

97108
- gRPC support currently targets only the standard health check API: `grpc.health.v1.Health/Check`
98-
- raw execution history and current check state are stored in PostgreSQL
109+
- raw execution history, current check state, and check state event history are stored in PostgreSQL
99110
- API configuration is read from the latest valid hot-reloaded config snapshot
100111
- internal architecture is still evolving
101112
- timeline and bucket endpoints respect per-check `allowed_buckets`

examples/checks/api-checks.yaml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ checks:
1010
retries: 3
1111
failure_threshold: 3
1212
allowed_buckets: [ minute, hour ]
13-
tags: [ public, critical ]
13+
status_impact: critical
1414
spec:
1515
url: http://localhost:8080
1616
method: GET
@@ -38,7 +38,7 @@ checks:
3838
retries: 3
3939
failure_threshold: 3
4040
allowed_buckets: [ minute, hour ]
41-
tags: [ public, critical ]
41+
status_impact: critical
4242
spec:
4343
host: localhost
4444
port: 8080
@@ -57,7 +57,7 @@ checks:
5757
retries: 3
5858
failure_threshold: 3
5959
allowed_buckets: [ minute, hour ]
60-
tags: [ internal, critical ]
60+
status_impact: major
6161
spec:
6262
host: localhost
6363
port: 8080
@@ -80,7 +80,7 @@ checks:
8080
retries: 3
8181
failure_threshold: 3
8282
allowed_buckets: [ minute, hour ]
83-
tags: [ public, critical ]
83+
status_impact: critical
8484
spec:
8585
server: 8.8.8.8
8686
name: api.example.com
@@ -100,7 +100,7 @@ checks:
100100
retries: 2
101101
failure_threshold: 2
102102
allowed_buckets: [ hour, day ]
103-
tags: [ public, security ]
103+
status_impact: critical
104104
spec:
105105
host: api.example.com
106106
port: 443

internal/api/router.go

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,46 @@
11
package api
22

3-
import "github.com/go-chi/chi/v5"
3+
import (
4+
"net/http"
5+
"os"
6+
"strconv"
7+
8+
"github.com/go-chi/chi/v5"
9+
)
410

511
func Routes(r chi.Router, h *Handler) {
6-
r.Route("/v1", func(r chi.Router) {
7-
r.Get("/services", h.Services)
8-
r.Get("/services/{serviceId}/checks/state", h.ServiceCheckStates)
9-
r.Get("/services/{serviceId}/checks/{checkId}/executions", h.CheckExecutions)
10-
r.Get("/services/{serviceId}/checks/{checkId}/timeline", h.CheckTimeline)
11-
r.Get("/services/{serviceId}/checks/{checkId}/buckets", h.CheckBuckets)
12+
public(r, h)
13+
internal(r, h)
14+
}
15+
16+
func public(r chi.Router, h *Handler) {
17+
enabled, _ := strconv.ParseBool(os.Getenv("PUBLIC_API_ENABLED"))
18+
if !enabled {
19+
return
20+
}
21+
22+
r.Route("/public", func(r chi.Router) {
23+
r.Route("/v1", func(r chi.Router) {
24+
r.Get("/status", func(w http.ResponseWriter, r *http.Request) {
25+
w.WriteHeader(http.StatusNotImplemented)
26+
})
27+
})
28+
})
29+
}
30+
31+
func internal(r chi.Router, h *Handler) {
32+
enabled, _ := strconv.ParseBool(os.Getenv("INTERNAL_API_ENABLED"))
33+
if !enabled {
34+
return
35+
}
36+
37+
r.Route("/internal", func(r chi.Router) {
38+
r.Route("/v1", func(r chi.Router) {
39+
r.Get("/services", h.Services)
40+
r.Get("/services/{serviceId}/checks/state", h.ServiceCheckStates)
41+
r.Get("/services/{serviceId}/checks/{checkId}/executions", h.CheckExecutions)
42+
r.Get("/services/{serviceId}/checks/{checkId}/timeline", h.CheckTimeline)
43+
r.Get("/services/{serviceId}/checks/{checkId}/buckets", h.CheckBuckets)
44+
})
1245
})
1346
}

internal/app/manager.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,6 @@ func sameCheckFields(a, b config.CheckFields) bool {
383383
a.Name == b.Name &&
384384
a.Service == b.Service &&
385385
a.Type == b.Type &&
386-
slices.Equal(a.Tags, b.Tags) &&
387386
a.Timeout == b.Timeout &&
388387
a.Jitter == b.Jitter &&
389388
a.Retries == b.Retries &&

internal/config/model.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ type GRPCHealthStatus string
1212
type RecordType string
1313
type GRPCHealthService string
1414
type GRPCHealthMethod string
15+
type StatusImpact string
1516

1617
const (
1718
HTTP CheckType = "http"
@@ -35,6 +36,11 @@ const (
3536

3637
HealthService GRPCHealthService = "grpc.health.v1.Health"
3738
HealthMethod GRPCHealthMethod = "Check"
39+
40+
StatusImpactNone StatusImpact = "none"
41+
StatusImpactMinor StatusImpact = "minor"
42+
StatusImpactMajor StatusImpact = "major"
43+
StatusImpactCritical StatusImpact = "critical"
3844
)
3945

4046
type Comparer[T any] interface {
@@ -67,17 +73,17 @@ type Service struct {
6773

6874
//nolint:lll
6975
type CheckFields struct {
70-
ID string `yaml:"id" json:"id" validate:"required,min=1"`
76+
StatusImpact StatusImpact `yaml:"status_impact" json:"status_impact" validate:"required,oneof=none minor major critical"`
7177
Name string `yaml:"name" json:"name" validate:"required,min=1"`
7278
Service string `yaml:"service" json:"service" validate:"required,min=1"`
7379
Type CheckType `yaml:"type" json:"type" validate:"required,oneof=http tcp grpc tls dns"`
74-
Tags []string `yaml:"tags" json:"tags" validate:"omitempty,min=1,dive,min=1"`
80+
ID string `yaml:"id" json:"id" validate:"required,min=1"`
7581
AllowedBuckets []string `yaml:"allowed_buckets" json:"allowed_buckets" validate:"omitempty,dive,oneof=second minute hour day"`
7682
Jitter time.Duration `yaml:"jitter" json:"jitter" validate:"gte=0"`
77-
Retries int `yaml:"retries" json:"retries" validate:"required,gte=0"`
7883
FailureThreshold int `yaml:"failure_threshold" json:"failure_threshold" validate:"required,gte=1"`
7984
Interval time.Duration `yaml:"interval" json:"interval" validate:"required,gt=0ms"`
8085
Timeout time.Duration `yaml:"timeout" json:"timeout" validate:"required,gt=0ms"`
86+
Retries int `yaml:"retries" json:"retries" validate:"required,gte=0"`
8187
Enabled bool `yaml:"enabled" json:"enabled"`
8288
}
8389

0 commit comments

Comments
 (0)