import type { RowDataPacket } from 'mysql2/promise'; import type { MySqlPool } from '../db/mysql.js'; export interface PlatformBootstrap { appId: string; tenantId: string; tenantCode: string; tenantName: string; brand: { name: string; logoUrl: string; themeColor: string; servicePhone: string; franchisePhone: string; shareTitle: string; shareImageUrl: string; }; defaultStoreId: string | null; } interface PlatformBootstrapRow extends RowDataPacket { appId: string; tenantId: string; tenantCode: string; tenantName: string; brandName: string; logoUrl: string; themeColor: string; servicePhone: string; franchisePhone: string; shareTitle: string; shareImageUrl: string; defaultStoreId: string | null; } export class AmbiguousAppTenantError extends Error { constructor(appId: string) { super(`Application ${appId} is bound to multiple tenants; tenant-id is required.`); this.name = 'AmbiguousAppTenantError'; } } export class PlatformConfigRepository { constructor(private readonly pool: Pick) {} async resolveBootstrap(appId: string, tenantId?: string): Promise { const tenantFilter = tenantId ? 'AND ta.tenant_id = ?' : ''; const params = tenantId ? [appId, tenantId] : [appId]; const [rows] = await this.pool.execute( `SELECT pa.appid AS appId, ta.tenant_id AS tenantId, t.code AS tenantCode, t.name AS tenantName, tc.brand_name AS brandName, tc.logo_url AS logoUrl, tc.theme_color AS themeColor, tc.service_phone AS servicePhone, tc.franchise_phone AS franchisePhone, tc.share_title AS shareTitle, tc.share_image_url AS shareImageUrl, tc.default_store_id AS defaultStoreId FROM qipai_platform_apps pa INNER JOIN qipai_tenant_apps ta ON ta.platform_app_id = pa.id AND ta.status = 'ACTIVE' AND ta.deleted_at IS NULL INNER JOIN qipai_tenants t ON t.id = ta.tenant_id AND t.status = 'ACTIVE' AND t.deleted_at IS NULL INNER JOIN qipai_tenant_configs tc ON tc.platform_app_id = pa.id AND tc.tenant_id = ta.tenant_id AND tc.deleted_at IS NULL WHERE pa.appid = ? AND pa.status = 'ACTIVE' AND pa.deleted_at IS NULL ${tenantFilter} ORDER BY ta.is_default DESC, ta.tenant_id ASC LIMIT 2`, params ); if (!tenantId && rows.length > 1) { throw new AmbiguousAppTenantError(appId); } const row = rows[0]; if (!row) return null; return { appId: row.appId, tenantId: String(row.tenantId), tenantCode: row.tenantCode, tenantName: row.tenantName, brand: { name: row.brandName, logoUrl: row.logoUrl, themeColor: row.themeColor, servicePhone: row.servicePhone, franchisePhone: row.franchisePhone, shareTitle: row.shareTitle, shareImageUrl: row.shareImageUrl }, defaultStoreId: row.defaultStoreId === null ? null : String(row.defaultStoreId) }; } }