Files
rmser/cmd/main.go

216 lines
8.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"log"
"os"
"time"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"go.uber.org/zap"
"rmser/config"
"rmser/internal/infrastructure/db"
"rmser/internal/infrastructure/ocr_client"
"rmser/internal/infrastructure/yookassa"
"rmser/internal/transport/http/middleware"
tgBot "rmser/internal/transport/telegram"
// Repositories
accountPkg "rmser/internal/infrastructure/repository/account"
billingPkg "rmser/internal/infrastructure/repository/billing"
catalogPkg "rmser/internal/infrastructure/repository/catalog"
draftsPkg "rmser/internal/infrastructure/repository/drafts"
invoicesPkg "rmser/internal/infrastructure/repository/invoices"
ocrRepoPkg "rmser/internal/infrastructure/repository/ocr"
opsRepoPkg "rmser/internal/infrastructure/repository/operations"
photosPkg "rmser/internal/infrastructure/repository/photos"
recipesPkg "rmser/internal/infrastructure/repository/recipes"
recRepoPkg "rmser/internal/infrastructure/repository/recommendations"
suppliersPkg "rmser/internal/infrastructure/repository/suppliers"
"rmser/internal/infrastructure/rms"
// Services
billingServicePkg "rmser/internal/services/billing"
draftsServicePkg "rmser/internal/services/drafts"
invoicesServicePkg "rmser/internal/services/invoices"
ocrServicePkg "rmser/internal/services/ocr"
photosServicePkg "rmser/internal/services/photos"
recServicePkg "rmser/internal/services/recommend"
"rmser/internal/services/sync"
// Handlers
"rmser/internal/transport/http/handlers"
"rmser/pkg/crypto"
"rmser/pkg/logger"
)
func main() {
// 1. Config
cfg, err := config.LoadConfig(".")
if err != nil {
log.Fatalf("Ошибка загрузки конфига: %v", err)
}
// 2. Logger
logger.Init(cfg.App.Mode)
defer logger.Log.Sync()
if err := os.MkdirAll(cfg.App.StoragePath, 0755); err != nil {
logger.Log.Fatal("Не удалось создать директорию для загрузок", zap.Error(err), zap.String("path", cfg.App.StoragePath))
}
logger.Log.Info("Запуск приложения rmser", zap.String("mode", cfg.App.Mode))
// 3. Crypto & DB
if cfg.Security.SecretKey == "" {
logger.Log.Fatal("Security.SecretKey не задан в конфиге!")
}
cryptoManager := crypto.NewCryptoManager(cfg.Security.SecretKey)
database := db.NewPostgresDB(cfg.DB.DSN)
// 4. Repositories
accountRepo := accountPkg.NewRepository(database)
billingRepo := billingPkg.NewRepository(database)
catalogRepo := catalogPkg.NewRepository(database)
recipesRepo := recipesPkg.NewRepository(database)
invoicesRepo := invoicesPkg.NewRepository(database)
opsRepo := opsRepoPkg.NewRepository(database)
recRepo := recRepoPkg.NewRepository(database)
ocrRepo := ocrRepoPkg.NewRepository(database)
photosRepo := photosPkg.NewRepository(database)
draftsRepo := draftsPkg.NewRepository(database)
supplierRepo := suppliersPkg.NewRepository(database)
// 5. RMS Factory
rmsFactory := rms.NewFactory(accountRepo, cryptoManager)
// 6. Services
pyClient := ocr_client.NewClient(cfg.OCR.ServiceURL)
ykClient := yookassa.NewClient(cfg.YooKassa.ShopID, cfg.YooKassa.SecretKey)
billingService := billingServicePkg.NewService(billingRepo, accountRepo, ykClient)
syncService := sync.NewService(rmsFactory, accountRepo, catalogRepo, recipesRepo, invoicesRepo, opsRepo, supplierRepo)
recService := recServicePkg.NewService(recRepo)
ocrService := ocrServicePkg.NewService(ocrRepo, catalogRepo, draftsRepo, accountRepo, photosRepo, pyClient, cfg.App.StoragePath)
// Устанавливаем DevIDs для OCR сервиса
ocrService.SetDevIDs(cfg.App.DevIDs)
draftsService := draftsServicePkg.NewService(draftsRepo, ocrRepo, catalogRepo, accountRepo, supplierRepo, photosRepo, invoicesRepo, rmsFactory, billingService)
invoicesService := invoicesServicePkg.NewService(invoicesRepo, draftsRepo, supplierRepo, rmsFactory)
photosService := photosServicePkg.NewService(photosRepo, draftsRepo, accountRepo)
// 7. Handlers
draftsHandler := handlers.NewDraftsHandler(draftsService)
billingHandler := handlers.NewBillingHandler(billingService)
ocrHandler := handlers.NewOCRHandler(ocrService)
photosHandler := handlers.NewPhotosHandler(photosService)
recommendHandler := handlers.NewRecommendationsHandler(recService)
settingsHandler := handlers.NewSettingsHandler(accountRepo, catalogRepo)
invoicesHandler := handlers.NewInvoiceHandler(invoicesService, syncService)
// 8. Telegram Bot (Передаем syncService)
if cfg.Telegram.Token != "" {
bot, err := tgBot.NewBot(cfg.Telegram, ocrService, syncService, billingService, accountRepo, rmsFactory, cryptoManager)
if err != nil {
logger.Log.Fatal("Ошибка создания Telegram бота", zap.Error(err))
}
billingService.SetNotifier(bot)
settingsHandler.SetNotifier(bot)
// Устанавливаем нотификатор для OCR сервиса
ocrService.SetNotifier(bot)
go bot.Start()
defer bot.Stop()
}
// 9. HTTP Server
if cfg.App.Mode == "release" {
gin.SetMode(gin.ReleaseMode)
}
r := gin.Default()
r.POST("/api/webhooks/yookassa", billingHandler.YooKassaWebhook)
corsConfig := cors.DefaultConfig()
corsConfig.AllowAllOrigins = true
corsConfig.AllowMethods = []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}
corsConfig.AllowHeaders = []string{"Origin", "Content-Length", "Content-Type", "Authorization", "X-Telegram-User-ID"}
r.Use(cors.New(corsConfig))
// --- STATIC FILES SERVING ---
// Раздаем папку uploads по урлу /api/uploads
r.Static("/api/uploads", cfg.App.StoragePath)
api := r.Group("/api")
api.Use(middleware.AuthMiddleware(accountRepo, cfg.Telegram.Token, cfg.App.MaintenanceMode, cfg.App.DevIDs))
{
// Drafts & Invoices
api.GET("/drafts", draftsHandler.GetDrafts)
api.GET("/drafts/:id", draftsHandler.GetDraft)
api.DELETE("/drafts/:id", draftsHandler.DeleteDraft)
// Items CRUD
api.POST("/drafts/:id/items", draftsHandler.AddDraftItem)
api.DELETE("/drafts/:id/items/:itemId", draftsHandler.DeleteDraftItem)
api.PATCH("/drafts/:id/items/:itemId", draftsHandler.UpdateItem)
api.POST("/drafts/:id/commit", draftsHandler.CommitDraft)
api.POST("/drafts/container", draftsHandler.AddContainer)
// Settings
api.GET("/settings", settingsHandler.GetSettings)
api.POST("/settings", settingsHandler.UpdateSettings)
// Photos Storage
api.GET("/photos", photosHandler.GetPhotos)
api.DELETE("/photos/:id", photosHandler.DeletePhoto)
// User Management
api.GET("/settings/users", settingsHandler.GetServerUsers)
api.PATCH("/settings/users/:userId", settingsHandler.UpdateUserRole)
api.DELETE("/settings/users/:userId", settingsHandler.RemoveUser)
// Dictionaries
api.GET("/dictionaries", draftsHandler.GetDictionaries)
api.GET("/dictionaries/groups", settingsHandler.GetGroupsTree)
api.GET("/dictionaries/stores", draftsHandler.GetStores)
// Recommendations
api.GET("/recommendations", recommendHandler.GetRecommendations)
// OCR & Matching
api.GET("/ocr/catalog", ocrHandler.GetCatalog)
api.GET("/ocr/matches", ocrHandler.GetMatches)
api.POST("/ocr/match", ocrHandler.SaveMatch)
api.DELETE("/ocr/match", ocrHandler.DeleteMatch)
api.GET("/ocr/unmatched", ocrHandler.GetUnmatched)
api.DELETE("/ocr/unmatched", ocrHandler.DiscardUnmatched)
api.GET("/ocr/search", ocrHandler.SearchProducts)
// Invoices
api.GET("/invoices/:id", invoicesHandler.GetInvoice)
api.POST("/invoices/sync", invoicesHandler.SyncInvoices)
// Manual Sync Trigger
api.POST("/sync/all", func(c *gin.Context) {
userID := c.MustGet("userID").(uuid.UUID)
force := c.Query("force") == "true"
// Запускаем в горутине, чтобы не держать соединение
go func() {
if err := syncService.SyncAllData(userID, force); err != nil {
logger.Log.Error("Manual sync failed", zap.Error(err))
}
}()
c.JSON(200, gin.H{"status": "sync_started", "message": "Синхронизация запущена в фоне"})
})
}
r.GET("/health", func(c *gin.Context) {
c.JSON(200, gin.H{"status": "ok", "time": time.Now().Format(time.RFC3339)})
})
logger.Log.Info("Сервер запускается", zap.String("port", cfg.App.Port))
if err := r.Run(":" + cfg.App.Port); err != nil {
logger.Log.Fatal("Ошибка запуска сервера", zap.Error(err))
}
}