194 lines
5.9 KiB
Go
194 lines
5.9 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
"gitea.mrixs.me/minecraft-platform/backend/internal/api"
|
|
"gitea.mrixs.me/minecraft-platform/backend/internal/core"
|
|
"gitea.mrixs.me/minecraft-platform/backend/internal/database"
|
|
"gitea.mrixs.me/minecraft-platform/backend/internal/ws"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/go-chi/chi/v5/middleware"
|
|
"github.com/go-chi/httprate"
|
|
)
|
|
|
|
func main() {
|
|
// --- Инициализация логгера (slog) ---
|
|
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
|
|
slog.SetDefault(logger)
|
|
|
|
slog.Info("Starting backend server initialization...")
|
|
|
|
dbPool := database.Connect()
|
|
defer dbPool.Close()
|
|
|
|
// --- Инициализация репозиториев ---
|
|
userRepo := &database.UserRepository{DB: dbPool}
|
|
serverRepo := &database.ServerRepository{DB: dbPool}
|
|
modpackRepo := &database.ModpackRepository{DB: dbPool}
|
|
jobRepo := &database.JobRepository{DB: dbPool}
|
|
|
|
// --- Инициализация сервисов ---
|
|
userService := &core.UserService{Repo: userRepo}
|
|
authService := &core.AuthService{UserRepo: userRepo}
|
|
serverPoller := &core.ServerPoller{Repo: serverRepo}
|
|
|
|
// --- Инициализация WebSocket Hub ---
|
|
hub := ws.NewHub()
|
|
go hub.Run()
|
|
|
|
keyPath := os.Getenv("RSA_PRIVATE_KEY_PATH")
|
|
if keyPath == "" {
|
|
slog.Error("RSA_PRIVATE_KEY_PATH environment variable is not set")
|
|
os.Exit(1)
|
|
}
|
|
domain := os.Getenv("APP_DOMAIN")
|
|
if domain == "" {
|
|
slog.Error("APP_DOMAIN environment variable is not set")
|
|
os.Exit(1)
|
|
}
|
|
profileService, err := core.NewProfileService(userRepo, keyPath, domain)
|
|
if err != nil {
|
|
slog.Error("Failed to create profile service", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
modpacksStoragePath := os.Getenv("MODPACKS_STORAGE_PATH")
|
|
if modpacksStoragePath == "" {
|
|
slog.Error("MODPACKS_STORAGE_PATH environment variable is not set")
|
|
os.Exit(1)
|
|
}
|
|
janitorService := core.NewFileJanitorService(modpackRepo, modpacksStoragePath)
|
|
|
|
// --- Запуск фоновых задач ---
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
go serverPoller.Start(ctx) // Передаем контекст для отмены
|
|
|
|
// --- Инициализация хендлеров ---
|
|
userHandler := &api.UserHandler{Service: userService}
|
|
authHandler := &api.AuthHandler{Service: authService}
|
|
profileHandler := &api.ProfileHandler{Service: profileService}
|
|
serverHandler := &api.ServerHandler{Repo: serverRepo}
|
|
launcherHandler := &api.LauncherHandler{ModpackRepo: modpackRepo}
|
|
modpackHandler := &api.ModpackHandler{
|
|
ModpackRepo: modpackRepo,
|
|
JobRepo: jobRepo,
|
|
JanitorService: janitorService,
|
|
Hub: hub,
|
|
}
|
|
adminUserHandler := &api.AdminUserHandler{UserRepo: userRepo}
|
|
|
|
// --- Настройка роутера ---
|
|
r := chi.NewRouter()
|
|
r.Use(middleware.Logger) // Можно заменить на slog middleware, но пока оставим standard
|
|
r.Use(middleware.Recoverer)
|
|
|
|
// Health Check
|
|
r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte("OK"))
|
|
})
|
|
|
|
// --- Публичные роуты ---
|
|
r.Route("/api", func(r chi.Router) {
|
|
// Rate limiting: 100 requests per minute for general API
|
|
r.Use(httprate.LimitByIP(100, time.Minute))
|
|
|
|
// Auth endpoints: stricter limit (10 requests per minute)
|
|
r.Group(func(r chi.Router) {
|
|
r.Use(httprate.LimitByIP(10, time.Minute))
|
|
r.Post("/register", userHandler.Register)
|
|
r.Post("/login", authHandler.Login)
|
|
})
|
|
|
|
r.Get("/servers", serverHandler.GetServers)
|
|
|
|
r.Route("/launcher", func(r chi.Router) {
|
|
r.Get("/modpacks/{name}/manifest", launcherHandler.GetModpackManifest)
|
|
r.Get("/modpacks/summary", launcherHandler.GetModpacksSummary)
|
|
})
|
|
})
|
|
r.Route("/authserver", func(r chi.Router) {
|
|
// Stricter rate limit for auth server (10 req/min)
|
|
r.Use(httprate.LimitByIP(10, time.Minute))
|
|
r.Post("/authenticate", authHandler.Authenticate)
|
|
})
|
|
r.Route("/sessionserver/session/minecraft", func(r chi.Router) {
|
|
// Rate limit for session endpoints (60 req/min)
|
|
r.Use(httprate.LimitByIP(60, time.Minute))
|
|
r.Post("/join", authHandler.Join)
|
|
r.Get("/profile/{uuid}", profileHandler.GetProfile)
|
|
})
|
|
|
|
// --- Защищенные роуты ---
|
|
r.Group(func(r chi.Router) {
|
|
r.Use(api.AuthMiddleware)
|
|
|
|
r.Route("/api/user", func(r chi.Router) {
|
|
r.Post("/skin", profileHandler.UploadSkin)
|
|
r.Get("/me", userHandler.GetMe)
|
|
})
|
|
|
|
r.Route("/api/admin", func(r chi.Router) {
|
|
r.Use(api.AdminMiddleware)
|
|
|
|
// WebSocket endpoint for jobs
|
|
r.Get("/ws/jobs", func(w http.ResponseWriter, r *http.Request) {
|
|
ws.ServeWs(hub, w, r)
|
|
})
|
|
|
|
r.Route("/modpacks", func(r chi.Router) {
|
|
r.Get("/", modpackHandler.GetModpacks)
|
|
r.Post("/import", modpackHandler.ImportModpack)
|
|
r.Post("/update", modpackHandler.UpdateModpack)
|
|
r.Get("/versions", modpackHandler.GetModpackVersions)
|
|
})
|
|
|
|
r.Route("/users", func(r chi.Router) {
|
|
r.Get("/", adminUserHandler.GetAllUsers)
|
|
r.Patch("/{id}/role", adminUserHandler.UpdateUserRole)
|
|
})
|
|
})
|
|
})
|
|
|
|
// --- Graceful Shutdown ---
|
|
srv := &http.Server{
|
|
Addr: ":8080",
|
|
Handler: r,
|
|
}
|
|
|
|
go func() {
|
|
slog.Info("Starting backend server on :8080")
|
|
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
slog.Error("Failed to start server", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
}()
|
|
|
|
// Wait for interrupt signal to gracefully shutdown the server with a timeout.
|
|
quit := make(chan os.Signal, 1)
|
|
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
|
<-quit
|
|
slog.Info("Shutting down server...")
|
|
|
|
// The context is used to inform the server it has 5 seconds to finish
|
|
// the request it is currently handling
|
|
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer shutdownCancel()
|
|
|
|
if err := srv.Shutdown(shutdownCtx); err != nil {
|
|
slog.Error("Server forced to shutdown", "error", err)
|
|
}
|
|
|
|
slog.Info("Server exiting")
|
|
}
|