Files
2026-10-17 16:00:21 +07:00

112 lines
3.9 KiB
Go

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)
}
})
}