mirror of
https://github.com/IS1DI/x0gp.git
synced 2026-07-19 05:47:02 +00:00
Add driver profile stats API, nickname update editing, and security checks
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user