package main import ( "context" "errors" "log/slog" "net/http" "os" "os/signal" "time" "git.amiirkhl.ir/starcoach-hackathon/zarinpal-analytics-backend/internal/analytics" "git.amiirkhl.ir/starcoach-hackathon/zarinpal-analytics-backend/internal/api" "git.amiirkhl.ir/starcoach-hackathon/zarinpal-analytics-backend/internal/config" "git.amiirkhl.ir/starcoach-hackathon/zarinpal-analytics-backend/internal/database" "git.amiirkhl.ir/starcoach-hackathon/zarinpal-analytics-backend/internal/growthreport" "git.amiirkhl.ir/starcoach-hackathon/zarinpal-analytics-backend/internal/identity" "github.com/redis/go-redis/v9" ) func main() { logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) cfg, err := config.Load() if err != nil { logger.Error("configuration failed", "error", err) os.Exit(1) } if err := cfg.ValidateAPI(); err != nil { logger.Error("api configuration failed", "error", err) os.Exit(1) } ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) defer cancel() pool, err := database.Open(ctx, cfg.DatabaseURL) if err != nil { logger.Error("database connection failed", "error", err) os.Exit(1) } defer pool.Close() redisClient := redis.NewClient(&redis.Options{Addr: cfg.RedisAddr, Password: cfg.RedisPassword}) defer redisClient.Close() repository := growthreport.NewRepository(pool) coordinator := growthreport.NewCoordinator(redisClient, growthreport.CoordinatorConfig{CreatePerMinute: cfg.AICreatePerMinute, UncachedPerDay: cfg.AIUncachedScopesPerDay, DailyRequests: cfg.AIDailyRequestBudget, DailyTokens: cfg.AIDailyTokenBudget, MaxConcurrent: cfg.AIMaxConcurrent}) growthService := growthreport.NewService(repository, coordinator, growthreport.ServiceConfig{Enabled: cfg.AIGrowthEnabled, Model: cfg.AvalAIModel, PromptVersion: "1.0.0", SchemaVersion: "1.0.0"}) handler := api.NewServer(identity.New(pool), analytics.New(pool, cfg.CursorSecret), logger, cfg.FrontendOrigin, cfg.CookieSecure, growthService).Handler() server := &http.Server{Addr: cfg.HTTPAddr, Handler: handler, ReadHeaderTimeout: 5 * time.Second, ReadTimeout: 15 * time.Second, WriteTimeout: 65 * time.Second, IdleTimeout: 60 * time.Second} go func() { <-ctx.Done() shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second) defer shutdownCancel() _ = server.Shutdown(shutdownCtx) }() logger.Info("api listening", "address", cfg.HTTPAddr) if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { logger.Error("api failed", "error", err) os.Exit(1) } }