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

@@ -55,22 +55,16 @@ function emailTemplatesPage() {
async loadData() {
this.loading = true;
try {
const [templatesRes, categoriesRes] = await Promise.all([
fetch('/api/v1/admin/email-templates'),
fetch('/api/v1/admin/email-templates/categories')
const [templatesData, categoriesData] = await Promise.all([
apiClient.get('/admin/email-templates'),
apiClient.get('/admin/email-templates/categories')
]);
if (templatesRes.ok) {
this.templates = await templatesRes.json();
}
if (categoriesRes.ok) {
const data = await categoriesRes.json();
this.categories = data.categories || [];
}
this.templates = templatesData.templates || [];
this.categories = categoriesData.categories || [];
} catch (error) {
console.error('Failed to load email templates:', error);
this.showNotification('Failed to load templates', 'error');
Utils.showToast('Failed to load templates', 'error');
} finally {
this.loading = false;
}
@@ -101,20 +95,19 @@ function emailTemplatesPage() {
this.loadingTemplate = true;
try {
const response = await fetch(
`/api/v1/admin/email-templates/${this.editingTemplate.code}/${this.editLanguage}`
const data = await apiClient.get(
`/admin/email-templates/${this.editingTemplate.code}/${this.editLanguage}`
);
if (response.ok) {
const data = await response.json();
this.editForm = {
subject: data.subject || '',
body_html: data.body_html || '',
body_text: data.body_text || '',
variables: data.variables || [],
required_variables: data.required_variables || []
};
} else if (response.status === 404) {
this.editForm = {
subject: data.subject || '',
body_html: data.body_html || '',
body_text: data.body_text || '',
variables: data.variables || [],
required_variables: data.required_variables || []
};
} catch (error) {
if (error.status === 404) {
// Template doesn't exist for this language yet
this.editForm = {
subject: '',
@@ -123,11 +116,11 @@ function emailTemplatesPage() {
variables: [],
required_variables: []
};
this.showNotification(`No template for ${this.editLanguage.toUpperCase()} - create one by saving`, 'info');
Utils.showToast(`No template for ${this.editLanguage.toUpperCase()} - create one by saving`, 'info');
} else {
console.error('Failed to load template:', error);
Utils.showToast('Failed to load template', 'error');
}
} catch (error) {
console.error('Failed to load template:', error);
this.showNotification('Failed to load template', 'error');
} finally {
this.loadingTemplate = false;
}
@@ -150,30 +143,21 @@ function emailTemplatesPage() {
this.saving = true;
try {
const response = await fetch(
`/api/v1/admin/email-templates/${this.editingTemplate.code}/${this.editLanguage}`,
await apiClient.put(
`/admin/email-templates/${this.editingTemplate.code}/${this.editLanguage}`,
{
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
subject: this.editForm.subject,
body_html: this.editForm.body_html,
body_text: this.editForm.body_text
})
subject: this.editForm.subject,
body_html: this.editForm.body_html,
body_text: this.editForm.body_text
}
);
if (response.ok) {
this.showNotification('Template saved successfully', 'success');
// Refresh templates list
await this.loadData();
} else {
const error = await response.json();
this.showNotification(error.detail || 'Failed to save template', 'error');
}
Utils.showToast('Template saved successfully', 'success');
// Refresh templates list
await this.loadData();
} catch (error) {
console.error('Failed to save template:', error);
this.showNotification('Failed to save template', 'error');
Utils.showToast(error.detail || 'Failed to save template', 'error');
} finally {
this.saving = false;
}
@@ -185,28 +169,20 @@ function emailTemplatesPage() {
// Use sample variables for preview
const sampleVariables = this.getSampleVariables(template.code);
const response = await fetch(
`/api/v1/admin/email-templates/${template.code}/preview`,
const data = await apiClient.post(
`/admin/email-templates/${template.code}/preview`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
template_code: template.code,
language: 'en',
variables: sampleVariables
})
template_code: template.code,
language: 'en',
variables: sampleVariables
}
);
if (response.ok) {
this.previewData = await response.json();
this.showPreviewModal = true;
} else {
this.showNotification('Failed to load preview', 'error');
}
this.previewData = data;
this.showPreviewModal = true;
} catch (error) {
console.error('Failed to preview template:', error);
this.showNotification('Failed to load preview', 'error');
Utils.showToast('Failed to load preview', 'error');
}
},
@@ -257,47 +233,29 @@ function emailTemplatesPage() {
this.sendingTest = true;
try {
const response = await fetch(
`/api/v1/admin/email-templates/${this.editingTemplate.code}/test`,
const result = await apiClient.post(
`/admin/email-templates/${this.editingTemplate.code}/test`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
template_code: this.editingTemplate.code,
language: this.editLanguage,
to_email: this.testEmailAddress,
variables: this.getSampleVariables(this.editingTemplate.code)
})
template_code: this.editingTemplate.code,
language: this.editLanguage,
to_email: this.testEmailAddress,
variables: this.getSampleVariables(this.editingTemplate.code)
}
);
const result = await response.json();
if (result.success) {
this.showNotification(`Test email sent to ${this.testEmailAddress}`, 'success');
Utils.showToast(`Test email sent to ${this.testEmailAddress}`, 'success');
this.showTestEmailModal = false;
this.testEmailAddress = '';
} else {
this.showNotification(result.message || 'Failed to send test email', 'error');
Utils.showToast(result.message || 'Failed to send test email', 'error');
}
} catch (error) {
console.error('Failed to send test email:', error);
this.showNotification('Failed to send test email', 'error');
Utils.showToast('Failed to send test email', 'error');
} finally {
this.sendingTest = false;
}
},
// Notifications
showNotification(message, type = 'info') {
// Use global notification system if available
if (window.showToast) {
window.showToast(message, type);
} else if (window.Alpine && Alpine.store('notifications')) {
Alpine.store('notifications').add(message, type);
} else {
console.log(`[${type.toUpperCase()}] ${message}`);
}
}
};
}