fix: notification integration issues

This commit is contained in:
Samuel Tariku 2025-05-22 00:15:30 +03:00
parent ccdfc537c2
commit 6a06b399c7
16 changed files with 352 additions and 70 deletions

View File

@ -1,21 +1,71 @@
-- name: CreateNotification :one -- name: CreateNotification :one
INSERT INTO notifications ( INSERT INTO notifications (
id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, timestamp, metadata id,
) VALUES ( recipient_id,
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13 type,
) RETURNING *; level,
error_severity,
reciever,
is_read,
delivery_status,
delivery_channel,
payload,
priority,
timestamp,
metadata
)
VALUES (
$1,
$2,
$3,
$4,
$5,
$6,
$7,
$8,
$9,
$10,
$11,
$12,
$13
)
RETURNING *;
-- name: GetNotification :one -- name: GetNotification :one
SELECT * FROM notifications WHERE id = $1 LIMIT 1; SELECT *
FROM notifications
WHERE id = $1
LIMIT 1;
-- name: GetAllNotifications :many
SELECT *
FROM notifications
ORDER BY timestamp DESC
LIMIT $1 OFFSET $2;
-- name: ListNotifications :many -- name: ListNotifications :many
SELECT * FROM notifications WHERE recipient_id = $1 ORDER BY timestamp DESC LIMIT $2 OFFSET $3; SELECT *
FROM notifications
WHERE recipient_id = $1
ORDER BY timestamp DESC
LIMIT $2 OFFSET $3;
-- name: CountUnreadNotifications :one
SELECT count(id)
FROM notifications
WHERE recipient_id = $1
AND is_read = false;
-- name: UpdateNotificationStatus :one -- name: UpdateNotificationStatus :one
UPDATE notifications SET delivery_status = $2, is_read = $3, metadata = $4 WHERE id = $1 RETURNING *; UPDATE notifications
SET delivery_status = $2,
is_read = $3,
metadata = $4
WHERE id = $1
RETURNING *;
-- 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 -- name: ListRecipientIDsByReceiver :many
SELECT recipient_id FROM notifications WHERE reciever = $1; SELECT recipient_id
FROM notifications
WHERE reciever = $1;

View File

@ -66,7 +66,7 @@ wHERE (
company_id = $2 company_id = $2
OR $2 IS NULL OR $2 IS NULL
) )
LIMIT $3 OFFSET $4; LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset');
-- name: GetTotalUsers :one -- name: GetTotalUsers :one
SELECT COUNT(*) SELECT COUNT(*)
FROM users FROM users
@ -109,7 +109,6 @@ WHERE id = $7;
UPDATE users UPDATE users
SET company_id = $1 SET company_id = $1
WHERE id = $2; WHERE id = $2;
-- name: SuspendUser :exec -- name: SuspendUser :exec
UPDATE users UPDATE users
SET suspended = $1, SET suspended = $1,

View File

@ -1,4 +1,3 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.28.0 // sqlc v1.28.0

View File

@ -11,12 +11,52 @@ import (
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
) )
const CountUnreadNotifications = `-- name: CountUnreadNotifications :one
SELECT count(id)
FROM notifications
WHERE recipient_id = $1
AND is_read = false
`
func (q *Queries) CountUnreadNotifications(ctx context.Context, recipientID int64) (int64, error) {
row := q.db.QueryRow(ctx, CountUnreadNotifications, recipientID)
var count int64
err := row.Scan(&count)
return count, err
}
const CreateNotification = `-- name: CreateNotification :one const CreateNotification = `-- name: CreateNotification :one
INSERT INTO notifications ( INSERT INTO notifications (
id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, timestamp, metadata id,
) VALUES ( recipient_id,
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13 type,
) RETURNING id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, version, timestamp, metadata level,
error_severity,
reciever,
is_read,
delivery_status,
delivery_channel,
payload,
priority,
timestamp,
metadata
)
VALUES (
$1,
$2,
$3,
$4,
$5,
$6,
$7,
$8,
$9,
$10,
$11,
$12,
$13
)
RETURNING id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, version, timestamp, metadata
` `
type CreateNotificationParams struct { type CreateNotificationParams struct {
@ -71,8 +111,58 @@ func (q *Queries) CreateNotification(ctx context.Context, arg CreateNotification
return i, err return i, err
} }
const GetAllNotifications = `-- name: GetAllNotifications :many
SELECT id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, version, timestamp, metadata
FROM notifications
ORDER BY timestamp DESC
LIMIT $1 OFFSET $2
`
type GetAllNotificationsParams struct {
Limit int32 `json:"limit"`
Offset int32 `json:"offset"`
}
func (q *Queries) GetAllNotifications(ctx context.Context, arg GetAllNotificationsParams) ([]Notification, error) {
rows, err := q.db.Query(ctx, GetAllNotifications, arg.Limit, arg.Offset)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Notification
for rows.Next() {
var i Notification
if err := rows.Scan(
&i.ID,
&i.RecipientID,
&i.Type,
&i.Level,
&i.ErrorSeverity,
&i.Reciever,
&i.IsRead,
&i.DeliveryStatus,
&i.DeliveryChannel,
&i.Payload,
&i.Priority,
&i.Version,
&i.Timestamp,
&i.Metadata,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const GetNotification = `-- name: GetNotification :one const GetNotification = `-- name: GetNotification :one
SELECT id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, version, timestamp, metadata FROM notifications WHERE id = $1 LIMIT 1 SELECT id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, version, timestamp, metadata
FROM notifications
WHERE id = $1
LIMIT 1
` `
func (q *Queries) GetNotification(ctx context.Context, id string) (Notification, error) { func (q *Queries) GetNotification(ctx context.Context, id string) (Notification, error) {
@ -98,7 +188,12 @@ func (q *Queries) GetNotification(ctx context.Context, id string) (Notification,
} }
const ListFailedNotifications = `-- name: ListFailedNotifications :many const ListFailedNotifications = `-- name: ListFailedNotifications :many
SELECT id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, version, timestamp, metadata FROM notifications WHERE delivery_status = 'failed' AND timestamp < NOW() - INTERVAL '1 hour' ORDER BY timestamp ASC LIMIT $1 SELECT id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, version, timestamp, metadata
FROM notifications
WHERE delivery_status = 'failed'
AND timestamp < NOW() - INTERVAL '1 hour'
ORDER BY timestamp ASC
LIMIT $1
` `
func (q *Queries) ListFailedNotifications(ctx context.Context, limit int32) ([]Notification, error) { func (q *Queries) ListFailedNotifications(ctx context.Context, limit int32) ([]Notification, error) {
@ -137,7 +232,11 @@ func (q *Queries) ListFailedNotifications(ctx context.Context, limit int32) ([]N
} }
const ListNotifications = `-- name: ListNotifications :many const ListNotifications = `-- name: ListNotifications :many
SELECT id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, version, timestamp, metadata FROM notifications WHERE recipient_id = $1 ORDER BY timestamp DESC LIMIT $2 OFFSET $3 SELECT id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, version, timestamp, metadata
FROM notifications
WHERE recipient_id = $1
ORDER BY timestamp DESC
LIMIT $2 OFFSET $3
` `
type ListNotificationsParams struct { type ListNotificationsParams struct {
@ -182,7 +281,9 @@ func (q *Queries) ListNotifications(ctx context.Context, arg ListNotificationsPa
} }
const ListRecipientIDsByReceiver = `-- name: ListRecipientIDsByReceiver :many const ListRecipientIDsByReceiver = `-- name: ListRecipientIDsByReceiver :many
SELECT recipient_id FROM notifications WHERE reciever = $1 SELECT recipient_id
FROM notifications
WHERE reciever = $1
` `
func (q *Queries) ListRecipientIDsByReceiver(ctx context.Context, reciever string) ([]int64, error) { func (q *Queries) ListRecipientIDsByReceiver(ctx context.Context, reciever string) ([]int64, error) {
@ -206,7 +307,12 @@ func (q *Queries) ListRecipientIDsByReceiver(ctx context.Context, reciever strin
} }
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
` `
type UpdateNotificationStatusParams struct { type UpdateNotificationStatusParams struct {

View File

@ -182,14 +182,14 @@ wHERE (
company_id = $2 company_id = $2
OR $2 IS NULL OR $2 IS NULL
) )
LIMIT $3 OFFSET $4 LIMIT $4 OFFSET $3
` `
type GetAllUsersParams struct { type GetAllUsersParams struct {
Role string `json:"role"` Role string `json:"role"`
CompanyID pgtype.Int8 `json:"company_id"` CompanyID pgtype.Int8 `json:"company_id"`
Limit int32 `json:"limit"` Offset pgtype.Int4 `json:"offset"`
Offset int32 `json:"offset"` Limit pgtype.Int4 `json:"limit"`
} }
type GetAllUsersRow struct { type GetAllUsersRow struct {
@ -212,8 +212,8 @@ func (q *Queries) GetAllUsers(ctx context.Context, arg GetAllUsersParams) ([]Get
rows, err := q.db.Query(ctx, GetAllUsers, rows, err := q.db.Query(ctx, GetAllUsers,
arg.Role, arg.Role,
arg.CompanyID, arg.CompanyID,
arg.Limit,
arg.Offset, arg.Offset,
arg.Limit,
) )
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -9,6 +9,10 @@ type ValidInt64 struct {
Value int64 Value int64
Valid bool Valid bool
} }
type ValidInt struct {
Value int
Valid bool
}
type ValidString struct { type ValidString struct {
Value string Value string

View File

@ -15,6 +15,8 @@ type NotificationRepository interface {
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) ListRecipientIDs(ctx context.Context, receiver domain.NotificationRecieverSide) ([]int64, error)
CountUnreadNotifications(ctx context.Context, recipient_id int64) (int64, error)
GetAllNotifications(ctx context.Context, limit, offset int) ([]domain.Notification, error)
} }
type Repository struct { type Repository struct {
@ -105,6 +107,24 @@ func (r *Repository) ListNotifications(ctx context.Context, recipientID int64, l
return result, nil return result, nil
} }
func (r *Repository) GetAllNotifications(ctx context.Context, limit, offset int) ([]domain.Notification, error) {
dbNotifications, err := r.store.queries.GetAllNotifications(ctx, dbgen.GetAllNotificationsParams{
Limit: int32(limit),
Offset: int32(offset),
})
if err != nil {
return nil, err
}
var result []domain.Notification = make([]domain.Notification, 0, len(dbNotifications))
for _, dbNotif := range dbNotifications {
domainNotif := r.mapDBToDomain(&dbNotif)
result = append(result, *domainNotif)
}
return result, nil
}
func (r *Repository) ListFailedNotifications(ctx context.Context, limit int) ([]domain.Notification, error) { func (r *Repository) ListFailedNotifications(ctx context.Context, limit int) ([]domain.Notification, error) {
dbNotifications, err := r.store.queries.ListFailedNotifications(ctx, int32(limit)) dbNotifications, err := r.store.queries.ListFailedNotifications(ctx, int32(limit))
if err != nil { if err != nil {
@ -177,3 +197,7 @@ func unmarshalPayload(data []byte) (domain.NotificationPayload, error) {
} }
return payload, nil return payload, nil
} }
func (r *Repository) CountUnreadNotifications(ctx context.Context, recipient_id int64) (int64, error) {
return r.store.queries.CountUnreadNotifications(ctx, recipient_id)
}

View File

@ -90,8 +90,14 @@ func (s *Store) GetAllUsers(ctx context.Context, filter user.Filter) ([]domain.U
Int64: filter.CompanyID.Value, Int64: filter.CompanyID.Value,
Valid: filter.CompanyID.Valid, Valid: filter.CompanyID.Valid,
}, },
Limit: int32(filter.PageSize), Limit: pgtype.Int4{
Offset: int32(filter.Page), Int32: int32(filter.PageSize.Value),
Valid: filter.PageSize.Valid,
},
Offset: pgtype.Int4{
Int32: int32(filter.Page.Value),
Valid: filter.Page.Valid,
},
}) })
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err

View File

@ -16,4 +16,6 @@ type NotificationStore interface {
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 ListRecipientIDs(ctx context.Context, receiver domain.NotificationRecieverSide) ([]int64, error) // New method
CountUnreadNotifications(ctx context.Context, recipient_id int64) (int64, error)
GetAllNotifications(ctx context.Context, limit, offset int) ([]domain.Notification, error)
} }

View File

@ -85,28 +85,31 @@ func (s *Service) SendNotification(ctx context.Context, notification *domain.Not
return nil return nil
} }
func (s *Service) MarkAsRead(ctx context.Context, notificationID string, recipientID int64) error { func (s *Service) MarkAsRead(ctx context.Context, notificationIDs []string, recipientID int64) error {
_, err := s.repo.UpdateNotificationStatus(ctx, notificationID, string(domain.DeliveryStatusSent), true, nil) for _, notificationID := range notificationIDs {
if err != nil { _, err := s.repo.UpdateNotificationStatus(ctx, notificationID, string(domain.DeliveryStatusSent), true, nil)
s.logger.Error("[NotificationSvc.MarkAsRead] Failed to mark notification as read", "notificationID", notificationID, "recipientID", recipientID, "error", err) if err != nil {
return err s.logger.Error("[NotificationSvc.MarkAsRead] Failed to mark notification as read", "notificationID", notificationID, "recipientID", recipientID, "error", err)
return err
}
// count, err := s.repo.CountUnreadNotifications(ctx, recipientID)
// if err != nil {
// s.logger.Error("[NotificationSvc.MarkAsRead] Failed to count unread notifications", "recipientID", recipientID, "error", err)
// return err
// }
// s.Hub.Broadcast <- map[string]interface{}{
// "type": "COUNT_NOT_OPENED_NOTIFICATION",
// "recipient_id": recipientID,
// "payload": map[string]int{
// "not_opened_notifications_count": int(count),
// },
// }
s.logger.Info("[NotificationSvc.MarkAsRead] Notification marked as read", "notificationID", notificationID, "recipientID", recipientID)
} }
// count, err := s.repo.CountUnreadNotifications(ctx, recipientID)
// if err != nil {
// s.logger.Error("[NotificationSvc.MarkAsRead] Failed to count unread notifications", "recipientID", recipientID, "error", err)
// return err
// }
// s.Hub.Broadcast <- map[string]interface{}{
// "type": "COUNT_NOT_OPENED_NOTIFICATION",
// "recipient_id": recipientID,
// "payload": map[string]int{
// "not_opened_notifications_count": int(count),
// },
// }
s.logger.Info("[NotificationSvc.MarkAsRead] Notification marked as read", "notificationID", notificationID, "recipientID", recipientID)
return nil return nil
} }
@ -120,6 +123,16 @@ func (s *Service) ListNotifications(ctx context.Context, recipientID int64, limi
return notifications, nil return notifications, nil
} }
func (s *Service) GetAllNotifications(ctx context.Context, limit, offset int) ([]domain.Notification, error) {
notifications, err := s.repo.GetAllNotifications(ctx, limit, offset)
if err != nil {
s.logger.Error("[NotificationSvc.ListNotifications] Failed to get all notifications")
return nil, err
}
s.logger.Info("[NotificationSvc.ListNotifications] Successfully retrieved all notifications", "count", len(notifications))
return notifications, nil
}
func (s *Service) ConnectWebSocket(ctx context.Context, recipientID int64, c *websocket.Conn) error { func (s *Service) ConnectWebSocket(ctx context.Context, recipientID int64, c *websocket.Conn) error {
s.addConnection(ctx, recipientID, c) s.addConnection(ctx, recipientID, c)
s.logger.Info("[NotificationSvc.ConnectWebSocket] WebSocket connection established", "recipientID", recipientID) s.logger.Info("[NotificationSvc.ConnectWebSocket] WebSocket connection established", "recipientID", recipientID)
@ -267,3 +280,6 @@ func (s *Service) retryFailedNotifications() {
} }
} }
func (s *Service) CountUnreadNotifications(ctx context.Context, recipient_id int64) (int64, error) {
return s.repo.CountUnreadNotifications(ctx, recipient_id)
}

View File

@ -46,8 +46,8 @@ func (s *Service) DeleteUser(ctx context.Context, id int64) error {
type Filter struct { type Filter struct {
Role string Role string
CompanyID domain.ValidInt64 CompanyID domain.ValidInt64
Page int Page domain.ValidInt
PageSize int PageSize domain.ValidInt
} }
type ValidRole struct { type ValidRole struct {
Value domain.Role Value domain.Role

View File

@ -124,14 +124,21 @@ type AdminRes struct {
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /admin [get] // @Router /admin [get]
func (h *Handler) GetAllAdmins(c *fiber.Ctx) error { func (h *Handler) GetAllAdmins(c *fiber.Ctx) error {
filter := user.Filter{ filter := user.Filter{
Role: string(domain.RoleAdmin), Role: string(domain.RoleAdmin),
CompanyID: domain.ValidInt64{ CompanyID: domain.ValidInt64{
Value: int64(c.QueryInt("company_id")), Value: int64(c.QueryInt("company_id")),
Valid: true, Valid: true,
}, },
Page: c.QueryInt("page", 1) - 1, Page: domain.ValidInt{
PageSize: c.QueryInt("page_size", 10), Value: c.QueryInt("page", 1) - 1,
Valid: true,
},
PageSize: domain.ValidInt{
Value: c.QueryInt("page_size", 10),
Valid: true,
},
} }
valErrs, ok := h.validator.Validate(c, filter) valErrs, ok := h.validator.Validate(c, filter)
if !ok { if !ok {
@ -171,6 +178,6 @@ func (h *Handler) GetAllAdmins(c *fiber.Ctx) error {
} }
} }
return response.WritePaginatedJSON(c, fiber.StatusOK, "Admins retrieved successfully", result, nil, filter.Page, int(total)) return response.WritePaginatedJSON(c, fiber.StatusOK, "Admins retrieved successfully", result, nil, filter.Page.Value, int(total))
} }

View File

@ -115,8 +115,14 @@ func (h *Handler) GetAllManagers(c *fiber.Ctx) error {
Value: int64(c.QueryInt("company_id")), Value: int64(c.QueryInt("company_id")),
Valid: true, Valid: true,
}, },
Page: c.QueryInt("page", 1) - 1, Page: domain.ValidInt{
PageSize: c.QueryInt("page_size", 10), Value: c.QueryInt("page", 1) - 1,
Valid: true,
},
PageSize: domain.ValidInt{
Value: c.QueryInt("page_size", 10),
Valid: true,
},
} }
valErrs, ok := h.validator.Validate(c, filter) valErrs, ok := h.validator.Validate(c, filter)
if !ok { if !ok {
@ -156,7 +162,7 @@ func (h *Handler) GetAllManagers(c *fiber.Ctx) error {
} }
} }
return response.WritePaginatedJSON(c, fiber.StatusOK, "Managers retrieved successfully", result, nil, filter.Page, int(total)) return response.WritePaginatedJSON(c, fiber.StatusOK, "Managers retrieved successfully", result, nil, filter.Page.Value, int(total))
} }

View File

@ -3,11 +3,13 @@ package handlers
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"net" "net"
"net/http" "net/http"
"strconv" "strconv"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/ws" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/ws"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/adaptor" "github.com/gofiber/fiber/v2/middleware/adaptor"
@ -101,7 +103,7 @@ func (h *Handler) ConnectSocket(c *fiber.Ctx) error {
func (h *Handler) MarkNotificationAsRead(c *fiber.Ctx) error { func (h *Handler) MarkNotificationAsRead(c *fiber.Ctx) error {
type Request struct { type Request struct {
NotificationID string `json:"notification_id" validate:"required"` NotificationIDs []string `json:"notification_ids" validate:"required"`
} }
var req Request var req Request
@ -110,14 +112,15 @@ func (h *Handler) MarkNotificationAsRead(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
} }
userID, ok := c.Locals("userID").(int64) userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 { if !ok || userID == 0 {
h.logger.Error("Invalid user ID in context") h.logger.Error("Invalid user ID in context")
return fiber.NewError(fiber.StatusUnauthorized, "invalid user identification") return fiber.NewError(fiber.StatusUnauthorized, "invalid user identification")
} }
if err := h.notificationSvc.MarkAsRead(context.Background(), req.NotificationID, userID); err != nil { fmt.Printf("Notification IDs: %v \n", req.NotificationIDs)
h.logger.Error("Failed to mark notification as read", "notificationID", req.NotificationID, "error", err) if err := h.notificationSvc.MarkAsRead(context.Background(), req.NotificationIDs, userID); err != nil {
h.logger.Error("Failed to mark notifications as read", "notificationID", req.NotificationIDs, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update notification status") return fiber.NewError(fiber.StatusInternalServerError, "Failed to update notification status")
} }
@ -181,17 +184,21 @@ func (h *Handler) CreateAndSendNotification(c *fiber.Ctx) error {
return c.Status(fiber.StatusCreated).JSON(fiber.Map{"message": "Single notification sent successfully", "notification_id": notification.ID}) return c.Status(fiber.StatusCreated).JSON(fiber.Map{"message": "Single notification sent successfully", "notification_id": notification.ID})
case domain.NotificationDeliverySchemeBulk: case domain.NotificationDeliverySchemeBulk:
recipients, err := h.getAllRecipientIDs(context.Background(), req.Reciever) recipients, _, err := h.userSvc.GetAllUsers(context.Background(), user.Filter{
Role: string(req.Reciever),
})
if err != nil { if err != nil {
h.logger.Error("[NotificationSvc.CreateAndSendNotification] Failed to fetch recipients for bulk notification", "error", err) h.logger.Error("[NotificationSvc.CreateAndSendNotification] Failed to fetch recipients for bulk notification", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch recipients") return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch recipients")
} }
fmt.Printf("Number of Recipients %d \n", len(recipients))
notificationIDs := make([]string, 0, len(recipients)) notificationIDs := make([]string, 0, len(recipients))
for _, recipientID := range recipients { for _, user := range recipients {
notification := &domain.Notification{ notification := &domain.Notification{
ID: "", ID: "",
RecipientID: recipientID, RecipientID: user.ID,
Type: req.Type, Type: req.Type,
Level: req.Level, Level: req.Level,
ErrorSeverity: req.ErrorSeverity, ErrorSeverity: req.ErrorSeverity,
@ -205,7 +212,7 @@ func (h *Handler) CreateAndSendNotification(c *fiber.Ctx) error {
} }
if err := h.notificationSvc.SendNotification(context.Background(), notification); err != nil { if err := h.notificationSvc.SendNotification(context.Background(), notification); err != nil {
h.logger.Error("[NotificationSvc.CreateAndSendNotification] Failed to send bulk notification", "recipientID", recipientID, "error", err) h.logger.Error("[NotificationSvc.CreateAndSendNotification] Failed to send bulk notification", "UserID", user.ID, "error", err)
continue continue
} }
notificationIDs = append(notificationIDs, notification.ID) notificationIDs = append(notificationIDs, notification.ID)
@ -258,8 +265,60 @@ func (h *Handler) GetNotifications(c *fiber.Ctx) error {
"limit": limit, "limit": limit,
"offset": offset, "offset": offset,
}) })
} }
func (h *Handler) getAllRecipientIDs(ctx context.Context, receiver domain.NotificationRecieverSide) ([]int64, error) { func (h *Handler) getAllRecipientIDs(ctx context.Context, receiver domain.NotificationRecieverSide) ([]int64, error) {
return h.notificationSvc.ListRecipientIDs(ctx, receiver) 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.logger.Error("[NotificationSvc.GetNotifications] Invalid user ID in context")
return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification")
}
total, err := h.notificationSvc.CountUnreadNotifications(c.Context(), userID)
if err != nil {
h.logger.Error("[NotificationSvc.CountUnreadNotifications] Failed to fetch unread notification count", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch notifications")
}
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.logger.Error("[NotificationSvc.GetNotifications] Invalid limit value", "error", err)
return fiber.NewError(fiber.StatusBadRequest, "Invalid limit value")
}
page, err := strconv.Atoi(pageStr)
if err != nil || page <= 0 {
h.logger.Error("[NotificationSvc.GetNotifications] Invalid page value", "error", err)
return fiber.NewError(fiber.StatusBadRequest, "Invalid page value")
}
notifications, err := h.notificationSvc.GetAllNotifications(context.Background(), limit, ((page - 1) * limit))
if err != nil {
h.logger.Error("[NotificationSvc.GetNotifications] Failed to fetch notifications", "error", err)
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,
})
}

View File

@ -177,7 +177,9 @@ func (a *App) initAppRoutes() {
// Notification Routes // Notification Routes
a.fiber.Get("/ws/connect", a.WebsocketAuthMiddleware, h.ConnectSocket) a.fiber.Get("/ws/connect", a.WebsocketAuthMiddleware, h.ConnectSocket)
a.fiber.Get("/notifications", a.authMiddleware, h.GetNotifications) a.fiber.Get("/notifications", a.authMiddleware, h.GetNotifications)
a.fiber.Post("/notifications/mark-as-read", h.MarkNotificationAsRead) a.fiber.Get("/notifications/all", a.authMiddleware, h.GetAllNotifications)
a.fiber.Post("/notifications/mark-as-read", a.authMiddleware, h.MarkNotificationAsRead)
a.fiber.Get("/notifications/unread", a.authMiddleware, h.CountUnreadNotifications)
a.fiber.Post("/notifications/create", h.CreateAndSendNotification) a.fiber.Post("/notifications/create", h.CreateAndSendNotification)
// Virtual Game Routes // Virtual Game Routes

View File

@ -46,11 +46,13 @@ swagger:
.PHONY: db-up .PHONY: db-up
db-up: db-up:
@docker compose up -d postgres @docker compose up -d postgres migrate
.PHONY: db-down .PHONY: db-down
db-down: db-down:
@docker compose down @docker compose down
postgres:
@docker exec -it fortunebet-backend-postgres-1 psql -U root -d gh
.PHONY: sqlc-gen .PHONY: sqlc-gen
sqlc-gen: sqlc-gen: