# 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) 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)