forgejo-tickets/cmd/server/main.go

106 lines
3.0 KiB
Go

package main
import (
"context"
"net/http"
"os"
"os/signal"
"strings"
"syscall"
"time"
"github.com/gin-gonic/gin"
"github.com/mattnite/forgejo-tickets/internal/auth"
"github.com/mattnite/forgejo-tickets/internal/config"
"github.com/mattnite/forgejo-tickets/internal/database"
"github.com/mattnite/forgejo-tickets/internal/email"
"github.com/mattnite/forgejo-tickets/internal/forgejo"
publichandlers "github.com/mattnite/forgejo-tickets/internal/handlers/public"
"github.com/mattnite/forgejo-tickets/internal/templates"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
func main() {
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
gin.SetMode(gin.ReleaseMode)
cfg, err := config.Load()
if err != nil {
log.Fatal().Msgf("failed to load config: %v", err)
}
db, err := database.Connect(cfg.DatabaseURL)
if err != nil {
log.Fatal().Msgf("failed to connect to database: %v", err)
}
if err := database.RunMigrations(db); err != nil {
log.Fatal().Msgf("failed to run migrations: %v", err)
}
renderer, err := templates.NewRenderer()
if err != nil {
log.Fatal().Msgf("failed to initialize templates: %v", err)
}
emailClient := email.NewClient(cfg.PostmarkServerToken, cfg.PostmarkFromEmail, cfg.BaseURL)
forgejoClient := forgejo.NewClient(cfg.ForgejoURL, cfg.ForgejoAPIToken)
// Discover the bot's username for comment attribution
if err := forgejoClient.InitBotLogin(); err != nil {
log.Warn().Err(err).Msg("failed to initialize bot login (comment attribution may not work)")
} else {
log.Info().Str("bot_login", forgejoClient.BotLogin).Msg("forgejo bot login initialized")
}
sessionStore := auth.NewPGStore(db, strings.HasPrefix(cfg.BaseURL, "https"), []byte(cfg.SessionSecret))
authService := auth.NewService(db, sessionStore, emailClient)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go sessionStore.Cleanup(ctx, 30*time.Minute)
go authService.CleanupExpiredTokens(ctx, 1*time.Hour)
router := publichandlers.NewRouter(publichandlers.Dependencies{
DB: db,
Renderer: renderer,
Auth: authService,
SessionStore: sessionStore,
EmailClient: emailClient,
ForgejoClient: forgejoClient,
Config: cfg,
})
server := &http.Server{
Addr: cfg.PublicAddr,
Handler: router,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
}
go func() {
log.Info().Msgf("Server listening on %s", cfg.PublicAddr)
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatal().Msgf("server error: %v", err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Info().Msg("Shutting down server...")
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second)
defer shutdownCancel()
if err := server.Shutdown(shutdownCtx); err != nil {
log.Error().Err(err).Msg("server shutdown error")
}
cancel()
log.Info().Msg("Server stopped")
}