fix: resolve all architecture validation errors (62 -> 0)

Major refactoring to achieve zero architecture violations:

API Layer:
- vendor/settings.py: Move validation to Pydantic field validators
  (tax rate, delivery method, boost sort, preorder days, languages, locales)
- admin/email_templates.py: Add Pydantic response models
  (TemplateListResponse, CategoriesResponse)
- shop/auth.py: Move password reset logic to CustomerService

Service Layer:
- customer_service.py: Add password reset methods
  (get_customer_for_password_reset, validate_and_reset_password)

Exception Layer:
- customer.py: Add InvalidPasswordResetTokenException,
  PasswordTooShortException

Frontend:
- admin/email-templates.js: Use apiClient, Utils.showToast()
- vendor/email-templates.js: Use apiClient, parent init pattern

Templates:
- admin/email-templates.html: Fix block name to extra_scripts
- shop/base.html: Add language default filter

Tooling:
- validate_architecture.py: Fix LANG-001 false positive for
  SUPPORTED_LANGUAGES and SUPPORTED_LOCALES blocks

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-03 18:48:59 +01:00
parent 370d61e8f7
commit 5155ef7445
10 changed files with 391 additions and 377 deletions

View File

@@ -19,10 +19,13 @@ from app.exceptions.customer import (
CustomerValidationException,
DuplicateCustomerEmailException,
InvalidCustomerCredentialsException,
InvalidPasswordResetTokenException,
PasswordTooShortException,
)
from app.exceptions.vendor import VendorNotActiveException, VendorNotFoundException
from app.services.auth_service import AuthService
from models.database.customer import Customer
from models.database.password_reset_token import PasswordResetToken
from models.database.vendor import Vendor
from models.schema.auth import UserLogin
from models.schema.customer import CustomerRegister, CustomerUpdate
@@ -410,6 +413,88 @@ class CustomerService:
return customer_number
def get_customer_for_password_reset(
self, db: Session, vendor_id: int, email: str
) -> Customer | None:
"""
Get active customer by email for password reset.
Args:
db: Database session
vendor_id: Vendor ID
email: Customer email
Returns:
Customer if found and active, None otherwise
"""
return (
db.query(Customer)
.filter(
Customer.vendor_id == vendor_id,
Customer.email == email.lower(),
Customer.is_active == True, # noqa: E712
)
.first()
)
def validate_and_reset_password(
self,
db: Session,
vendor_id: int,
reset_token: str,
new_password: str,
) -> Customer:
"""
Validate reset token and update customer password.
Args:
db: Database session
vendor_id: Vendor ID
reset_token: Password reset token from email
new_password: New password
Returns:
Customer: Updated customer
Raises:
PasswordTooShortException: If password too short
InvalidPasswordResetTokenException: If token invalid/expired
CustomerNotActiveException: If customer not active
"""
# Validate password length
if len(new_password) < 8:
raise PasswordTooShortException(min_length=8)
# Find valid token
token_record = PasswordResetToken.find_valid_token(db, reset_token)
if not token_record:
raise InvalidPasswordResetTokenException()
# Get the customer and verify they belong to this vendor
customer = (
db.query(Customer)
.filter(Customer.id == token_record.customer_id)
.first()
)
if not customer or customer.vendor_id != vendor_id:
raise InvalidPasswordResetTokenException()
if not customer.is_active:
raise CustomerNotActiveException(customer.email)
# Hash the new password and update customer
hashed_password = self.auth_service.hash_password(new_password)
customer.hashed_password = hashed_password
# Mark token as used
token_record.mark_used(db)
logger.info(f"Password reset completed for customer {customer.id}")
return customer
# Singleton instance
customer_service = CustomerService()