This commit is contained in:
2026-10-03 00:55:28 +07:00
parent bd1120d10f
commit fc2586f72a
6 changed files with 109 additions and 69 deletions
+8 -7
View File
@@ -36,11 +36,11 @@ func (s *server) DownloadScenarioArchive(ctx context.Context, req *proto.Downloa
if err != nil {
switch {
case errors.Is(err, scenarios_service.ErrScenarioNotFound):
return nil, status.Errorf(codes.NotFound, "%v", err)
return nil, status.Error(codes.NotFound, err.Error())
case errors.Is(err, scenarios_service.ErrScenarioNotOwner):
return nil, status.Errorf(codes.PermissionDenied, "%v", err)
return nil, status.Error(codes.PermissionDenied, err.Error())
default:
return nil, status.Errorf(codes.Internal, "%v", err)
return nil, status.Error(codes.Internal, "не удалось скачать архив сценария")
}
}
@@ -52,7 +52,7 @@ func (s *server) DownloadScenarioArchive(ctx context.Context, req *proto.Downloa
}
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 nil, status.Error(codes.Internal, "не удалось установить заголовки архива")
}
return &httpbody.HttpBody{
@@ -78,9 +78,10 @@ func (s *server) UploadScenarioArchive(ctx context.Context, req *httpbody.HttpBo
id, err := s.scenarioService.UploadArchive(ctx, req.Data, claims.UserID)
if err != nil {
return &proto.UploadScenarioArchiveRsp{
Error: err.Error(),
}, nil
if errors.Is(err, scenarios_service.ErrInvalidArchive) {
return nil, status.Error(codes.InvalidArgument, scenarios_service.ErrInvalidArchive.Error())
}
return nil, status.Error(codes.Internal, "не удалось загрузить архив сценария")
}
return &proto.UploadScenarioArchiveRsp{
ID: int32(id),
+25
View File
@@ -0,0 +1,25 @@
package app
import (
"errors"
"evening_detective_server/internal/services/scenarios_service"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func scenarioErrorStatus(err error, internalMessage string) error {
switch {
case errors.Is(err, scenarios_service.ErrScenarioNotFound):
return status.Error(codes.NotFound, err.Error())
case errors.Is(err, scenarios_service.ErrScenarioNotOwner):
return status.Error(codes.PermissionDenied, err.Error())
case errors.Is(err, scenarios_service.ErrScenarioPublished):
return status.Error(codes.FailedPrecondition, err.Error())
case errors.Is(err, scenarios_service.ErrInvalidScenarioData):
return status.Error(codes.InvalidArgument, err.Error())
default:
return status.Error(codes.Internal, internalMessage)
}
}
+35
View File
@@ -0,0 +1,35 @@
package app
import (
"errors"
"testing"
"evening_detective_server/internal/services/scenarios_service"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func TestScenarioErrorStatus(t *testing.T) {
for _, tc := range []struct {
name, internalMessage string
err error
code codes.Code
message string
}{
{"not found", "scenario failed", scenarios_service.ErrScenarioNotFound, codes.NotFound, "Сценарий не найден"},
{"not owner", "scenario failed", scenarios_service.ErrScenarioNotOwner, codes.PermissionDenied, "Сценарий принадлежит другому пользователю"},
{"published", "scenario failed", scenarios_service.ErrScenarioPublished, codes.FailedPrecondition, scenarios_service.ErrScenarioPublished.Error()},
{"internal", "scenario failed", errors.New("private database detail"), codes.Internal, "scenario failed"},
} {
t.Run(tc.name, func(t *testing.T) {
err := scenarioErrorStatus(tc.err, tc.internalMessage)
if status.Code(err) != tc.code || status.Convert(err).Message() != tc.message {
t.Fatalf("status=%v, want %v %q", err, tc.code, tc.message)
}
if status.Convert(err).Message() == "private database detail" {
t.Fatal("private error exposed")
}
})
}
}
+9 -25
View File
@@ -27,9 +27,7 @@ func (s *server) AddScenarioPlace(ctx context.Context, req *proto.AddScenarioPla
roles.HasRole(claims, roles.Admin),
)
if err != nil {
return &proto.AddScenarioPlaceRsp{
Error: err.Error(),
}, nil
return nil, scenarioErrorStatus(err, "не удалось добавить место сценария")
}
return &proto.AddScenarioPlaceRsp{}, nil
@@ -53,9 +51,7 @@ func (s *server) UpdateScenarioPlace(ctx context.Context, req *proto.UpdateScena
roles.HasRole(claims, roles.Admin),
)
if err != nil {
return &proto.UpdateScenarioPlaceRsp{
Error: err.Error(),
}, nil
return nil, scenarioErrorStatus(err, "не удалось изменить место сценария")
}
return &proto.UpdateScenarioPlaceRsp{}, nil
@@ -78,9 +74,7 @@ func (s *server) DeleteScenarioPlace(ctx context.Context, req *proto.DeleteScena
roles.HasRole(claims, roles.Admin),
)
if err != nil {
return &proto.DeleteScenarioPlaceRsp{
Error: err.Error(),
}, nil
return nil, scenarioErrorStatus(err, "не удалось удалить место сценария")
}
return &proto.DeleteScenarioPlaceRsp{}, nil
@@ -103,9 +97,7 @@ func (s *server) AddScenarioQuestion(ctx context.Context, req *proto.AddScenario
roles.HasRole(claims, roles.Admin),
)
if err != nil {
return &proto.AddScenarioQuestionRsp{
Error: err.Error(),
}, nil
return nil, scenarioErrorStatus(err, "не удалось добавить вопрос сценария")
}
return &proto.AddScenarioQuestionRsp{}, nil
@@ -129,9 +121,7 @@ func (s *server) UpdateScenarioQuestion(ctx context.Context, req *proto.UpdateSc
roles.HasRole(claims, roles.Admin),
)
if err != nil {
return &proto.UpdateScenarioQuestionRsp{
Error: err.Error(),
}, nil
return nil, scenarioErrorStatus(err, "не удалось изменить вопрос сценария")
}
return &proto.UpdateScenarioQuestionRsp{}, nil
@@ -154,9 +144,7 @@ func (s *server) DeleteScenarioQuestion(ctx context.Context, req *proto.DeleteSc
roles.HasRole(claims, roles.Admin),
)
if err != nil {
return &proto.DeleteScenarioQuestionRsp{
Error: err.Error(),
}, nil
return nil, scenarioErrorStatus(err, "не удалось удалить вопрос сценария")
}
return &proto.DeleteScenarioQuestionRsp{}, nil
@@ -179,9 +167,7 @@ func (s *server) UpdateScenarioBackground(ctx context.Context, req *proto.Update
roles.HasRole(claims, roles.Admin),
)
if err != nil {
return &proto.UpdateScenarioBackgroundRsp{
Error: err.Error(),
}, nil
return nil, scenarioErrorStatus(err, "не удалось изменить фон сценария")
}
return &proto.UpdateScenarioBackgroundRsp{}, nil
@@ -204,9 +190,7 @@ func (s *server) UpdateScenarioIntro(ctx context.Context, req *proto.UpdateScena
roles.HasRole(claims, roles.Admin),
)
if err != nil {
return &proto.UpdateScenarioIntroRsp{
Error: err.Error(),
}, nil
return nil, scenarioErrorStatus(err, "не удалось изменить введение сценария")
}
return &proto.UpdateScenarioIntroRsp{}, nil
@@ -221,7 +205,7 @@ func (s *server) UpdateScenarioConclusion(ctx context.Context, req *proto.Update
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 nil, scenarioErrorStatus(err, "не удалось изменить заключение сценария")
}
return &proto.UpdateScenarioConclusionRsp{}, nil
}
+11 -29
View File
@@ -22,9 +22,7 @@ func (s *server) AddScenario(ctx context.Context, req *proto.AddScenarioReq) (*p
id, err := s.scenarioService.AddScenario(ctx, req.Name, claims.UserID)
if err != nil {
return &proto.AddScenarioRsp{
Error: err.Error(),
}, nil
return nil, scenarioErrorStatus(err, "не удалось создать сценарий")
}
return &proto.AddScenarioRsp{
ID: int32(id),
@@ -42,9 +40,7 @@ func (s *server) GetMyScenarios(ctx context.Context, req *proto.GetMyScenariosRe
scenarios, err := s.scenarioService.GetScenariosByAuthorID(ctx, claims.UserID)
if err != nil {
return &proto.GetMyScenariosRsp{
Error: err.Error(),
}, nil
return nil, scenarioErrorStatus(err, "не удалось получить сценарии автора")
}
return &proto.GetMyScenariosRsp{
@@ -55,12 +51,10 @@ func (s *server) GetMyScenarios(ctx context.Context, req *proto.GetMyScenariosRe
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 nil, scenarioErrorStatus(err, "не удалось получить каталог сценариев")
}
if err := s.addScenarioRatings(ctx, scenarios); err != nil {
return &proto.GetScenariosCatalogRsp{Error: err.Error()}, nil
return nil, scenarioErrorStatus(err, "не удалось получить рейтинг сценариев")
}
return &proto.GetScenariosCatalogRsp{
@@ -79,9 +73,7 @@ func (s *server) GetFullScenario(ctx context.Context, req *proto.GetScenarioReq)
scenario, err := s.scenarioService.GetFullScenarioByID(ctx, int(req.ID))
if err != nil {
return &proto.GetScenarioRsp{
Error: err.Error(),
}, nil
return nil, scenarioErrorStatus(err, "не удалось получить сценарий")
}
return &proto.GetScenarioRsp{
@@ -92,12 +84,10 @@ func (s *server) GetFullScenario(ctx context.Context, req *proto.GetScenarioReq)
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 nil, scenarioErrorStatus(err, "не удалось получить сценарий")
}
if err := s.addScenarioRatings(ctx, []*scenarios_service.Scenario{scenario}); err != nil {
return &proto.GetScenarioRsp{Error: err.Error()}, nil
return nil, scenarioErrorStatus(err, "не удалось получить рейтинг сценария")
}
return &proto.GetScenarioRsp{
@@ -136,9 +126,7 @@ func (s *server) UpdateScenario(ctx context.Context, req *proto.UpdateScenarioRe
roles.HasRole(claims, roles.Admin),
)
if err != nil {
return &proto.UpdateScenarioRsp{
Error: err.Error(),
}, nil
return nil, scenarioErrorStatus(err, "не удалось изменить сценарий")
}
return &proto.UpdateScenarioRsp{}, nil
@@ -161,9 +149,7 @@ func (s *server) PublicScenario(ctx context.Context, req *proto.PublicScenarioRe
roles.HasRole(claims, roles.Admin), // публикация = момент отчуждения прав
)
if err != nil {
return &proto.PublicScenarioRsp{
Error: err.Error(),
}, nil
return nil, scenarioErrorStatus(err, "не удалось опубликовать сценарий")
}
return &proto.PublicScenarioRsp{}, nil
@@ -189,9 +175,7 @@ func (s *server) DraftScenario(ctx context.Context, req *proto.DraftScenarioReq)
true,
)
if err != nil {
return &proto.DraftScenarioRsp{
Error: err.Error(),
}, nil
return nil, scenarioErrorStatus(err, "не удалось снять сценарий с публикации")
}
return &proto.DraftScenarioRsp{}, nil
@@ -215,9 +199,7 @@ func (s *server) DeleteScenario(ctx context.Context, req *proto.DeleteScenarioRe
roles.HasRole(claims, roles.Admin),
)
if err != nil {
return &proto.DeleteScenarioRsp{
Error: err.Error(),
}, nil
return nil, scenarioErrorStatus(err, "не удалось удалить сценарий")
}
return &proto.DeleteScenarioRsp{}, nil
+21 -8
View File
@@ -91,7 +91,7 @@ func (s *ScenarioService) GetFullScenarioByID(
) (*Scenario, error) {
scenario, err := s.scenariosRepo.GetScenarioByID(ctx, id)
if err != nil {
return nil, err
return nil, mapScenarioError(err)
}
return mapScenario(scenario, s.domain)
}
@@ -102,7 +102,7 @@ func (s *ScenarioService) GetScenarioByID(
) (*Scenario, error) {
scenario, err := s.scenariosRepo.GetScenarioByID(ctx, id)
if err != nil {
return nil, err
return nil, mapScenarioError(err)
}
return MapScenarioLight(scenario, s.domain), nil
}
@@ -134,6 +134,12 @@ var ErrScenarioNotOwner = errors.New("Сценарий принадлежит д
// ErrScenarioNotFound — сценарий не найден или уже удалён.
var ErrScenarioNotFound = errors.New("Сценарий не найден")
// ErrInvalidArchive — архив не соответствует формату сценария.
var ErrInvalidArchive = errors.New("Некорректный архив сценария")
// ErrInvalidScenarioData — данные истории сценария не проходят валидацию.
var ErrInvalidScenarioData = errors.New("Некорректные данные сценария")
// MaxArchiveSize возвращает максимальный размер загружаемого архива.
func MaxArchiveSize() int {
return scenario_archive.MaxArchiveSize()
@@ -150,10 +156,10 @@ func (s *ScenarioService) getScenarioForChange(
) (*repos.Scenario, error) {
scenario, err := s.scenariosRepo.GetScenarioByID(ctx, id)
if err != nil {
return nil, err
return nil, mapScenarioError(err)
}
if scenario.IsDeleted {
return nil, scenarios_repo.ErrScenarioNotFound
return nil, ErrScenarioNotFound
}
if isAdmin {
return scenario, nil
@@ -164,6 +170,13 @@ func (s *ScenarioService) getScenarioForChange(
return scenario, nil
}
func mapScenarioError(err error) error {
if errors.Is(err, scenarios_repo.ErrScenarioNotFound) {
return ErrScenarioNotFound
}
return err
}
func (s *ScenarioService) UpdateScenarioStatusByID(
ctx context.Context,
id int,
@@ -455,7 +468,7 @@ func (s *ScenarioService) UploadArchive(
) (int, error) {
bundle, err := s.scenarioArchive.Unpack(data)
if err != nil {
return 0, err
return 0, fmt.Errorf("%w: %v", ErrInvalidArchive, err)
}
scenario := bundle.Scenario
@@ -482,7 +495,7 @@ func (s *ScenarioService) UploadArchive(
}
content, exists := bundle.Files[archiveRef]
if !exists {
return "", fmt.Errorf("изображение %q не найдено в архиве", archiveRef)
return "", fmt.Errorf("%w: изображение %q не найдено в архиве", ErrInvalidArchive, archiveRef)
}
name, err := file_storage.NewName()
if err != nil {
@@ -535,7 +548,7 @@ func (s *ScenarioService) UploadArchive(
}
storyJSON, err := normalizeStory(scenario.Story)
if err != nil {
return 0, err
return 0, fmt.Errorf("%w: %v", ErrInvalidArchive, err)
}
id, err := s.scenariosRepo.AddScenario(ctx, scenario.Name, authorID)
@@ -571,7 +584,7 @@ func (s *ScenarioService) updateStory(ctx context.Context, id int, story *storyt
}
storyString, err := normalizeStory(story)
if err != nil {
return err
return fmt.Errorf("%w: %v", ErrInvalidScenarioData, err)
}
return s.scenariosRepo.UpdateStoryByScenarioID(ctx, id, storyString)