Files
Control-plane/cmd/backend/main.go
T
Mephimeow ff355ad1d9
ci / build (push) Successful in 2m42s
ci / build (pull_request) Successful in 2m45s
feat: add API versioning , translate swagger, remove rate limiter
2026-06-14 17:14:37 +00:00

153 lines
4.4 KiB
Go

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/pressly/goose/v3"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
// @title AegisGuard API
// @version 1.0
// @description API системы управления AegisGuard. Позволяет управлять пользователями и организациями.
// @description Все защищённые эндпоинты требуют заголовок `Authorization: Bearer <token>`.
// @description Токен получается при регистрации или входе.
// @schemes http
// @BasePath /api/v1
//
// @securityDefinitions.apikey Bearer
// @in header
// @name Authorization
// @description Введите `Bearer <token>`, где token — access_token из ответа /auth/login или /auth/register
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()
gormDB, err := gorm.Open(postgres.Open(cfg.DatabaseURL), &gorm.Config{})
if err != nil {
log.Fatalf("failed to connect to postgres: %v", err)
}
sqlDB, err := gormDB.DB()
if err != nil {
log.Fatalf("failed to get underlying sql.DB: %v", err)
}
if err := sqlDB.PingContext(ctx); err != nil {
log.Fatalf("failed to ping postgres: %v", err)
}
log.Println("connected to postgres")
if err := goose.Up(sqlDB, "migrations"); err != nil {
log.Fatalf("failed to run migrations: %v", err)
}
log.Println("migrations applied")
repo := auth.NewRepository(gormDB)
orgRepo := org.NewRepository(gormDB)
svc := auth.NewService(repo, cfg.JWTSecret, cfg.JWTExpiration, cfg.JWTRefreshExpiration)
handler := auth.NewHandler(svc)
orgSvc := org.NewService(orgRepo)
orgHandler := org.NewHandler(orgSvc)
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 системы управления AegisGuard. Позволяет управлять пользователями и организациями."
docs.SwaggerInfo.Schemes = []string{"http"}
docs.SwaggerInfo.BasePath = "/api/v1"
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/v1/auth")
{
api.POST("/register", handler.Register)
api.POST("/login", 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/v1/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,
ReadHeaderTimeout: 10 * time.Second,
}
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)
}
_ = sqlDB.Close()
log.Println("server stopped")
}