diff --git a/internal/app/answers.go b/internal/app/answers.go new file mode 100644 index 0000000..8d79b80 --- /dev/null +++ b/internal/app/answers.go @@ -0,0 +1,72 @@ +package app + +import ( + "context" + + "evening_detective_server/internal/modules/processor_jwt" + "evening_detective_server/internal/modules/roles" + proto "evening_detective_server/proto" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func (s *server) GetTeamAnswers(ctx context.Context, req *proto.GetTeamAnswersReq) (*proto.GetTeamAnswersRsp, error) { + answers, err := s.gameService.GetTeamAnswers( + ctx, + int(req.Id), + req.Password, + ) + if err != nil { + return &proto.GetTeamAnswersRsp{ + Error: err.Error(), + }, nil + } + + return &proto.GetTeamAnswersRsp{ + Answers: mapAnswers(answers), + }, nil +} + +func (s *server) SubmitTeamAnswers(ctx context.Context, req *proto.SubmitTeamAnswersReq) (*proto.SubmitTeamAnswersRsp, error) { + err := s.gameService.SubmitTeamAnswers( + ctx, + int(req.Id), + req.Password, + convertAnswers(req.Answers), + ) + if err != nil { + return &proto.SubmitTeamAnswersRsp{ + Error: err.Error(), + }, nil + } + + return &proto.SubmitTeamAnswersRsp{}, nil +} + +func (s *server) SetTeamAnswerScore(ctx context.Context, req *proto.SetTeamAnswerScoreReq) (*proto.SetTeamAnswerScoreRsp, error) { + claims := ctx.Value("claims").(*processor_jwt.JWTClaims) + if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + var score *int + if req.Score != nil { + v := int(req.GetScore()) + score = &v + } + + err := s.gameService.SetTeamAnswerScore( + ctx, + int(req.Id), + req.QuestionCode, + score, + ) + if err != nil { + return &proto.SetTeamAnswerScoreRsp{ + Error: err.Error(), + }, nil + } + + return &proto.SetTeamAnswerScoreRsp{}, nil +} diff --git a/internal/app/games.go b/internal/app/games.go new file mode 100644 index 0000000..ce4d8d6 --- /dev/null +++ b/internal/app/games.go @@ -0,0 +1,242 @@ +package app + +import ( + "context" + + "evening_detective_server/internal/modules/processor_jwt" + "evening_detective_server/internal/modules/roles" + proto "evening_detective_server/proto" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func (s *server) AddGame(ctx context.Context, req *proto.AddGameReq) (*proto.AddGameRsp, error) { + claims := ctx.Value("claims").(*processor_jwt.JWTClaims) + if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + if req.StartAt == nil { + return nil, status.Errorf(codes.InvalidArgument, "startAt must not be nil") + } + + id, err := s.gameService.AddGame( + ctx, + req.Name, + req.Description, + req.StartAt.AsTime(), + req.ScenarioId, + req.Format, + ) + if err != nil { + return &proto.AddGameRsp{ + Error: err.Error(), + }, nil + } + + return &proto.AddGameRsp{ + Id: int32(id), + }, nil +} + +func (s *server) DeleteGame(ctx context.Context, req *proto.DeleteGameReq) (*proto.DeleteGameRsp, error) { + claims := ctx.Value("claims").(*processor_jwt.JWTClaims) + if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + err := s.gameService.DeleteGame( + ctx, + int(req.Id), + ) + if err != nil { + return &proto.DeleteGameRsp{ + Error: err.Error(), + }, nil + } + + return &proto.DeleteGameRsp{}, nil +} + +func (s *server) GetGame(ctx context.Context, req *proto.GetGameReq) (*proto.GetGameRsp, error) { + claims := ctx.Value("claims").(*processor_jwt.JWTClaims) + if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + game, err := s.gameService.GetFullGame(ctx, int(req.Id)) + if err != nil { + return &proto.GetGameRsp{ + Error: err.Error(), + }, nil + } + + return &proto.GetGameRsp{ + Game: mapGame(game), + }, nil +} + +func (s *server) GetGameAnswers(ctx context.Context, req *proto.GetGameAnswersReq) (*proto.GetGameAnswersRsp, error) { + claims := ctx.Value("claims").(*processor_jwt.JWTClaims) + if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + gameAnswers, err := s.gameService.GetGameAnswers(ctx, int(req.GameId)) + if err != nil { + return &proto.GetGameAnswersRsp{ + Error: err.Error(), + }, nil + } + + return &proto.GetGameAnswersRsp{ + Questions: mapQuestions(gameAnswers.Questions), + Teams: mapTeamAnswers(gameAnswers.Teams), + }, nil +} + +func (s *server) GetGameResults(ctx context.Context, req *proto.GetGameResultsReq) (*proto.GetGameResultsRsp, error) { + claims := ctx.Value("claims").(*processor_jwt.JWTClaims) + if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + results, err := s.gameService.GetGameResults(ctx, int(req.GameId)) + if err != nil { + return &proto.GetGameResultsRsp{ + Error: err.Error(), + }, nil + } + + return &proto.GetGameResultsRsp{ + Questions: mapResultsQuestions(results.Questions), + Teams: mapTeamResults(results.Teams), + }, nil +} + +func (s *server) GetGames(ctx context.Context, req *proto.GetGamesReq) (*proto.GetGamesRsp, error) { + claims := ctx.Value("claims").(*processor_jwt.JWTClaims) + if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + games, err := s.gameService.GetGames(ctx) + if err != nil { + return &proto.GetGamesRsp{ + Error: err.Error(), + }, nil + } + + return &proto.GetGamesRsp{ + Games: mapGames(games), + }, nil +} + +func (s *server) UpdateGame(ctx context.Context, req *proto.UpdateGameReq) (*proto.UpdateGameRsp, error) { + claims := ctx.Value("claims").(*processor_jwt.JWTClaims) + if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + if req.StartAt == nil { + return nil, status.Errorf(codes.InvalidArgument, "startAt must not be nil") + } + + err := s.gameService.UpdateGame( + ctx, + int(req.Id), + req.Name, + req.Description, + req.StartAt.AsTime(), + req.ScenarioId, + req.Format, + ) + if err != nil { + return &proto.UpdateGameRsp{ + Error: err.Error(), + }, nil + } + + return &proto.UpdateGameRsp{}, nil +} + +func (s *server) StartGame(ctx context.Context, req *proto.StartGameReq) (*proto.StartGameRsp, error) { + claims := ctx.Value("claims").(*processor_jwt.JWTClaims) + if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + err := s.gameService.StartGame(ctx, int(req.Id)) + if err != nil { + return &proto.StartGameRsp{ + Error: err.Error(), + }, nil + } + + return &proto.StartGameRsp{}, nil +} + +func (s *server) PauseGame(ctx context.Context, req *proto.PauseGameReq) (*proto.PauseGameRsp, error) { + claims := ctx.Value("claims").(*processor_jwt.JWTClaims) + if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + err := s.gameService.PauseGame(ctx, int(req.Id)) + if err != nil { + return &proto.PauseGameRsp{ + Error: err.Error(), + }, nil + } + + return &proto.PauseGameRsp{}, nil +} + +func (s *server) PlayGame(ctx context.Context, req *proto.PlayGameReq) (*proto.PlayGameRsp, error) { + claims := ctx.Value("claims").(*processor_jwt.JWTClaims) + if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + err := s.gameService.PlayGame(ctx, int(req.Id)) + if err != nil { + return &proto.PlayGameRsp{ + Error: err.Error(), + }, nil + } + + return &proto.PlayGameRsp{}, nil +} + +func (s *server) FinishGame(ctx context.Context, req *proto.FinishGameReq) (*proto.FinishGameRsp, error) { + claims := ctx.Value("claims").(*processor_jwt.JWTClaims) + if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + err := s.gameService.FinishGame(ctx, int(req.Id)) + if err != nil { + return &proto.FinishGameRsp{ + Error: err.Error(), + }, nil + } + + return &proto.FinishGameRsp{}, nil +} + +func (s *server) ResetGame(ctx context.Context, req *proto.ResetGameReq) (*proto.ResetGameRsp, error) { + claims := ctx.Value("claims").(*processor_jwt.JWTClaims) + if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + err := s.gameService.ResetGame(ctx, int(req.Id)) + if err != nil { + return &proto.ResetGameRsp{ + Error: err.Error(), + }, nil + } + + return &proto.ResetGameRsp{}, nil +} diff --git a/internal/app/scenario_archive.go b/internal/app/scenario_archive.go new file mode 100644 index 0000000..5dd068c --- /dev/null +++ b/internal/app/scenario_archive.go @@ -0,0 +1,94 @@ +package app + +import ( + "context" + "errors" + "fmt" + "strings" + + "evening_detective_server/internal/modules/processor_jwt" + "evening_detective_server/internal/modules/roles" + "evening_detective_server/internal/modules/scenario_archive" + "evening_detective_server/internal/repos/scenarios_repo" + "evening_detective_server/internal/services/scenarios_service" + proto "evening_detective_server/proto" + + "google.golang.org/genproto/googleapis/api/httpbody" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +func (s *server) DownloadScenarioArchive(ctx context.Context, req *proto.DownloadScenarioArchiveReq) (*httpbody.HttpBody, error) { + claims := ctx.Value("claims").(*processor_jwt.JWTClaims) + if !roles.HasRole(claims, roles.Author) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + data, name, err := s.scenarioService.DownloadArchive( + ctx, + int(req.Id), + claims.UserID, + roles.HasRole(claims, roles.Admin), + ) + if err != nil { + switch { + case errors.Is(err, scenarios_repo.ErrScenarioNotFound): + return nil, status.Errorf(codes.NotFound, "%v", err) + case errors.Is(err, scenarios_service.ErrScenarioNotOwner): + return nil, status.Errorf(codes.PermissionDenied, "%v", err) + default: + return nil, status.Errorf(codes.Internal, "%v", err) + } + } + + // Имя файла транслитерируется (заголовки HTTP — только ASCII) и + // санитайзится от header injection; заголовок пробрасывается gateway. + transliterated := sanitizeFilename(stringTools.Transliterate(name)) + if transliterated == "" { + transliterated = "scenario" + } + filename := transliterated + ".zip" + if err := grpc.SetHeader(ctx, metadata.Pairs("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename))); err != nil { + return nil, status.Errorf(codes.Internal, "failed to set response header: %v", err) + } + + return &httpbody.HttpBody{ + Data: data, + ContentType: "application/zip", + }, nil +} + +func (s *server) UploadScenarioArchive(ctx context.Context, req *httpbody.HttpBody) (*proto.UploadScenarioArchiveRsp, error) { + claims := ctx.Value("claims").(*processor_jwt.JWTClaims) + if !roles.HasRole(claims, roles.Author) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + if req == nil || len(req.Data) == 0 { + return nil, status.Errorf(codes.InvalidArgument, "пустое тело запроса") + } + if len(req.Data) > scenario_archive.MaxArchiveSize() { + return nil, status.Errorf(codes.InvalidArgument, "архив больше максимального размера (%d байт)", scenario_archive.MaxArchiveSize()) + } + + id, err := s.scenarioService.UploadArchive(ctx, req.Data, claims.UserID) + if err != nil { + return &proto.UploadScenarioArchiveRsp{ + Error: err.Error(), + }, nil + } + return &proto.UploadScenarioArchiveRsp{ + Id: int32(id), + }, nil +} + +// sanitizeFilename — защита от инъекции заголовков в Content-Disposition. +func sanitizeFilename(name string) string { + replacer := strings.NewReplacer( + `"`, "_", + "\r", "_", + "\n", "_", + ) + return replacer.Replace(name) +} diff --git a/internal/app/scenario_story.go b/internal/app/scenario_story.go new file mode 100644 index 0000000..2c39fd0 --- /dev/null +++ b/internal/app/scenario_story.go @@ -0,0 +1,201 @@ +package app + +import ( + "context" + + "evening_detective_server/internal/modules/processor_jwt" + "evening_detective_server/internal/modules/roles" + proto "evening_detective_server/proto" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func (s *server) AddScenarioPlace(ctx context.Context, req *proto.AddScenarioPlaceReq) (*proto.AddScenarioPlaceRsp, error) { + claims := ctx.Value("claims").(*processor_jwt.JWTClaims) + if !roles.HasRole(claims, roles.Author) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + err := s.scenarioService.AddScenarioPlace( + ctx, + int(req.Id), + convertPlace(req.Place), + claims.UserID, + roles.HasRole(claims, roles.Admin), + ) + if err != nil { + return &proto.AddScenarioPlaceRsp{ + Error: err.Error(), + }, nil + } + + return &proto.AddScenarioPlaceRsp{}, nil +} + +func (s *server) UpdateScenarioPlace(ctx context.Context, req *proto.UpdateScenarioPlaceReq) (*proto.UpdateScenarioPlaceRsp, error) { + claims := ctx.Value("claims").(*processor_jwt.JWTClaims) + if !roles.HasRole(claims, roles.Author) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + err := s.scenarioService.UpdateScenarioPlace( + ctx, + int(req.Id), + req.Code, + convertPlace(req.Place), + claims.UserID, + roles.HasRole(claims, roles.Admin), + ) + if err != nil { + return &proto.UpdateScenarioPlaceRsp{ + Error: err.Error(), + }, nil + } + + return &proto.UpdateScenarioPlaceRsp{}, nil +} + +func (s *server) DeleteScenarioPlace(ctx context.Context, req *proto.DeleteScenarioPlaceReq) (*proto.DeleteScenarioPlaceRsp, error) { + claims := ctx.Value("claims").(*processor_jwt.JWTClaims) + if !roles.HasRole(claims, roles.Author) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + err := s.scenarioService.DeleteScenarioPlace( + ctx, + int(req.Id), + req.Code, + claims.UserID, + roles.HasRole(claims, roles.Admin), + ) + if err != nil { + return &proto.DeleteScenarioPlaceRsp{ + Error: err.Error(), + }, nil + } + + return &proto.DeleteScenarioPlaceRsp{}, nil +} + +func (s *server) AddScenarioQuestion(ctx context.Context, req *proto.AddScenarioQuestionReq) (*proto.AddScenarioQuestionRsp, error) { + claims := ctx.Value("claims").(*processor_jwt.JWTClaims) + if !roles.HasRole(claims, roles.Author) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + err := s.scenarioService.AddScenarioQuestion( + ctx, + int(req.Id), + convertQuestion(req.Question), + claims.UserID, + roles.HasRole(claims, roles.Admin), + ) + if err != nil { + return &proto.AddScenarioQuestionRsp{ + Error: err.Error(), + }, nil + } + + return &proto.AddScenarioQuestionRsp{}, nil +} + +func (s *server) UpdateScenarioQuestion(ctx context.Context, req *proto.UpdateScenarioQuestionReq) (*proto.UpdateScenarioQuestionRsp, error) { + claims := ctx.Value("claims").(*processor_jwt.JWTClaims) + if !roles.HasRole(claims, roles.Author) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + err := s.scenarioService.UpdateScenarioQuestion( + ctx, + int(req.Id), + req.Code, + convertQuestion(req.Question), + claims.UserID, + roles.HasRole(claims, roles.Admin), + ) + if err != nil { + return &proto.UpdateScenarioQuestionRsp{ + Error: err.Error(), + }, nil + } + + return &proto.UpdateScenarioQuestionRsp{}, nil +} + +func (s *server) DeleteScenarioQuestion(ctx context.Context, req *proto.DeleteScenarioQuestionReq) (*proto.DeleteScenarioQuestionRsp, error) { + claims := ctx.Value("claims").(*processor_jwt.JWTClaims) + if !roles.HasRole(claims, roles.Author) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + err := s.scenarioService.DeleteScenarioQuestion( + ctx, + int(req.Id), + req.Code, + claims.UserID, + roles.HasRole(claims, roles.Admin), + ) + if err != nil { + return &proto.DeleteScenarioQuestionRsp{ + Error: err.Error(), + }, nil + } + + return &proto.DeleteScenarioQuestionRsp{}, nil +} + +func (s *server) UpdateScenarioBackground(ctx context.Context, req *proto.UpdateScenarioBackgroundReq) (*proto.UpdateScenarioBackgroundRsp, error) { + claims := ctx.Value("claims").(*processor_jwt.JWTClaims) + if !roles.HasRole(claims, roles.Author) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + err := s.scenarioService.UpdateScenarioBackground( + ctx, + int(req.Id), + req.Background, + claims.UserID, + roles.HasRole(claims, roles.Admin), + ) + if err != nil { + return &proto.UpdateScenarioBackgroundRsp{ + Error: err.Error(), + }, nil + } + + return &proto.UpdateScenarioBackgroundRsp{}, nil +} + +func (s *server) UpdateScenarioIntro(ctx context.Context, req *proto.UpdateScenarioIntroReq) (*proto.UpdateScenarioIntroRsp, error) { + claims := ctx.Value("claims").(*processor_jwt.JWTClaims) + if !roles.HasRole(claims, roles.Author) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + err := s.scenarioService.UpdateScenarioIntro( + ctx, + int(req.Id), + convertIntroduction(req.Introduction), + claims.UserID, + roles.HasRole(claims, roles.Admin), + ) + if err != nil { + return &proto.UpdateScenarioIntroRsp{ + Error: err.Error(), + }, nil + } + + return &proto.UpdateScenarioIntroRsp{}, nil +} + +func (s *server) UpdateScenarioConclusion(ctx context.Context, req *proto.UpdateScenarioConclusionReq) (*proto.UpdateScenarioConclusionRsp, error) { + claims := ctx.Value("claims").(*processor_jwt.JWTClaims) + if !roles.HasRole(claims, roles.Author) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + if err := s.scenarioService.UpdateScenarioConclusion(ctx, int(req.Id), convertConclusion(req.Conclusion), claims.UserID, roles.HasRole(claims, roles.Admin)); err != nil { + return &proto.UpdateScenarioConclusionRsp{Error: err.Error()}, nil + } + return &proto.UpdateScenarioConclusionRsp{}, nil +} diff --git a/internal/app/scenarios.go b/internal/app/scenarios.go new file mode 100644 index 0000000..7ee961a --- /dev/null +++ b/internal/app/scenarios.go @@ -0,0 +1,185 @@ +package app + +import ( + "context" + + "evening_detective_server/internal/modules/processor_jwt" + "evening_detective_server/internal/modules/roles" + proto "evening_detective_server/proto" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func (s *server) AddScenario(ctx context.Context, req *proto.AddScenarioReq) (*proto.AddScenarioRsp, error) { + claims := ctx.Value("claims").(*processor_jwt.JWTClaims) + if !roles.HasRole(claims, roles.Author) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + id, err := s.scenarioService.AddScenario(ctx, req.Name, claims.UserID) + if err != nil { + return &proto.AddScenarioRsp{ + Error: err.Error(), + }, nil + } + return &proto.AddScenarioRsp{ + Id: int32(id), + }, nil +} + +func (s *server) GetMyScenarios(ctx context.Context, req *proto.GetMyScenariosReq) (*proto.GetMyScenariosRsp, error) { + claims := ctx.Value("claims").(*processor_jwt.JWTClaims) + if !roles.HasRole(claims, roles.Author) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + scenarios, err := s.scenarioService.GetScenariosByAuthorID(ctx, claims.UserID) + if err != nil { + return &proto.GetMyScenariosRsp{ + Error: err.Error(), + }, nil + } + + return &proto.GetMyScenariosRsp{ + Scenarios: mapScenarios(scenarios), + }, nil +} + +func (s *server) GetScenariosCatalog(ctx context.Context, req *proto.GetScenariosCatalogReq) (*proto.GetScenariosCatalogRsp, error) { + scenarios, err := s.scenarioService.GetScenariosCatalog(ctx) + if err != nil { + return &proto.GetScenariosCatalogRsp{ + Error: err.Error(), + }, nil + } + + return &proto.GetScenariosCatalogRsp{ + Scenarios: mapScenarios(scenarios), + }, nil +} + +func (s *server) GetFullScenario(ctx context.Context, req *proto.GetScenarioReq) (*proto.GetScenarioRsp, error) { + claims := ctx.Value("claims").(*processor_jwt.JWTClaims) + if !roles.HasRole(claims, roles.Author) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + scenario, err := s.scenarioService.GetFullScenarioByID(ctx, int(req.Id)) + if err != nil { + return &proto.GetScenarioRsp{ + Error: err.Error(), + }, nil + } + + return &proto.GetScenarioRsp{ + Scenario: mapScenario(scenario), + }, nil +} + +func (s *server) GetScenario(ctx context.Context, req *proto.GetScenarioReq) (*proto.GetScenarioRsp, error) { + scenario, err := s.scenarioService.GetScenarioByID(ctx, int(req.Id)) + if err != nil { + return &proto.GetScenarioRsp{ + Error: err.Error(), + }, nil + } + + return &proto.GetScenarioRsp{ + Scenario: mapScenario(scenario), + }, nil +} + +func (s *server) UpdateScenario(ctx context.Context, req *proto.UpdateScenarioReq) (*proto.UpdateScenarioRsp, error) { + claims := ctx.Value("claims").(*processor_jwt.JWTClaims) + if !roles.HasRole(claims, roles.Author) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + err := s.scenarioService.UpdateScenarioByID( + ctx, + int(req.Id), + req.Name, + req.Description, + req.Image, + claims.UserID, + roles.HasRole(claims, roles.Admin), + ) + if err != nil { + return &proto.UpdateScenarioRsp{ + Error: err.Error(), + }, nil + } + + return &proto.UpdateScenarioRsp{}, nil +} + +func (s *server) PublicScenario(ctx context.Context, req *proto.PublicScenarioReq) (*proto.PublicScenarioRsp, error) { + claims := ctx.Value("claims").(*processor_jwt.JWTClaims) + if !roles.HasRole(claims, roles.Author) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + err := s.scenarioService.UpdateScenarioStatusByID( + ctx, + int(req.Id), + "public", + claims.UserID, + roles.HasRole(claims, roles.Admin), // публикация = момент отчуждения прав + ) + if err != nil { + return &proto.PublicScenarioRsp{ + Error: err.Error(), + }, nil + } + + return &proto.PublicScenarioRsp{}, nil +} + +func (s *server) DraftScenario(ctx context.Context, req *proto.DraftScenarioReq) (*proto.DraftScenarioRsp, error) { + claims := ctx.Value("claims").(*processor_jwt.JWTClaims) + // Снять с публикации опубликованный сценарий может только админ: + // исключительное право отчуждено Оператору в момент публикации + // (п. 7.2 Пользовательского соглашения, ст. 1234, 1269 ГК РФ). + if !roles.HasRole(claims, roles.Admin) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + err := s.scenarioService.UpdateScenarioStatusByID( + ctx, + int(req.Id), + "draft", + claims.UserID, + true, + ) + if err != nil { + return &proto.DraftScenarioRsp{ + Error: err.Error(), + }, nil + } + + return &proto.DraftScenarioRsp{}, nil +} + +func (s *server) DeleteScenario(ctx context.Context, req *proto.DeleteScenarioReq) (*proto.DeleteScenarioRsp, error) { + claims := ctx.Value("claims").(*processor_jwt.JWTClaims) + if !roles.HasRole(claims, roles.Author) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + // Автор не может удалить опубликованный сценарий (исключительное право + // отчуждено); админ может. + err := s.scenarioService.DeleteScenarioByID( + ctx, + int(req.Id), + claims.UserID, + roles.HasRole(claims, roles.Admin), + ) + if err != nil { + return &proto.DeleteScenarioRsp{ + Error: err.Error(), + }, nil + } + + return &proto.DeleteScenarioRsp{}, nil +} diff --git a/internal/app/server.go b/internal/app/server.go index bffd54c..06d83c6 100644 --- a/internal/app/server.go +++ b/internal/app/server.go @@ -1,17 +1,8 @@ package app import ( - "context" - "errors" - "fmt" - "strings" - - "evening_detective_server/internal/modules/processor_jwt" - "evening_detective_server/internal/modules/roles" - "evening_detective_server/internal/modules/scenario_archive" "evening_detective_server/internal/modules/string_tools" "evening_detective_server/internal/repos/game_map_repo" - "evening_detective_server/internal/repos/scenarios_repo" "evening_detective_server/internal/repos/screen_sessions_repo" "evening_detective_server/internal/services/file_service" "evening_detective_server/internal/services/game_service" @@ -19,12 +10,6 @@ import ( "evening_detective_server/internal/services/ui_service" "evening_detective_server/internal/services/users_service" proto "evening_detective_server/proto" - - "google.golang.org/genproto/googleapis/api/httpbody" - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/status" ) // stringTools — транслитерация имён файлов для HTTP-заголовков. @@ -61,874 +46,3 @@ func NewServer( mapRepo: mapRepo, } } - -func (s *server) AddScenario(ctx context.Context, req *proto.AddScenarioReq) (*proto.AddScenarioRsp, error) { - claims := ctx.Value("claims").(*processor_jwt.JWTClaims) - if !roles.HasRole(claims, roles.Author) { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - - id, err := s.scenarioService.AddScenario(ctx, req.Name, claims.UserID) - if err != nil { - return &proto.AddScenarioRsp{ - Error: err.Error(), - }, nil - } - return &proto.AddScenarioRsp{ - Id: int32(id), - }, nil -} - -func (s *server) GetMyScenarios(ctx context.Context, req *proto.GetMyScenariosReq) (*proto.GetMyScenariosRsp, error) { - claims := ctx.Value("claims").(*processor_jwt.JWTClaims) - if !roles.HasRole(claims, roles.Author) { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - - scenarios, err := s.scenarioService.GetScenariosByAuthorID(ctx, claims.UserID) - if err != nil { - return &proto.GetMyScenariosRsp{ - Error: err.Error(), - }, nil - } - - return &proto.GetMyScenariosRsp{ - Scenarios: mapScenarios(scenarios), - }, nil -} - -func (s *server) GetScenariosCatalog(ctx context.Context, req *proto.GetScenariosCatalogReq) (*proto.GetScenariosCatalogRsp, error) { - scenarios, err := s.scenarioService.GetScenariosCatalog(ctx) - if err != nil { - return &proto.GetScenariosCatalogRsp{ - Error: err.Error(), - }, nil - } - - return &proto.GetScenariosCatalogRsp{ - Scenarios: mapScenarios(scenarios), - }, nil -} - -func (s *server) GetFullScenario(ctx context.Context, req *proto.GetScenarioReq) (*proto.GetScenarioRsp, error) { - claims := ctx.Value("claims").(*processor_jwt.JWTClaims) - if !roles.HasRole(claims, roles.Author) { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - - scenario, err := s.scenarioService.GetFullScenarioByID(ctx, int(req.Id)) - if err != nil { - return &proto.GetScenarioRsp{ - Error: err.Error(), - }, nil - } - - return &proto.GetScenarioRsp{ - Scenario: mapScenario(scenario), - }, nil -} - -func (s *server) GetScenario(ctx context.Context, req *proto.GetScenarioReq) (*proto.GetScenarioRsp, error) { - scenario, err := s.scenarioService.GetScenarioByID(ctx, int(req.Id)) - if err != nil { - return &proto.GetScenarioRsp{ - Error: err.Error(), - }, nil - } - - return &proto.GetScenarioRsp{ - Scenario: mapScenario(scenario), - }, nil -} - -func (s *server) UpdateScenario(ctx context.Context, req *proto.UpdateScenarioReq) (*proto.UpdateScenarioRsp, error) { - claims := ctx.Value("claims").(*processor_jwt.JWTClaims) - if !roles.HasRole(claims, roles.Author) { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - - err := s.scenarioService.UpdateScenarioByID( - ctx, - int(req.Id), - req.Name, - req.Description, - req.Image, - claims.UserID, - roles.HasRole(claims, roles.Admin), - ) - if err != nil { - return &proto.UpdateScenarioRsp{ - Error: err.Error(), - }, nil - } - - return &proto.UpdateScenarioRsp{}, nil -} - -func (s *server) PublicScenario(ctx context.Context, req *proto.PublicScenarioReq) (*proto.PublicScenarioRsp, error) { - claims := ctx.Value("claims").(*processor_jwt.JWTClaims) - if !roles.HasRole(claims, roles.Author) { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - - err := s.scenarioService.UpdateScenarioStatusByID( - ctx, - int(req.Id), - "public", - claims.UserID, - roles.HasRole(claims, roles.Admin), // публикация = момент отчуждения прав - ) - if err != nil { - return &proto.PublicScenarioRsp{ - Error: err.Error(), - }, nil - } - - return &proto.PublicScenarioRsp{}, nil -} - -func (s *server) DraftScenario(ctx context.Context, req *proto.DraftScenarioReq) (*proto.DraftScenarioRsp, error) { - claims := ctx.Value("claims").(*processor_jwt.JWTClaims) - // Снять с публикации опубликованный сценарий может только админ: - // исключительное право отчуждено Оператору в момент публикации - // (п. 7.2 Пользовательского соглашения, ст. 1234, 1269 ГК РФ). - if !roles.HasRole(claims, roles.Admin) { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - - err := s.scenarioService.UpdateScenarioStatusByID( - ctx, - int(req.Id), - "draft", - claims.UserID, - true, - ) - if err != nil { - return &proto.DraftScenarioRsp{ - Error: err.Error(), - }, nil - } - - return &proto.DraftScenarioRsp{}, nil -} - -func (s *server) DeleteScenario(ctx context.Context, req *proto.DeleteScenarioReq) (*proto.DeleteScenarioRsp, error) { - claims := ctx.Value("claims").(*processor_jwt.JWTClaims) - if !roles.HasRole(claims, roles.Author) { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - - // Автор не может удалить опубликованный сценарий (исключительное право - // отчуждено); админ может. - err := s.scenarioService.DeleteScenarioByID( - ctx, - int(req.Id), - claims.UserID, - roles.HasRole(claims, roles.Admin), - ) - if err != nil { - return &proto.DeleteScenarioRsp{ - Error: err.Error(), - }, nil - } - - return &proto.DeleteScenarioRsp{}, nil -} - -func (s *server) AddScenarioPlace(ctx context.Context, req *proto.AddScenarioPlaceReq) (*proto.AddScenarioPlaceRsp, error) { - claims := ctx.Value("claims").(*processor_jwt.JWTClaims) - if !roles.HasRole(claims, roles.Author) { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - - err := s.scenarioService.AddScenarioPlace( - ctx, - int(req.Id), - convertPlace(req.Place), - claims.UserID, - roles.HasRole(claims, roles.Admin), - ) - if err != nil { - return &proto.AddScenarioPlaceRsp{ - Error: err.Error(), - }, nil - } - - return &proto.AddScenarioPlaceRsp{}, nil -} - -func (s *server) UpdateScenarioPlace(ctx context.Context, req *proto.UpdateScenarioPlaceReq) (*proto.UpdateScenarioPlaceRsp, error) { - claims := ctx.Value("claims").(*processor_jwt.JWTClaims) - if !roles.HasRole(claims, roles.Author) { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - - err := s.scenarioService.UpdateScenarioPlace( - ctx, - int(req.Id), - req.Code, - convertPlace(req.Place), - claims.UserID, - roles.HasRole(claims, roles.Admin), - ) - if err != nil { - return &proto.UpdateScenarioPlaceRsp{ - Error: err.Error(), - }, nil - } - - return &proto.UpdateScenarioPlaceRsp{}, nil -} - -func (s *server) DeleteScenarioPlace(ctx context.Context, req *proto.DeleteScenarioPlaceReq) (*proto.DeleteScenarioPlaceRsp, error) { - claims := ctx.Value("claims").(*processor_jwt.JWTClaims) - if !roles.HasRole(claims, roles.Author) { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - - err := s.scenarioService.DeleteScenarioPlace( - ctx, - int(req.Id), - req.Code, - claims.UserID, - roles.HasRole(claims, roles.Admin), - ) - if err != nil { - return &proto.DeleteScenarioPlaceRsp{ - Error: err.Error(), - }, nil - } - - return &proto.DeleteScenarioPlaceRsp{}, nil -} - -func (s *server) AddScenarioQuestion(ctx context.Context, req *proto.AddScenarioQuestionReq) (*proto.AddScenarioQuestionRsp, error) { - claims := ctx.Value("claims").(*processor_jwt.JWTClaims) - if !roles.HasRole(claims, roles.Author) { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - - err := s.scenarioService.AddScenarioQuestion( - ctx, - int(req.Id), - convertQuestion(req.Question), - claims.UserID, - roles.HasRole(claims, roles.Admin), - ) - if err != nil { - return &proto.AddScenarioQuestionRsp{ - Error: err.Error(), - }, nil - } - - return &proto.AddScenarioQuestionRsp{}, nil -} - -func (s *server) UpdateScenarioQuestion(ctx context.Context, req *proto.UpdateScenarioQuestionReq) (*proto.UpdateScenarioQuestionRsp, error) { - claims := ctx.Value("claims").(*processor_jwt.JWTClaims) - if !roles.HasRole(claims, roles.Author) { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - - err := s.scenarioService.UpdateScenarioQuestion( - ctx, - int(req.Id), - req.Code, - convertQuestion(req.Question), - claims.UserID, - roles.HasRole(claims, roles.Admin), - ) - if err != nil { - return &proto.UpdateScenarioQuestionRsp{ - Error: err.Error(), - }, nil - } - - return &proto.UpdateScenarioQuestionRsp{}, nil -} - -func (s *server) DeleteScenarioQuestion(ctx context.Context, req *proto.DeleteScenarioQuestionReq) (*proto.DeleteScenarioQuestionRsp, error) { - claims := ctx.Value("claims").(*processor_jwt.JWTClaims) - if !roles.HasRole(claims, roles.Author) { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - - err := s.scenarioService.DeleteScenarioQuestion( - ctx, - int(req.Id), - req.Code, - claims.UserID, - roles.HasRole(claims, roles.Admin), - ) - if err != nil { - return &proto.DeleteScenarioQuestionRsp{ - Error: err.Error(), - }, nil - } - - return &proto.DeleteScenarioQuestionRsp{}, nil -} - -func (s *server) UpdateScenarioBackground(ctx context.Context, req *proto.UpdateScenarioBackgroundReq) (*proto.UpdateScenarioBackgroundRsp, error) { - claims := ctx.Value("claims").(*processor_jwt.JWTClaims) - if !roles.HasRole(claims, roles.Author) { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - - err := s.scenarioService.UpdateScenarioBackground( - ctx, - int(req.Id), - req.Background, - claims.UserID, - roles.HasRole(claims, roles.Admin), - ) - if err != nil { - return &proto.UpdateScenarioBackgroundRsp{ - Error: err.Error(), - }, nil - } - - return &proto.UpdateScenarioBackgroundRsp{}, nil -} - -func (s *server) UpdateScenarioIntro(ctx context.Context, req *proto.UpdateScenarioIntroReq) (*proto.UpdateScenarioIntroRsp, error) { - claims := ctx.Value("claims").(*processor_jwt.JWTClaims) - if !roles.HasRole(claims, roles.Author) { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - - err := s.scenarioService.UpdateScenarioIntro( - ctx, - int(req.Id), - convertIntroduction(req.Introduction), - claims.UserID, - roles.HasRole(claims, roles.Admin), - ) - if err != nil { - return &proto.UpdateScenarioIntroRsp{ - Error: err.Error(), - }, nil - } - - return &proto.UpdateScenarioIntroRsp{}, nil -} - -func (s *server) UpdateScenarioConclusion(ctx context.Context, req *proto.UpdateScenarioConclusionReq) (*proto.UpdateScenarioConclusionRsp, error) { - claims := ctx.Value("claims").(*processor_jwt.JWTClaims) - if !roles.HasRole(claims, roles.Author) { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - if err := s.scenarioService.UpdateScenarioConclusion(ctx, int(req.Id), convertConclusion(req.Conclusion), claims.UserID, roles.HasRole(claims, roles.Admin)); err != nil { - return &proto.UpdateScenarioConclusionRsp{Error: err.Error()}, nil - } - return &proto.UpdateScenarioConclusionRsp{}, nil -} - -// DownloadScenarioArchive отдаёт ZIP-архив сценария; ошибки — gRPC-статусами. -func (s *server) DownloadScenarioArchive(ctx context.Context, req *proto.DownloadScenarioArchiveReq) (*httpbody.HttpBody, error) { - claims := ctx.Value("claims").(*processor_jwt.JWTClaims) - if !roles.HasRole(claims, roles.Author) { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - - data, name, err := s.scenarioService.DownloadArchive( - ctx, - int(req.Id), - claims.UserID, - roles.HasRole(claims, roles.Admin), - ) - if err != nil { - switch { - case errors.Is(err, scenarios_repo.ErrScenarioNotFound): - return nil, status.Errorf(codes.NotFound, "%v", err) - case errors.Is(err, scenarios_service.ErrScenarioNotOwner): - return nil, status.Errorf(codes.PermissionDenied, "%v", err) - default: - return nil, status.Errorf(codes.Internal, "%v", err) - } - } - - // Имя файла транслитерируется (заголовки HTTP — только ASCII) и - // санитайзится от header injection; заголовок пробрасывается gateway. - transliterated := sanitizeFilename(stringTools.Transliterate(name)) - if transliterated == "" { - transliterated = "scenario" - } - filename := transliterated + ".zip" - if err := grpc.SetHeader(ctx, metadata.Pairs("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename))); err != nil { - return nil, status.Errorf(codes.Internal, "failed to set response header: %v", err) - } - - return &httpbody.HttpBody{ - Data: data, - ContentType: "application/zip", - }, nil -} - -// UploadScenarioArchive создаёт сценарий из ZIP-архива. Тело — сырые байты; -// JSON base64 тоже поддерживается. -func (s *server) UploadScenarioArchive(ctx context.Context, req *httpbody.HttpBody) (*proto.UploadScenarioArchiveRsp, error) { - claims := ctx.Value("claims").(*processor_jwt.JWTClaims) - if !roles.HasRole(claims, roles.Author) { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - if req == nil || len(req.Data) == 0 { - return nil, status.Errorf(codes.InvalidArgument, "пустое тело запроса") - } - if len(req.Data) > scenario_archive.MaxArchiveSize() { - return nil, status.Errorf(codes.InvalidArgument, "архив больше максимального размера (%d байт)", scenario_archive.MaxArchiveSize()) - } - - id, err := s.scenarioService.UploadArchive(ctx, req.Data, claims.UserID) - if err != nil { - return &proto.UploadScenarioArchiveRsp{ - Error: err.Error(), - }, nil - } - return &proto.UploadScenarioArchiveRsp{ - Id: int32(id), - }, nil -} - -// sanitizeFilename — защита от инъекции заголовков в Content-Disposition. -func sanitizeFilename(name string) string { - replacer := strings.NewReplacer( - `"`, "_", - "\r", "_", - "\n", "_", - ) - return replacer.Replace(name) -} - -func (s *server) AddGame(ctx context.Context, req *proto.AddGameReq) (*proto.AddGameRsp, error) { - claims := ctx.Value("claims").(*processor_jwt.JWTClaims) - if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - - if req.StartAt == nil { - return nil, status.Errorf(codes.InvalidArgument, "startAt must not be nil") - } - - id, err := s.gameService.AddGame( - ctx, - req.Name, - req.Description, - req.StartAt.AsTime(), - req.ScenarioId, - req.Format, - ) - if err != nil { - return &proto.AddGameRsp{ - Error: err.Error(), - }, nil - } - - return &proto.AddGameRsp{ - Id: int32(id), - }, nil -} - -func (s *server) DeleteGame(ctx context.Context, req *proto.DeleteGameReq) (*proto.DeleteGameRsp, error) { - claims := ctx.Value("claims").(*processor_jwt.JWTClaims) - if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - - err := s.gameService.DeleteGame( - ctx, - int(req.Id), - ) - if err != nil { - return &proto.DeleteGameRsp{ - Error: err.Error(), - }, nil - } - - return &proto.DeleteGameRsp{}, nil -} - -func (s *server) GetGame(ctx context.Context, req *proto.GetGameReq) (*proto.GetGameRsp, error) { - claims := ctx.Value("claims").(*processor_jwt.JWTClaims) - if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - - game, err := s.gameService.GetFullGame(ctx, int(req.Id)) - if err != nil { - return &proto.GetGameRsp{ - Error: err.Error(), - }, nil - } - - return &proto.GetGameRsp{ - Game: mapGame(game), - }, nil -} - -func (s *server) GetGameAnswers(ctx context.Context, req *proto.GetGameAnswersReq) (*proto.GetGameAnswersRsp, error) { - claims := ctx.Value("claims").(*processor_jwt.JWTClaims) - if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - - gameAnswers, err := s.gameService.GetGameAnswers(ctx, int(req.GameId)) - if err != nil { - return &proto.GetGameAnswersRsp{ - Error: err.Error(), - }, nil - } - - return &proto.GetGameAnswersRsp{ - Questions: mapQuestions(gameAnswers.Questions), - Teams: mapTeamAnswers(gameAnswers.Teams), - }, nil -} - -func (s *server) GetGameResults(ctx context.Context, req *proto.GetGameResultsReq) (*proto.GetGameResultsRsp, error) { - claims := ctx.Value("claims").(*processor_jwt.JWTClaims) - if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - - results, err := s.gameService.GetGameResults(ctx, int(req.GameId)) - if err != nil { - return &proto.GetGameResultsRsp{ - Error: err.Error(), - }, nil - } - - return &proto.GetGameResultsRsp{ - Questions: mapResultsQuestions(results.Questions), - Teams: mapTeamResults(results.Teams), - }, nil -} - -func (s *server) GetGames(ctx context.Context, req *proto.GetGamesReq) (*proto.GetGamesRsp, error) { - claims := ctx.Value("claims").(*processor_jwt.JWTClaims) - if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - - games, err := s.gameService.GetGames(ctx) - if err != nil { - return &proto.GetGamesRsp{ - Error: err.Error(), - }, nil - } - - return &proto.GetGamesRsp{ - Games: mapGames(games), - }, nil -} - -func (s *server) UpdateGame(ctx context.Context, req *proto.UpdateGameReq) (*proto.UpdateGameRsp, error) { - claims := ctx.Value("claims").(*processor_jwt.JWTClaims) - if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - - if req.StartAt == nil { - return nil, status.Errorf(codes.InvalidArgument, "startAt must not be nil") - } - - err := s.gameService.UpdateGame( - ctx, - int(req.Id), - req.Name, - req.Description, - req.StartAt.AsTime(), - req.ScenarioId, - req.Format, - ) - if err != nil { - return &proto.UpdateGameRsp{ - Error: err.Error(), - }, nil - } - - return &proto.UpdateGameRsp{}, nil -} - -func (s *server) StartGame(ctx context.Context, req *proto.StartGameReq) (*proto.StartGameRsp, error) { - claims := ctx.Value("claims").(*processor_jwt.JWTClaims) - if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - - err := s.gameService.StartGame(ctx, int(req.Id)) - if err != nil { - return &proto.StartGameRsp{ - Error: err.Error(), - }, nil - } - - return &proto.StartGameRsp{}, nil -} - -func (s *server) PauseGame(ctx context.Context, req *proto.PauseGameReq) (*proto.PauseGameRsp, error) { - claims := ctx.Value("claims").(*processor_jwt.JWTClaims) - if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - - err := s.gameService.PauseGame(ctx, int(req.Id)) - if err != nil { - return &proto.PauseGameRsp{ - Error: err.Error(), - }, nil - } - - return &proto.PauseGameRsp{}, nil -} - -func (s *server) PlayGame(ctx context.Context, req *proto.PlayGameReq) (*proto.PlayGameRsp, error) { - claims := ctx.Value("claims").(*processor_jwt.JWTClaims) - if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - - err := s.gameService.PlayGame(ctx, int(req.Id)) - if err != nil { - return &proto.PlayGameRsp{ - Error: err.Error(), - }, nil - } - - return &proto.PlayGameRsp{}, nil -} - -func (s *server) FinishGame(ctx context.Context, req *proto.FinishGameReq) (*proto.FinishGameRsp, error) { - claims := ctx.Value("claims").(*processor_jwt.JWTClaims) - if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - - err := s.gameService.FinishGame(ctx, int(req.Id)) - if err != nil { - return &proto.FinishGameRsp{ - Error: err.Error(), - }, nil - } - - return &proto.FinishGameRsp{}, nil -} - -func (s *server) ResetGame(ctx context.Context, req *proto.ResetGameReq) (*proto.ResetGameRsp, error) { - claims := ctx.Value("claims").(*processor_jwt.JWTClaims) - if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - - err := s.gameService.ResetGame(ctx, int(req.Id)) - if err != nil { - return &proto.ResetGameRsp{ - Error: err.Error(), - }, nil - } - - return &proto.ResetGameRsp{}, nil -} - -func (s *server) AddTeam(ctx context.Context, req *proto.AddTeamReq) (*proto.AddTeamRsp, error) { - claims := ctx.Value("claims").(*processor_jwt.JWTClaims) - if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - - team, err := s.gameService.AddTeam( - ctx, - claims.UserID, - int(req.GameId), - req.Name, - req.CaptainEmail, - ) - if err != nil { - return &proto.AddTeamRsp{ - Error: err.Error(), - }, nil - } - - return &proto.AddTeamRsp{ - Id: int32(team.ID), - Password: team.Password, - }, nil -} - -func (s *server) UpdateTeam(ctx context.Context, req *proto.UpdateTeamReq) (*proto.UpdateTeamRsp, error) { - claims := ctx.Value("claims").(*processor_jwt.JWTClaims) - if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - - err := s.gameService.UpdateTeam( - ctx, - int(req.Id), - req.Name, - ) - if err != nil { - return &proto.UpdateTeamRsp{ - Error: err.Error(), - }, nil - } - - return &proto.UpdateTeamRsp{}, nil -} - -func (s *server) DeleteTeam(ctx context.Context, req *proto.DeleteTeamReq) (*proto.DeleteTeamRsp, error) { - claims := ctx.Value("claims").(*processor_jwt.JWTClaims) - if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - - err := s.gameService.DeleteTeam( - ctx, - int(req.Id), - ) - if err != nil { - return &proto.DeleteTeamRsp{ - Error: err.Error(), - }, nil - } - - return &proto.DeleteTeamRsp{}, nil -} - -func (s *server) GiveTeamApplications(ctx context.Context, req *proto.GiveTeamApplicationsReq) (*proto.GiveTeamApplicationsRsp, error) { - claims := ctx.Value("claims").(*processor_jwt.JWTClaims) - if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - - err := s.gameService.GiveTeamApplications( - ctx, - int(req.Id), - req.Name, - ) - if err != nil { - return &proto.GiveTeamApplicationsRsp{ - Error: err.Error(), - }, nil - } - - return &proto.GiveTeamApplicationsRsp{}, nil -} - -func (s *server) GetTeamStory(ctx context.Context, req *proto.GetTeamStoryReq) (*proto.GetTeamStoryRsp, error) { - story, game, actions, err := s.gameService.GetTeamActions( - ctx, - int(req.Id), - req.Password, - ) - if err != nil { - return &proto.GetTeamStoryRsp{ - Error: err.Error(), - }, nil - } - protoActions := make([]*proto.TeamAction, 0, len(actions)) - for _, action := range actions { - protoActions = append(protoActions, &proto.TeamAction{Code: action.Code, Mcp: action.ViaMCP}) - } - - return &proto.GetTeamStoryRsp{ - Story: mapStory(story), - Game: mapGame(game), - Actions: protoActions, - }, nil -} - -func (s *server) AddTeamAction(ctx context.Context, req *proto.AddTeamActionReq) (*proto.AddTeamActionRsp, error) { - err := s.gameService.AddTeamAction( - ctx, - int(req.Id), - req.Password, - req.Code, - false, - ) - if err != nil { - return &proto.AddTeamActionRsp{ - Error: err.Error(), - }, nil - } - - return &proto.AddTeamActionRsp{}, nil -} - -func (s *server) DeleteLastTeamAction(ctx context.Context, req *proto.DeleteLastTeamActionReq) (*proto.DeleteLastTeamActionRsp, error) { - claims := ctx.Value("claims").(*processor_jwt.JWTClaims) - if !roles.HasRole(claims, roles.Author) { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - - err := s.gameService.DeleteLastTeamAction( - ctx, - int(req.Id), - ) - if err != nil { - return &proto.DeleteLastTeamActionRsp{ - Error: err.Error(), - }, nil - } - - return &proto.DeleteLastTeamActionRsp{}, nil -} - -func (s *server) GetTeamAnswers(ctx context.Context, req *proto.GetTeamAnswersReq) (*proto.GetTeamAnswersRsp, error) { - answers, err := s.gameService.GetTeamAnswers( - ctx, - int(req.Id), - req.Password, - ) - if err != nil { - return &proto.GetTeamAnswersRsp{ - Error: err.Error(), - }, nil - } - - return &proto.GetTeamAnswersRsp{ - Answers: mapAnswers(answers), - }, nil -} - -func (s *server) SubmitTeamAnswers(ctx context.Context, req *proto.SubmitTeamAnswersReq) (*proto.SubmitTeamAnswersRsp, error) { - err := s.gameService.SubmitTeamAnswers( - ctx, - int(req.Id), - req.Password, - convertAnswers(req.Answers), - ) - if err != nil { - return &proto.SubmitTeamAnswersRsp{ - Error: err.Error(), - }, nil - } - - return &proto.SubmitTeamAnswersRsp{}, nil -} - -func (s *server) SetTeamAnswerScore(ctx context.Context, req *proto.SetTeamAnswerScoreReq) (*proto.SetTeamAnswerScoreRsp, error) { - claims := ctx.Value("claims").(*processor_jwt.JWTClaims) - if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - - var score *int - if req.Score != nil { - v := int(req.GetScore()) - score = &v - } - - err := s.gameService.SetTeamAnswerScore( - ctx, - int(req.Id), - req.QuestionCode, - score, - ) - if err != nil { - return &proto.SetTeamAnswerScoreRsp{ - Error: err.Error(), - }, nil - } - - return &proto.SetTeamAnswerScoreRsp{}, nil -} diff --git a/internal/app/teams.go b/internal/app/teams.go new file mode 100644 index 0000000..6527996 --- /dev/null +++ b/internal/app/teams.go @@ -0,0 +1,155 @@ +package app + +import ( + "context" + + "evening_detective_server/internal/modules/processor_jwt" + "evening_detective_server/internal/modules/roles" + proto "evening_detective_server/proto" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func (s *server) AddTeam(ctx context.Context, req *proto.AddTeamReq) (*proto.AddTeamRsp, error) { + claims := ctx.Value("claims").(*processor_jwt.JWTClaims) + if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + team, err := s.gameService.AddTeam( + ctx, + claims.UserID, + int(req.GameId), + req.Name, + req.CaptainEmail, + ) + if err != nil { + return &proto.AddTeamRsp{ + Error: err.Error(), + }, nil + } + + return &proto.AddTeamRsp{ + Id: int32(team.ID), + Password: team.Password, + }, nil +} + +func (s *server) UpdateTeam(ctx context.Context, req *proto.UpdateTeamReq) (*proto.UpdateTeamRsp, error) { + claims := ctx.Value("claims").(*processor_jwt.JWTClaims) + if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + err := s.gameService.UpdateTeam( + ctx, + int(req.Id), + req.Name, + ) + if err != nil { + return &proto.UpdateTeamRsp{ + Error: err.Error(), + }, nil + } + + return &proto.UpdateTeamRsp{}, nil +} + +func (s *server) DeleteTeam(ctx context.Context, req *proto.DeleteTeamReq) (*proto.DeleteTeamRsp, error) { + claims := ctx.Value("claims").(*processor_jwt.JWTClaims) + if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + err := s.gameService.DeleteTeam( + ctx, + int(req.Id), + ) + if err != nil { + return &proto.DeleteTeamRsp{ + Error: err.Error(), + }, nil + } + + return &proto.DeleteTeamRsp{}, nil +} + +func (s *server) GiveTeamApplications(ctx context.Context, req *proto.GiveTeamApplicationsReq) (*proto.GiveTeamApplicationsRsp, error) { + claims := ctx.Value("claims").(*processor_jwt.JWTClaims) + if !roles.HasRole(claims, roles.Organizer) && !roles.HasRole(claims, roles.Author) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + err := s.gameService.GiveTeamApplications( + ctx, + int(req.Id), + req.Name, + ) + if err != nil { + return &proto.GiveTeamApplicationsRsp{ + Error: err.Error(), + }, nil + } + + return &proto.GiveTeamApplicationsRsp{}, nil +} + +func (s *server) GetTeamStory(ctx context.Context, req *proto.GetTeamStoryReq) (*proto.GetTeamStoryRsp, error) { + story, game, actions, err := s.gameService.GetTeamActions( + ctx, + int(req.Id), + req.Password, + ) + if err != nil { + return &proto.GetTeamStoryRsp{ + Error: err.Error(), + }, nil + } + protoActions := make([]*proto.TeamAction, 0, len(actions)) + for _, action := range actions { + protoActions = append(protoActions, &proto.TeamAction{Code: action.Code, Mcp: action.ViaMCP}) + } + + return &proto.GetTeamStoryRsp{ + Story: mapStory(story), + Game: mapGame(game), + Actions: protoActions, + }, nil +} + +func (s *server) AddTeamAction(ctx context.Context, req *proto.AddTeamActionReq) (*proto.AddTeamActionRsp, error) { + err := s.gameService.AddTeamAction( + ctx, + int(req.Id), + req.Password, + req.Code, + false, + ) + if err != nil { + return &proto.AddTeamActionRsp{ + Error: err.Error(), + }, nil + } + + return &proto.AddTeamActionRsp{}, nil +} + +func (s *server) DeleteLastTeamAction(ctx context.Context, req *proto.DeleteLastTeamActionReq) (*proto.DeleteLastTeamActionRsp, error) { + claims := ctx.Value("claims").(*processor_jwt.JWTClaims) + if !roles.HasRole(claims, roles.Author) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + err := s.gameService.DeleteLastTeamAction( + ctx, + int(req.Id), + ) + if err != nil { + return &proto.DeleteLastTeamActionRsp{ + Error: err.Error(), + }, nil + } + + return &proto.DeleteLastTeamActionRsp{}, nil +}