Files
orion/app/api/v1/shop/profile.py
Samir Boulahtit 82c07c165f feat: add customer profile, VAT alignment, and fix shop auth
Customer Profile:
- Add profile API (GET/PUT /api/v1/shop/profile)
- Add password change endpoint (PUT /api/v1/shop/profile/password)
- Implement full profile page with preferences and password sections
- Add CustomerPasswordChange schema

Shop Authentication Fixes:
- Add Authorization header to all shop account API calls
- Fix orders, order-detail, messages pages authentication
- Add proper redirect to login on 401 responses
- Fix toast message showing noqa comment in shop-layout.js

VAT Calculation:
- Add shared VAT utility (app/utils/vat.py)
- Add VAT fields to Order model (vat_regime, vat_rate, etc.)
- Align order VAT calculation with invoice settings
- Add migration for VAT fields on orders

Validation Framework:
- Fix base_validator.py with missing methods
- Add validate_file, output_results, get_exit_code methods
- Fix validate_all.py import issues

Documentation:
- Add launch-readiness.md tracking OMS status
- Update to 95% feature complete

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 20:31:48 +01:00

162 lines
4.7 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.core.security import get_password_hash, verify_password
from app.exceptions import ValidationException
from models.database.customer import Customer
from models.schema.customer import (
CustomerPasswordChange,
CustomerResponse,
CustomerUpdate,
)
router = APIRouter()
logger = logging.getLogger(__name__)
@router.get("/profile", response_model=CustomerResponse)
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 = (
db.query(Customer)
.filter(
Customer.vendor_id == customer.vendor_id,
Customer.email == update_data.email,
Customer.id != customer.id,
)
.first()
)
if existing:
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 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 = get_password_hash(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"}