feat: admin side ntification creation handler added

This commit is contained in:
dawitel 2025-04-03 23:00:54 +03:00
parent 9ffcac096f
commit 6b52d139d4
7 changed files with 144 additions and 0 deletions

View File

@ -16,3 +16,6 @@ UPDATE notifications SET delivery_status = $2, is_read = $3, metadata = $4 WHERE
-- name: ListFailedNotifications :many -- name: ListFailedNotifications :many
SELECT * FROM notifications WHERE delivery_status = 'failed' AND timestamp < NOW() - INTERVAL '1 hour' ORDER BY timestamp ASC LIMIT $1; SELECT * FROM notifications WHERE delivery_status = 'failed' AND timestamp < NOW() - INTERVAL '1 hour' ORDER BY timestamp ASC LIMIT $1;
-- name: ListRecipientIDsByReceiver :many
SELECT recipient_id FROM notifications WHERE reciever = $1;

View File

@ -181,6 +181,30 @@ func (q *Queries) ListNotifications(ctx context.Context, arg ListNotificationsPa
return items, nil return items, nil
} }
const ListRecipientIDsByReceiver = `-- name: ListRecipientIDsByReceiver :many
SELECT recipient_id FROM notifications WHERE reciever = $1
`
func (q *Queries) ListRecipientIDsByReceiver(ctx context.Context, reciever string) ([]int64, error) {
rows, err := q.db.Query(ctx, ListRecipientIDsByReceiver, reciever)
if err != nil {
return nil, err
}
defer rows.Close()
var items []int64
for rows.Next() {
var recipient_id int64
if err := rows.Scan(&recipient_id); err != nil {
return nil, err
}
items = append(items, recipient_id)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const UpdateNotificationStatus = `-- name: UpdateNotificationStatus :one const UpdateNotificationStatus = `-- name: UpdateNotificationStatus :one
UPDATE notifications SET delivery_status = $2, is_read = $3, metadata = $4 WHERE id = $1 RETURNING id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, version, timestamp, metadata UPDATE notifications SET delivery_status = $2, is_read = $3, metadata = $4 WHERE id = $1 RETURNING id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, version, timestamp, metadata
` `

View File

@ -14,6 +14,7 @@ type NotificationRepository interface {
UpdateNotificationStatus(ctx context.Context, id, status string, isRead bool, metadata []byte) (*domain.Notification, error) UpdateNotificationStatus(ctx context.Context, id, status string, isRead bool, metadata []byte) (*domain.Notification, error)
ListNotifications(ctx context.Context, recipientID int64, limit, offset int) ([]domain.Notification, error) ListNotifications(ctx context.Context, recipientID int64, limit, offset int) ([]domain.Notification, error)
ListFailedNotifications(ctx context.Context, limit int) ([]domain.Notification, error) ListFailedNotifications(ctx context.Context, limit int) ([]domain.Notification, error)
ListRecipientIDs(ctx context.Context, receiver domain.NotificationRecieverSide) ([]int64, error)
} }
type Repository struct { type Repository struct {
@ -119,6 +120,10 @@ func (r *Repository) ListFailedNotifications(ctx context.Context, limit int) ([]
return result, nil return result, nil
} }
func (r *Repository) ListRecipientIDs(ctx context.Context, receiver domain.NotificationRecieverSide) ([]int64, error) {
return r.store.queries.ListRecipientIDsByReceiver(ctx, string(receiver))
}
func (r *Repository) mapDBToDomain(dbNotif *dbgen.Notification) *domain.Notification { func (r *Repository) mapDBToDomain(dbNotif *dbgen.Notification) *domain.Notification {
var errorSeverity *domain.NotificationErrorSeverity var errorSeverity *domain.NotificationErrorSeverity
if dbNotif.ErrorSeverity.Valid { if dbNotif.ErrorSeverity.Valid {

View File

@ -15,4 +15,5 @@ type NotificationStore interface {
DisconnectWebSocket(recipientID int64) DisconnectWebSocket(recipientID int64)
SendSMS(ctx context.Context, recipientID int64, message string) error SendSMS(ctx context.Context, recipientID int64, message string) error
SendEmail(ctx context.Context, recipientID int64, subject, message string) error SendEmail(ctx context.Context, recipientID int64, subject, message string) error
ListRecipientIDs(ctx context.Context, receiver domain.NotificationRecieverSide) ([]int64, error) // New method
} }

View File

@ -153,6 +153,10 @@ func (s *Service) startWorker() {
} }
} }
func (s *Service) ListRecipientIDs(ctx context.Context, receiver domain.NotificationRecieverSide) ([]int64, error) {
return s.repo.ListRecipientIDs(ctx, receiver)
}
func (s *Service) handleNotification(notification *domain.Notification) { func (s *Service) handleNotification(notification *domain.Notification) {
ctx := context.Background() ctx := context.Background()

View File

@ -2,7 +2,9 @@ package handlers
import ( import (
"context" "context"
"encoding/json"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/gofiber/websocket/v2" "github.com/gofiber/websocket/v2"
) )
@ -74,3 +76,107 @@ func (h *Handler) MarkNotificationAsRead(c *fiber.Ctx) error {
return c.Status(fiber.StatusOK).JSON(fiber.Map{"message": "Notification marked as read"}) return c.Status(fiber.StatusOK).JSON(fiber.Map{"message": "Notification marked as read"})
} }
func (h *Handler) CreateAndSendNotification(c *fiber.Ctx) error {
type Request struct {
RecipientID int64 `json:"recipient_id" validate:"required_if=DeliveryScheme single"`
Type domain.NotificationType `json:"type" validate:"required"`
Level domain.NotificationLevel `json:"level" validate:"required"`
ErrorSeverity *domain.NotificationErrorSeverity `json:"error_severity"`
Reciever domain.NotificationRecieverSide `json:"reciever" validate:"required"`
DeliveryScheme domain.NotificationDeliveryScheme `json:"delivery_scheme" validate:"required"`
DeliveryChannel domain.DeliveryChannel `json:"delivery_channel" validate:"required"`
Payload domain.NotificationPayload `json:"payload" validate:"required"`
Priority int `json:"priority"`
Metadata json.RawMessage `json:"metadata,omitempty"`
}
var req Request
if err := c.BodyParser(&req); err != nil {
h.logger.Error("[NotificationSvc.CreateAndSendNotification] Failed to parse request body", "error", err)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
}
userID, ok := c.Locals("userID").(int64)
if !ok || userID == 0 {
h.logger.Error("[NotificationSvc.CreateAndSendNotification] Invalid user ID in context")
return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification")
}
switch req.DeliveryScheme {
case domain.NotificationDeliverySchemeSingle:
if req.Reciever == domain.NotificationRecieverSideCustomer && req.RecipientID != userID {
h.logger.Warn("[NotificationSvc.CreateAndSendNotification] Unauthorized attempt to send notification", "userID", userID, "recipientID", req.RecipientID)
return fiber.NewError(fiber.StatusForbidden, "Unauthorized to send notification to this recipient")
}
notification := &domain.Notification{
ID: "",
RecipientID: req.RecipientID,
Type: req.Type,
Level: req.Level,
ErrorSeverity: req.ErrorSeverity,
Reciever: req.Reciever,
IsRead: false,
DeliveryStatus: domain.DeliveryStatusPending,
DeliveryChannel: req.DeliveryChannel,
Payload: req.Payload,
Priority: req.Priority,
Metadata: req.Metadata,
}
if err := h.notificationSvc.SendNotification(context.Background(), notification); err != nil {
h.logger.Error("[NotificationSvc.CreateAndSendNotification] Failed to send single notification", "recipientID", req.RecipientID, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to send notification")
}
h.logger.Info("[NotificationSvc.CreateAndSendNotification] Single notification sent successfully", "recipientID", req.RecipientID, "type", req.Type)
return c.Status(fiber.StatusCreated).JSON(fiber.Map{"message": "Single notification sent successfully", "notification_id": notification.ID})
case domain.NotificationDeliverySchemeBulk:
recipients, err := h.getAllRecipientIDs(context.Background(), req.Reciever)
if err != nil {
h.logger.Error("[NotificationSvc.CreateAndSendNotification] Failed to fetch recipients for bulk notification", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch recipients")
}
notificationIDs := make([]string, 0, len(recipients))
for _, recipientID := range recipients {
notification := &domain.Notification{
ID: "",
RecipientID: recipientID,
Type: req.Type,
Level: req.Level,
ErrorSeverity: req.ErrorSeverity,
Reciever: req.Reciever,
IsRead: false,
DeliveryStatus: domain.DeliveryStatusPending,
DeliveryChannel: req.DeliveryChannel,
Payload: req.Payload,
Priority: req.Priority,
Metadata: req.Metadata,
}
if err := h.notificationSvc.SendNotification(context.Background(), notification); err != nil {
h.logger.Error("[NotificationSvc.CreateAndSendNotification] Failed to send bulk notification", "recipientID", recipientID, "error", err)
continue
}
notificationIDs = append(notificationIDs, notification.ID)
}
h.logger.Info("[NotificationSvc.CreateAndSendNotification] Bulk notification sent successfully", "recipient_count", len(recipients), "type", req.Type)
return c.Status(fiber.StatusCreated).JSON(fiber.Map{
"message": "Bulk notification sent successfully",
"recipient_count": len(recipients),
"notification_ids": notificationIDs,
})
default:
h.logger.Error("[NotificationSvc.CreateAndSendNotification] Invalid delivery scheme", "delivery_scheme", req.DeliveryScheme)
return fiber.NewError(fiber.StatusBadRequest, "Invalid delivery scheme")
}
}
func (h *Handler) getAllRecipientIDs(ctx context.Context, receiver domain.NotificationRecieverSide) ([]int64, error) {
return h.notificationSvc.ListRecipientIDs(ctx, receiver)
}

View File

@ -45,6 +45,7 @@ func (a *App) initAppRoutes() {
a.fiber.Get("/notifications/ws/connect/:recipientID", handler.ConnectSocket) a.fiber.Get("/notifications/ws/connect/:recipientID", handler.ConnectSocket)
a.fiber.Post("/notifications/mark-as-read", handler.MarkNotificationAsRead) a.fiber.Post("/notifications/mark-as-read", handler.MarkNotificationAsRead)
a.fiber.Post("/notifications/create", handler.CreateAndSendNotification)
} }
///user/profile get ///user/profile get