Merge pull request #2932 from fatelei/issue-2917
fix: change balance to pointer type
This commit is contained in:
commit
a0057f44f2
@ -48,14 +48,14 @@ func NewUserHandler(
|
|||||||
|
|
||||||
// CreateUserRequest represents admin create user request
|
// CreateUserRequest represents admin create user request
|
||||||
type CreateUserRequest struct {
|
type CreateUserRequest struct {
|
||||||
Email string `json:"email" binding:"required,email"`
|
Email string `json:"email" binding:"required,email"`
|
||||||
Password string `json:"password" binding:"required,min=6"`
|
Password string `json:"password" binding:"required,min=6"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Notes string `json:"notes"`
|
Notes string `json:"notes"`
|
||||||
Balance float64 `json:"balance"`
|
Balance *float64 `json:"balance"`
|
||||||
Concurrency int `json:"concurrency"`
|
Concurrency int `json:"concurrency"`
|
||||||
RPMLimit int `json:"rpm_limit"`
|
RPMLimit int `json:"rpm_limit"`
|
||||||
AllowedGroups []int64 `json:"allowed_groups"`
|
AllowedGroups []int64 `json:"allowed_groups"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateUserRequest represents admin update user request
|
// UpdateUserRequest represents admin update user request
|
||||||
|
|||||||
@ -129,7 +129,7 @@ type CreateUserInput struct {
|
|||||||
Password string
|
Password string
|
||||||
Username string
|
Username string
|
||||||
Notes string
|
Notes string
|
||||||
Balance float64
|
Balance *float64
|
||||||
Concurrency int
|
Concurrency int
|
||||||
RPMLimit int
|
RPMLimit int
|
||||||
AllowedGroups []int64
|
AllowedGroups []int64
|
||||||
@ -680,12 +680,19 @@ func (s *adminServiceImpl) GetUserIncludeDeleted(ctx context.Context, id int64)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *adminServiceImpl) CreateUser(ctx context.Context, input *CreateUserInput) (*User, error) {
|
func (s *adminServiceImpl) CreateUser(ctx context.Context, input *CreateUserInput) (*User, error) {
|
||||||
|
balance := 0.0
|
||||||
|
if input.Balance != nil {
|
||||||
|
balance = *input.Balance
|
||||||
|
} else if s.settingService != nil {
|
||||||
|
balance = s.settingService.GetDefaultBalance(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
user := &User{
|
user := &User{
|
||||||
Email: input.Email,
|
Email: input.Email,
|
||||||
Username: input.Username,
|
Username: input.Username,
|
||||||
Notes: input.Notes,
|
Notes: input.Notes,
|
||||||
Role: RoleUser, // Always create as regular user, never admin
|
Role: RoleUser, // Always create as regular user, never admin
|
||||||
Balance: input.Balance,
|
Balance: balance,
|
||||||
Concurrency: input.Concurrency,
|
Concurrency: input.Concurrency,
|
||||||
RPMLimit: input.RPMLimit,
|
RPMLimit: input.RPMLimit,
|
||||||
Status: StatusActive,
|
Status: StatusActive,
|
||||||
|
|||||||
@ -14,13 +14,14 @@ import (
|
|||||||
func TestAdminService_CreateUser_Success(t *testing.T) {
|
func TestAdminService_CreateUser_Success(t *testing.T) {
|
||||||
repo := &userRepoStub{nextID: 10}
|
repo := &userRepoStub{nextID: 10}
|
||||||
svc := &adminServiceImpl{userRepo: repo}
|
svc := &adminServiceImpl{userRepo: repo}
|
||||||
|
balance := 12.5
|
||||||
|
|
||||||
input := &CreateUserInput{
|
input := &CreateUserInput{
|
||||||
Email: "user@test.com",
|
Email: "user@test.com",
|
||||||
Password: "strong-pass",
|
Password: "strong-pass",
|
||||||
Username: "tester",
|
Username: "tester",
|
||||||
Notes: "note",
|
Notes: "note",
|
||||||
Balance: 12.5,
|
Balance: &balance,
|
||||||
Concurrency: 7,
|
Concurrency: 7,
|
||||||
AllowedGroups: []int64{3, 5},
|
AllowedGroups: []int64{3, 5},
|
||||||
}
|
}
|
||||||
@ -32,7 +33,7 @@ func TestAdminService_CreateUser_Success(t *testing.T) {
|
|||||||
require.Equal(t, input.Email, user.Email)
|
require.Equal(t, input.Email, user.Email)
|
||||||
require.Equal(t, input.Username, user.Username)
|
require.Equal(t, input.Username, user.Username)
|
||||||
require.Equal(t, input.Notes, user.Notes)
|
require.Equal(t, input.Notes, user.Notes)
|
||||||
require.Equal(t, input.Balance, user.Balance)
|
require.Equal(t, balance, user.Balance)
|
||||||
require.Equal(t, input.Concurrency, user.Concurrency)
|
require.Equal(t, input.Concurrency, user.Concurrency)
|
||||||
require.Equal(t, input.AllowedGroups, user.AllowedGroups)
|
require.Equal(t, input.AllowedGroups, user.AllowedGroups)
|
||||||
require.Equal(t, RoleUser, user.Role)
|
require.Equal(t, RoleUser, user.Role)
|
||||||
@ -42,6 +43,56 @@ func TestAdminService_CreateUser_Success(t *testing.T) {
|
|||||||
require.Equal(t, user, repo.created[0])
|
require.Equal(t, user, repo.created[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAdminService_CreateUser_UsesDefaultBalanceWhenBalanceOmitted(t *testing.T) {
|
||||||
|
repo := &userRepoStub{nextID: 11}
|
||||||
|
cfg := &config.Config{
|
||||||
|
Default: config.DefaultConfig{
|
||||||
|
UserBalance: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
settingService := NewSettingService(&settingRepoStub{values: map[string]string{
|
||||||
|
SettingKeyDefaultBalance: "0.02",
|
||||||
|
}}, cfg)
|
||||||
|
svc := &adminServiceImpl{userRepo: repo, settingService: settingService}
|
||||||
|
|
||||||
|
user, err := svc.CreateUser(context.Background(), &CreateUserInput{
|
||||||
|
Email: "default-balance@test.com",
|
||||||
|
Password: "strong-pass",
|
||||||
|
})
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, user)
|
||||||
|
require.Equal(t, 0.02, user.Balance)
|
||||||
|
require.Len(t, repo.created, 1)
|
||||||
|
require.Equal(t, 0.02, repo.created[0].Balance)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdminService_CreateUser_ExplicitZeroBalanceOverridesDefault(t *testing.T) {
|
||||||
|
repo := &userRepoStub{nextID: 12}
|
||||||
|
cfg := &config.Config{
|
||||||
|
Default: config.DefaultConfig{
|
||||||
|
UserBalance: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
settingService := NewSettingService(&settingRepoStub{values: map[string]string{
|
||||||
|
SettingKeyDefaultBalance: "0.02",
|
||||||
|
}}, cfg)
|
||||||
|
svc := &adminServiceImpl{userRepo: repo, settingService: settingService}
|
||||||
|
balance := 0.0
|
||||||
|
|
||||||
|
user, err := svc.CreateUser(context.Background(), &CreateUserInput{
|
||||||
|
Email: "zero-balance@test.com",
|
||||||
|
Password: "strong-pass",
|
||||||
|
Balance: &balance,
|
||||||
|
})
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, user)
|
||||||
|
require.Equal(t, 0.0, user.Balance)
|
||||||
|
require.Len(t, repo.created, 1)
|
||||||
|
require.Equal(t, 0.0, repo.created[0].Balance)
|
||||||
|
}
|
||||||
|
|
||||||
func TestAdminService_CreateUser_EmailExists(t *testing.T) {
|
func TestAdminService_CreateUser_EmailExists(t *testing.T) {
|
||||||
repo := &userRepoStub{createErr: ErrEmailExists}
|
repo := &userRepoStub{createErr: ErrEmailExists}
|
||||||
svc := &adminServiceImpl{userRepo: repo}
|
svc := &adminServiceImpl{userRepo: repo}
|
||||||
|
|||||||
@ -117,8 +117,11 @@ export async function getById(id: number, includeDeleted = false): Promise<Admin
|
|||||||
export async function create(userData: {
|
export async function create(userData: {
|
||||||
email: string
|
email: string
|
||||||
password: string
|
password: string
|
||||||
|
username?: string
|
||||||
|
notes?: string
|
||||||
balance?: number
|
balance?: number
|
||||||
concurrency?: number
|
concurrency?: number
|
||||||
|
rpm_limit?: number
|
||||||
allowed_groups?: number[] | null
|
allowed_groups?: number[] | null
|
||||||
}): Promise<AdminUser> {
|
}): Promise<AdminUser> {
|
||||||
const { data } = await apiClient.post<AdminUser>('/admin/users', userData)
|
const { data } = await apiClient.post<AdminUser>('/admin/users', userData)
|
||||||
|
|||||||
@ -28,7 +28,7 @@
|
|||||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label class="input-label">{{ t('admin.users.columns.balance') }}</label>
|
<label class="input-label">{{ t('admin.users.columns.balance') }}</label>
|
||||||
<input v-model.number="form.balance" type="number" step="any" class="input" />
|
<input v-model="form.balance" type="number" step="any" class="input" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="input-label">{{ t('admin.users.columns.concurrency') }}</label>
|
<label class="input-label">{{ t('admin.users.columns.concurrency') }}</label>
|
||||||
@ -69,18 +69,24 @@ import Icon from '@/components/icons/Icon.vue'
|
|||||||
const props = defineProps<{ show: boolean }>()
|
const props = defineProps<{ show: boolean }>()
|
||||||
const emit = defineEmits(['close', 'success']); const { t } = useI18n()
|
const emit = defineEmits(['close', 'success']); const { t } = useI18n()
|
||||||
|
|
||||||
const form = reactive({ email: '', password: '', username: '', notes: '', balance: 0, concurrency: 1, rpm_limit: 0 })
|
const form = reactive({ email: '', password: '', username: '', notes: '', balance: '', concurrency: 1, rpm_limit: 0 })
|
||||||
|
|
||||||
const { loading, submit } = useForm({
|
const { loading, submit } = useForm({
|
||||||
form,
|
form,
|
||||||
submitFn: async (data) => {
|
submitFn: async (data) => {
|
||||||
await adminAPI.users.create(data)
|
const { balance: rawBalance, ...rest } = data
|
||||||
|
const balance = String(rawBalance).trim()
|
||||||
|
const payload: typeof rest & { balance?: number } = { ...rest }
|
||||||
|
if (balance !== '') {
|
||||||
|
payload.balance = Number(balance)
|
||||||
|
}
|
||||||
|
await adminAPI.users.create(payload)
|
||||||
emit('success'); emit('close')
|
emit('success'); emit('close')
|
||||||
},
|
},
|
||||||
successMsg: t('admin.users.userCreated')
|
successMsg: t('admin.users.userCreated')
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(() => props.show, (v) => { if(v) Object.assign(form, { email: '', password: '', username: '', notes: '', balance: 0, concurrency: 1, rpm_limit: 0 }) })
|
watch(() => props.show, (v) => { if(v) Object.assign(form, { email: '', password: '', username: '', notes: '', balance: '', concurrency: 1, rpm_limit: 0 }) })
|
||||||
|
|
||||||
const generateRandomPassword = () => {
|
const generateRandomPassword = () => {
|
||||||
const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789!@#$%^&*'
|
const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789!@#$%^&*'
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user