generated from VLADIMIR/template
add diffectly
This commit is contained in:
@@ -13,15 +13,16 @@ import (
|
||||
|
||||
func mapGameReview(r *game_review_service.Review) *proto.GameReview {
|
||||
return &proto.GameReview{
|
||||
ID: int32(r.ID),
|
||||
GameID: int32(r.GameID),
|
||||
TeamID: int32(r.TeamID),
|
||||
TeamName: r.TeamName,
|
||||
Rating: r.Rating,
|
||||
Text: r.Text,
|
||||
Status: r.Status,
|
||||
CreatedAt: timestamppb.New(r.CreatedAt),
|
||||
GameName: r.GameName,
|
||||
ID: int32(r.ID),
|
||||
GameID: int32(r.GameID),
|
||||
TeamID: int32(r.TeamID),
|
||||
TeamName: r.TeamName,
|
||||
Rating: r.Rating,
|
||||
Difficulty: r.Difficulty,
|
||||
Text: r.Text,
|
||||
Status: r.Status,
|
||||
CreatedAt: timestamppb.New(r.CreatedAt),
|
||||
GameName: r.GameName,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +35,7 @@ func mapGameReviews(reviews []*game_review_service.Review) []*proto.GameReview {
|
||||
}
|
||||
|
||||
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.Difficulty, req.Text)
|
||||
if err != nil {
|
||||
return nil, reviewErrorStatus(err, "не удалось создать отзыв")
|
||||
}
|
||||
|
||||
@@ -97,12 +97,13 @@ func (s *server) GetScenario(ctx context.Context, req *proto.GetScenarioReq) (*p
|
||||
|
||||
func (s *server) addScenarioRatings(ctx context.Context, scenarios []*scenarios_service.Scenario) error {
|
||||
for _, scenario := range scenarios {
|
||||
average, count, err := s.reviewService.ScenarioRating(ctx, scenario.ID)
|
||||
average, count, difficulty, err := s.reviewService.ScenarioRating(ctx, scenario.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
scenario.AverageRating = average
|
||||
scenario.ReviewsCount = count
|
||||
scenario.Difficulty = difficulty
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ func mapScenario(o *scenarios_service.Scenario) *proto.Scenario {
|
||||
PublishedAt: publishedAt,
|
||||
AverageRating: o.AverageRating,
|
||||
ReviewsCount: o.ReviewsCount,
|
||||
Difficulty: o.Difficulty,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,15 +23,16 @@ var (
|
||||
|
||||
// Review describes a game review.
|
||||
type Review struct {
|
||||
ID int
|
||||
GameID int
|
||||
GameName string
|
||||
TeamID int
|
||||
TeamName string
|
||||
Rating int32
|
||||
Text string
|
||||
Status string
|
||||
CreatedAt time.Time
|
||||
ID int
|
||||
GameID int
|
||||
GameName string
|
||||
TeamID int
|
||||
TeamName string
|
||||
Rating int32
|
||||
Difficulty int32
|
||||
Text string
|
||||
Status string
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
// Repo persists game reviews.
|
||||
@@ -41,13 +42,13 @@ type Repo struct{ pool *pgxpool.Pool }
|
||||
func NewGameReviewsRepo(pool *pgxpool.Pool) *Repo { return &Repo{pool: pool} }
|
||||
|
||||
// Create stores a review.
|
||||
func (r *Repo) Create(ctx context.Context, gameID, teamID int, rating int32, text string) (*Review, error) {
|
||||
func (r *Repo) Create(ctx context.Context, gameID, teamID int, rating, difficulty int32, text string) (*Review, error) {
|
||||
review := &Review{}
|
||||
err := r.pool.QueryRow(
|
||||
ctx,
|
||||
`
|
||||
INSERT INTO game_reviews (game_id, team_id, rating, text)
|
||||
SELECT $1, t.id, $3, $4
|
||||
INSERT INTO game_reviews (game_id, team_id, rating, difficulty, text)
|
||||
SELECT $1, t.id, $3, $4, $5
|
||||
FROM teams t
|
||||
WHERE t.id = $2 AND t.game_id = $1
|
||||
ON CONFLICT (game_id, team_id) DO NOTHING
|
||||
@@ -55,9 +56,10 @@ func (r *Repo) Create(ctx context.Context, gameID, teamID int, rating int32, tex
|
||||
gameID,
|
||||
teamID,
|
||||
rating,
|
||||
difficulty,
|
||||
text,
|
||||
).Scan(
|
||||
&review.ID, &review.GameID, &review.TeamID, &review.Rating, &review.Text, &review.Status, &review.CreatedAt)
|
||||
&review.ID, &review.GameID, &review.TeamID, &review.Rating, &review.Difficulty, &review.Text, &review.Status, &review.CreatedAt)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
var exists bool
|
||||
if err := r.pool.QueryRow(
|
||||
@@ -89,7 +91,7 @@ func (r *Repo) GetByID(ctx context.Context, id int) (*Review, error) {
|
||||
func (r *Repo) List(ctx context.Context, gameID int, all bool) ([]*Review, float64, int32, error) {
|
||||
rows, err := r.pool.Query(
|
||||
ctx,
|
||||
`SELECT r.id, r.game_id, r.team_id, t.name, g.name, r.rating, r.text, r.status, r.created_at
|
||||
`SELECT r.id, r.game_id, r.team_id, t.name, g.name, r.rating, r.difficulty, r.text, r.status, r.created_at
|
||||
FROM game_reviews r
|
||||
JOIN teams t ON t.id = r.team_id
|
||||
JOIN games g ON g.id = r.game_id
|
||||
@@ -105,7 +107,7 @@ func (r *Repo) List(ctx context.Context, gameID int, all bool) ([]*Review, float
|
||||
var reviews []*Review
|
||||
for rows.Next() {
|
||||
review := &Review{}
|
||||
if err := rows.Scan(&review.ID, &review.GameID, &review.TeamID, &review.TeamName, &review.GameName, &review.Rating, &review.Text, &review.Status, &review.CreatedAt); err != nil {
|
||||
if err := rows.Scan(&review.ID, &review.GameID, &review.TeamID, &review.TeamName, &review.GameName, &review.Rating, &review.Difficulty, &review.Text, &review.Status, &review.CreatedAt); err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
reviews = append(reviews, review)
|
||||
@@ -131,7 +133,7 @@ func (r *Repo) List(ctx context.Context, gameID int, all bool) ([]*Review, float
|
||||
func (r *Repo) ListForModeration(ctx context.Context) ([]*Review, error) {
|
||||
rows, err := r.pool.Query(
|
||||
ctx,
|
||||
`SELECT r.id, r.game_id, r.team_id, t.name, g.name, r.rating, r.text, r.status, r.created_at
|
||||
`SELECT r.id, r.game_id, r.team_id, t.name, g.name, r.rating, r.difficulty, r.text, r.status, r.created_at
|
||||
FROM game_reviews r
|
||||
JOIN teams t ON t.id = r.team_id
|
||||
JOIN games g ON g.id = r.game_id
|
||||
@@ -144,7 +146,7 @@ func (r *Repo) ListForModeration(ctx context.Context) ([]*Review, error) {
|
||||
var reviews []*Review
|
||||
for rows.Next() {
|
||||
review := &Review{}
|
||||
if err := rows.Scan(&review.ID, &review.GameID, &review.TeamID, &review.TeamName, &review.GameName, &review.Rating, &review.Text, &review.Status, &review.CreatedAt); err != nil {
|
||||
if err := rows.Scan(&review.ID, &review.GameID, &review.TeamID, &review.TeamName, &review.GameName, &review.Rating, &review.Difficulty, &review.Text, &review.Status, &review.CreatedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reviews = append(reviews, review)
|
||||
@@ -175,25 +177,26 @@ func (r *Repo) Moderate(ctx context.Context, id int, status string) (*Review, er
|
||||
}
|
||||
|
||||
// ScenarioRating returns aggregate ratings for a scenario.
|
||||
func (r *Repo) ScenarioRating(ctx context.Context, scenarioID int) (float64, int32, error) {
|
||||
func (r *Repo) ScenarioRating(ctx context.Context, scenarioID int) (float64, int32, float64, error) {
|
||||
var average float64
|
||||
var averageDifficulty float64
|
||||
var count int64
|
||||
err := r.pool.QueryRow(
|
||||
ctx,
|
||||
`SELECT COALESCE(AVG(r.rating), 0), COUNT(*)
|
||||
`SELECT COALESCE(AVG(r.rating), 0), COUNT(*), COALESCE(AVG(r.difficulty), 0)
|
||||
FROM game_reviews r
|
||||
JOIN games g ON g.id = r.game_id
|
||||
WHERE g.scenario_id = $1 AND g.status = 'finish' AND r.status = 'approved'`,
|
||||
scenarioID,
|
||||
).Scan(&average, &count)
|
||||
return average, int32(count), err
|
||||
).Scan(&average, &count, &averageDifficulty)
|
||||
return average, int32(count), averageDifficulty, err
|
||||
}
|
||||
|
||||
// ListByScenario returns reviews and aggregate ratings for a scenario.
|
||||
func (r *Repo) ListByScenario(ctx context.Context, scenarioID int) ([]*Review, float64, int32, error) {
|
||||
rows, err := r.pool.Query(
|
||||
ctx,
|
||||
`SELECT r.id, r.game_id, r.team_id, t.name, g.name, r.rating, r.text, r.status, r.created_at
|
||||
`SELECT r.id, r.game_id, r.team_id, t.name, g.name, r.rating, r.difficulty, r.text, r.status, r.created_at
|
||||
FROM game_reviews r
|
||||
JOIN teams t ON t.id = r.team_id
|
||||
JOIN games g ON g.id = r.game_id
|
||||
@@ -208,7 +211,7 @@ func (r *Repo) ListByScenario(ctx context.Context, scenarioID int) ([]*Review, f
|
||||
var reviews []*Review
|
||||
for rows.Next() {
|
||||
review := &Review{}
|
||||
if err := rows.Scan(&review.ID, &review.GameID, &review.TeamID, &review.TeamName, &review.GameName, &review.Rating, &review.Text, &review.Status, &review.CreatedAt); err != nil {
|
||||
if err := rows.Scan(&review.ID, &review.GameID, &review.TeamID, &review.TeamName, &review.GameName, &review.Rating, &review.Difficulty, &review.Text, &review.Status, &review.CreatedAt); err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
reviews = append(reviews, review)
|
||||
@@ -216,7 +219,7 @@ func (r *Repo) ListByScenario(ctx context.Context, scenarioID int) ([]*Review, f
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
average, count, err := r.ScenarioRating(ctx, scenarioID)
|
||||
average, count, _, err := r.ScenarioRating(ctx, scenarioID)
|
||||
return reviews, average, count, err
|
||||
}
|
||||
|
||||
@@ -224,14 +227,14 @@ func (r *Repo) queryOne(ctx context.Context, id int) (*Review, error) {
|
||||
review := &Review{}
|
||||
err := r.pool.QueryRow(
|
||||
ctx,
|
||||
`SELECT r.id, r.game_id, r.team_id, t.name, g.name, r.rating, r.text, r.status, r.created_at
|
||||
`SELECT r.id, r.game_id, r.team_id, t.name, g.name, r.rating, r.difficulty, r.text, r.status, r.created_at
|
||||
FROM game_reviews r
|
||||
JOIN teams t ON t.id = r.team_id
|
||||
JOIN games g ON g.id = r.game_id
|
||||
WHERE r.id = $1`,
|
||||
id,
|
||||
).Scan(
|
||||
&review.ID, &review.GameID, &review.TeamID, &review.TeamName, &review.GameName, &review.Rating, &review.Text, &review.Status, &review.CreatedAt)
|
||||
&review.ID, &review.GameID, &review.TeamID, &review.TeamName, &review.GameName, &review.Rating, &review.Difficulty, &review.Text, &review.Status, &review.CreatedAt)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, ErrReviewNotFound
|
||||
}
|
||||
|
||||
@@ -9,15 +9,16 @@ import (
|
||||
|
||||
// Review describes a game review exposed by the service layer.
|
||||
type Review struct {
|
||||
ID int
|
||||
GameID int
|
||||
GameName string
|
||||
TeamID int
|
||||
TeamName string
|
||||
Rating int32
|
||||
Text string
|
||||
Status string
|
||||
CreatedAt time.Time
|
||||
ID int
|
||||
GameID int
|
||||
GameName string
|
||||
TeamID int
|
||||
TeamName string
|
||||
Rating int32
|
||||
Difficulty int32
|
||||
Text string
|
||||
Status string
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
func mapReview(review *game_reviews_repo.Review) *Review {
|
||||
@@ -25,15 +26,16 @@ func mapReview(review *game_reviews_repo.Review) *Review {
|
||||
return nil
|
||||
}
|
||||
return &Review{
|
||||
ID: review.ID,
|
||||
GameID: review.GameID,
|
||||
GameName: review.GameName,
|
||||
TeamID: review.TeamID,
|
||||
TeamName: review.TeamName,
|
||||
Rating: review.Rating,
|
||||
Text: review.Text,
|
||||
Status: review.Status,
|
||||
CreatedAt: review.CreatedAt,
|
||||
ID: review.ID,
|
||||
GameID: review.GameID,
|
||||
GameName: review.GameName,
|
||||
TeamID: review.TeamID,
|
||||
TeamName: review.TeamName,
|
||||
Rating: review.Rating,
|
||||
Difficulty: review.Difficulty,
|
||||
Text: review.Text,
|
||||
Status: review.Status,
|
||||
CreatedAt: review.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ var (
|
||||
// ErrGameNotFinished indicates that the game is not finished.
|
||||
ErrGameNotFinished = errors.New("оценить можно только завершённую игру")
|
||||
// ErrInvalidReview indicates that review data is invalid.
|
||||
ErrInvalidReview = errors.New("оценка должна быть от 1 до 5, текст обязателен")
|
||||
ErrInvalidReview = errors.New("оценка должна быть от 1 до 5, сложность от 1 до 3, текст обязателен")
|
||||
)
|
||||
|
||||
// GameReviewService handles game reviews.
|
||||
@@ -49,8 +49,8 @@ func NewGameReviewService(reviews *game_reviews_repo.Repo, games *games_repo.Gam
|
||||
}
|
||||
|
||||
// Create stores a game review.
|
||||
func (s *GameReviewService) Create(ctx context.Context, gameID, teamID int, password string, rating int32, text string) (*Review, error) {
|
||||
if rating < 1 || rating > 5 || strings.TrimSpace(text) == "" {
|
||||
func (s *GameReviewService) Create(ctx context.Context, gameID, teamID int, password string, rating, difficulty int32, text string) (*Review, error) {
|
||||
if rating < 1 || rating > 5 || difficulty < 1 || difficulty > 3 || strings.TrimSpace(text) == "" {
|
||||
return nil, ErrInvalidReview
|
||||
}
|
||||
team, err := s.teams.GetTeamByID(ctx, teamID)
|
||||
@@ -73,7 +73,7 @@ func (s *GameReviewService) Create(ctx context.Context, gameID, teamID int, pass
|
||||
if game.Status != "finish" {
|
||||
return nil, ErrGameNotFinished
|
||||
}
|
||||
review, err := s.reviews.Create(ctx, gameID, teamID, rating, strings.TrimSpace(text))
|
||||
review, err := s.reviews.Create(ctx, gameID, teamID, rating, difficulty, strings.TrimSpace(text))
|
||||
if err != nil {
|
||||
if errors.Is(err, game_reviews_repo.ErrReviewExists) {
|
||||
return nil, ErrReviewExists
|
||||
@@ -117,8 +117,25 @@ func (s *GameReviewService) Moderate(ctx context.Context, id int, status string)
|
||||
}
|
||||
|
||||
// ScenarioRating returns aggregate ratings for a scenario.
|
||||
func (s *GameReviewService) ScenarioRating(ctx context.Context, scenarioID int) (float64, int32, error) {
|
||||
return s.reviews.ScenarioRating(ctx, scenarioID)
|
||||
func (s *GameReviewService) ScenarioRating(ctx context.Context, scenarioID int) (float64, int32, string, error) {
|
||||
average, count, averageDifficulty, err := s.reviews.ScenarioRating(ctx, scenarioID)
|
||||
if err != nil {
|
||||
return 0, 0, "", err
|
||||
}
|
||||
return average, count, difficultyCategory(averageDifficulty, count), nil
|
||||
}
|
||||
|
||||
func difficultyCategory(average float64, count int32) string {
|
||||
if count == 0 {
|
||||
return ""
|
||||
}
|
||||
if average <= 1.66 {
|
||||
return "лёгкая"
|
||||
}
|
||||
if average <= 2.33 {
|
||||
return "средняя"
|
||||
}
|
||||
return "тяжёлая"
|
||||
}
|
||||
|
||||
// ListByScenario returns reviews for a scenario.
|
||||
|
||||
@@ -33,3 +33,20 @@ func TestReviewReminderEmail(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDifficultyCategory(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
average float64
|
||||
count int32
|
||||
want string
|
||||
}{
|
||||
{average: 0, count: 0, want: ""},
|
||||
{average: 1.66, count: 1, want: "лёгкая"},
|
||||
{average: 2, count: 1, want: "средняя"},
|
||||
{average: 2.34, count: 1, want: "тяжёлая"},
|
||||
} {
|
||||
if got := difficultyCategory(test.average, test.count); got != test.want {
|
||||
t.Errorf("difficultyCategory(%v, %d) = %q, want %q", test.average, test.count, got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,5 +21,6 @@ type Scenario struct {
|
||||
PublishedAt *time.Time
|
||||
AverageRating float64
|
||||
ReviewsCount int32
|
||||
Difficulty string
|
||||
IsDeleted bool
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user