- Use error_state macro in vendor dashboard instead of inline HTML - Add # authenticated marker to shop profile/addresses endpoints 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
270 lines
8.1 KiB
Python
270 lines
8.1 KiB
Python
# app/api/v1/shop/addresses.py
|
|
"""
|
|
Shop Addresses API (Customer authenticated)
|
|
|
|
Endpoints for managing customer addresses in shop frontend.
|
|
Uses vendor from request.state (injected by VendorContextMiddleware).
|
|
Requires customer authentication.
|
|
"""
|
|
|
|
import logging
|
|
|
|
from fastapi import APIRouter, Depends, Path, Request
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.api.deps import get_current_customer_api
|
|
from app.core.database import get_db
|
|
from app.exceptions import VendorNotFoundException
|
|
from app.services.customer_address_service import customer_address_service
|
|
from models.database.customer import Customer
|
|
from models.schema.customer import (
|
|
CustomerAddressCreate,
|
|
CustomerAddressListResponse,
|
|
CustomerAddressResponse,
|
|
CustomerAddressUpdate,
|
|
)
|
|
|
|
router = APIRouter()
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@router.get("/addresses", response_model=CustomerAddressListResponse) # authenticated
|
|
def list_addresses(
|
|
request: Request,
|
|
customer: Customer = Depends(get_current_customer_api),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""
|
|
List all addresses for authenticated customer.
|
|
|
|
Vendor is automatically determined from request context.
|
|
Returns all addresses sorted by default first, then by creation date.
|
|
"""
|
|
vendor = getattr(request.state, "vendor", None)
|
|
if not vendor:
|
|
raise VendorNotFoundException("context", identifier_type="subdomain")
|
|
|
|
logger.debug(
|
|
f"[SHOP_API] list_addresses for customer {customer.id}",
|
|
extra={
|
|
"vendor_id": vendor.id,
|
|
"vendor_code": vendor.subdomain,
|
|
"customer_id": customer.id,
|
|
},
|
|
)
|
|
|
|
addresses = customer_address_service.list_addresses(
|
|
db=db, vendor_id=vendor.id, customer_id=customer.id
|
|
)
|
|
|
|
return CustomerAddressListResponse(
|
|
addresses=[CustomerAddressResponse.model_validate(a) for a in addresses],
|
|
total=len(addresses),
|
|
)
|
|
|
|
|
|
@router.get("/addresses/{address_id}", response_model=CustomerAddressResponse)
|
|
def get_address(
|
|
request: Request,
|
|
address_id: int = Path(..., description="Address ID", gt=0),
|
|
customer: Customer = Depends(get_current_customer_api),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""
|
|
Get specific address by ID.
|
|
|
|
Vendor is automatically determined from request context.
|
|
Customer can only access their own addresses.
|
|
"""
|
|
vendor = getattr(request.state, "vendor", None)
|
|
if not vendor:
|
|
raise VendorNotFoundException("context", identifier_type="subdomain")
|
|
|
|
logger.debug(
|
|
f"[SHOP_API] get_address {address_id} for customer {customer.id}",
|
|
extra={
|
|
"vendor_id": vendor.id,
|
|
"customer_id": customer.id,
|
|
"address_id": address_id,
|
|
},
|
|
)
|
|
|
|
address = customer_address_service.get_address(
|
|
db=db, vendor_id=vendor.id, customer_id=customer.id, address_id=address_id
|
|
)
|
|
|
|
return CustomerAddressResponse.model_validate(address)
|
|
|
|
|
|
@router.post("/addresses", response_model=CustomerAddressResponse, status_code=201)
|
|
def create_address(
|
|
request: Request,
|
|
address_data: CustomerAddressCreate,
|
|
customer: Customer = Depends(get_current_customer_api),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""
|
|
Create new address for authenticated customer.
|
|
|
|
Vendor is automatically determined from request context.
|
|
Maximum 10 addresses per customer.
|
|
If is_default=True, clears default flag on other addresses of same type.
|
|
"""
|
|
vendor = getattr(request.state, "vendor", None)
|
|
if not vendor:
|
|
raise VendorNotFoundException("context", identifier_type="subdomain")
|
|
|
|
logger.debug(
|
|
f"[SHOP_API] create_address for customer {customer.id}",
|
|
extra={
|
|
"vendor_id": vendor.id,
|
|
"customer_id": customer.id,
|
|
"address_type": address_data.address_type,
|
|
},
|
|
)
|
|
|
|
address = customer_address_service.create_address(
|
|
db=db,
|
|
vendor_id=vendor.id,
|
|
customer_id=customer.id,
|
|
address_data=address_data,
|
|
)
|
|
db.commit()
|
|
|
|
logger.info(
|
|
f"Created address {address.id} for customer {customer.id} "
|
|
f"(type={address_data.address_type})",
|
|
extra={
|
|
"address_id": address.id,
|
|
"customer_id": customer.id,
|
|
"address_type": address_data.address_type,
|
|
},
|
|
)
|
|
|
|
return CustomerAddressResponse.model_validate(address)
|
|
|
|
|
|
@router.put("/addresses/{address_id}", response_model=CustomerAddressResponse)
|
|
def update_address(
|
|
request: Request,
|
|
address_data: CustomerAddressUpdate,
|
|
address_id: int = Path(..., description="Address ID", gt=0),
|
|
customer: Customer = Depends(get_current_customer_api),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""
|
|
Update existing address.
|
|
|
|
Vendor is automatically determined from request context.
|
|
Customer can only update their own addresses.
|
|
If is_default=True, clears default flag on other addresses of same type.
|
|
"""
|
|
vendor = getattr(request.state, "vendor", None)
|
|
if not vendor:
|
|
raise VendorNotFoundException("context", identifier_type="subdomain")
|
|
|
|
logger.debug(
|
|
f"[SHOP_API] update_address {address_id} for customer {customer.id}",
|
|
extra={
|
|
"vendor_id": vendor.id,
|
|
"customer_id": customer.id,
|
|
"address_id": address_id,
|
|
},
|
|
)
|
|
|
|
address = customer_address_service.update_address(
|
|
db=db,
|
|
vendor_id=vendor.id,
|
|
customer_id=customer.id,
|
|
address_id=address_id,
|
|
address_data=address_data,
|
|
)
|
|
db.commit()
|
|
|
|
logger.info(
|
|
f"Updated address {address_id} for customer {customer.id}",
|
|
extra={"address_id": address_id, "customer_id": customer.id},
|
|
)
|
|
|
|
return CustomerAddressResponse.model_validate(address)
|
|
|
|
|
|
@router.delete("/addresses/{address_id}", status_code=204)
|
|
def delete_address(
|
|
request: Request,
|
|
address_id: int = Path(..., description="Address ID", gt=0),
|
|
customer: Customer = Depends(get_current_customer_api),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""
|
|
Delete address.
|
|
|
|
Vendor is automatically determined from request context.
|
|
Customer can only delete their own addresses.
|
|
"""
|
|
vendor = getattr(request.state, "vendor", None)
|
|
if not vendor:
|
|
raise VendorNotFoundException("context", identifier_type="subdomain")
|
|
|
|
logger.debug(
|
|
f"[SHOP_API] delete_address {address_id} for customer {customer.id}",
|
|
extra={
|
|
"vendor_id": vendor.id,
|
|
"customer_id": customer.id,
|
|
"address_id": address_id,
|
|
},
|
|
)
|
|
|
|
customer_address_service.delete_address(
|
|
db=db, vendor_id=vendor.id, customer_id=customer.id, address_id=address_id
|
|
)
|
|
db.commit()
|
|
|
|
logger.info(
|
|
f"Deleted address {address_id} for customer {customer.id}",
|
|
extra={"address_id": address_id, "customer_id": customer.id},
|
|
)
|
|
|
|
|
|
@router.put("/addresses/{address_id}/default", response_model=CustomerAddressResponse)
|
|
def set_address_default(
|
|
request: Request,
|
|
address_id: int = Path(..., description="Address ID", gt=0),
|
|
customer: Customer = Depends(get_current_customer_api),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""
|
|
Set address as default for its type.
|
|
|
|
Vendor is automatically determined from request context.
|
|
Clears default flag on other addresses of the same type.
|
|
"""
|
|
vendor = getattr(request.state, "vendor", None)
|
|
if not vendor:
|
|
raise VendorNotFoundException("context", identifier_type="subdomain")
|
|
|
|
logger.debug(
|
|
f"[SHOP_API] set_address_default {address_id} for customer {customer.id}",
|
|
extra={
|
|
"vendor_id": vendor.id,
|
|
"customer_id": customer.id,
|
|
"address_id": address_id,
|
|
},
|
|
)
|
|
|
|
address = customer_address_service.set_default(
|
|
db=db, vendor_id=vendor.id, customer_id=customer.id, address_id=address_id
|
|
)
|
|
db.commit()
|
|
|
|
logger.info(
|
|
f"Set address {address_id} as default for customer {customer.id}",
|
|
extra={
|
|
"address_id": address_id,
|
|
"customer_id": customer.id,
|
|
"address_type": address.address_type,
|
|
},
|
|
)
|
|
|
|
return CustomerAddressResponse.model_validate(address)
|