152 lines
3.2 KiB
Go
152 lines
3.2 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/ThreeDotsLabs/watermill"
|
|
watermillHTTP "github.com/ThreeDotsLabs/watermill-http/pkg/http"
|
|
"github.com/ThreeDotsLabs/watermill-routing-example/server/common"
|
|
"github.com/ThreeDotsLabs/watermill/message"
|
|
"github.com/go-chi/chi/v5"
|
|
)
|
|
|
|
type Handler struct {
|
|
storage *storage
|
|
|
|
subscriber message.Subscriber
|
|
publisher message.Publisher
|
|
logger watermill.LoggerAdapter
|
|
|
|
lastIDs map[string]int
|
|
}
|
|
|
|
func (h Handler) Mux() *chi.Mux {
|
|
r := chi.NewRouter()
|
|
|
|
fileServer(r, "/", http.Dir("./public"))
|
|
|
|
sseRouter, err := watermillHTTP.NewSSERouter(
|
|
watermillHTTP.SSERouterConfig{
|
|
UpstreamSubscriber: h.subscriber,
|
|
},
|
|
h.logger,
|
|
)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
messagesHandler := sseRouter.AddHandler(common.UpdatesTopic, messagesStream{
|
|
logger: h.logger,
|
|
storage: h.storage,
|
|
})
|
|
|
|
r.Route("/api", func(r chi.Router) {
|
|
r.Use(requestIDMiddleware)
|
|
r.Get("/messages", messagesHandler)
|
|
r.Post("/messages/{topic}", h.SendMessage)
|
|
})
|
|
|
|
go func() {
|
|
err = sseRouter.Run(context.Background())
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}()
|
|
|
|
<-sseRouter.Running()
|
|
|
|
return r
|
|
}
|
|
|
|
func (h Handler) SendMessage(w http.ResponseWriter, r *http.Request) {
|
|
topic := chi.URLParam(r, "topic")
|
|
|
|
h.lastIDs[topic]++
|
|
|
|
msgID := fmt.Sprintf("%v", h.lastIDs[topic])
|
|
|
|
event := common.UserSignedUp{
|
|
UserID: watermill.NewUUID(),
|
|
Consents: common.Consents{
|
|
Marketing: true,
|
|
News: true,
|
|
},
|
|
}
|
|
|
|
payload, err := json.Marshal(event)
|
|
if err != nil {
|
|
logAndWriteError(h.logger, w, err)
|
|
return
|
|
}
|
|
|
|
msg := message.NewMessage(msgID, payload)
|
|
msg.Metadata.Set("name", "UserSignedUp")
|
|
|
|
err = h.publisher.Publish(topic, msg)
|
|
if err != nil {
|
|
logAndWriteError(h.logger, w, err)
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(204)
|
|
}
|
|
|
|
type messagesStream struct {
|
|
storage *storage
|
|
logger watermill.LoggerAdapter
|
|
}
|
|
|
|
func (p messagesStream) GetResponse(w http.ResponseWriter, r *http.Request) (response interface{}, ok bool) {
|
|
reqID := r.Context().Value("request_id").(string)
|
|
messages := p.storage.PopAll(reqID)
|
|
return messages, true
|
|
}
|
|
|
|
func (p messagesStream) Validate(r *http.Request, msg *message.Message) (ok bool) {
|
|
var payload common.MessageReceived
|
|
err := json.Unmarshal(msg.Payload, &payload)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
p.storage.Append(payload)
|
|
|
|
return true
|
|
}
|
|
|
|
func fileServer(r chi.Router, path string, root http.FileSystem) {
|
|
if strings.ContainsAny(path, "{}*") {
|
|
panic("FileServer does not permit URL parameters.")
|
|
}
|
|
|
|
fs := http.StripPrefix(path, http.FileServer(root))
|
|
|
|
if path != "/" && path[len(path)-1] != '/' {
|
|
r.Get(path, http.RedirectHandler(path+"/", 301).ServeHTTP)
|
|
path += "/"
|
|
}
|
|
path += "*"
|
|
|
|
r.Get(path, func(w http.ResponseWriter, r *http.Request) {
|
|
fs.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
func logAndWriteError(logger watermill.LoggerAdapter, w http.ResponseWriter, err error) {
|
|
logger.Error("Error", err, nil)
|
|
w.WriteHeader(500)
|
|
}
|
|
|
|
func requestIDMiddleware(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
requestID := watermill.NewUUID()
|
|
ctx = context.WithValue(ctx, "request_id", requestID)
|
|
next.ServeHTTP(w, r.WithContext(ctx))
|
|
})
|
|
}
|