package main import ( "context" "net/http" "os" "os/signal" "syscall" "time" "runtime" "github.com/go-chi/chi/v5" "github.com/rs/cors" "messenger/internal/api/handlers" "messenger/internal/api/middleware" "messenger/internal/config" "messenger/internal/crypto" "messenger/internal/pkg/logger" "messenger/internal/repository/sqlite" "messenger/internal/service" "messenger/internal/websocket" ) func main() { // Загружаем конфиг cfg := config.Load() // Инициализируем логгер logger.Init(cfg.Environment) // Подключаемся к БД db, err := sqlite.NewDB(cfg.DBPath) if err != nil { logger.Error("Failed to connect to database", "error", err) os.Exit(1) } defer db.Close() // Инициализируем шифрование encryptor, err := crypto.NewEncryptor(cfg.EncryptionKey) if err != nil { logger.Error("Failed to initialize encryption", "error", err) os.Exit(1) } // Создаем репозитории userRepo := sqlite.NewUserRepository(db) profileRepo := sqlite.NewProfileRepository(db) chatRepo := sqlite.NewChatRepository(db) messageRepo := sqlite.NewMessageRepository(db) attachmentRepo := sqlite.NewAttachmentRepository(db) // Создаем сервисы authService := service.NewAuthService(userRepo, profileRepo, cfg.JWTSecret, cfg.JWTExpiryHours) userService := service.NewUserService(userRepo, profileRepo) chatService := service.NewChatService(chatRepo, userRepo, messageRepo) messageService := service.NewMessageService(messageRepo, chatRepo, userRepo, attachmentRepo, encryptor) fileService := service.NewFileService(attachmentRepo, chatRepo, cfg.StoragePath, cfg.MaxFileSizeMB) adminService := service.NewAdminService(userRepo, chatRepo, messageRepo, fileService) // Создаем WebSocket Hub wsHub := websocket.NewHub(messageService, chatService) go wsHub.Run() // Создаем хендлеры authHandler := handlers.NewAuthHandler(authService) userHandler := handlers.NewUserHandler(userService) chatHandler := handlers.NewChatHandler(chatService, userService) messageHandler := handlers.NewMessageHandler(messageService, chatService) fileHandler := handlers.NewFileHandler(fileService) adminHandler := handlers.NewAdminHandler(adminService) wsHandler := handlers.NewWebSocketHandler(wsHub, authService) // Создаем основной роутер для API apiRouter := chi.NewRouter() // Middleware для API (кроме WebSocket) apiRouter.Use(middleware.Recovery) apiRouter.Use(middleware.Logging) apiRouter.Use(cors.New(cors.Options{ AllowedOrigins: cfg.CORSAllowedOrigins, AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, AllowedHeaders: []string{"Content-Type", "Authorization"}, AllowCredentials: true, }).Handler) // Публичные роуты API apiRouter.Post("/api/register", authHandler.Register) apiRouter.Post("/api/login", authHandler.Login) apiRouter.Get("/health", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(`{"status":"ok"}`)) }) // Защищенные API роуты apiRouter.Group(func(r chi.Router) { r.Use(middleware.JWTAuth(authService)) r.Get("/api/me", authHandler.GetMe) r.Get("/api/profile", userHandler.GetProfile) r.Put("/api/profile", userHandler.UpdateProfile) r.Get("/api/users", userHandler.SearchUsers) r.Post("/api/chats/private", chatHandler.CreatePrivateChat) r.Post("/api/chats/group", chatHandler.CreateGroupChat) r.Get("/api/chats", chatHandler.GetMyChats) r.Get("/api/chats/{id}", chatHandler.GetChatByID) r.Get("/api/chats/{id}/members", chatHandler.GetChatMembers) r.Post("/api/chats/{id}/members", chatHandler.AddMembers) r.Delete("/api/chats/{id}/members/{user_id}", chatHandler.RemoveMember) r.Put("/api/chats/{id}/title", chatHandler.UpdateChatTitle) r.Get("/api/chats/{id}/messages", messageHandler.GetMessages) r.Put("/api/messages/{id}/read", messageHandler.MarkAsRead) r.Post("/api/chats/{id}/upload", fileHandler.UploadFile) r.Get("/api/attachments/{id}", fileHandler.DownloadFile) r.Group(func(r chi.Router) { r.Use(middleware.RequireGlobalAdmin) r.Delete("/api/admin/users/{id}", adminHandler.DeleteUser) r.Delete("/api/admin/messages/{id}", adminHandler.DeleteMessage) }) }) // Создаем отдельный роутер для WebSocket (без middleware) wsRouter := http.NewServeMux() wsRouter.HandleFunc("/ws", wsHandler.HandleWebSocket) // Объединяем роутеры mainRouter := http.NewServeMux() mainRouter.Handle("/", apiRouter) mainRouter.Handle("/ws", wsRouter) // Запускаем сервер srv := &http.Server{ Addr: ":" + cfg.ServerPort, Handler: mainRouter, ReadTimeout: 15 * time.Second, WriteTimeout: 15 * time.Second, IdleTimeout: 60 * time.Second, } // Graceful shutdown go func() { logger.Info("Server starting", "port", cfg.ServerPort) if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { logger.Error("Server failed", "error", err) os.Exit(1) } }() // Запускаем периодический сборщик мусора для оптимизации памяти go func() { ticker := time.NewTicker(5 * time.Minute) defer ticker.Stop() for range ticker.C { runtime.GC() logger.Debug("Garbage collection triggered") } }() // Ожидаем сигнал завершения quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit logger.Info("Shutting down server...") ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() wsHub.Stop() if err := srv.Shutdown(ctx); err != nil { logger.Error("Server forced to shutdown", "error", err) } logger.Info("Server stopped") }