diff --git a/api/main.proto b/api/main.proto index 95a3e3f..c6ce1b9 100644 --- a/api/main.proto +++ b/api/main.proto @@ -1110,16 +1110,18 @@ message GetScenarioRsp { } message Scenario { - int32 id = 1; - string name = 2; - optional string description = 3; - optional string image = 4; - Story story = 5; - string status = 6; - User author = 7; - google.protobuf.Timestamp updated_at = 8; - google.protobuf.Timestamp created_at = 9; - google.protobuf.Timestamp published_at = 10; + int32 id = 1; + string name = 2; + optional string description = 3; + optional string image = 4; + Story story = 5; + string status = 6; + User author = 7; + google.protobuf.Timestamp updated_at = 8; + google.protobuf.Timestamp created_at = 9; + google.protobuf.Timestamp published_at = 10; + double average_rating = 11; + int32 reviews_count = 12; } message Story { diff --git a/bin/evening_detective_server b/bin/evening_detective_server index 1d5b119..b650d32 100755 Binary files a/bin/evening_detective_server and b/bin/evening_detective_server differ diff --git a/cmd/evening_detective_server/main.swagger.json b/cmd/evening_detective_server/main.swagger.json index 9e45366..885018f 100644 --- a/cmd/evening_detective_server/main.swagger.json +++ b/cmd/evening_detective_server/main.swagger.json @@ -4195,6 +4195,14 @@ "publishedAt": { "type": "string", "format": "date-time" + }, + "averageRating": { + "type": "number", + "format": "double" + }, + "reviewsCount": { + "type": "integer", + "format": "int32" } } }, diff --git a/internal/app/scenarios.go b/internal/app/scenarios.go index 7ee961a..a35409e 100644 --- a/internal/app/scenarios.go +++ b/internal/app/scenarios.go @@ -5,6 +5,7 @@ import ( "evening_detective_server/internal/modules/processor_jwt" "evening_detective_server/internal/modules/roles" + "evening_detective_server/internal/services/scenarios_service" proto "evening_detective_server/proto" "google.golang.org/grpc/codes" @@ -53,6 +54,9 @@ func (s *server) GetScenariosCatalog(ctx context.Context, req *proto.GetScenario Error: err.Error(), }, nil } + if err := s.addScenarioRatings(ctx, scenarios); err != nil { + return &proto.GetScenariosCatalogRsp{Error: err.Error()}, nil + } return &proto.GetScenariosCatalogRsp{ Scenarios: mapScenarios(scenarios), @@ -84,12 +88,27 @@ func (s *server) GetScenario(ctx context.Context, req *proto.GetScenarioReq) (*p Error: err.Error(), }, nil } + if err := s.addScenarioRatings(ctx, []*scenarios_service.Scenario{scenario}); err != nil { + return &proto.GetScenarioRsp{Error: err.Error()}, nil + } return &proto.GetScenarioRsp{ Scenario: mapScenario(scenario), }, nil } +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) + if err != nil { + return err + } + scenario.AverageRating = average + scenario.ReviewsCount = count + } + return 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) { diff --git a/internal/app/story_mapper.go b/internal/app/story_mapper.go index 39d2fd4..5fb7347 100644 --- a/internal/app/story_mapper.go +++ b/internal/app/story_mapper.go @@ -26,16 +26,18 @@ func mapScenario(o *scenarios_service.Scenario) *proto.Scenario { publishedAt = timestamppb.New(*o.PublishedAt) } return &proto.Scenario{ - Id: int32(o.ID), - Name: o.Name, - Description: o.Description, - Image: o.Image, - Story: mapStory(o.Story), - Author: mapUser(o.Author), - Status: o.Status, - UpdatedAt: timestamppb.New(o.UpdatedAt), - CreatedAt: timestamppb.New(o.CreatedAt), - PublishedAt: publishedAt, + Id: int32(o.ID), + Name: o.Name, + Description: o.Description, + Image: o.Image, + Story: mapStory(o.Story), + Author: mapUser(o.Author), + Status: o.Status, + UpdatedAt: timestamppb.New(o.UpdatedAt), + CreatedAt: timestamppb.New(o.CreatedAt), + PublishedAt: publishedAt, + AverageRating: o.AverageRating, + ReviewsCount: o.ReviewsCount, } } diff --git a/internal/repos/game_reviews_repo/repo.go b/internal/repos/game_reviews_repo/repo.go index 9434d3d..8c9b40d 100644 --- a/internal/repos/game_reviews_repo/repo.go +++ b/internal/repos/game_reviews_repo/repo.go @@ -99,6 +99,15 @@ func (r *Repo) Moderate(ctx context.Context, id int, status string) (*Review, er return review, errors.New("Недопустимый статус модерации") } +func (r *Repo) ScenarioRating(ctx context.Context, scenarioID int) (float64, int32, error) { + var average float64 + var count int64 + err := r.pool.QueryRow(ctx, `SELECT COALESCE(AVG(r.rating),0), COUNT(*) + 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 +} + func (r *Repo) queryOne(ctx context.Context, condition string, id int) (*Review, error) { review := &Review{} err := r.pool.QueryRow(ctx, `SELECT r.id,r.game_id,r.team_id,t.name,r.rating,r.text,r.status,r.created_at diff --git a/internal/services/game_review_service/service.go b/internal/services/game_review_service/service.go index 5179381..807fb28 100644 --- a/internal/services/game_review_service/service.go +++ b/internal/services/game_review_service/service.go @@ -62,6 +62,10 @@ func (s *Service) Moderate(ctx context.Context, id int, status string) (*game_re return s.reviews.Moderate(ctx, id, status) } +func (s *Service) ScenarioRating(ctx context.Context, scenarioID int) (float64, int32, error) { + return s.reviews.ScenarioRating(ctx, scenarioID) +} + func reviewReminderEmail(job *teams_repo.ReviewReminder, baseURL string) email_sender.Message { link := strings.TrimRight(baseURL, "/") + "/team-story/" + fmt.Sprint(job.TeamID) + "?" + url.Values{"password": {job.Password}, "review": {"1"}}.Encode() body := fmt.Sprintf("Команда «%s», поделитесь впечатлениями об игре «%s».\n\nОценка и отзыв займут пару минут: %s\n\nОтзыв можно оставить один раз от команды.", job.TeamName, job.GameName, link) diff --git a/internal/services/scenarios_service/scenario.go b/internal/services/scenarios_service/scenario.go index aba52e8..0a9fb11 100644 --- a/internal/services/scenarios_service/scenario.go +++ b/internal/services/scenarios_service/scenario.go @@ -7,15 +7,17 @@ import ( ) type Scenario struct { - ID int - Name string - Description *string - Image *string - Story *storytelling.Story - Author *repos.User - Status string - UpdatedAt time.Time - CreatedAt time.Time - PublishedAt *time.Time - IsDeleted bool + ID int + Name string + Description *string + Image *string + Story *storytelling.Story + Author *repos.User + Status string + UpdatedAt time.Time + CreatedAt time.Time + PublishedAt *time.Time + AverageRating float64 + ReviewsCount int32 + IsDeleted bool } diff --git a/proto/main.pb.go b/proto/main.pb.go index a244e8a..ac5ff8c 100644 --- a/proto/main.pb.go +++ b/proto/main.pb.go @@ -2338,6 +2338,8 @@ type Scenario struct { UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` CreatedAt *timestamppb.Timestamp `protobuf:"bytes,9,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` 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"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -2442,6 +2444,20 @@ func (x *Scenario) GetPublishedAt() *timestamppb.Timestamp { return nil } +func (x *Scenario) GetAverageRating() float64 { + if x != nil { + return x.AverageRating + } + return 0 +} + +func (x *Scenario) GetReviewsCount() int32 { + if x != nil { + return x.ReviewsCount + } + return 0 +} + type Story struct { state protoimpl.MessageState `protogen:"open.v1"` Conclusion *Conclusion `protobuf:"bytes,5,opt,name=conclusion,proto3" json:"conclusion,omitempty"` // Только в полном сценарии, игрокам не передаётся. @@ -9272,7 +9288,7 @@ const file_main_proto_rawDesc = "" + "\x02id\x18\x01 \x01(\x05R\x02id\"l\n" + "\x0eGetScenarioRsp\x12\x14\n" + "\x05error\x18\x01 \x01(\tR\x05error\x12D\n" + - "\bscenario\x18\x02 \x01(\v2(.crabs.evening_detective_server.ScenarioR\bscenario\"\xd2\x03\n" + + "\bscenario\x18\x02 \x01(\v2(.crabs.evening_detective_server.ScenarioR\bscenario\"\x9e\x04\n" + "\bScenario\x12\x0e\n" + "\x02id\x18\x01 \x01(\x05R\x02id\x12\x12\n" + "\x04name\x18\x02 \x01(\tR\x04name\x12%\n" + @@ -9286,7 +9302,9 @@ const file_main_proto_rawDesc = "" + "\n" + "created_at\x18\t \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x12=\n" + "\fpublished_at\x18\n" + - " \x01(\v2\x1a.google.protobuf.TimestampR\vpublishedAtB\x0e\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" + "\f_descriptionB\b\n" + "\x06_image\"\xcc\x02\n" + "\x05Story\x12J\n" +