183 lines
5.8 KiB
Go
183 lines
5.8 KiB
Go
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")
|
|
} |