fix: card lookup 422 caused by route ordering conflict
Some checks failed
Some checks failed
Move /cards/lookup (GET and POST) before /cards/{card_id} so FastAPI
matches the literal path before the parameterized one. Previously,
"lookup" was parsed as card_id (int), causing a 422 validation error.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -315,79 +315,6 @@ def list_cards(
|
||||
return CardListResponse(cards=card_responses, total=total)
|
||||
|
||||
|
||||
@store_router.get("/cards/{card_id}", response_model=CardDetailResponse)
|
||||
def get_card_detail(
|
||||
card_id: int = Path(..., gt=0),
|
||||
current_user: User = Depends(get_current_store_api),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Get detailed loyalty card info by ID."""
|
||||
store_id = current_user.token_store_id
|
||||
merchant_id = get_store_merchant_id(db, store_id)
|
||||
|
||||
card = card_service.get_card(db, card_id)
|
||||
if not card or card.merchant_id != merchant_id:
|
||||
from app.modules.loyalty.exceptions import LoyaltyCardNotFoundException
|
||||
|
||||
raise LoyaltyCardNotFoundException(str(card_id))
|
||||
|
||||
program = card.program
|
||||
customer = card.customer
|
||||
|
||||
return CardDetailResponse(
|
||||
id=card.id,
|
||||
card_number=card.card_number,
|
||||
customer_id=card.customer_id,
|
||||
merchant_id=card.merchant_id,
|
||||
program_id=card.program_id,
|
||||
enrolled_at_store_id=card.enrolled_at_store_id,
|
||||
customer_name=customer.full_name if customer else None,
|
||||
customer_email=customer.email if customer else None,
|
||||
merchant_name=card.merchant.name if card.merchant else None,
|
||||
qr_code_data=card.qr_code_data or card.card_number,
|
||||
program_name=program.display_name,
|
||||
program_type=program.loyalty_type,
|
||||
reward_description=program.stamps_reward_description,
|
||||
stamp_count=card.stamp_count,
|
||||
stamps_target=program.stamps_target,
|
||||
stamps_until_reward=max(0, program.stamps_target - card.stamp_count),
|
||||
total_stamps_earned=card.total_stamps_earned,
|
||||
stamps_redeemed=card.stamps_redeemed,
|
||||
points_balance=card.points_balance,
|
||||
total_points_earned=card.total_points_earned,
|
||||
points_redeemed=card.points_redeemed,
|
||||
is_active=card.is_active,
|
||||
created_at=card.created_at,
|
||||
last_stamp_at=card.last_stamp_at,
|
||||
last_points_at=card.last_points_at,
|
||||
last_redemption_at=card.last_redemption_at,
|
||||
last_activity_at=card.last_activity_at,
|
||||
has_google_wallet=bool(card.google_object_id),
|
||||
has_apple_wallet=bool(card.apple_serial_number),
|
||||
)
|
||||
|
||||
|
||||
@store_router.get("/transactions", response_model=TransactionListResponse)
|
||||
def list_store_transactions(
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(10, ge=1, le=100),
|
||||
current_user: User = Depends(get_current_store_api),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""List recent transactions for this merchant's loyalty program."""
|
||||
store_id = current_user.token_store_id
|
||||
merchant_id = get_store_merchant_id(db, store_id)
|
||||
|
||||
transactions, total = card_service.get_store_transactions(
|
||||
db, merchant_id, skip=skip, limit=limit
|
||||
)
|
||||
|
||||
return TransactionListResponse(
|
||||
transactions=[TransactionResponse.model_validate(t) for t in transactions],
|
||||
total=total,
|
||||
)
|
||||
|
||||
|
||||
def _build_card_lookup_response(card, db=None) -> CardLookupResponse:
|
||||
"""Build a CardLookupResponse from a card object."""
|
||||
from datetime import timedelta
|
||||
@@ -484,6 +411,79 @@ def lookup_card(
|
||||
return _build_card_lookup_response(card, db)
|
||||
|
||||
|
||||
@store_router.get("/cards/{card_id}", response_model=CardDetailResponse)
|
||||
def get_card_detail(
|
||||
card_id: int = Path(..., gt=0),
|
||||
current_user: User = Depends(get_current_store_api),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Get detailed loyalty card info by ID."""
|
||||
store_id = current_user.token_store_id
|
||||
merchant_id = get_store_merchant_id(db, store_id)
|
||||
|
||||
card = card_service.get_card(db, card_id)
|
||||
if not card or card.merchant_id != merchant_id:
|
||||
from app.modules.loyalty.exceptions import LoyaltyCardNotFoundException
|
||||
|
||||
raise LoyaltyCardNotFoundException(str(card_id))
|
||||
|
||||
program = card.program
|
||||
customer = card.customer
|
||||
|
||||
return CardDetailResponse(
|
||||
id=card.id,
|
||||
card_number=card.card_number,
|
||||
customer_id=card.customer_id,
|
||||
merchant_id=card.merchant_id,
|
||||
program_id=card.program_id,
|
||||
enrolled_at_store_id=card.enrolled_at_store_id,
|
||||
customer_name=customer.full_name if customer else None,
|
||||
customer_email=customer.email if customer else None,
|
||||
merchant_name=card.merchant.name if card.merchant else None,
|
||||
qr_code_data=card.qr_code_data or card.card_number,
|
||||
program_name=program.display_name,
|
||||
program_type=program.loyalty_type,
|
||||
reward_description=program.stamps_reward_description,
|
||||
stamp_count=card.stamp_count,
|
||||
stamps_target=program.stamps_target,
|
||||
stamps_until_reward=max(0, program.stamps_target - card.stamp_count),
|
||||
total_stamps_earned=card.total_stamps_earned,
|
||||
stamps_redeemed=card.stamps_redeemed,
|
||||
points_balance=card.points_balance,
|
||||
total_points_earned=card.total_points_earned,
|
||||
points_redeemed=card.points_redeemed,
|
||||
is_active=card.is_active,
|
||||
created_at=card.created_at,
|
||||
last_stamp_at=card.last_stamp_at,
|
||||
last_points_at=card.last_points_at,
|
||||
last_redemption_at=card.last_redemption_at,
|
||||
last_activity_at=card.last_activity_at,
|
||||
has_google_wallet=bool(card.google_object_id),
|
||||
has_apple_wallet=bool(card.apple_serial_number),
|
||||
)
|
||||
|
||||
|
||||
@store_router.get("/transactions", response_model=TransactionListResponse)
|
||||
def list_store_transactions(
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(10, ge=1, le=100),
|
||||
current_user: User = Depends(get_current_store_api),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""List recent transactions for this merchant's loyalty program."""
|
||||
store_id = current_user.token_store_id
|
||||
merchant_id = get_store_merchant_id(db, store_id)
|
||||
|
||||
transactions, total = card_service.get_store_transactions(
|
||||
db, merchant_id, skip=skip, limit=limit
|
||||
)
|
||||
|
||||
return TransactionListResponse(
|
||||
transactions=[TransactionResponse.model_validate(t) for t in transactions],
|
||||
total=total,
|
||||
)
|
||||
|
||||
|
||||
@store_router.post("/cards/enroll", response_model=CardResponse, status_code=201)
|
||||
def enroll_customer(
|
||||
data: CardEnrollRequest,
|
||||
|
||||
Reference in New Issue
Block a user