Documentation
¶
Overview ¶
Package user is the business-logic layer for registered users — the people the platform serves and the staff who administer it. A user is identified by a mobile number, and carries a national ID, a KYC state, a role, and a lifecycle status. Service wraps the user repository and adds validation, uniqueness, the state machines below, and the guardrails that protect administrators from locking themselves out.
Build a Service with NewService. It creates users, looks them up (by ID, mobile number, or national ID), lists them with filters and pagination, and updates their profile, KYC, status, and role. It also exposes the counts the admin dashboards report on. Requests and responses are the DTOs in dto.go; failures are the sentinel errors in errors.go, which handlers map onto HTTP status codes. The mobile number and national ID are each unique across all users, and a duplicate is rejected at creation.
Three dimensions of state ¶
A user's state is tracked along three independent axes:
- KYC moves from pending to verified or rejected; a verified user can lapse to expired, and expired or rejected users return to pending to try again.
- Status follows the same lifecycle as accounts — active, suspended, frozen, blocked, closed — with closed terminal.
- Role is one of user, admin, or agent.
KYC and status changes are checked against a transition table and an illegal move is rejected with ErrInvalidKYCTransition or ErrInvalidStatusTransition.
Administrative guardrails ¶
The privileged operations take the requester's ID alongside the target's, so the service can refuse changes that would compromise administration: a user cannot delete their own account (ErrCannotDeleteSelf), cannot change their own role (ErrCannotChangeOwnRole), and the last remaining admin cannot be deleted (ErrCannotDeleteLastAdmin). A deleted user cannot be modified until restored.
Transactions ¶
CreateWithTx runs inside a *gorm.DB the caller owns, so a user can be created in the same database transaction as their first account.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( // Resource not found errors ErrUserNotFound = errors.New("user not found") // Conflict errors ErrMobileNumberAlreadyExists = errors.New("mobile number already registered") ErrNationalIDAlreadyExists = errors.New("national ID already registered") // Business logic errors ErrCannotDeleteLastAdmin = errors.New("cannot delete the last admin") ErrInvalidKYCTransition = errors.New("invalid KYC status transition") ErrCannotModifyDeletedUser = errors.New("cannot modify deleted user") ErrUserAlreadyDeleted = errors.New("user is already deleted") ErrUserNotDeleted = errors.New("user is not deleted") ErrInvalidStatusTransition = errors.New("invalid status transition") ErrCannotDeleteSelf = errors.New("cannot delete your own account") ErrCannotChangeOwnRole = errors.New("cannot change your own role") // Validation errors ErrInvalidInput = errors.New("invalid input") ErrInvalidMobileNumber = errors.New("invalid mobile number format") ErrInvalidNationalID = errors.New("invalid national ID format") ErrInvalidRole = errors.New("invalid role") ErrInvalidKYCStatus = errors.New("invalid KYC status") ErrInvalidUserStatus = errors.New("invalid user status") )
User service specific errors
Functions ¶
This section is empty.
Types ¶
type CreateUserRequest ¶
type CreateUserRequest struct {
MobileNumber string `json:"mobile_number" validate:"required"`
CountryCode string `json:"country_code"`
BirthDate *models.Date `json:"birth_date,omitempty"`
Address string `json:"address,omitempty"`
City string `json:"city,omitempty"`
PostalCode string `json:"postal_code,omitempty"`
MobileNetworkCode string `json:"mobile_network_code"`
MomoNetworkCode string `json:"momo_network_code"`
MomoNetworkName string `json:"momo_network_name,omitempty"`
TelcoName string `json:"telco_name,omitempty"`
FullName string `json:"full_name"`
NationalID string `json:"national_id"`
PreferredLanguage string `json:"preferred_language,omitempty"`
Role string `json:"role,omitempty"` // For admin creating users with specific roles
// PinHash + PinSetAt let a user be created with a PIN already set, in one
// atomic insert (USSD registration). Both nil for admin/API creation.
PinHash *string `json:"-"`
PinSetAt *time.Time `json:"-"`
}
CreateUserRequest represents the request to create a new user
type Service ¶
type Service interface {
// User management
Create(ctx context.Context, req CreateUserRequest) (*UserResponse, error)
CreateWithTx(ctx context.Context, tx *gorm.DB, req CreateUserRequest) (*UserResponse, error)
GetByID(ctx context.Context, id string) (*UserResponse, error)
GetByMobileNumber(ctx context.Context, mobileNumber string) (*UserResponse, error)
GetByNationalID(ctx context.Context, nationalID string) (*UserResponse, error)
// RebindMobileNumber moves an account to a new MSISDN. Used only by
// new-SIM recovery, after ownership has been verified — it changes the
// account's identity anchor.
RebindMobileNumber(ctx context.Context, userID, mobileNumber string) error
List(ctx context.Context, filters UserFilters, pagination services.Pagination) (*services.PaginatedResponse[UserResponse], error)
Update(ctx context.Context, id string, req UpdateUserRequest) (*UserResponse, error)
Delete(ctx context.Context, requesterID, targetID string) error
Restore(ctx context.Context, requesterID, targetID string) error
// KYC management
UpdateKYCStatus(ctx context.Context, adminID, userID string, req UpdateKYCStatusRequest) (*UserResponse, error)
// Status management
UpdateUserStatus(ctx context.Context, adminID, userID string, req UpdateUserStatusRequest) (*UserResponse, error)
// Role management
UpdateUserRole(ctx context.Context, adminID, userID string, req UpdateUserRoleRequest) (*UserResponse, error)
// Admin operations and analytics
Count(ctx context.Context) (int64, error)
CountByKYCStatus(ctx context.Context, kycStatus string) (int64, error)
CountByRole(ctx context.Context, role string) (int64, error)
CountAdmins(ctx context.Context) (int, error)
}
Service defines the interface for user business logic operations
func NewService ¶
func NewService(repo repository.UserRepository) Service
NewService creates a new user service instance
type UpdateKYCStatusRequest ¶
type UpdateKYCStatusRequest struct {
KYCStatus string `json:"kyc_status" validate:"required,oneof=pending in_progress verified rejected"`
}
UpdateKYCStatusRequest represents the request to update user KYC status
type UpdateUserRequest ¶
type UpdateUserRequest struct {
FullName *string `json:"full_name,omitempty"`
NationalID *string `json:"national_id,omitempty"`
BirthDate *models.Date `json:"birth_date,omitempty"`
Address *string `json:"address,omitempty"`
City *string `json:"city,omitempty"`
PostalCode *string `json:"postal_code,omitempty"`
PreferredLanguage *string `json:"preferred_language,omitempty"`
}
UpdateUserRequest represents the request to update user information
type UpdateUserRoleRequest ¶
type UpdateUserRoleRequest struct {
Role string `json:"role" validate:"required,oneof=user admin agent"`
}
UpdateUserRoleRequest represents the request to update user role
type UpdateUserStatusRequest ¶
type UpdateUserStatusRequest struct {
Status string `json:"status" validate:"required,oneof=active suspended inactive"`
}
UpdateUserStatusRequest represents the request to update user status
type UserFilters ¶
type UserFilters struct {
KYCStatus string `json:"kyc_status,omitempty"`
Role string `json:"role,omitempty"`
Status string `json:"status,omitempty"`
}
UserFilters represents the filters for listing users
type UserResponse ¶
type UserResponse struct {
ID string `json:"id"`
MobileNumber string `json:"mobile_number"`
CountryCode string `json:"country_code"`
BirthDate *models.Date `json:"birth_date,omitempty"`
Address string `json:"address,omitempty"`
City string `json:"city,omitempty"`
PostalCode string `json:"postal_code,omitempty"`
MobileNetworkCode string `json:"mobile_network_code"`
MomoNetworkCode string `json:"momo_network_code"`
MomoNetworkName string `json:"momo_network_name,omitempty"`
TelcoName string `json:"telco_name,omitempty"`
FullName string `json:"full_name,omitempty"`
NationalID string `json:"national_id,omitempty"`
KYCStatus string `json:"kyc_status"`
KYCVerifiedAt *time.Time `json:"kyc_verified_at,omitempty"`
PreferredLanguage string `json:"preferred_language"`
Status string `json:"status"`
Role string `json:"role"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
UserResponse represents the response containing user information