mirror of
http://88.130.71.182:3000/BlitTech/badoHair_be.git
synced 2026-06-13 11:16:08 +00:00
117 lines
3.9 KiB
Python
117 lines
3.9 KiB
Python
from typing import Annotated
|
|
from fastapi import APIRouter, Depends, Query, UploadFile, File
|
|
import asyncpg
|
|
|
|
from app.core.pagination import pagination_params
|
|
from app.core.responses import ok, paginated
|
|
from app.dependencies import get_db, require_admin
|
|
from app.exceptions import ValidationError
|
|
from app.models.products import ProductCreate, ProductUpdate, BulkStockUpdateRequest
|
|
from app.services import product_service, storage_service
|
|
|
|
router = APIRouter(prefix="/products", tags=["Admin — Products"])
|
|
|
|
|
|
@router.get("")
|
|
async def list_products(
|
|
pagination: Annotated[tuple, Depends(pagination_params)],
|
|
search: str | None = Query(None),
|
|
category: str | None = Query(None),
|
|
bestseller: bool = Query(False),
|
|
db: asyncpg.Connection = Depends(get_db),
|
|
_: dict = Depends(require_admin),
|
|
):
|
|
page, per_page, offset = pagination
|
|
products, total = await product_service.list_products(
|
|
db, page, per_page, offset,
|
|
include_hidden=True,
|
|
category=category,
|
|
bestseller=bestseller,
|
|
search=search,
|
|
)
|
|
return paginated(products, total, page, per_page)
|
|
|
|
|
|
@router.post("", status_code=201)
|
|
async def create_product(
|
|
body: ProductCreate,
|
|
db: asyncpg.Connection = Depends(get_db),
|
|
admin: dict = Depends(require_admin),
|
|
):
|
|
product = await product_service.create_product(db, body)
|
|
await _log(db, admin, "product.created", str(product["id"]))
|
|
return ok(product)
|
|
|
|
|
|
@router.put("/{product_id}")
|
|
async def update_product(
|
|
product_id: str,
|
|
body: ProductUpdate,
|
|
db: asyncpg.Connection = Depends(get_db),
|
|
admin: dict = Depends(require_admin),
|
|
):
|
|
product = await product_service.update_product(db, product_id, body)
|
|
await _log(db, admin, "product.updated", product_id)
|
|
return ok(product)
|
|
|
|
|
|
@router.delete("/{product_id}", status_code=204)
|
|
async def delete_product(
|
|
product_id: str,
|
|
db: asyncpg.Connection = Depends(get_db),
|
|
admin: dict = Depends(require_admin),
|
|
):
|
|
await product_service.delete_product(db, product_id)
|
|
await _log(db, admin, "product.deleted", product_id)
|
|
|
|
|
|
@router.post("/{product_id}/images", status_code=201)
|
|
async def upload_image(
|
|
product_id: str,
|
|
file: UploadFile = File(...),
|
|
db: asyncpg.Connection = Depends(get_db),
|
|
admin: dict = Depends(require_admin),
|
|
):
|
|
if not file.content_type or not file.content_type.startswith("image/"):
|
|
raise ValidationError("Only image files are allowed")
|
|
|
|
content = await file.read()
|
|
if len(content) > 10 * 1024 * 1024:
|
|
raise ValidationError("Image must be under 10MB")
|
|
|
|
image_data = await storage_service.upload_product_image(product_id, content, file.content_type)
|
|
product = await product_service.add_image(db, product_id, image_data["url"])
|
|
return ok(product)
|
|
|
|
|
|
@router.delete("/{product_id}/images")
|
|
async def delete_image(
|
|
product_id: str,
|
|
url: str = Query(..., description="Full image URL to remove"),
|
|
storage_path: str = Query(..., description="Storage path for deletion from bucket"),
|
|
db: asyncpg.Connection = Depends(get_db),
|
|
admin: dict = Depends(require_admin),
|
|
):
|
|
await storage_service.delete_product_image(storage_path)
|
|
product = await product_service.remove_image(db, product_id, url)
|
|
return ok(product)
|
|
|
|
|
|
@router.post("/bulk-stock")
|
|
async def bulk_stock(
|
|
body: BulkStockUpdateRequest,
|
|
db: asyncpg.Connection = Depends(get_db),
|
|
admin: dict = Depends(require_admin),
|
|
):
|
|
updates = [u.model_dump() for u in body.updates]
|
|
await product_service.bulk_stock_update(db, updates)
|
|
await _log(db, admin, "product.bulk_stock_updated", None)
|
|
return ok({"updated": len(updates)})
|
|
|
|
|
|
async def _log(db, admin, action, entity_id, metadata=None):
|
|
await db.execute(
|
|
"INSERT INTO activity_log (actor_id, action, entity_type, entity_id, metadata) VALUES ($1, $2, 'product', $3, $4)",
|
|
str(admin["id"]), action, entity_id, metadata,
|
|
)
|