feat(middleware): harden routing with fail-closed policy, custom subdomain management, and perf fixes
- Fix IPv6 host parsing with _strip_port() utility - Remove dangerous StorePlatform→Store.subdomain silent fallback - Close storefront gate bypass when frontend_type is None - Add custom subdomain management UI and API for stores - Add domain health diagnostic tool - Convert db.add() in loops to db.add_all() (24 PERF-006 fixes) - Add tests for all new functionality (18 subdomain service tests) - Add .github templates for validator compliance Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,13 @@ function adminStoreDetail() {
|
||||
domainSaving: false,
|
||||
newDomain: { domain: '', platform_id: '' },
|
||||
|
||||
// Custom subdomain management state
|
||||
customSubdomains: [],
|
||||
customSubdomainsLoading: false,
|
||||
editingSubdomainPlatformId: null,
|
||||
editingSubdomainValue: '',
|
||||
subdomainSaving: false,
|
||||
|
||||
// Initialize
|
||||
async init() {
|
||||
// Load i18n translations
|
||||
@@ -54,6 +61,7 @@ function adminStoreDetail() {
|
||||
await Promise.all([
|
||||
this.loadSubscriptions(),
|
||||
this.loadDomains(),
|
||||
this.loadCustomSubdomains(),
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
@@ -274,6 +282,70 @@ function adminStoreDetail() {
|
||||
}
|
||||
},
|
||||
|
||||
// ====================================================================
|
||||
// CUSTOM SUBDOMAIN MANAGEMENT
|
||||
// ====================================================================
|
||||
|
||||
async loadCustomSubdomains() {
|
||||
if (!this.store?.id) return;
|
||||
this.customSubdomainsLoading = true;
|
||||
try {
|
||||
const url = `/admin/stores/${this.store.id}/custom-subdomains`;
|
||||
const response = await apiClient.get(url);
|
||||
this.customSubdomains = response.subdomains || [];
|
||||
detailLog.info('Custom subdomains loaded:', this.customSubdomains.length);
|
||||
} catch (error) {
|
||||
if (error.status === 404) {
|
||||
this.customSubdomains = [];
|
||||
} else {
|
||||
detailLog.warn('Failed to load custom subdomains:', error.message);
|
||||
}
|
||||
} finally {
|
||||
this.customSubdomainsLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
startEditSubdomain(entry) {
|
||||
this.editingSubdomainPlatformId = entry.platform_id;
|
||||
this.editingSubdomainValue = entry.custom_subdomain || '';
|
||||
},
|
||||
|
||||
cancelEditSubdomain() {
|
||||
this.editingSubdomainPlatformId = null;
|
||||
this.editingSubdomainValue = '';
|
||||
},
|
||||
|
||||
async saveCustomSubdomain(platformId) {
|
||||
if (!this.editingSubdomainValue || this.subdomainSaving) return;
|
||||
this.subdomainSaving = true;
|
||||
try {
|
||||
await apiClient.put(
|
||||
`/admin/stores/${this.store.id}/custom-subdomains/${platformId}`,
|
||||
{ subdomain: this.editingSubdomainValue.trim().toLowerCase() }
|
||||
);
|
||||
Utils.showToast('Custom subdomain saved', 'success');
|
||||
this.cancelEditSubdomain();
|
||||
await this.loadCustomSubdomains();
|
||||
} catch (error) {
|
||||
Utils.showToast(error.message || 'Failed to save subdomain', 'error');
|
||||
} finally {
|
||||
this.subdomainSaving = false;
|
||||
}
|
||||
},
|
||||
|
||||
async clearCustomSubdomain(platformId, subdomainName) {
|
||||
if (!confirm(`Clear custom subdomain "${subdomainName}"? The store will use its default subdomain on this platform.`)) return;
|
||||
try {
|
||||
await apiClient.delete(
|
||||
`/admin/stores/${this.store.id}/custom-subdomains/${platformId}`
|
||||
);
|
||||
Utils.showToast('Custom subdomain cleared', 'success');
|
||||
await this.loadCustomSubdomains();
|
||||
} catch (error) {
|
||||
Utils.showToast(error.message || 'Failed to clear subdomain', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
// Refresh store data
|
||||
async refresh() {
|
||||
detailLog.info('=== STORE REFRESH TRIGGERED ===');
|
||||
|
||||
Reference in New Issue
Block a user