package main import ( "context" "errors" "log/slog" "net/http" "os" "os/signal" "syscall" "time" "gitea.meghsakha.com/platform/tenant-registry/internal/config" "gitea.meghsakha.com/platform/tenant-registry/internal/keycloak" "gitea.meghsakha.com/platform/tenant-registry/internal/server" "gitea.meghsakha.com/platform/tenant-registry/internal/store" ) func main() { logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})) slog.SetDefault(logger) cfg, err := config.Load() if err != nil { slog.Error("config load failed", "err", err) os.Exit(1) } bootCtx, cancelBoot := context.WithTimeout(context.Background(), 15*time.Second) defer cancelBoot() var s store.Store if cfg.DatabaseURL == "" { slog.Warn("DATABASE_URL not set — running with in-memory store (dev only)") s = store.NewMemory() } else { pg, err := store.NewPostgres(bootCtx, cfg.DatabaseURL) if err != nil { slog.Error("postgres connect failed", "err", err) os.Exit(1) } s = pg } defer s.Close() var kc keycloak.Adapter if cfg.KeycloakAdminURL != "" && cfg.KeycloakClientID != "" { kc = keycloak.NewHTTPAdapter(keycloak.HTTPConfig{ BaseURL: cfg.KeycloakAdminURL, Realm: cfg.KeycloakRealm, ClientID: cfg.KeycloakClientID, ClientSecret: cfg.KeycloakClientSecret, Timeout: cfg.KeycloakTimeout, }) slog.Info("keycloak adapter configured", "url", cfg.KeycloakAdminURL, "realm", cfg.KeycloakRealm, "client_id", cfg.KeycloakClientID) } else { slog.Warn("KEYCLOAK_ADMIN_URL not set — using mock adapter (dev only; no real KC writes)") kc = keycloak.NewMock() } handler := server.NewRouter(&server.Server{Cfg: cfg, Log: logger, Store: s, Keycloak: kc}) srv := &http.Server{ Addr: cfg.Addr, Handler: handler, ReadTimeout: 10 * time.Second, WriteTimeout: 30 * time.Second, IdleTimeout: 60 * time.Second, } ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() go func() { slog.Info("tenant-registry listening", "addr", cfg.Addr, "env", cfg.Env, "store", storeKind(s)) if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { slog.Error("server crashed", "err", err) os.Exit(1) } }() <-ctx.Done() slog.Info("shutdown requested") shutdownCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() if err := srv.Shutdown(shutdownCtx); err != nil { slog.Error("shutdown failed", "err", err) os.Exit(1) } slog.Info("bye") } func storeKind(s store.Store) string { switch s.(type) { case *store.Memory: return "memory" case *store.Postgres: return "postgres" default: return "unknown" } }