feat: create user api
This commit is contained in:
@@ -28,6 +28,7 @@ Workspace membership CRUD::
|
||||
Platform employee roster::
|
||||
|
||||
GET /employees — list all non-deleted users
|
||||
POST /employees — create a user without workspace membership
|
||||
|
||||
Invariants
|
||||
----------
|
||||
@@ -50,10 +51,11 @@ from typing import Any, Literal
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from sqlalchemy import func, select, update
|
||||
from sqlalchemy import func, or_, select, update
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from backend.dependencies import current_user, database_session
|
||||
from common.auth.passwords import hash_password
|
||||
from common.db.models import Roles, Users, WorkspaceMembers, Workspaces
|
||||
from common.ids import new_ulid
|
||||
|
||||
@@ -111,6 +113,15 @@ class MemberUpdate(BaseModel):
|
||||
member_status: Literal["active", "disabled", "locked"] | None = None
|
||||
|
||||
|
||||
class PlatformEmployeeCreate(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
username: str = Field(min_length=2, max_length=64)
|
||||
display_name: str = Field(min_length=1, max_length=100)
|
||||
email: str | None = Field(default=None, max_length=255)
|
||||
password: str = Field(min_length=8, max_length=72)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# System-admin context dependency
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -302,6 +313,39 @@ async def list_platform_employees(
|
||||
)
|
||||
|
||||
|
||||
@router.post("/employees", status_code=status.HTTP_201_CREATED)
|
||||
async def create_platform_employee(
|
||||
payload: PlatformEmployeeCreate,
|
||||
context: SystemAdminContext = Depends(system_admin_context),
|
||||
session: AsyncSession = Depends(database_session),
|
||||
) -> dict[str, Any]:
|
||||
"""Create a platform user without assigning workspace membership."""
|
||||
username = payload.username.strip()
|
||||
display_name = payload.display_name.strip()
|
||||
duplicate_conditions = [Users.username == username]
|
||||
if payload.email:
|
||||
duplicate_conditions.append(Users.email == payload.email.strip())
|
||||
duplicate = await session.scalar(
|
||||
select(Users.user_id).where(or_(*duplicate_conditions))
|
||||
)
|
||||
if duplicate is not None:
|
||||
raise HTTPException(status.HTTP_409_CONFLICT, "用户名或邮箱已存在")
|
||||
|
||||
user = Users(
|
||||
user_id=new_ulid(),
|
||||
username=username,
|
||||
display_name=display_name,
|
||||
email=payload.email.strip() if payload.email else None,
|
||||
password_hash=hash_password(payload.password),
|
||||
status="active",
|
||||
platform_role_id=None,
|
||||
)
|
||||
session.add(user)
|
||||
await session.flush()
|
||||
await session.refresh(user)
|
||||
return _envelope(context.request_id, platform_employee_payload(user, None))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Workspace CRUD
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -502,8 +546,13 @@ async def add_member(
|
||||
"""Add a user to a workspace. The new row starts with member_status='active'."""
|
||||
await _load_workspace(session, workspace_id)
|
||||
user = await session.get(Users, payload.user_id)
|
||||
if user is None:
|
||||
if user is None or user.is_deleted != 0:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, "用户不存在")
|
||||
if user.status != "active":
|
||||
raise HTTPException(
|
||||
status.HTTP_409_CONFLICT,
|
||||
f"用户状态为 {user.status},无法加入 workspace",
|
||||
)
|
||||
role = await _load_role_by_code(session, payload.role_code)
|
||||
duplicate = await session.scalar(
|
||||
select(WorkspaceMembers.user_id).where(
|
||||
|
||||
Reference in New Issue
Block a user