Main #41

Merged
aefril merged 16 commits from main into staging 2026-10-08 04:32:27 +02:00
4 changed files with 44 additions and 2 deletions
Showing only changes of commit a3cb7dd5fd - Show all commits
@@ -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 {
+22 -2
View File
@@ -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