tercul-backend/internal/app/server_factory.go
google-labs-jules[bot] f8b3ecb9bd feat: Implement trending works feature
This commit introduces a new trending works feature to the application.

The feature includes:
- A new `Trending` domain model to store ranked works.
- An `UpdateTrending` method in the `AnalyticsService` that calculates a trending score for each work based on views, likes, and comments.
- A background job that runs hourly to update the trending works.
- A new `trendingWorks` query in the GraphQL API to expose the trending works.
- New tests for the trending feature, and fixes for existing tests.

This commit also includes a refactoring of the analytics repository to use a more generic `IncrementWorkCounter` method, and enhancements to the `WorkStats` and `TranslationStats` models with new metrics like `readingTime`, `complexity`, and `sentiment`.
2025-09-07 20:40:35 +00:00

98 lines
2.8 KiB
Go

package app
import (
"tercul/internal/jobs/linguistics"
syncjob "tercul/internal/jobs/sync"
"tercul/internal/jobs/trending"
"tercul/internal/platform/config"
"tercul/internal/platform/log"
"github.com/hibiken/asynq"
)
// ServerFactory handles the creation of HTTP and background job servers
type ServerFactory struct {
appBuilder *ApplicationBuilder
}
// NewServerFactory creates a new ServerFactory
func NewServerFactory(appBuilder *ApplicationBuilder) *ServerFactory {
return &ServerFactory{
appBuilder: appBuilder,
}
}
// CreateBackgroundJobServers creates and configures background job servers
func (f *ServerFactory) CreateBackgroundJobServers() ([]*asynq.Server, error) {
log.LogInfo("Setting up background job servers")
redisOpt := asynq.RedisClientOpt{
Addr: config.Cfg.RedisAddr,
Password: config.Cfg.RedisPassword,
DB: config.Cfg.RedisDB,
}
var servers []*asynq.Server
// Setup data synchronization server
log.LogInfo("Setting up data synchronization server",
log.F("concurrency", config.Cfg.MaxRetries))
syncServer := asynq.NewServer(redisOpt, asynq.Config{Concurrency: config.Cfg.MaxRetries})
// Create sync job instance
syncJobInstance := syncjob.NewSyncJob(
f.appBuilder.GetDB(),
f.appBuilder.GetAsynq(),
)
// Register sync job handlers
syncjob.RegisterQueueHandlers(syncServer, syncJobInstance)
servers = append(servers, syncServer)
// Setup linguistic analysis server
log.LogInfo("Setting up linguistic analysis server",
log.F("concurrency", config.Cfg.MaxRetries))
// Create linguistic sync job
linguisticSyncJob := linguistics.NewLinguisticSyncJob(
f.appBuilder.GetDB(),
f.appBuilder.GetLinguisticsFactory().GetAnalyzer(),
f.appBuilder.GetAsynq(),
)
// Create linguistic server and register handlers
linguisticServer := asynq.NewServer(redisOpt, asynq.Config{Concurrency: config.Cfg.MaxRetries})
// Register linguistic handlers
linguisticMux := asynq.NewServeMux()
linguistics.RegisterLinguisticHandlers(linguisticMux, linguisticSyncJob)
// For now, we'll need to run the server with the mux when it's started
// This is a temporary workaround - in production, you'd want to properly configure the server
servers = append(servers, linguisticServer)
// Setup trending job server
log.LogInfo("Setting up trending job server")
scheduler := asynq.NewScheduler(redisOpt, &asynq.SchedulerOpts{})
task, err := trending.NewUpdateTrendingTask()
if err != nil {
return nil, err
}
if _, err := scheduler.Register("@hourly", task); err != nil {
return nil, err
}
go func() {
if err := scheduler.Run(); err != nil {
log.LogError("could not start scheduler", log.F("error", err))
}
}()
log.LogInfo("Background job servers created successfully",
log.F("serverCount", len(servers)))
return servers, nil
}