Expensive bee
This commit is contained in:
25
api/api.go
25
api/api.go
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"woke/db"
|
||||
)
|
||||
|
||||
@@ -12,13 +13,14 @@ type Notifier func()
|
||||
|
||||
// Server provides a REST API for alarm CRUD.
|
||||
type Server struct {
|
||||
store *db.Store
|
||||
mux *http.ServeMux
|
||||
notify Notifier
|
||||
store *db.Store
|
||||
mux *http.ServeMux
|
||||
notify Notifier
|
||||
password string
|
||||
}
|
||||
|
||||
func New(store *db.Store, notify Notifier) *Server {
|
||||
s := &Server{store: store, notify: notify}
|
||||
func New(store *db.Store, password string, notify Notifier) *Server {
|
||||
s := &Server{store: store, notify: notify, password: password}
|
||||
s.mux = http.NewServeMux()
|
||||
s.mux.HandleFunc("GET /api/alarms", s.listAlarms)
|
||||
s.mux.HandleFunc("GET /api/alarms/{id}", s.getAlarm)
|
||||
@@ -30,7 +32,18 @@ func New(store *db.Store, notify Notifier) *Server {
|
||||
}
|
||||
|
||||
func (s *Server) Handler() http.Handler {
|
||||
return s.mux
|
||||
if s.password == "" {
|
||||
return s.mux
|
||||
}
|
||||
// Wrap with auth middleware
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
auth := r.Header.Get("Authorization")
|
||||
if !strings.HasPrefix(auth, "Bearer ") || auth[7:] != s.password {
|
||||
writeError(w, http.StatusUnauthorized, "invalid or missing authorization")
|
||||
return
|
||||
}
|
||||
s.mux.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
type alarmRequest struct {
|
||||
|
||||
192
api/client.go
Normal file
192
api/client.go
Normal file
@@ -0,0 +1,192 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
"woke/db"
|
||||
)
|
||||
|
||||
// Client implements db.AlarmStore via HTTP calls to a remote woke server.
|
||||
type Client struct {
|
||||
baseURL string
|
||||
password string
|
||||
http *http.Client
|
||||
}
|
||||
|
||||
func NewClient(baseURL, password string) *Client {
|
||||
return &Client{
|
||||
baseURL: baseURL,
|
||||
password: password,
|
||||
http: &http.Client{Timeout: 10 * time.Second},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) do(method, path string, body any) (*http.Response, error) {
|
||||
var reqBody *bytes.Buffer
|
||||
if body != nil {
|
||||
data, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqBody = bytes.NewBuffer(data)
|
||||
} else {
|
||||
reqBody = &bytes.Buffer{}
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, c.baseURL+path, reqBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
if c.password != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+c.password)
|
||||
}
|
||||
return c.http.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) ListAlarms() ([]db.Alarm, error) {
|
||||
resp, err := c.do("GET", "/api/alarms", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("server returned %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var alarms []alarmResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&alarms); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]db.Alarm, len(alarms))
|
||||
for i, a := range alarms {
|
||||
result[i] = a.toAlarm()
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetAlarm(id int) (db.Alarm, error) {
|
||||
resp, err := c.do("GET", fmt.Sprintf("/api/alarms/%d", id), nil)
|
||||
if err != nil {
|
||||
return db.Alarm{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return db.Alarm{}, fmt.Errorf("server returned %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var a alarmResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&a); err != nil {
|
||||
return db.Alarm{}, err
|
||||
}
|
||||
return a.toAlarm(), nil
|
||||
}
|
||||
|
||||
func (c *Client) CreateAlarm(a db.Alarm) (int, error) {
|
||||
req := alarmRequest{
|
||||
Name: a.Name,
|
||||
Description: a.Description,
|
||||
Time: a.Time,
|
||||
Trigger: a.Trigger,
|
||||
SoundPath: a.SoundPath,
|
||||
Enabled: &a.Enabled,
|
||||
}
|
||||
resp, err := c.do("POST", "/api/alarms", req)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return 0, fmt.Errorf("server returned %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var created alarmResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&created); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return created.ID, nil
|
||||
}
|
||||
|
||||
func (c *Client) UpdateAlarm(a db.Alarm) error {
|
||||
req := alarmRequest{
|
||||
Name: a.Name,
|
||||
Description: a.Description,
|
||||
Time: a.Time,
|
||||
Trigger: a.Trigger,
|
||||
SoundPath: a.SoundPath,
|
||||
Enabled: &a.Enabled,
|
||||
}
|
||||
resp, err := c.do("PUT", fmt.Sprintf("/api/alarms/%d", a.ID), req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("server returned %d", resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) DeleteAlarm(id int) error {
|
||||
resp, err := c.do("DELETE", fmt.Sprintf("/api/alarms/%d", id), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return fmt.Errorf("server returned %d", resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) ToggleAlarm(id int) error {
|
||||
resp, err := c.do("PATCH", fmt.Sprintf("/api/alarms/%d/toggle", id), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("server returned %d", resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) MarkTriggered(id int) error {
|
||||
// Client mode doesn't mark triggered — server handles this
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a alarmResponse) toAlarm() db.Alarm {
|
||||
alarm := db.Alarm{
|
||||
ID: a.ID,
|
||||
Name: a.Name,
|
||||
Description: a.Description,
|
||||
Time: a.Time,
|
||||
Trigger: a.Trigger,
|
||||
SoundPath: a.SoundPath,
|
||||
Enabled: a.Enabled,
|
||||
SnoozeCount: a.SnoozeCount,
|
||||
}
|
||||
if t, err := time.Parse("2006-01-02T15:04:05Z", a.CreatedAt); err == nil {
|
||||
alarm.CreatedAt = t
|
||||
}
|
||||
if t, err := time.Parse("2006-01-02T15:04:05Z", a.UpdatedAt); err == nil {
|
||||
alarm.UpdatedAt = t
|
||||
}
|
||||
if a.LastTriggered != nil {
|
||||
if t, err := time.Parse("2006-01-02T15:04:05Z", *a.LastTriggered); err == nil {
|
||||
alarm.LastTriggered = &t
|
||||
}
|
||||
}
|
||||
return alarm
|
||||
}
|
||||
Reference in New Issue
Block a user