ForFarm/backend/internal/cmd/api.go

126 lines
3.9 KiB
Go

// backend/internal/cmd/api.go
package cmd
import (
"context"
"fmt"
"log/slog"
"net/http"
"os"
"time"
"github.com/spf13/cobra"
"github.com/forfarm/backend/internal/api"
"github.com/forfarm/backend/internal/cmdutil"
"github.com/forfarm/backend/internal/config"
"github.com/forfarm/backend/internal/event"
"github.com/forfarm/backend/internal/repository"
"github.com/forfarm/backend/internal/services"
"github.com/forfarm/backend/internal/workers"
)
func APICmd(ctx context.Context) *cobra.Command {
var port int = config.PORT
cmd := &cobra.Command{
Use: "api",
Args: cobra.ExactArgs(0),
Short: "Run RESTful API",
RunE: func(cmd *cobra.Command, args []string) error {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
pool, err := cmdutil.NewDatabasePool(ctx, 16)
if err != nil {
logger.Error("failed to create database pool", "error", err)
return err
}
defer pool.Close()
logger.Info("connected to database")
// --- Event Bus ---
eventBus, err := event.NewRabbitMQEventBus(config.RABBITMQ_URL, logger)
if err != nil {
logger.Error("failed to connect to event bus", "url", config.RABBITMQ_URL, "error", err)
return fmt.Errorf("event bus connection failed: %w", err)
}
defer eventBus.Close()
logger.Info("connected to event bus", "url", config.RABBITMQ_URL)
logger.Info("starting AnalyticService worker for farm-crop analytics")
analyticService := services.NewAnalyticsService()
analyticsRepo := repository.NewPostgresFarmAnalyticsRepository(pool, logger, analyticService)
farmRepo := repository.NewPostgresFarm(pool)
farmRepo.SetEventPublisher(eventBus)
inventoryRepo := repository.NewPostgresInventory(pool, eventBus)
croplandRepo := repository.NewPostgresCropland(pool)
croplandRepo.SetEventPublisher(eventBus)
projection := event.NewFarmAnalyticsProjection(eventBus, analyticsRepo, logger)
go func() {
if err := projection.Start(ctx); err != nil {
logger.Error("FarmAnalyticsProjection failed to start listening", "error", err)
}
}()
logger.Info("Farm Analytics Projection started")
apiInstance := api.NewAPI(ctx, logger, pool, eventBus, analyticsRepo, inventoryRepo, croplandRepo, farmRepo)
weatherFetcher := apiInstance.GetWeatherFetcher()
weatherInterval, err := time.ParseDuration(config.WEATHER_FETCH_INTERVAL)
if err != nil {
logger.Warn("Invalid WEATHER_FETCH_INTERVAL, using default 15m", "value", config.WEATHER_FETCH_INTERVAL, "error", err)
weatherInterval = 15 * time.Minute
}
weatherUpdater, err := workers.NewWeatherUpdater(farmRepo, weatherFetcher, eventBus, logger, weatherInterval)
if err != nil {
logger.Error("failed to create WeatherUpdater", "error", err)
}
weatherUpdater.Start(ctx)
logger.Info("Weather Updater worker started", "interval", weatherInterval)
server := apiInstance.Server(port)
serverErrChan := make(chan error, 1)
go func() {
logger.Info("starting API server", "port", port)
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
logger.Error("API server failed", "error", err)
serverErrChan <- err
}
close(serverErrChan)
}()
select {
case err := <-serverErrChan:
logger.Error("Server error received, initiating shutdown.", "error", err)
case <-ctx.Done():
logger.Info("Shutdown signal received, initiating graceful shutdown...")
shutdownCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
weatherUpdater.Stop()
if err := server.Shutdown(shutdownCtx); err != nil {
logger.Error("HTTP server graceful shutdown failed", "error", err)
} else {
logger.Info("HTTP server shutdown complete.")
}
}
logger.Info("Application shutdown complete.")
return nil
},
}
// Add flags if needed (e.g., --port)
// cmd.Flags().IntVarP(&port, "port", "p", config.PORT, "Port for the API server")
return cmd
}