package category import ( "database/sql" "encoding/json" "net/http" "github.com/google/uuid" "github.com/gorilla/mux" ) type Category struct { ID string `json:"id"` Name string `json:"name"` Type string `json:"type"` // income | expense TaxDeductible bool `json:"tax_deductible"` Description string `json:"description"` } type Store struct{ db *sql.DB } func NewStore(db *sql.DB) *Store { return &Store{db: db} } func (s *Store) List(txType string) ([]Category, error) { query := `SELECT id, name, type, tax_deductible, COALESCE(description,'') FROM categories WHERE 1=1` args := []any{} if txType != "" { query += " AND type=?" args = append(args, txType) } query += " ORDER BY type, name" rows, err := s.db.Query(query, args...) if err != nil { return nil, err } defer rows.Close() var cats []Category for rows.Next() { var c Category var td int if err := rows.Scan(&c.ID, &c.Name, &c.Type, &td, &c.Description); err != nil { return nil, err } c.TaxDeductible = td == 1 cats = append(cats, c) } return cats, nil } func (s *Store) Create(c *Category) error { c.ID = uuid.NewString() td := 0 if c.TaxDeductible { td = 1 } _, err := s.db.Exec( `INSERT INTO categories (id, name, type, tax_deductible, description) VALUES (?,?,?,?,?)`, c.ID, c.Name, c.Type, td, c.Description, ) return err } func (s *Store) Update(c *Category) error { td := 0 if c.TaxDeductible { td = 1 } _, err := s.db.Exec( `UPDATE categories SET name=?, type=?, tax_deductible=?, description=? WHERE id=?`, c.Name, c.Type, td, c.Description, c.ID, ) return err } func (s *Store) Delete(id string) error { s.db.Exec(`UPDATE transactions SET category_id=NULL WHERE category_id=?`, id) _, err := s.db.Exec(`DELETE FROM categories WHERE id=?`, id) return err } type Handler struct{ store *Store } func NewHandler(store *Store) *Handler { return &Handler{store: store} } func (h *Handler) List(w http.ResponseWriter, r *http.Request) { cats, err := h.store.List(r.URL.Query().Get("type")) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if cats == nil { cats = []Category{} } respond(w, cats) } func (h *Handler) Create(w http.ResponseWriter, r *http.Request) { var c Category if err := json.NewDecoder(r.Body).Decode(&c); err != nil { http.Error(w, "invalid body", http.StatusBadRequest) return } if c.Name == "" || (c.Type != "income" && c.Type != "expense") { http.Error(w, "nom et type (income/expense) requis", http.StatusBadRequest) return } if err := h.store.Create(&c); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusCreated) respond(w, c) } func (h *Handler) Update(w http.ResponseWriter, r *http.Request) { var c Category if err := json.NewDecoder(r.Body).Decode(&c); err != nil { http.Error(w, "invalid body", http.StatusBadRequest) return } c.ID = mux.Vars(r)["id"] if err := h.store.Update(&c); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } respond(w, c) } func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) { if err := h.store.Delete(mux.Vars(r)["id"]); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusNoContent) } func respond(w http.ResponseWriter, v any) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(v) }