mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 20:51:30 +00:00
7cb453a336
Mechanical reformat. The new 'gofmt drift' CI step (added in
ci-pipeline-cleanup Phase 4, commit 0f205a8) surfaced 111 files
with accumulated gofmt drift across cmd/, internal/, and deploy/test/.
Each file's diff is gofmt-standard: whitespace adjustments, intra-
group import sorting (alphabetical by import path within blank-line-
separated groups), and struct-tag column alignment. No semantic
changes — verified via 'git diff --ignore-all-space' which shows only
the line-position deltas from import reordering.
The gate stays in place after this commit. Going forward it catches
gofmt drift at PR time.
210 lines
6.4 KiB
Go
210 lines
6.4 KiB
Go
package handler
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"github.com/shankar0123/certctl/internal/repository"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/shankar0123/certctl/internal/api/middleware"
|
|
"github.com/shankar0123/certctl/internal/domain"
|
|
)
|
|
|
|
// ProfileService defines the service interface for certificate profile operations.
|
|
type ProfileService interface {
|
|
ListProfiles(ctx context.Context, page, perPage int) ([]domain.CertificateProfile, int64, error)
|
|
GetProfile(ctx context.Context, id string) (*domain.CertificateProfile, error)
|
|
CreateProfile(ctx context.Context, profile domain.CertificateProfile) (*domain.CertificateProfile, error)
|
|
UpdateProfile(ctx context.Context, id string, profile domain.CertificateProfile) (*domain.CertificateProfile, error)
|
|
DeleteProfile(ctx context.Context, id string) error
|
|
}
|
|
|
|
// ProfileHandler handles HTTP requests for certificate profile operations.
|
|
type ProfileHandler struct {
|
|
svc ProfileService
|
|
}
|
|
|
|
// NewProfileHandler creates a new ProfileHandler with a service dependency.
|
|
func NewProfileHandler(svc ProfileService) ProfileHandler {
|
|
return ProfileHandler{svc: svc}
|
|
}
|
|
|
|
// ListProfiles lists all certificate profiles.
|
|
// GET /api/v1/profiles?page=1&per_page=50
|
|
func (h ProfileHandler) ListProfiles(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
Error(w, http.StatusMethodNotAllowed, "Method not allowed")
|
|
return
|
|
}
|
|
|
|
requestID := middleware.GetRequestID(r.Context())
|
|
|
|
page := 1
|
|
perPage := 50
|
|
query := r.URL.Query()
|
|
if p := query.Get("page"); p != "" {
|
|
if parsed, err := strconv.Atoi(p); err == nil && parsed > 0 {
|
|
page = parsed
|
|
}
|
|
}
|
|
if pp := query.Get("per_page"); pp != "" {
|
|
if parsed, err := strconv.Atoi(pp); err == nil && parsed > 0 && parsed <= 500 {
|
|
perPage = parsed
|
|
}
|
|
}
|
|
|
|
profiles, total, err := h.svc.ListProfiles(r.Context(), page, perPage)
|
|
if err != nil {
|
|
ErrorWithRequestID(w, http.StatusInternalServerError, "Failed to list profiles", requestID)
|
|
return
|
|
}
|
|
|
|
response := PagedResponse{
|
|
Data: profiles,
|
|
Total: total,
|
|
Page: page,
|
|
PerPage: perPage,
|
|
}
|
|
|
|
JSON(w, http.StatusOK, response)
|
|
}
|
|
|
|
// GetProfile retrieves a single certificate profile by ID.
|
|
// GET /api/v1/profiles/{id}
|
|
func (h ProfileHandler) GetProfile(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
Error(w, http.StatusMethodNotAllowed, "Method not allowed")
|
|
return
|
|
}
|
|
|
|
requestID := middleware.GetRequestID(r.Context())
|
|
|
|
id := strings.TrimPrefix(r.URL.Path, "/api/v1/profiles/")
|
|
if id == "" || strings.Contains(id, "/") {
|
|
ErrorWithRequestID(w, http.StatusBadRequest, "Profile ID is required", requestID)
|
|
return
|
|
}
|
|
|
|
profile, err := h.svc.GetProfile(r.Context(), id)
|
|
if err != nil {
|
|
ErrorWithRequestID(w, http.StatusNotFound, "Profile not found", requestID)
|
|
return
|
|
}
|
|
|
|
JSON(w, http.StatusOK, profile)
|
|
}
|
|
|
|
// CreateProfile creates a new certificate profile.
|
|
// POST /api/v1/profiles
|
|
func (h ProfileHandler) CreateProfile(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
Error(w, http.StatusMethodNotAllowed, "Method not allowed")
|
|
return
|
|
}
|
|
|
|
requestID := middleware.GetRequestID(r.Context())
|
|
|
|
var profile domain.CertificateProfile
|
|
if err := json.NewDecoder(r.Body).Decode(&profile); err != nil {
|
|
ErrorWithRequestID(w, http.StatusBadRequest, "Invalid request body", requestID)
|
|
return
|
|
}
|
|
|
|
// Validate required fields
|
|
if err := ValidateRequired("name", profile.Name); err != nil {
|
|
ErrorWithRequestID(w, http.StatusBadRequest, err.Error(), requestID)
|
|
return
|
|
}
|
|
if err := ValidateStringLength("name", profile.Name, 255); err != nil {
|
|
ErrorWithRequestID(w, http.StatusBadRequest, err.Error(), requestID)
|
|
return
|
|
}
|
|
|
|
created, err := h.svc.CreateProfile(r.Context(), profile)
|
|
if err != nil {
|
|
// Check if it's a validation error from the service
|
|
if strings.Contains(err.Error(), "invalid") || strings.Contains(err.Error(), "required") ||
|
|
strings.Contains(err.Error(), "must be") || strings.Contains(err.Error(), "cannot") {
|
|
ErrorWithRequestID(w, http.StatusBadRequest, err.Error(), requestID)
|
|
return
|
|
}
|
|
ErrorWithRequestID(w, http.StatusInternalServerError, "Failed to create profile", requestID)
|
|
return
|
|
}
|
|
|
|
JSON(w, http.StatusCreated, created)
|
|
}
|
|
|
|
// UpdateProfile updates an existing certificate profile.
|
|
// PUT /api/v1/profiles/{id}
|
|
func (h ProfileHandler) UpdateProfile(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPut {
|
|
Error(w, http.StatusMethodNotAllowed, "Method not allowed")
|
|
return
|
|
}
|
|
|
|
requestID := middleware.GetRequestID(r.Context())
|
|
|
|
id := strings.TrimPrefix(r.URL.Path, "/api/v1/profiles/")
|
|
parts := strings.Split(id, "/")
|
|
if len(parts) == 0 || parts[0] == "" {
|
|
ErrorWithRequestID(w, http.StatusBadRequest, "Profile ID is required", requestID)
|
|
return
|
|
}
|
|
id = parts[0]
|
|
|
|
var profile domain.CertificateProfile
|
|
if err := json.NewDecoder(r.Body).Decode(&profile); err != nil {
|
|
ErrorWithRequestID(w, http.StatusBadRequest, "Invalid request body", requestID)
|
|
return
|
|
}
|
|
|
|
updated, err := h.svc.UpdateProfile(r.Context(), id, profile)
|
|
if err != nil {
|
|
if errors.Is(err, repository.ErrNotFound) {
|
|
ErrorWithRequestID(w, http.StatusNotFound, "Profile not found", requestID)
|
|
return
|
|
}
|
|
if strings.Contains(err.Error(), "invalid") || strings.Contains(err.Error(), "required") ||
|
|
strings.Contains(err.Error(), "must be") || strings.Contains(err.Error(), "cannot") {
|
|
ErrorWithRequestID(w, http.StatusBadRequest, err.Error(), requestID)
|
|
return
|
|
}
|
|
ErrorWithRequestID(w, http.StatusInternalServerError, "Failed to update profile", requestID)
|
|
return
|
|
}
|
|
|
|
JSON(w, http.StatusOK, updated)
|
|
}
|
|
|
|
// DeleteProfile deletes a certificate profile.
|
|
// DELETE /api/v1/profiles/{id}
|
|
func (h ProfileHandler) DeleteProfile(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodDelete {
|
|
Error(w, http.StatusMethodNotAllowed, "Method not allowed")
|
|
return
|
|
}
|
|
|
|
requestID := middleware.GetRequestID(r.Context())
|
|
|
|
id := strings.TrimPrefix(r.URL.Path, "/api/v1/profiles/")
|
|
if id == "" || strings.Contains(id, "/") {
|
|
ErrorWithRequestID(w, http.StatusBadRequest, "Profile ID is required", requestID)
|
|
return
|
|
}
|
|
|
|
if err := h.svc.DeleteProfile(r.Context(), id); err != nil {
|
|
if errors.Is(err, repository.ErrNotFound) {
|
|
ErrorWithRequestID(w, http.StatusNotFound, "Profile not found", requestID)
|
|
return
|
|
}
|
|
ErrorWithRequestID(w, http.StatusInternalServerError, "Failed to delete profile", requestID)
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|