Add team renaming functionality to API and frontend. Implement backend logic for renaming teams, including validation and synchronization with event rosters. Update frontend components to allow team name editing and ensure proper state management. Enhance translations and styles for new features.
This commit is contained in:
@@ -96,6 +96,7 @@ func New(service *application.Service, store application.Store, hub *realtime.Hu
|
||||
api.Post("/api/events/{eventID}/workflow/back", s.revertWorkflowStage)
|
||||
api.Post("/api/events/{eventID}/start", s.startScrim)
|
||||
api.Put("/api/teams/{teamID}/captain", s.assignCaptain)
|
||||
api.Put("/api/teams/{teamID}/name", s.renameTeam)
|
||||
api.Post("/api/rulesets", s.saveRuleset)
|
||||
api.Get("/api/rulesets", s.listRulesets)
|
||||
api.Get("/api/rulesets/{id}", s.getRuleset)
|
||||
@@ -379,6 +380,18 @@ func (s *Server) assignCaptain(w http.ResponseWriter, r *http.Request) {
|
||||
respond(w, out, err, 200)
|
||||
}
|
||||
|
||||
func (s *Server) renameTeam(w http.ResponseWriter, r *http.Request) {
|
||||
var in struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
if !decode(w, r, &in) {
|
||||
return
|
||||
}
|
||||
id := who(r)
|
||||
out, err := s.service.RenameTeam(r.Context(), id.account, id.player, chi.URLParam(r, "teamID"), in.Name)
|
||||
respond(w, out, err, http.StatusOK)
|
||||
}
|
||||
|
||||
func (s *Server) saveRuleset(w http.ResponseWriter, r *http.Request) {
|
||||
if err := requireStaff(r); err != nil {
|
||||
writeError(w, err)
|
||||
|
||||
@@ -476,6 +476,52 @@ func (s *Store) ListTeams(ctx context.Context, eventID string) ([]domain.Team, e
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func (s *Store) GetTeam(ctx context.Context, teamID string) (domain.Team, error) {
|
||||
var team domain.Team
|
||||
var body []byte
|
||||
err := s.pool.QueryRow(ctx, `SELECT id,event_id,name,COALESCE(captain_player_id,''),slots FROM teams WHERE id=$1`, teamID).
|
||||
Scan(&team.ID, &team.EventID, &team.Name, &team.CaptainPlayerID, &body)
|
||||
if err == nil {
|
||||
err = json.Unmarshal(body, &team.Slots)
|
||||
}
|
||||
return team, mapError(err)
|
||||
}
|
||||
|
||||
func (s *Store) RenameTeam(ctx context.Context, teamID, name string) (domain.Team, error) {
|
||||
tx, err := s.pool.Begin(ctx)
|
||||
if err != nil {
|
||||
return domain.Team{}, err
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
var eventID string
|
||||
if err = tx.QueryRow(ctx, `UPDATE teams SET name=$2 WHERE id=$1 RETURNING event_id`, teamID, name).Scan(&eventID); err != nil {
|
||||
return domain.Team{}, mapError(err)
|
||||
}
|
||||
var rosterBody []byte
|
||||
err = tx.QueryRow(ctx, `SELECT body FROM event_rosters WHERE event_id=$1`, eventID).Scan(&rosterBody)
|
||||
if err == nil {
|
||||
var roster domain.RosterDraft
|
||||
if err = json.Unmarshal(rosterBody, &roster); err != nil {
|
||||
return domain.Team{}, err
|
||||
}
|
||||
for index := range roster.Teams {
|
||||
if roster.Teams[index].ID == teamID {
|
||||
roster.Teams[index].Name = name
|
||||
}
|
||||
}
|
||||
rosterBody, _ = json.Marshal(roster)
|
||||
if _, err = tx.Exec(ctx, `UPDATE event_rosters SET body=$2,updated_at=now() WHERE event_id=$1`, eventID, rosterBody); err != nil {
|
||||
return domain.Team{}, err
|
||||
}
|
||||
} else if !errors.Is(err, pgx.ErrNoRows) {
|
||||
return domain.Team{}, err
|
||||
}
|
||||
if err = tx.Commit(ctx); err != nil {
|
||||
return domain.Team{}, err
|
||||
}
|
||||
return s.GetTeam(ctx, teamID)
|
||||
}
|
||||
|
||||
func (s *Store) AssignCaptain(ctx context.Context, teamID, playerID string) (domain.Team, error) {
|
||||
var eventID string
|
||||
if err := s.pool.QueryRow(ctx, `SELECT event_id FROM teams WHERE id=$1 AND slots @> $2::jsonb`, teamID, fmt.Sprintf(`[{"playerId":%q}]`, playerID)).Scan(&eventID); err != nil {
|
||||
|
||||
@@ -37,6 +37,8 @@ type Store interface {
|
||||
DeleteRSVP(context.Context, string, string) error
|
||||
SaveTeams(context.Context, string, []domain.Team) error
|
||||
ListTeams(context.Context, string) ([]domain.Team, error)
|
||||
GetTeam(context.Context, string) (domain.Team, error)
|
||||
RenameTeam(context.Context, string, string) (domain.Team, error)
|
||||
AssignCaptain(context.Context, string, string) (domain.Team, error)
|
||||
SaveRuleset(context.Context, domain.Ruleset) (domain.Ruleset, error)
|
||||
ListRulesets(context.Context) ([]domain.Ruleset, error)
|
||||
@@ -339,6 +341,34 @@ func (s *Service) AssignCaptain(ctx context.Context, actor domain.Account, teamI
|
||||
return team, err
|
||||
}
|
||||
|
||||
func (s *Service) RenameTeam(ctx context.Context, actor domain.Account, player domain.Player, teamID, name string) (domain.Team, error) {
|
||||
team, err := s.Store.GetTeam(ctx, teamID)
|
||||
if err != nil {
|
||||
return team, err
|
||||
}
|
||||
event, err := s.Store.GetEvent(ctx, team.EventID)
|
||||
if err != nil {
|
||||
return team, err
|
||||
}
|
||||
if event.State != domain.Live {
|
||||
return team, fmt.Errorf("%w: team names can only be changed during a live scrim", domain.ErrConflict)
|
||||
}
|
||||
if !CanActForTeam(actor, player, team) {
|
||||
return team, domain.ErrForbidden
|
||||
}
|
||||
name = strings.TrimSpace(name)
|
||||
if len([]rune(name)) < 2 || len([]rune(name)) > 32 {
|
||||
return team, fmt.Errorf("%w: team name must contain 2 to 32 characters", domain.ErrInvalid)
|
||||
}
|
||||
team, err = s.Store.RenameTeam(ctx, teamID, name)
|
||||
if err == nil {
|
||||
_ = s.Store.AppendAudit(ctx, actor.ID, "team.renamed", teamID, map[string]string{"name": name})
|
||||
s.Bus.Publish("team:"+teamID, team)
|
||||
s.Bus.Publish("event:"+team.EventID, team)
|
||||
}
|
||||
return team, err
|
||||
}
|
||||
|
||||
func CanActForTeam(actor domain.Account, player domain.Player, team domain.Team) bool {
|
||||
return actor.IsStaff() || (team.CaptainPlayerID != "" && team.CaptainPlayerID == player.ID)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user