refactor: bookings and services handlers with shared formatting
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -19,35 +19,38 @@ import (
|
||||
|
||||
// Service represents a service in the system
|
||||
type Service struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Price float64 `json:"price"`
|
||||
DurationMinutes int `json:"duration_minutes"`
|
||||
IsActive bool `json:"is_active"`
|
||||
MinimumAgeRequired int `json:"minimum_age_required"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
CreatedBy *string `json:"created_by,omitempty"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Price float64 `json:"price"`
|
||||
DurationMinutes int `json:"duration_minutes"`
|
||||
IsActive bool `json:"is_active"`
|
||||
MinimumAgeRequired int `json:"minimum_age_required"`
|
||||
PatchTestDurationHours int `json:"patch_test_duration_hours"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
CreatedBy *string `json:"created_by,omitempty"`
|
||||
}
|
||||
|
||||
type ServiceResponse struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Price float64 `json:"price"`
|
||||
DurationMinutes int `json:"duration_minutes"`
|
||||
MinimumAgeRequired int `json:"minimum_age_required"`
|
||||
// Patch test status for non-admin users
|
||||
PatchTestStatus *string `json:"patch_test_status,omitempty"` // nil = not checked, "ok" = valid, "required" = no record, "expired" = record too old
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Price float64 `json:"price"`
|
||||
DurationMinutes int `json:"duration_minutes"`
|
||||
MinimumAgeRequired int `json:"minimum_age_required"`
|
||||
PatchTestDurationHours int `json:"patch_test_duration_hours"`
|
||||
// Patch test status for non-admin logged-in users
|
||||
PatchTestStatus *string `json:"patch_test_status,omitempty"`
|
||||
}
|
||||
|
||||
// CreateServiceRequest represents the request payload for creating a new service
|
||||
type CreateServiceRequest struct {
|
||||
Name string `json:"name" validate:"required,min=1,max=100"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Price float64 `json:"price" validate:"required,gt=0"`
|
||||
DurationMinutes int `json:"duration_minutes" validate:"required,gt=0"`
|
||||
MinimumAgeRequired int `json:"minimum_age_required" validate:"gte=0,lte=100"`
|
||||
Name string `json:"name" validate:"required,min=1,max=100"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Price float64 `json:"price" validate:"required,gt=0"`
|
||||
DurationMinutes int `json:"duration_minutes" validate:"required,gt=0"`
|
||||
MinimumAgeRequired int `json:"minimum_age_required" validate:"gte=0,lte=100"`
|
||||
PatchTestDurationHours int `json:"patch_test_duration_hours"`
|
||||
}
|
||||
|
||||
// ToggleServiceHandler handles toggling a service's active status
|
||||
@@ -115,7 +118,7 @@ func CreateServiceHandler(w http.ResponseWriter, r *http.Request) {
|
||||
createdBy = &userID
|
||||
}
|
||||
|
||||
// Insert new service (no patch test columns)
|
||||
// Insert new service
|
||||
query := `
|
||||
INSERT INTO services (
|
||||
name, description, price, duration_minutes,
|
||||
@@ -160,6 +163,29 @@ func CreateServiceHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Create patch test record if duration > 0
|
||||
if req.PatchTestDurationHours > 0 {
|
||||
_, err = db.DB.Exec(r.Context(), `
|
||||
INSERT INTO patch_tests (name, description, notice_duration_hours, expiry_months, service_ids)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
`, req.Name+" Patch Test", "Patch test for "+req.Name, req.PatchTestDurationHours, 6, []string{service.ID})
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to create patch test: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
service.PatchTestDurationHours = req.PatchTestDurationHours
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// Check for duplicate name or other constraints
|
||||
if err.Error() == "pq: duplicate key value violates unique constraint" {
|
||||
http.Error(w, "A service with this name already exists", http.StatusConflict)
|
||||
return
|
||||
}
|
||||
http.Error(w, "Failed to create service: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Convert nullable fields to pointers
|
||||
if createdByDB.Valid {
|
||||
service.CreatedBy = &createdByDB.String
|
||||
@@ -230,11 +256,12 @@ func ServicesHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// If not logged in or admin, return all services (current behavior)
|
||||
if !hasUser || userID == "" || role == "admin" {
|
||||
query := `
|
||||
SELECT id, name, description, price, duration_minutes,
|
||||
minimum_age_required
|
||||
FROM services
|
||||
WHERE is_active = TRUE
|
||||
ORDER BY name
|
||||
SELECT s.id, s.name, s.description, s.price, s.duration_minutes,
|
||||
s.minimum_age_required, COALESCE(pt.notice_duration_hours, 0)
|
||||
FROM services s
|
||||
LEFT JOIN patch_tests pt ON s.id = ANY(pt.service_ids)
|
||||
WHERE s.is_active = TRUE
|
||||
ORDER BY s.name
|
||||
`
|
||||
|
||||
rows, err := db.DB.Query(r.Context(), query)
|
||||
@@ -256,6 +283,7 @@ func ServicesHandler(w http.ResponseWriter, r *http.Request) {
|
||||
&service.Price,
|
||||
&service.DurationMinutes,
|
||||
&service.MinimumAgeRequired,
|
||||
&service.PatchTestDurationHours,
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to read service data: "+err.Error(), http.StatusInternalServerError)
|
||||
@@ -301,11 +329,12 @@ func ServicesHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Get all active services
|
||||
query := `
|
||||
SELECT id, name, description, price, duration_minutes,
|
||||
minimum_age_required
|
||||
FROM services
|
||||
WHERE is_active = TRUE
|
||||
ORDER BY name
|
||||
SELECT s.id, s.name, s.description, s.price, s.duration_minutes,
|
||||
s.minimum_age_required, COALESCE(pt.notice_duration_hours, 0)
|
||||
FROM services s
|
||||
LEFT JOIN patch_tests pt ON s.id = ANY(pt.service_ids)
|
||||
WHERE s.is_active = TRUE
|
||||
ORDER BY s.name
|
||||
`
|
||||
|
||||
rows, err := db.DB.Query(r.Context(), query)
|
||||
@@ -328,6 +357,7 @@ func ServicesHandler(w http.ResponseWriter, r *http.Request) {
|
||||
&service.Price,
|
||||
&service.DurationMinutes,
|
||||
&service.MinimumAgeRequired,
|
||||
&service.PatchTestDurationHours,
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to read service data: "+err.Error(), http.StatusInternalServerError)
|
||||
@@ -403,11 +433,12 @@ func ServicesEligibleForUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Get all active services
|
||||
query := `
|
||||
SELECT id, name, description, price, duration_minutes,
|
||||
minimum_age_required
|
||||
FROM services
|
||||
WHERE is_active = TRUE
|
||||
ORDER BY name
|
||||
SELECT s.id, s.name, s.description, s.price, s.duration_minutes,
|
||||
s.minimum_age_required, COALESCE(pt.notice_duration_hours, 0)
|
||||
FROM services s
|
||||
LEFT JOIN patch_tests pt ON s.id = ANY(pt.service_ids)
|
||||
WHERE s.is_active = TRUE
|
||||
ORDER BY s.name
|
||||
`
|
||||
|
||||
rows, err := db.DB.Query(r.Context(), query)
|
||||
@@ -430,6 +461,7 @@ func ServicesEligibleForUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||
&service.Price,
|
||||
&service.DurationMinutes,
|
||||
&service.MinimumAgeRequired,
|
||||
&service.PatchTestDurationHours,
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to read service data: "+err.Error(), http.StatusInternalServerError)
|
||||
@@ -543,10 +575,12 @@ func checkPatchTestStatus(ctx context.Context, userID, serviceID string) *string
|
||||
func AllServicesHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Query all services including inactive ones
|
||||
query := `
|
||||
SELECT id, name, description, price, duration_minutes, is_active,
|
||||
minimum_age_required, created_at, created_by
|
||||
FROM services
|
||||
ORDER BY is_active DESC, name
|
||||
SELECT s.id, s.name, s.description, s.price, s.duration_minutes, s.is_active,
|
||||
s.minimum_age_required, COALESCE(pt.notice_duration_hours, 0),
|
||||
s.created_at, s.created_by
|
||||
FROM services s
|
||||
LEFT JOIN patch_tests pt ON s.id = ANY(pt.service_ids)
|
||||
ORDER BY s.is_active DESC, s.name
|
||||
`
|
||||
|
||||
rows, err := db.DB.Query(r.Context(), query)
|
||||
@@ -570,6 +604,7 @@ func AllServicesHandler(w http.ResponseWriter, r *http.Request) {
|
||||
&service.DurationMinutes,
|
||||
&service.IsActive,
|
||||
&service.MinimumAgeRequired,
|
||||
&service.PatchTestDurationHours,
|
||||
&service.CreatedAt,
|
||||
&createdBy,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user