162 lines
5.4 KiB
Go
162 lines
5.4 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
"log/slog"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"backend/internal/modules/auth"
|
|
"backend/internal/modules/categories"
|
|
"backend/internal/modules/media"
|
|
"backend/internal/modules/orders"
|
|
"backend/internal/modules/pricing"
|
|
"backend/internal/modules/products"
|
|
"backend/internal/modules/site"
|
|
"backend/internal/modules/telegram"
|
|
"backend/internal/modules/units"
|
|
"backend/internal/modules/users"
|
|
"backend/internal/platform/config"
|
|
"backend/internal/platform/db"
|
|
"backend/internal/platform/logger"
|
|
appmiddleware "backend/internal/platform/middleware"
|
|
"backend/internal/platform/redis"
|
|
)
|
|
|
|
func main() {
|
|
cfg, err := config.Load()
|
|
if err != nil {
|
|
log.Fatalf("load config: %v", err)
|
|
}
|
|
|
|
appLogger := logger.New(cfg.Server.GinMode == "debug")
|
|
slog.SetDefault(appLogger)
|
|
|
|
database, err := db.Connect(cfg.Database.URL, cfg.Server.GinMode == "debug")
|
|
if err != nil {
|
|
log.Fatalf("connect database: %v", err)
|
|
}
|
|
|
|
redisClient, err := redis.Connect(cfg.Redis.URL)
|
|
if err != nil {
|
|
log.Fatalf("connect redis: %v", err)
|
|
}
|
|
|
|
// --- Users module ---
|
|
usersRepo := users.NewRepository(database)
|
|
usersService := users.NewService(usersRepo)
|
|
usersHandler := users.NewHandler(usersService)
|
|
|
|
// --- Auth module (admin space mounted; customer space wired but not mounted) ---
|
|
refreshStore := auth.NewRefreshStore(redisClient)
|
|
authService := auth.NewService(usersService, refreshStore, cfg.JWT.Secret, cfg.JWT.AccessTTL, cfg.JWT.RefreshTTL)
|
|
adminAuthHandler := auth.NewAdminHandler(authService, cfg.Server.CookieSecure)
|
|
|
|
// --- Site module ---
|
|
siteRepo := site.NewRepository(database)
|
|
siteService := site.NewService(siteRepo)
|
|
siteHandler := site.NewHandler(siteService)
|
|
|
|
// --- Media module (pluggable local/S3 storage) ---
|
|
mediaStorage, err := buildMediaStorage(cfg.Media)
|
|
if err != nil {
|
|
log.Fatalf("init media storage: %v", err)
|
|
}
|
|
mediaRepo := media.NewRepository(database)
|
|
mediaService := media.NewService(mediaRepo, mediaStorage, cfg.Media.MaxUploadMB*1024*1024)
|
|
mediaHandler := media.NewHandler(mediaService)
|
|
|
|
// --- Categories module ---
|
|
categoriesRepo := categories.NewRepository(database)
|
|
categoriesService := categories.NewService(categoriesRepo)
|
|
categoriesHandler := categories.NewHandler(categoriesService)
|
|
|
|
// --- Units module ---
|
|
unitsRepo := units.NewRepository(database)
|
|
unitsService := units.NewService(unitsRepo)
|
|
unitsHandler := units.NewHandler(unitsService)
|
|
|
|
// --- Products module ---
|
|
productsRepo := products.NewRepository(database)
|
|
productsService := products.NewService(productsRepo)
|
|
productsHandler := products.NewHandler(productsService)
|
|
|
|
// --- Pricing module (quantity-based tiers, spec section 16) ---
|
|
pricingRepo := pricing.NewRepository(database)
|
|
pricingService := pricing.NewService(pricingRepo)
|
|
pricingHandler := pricing.NewHandler(pricingService)
|
|
|
|
// --- Telegram module (notifications, spec section 20) ---
|
|
telegramRepo := telegram.NewRepository(database)
|
|
telegramService := telegram.NewService(telegramRepo, telegram.NewHTTPSender(), appLogger)
|
|
telegramHandler := telegram.NewHandler(telegramService)
|
|
|
|
// --- Orders module (depends on products/units/pricing for authoritative
|
|
// pricing, and on telegram as its pluggable OrderNotifier) ---
|
|
ordersRepo := orders.NewRepository(database)
|
|
ordersService := orders.NewService(ordersRepo, productsService, unitsService, pricingService, telegramService)
|
|
ordersHandler := orders.NewHandler(ordersService)
|
|
|
|
requireAdmin := appmiddleware.RequireAdmin(cfg.JWT.Secret)
|
|
loginRateLimit := appmiddleware.NewPerIPRateLimiter(5, 5).Middleware()
|
|
orderRateLimit := appmiddleware.NewPerIPRateLimiter(10, 10).Middleware()
|
|
|
|
gin.SetMode(cfg.Server.GinMode)
|
|
router := gin.New()
|
|
router.Use(gin.Recovery())
|
|
router.Use(gin.Logger())
|
|
router.Use(appmiddleware.CORS(cfg.Server.CORSOrigin))
|
|
|
|
if cfg.Media.Driver == "local" {
|
|
router.Static("/uploads", cfg.Media.LocalDir)
|
|
}
|
|
|
|
router.GET("/health", func(c *gin.Context) {
|
|
c.JSON(200, gin.H{"status": "ok"})
|
|
})
|
|
|
|
api := router.Group("/api")
|
|
|
|
auth.RegisterAdminRoutes(api, adminAuthHandler, requireAdmin, loginRateLimit)
|
|
users.RegisterAdminRoutes(api, usersHandler, requireAdmin)
|
|
site.RegisterRoutes(api, siteHandler, requireAdmin)
|
|
media.RegisterAdminRoutes(api, mediaHandler, requireAdmin)
|
|
|
|
categories.RegisterAdminRoutes(api, categoriesHandler, requireAdmin)
|
|
categories.RegisterPublicRoutes(api, categoriesHandler)
|
|
|
|
units.RegisterAdminRoutes(api, unitsHandler, requireAdmin)
|
|
|
|
products.RegisterAdminRoutes(api, productsHandler, requireAdmin)
|
|
products.RegisterPublicRoutes(api, productsHandler)
|
|
|
|
pricing.RegisterAdminRoutes(api, pricingHandler, requireAdmin)
|
|
pricing.RegisterPublicRoutes(api, pricingHandler)
|
|
|
|
telegram.RegisterAdminRoutes(api, telegramHandler, requireAdmin)
|
|
|
|
orders.RegisterPublicRoutes(api, ordersHandler, orderRateLimit)
|
|
orders.RegisterAdminRoutes(api, ordersHandler, requireAdmin)
|
|
|
|
appLogger.Info("starting server", "port", cfg.Server.Port)
|
|
if err := router.Run(":" + cfg.Server.Port); err != nil {
|
|
log.Fatalf("server error: %v", err)
|
|
}
|
|
}
|
|
|
|
func buildMediaStorage(cfg config.MediaConfig) (media.Storage, error) {
|
|
if cfg.Driver == "s3" {
|
|
return media.NewS3Storage(context.Background(), media.S3Config{
|
|
Bucket: cfg.S3Bucket,
|
|
Region: cfg.S3Region,
|
|
Endpoint: cfg.S3Endpoint,
|
|
AccessKeyID: cfg.S3AccessKeyID,
|
|
SecretKey: cfg.S3SecretKey,
|
|
UsePathStyle: cfg.S3UsePathStyle,
|
|
PublicBaseURL: cfg.S3PublicBaseURL,
|
|
})
|
|
}
|
|
return media.NewLocalStorage(cfg.LocalDir, cfg.LocalBaseURL)
|
|
}
|