feat: initialize aivideo project

This commit is contained in:
2026-04-17 18:33:05 +08:00
commit 14b18d67fe
162 changed files with 26251 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
from sqlalchemy import or_, select
from sqlalchemy.orm import Session
from app.models.entities import User
class AuthRepository:
def __init__(self, db: Session) -> None:
self.db = db
def get_user_by_account(self, account: str) -> User | None:
return self.db.scalar(
select(User).where(
or_(
User.email == account,
User.mobile == account,
User.username == account,
)
)
)
def get_user_by_public_id(self, public_id: str) -> User | None:
return self.db.scalar(select(User).where(User.public_id == public_id))

View File

@@ -0,0 +1,55 @@
from fastapi import APIRouter, Cookie, Depends, Request, Response
from sqlalchemy.orm import Session
from app.common.db.session import get_db
from app.common.responses.api_response import success_response
from app.common.security.deps import get_current_user
from app.models.entities import User
from app.modules.auth.schema import LoginRequest, RegisterRequest
from app.modules.auth.service import AuthService
router = APIRouter(prefix="/api/v1/auth", tags=["auth"])
@router.post("/register")
def register(
payload: RegisterRequest,
request: Request,
response: Response,
db: Session = Depends(get_db),
):
data = AuthService(db).register(payload, request, response)
return {"code": 0, "message": "ok", "data": data}
@router.post("/login")
def login(
payload: LoginRequest,
request: Request,
response: Response,
db: Session = Depends(get_db),
):
data = AuthService(db).login(payload, request, response)
return {"code": 0, "message": "ok", "data": data}
@router.post("/refresh")
def refresh(
response: Response,
db: Session = Depends(get_db),
user_refresh_token: str | None = Cookie(default=None),
):
data = AuthService(db).refresh(user_refresh_token, response)
return {"code": 0, "message": "ok", "data": data}
@router.post("/logout")
def logout(response: Response, db: Session = Depends(get_db)):
AuthService(db).logout(response)
return {"code": 0, "message": "ok", "data": {"success": True}}
@router.get("/me")
def me(current_user: User = Depends(get_current_user)):
return success_response(AuthService.serialize_user(current_user))

View File

@@ -0,0 +1,13 @@
from pydantic import BaseModel, EmailStr, Field
class RegisterRequest(BaseModel):
account: EmailStr
password: str = Field(min_length=8, max_length=64)
invite_code: str | None = None
class LoginRequest(BaseModel):
account: str
password: str = Field(min_length=8, max_length=64)

View File

@@ -0,0 +1,123 @@
from __future__ import annotations
from datetime import datetime
from fastapi import Request, Response
from sqlalchemy.orm import Session
from app.common.errors.app_error import AuthenticationError, ConflictAppError
from app.common.security.jwt import (
clear_auth_cookies,
create_access_token,
create_refresh_token,
decode_refresh_token,
set_auth_cookies,
)
from app.common.security.password import hash_password, verify_password
from app.common.utils.id_gen import new_public_id
from app.models.entities import InviteCode, InviteRelation, User, Wallet
from app.modules.auth.repository import AuthRepository
from app.modules.wallets.service import WalletService
class AuthService:
def __init__(self, db: Session) -> None:
self.db = db
self.repository = AuthRepository(db)
self.wallet_service = WalletService(db)
def register(self, payload, request: Request, response: Response) -> dict:
if self.repository.get_user_by_account(payload.account):
raise ConflictAppError("account already exists", code=10010)
user = User(
public_id=new_public_id("usr"),
email=payload.account,
password_hash=hash_password(payload.password),
nickname=payload.account.split("@")[0],
status=1,
register_ip=request.client.host if request.client else "",
last_login_ip=request.client.host if request.client else "",
last_login_at=datetime.utcnow(),
)
self.db.add(user)
self.db.flush()
self.db.add(Wallet(user_id=user.id))
self.db.flush()
self._bind_invite_relation(user.id, payload.invite_code, request)
self.wallet_service.try_issue_signup_reward(user.id)
self.db.commit()
self.db.refresh(user)
self._issue_tokens(user.public_id, response)
return self.serialize_user(user)
def login(self, payload, request: Request, response: Response) -> dict:
user = self.repository.get_user_by_account(payload.account)
if not user or not verify_password(payload.password, user.password_hash):
raise AuthenticationError("invalid credentials")
user.last_login_at = datetime.utcnow()
user.last_login_ip = request.client.host if request.client else ""
self.db.commit()
self._issue_tokens(user.public_id, response)
return self.serialize_user(user)
def refresh(self, refresh_token: str | None, response: Response) -> dict:
if not refresh_token:
raise AuthenticationError()
try:
payload = decode_refresh_token(refresh_token)
except Exception as exc: # noqa: BLE001
raise AuthenticationError() from exc
if payload.get("scope") != "user":
raise AuthenticationError()
user = self.repository.get_user_by_public_id(payload["sub"])
if not user:
raise AuthenticationError()
self._issue_tokens(user.public_id, response)
return self.serialize_user(user)
def logout(self, response: Response) -> None:
clear_auth_cookies(response, prefix="user")
@staticmethod
def serialize_user(user: User) -> dict:
return {
"publicId": user.public_id,
"username": user.username or "",
"nickname": user.nickname,
"avatarUrl": user.avatar_url,
"email": user.email or "",
"mobile": user.mobile or "",
"status": user.status,
}
def _issue_tokens(self, public_id: str, response: Response) -> None:
access_token = create_access_token(public_id, scope="user")
refresh_token = create_refresh_token(public_id, scope="user")
set_auth_cookies(response, access_token, refresh_token, prefix="user")
def _bind_invite_relation(
self,
invitee_user_id: int,
invite_code_value: str | None,
request: Request,
) -> None:
if not invite_code_value:
return
invite_code = self.db.query(InviteCode).filter(
InviteCode.invite_code == invite_code_value,
InviteCode.status == 1,
).first()
if not invite_code:
return
self.db.add(
InviteRelation(
inviter_user_id=invite_code.user_id,
invitee_user_id=invitee_user_id,
invite_code_id=invite_code.id,
reward_status="pending",
reward_points=0,
register_ip=request.client.host if request.client else "",
)
)
invite_code.used_count += 1