Add driver profile stats API, nickname update editing, and security checks

This commit is contained in:
2026-07-17 23:18:16 +04:00
parent a5d2ee32c6
commit 5d2e0d59df
14 changed files with 882 additions and 403 deletions
+12
View File
@@ -78,6 +78,7 @@ func (s *Service) List(ctx context.Context, limit, offset int, clanID string) ([
// UpdateInput is the patch payload.
type UpdateInput struct {
Nickname string
Name string
AvatarURL string
ClanID *string // nil = leave unchanged, &"" = clear, &"..." = set
@@ -89,6 +90,13 @@ func (s *Service) Update(ctx context.Context, id string, in UpdateInput) (Driver
if err != nil {
return Driver{}, err
}
if in.Nickname != "" {
nick := strings.ToUpper(strings.TrimSpace(in.Nickname))
if !IsValidNickname(nick) {
return Driver{}, fmt.Errorf("%w: nickname must be 3 uppercase ASCII letters", ErrInvalidInput)
}
cur.Nickname = nick
}
if in.Name != "" {
cur.Name = in.Name
}
@@ -99,6 +107,10 @@ func (s *Service) Update(ctx context.Context, id string, in UpdateInput) (Driver
cur.ClanID = *in.ClanID
}
if err := s.pg.Update(ctx, cur); err != nil {
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) && pgErr.Code == "23505" {
return Driver{}, fmt.Errorf("%w: %s", ErrAlreadyExists, cur.Nickname)
}
return Driver{}, err
}
return s.pg.Get(ctx, id)
+30 -2
View File
@@ -70,6 +70,11 @@ func (m *memoryStore) Update(ctx context.Context, d Driver) error {
if _, ok := m.drivers[d.ID]; !ok {
return ErrNotFound
}
for id, existing := range m.drivers {
if id != d.ID && existing.Nickname == d.Nickname {
return &pgconn.PgError{Code: "23505", Message: "duplicate key value violates unique constraint"}
}
}
m.drivers[d.ID] = d
return nil
}
@@ -141,9 +146,10 @@ func TestDriversService(t *testing.T) {
t.Errorf("Expected driver-1, got %s", gotByNick.ID)
}
// 7. Update driver
// 7. Update driver (including nickname)
clanID := "clan-2"
updated, err := svc.Update(ctx, "driver-1", UpdateInput{
Nickname: "VET",
Name: "Lewis Hamilton MBE",
AvatarURL: "https://example.com/ham-mbe.png",
ClanID: &clanID,
@@ -151,10 +157,32 @@ func TestDriversService(t *testing.T) {
if err != nil {
t.Fatalf("Update failed: %v", err)
}
if updated.Name != "Lewis Hamilton MBE" || updated.AvatarURL != "https://example.com/ham-mbe.png" || updated.ClanID != "clan-2" {
if updated.Nickname != "VET" || updated.Name != "Lewis Hamilton MBE" || updated.AvatarURL != "https://example.com/ham-mbe.png" || updated.ClanID != "clan-2" {
t.Errorf("Unexpected updated driver fields: %+v", updated)
}
// 7a. Update with invalid nickname
_, err = svc.Update(ctx, "driver-1", UpdateInput{Nickname: "TOOLONG"})
if !errors.Is(err, ErrInvalidInput) {
t.Errorf("Expected ErrInvalidInput for invalid nickname update, got: %v", err)
}
// 7b. Create driver-2 to test duplicate nickname conflict
_, err = svc.Create(ctx, CreateInput{
ID: "driver-2",
Nickname: "HAM",
Name: "Lewis",
})
if err != nil {
t.Fatalf("Create driver-2 failed: %v", err)
}
// 7c. Update driver-2 to nickname "VET" (should fail, already taken by driver-1)
_, err = svc.Update(ctx, "driver-2", UpdateInput{Nickname: "VET"})
if !errors.Is(err, ErrAlreadyExists) {
t.Errorf("Expected ErrAlreadyExists for duplicate nickname update, got: %v", err)
}
// 8. Delete driver
err = svc.Delete(ctx, "driver-1")
if err != nil {
+2 -2
View File
@@ -165,9 +165,9 @@ func (s *PgStore) Update(ctx context.Context, d Driver) error {
d.UpdatedMs = time.Now().UnixMilli()
tag, err := s.pool.Exec(ctx, `
UPDATE drivers
SET name = $2, avatar_url = $3, clan_id = $4, updated_ms = $5
SET nickname = $2, name = $3, avatar_url = $4, clan_id = $5, updated_ms = $6
WHERE id = $1`,
d.ID, d.Name, d.AvatarURL, nullableClan(d.ClanID), d.UpdatedMs)
d.ID, d.Nickname, d.Name, d.AvatarURL, nullableClan(d.ClanID), d.UpdatedMs)
if err != nil {
return err
}