Files
2026-09-15 21:27:46 +05:00

191 lines
4.6 KiB
Go

package main
import (
"context"
"fmt"
"time"
"cloud.google.com/go/pubsub/v2/apiv1/pubsubpb"
"github.com/ThreeDotsLabs/watermill"
"github.com/ThreeDotsLabs/watermill-googlecloud/v2/pkg/googlecloud"
"github.com/ThreeDotsLabs/watermill-http/v2/pkg/http"
"github.com/ThreeDotsLabs/watermill/components/cqrs"
"github.com/ThreeDotsLabs/watermill/message"
"github.com/ThreeDotsLabs/watermill/message/router/middleware"
"google.golang.org/protobuf/types/known/durationpb"
)
type PostViewed struct {
PostID int `json:"post_id"`
}
type PostReactionAdded struct {
PostID int `json:"post_id"`
ReactionID string `json:"reaction_id"`
}
type PostStatsUpdated struct {
PostID int `json:"post_id"`
Views int `json:"views"`
ViewsUpdated bool `json:"views_updated"`
Reactions map[string]int `json:"reactions"`
ReactionUpdated *string `json:"reaction_updated"`
}
type Routers struct {
EventsRouter *message.Router
SSERouter http.SSERouter
EventBus *cqrs.EventBus
}
func NewRouters(cfg config, repo *Repository) (Routers, error) {
logger := watermill.NewStdLogger(false, false)
publisher, err := googlecloud.NewPublisher(
googlecloud.PublisherConfig{
ProjectID: cfg.PubSubProjectID,
},
logger,
)
if err != nil {
return Routers{}, err
}
eventBus, err := cqrs.NewEventBusWithConfig(
publisher,
cqrs.EventBusConfig{
GeneratePublishTopic: func(params cqrs.GenerateEventPublishTopicParams) (string, error) {
return params.EventName, nil
},
Marshaler: cqrs.JSONMarshaler{},
Logger: logger,
},
)
if err != nil {
return Routers{}, err
}
eventsRouter, err := message.NewRouter(message.RouterConfig{}, logger)
if err != nil {
return Routers{}, err
}
eventsRouter.AddMiddleware(middleware.Recoverer)
eventProcessor, err := cqrs.NewEventProcessorWithConfig(
eventsRouter,
cqrs.EventProcessorConfig{
GenerateSubscribeTopic: func(params cqrs.EventProcessorGenerateSubscribeTopicParams) (string, error) {
return params.EventName, nil
},
SubscriberConstructor: func(params cqrs.EventProcessorSubscriberConstructorParams) (message.Subscriber, error) {
return googlecloud.NewSubscriber(
googlecloud.SubscriberConfig{
ProjectID: cfg.PubSubProjectID,
GenerateSubscriptionName: func(topic string) string {
return fmt.Sprintf("%v_%v", topic, params.HandlerName)
},
},
logger,
)
},
Marshaler: cqrs.JSONMarshaler{},
Logger: logger,
},
)
if err != nil {
return Routers{}, err
}
err = eventProcessor.AddHandlers(
cqrs.NewEventHandler(
"UpdateViews",
func(ctx context.Context, event *PostViewed) error {
var views int
var reactions map[string]int
err = repo.UpdatePost(ctx, event.PostID, func(post *Post) {
post.Views++
views = post.Views
reactions = post.Reactions
})
if err != nil {
return err
}
statsUpdated := PostStatsUpdated{
PostID: event.PostID,
ViewsUpdated: true,
Views: views,
Reactions: reactions,
}
return eventBus.Publish(ctx, statsUpdated)
},
),
cqrs.NewEventHandler(
"UpdateReactions",
func(ctx context.Context, event *PostReactionAdded) error {
var views int
var reactions map[string]int
err := repo.UpdatePost(ctx, event.PostID, func(post *Post) {
post.Reactions[event.ReactionID]++
views = post.Views
reactions = post.Reactions
})
if err != nil {
return err
}
statsUpdated := PostStatsUpdated{
PostID: event.PostID,
Views: views,
ReactionUpdated: &event.ReactionID,
Reactions: reactions,
}
return eventBus.Publish(ctx, statsUpdated)
},
),
)
if err != nil {
return Routers{}, err
}
sseSubscriber, err := googlecloud.NewSubscriber(
googlecloud.SubscriberConfig{
ProjectID: cfg.PubSubProjectID,
GenerateSubscriptionName: func(topic string) string {
return fmt.Sprintf("%v_%v", topic, watermill.NewShortUUID())
},
GenerateSubscription: func(params googlecloud.GenerateSubscriptionParams) *pubsubpb.Subscription {
return &pubsubpb.Subscription{
ExpirationPolicy: &pubsubpb.ExpirationPolicy{
Ttl: durationpb.New(time.Hour * 24),
},
}
},
},
logger,
)
if err != nil {
return Routers{}, err
}
sseRouter, err := http.NewSSERouter(
http.SSERouterConfig{
UpstreamSubscriber: sseSubscriber,
Marshaler: http.StringSSEMarshaler{},
},
logger,
)
if err != nil {
return Routers{}, err
}
return Routers{
EventsRouter: eventsRouter,
SSERouter: sseRouter,
EventBus: eventBus,
}, nil
}