package drivers import ( "context" "errors" "fmt" "strings" "time" "github.com/jackc/pgx/v5/pgconn" ) // Service composes validation on top of PgStore. type Service struct { pg *PgStore now func() time.Time } // NewService wires a service. func NewService(pg *PgStore) *Service { return &Service{pg: pg, now: time.Now} } // CreateInput is what the HTTP handler hands to the service. type CreateInput struct { ID string Nickname string Name string AvatarURL string ClanID string } // Create validates and inserts a driver. func (s *Service) Create(ctx context.Context, in CreateInput) (Driver, error) { nick := strings.ToUpper(strings.TrimSpace(in.Nickname)) if !IsValidNickname(nick) { return Driver{}, fmt.Errorf("%w: nickname must be 3 uppercase ASCII letters", ErrInvalidInput) } name := strings.TrimSpace(in.Name) if name == "" { return Driver{}, fmt.Errorf("%w: name required", ErrInvalidInput) } d := Driver{ ID: in.ID, Nickname: nick, Name: name, AvatarURL: in.AvatarURL, ClanID: in.ClanID, } if d.ID == "" { d.ID = fmt.Sprintf("driver-%d-%d", s.now().UnixMilli(), time.Now().UnixNano()%1000) } if err := s.pg.Create(ctx, d); err != nil { var pgErr *pgconn.PgError if errors.As(err, &pgErr) && pgErr.Code == "23505" { return Driver{}, fmt.Errorf("%w: %s", ErrAlreadyExists, nick) } return Driver{}, err } return s.pg.Get(ctx, d.ID) } // Get loads a driver by id. func (s *Service) Get(ctx context.Context, id string) (Driver, error) { return s.pg.Get(ctx, id) } // GetByNickname loads a driver by nickname. func (s *Service) GetByNickname(ctx context.Context, nick string) (Driver, error) { nick = strings.ToUpper(strings.TrimSpace(nick)) return s.pg.GetByNickname(ctx, nick) } // List returns drivers with limit/offset and optional clan filter. func (s *Service) List(ctx context.Context, limit, offset int, clanID string) ([]Driver, error) { return s.pg.List(ctx, limit, offset, clanID) } // UpdateInput is the patch payload. type UpdateInput struct { Name string AvatarURL string ClanID *string // nil = leave unchanged, &"" = clear, &"..." = set } // Update applies a partial patch. func (s *Service) Update(ctx context.Context, id string, in UpdateInput) (Driver, error) { cur, err := s.pg.Get(ctx, id) if err != nil { return Driver{}, err } if in.Name != "" { cur.Name = in.Name } if in.AvatarURL != "" { cur.AvatarURL = in.AvatarURL } if in.ClanID != nil { cur.ClanID = *in.ClanID } if err := s.pg.Update(ctx, cur); err != nil { return Driver{}, err } return s.pg.Get(ctx, id) } // Delete removes a driver. func (s *Service) Delete(ctx context.Context, id string) error { return s.pg.Delete(ctx, id) }