Yimaru-BackEnd/internal/web_server/handlers/notification_handler.go

815 lines
27 KiB
Go

package handlers
import (
"Yimaru-Backend/internal/domain"
"Yimaru-Backend/internal/web_server/ws"
"bufio"
"context"
"encoding/json"
"fmt"
"net"
"net/http"
"strconv"
"time"
"github.com/gofiber/fiber/v2"
"github.com/gorilla/websocket"
"go.uber.org/zap"
)
func (h *Handler) ConnectSocket(c *fiber.Ctx) error {
userID, ok := c.Locals("userID").(int64)
if !ok || userID == 0 {
h.mongoLoggerSvc.Info("Invalid user ID in context",
zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusUnauthorized),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification")
}
// Hijack the underlying net.Conn from fasthttp
done := make(chan struct{})
c.Context().HijackSetNoResponse(true)
c.Context().Hijack(func(netConn net.Conn) {
defer close(done)
// Build a minimal http.Request for gorilla's upgrader
stdReq := &http.Request{
Method: http.MethodGet,
Header: make(http.Header),
}
c.Context().Request.Header.VisitAll(func(key, value []byte) {
stdReq.Header.Set(string(key), string(value))
})
stdReq.Host = string(c.Context().Host())
stdReq.RequestURI = string(c.Context().RequestURI())
// Create a hijackable response writer around the raw connection
hjRW := &hijackResponseWriter{
conn: netConn,
brw: bufio.NewReadWriter(bufio.NewReader(netConn), bufio.NewWriter(netConn)),
h: make(http.Header),
}
wsConn, err := ws.Upgrader.Upgrade(hjRW, stdReq, nil)
if err != nil {
h.mongoLoggerSvc.Error("WebSocket upgrade failed",
zap.Int64("userID", userID),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
netConn.Close()
return
}
client := &ws.Client{
Conn: wsConn,
RecipientID: userID,
}
h.notificationSvc.Hub.Register <- client
defer func() {
h.notificationSvc.Hub.Unregister <- client
wsConn.Close()
}()
h.mongoLoggerSvc.Info("WebSocket connection established",
zap.Int64("userID", userID),
zap.Time("timestamp", time.Now()),
)
for {
_, _, err := wsConn.ReadMessage()
if err != nil {
if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseGoingAway) {
h.mongoLoggerSvc.Info("WebSocket closed normally",
zap.Int64("userID", userID),
zap.Time("timestamp", time.Now()),
)
} else {
h.mongoLoggerSvc.Info("Unexpected WebSocket closure",
zap.Int64("userID", userID),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
}
break
}
}
})
<-done
return nil
}
// hijackResponseWriter implements http.ResponseWriter and http.Hijacker
// so gorilla/websocket can upgrade over a raw net.Conn.
type hijackResponseWriter struct {
conn net.Conn
brw *bufio.ReadWriter
h http.Header
}
func (w *hijackResponseWriter) Header() http.Header { return w.h }
func (w *hijackResponseWriter) WriteHeader(statusCode int) {}
func (w *hijackResponseWriter) Write(b []byte) (int, error) { return w.conn.Write(b) }
func (w *hijackResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return w.conn, w.brw, nil
}
func (h *Handler) MarkNotificationAsRead(c *fiber.Ctx) error {
idStr := c.Params("id")
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Invalid notification ID",
Error: err.Error(),
})
}
notification, err := h.notificationSvc.MarkNotificationAsRead(context.Background(), id)
if err != nil {
h.mongoLoggerSvc.Error("[NotificationHandler.MarkNotificationAsRead] Failed to mark notification as read",
zap.Int64("notificationID", id),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to mark notification as read",
Error: err.Error(),
})
}
return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "Notification marked as read",
Success: true,
StatusCode: fiber.StatusOK,
Data: notification,
})
}
func (h *Handler) MarkAllNotificationsAsRead(c *fiber.Ctx) error {
userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 {
return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{
Message: "Invalid user identification",
Error: "User ID not found in context",
})
}
if err := h.notificationSvc.MarkAllUserNotificationsAsRead(context.Background(), userID); err != nil {
h.mongoLoggerSvc.Error("[NotificationHandler.MarkAllNotificationsAsRead] Failed to mark all notifications as read",
zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to mark all notifications as read",
Error: err.Error(),
})
}
return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "All notifications marked as read",
Success: true,
StatusCode: fiber.StatusOK,
})
}
func (h *Handler) MarkNotificationAsUnread(c *fiber.Ctx) error {
idStr := c.Params("id")
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Invalid notification ID",
Error: err.Error(),
})
}
notification, err := h.notificationSvc.MarkNotificationAsUnread(context.Background(), id)
if err != nil {
h.mongoLoggerSvc.Error("[NotificationHandler.MarkNotificationAsUnread] Failed to mark notification as unread",
zap.Int64("notificationID", id),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to mark notification as unread",
Error: err.Error(),
})
}
return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "Notification marked as unread",
Success: true,
StatusCode: fiber.StatusOK,
Data: notification,
})
}
func (h *Handler) MarkAllNotificationsAsUnread(c *fiber.Ctx) error {
userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 {
return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{
Message: "Invalid user identification",
Error: "User ID not found in context",
})
}
if err := h.notificationSvc.MarkAllUserNotificationsAsUnread(context.Background(), userID); err != nil {
h.mongoLoggerSvc.Error("[NotificationHandler.MarkAllNotificationsAsUnread] Failed to mark all notifications as unread",
zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to mark all notifications as unread",
Error: err.Error(),
})
}
return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "All notifications marked as unread",
Success: true,
StatusCode: fiber.StatusOK,
})
}
func (h *Handler) DeleteUserNotifications(c *fiber.Ctx) error {
userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 {
return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{
Message: "Invalid user identification",
Error: "User ID not found in context",
})
}
if err := h.notificationSvc.DeleteUserNotifications(context.Background(), userID); err != nil {
h.mongoLoggerSvc.Error("[NotificationHandler.DeleteUserNotifications] Failed to delete notifications",
zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to delete notifications",
Error: err.Error(),
})
}
return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "Notifications deleted successfully",
Success: true,
StatusCode: fiber.StatusOK,
})
}
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.mongoLoggerSvc.Info("[NotificationSvc.CreateAndSendNotification] Failed to parse request body",
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
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 {
// h.logger.Warn("[NotificationSvc.CreateAndSendNotification] Unauthorized attempt to send notification", "recipientID", req.RecipientID)
// return fiber.NewError(fiber.StatusForbidden, "Unauthorized to send notification to this recipient")
// }
errorSeverity := domain.NotificationErrorSeverityMedium
if req.ErrorSeverity != nil {
errorSeverity = *req.ErrorSeverity
}
notification := &domain.Notification{
ID: "",
RecipientID: req.RecipientID,
Type: req.Type,
Level: req.Level,
ErrorSeverity: 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.mongoLoggerSvc.Error("[NotificationSvc.CreateAndSendNotification] Failed to send single notification",
zap.Int64("recipientID", req.RecipientID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to send notification:"+err.Error())
}
h.mongoLoggerSvc.Info("[NotificationSvc.CreateAndSendNotification] Single notification sent successfully",
zap.Int64("recipientID", req.RecipientID),
zap.String("type", string(req.Type)),
zap.Time("timestamp", time.Now()),
)
return c.Status(fiber.StatusCreated).JSON(fiber.Map{"message": "Single notification sent successfully", "notification_id": notification.ID})
case domain.NotificationDeliverySchemeBulk:
recipients, _, err := h.userSvc.GetAllUsers(context.Background(), domain.UserFilter{
Role: string(req.Reciever),
})
if err != nil {
h.mongoLoggerSvc.Error("[NotificationSvc.CreateAndSendNotification] Failed to fetch recipients for bulk notification",
zap.Int64("RecipientID", req.RecipientID),
zap.String("Reciever", string(req.Reciever)),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch recipients:"+err.Error())
}
notificationIDs := make([]string, 0, len(recipients))
for _, user := range recipients {
errorSeverity := domain.NotificationErrorSeverityMedium
if req.ErrorSeverity != nil {
errorSeverity = *req.ErrorSeverity
}
notification := &domain.Notification{
ID: "",
RecipientID: user.ID,
Type: req.Type,
Level: req.Level,
ErrorSeverity: 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.mongoLoggerSvc.Error("[NotificationSvc.CreateAndSendNotification] Failed to send bulk notification",
zap.Int64("UserID", user.ID),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
continue
}
notificationIDs = append(notificationIDs, notification.ID)
}
h.mongoLoggerSvc.Error("[NotificationSvc.CreateAndSendNotification] Bulk notification sent successfully",
zap.Int("recipient_count", len(recipients)),
zap.String("type", string(req.Type)),
zap.Int("status_code", fiber.StatusCreated),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return c.Status(fiber.StatusCreated).JSON(fiber.Map{
"message": "Bulk notification sent successfully",
"recipient_count": len(recipients),
"notification_ids": notificationIDs,
})
default:
h.mongoLoggerSvc.Info("[NotificationSvc.CreateAndSendNotification] Invalid delivery scheme",
zap.String("delivery_scheme", string(req.DeliveryScheme)),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid delivery scheme")
}
}
func (h *Handler) GetUserNotification(c *fiber.Ctx) error {
limitStr := c.Query("limit", "10")
offsetStr := c.Query("offset", "0")
// Convert limit and offset to integers
limit, err := strconv.Atoi(limitStr)
if err != nil || limit <= 0 {
h.mongoLoggerSvc.Info("[NotificationSvc.GetUserNotification] Invalid limit value",
zap.String("limit", limitStr),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid limit value")
}
offset, err := strconv.Atoi(offsetStr)
if err != nil || offset < 0 {
h.mongoLoggerSvc.Info("[NotificationSvc.GetUserNotification] Invalid offset value",
zap.String("offset", offsetStr),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid offset value")
}
userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 {
h.mongoLoggerSvc.Error("[NotificationSvc.GetUserNotification] Invalid user ID in context",
zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Invalid user identification")
}
notifications, total, err := h.notificationSvc.GetUserNotifications(context.Background(), userID, limit, offset)
if err != nil {
h.mongoLoggerSvc.Error("[NotificationSvc.GetUserNotification] Failed to fetch notifications",
zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch notifications:"+err.Error())
}
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"notifications": notifications,
"total_count": total,
"limit": limit,
"offset": offset,
})
}
// func (h *Handler) GetAllRecipientIDs(ctx context.Context, receiver domain.NotificationRecieverSide) ([]int64, error) {
// return h.notificationSvc.ListRecipientIDs(ctx, receiver)
// }
func (h *Handler) CountUnreadNotifications(c *fiber.Ctx) error {
userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 {
h.mongoLoggerSvc.Error("NotificationSvc.GetNotifications] Invalid user ID in context",
zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Invalid user identification")
}
total, err := h.notificationSvc.CountUnreadNotifications(c.Context(), userID)
if err != nil {
h.mongoLoggerSvc.Error("[NotificationSvc.CountUnreadNotifications] Failed to fetch unread notification count",
zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch notifications:"+err.Error())
}
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"unread": total,
})
}
func (h *Handler) GetAllNotifications(c *fiber.Ctx) error {
limitStr := c.Query("limit", "10")
pageStr := c.Query("page", "1")
// Convert limit and offset to integers
limit, err := strconv.Atoi(limitStr)
if err != nil || limit <= 0 {
h.mongoLoggerSvc.Info("[NotificationSvc.GetNotifications] Invalid limit value",
zap.String("limit", limitStr),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid limit value")
}
page, err := strconv.Atoi(pageStr)
if err != nil || page <= 0 {
h.mongoLoggerSvc.Info("[NotificationSvc.GetNotifications] Invalid page value",
zap.String("page", pageStr),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid page value")
}
notifications, err := h.notificationSvc.GetAllNotifications(context.Background(), limit, ((page - 1) * limit))
if err != nil {
h.mongoLoggerSvc.Error("[NotificationSvc.GetNotifications] Failed to fetch notifications",
zap.Int64("limit", int64(limit)),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch notifications")
}
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"notifications": notifications,
"total_count": len(notifications),
"limit": limit,
"page": page,
})
}
type SendSingleAfroSMSReq struct {
Recipient string `json:"recipient" validate:"required" example:"+251912345678"`
Message string `json:"message" validate:"required" example:"Hello world"`
}
// SendSingleAfroSMS godoc
// @Summary Send single SMS via AfroMessage
// @Description Sends an SMS message to a single phone number using AfroMessage
// @Tags user
// @Accept json
// @Produce json
// @Param sendSMS body SendSingleAfroSMSReq true "Send SMS request"
// @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /api/v1/sendSMS [post]
func (h *Handler) SendSingleAfroSMS(c *fiber.Ctx) error {
var req SendSingleAfroSMSReq
if err := c.BodyParser(&req); err != nil {
h.mongoLoggerSvc.Info("Failed to parse SendSingleAfroSMS request",
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Failed to send SMS",
Error: "Invalid request body: " + err.Error(),
})
}
// Validate request
if valErrs, ok := h.validator.Validate(c, req); !ok {
var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Failed to send SMS",
Error: errMsg,
})
}
// Send SMS via service
if err := h.notificationSvc.SendAfroMessageSMS(
c.Context(),
req.Recipient,
req.Message,
); err != nil {
h.mongoLoggerSvc.Error("Failed to send AfroMessage SMS",
zap.String("phone_number", req.Recipient),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to send SMS",
Error: err.Error(),
})
}
return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "SMS sent successfully",
Success: true,
StatusCode: fiber.StatusOK,
Data: req,
})
}
func (h *Handler) RegisterDeviceToken(c *fiber.Ctx) error {
type Request struct {
DeviceToken string `json:"device_token" validate:"required"`
Platform string `json:"platform" validate:"required,oneof=android ios web"`
}
var req Request
if err := c.BodyParser(&req); err != nil {
h.mongoLoggerSvc.Info("[NotificationHandler.RegisterDeviceToken] Failed to parse request body",
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
}
userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 {
h.mongoLoggerSvc.Error("[NotificationHandler.RegisterDeviceToken] Invalid user ID in context",
zap.Int("status_code", fiber.StatusUnauthorized),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification")
}
if err := h.userSvc.RegisterDevice(c.Context(), userID, req.DeviceToken, req.Platform); err != nil {
h.mongoLoggerSvc.Error("[NotificationHandler.RegisterDeviceToken] Failed to register device token",
zap.Int64("userID", userID),
zap.String("platform", req.Platform),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to register device token: "+err.Error())
}
h.mongoLoggerSvc.Info("[NotificationHandler.RegisterDeviceToken] Device token registered successfully",
zap.Int64("userID", userID),
zap.String("platform", req.Platform),
zap.Time("timestamp", time.Now()),
)
return c.Status(fiber.StatusCreated).JSON(domain.Response{
Message: "Device token registered successfully",
Success: true,
StatusCode: fiber.StatusCreated,
})
}
// SendTestPushNotification sends a test push notification to the authenticated user
// @Summary Send test push notification
// @Description Sends a test push notification to all registered devices of the current user
// @Tags notifications
// @Accept json
// @Produce json
// @Param body body object{title=string,message=string} true "Test notification content"
// @Success 200 {object} domain.Response
// @Failure 400 {object} domain.ErrorResponse
// @Failure 401 {object} domain.ErrorResponse
// @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/notifications/test-push [post]
func (h *Handler) SendTestPushNotification(c *fiber.Ctx) error {
type Request struct {
Title string `json:"title"`
Message string `json:"message"`
}
var req Request
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Invalid request body",
Error: err.Error(),
})
}
if req.Title == "" {
req.Title = "Test Push Notification"
}
if req.Message == "" {
req.Message = "This is a test push notification from Yimaru Backend"
}
userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 {
return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{
Message: "Invalid user identification",
Error: "User ID not found in context",
})
}
// Get user's device tokens first to provide feedback
tokens, err := h.userSvc.GetUserDeviceTokens(c.Context(), userID)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to get device tokens",
Error: err.Error(),
})
}
if len(tokens) == 0 {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "No registered devices found",
Error: "Please register a device token first using POST /devices/register",
})
}
// Create test notification
notification := &domain.Notification{
RecipientID: userID,
Type: "system_alert",
DeliveryChannel: domain.DeliveryChannelPush,
Payload: domain.NotificationPayload{
Headline: req.Title,
Message: req.Message,
},
}
// Send push notification
err = h.notificationSvc.SendPushNotification(c.Context(), notification)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to send push notification",
Error: err.Error(),
})
}
h.mongoLoggerSvc.Info("[NotificationHandler.SendTestPushNotification] Test push sent",
zap.Int64("userID", userID),
zap.Int("deviceCount", len(tokens)),
zap.Time("timestamp", time.Now()),
)
return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "Test push notification sent successfully",
Success: true,
StatusCode: fiber.StatusOK,
Data: map[string]interface{}{
"devices_count": len(tokens),
"title": req.Title,
"message": req.Message,
},
})
}
func (h *Handler) UnregisterDeviceToken(c *fiber.Ctx) error {
type Request struct {
DeviceToken string `json:"device_token" validate:"required"`
}
var req Request
if err := c.BodyParser(&req); err != nil {
h.mongoLoggerSvc.Info("[NotificationHandler.UnregisterDeviceToken] Failed to parse request body",
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
}
userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 {
h.mongoLoggerSvc.Error("[NotificationHandler.UnregisterDeviceToken] Invalid user ID in context",
zap.Int("status_code", fiber.StatusUnauthorized),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification")
}
if err := h.userSvc.DeactivateDevice(c.Context(), userID, req.DeviceToken); err != nil {
h.mongoLoggerSvc.Error("[NotificationHandler.UnregisterDeviceToken] Failed to unregister device token",
zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to unregister device token: "+err.Error())
}
h.mongoLoggerSvc.Info("[NotificationHandler.UnregisterDeviceToken] Device token unregistered successfully",
zap.Int64("userID", userID),
zap.Time("timestamp", time.Now()),
)
return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "Device token unregistered successfully",
Success: true,
StatusCode: fiber.StatusOK,
})
}