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