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)
|
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:
|
def _build_card_lookup_response(card, db=None) -> CardLookupResponse:
|
||||||
"""Build a CardLookupResponse from a card object."""
|
"""Build a CardLookupResponse from a card object."""
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
@@ -484,6 +411,79 @@ def lookup_card(
|
|||||||
return _build_card_lookup_response(card, db)
|
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)
|
@store_router.post("/cards/enroll", response_model=CardResponse, status_code=201)
|
||||||
def enroll_customer(
|
def enroll_customer(
|
||||||
data: CardEnrollRequest,
|
data: CardEnrollRequest,
|
||||||
|
|||||||
Reference in New Issue
Block a user