package handler import ( "encoding/json" "errors" "log/slog" "net/http" "github.com/google/uuid" "learning-service/internal/repository" ) func writeJSON(w http.ResponseWriter, status int, v any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(v) } func writeError(w http.ResponseWriter, status int, msg string) { writeJSON(w, status, map[string]any{"error": msg}) } // writeRepoError — маппит repository.Err* на HTTP-коды. Для остальных // логируем и отдаём 500 без деталей (наружу не светим внутренние ошибки). func writeRepoError(w http.ResponseWriter, r *http.Request, err error, action string) { switch { case errors.Is(err, repository.ErrNotFound): writeError(w, http.StatusNotFound, "not found") case errors.Is(err, repository.ErrForbidden): writeError(w, http.StatusForbidden, "forbidden") default: slog.Error("repo error", "action", action, "error", err, "path", r.URL.Path) writeError(w, http.StatusInternalServerError, "internal error") } } func decodeJSON(r *http.Request, v any) error { dec := json.NewDecoder(r.Body) dec.DisallowUnknownFields() return dec.Decode(v) } func parseUUID(s string) (uuid.UUID, error) { if s == "" { return uuid.Nil, errors.New("empty uuid") } return uuid.Parse(s) } // userIDFromHeader — portal-gateway проставляет X-User-Id после валидации JWT. // Все НЕ-internal handler'ы читают отсюда; пустой = unauthorized. func userIDFromHeader(r *http.Request) (uuid.UUID, bool) { v := r.Header.Get("X-User-Id") if v == "" { return uuid.Nil, false } id, err := uuid.Parse(v) if err != nil { return uuid.Nil, false } return id, true }