Fix/customer register organization #39
@@ -16,6 +16,9 @@ type RegisterStartRequest struct {
|
|||||||
PhoneNumber string `json:"phone_number" binding:"required"`
|
PhoneNumber string `json:"phone_number" binding:"required"`
|
||||||
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
|
||||||
|
// organization; the app sends the one it is built for.
|
||||||
|
OrganizationID string `json:"organization_id" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RegisterVerifyOtpRequest struct {
|
type RegisterVerifyOtpRequest struct {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package processor
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"apskel-pos-be/internal/contract"
|
"apskel-pos-be/internal/contract"
|
||||||
@@ -142,6 +143,20 @@ 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,
|
||||||
|
// 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)
|
||||||
|
if err != nil {
|
||||||
|
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()
|
||||||
|
|
||||||
@@ -156,6 +171,7 @@ func (p *customerAuthProcessor) StartRegistration(ctx context.Context, req *cont
|
|||||||
"registration_token": registrationToken,
|
"registration_token": registrationToken,
|
||||||
"name": req.Name,
|
"name": req.Name,
|
||||||
"birth_date": req.BirthDate,
|
"birth_date": req.BirthDate,
|
||||||
|
"organization_id": organizationID.String(),
|
||||||
"step": "otp_sent",
|
"step": "otp_sent",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,10 +310,14 @@ func (p *customerAuthProcessor) SetPassword(ctx context.Context, req *contract.R
|
|||||||
return nil, fmt.Errorf("invalid birth date format: %w", err)
|
return nil, fmt.Errorf("invalid birth date format: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultOrgID := uuid.MustParse("87bec7c1-e274-4f66-bac5-84e632208470") // This should be configurable
|
orgIDStr, _ := otpSession.Metadata["organization_id"].(string)
|
||||||
|
organizationID, err := uuid.Parse(orgIDStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid registration data: organization not found, start the registration again")
|
||||||
|
}
|
||||||
|
|
||||||
customer := &entities.Customer{
|
customer := &entities.Customer{
|
||||||
OrganizationID: defaultOrgID,
|
OrganizationID: organizationID,
|
||||||
Name: name,
|
Name: name,
|
||||||
PhoneNumber: &otpSession.PhoneNumber,
|
PhoneNumber: &otpSession.PhoneNumber,
|
||||||
BirthDate: &birthDate,
|
BirthDate: &birthDate,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"apskel-pos-be/internal/entities"
|
"apskel-pos-be/internal/entities"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,6 +17,8 @@ type CustomerAuthRepository interface {
|
|||||||
UpdateCustomer(ctx context.Context, customer *entities.Customer) error
|
UpdateCustomer(ctx context.Context, customer *entities.Customer) error
|
||||||
CheckPhoneNumberExists(ctx context.Context, phoneNumber string) (bool, error)
|
CheckPhoneNumberExists(ctx context.Context, phoneNumber string) (bool, error)
|
||||||
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(ctx context.Context, organizationID uuid.UUID) (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type customerAuthRepository struct {
|
type customerAuthRepository struct {
|
||||||
@@ -78,3 +81,12 @@ func (r *customerAuthRepository) SetCustomerPassword(ctx context.Context, custom
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *customerAuthRepository) OrganizationExists(ctx context.Context, organizationID uuid.UUID) (bool, error) {
|
||||||
|
var count int64
|
||||||
|
err := r.db.WithContext(ctx).Table("organizations").Where("id = ?", organizationID).Count(&count).Error
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to check organization: %w", err)
|
||||||
|
}
|
||||||
|
return count > 0, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
|
||||||
"apskel-pos-be/internal/constants"
|
"apskel-pos-be/internal/constants"
|
||||||
"apskel-pos-be/internal/contract"
|
"apskel-pos-be/internal/contract"
|
||||||
)
|
)
|
||||||
@@ -68,6 +70,11 @@ func (v *CustomerAuthValidatorImpl) ValidateRegisterStartRequest(req *contract.R
|
|||||||
return errors.New("name cannot exceed 100 characters"), constants.ValidationErrorCode
|
return errors.New("name cannot exceed 100 characters"), constants.ValidationErrorCode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate organization
|
||||||
|
if _, err := uuid.Parse(strings.TrimSpace(req.OrganizationID)); err != nil {
|
||||||
|
return errors.New("organization_id must be a valid UUID"), constants.ValidationErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
// Validate birth date
|
// Validate birth date
|
||||||
if strings.TrimSpace(req.BirthDate) == "" {
|
if strings.TrimSpace(req.BirthDate) == "" {
|
||||||
return errors.New("birth date is required"), constants.ValidationErrorCode
|
return errors.New("birth date is required"), constants.ValidationErrorCode
|
||||||
|
|||||||
Reference in New Issue
Block a user