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:
@@ -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}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
191
static/vendor/js/email-templates.js
vendored
191
static/vendor/js/email-templates.js
vendored
@@ -54,6 +54,14 @@ function vendorEmailTemplates() {
|
||||
|
||||
// Lifecycle
|
||||
async init() {
|
||||
vendorEmailTemplatesLog.info('Email templates init() called');
|
||||
|
||||
// Call parent init to set vendorCode and other base state
|
||||
const parentInit = data().init;
|
||||
if (parentInit) {
|
||||
await parentInit.call(this);
|
||||
}
|
||||
|
||||
await this.loadData();
|
||||
},
|
||||
|
||||
@@ -63,37 +71,17 @@ function vendorEmailTemplates() {
|
||||
this.error = '';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/v1/vendor/email-templates', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${this.getAuthToken()}`
|
||||
}
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
this.templates = data.templates || [];
|
||||
this.supportedLanguages = data.supported_languages || ['en', 'fr', 'de', 'lb'];
|
||||
} else {
|
||||
const error = await response.json();
|
||||
this.error = error.detail || 'Failed to load templates';
|
||||
}
|
||||
const response = await apiClient.get('/vendor/email-templates');
|
||||
this.templates = response.templates || [];
|
||||
this.supportedLanguages = response.supported_languages || ['en', 'fr', 'de', 'lb'];
|
||||
} catch (error) {
|
||||
vendorEmailTemplatesLog.error('Failed to load templates:', error);
|
||||
this.error = 'Failed to load templates';
|
||||
this.error = error.detail || 'Failed to load templates';
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// Auth token helper
|
||||
getAuthToken() {
|
||||
// Get from cookie or localStorage depending on your auth setup
|
||||
return document.cookie
|
||||
.split('; ')
|
||||
.find(row => row.startsWith('vendor_token='))
|
||||
?.split('=')[1] || '';
|
||||
},
|
||||
|
||||
// Category styling
|
||||
getCategoryClass(category) {
|
||||
const classes = {
|
||||
@@ -121,24 +109,18 @@ function vendorEmailTemplates() {
|
||||
this.loadingTemplate = true;
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/v1/vendor/email-templates/${this.editingTemplate.code}/${this.editLanguage}`,
|
||||
{
|
||||
headers: {
|
||||
'Authorization': `Bearer ${this.getAuthToken()}`
|
||||
}
|
||||
}
|
||||
const data = await apiClient.get(
|
||||
`/vendor/email-templates/${this.editingTemplate.code}/${this.editLanguage}`
|
||||
);
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
this.templateSource = data.source;
|
||||
this.editForm = {
|
||||
subject: data.subject || '',
|
||||
body_html: data.body_html || '',
|
||||
body_text: data.body_text || ''
|
||||
};
|
||||
} else if (response.status === 404) {
|
||||
this.templateSource = data.source;
|
||||
this.editForm = {
|
||||
subject: data.subject || '',
|
||||
body_html: data.body_html || '',
|
||||
body_text: data.body_text || ''
|
||||
};
|
||||
} catch (error) {
|
||||
if (error.status === 404) {
|
||||
// No template for this language
|
||||
this.templateSource = 'none';
|
||||
this.editForm = {
|
||||
@@ -146,11 +128,11 @@ function vendorEmailTemplates() {
|
||||
body_html: '',
|
||||
body_text: ''
|
||||
};
|
||||
this.showNotification(`No template available for ${this.editLanguage.toUpperCase()}`, 'info');
|
||||
Utils.showToast(`No template available for ${this.editLanguage.toUpperCase()}`, 'info');
|
||||
} else {
|
||||
vendorEmailTemplatesLog.error('Failed to load template:', error);
|
||||
Utils.showToast('Failed to load template', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
vendorEmailTemplatesLog.error('Failed to load template:', error);
|
||||
this.showNotification('Failed to load template', 'error');
|
||||
} finally {
|
||||
this.loadingTemplate = false;
|
||||
}
|
||||
@@ -172,34 +154,22 @@ function vendorEmailTemplates() {
|
||||
this.saving = true;
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/v1/vendor/email-templates/${this.editingTemplate.code}/${this.editLanguage}`,
|
||||
await apiClient.put(
|
||||
`/vendor/email-templates/${this.editingTemplate.code}/${this.editLanguage}`,
|
||||
{
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${this.getAuthToken()}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
subject: this.editForm.subject,
|
||||
body_html: this.editForm.body_html,
|
||||
body_text: this.editForm.body_text || null
|
||||
})
|
||||
subject: this.editForm.subject,
|
||||
body_html: this.editForm.body_html,
|
||||
body_text: this.editForm.body_text || null
|
||||
}
|
||||
);
|
||||
|
||||
if (response.ok) {
|
||||
this.showNotification('Template saved successfully', 'success');
|
||||
this.templateSource = 'vendor_override';
|
||||
// Refresh list to show updated status
|
||||
await this.loadData();
|
||||
} else {
|
||||
const error = await response.json();
|
||||
this.showNotification(error.detail || 'Failed to save template', 'error');
|
||||
}
|
||||
Utils.showToast('Template saved successfully', 'success');
|
||||
this.templateSource = 'vendor_override';
|
||||
// Refresh list to show updated status
|
||||
await this.loadData();
|
||||
} catch (error) {
|
||||
vendorEmailTemplatesLog.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;
|
||||
}
|
||||
@@ -215,29 +185,18 @@ function vendorEmailTemplates() {
|
||||
this.reverting = true;
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/v1/vendor/email-templates/${this.editingTemplate.code}/${this.editLanguage}`,
|
||||
{
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${this.getAuthToken()}`
|
||||
}
|
||||
}
|
||||
await apiClient.delete(
|
||||
`/vendor/email-templates/${this.editingTemplate.code}/${this.editLanguage}`
|
||||
);
|
||||
|
||||
if (response.ok) {
|
||||
this.showNotification('Reverted to platform default', 'success');
|
||||
// Reload the template to show platform version
|
||||
await this.loadTemplateLanguage();
|
||||
// Refresh list
|
||||
await this.loadData();
|
||||
} else {
|
||||
const error = await response.json();
|
||||
this.showNotification(error.detail || 'Failed to revert', 'error');
|
||||
}
|
||||
Utils.showToast('Reverted to platform default', 'success');
|
||||
// Reload the template to show platform version
|
||||
await this.loadTemplateLanguage();
|
||||
// Refresh list
|
||||
await this.loadData();
|
||||
} catch (error) {
|
||||
vendorEmailTemplatesLog.error('Failed to revert template:', error);
|
||||
this.showNotification('Failed to revert', 'error');
|
||||
Utils.showToast(error.detail || 'Failed to revert', 'error');
|
||||
} finally {
|
||||
this.reverting = false;
|
||||
}
|
||||
@@ -248,30 +207,19 @@ function vendorEmailTemplates() {
|
||||
if (!this.editingTemplate) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/v1/vendor/email-templates/${this.editingTemplate.code}/preview`,
|
||||
const data = await apiClient.post(
|
||||
`/vendor/email-templates/${this.editingTemplate.code}/preview`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${this.getAuthToken()}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
language: this.editLanguage,
|
||||
variables: {}
|
||||
})
|
||||
language: this.editLanguage,
|
||||
variables: {}
|
||||
}
|
||||
);
|
||||
|
||||
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) {
|
||||
vendorEmailTemplatesLog.error('Failed to preview template:', error);
|
||||
this.showNotification('Failed to load preview', 'error');
|
||||
Utils.showToast('Failed to load preview', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -286,49 +234,28 @@ function vendorEmailTemplates() {
|
||||
this.sendingTest = true;
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/v1/vendor/email-templates/${this.editingTemplate.code}/test`,
|
||||
const result = await apiClient.post(
|
||||
`/vendor/email-templates/${this.editingTemplate.code}/test`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${this.getAuthToken()}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
to_email: this.testEmailAddress,
|
||||
language: this.editLanguage,
|
||||
variables: {}
|
||||
})
|
||||
to_email: this.testEmailAddress,
|
||||
language: this.editLanguage,
|
||||
variables: {}
|
||||
}
|
||||
);
|
||||
|
||||
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) {
|
||||
vendorEmailTemplatesLog.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}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user