buhapp-backend/cmd/server/main.go
ga 8d754f9834 Sprint 1: scaffold backend (Go + Fiber + PG + Redis + MinIO)
- POST /api/v1/auth/register with mandatory consents
- POST /api/v1/auth/login
- GET  /api/v1/me (protected)
- GET  /api/v1/legal/{terms,privacy,disclaimer}
- Migrations for users, consent_log, audit_log
- bcrypt + JWT (access + refresh)
- Docker Compose stack
2026-08-20 15:41:05 +00:00

138 lines
3.6 KiB
Go

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)
}
}