up
This commit is contained in:
166
internal/property/property.go
Normal file
166
internal/property/property.go
Normal file
@@ -0,0 +1,166 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user