package app import ( "context" "io" "log" "net/http" "strconv" "strings" "evening_detective_server/internal/modules/processor_jwt" "evening_detective_server/internal/modules/roles" "evening_detective_server/internal/services/game_export_service" "evening_detective_server/proto" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) func canExportGame(claims *processor_jwt.JWTClaims) bool { return claims != nil && (roles.HasRole(claims, roles.Organizer) || claims.HasRole(roles.Developer)) } func (s *server) StartGameExport(ctx context.Context, req *proto.StartGameExportReq) (*proto.GameExportRsp, error) { claims, err := claimsFromContext(ctx) if err != nil { return nil, err } if !canExportGame(claims) { return nil, status.Error(codes.PermissionDenied, "permission denied") } result, err := s.gameExportService.Start(ctx, int(req.GetID()), claims.UserID) if err != nil { return nil, gameExportErrorStatus(err) } return &proto.GameExportRsp{Export: mapGameExport(result)}, nil } func (s *server) GetGameExport(ctx context.Context, req *proto.GetGameExportReq) (*proto.GameExportRsp, error) { claims, err := claimsFromContext(ctx) if err != nil { return nil, err } if !canExportGame(claims) { return nil, status.Error(codes.PermissionDenied, "permission denied") } result, err := s.gameExportService.Get(ctx, int(req.GetID())) if err != nil { return nil, gameExportErrorStatus(err) } return &proto.GameExportRsp{Export: mapGameExport(result)}, nil } func (s *server) ResetGameLocalMode(ctx context.Context, req *proto.ResetGameLocalModeReq) (*proto.ResetGameLocalModeRsp, error) { claims, err := claimsFromContext(ctx) if err != nil { return nil, err } if !canExportGame(claims) { return nil, status.Error(codes.PermissionDenied, "permission denied") } if err := s.gameExportService.ResetLocalMode(ctx, int(req.GetID()), req.GetExportID()); err != nil { return nil, gameExportErrorStatus(err) } return &proto.ResetGameLocalModeRsp{}, nil } // GameExportDownloadHandler authenticates with the existing JWT processor and streams private ZIPs. func GameExportDownloadHandler(jwt processor_jwt.IProcessorJWT, exports *game_export_service.GameExportService) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Cache-Control", "no-store") fail := func(err error) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(runtime.HTTPStatusFromCode(status.Code(err))) // The message has already been reduced to a safe transport error. _, _ = w.Write([]byte(`{"error":` + strconv.Quote(status.Convert(err).Message()) + `}`)) } token := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ") if token == "" { fail(status.Error(codes.Unauthenticated, "Требуется авторизация")) return } claims, err := jwt.GetClaims(token) if err != nil { fail(status.Error(codes.Unauthenticated, "Недействительный токен")) return } if !canExportGame(claims) { fail(status.Error(codes.PermissionDenied, "permission denied")) return } gameID, err := strconv.ParseInt(r.PathValue("id"), 10, 32) if err != nil || gameID <= 0 { fail(status.Error(codes.InvalidArgument, "Некорректный идентификатор игры")) return } result, body, err := exports.OpenDownload(r.Context(), int(gameID)) if err != nil { fail(gameExportErrorStatus(err)) return } defer func() { _ = body.Close() }() w.Header().Set("Content-Type", "application/zip") w.Header().Set("Content-Disposition", `attachment; filename="game-`+result.PackageID+`.zip"`) w.Header().Set("Content-Length", strconv.FormatInt(result.Size, 10)) w.Header().Set("X-Content-Type-Options", "nosniff") if _, err := io.CopyBuffer(w, body, make([]byte, 32<<10)); err != nil && r.Context().Err() == nil { log.Printf("game export download: %v", err) } }) }