This commit is contained in:
Sun Tao 2025-08-20 23:05:54 +08:00
parent c8a0a21ef2
commit 3f21c2b2c2
83 changed files with 6355 additions and 0 deletions

View file

@ -0,0 +1,90 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi_babel import _
from sqlmodel import Session
from app.component import code
from app.component.auth import Auth
from app.component.database import session
from app.component.encrypt import password_verify
from app.component.stack_auth import StackAuth
from app.exception.exception import UserException
from app.model.user.user import LoginByPasswordIn, LoginResponse, Status, User, RegisterIn
from loguru import logger
from app.component.environment import env
router = APIRouter(tags=["Login/Registration"])
@router.post("/login", name="login by email or password")
async def by_password(data: LoginByPasswordIn, session: Session = Depends(session)) -> LoginResponse:
"""
User login with email and password
"""
user = User.by(User.email == data.email, s=session).one_or_none()
if not user or not password_verify(data.password, user.password):
raise UserException(code.password, _("Account or password error"))
return LoginResponse(token=Auth.create_access_token(user.id), email=user.email)
@router.post("/login-by_stack", name="login by stack")
async def by_stack_auth(
token: str,
type: str = "signup",
invite_code: str | None = None,
session: Session = Depends(session),
):
try:
stack_id = await StackAuth.user_id(token)
info = await StackAuth.user_info(token)
except Exception as e:
logger.error(e)
raise HTTPException(500, detail=_(f"{e}"))
user = User.by(User.stack_id == stack_id, s=session).one_or_none()
if not user:
# Only signup can create user
if type != "signup":
raise UserException(code.error, _("User not found"))
with session as s:
try:
user = User(
username=info["username"] if "username" in info else None,
nickname=info["display_name"],
email=info["primary_email"],
avatar=info["profile_image_url"],
stack_id=stack_id,
)
s.add(user)
s.commit()
session.refresh(user)
return LoginResponse(token=Auth.create_access_token(user.id), email=user.email)
except Exception as e:
s.rollback()
logger.error(f"Failed to register: {e}")
raise UserException(code.error, _("Failed to register"))
else:
if user.status == Status.Block:
raise UserException(code.error, _("Your account has been blocked."))
return LoginResponse(token=Auth.create_access_token(user.id), email=user.email)
@router.post("/register", name="register by email/password")
async def register(data: RegisterIn, session: Session = Depends(session)):
# Check if email is already registered
if User.by(User.email == data.email, s=session).one_or_none():
raise UserException(code.error, _("Email already registered"))
with session as s:
try:
user = User(
email=data.email,
password=data.password,
)
s.add(user)
s.commit()
s.refresh(user)
except Exception as e:
s.rollback()
logger.error(f"Failed to register: {e}")
raise UserException(code.error, _("Failed to register"))
return {"status": "success"}

View file

@ -0,0 +1,115 @@
from fastapi import APIRouter, Depends
from sqlalchemy import func
from sqlmodel import Session, select
from app.component.auth import Auth, auth_must
from app.component.database import session
from app.model.user.privacy import UserPrivacy, UserPrivacySettings
from app.model.user.user import User, UserIn, UserOut, UserProfile
from app.model.user.user_stat import UserStat, UserStatActionIn, UserStatOut
from app.model.chat.chat_history import ChatHistory
from app.model.mcp.mcp_user import McpUser
from app.model.config.config import Config
from app.model.chat.chat_snpshot import ChatSnapshot
from app.model.user.user_credits_record import UserCreditsRecord
router = APIRouter(tags=["User"])
@router.get("/user", name="user info", response_model=UserOut)
def get(auth: Auth = Depends(auth_must), session: Session = Depends(session)):
# 获取用户信息时触发积分刷新
user: User = auth.user
user.refresh_credits_on_active(session)
return user
@router.put("/user", name="update user info", response_model=UserOut)
def put(data: UserIn, session: Session = Depends(session), auth: Auth = Depends(auth_must)):
model = auth.user
model.username = data.username
model.save(session)
return model
@router.put("/user/profile", name="update user profile", response_model=UserProfile)
def put_profile(data: UserProfile, session: Session = Depends(session), auth: Auth = Depends(auth_must)):
model = auth.user
model.nickname = data.nickname
model.fullname = data.fullname
model.work_desc = data.work_desc
model.save(session)
return model
@router.get("/user/privacy", name="get user privacy")
def get_privacy(session: Session = Depends(session), auth: Auth = Depends(auth_must)):
user_id = auth.user.id
stmt = select(UserPrivacy).where(UserPrivacy.user_id == user_id)
model = session.exec(stmt).one_or_none()
if not model:
return UserPrivacySettings.default_settings()
return model.pricacy_setting
@router.put("/user/privacy", name="update user privacy")
def put_privacy(data: UserPrivacySettings, session: Session = Depends(session), auth: Auth = Depends(auth_must)):
user_id = auth.user.id
stmt = select(UserPrivacy).where(UserPrivacy.user_id == user_id)
model = session.exec(stmt).one_or_none()
default_settings = UserPrivacySettings.default_settings()
if model:
model.pricacy_setting = {**model.pricacy_setting, **data.model_dump()}
model.save(session)
else:
model = UserPrivacy(user_id=user_id, pricacy_setting={**default_settings, **data.model_dump()})
model.save(session)
return model.pricacy_setting
@router.get("/user/current_credits", name="get user current credits")
def get_user_credits(auth: Auth = Depends(auth_must), session: Session = Depends(session)):
user = auth.user
user.refresh_credits_on_active(session)
credits = user.credits
daily_credits: UserCreditsRecord | None = UserCreditsRecord.get_daily_balance(user.id)
current_daily_credits = 0
if daily_credits:
current_daily_credits = daily_credits.amount - daily_credits.balance
credits += current_daily_credits if current_daily_credits > 0 else 0
return {"credits": credits, "daily_credits": current_daily_credits}
@router.get("/user/stat", name="get user stat", response_model=UserStatOut)
def get_user_stat(auth: Auth = Depends(auth_must), session: Session = Depends(session)):
"""Get current user's operation statistics."""
stat = session.exec(select(UserStat).where(UserStat.user_id == auth.user.id)).first()
data = UserStatOut()
if stat:
data = UserStatOut(**stat.model_dump())
else:
data = UserStatOut(user_id=auth.user.id)
data.task_queries = ChatHistory.count(ChatHistory.user_id == auth.user.id, s=session)
mcp = McpUser.count(McpUser.user_id == auth.user.id, s=session)
tool: list = session.exec(
select(func.count("*")).where(Config.user_id == auth.user.id).group_by(Config.config_group)
).all()
tool = tool.__len__()
data.mcp_install_count = mcp + tool
data.storage_used = ChatSnapshot.caclDir(ChatSnapshot.get_user_dir(auth.user.id))
return data
@router.post("/user/stat", name="record user stat")
def record_user_stat(
data: UserStatActionIn,
auth: Auth = Depends(auth_must),
session: Session = Depends(session),
):
"""Record or update current user's operation statistics."""
data.user_id = auth.user.id
stat = UserStat.record_action(session, data)
return stat

View file

@ -0,0 +1,24 @@
from fastapi import APIRouter, Depends
from sqlmodel import Session
from app.component import code
from app.component.auth import Auth, auth_must
from app.component.database import session
from app.component.encrypt import password_hash, password_verify
from app.exception.exception import UserException
from app.model.user.user import UpdatePassword, UserOut
from fastapi_babel import _
router = APIRouter(tags=["User"])
@router.put("/user/update-password", name="update password", response_model=UserOut)
def update_password(data: UpdatePassword, auth: Auth = Depends(auth_must), session: Session = Depends(session)):
model = auth.user
if not password_verify(data.password, model.password):
raise UserException(code.error, _("Password is incorrect"))
if data.new_password != data.re_new_password:
raise UserException(code.error, _("The two passwords do not match"))
model.password = password_hash(data.new_password)
model.save(session)
return model