fix: notification integration issues
This commit is contained in:
parent
ccdfc537c2
commit
6a06b399c7
|
|
@ -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;
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
4
makefile
4
makefile
|
|
@ -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:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user