mirror of
http://88.130.71.182:3000/BlitTech/badoHair_be.git
synced 2026-06-13 09:00:42 +00:00
160 lines
5.1 KiB
Python
160 lines
5.1 KiB
Python
import json
|
|
from uuid import UUID
|
|
|
|
import asyncpg
|
|
|
|
from app.exceptions import NotFoundError
|
|
from app.models.products import ProductCreate, ProductUpdate
|
|
|
|
|
|
async def list_products(
|
|
db: asyncpg.Connection,
|
|
page: int,
|
|
per_page: int,
|
|
offset: int,
|
|
include_hidden: bool = False,
|
|
category: str | None = None,
|
|
bestseller: bool = False,
|
|
is_new: bool = False,
|
|
search: str | None = None,
|
|
exclude_id: str | None = None,
|
|
) -> tuple[list[dict], int]:
|
|
conditions: list[str] = []
|
|
params: list = []
|
|
|
|
if not include_hidden:
|
|
conditions.append("is_hidden = false")
|
|
if category:
|
|
params.append(category)
|
|
conditions.append(f"category = ${len(params)}")
|
|
if bestseller:
|
|
conditions.append("is_bestseller = true")
|
|
if is_new:
|
|
conditions.append("is_new = true")
|
|
if search:
|
|
params.append(f"%{search}%")
|
|
conditions.append(f"(name ILIKE ${len(params)} OR description ILIKE ${len(params)})")
|
|
if exclude_id:
|
|
params.append(exclude_id)
|
|
conditions.append(f"id != ${len(params)}")
|
|
|
|
where = f"WHERE {' AND '.join(conditions)}" if conditions else ""
|
|
total = await db.fetchval(f"SELECT COUNT(*) FROM products {where}", *params)
|
|
|
|
params.extend([per_page, offset])
|
|
rows = await db.fetch(
|
|
f"""
|
|
SELECT id, name, description, price, original_price, category,
|
|
images, colors, lengths, features, stock_quantity,
|
|
is_featured, is_hidden, is_new, is_bestseller,
|
|
rating, review_count, created_at, updated_at
|
|
FROM products {where}
|
|
ORDER BY is_bestseller DESC, is_new DESC, created_at DESC
|
|
LIMIT ${len(params) - 1} OFFSET ${len(params)}
|
|
""",
|
|
*params,
|
|
)
|
|
return [_row(r) for r in rows], total
|
|
|
|
|
|
async def get_product(db: asyncpg.Connection, product_id: str) -> dict:
|
|
row = await db.fetchrow("SELECT * FROM products WHERE id = $1", product_id)
|
|
if not row:
|
|
raise NotFoundError("product")
|
|
return _row(row)
|
|
|
|
|
|
async def create_product(db: asyncpg.Connection, data: ProductCreate) -> dict:
|
|
row = await db.fetchrow(
|
|
"""
|
|
INSERT INTO products (
|
|
name, description, price, original_price, category,
|
|
colors, lengths, features, stock_quantity,
|
|
is_featured, is_hidden, is_new, is_bestseller, images
|
|
) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,'[]'::jsonb)
|
|
RETURNING *
|
|
""",
|
|
data.name, data.description, data.price, data.original_price, data.category,
|
|
data.colors, data.lengths, data.features,
|
|
data.stock_quantity, data.is_featured, data.is_hidden, data.is_new, data.is_bestseller,
|
|
)
|
|
return _row(row)
|
|
|
|
|
|
async def update_product(db: asyncpg.Connection, product_id: str, data: ProductUpdate) -> dict:
|
|
await get_product(db, product_id)
|
|
updates = {k: v for k, v in data.model_dump().items() if v is not None}
|
|
if not updates:
|
|
return await get_product(db, product_id)
|
|
|
|
json_fields = {"colors", "lengths", "features"}
|
|
set_parts = []
|
|
values = []
|
|
for i, (k, v) in enumerate(updates.items()):
|
|
set_parts.append(f"{k} = ${i + 2}")
|
|
values.append(v)
|
|
|
|
row = await db.fetchrow(
|
|
f"UPDATE products SET {', '.join(set_parts)}, updated_at = now() WHERE id = $1 RETURNING *",
|
|
product_id, *values,
|
|
)
|
|
return _row(row)
|
|
|
|
|
|
async def delete_product(db: asyncpg.Connection, product_id: str):
|
|
result = await db.execute("DELETE FROM products WHERE id = $1", product_id)
|
|
if result == "DELETE 0":
|
|
raise NotFoundError("product")
|
|
|
|
|
|
async def add_image(db: asyncpg.Connection, product_id: str, url: str) -> dict:
|
|
await get_product(db, product_id)
|
|
row = await db.fetchrow(
|
|
"""
|
|
UPDATE products
|
|
SET images = images || $2, updated_at = now()
|
|
WHERE id = $1
|
|
RETURNING *
|
|
""",
|
|
product_id, [url],
|
|
)
|
|
return _row(row)
|
|
|
|
|
|
async def remove_image(db: asyncpg.Connection, product_id: str, url: str) -> dict:
|
|
product = await get_product(db, product_id)
|
|
images = [img for img in product["_raw_images"] if img != url]
|
|
row = await db.fetchrow(
|
|
"UPDATE products SET images = $2, updated_at = now() WHERE id = $1 RETURNING *",
|
|
product_id, images,
|
|
)
|
|
return _row(row)
|
|
|
|
|
|
async def bulk_stock_update(db: asyncpg.Connection, updates: list[dict]):
|
|
async with db.transaction():
|
|
for u in updates:
|
|
await db.execute(
|
|
"UPDATE products SET stock_quantity = $2, updated_at = now() WHERE id = $1",
|
|
str(u["id"]), u["stock_quantity"],
|
|
)
|
|
|
|
|
|
def _row(r) -> dict:
|
|
d = dict(r)
|
|
|
|
# Decode JSONB lists
|
|
for field in ("images", "colors", "lengths", "features"):
|
|
val = d.get(field, [])
|
|
if isinstance(val, str):
|
|
val = json.loads(val)
|
|
d[field] = val if isinstance(val, list) else []
|
|
|
|
# images stored as plain URL strings; keep a raw copy for internal use
|
|
d["_raw_images"] = d["images"]
|
|
|
|
# Derive convenience `image` field (first URL for product cards)
|
|
d["image"] = d["images"][0] if d["images"] else ""
|
|
|
|
return d
|