from app.api.chat_service import get_user_balance from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session from sqlalchemy import and_, desc, asc from pydantic import BaseModel, Field from typing import Optional import uuid from datetime import datetime, date from ..database import get_db from ..models import ( User, LoginLog, ApiKey, MembershipSubscription, AppPurchase, MarketApp, SmsConfig, gen_uuid ) from ..auth import get_current_user, hash_password, verify_password from ..middleware.log_service import LogService from ..sms_client import send_sms_text router = APIRouter() class UpdateProfileRequest(BaseModel): username: Optional[str] = None email: Optional[str] = None phone: Optional[str] = None avatar: Optional[str] = None class UpdatePasswordRequest(BaseModel): old_password: str new_password: str = Field(min_length=6, max_length=100) class CreateApiKeyRequest(BaseModel): name: str = Field(min_length=1, max_length=100) class UpdateApiKeyRequest(BaseModel): status: str # enabled, disabled, revoked @router.get("/balance") async def get_balance(user=Depends(get_current_user)): balance = await get_user_balance(user["id"]) return {"code": 0, "data": balance, "message": "ok"} @router.get("/") def get_profile( db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): # 获取会员信息 membership = db.query(MembershipSubscription).filter( MembershipSubscription.user_id == current_user.id ).first() membership_info = None if membership: membership_info = { "id": membership.id, "plan_id": membership.plan_id, "status": membership.status, "start_at": str(membership.start_at) if membership.start_at else None, "expire_at": str(membership.end_at) if membership.end_at else None, "is_expired": membership.end_at < datetime.now() if membership.end_at else False, } return { "code": 0, "data": { "id": current_user.id, "username": current_user.username, "email": current_user.email, "phone": current_user.phone, "avatar": current_user.avatar, "role": current_user.role, "status": current_user.status, "membership": membership_info, "created_at": str(current_user.created_at) if current_user.created_at else None, "updated_at": str(current_user.updated_at) if current_user.updated_at else None, }, "message": "ok", } @router.put("/") def update_profile( req: UpdateProfileRequest, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): # 从当前会话中重新获取用户对象 db_user = db.query(User).filter(User.id == current_user.id).first() if not db_user: raise HTTPException(status_code=404, detail="用户不存在") if req.username is not None and req.username != db_user.username: # 检查用户名是否被占用 existing = db.query(User).filter(User.username == req.username).first() if existing: raise HTTPException(status_code=400, detail="用户名已被占用") db_user.username = req.username if req.email is not None and req.email != db_user.email: existing = db.query(User).filter(User.email == req.email).first() if existing: raise HTTPException(status_code=400, detail="邮箱已被占用") db_user.email = req.email if req.phone is not None: db_user.phone = req.phone if req.avatar is not None: db_user.avatar = req.avatar db_user.updated_at = datetime.now() LogService.log_operation( user_id=db_user.id, username=db_user.username, action="update_profile", target="user", target_id=db_user.id, detail=f"用户 {db_user.username} 更新了个人信息", ) db.commit() db.refresh(db_user) return { "code": 0, "data": { "id": db_user.id, "username": db_user.username, "email": db_user.email, "phone": db_user.phone, "avatar": db_user.avatar, }, "message": "更新成功", } @router.put("/password") def update_password( req: UpdatePasswordRequest, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): db_user = db.query(User).filter(User.id == current_user.id).first() if not db_user: raise HTTPException(status_code=404, detail="用户不存在") if not verify_password(req.old_password, db_user.password_hash): raise HTTPException(status_code=400, detail="原密码错误") db_user.password_hash = hash_password(req.new_password) db_user.updated_at = datetime.now() # 密码修改成功后发送短信通知(若已配置短信且用户绑定手机号) try: sms_cfg = db.query(SmsConfig).first() if sms_cfg and sms_cfg.provider and db_user.phone: send_sms_text(sms_cfg, db_user.phone, "您的登录密码已修改,如非本人操作请尽快联系客服。") except Exception as sms_err: import logging logging.getLogger("aiflow").warning(f"密码修改短信通知失败(不影响修改结果): {sms_err}") LogService.log_operation( user_id=db_user.id, username=db_user.username, action="change_password", target="user", target_id=db_user.id, detail=f"用户 {db_user.username} 修改了密码", ) db.commit() return {"code": 0, "message": "密码修改成功"} @router.get("/login-logs") def get_login_logs( page: int = Query(default=1, ge=1), page_size: int = Query(default=20, ge=1, le=100), db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): query = db.query(LoginLog).filter(LoginLog.user_id == current_user.id) total = query.count() logs = ( query.order_by(desc(LoginLog.login_at)) .offset((page - 1) * page_size) .limit(page_size) .all() ) result = [] for log_item in logs: result.append({ "id": log_item.id, "ip": log_item.ip, "user_agent": log_item.user_agent, "login_at": str(log_item.login_at) if log_item.login_at else None, }) return { "code": 0, "data": {"total": total, "page": page, "page_size": page_size, "items": result}, "message": "ok", } @router.get("/api-keys") def get_api_keys( db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): keys = ( db.query(ApiKey) .filter(ApiKey.user_id == current_user.id) .order_by(desc(ApiKey.created_at)) .all() ) result = [] for k in keys: preview = None if k.api_key and len(k.api_key) > 12: preview = k.api_key[:8] + "****" + k.api_key[-4:] result.append({ "id": k.id, "name": k.name, "key_preview": preview or "****", "status": k.status, "last_used_at": str(k.last_used_at) if k.last_used_at else None, "created_at": str(k.created_at) if k.created_at else None, "expire_at": str(k.expired_at) if k.expired_at else None, }) return {"code": 0, "data": result, "message": "ok"} @router.post("/api-keys") def create_api_key( req: CreateApiKeyRequest, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): # 生成API密钥 key_value = f"sk-{gen_uuid().replace('-', '')}" api_key = ApiKey( id=gen_uuid(), user_id=current_user.id, name=req.name, api_key=key_value, secret=f"sec-{gen_uuid().replace('-', '')}", status="active", created_at=datetime.now(), ) db.add(api_key) LogService.log_operation( user_id=current_user.id, username=current_user.username, action="create_api_key", target="api_key", target_id=api_key.id, detail=f"用户 {current_user.username} 创建了API密钥 {api_key.name}", ) db.commit() db.refresh(api_key) return { "code": 0, "data": { "id": api_key.id, "name": api_key.name, "api_key": key_value, # 仅创建时返回完整key "status": api_key.status, "created_at": str(api_key.created_at), }, "message": "API密钥创建成功,请妥善保管", } @router.put("/api-keys/{key_id}") def update_api_key( key_id: str, req: UpdateApiKeyRequest, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): api_key = db.query(ApiKey).filter( and_(ApiKey.id == key_id, ApiKey.user_id == current_user.id) ).first() if not api_key: raise HTTPException(status_code=404, detail="API密钥不存在") if req.status not in ("enabled", "disabled", "revoked"): raise HTTPException(status_code=400, detail="无效的状态值") api_key.status = req.status LogService.log_operation( user_id=current_user.id, username=current_user.username, action="update_api_key", target="api_key", target_id=api_key.id, detail=f"用户 {current_user.username} 更新了API密钥 {api_key.name} 状态为 {req.status}", ) db.commit() return {"code": 0, "data": {"id": api_key.id, "status": api_key.status}, "message": "更新成功"} @router.get("/membership") def get_membership( db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): membership = db.query(MembershipSubscription).filter( MembershipSubscription.user_id == current_user.id ).first() if not membership: return { "code": 0, "data": {"has_membership": False, "status": None}, "message": "ok", } is_expired = membership.end_at < datetime.now() if membership.end_at else False return { "code": 0, "data": { "has_membership": True, "id": membership.id, "plan_id": membership.plan_id, "status": "expired" if is_expired else membership.status, "start_at": str(membership.start_at) if membership.start_at else None, "expire_at": str(membership.end_at) if membership.end_at else None, "is_expired": is_expired, }, "message": "ok", } @router.get("/purchased-apps") def get_purchased_apps( db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): purchases = ( db.query(AppPurchase) .filter(AppPurchase.user_id == current_user.id) .order_by(desc(AppPurchase.created_at)) .all() ) result = [] for p in purchases: app = db.query(MarketApp).filter(MarketApp.id == p.app_id).first() result.append({ "id": p.id, "app_id": p.app_id, "app_name": app.name if app else "unknown", "app_icon": app.icon if app else None, "app_type": app.type if app else None, "price": float(p.price if p.price and app else 0), "purchased_at": str(p.created_at) if p.created_at else None, }) return {"code": 0, "data": result, "message": "ok"}