Stage 16: deploy infra & test contour
- backend + gateway multi-stage distroless Dockerfiles; the gateway embeds and
serves the SPA at / and /telegram/ via go:embed (committed dist placeholder,
real build baked in by the image's node stage)
- deploy/docker-compose.yml: backend + gateway + Postgres + Telegram connector
(VPN sidecar) + OTel Collector + Prometheus (15d) + Tempo (72h) + Grafana,
fronted by a caddy owning a single /_gm Basic-Auth (admin console + Grafana
subpath); inter-service on a private network, only caddy on the edge network
- new metrics: backend accounts_created_total{kind} (robots excluded) and an
in-memory gateway active_users{window=24h,7d} gauge
- CI: single .gitea/workflows/ci.yaml (unit/integration/ui + a gated test-contour
deploy) on the new feature/* -> development -> master branch model; the old
go-unit/integration/ui-test workflows are folded in; the connector-scoped
compose is retired (superseded by deploy/)
- docs: ARCHITECTURE §11/§12/§13, root + gateway READMEs, CLAUDE.md branching,
PLAN.md (stage 16 done + refinements + Stage 17 forward-notes)
This commit is contained in:
@@ -93,12 +93,14 @@ type Identity struct {
|
||||
|
||||
// Store is the Postgres-backed query surface for accounts and identities.
|
||||
type Store struct {
|
||||
db *sql.DB
|
||||
db *sql.DB
|
||||
metrics *accountMetrics
|
||||
}
|
||||
|
||||
// NewStore constructs a Store wrapping db.
|
||||
// NewStore constructs a Store wrapping db. Metrics default to a no-op meter until
|
||||
// SetMetrics installs the real one during startup wiring.
|
||||
func NewStore(db *sql.DB) *Store {
|
||||
return &Store{db: db}
|
||||
return &Store{db: db, metrics: defaultAccountMetrics()}
|
||||
}
|
||||
|
||||
// ProvisionByIdentity returns the account bound to (kind, externalID), creating
|
||||
@@ -331,6 +333,11 @@ func (s *Store) create(ctx context.Context, kind, externalID string, seed provis
|
||||
if err != nil {
|
||||
return Account{}, fmt.Errorf("account: create for identity (%s, %s): %w", kind, externalID, err)
|
||||
}
|
||||
// Count genuinely new durable accounts; robots are a fixed provisioned pool,
|
||||
// not users, so they are excluded.
|
||||
if kind != KindRobot {
|
||||
s.metrics.recordCreated(ctx, kind)
|
||||
}
|
||||
return created, nil
|
||||
}
|
||||
|
||||
@@ -355,6 +362,7 @@ func (s *Store) ProvisionGuest(ctx context.Context) (Account, error) {
|
||||
if err := stmt.QueryContext(ctx, s.db, &row); err != nil {
|
||||
return Account{}, fmt.Errorf("account: provision guest: %w", err)
|
||||
}
|
||||
s.metrics.recordCreated(ctx, kindGuest)
|
||||
return modelToAccount(row), nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user