diff --git a/api/main.proto b/api/main.proto index 0d39fd2..bb1be57 100644 --- a/api/main.proto +++ b/api/main.proto @@ -1631,8 +1631,9 @@ message DeleteGameRsp { } message AddTeamReq { - int32 game_id = 1; - string name = 2; + int32 game_id = 1; + string name = 2; + string captain_email = 3; } message AddTeamRsp { diff --git a/bin/evening_detective_server b/bin/evening_detective_server index 2846676..2b5572b 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 4772d87..ac0d611 100644 --- a/cmd/evening_detective_server/main.swagger.json +++ b/cmd/evening_detective_server/main.swagger.json @@ -3034,6 +3034,9 @@ }, "name": { "type": "string" + }, + "captainEmail": { + "type": "string" } } }, diff --git a/internal/app/server.go b/internal/app/server.go index 291af35..e4d6729 100644 --- a/internal/app/server.go +++ b/internal/app/server.go @@ -1384,6 +1384,7 @@ func (s *server) AddTeam(ctx context.Context, req *proto.AddTeamReq) (*proto.Add claims.UserID, int(req.GameId), req.Name, + req.CaptainEmail, ) if err != nil { return &proto.AddTeamRsp{ diff --git a/internal/repos/teams_repo/registration_test.go b/internal/repos/teams_repo/registration_test.go index 49776a0..23717d8 100644 --- a/internal/repos/teams_repo/registration_test.go +++ b/internal/repos/teams_repo/registration_test.go @@ -118,4 +118,18 @@ func TestRegistrationPostgres(t *testing.T) { if otherID, err := repo.RegisterTeam(ctx, 2, "delivery", "Team", "captain@example.org", 5, "abcdefgh"); err != nil || otherID == id { t.Fatalf("other game: %d %v", otherID, err) } + for _, email := range []string{"", "manual@example.org"} { + manualID, err := repo.AddTeamWithParticipation(ctx, 1, 2, "Manual", "password", email, false) + if err != nil { + t.Fatal(err) + } + manual, err := repo.GetTeamByID(ctx, manualID) + if err != nil { + t.Fatal(err) + } + if manual.CaptainEmail != email || manual.ParticipationStatus != "pending" { + t.Fatalf("incorrect manual team: %+v", manual) + } + } + } diff --git a/internal/repos/teams_repo/repo.go b/internal/repos/teams_repo/repo.go index c981441..a33c5a7 100644 --- a/internal/repos/teams_repo/repo.go +++ b/internal/repos/teams_repo/repo.go @@ -115,10 +115,10 @@ func (r *TeamsRepo) AddTeam( name string, password string, ) (int, error) { - return r.AddTeamWithParticipation(ctx, creatorId, gameId, name, password, false) + return r.AddTeamWithParticipation(ctx, creatorId, gameId, name, password, "", false) } -func (r *TeamsRepo) AddTeamWithParticipation(ctx context.Context, creatorId, gameId int, name, password string, autoConfirm bool) (int, error) { +func (r *TeamsRepo) AddTeamWithParticipation(ctx context.Context, creatorId, gameId int, name, password, captainEmail string, autoConfirm bool) (int, error) { id := 0 status, source := "pending", "" if autoConfirm { @@ -126,8 +126,8 @@ func (r *TeamsRepo) AddTeamWithParticipation(ctx context.Context, creatorId, gam } err := r.pool.QueryRow( ctx, - `INSERT INTO teams (name, creator_id, game_id, password, participation_status, participation_source, participation_answered_at) - VALUES ($1, $2, $3, $4, $5, $6, CASE WHEN $5 = 'confirmed' THEN NOW() ELSE NULL END) + `INSERT INTO teams (name, creator_id, game_id, password, participation_status, participation_source, participation_answered_at, captain_email) + VALUES ($1, $2, $3, $4, $5, $6, CASE WHEN $5 = 'confirmed' THEN NOW() ELSE NULL END, $7) RETURNING id`, name, creatorId, @@ -135,6 +135,7 @@ func (r *TeamsRepo) AddTeamWithParticipation(ctx context.Context, creatorId, gam password, status, source, + captainEmail, ).Scan(&id) if err != nil { diff --git a/internal/services/game_service/manual_team_test.go b/internal/services/game_service/manual_team_test.go new file mode 100644 index 0000000..24bb3c2 --- /dev/null +++ b/internal/services/game_service/manual_team_test.go @@ -0,0 +1,21 @@ +package game_service + +import ( + "context" + "strings" + "testing" +) + +func TestManualTeamEmail(t *testing.T) { + for _, email := range []string{"captain@example.org", "captain+team@example.org"} { + if !validCaptainEmail(email) { + t.Errorf("rejected %q", email) + } + } + for _, email := range []string{"invalid", "Captain ", "a@example.org\r\nBcc: other@example.org", strings.Repeat("a", 250) + "@example.org"} { + service := &GameService{} + if _, err := service.AddTeam(context.Background(), 1, 1, "Team", email); err == nil { + t.Errorf("accepted invalid email %q", email) + } + } +} diff --git a/internal/services/game_service/registration.go b/internal/services/game_service/registration.go index ea5800a..53d50fb 100644 --- a/internal/services/game_service/registration.go +++ b/internal/services/game_service/registration.go @@ -51,12 +51,16 @@ func (r *Registration) UnmarshalJSON(data []byte) error { return nil } +func validCaptainEmail(email string) bool { + address, err := mail.ParseAddress(email) + return err == nil && address.Address == email && len(email) <= 254 +} + func (r *Registration) Validate() error { r.Name = strings.TrimSpace(r.Name) r.Email = strings.TrimSpace(r.Email) - address, err := mail.ParseAddress(r.Email) if r.Name == "" || !utf8.ValidString(r.Name) || strings.ContainsRune(r.Name, 0) || utf8.RuneCountInString(r.Name) > 255 || - err != nil || address.Address != r.Email || len(r.Email) > 254 || r.ParticipantsCount <= 0 { + !validCaptainEmail(r.Email) || r.ParticipantsCount <= 0 { return ErrInvalidRegistration } return nil diff --git a/internal/services/game_service/service.go b/internal/services/game_service/service.go index 5e1d476..cac66ee 100644 --- a/internal/services/game_service/service.go +++ b/internal/services/game_service/service.go @@ -15,6 +15,7 @@ import ( "evening_detective_server/internal/repos/team_answers_repo" "evening_detective_server/internal/repos/teams_repo" "evening_detective_server/internal/services/scenarios_service" + "strings" "time" ) @@ -217,7 +218,12 @@ func (s *GameService) AddTeam( creatorId int, gameId int, name string, + captainEmail string, ) (*Team, error) { + captainEmail = strings.TrimSpace(captainEmail) + if captainEmail != "" && !validCaptainEmail(captainEmail) { + return nil, errors.New("Укажите корректную почту капитана") + } password, err := s.passwordGenerator.Generate() if err != nil { return nil, err @@ -226,7 +232,7 @@ func (s *GameService) AddTeam( if err != nil { return nil, err } - id, err := s.teamsRepo.AddTeamWithParticipation(ctx, creatorId, gameId, name, password, !time.Now().Before(game.StartAt.Add(-24*time.Hour))) + id, err := s.teamsRepo.AddTeamWithParticipation(ctx, creatorId, gameId, name, password, captainEmail, !time.Now().Before(game.StartAt.Add(-24*time.Hour))) if err != nil { return nil, err } diff --git a/proto/main.pb.go b/proto/main.pb.go index 2a44516..23036de 100644 --- a/proto/main.pb.go +++ b/proto/main.pb.go @@ -7540,6 +7540,7 @@ type AddTeamReq struct { state protoimpl.MessageState `protogen:"open.v1"` GameId int32 `protobuf:"varint,1,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"` Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + CaptainEmail string `protobuf:"bytes,3,opt,name=captain_email,json=captainEmail,proto3" json:"captain_email,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -7588,6 +7589,13 @@ func (x *AddTeamReq) GetName() string { return "" } +func (x *AddTeamReq) GetCaptainEmail() string { + if x != nil { + return x.CaptainEmail + } + return "" +} + type AddTeamRsp struct { state protoimpl.MessageState `protogen:"open.v1"` Error string `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"` @@ -9174,11 +9182,12 @@ const file_main_proto_rawDesc = "" + "\rDeleteGameReq\x12\x0e\n" + "\x02id\x18\x01 \x01(\x05R\x02id\"%\n" + "\rDeleteGameRsp\x12\x14\n" + - "\x05error\x18\x01 \x01(\tR\x05error\"9\n" + + "\x05error\x18\x01 \x01(\tR\x05error\"^\n" + "\n" + "AddTeamReq\x12\x17\n" + "\agame_id\x18\x01 \x01(\x05R\x06gameId\x12\x12\n" + - "\x04name\x18\x02 \x01(\tR\x04name\"N\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12#\n" + + "\rcaptain_email\x18\x03 \x01(\tR\fcaptainEmail\"N\n" + "\n" + "AddTeamRsp\x12\x14\n" + "\x05error\x18\x01 \x01(\tR\x05error\x12\x0e\n" +