feat: add wallet settlement audit flow

This commit is contained in:
2026-05-11 22:59:26 +08:00
parent da1e19d0a9
commit c992f1de60
22 changed files with 1452 additions and 44 deletions
+60 -33
View File
@@ -22,13 +22,15 @@ type Store struct {
}
var (
ErrInvalidCredentials = errors.New("invalid account or password")
ErrInvalidInvitation = errors.New("invalid or expired invitation code")
ErrAccessRuleResourceDenied = errors.New("access rule resource is not available")
ErrLocalUserRequired = errors.New("local gateway user is required")
ErrProtectedDefault = errors.New("protected default resource cannot be deleted")
ErrUserAlreadyExists = errors.New("user already exists")
ErrWeakPassword = errors.New("password must be at least 8 characters")
ErrInvalidCredentials = errors.New("invalid account or password")
ErrInvalidInvitation = errors.New("invalid or expired invitation code")
ErrAccessRuleResourceDenied = errors.New("access rule resource is not available")
ErrInsufficientWalletBalance = errors.New("insufficient wallet balance")
ErrLocalUserRequired = errors.New("local gateway user is required")
ErrWalletBalanceUnchanged = errors.New("wallet balance unchanged")
ErrProtectedDefault = errors.New("protected default resource cannot be deleted")
ErrUserAlreadyExists = errors.New("user already exists")
ErrWeakPassword = errors.New("password must be at least 8 characters")
)
func Connect(ctx context.Context, databaseURL string) (*Store, error) {
@@ -298,28 +300,29 @@ type LocalLoginInput struct {
}
type GatewayUser struct {
ID string `json:"id"`
UserKey string `json:"userKey"`
Source string `json:"source"`
ExternalUserID string `json:"externalUserId,omitempty"`
Username string `json:"username"`
DisplayName string `json:"displayName,omitempty"`
Email string `json:"email,omitempty"`
Phone string `json:"phone,omitempty"`
AvatarURL string `json:"avatarUrl,omitempty"`
GatewayTenantID string `json:"gatewayTenantId,omitempty"`
TenantID string `json:"tenantId,omitempty"`
TenantKey string `json:"tenantKey,omitempty"`
DefaultUserGroupID string `json:"defaultUserGroupId,omitempty"`
Roles []string `json:"roles,omitempty"`
AuthProfile map[string]any `json:"authProfile,omitempty"`
Metadata map[string]any `json:"metadata,omitempty"`
Status string `json:"status"`
LastLoginAt string `json:"lastLoginAt,omitempty"`
SyncedAt string `json:"syncedAt,omitempty"`
SourceUpdatedAt string `json:"sourceUpdatedAt,omitempty"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
ID string `json:"id"`
UserKey string `json:"userKey"`
Source string `json:"source"`
ExternalUserID string `json:"externalUserId,omitempty"`
Username string `json:"username"`
DisplayName string `json:"displayName,omitempty"`
Email string `json:"email,omitempty"`
Phone string `json:"phone,omitempty"`
AvatarURL string `json:"avatarUrl,omitempty"`
GatewayTenantID string `json:"gatewayTenantId,omitempty"`
TenantID string `json:"tenantId,omitempty"`
TenantKey string `json:"tenantKey,omitempty"`
DefaultUserGroupID string `json:"defaultUserGroupId,omitempty"`
Roles []string `json:"roles,omitempty"`
AuthProfile map[string]any `json:"authProfile,omitempty"`
Metadata map[string]any `json:"metadata,omitempty"`
WalletAccounts []GatewayWalletAccount `json:"walletAccounts,omitempty"`
Status string `json:"status"`
LastLoginAt string `json:"lastLoginAt,omitempty"`
SyncedAt string `json:"syncedAt,omitempty"`
SourceUpdatedAt string `json:"sourceUpdatedAt,omitempty"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
type UserGroup struct {
@@ -927,10 +930,31 @@ SELECT id::text, user_key, source, COALESCE(external_user_id, ''), username,
COALESCE(gateway_tenant_id::text, ''), COALESCE(tenant_id, ''), COALESCE(tenant_key, ''),
COALESCE(default_user_group_id::text, ''), roles, auth_profile, metadata,
status, COALESCE(last_login_at::text, ''), COALESCE(synced_at::text, ''), COALESCE(source_updated_at::text, ''),
created_at, updated_at
FROM gateway_users
WHERE deleted_at IS NULL
ORDER BY created_at DESC`)
created_at, updated_at, COALESCE(wallets.wallet_accounts, '[]'::jsonb)
FROM gateway_users u
LEFT JOIN LATERAL (
SELECT jsonb_agg(jsonb_build_object(
'id', a.id::text,
'gatewayTenantId', COALESCE(a.gateway_tenant_id::text, ''),
'gatewayUserId', a.gateway_user_id::text,
'tenantId', COALESCE(a.tenant_id, ''),
'tenantKey', COALESCE(a.tenant_key, ''),
'userId', COALESCE(a.user_id, ''),
'currency', a.currency,
'balance', a.balance::float8,
'frozenBalance', a.frozen_balance::float8,
'totalRecharged', a.total_recharged::float8,
'totalSpent', a.total_spent::float8,
'status', a.status,
'metadata', a.metadata,
'createdAt', a.created_at,
'updatedAt', a.updated_at
) ORDER BY a.currency ASC) AS wallet_accounts
FROM gateway_wallet_accounts a
WHERE a.gateway_user_id = u.id
) wallets ON true
WHERE u.deleted_at IS NULL
ORDER BY u.created_at DESC`)
if err != nil {
return nil, err
}
@@ -942,6 +966,7 @@ ORDER BY created_at DESC`)
var roles []byte
var authProfile []byte
var metadata []byte
var walletAccounts []byte
if err := rows.Scan(
&item.ID,
&item.UserKey,
@@ -965,12 +990,14 @@ ORDER BY created_at DESC`)
&item.SourceUpdatedAt,
&item.CreatedAt,
&item.UpdatedAt,
&walletAccounts,
); err != nil {
return nil, err
}
item.Roles = decodeStringArray(roles)
item.AuthProfile = decodeObject(authProfile)
item.Metadata = decodeObject(metadata)
item.WalletAccounts = decodeWalletAccounts(walletAccounts)
items = append(items, item)
}
return items, rows.Err()