fix(customer-auth): make organization_id optional at registration

Requiring organization_id broke the current app, which does not send it.
When it is left out and the database has exactly one organization, the
customer now joins that one, so the app works unchanged. A sent
organization_id must still exist, and with several organizations and none
sent registration is refused with a clear message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
efrilm
2026-09-30 18:16:31 +07:00
co-authored by Claude Opus 5.5
parent a3cb7dd5fd
commit 8bf2fe5585
4 changed files with 55 additions and 14 deletions
+37 -10
View File
@@ -143,19 +143,12 @@ func (p *customerAuthProcessor) StartRegistration(ctx context.Context, req *cont
return nil, fmt.Errorf("phone number already registered")
}
// The customer joins the organization the app is built for. Check it exists now,
// before an OTP is sent, rather than failing on a foreign key at the last step.
organizationID, err := uuid.Parse(strings.TrimSpace(req.OrganizationID))
if err != nil {
return nil, fmt.Errorf("organization_id must be a valid UUID")
}
orgExists, err := p.customerAuthRepo.OrganizationExists(ctx, organizationID)
// Resolve the organization before an OTP is sent, rather than failing on a foreign
// key at the last step.
organizationID, err := p.registrationOrganization(ctx, req.OrganizationID)
if err != nil {
return nil, err
}
if !orgExists {
return nil, fmt.Errorf("organization not found")
}
// Generate registration token and create OTP session
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
// 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")
}
}