From 96fb4724973fe10a90d97e8846603491b5cde2f7 Mon Sep 17 00:00:00 2001 From: Vladimir Zagainov Date: Wed, 18 Jun 2025 18:18:16 +0300 Subject: [PATCH] feat(modpack): modpack updates --- cmd/server/main.go | 48 ++++++++++++------- internal/api/modpack_handler.go | 9 +++- internal/core/file_janitor.go | 61 +++++++++++++++++++++++++ internal/database/modpack_repository.go | 22 ++++++++- 4 files changed, 121 insertions(+), 19 deletions(-) create mode 100644 internal/core/file_janitor.go diff --git a/cmd/server/main.go b/cmd/server/main.go index 8903df3..230860c 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -15,26 +15,18 @@ import ( ) func main() { - // Инициализируем соединение с БД dbPool := database.Connect() defer dbPool.Close() + // --- Инициализация репозиториев --- userRepo := &database.UserRepository{DB: dbPool} serverRepo := &database.ServerRepository{DB: dbPool} modpackRepo := &database.ModpackRepository{DB: dbPool} - serverPoller := &core.ServerPoller{Repo: serverRepo} - - modpackHandler := &api.ModpackHandler{ModpackRepo: modpackRepo} - launcherHandler := &api.LauncherHandler{ModpackRepo: modpackRepo} - adminUserHandler := &api.AdminUserHandler{UserRepo: userRepo} - - // Запускаем поллер в фоновой горутине - go serverPoller.Start(context.Background()) - - // Сервисы + // --- Инициализация сервисов --- userService := &core.UserService{Repo: userRepo} authService := &core.AuthService{UserRepo: userRepo} + serverPoller := &core.ServerPoller{Repo: serverRepo} keyPath := os.Getenv("RSA_PRIVATE_KEY_PATH") if keyPath == "" { @@ -48,12 +40,29 @@ func main() { if err != nil { log.Fatalf("Failed to create profile service: %v", err) } - // Хендлеры + + modpacksStoragePath := os.Getenv("MODPACKS_STORAGE_PATH") + if modpacksStoragePath == "" { + log.Fatal("MODPACKS_STORAGE_PATH environment variable is not set") + } + janitorService := core.NewFileJanitorService(modpackRepo, modpacksStoragePath) + + // --- Запуск фоновых задач --- + go serverPoller.Start(context.Background()) + + // --- Инициализация хендлеров --- 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, + JanitorService: janitorService, + } + adminUserHandler := &api.AdminUserHandler{UserRepo: userRepo} // Этот хендлер мы создали для админских функций + + // --- Настройка роутера --- r := chi.NewRouter() r.Use(middleware.Logger) r.Use(middleware.Recoverer) @@ -63,6 +72,10 @@ func main() { 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.Route("/authserver", func(r chi.Router) { r.Post("/authenticate", authHandler.Authenticate) @@ -71,23 +84,24 @@ func main() { r.Post("/join", authHandler.Join) r.Get("/profile/{uuid}", profileHandler.GetProfile) }) - r.Route("/launcher", func(r chi.Router) { - r.Get("/modpacks/{name}/manifest", launcherHandler.GetModpackManifest) - }) // --- Защищенные роуты --- r.Group(func(r chi.Router) { - r.Use(api.AuthMiddleware) // TODO: Заменить на AdminMiddleware + r.Use(api.AuthMiddleware) r.Route("/api/user", func(r chi.Router) { r.Post("/skin", profileHandler.UploadSkin) }) + r.Route("/api/admin", func(r chi.Router) { r.Use(api.AdminMiddleware) + r.Route("/modpacks", func(r chi.Router) { r.Post("/import", modpackHandler.ImportModpack) }) + r.Route("/users", func(r chi.Router) { + // ИСПРАВЛЕНО: Используем adminUserHandler r.Get("/", adminUserHandler.GetAllUsers) r.Patch("/{id}/role", adminUserHandler.UpdateUserRole) }) diff --git a/internal/api/modpack_handler.go b/internal/api/modpack_handler.go index 768cc74..2b30393 100644 --- a/internal/api/modpack_handler.go +++ b/internal/api/modpack_handler.go @@ -1,20 +1,24 @@ package api import ( + "context" "fmt" "io" "net/http" "os" + "gitea.mrixs.me/minecraft-platform/backend/internal/core" "gitea.mrixs.me/minecraft-platform/backend/internal/core/importer" "gitea.mrixs.me/minecraft-platform/backend/internal/database" "gitea.mrixs.me/minecraft-platform/backend/internal/models" ) type ModpackHandler struct { - ModpackRepo *database.ModpackRepository + ModpackRepo *database.ModpackRepository + JanitorService *core.FileJanitorService } +// ImportModpack обрабатывает загрузку и импорт модпака. func (h *ModpackHandler) ImportModpack(w http.ResponseWriter, r *http.Request) { if err := r.ParseMultipartForm(512 << 20); err != nil { // 512 MB лимит http.Error(w, "File too large", http.StatusBadRequest) @@ -65,4 +69,7 @@ func (h *ModpackHandler) ImportModpack(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusCreated) fmt.Fprintf(w, "Modpack '%s' imported successfully with %d files.", modpack.DisplayName, len(files)) + + // Запускаем очистку в фоне, чтобы не блокировать ответ + go h.JanitorService.CleanOrphanedFiles(context.Background()) } diff --git a/internal/core/file_janitor.go b/internal/core/file_janitor.go new file mode 100644 index 0000000..9263b15 --- /dev/null +++ b/internal/core/file_janitor.go @@ -0,0 +1,61 @@ +package core + +import ( + "context" + "log" + "os" + "path/filepath" + + "gitea.mrixs.me/minecraft-platform/backend/internal/database" +) + +// FileJanitorService отвечает за очистку осиротевших файлов. +type FileJanitorService struct { + ModpackRepo *database.ModpackRepository + StoragePath string +} + +// NewFileJanitorService создает новый экземпляр сервиса-уборщика. +func NewFileJanitorService(repo *database.ModpackRepository, storagePath string) *FileJanitorService { + return &FileJanitorService{ + ModpackRepo: repo, + StoragePath: storagePath, + } +} + +// CleanOrphanedFiles сканирует хранилище и удаляет файлы, отсутствующие в БД. +func (s *FileJanitorService) CleanOrphanedFiles(ctx context.Context) { + log.Println("Janitor: Starting orphaned file cleanup...") + + liveHashes, err := s.ModpackRepo.GetAllFileHashes(ctx) + if err != nil { + log.Printf("Janitor: Error getting live hashes from DB: %v", err) + return + } + + diskFiles, err := os.ReadDir(s.StoragePath) + if err != nil { + log.Printf("Janitor: Error reading storage directory %s: %v", s.StoragePath, err) + return + } + + removedCount := 0 + for _, file := range diskFiles { + if file.IsDir() { + continue + } + + fileName := file.Name() + if _, isLive := liveHashes[fileName]; !isLive { + filePath := filepath.Join(s.StoragePath, fileName) + if err := os.Remove(filePath); err != nil { + log.Printf("Janitor: Failed to remove orphaned file %s: %v", filePath, err) + } else { + log.Printf("Janitor: Removed orphaned file %s", filePath) + removedCount++ + } + } + } + + log.Printf("Janitor: Cleanup complete. Removed %d orphaned files.", removedCount) +} diff --git a/internal/database/modpack_repository.go b/internal/database/modpack_repository.go index 8da2ce4..730227c 100644 --- a/internal/database/modpack_repository.go +++ b/internal/database/modpack_repository.go @@ -1,4 +1,3 @@ -// File: backend/internal/database/modpack_repository.go package database import ( @@ -81,3 +80,24 @@ func (r *ModpackRepository) GetModpackManifest(ctx context.Context, modpackName return manifest, nil } + +// GetAllFileHashes извлекает все уникальные хеши файлов из базы данных. +func (r *ModpackRepository) GetAllFileHashes(ctx context.Context) (map[string]struct{}, error) { + query := "SELECT DISTINCT file_hash FROM modpack_files" + rows, err := r.DB.Query(ctx, query) + if err != nil { + return nil, err + } + defer rows.Close() + + hashes := make(map[string]struct{}) + for rows.Next() { + var hash string + if err := rows.Scan(&hash); err != nil { + return nil, err + } + hashes[hash] = struct{}{} + } + + return hashes, rows.Err() +}