generated from VLADIMIR/template
add diffectly
This commit is contained in:
+40
-37
@@ -1045,19 +1045,19 @@ message DeleteAccountRsp {
|
||||
message GetTermsReq {}
|
||||
|
||||
message GetTermsRsp {
|
||||
string text = 1;
|
||||
string text = 1;
|
||||
}
|
||||
|
||||
message GetPrivacyReq {}
|
||||
|
||||
message GetPrivacyRsp {
|
||||
string text = 1;
|
||||
string text = 1;
|
||||
}
|
||||
|
||||
message GetConsentReq {}
|
||||
|
||||
message GetConsentRsp {
|
||||
string text = 1;
|
||||
string text = 1;
|
||||
}
|
||||
|
||||
message GetUsersReq {}
|
||||
@@ -1080,13 +1080,13 @@ message GetUserByIDReq {
|
||||
}
|
||||
|
||||
message GetUserByIDRsp {
|
||||
User user = 1;
|
||||
User user = 1;
|
||||
}
|
||||
|
||||
message GetMeReq {}
|
||||
|
||||
message GetMeRsp {
|
||||
User user = 1;
|
||||
User user = 1;
|
||||
}
|
||||
|
||||
message ChangeUserPasswordReq {
|
||||
@@ -1139,7 +1139,7 @@ message AddScenarioReq {
|
||||
}
|
||||
|
||||
message AddScenarioRsp {
|
||||
int32 ID = 1;
|
||||
int32 ID = 1;
|
||||
}
|
||||
|
||||
message GetMyScenariosReq {}
|
||||
@@ -1175,14 +1175,15 @@ message Scenario {
|
||||
google.protobuf.Timestamp published_at = 10;
|
||||
double average_rating = 11;
|
||||
int32 reviews_count = 12;
|
||||
string difficulty = 13;
|
||||
}
|
||||
|
||||
message Story {
|
||||
repeated Place places = 1;
|
||||
Introduction introduction = 2;
|
||||
repeated Question questions = 3;
|
||||
string background = 4;
|
||||
Conclusion conclusion = 5; // Только в полном сценарии, игрокам не передаётся.
|
||||
repeated Place places = 1;
|
||||
Introduction introduction = 2;
|
||||
repeated Question questions = 3;
|
||||
string background = 4;
|
||||
Conclusion conclusion = 5; // Только в полном сценарии, игрокам не передаётся.
|
||||
}
|
||||
|
||||
message Conclusion {
|
||||
@@ -1364,20 +1365,20 @@ message DownloadScenarioArchiveReq {
|
||||
}
|
||||
|
||||
message UploadScenarioArchiveRsp {
|
||||
int32 ID = 1;
|
||||
int32 ID = 1;
|
||||
}
|
||||
|
||||
message AddGameReq {
|
||||
string name = 1;
|
||||
string description = 2;
|
||||
google.protobuf.Timestamp start_at = 3;
|
||||
int32 scenarioID = 4;
|
||||
int32 scenarioID = 4;
|
||||
string format = 5;
|
||||
string location = 6;
|
||||
}
|
||||
|
||||
message AddGameRsp {
|
||||
int32 ID = 1;
|
||||
int32 ID = 1;
|
||||
}
|
||||
|
||||
message GetGamesReq {}
|
||||
@@ -1437,7 +1438,7 @@ enum ScreenCommand {
|
||||
message ScreenState {
|
||||
string ID = 1;
|
||||
string status = 2;
|
||||
optional int32 gameID = 3;
|
||||
optional int32 gameID = 3;
|
||||
ScreenMode mode = 4;
|
||||
bool sound_enabled = 5;
|
||||
string error = 6;
|
||||
@@ -1459,7 +1460,7 @@ message ScreenState {
|
||||
message CreateScreenSessionReq {}
|
||||
|
||||
message CreateScreenSessionRsp {
|
||||
string screenID = 1;
|
||||
string screenID = 1;
|
||||
string access_token = 2;
|
||||
}
|
||||
|
||||
@@ -1507,7 +1508,7 @@ message SetScreenSoundRsp {
|
||||
|
||||
message MapItem {
|
||||
string kind = 1;
|
||||
int32 itemID = 2;
|
||||
int32 itemID = 2;
|
||||
string label = 3;
|
||||
int32 x = 4;
|
||||
int32 y = 5;
|
||||
@@ -1601,22 +1602,22 @@ message ParticipationRsp {
|
||||
}
|
||||
|
||||
message Team {
|
||||
int32 ID = 1;
|
||||
string name = 2;
|
||||
string status = 3;
|
||||
string password = 4;
|
||||
int32 actions_count = 5;
|
||||
repeated Application applications = 6;
|
||||
string captain_email = 7;
|
||||
int32 participants_count = 8;
|
||||
int32 ID = 1;
|
||||
string name = 2;
|
||||
string status = 3;
|
||||
string password = 4;
|
||||
int32 actions_count = 5;
|
||||
repeated Application applications = 6;
|
||||
string captain_email = 7;
|
||||
int32 participants_count = 8;
|
||||
// Статус участия: pending, confirmed или cancelled. Поле панели организатора.
|
||||
string participation_status = 9;
|
||||
string participation_status = 9;
|
||||
// Источник решения: captain, automatic либо пустая строка до ответа.
|
||||
string participation_source = 10;
|
||||
// Последняя ошибка отправки; пустая строка, если ошибки нет.
|
||||
string participation_email_error = 11;
|
||||
// Письмо принято SMTP-сервером; не означает прочтение или доставку во входящие.
|
||||
bool participation_email_sent = 12;
|
||||
bool participation_email_sent = 12;
|
||||
}
|
||||
|
||||
message GetGameReq {
|
||||
@@ -1624,7 +1625,7 @@ message GetGameReq {
|
||||
}
|
||||
|
||||
message GetGameRsp {
|
||||
Game game = 1;
|
||||
Game game = 1;
|
||||
}
|
||||
|
||||
message GetGameAnswersReq {
|
||||
@@ -1668,22 +1669,24 @@ message GetGameResultsRsp {
|
||||
|
||||
message GameReview {
|
||||
int32 ID = 1;
|
||||
int32 gameID = 2;
|
||||
int32 teamID = 3;
|
||||
int32 gameID = 2;
|
||||
int32 teamID = 3;
|
||||
string team_name = 4;
|
||||
int32 rating = 5;
|
||||
string text = 6;
|
||||
string status = 7;
|
||||
google.protobuf.Timestamp created_at = 8;
|
||||
string game_name = 9;
|
||||
int32 difficulty = 10;
|
||||
}
|
||||
|
||||
message CreateGameReviewReq {
|
||||
int32 gameID = 1;
|
||||
int32 teamID = 2;
|
||||
string password = 3;
|
||||
int32 rating = 4;
|
||||
string text = 5;
|
||||
int32 gameID = 1;
|
||||
int32 teamID = 2;
|
||||
string password = 3;
|
||||
int32 rating = 4;
|
||||
string text = 5;
|
||||
int32 difficulty = 6;
|
||||
}
|
||||
|
||||
message CreateGameReviewRsp {
|
||||
@@ -1720,7 +1723,7 @@ message UpdateGameReq {
|
||||
string name = 2;
|
||||
string description = 3;
|
||||
google.protobuf.Timestamp start_at = 4;
|
||||
int32 scenarioID = 5;
|
||||
int32 scenarioID = 5;
|
||||
string format = 6;
|
||||
string location = 7;
|
||||
}
|
||||
@@ -1771,7 +1774,7 @@ message DeleteGameRsp {
|
||||
}
|
||||
|
||||
message AddTeamReq {
|
||||
int32 gameID = 1;
|
||||
int32 gameID = 1;
|
||||
string name = 2;
|
||||
string captain_email = 3;
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -3118,6 +3118,10 @@
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"difficulty": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3710,6 +3714,10 @@
|
||||
},
|
||||
"gameName": {
|
||||
"type": "string"
|
||||
},
|
||||
"difficulty": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -4293,6 +4301,9 @@
|
||||
"reviewsCount": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"difficulty": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
-- +goose Up
|
||||
ALTER TABLE game_reviews
|
||||
ADD COLUMN difficulty INTEGER NOT NULL DEFAULT 2 CHECK (difficulty BETWEEN 1 AND 3);
|
||||
|
||||
-- +goose Down
|
||||
ALTER TABLE game_reviews DROP COLUMN difficulty;
|
||||
+39
-5
@@ -2264,6 +2264,7 @@ type Scenario struct {
|
||||
PublishedAt *timestamppb.Timestamp `protobuf:"bytes,10,opt,name=published_at,json=publishedAt,proto3" json:"published_at,omitempty"`
|
||||
AverageRating float64 `protobuf:"fixed64,11,opt,name=average_rating,json=averageRating,proto3" json:"average_rating,omitempty"`
|
||||
ReviewsCount int32 `protobuf:"varint,12,opt,name=reviews_count,json=reviewsCount,proto3" json:"reviews_count,omitempty"`
|
||||
Difficulty string `protobuf:"bytes,13,opt,name=difficulty,proto3" json:"difficulty,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -2382,6 +2383,13 @@ func (x *Scenario) GetReviewsCount() int32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Scenario) GetDifficulty() string {
|
||||
if x != nil {
|
||||
return x.Difficulty
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type Story struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Places []*Place `protobuf:"bytes,1,rep,name=places,proto3" json:"places,omitempty"`
|
||||
@@ -6734,6 +6742,7 @@ type GameReview struct {
|
||||
Status string `protobuf:"bytes,7,opt,name=status,proto3" json:"status,omitempty"`
|
||||
CreatedAt *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
|
||||
GameName string `protobuf:"bytes,9,opt,name=game_name,json=gameName,proto3" json:"game_name,omitempty"`
|
||||
Difficulty int32 `protobuf:"varint,10,opt,name=difficulty,proto3" json:"difficulty,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -6831,6 +6840,13 @@ func (x *GameReview) GetGameName() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *GameReview) GetDifficulty() int32 {
|
||||
if x != nil {
|
||||
return x.Difficulty
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type CreateGameReviewReq struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
GameID int32 `protobuf:"varint,1,opt,name=gameID,proto3" json:"gameID,omitempty"`
|
||||
@@ -6838,6 +6854,7 @@ type CreateGameReviewReq struct {
|
||||
Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"`
|
||||
Rating int32 `protobuf:"varint,4,opt,name=rating,proto3" json:"rating,omitempty"`
|
||||
Text string `protobuf:"bytes,5,opt,name=text,proto3" json:"text,omitempty"`
|
||||
Difficulty int32 `protobuf:"varint,6,opt,name=difficulty,proto3" json:"difficulty,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -6907,6 +6924,13 @@ func (x *CreateGameReviewReq) GetText() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *CreateGameReviewReq) GetDifficulty() int32 {
|
||||
if x != nil {
|
||||
return x.Difficulty
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type CreateGameReviewRsp struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Review *GameReview `protobuf:"bytes,1,opt,name=review,proto3" json:"review,omitempty"`
|
||||
@@ -9448,7 +9472,7 @@ const file_main_proto_rawDesc = "" +
|
||||
"\x0eGetScenarioReq\x12\x0e\n" +
|
||||
"\x02ID\x18\x01 \x01(\x05R\x02ID\"V\n" +
|
||||
"\x0eGetScenarioRsp\x12D\n" +
|
||||
"\bscenario\x18\x01 \x01(\v2(.crabs.evening_detective_server.ScenarioR\bscenario\"\x9e\x04\n" +
|
||||
"\bscenario\x18\x01 \x01(\v2(.crabs.evening_detective_server.ScenarioR\bscenario\"\xbe\x04\n" +
|
||||
"\bScenario\x12\x0e\n" +
|
||||
"\x02ID\x18\x01 \x01(\x05R\x02ID\x12\x12\n" +
|
||||
"\x04name\x18\x02 \x01(\tR\x04name\x12%\n" +
|
||||
@@ -9464,7 +9488,10 @@ const file_main_proto_rawDesc = "" +
|
||||
"\fpublished_at\x18\n" +
|
||||
" \x01(\v2\x1a.google.protobuf.TimestampR\vpublishedAt\x12%\n" +
|
||||
"\x0eaverage_rating\x18\v \x01(\x01R\raverageRating\x12#\n" +
|
||||
"\rreviews_count\x18\f \x01(\x05R\freviewsCountB\x0e\n" +
|
||||
"\rreviews_count\x18\f \x01(\x05R\freviewsCount\x12\x1e\n" +
|
||||
"\n" +
|
||||
"difficulty\x18\r \x01(\tR\n" +
|
||||
"difficultyB\x0e\n" +
|
||||
"\f_descriptionB\b\n" +
|
||||
"\x06_image\"\xcc\x02\n" +
|
||||
"\x05Story\x12=\n" +
|
||||
@@ -9753,7 +9780,7 @@ const file_main_proto_rawDesc = "" +
|
||||
"\ractions_count\x18\x05 \x01(\x05R\factionsCount\"\xa4\x01\n" +
|
||||
"\x11GetGameResultsRsp\x12M\n" +
|
||||
"\tquestions\x18\x01 \x03(\v2/.crabs.evening_detective_server.ResultsQuestionR\tquestions\x12@\n" +
|
||||
"\x05teams\x18\x02 \x03(\v2*.crabs.evening_detective_server.TeamResultR\x05teams\"\x85\x02\n" +
|
||||
"\x05teams\x18\x02 \x03(\v2*.crabs.evening_detective_server.TeamResultR\x05teams\"\xa5\x02\n" +
|
||||
"\n" +
|
||||
"GameReview\x12\x0e\n" +
|
||||
"\x02ID\x18\x01 \x01(\x05R\x02ID\x12\x16\n" +
|
||||
@@ -9765,13 +9792,20 @@ const file_main_proto_rawDesc = "" +
|
||||
"\x06status\x18\a \x01(\tR\x06status\x129\n" +
|
||||
"\n" +
|
||||
"created_at\x18\b \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x12\x1b\n" +
|
||||
"\tgame_name\x18\t \x01(\tR\bgameName\"\x8d\x01\n" +
|
||||
"\tgame_name\x18\t \x01(\tR\bgameName\x12\x1e\n" +
|
||||
"\n" +
|
||||
"difficulty\x18\n" +
|
||||
" \x01(\x05R\n" +
|
||||
"difficulty\"\xad\x01\n" +
|
||||
"\x13CreateGameReviewReq\x12\x16\n" +
|
||||
"\x06gameID\x18\x01 \x01(\x05R\x06gameID\x12\x16\n" +
|
||||
"\x06teamID\x18\x02 \x01(\x05R\x06teamID\x12\x1a\n" +
|
||||
"\bpassword\x18\x03 \x01(\tR\bpassword\x12\x16\n" +
|
||||
"\x06rating\x18\x04 \x01(\x05R\x06rating\x12\x12\n" +
|
||||
"\x04text\x18\x05 \x01(\tR\x04text\"Y\n" +
|
||||
"\x04text\x18\x05 \x01(\tR\x04text\x12\x1e\n" +
|
||||
"\n" +
|
||||
"difficulty\x18\x06 \x01(\x05R\n" +
|
||||
"difficulty\"Y\n" +
|
||||
"\x13CreateGameReviewRsp\x12B\n" +
|
||||
"\x06review\x18\x01 \x01(\v2*.crabs.evening_detective_server.GameReviewR\x06review\"+\n" +
|
||||
"\x11GetGameReviewsReq\x12\x16\n" +
|
||||
|
||||
Reference in New Issue
Block a user