141 lines
3.4 KiB
Go
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)
|
|
}
|