Fix/customer register organization #39
@@ -17,8 +17,9 @@ type RegisterStartRequest struct {
|
|||||||
Name string `json:"name" binding:"required"`
|
Name string `json:"name" binding:"required"`
|
||||||
BirthDate string `json:"birth_date" binding:"required"`
|
BirthDate string `json:"birth_date" binding:"required"`
|
||||||
// The organization (brand) the customer registers with. A customer belongs to one
|
// The organization (brand) the customer registers with. A customer belongs to one
|
||||||
// organization; the app sends the one it is built for.
|
// organization. Optional: when it is left out and the database has exactly one
|
||||||
OrganizationID string `json:"organization_id" binding:"required"`
|
// organization, the customer joins that one.
|
||||||
|
OrganizationID string `json:"organization_id,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RegisterVerifyOtpRequest struct {
|
type RegisterVerifyOtpRequest struct {
|
||||||
|
|||||||
@@ -143,19 +143,12 @@ func (p *customerAuthProcessor) StartRegistration(ctx context.Context, req *cont
|
|||||||
return nil, fmt.Errorf("phone number already registered")
|
return nil, fmt.Errorf("phone number already registered")
|
||||||
}
|
}
|
||||||
|
|
||||||
// The customer joins the organization the app is built for. Check it exists now,
|
// Resolve the organization before an OTP is sent, rather than failing on a foreign
|
||||||
// before an OTP is sent, rather than failing on a foreign key at the last step.
|
// key at the last step.
|
||||||
organizationID, err := uuid.Parse(strings.TrimSpace(req.OrganizationID))
|
organizationID, err := p.registrationOrganization(ctx, req.OrganizationID)
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("organization_id must be a valid UUID")
|
|
||||||
}
|
|
||||||
orgExists, err := p.customerAuthRepo.OrganizationExists(ctx, organizationID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !orgExists {
|
|
||||||
return nil, fmt.Errorf("organization not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate registration token and create OTP session
|
// Generate registration token and create OTP session
|
||||||
registrationToken := uuid.New().String()
|
registrationToken := uuid.New().String()
|
||||||
@@ -458,3 +451,37 @@ func (p *customerAuthProcessor) ResendOtp(ctx context.Context, req *contract.Res
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper functions - OTP generation is now handled by OtpProcessor
|
// Helper functions - OTP generation is now handled by OtpProcessor
|
||||||
|
|
||||||
|
// registrationOrganization is the organization a new customer joins: the one the app
|
||||||
|
// sent, which must exist, or, when the app sent none, the only organization there is.
|
||||||
|
// With several organizations and none sent there is no way to choose, so it refuses.
|
||||||
|
func (p *customerAuthProcessor) registrationOrganization(ctx context.Context, requested string) (uuid.UUID, error) {
|
||||||
|
requested = strings.TrimSpace(requested)
|
||||||
|
if requested != "" {
|
||||||
|
id, err := uuid.Parse(requested)
|
||||||
|
if err != nil {
|
||||||
|
return uuid.Nil, fmt.Errorf("organization_id must be a valid UUID")
|
||||||
|
}
|
||||||
|
exists, err := p.customerAuthRepo.OrganizationExists(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return uuid.Nil, err
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
return uuid.Nil, fmt.Errorf("organization not found")
|
||||||
|
}
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ids, err := p.customerAuthRepo.OrganizationIDs(ctx, 2)
|
||||||
|
if err != nil {
|
||||||
|
return uuid.Nil, err
|
||||||
|
}
|
||||||
|
switch len(ids) {
|
||||||
|
case 1:
|
||||||
|
return ids[0], nil
|
||||||
|
case 0:
|
||||||
|
return uuid.Nil, fmt.Errorf("no organization exists to register customers into")
|
||||||
|
default:
|
||||||
|
return uuid.Nil, fmt.Errorf("organization_id is required: there is more than one organization")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ type CustomerAuthRepository interface {
|
|||||||
SetCustomerPassword(ctx context.Context, customerID string, passwordHash string) error
|
SetCustomerPassword(ctx context.Context, customerID string, passwordHash string) error
|
||||||
// OrganizationExists reports whether an organization with this id exists.
|
// OrganizationExists reports whether an organization with this id exists.
|
||||||
OrganizationExists(ctx context.Context, organizationID uuid.UUID) (bool, error)
|
OrganizationExists(ctx context.Context, organizationID uuid.UUID) (bool, error)
|
||||||
|
// OrganizationIDs returns up to limit organization ids.
|
||||||
|
OrganizationIDs(ctx context.Context, limit int) ([]uuid.UUID, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type customerAuthRepository struct {
|
type customerAuthRepository struct {
|
||||||
@@ -90,3 +92,12 @@ func (r *customerAuthRepository) OrganizationExists(ctx context.Context, organiz
|
|||||||
}
|
}
|
||||||
return count > 0, nil
|
return count > 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *customerAuthRepository) OrganizationIDs(ctx context.Context, limit int) ([]uuid.UUID, error) {
|
||||||
|
var ids []uuid.UUID
|
||||||
|
err := r.db.WithContext(ctx).Table("organizations").Order("created_at").Limit(limit).Pluck("id", &ids).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to list organizations: %w", err)
|
||||||
|
}
|
||||||
|
return ids, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -71,8 +71,10 @@ func (v *CustomerAuthValidatorImpl) ValidateRegisterStartRequest(req *contract.R
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate organization
|
// Validate organization
|
||||||
if _, err := uuid.Parse(strings.TrimSpace(req.OrganizationID)); err != nil {
|
if orgID := strings.TrimSpace(req.OrganizationID); orgID != "" {
|
||||||
return errors.New("organization_id must be a valid UUID"), constants.ValidationErrorCode
|
if _, err := uuid.Parse(orgID); err != nil {
|
||||||
|
return errors.New("organization_id must be a valid UUID"), constants.ValidationErrorCode
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate birth date
|
// Validate birth date
|
||||||
|
|||||||
Reference in New Issue
Block a user