fix: restore main.go to correct architecture (was urfave/cli corruption)
This commit is contained in:
+105
-131
@@ -2,170 +2,144 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"flag"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strings"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
|
|
||||||
|
"github.com/vincentc-afk/gitea-notification-hub/internal/config"
|
||||||
"github.com/vincentc-afk/gitea-notification-hub/internal/event"
|
"github.com/vincentc-afk/gitea-notification-hub/internal/event"
|
||||||
"github.com/vincentc-afk/gitea-notification-hub/internal/identity"
|
"github.com/vincentc-afk/gitea-notification-hub/internal/identity"
|
||||||
"github.com/vincentc-afk/gitea-notification-hub/internal/identity/cache"
|
"github.com/vincentc-afk/gitea-notification-hub/internal/identity/cache"
|
||||||
identitygitea "github.com/vincentc-afk/gitea-notification-hub/internal/identity/gitea"
|
"github.com/vincentc-afk/gitea-notification-hub/internal/identity/gitea"
|
||||||
identityslack "github.com/vincentc-afk/gitea-notification-hub/internal/identity/slack"
|
slacknotifier "github.com/vincentc-afk/gitea-notification-hub/internal/notifier/slack"
|
||||||
"github.com/vincentc-afk/gitea-notification-hub/internal/slack"
|
"github.com/vincentc-afk/gitea-notification-hub/internal/server"
|
||||||
"github.com/vincentc-afk/gitea-notification-hub/internal/storage"
|
|
||||||
"github.com/vincentc-afk/gitea-notification-hub/internal/storage/sqlite"
|
"github.com/vincentc-afk/gitea-notification-hub/internal/storage/sqlite"
|
||||||
"github.com/vincentc-afk/gitea-notification-hub/internal/webhook"
|
"github.com/vincentc-afk/gitea-notification-hub/internal/webhook"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app := &cli.App{
|
// Parse flags
|
||||||
Name: "gitea-notification-hub",
|
configPath := flag.String("config", "config/config.yaml", "path to config file")
|
||||||
Usage: "Gitea webhook event processor that routes notifications to Slack",
|
debug := flag.Bool("debug", false, "enable debug logging")
|
||||||
Flags: []cli.Flag{
|
flag.Parse()
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "config",
|
|
||||||
Aliases: []string{"c"},
|
|
||||||
Usage: "Path to config file",
|
|
||||||
Value: "config.yaml",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "debug",
|
|
||||||
Aliases: []string{"d"},
|
|
||||||
Usage: "Enable debug logging",
|
|
||||||
Value: false,
|
|
||||||
},
|
|
||||||
&cli.IntFlag{
|
|
||||||
Name: "port",
|
|
||||||
Usage: "HTTP server port",
|
|
||||||
Value: 8080,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Action: run,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := app.Run(os.Args); err != nil {
|
// Setup logger
|
||||||
// Use default logger since setup might have failed
|
logger := setupLogger(*debug)
|
||||||
fallback := zerolog.New(os.Stderr).With().Timestamp().Logger()
|
|
||||||
fallback.Fatal().Err(err).Msg("application failed to start")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func run(ctx *cli.Context) error {
|
// Load configuration
|
||||||
// --- Logger setup ---
|
cfg, err := config.Load(*configPath)
|
||||||
logLevel := ctx.String("log-level")
|
|
||||||
if logLevel == "" {
|
|
||||||
// Check LOG_LEVEL env var (overrides --debug flag)
|
|
||||||
logLevel = os.Getenv("LOG_LEVEL")
|
|
||||||
}
|
|
||||||
if logLevel == "" && ctx.Bool("debug") {
|
|
||||||
logLevel = "debug"
|
|
||||||
}
|
|
||||||
if logLevel == "" {
|
|
||||||
logLevel = "info"
|
|
||||||
}
|
|
||||||
|
|
||||||
level, err := zerolog.ParseLevel(strings.ToLower(logLevel))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
level = zerolog.InfoLevel
|
logger.Fatal().Err(err).Msg("failed to load configuration")
|
||||||
}
|
}
|
||||||
|
logger.Info().Str("path", *configPath).Msg("configuration loaded")
|
||||||
|
|
||||||
logger := zerolog.New(os.Stdout).
|
ctx := context.Background()
|
||||||
With().
|
|
||||||
Timestamp().
|
|
||||||
Logger().
|
|
||||||
Level(level)
|
|
||||||
|
|
||||||
logger.Info().Str("level", level.String()).Msg("logger initialized")
|
// Initialize storage
|
||||||
|
repo, err := sqlite.New(cfg.Database.DSN)
|
||||||
// --- Create storage ---
|
|
||||||
repo, err := sqlite.NewSQLiteRepository("data/notifications.db", logger)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatal().Err(err).Msg("failed to initialize storage")
|
logger.Fatal().Err(err).Msg("failed to initialize database")
|
||||||
}
|
}
|
||||||
defer repo.Close()
|
defer repo.Close()
|
||||||
|
|
||||||
// --- Create identity resolver ---
|
// Run migrations
|
||||||
giteaEmailLookup := identitygitea.NewEmailLookup(logger)
|
if err := repo.Migrate(ctx); err != nil {
|
||||||
slackLookup := identityslack.NewLookup(logger)
|
logger.Fatal().Err(err).Msg("failed to run migrations")
|
||||||
|
}
|
||||||
|
logger.Info().Msg("database initialized")
|
||||||
|
|
||||||
// Parse manual user mappings
|
// Initialize Gitea API provider for email lookup
|
||||||
manualMappingsRaw := os.Getenv("MANUAL_USER_MAPPINGS")
|
var emailLookup identity.EmailLookup
|
||||||
manualMappings := cache.ParseManualMappings(manualMappingsRaw)
|
if cfg.Identity.Gitea.URL != "" {
|
||||||
|
giteaProvider := gitea.New(&cfg.Identity.Gitea, logger)
|
||||||
|
emailLookup = giteaProvider
|
||||||
|
logger.Info().Str("url", cfg.Identity.Gitea.URL).Msg("Gitea identity provider initialized")
|
||||||
|
} else {
|
||||||
|
logger.Warn().Msg("Gitea URL not configured, email lookup disabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize Slack notifier
|
||||||
|
slackClient := slacknotifier.New(&cfg.Notification.Slack, logger)
|
||||||
|
logger.Info().Msg("Slack notifier initialized")
|
||||||
|
|
||||||
|
// Initialize cached identity resolver with optional manual mappings
|
||||||
|
manualMappings := cache.ParseManualMappings(os.Getenv("MANUAL_USER_MAPPINGS"))
|
||||||
if len(manualMappings) > 0 {
|
if len(manualMappings) > 0 {
|
||||||
logger.Info().Int("count", len(manualMappings)).Msg("loaded manual user mappings")
|
logger.Info().Int("count", len(manualMappings)).Msg("manual user mappings configured")
|
||||||
}
|
for username := range manualMappings {
|
||||||
for giteaUser, slackID := range manualMappings {
|
logger.Debug().Str("username", username).Msg("manual mapping registered")
|
||||||
logger.Debug().
|
}
|
||||||
Str("gitea_user", giteaUser).
|
|
||||||
Str("slack_id", slackID).
|
|
||||||
Msg("manual mapping entry")
|
|
||||||
}
|
}
|
||||||
|
resolver := cache.NewCachedResolver(repo, emailLookup, slackClient, manualMappings, logger)
|
||||||
|
logger.Info().Msg("identity resolver initialized")
|
||||||
|
|
||||||
resolver := cache.NewCachedResolver(repo, giteaEmailLookup, slackLookup, manualMappings, logger)
|
// Create processor adapter that implements webhook.EventHandler
|
||||||
|
processor := event.NewProcessor(cfg, &resolverAdapter{resolver}, slackClient, logger)
|
||||||
|
logger.Info().Msg("event processor initialized")
|
||||||
|
|
||||||
// --- Create Slack client ---
|
// Create HTTP server
|
||||||
slackClient, err := slack.NewClient(logger)
|
srv := server.New(cfg, logger)
|
||||||
if err != nil {
|
|
||||||
logger.Fatal().Err(err).Msg("failed to create Slack client")
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Create event processor ---
|
// Register webhook handler
|
||||||
processor := event.NewProcessor(resolver, slackClient, logger)
|
webhookHandler := webhook.NewHandler(cfg.Server.WebhookSecret, processor, logger)
|
||||||
|
srv.Router().Post("/webhook", webhookHandler.ServeHTTP)
|
||||||
// --- Create webhook handler ---
|
logger.Info().Msg("webhook handler registered at POST /webhook")
|
||||||
handler := webhook.NewHandler(processor, logger)
|
|
||||||
|
|
||||||
// --- Setup HTTP routes ---
|
|
||||||
mux := setupRouter(handler, logger)
|
|
||||||
|
|
||||||
// --- Start HTTP server ---
|
|
||||||
port := ctx.Int("port")
|
|
||||||
srv := startServer(mux, port, logger)
|
|
||||||
|
|
||||||
// --- Wait for shutdown signal ---
|
|
||||||
sigCh := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
|
||||||
sig := <-sigCh
|
|
||||||
logger.Info().Str("signal", sig.String()).Msg("received shutdown signal")
|
|
||||||
|
|
||||||
// --- Graceful shutdown ---
|
|
||||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10_000)
|
|
||||||
defer cancel()
|
|
||||||
if err := srv.Shutdown(shutdownCtx); err != nil {
|
|
||||||
logger.Error().Err(err).Msg("server shutdown error")
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info().Msg("server stopped")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// setupRouter configures the HTTP router with all routes
|
|
||||||
func setupRouter(handler *webhook.Handler, logger zerolog.Logger) *webhook.Router {
|
|
||||||
router := webhook.NewRouter(logger)
|
|
||||||
|
|
||||||
// Health check
|
|
||||||
router.Get("/health", handler.HealthCheck)
|
|
||||||
|
|
||||||
// Webhook endpoint
|
|
||||||
router.Post("/webhook", handler.HandleWebhook)
|
|
||||||
|
|
||||||
return router
|
|
||||||
}
|
|
||||||
|
|
||||||
// startServer starts the HTTP server on the given port
|
|
||||||
func startServer(router *webhook.Router, port int, logger zerolog.Logger) *webhook.Server {
|
|
||||||
srv := webhook.NewServer(router, port, logger)
|
|
||||||
|
|
||||||
|
// Graceful shutdown
|
||||||
go func() {
|
go func() {
|
||||||
logger.Info().Int("port", port).Msg("starting HTTP server")
|
sigCh := make(chan os.Signal, 1)
|
||||||
if err := srv.Start(); err != nil {
|
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
||||||
logger.Fatal().Err(err).Msg("server error")
|
<-sigCh
|
||||||
|
|
||||||
|
logger.Info().Msg("received shutdown signal")
|
||||||
|
|
||||||
|
shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := srv.Shutdown(shutdownCtx); err != nil {
|
||||||
|
logger.Error().Err(err).Msg("shutdown error")
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return srv
|
// Start server
|
||||||
|
logger.Info().Int("port", cfg.Server.Port).Msg("starting server")
|
||||||
|
if err := srv.Start(); err != nil && err != http.ErrServerClosed {
|
||||||
|
logger.Fatal().Err(err).Msg("server error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupLogger(debug bool) zerolog.Logger {
|
||||||
|
zerolog.TimeFieldFormat = time.RFC3339
|
||||||
|
|
||||||
|
level := zerolog.InfoLevel
|
||||||
|
if debug {
|
||||||
|
level = zerolog.DebugLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
return zerolog.New(zerolog.ConsoleWriter{
|
||||||
|
Out: os.Stdout,
|
||||||
|
TimeFormat: "15:04:05",
|
||||||
|
}).Level(level).With().Timestamp().Caller().Logger()
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolverAdapter adapts cache.CachedResolver to event.IdentityResolver
|
||||||
|
type resolverAdapter struct {
|
||||||
|
resolver *cache.CachedResolver
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *resolverAdapter) Resolve(ctx context.Context, user event.User) (*event.ResolvedIdentity, error) {
|
||||||
|
identity, err := a.resolver.Resolve(ctx, user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &event.ResolvedIdentity{
|
||||||
|
Email: identity.Email,
|
||||||
|
SlackID: identity.SlackID,
|
||||||
|
SlackName: identity.SlackName,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user