From af2b599dd6af2cf12643721cf33784b95958f9eb Mon Sep 17 00:00:00 2001 From: Whispering Wind <151555003+ReturnFI@users.noreply.github.com> Date: Wed, 20 Aug 2025 23:59:15 +0330 Subject: [PATCH] feat(api): add bulk user creation endpoint --- .../webpanel/routers/api/v1/schema/user.py | 30 ++++++++++++++++++- core/scripts/webpanel/routers/api/v1/user.py | 27 +++++++++++++++-- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/core/scripts/webpanel/routers/api/v1/schema/user.py b/core/scripts/webpanel/routers/api/v1/schema/user.py index 8d1fbeb..4f69289 100644 --- a/core/scripts/webpanel/routers/api/v1/schema/user.py +++ b/core/scripts/webpanel/routers/api/v1/schema/user.py @@ -1,5 +1,6 @@ +import re from typing import Optional, List -from pydantic import BaseModel, RootModel, Field +from pydantic import BaseModel, RootModel, Field, field_validator class UserInfoResponse(BaseModel): @@ -26,6 +27,27 @@ class AddUserInputBody(BaseModel): creation_date: Optional[str] = None 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): new_username: Optional[str] = None @@ -36,6 +58,12 @@ class EditUserInputBody(BaseModel): blocked: 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): name: str uri: str diff --git a/core/scripts/webpanel/routers/api/v1/user.py b/core/scripts/webpanel/routers/api/v1/user.py index 63f262e..2164a48 100644 --- a/core/scripts/webpanel/routers/api/v1/user.py +++ b/core/scripts/webpanel/routers/api/v1/user.py @@ -1,7 +1,7 @@ import json 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 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)}") +@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) async def get_user_api(username: str): """ @@ -191,4 +214,4 @@ async def show_user_uri_api(username: str): except HTTPException: raise 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)}') \ No newline at end of file