Files
efir-api-server/cmd/server/main.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")
}