更新metadb引用路径
This commit is contained in:
@@ -3,12 +3,13 @@
|
|||||||
|
|
||||||
仅管理员可访问
|
仅管理员可访问
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from fastapi import APIRouter, Depends, Query, Path
|
from fastapi import APIRouter, Depends, Query, Path
|
||||||
from app.domain.schemas.audit import AuditLogResponse
|
from app.domain.schemas.audit import AuditLogResponse
|
||||||
from app.infra.repositories.audit_repository import AuditRepository
|
from app.infra.db.metadb.repositories.audit_repository import AuditRepository
|
||||||
from app.auth.metadata_dependencies import (
|
from app.auth.metadata_dependencies import (
|
||||||
get_current_metadata_admin,
|
get_current_metadata_admin,
|
||||||
get_current_metadata_user,
|
get_current_metadata_user,
|
||||||
@@ -18,13 +19,20 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
async def get_audit_repository(
|
async def get_audit_repository(
|
||||||
session: AsyncSession = Depends(get_metadata_session),
|
session: AsyncSession = Depends(get_metadata_session),
|
||||||
) -> AuditRepository:
|
) -> AuditRepository:
|
||||||
"""获取审计日志仓储"""
|
"""获取审计日志仓储"""
|
||||||
return AuditRepository(session)
|
return AuditRepository(session)
|
||||||
|
|
||||||
@router.get("/logs", summary="查询审计日志", description="查询审计日志(仅管理员)", response_model=List[AuditLogResponse])
|
|
||||||
|
@router.get(
|
||||||
|
"/logs",
|
||||||
|
summary="查询审计日志",
|
||||||
|
description="查询审计日志(仅管理员)",
|
||||||
|
response_model=List[AuditLogResponse],
|
||||||
|
)
|
||||||
async def get_audit_logs(
|
async def get_audit_logs(
|
||||||
user_id: Optional[UUID] = Query(None, description="按用户ID过滤"),
|
user_id: Optional[UUID] = Query(None, description="按用户ID过滤"),
|
||||||
project_id: Optional[UUID] = Query(None, description="按项目ID过滤"),
|
project_id: Optional[UUID] = Query(None, description="按项目ID过滤"),
|
||||||
@@ -50,11 +58,16 @@ async def get_audit_logs(
|
|||||||
start_time=start_time,
|
start_time=start_time,
|
||||||
end_time=end_time,
|
end_time=end_time,
|
||||||
skip=skip,
|
skip=skip,
|
||||||
limit=limit
|
limit=limit,
|
||||||
)
|
)
|
||||||
return logs
|
return logs
|
||||||
|
|
||||||
@router.get("/logs/count", summary="获取审计日志总数", description="获取审计日志总数(仅管理员)")
|
|
||||||
|
@router.get(
|
||||||
|
"/logs/count",
|
||||||
|
summary="获取审计日志总数",
|
||||||
|
description="获取审计日志总数(仅管理员)",
|
||||||
|
)
|
||||||
async def get_audit_logs_count(
|
async def get_audit_logs_count(
|
||||||
user_id: Optional[UUID] = Query(None, description="按用户ID过滤"),
|
user_id: Optional[UUID] = Query(None, description="按用户ID过滤"),
|
||||||
project_id: Optional[UUID] = Query(None, description="按项目ID过滤"),
|
project_id: Optional[UUID] = Query(None, description="按项目ID过滤"),
|
||||||
@@ -76,11 +89,17 @@ async def get_audit_logs_count(
|
|||||||
action=action,
|
action=action,
|
||||||
resource_type=resource_type,
|
resource_type=resource_type,
|
||||||
start_time=start_time,
|
start_time=start_time,
|
||||||
end_time=end_time
|
end_time=end_time,
|
||||||
)
|
)
|
||||||
return {"count": count}
|
return {"count": count}
|
||||||
|
|
||||||
@router.get("/logs/my", summary="查询我的审计日志", description="查询当前用户的审计日志", response_model=List[AuditLogResponse])
|
|
||||||
|
@router.get(
|
||||||
|
"/logs/my",
|
||||||
|
summary="查询我的审计日志",
|
||||||
|
description="查询当前用户的审计日志",
|
||||||
|
response_model=List[AuditLogResponse],
|
||||||
|
)
|
||||||
async def get_my_audit_logs(
|
async def get_my_audit_logs(
|
||||||
action: Optional[str] = Query(None, description="按操作类型过滤"),
|
action: Optional[str] = Query(None, description="按操作类型过滤"),
|
||||||
start_time: Optional[datetime] = Query(None, description="开始时间"),
|
start_time: Optional[datetime] = Query(None, description="开始时间"),
|
||||||
@@ -101,6 +120,6 @@ async def get_my_audit_logs(
|
|||||||
start_time=start_time,
|
start_time=start_time,
|
||||||
end_time=end_time,
|
end_time=end_time,
|
||||||
skip=skip,
|
skip=skip,
|
||||||
limit=limit
|
limit=limit,
|
||||||
)
|
)
|
||||||
return logs
|
return logs
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from fastapi.security import OAuth2PasswordRequestForm
|
|||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
from app.core.security import create_access_token, create_refresh_token, verify_password
|
from app.core.security import create_access_token, create_refresh_token, verify_password
|
||||||
from app.domain.schemas.user import UserCreate, UserResponse, UserLogin, Token
|
from app.domain.schemas.user import UserCreate, UserResponse, UserLogin, Token
|
||||||
from app.infra.repositories.user_repository import UserRepository
|
from app.infra.db.metadb.repositories.user_repository import UserRepository
|
||||||
from app.auth.dependencies import get_user_repository, get_current_active_user
|
from app.auth.dependencies import get_user_repository, get_current_active_user
|
||||||
from app.domain.schemas.user import UserInDB
|
from app.domain.schemas.user import UserInDB
|
||||||
import logging
|
import logging
|
||||||
@@ -14,10 +14,12 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
@router.post("/register", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
|
|
||||||
|
@router.post(
|
||||||
|
"/register", response_model=UserResponse, status_code=status.HTTP_201_CREATED
|
||||||
|
)
|
||||||
async def register(
|
async def register(
|
||||||
user_data: UserCreate,
|
user_data: UserCreate, user_repo: UserRepository = Depends(get_user_repository)
|
||||||
user_repo: UserRepository = Depends(get_user_repository)
|
|
||||||
) -> UserResponse:
|
) -> UserResponse:
|
||||||
"""
|
"""
|
||||||
用户注册
|
用户注册
|
||||||
@@ -28,13 +30,12 @@ async def register(
|
|||||||
if await user_repo.user_exists(username=user_data.username):
|
if await user_repo.user_exists(username=user_data.username):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
detail="Username already registered"
|
detail="Username already registered",
|
||||||
)
|
)
|
||||||
|
|
||||||
if await user_repo.user_exists(email=user_data.email):
|
if await user_repo.user_exists(email=user_data.email):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST, detail="Email already registered"
|
||||||
detail="Email already registered"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# 创建用户
|
# 创建用户
|
||||||
@@ -43,20 +44,21 @@ async def register(
|
|||||||
if not user:
|
if not user:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
detail="Failed to create user"
|
detail="Failed to create user",
|
||||||
)
|
)
|
||||||
return UserResponse.model_validate(user)
|
return UserResponse.model_validate(user)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error during user registration: {e}")
|
logger.error(f"Error during user registration: {e}")
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
detail="Registration failed"
|
detail="Registration failed",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/login", response_model=Token)
|
@router.post("/login", response_model=Token)
|
||||||
async def login(
|
async def login(
|
||||||
form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
|
form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
|
||||||
user_repo: UserRepository = Depends(get_user_repository)
|
user_repo: UserRepository = Depends(get_user_repository),
|
||||||
) -> Token:
|
) -> Token:
|
||||||
"""
|
"""
|
||||||
用户登录(OAuth2 标准格式)
|
用户登录(OAuth2 标准格式)
|
||||||
@@ -78,8 +80,7 @@ async def login(
|
|||||||
|
|
||||||
if not user.is_active:
|
if not user.is_active:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_403_FORBIDDEN,
|
status_code=status.HTTP_403_FORBIDDEN, detail="Inactive user account"
|
||||||
detail="Inactive user account"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# 生成 Token
|
# 生成 Token
|
||||||
@@ -90,14 +91,15 @@ async def login(
|
|||||||
access_token=access_token,
|
access_token=access_token,
|
||||||
refresh_token=refresh_token,
|
refresh_token=refresh_token,
|
||||||
token_type="bearer",
|
token_type="bearer",
|
||||||
expires_in=settings.ACCESS_TOKEN_EXPIRE_MINUTES * 60
|
expires_in=settings.ACCESS_TOKEN_EXPIRE_MINUTES * 60,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/login/simple", response_model=Token)
|
@router.post("/login/simple", response_model=Token)
|
||||||
async def login_simple(
|
async def login_simple(
|
||||||
username: str,
|
username: str,
|
||||||
password: str,
|
password: str,
|
||||||
user_repo: UserRepository = Depends(get_user_repository)
|
user_repo: UserRepository = Depends(get_user_repository),
|
||||||
) -> Token:
|
) -> Token:
|
||||||
"""
|
"""
|
||||||
简化版登录接口(保持向后兼容)
|
简化版登录接口(保持向后兼容)
|
||||||
@@ -112,13 +114,12 @@ async def login_simple(
|
|||||||
if not user or not verify_password(password, user.hashed_password):
|
if not user or not verify_password(password, user.hashed_password):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
detail="Incorrect username or password"
|
detail="Incorrect username or password",
|
||||||
)
|
)
|
||||||
|
|
||||||
if not user.is_active:
|
if not user.is_active:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_403_FORBIDDEN,
|
status_code=status.HTTP_403_FORBIDDEN, detail="Inactive user account"
|
||||||
detail="Inactive user account"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# 生成 Token
|
# 生成 Token
|
||||||
@@ -129,22 +130,23 @@ async def login_simple(
|
|||||||
access_token=access_token,
|
access_token=access_token,
|
||||||
refresh_token=refresh_token,
|
refresh_token=refresh_token,
|
||||||
token_type="bearer",
|
token_type="bearer",
|
||||||
expires_in=settings.ACCESS_TOKEN_EXPIRE_MINUTES * 60
|
expires_in=settings.ACCESS_TOKEN_EXPIRE_MINUTES * 60,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/me", response_model=UserResponse)
|
@router.get("/me", response_model=UserResponse)
|
||||||
async def get_current_user_info(
|
async def get_current_user_info(
|
||||||
current_user: UserInDB = Depends(get_current_active_user)
|
current_user: UserInDB = Depends(get_current_active_user),
|
||||||
) -> UserResponse:
|
) -> UserResponse:
|
||||||
"""
|
"""
|
||||||
获取当前登录用户信息
|
获取当前登录用户信息
|
||||||
"""
|
"""
|
||||||
return UserResponse.model_validate(current_user)
|
return UserResponse.model_validate(current_user)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/refresh", response_model=Token)
|
@router.post("/refresh", response_model=Token)
|
||||||
async def refresh_token(
|
async def refresh_token(
|
||||||
refresh_token: str,
|
refresh_token: str, user_repo: UserRepository = Depends(get_user_repository)
|
||||||
user_repo: UserRepository = Depends(get_user_repository)
|
|
||||||
) -> Token:
|
) -> Token:
|
||||||
"""
|
"""
|
||||||
刷新 Access Token
|
刷新 Access Token
|
||||||
@@ -160,7 +162,9 @@ async def refresh_token(
|
|||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
payload = jwt.decode(refresh_token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
|
payload = jwt.decode(
|
||||||
|
refresh_token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]
|
||||||
|
)
|
||||||
username: str = payload.get("sub")
|
username: str = payload.get("sub")
|
||||||
token_type: str = payload.get("type")
|
token_type: str = payload.get("type")
|
||||||
|
|
||||||
@@ -182,5 +186,5 @@ async def refresh_token(
|
|||||||
access_token=new_access_token,
|
access_token=new_access_token,
|
||||||
refresh_token=refresh_token, # 保持原 refresh token
|
refresh_token=refresh_token, # 保持原 refresh token
|
||||||
token_type="bearer",
|
token_type="bearer",
|
||||||
expires_in=settings.ACCESS_TOKEN_EXPIRE_MINUTES * 60
|
expires_in=settings.ACCESS_TOKEN_EXPIRE_MINUTES * 60,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ from app.domain.schemas.metadata import (
|
|||||||
ProjectMetaResponse,
|
ProjectMetaResponse,
|
||||||
ProjectSummaryResponse,
|
ProjectSummaryResponse,
|
||||||
)
|
)
|
||||||
from app.infra.repositories.metadata_repository import MetadataRepository
|
from app.infra.db.metadb.repositories.metadata_repository import MetadataRepository
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -3,23 +3,30 @@
|
|||||||
|
|
||||||
演示权限控制的使用
|
演示权限控制的使用
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import List
|
from typing import List
|
||||||
from fastapi import APIRouter, Depends, HTTPException, status, Path, Query
|
from fastapi import APIRouter, Depends, HTTPException, status, Path, Query
|
||||||
from app.domain.schemas.user import UserResponse, UserUpdate, UserCreate
|
from app.domain.schemas.user import UserResponse, UserUpdate, UserCreate
|
||||||
from app.domain.models.role import UserRole
|
from app.domain.models.role import UserRole
|
||||||
from app.domain.schemas.user import UserInDB
|
from app.domain.schemas.user import UserInDB
|
||||||
from app.infra.repositories.user_repository import UserRepository
|
from app.infra.db.metadb.repositories.user_repository import UserRepository
|
||||||
from app.auth.dependencies import get_user_repository, get_current_active_user
|
from app.auth.dependencies import get_user_repository, get_current_active_user
|
||||||
from app.auth.permissions import get_current_admin, require_role, check_resource_owner
|
from app.auth.permissions import get_current_admin, require_role, check_resource_owner
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
@router.get("/", summary="列出所有用户", description="获取用户列表(仅管理员)", response_model=List[UserResponse])
|
|
||||||
|
@router.get(
|
||||||
|
"/",
|
||||||
|
summary="列出所有用户",
|
||||||
|
description="获取用户列表(仅管理员)",
|
||||||
|
response_model=List[UserResponse],
|
||||||
|
)
|
||||||
async def list_users(
|
async def list_users(
|
||||||
skip: int = Query(0, ge=0, description="跳过的用户数"),
|
skip: int = Query(0, ge=0, description="跳过的用户数"),
|
||||||
limit: int = Query(100, ge=1, le=1000, description="返回的最大用户数"),
|
limit: int = Query(100, ge=1, le=1000, description="返回的最大用户数"),
|
||||||
current_user: UserInDB = Depends(require_role(UserRole.ADMIN)),
|
current_user: UserInDB = Depends(require_role(UserRole.ADMIN)),
|
||||||
user_repo: UserRepository = Depends(get_user_repository)
|
user_repo: UserRepository = Depends(get_user_repository),
|
||||||
) -> List[UserResponse]:
|
) -> List[UserResponse]:
|
||||||
"""
|
"""
|
||||||
获取用户列表
|
获取用户列表
|
||||||
@@ -29,11 +36,17 @@ async def list_users(
|
|||||||
users = await user_repo.get_all_users(skip=skip, limit=limit)
|
users = await user_repo.get_all_users(skip=skip, limit=limit)
|
||||||
return [UserResponse.model_validate(user) for user in users]
|
return [UserResponse.model_validate(user) for user in users]
|
||||||
|
|
||||||
@router.get("/{user_id}", summary="获取用户详情", description="获取指定用户的详细信息", response_model=UserResponse)
|
|
||||||
|
@router.get(
|
||||||
|
"/{user_id}",
|
||||||
|
summary="获取用户详情",
|
||||||
|
description="获取指定用户的详细信息",
|
||||||
|
response_model=UserResponse,
|
||||||
|
)
|
||||||
async def get_user(
|
async def get_user(
|
||||||
user_id: int = Path(..., gt=0, description="用户ID"),
|
user_id: int = Path(..., gt=0, description="用户ID"),
|
||||||
current_user: UserInDB = Depends(get_current_active_user),
|
current_user: UserInDB = Depends(get_current_active_user),
|
||||||
user_repo: UserRepository = Depends(get_user_repository)
|
user_repo: UserRepository = Depends(get_user_repository),
|
||||||
) -> UserResponse:
|
) -> UserResponse:
|
||||||
"""
|
"""
|
||||||
获取用户详情
|
获取用户详情
|
||||||
@@ -44,24 +57,29 @@ async def get_user(
|
|||||||
if not check_resource_owner(user_id, current_user):
|
if not check_resource_owner(user_id, current_user):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_403_FORBIDDEN,
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
detail="You don't have permission to view this user"
|
detail="You don't have permission to view this user",
|
||||||
)
|
)
|
||||||
|
|
||||||
user = await user_repo.get_user_by_id(user_id)
|
user = await user_repo.get_user_by_id(user_id)
|
||||||
if not user:
|
if not user:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
status_code=status.HTTP_404_NOT_FOUND, detail="User not found"
|
||||||
detail="User not found"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return UserResponse.model_validate(user)
|
return UserResponse.model_validate(user)
|
||||||
|
|
||||||
@router.put("/{user_id}", summary="更新用户信息", description="更新指定用户的信息", response_model=UserResponse)
|
|
||||||
|
@router.put(
|
||||||
|
"/{user_id}",
|
||||||
|
summary="更新用户信息",
|
||||||
|
description="更新指定用户的信息",
|
||||||
|
response_model=UserResponse,
|
||||||
|
)
|
||||||
async def update_user(
|
async def update_user(
|
||||||
user_id: int = Path(..., gt=0, description="用户ID"),
|
user_id: int = Path(..., gt=0, description="用户ID"),
|
||||||
user_update: UserUpdate = None,
|
user_update: UserUpdate = None,
|
||||||
current_user: UserInDB = Depends(get_current_active_user),
|
current_user: UserInDB = Depends(get_current_active_user),
|
||||||
user_repo: UserRepository = Depends(get_user_repository)
|
user_repo: UserRepository = Depends(get_user_repository),
|
||||||
) -> UserResponse:
|
) -> UserResponse:
|
||||||
"""
|
"""
|
||||||
更新用户信息
|
更新用户信息
|
||||||
@@ -72,8 +90,7 @@ async def update_user(
|
|||||||
target_user = await user_repo.get_user_by_id(user_id)
|
target_user = await user_repo.get_user_by_id(user_id)
|
||||||
if not target_user:
|
if not target_user:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
status_code=status.HTTP_404_NOT_FOUND, detail="User not found"
|
||||||
detail="User not found"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# 权限检查
|
# 权限检查
|
||||||
@@ -83,7 +100,7 @@ async def update_user(
|
|||||||
if not is_owner and not is_admin:
|
if not is_owner and not is_admin:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_403_FORBIDDEN,
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
detail="You don't have permission to update this user"
|
detail="You don't have permission to update this user",
|
||||||
)
|
)
|
||||||
|
|
||||||
# 非管理员不能修改角色和激活状态
|
# 非管理员不能修改角色和激活状态
|
||||||
@@ -91,12 +108,12 @@ async def update_user(
|
|||||||
if user_update.role is not None:
|
if user_update.role is not None:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_403_FORBIDDEN,
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
detail="Only admins can change user roles"
|
detail="Only admins can change user roles",
|
||||||
)
|
)
|
||||||
if user_update.is_active is not None:
|
if user_update.is_active is not None:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_403_FORBIDDEN,
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
detail="Only admins can change user active status"
|
detail="Only admins can change user active status",
|
||||||
)
|
)
|
||||||
|
|
||||||
# 更新用户
|
# 更新用户
|
||||||
@@ -104,16 +121,17 @@ async def update_user(
|
|||||||
if not updated_user:
|
if not updated_user:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
detail="Failed to update user"
|
detail="Failed to update user",
|
||||||
)
|
)
|
||||||
|
|
||||||
return UserResponse.model_validate(updated_user)
|
return UserResponse.model_validate(updated_user)
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{user_id}", summary="删除用户", description="删除指定用户(仅管理员)")
|
@router.delete("/{user_id}", summary="删除用户", description="删除指定用户(仅管理员)")
|
||||||
async def delete_user(
|
async def delete_user(
|
||||||
user_id: int = Path(..., gt=0, description="用户ID"),
|
user_id: int = Path(..., gt=0, description="用户ID"),
|
||||||
current_user: UserInDB = Depends(get_current_admin),
|
current_user: UserInDB = Depends(get_current_admin),
|
||||||
user_repo: UserRepository = Depends(get_user_repository)
|
user_repo: UserRepository = Depends(get_user_repository),
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""
|
"""
|
||||||
删除用户
|
删除用户
|
||||||
@@ -124,23 +142,28 @@ async def delete_user(
|
|||||||
if current_user.id == user_id:
|
if current_user.id == user_id:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
detail="You cannot delete your own account"
|
detail="You cannot delete your own account",
|
||||||
)
|
)
|
||||||
|
|
||||||
success = await user_repo.delete_user(user_id)
|
success = await user_repo.delete_user(user_id)
|
||||||
if not success:
|
if not success:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
status_code=status.HTTP_404_NOT_FOUND, detail="User not found"
|
||||||
detail="User not found"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return {"message": "User deleted successfully"}
|
return {"message": "User deleted successfully"}
|
||||||
|
|
||||||
@router.post("/{user_id}/activate", summary="激活用户", description="激活指定用户账户(仅管理员)", response_model=UserResponse)
|
|
||||||
|
@router.post(
|
||||||
|
"/{user_id}/activate",
|
||||||
|
summary="激活用户",
|
||||||
|
description="激活指定用户账户(仅管理员)",
|
||||||
|
response_model=UserResponse,
|
||||||
|
)
|
||||||
async def activate_user(
|
async def activate_user(
|
||||||
user_id: int = Path(..., gt=0, description="用户ID"),
|
user_id: int = Path(..., gt=0, description="用户ID"),
|
||||||
current_user: UserInDB = Depends(get_current_admin),
|
current_user: UserInDB = Depends(get_current_admin),
|
||||||
user_repo: UserRepository = Depends(get_user_repository)
|
user_repo: UserRepository = Depends(get_user_repository),
|
||||||
) -> UserResponse:
|
) -> UserResponse:
|
||||||
"""
|
"""
|
||||||
激活用户
|
激活用户
|
||||||
@@ -152,17 +175,22 @@ async def activate_user(
|
|||||||
|
|
||||||
if not updated_user:
|
if not updated_user:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
status_code=status.HTTP_404_NOT_FOUND, detail="User not found"
|
||||||
detail="User not found"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return UserResponse.model_validate(updated_user)
|
return UserResponse.model_validate(updated_user)
|
||||||
|
|
||||||
@router.post("/{user_id}/deactivate", summary="停用用户", description="停用指定用户账户(仅管理员)", response_model=UserResponse)
|
|
||||||
|
@router.post(
|
||||||
|
"/{user_id}/deactivate",
|
||||||
|
summary="停用用户",
|
||||||
|
description="停用指定用户账户(仅管理员)",
|
||||||
|
response_model=UserResponse,
|
||||||
|
)
|
||||||
async def deactivate_user(
|
async def deactivate_user(
|
||||||
user_id: int = Path(..., gt=0, description="用户ID"),
|
user_id: int = Path(..., gt=0, description="用户ID"),
|
||||||
current_user: UserInDB = Depends(get_current_admin),
|
current_user: UserInDB = Depends(get_current_admin),
|
||||||
user_repo: UserRepository = Depends(get_user_repository)
|
user_repo: UserRepository = Depends(get_user_repository),
|
||||||
) -> UserResponse:
|
) -> UserResponse:
|
||||||
"""
|
"""
|
||||||
停用用户
|
停用用户
|
||||||
@@ -173,7 +201,7 @@ async def deactivate_user(
|
|||||||
if current_user.id == user_id:
|
if current_user.id == user_id:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
detail="You cannot deactivate your own account"
|
detail="You cannot deactivate your own account",
|
||||||
)
|
)
|
||||||
|
|
||||||
user_update = UserUpdate(is_active=False)
|
user_update = UserUpdate(is_active=False)
|
||||||
@@ -181,8 +209,7 @@ async def deactivate_user(
|
|||||||
|
|
||||||
if not updated_user:
|
if not updated_user:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
status_code=status.HTTP_404_NOT_FOUND, detail="User not found"
|
||||||
detail="User not found"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return UserResponse.model_validate(updated_user)
|
return UserResponse.model_validate(updated_user)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from fastapi.security import OAuth2PasswordBearer
|
|||||||
from jose import jwt, JWTError
|
from jose import jwt, JWTError
|
||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
from app.domain.schemas.user import UserInDB, TokenPayload
|
from app.domain.schemas.user import UserInDB, TokenPayload
|
||||||
from app.infra.repositories.user_repository import UserRepository
|
from app.infra.db.metadb.repositories.user_repository import UserRepository
|
||||||
from app.infra.db.postgresql.database import Database
|
from app.infra.db.postgresql.database import Database
|
||||||
|
|
||||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl=f"{settings.API_V1_STR}/auth/login")
|
oauth2_scheme = OAuth2PasswordBearer(tokenUrl=f"{settings.API_V1_STR}/auth/login")
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||||||
from app.auth.keycloak_dependencies import get_current_keycloak_sub
|
from app.auth.keycloak_dependencies import get_current_keycloak_sub
|
||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
from app.infra.db.metadb.database import get_metadata_session
|
from app.infra.db.metadb.database import get_metadata_session
|
||||||
from app.infra.repositories.metadata_repository import MetadataRepository
|
from app.infra.db.metadb.repositories.metadata_repository import MetadataRepository
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from app.auth.keycloak_dependencies import get_current_keycloak_sub
|
|||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
from app.infra.db.dynamic_manager import project_connection_manager
|
from app.infra.db.dynamic_manager import project_connection_manager
|
||||||
from app.infra.db.metadb.database import get_metadata_session
|
from app.infra.db.metadb.database import get_metadata_session
|
||||||
from app.infra.repositories.metadata_repository import MetadataRepository
|
from app.infra.db.metadb.repositories.metadata_repository import MetadataRepository
|
||||||
|
|
||||||
DB_ROLE_BIZ_DATA = "biz_data"
|
DB_ROLE_BIZ_DATA = "biz_data"
|
||||||
DB_ROLE_IOT_DATA = "iot_data"
|
DB_ROLE_IOT_DATA = "iot_data"
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ from jose import JWTError, jwt
|
|||||||
|
|
||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
from app.infra.db.metadb.database import SessionLocal
|
from app.infra.db.metadb.database import SessionLocal
|
||||||
from app.infra.repositories.metadata_repository import MetadataRepository
|
from app.infra.db.metadb.repositories.metadata_repository import MetadataRepository
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from uuid import uuid4
|
|||||||
import pytest
|
import pytest
|
||||||
from cryptography.fernet import InvalidToken
|
from cryptography.fernet import InvalidToken
|
||||||
|
|
||||||
from app.infra.repositories.metadata_repository import MetadataRepository
|
from app.infra.db.metadb.repositories.metadata_repository import MetadataRepository
|
||||||
|
|
||||||
|
|
||||||
class _DummyResult:
|
class _DummyResult:
|
||||||
@@ -51,11 +51,11 @@ def test_invalid_token_with_plaintext_dsn_value_raises_clear_error(monkeypatch):
|
|||||||
repo = MetadataRepository(session)
|
repo = MetadataRepository(session)
|
||||||
|
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"app.infra.repositories.metadata_repository.is_database_encryption_configured",
|
"app.infra.db.metadb.repositories.metadata_repository.is_database_encryption_configured",
|
||||||
lambda: True,
|
lambda: True,
|
||||||
)
|
)
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"app.infra.repositories.metadata_repository.get_database_encryptor",
|
"app.infra.db.metadb.repositories.metadata_repository.get_database_encryptor",
|
||||||
lambda: encryptor,
|
lambda: encryptor,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -78,11 +78,11 @@ def test_invalid_token_with_non_dsn_value_raises_clear_error(monkeypatch):
|
|||||||
repo = MetadataRepository(session)
|
repo = MetadataRepository(session)
|
||||||
|
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"app.infra.repositories.metadata_repository.is_database_encryption_configured",
|
"app.infra.db.metadb.repositories.metadata_repository.is_database_encryption_configured",
|
||||||
lambda: True,
|
lambda: True,
|
||||||
)
|
)
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"app.infra.repositories.metadata_repository.get_database_encryptor",
|
"app.infra.db.metadb.repositories.metadata_repository.get_database_encryptor",
|
||||||
lambda: _DummyEncryptor(raise_invalid_token=True),
|
lambda: _DummyEncryptor(raise_invalid_token=True),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -105,11 +105,11 @@ def test_encrypted_dsn_decrypts_without_migration(monkeypatch):
|
|||||||
repo = MetadataRepository(session)
|
repo = MetadataRepository(session)
|
||||||
|
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"app.infra.repositories.metadata_repository.is_database_encryption_configured",
|
"app.infra.db.metadb.repositories.metadata_repository.is_database_encryption_configured",
|
||||||
lambda: True,
|
lambda: True,
|
||||||
)
|
)
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"app.infra.repositories.metadata_repository.get_database_encryptor",
|
"app.infra.db.metadb.repositories.metadata_repository.get_database_encryptor",
|
||||||
lambda: _DummyEncryptor(decrypted="postgresql://u:p@ss@host/db"),
|
lambda: _DummyEncryptor(decrypted="postgresql://u:p@ss@host/db"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user