feat: method impl added for the notfication
This commit is contained in:
parent
dd1d05929a
commit
4b06c5386b
16
cmd/main.go
16
cmd/main.go
|
|
@ -2,11 +2,15 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||||
|
customlogger "github.com/SamuelTariku/FortuneBet-Backend/internal/logger"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
||||||
|
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
|
||||||
|
httpserver "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server"
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -21,11 +25,21 @@ func main() {
|
||||||
slog.Error(err.Error())
|
slog.Error(err.Error())
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
db, _, err := repository.OpenDB(cfg.DbUrl)
|
db, _, err := repository.OpenDB(cfg.DbUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Print(err)
|
fmt.Print(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger := customlogger.NewLogger("development", slog.LevelDebug, "1.0")
|
||||||
|
|
||||||
store := repository.NewStore(db)
|
store := repository.NewStore(db)
|
||||||
fmt.Println(store)
|
notificationRepo := repository.NewNotificationRepository(store)
|
||||||
|
notificationSvc := notificationservice.New(notificationRepo, logger)
|
||||||
|
|
||||||
|
app := httpserver.NewApp(cfg.Port, logger, notificationSvc)
|
||||||
|
if err := app.Run(); err != nil {
|
||||||
|
log.Fatal("Failed to start server with error: ", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
18
db/query/notification.sql
Normal file
18
db/query/notification.sql
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
-- name: CreateNotification :one
|
||||||
|
INSERT INTO notifications (
|
||||||
|
id, recipient_id, type, 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
|
||||||
|
SELECT * FROM notifications WHERE id = $1 LIMIT 1;
|
||||||
|
|
||||||
|
-- name: ListNotifications :many
|
||||||
|
SELECT * FROM notifications WHERE recipient_id = $1 ORDER BY timestamp DESC LIMIT $2 OFFSET $3;
|
||||||
|
|
||||||
|
-- name: UpdateNotificationStatus :one
|
||||||
|
UPDATE notifications SET delivery_status = $2, is_read = $3, metadata = $4 WHERE id = $1 RETURNING *;
|
||||||
|
|
||||||
|
-- name: ListFailedNotifications :many
|
||||||
|
SELECT * FROM notifications WHERE delivery_status = 'failed' AND timestamp < NOW() - INTERVAL '1 hour' ORDER BY timestamp ASC LIMIT $1;
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.26.0
|
// sqlc v1.28.0
|
||||||
|
|
||||||
package dbgen
|
package dbgen
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.26.0
|
// sqlc v1.28.0
|
||||||
|
|
||||||
package dbgen
|
package dbgen
|
||||||
|
|
||||||
|
|
@ -8,6 +8,23 @@ import (
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Notification struct {
|
||||||
|
ID string
|
||||||
|
RecipientID string
|
||||||
|
Type string
|
||||||
|
Level string
|
||||||
|
ErrorSeverity pgtype.Text
|
||||||
|
Reciever string
|
||||||
|
IsRead bool
|
||||||
|
DeliveryStatus string
|
||||||
|
DeliveryChannel pgtype.Text
|
||||||
|
Payload []byte
|
||||||
|
Priority pgtype.Int4
|
||||||
|
Version int32
|
||||||
|
Timestamp pgtype.Timestamptz
|
||||||
|
Metadata []byte
|
||||||
|
}
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
ID int64
|
ID int64
|
||||||
FirstName string
|
FirstName string
|
||||||
|
|
|
||||||
220
gen/db/notification.sql.go
Normal file
220
gen/db/notification.sql.go
Normal file
|
|
@ -0,0 +1,220 @@
|
||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.28.0
|
||||||
|
// source: notification.sql
|
||||||
|
|
||||||
|
package dbgen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
const CreateNotification = `-- name: CreateNotification :one
|
||||||
|
INSERT INTO notifications (
|
||||||
|
id, recipient_id, type, 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 {
|
||||||
|
ID string
|
||||||
|
RecipientID string
|
||||||
|
Type string
|
||||||
|
Level string
|
||||||
|
ErrorSeverity pgtype.Text
|
||||||
|
Reciever string
|
||||||
|
IsRead bool
|
||||||
|
DeliveryStatus string
|
||||||
|
DeliveryChannel pgtype.Text
|
||||||
|
Payload []byte
|
||||||
|
Priority pgtype.Int4
|
||||||
|
Timestamp pgtype.Timestamptz
|
||||||
|
Metadata []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateNotification(ctx context.Context, arg CreateNotificationParams) (Notification, error) {
|
||||||
|
row := q.db.QueryRow(ctx, CreateNotification,
|
||||||
|
arg.ID,
|
||||||
|
arg.RecipientID,
|
||||||
|
arg.Type,
|
||||||
|
arg.Level,
|
||||||
|
arg.ErrorSeverity,
|
||||||
|
arg.Reciever,
|
||||||
|
arg.IsRead,
|
||||||
|
arg.DeliveryStatus,
|
||||||
|
arg.DeliveryChannel,
|
||||||
|
arg.Payload,
|
||||||
|
arg.Priority,
|
||||||
|
arg.Timestamp,
|
||||||
|
arg.Metadata,
|
||||||
|
)
|
||||||
|
var i Notification
|
||||||
|
err := row.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,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetNotification(ctx context.Context, id string) (Notification, error) {
|
||||||
|
row := q.db.QueryRow(ctx, GetNotification, id)
|
||||||
|
var i Notification
|
||||||
|
err := row.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,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) ListFailedNotifications(ctx context.Context, limit int32) ([]Notification, error) {
|
||||||
|
rows, err := q.db.Query(ctx, ListFailedNotifications, limit)
|
||||||
|
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 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
|
||||||
|
`
|
||||||
|
|
||||||
|
type ListNotificationsParams struct {
|
||||||
|
RecipientID string
|
||||||
|
Limit int32
|
||||||
|
Offset int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) ListNotifications(ctx context.Context, arg ListNotificationsParams) ([]Notification, error) {
|
||||||
|
rows, err := q.db.Query(ctx, ListNotifications, arg.RecipientID, 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 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
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateNotificationStatusParams struct {
|
||||||
|
ID string
|
||||||
|
DeliveryStatus string
|
||||||
|
IsRead bool
|
||||||
|
Metadata []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateNotificationStatus(ctx context.Context, arg UpdateNotificationStatusParams) (Notification, error) {
|
||||||
|
row := q.db.QueryRow(ctx, UpdateNotificationStatus,
|
||||||
|
arg.ID,
|
||||||
|
arg.DeliveryStatus,
|
||||||
|
arg.IsRead,
|
||||||
|
arg.Metadata,
|
||||||
|
)
|
||||||
|
var i Notification
|
||||||
|
err := row.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,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.26.0
|
// sqlc v1.28.0
|
||||||
// source: user.sql
|
// source: user.sql
|
||||||
|
|
||||||
package dbgen
|
package dbgen
|
||||||
|
|
|
||||||
5
go.mod
5
go.mod
|
|
@ -5,6 +5,8 @@ go 1.24.1
|
||||||
require (
|
require (
|
||||||
github.com/bytedance/sonic v1.13.2
|
github.com/bytedance/sonic v1.13.2
|
||||||
github.com/gofiber/fiber/v2 v2.52.6
|
github.com/gofiber/fiber/v2 v2.52.6
|
||||||
|
github.com/gofiber/websocket/v2 v2.2.1
|
||||||
|
github.com/google/uuid v1.6.0
|
||||||
github.com/jackc/pgx/v5 v5.7.4
|
github.com/jackc/pgx/v5 v5.7.4
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
)
|
)
|
||||||
|
|
@ -13,7 +15,7 @@ require (
|
||||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/fasthttp/websocket v1.5.3 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
|
|
@ -23,6 +25,7 @@ require (
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
github.com/rivo/uniseg v0.2.0 // indirect
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
|
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
|
||||||
github.com/stretchr/testify v1.8.4 // indirect
|
github.com/stretchr/testify v1.8.4 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
|
|
|
||||||
6
go.sum
6
go.sum
|
|
@ -11,8 +11,12 @@ github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQ
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/fasthttp/websocket v1.5.3 h1:TPpQuLwJYfd4LJPXvHDYPMFWbLjsT91n3GpWtCQtdek=
|
||||||
|
github.com/fasthttp/websocket v1.5.3/go.mod h1:46gg/UBmTU1kUaTcwQXpUxtRwG2PvIZYeA8oL6vF3Fs=
|
||||||
github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
|
github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
|
||||||
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
||||||
|
github.com/gofiber/websocket/v2 v2.2.1 h1:C9cjxvloojayOp9AovmpQrk8VqvVnT8Oao3+IUygH7w=
|
||||||
|
github.com/gofiber/websocket/v2 v2.2.1/go.mod h1:Ao/+nyNnX5u/hIFPuHl28a+NIkrqK7PRimyKaj4JxVU=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
|
|
@ -41,6 +45,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
|
||||||
|
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
|
|
||||||
7
internal/pkgs/helpers/helpers.go
Normal file
7
internal/pkgs/helpers/helpers.go
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
package helpers
|
||||||
|
|
||||||
|
import "github.com/google/uuid"
|
||||||
|
|
||||||
|
func GenerateID() string {
|
||||||
|
return uuid.New().String()
|
||||||
|
}
|
||||||
174
internal/repository/notification.go
Normal file
174
internal/repository/notification.go
Normal file
|
|
@ -0,0 +1,174 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NotificationRepository interface {
|
||||||
|
CreateNotification(ctx context.Context, notification *domain.Notification) (*domain.Notification, error)
|
||||||
|
UpdateNotificationStatus(ctx context.Context, id, status string, isRead bool, metadata []byte) (*domain.Notification, error)
|
||||||
|
ListNotifications(ctx context.Context, recipientID string, limit, offset int) ([]domain.Notification, error)
|
||||||
|
ListFailedNotifications(ctx context.Context, limit int) ([]domain.Notification, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Repository struct {
|
||||||
|
store *Store
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNotificationRepository(store *Store) NotificationRepository {
|
||||||
|
return &Repository{store: store}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) CreateNotification(ctx context.Context, notification *domain.Notification) (*domain.Notification, error) {
|
||||||
|
var errorSeverity pgtype.Text
|
||||||
|
if notification.ErrorSeverity != nil {
|
||||||
|
errorSeverity.String = string(*notification.ErrorSeverity)
|
||||||
|
errorSeverity.Valid = true
|
||||||
|
}
|
||||||
|
|
||||||
|
var deliveryChannel pgtype.Text
|
||||||
|
if notification.DeliveryChannel != "" {
|
||||||
|
deliveryChannel.String = string(notification.DeliveryChannel)
|
||||||
|
deliveryChannel.Valid = true
|
||||||
|
}
|
||||||
|
|
||||||
|
var priority pgtype.Int4
|
||||||
|
if notification.Priority != 0 {
|
||||||
|
priority.Int32 = int32(notification.Priority)
|
||||||
|
priority.Valid = true
|
||||||
|
}
|
||||||
|
|
||||||
|
params := dbgen.CreateNotificationParams{
|
||||||
|
ID: notification.ID,
|
||||||
|
RecipientID: notification.RecipientID,
|
||||||
|
Type: string(notification.Type),
|
||||||
|
Level: string(notification.Level),
|
||||||
|
ErrorSeverity: errorSeverity,
|
||||||
|
Reciever: string(notification.Reciever),
|
||||||
|
IsRead: notification.IsRead,
|
||||||
|
DeliveryStatus: string(notification.DeliveryStatus),
|
||||||
|
DeliveryChannel: deliveryChannel,
|
||||||
|
Payload: marshalPayload(notification.Payload),
|
||||||
|
Priority: priority,
|
||||||
|
Timestamp: pgtype.Timestamptz{Time: notification.Timestamp, Valid: true},
|
||||||
|
Metadata: notification.Metadata,
|
||||||
|
}
|
||||||
|
|
||||||
|
dbNotification, err := r.store.queries.CreateNotification(ctx, params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.mapDBToDomain(&dbNotification), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) UpdateNotificationStatus(ctx context.Context, id, status string, isRead bool, metadata []byte) (*domain.Notification, error) {
|
||||||
|
params := dbgen.UpdateNotificationStatusParams{
|
||||||
|
ID: id,
|
||||||
|
DeliveryStatus: status,
|
||||||
|
IsRead: isRead,
|
||||||
|
Metadata: metadata,
|
||||||
|
}
|
||||||
|
|
||||||
|
dbNotification, err := r.store.queries.UpdateNotificationStatus(ctx, params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.mapDBToDomain(&dbNotification), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) ListNotifications(ctx context.Context, recipientID string, limit, offset int) ([]domain.Notification, error) {
|
||||||
|
params := dbgen.ListNotificationsParams{
|
||||||
|
RecipientID: recipientID,
|
||||||
|
Limit: int32(limit),
|
||||||
|
Offset: int32(offset),
|
||||||
|
}
|
||||||
|
|
||||||
|
dbNotifications, err := r.store.queries.ListNotifications(ctx, params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var result []domain.Notification
|
||||||
|
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) {
|
||||||
|
dbNotifications, err := r.store.queries.ListFailedNotifications(ctx, int32(limit))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var result []domain.Notification
|
||||||
|
for _, dbNotif := range dbNotifications {
|
||||||
|
domainNotif := r.mapDBToDomain(&dbNotif)
|
||||||
|
result = append(result, *domainNotif)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) mapDBToDomain(dbNotif *dbgen.Notification) *domain.Notification {
|
||||||
|
var errorSeverity *domain.NotificationErrorSeverity
|
||||||
|
if dbNotif.ErrorSeverity.Valid {
|
||||||
|
s := domain.NotificationErrorSeverity(dbNotif.ErrorSeverity.String)
|
||||||
|
errorSeverity = &s
|
||||||
|
}
|
||||||
|
|
||||||
|
var deliveryChannel domain.DeliveryChannel
|
||||||
|
if dbNotif.DeliveryChannel.Valid {
|
||||||
|
deliveryChannel = domain.DeliveryChannel(dbNotif.DeliveryChannel.String)
|
||||||
|
} else {
|
||||||
|
deliveryChannel = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var priority int
|
||||||
|
if dbNotif.Priority.Valid {
|
||||||
|
priority = int(dbNotif.Priority.Int32)
|
||||||
|
}
|
||||||
|
|
||||||
|
payload, err := unmarshalPayload(dbNotif.Payload)
|
||||||
|
if err != nil {
|
||||||
|
payload = domain.NotificationPayload{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &domain.Notification{
|
||||||
|
ID: dbNotif.ID,
|
||||||
|
RecipientID: dbNotif.RecipientID,
|
||||||
|
Type: domain.NotificationType(dbNotif.Type),
|
||||||
|
Level: domain.NotificationLevel(dbNotif.Level),
|
||||||
|
ErrorSeverity: errorSeverity,
|
||||||
|
Reciever: domain.NotificationRecieverSide(dbNotif.Reciever),
|
||||||
|
IsRead: dbNotif.IsRead,
|
||||||
|
DeliveryStatus: domain.NotificationDeliveryStatus(dbNotif.DeliveryStatus),
|
||||||
|
DeliveryChannel: deliveryChannel,
|
||||||
|
Payload: payload,
|
||||||
|
Priority: priority,
|
||||||
|
Timestamp: dbNotif.Timestamp.Time,
|
||||||
|
Metadata: dbNotif.Metadata,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshalPayload(payload domain.NotificationPayload) []byte {
|
||||||
|
data, _ := json.Marshal(payload)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalPayload(data []byte) (domain.NotificationPayload, error) {
|
||||||
|
var payload domain.NotificationPayload
|
||||||
|
if err := json.Unmarshal(data, &payload); err != nil {
|
||||||
|
return domain.NotificationPayload{}, err
|
||||||
|
}
|
||||||
|
return payload, nil
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,18 @@
|
||||||
package notficationservice
|
package notificationservice
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
"github.com/gofiber/websocket/v2"
|
||||||
|
)
|
||||||
|
|
||||||
type NotificationStore interface {
|
type NotificationStore interface {
|
||||||
|
SendNotification(ctx context.Context, notification *domain.Notification) error
|
||||||
|
MarkAsRead(ctx context.Context, notificationID, recipientID string) error
|
||||||
|
ListNotifications(ctx context.Context, recipientID string, limit, offset int) ([]domain.Notification, error)
|
||||||
|
ConnectWebSocket(ctx context.Context, recipientID string, c *websocket.Conn) error
|
||||||
|
DisconnectWebSocket(recipientID string)
|
||||||
|
SendSMS(ctx context.Context, recipientID, message string) error
|
||||||
|
SendEmail(ctx context.Context, recipientID, subject, message string) error
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,196 @@
|
||||||
package notficationservice
|
package notificationservice
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log/slog"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/pkgs/helpers"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
||||||
|
"github.com/gofiber/websocket/v2"
|
||||||
|
)
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
store NotificationStore
|
repo repository.NotificationRepository
|
||||||
|
logger *slog.Logger
|
||||||
|
connections sync.Map
|
||||||
|
notificationCh chan *domain.Notification
|
||||||
|
stopCh chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(store NotificationStore) *Service {
|
func New(repo repository.NotificationRepository, logger *slog.Logger) NotificationStore {
|
||||||
return &Service{
|
svc := &Service{
|
||||||
store: store,
|
repo: repo,
|
||||||
|
logger: logger,
|
||||||
|
connections: sync.Map{},
|
||||||
|
notificationCh: make(chan *domain.Notification, 1000),
|
||||||
|
stopCh: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
go svc.startWorker()
|
||||||
|
go svc.startRetryWorker()
|
||||||
|
|
||||||
|
return svc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) addConnection(ctx context.Context, recipientID string, c *websocket.Conn) {
|
||||||
|
if c == nil {
|
||||||
|
s.logger.Warn("Attempted to add nil WebSocket connection", "recipientID", recipientID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.connections.Store(recipientID, c)
|
||||||
|
s.logger.Info("Added WebSocket connection", "recipientID", recipientID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) SendNotification(ctx context.Context, notification *domain.Notification) error {
|
||||||
|
notification.ID = helpers.GenerateID()
|
||||||
|
notification.Timestamp = time.Now()
|
||||||
|
notification.DeliveryStatus = domain.DeliveryStatusPending
|
||||||
|
|
||||||
|
created, err := s.repo.CreateNotification(ctx, notification)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
notification = created
|
||||||
|
|
||||||
|
select {
|
||||||
|
case s.notificationCh <- notification:
|
||||||
|
default:
|
||||||
|
s.logger.Error("Notification channel full, dropping notification", "id", notification.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) MarkAsRead(ctx context.Context, notificationID, recipientID string) error {
|
||||||
|
_, err := s.repo.UpdateNotificationStatus(ctx, notificationID, string(domain.DeliveryStatusSent), true, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) ListNotifications(ctx context.Context, recipientID string, limit, offset int) ([]domain.Notification, error) {
|
||||||
|
return s.repo.ListNotifications(ctx, recipientID, limit, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) ConnectWebSocket(ctx context.Context, recipientID string, c *websocket.Conn) error {
|
||||||
|
s.addConnection(ctx, recipientID, c)
|
||||||
|
defer func() {
|
||||||
|
s.DisconnectWebSocket(recipientID)
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
_, _, err := c.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
|
||||||
|
s.logger.Error("WebSocket error", "recipientID", recipientID, "error", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) DisconnectWebSocket(recipientID string) {
|
||||||
|
s.connections.Delete(recipientID)
|
||||||
|
if conn, loaded := s.connections.LoadAndDelete(recipientID); loaded {
|
||||||
|
conn.(*websocket.Conn).Close()
|
||||||
|
s.logger.Info("Disconnected WebSocket", "recipientID", recipientID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) SendSMS(ctx context.Context, recipientID, message string) error {
|
||||||
|
s.logger.Info("SMS notification requested", "recipientID", recipientID, "message", message)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) SendEmail(ctx context.Context, recipientID, subject, message string) error {
|
||||||
|
s.logger.Info("Email notification requested", "recipientID", recipientID, "subject", subject)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) startWorker() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case notification := <-s.notificationCh:
|
||||||
|
s.handleNotification(notification)
|
||||||
|
case <-s.stopCh:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) handleNotification(notification *domain.Notification) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
if conn, ok := s.connections.Load(notification.RecipientID); ok {
|
||||||
|
data, err := notification.ToJSON()
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to serialize notification", "id", notification.ID, "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := conn.(*websocket.Conn).WriteMessage(websocket.TextMessage, data); err != nil {
|
||||||
|
s.logger.Error("Failed to send WebSocket message", "id", notification.ID, "error", err)
|
||||||
|
notification.DeliveryStatus = domain.DeliveryStatusFailed
|
||||||
|
} else {
|
||||||
|
notification.DeliveryStatus = domain.DeliveryStatusSent
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s.logger.Warn("No WebSocket connection for recipient", "recipientID", notification.RecipientID)
|
||||||
|
notification.DeliveryStatus = domain.DeliveryStatusFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := s.repo.UpdateNotificationStatus(ctx, notification.ID, string(notification.DeliveryStatus), notification.IsRead, notification.Metadata); err != nil {
|
||||||
|
s.logger.Error("Failed to update notification status", "id", notification.ID, "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) startRetryWorker() {
|
||||||
|
ticker := time.NewTicker(1 * time.Minute)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
s.retryFailedNotifications()
|
||||||
|
case <-s.stopCh:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) retryFailedNotifications() {
|
||||||
|
ctx := context.Background()
|
||||||
|
failedNotifications, err := s.repo.ListFailedNotifications(ctx, 100)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to list failed notifications", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, n := range failedNotifications {
|
||||||
|
notification := &n
|
||||||
|
go func(notification *domain.Notification) {
|
||||||
|
for attempt := 0; attempt < 3; attempt++ {
|
||||||
|
time.Sleep(time.Duration(attempt) * time.Second)
|
||||||
|
if conn, ok := s.connections.Load(notification.RecipientID); ok {
|
||||||
|
data, err := notification.ToJSON()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := conn.(*websocket.Conn).WriteMessage(websocket.TextMessage, data); err == nil {
|
||||||
|
notification.DeliveryStatus = domain.DeliveryStatusSent
|
||||||
|
if _, err := s.repo.UpdateNotificationStatus(ctx, notification.ID, string(notification.DeliveryStatus), notification.IsRead, notification.Metadata); err != nil {
|
||||||
|
s.logger.Error("Failed to update after retry", "id", notification.ID, "error", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.logger.Error("Max retries reached for notification", "id", notification.ID)
|
||||||
|
}(notification)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,21 @@ package httpserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
|
||||||
"github.com/bytedance/sonic"
|
"github.com/bytedance/sonic"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
fiber *fiber.App
|
fiber *fiber.App
|
||||||
port int
|
NotidicationStore notificationservice.NotificationStore
|
||||||
|
port int
|
||||||
|
Logger *slog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewApp(port int) *App {
|
func NewApp(port int, logger *slog.Logger, notidicationStore notificationservice.NotificationStore) *App {
|
||||||
app := fiber.New(fiber.Config{
|
app := fiber.New(fiber.Config{
|
||||||
CaseSensitive: true,
|
CaseSensitive: true,
|
||||||
DisableHeaderNormalizing: true,
|
DisableHeaderNormalizing: true,
|
||||||
|
|
@ -20,8 +24,10 @@ func NewApp(port int) *App {
|
||||||
JSONDecoder: sonic.Unmarshal,
|
JSONDecoder: sonic.Unmarshal,
|
||||||
})
|
})
|
||||||
s := &App{
|
s := &App{
|
||||||
fiber: app,
|
fiber: app,
|
||||||
port: port,
|
port: port,
|
||||||
|
NotidicationStore: notidicationStore,
|
||||||
|
Logger: logger,
|
||||||
}
|
}
|
||||||
|
|
||||||
s.initAppRoutes()
|
s.initAppRoutes()
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,35 @@
|
||||||
package httpserver
|
package httpserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/gofiber/websocket/v2"
|
||||||
|
)
|
||||||
|
|
||||||
func (a *App) initAppRoutes() {
|
func (a *App) initAppRoutes() {
|
||||||
// a.fiber.Group("/users", users.CreateAccount(a.userAPI))
|
// a.fiber.Group("/users", users.CreateAccount(a.userAPI))
|
||||||
|
|
||||||
|
a.fiber.Get("/ws/:recipientID", func(c *fiber.Ctx) error {
|
||||||
|
if websocket.IsWebSocketUpgrade(c) {
|
||||||
|
c.Locals("allowed", true)
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
return fiber.ErrUpgradeRequired
|
||||||
|
}, websocket.New(func(c *websocket.Conn) {
|
||||||
|
recipientID := c.Params("recipientID")
|
||||||
|
a.NotidicationStore.ConnectWebSocket(context.Background(), recipientID, c)
|
||||||
|
|
||||||
|
defer a.NotidicationStore.DisconnectWebSocket(recipientID)
|
||||||
|
|
||||||
|
for {
|
||||||
|
_, _, err := c.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
|
||||||
|
a.Logger.Error("WebSocket error", "recipientID", recipientID, "error", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user