Files
aura-crm/production/backend/internal/cartridgecatalog/handler.go
T

161 lines
4.8 KiB
Go

package cartridgecatalog
import (
"context"
"strings"
"production/internal/dbutil"
"github.com/gofiber/fiber/v2"
"github.com/jackc/pgx/v5/pgxpool"
)
type Handler struct {
db *pgxpool.Pool
}
func NewHandler(db *pgxpool.Pool) *Handler {
return &Handler{db: db}
}
type brandRow struct {
ID string `json:"id"`
Name string `json:"name"`
}
type modelRow struct {
ID string `json:"id"`
BrandID string `json:"brand_id"`
ModelCode string `json:"model_code"`
}
func (h *Handler) ListBrands(c *fiber.Ctx) error {
rows, err := h.db.Query(context.Background(), `SELECT id, name FROM cartridge_brands ORDER BY name`)
if err != nil {
return c.Status(500).JSON(fiber.Map{"error": "internal error"})
}
defer rows.Close()
brands := []brandRow{}
for rows.Next() {
var b brandRow
if err := rows.Scan(&b.ID, &b.Name); err != nil {
return c.Status(500).JSON(fiber.Map{"error": "internal error"})
}
brands = append(brands, b)
}
return c.JSON(brands)
}
// CreateBrand is open to any staff role — same reasoning as
// devicecatalog.CreateBrand, a brand name carries no structural risk.
func (h *Handler) CreateBrand(c *fiber.Ctx) error {
var body struct {
Name string `json:"name"`
}
if err := c.BodyParser(&body); err != nil {
return c.Status(400).JSON(fiber.Map{"error": "invalid request body"})
}
if msg := (brandInput{Name: body.Name}).validate(); msg != "" {
return c.Status(400).JSON(fiber.Map{"error": msg})
}
name := strings.TrimSpace(body.Name)
var id string
err := h.db.QueryRow(context.Background(),
`INSERT INTO cartridge_brands (name) VALUES ($1) RETURNING id`, name,
).Scan(&id)
if err != nil {
if dbutil.IsUniqueViolation(err) {
return c.Status(409).JSON(fiber.Map{"error": "brand already exists"})
}
return c.Status(500).JSON(fiber.Map{"error": "internal error"})
}
return c.Status(201).JSON(fiber.Map{"id": id, "name": name})
}
func (h *Handler) DeleteBrand(c *fiber.Ctx) error {
id := c.Params("id")
tag, err := h.db.Exec(context.Background(), `DELETE FROM cartridge_brands WHERE id = $1::uuid`, id)
if err != nil {
if dbutil.IsFKViolation(err) {
return c.Status(409).JSON(fiber.Map{"error": "brand is used by existing models or cartridges"})
}
return c.Status(500).JSON(fiber.Map{"error": "internal error"})
}
if tag.RowsAffected() == 0 {
return c.Status(404).JSON(fiber.Map{"error": "brand not found"})
}
return c.JSON(fiber.Map{"ok": true})
}
// ListModels returns models for a brand; brand_id is required — no reason
// to ever list every model across every brand for a select-by-brand UI.
func (h *Handler) ListModels(c *fiber.Ctx) error {
brandID := c.Query("brand_id")
if brandID == "" {
return c.Status(400).JSON(fiber.Map{"error": "brand_id is required"})
}
rows, err := h.db.Query(context.Background(),
`SELECT id, brand_id, model_code FROM cartridge_models WHERE brand_id = $1::uuid ORDER BY model_code`, brandID)
if err != nil {
return c.Status(500).JSON(fiber.Map{"error": "internal error"})
}
defer rows.Close()
models := []modelRow{}
for rows.Next() {
var m modelRow
if err := rows.Scan(&m.ID, &m.BrandID, &m.ModelCode); err != nil {
return c.Status(500).JSON(fiber.Map{"error": "internal error"})
}
models = append(models, m)
}
return c.JSON(models)
}
func (h *Handler) CreateModel(c *fiber.Ctx) error {
var body struct {
BrandID string `json:"brand_id"`
ModelCode string `json:"model_code"`
}
if err := c.BodyParser(&body); err != nil {
return c.Status(400).JSON(fiber.Map{"error": "invalid request body"})
}
if msg := (modelInput{BrandID: body.BrandID, ModelCode: body.ModelCode}).validate(); msg != "" {
return c.Status(400).JSON(fiber.Map{"error": msg})
}
code := strings.TrimSpace(body.ModelCode)
var id string
err := h.db.QueryRow(context.Background(),
`INSERT INTO cartridge_models (brand_id, model_code) VALUES ($1::uuid, $2) RETURNING id`,
body.BrandID, code,
).Scan(&id)
if err != nil {
if dbutil.IsUniqueViolation(err) {
return c.Status(409).JSON(fiber.Map{"error": "model already exists for this brand"})
}
if dbutil.IsFKViolation(err) {
return c.Status(400).JSON(fiber.Map{"error": "unknown brand_id"})
}
return c.Status(500).JSON(fiber.Map{"error": "internal error"})
}
return c.Status(201).JSON(fiber.Map{"id": id, "brand_id": body.BrandID, "model_code": code})
}
func (h *Handler) DeleteModel(c *fiber.Ctx) error {
id := c.Params("id")
tag, err := h.db.Exec(context.Background(), `DELETE FROM cartridge_models WHERE id = $1::uuid`, id)
if err != nil {
if dbutil.IsFKViolation(err) {
return c.Status(409).JSON(fiber.Map{"error": "model is used by existing cartridges"})
}
return c.Status(500).JSON(fiber.Map{"error": "internal error"})
}
if tag.RowsAffected() == 0 {
return c.Status(404).JSON(fiber.Map{"error": "model not found"})
}
return c.JSON(fiber.Map{"ok": true})
}