sync: migrate secure-online-shop to Gitea (2026-08-10)

This commit is contained in:
konturai-ops
2026-08-10 15:26:59 +00:00
commit 2022c8890d
44 changed files with 3196 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
"""API package."""
+69
View File
@@ -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)
+37
View File
@@ -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
+58
View File
@@ -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)
+29
View File
@@ -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)
+42
View File
@@ -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)
+9
View File
@@ -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"])