feat: add auth and account routes
This commit is contained in:
parent
f65179bd0b
commit
396ed3d029
165
src/proxy_pool/accounts/router.py
Normal file
165
src/proxy_pool/accounts/router.py
Normal file
@ -0,0 +1,165 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from proxy_pool.accounts.auth import get_current_user
|
||||
from proxy_pool.accounts.models import ApiKey, CreditLedger, User
|
||||
from proxy_pool.accounts.schemas import (
|
||||
ApiKeyCreate,
|
||||
ApiKeyCreated,
|
||||
ApiKeyResponse,
|
||||
CreditBalanceResponse,
|
||||
CreditTransactionResponse,
|
||||
RegisterResponse,
|
||||
UserCreate,
|
||||
UserResponse,
|
||||
)
|
||||
from proxy_pool.accounts.service import (
|
||||
create_user,
|
||||
generate_api_key,
|
||||
get_credit_balance,
|
||||
)
|
||||
from proxy_pool.common.dependencies import get_db
|
||||
from proxy_pool.config import get_settings
|
||||
|
||||
auth_router = APIRouter(prefix="/auth", tags=["auth"])
|
||||
account_router = APIRouter(prefix="/account", tags=["account"])
|
||||
|
||||
|
||||
@auth_router.post(
|
||||
"/register",
|
||||
response_model=RegisterResponse,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
)
|
||||
async def register(
|
||||
body: UserCreate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
) -> RegisterResponse:
|
||||
settings = get_settings()
|
||||
|
||||
# Check for duplicate email
|
||||
existing = await db.execute(select(User).where(User.email == body.email))
|
||||
if existing.scalar_one_or_none() is not None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail="A user with this email already exists",
|
||||
)
|
||||
|
||||
user, raw_key = await create_user(
|
||||
db,
|
||||
email=body.email,
|
||||
display_name=body.display_name,
|
||||
settings=settings.account,
|
||||
)
|
||||
|
||||
# Find the key we just created to build the response
|
||||
key_result = await db.execute(select(ApiKey).where(ApiKey.user_id == user.id))
|
||||
api_key = key_result.scalar_one()
|
||||
|
||||
return RegisterResponse(
|
||||
user=UserResponse.model_validate(user),
|
||||
api_key=ApiKeyCreated(
|
||||
id=api_key.id,
|
||||
key=raw_key,
|
||||
prefix=api_key.prefix,
|
||||
label=api_key.label,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@auth_router.post(
|
||||
"/keys",
|
||||
response_model=ApiKeyCreated,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
)
|
||||
async def create_api_key(
|
||||
body: ApiKeyCreate,
|
||||
user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
) -> ApiKeyCreated:
|
||||
settings = get_settings()
|
||||
|
||||
raw_key, key_hash, prefix = generate_api_key(settings.account.api_key_prefix)
|
||||
api_key = ApiKey(
|
||||
user_id=user.id,
|
||||
key_hash=key_hash,
|
||||
prefix=prefix,
|
||||
label=body.label,
|
||||
)
|
||||
db.add(api_key)
|
||||
await db.commit()
|
||||
await db.refresh(api_key)
|
||||
|
||||
return ApiKeyCreated(
|
||||
id=api_key.id,
|
||||
key=raw_key,
|
||||
prefix=api_key.prefix,
|
||||
label=api_key.label,
|
||||
)
|
||||
|
||||
|
||||
@auth_router.get("/keys", response_model=list[ApiKeyResponse])
|
||||
async def list_api_keys(
|
||||
user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
) -> list[ApiKeyResponse]:
|
||||
result = await db.execute(
|
||||
select(ApiKey)
|
||||
.where(ApiKey.user_id == user.id)
|
||||
.order_by(ApiKey.created_at.desc())
|
||||
)
|
||||
keys = result.scalars().all()
|
||||
return [ApiKeyResponse.model_validate(k) for k in keys]
|
||||
|
||||
|
||||
@auth_router.delete("/keys/{key_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def revoke_api_key(
|
||||
key_id: str,
|
||||
user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
) -> None:
|
||||
result = await db.execute(
|
||||
select(ApiKey).where(
|
||||
ApiKey.id == key_id,
|
||||
ApiKey.user_id == user.id,
|
||||
)
|
||||
)
|
||||
api_key = result.scalar_one_or_none()
|
||||
if api_key is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="API key not found",
|
||||
)
|
||||
|
||||
api_key.is_active = False
|
||||
await db.commit()
|
||||
|
||||
|
||||
@account_router.get("", response_model=UserResponse)
|
||||
async def get_account(
|
||||
user: User = Depends(get_current_user),
|
||||
) -> UserResponse:
|
||||
return UserResponse.model_validate(user)
|
||||
|
||||
|
||||
@account_router.get("/credits", response_model=CreditBalanceResponse)
|
||||
async def get_credits(
|
||||
user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
) -> CreditBalanceResponse:
|
||||
balance = await get_credit_balance(db, user.id)
|
||||
|
||||
result = await db.execute(
|
||||
select(CreditLedger)
|
||||
.where(CreditLedger.user_id == user.id)
|
||||
.order_by(CreditLedger.created_at.desc())
|
||||
.limit(20)
|
||||
)
|
||||
transactions = result.scalars().all()
|
||||
|
||||
return CreditBalanceResponse(
|
||||
balance=balance,
|
||||
recent_transactions=[
|
||||
CreditTransactionResponse.model_validate(t) for t in transactions
|
||||
],
|
||||
)
|
||||
@ -4,6 +4,7 @@ from contextlib import asynccontextmanager
|
||||
from fastapi import FastAPI
|
||||
from redis.asyncio import from_url as redis_from_url
|
||||
|
||||
from proxy_pool.accounts.router import account_router, auth_router
|
||||
from proxy_pool.common.router import router as health_router
|
||||
from proxy_pool.config import get_settings
|
||||
from proxy_pool.db.session import create_session_factory
|
||||
@ -63,5 +64,7 @@ def create_app() -> FastAPI:
|
||||
app.include_router(health_router)
|
||||
app.include_router(source_router)
|
||||
app.include_router(proxy_router)
|
||||
app.include_router(auth_router)
|
||||
app.include_router(account_router)
|
||||
|
||||
return app
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user