feat: scaffold app skeleton with config, ULID, health endpoints, compose stack (phase 1)

- internal .env loader (real env wins) + strictly validated config struct
- ULID package: 48-bit ms timestamp + 80-bit crypto randomness, sortable
- HTTP server with recovery/request-id/trusted-proxy/access-log middleware
- /healthz, /readyz (pluggable checkers), /metricsz (counter registry)
- multi-stage Dockerfile (alpine, non-root) + compose with healthchecks,
  app bound to 127.0.0.1:8787, mongo unpublished, graceful 15s shutdown

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
etalon
2026-06-12 18:10:30 +02:00
commit 976f5238f8
26 changed files with 2478 additions and 0 deletions
+34
View File
@@ -0,0 +1,34 @@
// Package httpx implements the application HTTP server: routing, middleware
// (recovery, request id, trusted-proxy client resolution, access logging),
// health endpoints, and shared JSON response helpers. Named httpx to avoid
// clashing with net/http; it lives in internal/http per the repository layout.
package httpx
import (
"encoding/json"
"log/slog"
"net/http"
)
// errorEnvelope is the consistent error body for every JSON error response:
// {"error":{"code":"...","message":"..."}}.
type errorEnvelope struct {
Error errorBody `json:"error"`
}
type errorBody struct {
Code string `json:"code"`
Message string `json:"message"`
}
func writeJSON(w http.ResponseWriter, status int, v any) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(status)
if err := json.NewEncoder(w).Encode(v); err != nil {
slog.Error("write json response", "err", err)
}
}
func writeError(w http.ResponseWriter, status int, code, message string) {
writeJSON(w, status, errorEnvelope{Error: errorBody{Code: code, Message: message}})
}