Files
orion/app/api/v1/storefront/profile.py
Samir Boulahtit 3e86d4b58b refactor: rename shop to storefront throughout codebase
Rename "shop" to "storefront" as not all platforms sell items -
storefront is a more accurate term for the customer-facing interface.

Changes:
- Rename app/api/v1/shop/ → app/api/v1/storefront/
- Rename app/routes/shop_pages.py → app/routes/storefront_pages.py
- Rename app/modules/cms/routes/api/shop.py → storefront.py
- Rename tests/integration/api/v1/shop/ → storefront/
- Update API prefix from /api/v1/shop to /api/v1/storefront
- Update route tags from shop-* to storefront-*
- Rename get_shop_context() → get_storefront_context()
- Update architecture rules to reference storefront paths
- Update all test API endpoint paths

This is Phase 2 of the storefront module restructure plan.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 22:42:01 +01:00

162 lines
4.8 KiB
Python

# app/api/v1/shop/profile.py
"""
Shop Profile API (Customer authenticated)
Endpoints for managing customer profile in shop frontend.
Requires customer authentication.
"""
import logging
from fastapi import APIRouter, Depends
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 ValidationException
from app.services.auth_service import AuthService
from app.services.customer_service import customer_service
from models.database.customer import Customer
from models.schema.customer import (
CustomerPasswordChange,
CustomerResponse,
CustomerUpdate,
)
# Auth service for password operations
auth_service = AuthService()
router = APIRouter()
logger = logging.getLogger(__name__)
@router.get("/profile", response_model=CustomerResponse) # authenticated
def get_profile(
customer: Customer = Depends(get_current_customer_api),
db: Session = Depends(get_db),
):
"""
Get current customer profile.
Returns the authenticated customer's profile information.
"""
logger.debug(
f"[SHOP_API] get_profile for customer {customer.id}",
extra={
"customer_id": customer.id,
"email": customer.email,
},
)
return CustomerResponse.model_validate(customer)
@router.put("/profile", response_model=CustomerResponse)
def update_profile(
update_data: CustomerUpdate,
customer: Customer = Depends(get_current_customer_api),
db: Session = Depends(get_db),
):
"""
Update current customer profile.
Allows updating profile fields like name, phone, marketing consent, etc.
Email changes require the new email to be unique within the vendor.
Request Body:
- email: New email address (optional)
- first_name: First name (optional)
- last_name: Last name (optional)
- phone: Phone number (optional)
- marketing_consent: Marketing consent (optional)
- preferred_language: Preferred language (optional)
"""
logger.debug(
f"[SHOP_API] update_profile for customer {customer.id}",
extra={
"customer_id": customer.id,
"email": customer.email,
"update_fields": [k for k, v in update_data.model_dump().items() if v is not None],
},
)
# If email is being changed, check uniqueness within vendor
if update_data.email and update_data.email != customer.email:
existing = customer_service.get_customer_by_email(
db, customer.vendor_id, update_data.email
)
if existing and existing.id != customer.id:
raise ValidationException("Email already in use")
# Update only provided fields
update_dict = update_data.model_dump(exclude_unset=True)
for field, value in update_dict.items():
if value is not None:
setattr(customer, field, value)
db.commit()
db.refresh(customer)
logger.info(
f"Customer {customer.id} updated profile",
extra={
"customer_id": customer.id,
"updated_fields": list(update_dict.keys()),
},
)
return CustomerResponse.model_validate(customer)
@router.put("/profile/password", response_model=dict)
def change_password(
password_data: CustomerPasswordChange,
customer: Customer = Depends(get_current_customer_api),
db: Session = Depends(get_db),
):
"""
Change customer password.
Requires current password verification and matching new password confirmation.
Request Body:
- current_password: Current password
- new_password: New password (min 8 chars, must contain letter and digit)
- confirm_password: Confirmation of new password
"""
logger.debug(
f"[SHOP_API] change_password for customer {customer.id}",
extra={
"customer_id": customer.id,
"email": customer.email,
},
)
# Verify current password
if not auth_service.auth_manager.verify_password(
password_data.current_password, customer.hashed_password
):
raise ValidationException("Current password is incorrect")
# Verify passwords match
if password_data.new_password != password_data.confirm_password:
raise ValidationException("New passwords do not match")
# Check new password is different
if password_data.new_password == password_data.current_password:
raise ValidationException("New password must be different from current password")
# Update password
customer.hashed_password = auth_service.hash_password(password_data.new_password)
db.commit()
logger.info(
f"Customer {customer.id} changed password",
extra={
"customer_id": customer.id,
"email": customer.email,
},
)
return {"message": "Password changed successfully"}