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

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