167 lines
5.1 KiB
Go
167 lines
5.1 KiB
Go
package property
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/gorilla/mux"
|
|
)
|
|
|
|
// ── Model ─────────────────────────────────────────────────────────────────────
|
|
|
|
type Property struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Address string `json:"address"`
|
|
Type string `json:"type"` // airbnb | longterm
|
|
BankAccount string `json:"bank_account"`
|
|
IcalURL string `json:"ical_url"`
|
|
Notes string `json:"notes"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
}
|
|
|
|
// ── Store ─────────────────────────────────────────────────────────────────────
|
|
|
|
type Store struct{ db *sql.DB }
|
|
|
|
func NewStore(db *sql.DB) *Store { return &Store{db: db} }
|
|
|
|
func (s *Store) List() ([]Property, error) {
|
|
rows, err := s.db.Query(`SELECT id, name, address, type, COALESCE(bank_account,''), COALESCE(ical_url,''), COALESCE(notes,''), created_at FROM properties ORDER BY name`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var props []Property
|
|
for rows.Next() {
|
|
var p Property
|
|
if err := rows.Scan(&p.ID, &p.Name, &p.Address, &p.Type, &p.BankAccount, &p.IcalURL, &p.Notes, &p.CreatedAt); err != nil {
|
|
return nil, err
|
|
}
|
|
props = append(props, p)
|
|
}
|
|
return props, nil
|
|
}
|
|
|
|
func (s *Store) Get(id string) (*Property, error) {
|
|
var p Property
|
|
err := s.db.QueryRow(
|
|
`SELECT id, name, address, type, COALESCE(bank_account,''), COALESCE(ical_url,''), COALESCE(notes,''), created_at FROM properties WHERE id=?`, id,
|
|
).Scan(&p.ID, &p.Name, &p.Address, &p.Type, &p.BankAccount, &p.IcalURL, &p.Notes, &p.CreatedAt)
|
|
return &p, err
|
|
}
|
|
|
|
func (s *Store) Create(p *Property) error {
|
|
p.ID = uuid.NewString()
|
|
_, err := s.db.Exec(
|
|
`INSERT INTO properties (id, name, address, type, bank_account, ical_url, notes) VALUES (?,?,?,?,?,?,?)`,
|
|
p.ID, p.Name, p.Address, p.Type, p.BankAccount, p.IcalURL, p.Notes,
|
|
)
|
|
return err
|
|
}
|
|
|
|
func (s *Store) Update(p *Property) error {
|
|
_, err := s.db.Exec(
|
|
`UPDATE properties SET name=?, address=?, type=?, bank_account=?, ical_url=?, notes=? WHERE id=?`,
|
|
p.Name, p.Address, p.Type, p.BankAccount, p.IcalURL, p.Notes, p.ID,
|
|
)
|
|
return err
|
|
}
|
|
|
|
func (s *Store) Delete(id string) error {
|
|
_, err := s.db.Exec(`DELETE FROM properties WHERE id=?`, id)
|
|
return err
|
|
}
|
|
|
|
func (s *Store) ListWithIcal() ([]Property, error) {
|
|
rows, err := s.db.Query(`SELECT id, name, address, type, COALESCE(bank_account,''), COALESCE(ical_url,''), COALESCE(notes,''), created_at FROM properties WHERE ical_url IS NOT NULL AND ical_url != ''`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var props []Property
|
|
for rows.Next() {
|
|
var p Property
|
|
if err := rows.Scan(&p.ID, &p.Name, &p.Address, &p.Type, &p.BankAccount, &p.IcalURL, &p.Notes, &p.CreatedAt); err != nil {
|
|
return nil, err
|
|
}
|
|
props = append(props, p)
|
|
}
|
|
return props, nil
|
|
}
|
|
|
|
// ── Handler ───────────────────────────────────────────────────────────────────
|
|
|
|
type Handler struct{ store *Store }
|
|
|
|
func NewHandler(store *Store) *Handler { return &Handler{store: store} }
|
|
|
|
func (h *Handler) List(w http.ResponseWriter, r *http.Request) {
|
|
props, err := h.store.List()
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
if props == nil {
|
|
props = []Property{}
|
|
}
|
|
respond(w, props)
|
|
}
|
|
|
|
func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
|
|
id := mux.Vars(r)["id"]
|
|
p, err := h.store.Get(id)
|
|
if err == sql.ErrNoRows {
|
|
http.Error(w, "not found", http.StatusNotFound)
|
|
return
|
|
} else if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
respond(w, p)
|
|
}
|
|
|
|
func (h *Handler) Create(w http.ResponseWriter, r *http.Request) {
|
|
var p Property
|
|
if err := json.NewDecoder(r.Body).Decode(&p); err != nil {
|
|
http.Error(w, "invalid body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
if err := h.store.Create(&p); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusCreated)
|
|
respond(w, p)
|
|
}
|
|
|
|
func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
|
|
var p Property
|
|
if err := json.NewDecoder(r.Body).Decode(&p); err != nil {
|
|
http.Error(w, "invalid body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
p.ID = mux.Vars(r)["id"]
|
|
if err := h.store.Update(&p); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
respond(w, p)
|
|
}
|
|
|
|
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)
|
|
}
|