mirror of
https://github.com/serty2005/rmser.git
synced 2026-02-04 19:02:33 -06:00
243 lines
9.5 KiB
Go
243 lines
9.5 KiB
Go
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/services/auth"
|
||
"rmser/internal/transport/http/middleware"
|
||
"rmser/internal/transport/ws"
|
||
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)
|
||
}
|
||
|
||
// Проверяем, что bot_username задан в конфиге
|
||
if cfg.Telegram.BotUsername == "" {
|
||
log.Fatalf("Telegram.BotUsername не задан в конфиге! Это обязательное поле для авторизации.")
|
||
}
|
||
|
||
// 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. WebSocket сервер для desktop авторизации
|
||
wsServer := ws.NewServer()
|
||
go wsServer.Run()
|
||
|
||
// 8. Сервис авторизации для desktop auth
|
||
authService := auth.NewService(accountRepo, wsServer, cfg.Security.SecretKey)
|
||
|
||
// 9. Handlers
|
||
draftsHandler := handlers.NewDraftsHandler(draftsService, ocrService)
|
||
billingHandler := handlers.NewBillingHandler(billingService)
|
||
ocrHandler := handlers.NewOCRHandler(ocrService)
|
||
photosHandler := handlers.NewPhotosHandler(photosService)
|
||
recommendHandler := handlers.NewRecommendationsHandler(recService)
|
||
settingsHandler := handlers.NewSettingsHandler(accountRepo, catalogRepo)
|
||
settingsHandler.SetRMSFactory(rmsFactory)
|
||
invoicesHandler := handlers.NewInvoiceHandler(invoicesService, syncService)
|
||
authHandler := handlers.NewAuthHandler(authService, cfg.Telegram.BotUsername)
|
||
|
||
// 10. Telegram Bot (Передаем syncService и authService)
|
||
if cfg.Telegram.Token != "" {
|
||
bot, err := tgBot.NewBot(cfg.Telegram, ocrService, syncService, billingService, accountRepo, rmsFactory, cryptoManager, draftsService, authService, cfg.App.MaintenanceMode, cfg.App.DevIDs)
|
||
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()
|
||
}
|
||
|
||
// 11. HTTP Server
|
||
if cfg.App.Mode == "release" {
|
||
gin.SetMode(gin.ReleaseMode)
|
||
}
|
||
r := gin.Default()
|
||
|
||
// Регистрируем WebSocket хендлер
|
||
r.GET("/socket.io/", wsServer.HandleConnections)
|
||
|
||
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")
|
||
|
||
// Хендлер инициализации desktop авторизации (без middleware)
|
||
api.POST("/auth/init-desktop", authHandler.InitDesktopAuth)
|
||
|
||
api.Use(middleware.AuthMiddleware(accountRepo, cfg.Telegram.Token, cfg.Security.SecretKey, 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)
|
||
api.POST("/drafts/upload", draftsHandler.Upload)
|
||
// 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/reorder", draftsHandler.ReorderItems)
|
||
api.POST("/drafts/:id/commit", draftsHandler.CommitDraft)
|
||
api.POST("/drafts/container", draftsHandler.AddContainer)
|
||
|
||
// Settings
|
||
api.GET("/settings", settingsHandler.GetSettings)
|
||
api.POST("/settings", settingsHandler.UpdateSettings)
|
||
// User Servers
|
||
api.GET("/user/servers", settingsHandler.GetUserServers)
|
||
api.POST("/user/servers/active", settingsHandler.SwitchActiveServer)
|
||
// 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))
|
||
}
|
||
}
|