Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 77 additions & 2 deletions apps/api/backend/services/cart_item_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@
CartItemCreateRequest,
CartItemQuantityUpdateRequest,
)
from backend.services.event_log_service import record_domain_event_safely


MAX_CART_ITEM_QUANTITY = 99

def add_item_to_cart(cart_id: UUID, payload: CartItemCreateRequest) -> dict[str, Any]:
cart_query = text("""
SELECT
cart_id,
user_id,
cart_status
FROM carts
WHERE cart_id = :cart_id
Expand Down Expand Up @@ -157,6 +160,24 @@ def add_item_to_cart(cart_id: UUID, payload: CartItemCreateRequest) -> dict[str,
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to update cart item",
)

record_domain_event_safely(
event_name="cart_item_quantity_changed",
user_id=cart["user_id"],
entity_type="cart",
entity_id=updated_item["cart_id"],
properties={
"cart_id": updated_item["cart_id"],
"cart_item_id": updated_item["cart_item_id"],
"product_id": updated_item["product_id"],
"previous_quantity": existing_item["quantity"],
"next_quantity": updated_item["quantity"],
"quantity_delta": payload.quantity,
"unit_price": updated_item["unit_price"],
"currency": updated_item["currency"],
"change_source": "add_item_to_cart",
},
)

return {
"cart_item_id": updated_item["cart_item_id"],
Expand Down Expand Up @@ -185,6 +206,21 @@ def add_item_to_cart(cart_id: UUID, payload: CartItemCreateRequest) -> dict[str,
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to add item to cart",
)

record_domain_event_safely(
event_name="cart_item_added",
user_id=cart["user_id"],
entity_type="cart",
entity_id=created_item["cart_id"],
properties={
"cart_id": created_item["cart_id"],
"cart_item_id": created_item["cart_item_id"],
"product_id": created_item["product_id"],
"quantity": created_item["quantity"],
"unit_price": created_item["unit_price"],
"currency": created_item["currency"],
},
)

return {
"cart_item_id": created_item["cart_item_id"],
Expand All @@ -201,7 +237,8 @@ def add_item_to_cart(cart_id: UUID, payload: CartItemCreateRequest) -> dict[str,
def remove_item_from_cart(cart_id: UUID, cart_item_id: UUID) -> dict[str, Any]:
cart_query = text("""
SELECT
cart_id
cart_id,
user_id
FROM carts
WHERE cart_id = :cart_id
AND cart_status = 'active'
Expand All @@ -214,7 +251,11 @@ def remove_item_from_cart(cart_id: UUID, cart_item_id: UUID) -> dict[str, Any]:
AND cart_id = :cart_id
RETURNING
cart_item_id,
cart_id
cart_id,
product_id,
quantity,
unit_price,
currency
""")

with engine.begin() as connection:
Expand Down Expand Up @@ -242,6 +283,21 @@ def remove_item_from_cart(cart_id: UUID, cart_item_id: UUID) -> dict[str, Any]:
status_code=status.HTTP_404_NOT_FOUND,
detail="Cart item not found",
)

record_domain_event_safely(
event_name="cart_item_removed",
user_id=cart["user_id"],
entity_type="cart",
entity_id=deleted_item["cart_id"],
properties={
"cart_id": deleted_item["cart_id"],
"cart_item_id": deleted_item["cart_item_id"],
"product_id": deleted_item["product_id"],
"quantity": deleted_item["quantity"],
"unit_price": deleted_item["unit_price"],
"currency": deleted_item["currency"],
},
)

return {
"cart_item_id": deleted_item["cart_item_id"],
Expand All @@ -257,6 +313,7 @@ def update_cart_item_quantity(
cart_query = text("""
SELECT
cart_id,
user_id,
cart_status
FROM carts
WHERE cart_id = :cart_id
Expand All @@ -269,6 +326,7 @@ def update_cart_item_quantity(
cart_item_id,
cart_id,
product_id,
quantity,
unit_price,
currency
FROM cart_items
Expand Down Expand Up @@ -335,6 +393,23 @@ def update_cart_item_quantity(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to update cart item quantity",
)

record_domain_event_safely(
event_name="cart_item_quantity_changed",
user_id=cart["user_id"],
entity_type="cart",
entity_id=updated_item["cart_id"],
properties={
"cart_id": updated_item["cart_id"],
"cart_item_id": updated_item["cart_item_id"],
"product_id": updated_item["product_id"],
"previous_quantity": item["quantity"],
"next_quantity": updated_item["quantity"],
"unit_price": updated_item["unit_price"],
"currency": updated_item["currency"],
"change_source": "update_cart_item_quantity",
},
)

return {
"cart_item_id": updated_item["cart_item_id"],
Expand Down
67 changes: 39 additions & 28 deletions apps/api/backend/services/cart_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from backend.db.connection import engine
from backend.schemas.cart import CartCreateRequest
from backend.services.event_log_service import record_domain_event_safely


def create_or_get_active_cart(payload: CartCreateRequest) -> dict[str, Any]:
Expand Down Expand Up @@ -49,42 +50,52 @@ def create_or_get_active_cart(payload: CartCreateRequest) -> dict[str, Any]:
checked_out_at
""")

is_created = False

with engine.begin() as connection:
existing = connection.execute(
select_query,
{"user_id": payload.user_id},
).mappings().first()

if existing is not None:
return {
"cart_id": existing["cart_id"],
"user_id": existing["user_id"],
"cart_status": existing["cart_status"],
"created_at": existing["created_at"],
"updated_at": existing["updated_at"],
"checked_out_at": existing["checked_out_at"],
}

created = connection.execute(
insert_query,
{"user_id": payload.user_id},
).mappings().first()

if created is None:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to create cart",
)

return {
"cart_id": created["cart_id"],
"user_id": created["user_id"],
"cart_status": created["cart_status"],
"created_at": created["created_at"],
"updated_at": created["updated_at"],
"checked_out_at": created["checked_out_at"],
}
cart = existing
else:
created = connection.execute(
insert_query,
{"user_id": payload.user_id},
).mappings().first()

if created is None:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to create cart",
)

cart = created
is_created = True

if is_created:
record_domain_event_safely(
event_name="cart_created",
user_id=cart["user_id"],
entity_type="cart",
entity_id=cart["cart_id"],
properties={
"cart_id": cart["cart_id"],
"cart_status": cart["cart_status"],
"created_at": cart["created_at"],
},
)

return {
"cart_id": cart["cart_id"],
"user_id": cart["user_id"],
"cart_status": cart["cart_status"],
"created_at": cart["created_at"],
"updated_at": cart["updated_at"],
"checked_out_at": cart["checked_out_at"],
}

def get_cart_detail(cart_id: UUID) -> dict[str, Any]:
cart_query = text("""
Expand Down
46 changes: 46 additions & 0 deletions apps/api/backend/services/coupon_service.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import logging
from decimal import Decimal, ROUND_HALF_UP
from typing import Any
from uuid import UUID
Expand All @@ -7,8 +8,34 @@

from backend.db.connection import engine
from backend.schemas.coupon_apply import CouponApplyRequest
from backend.services.event_log_service import record_event


logger = logging.getLogger(__name__)


def record_domain_event_safely(
*,
event_name: str,
user_id: UUID | None,
entity_type: str | None,
entity_id: UUID | None,
properties: dict[str, Any],
) -> None:
try:
record_event(
event_name=event_name,
event_type="domain_event",
source="backend",
user_id=user_id,
session_id=None,
entity_type=entity_type,
entity_id=entity_id,
properties=properties,
)
except Exception: # pylint: disable=broad-exception-caught
logger.exception("Failed to record domain event: %s", event_name)

def apply_coupon_to_cart(cart_id: UUID, payload: CouponApplyRequest) -> dict[str, Any]:
cart_query = text("""
SELECT
Expand Down Expand Up @@ -136,6 +163,25 @@ def apply_coupon_to_cart(cart_id: UUID, payload: CouponApplyRequest) -> dict[str

final_amount = total_amount - discount_amount

record_domain_event_safely(
event_name="coupon_applied",
user_id=cart["user_id"],
entity_type="coupon",
entity_id=coupon["coupon_id"],
properties={
"cart_id": cart_id,
"coupon_id": coupon["coupon_id"],
"coupon_name": coupon["coupon_name"],
"coupon_type": coupon["coupon_type"],
"discount_value": discount_value,
"minimum_order_amount": minimum_order_amount,
"total_amount": total_amount,
"discount_amount": discount_amount,
"final_amount": final_amount,
"currency": currency,
},
)

return {
"cart_id": cart_id,
"coupon": {
Expand Down
25 changes: 25 additions & 0 deletions apps/api/backend/services/event_log_service.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import logging
from typing import Any
from uuid import UUID, uuid4

Expand All @@ -11,6 +12,7 @@

ALLOWED_EVENT_TYPES = {"user_behavior", "domain_event", "system_event"}
ALLOWED_SOURCES = {"frontend", "backend", "script"}
logger = logging.getLogger(__name__)


def record_event(
Expand Down Expand Up @@ -108,6 +110,29 @@ def record_event(
}


def record_domain_event_safely(
*,
event_name: str,
user_id: UUID | None,
entity_type: str | None,
entity_id: UUID | None,
properties: dict[str, Any],
) -> None:
try:
record_event(
event_name=event_name,
event_type="domain_event",
source="backend",
user_id=user_id,
session_id=None,
entity_type=entity_type,
entity_id=entity_id,
properties=properties,
)
except Exception: # pylint: disable=broad-exception-caught
logger.exception("Failed to record domain event: %s", event_name)


def create_event_log(payload: EventLogCreateRequest) -> dict[str, Any]:
return record_event(
event_name=payload.event_name,
Expand Down
Loading
Loading