feat(api): add bulk user creation endpoint

This commit is contained in:
Whispering Wind
2025-08-20 23:59:15 +03:30
committed by GitHub
parent 5a762ad918
commit af2b599dd6
2 changed files with 54 additions and 3 deletions

View File

@ -1,5 +1,6 @@
import re
from typing import Optional, List from typing import Optional, List
from pydantic import BaseModel, RootModel, Field from pydantic import BaseModel, RootModel, Field, field_validator
class UserInfoResponse(BaseModel): class UserInfoResponse(BaseModel):
@ -26,6 +27,27 @@ class AddUserInputBody(BaseModel):
creation_date: Optional[str] = None creation_date: Optional[str] = None
unlimited: bool = False unlimited: bool = False
@field_validator('username')
def validate_username(cls, v):
if not re.match(r"^[a-zA-Z0-9_]+$", v):
raise ValueError('Username can only contain letters, numbers, and underscores.')
return v
class AddBulkUsersInputBody(BaseModel):
traffic_gb: float
expiration_days: int
count: int
prefix: str
start_number: int = 1
unlimited: bool = False
@field_validator('prefix')
def validate_prefix(cls, v):
if not re.match(r"^[a-zA-Z0-9_]*$", v):
raise ValueError('Prefix can only contain letters, numbers, and underscores.')
return v
class EditUserInputBody(BaseModel): class EditUserInputBody(BaseModel):
new_username: Optional[str] = None new_username: Optional[str] = None
@ -36,6 +58,12 @@ class EditUserInputBody(BaseModel):
blocked: Optional[bool] = None blocked: Optional[bool] = None
unlimited_ip: Optional[bool] = None unlimited_ip: Optional[bool] = None
@field_validator('new_username')
def validate_new_username(cls, v):
if v and not re.match(r"^[a-zA-Z0-9_]+$", v):
raise ValueError('Username can only contain letters, numbers, and underscores.')
return v
class NodeUri(BaseModel): class NodeUri(BaseModel):
name: str name: str
uri: str uri: str

View File

@ -1,7 +1,7 @@
import json import json
from fastapi import APIRouter, HTTPException from fastapi import APIRouter, HTTPException
from .schema.user import UserListResponse, UserInfoResponse, AddUserInputBody, EditUserInputBody, UserUriResponse from .schema.user import UserListResponse, UserInfoResponse, AddUserInputBody, EditUserInputBody, UserUriResponse, AddBulkUsersInputBody
from .schema.response import DetailResponse from .schema.response import DetailResponse
import cli_api import cli_api
@ -57,6 +57,29 @@ async def add_user_api(body: AddUserInputBody):
detail=f"An unexpected error occurred while adding user '{body.username}': {str(e)}") detail=f"An unexpected error occurred while adding user '{body.username}': {str(e)}")
@router.post('/bulk/', response_model=DetailResponse, status_code=201)
async def add_bulk_users_api(body: AddBulkUsersInputBody):
"""
Add multiple users in bulk.
"""
try:
cli_api.bulk_user_add(
traffic_gb=body.traffic_gb,
expiration_days=body.expiration_days,
count=body.count,
prefix=body.prefix,
start_number=body.start_number,
unlimited=body.unlimited
)
return DetailResponse(detail=f"Successfully started adding {body.count} users with prefix '{body.prefix}'.")
except cli_api.CommandExecutionError as e:
raise HTTPException(status_code=400,
detail=f'Failed to add bulk users: {str(e)}')
except Exception as e:
raise HTTPException(status_code=500,
detail=f"An unexpected error occurred while adding bulk users: {str(e)}")
@router.get('/{username}', response_model=UserInfoResponse) @router.get('/{username}', response_model=UserInfoResponse)
async def get_user_api(username: str): async def get_user_api(username: str):
""" """
@ -191,4 +214,4 @@ async def show_user_uri_api(username: str):
except HTTPException: except HTTPException:
raise raise
except Exception as e: except Exception as e:
raise HTTPException(status_code=400, detail=f'Unexpected error: {str(e)}') raise HTTPException(status_code=400, detail=f'Unexpected error: {str(e)}')