682 lines
20 KiB
Go
682 lines
20 KiB
Go
package handlers
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"game-admin/internal/models"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/uptrace/bun"
|
|
)
|
|
|
|
var defaultNotificationMacroKeys = []string{"time_second", "image", "login"}
|
|
|
|
func (h *GameHandler) ListNotifications(c *gin.Context) {
|
|
gameID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid game id"})
|
|
return
|
|
}
|
|
|
|
if err := h.ensureNotificationModuleEnabled(c, gameID); err != nil {
|
|
renderNotificationModuleError(c, err)
|
|
return
|
|
}
|
|
|
|
var notifications []models.Notification
|
|
err = h.db.NewSelect().
|
|
Model(¬ifications).
|
|
Relation("Descriptions", func(q *bun.SelectQuery) *bun.SelectQuery {
|
|
return q.Relation("Language").OrderExpr("nd.id ASC")
|
|
}).
|
|
Relation("Variables", func(q *bun.SelectQuery) *bun.SelectQuery {
|
|
return q.OrderExpr("nvd.id ASC")
|
|
}).
|
|
Relation("Entries", func(q *bun.SelectQuery) *bun.SelectQuery {
|
|
return q.OrderExpr("ne.id ASC")
|
|
}).
|
|
Where("n.game_id = ?", gameID).
|
|
OrderExpr("n.id DESC").
|
|
Scan(c)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
if err := h.loadNotificationEntryVariables(c, notifications); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
for i := range notifications {
|
|
prepareNotificationResponse(¬ifications[i])
|
|
}
|
|
|
|
c.JSON(http.StatusOK, notifications)
|
|
}
|
|
|
|
func (h *GameHandler) GetNotification(c *gin.Context) {
|
|
gameID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid game id"})
|
|
return
|
|
}
|
|
notificationID, err := strconv.ParseInt(c.Param("notification_id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid notification id"})
|
|
return
|
|
}
|
|
|
|
if err := h.ensureNotificationModuleEnabled(c, gameID); err != nil {
|
|
renderNotificationModuleError(c, err)
|
|
return
|
|
}
|
|
|
|
notification, err := h.getNotification(c, gameID, notificationID)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Notification not found"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, notification)
|
|
}
|
|
|
|
func (h *GameHandler) CreateNotification(c *gin.Context) {
|
|
gameID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid game id"})
|
|
return
|
|
}
|
|
|
|
var req models.UpsertNotificationRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if err := validateUpsertNotificationRequest(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if err := h.ensureNotificationModuleEnabled(c, gameID); err != nil {
|
|
renderNotificationModuleError(c, err)
|
|
return
|
|
}
|
|
if err := h.ensureLanguagesExist(c, req.Descriptions); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
tx, err := h.db.BeginTx(c, nil)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
committed := false
|
|
defer func() {
|
|
if !committed {
|
|
_ = tx.Rollback()
|
|
}
|
|
}()
|
|
|
|
now := time.Now()
|
|
notification := &models.Notification{
|
|
GameID: gameID,
|
|
Name: strings.TrimSpace(req.Name),
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
_, err = tx.NewInsert().Model(notification).Exec(c)
|
|
if err != nil {
|
|
c.JSON(http.StatusConflict, gin.H{"error": "Notification already exists for this game"})
|
|
return
|
|
}
|
|
|
|
if err := saveNotificationDetails(c, tx, notification.ID, req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
if err := tx.Commit(); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
committed = true
|
|
|
|
created, err := h.getNotification(c, gameID, notification.ID)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, created)
|
|
}
|
|
|
|
func (h *GameHandler) UpdateNotification(c *gin.Context) {
|
|
gameID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid game id"})
|
|
return
|
|
}
|
|
notificationID, err := strconv.ParseInt(c.Param("notification_id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid notification id"})
|
|
return
|
|
}
|
|
|
|
var req models.UpsertNotificationRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if err := validateUpsertNotificationRequest(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if err := h.ensureNotificationModuleEnabled(c, gameID); err != nil {
|
|
renderNotificationModuleError(c, err)
|
|
return
|
|
}
|
|
if err := h.ensureLanguagesExist(c, req.Descriptions); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if _, err := h.getNotification(c, gameID, notificationID); err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Notification not found"})
|
|
return
|
|
}
|
|
|
|
tx, err := h.db.BeginTx(c, nil)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
committed := false
|
|
defer func() {
|
|
if !committed {
|
|
_ = tx.Rollback()
|
|
}
|
|
}()
|
|
|
|
_, err = tx.NewUpdate().
|
|
Model((*models.Notification)(nil)).
|
|
Set("name = ?", strings.TrimSpace(req.Name)).
|
|
Set("updated_at = ?", time.Now()).
|
|
Where("id = ? AND game_id = ?", notificationID, gameID).
|
|
Exec(c)
|
|
if err != nil {
|
|
c.JSON(http.StatusConflict, gin.H{"error": "Could not update notification"})
|
|
return
|
|
}
|
|
|
|
if err := deleteNotificationDetails(c, tx, notificationID); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
if err := saveNotificationDetails(c, tx, notificationID, req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
if err := tx.Commit(); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
committed = true
|
|
|
|
updated, err := h.getNotification(c, gameID, notificationID)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, updated)
|
|
}
|
|
|
|
func (h *GameHandler) DeleteNotification(c *gin.Context) {
|
|
gameID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid game id"})
|
|
return
|
|
}
|
|
notificationID, err := strconv.ParseInt(c.Param("notification_id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid notification id"})
|
|
return
|
|
}
|
|
|
|
if err := h.ensureNotificationModuleEnabled(c, gameID); err != nil {
|
|
renderNotificationModuleError(c, err)
|
|
return
|
|
}
|
|
if _, err := h.getNotification(c, gameID, notificationID); err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Notification not found"})
|
|
return
|
|
}
|
|
|
|
tx, err := h.db.BeginTx(c, nil)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
committed := false
|
|
defer func() {
|
|
if !committed {
|
|
_ = tx.Rollback()
|
|
}
|
|
}()
|
|
|
|
if err := deleteNotificationDetails(c, tx, notificationID); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
_, err = tx.NewDelete().
|
|
Model((*models.Notification)(nil)).
|
|
Where("id = ? AND game_id = ?", notificationID, gameID).
|
|
Exec(c)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
if err := tx.Commit(); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
committed = true
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "Notification deleted"})
|
|
}
|
|
|
|
func (h *GameHandler) ensureNotificationModuleEnabled(c *gin.Context, gameID int64) error {
|
|
exists, err := h.gameExists(c, gameID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !exists {
|
|
return fmt.Errorf("game_not_found")
|
|
}
|
|
|
|
hasModule, err := h.gameHasModule(c, gameID, "notification")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !hasModule {
|
|
return fmt.Errorf("notification_module_disabled")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func renderNotificationModuleError(c *gin.Context, err error) {
|
|
switch err.Error() {
|
|
case "game_not_found":
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Game not found"})
|
|
case "notification_module_disabled":
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "Notification module is not enabled for this game"})
|
|
default:
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
}
|
|
}
|
|
|
|
func (h *GameHandler) ensureLanguagesExist(c *gin.Context, descriptions []models.NotificationDescriptionRequest) error {
|
|
languageIDs := make([]int64, 0, len(descriptions))
|
|
seen := make(map[int64]struct{}, len(descriptions))
|
|
for _, description := range descriptions {
|
|
if _, ok := seen[description.LanguageID]; ok {
|
|
continue
|
|
}
|
|
seen[description.LanguageID] = struct{}{}
|
|
languageIDs = append(languageIDs, description.LanguageID)
|
|
}
|
|
|
|
var languages []models.Language
|
|
err := h.db.NewSelect().
|
|
Model(&languages).
|
|
Where("id IN (?)", bun.In(languageIDs)).
|
|
Scan(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(languages) != len(languageIDs) {
|
|
return fmt.Errorf("one or more languages do not exist")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (h *GameHandler) getNotification(c *gin.Context, gameID, notificationID int64) (*models.Notification, error) {
|
|
notification := new(models.Notification)
|
|
err := h.db.NewSelect().
|
|
Model(notification).
|
|
Relation("Descriptions", func(q *bun.SelectQuery) *bun.SelectQuery {
|
|
return q.Relation("Language").OrderExpr("nd.id ASC")
|
|
}).
|
|
Relation("Variables", func(q *bun.SelectQuery) *bun.SelectQuery {
|
|
return q.OrderExpr("nvd.id ASC")
|
|
}).
|
|
Relation("Entries", func(q *bun.SelectQuery) *bun.SelectQuery {
|
|
return q.OrderExpr("ne.id ASC")
|
|
}).
|
|
Where("n.id = ? AND n.game_id = ?", notificationID, gameID).
|
|
Scan(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := h.loadNotificationEntryVariablesForOne(c, notification); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
prepareNotificationResponse(notification)
|
|
return notification, nil
|
|
}
|
|
|
|
func (h *GameHandler) loadNotificationEntryVariables(c *gin.Context, notifications []models.Notification) error {
|
|
entryIDs := make([]int64, 0)
|
|
for i := range notifications {
|
|
for j := range notifications[i].Entries {
|
|
entryIDs = append(entryIDs, notifications[i].Entries[j].ID)
|
|
}
|
|
}
|
|
if len(entryIDs) == 0 {
|
|
return nil
|
|
}
|
|
|
|
var entryVariables []models.NotificationEntryVariable
|
|
err := h.db.NewSelect().
|
|
Model(&entryVariables).
|
|
Relation("Variable").
|
|
Where("nev.notification_entry_id IN (?)", bun.In(entryIDs)).
|
|
OrderExpr("nev.id ASC").
|
|
Scan(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
variablesByEntry := make(map[int64][]models.NotificationEntryVariable, len(entryIDs))
|
|
for _, item := range entryVariables {
|
|
if item.Variable != nil {
|
|
item.Key = item.Variable.Key
|
|
}
|
|
variablesByEntry[item.NotificationEntryID] = append(variablesByEntry[item.NotificationEntryID], item)
|
|
}
|
|
|
|
for i := range notifications {
|
|
for j := range notifications[i].Entries {
|
|
notifications[i].Entries[j].Variables = variablesByEntry[notifications[i].Entries[j].ID]
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (h *GameHandler) loadNotificationEntryVariablesForOne(c *gin.Context, notification *models.Notification) error {
|
|
entryIDs := make([]int64, 0, len(notification.Entries))
|
|
for i := range notification.Entries {
|
|
entryIDs = append(entryIDs, notification.Entries[i].ID)
|
|
}
|
|
if len(entryIDs) == 0 {
|
|
return nil
|
|
}
|
|
|
|
var entryVariables []models.NotificationEntryVariable
|
|
err := h.db.NewSelect().
|
|
Model(&entryVariables).
|
|
Relation("Variable").
|
|
Where("nev.notification_entry_id IN (?)", bun.In(entryIDs)).
|
|
OrderExpr("nev.id ASC").
|
|
Scan(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
variablesByEntry := make(map[int64][]models.NotificationEntryVariable, len(entryIDs))
|
|
for _, item := range entryVariables {
|
|
if item.Variable != nil {
|
|
item.Key = item.Variable.Key
|
|
}
|
|
variablesByEntry[item.NotificationEntryID] = append(variablesByEntry[item.NotificationEntryID], item)
|
|
}
|
|
|
|
for i := range notification.Entries {
|
|
notification.Entries[i].Variables = variablesByEntry[notification.Entries[i].ID]
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func deleteNotificationDetails(c *gin.Context, tx bun.Tx, notificationID int64) error {
|
|
var entries []models.NotificationEntry
|
|
if err := tx.NewSelect().
|
|
Model(&entries).
|
|
Where("notification_id = ?", notificationID).
|
|
Scan(c); err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(entries) > 0 {
|
|
entryIDs := make([]int64, 0, len(entries))
|
|
for _, entry := range entries {
|
|
entryIDs = append(entryIDs, entry.ID)
|
|
}
|
|
if _, err := tx.NewDelete().
|
|
Model((*models.NotificationEntryVariable)(nil)).
|
|
Where("notification_entry_id IN (?)", bun.In(entryIDs)).
|
|
Exec(c); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if _, err := tx.NewDelete().
|
|
Model((*models.NotificationEntry)(nil)).
|
|
Where("notification_id = ?", notificationID).
|
|
Exec(c); err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := tx.NewDelete().
|
|
Model((*models.NotificationVariableDef)(nil)).
|
|
Where("notification_id = ?", notificationID).
|
|
Exec(c); err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := tx.NewDelete().
|
|
Model((*models.NotificationDescription)(nil)).
|
|
Where("notification_id = ?", notificationID).
|
|
Exec(c); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func saveNotificationDetails(c *gin.Context, tx bun.Tx, notificationID int64, req models.UpsertNotificationRequest) error {
|
|
now := time.Now()
|
|
|
|
for _, item := range req.Descriptions {
|
|
description := &models.NotificationDescription{
|
|
NotificationID: notificationID,
|
|
LanguageID: item.LanguageID,
|
|
Description: strings.TrimSpace(item.Description),
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
if _, err := tx.NewInsert().Model(description).Exec(c); err != nil {
|
|
return fmt.Errorf("could not save notification descriptions")
|
|
}
|
|
}
|
|
|
|
variableIDs := make(map[string]int64, len(req.Variables))
|
|
for _, item := range req.Variables {
|
|
variable := &models.NotificationVariableDef{
|
|
NotificationID: notificationID,
|
|
Key: strings.TrimSpace(item.Key),
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
if _, err := tx.NewInsert().Model(variable).Exec(c); err != nil {
|
|
return fmt.Errorf("could not save notification variable definitions")
|
|
}
|
|
variableIDs[variable.Key] = variable.ID
|
|
}
|
|
|
|
for _, entryReq := range req.Entries {
|
|
entry := &models.NotificationEntry{
|
|
NotificationID: notificationID,
|
|
TimeSecond: entryReq.TimeSecond,
|
|
Image: strings.TrimSpace(entryReq.Image),
|
|
Login: strings.TrimSpace(entryReq.Login),
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
if _, err := tx.NewInsert().Model(entry).Exec(c); err != nil {
|
|
return fmt.Errorf("could not save notification entries")
|
|
}
|
|
|
|
for _, variableReq := range entryReq.Variables {
|
|
entryVariable := &models.NotificationEntryVariable{
|
|
NotificationEntryID: entry.ID,
|
|
NotificationVariableID: variableIDs[strings.TrimSpace(variableReq.Key)],
|
|
Value: strings.TrimSpace(variableReq.Value),
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
if _, err := tx.NewInsert().Model(entryVariable).Exec(c); err != nil {
|
|
return fmt.Errorf("could not save notification entry variables")
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func validateUpsertNotificationRequest(req *models.UpsertNotificationRequest) error {
|
|
req.Name = strings.TrimSpace(req.Name)
|
|
if req.Name == "" {
|
|
return fmt.Errorf("name is required")
|
|
}
|
|
if len(req.Descriptions) == 0 {
|
|
return fmt.Errorf("at least one description is required")
|
|
}
|
|
if len(req.Entries) == 0 {
|
|
return fmt.Errorf("at least one notification entry is required")
|
|
}
|
|
|
|
seenLanguages := make(map[int64]struct{}, len(req.Descriptions))
|
|
for i := range req.Descriptions {
|
|
req.Descriptions[i].Description = strings.TrimSpace(req.Descriptions[i].Description)
|
|
if req.Descriptions[i].LanguageID <= 0 {
|
|
return fmt.Errorf("descriptions[%d].language_id is required", i)
|
|
}
|
|
if req.Descriptions[i].Description == "" {
|
|
return fmt.Errorf("descriptions[%d].description is required", i)
|
|
}
|
|
if _, exists := seenLanguages[req.Descriptions[i].LanguageID]; exists {
|
|
return fmt.Errorf("duplicate description language_id: %d", req.Descriptions[i].LanguageID)
|
|
}
|
|
seenLanguages[req.Descriptions[i].LanguageID] = struct{}{}
|
|
}
|
|
|
|
variableKeys := make([]string, 0, len(req.Variables))
|
|
expectedKeys := make(map[string]struct{}, len(req.Variables))
|
|
for i := range req.Variables {
|
|
req.Variables[i].Key = strings.TrimSpace(req.Variables[i].Key)
|
|
if req.Variables[i].Key == "" {
|
|
return fmt.Errorf("variables[%d].key is required", i)
|
|
}
|
|
for _, reserved := range defaultNotificationMacroKeys {
|
|
if req.Variables[i].Key == reserved {
|
|
return fmt.Errorf("variables[%d].key uses reserved macro name: %s", i, reserved)
|
|
}
|
|
}
|
|
if _, exists := expectedKeys[req.Variables[i].Key]; exists {
|
|
return fmt.Errorf("duplicate variable key: %s", req.Variables[i].Key)
|
|
}
|
|
expectedKeys[req.Variables[i].Key] = struct{}{}
|
|
variableKeys = append(variableKeys, req.Variables[i].Key)
|
|
}
|
|
|
|
for i := range req.Entries {
|
|
req.Entries[i].Image = strings.TrimSpace(req.Entries[i].Image)
|
|
req.Entries[i].Login = strings.TrimSpace(req.Entries[i].Login)
|
|
if req.Entries[i].TimeSecond < 0 {
|
|
return fmt.Errorf("entries[%d].time_second must be >= 0", i)
|
|
}
|
|
if req.Entries[i].Image == "" {
|
|
return fmt.Errorf("entries[%d].image is required", i)
|
|
}
|
|
if req.Entries[i].Login == "" {
|
|
return fmt.Errorf("entries[%d].login is required", i)
|
|
}
|
|
|
|
entrySeen := make(map[string]struct{}, len(req.Entries[i].Variables))
|
|
for j := range req.Entries[i].Variables {
|
|
req.Entries[i].Variables[j].Key = strings.TrimSpace(req.Entries[i].Variables[j].Key)
|
|
req.Entries[i].Variables[j].Value = strings.TrimSpace(req.Entries[i].Variables[j].Value)
|
|
if req.Entries[i].Variables[j].Key == "" {
|
|
return fmt.Errorf("entries[%d].variables[%d].key is required", i, j)
|
|
}
|
|
if _, exists := expectedKeys[req.Entries[i].Variables[j].Key]; !exists {
|
|
return fmt.Errorf("entries[%d].variables[%d].key is not declared in notification variables", i, j)
|
|
}
|
|
if _, exists := entrySeen[req.Entries[i].Variables[j].Key]; exists {
|
|
return fmt.Errorf("entries[%d] has duplicate variable key: %s", i, req.Entries[i].Variables[j].Key)
|
|
}
|
|
entrySeen[req.Entries[i].Variables[j].Key] = struct{}{}
|
|
}
|
|
|
|
if len(entrySeen) != len(expectedKeys) {
|
|
missing := make([]string, 0, len(expectedKeys)-len(entrySeen))
|
|
for _, key := range variableKeys {
|
|
if _, exists := entrySeen[key]; !exists {
|
|
missing = append(missing, key)
|
|
}
|
|
}
|
|
if len(missing) > 0 {
|
|
return fmt.Errorf("entries[%d] is missing variable values for: %s", i, strings.Join(missing, ", "))
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func prepareNotificationResponse(notification *models.Notification) {
|
|
sort.SliceStable(notification.Descriptions, func(i, j int) bool {
|
|
return notification.Descriptions[i].LanguageID < notification.Descriptions[j].LanguageID
|
|
})
|
|
sort.SliceStable(notification.Variables, func(i, j int) bool {
|
|
return notification.Variables[i].ID < notification.Variables[j].ID
|
|
})
|
|
sort.SliceStable(notification.Entries, func(i, j int) bool {
|
|
return notification.Entries[i].ID < notification.Entries[j].ID
|
|
})
|
|
|
|
macros := make([]string, 0, len(defaultNotificationMacroKeys)+len(notification.Variables))
|
|
for _, key := range defaultNotificationMacroKeys {
|
|
macros = append(macros, "{{"+key+"}}")
|
|
}
|
|
for _, variable := range notification.Variables {
|
|
macros = append(macros, "{{"+variable.Key+"}}")
|
|
}
|
|
notification.Macros = macros
|
|
|
|
for i := range notification.Entries {
|
|
sort.SliceStable(notification.Entries[i].Variables, func(left, right int) bool {
|
|
return notification.Entries[i].Variables[left].ID < notification.Entries[i].Variables[right].ID
|
|
})
|
|
for j := range notification.Entries[i].Variables {
|
|
if notification.Entries[i].Variables[j].Variable != nil {
|
|
notification.Entries[i].Variables[j].Key = notification.Entries[i].Variables[j].Variable.Key
|
|
}
|
|
}
|
|
}
|
|
}
|