Files
badoHair_be/app/services/product_service.py
belviskhoremk d2dc43b16f Initial Commit
2026-05-12 00:34:21 +00:00

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