This commit is contained in:
2026-10-03 01:16:40 +07:00
parent 3f7731411b
commit c69dfb8503
10 changed files with 234 additions and 59 deletions
+6 -6
View File
@@ -36,7 +36,7 @@ func mapGameReviews(reviews []*game_review_service.Review) []*proto.GameReview {
func (s *server) CreateGameReview(ctx context.Context, req *proto.CreateGameReviewReq) (*proto.CreateGameReviewRsp, error) { func (s *server) CreateGameReview(ctx context.Context, req *proto.CreateGameReviewReq) (*proto.CreateGameReviewRsp, error) {
review, err := s.reviewService.Create(ctx, int(req.GameID), int(req.TeamID), req.Password, req.Rating, req.Text) review, err := s.reviewService.Create(ctx, int(req.GameID), int(req.TeamID), req.Password, req.Rating, req.Text)
if err != nil { if err != nil {
return &proto.CreateGameReviewRsp{Error: err.Error()}, nil return nil, reviewErrorStatus(err, "не удалось создать отзыв")
} }
return &proto.CreateGameReviewRsp{Review: mapGameReview(review)}, nil return &proto.CreateGameReviewRsp{Review: mapGameReview(review)}, nil
} }
@@ -44,7 +44,7 @@ func (s *server) CreateGameReview(ctx context.Context, req *proto.CreateGameRevi
func (s *server) GetGameReviews(ctx context.Context, req *proto.GetGameReviewsReq) (*proto.GetGameReviewsRsp, error) { func (s *server) GetGameReviews(ctx context.Context, req *proto.GetGameReviewsReq) (*proto.GetGameReviewsRsp, error) {
reviews, average, count, err := s.reviewService.List(ctx, int(req.GameID), false) reviews, average, count, err := s.reviewService.List(ctx, int(req.GameID), false)
if err != nil { if err != nil {
return &proto.GetGameReviewsRsp{Error: err.Error()}, nil return nil, reviewErrorStatus(err, "не удалось получить отзывы игры")
} }
return &proto.GetGameReviewsRsp{AverageRating: average, ReviewsCount: count, Reviews: mapGameReviews(reviews)}, nil return &proto.GetGameReviewsRsp{AverageRating: average, ReviewsCount: count, Reviews: mapGameReviews(reviews)}, nil
} }
@@ -52,7 +52,7 @@ func (s *server) GetGameReviews(ctx context.Context, req *proto.GetGameReviewsRe
func (s *server) GetScenarioReviews(ctx context.Context, req *proto.GetScenarioReviewsReq) (*proto.GetGameReviewsRsp, error) { func (s *server) GetScenarioReviews(ctx context.Context, req *proto.GetScenarioReviewsReq) (*proto.GetGameReviewsRsp, error) {
reviews, average, count, err := s.reviewService.ListByScenario(ctx, int(req.ScenarioID)) reviews, average, count, err := s.reviewService.ListByScenario(ctx, int(req.ScenarioID))
if err != nil { if err != nil {
return &proto.GetGameReviewsRsp{Error: err.Error()}, nil return nil, reviewErrorStatus(err, "не удалось получить отзывы сценария")
} }
return &proto.GetGameReviewsRsp{AverageRating: average, ReviewsCount: count, Reviews: mapGameReviews(reviews)}, nil return &proto.GetGameReviewsRsp{AverageRating: average, ReviewsCount: count, Reviews: mapGameReviews(reviews)}, nil
} }
@@ -67,7 +67,7 @@ func (s *server) GetGameReviewsForModeration(ctx context.Context, req *proto.Get
} }
reviews, average, count, err := s.reviewService.List(ctx, int(req.GameID), true) reviews, average, count, err := s.reviewService.List(ctx, int(req.GameID), true)
if err != nil { if err != nil {
return &proto.GetGameReviewsRsp{Error: err.Error()}, nil return nil, reviewErrorStatus(err, "не удалось получить отзывы для модерации")
} }
return &proto.GetGameReviewsRsp{AverageRating: average, ReviewsCount: count, Reviews: mapGameReviews(reviews)}, nil return &proto.GetGameReviewsRsp{AverageRating: average, ReviewsCount: count, Reviews: mapGameReviews(reviews)}, nil
} }
@@ -82,7 +82,7 @@ func (s *server) GetReviewsForModeration(ctx context.Context, req *proto.GetRevi
} }
reviews, err := s.reviewService.ListForModeration(ctx) reviews, err := s.reviewService.ListForModeration(ctx)
if err != nil { if err != nil {
return &proto.GetGameReviewsRsp{Error: err.Error()}, nil return nil, reviewErrorStatus(err, "не удалось получить отзывы для модерации")
} }
return &proto.GetGameReviewsRsp{Reviews: mapGameReviews(reviews)}, nil return &proto.GetGameReviewsRsp{Reviews: mapGameReviews(reviews)}, nil
} }
@@ -97,7 +97,7 @@ func (s *server) ModerateGameReview(ctx context.Context, req *proto.ModerateGame
} }
review, err := s.reviewService.Moderate(ctx, int(req.ID), req.Status) review, err := s.reviewService.Moderate(ctx, int(req.ID), req.Status)
if err != nil { if err != nil {
return &proto.ModerateGameReviewRsp{Error: err.Error()}, nil return nil, reviewErrorStatus(err, "не удалось изменить статус отзыва")
} }
return &proto.ModerateGameReviewRsp{Review: mapGameReview(review)}, nil return &proto.ModerateGameReviewRsp{Review: mapGameReview(review)}, nil
} }
+23
View File
@@ -0,0 +1,23 @@
package app
import (
"errors"
"evening_detective_server/internal/services/game_review_service"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func reviewErrorStatus(err error, internalMessage string) error {
switch {
case errors.Is(err, game_review_service.ErrTeamNotFound), errors.Is(err, game_review_service.ErrGameNotFound), errors.Is(err, game_review_service.ErrReviewNotFound):
return status.Error(codes.NotFound, err.Error())
case errors.Is(err, game_review_service.ErrInvalidReview), errors.Is(err, game_review_service.ErrInvalidStatus):
return status.Error(codes.InvalidArgument, err.Error())
case errors.Is(err, game_review_service.ErrGameNotFinished), errors.Is(err, game_review_service.ErrReviewExists):
return status.Error(codes.FailedPrecondition, err.Error())
default:
return status.Error(codes.Internal, internalMessage)
}
}
@@ -0,0 +1,48 @@
package app
import (
"errors"
"strings"
"testing"
"evening_detective_server/internal/services/game_review_service"
"evening_detective_server/internal/services/ticket_service"
"evening_detective_server/internal/services/users_service"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func TestReviewErrorStatus(t *testing.T) {
err := reviewErrorStatus(game_review_service.ErrInvalidReview, "review failed")
if status.Code(err) != codes.InvalidArgument || status.Convert(err).Message() != game_review_service.ErrInvalidReview.Error() {
t.Fatalf("status=%v", err)
}
err = reviewErrorStatus(errors.New("private database detail"), "review failed")
if status.Code(err) != codes.Internal || status.Convert(err).Message() != "review failed" {
t.Fatalf("internal status=%v", err)
}
}
func TestTicketErrorStatus(t *testing.T) {
err := ticketErrorStatus(ticket_service.ErrTicketUsed, "ticket failed")
if status.Code(err) != codes.FailedPrecondition || status.Convert(err).Message() != ticket_service.ErrTicketUsed.Error() {
t.Fatalf("status=%v", err)
}
err = ticketErrorStatus(errors.New("private database detail"), "ticket failed")
if status.Code(err) != codes.Internal || status.Convert(err).Message() != "ticket failed" {
t.Fatalf("internal status=%v", err)
}
}
func TestUserErrorStatusDoesNotExposeCredentials(t *testing.T) {
err := userErrorStatus(users_service.ErrInvalidCredentials, "login failed")
if status.Code(err) != codes.Unauthenticated || status.Convert(err).Message() != users_service.ErrInvalidCredentials.Error() {
t.Fatalf("status=%v", err)
}
for _, secret := range []string{"password", "token", "private database detail"} {
if strings.Contains(status.Convert(err).Message(), secret) {
t.Fatalf("secret %q exposed", secret)
}
}
}
+25
View File
@@ -0,0 +1,25 @@
package app
import (
"errors"
"evening_detective_server/internal/services/ticket_service"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func ticketErrorStatus(err error, internalMessage string) error {
switch {
case errors.Is(err, ticket_service.ErrTicketNotFound):
return status.Error(codes.NotFound, err.Error())
case errors.Is(err, ticket_service.ErrInvalidTicketCount):
return status.Error(codes.InvalidArgument, err.Error())
case errors.Is(err, ticket_service.ErrTicketUsed), errors.Is(err, ticket_service.ErrGameFinished):
return status.Error(codes.FailedPrecondition, err.Error())
case errors.Is(err, ticket_service.ErrInvalidBaseURL):
return status.Error(codes.FailedPrecondition, err.Error())
default:
return status.Error(codes.Internal, internalMessage)
}
}
+2 -2
View File
@@ -53,7 +53,7 @@ func (s *server) AcceptTicket(ctx context.Context, req *proto.AcceptTicketReq) (
} }
page, err := s.ticketService.Accept(ctx, req.Token, claims.UserID) page, err := s.ticketService.Accept(ctx, req.Token, claims.UserID)
if err != nil { if err != nil {
return &proto.AcceptTicketRsp{Error: err.Error()}, nil return nil, ticketErrorStatus(err, "не удалось принять билет")
} }
result := &proto.TicketPage{ID: int32(page.ID), GameName: page.GameName, StartAt: timestamppb.New(page.StartAt), Status: page.Status, GameStatus: page.GameStatus} result := &proto.TicketPage{ID: int32(page.ID), GameName: page.GameName, StartAt: timestamppb.New(page.StartAt), Status: page.Status, GameStatus: page.GameStatus}
if page.Location != nil { if page.Location != nil {
@@ -82,7 +82,7 @@ func (s *server) IssueGameTickets(ctx context.Context, req *proto.IssueGameTicke
tickets, err := s.ticketService.Issue(ctx, int(req.GameID), int(req.Count)) tickets, err := s.ticketService.Issue(ctx, int(req.GameID), int(req.Count))
if err != nil { if err != nil {
return &proto.IssueGameTicketsRsp{Error: err.Error()}, nil return nil, ticketErrorStatus(err, "не удалось выпустить билеты")
} }
result := make([]*proto.Ticket, 0, len(tickets)) result := make([]*proto.Ticket, 0, len(tickets))
+26
View File
@@ -0,0 +1,26 @@
package app
import (
"errors"
"evening_detective_server/internal/repos/users_repo"
"evening_detective_server/internal/services/users_service"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func userErrorStatus(err error, internalMessage string) error {
switch {
case errors.Is(err, users_service.ErrUserNotFound):
return status.Error(codes.NotFound, err.Error())
case errors.Is(err, users_service.ErrTermsNotAccepted), errors.Is(err, users_service.ErrInvalidEmail), errors.Is(err, users_service.ErrInvalidRole), errors.Is(err, users_service.ErrInvalidPasswordChange):
return status.Error(codes.InvalidArgument, err.Error())
case errors.Is(err, users_service.ErrInvalidCredentials), errors.Is(err, users_repo.ErrUserNotFound):
return status.Error(codes.Unauthenticated, users_service.ErrInvalidCredentials.Error())
case errors.Is(err, users_service.ErrInvalidAccountPassword):
return status.Error(codes.PermissionDenied, err.Error())
default:
return status.Error(codes.Internal, internalMessage)
}
}
+13 -30
View File
@@ -28,9 +28,7 @@ func (s *server) Signup(ctx context.Context, req *proto.SignupReq) (*proto.Signu
userAgent, userAgent,
) )
if err != nil { if err != nil {
return &proto.SignupRsp{ return nil, userErrorStatus(err, "не удалось зарегистрировать пользователя")
Error: err.Error(),
}, nil
} }
return &proto.SignupRsp{}, nil return &proto.SignupRsp{}, nil
} }
@@ -51,9 +49,7 @@ func (s *server) DeleteAccount(ctx context.Context, req *proto.DeleteAccountReq)
} }
err := s.usersService.DeleteAccount(ctx, claims.UserID, password) err := s.usersService.DeleteAccount(ctx, claims.UserID, password)
if err != nil { if err != nil {
return &proto.DeleteAccountRsp{ return nil, userErrorStatus(err, "не удалось удалить аккаунт")
Error: err.Error(),
}, nil
} }
return &proto.DeleteAccountRsp{}, nil return &proto.DeleteAccountRsp{}, nil
} }
@@ -117,9 +113,7 @@ func clientInfoFromContext(ctx context.Context) (ip string, userAgent string) {
func (s *server) RefreshPassword(ctx context.Context, req *proto.RefreshPasswordReq) (*proto.RefreshPasswordRsp, error) { func (s *server) RefreshPassword(ctx context.Context, req *proto.RefreshPasswordReq) (*proto.RefreshPasswordRsp, error) {
err := s.usersService.RefreshPassword(ctx, req.Email) err := s.usersService.RefreshPassword(ctx, req.Email)
if err != nil { if err != nil {
return &proto.RefreshPasswordRsp{ return nil, userErrorStatus(err, "не удалось обновить пароль")
Error: err.Error(),
}, nil
} }
return &proto.RefreshPasswordRsp{}, nil return &proto.RefreshPasswordRsp{}, nil
} }
@@ -127,9 +121,7 @@ func (s *server) RefreshPassword(ctx context.Context, req *proto.RefreshPassword
func (s *server) Login(ctx context.Context, req *proto.LoginReq) (*proto.LoginRsp, error) { func (s *server) Login(ctx context.Context, req *proto.LoginReq) (*proto.LoginRsp, error) {
accessToken, refreshToken, err := s.usersService.Login(ctx, req.Email, req.Password) accessToken, refreshToken, err := s.usersService.Login(ctx, req.Email, req.Password)
if err != nil { if err != nil {
return &proto.LoginRsp{ return nil, userErrorStatus(err, "не удалось выполнить вход")
Error: err.Error(),
}, nil
} }
return &proto.LoginRsp{ return &proto.LoginRsp{
AccessToken: accessToken, AccessToken: accessToken,
@@ -140,9 +132,10 @@ func (s *server) Login(ctx context.Context, req *proto.LoginReq) (*proto.LoginRs
func (s *server) Refresh(ctx context.Context, req *proto.RefreshReq) (*proto.RefreshRsp, error) { func (s *server) Refresh(ctx context.Context, req *proto.RefreshReq) (*proto.RefreshRsp, error) {
accessToken, refreshToken, err := s.usersService.Refresh(ctx, req.RefreshToken) accessToken, refreshToken, err := s.usersService.Refresh(ctx, req.RefreshToken)
if err != nil { if err != nil {
return &proto.RefreshRsp{ if errors.Is(err, users_service.ErrInvalidRefreshToken) {
Error: err.Error(), return nil, status.Error(codes.Unauthenticated, err.Error())
}, nil }
return nil, userErrorStatus(err, "не удалось обновить сессию")
} }
return &proto.RefreshRsp{ return &proto.RefreshRsp{
AccessToken: accessToken, AccessToken: accessToken,
@@ -160,9 +153,7 @@ func (s *server) GetUsers(ctx context.Context, req *proto.GetUsersReq) (*proto.G
} }
users, err := s.usersService.GetUsers(ctx) users, err := s.usersService.GetUsers(ctx)
if err != nil { if err != nil {
return &proto.GetUsersRsp{ return nil, userErrorStatus(err, "не удалось получить пользователей")
Error: err.Error(),
}, nil
} }
resUsers := make([]*proto.User, 0, len(users)) resUsers := make([]*proto.User, 0, len(users))
@@ -177,9 +168,7 @@ func (s *server) GetUsers(ctx context.Context, req *proto.GetUsersReq) (*proto.G
func (s *server) GetUserByID(ctx context.Context, req *proto.GetUserByIDReq) (*proto.GetUserByIDRsp, error) { func (s *server) GetUserByID(ctx context.Context, req *proto.GetUserByIDReq) (*proto.GetUserByIDRsp, error) {
user, err := s.usersService.GetUserByID(ctx, int(req.ID)) user, err := s.usersService.GetUserByID(ctx, int(req.ID))
if err != nil { if err != nil {
return &proto.GetUserByIDRsp{ return nil, userErrorStatus(err, "не удалось получить пользователя")
Error: err.Error(),
}, nil
} }
return &proto.GetUserByIDRsp{ return &proto.GetUserByIDRsp{
@@ -194,9 +183,7 @@ func (s *server) GetMe(ctx context.Context, req *proto.GetMeReq) (*proto.GetMeRs
} }
user, err := s.usersService.GetUserByID(ctx, claims.UserID) user, err := s.usersService.GetUserByID(ctx, claims.UserID)
if err != nil { if err != nil {
return &proto.GetMeRsp{ return nil, userErrorStatus(err, "не удалось получить профиль")
Error: err.Error(),
}, nil
} }
return &proto.GetMeRsp{ return &proto.GetMeRsp{
@@ -213,9 +200,7 @@ func (s *server) AddUserRole(ctx context.Context, req *proto.AddUserRoleReq) (*p
return nil, status.Errorf(codes.PermissionDenied, "permission denied") return nil, status.Errorf(codes.PermissionDenied, "permission denied")
} }
if err := s.usersService.AddUserRole(ctx, int(req.ID), req.Role); err != nil { if err := s.usersService.AddUserRole(ctx, int(req.ID), req.Role); err != nil {
return &proto.AddUserRoleRsp{ return nil, userErrorStatus(err, "не удалось добавить роль")
Error: err.Error(),
}, nil
} }
return &proto.AddUserRoleRsp{}, nil return &proto.AddUserRoleRsp{}, nil
} }
@@ -229,9 +214,7 @@ func (s *server) DeleteUserRole(ctx context.Context, req *proto.DeleteUserRoleRe
return nil, status.Errorf(codes.PermissionDenied, "permission denied") return nil, status.Errorf(codes.PermissionDenied, "permission denied")
} }
if err := s.usersService.DeleteUserRole(ctx, int(req.ID), req.Role); err != nil { if err := s.usersService.DeleteUserRole(ctx, int(req.ID), req.Role); err != nil {
return &proto.DeleteUserRoleRsp{ return nil, userErrorStatus(err, "не удалось удалить роль")
Error: err.Error(),
}, nil
} }
return &proto.DeleteUserRoleRsp{}, nil return &proto.DeleteUserRoleRsp{}, nil
} }
@@ -18,6 +18,11 @@ import (
) )
var ( var (
ErrTeamNotFound = errors.New("Команда не найдена")
ErrGameNotFound = errors.New("Игра не найдена")
ErrReviewNotFound = errors.New("Отзыв не найден")
ErrReviewExists = errors.New("Отзыв от команды уже оставлен")
ErrInvalidStatus = errors.New("Недопустимый статус модерации")
ErrGameNotFinished = errors.New("Оценить можно только завершённую игру") ErrGameNotFinished = errors.New("Оценить можно только завершённую игру")
ErrInvalidReview = errors.New("Оценка должна быть от 1 до 5, текст обязателен") ErrInvalidReview = errors.New("Оценка должна быть от 1 до 5, текст обязателен")
) )
@@ -39,11 +44,20 @@ func (s *GameReviewService) Create(ctx context.Context, gameID, teamID int, pass
return nil, ErrInvalidReview return nil, ErrInvalidReview
} }
team, err := s.teams.GetTeamByID(ctx, teamID) team, err := s.teams.GetTeamByID(ctx, teamID)
if err != nil || team.GameID != gameID || team.Password != password || team.ParticipationStatus == "cancelled" { if err != nil {
return nil, teams_repo.ErrTeamNotFound if errors.Is(err, teams_repo.ErrTeamNotFound) {
return nil, ErrTeamNotFound
}
return nil, err
}
if team.GameID != gameID || team.Password != password || team.ParticipationStatus == "cancelled" {
return nil, ErrTeamNotFound
} }
game, err := s.games.GetGameByID(ctx, gameID) game, err := s.games.GetGameByID(ctx, gameID)
if err != nil { if err != nil {
if errors.Is(err, games_repo.ErrGameNotFound) {
return nil, ErrGameNotFound
}
return nil, err return nil, err
} }
if game.Status != "finish" { if game.Status != "finish" {
@@ -51,6 +65,9 @@ func (s *GameReviewService) Create(ctx context.Context, gameID, teamID int, pass
} }
review, err := s.reviews.Create(ctx, gameID, teamID, rating, strings.TrimSpace(text)) review, err := s.reviews.Create(ctx, gameID, teamID, rating, strings.TrimSpace(text))
if err != nil { if err != nil {
if errors.Is(err, game_reviews_repo.ErrReviewExists) {
return nil, ErrReviewExists
}
return nil, err return nil, err
} }
return mapReview(review), nil return mapReview(review), nil
@@ -58,6 +75,9 @@ func (s *GameReviewService) Create(ctx context.Context, gameID, teamID int, pass
func (s *GameReviewService) List(ctx context.Context, gameID int, all bool) ([]*Review, float64, int32, error) { func (s *GameReviewService) List(ctx context.Context, gameID int, all bool) ([]*Review, float64, int32, error) {
if _, err := s.games.GetGameByID(ctx, gameID); err != nil { if _, err := s.games.GetGameByID(ctx, gameID); err != nil {
if errors.Is(err, games_repo.ErrGameNotFound) {
return nil, 0, 0, ErrGameNotFound
}
return nil, 0, 0, err return nil, 0, 0, err
} }
reviews, average, count, err := s.reviews.List(ctx, gameID, all) reviews, average, count, err := s.reviews.List(ctx, gameID, all)
@@ -72,6 +92,12 @@ func (s *GameReviewService) ListForModeration(ctx context.Context) ([]*Review, e
func (s *GameReviewService) Moderate(ctx context.Context, id int, status string) (*Review, error) { func (s *GameReviewService) Moderate(ctx context.Context, id int, status string) (*Review, error) {
review, err := s.reviews.Moderate(ctx, id, status) review, err := s.reviews.Moderate(ctx, id, status)
if err != nil { if err != nil {
if errors.Is(err, game_reviews_repo.ErrReviewNotFound) {
return nil, ErrReviewNotFound
}
if errors.Is(err, game_reviews_repo.ErrInvalidStatus) {
return nil, ErrInvalidStatus
}
return nil, err return nil, err
} }
return mapReview(review), nil return mapReview(review), nil
+18 -5
View File
@@ -19,9 +19,9 @@ const maxTicketsPerRequest = 100
var ( var (
ErrInvalidTicketCount = errors.New("количество билетов должно быть от 1 до 100") ErrInvalidTicketCount = errors.New("количество билетов должно быть от 1 до 100")
ErrInvalidBaseURL = errors.New("необходимо настроить APP_BASE_URL — публичный адрес приложения") ErrInvalidBaseURL = errors.New("необходимо настроить APP_BASE_URL — публичный адрес приложения")
ErrTicketNotFound = tickets_repo.ErrTicketNotFound ErrTicketNotFound = errors.New("Билет не найден")
ErrTicketUsed = tickets_repo.ErrTicketUsed ErrTicketUsed = errors.New("Билет уже использован")
ErrGameFinished = tickets_repo.ErrGameFinished ErrGameFinished = errors.New("Игра уже завершена")
) )
type Ticket struct { type Ticket struct {
@@ -93,7 +93,7 @@ func (s *TicketService) Get(ctx context.Context, token string) (*Page, error) {
} }
info, err := s.repo.GetByHash(ctx, hash) info, err := s.repo.GetByHash(ctx, hash)
if err != nil { if err != nil {
return nil, err return nil, mapTicketError(err)
} }
return &Page{ return &Page{
ID: info.ID, ID: info.ID,
@@ -114,11 +114,24 @@ func (s *TicketService) Accept(ctx context.Context, token string, userID int) (*
} }
info, err := s.repo.Accept(ctx, hash, userID) info, err := s.repo.Accept(ctx, hash, userID)
if err != nil { if err != nil {
return nil, err return nil, mapTicketError(err)
} }
return &Page{ID: info.ID, Status: info.Status, GameName: info.GameName, StartAt: info.StartAt, Location: info.Location, GameStatus: info.GameStatus, UsedAt: info.UsedAt, UsedBy: info.UsedBy}, nil return &Page{ID: info.ID, Status: info.Status, GameName: info.GameName, StartAt: info.StartAt, Location: info.Location, GameStatus: info.GameStatus, UsedAt: info.UsedAt, UsedBy: info.UsedBy}, nil
} }
func mapTicketError(err error) error {
switch {
case errors.Is(err, tickets_repo.ErrTicketNotFound):
return ErrTicketNotFound
case errors.Is(err, tickets_repo.ErrTicketUsed):
return ErrTicketUsed
case errors.Is(err, tickets_repo.ErrGameFinished):
return ErrGameFinished
default:
return err
}
}
func newToken() (string, error) { func newToken() (string, error) {
data := make([]byte, 32) data := make([]byte, 32)
if _, err := rand.Read(data); err != nil { if _, err := rand.Read(data); err != nil {
+45 -14
View File
@@ -82,8 +82,15 @@ func NewUsersService(
} }
} }
// ErrTermsNotAccepted — регистрация невозможна без акцепта соглашений. var (
var ErrTermsNotAccepted = errors.New("Необходимо принять Пользовательское соглашение и Политику конфиденциальности") // ErrTermsNotAccepted — регистрация невозможна без акцепта соглашений.
ErrTermsNotAccepted = errors.New("Необходимо принять Пользовательское соглашение и Политику конфиденциальности")
ErrInvalidEmail = errors.New("Некорректный email")
ErrInvalidCredentials = errors.New("Не верен email или пароль")
ErrInvalidAccountPassword = errors.New("Неверный пароль")
ErrInvalidRefreshToken = errors.New("Пользователь не найден")
ErrInvalidRole = errors.New("Недопустимая роль")
)
func (s *UsersService) AddUser( func (s *UsersService) AddUser(
ctx context.Context, ctx context.Context,
@@ -170,12 +177,19 @@ func (s *UsersService) DeleteAccount(
) error { ) error {
user, err := s.usersRepo.GetUserByID(ctx, userID) user, err := s.usersRepo.GetUserByID(ctx, userID)
if err != nil { if err != nil {
if errors.Is(err, users_repo.ErrUserNotFound) {
return ErrUserNotFound
}
return err return err
} }
if err = bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password)); err != nil { if err = bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password)); err != nil {
return errors.New("Неверный пароль") return ErrInvalidAccountPassword
} }
return s.usersRepo.DeleteUser(ctx, userID) err = s.usersRepo.DeleteUser(ctx, userID)
if errors.Is(err, users_repo.ErrUserNotFound) {
return ErrUserNotFound
}
return err
} }
func (s *UsersService) RefreshPassword( func (s *UsersService) RefreshPassword(
@@ -225,15 +239,15 @@ func (s *UsersService) RefreshPassword(
// validateEmail проверяет, что строка является одиночным email-адресом. // validateEmail проверяет, что строка является одиночным email-адресом.
func validateEmail(email string) error { func validateEmail(email string) error {
if email == "" { if email == "" {
return errors.New("Email не указан") return ErrInvalidEmail
} }
addr, err := mail.ParseAddress(email) addr, err := mail.ParseAddress(email)
if err != nil { if err != nil {
return errors.New("Некорректный email") return ErrInvalidEmail
} }
// Отклоняем форму "Имя <a@b.c>" и всё, что не является чистым адресом. // Отклоняем форму "Имя <a@b.c>" и всё, что не является чистым адресом.
if !strings.EqualFold(addr.Address, email) { if !strings.EqualFold(addr.Address, email) {
return errors.New("Некорректный email") return ErrInvalidEmail
} }
return nil return nil
} }
@@ -249,11 +263,14 @@ func (s *UsersService) Login(
strings.TrimSpace(email), strings.TrimSpace(email),
) )
if err != nil { if err != nil {
if errors.Is(err, users_repo.ErrUserNotFound) {
return "", "", err
}
return "", "", err return "", "", err
} }
if err = bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password)); err != nil { if err = bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password)); err != nil {
return "", "", errors.New("Не верен email или пароль") return "", "", ErrInvalidCredentials
} }
accessToken, err := s.processorJWT.GenerateAccessToken(user.ID, user.Email, user.Roles) accessToken, err := s.processorJWT.GenerateAccessToken(user.ID, user.Email, user.Roles)
@@ -283,7 +300,7 @@ func (s *UsersService) Refresh(
) (string, string, error) { ) (string, string, error) {
claims, err := s.processorJWT.GetClaims(token) claims, err := s.processorJWT.GetClaims(token)
if err != nil { if err != nil {
return "", "", errors.New("Пользователь не найден") return "", "", ErrInvalidRefreshToken
} }
now := time.Now() now := time.Now()
@@ -292,7 +309,7 @@ func (s *UsersService) Refresh(
return "", "", err return "", "", err
} }
if !ok { if !ok {
return "", "", errors.New("Пользователь не найден") return "", "", ErrInvalidRefreshToken
} }
if err := s.refreshTokensRepo.DeleteRefreshTokenByJti(ctx, claims.ID); err != nil { if err := s.refreshTokensRepo.DeleteRefreshTokenByJti(ctx, claims.ID); err != nil {
return "", "", err return "", "", err
@@ -300,6 +317,9 @@ func (s *UsersService) Refresh(
user, err := s.usersRepo.GetUserByID(ctx, claims.UserID) user, err := s.usersRepo.GetUserByID(ctx, claims.UserID)
if err != nil { if err != nil {
if errors.Is(err, users_repo.ErrUserNotFound) {
return "", "", ErrInvalidRefreshToken
}
return "", "", err return "", "", err
} }
@@ -340,6 +360,9 @@ func (s *UsersService) GetUserByID(
) (*User, error) { ) (*User, error) {
user, err := s.usersRepo.GetUserByID(ctx, userID) user, err := s.usersRepo.GetUserByID(ctx, userID)
if err != nil { if err != nil {
if errors.Is(err, users_repo.ErrUserNotFound) {
return nil, ErrUserNotFound
}
return nil, err return nil, err
} }
return mapUser(user), nil return mapUser(user), nil
@@ -351,9 +374,13 @@ func (s *UsersService) AddUserRole(
role string, role string,
) error { ) error {
if !slices.Contains(roles.AllRoles, role) { if !slices.Contains(roles.AllRoles, role) {
return fmt.Errorf("role %s not support", role) return ErrInvalidRole
} }
return s.usersRepo.AddUserRole(ctx, userID, role) err := s.usersRepo.AddUserRole(ctx, userID, role)
if errors.Is(err, users_repo.ErrUserNotFound) {
return ErrUserNotFound
}
return err
} }
func (s *UsersService) DeleteUserRole( func (s *UsersService) DeleteUserRole(
@@ -362,9 +389,13 @@ func (s *UsersService) DeleteUserRole(
role string, role string,
) error { ) error {
if !slices.Contains(roles.AllRoles, role) { if !slices.Contains(roles.AllRoles, role) {
return fmt.Errorf("role %s not support", role) return ErrInvalidRole
} }
return s.usersRepo.DeleteUserRole(ctx, userID, role) err := s.usersRepo.DeleteUserRole(ctx, userID, role)
if errors.Is(err, users_repo.ErrUserNotFound) {
return ErrUserNotFound
}
return err
} }
var ( var (