package main import ( "context" "fmt" "log" "os" "os/signal" "syscall" "time" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/cors" "github.com/gofiber/fiber/v2/middleware/logger" "github.com/gofiber/fiber/v2/middleware/recover" "github.com/buhapp/backend/internal/audit" "github.com/buhapp/backend/internal/auth" "github.com/buhapp/backend/internal/config" "github.com/buhapp/backend/internal/consent" "github.com/buhapp/backend/internal/db" "github.com/buhapp/backend/internal/handlers" "github.com/buhapp/backend/internal/redis" "github.com/buhapp/backend/internal/storage" "github.com/buhapp/backend/internal/users" ) func main() { cfg, err := config.Load() if err != nil { log.Fatalf("config: %v", err) } ctx := context.Background() // PostgreSQL pg, err := db.New(ctx, cfg.PostgresDSN()) if err != nil { log.Fatalf("postgres: %v", err) } defer pg.Close() log.Println("postgres: connected") // Run migrations migDir := os.Getenv("MIGRATIONS_DIR") if migDir == "" { migDir = "./migrations" } if err := db.RunMigrations(ctx, pg, migDir); err != nil { log.Fatalf("migrations: %v", err) } // Redis rdb, err := redis.New(ctx, cfg.RedisAddr(), cfg.RedisPassword, cfg.RedisDB) if err != nil { log.Printf("redis warning: %v", err) } else { log.Println("redis: connected") } // MinIO st, err := storage.New(ctx, cfg.MinIOEndpoint, cfg.MinIOAccessKey, cfg.MinIOSecretKey, cfg.MinIOBucket, cfg.MinIOUseSSL) if err != nil { log.Printf("minio warning: %v", err) } else { log.Println("minio: connected") } // Services usersRepo := users.NewRepo(pg.Pool) consentRepo := consent.NewRepo(pg.Pool) auditRepo := audit.NewRepo(pg.Pool) authSvc := auth.NewService(cfg.JWTSecret, cfg.JWTAccessTTL, cfg.JWTRefreshTTL) authH := handlers.NewAuthHandler(cfg, usersRepo, consentRepo, auditRepo, authSvc) // Fiber app app := fiber.New(fiber.Config{ AppName: "buhapp-api", ReadTimeout: 15 * time.Second, WriteTimeout: 15 * time.Second, }) app.Use(recover.New()) app.Use(logger.New()) app.Use(cors.New(cors.Config{ AllowOrigins: "*", AllowHeaders: "Origin, Content-Type, Accept, Authorization", AllowMethods: "GET, POST, PUT, DELETE, OPTIONS", })) // Health app.Get("/health", func(c *fiber.Ctx) error { return c.JSON(fiber.Map{ "status": "ok", "time": time.Now().UTC().Format(time.RFC3339), "version": "0.1.0", "db": pg.Pool != nil, "redis": rdb != nil, "storage": st != nil, }) }) // Legal — публичные версии документов app.Get("/api/v1/legal/terms", func(c *fiber.Ctx) error { return c.JSON(fiber.Map{"version": cfg.TermsVersion, "url": "/legal/TERMS.md"}) }) app.Get("/api/v1/legal/privacy", func(c *fiber.Ctx) error { return c.JSON(fiber.Map{"version": cfg.PrivacyVersion, "url": "/legal/PRIVACY.md"}) }) app.Get("/api/v1/legal/disclaimer", func(c *fiber.Ctx) error { return c.JSON(fiber.Map{"version": cfg.DisclaimerVersion, "url": "/legal/DISCLAIMER.md"}) }) // Auth (public) api := app.Group("/api/v1") api.Post("/auth/register", authH.Register) api.Post("/auth/login", authH.Login) // Auth (protected) protected := api.Group("", auth.Middleware(authSvc)) protected.Get("/me", authH.Me) // Graceful shutdown go func() { sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) <-sigCh log.Println("shutdown...") _ = app.ShutdownWithTimeout(10 * time.Second) }() addr := fmt.Sprintf(":%d", cfg.AppPort) log.Printf("listening on %s", addr) if err := app.Listen(addr); err != nil { log.Fatalf("listen: %v", err) } }