package main import ( "context" "log" "net/http" "os" "os/signal" "syscall" "time" docs "gitea.d3m0k1d.ru/HellreigN/Control-plane/docs" "gitea.d3m0k1d.ru/HellreigN/Control-plane/internal/auth" "gitea.d3m0k1d.ru/HellreigN/Control-plane/internal/config" "gitea.d3m0k1d.ru/HellreigN/Control-plane/internal/org" "github.com/gin-gonic/gin" "github.com/jackc/pgx/v5/pgxpool" "github.com/jackc/pgx/v5/stdlib" "github.com/pressly/goose/v3" "github.com/swaggo/files" "github.com/swaggo/gin-swagger" ) // @title AegisGuard API // @version 1.0 // @description API for AegisGuard control plane // @schemes http // // @securityDefinitions.apikey Bearer // @in header // @name Authorization // @description Type "Bearer" followed by a space and the JWT token. func main() { cfg, err := config.Load() if err != nil { log.Fatalf("failed to load config: %v", err) } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() pool, err := pgxpool.New(ctx, cfg.DatabaseURL) if err != nil { log.Fatalf("failed to create postgres pool: %v", err) } defer pool.Close() if err := pool.Ping(ctx); err != nil { log.Fatalf("failed to ping postgres: %v", err) } log.Println("connected to postgres") db := stdlib.OpenDBFromPool(pool) defer db.Close() if err := goose.Up(db, "migrations"); err != nil { log.Fatalf("failed to run migrations: %v", err) } log.Println("migrations applied") repo := auth.NewRepository(pool) orgRepo := org.NewRepository(pool) svc := auth.NewService(repo, cfg.JWTSecret, cfg.JWTExpiration, cfg.JWTRefreshExpiration) handler := auth.NewHandler(svc) orgSvc := org.NewService(orgRepo) orgHandler := org.NewHandler(orgSvc) loginLimiter := auth.NewRateLimiter(10, time.Minute) authMW := auth.AuthMiddleware([]byte(cfg.JWTSecret)) go func() { ticker := time.NewTicker(30 * time.Minute) defer ticker.Stop() for range ticker.C { cleanupCtx, cleanupCancel := context.WithTimeout(context.Background(), 30*time.Second) if err := repo.DeleteExpiredRefreshTokens(cleanupCtx); err != nil { log.Printf("failed to cleanup expired tokens: %v", err) } cleanupCancel() } }() gin.SetMode(gin.ReleaseMode) r := gin.New() r.Use(gin.Logger(), gin.Recovery()) docs.SwaggerInfo.Title = "AegisGuard API" docs.SwaggerInfo.Version = "1.0" docs.SwaggerInfo.Description = "API for AegisGuard" docs.SwaggerInfo.Schemes = []string{"http"} r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) r.GET("/health", func(c *gin.Context) { c.JSON(200, gin.H{"status": "ok"}) }) api := r.Group("/api/auth") { api.POST("/register", handler.Register) api.POST("/login", loginLimiter.Middleware(), handler.Login) api.POST("/refresh", handler.Refresh) api.POST("/logout", handler.Logout) api.GET("/me", authMW, handler.Me) api.PUT("/me", authMW, handler.UpdateProfile) api.PUT("/password", authMW, handler.ChangePassword) } orgs := r.Group("/api/organizations", authMW) { orgs.POST("", orgHandler.Create) orgs.GET("", orgHandler.List) orgs.GET("/:id", orgHandler.GetByID) orgs.PUT("/:id", orgHandler.Update) orgs.DELETE("/:id", orgHandler.Delete) } srv := &http.Server{ Addr: ":" + cfg.ServerPort, Handler: r, } go func() { log.Printf("server starting on :%s", cfg.ServerPort) if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("failed to start server: %v", err) } }() quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit log.Println("shutting down server...") shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second) defer shutdownCancel() if err := srv.Shutdown(shutdownCtx); err != nil { log.Fatalf("server forced to shutdown: %v", err) } pool.Close() log.Println("server stopped") }