87 lines
2.7 KiB
TypeScript
87 lines
2.7 KiB
TypeScript
import { Injectable, Logger } from '@nestjs/common';
|
|
import { DataSource } from 'typeorm';
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
|
|
@Injectable()
|
|
export class TenantProvisioningService {
|
|
private readonly logger = new Logger(TenantProvisioningService.name);
|
|
|
|
constructor(private readonly dataSource: DataSource) {}
|
|
|
|
/**
|
|
* Create a new tenant schema with all required tables.
|
|
* Executes the tenant schema template SQL with the given tenant ID.
|
|
*/
|
|
async provisionTenant(tenantId: string): Promise<void> {
|
|
const schemaName = `it0_t_${tenantId}`;
|
|
this.logger.log(`Provisioning tenant schema: ${schemaName}`);
|
|
|
|
const queryRunner = this.dataSource.createQueryRunner();
|
|
await queryRunner.connect();
|
|
|
|
try {
|
|
await queryRunner.startTransaction();
|
|
|
|
// Read and execute the tenant schema template
|
|
const templatePath = path.resolve(__dirname, './migrations/002-create-tenant-schema-template.sql');
|
|
let sql = fs.readFileSync(templatePath, 'utf-8');
|
|
sql = sql.replace(/{TENANT_ID}/g, tenantId);
|
|
|
|
// Remove comment lines, then split by semicolon
|
|
const cleanedSql = sql
|
|
.split('\n')
|
|
.filter((line) => !line.trimStart().startsWith('--'))
|
|
.join('\n');
|
|
|
|
const statements = cleanedSql
|
|
.split(';')
|
|
.map((s) => s.trim())
|
|
.filter((s) => s.length > 0);
|
|
|
|
for (const stmt of statements) {
|
|
await queryRunner.query(stmt);
|
|
}
|
|
|
|
await queryRunner.commitTransaction();
|
|
this.logger.log(`Tenant schema ${schemaName} provisioned successfully.`);
|
|
} catch (err) {
|
|
await queryRunner.rollbackTransaction();
|
|
this.logger.error(`Failed to provision tenant ${tenantId}:`, err);
|
|
throw err;
|
|
} finally {
|
|
await queryRunner.release();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Drop a tenant schema (use with extreme caution).
|
|
*/
|
|
async deprovisionTenant(tenantId: string): Promise<void> {
|
|
const schemaName = `it0_t_${tenantId}`;
|
|
this.logger.warn(`Deprovisioning tenant schema: ${schemaName}`);
|
|
|
|
const queryRunner = this.dataSource.createQueryRunner();
|
|
await queryRunner.connect();
|
|
|
|
try {
|
|
await queryRunner.query(`DROP SCHEMA IF EXISTS "${schemaName}" CASCADE`);
|
|
this.logger.log(`Tenant schema ${schemaName} dropped.`);
|
|
} finally {
|
|
await queryRunner.release();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if a tenant schema exists.
|
|
*/
|
|
async schemaExists(tenantId: string): Promise<boolean> {
|
|
const schemaName = `it0_t_${tenantId}`;
|
|
const result = await this.dataSource.query(
|
|
`SELECT EXISTS (SELECT 1 FROM information_schema.schemata WHERE schema_name = $1)`,
|
|
[schemaName],
|
|
);
|
|
return result[0]?.exists ?? false;
|
|
}
|
|
}
|