108 lines
3.2 KiB
TypeScript
108 lines
3.2 KiB
TypeScript
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<MySqlPool, 'execute'>) {}
|
|
|
|
async resolveBootstrap(appId: string, tenantId?: string): Promise<PlatformBootstrap | null> {
|
|
const tenantFilter = tenantId ? 'AND ta.tenant_id = ?' : '';
|
|
const params = tenantId ? [appId, tenantId] : [appId];
|
|
const [rows] = await this.pool.execute<PlatformBootstrapRow[]>(
|
|
`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)
|
|
};
|
|
}
|
|
}
|