sync: migrate secure-online-shop to Gitea (2026-08-10)
This commit is contained in:
@@ -0,0 +1 @@
|
||||
"""API package."""
|
||||
@@ -0,0 +1,69 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends, Request, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.dependencies import get_current_user
|
||||
from app.core.exceptions import UnauthorizedError
|
||||
from app.core.rate_limit import login_rate_limiter
|
||||
from app.db.session import get_db
|
||||
from app.models.user import User
|
||||
from app.schemas.auth import LoginRequest, TokenResponse, UserCreate, UserResponse
|
||||
from app.services.auth_service import AuthService
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/register",
|
||||
response_model=UserResponse,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
summary="Register a customer account",
|
||||
)
|
||||
def register_user(
|
||||
payload: UserCreate,
|
||||
db: Annotated[Session, Depends(get_db)],
|
||||
) -> UserResponse:
|
||||
user = AuthService(db).register_user(payload)
|
||||
return UserResponse.model_validate(user)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/login",
|
||||
response_model=TokenResponse,
|
||||
status_code=status.HTTP_200_OK,
|
||||
summary="Authenticate and receive a JWT access token",
|
||||
)
|
||||
def login(
|
||||
payload: LoginRequest,
|
||||
request: Request,
|
||||
db: Annotated[Session, Depends(get_db)],
|
||||
) -> TokenResponse:
|
||||
client_host = request.client.host if request.client else "unknown"
|
||||
throttle_key = login_rate_limiter.build_key(payload.username, client_host)
|
||||
|
||||
if login_rate_limiter.is_limited(throttle_key):
|
||||
login_rate_limiter.raise_limit_exceeded()
|
||||
|
||||
try:
|
||||
access_token = AuthService(db).authenticate_user(payload)
|
||||
except UnauthorizedError:
|
||||
login_rate_limiter.record_failure(throttle_key)
|
||||
raise
|
||||
|
||||
login_rate_limiter.reset(throttle_key)
|
||||
return TokenResponse(access_token=access_token)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/me",
|
||||
response_model=UserResponse,
|
||||
status_code=status.HTTP_200_OK,
|
||||
summary="Get the authenticated user profile",
|
||||
)
|
||||
def get_me(
|
||||
current_user: Annotated[User, Depends(get_current_user)],
|
||||
) -> UserResponse:
|
||||
return UserResponse.model_validate(current_user)
|
||||
@@ -0,0 +1,37 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Depends
|
||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.exceptions import ForbiddenError, UnauthorizedError
|
||||
from app.core.security import decode_access_token
|
||||
from app.db.session import get_db
|
||||
from app.models.user import RoleEnum, User
|
||||
from app.services.auth_service import AuthService
|
||||
|
||||
bearer_scheme = HTTPBearer(
|
||||
scheme_name="BearerAuth",
|
||||
description="JWT token from /api/v1/auth/login",
|
||||
)
|
||||
|
||||
|
||||
def get_current_user(
|
||||
db: Annotated[Session, Depends(get_db)],
|
||||
credentials: Annotated[HTTPAuthorizationCredentials, Depends(bearer_scheme)],
|
||||
) -> User:
|
||||
token_payload = decode_access_token(credentials.credentials)
|
||||
user = AuthService(db).get_user_by_id(token_payload.user_id)
|
||||
if user is None or not user.is_active:
|
||||
raise UnauthorizedError("Could not validate credentials")
|
||||
return user
|
||||
|
||||
|
||||
def get_current_shop(
|
||||
current_user: Annotated[User, Depends(get_current_user)],
|
||||
) -> User:
|
||||
if current_user.role != RoleEnum.shop:
|
||||
raise ForbiddenError("Shop privileges are required")
|
||||
return current_user
|
||||
@@ -0,0 +1,58 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.dependencies import get_current_user
|
||||
from app.db.session import get_db
|
||||
from app.models.user import User
|
||||
from app.schemas.order import OrderCreate, OrderResponse
|
||||
from app.services.order_service import OrderService
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post(
|
||||
"",
|
||||
response_model=OrderResponse,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
summary="Create an order from cart items",
|
||||
)
|
||||
def create_order(
|
||||
payload: OrderCreate,
|
||||
db: Annotated[Session, Depends(get_db)],
|
||||
current_user: Annotated[User, Depends(get_current_user)],
|
||||
) -> OrderResponse:
|
||||
order = OrderService(db).create_order(current_user, payload)
|
||||
return OrderResponse.model_validate(order)
|
||||
|
||||
|
||||
@router.get(
|
||||
"",
|
||||
response_model=list[OrderResponse],
|
||||
status_code=status.HTTP_200_OK,
|
||||
summary="View the authenticated user's orders",
|
||||
)
|
||||
def list_my_orders(
|
||||
db: Annotated[Session, Depends(get_db)],
|
||||
current_user: Annotated[User, Depends(get_current_user)],
|
||||
) -> list[OrderResponse]:
|
||||
orders = OrderService(db).get_orders_for_user(current_user)
|
||||
return [OrderResponse.model_validate(order) for order in orders]
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{order_id}",
|
||||
response_model=OrderResponse,
|
||||
status_code=status.HTTP_200_OK,
|
||||
summary="View a specific order owned by the authenticated user",
|
||||
)
|
||||
def get_my_order(
|
||||
order_id: int,
|
||||
db: Annotated[Session, Depends(get_db)],
|
||||
current_user: Annotated[User, Depends(get_current_user)],
|
||||
) -> OrderResponse:
|
||||
order = OrderService(db).get_order_for_user(order_id, current_user)
|
||||
return OrderResponse.model_validate(order)
|
||||
@@ -0,0 +1,29 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.dependencies import get_current_user
|
||||
from app.db.session import get_db
|
||||
from app.models.user import User
|
||||
from app.schemas.payment import PaymentResponse
|
||||
from app.services.payment_service import PaymentService
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/orders/{order_id}/confirm",
|
||||
response_model=PaymentResponse,
|
||||
status_code=status.HTTP_200_OK,
|
||||
summary="Mock payment confirmation for an owned order",
|
||||
)
|
||||
def confirm_order_payment(
|
||||
order_id: int,
|
||||
db: Annotated[Session, Depends(get_db)],
|
||||
current_user: Annotated[User, Depends(get_current_user)],
|
||||
) -> PaymentResponse:
|
||||
order = PaymentService(db).confirm_payment(order_id, current_user)
|
||||
return PaymentResponse.model_validate(order)
|
||||
@@ -0,0 +1,42 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.dependencies import get_current_shop
|
||||
from app.db.session import get_db
|
||||
from app.models.user import User
|
||||
from app.schemas.product import ProductCreate, ProductResponse
|
||||
from app.services.product_service import ProductService
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get(
|
||||
"",
|
||||
response_model=list[ProductResponse],
|
||||
status_code=status.HTTP_200_OK,
|
||||
summary="Browse available products",
|
||||
)
|
||||
def list_products(
|
||||
db: Annotated[Session, Depends(get_db)],
|
||||
) -> list[ProductResponse]:
|
||||
products = ProductService(db).list_available_products()
|
||||
return [ProductResponse.model_validate(product) for product in products]
|
||||
|
||||
|
||||
@router.post(
|
||||
"",
|
||||
response_model=ProductResponse,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
summary="Create a product (shop only)",
|
||||
)
|
||||
def create_product(
|
||||
payload: ProductCreate,
|
||||
db: Annotated[Session, Depends(get_db)],
|
||||
current_shop: Annotated[User, Depends(get_current_shop)],
|
||||
) -> ProductResponse:
|
||||
product = ProductService(db).create_product(payload, current_shop)
|
||||
return ProductResponse.model_validate(product)
|
||||
@@ -0,0 +1,9 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from app.api import auth, orders, payments, products
|
||||
|
||||
api_router = APIRouter()
|
||||
api_router.include_router(auth.router, prefix="/auth", tags=["auth"])
|
||||
api_router.include_router(products.router, prefix="/products", tags=["catalog"])
|
||||
api_router.include_router(orders.router, prefix="/orders", tags=["orders"])
|
||||
api_router.include_router(payments.router, prefix="/payments", tags=["payments"])
|
||||
Reference in New Issue
Block a user