mirror of
https://github.com/SamyRai/tercul-backend.git
synced 2025-12-27 00:31:35 +00:00
This commit implements a robust, production-ready analytics system using an event-driven architecture with Redis and `asynq`. Key changes: - Event-Driven Architecture: Instead of synchronous database updates, analytics events (e.g., views, likes, comments) are now published to a Redis queue. This improves API response times and decouples the analytics system from the main application flow. - Background Worker: A new worker process (`cmd/worker`) has been created to consume events from the queue and update the analytics counters in the database. - View Counting: Implemented the missing view counting feature for both works and translations. - New Analytics Query: Added a `popularTranslations` GraphQL query to demonstrate how to use the collected analytics data. - Testing: Added unit tests for the new event publisher and integration tests for the analytics worker. Known Issue: The integration tests for the analytics worker (`AnalyticsWorkerSuite`) and the GraphQL API (`GraphQLIntegrationSuite`) are currently failing due to the lack of a Redis service in the test environment. The tests are written and are expected to pass in an environment where Redis is available on `localhost:6379`, as configured in the CI pipeline.
61 lines
1.7 KiB
Go
61 lines
1.7 KiB
Go
package analytics
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"tercul/internal/app/analytics"
|
|
|
|
"github.com/hibiken/asynq"
|
|
)
|
|
|
|
type Worker struct {
|
|
analyticsService analytics.Service
|
|
}
|
|
|
|
func NewWorker(analyticsService analytics.Service) *Worker {
|
|
return &Worker{analyticsService: analyticsService}
|
|
}
|
|
|
|
func (w *Worker) ProcessTask(ctx context.Context, t *asynq.Task) error {
|
|
var event analytics.AnalyticsEvent
|
|
if err := json.Unmarshal(t.Payload(), &event); err != nil {
|
|
return fmt.Errorf("failed to unmarshal analytics event: %w", err)
|
|
}
|
|
|
|
switch event.EventType {
|
|
case analytics.EventTypeWorkViewed:
|
|
if event.WorkID != nil {
|
|
return w.analyticsService.IncrementWorkViews(ctx, *event.WorkID)
|
|
}
|
|
case analytics.EventTypeWorkLiked:
|
|
if event.WorkID != nil {
|
|
return w.analyticsService.IncrementWorkLikes(ctx, *event.WorkID)
|
|
}
|
|
case analytics.EventTypeWorkCommented:
|
|
if event.WorkID != nil {
|
|
return w.analyticsService.IncrementWorkComments(ctx, *event.WorkID)
|
|
}
|
|
case analytics.EventTypeWorkBookmarked:
|
|
if event.WorkID != nil {
|
|
return w.analyticsService.IncrementWorkBookmarks(ctx, *event.WorkID)
|
|
}
|
|
case analytics.EventTypeTranslationViewed:
|
|
if event.TranslationID != nil {
|
|
return w.analyticsService.IncrementTranslationViews(ctx, *event.TranslationID)
|
|
}
|
|
case analytics.EventTypeTranslationLiked:
|
|
if event.TranslationID != nil {
|
|
return w.analyticsService.IncrementTranslationLikes(ctx, *event.TranslationID)
|
|
}
|
|
case analytics.EventTypeTranslationCommented:
|
|
if event.TranslationID != nil {
|
|
return w.analyticsService.IncrementTranslationComments(ctx, *event.TranslationID)
|
|
}
|
|
default:
|
|
return fmt.Errorf("unknown analytics event type: %s", event.EventType)
|
|
}
|
|
|
|
return nil
|
|
}
|