diff --git a/main.go b/main.go index a09b4db..40ac6e1 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( "backend/src/args_parser" + "backend/src/client_notifier" "backend/src/config" "backend/src/core/models" "backend/src/core/repos" @@ -80,6 +81,8 @@ func main() { emailRepo := repos.NewEmailRepo() actionTokenRepo := repos.NewActionTokenRepo(sqlDb) + clientNotifier := client_notifier.NewBasicNotifier() + userService := services.NewUserService( services.UserServiceDeps{ Jwt: jwtUtil, @@ -116,6 +119,9 @@ func main() { dummyGroup.Use(middleware.NewAuthMiddleware(userService)) dummyGroup.GET("/", handlers.NewDummyHandler()) + lpGroup := r.Group("/pooling") + lpGroup.GET("/", handlers.NewLongPoolingHandler(clientNotifier)) + listenAddr := fmt.Sprintf(":%d", conf.GetPort()) logger.Log().Msgf("server listening on %s", listenAddr) diff --git a/src/client_notifier/event.go b/src/client_notifier/event.go new file mode 100644 index 0000000..f577329 --- /dev/null +++ b/src/client_notifier/event.go @@ -0,0 +1,12 @@ +package client_notifier + +type Event struct { + Type EventType + Data []byte +} + +type EventType string + +const ( + EventTypeEmailConfirmed EventType = "event_email_confirmed" +) diff --git a/src/client_notifier/notifier.go b/src/client_notifier/notifier.go new file mode 100644 index 0000000..cd03efc --- /dev/null +++ b/src/client_notifier/notifier.go @@ -0,0 +1,57 @@ +package client_notifier + +import "sync" + +type ClientNotifier interface { + RegisterClient(id string) <-chan Event + UnregisterClient(id string) + NotifyClient(id string, e Event) +} + +type client struct { + id string + eventChan chan Event +} + +func NewBasicNotifier() ClientNotifier { + return &basicNotifier{ + m: &sync.RWMutex{}, + clients: map[string]client{}, + } +} + +type basicNotifier struct { + m *sync.RWMutex + clients map[string]client +} + +func (p *basicNotifier) RegisterClient(id string) <-chan Event { + p.m.Lock() + defer p.m.Unlock() + + eventChan := make(chan Event) + p.clients[id] = client{ + id: id, + eventChan: eventChan, + } + + return eventChan +} + +func (p *basicNotifier) UnregisterClient(id string) { + p.m.Lock() + defer p.m.Unlock() + + delete(p.clients, id) +} + +func (p *basicNotifier) NotifyClient(id string, e Event) { + p.m.RLock() + defer p.m.RUnlock() + + client, ok := p.clients[id] + if !ok { + return + } + client.eventChan <- e +} diff --git a/src/server/handlers/long_pooling_handler.go b/src/server/handlers/long_pooling_handler.go new file mode 100644 index 0000000..332436e --- /dev/null +++ b/src/server/handlers/long_pooling_handler.go @@ -0,0 +1,27 @@ +package handlers + +import ( + "backend/src/client_notifier" + "backend/src/server/utils" + + "github.com/gin-gonic/gin" +) + +func NewLongPoolingHandler(notifier client_notifier.ClientNotifier) gin.HandlerFunc { + return func(c *gin.Context) { + user := utils.GetUserFromRequest(c) + if user == nil { + c.Data(403, "plain/text", []byte("Unauthorized")) + return + } + + eventChan := notifier.RegisterClient(user.Id) + + select { + case <-c.Done(): + notifier.UnregisterClient(user.Id) + case event := <-eventChan: + c.Data(200, "application/json", event.Data) + } + } +} diff --git a/src/server/utils/user.go b/src/server/utils/user.go new file mode 100644 index 0000000..0f6bb32 --- /dev/null +++ b/src/server/utils/user.go @@ -0,0 +1,14 @@ +package utils + +import ( + "backend/src/core/models" + + "github.com/gin-gonic/gin" +) + +func GetUserFromRequest(c *gin.Context) *models.UserDTO { + if user, ok := c.Get("user"); ok { + return user.(*models.UserDTO) + } + return nil +}