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 fastapi import FastAPI
|
||||||
from redis.asyncio import from_url as redis_from_url
|
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.common.router import router as health_router
|
||||||
from proxy_pool.config import get_settings
|
from proxy_pool.config import get_settings
|
||||||
from proxy_pool.db.session import create_session_factory
|
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(health_router)
|
||||||
app.include_router(source_router)
|
app.include_router(source_router)
|
||||||
app.include_router(proxy_router)
|
app.include_router(proxy_router)
|
||||||
|
app.include_router(auth_router)
|
||||||
|
app.include_router(account_router)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user