| package database_clients |
|
|
| import ( |
| "encoding/json" |
| "errors" |
| "log/slog" |
| "net/http" |
|
|
| "project/internal/api" |
| "project/internal/auth" |
| ) |
|
|
| type Handler struct { |
| svc *Service |
| } |
|
|
| func NewHandler(svc *Service) *Handler { |
| return &Handler{svc: svc} |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| func (h *Handler) ListDBTypes(w http.ResponseWriter, r *http.Request) { |
| writeJSON(w, http.StatusOK, api.APIResponse{ |
| Status: "success", |
| Message: "supported database types", |
| Data: h.svc.ListDBTypes(), |
| }) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| func (h *Handler) Create(w http.ResponseWriter, r *http.Request) { |
| var req struct { |
| UserID string `json:"user_id"` |
| Name string `json:"name"` |
| DBType string `json:"db_type"` |
| Credentials map[string]any `json:"credentials"` |
| } |
| if err := json.NewDecoder(r.Body).Decode(&req); err != nil { |
| writeJSON(w, http.StatusBadRequest, api.APIResponse{Status: "error", Message: "invalid request body"}) |
| return |
| } |
| if req.UserID == "" || req.Name == "" || req.DBType == "" || req.Credentials == nil { |
| writeJSON(w, http.StatusBadRequest, api.APIResponse{Status: "error", Message: "user_id, name, db_type, and credentials are required"}) |
| return |
| } |
| if rejectUserMismatch(w, r, req.UserID) { |
| return |
| } |
|
|
| client, isDuplicate, err := h.svc.Create(r.Context(), req.UserID, req.Name, req.DBType, req.Credentials) |
| if err != nil { |
| if errors.Is(err, ErrUnsupportedType) { |
| writeJSON(w, http.StatusBadRequest, api.APIResponse{Status: "error", Message: "unsupported database type"}) |
| return |
| } |
| slog.Error("database client create failed", "err", err) |
| writeJSON(w, http.StatusInternalServerError, api.APIResponse{Status: "error", Message: "internal server error"}) |
| return |
| } |
|
|
| status := http.StatusCreated |
| msg := "database client created" |
| if isDuplicate { |
| status = http.StatusOK |
| msg = "database client already exists" |
| } |
| writeJSON(w, status, api.APIResponse{Status: "success", Message: msg, Data: client}) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| func (h *Handler) List(w http.ResponseWriter, r *http.Request) { |
| userID := r.PathValue("user_id") |
| if rejectUserMismatch(w, r, userID) { |
| return |
| } |
| clients, err := h.svc.List(r.Context(), userID) |
| if err != nil { |
| writeJSON(w, http.StatusInternalServerError, api.APIResponse{Status: "error", Message: "internal server error"}) |
| return |
| } |
| writeJSON(w, http.StatusOK, api.APIResponse{Status: "success", Message: "database clients", Data: clients}) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| func (h *Handler) GetOne(w http.ResponseWriter, r *http.Request) { |
| userID := r.PathValue("user_id") |
| if rejectUserMismatch(w, r, userID) { |
| return |
| } |
| clientID := r.PathValue("client_id") |
| client, err := h.svc.Get(r.Context(), userID, clientID) |
| if err != nil { |
| switch { |
| case errors.Is(err, ErrNotFound): |
| writeJSON(w, http.StatusNotFound, api.APIResponse{Status: "error", Message: "database client not found"}) |
| case errors.Is(err, ErrForbidden): |
| writeJSON(w, http.StatusForbidden, api.APIResponse{Status: "error", Message: "access denied"}) |
| default: |
| writeJSON(w, http.StatusInternalServerError, api.APIResponse{Status: "error", Message: "internal server error"}) |
| } |
| return |
| } |
| writeJSON(w, http.StatusOK, api.APIResponse{Status: "success", Message: "database client", Data: client}) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| func (h *Handler) Update(w http.ResponseWriter, r *http.Request) { |
| clientID := r.PathValue("client_id") |
| userID := r.URL.Query().Get("user_id") |
| if userID == "" { |
| writeJSON(w, http.StatusBadRequest, api.APIResponse{Status: "error", Message: "user_id is required"}) |
| return |
| } |
| if rejectUserMismatch(w, r, userID) { |
| return |
| } |
| var req struct { |
| Name *string `json:"name"` |
| Credentials map[string]any `json:"credentials"` |
| Status *string `json:"status"` |
| } |
| if err := json.NewDecoder(r.Body).Decode(&req); err != nil { |
| writeJSON(w, http.StatusBadRequest, api.APIResponse{Status: "error", Message: "invalid request body"}) |
| return |
| } |
|
|
| client, err := h.svc.Update(r.Context(), clientID, userID, req.Name, req.Credentials, req.Status) |
| if err != nil { |
| switch { |
| case errors.Is(err, ErrNotFound): |
| writeJSON(w, http.StatusNotFound, api.APIResponse{Status: "error", Message: "database client not found"}) |
| case errors.Is(err, ErrForbidden): |
| writeJSON(w, http.StatusForbidden, api.APIResponse{Status: "error", Message: "access denied"}) |
| default: |
| writeJSON(w, http.StatusInternalServerError, api.APIResponse{Status: "error", Message: "internal server error"}) |
| } |
| return |
| } |
| writeJSON(w, http.StatusOK, api.APIResponse{Status: "success", Message: "database client updated", Data: client}) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) { |
| clientID := r.PathValue("client_id") |
| userID := r.URL.Query().Get("user_id") |
| if userID == "" { |
| writeJSON(w, http.StatusBadRequest, api.APIResponse{Status: "error", Message: "user_id is required"}) |
| return |
| } |
| if rejectUserMismatch(w, r, userID) { |
| return |
| } |
| if err := h.svc.Delete(r.Context(), clientID, userID); err != nil { |
| switch { |
| case errors.Is(err, ErrNotFound): |
| writeJSON(w, http.StatusNotFound, api.APIResponse{Status: "error", Message: "database client not found"}) |
| case errors.Is(err, ErrForbidden): |
| writeJSON(w, http.StatusForbidden, api.APIResponse{Status: "error", Message: "access denied"}) |
| default: |
| writeJSON(w, http.StatusInternalServerError, api.APIResponse{Status: "error", Message: "internal server error"}) |
| } |
| return |
| } |
| writeJSON(w, http.StatusOK, api.APIResponse{Status: "success", Message: "database client deleted"}) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| func (h *Handler) Ingest(w http.ResponseWriter, r *http.Request) { |
| clientID := r.PathValue("client_id") |
| userID := r.URL.Query().Get("user_id") |
| if userID == "" { |
| writeJSON(w, http.StatusBadRequest, api.APIResponse{Status: "error", Message: "user_id is required"}) |
| return |
| } |
| if rejectUserMismatch(w, r, userID) { |
| return |
| } |
| schema, err := h.svc.Ingest(r.Context(), clientID, userID) |
| if err != nil { |
| switch { |
| case errors.Is(err, ErrNotFound): |
| writeJSON(w, http.StatusNotFound, api.APIResponse{Status: "error", Message: "database client not found"}) |
| case errors.Is(err, ErrForbidden): |
| writeJSON(w, http.StatusForbidden, api.APIResponse{Status: "error", Message: "access denied"}) |
| case errors.Is(err, ErrInactive): |
| writeJSON(w, http.StatusConflict, api.APIResponse{Status: "error", Message: "database client is inactive"}) |
| default: |
| writeJSON(w, http.StatusInternalServerError, api.APIResponse{Status: "error", Message: err.Error()}) |
| } |
| return |
| } |
| writeJSON(w, http.StatusOK, api.APIResponse{Status: "success", Message: "schema ingested", Data: schema}) |
| } |
|
|
| func rejectUserMismatch(w http.ResponseWriter, r *http.Request, userID string) bool { |
| if err := auth.MatchContextUserID(r.Context(), userID); err != nil { |
| writeJSON(w, http.StatusForbidden, api.APIResponse{Status: "error", Message: "user_id does not match authenticated user"}) |
| return true |
| } |
| return false |
| } |
|
|
| func writeJSON(w http.ResponseWriter, status int, v any) { |
| w.Header().Set("Content-Type", "application/json") |
| w.WriteHeader(status) |
| _ = json.NewEncoder(w).Encode(v) |
| } |
|
|