|
- package server
-
- import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "log/slog"
- "net/http"
- "os"
- "sync"
-
- "github.com/AFASystems/presence/internal/pkg/apiclient"
- "github.com/AFASystems/presence/internal/pkg/common/appcontext"
- "github.com/AFASystems/presence/internal/pkg/config"
- "github.com/AFASystems/presence/internal/pkg/database"
- "github.com/AFASystems/presence/internal/pkg/kafkaclient"
- "github.com/AFASystems/presence/internal/pkg/logger"
- "github.com/AFASystems/presence/internal/pkg/model"
- "github.com/AFASystems/presence/internal/pkg/service"
- "gorm.io/gorm"
- )
-
- // ServerApp holds dependencies and state for the server service.
- type ServerApp struct {
- Cfg *config.Config
- DB *gorm.DB
- KafkaManager *kafkaclient.KafkaManager
- AppState *appcontext.AppState
- ChLoc chan model.HTTPLocation
- ChEvents chan model.BeaconEvent
- ctx context.Context
- Server *http.Server
- Cleanup func()
- wg sync.WaitGroup
- }
-
- // New creates a ServerApp: loads config, creates logger, connects DB, creates Kafka manager and writers.
- // Caller must call Init(ctx) then Run(ctx) then Shutdown().
- func New(cfg *config.Config) (*ServerApp, error) {
- srvLogger, cleanup := logger.CreateLogger("server.log")
- slog.SetDefault(srvLogger)
-
- db, err := database.Connect(cfg)
- if err != nil {
- cleanup()
- return nil, fmt.Errorf("database: %w", err)
- }
-
- appState := appcontext.NewAppState()
- kafkaManager := kafkaclient.InitKafkaManager()
-
- writerTopics := []string{"apibeacons", "alert", "mqtt", "settings", "parser"}
- kafkaManager.PopulateKafkaManager(cfg.KafkaURL, "", writerTopics)
- slog.Info("Kafka writers initialized", "topics", writerTopics)
-
- return &ServerApp{
- Cfg: cfg,
- DB: db,
- KafkaManager: kafkaManager,
- AppState: appState,
- Cleanup: cleanup,
- }, nil
- }
-
- // Init loads config from file, seeds DB, runs UpdateDB, adds Kafka readers and starts consumers.
- func (a *ServerApp) Init(ctx context.Context) error {
- a.ctx = ctx
-
- configFile, err := os.Open(a.Cfg.ConfigPath)
- if err != nil {
- return fmt.Errorf("config file: %w", err)
- }
- defer configFile.Close()
-
- b, err := io.ReadAll(configFile)
- if err != nil {
- return fmt.Errorf("read config: %w", err)
- }
-
- var configs []model.Config
- if err := json.Unmarshal(b, &configs); err != nil {
- return fmt.Errorf("unmarshal config: %w", err)
- }
-
- for _, c := range configs {
- a.DB.Create(&c)
- }
- a.DB.Find(&configs)
- for _, c := range configs {
- kp := model.KafkaParser{ID: "add", Config: c}
- if err := service.SendParserConfig(kp, a.KafkaManager.GetWriter("parser"), ctx); err != nil {
- slog.Error("sending parser config to kafka", "err", err, "name", c.Name)
- }
- }
-
- if err := apiclient.UpdateDB(a.DB, ctx, a.Cfg, a.KafkaManager.GetWriter("apibeacons"), a.AppState); err != nil {
- slog.Error("UpdateDB", "err", err)
- }
-
- readerTopics := []string{"locevents", "alertbeacons"}
- a.KafkaManager.PopulateKafkaManager(a.Cfg.KafkaURL, "server", readerTopics)
- slog.Info("Kafka readers initialized", "topics", readerTopics)
-
- a.ChLoc = make(chan model.HTTPLocation, config.SMALL_CHANNEL_SIZE)
- a.ChEvents = make(chan model.BeaconEvent, config.MEDIUM_CHANNEL_SIZE)
-
- a.wg.Add(2)
- go kafkaclient.Consume(a.KafkaManager.GetReader("locevents"), a.ChLoc, ctx, &a.wg)
- go kafkaclient.Consume(a.KafkaManager.GetReader("alertbeacons"), a.ChEvents, ctx, &a.wg)
-
- a.Server = &http.Server{
- Addr: a.Cfg.HTTPAddr,
- Handler: a.RegisterRoutes(),
- }
- return nil
- }
-
- // Run starts the HTTP server and runs the event loop until ctx is cancelled.
- func (a *ServerApp) Run(ctx context.Context) {
- go func() {
- if err := a.Server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
- slog.Error("HTTP server", "err", err)
- }
- }()
- RunEventLoop(ctx, a)
- }
-
- // Shutdown stops the HTTP server, waits for consumers, and cleans up Kafka and logger.
- func (a *ServerApp) Shutdown() {
- if a.Server != nil {
- if err := a.Server.Shutdown(context.Background()); err != nil {
- slog.Error("server shutdown", "err", err)
- }
- slog.Info("HTTP server stopped")
- }
- a.wg.Wait()
- slog.Info("Kafka consumers stopped")
- a.KafkaManager.CleanKafkaReaders()
- a.KafkaManager.CleanKafkaWriters()
- if a.Cleanup != nil {
- a.Cleanup()
- }
- slog.Info("server shutdown complete")
- }
|