Files
RentalManager/internal/category/category.go
2026-04-11 12:12:07 +02:00

141 lines
3.4 KiB
Go

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)
}