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>
149 lines
4.9 KiB
Go
149 lines
4.9 KiB
Go
package contract
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// Request Contracts
|
|
type CheckPhoneRequest struct {
|
|
PhoneNumber string `json:"phone_number" binding:"required"`
|
|
Password string `json:"password,omitempty"` // Optional - only required if user exists
|
|
}
|
|
|
|
type RegisterStartRequest struct {
|
|
PhoneNumber string `json:"phone_number" binding:"required"`
|
|
Name string `json:"name" binding:"required"`
|
|
BirthDate string `json:"birth_date" binding:"required"`
|
|
// The organization (brand) the customer registers with. A customer belongs to one
|
|
// organization. Optional: when it is left out and the database has exactly one
|
|
// organization, the customer joins that one.
|
|
OrganizationID string `json:"organization_id,omitempty"`
|
|
}
|
|
|
|
type RegisterVerifyOtpRequest struct {
|
|
RegistrationToken string `json:"registration_token" binding:"required"`
|
|
OtpCode string `json:"otp_code" binding:"required"`
|
|
}
|
|
|
|
type RegisterSetPasswordRequest struct {
|
|
RegistrationToken string `json:"registration_token" binding:"required"`
|
|
Password string `json:"password" binding:"required,min=8"`
|
|
ConfirmPassword string `json:"confirm_password" binding:"required"`
|
|
}
|
|
|
|
type CustomerLoginRequest struct {
|
|
PhoneNumber string `json:"phone_number" binding:"required"`
|
|
Password string `json:"password" binding:"required"`
|
|
}
|
|
|
|
type ResendOtpRequest struct {
|
|
PhoneNumber string `json:"phone_number" binding:"required"`
|
|
Purpose string `json:"purpose" binding:"required,oneof=login registration"`
|
|
}
|
|
|
|
// Response Contracts
|
|
type CheckPhoneResponse struct {
|
|
Status string `json:"status"`
|
|
Message string `json:"message"`
|
|
Data *CheckPhoneResponseData `json:"data,omitempty"`
|
|
}
|
|
|
|
type CheckPhoneResponseData struct {
|
|
// For NOT_REGISTERED status
|
|
PhoneNumber string `json:"phone_number,omitempty"`
|
|
|
|
// For PASSWORD_REQUIRED status
|
|
AccessToken string `json:"access_token,omitempty"`
|
|
RefreshToken string `json:"refresh_token,omitempty"`
|
|
User *CustomerUserData `json:"user,omitempty"`
|
|
|
|
// For OTP_REQUIRED status (if password doesn't exist)
|
|
OtpToken string `json:"otp_token,omitempty"`
|
|
ExpiresIn int `json:"expires_in,omitempty"`
|
|
}
|
|
|
|
type RegisterStartResponse struct {
|
|
Status string `json:"status"`
|
|
Message string `json:"message"`
|
|
Data *RegisterStartResponseData `json:"data,omitempty"`
|
|
}
|
|
|
|
type RegisterStartResponseData struct {
|
|
RegistrationToken string `json:"registration_token"`
|
|
OtpToken string `json:"otp_token"`
|
|
ExpiresIn int `json:"expires_in"`
|
|
}
|
|
|
|
type RegisterVerifyOtpResponse struct {
|
|
Status string `json:"status"`
|
|
Message string `json:"message"`
|
|
Data *RegisterVerifyOtpResponseData `json:"data,omitempty"`
|
|
}
|
|
|
|
type RegisterVerifyOtpResponseData struct {
|
|
RegistrationToken string `json:"registration_token"`
|
|
}
|
|
|
|
type RegisterSetPasswordResponse struct {
|
|
Status string `json:"status"`
|
|
Message string `json:"message"`
|
|
Data *RegisterSetPasswordResponseData `json:"data,omitempty"`
|
|
}
|
|
|
|
type RegisterSetPasswordResponseData struct {
|
|
AccessToken string `json:"access_token"`
|
|
RefreshToken string `json:"refresh_token"`
|
|
User *CustomerUserData `json:"user"`
|
|
}
|
|
|
|
type CustomerUserData struct {
|
|
ID uuid.UUID `json:"id"`
|
|
Name string `json:"name"`
|
|
PhoneNumber string `json:"phone_number"`
|
|
BirthDate string `json:"birth_date"`
|
|
}
|
|
|
|
type CustomerLoginResponse struct {
|
|
Status string `json:"status"`
|
|
Message string `json:"message"`
|
|
Data *CustomerLoginResponseData `json:"data,omitempty"`
|
|
}
|
|
|
|
type CustomerLoginResponseData struct {
|
|
AccessToken string `json:"access_token"`
|
|
RefreshToken string `json:"refresh_token"`
|
|
User *CustomerUserData `json:"user"`
|
|
}
|
|
|
|
type ResendOtpResponse struct {
|
|
Status string `json:"status"`
|
|
Message string `json:"message"`
|
|
Data *ResendOtpResponseData `json:"data,omitempty"`
|
|
}
|
|
|
|
type ResendOtpResponseData struct {
|
|
OtpToken string `json:"otp_token"`
|
|
ExpiresIn int `json:"expires_in"`
|
|
NextResendIn int `json:"next_resend_in"` // Seconds until next resend is allowed
|
|
}
|
|
|
|
// Internal structures for OTP and registration tokens
|
|
type OtpSession struct {
|
|
Token string `json:"token"`
|
|
Code string `json:"code"`
|
|
PhoneNumber string `json:"phone_number"`
|
|
ExpiresAt time.Time `json:"expires_at"`
|
|
Purpose string `json:"purpose"` // "login" or "registration"
|
|
}
|
|
|
|
type RegistrationSession struct {
|
|
Token string `json:"token"`
|
|
PhoneNumber string `json:"phone_number"`
|
|
Name string `json:"name"`
|
|
BirthDate string `json:"birth_date"`
|
|
ExpiresAt time.Time `json:"expires_at"`
|
|
Step string `json:"step"` // "otp_sent", "otp_verified", "password_set"
|
|
}
|