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

@@ -3228,7 +3228,12 @@ class ArchitectureValidator:
"""Validate template patterns"""
print("📄 Validating templates...")
template_files = list(target_path.glob("app/templates/admin/**/*.html"))
# Include admin, vendor, and shop templates
template_files = (
list(target_path.glob("app/templates/admin/**/*.html")) +
list(target_path.glob("app/templates/vendor/**/*.html")) +
list(target_path.glob("app/templates/shop/**/*.html"))
)
self.result.files_checked += len(template_files)
# TPL-001 exclusion patterns
@@ -3254,6 +3259,11 @@ class ArchitectureValidator:
# Skip components showcase page
is_components_page = "components.html" in file_path.name
# Determine template type
is_admin = "/admin/" in file_path_str or "\\admin\\" in file_path_str
is_vendor = "/vendor/" in file_path_str or "\\vendor\\" in file_path_str
is_shop = "/shop/" in file_path_str or "\\shop\\" in file_path_str
content = file_path.read_text()
lines = content.split("\n")
@@ -3298,13 +3308,24 @@ class ArchitectureValidator:
# TPL-010: Check Alpine variables are defined in JS
if not is_base_or_partial and not is_macro and not is_components_page:
# Try to find corresponding JS file
# Try to find corresponding JS file based on template type
# Template: app/templates/admin/messages.html -> JS: static/admin/js/messages.js
# Template: app/templates/vendor/analytics.html -> JS: static/vendor/js/analytics.js
template_name = file_path.stem # e.g., "messages"
js_file = target_path / f"static/admin/js/{template_name}.js"
if js_file.exists():
js_content = js_file.read_text()
self._check_alpine_template_vars(file_path, content, lines, js_content)
if is_admin:
js_dir = "admin"
elif is_vendor:
js_dir = "vendor"
elif is_shop:
js_dir = "shop"
else:
js_dir = None
if js_dir:
js_file = target_path / f"static/{js_dir}/js/{template_name}.js"
if js_file.exists():
js_content = js_file.read_text()
self._check_alpine_template_vars(file_path, content, lines, js_content)
# TPL-011: Check for deprecated macros
self._check_deprecated_macros(file_path, content, lines)
@@ -3339,21 +3360,34 @@ class ArchitectureValidator:
if "standalone" in first_lines or "noqa: tpl-001" in first_lines:
continue
# TPL-001: Check for extends
# TPL-001: Check for extends (template-type specific)
if is_admin:
expected_base = "admin/base.html"
rule_id = "TPL-001"
elif is_vendor:
expected_base = "vendor/base.html"
rule_id = "TPL-001"
elif is_shop:
expected_base = "shop/base.html"
rule_id = "TPL-001"
else:
continue # Skip unknown template types
has_extends = any(
"{% extends" in line and "admin/base.html" in line for line in lines
"{% extends" in line and expected_base in line for line in lines
)
if not has_extends:
template_type = "Admin" if is_admin else "Vendor" if is_vendor else "Shop"
self._add_violation(
rule_id="TPL-001",
rule_id=rule_id,
rule_name="Templates must extend base",
severity=Severity.ERROR,
file_path=file_path,
line_number=1,
message="Admin template does not extend admin/base.html",
message=f"{template_type} template does not extend {expected_base}",
context=file_path.name,
suggestion="Add {% extends 'admin/base.html' %} at the top, or add {# standalone #} if intentional",
suggestion=f"Add {{% extends '{expected_base}' %}} at the top, or add {{# standalone #}} if intentional",
)
# =========================================================================
@@ -3408,8 +3442,9 @@ class ArchitectureValidator:
content = file_path.read_text()
lines = content.split("\n")
# Track if we're inside LANGUAGE_NAMES dicts (allowed to use language names)
in_language_names_dict = False
# Track if we're inside LANGUAGE_NAMES dicts or SUPPORTED_LANGUAGES lists
# (allowed to use language names as display values)
in_language_names_block = False
for i, line in enumerate(lines, 1):
# Skip comments
@@ -3417,15 +3452,22 @@ class ArchitectureValidator:
if stripped.startswith("#"):
continue
# Track LANGUAGE_NAMES/LANGUAGE_NAMES_EN blocks - name values are allowed
if (
"LANGUAGE_NAMES" in line or "LANGUAGE_NAMES_EN" in line
# Track LANGUAGE_NAMES, LANGUAGE_NAMES_EN, or SUPPORTED_LANGUAGES/LOCALES blocks
# - name values are allowed in these structures
if any(
name in line
for name in [
"LANGUAGE_NAMES",
"LANGUAGE_NAMES_EN",
"SUPPORTED_LANGUAGES",
"SUPPORTED_LOCALES",
]
) and "=" in line:
in_language_names_dict = True
if in_language_names_dict and stripped == "}":
in_language_names_dict = False
in_language_names_block = True
if in_language_names_block and stripped in ("}", "]"):
in_language_names_block = False
continue
if in_language_names_dict:
if in_language_names_block:
continue
for wrong, correct in invalid_codes: