Files
woke/api/client.go
2026-02-03 16:49:05 +02:00

193 lines
4.2 KiB
Go

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
}