package notifications import ( "encoding/json" "net/http" "github.com/henrygd/beszel/internal/entities/notification" "github.com/pocketbase/dbx" "github.com/pocketbase/pocketbase/apis" "github.com/pocketbase/pocketbase/core" ) // RegisterRoutes registers notification API routes func RegisterRoutes(app core.App, se *core.ServeEvent) { api := &NotificationAPI{app: app} group := se.Router.Group("/api/beszel/notifications") group.Bind(apis.RequireAuth()) group.GET("/", api.listNotifications) group.POST("/", api.createNotification) group.GET("/{id}", api.getNotification) group.PATCH("/{id}", api.updateNotification) group.DELETE("/{id}", api.deleteNotification) group.POST("/{id}/test", api.testNotification) } // NotificationAPI handles notification API requests type NotificationAPI struct { app core.App } // CreateNotificationRequest represents a notification creation request type CreateNotificationRequest struct { Name string `json:"name"` Type string `json:"type"` Settings map[string]interface{} `json:"settings"` IsDefault bool `json:"is_default"` } // UpdateNotificationRequest represents a notification update request type UpdateNotificationRequest struct { Name string `json:"name,omitempty"` Settings map[string]interface{} `json:"settings,omitempty"` IsDefault *bool `json:"is_default,omitempty"` Active *bool `json:"active,omitempty"` } // NotificationResponse represents a notification response type NotificationResponse struct { ID string `json:"id"` Name string `json:"name"` Type string `json:"type"` IsDefault bool `json:"is_default"` Active bool `json:"active"` Settings map[string]interface{} `json:"settings"` Created string `json:"created"` Updated string `json:"updated"` } // listNotifications lists all notifications for the authenticated user func (api *NotificationAPI) listNotifications(e *core.RequestEvent) error { authRecord := e.Auth if authRecord == nil { return e.UnauthorizedError("unauthorized", nil) } records, err := api.app.FindAllRecords("notifications", dbx.NewExp("user = {:user}", dbx.Params{"user": authRecord.Id}), ) if err != nil { return e.InternalServerError("failed to fetch notifications", err) } notifications := make([]NotificationResponse, 0, len(records)) for _, record := range records { notifications = append(notifications, api.recordToResponse(record)) } return e.JSON(http.StatusOK, notifications) } // createNotification creates a new notification func (api *NotificationAPI) createNotification(e *core.RequestEvent) error { authRecord := e.Auth if authRecord == nil { return e.UnauthorizedError("unauthorized", nil) } var req CreateNotificationRequest if err := json.NewDecoder(e.Request.Body).Decode(&req); err != nil { return e.BadRequestError("invalid request body", err) } if req.Name == "" || req.Type == "" { return e.BadRequestError("name and type are required", nil) } collection, err := api.app.FindCollectionByNameOrId("notifications") if err != nil { return e.InternalServerError("failed to find collection", err) } settingsJSON, _ := json.Marshal(req.Settings) record := core.NewRecord(collection) record.Set("name", req.Name) record.Set("type", req.Type) record.Set("settings", string(settingsJSON)) record.Set("is_default", req.IsDefault) record.Set("active", true) record.Set("user", authRecord.Id) if err := api.app.Save(record); err != nil { return e.InternalServerError("failed to create notification", err) } return e.JSON(http.StatusCreated, api.recordToResponse(record)) } // getNotification gets a single notification func (api *NotificationAPI) getNotification(e *core.RequestEvent) error { authRecord := e.Auth if authRecord == nil { return e.UnauthorizedError("unauthorized", nil) } id := e.Request.PathValue("id") record, err := api.app.FindRecordById("notifications", id) if err != nil { return e.NotFoundError("notification not found", err) } if record.GetString("user") != authRecord.Id { return e.ForbiddenError("not authorized", nil) } return e.JSON(http.StatusOK, api.recordToResponse(record)) } // updateNotification updates a notification func (api *NotificationAPI) updateNotification(e *core.RequestEvent) error { authRecord := e.Auth if authRecord == nil { return e.UnauthorizedError("unauthorized", nil) } id := e.Request.PathValue("id") record, err := api.app.FindRecordById("notifications", id) if err != nil { return e.NotFoundError("notification not found", err) } if record.GetString("user") != authRecord.Id { return e.ForbiddenError("not authorized", nil) } var req UpdateNotificationRequest if err := json.NewDecoder(e.Request.Body).Decode(&req); err != nil { return e.BadRequestError("invalid request body", err) } if req.Name != "" { record.Set("name", req.Name) } if req.Settings != nil { settingsJSON, _ := json.Marshal(req.Settings) record.Set("settings", string(settingsJSON)) } if req.IsDefault != nil { record.Set("is_default", *req.IsDefault) } if req.Active != nil { record.Set("active", *req.Active) } if err := api.app.Save(record); err != nil { return e.InternalServerError("failed to update notification", err) } return e.JSON(http.StatusOK, api.recordToResponse(record)) } // deleteNotification deletes a notification func (api *NotificationAPI) deleteNotification(e *core.RequestEvent) error { authRecord := e.Auth if authRecord == nil { return e.UnauthorizedError("unauthorized", nil) } id := e.Request.PathValue("id") record, err := api.app.FindRecordById("notifications", id) if err != nil { return e.NotFoundError("notification not found", err) } if record.GetString("user") != authRecord.Id { return e.ForbiddenError("not authorized", nil) } if err := api.app.Delete(record); err != nil { return e.InternalServerError("failed to delete notification", err) } return e.NoContent(http.StatusNoContent) } // testNotification sends a test notification func (api *NotificationAPI) testNotification(e *core.RequestEvent) error { authRecord := e.Auth if authRecord == nil { return e.UnauthorizedError("unauthorized", nil) } id := e.Request.PathValue("id") record, err := api.app.FindRecordById("notifications", id) if err != nil { return e.NotFoundError("notification not found", err) } if record.GetString("user") != authRecord.Id { return e.ForbiddenError("not authorized", nil) } notif := ¬ification.Notification{ ID: record.Id, Name: record.GetString("name"), Type: record.GetString("type"), Active: record.GetBool("active"), } if settingsJSON := record.GetString("settings"); settingsJSON != "" { var settings map[string]interface{} if err := json.Unmarshal([]byte(settingsJSON), &settings); err == nil { notif.Settings = settings } } dispatcher := NewDispatcher(api.app) msg := ¬ification.NotificationMessage{ Title: "Test Notification", Body: "This is a test notification from Beszel.", MonitorName: "Test Monitor", Status: "UP", Message: "Test message", } provider, err := dispatcher.getProvider(notif) if err != nil { return e.InternalServerError("failed to create provider", err) } if err := provider.Send(msg); err != nil { return e.InternalServerError("failed to send test notification", err) } return e.JSON(http.StatusOK, map[string]string{"status": "sent"}) } // recordToResponse converts a record to a response func (api *NotificationAPI) recordToResponse(record *core.Record) NotificationResponse { var settings map[string]interface{} if settingsJSON := record.GetString("settings"); settingsJSON != "" { json.Unmarshal([]byte(settingsJSON), &settings) } return NotificationResponse{ ID: record.Id, Name: record.GetString("name"), Type: record.GetString("type"), IsDefault: record.GetBool("is_default"), Active: record.GetBool("active"), Settings: settings, Created: record.GetDateTime("created").String(), Updated: record.GetDateTime("updated").String(), } }