feat: add admin UI frontend and complete backend APIs
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
- Add Vue 3 frontend with Element Plus - Implement login, dashboard, tenant management - Add app configuration, logs viewer, stats pages - Add user management for admins - Update Drone CI to build and deploy frontend - Frontend ports: 3001 (test), 4001 (prod)
This commit is contained in:
223
backend/app/routers/auth.py
Normal file
223
backend/app/routers/auth.py
Normal file
@@ -0,0 +1,223 @@
|
||||
"""认证路由"""
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ..database import get_db
|
||||
from ..services.auth import (
|
||||
authenticate_user,
|
||||
create_access_token,
|
||||
decode_token,
|
||||
update_last_login,
|
||||
hash_password,
|
||||
TokenData,
|
||||
UserInfo
|
||||
)
|
||||
from ..models.user import User
|
||||
|
||||
router = APIRouter(prefix="/auth", tags=["认证"])
|
||||
security = HTTPBearer()
|
||||
|
||||
|
||||
class LoginRequest(BaseModel):
|
||||
"""登录请求"""
|
||||
username: str
|
||||
password: str
|
||||
|
||||
|
||||
class LoginResponse(BaseModel):
|
||||
"""登录响应"""
|
||||
success: bool
|
||||
token: Optional[str] = None
|
||||
user: Optional[UserInfo] = None
|
||||
error: Optional[str] = None
|
||||
|
||||
|
||||
class ChangePasswordRequest(BaseModel):
|
||||
"""修改密码请求"""
|
||||
old_password: str
|
||||
new_password: str
|
||||
|
||||
|
||||
# 权限依赖
|
||||
|
||||
async def get_current_user(
|
||||
credentials: HTTPAuthorizationCredentials = Depends(security),
|
||||
db: Session = Depends(get_db)
|
||||
) -> User:
|
||||
"""获取当前用户"""
|
||||
token = credentials.credentials
|
||||
token_data = decode_token(token)
|
||||
|
||||
if not token_data:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Token 无效或已过期"
|
||||
)
|
||||
|
||||
user = db.query(User).filter(User.id == token_data.user_id).first()
|
||||
if not user or user.status != 1:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="用户不存在或已禁用"
|
||||
)
|
||||
|
||||
return user
|
||||
|
||||
|
||||
async def require_admin(user: User = Depends(get_current_user)) -> User:
|
||||
"""要求管理员权限"""
|
||||
if user.role != 'admin':
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="需要管理员权限"
|
||||
)
|
||||
return user
|
||||
|
||||
|
||||
async def require_operator(user: User = Depends(get_current_user)) -> User:
|
||||
"""要求操作员以上权限"""
|
||||
if user.role not in ('admin', 'operator'):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="需要操作员以上权限"
|
||||
)
|
||||
return user
|
||||
|
||||
|
||||
# API 端点
|
||||
|
||||
@router.post("/login", response_model=LoginResponse)
|
||||
async def login(request: LoginRequest, db: Session = Depends(get_db)):
|
||||
"""用户登录"""
|
||||
user = authenticate_user(db, request.username, request.password)
|
||||
|
||||
if not user:
|
||||
return LoginResponse(success=False, error="用户名或密码错误")
|
||||
|
||||
# 更新登录时间
|
||||
update_last_login(db, user.id)
|
||||
|
||||
# 生成 Token
|
||||
token = create_access_token({
|
||||
"user_id": user.id,
|
||||
"username": user.username,
|
||||
"role": user.role
|
||||
})
|
||||
|
||||
return LoginResponse(
|
||||
success=True,
|
||||
token=token,
|
||||
user=UserInfo(
|
||||
id=user.id,
|
||||
username=user.username,
|
||||
nickname=user.nickname,
|
||||
role=user.role
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@router.get("/me", response_model=UserInfo)
|
||||
async def get_me(user: User = Depends(get_current_user)):
|
||||
"""获取当前用户信息"""
|
||||
return UserInfo(
|
||||
id=user.id,
|
||||
username=user.username,
|
||||
nickname=user.nickname,
|
||||
role=user.role
|
||||
)
|
||||
|
||||
|
||||
@router.post("/change-password")
|
||||
async def change_password(
|
||||
request: ChangePasswordRequest,
|
||||
user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""修改密码"""
|
||||
from ..services.auth import verify_password
|
||||
|
||||
if not verify_password(request.old_password, user.password_hash):
|
||||
raise HTTPException(status_code=400, detail="原密码错误")
|
||||
|
||||
new_hash = hash_password(request.new_password)
|
||||
db.query(User).filter(User.id == user.id).update({"password_hash": new_hash})
|
||||
db.commit()
|
||||
|
||||
return {"success": True, "message": "密码修改成功"}
|
||||
|
||||
|
||||
@router.get("/users")
|
||||
async def list_users(
|
||||
user: User = Depends(require_admin),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取用户列表(仅管理员)"""
|
||||
users = db.query(User).all()
|
||||
return [
|
||||
{
|
||||
"id": u.id,
|
||||
"username": u.username,
|
||||
"nickname": u.nickname,
|
||||
"role": u.role,
|
||||
"status": u.status,
|
||||
"last_login_at": u.last_login_at,
|
||||
"created_at": u.created_at
|
||||
}
|
||||
for u in users
|
||||
]
|
||||
|
||||
|
||||
class CreateUserRequest(BaseModel):
|
||||
username: str
|
||||
password: str
|
||||
nickname: Optional[str] = None
|
||||
role: str = "viewer"
|
||||
|
||||
|
||||
@router.post("/users")
|
||||
async def create_user(
|
||||
request: CreateUserRequest,
|
||||
user: User = Depends(require_admin),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""创建用户(仅管理员)"""
|
||||
# 检查用户名是否存在
|
||||
exists = db.query(User).filter(User.username == request.username).first()
|
||||
if exists:
|
||||
raise HTTPException(status_code=400, detail="用户名已存在")
|
||||
|
||||
new_user = User(
|
||||
username=request.username,
|
||||
password_hash=hash_password(request.password),
|
||||
nickname=request.nickname,
|
||||
role=request.role,
|
||||
status=1
|
||||
)
|
||||
db.add(new_user)
|
||||
db.commit()
|
||||
db.refresh(new_user)
|
||||
|
||||
return {"success": True, "id": new_user.id}
|
||||
|
||||
|
||||
@router.delete("/users/{user_id}")
|
||||
async def delete_user(
|
||||
user_id: int,
|
||||
user: User = Depends(require_admin),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""删除用户(仅管理员)"""
|
||||
if user_id == user.id:
|
||||
raise HTTPException(status_code=400, detail="不能删除自己")
|
||||
|
||||
target = db.query(User).filter(User.id == user_id).first()
|
||||
if not target:
|
||||
raise HTTPException(status_code=404, detail="用户不存在")
|
||||
|
||||
db.delete(target)
|
||||
db.commit()
|
||||
|
||||
return {"success": True}
|
||||
240
backend/app/routers/tenant_apps.py
Normal file
240
backend/app/routers/tenant_apps.py
Normal file
@@ -0,0 +1,240 @@
|
||||
"""租户应用配置路由"""
|
||||
import json
|
||||
import secrets
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, List
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ..database import get_db
|
||||
from ..models.tenant_app import TenantApp
|
||||
from .auth import get_current_user, require_operator
|
||||
from ..models.user import User
|
||||
from ..services.crypto import encrypt_config, decrypt_config
|
||||
|
||||
router = APIRouter(prefix="/tenant-apps", tags=["应用配置"])
|
||||
|
||||
|
||||
# Schemas
|
||||
|
||||
class TenantAppCreate(BaseModel):
|
||||
tenant_id: str
|
||||
app_code: str = "tools"
|
||||
app_name: Optional[str] = None
|
||||
wechat_corp_id: Optional[str] = None
|
||||
wechat_agent_id: Optional[str] = None
|
||||
wechat_secret: Optional[str] = None # 明文,存储时加密
|
||||
token_secret: Optional[str] = None # 如果不传则自动生成
|
||||
token_required: bool = False
|
||||
allowed_origins: Optional[List[str]] = None
|
||||
allowed_tools: Optional[List[str]] = None
|
||||
|
||||
|
||||
class TenantAppUpdate(BaseModel):
|
||||
app_name: Optional[str] = None
|
||||
wechat_corp_id: Optional[str] = None
|
||||
wechat_agent_id: Optional[str] = None
|
||||
wechat_secret: Optional[str] = None
|
||||
token_secret: Optional[str] = None
|
||||
token_required: Optional[bool] = None
|
||||
allowed_origins: Optional[List[str]] = None
|
||||
allowed_tools: Optional[List[str]] = None
|
||||
status: Optional[int] = None
|
||||
|
||||
|
||||
# API Endpoints
|
||||
|
||||
@router.get("")
|
||||
async def list_tenant_apps(
|
||||
page: int = Query(1, ge=1),
|
||||
size: int = Query(20, ge=1, le=100),
|
||||
tenant_id: Optional[str] = None,
|
||||
app_code: Optional[str] = None,
|
||||
user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取应用配置列表"""
|
||||
query = db.query(TenantApp)
|
||||
|
||||
if tenant_id:
|
||||
query = query.filter(TenantApp.tenant_id == tenant_id)
|
||||
if app_code:
|
||||
query = query.filter(TenantApp.app_code == app_code)
|
||||
|
||||
total = query.count()
|
||||
apps = query.order_by(TenantApp.id.desc()).offset((page - 1) * size).limit(size).all()
|
||||
|
||||
return {
|
||||
"total": total,
|
||||
"page": page,
|
||||
"size": size,
|
||||
"items": [format_tenant_app(app, mask_secret=True) for app in apps]
|
||||
}
|
||||
|
||||
|
||||
@router.get("/{app_id}")
|
||||
async def get_tenant_app(
|
||||
app_id: int,
|
||||
user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取应用配置详情"""
|
||||
app = db.query(TenantApp).filter(TenantApp.id == app_id).first()
|
||||
if not app:
|
||||
raise HTTPException(status_code=404, detail="应用配置不存在")
|
||||
|
||||
return format_tenant_app(app, mask_secret=True)
|
||||
|
||||
|
||||
@router.post("")
|
||||
async def create_tenant_app(
|
||||
data: TenantAppCreate,
|
||||
user: User = Depends(require_operator),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""创建应用配置"""
|
||||
# 检查是否重复
|
||||
exists = db.query(TenantApp).filter(
|
||||
TenantApp.tenant_id == data.tenant_id,
|
||||
TenantApp.app_code == data.app_code
|
||||
).first()
|
||||
if exists:
|
||||
raise HTTPException(status_code=400, detail="该租户应用配置已存在")
|
||||
|
||||
# 自动生成 token_secret
|
||||
token_secret = data.token_secret or secrets.token_hex(32)
|
||||
|
||||
# 加密 wechat_secret
|
||||
wechat_secret_encrypted = None
|
||||
if data.wechat_secret:
|
||||
wechat_secret_encrypted = encrypt_config(data.wechat_secret)
|
||||
|
||||
app = TenantApp(
|
||||
tenant_id=data.tenant_id,
|
||||
app_code=data.app_code,
|
||||
app_name=data.app_name,
|
||||
wechat_corp_id=data.wechat_corp_id,
|
||||
wechat_agent_id=data.wechat_agent_id,
|
||||
wechat_secret_encrypted=wechat_secret_encrypted,
|
||||
token_secret=token_secret,
|
||||
token_required=1 if data.token_required else 0,
|
||||
allowed_origins=json.dumps(data.allowed_origins) if data.allowed_origins else None,
|
||||
allowed_tools=json.dumps(data.allowed_tools) if data.allowed_tools else None,
|
||||
status=1
|
||||
)
|
||||
db.add(app)
|
||||
db.commit()
|
||||
db.refresh(app)
|
||||
|
||||
return {"success": True, "id": app.id, "token_secret": token_secret}
|
||||
|
||||
|
||||
@router.put("/{app_id}")
|
||||
async def update_tenant_app(
|
||||
app_id: int,
|
||||
data: TenantAppUpdate,
|
||||
user: User = Depends(require_operator),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""更新应用配置"""
|
||||
app = db.query(TenantApp).filter(TenantApp.id == app_id).first()
|
||||
if not app:
|
||||
raise HTTPException(status_code=404, detail="应用配置不存在")
|
||||
|
||||
update_data = data.model_dump(exclude_unset=True)
|
||||
|
||||
# 处理 wechat_secret
|
||||
if 'wechat_secret' in update_data:
|
||||
if update_data['wechat_secret']:
|
||||
app.wechat_secret_encrypted = encrypt_config(update_data['wechat_secret'])
|
||||
del update_data['wechat_secret']
|
||||
|
||||
# 处理 JSON 字段
|
||||
if 'allowed_origins' in update_data:
|
||||
update_data['allowed_origins'] = json.dumps(update_data['allowed_origins']) if update_data['allowed_origins'] else None
|
||||
if 'allowed_tools' in update_data:
|
||||
update_data['allowed_tools'] = json.dumps(update_data['allowed_tools']) if update_data['allowed_tools'] else None
|
||||
|
||||
# 处理 token_required
|
||||
if 'token_required' in update_data:
|
||||
update_data['token_required'] = 1 if update_data['token_required'] else 0
|
||||
|
||||
for key, value in update_data.items():
|
||||
setattr(app, key, value)
|
||||
|
||||
db.commit()
|
||||
return {"success": True}
|
||||
|
||||
|
||||
@router.delete("/{app_id}")
|
||||
async def delete_tenant_app(
|
||||
app_id: int,
|
||||
user: User = Depends(require_operator),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""删除应用配置"""
|
||||
app = db.query(TenantApp).filter(TenantApp.id == app_id).first()
|
||||
if not app:
|
||||
raise HTTPException(status_code=404, detail="应用配置不存在")
|
||||
|
||||
db.delete(app)
|
||||
db.commit()
|
||||
|
||||
return {"success": True}
|
||||
|
||||
|
||||
@router.post("/{app_id}/regenerate-token")
|
||||
async def regenerate_token(
|
||||
app_id: int,
|
||||
user: User = Depends(require_operator),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""重新生成 token_secret"""
|
||||
app = db.query(TenantApp).filter(TenantApp.id == app_id).first()
|
||||
if not app:
|
||||
raise HTTPException(status_code=404, detail="应用配置不存在")
|
||||
|
||||
new_token = secrets.token_hex(32)
|
||||
app.token_secret = new_token
|
||||
db.commit()
|
||||
|
||||
return {"success": True, "token_secret": new_token}
|
||||
|
||||
|
||||
@router.get("/{app_id}/wechat-secret")
|
||||
async def get_wechat_secret(
|
||||
app_id: int,
|
||||
user: User = Depends(require_operator),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取解密的 wechat_secret(仅操作员以上)"""
|
||||
app = db.query(TenantApp).filter(TenantApp.id == app_id).first()
|
||||
if not app:
|
||||
raise HTTPException(status_code=404, detail="应用配置不存在")
|
||||
|
||||
secret = None
|
||||
if app.wechat_secret_encrypted:
|
||||
secret = decrypt_config(app.wechat_secret_encrypted)
|
||||
|
||||
return {"wechat_secret": secret}
|
||||
|
||||
|
||||
def format_tenant_app(app: TenantApp, mask_secret: bool = True) -> dict:
|
||||
"""格式化应用配置"""
|
||||
result = {
|
||||
"id": app.id,
|
||||
"tenant_id": app.tenant_id,
|
||||
"app_code": app.app_code,
|
||||
"app_name": app.app_name,
|
||||
"wechat_corp_id": app.wechat_corp_id,
|
||||
"wechat_agent_id": app.wechat_agent_id,
|
||||
"has_wechat_secret": bool(app.wechat_secret_encrypted),
|
||||
"token_secret": "******" if mask_secret and app.token_secret else app.token_secret,
|
||||
"token_required": bool(app.token_required),
|
||||
"allowed_origins": json.loads(app.allowed_origins) if app.allowed_origins else [],
|
||||
"allowed_tools": json.loads(app.allowed_tools) if app.allowed_tools else [],
|
||||
"status": app.status,
|
||||
"created_at": app.created_at,
|
||||
"updated_at": app.updated_at
|
||||
}
|
||||
return result
|
||||
301
backend/app/routers/tenants.py
Normal file
301
backend/app/routers/tenants.py
Normal file
@@ -0,0 +1,301 @@
|
||||
"""租户管理路由"""
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, List
|
||||
from datetime import date
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func
|
||||
|
||||
from ..database import get_db
|
||||
from ..models.tenant import Tenant, Subscription
|
||||
from ..models.stats import TenantUsageDaily
|
||||
from .auth import get_current_user, require_operator
|
||||
from ..models.user import User
|
||||
|
||||
router = APIRouter(prefix="/tenants", tags=["租户管理"])
|
||||
|
||||
|
||||
# Schemas
|
||||
|
||||
class TenantCreate(BaseModel):
|
||||
code: str
|
||||
name: str
|
||||
contact_info: Optional[dict] = None
|
||||
status: str = "active"
|
||||
expired_at: Optional[date] = None
|
||||
|
||||
|
||||
class TenantUpdate(BaseModel):
|
||||
name: Optional[str] = None
|
||||
contact_info: Optional[dict] = None
|
||||
status: Optional[str] = None
|
||||
expired_at: Optional[date] = None
|
||||
|
||||
|
||||
class SubscriptionCreate(BaseModel):
|
||||
tenant_id: int
|
||||
app_code: str
|
||||
start_date: Optional[date] = None
|
||||
end_date: Optional[date] = None
|
||||
quota: Optional[dict] = None
|
||||
status: str = "active"
|
||||
|
||||
|
||||
class SubscriptionUpdate(BaseModel):
|
||||
start_date: Optional[date] = None
|
||||
end_date: Optional[date] = None
|
||||
quota: Optional[dict] = None
|
||||
status: Optional[str] = None
|
||||
|
||||
|
||||
# API Endpoints
|
||||
|
||||
@router.get("")
|
||||
async def list_tenants(
|
||||
page: int = Query(1, ge=1),
|
||||
size: int = Query(20, ge=1, le=100),
|
||||
status: Optional[str] = None,
|
||||
keyword: Optional[str] = None,
|
||||
user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取租户列表"""
|
||||
query = db.query(Tenant)
|
||||
|
||||
if status:
|
||||
query = query.filter(Tenant.status == status)
|
||||
if keyword:
|
||||
query = query.filter(
|
||||
(Tenant.code.contains(keyword)) | (Tenant.name.contains(keyword))
|
||||
)
|
||||
|
||||
total = query.count()
|
||||
tenants = query.order_by(Tenant.id.desc()).offset((page - 1) * size).limit(size).all()
|
||||
|
||||
return {
|
||||
"total": total,
|
||||
"page": page,
|
||||
"size": size,
|
||||
"items": [
|
||||
{
|
||||
"id": t.id,
|
||||
"code": t.code,
|
||||
"name": t.name,
|
||||
"contact_info": t.contact_info,
|
||||
"status": t.status,
|
||||
"expired_at": t.expired_at,
|
||||
"created_at": t.created_at
|
||||
}
|
||||
for t in tenants
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@router.get("/{tenant_id}")
|
||||
async def get_tenant(
|
||||
tenant_id: int,
|
||||
user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取租户详情"""
|
||||
tenant = db.query(Tenant).filter(Tenant.id == tenant_id).first()
|
||||
if not tenant:
|
||||
raise HTTPException(status_code=404, detail="租户不存在")
|
||||
|
||||
# 获取订阅
|
||||
subscriptions = db.query(Subscription).filter(
|
||||
Subscription.tenant_id == tenant_id
|
||||
).all()
|
||||
|
||||
# 获取用量统计(最近30天)
|
||||
usage = db.query(
|
||||
func.sum(TenantUsageDaily.ai_calls).label('total_calls'),
|
||||
func.sum(TenantUsageDaily.ai_tokens).label('total_tokens'),
|
||||
func.sum(TenantUsageDaily.ai_cost).label('total_cost')
|
||||
).filter(
|
||||
TenantUsageDaily.tenant_id == tenant_id
|
||||
).first()
|
||||
|
||||
return {
|
||||
"id": tenant.id,
|
||||
"code": tenant.code,
|
||||
"name": tenant.name,
|
||||
"contact_info": tenant.contact_info,
|
||||
"status": tenant.status,
|
||||
"expired_at": tenant.expired_at,
|
||||
"created_at": tenant.created_at,
|
||||
"updated_at": tenant.updated_at,
|
||||
"subscriptions": [
|
||||
{
|
||||
"id": s.id,
|
||||
"app_code": s.app_code,
|
||||
"start_date": s.start_date,
|
||||
"end_date": s.end_date,
|
||||
"quota": s.quota,
|
||||
"status": s.status
|
||||
}
|
||||
for s in subscriptions
|
||||
],
|
||||
"usage_summary": {
|
||||
"total_calls": int(usage.total_calls or 0),
|
||||
"total_tokens": int(usage.total_tokens or 0),
|
||||
"total_cost": float(usage.total_cost or 0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@router.post("")
|
||||
async def create_tenant(
|
||||
data: TenantCreate,
|
||||
user: User = Depends(require_operator),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""创建租户"""
|
||||
# 检查 code 是否重复
|
||||
exists = db.query(Tenant).filter(Tenant.code == data.code).first()
|
||||
if exists:
|
||||
raise HTTPException(status_code=400, detail="租户代码已存在")
|
||||
|
||||
tenant = Tenant(
|
||||
code=data.code,
|
||||
name=data.name,
|
||||
contact_info=data.contact_info,
|
||||
status=data.status,
|
||||
expired_at=data.expired_at
|
||||
)
|
||||
db.add(tenant)
|
||||
db.commit()
|
||||
db.refresh(tenant)
|
||||
|
||||
return {"success": True, "id": tenant.id}
|
||||
|
||||
|
||||
@router.put("/{tenant_id}")
|
||||
async def update_tenant(
|
||||
tenant_id: int,
|
||||
data: TenantUpdate,
|
||||
user: User = Depends(require_operator),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""更新租户"""
|
||||
tenant = db.query(Tenant).filter(Tenant.id == tenant_id).first()
|
||||
if not tenant:
|
||||
raise HTTPException(status_code=404, detail="租户不存在")
|
||||
|
||||
update_data = data.model_dump(exclude_unset=True)
|
||||
for key, value in update_data.items():
|
||||
setattr(tenant, key, value)
|
||||
|
||||
db.commit()
|
||||
return {"success": True}
|
||||
|
||||
|
||||
@router.delete("/{tenant_id}")
|
||||
async def delete_tenant(
|
||||
tenant_id: int,
|
||||
user: User = Depends(require_operator),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""删除租户"""
|
||||
tenant = db.query(Tenant).filter(Tenant.id == tenant_id).first()
|
||||
if not tenant:
|
||||
raise HTTPException(status_code=404, detail="租户不存在")
|
||||
|
||||
# 删除关联的订阅
|
||||
db.query(Subscription).filter(Subscription.tenant_id == tenant_id).delete()
|
||||
db.delete(tenant)
|
||||
db.commit()
|
||||
|
||||
return {"success": True}
|
||||
|
||||
|
||||
# 订阅管理
|
||||
|
||||
@router.get("/{tenant_id}/subscriptions")
|
||||
async def list_subscriptions(
|
||||
tenant_id: int,
|
||||
user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取租户订阅列表"""
|
||||
subscriptions = db.query(Subscription).filter(
|
||||
Subscription.tenant_id == tenant_id
|
||||
).all()
|
||||
|
||||
return [
|
||||
{
|
||||
"id": s.id,
|
||||
"tenant_id": s.tenant_id,
|
||||
"app_code": s.app_code,
|
||||
"start_date": s.start_date,
|
||||
"end_date": s.end_date,
|
||||
"quota": s.quota,
|
||||
"status": s.status,
|
||||
"created_at": s.created_at
|
||||
}
|
||||
for s in subscriptions
|
||||
]
|
||||
|
||||
|
||||
@router.post("/{tenant_id}/subscriptions")
|
||||
async def create_subscription(
|
||||
tenant_id: int,
|
||||
data: SubscriptionCreate,
|
||||
user: User = Depends(require_operator),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""创建订阅"""
|
||||
tenant = db.query(Tenant).filter(Tenant.id == tenant_id).first()
|
||||
if not tenant:
|
||||
raise HTTPException(status_code=404, detail="租户不存在")
|
||||
|
||||
subscription = Subscription(
|
||||
tenant_id=tenant_id,
|
||||
app_code=data.app_code,
|
||||
start_date=data.start_date,
|
||||
end_date=data.end_date,
|
||||
quota=data.quota,
|
||||
status=data.status
|
||||
)
|
||||
db.add(subscription)
|
||||
db.commit()
|
||||
db.refresh(subscription)
|
||||
|
||||
return {"success": True, "id": subscription.id}
|
||||
|
||||
|
||||
@router.put("/subscriptions/{subscription_id}")
|
||||
async def update_subscription(
|
||||
subscription_id: int,
|
||||
data: SubscriptionUpdate,
|
||||
user: User = Depends(require_operator),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""更新订阅"""
|
||||
subscription = db.query(Subscription).filter(Subscription.id == subscription_id).first()
|
||||
if not subscription:
|
||||
raise HTTPException(status_code=404, detail="订阅不存在")
|
||||
|
||||
update_data = data.model_dump(exclude_unset=True)
|
||||
for key, value in update_data.items():
|
||||
setattr(subscription, key, value)
|
||||
|
||||
db.commit()
|
||||
return {"success": True}
|
||||
|
||||
|
||||
@router.delete("/subscriptions/{subscription_id}")
|
||||
async def delete_subscription(
|
||||
subscription_id: int,
|
||||
user: User = Depends(require_operator),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""删除订阅"""
|
||||
subscription = db.query(Subscription).filter(Subscription.id == subscription_id).first()
|
||||
if not subscription:
|
||||
raise HTTPException(status_code=404, detail="订阅不存在")
|
||||
|
||||
db.delete(subscription)
|
||||
db.commit()
|
||||
|
||||
return {"success": True}
|
||||
Reference in New Issue
Block a user