| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
af221806fb | fix: catch error in subscription, propagate it to the main file | 5 days ago |
|
|
2dec06e7b7 | feat: alerts define operator and resolved timestamp | 5 days ago |
|
|
c917ace01e | fix: health does not report infinite array of strings, add build files to gitignore | 5 days ago |
| @@ -26,6 +26,11 @@ vendor/ | |||||
| volumes/node-red/ | volumes/node-red/ | ||||
| main | main | ||||
| bridge | |||||
| decoder | |||||
| server | |||||
| location | |||||
| **/*.log | **/*.log | ||||
| ROADMAP.md | ROADMAP.md | ||||
| @@ -52,7 +52,11 @@ func New(cfg *config.Config) (*BridgeApp, error) { | |||||
| cleanup() | cleanup() | ||||
| return nil, err | return nil, err | ||||
| } | } | ||||
| mqttClient.Subscribe() | |||||
| if err := mqttClient.Subscribe(); err != nil { | |||||
| cleanup() | |||||
| return nil, err | |||||
| } | |||||
| return &BridgeApp{ | return &BridgeApp{ | ||||
| Cfg: cfg, | Cfg: cfg, | ||||
| @@ -1,8 +1,10 @@ | |||||
| package apiclient | package apiclient | ||||
| import ( | import ( | ||||
| "bytes" | |||||
| "encoding/json" | "encoding/json" | ||||
| "fmt" | "fmt" | ||||
| "io" | |||||
| "net/http" | "net/http" | ||||
| "github.com/AFASystems/presence/internal/pkg/config" | "github.com/AFASystems/presence/internal/pkg/config" | ||||
| @@ -30,8 +32,14 @@ func GetGateways(token string, client *http.Client, cfg *config.Config) ([]model | |||||
| return []model.Gateway{}, err | return []model.Gateway{}, err | ||||
| } | } | ||||
| body, err := io.ReadAll(res.Body) | |||||
| if err != nil { | |||||
| return []model.Gateway{}, err | |||||
| } | |||||
| fmt.Printf("Gateways raw response: %s\n", body) | |||||
| var i []model.Gateway | var i []model.Gateway | ||||
| err = json.NewDecoder(res.Body).Decode(&i) | |||||
| err = json.NewDecoder(bytes.NewReader(body)).Decode(&i) | |||||
| if err != nil { | if err != nil { | ||||
| return []model.Gateway{}, err | return []model.Gateway{}, err | ||||
| } | } | ||||
| @@ -48,10 +48,14 @@ func NewMQTTClient(cfg *config.Config, publishHandler func(mqtt.Message)) (*MQTT | |||||
| } | } | ||||
| // Subscribe subscribes to the default bridge topic. | // Subscribe subscribes to the default bridge topic. | ||||
| func (m *MQTTClient) Subscribe() { | |||||
| func (m *MQTTClient) Subscribe() error { | |||||
| token := m.Client.Subscribe(subscribeTopic, 1, nil) | token := m.Client.Subscribe(subscribeTopic, 1, nil) | ||||
| token.Wait() | token.Wait() | ||||
| if err := token.Error(); err != nil { | |||||
| return fmt.Errorf("mqtt subscribe: %w", err) | |||||
| } | |||||
| slog.Info("MQTT subscribed", "topic", subscribeTopic) | slog.Info("MQTT subscribed", "topic", subscribeTopic) | ||||
| return nil | |||||
| } | } | ||||
| // Disconnect disconnects the client with quiesce. | // Disconnect disconnects the client with quiesce. | ||||
| @@ -9,7 +9,7 @@ import ( | |||||
| // ServiceStatus represents the health of an external service (e.g. Kafka, database). | // ServiceStatus represents the health of an external service (e.g. Kafka, database). | ||||
| type ServiceStatus struct { | type ServiceStatus struct { | ||||
| Status string `json:"status"` // "up", "down", "unknown" | |||||
| Status string `json:"status"` // "up", "down", "unknown" | |||||
| Message string `json:"message,omitempty"` | Message string `json:"message,omitempty"` | ||||
| } | } | ||||
| @@ -56,6 +56,7 @@ func (b *BaseHealth) GetActiveWriters(m *kafkaclient.KafkaManager) { | |||||
| func (b *BaseHealth) GetActiveBeacons(m *AppState) { | func (b *BaseHealth) GetActiveBeacons(m *AppState) { | ||||
| beacons := m.GetAllBeacons() | beacons := m.GetAllBeacons() | ||||
| b.ActiveBeacons = []string{} | |||||
| for beacon := range beacons { | for beacon := range beacons { | ||||
| b.ActiveBeacons = append(b.ActiveBeacons, beacon) | b.ActiveBeacons = append(b.ActiveBeacons, beacon) | ||||
| } | } | ||||
| @@ -81,9 +81,7 @@ func AlertDeleteController(db *gorm.DB, ctx context.Context) http.HandlerFunc { | |||||
| func AlertUpdateStatusController(db *gorm.DB, ctx context.Context) http.HandlerFunc { | func AlertUpdateStatusController(db *gorm.DB, ctx context.Context) http.HandlerFunc { | ||||
| return func(w http.ResponseWriter, r *http.Request) { | return func(w http.ResponseWriter, r *http.Request) { | ||||
| id := mux.Vars(r)["id"] | id := mux.Vars(r)["id"] | ||||
| var body struct { | |||||
| Status string `json:"status"` | |||||
| } | |||||
| var body service.AlertPatchBody | |||||
| if err := json.NewDecoder(r.Body).Decode(&body); err != nil { | if err := json.NewDecoder(r.Body).Decode(&body); err != nil { | ||||
| response.BadRequest(w, "invalid request body") | response.BadRequest(w, "invalid request body") | ||||
| return | return | ||||
| @@ -92,7 +90,13 @@ func AlertUpdateStatusController(db *gorm.DB, ctx context.Context) http.HandlerF | |||||
| response.BadRequest(w, err.Error()) | response.BadRequest(w, err.Error()) | ||||
| return | return | ||||
| } | } | ||||
| if err := service.UpdateAlertStatus(id, body.Status, db, ctx); err != nil { | |||||
| if err := validation.Var(body.Operator, "required"); err != nil { | |||||
| response.BadRequest(w, err.Error()) | |||||
| return | |||||
| } | |||||
| if err := service.UpdateAlertStatus(id, body, db, ctx); err != nil { | |||||
| if errors.Is(err, gorm.ErrRecordNotFound) { | if errors.Is(err, gorm.ErrRecordNotFound) { | ||||
| response.NotFound(w, "alert not found") | response.NotFound(w, "alert not found") | ||||
| return | return | ||||
| @@ -3,9 +3,12 @@ package model | |||||
| import "time" | import "time" | ||||
| type Alert struct { | type Alert struct { | ||||
| ID string `json:"id" gorm:"primaryKey"` | |||||
| TrackerID string `json:"tracker_id"` | |||||
| Type string `json:"type"` | |||||
| Status string `json:"status"` | |||||
| Timestamp time.Time `json:"timestamp"` | |||||
| ID string `json:"id" gorm:"primaryKey"` | |||||
| TrackerID string `json:"tracker_id"` | |||||
| Type string `json:"type"` | |||||
| Status string `json:"status"` | |||||
| Timestamp time.Time `json:"timestamp"` | |||||
| ResolutionTimestamp time.Time `json:"resolution_timestamp"` | |||||
| Operator string `json:"operator"` | |||||
| Zone string `json:"zone"` | |||||
| } | } | ||||
| @@ -13,4 +13,5 @@ type Gateway struct { | |||||
| Notes string `json:"notes"` | Notes string `json:"notes"` | ||||
| Floor string `json:"floor"` | Floor string `json:"floor"` | ||||
| Building string `json:"building"` | Building string `json:"building"` | ||||
| Zone string `json:"zone"` | |||||
| } | } | ||||
| @@ -2,6 +2,7 @@ package service | |||||
| import ( | import ( | ||||
| "context" | "context" | ||||
| "time" | |||||
| "github.com/AFASystems/presence/internal/pkg/model" | "github.com/AFASystems/presence/internal/pkg/model" | ||||
| "gorm.io/gorm" | "gorm.io/gorm" | ||||
| @@ -38,9 +39,14 @@ func GetAlertById(id string, db *gorm.DB, ctx context.Context) (model.Alert, err | |||||
| return alert, nil | return alert, nil | ||||
| } | } | ||||
| type AlertPatchBody struct { | |||||
| Status string `json:"status"` | |||||
| Operator string `json:"operator"` | |||||
| } | |||||
| // UpdateAlertStatus updates the status of an alert by id. Returns gorm.ErrRecordNotFound if the alert does not exist. | // UpdateAlertStatus updates the status of an alert by id. Returns gorm.ErrRecordNotFound if the alert does not exist. | ||||
| func UpdateAlertStatus(id string, status string, db *gorm.DB, ctx context.Context) error { | |||||
| result := db.WithContext(ctx).Model(&model.Alert{}).Where("id = ?", id).Update("status", status) | |||||
| func UpdateAlertStatus(id string, body AlertPatchBody, db *gorm.DB, ctx context.Context) error { | |||||
| result := db.WithContext(ctx).Model(&model.Alert{}).Where("id = ?", id).Updates(model.Alert{Status: body.Status, Operator: body.Operator, ResolutionTimestamp: time.Now()}) | |||||
| if result.Error != nil { | if result.Error != nil { | ||||
| return result.Error | return result.Error | ||||
| } | } | ||||