package server import ( "context" "encoding/json" "errors" "log/slog" "net/http" "time" "gitea.meghsakha.com/platform/tenant-registry/internal/config" "gitea.meghsakha.com/platform/tenant-registry/internal/store" ) type deps struct { cfg *config.Config log *slog.Logger tenant *store.Memory } func NewRouter(cfg *config.Config, log *slog.Logger) http.Handler { d := &deps{cfg: cfg, log: log, tenant: store.NewMemory()} mux := http.NewServeMux() mux.HandleFunc("GET /healthz", d.healthz) mux.HandleFunc("GET /v1/tenants/by-slug/{slug}", d.tenantBySlug) mux.HandleFunc("GET /v1/tenants/{id}", d.tenantByID) return logRequest(log)(mux) } func (d *deps) healthz(w http.ResponseWriter, _ *http.Request) { writeJSON(w, http.StatusOK, map[string]string{"status": "ok"}) } func (d *deps) tenantBySlug(w http.ResponseWriter, r *http.Request) { slug := r.PathValue("slug") ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second) defer cancel() t, err := d.tenant.BySlug(ctx, slug) if errors.Is(err, store.ErrNotFound) { writeError(w, http.StatusNotFound, "tenant_not_found", "no tenant with that slug") return } if err != nil { d.log.Error("tenant lookup failed", "err", err) writeError(w, http.StatusInternalServerError, "internal", "lookup failed") return } writeJSON(w, http.StatusOK, t) } func (d *deps) tenantByID(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second) defer cancel() t, err := d.tenant.ByID(ctx, id) if errors.Is(err, store.ErrNotFound) { writeError(w, http.StatusNotFound, "tenant_not_found", "no tenant with that id") return } if err != nil { d.log.Error("tenant lookup failed", "err", err) writeError(w, http.StatusInternalServerError, "internal", "lookup failed") return } writeJSON(w, http.StatusOK, t) } func writeJSON(w http.ResponseWriter, code int, body any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(code) _ = json.NewEncoder(w).Encode(body) } func writeError(w http.ResponseWriter, code int, kind, msg string) { writeJSON(w, code, map[string]string{"error": kind, "message": msg}) } func logRequest(log *slog.Logger) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() ww := &statusRecorder{ResponseWriter: w, code: 200} next.ServeHTTP(ww, r) log.Info("http", "method", r.Method, "path", r.URL.Path, "status", ww.code, "duration_ms", time.Since(start).Milliseconds(), ) }) } } type statusRecorder struct { http.ResponseWriter code int } func (s *statusRecorder) WriteHeader(c int) { s.code = c s.ResponseWriter.WriteHeader(c) }