From bdeb82441d2f92ff026a0f72be2c8928ee43bf85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Z=C3=ADpek?= Date: Fri, 29 Mar 2024 09:55:51 +0100 Subject: [PATCH] Working on alerts --- server/app/alerts.go | 14 ++ server/go.mod | 1 + server/go.sum | 2 + server/integrations/integrations.go | 27 +++ server/integrations/telegram.go | 158 +++++++++++++++ server/main.go | 8 + server/routes/alerts.go | 127 ++++++++++++ server/routes/contact_points.go | 1 + server/services/alerts_service.go | 302 ++++++++++++++++++++++++++++ server/services/contact_points.go | 154 ++++++++++++++ server/services/services.go | 31 ++- 11 files changed, 816 insertions(+), 9 deletions(-) create mode 100644 server/app/alerts.go create mode 100644 server/integrations/integrations.go create mode 100644 server/integrations/telegram.go create mode 100644 server/routes/alerts.go create mode 100644 server/routes/contact_points.go create mode 100644 server/services/alerts_service.go create mode 100644 server/services/contact_points.go diff --git a/server/app/alerts.go b/server/app/alerts.go new file mode 100644 index 0000000..ec02767 --- /dev/null +++ b/server/app/alerts.go @@ -0,0 +1,14 @@ +package app + +import "time" + +func (s *Server) StartAlerts() { + ticker := time.NewTicker(time.Second * 10) + + go func() { + for { + s.Services.Alerts.EvaluateAlerts() + <-ticker.C + } + }() +} diff --git a/server/go.mod b/server/go.mod index b249ad3..eeb3725 100644 --- a/server/go.mod +++ b/server/go.mod @@ -14,6 +14,7 @@ require ( github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect github.com/go-playground/validator/v10 v10.11.0 // indirect + github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 // indirect github.com/goccy/go-json v0.9.10 // indirect github.com/golobby/cast v1.3.0 // indirect github.com/json-iterator/go v1.1.12 // indirect diff --git a/server/go.sum b/server/go.sum index 75e2343..0de950a 100644 --- a/server/go.sum +++ b/server/go.sum @@ -14,6 +14,8 @@ github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw= github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc= +github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8= github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM= github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.9.10 h1:hCeNmprSNLB8B8vQKWl6DpuH0t60oEs+TAk9a7CScKc= diff --git a/server/integrations/integrations.go b/server/integrations/integrations.go new file mode 100644 index 0000000..150727d --- /dev/null +++ b/server/integrations/integrations.go @@ -0,0 +1,27 @@ +package integrations + +type ContactPointMessageType string + +const ( + ContactPointEventAlertTriggered = "alert_triggered" + ContactPointEventTest = "test" +) + +type ContactPointEvent struct { + Type ContactPointMessageType + + AlertTriggeredEvent *ContactPointAlertTriggeredEvent +} + +type ContactPointAlertTriggeredEvent struct { + AlertId int64 + AlertName string + AlertValue string + SensorName string + CustomMessage string +} + +type ContactPointIntegration interface { + ProcessEvent(evt *ContactPointEvent, rawConfig string) error + ValidateConfig(rawConfig string) error +} diff --git a/server/integrations/telegram.go b/server/integrations/telegram.go new file mode 100644 index 0000000..aa5b59f --- /dev/null +++ b/server/integrations/telegram.go @@ -0,0 +1,158 @@ +package integrations + +import ( + "encoding/json" + "fmt" + "strings" + + tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" +) + +type TelegramIntegration struct{} + +type TelegramNotificationChannelConfig struct { + Id int64 `json:"id"` + ApiKey string `json:"apiKey"` + TargetChannel int64 `json:"targetChannel"` +} + +func (s TelegramIntegration) ProcessEvent(evt *ContactPointEvent, rawConfig string) error { + config := TelegramNotificationChannelConfig{} + err := json.Unmarshal([]byte(rawConfig), &config) + + if err != nil { + return fmt.Errorf("failed to parse telegram integration config - %w", err) + } + + bot, err := tgbotapi.NewBotAPI(config.ApiKey) + + if err != nil { + return err + } + + switch evt.Type { + case ContactPointEventAlertTriggered: + data := evt.AlertTriggeredEvent + + text := fmt.Sprintf("🚨 %s is at {value}", data.SensorName) + if data.CustomMessage != "" { + text = data.CustomMessage + } + + text = strings.Replace(text, "{value}", data.AlertValue, -1) + + msg := tgbotapi.NewMessage(config.TargetChannel, text) + msg.ParseMode = "Markdown" + _, err = bot.Send(msg) + return err + } + + /* + cameraUrl := "" + + if event != nil { + cameraUrl = fmt.Sprintf("%s/#/cameras/%d", evt.AppConfig.PublicUrl, event.CameraId) + } + + switch evt.Type { + case ChannelEventTest: + msg := tgbotapi.NewMessage(config.TargetChannel, "👋 Test message") + _, err = bot.Send(msg) + return err + + case ChannelEventMotionStart: + msg := tgbotapi.NewMessage(config.TargetChannel, fmt.Sprintf("👁 **Motion detected** by %s. [Go to stream](%s)", camera.Name, cameraUrl)) + msg.ParseMode = "Markdown" + _, err = bot.Send(msg) + return err + + case ChannelEventCameraLost: + msg := tgbotapi.NewMessage(config.TargetChannel, fmt.Sprintf("🔴 Connection to camera %s **lost**. [Go to camera](%s)", camera.Name, cameraUrl)) + msg.ParseMode = "Markdown" + _, err = bot.Send(msg) + return err + + case ChannelEventCameraFound: + msg := tgbotapi.NewMessage(config.TargetChannel, fmt.Sprintf("🟢 Connection to camera %s **re-established**. [Go to camera](%s)", camera.Name, cameraUrl)) + msg.ParseMode = "Markdown" + _, err = bot.Send(msg) + return err + + case ChannelEventPreview: + if !event.Preview.Valid { + return nil + } + + reader, err := os.Open(event.Preview.String) + + if err != nil { + return fmt.Errorf("failed to open preview file - %w", err) + } + + file := tgbotapi.FileReader{ + Name: event.Preview.String, + Reader: reader, + } + + previewMsg := tgbotapi.NewVideo(config.TargetChannel, file) + // previewMsg.Caption = fmt.Sprintf("Captured by %s", camera.Name) + _, err = bot.Send(previewMsg) + + if err != nil { + return err + } + + case ChannelEventMovie: + if !event.Movie.Valid { + return nil + } + + reader, err := os.Open(event.Movie.String) + + if err != nil { + return err + } + + videoFile := tgbotapi.FileReader{ + Name: event.Movie.String, + Reader: reader, + } + + msg := tgbotapi.NewVideo(config.TargetChannel, videoFile) + // msg.Caption = fmt.Sprintf("Captured by %s", camera.Name) + _, err = bot.Send(msg) + + return err + + case ChannelEventPicture: + if !event.Picture.Valid { + return nil + } + + reader, err := os.Open(event.Picture.String) + + if err != nil { + return err + } + + videoFile := tgbotapi.FileReader{ + Name: event.Movie.String, + Reader: reader, + } + + msg := tgbotapi.NewPhoto(config.TargetChannel, videoFile) + msg.Caption = fmt.Sprintf(`Captured by %s`, camera.Name) + + _, err = bot.Send(msg) + + return err + } + */ + + return nil +} + +func (s TelegramIntegration) ValidateConfig(rawConfig string) error { + config := TelegramNotificationChannelConfig{} + return json.Unmarshal([]byte(rawConfig), &config) +} diff --git a/server/main.go b/server/main.go index b6f68ed..8186a19 100644 --- a/server/main.go +++ b/server/main.go @@ -52,6 +52,11 @@ func main() { loginProtected.GET("/api/dashboards/:id", routes.GetDashboardById(server)) loginProtected.PUT("/api/dashboards/:id", routes.PutDashboard(server)) loginProtected.DELETE("/api/dashboards/:id", routes.DeleteDashboard(server)) + loginProtected.GET("/api/alerts", routes.GetAlerts(server)) + loginProtected.POST("/api/alerts", routes.PostAlerts(server)) + loginProtected.GET("/api/alerts/:alertId", routes.GetAlert(server)) + loginProtected.PUT("/api/alerts/:alertId", routes.PutAlert(server)) + loginProtected.DELETE("/api/alerts/:alertId", routes.DeleteAlert(server)) loginProtected.POST("/api/logout", routes.Logout(server)) // Routes accessible using auth key @@ -61,6 +66,9 @@ func main() { // Starts session cleanup goroutine server.StartCleaner() + // Starts alerts handling goroutine + server.StartAlerts() + // Run the server router.Run(fmt.Sprintf("%s:%d", server.Config.Ip, server.Config.Port)) } diff --git a/server/routes/alerts.go b/server/routes/alerts.go new file mode 100644 index 0000000..082c3a5 --- /dev/null +++ b/server/routes/alerts.go @@ -0,0 +1,127 @@ +package routes + +import ( + "basic-sensor-receiver/app" + "net/http" + "strconv" + + "github.com/gin-gonic/gin" +) + +type postAlertsBody struct { + ContactPointId int64 `json:"contactPointId"` + Name string `json:"name"` + Condition string `json:"condition"` + TriggerInterval int64 `json:"triggerInterval"` +} + +type putAlertsBody struct { + ContactPointId int64 `json:"contactPointId"` + Name string `json:"name"` + Condition string `json:"condition"` + TriggerInterval int64 `json:"triggerInterval"` +} + +func GetAlerts(s *app.Server) gin.HandlerFunc { + return func(c *gin.Context) { + alerts, err := s.Services.Alerts.GetList() + + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + + c.JSON(http.StatusOK, alerts) + } +} + +func PostAlerts(s *app.Server) gin.HandlerFunc { + return func(c *gin.Context) { + body := postAlertsBody{} + + if err := c.BindJSON(&body); err != nil { + c.AbortWithError(http.StatusBadRequest, err) + return + } + + alert, err := s.Services.Alerts.Create(body.ContactPointId, body.Name, body.Condition, body.TriggerInterval) + + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + + c.JSON(http.StatusOK, alert) + } +} + +func PutAlert(s *app.Server) gin.HandlerFunc { + return func(c *gin.Context) { + body := putAlertsBody{} + + alertId, err := getAlertId(c) + + if err != nil { + c.AbortWithError(http.StatusBadRequest, err) + return + } + + if err := c.BindJSON(&body); err != nil { + c.AbortWithError(http.StatusBadRequest, err) + return + } + + alert, err := s.Services.Alerts.Update(alertId, body.ContactPointId, body.Name, body.Condition, body.TriggerInterval) + + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + + c.JSON(http.StatusOK, alert) + } +} + +func GetAlert(s *app.Server) gin.HandlerFunc { + return func(c *gin.Context) { + alertId, err := getAlertId(c) + + if err != nil { + c.AbortWithError(http.StatusBadRequest, err) + return + } + + alert, err := s.Services.Alerts.GetById(alertId) + + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + + c.JSON(http.StatusOK, alert) + } +} + +func DeleteAlert(s *app.Server) gin.HandlerFunc { + return func(c *gin.Context) { + alertId, err := getAlertId(c) + + if err != nil { + c.AbortWithError(http.StatusBadRequest, err) + return + } + + if err := s.Services.Alerts.DeleteById(alertId); err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + + c.Status(http.StatusOK) + } +} + +func getAlertId(c *gin.Context) (int64, error) { + id := c.Param("alertId") + + return strconv.ParseInt(id, 10, 64) +} diff --git a/server/routes/contact_points.go b/server/routes/contact_points.go new file mode 100644 index 0000000..0db51ae --- /dev/null +++ b/server/routes/contact_points.go @@ -0,0 +1 @@ +package routes diff --git a/server/services/alerts_service.go b/server/services/alerts_service.go new file mode 100644 index 0000000..79bc6a0 --- /dev/null +++ b/server/services/alerts_service.go @@ -0,0 +1,302 @@ +package services + +import ( + "basic-sensor-receiver/integrations" + "encoding/json" + "fmt" + "time" +) + +type AlertsService struct { + ctx *Context +} + +type AlertItem struct { + Id int64 `json:"id"` + ContactPointId int64 `json:"contactPointId"` + Name string `json:"name"` + Condition string `json:"condition"` + + /* how long does the condition have to be true for the alert to go off */ + TriggerInterval int64 `json:"triggerInterval"` + + /* current alert status, possible values: good, pending, alerting */ + LastStatus string `json:"lastStatus"` + /* time at which was status last changed */ + LastStatusAt int64 `json:"lastStatusAt"` +} + +type AlertConditionSensorValue struct { + SensorId int64 `json:"sensorId"` + Condition string `json:"condition"` + Value float64 `json:"value"` +} + +type AlertConditionSensorLastContact struct { + SensorId int64 `json:"sensorId"` + Value int64 `json:"value"` + ValueUnit string `json:"valueUnit"` +} + +/* +Conditions examples: + sensor value (temperature) is less/more/equal to <> + last contact by sensor is more than 5 minutes ago + + +// When value of sensor 1 is less than 20 +{ + type: 'sensor_value', + sensorId: 1, + condition: 'less', + value: 20 +} + +// When last contact by sensor was more than 10 minutes ago +{ + type: 'sensor_last_contact', + sensorId: 1, + value: 10 + valueUnit: 'minutes' +} + +*/ + +func evaluateSensorValueCondition(condition *AlertConditionSensorValue, value *sensorValue) bool { + switch condition.Condition { + case "less": + return value.Value < condition.Value + case "more": + return value.Value > condition.Value + case "equal": + return value.Value == condition.Value + } + + return false +} + +func (s *AlertsService) EvaluateAlerts() error { + alerts, err := s.GetList() + + if err != nil { + return err + } + + for _, alert := range alerts { + err := s.EvaluateAlert(alert) + + if err != nil { + return err + } + } + + return nil + +} + +func (s *AlertsService) EvaluateAlert(alert *AlertItem) error { + condition := map[string]interface{}{} + err := json.Unmarshal([]byte(alert.Condition), &condition) + if err != nil { + return err + } + + newStatus := alert.LastStatus + lastValue := "" + conditionMet := false + sensorId := int64(-1) + + switch condition["type"].(string) { + case "sensor_value": + { + conditionData := AlertConditionSensorValue{ + SensorId: condition["sensorId"].(int64), + Condition: condition["condition"].(string), + Value: condition["value"].(float64), + } + + value, err := s.ctx.Services.SensorValues.GetLatest(conditionData.SensorId, time.Now().Unix()) + lastValue = fmt.Sprintf("%f", value.Value) + sensorId = int64(conditionData.SensorId) + + if err != nil { + return err + } + + conditionMet = evaluateSensorValueCondition(&conditionData, value) + + break + } + /* + TODO: + case "sensor_last_contact": + { + conditionData := AlertConditionSensorLastContact{ + SensorId: condition["sensorId"].(int64), + Value: condition["value"].(int64), + ValueUnit: condition["valueUnit"].(string), + } + + + value, err := s.ctx.Services.Sensors.GetLastContact(conditionData.SensorId) + + if err != nil { + return err + } + + conditionMet := time.Now().Unix()-value > conditionData.Value + + break + } + */ + } + + if conditionMet { + if newStatus == "good" { + newStatus = "alerting" + } else if newStatus == "pending" { + if time.Now().Unix()-alert.LastStatusAt > alert.TriggerInterval { + newStatus = "alerting" + } + } + } + + if newStatus != alert.LastStatus { + s.ctx.DB.Exec("UPDATE alerts SET last_status = ?, last_status_at = ? WHERE id = ?", newStatus, time.Now().Unix(), alert.Id) + + sensor, err := s.ctx.Services.Sensors.GetById(sensorId) + if err != nil { + return err + } + + contactPoint, err := s.ctx.Services.ContactPoints.GetById(alert.ContactPointId) + if err != nil { + return err + } + + dispatchService, err := contactPoint.getService(s.ctx) + if err != nil { + return err + } + + err = dispatchService.ProcessEvent(&integrations.ContactPointEvent{ + Type: "alert", + AlertTriggeredEvent: &integrations.ContactPointAlertTriggeredEvent{ + AlertId: alert.Id, + AlertName: alert.Name, + AlertValue: lastValue, + SensorName: sensor.Name, + CustomMessage: "", + }, + }, contactPoint.TypeConfig) + + if err != nil { + return err + } + } + + return nil +} + +func (s *AlertsService) GetList() ([]*AlertItem, error) { + rows, err := s.ctx.DB.Query("SELECT id, name, condition, trigger_interval, last_status, last_status_at FROM alerts ORDER BY name ASC") + + if err != nil { + return nil, err + } + + defer rows.Close() + + alerts := []*AlertItem{} + + for rows.Next() { + alert := AlertItem{} + + err := rows.Scan(&alert.Id, &alert.Name, &alert.Condition, &alert.TriggerInterval, &alert.LastStatus, &alert.LastStatusAt) + + if err != nil { + return nil, err + } + + alerts = append(alerts, &alert) + } + + err = rows.Err() + if err != nil { + return nil, err + } + + return alerts, nil +} + +func (s *AlertsService) GetById(id int64) (*AlertItem, error) { + alert := AlertItem{} + + row := s.ctx.DB.QueryRow("SELECT id, name, condition, trigger_interval, last_status, last_status_at FROM alerts WHERE id = ?", id) + err := row.Scan(&alert.Id, &alert.Name, &alert.Condition, &alert.TriggerInterval, &alert.LastStatus, &alert.LastStatusAt) + + if err != nil { + return nil, err + } + + return &alert, nil +} + +func (s *AlertsService) Create(contactPointId int64, name string, condition string, triggerInterval int64) (*AlertItem, error) { + alert := AlertItem{ + ContactPointId: contactPointId, + Name: name, + Condition: condition, + TriggerInterval: triggerInterval, + LastStatus: "good", + LastStatusAt: 0, + } + + res, err := s.ctx.DB.Exec( + "INSERT INTO alerts (contact_point_id, name, condition, trigger_interval, last_status, last_status_at) VALUES (?, ?, ?, ?, ?)", + alert.ContactPointId, alert.Name, alert.Condition, alert.TriggerInterval, alert.LastStatus, alert.LastStatusAt, + ) + + if err != nil { + return nil, err + } + + alert.Id, err = res.LastInsertId() + + if err != nil { + return nil, err + } + + return &alert, nil +} + +func (s *AlertsService) DeleteById(id int64) error { + _, err := s.ctx.DB.Exec("DELETE FROM alerts WHERE id = ?", id) + + if err != nil { + return err + } + + return nil +} + +func (s *AlertsService) Update(id int64, contactPointId int64, name string, condition string, triggerInterval int64) (*AlertItem, error) { + alert := AlertItem{ + Id: id, + ContactPointId: contactPointId, + Name: name, + Condition: condition, + TriggerInterval: triggerInterval, + } + + _, err := s.ctx.DB.Exec( + "UPDATE alerts SET contact_point_id = ?, name = ?, condition = ?, trigger_interval = ? WHERE id = ?", + alert.ContactPointId, alert.Name, alert.Condition, alert.TriggerInterval, alert.Id, + ) + + if err != nil { + return nil, err + } + + return &alert, nil +} diff --git a/server/services/contact_points.go b/server/services/contact_points.go new file mode 100644 index 0000000..ed02a04 --- /dev/null +++ b/server/services/contact_points.go @@ -0,0 +1,154 @@ +package services + +import ( + "basic-sensor-receiver/integrations" + "database/sql" + "errors" + "fmt" +) + +type ContactPointsService struct { + ctx *Context +} + +type ContactPointItem struct { + Id int64 `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + TypeConfig string `json:"typeConfig"` +} + +func (s *ContactPointsService) GetList() ([]ContactPointItem, error) { + contactPoints := make([]ContactPointItem, 0) + + rows, err := s.ctx.DB.Query("SELECT id, name, type, type_config FROM contact_points") + + if err != nil { + if err == sql.ErrNoRows { + return contactPoints, nil + } + + return nil, err + } + + defer rows.Close() + + for rows.Next() { + item := ContactPointItem{} + + err := rows.Scan(&item.Id, &item.Name, &item.Type, &item.TypeConfig) + if err != nil { + return nil, err + } + + contactPoints = append(contactPoints, item) + } + + err = rows.Err() + if err != nil { + return nil, err + } + + return contactPoints, nil +} + +func (s *ContactPointsService) Create(name string, contactType string, contactTypeConfig string) (*ContactPointItem, error) { + switch contactType { + case "telegram": + { + err := s.ctx.Integrations.Telegram.ValidateConfig(contactTypeConfig) + + if err != nil { + return nil, fmt.Errorf("failed to validate telegram config - %w", err) + } + } + } + + item := ContactPointItem{ + Name: name, + Type: contactType, + TypeConfig: contactTypeConfig, + } + + res, err := s.ctx.DB.Exec("INSERT INTO contact_points (name, type, type_config) VALUES (?, ?, ?)", item.Name, item.Type, item.TypeConfig) + + if err != nil { + return nil, err + } + + id, err := res.LastInsertId() + + if err != nil { + return nil, err + } + + item.Id = id + + return &item, nil +} + +func (s *ContactPointsService) GetById(id int64) (*ContactPointItem, error) { + item := ContactPointItem{} + + row := s.ctx.DB.QueryRow("SELECT id, name, type, type_config FROM contact_points WHERE id = ?", id) + err := row.Scan(&item.Id, &item.Name, &item.Type, &item.TypeConfig) + + if err != nil { + return nil, err + } + + return &item, nil +} + +func (s *ContactPointsService) Update(id int64, name string, contactType string, contactTypeConfig string) (*ContactPointItem, error) { + item := ContactPointItem{ + Id: id, + Name: name, + Type: contactType, + TypeConfig: contactTypeConfig, + } + + _, err := s.ctx.DB.Exec("UPDATE contact_points SET name = ?, type = ?, type_config = ? WHERE id = ?", item.Name, item.Type, item.TypeConfig, item.Id) + + if err != nil { + return nil, err + } + + return &item, nil +} + +func (s *ContactPointsService) Delete(id int64) error { + _, err := s.ctx.DB.Exec("DELETE FROM contact_points WHERE id = ?", id) + + return err +} + +func (s *ContactPointsService) Test(item *ContactPointItem) error { + service, err := item.getTestService(s.ctx) + + if err != nil { + return err + } + + return service.ProcessEvent(&integrations.ContactPointEvent{ + Type: integrations.ContactPointEventTest, + }, item.TypeConfig) +} + +func (channel *ContactPointItem) getService(ctx *Context) (integrations.ContactPointIntegration, error) { + switch channel.Type { + case "telegram": + return ctx.Integrations.Telegram, nil + } + + return nil, errors.New("unknown channel type") +} + +func (channel *ContactPointItem) getTestService(ctx *Context) (integrations.ContactPointIntegration, error) { + switch channel.Type { + case "telegram": + return ctx.Integrations.Telegram, nil + } + + return nil, errors.New("unknown channel type") +} diff --git a/server/services/services.go b/server/services/services.go index ab9e13e..07060b8 100644 --- a/server/services/services.go +++ b/server/services/services.go @@ -2,22 +2,30 @@ package services import ( "basic-sensor-receiver/config" + "basic-sensor-receiver/integrations" "database/sql" ) type Services struct { - SensorConfig *SensorConfigService - SensorValues *SensorValuesService - Sensors *SensorsService - Sessions *SessionsService - Auth *AuthService - Dashboards *DashboardsService + SensorConfig *SensorConfigService + SensorValues *SensorValuesService + Sensors *SensorsService + Sessions *SessionsService + Auth *AuthService + Dashboards *DashboardsService + Alerts *AlertsService + ContactPoints *ContactPointsService +} + +type Integrations struct { + Telegram *integrations.TelegramIntegration } type Context struct { - DB *sql.DB - Config *config.Config - Services *Services + DB *sql.DB + Config *config.Config + Services *Services + Integrations *Integrations } func InitializeServices(ctx *Context) *Services { @@ -31,6 +39,11 @@ func InitializeServices(ctx *Context) *Services { services.Sessions = &SessionsService{ctx: ctx} services.Auth = &AuthService{ctx: ctx} services.Dashboards = &DashboardsService{ctx: ctx} + services.Alerts = &AlertsService{ctx: ctx} + services.ContactPoints = &ContactPointsService{ctx: ctx} + + ctx.Integrations = &Integrations{} + ctx.Integrations.Telegram = &integrations.TelegramIntegration{} return &services }