mirror of
http://88.130.71.182:3000/BlitTech/badoHair_be.git
synced 2026-06-12 23:23:22 +00:00
Initial Commit
This commit is contained in:
159
app/services/product_service.py
Normal file
159
app/services/product_service.py
Normal file
@@ -0,0 +1,159 @@
|
||||
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
|
||||
Reference in New Issue
Block a user