feat: create per-tenant PG database during provisioning, drop on delete
Inject TenantDatabaseService; call createTenantDatabase() at the start of provisionAsync() (stores generated password on TenantEntity), and dropTenantDatabase() in delete() before GDPR data erasure. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import net.siegeln.cameleer.saas.identity.LogtoConfig;
|
|||||||
import net.siegeln.cameleer.saas.identity.LogtoManagementClient;
|
import net.siegeln.cameleer.saas.identity.LogtoManagementClient;
|
||||||
import net.siegeln.cameleer.saas.identity.ServerApiClient;
|
import net.siegeln.cameleer.saas.identity.ServerApiClient;
|
||||||
import net.siegeln.cameleer.saas.provisioning.ProvisioningProperties;
|
import net.siegeln.cameleer.saas.provisioning.ProvisioningProperties;
|
||||||
|
import net.siegeln.cameleer.saas.provisioning.TenantDatabaseService;
|
||||||
import net.siegeln.cameleer.saas.provisioning.TenantDataCleanupService;
|
import net.siegeln.cameleer.saas.provisioning.TenantDataCleanupService;
|
||||||
import net.siegeln.cameleer.saas.identity.ServerApiClient.ServerHealthResponse;
|
import net.siegeln.cameleer.saas.identity.ServerApiClient.ServerHealthResponse;
|
||||||
import net.siegeln.cameleer.saas.license.LicenseEntity;
|
import net.siegeln.cameleer.saas.license.LicenseEntity;
|
||||||
@@ -47,6 +48,7 @@ public class VendorTenantService {
|
|||||||
private final AuditService auditService;
|
private final AuditService auditService;
|
||||||
private final ProvisioningProperties provisioningProps;
|
private final ProvisioningProperties provisioningProps;
|
||||||
private final TenantDataCleanupService dataCleanupService;
|
private final TenantDataCleanupService dataCleanupService;
|
||||||
|
private final TenantDatabaseService tenantDatabaseService;
|
||||||
|
|
||||||
public VendorTenantService(TenantService tenantService,
|
public VendorTenantService(TenantService tenantService,
|
||||||
TenantRepository tenantRepository,
|
TenantRepository tenantRepository,
|
||||||
@@ -57,7 +59,8 @@ public class VendorTenantService {
|
|||||||
LogtoConfig logtoConfig,
|
LogtoConfig logtoConfig,
|
||||||
AuditService auditService,
|
AuditService auditService,
|
||||||
ProvisioningProperties provisioningProps,
|
ProvisioningProperties provisioningProps,
|
||||||
TenantDataCleanupService dataCleanupService) {
|
TenantDataCleanupService dataCleanupService,
|
||||||
|
TenantDatabaseService tenantDatabaseService) {
|
||||||
this.tenantService = tenantService;
|
this.tenantService = tenantService;
|
||||||
this.tenantRepository = tenantRepository;
|
this.tenantRepository = tenantRepository;
|
||||||
this.licenseService = licenseService;
|
this.licenseService = licenseService;
|
||||||
@@ -68,6 +71,7 @@ public class VendorTenantService {
|
|||||||
this.auditService = auditService;
|
this.auditService = auditService;
|
||||||
this.provisioningProps = provisioningProps;
|
this.provisioningProps = provisioningProps;
|
||||||
this.dataCleanupService = dataCleanupService;
|
this.dataCleanupService = dataCleanupService;
|
||||||
|
this.tenantDatabaseService = tenantDatabaseService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@@ -119,7 +123,30 @@ public class VendorTenantService {
|
|||||||
@Async
|
@Async
|
||||||
public void provisionAsync(UUID tenantId, String slug, String tier, String licenseToken, UUID actorId) {
|
public void provisionAsync(UUID tenantId, String slug, String tier, String licenseToken, UUID actorId) {
|
||||||
try {
|
try {
|
||||||
var provisionRequest = new TenantProvisionRequest(tenantId, slug, tier, licenseToken);
|
// Create per-tenant PG user + schema
|
||||||
|
String dbPassword = java.util.UUID.randomUUID().toString().replace("-", "")
|
||||||
|
+ java.util.UUID.randomUUID().toString().replace("-", "").substring(0, 8);
|
||||||
|
try {
|
||||||
|
tenantDatabaseService.createTenantDatabase(slug, dbPassword);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to create tenant database for {}: {}", slug, e.getMessage(), e);
|
||||||
|
tenantRepository.findById(tenantId).ifPresent(t -> {
|
||||||
|
t.setProvisionError("Database setup failed: " + e.getMessage());
|
||||||
|
tenantRepository.save(t);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store DB password on entity
|
||||||
|
TenantEntity tenantForDb = tenantRepository.findById(tenantId).orElse(null);
|
||||||
|
if (tenantForDb == null) {
|
||||||
|
log.error("Tenant {} disappeared during provisioning", slug);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tenantForDb.setDbPassword(dbPassword);
|
||||||
|
tenantRepository.save(tenantForDb);
|
||||||
|
|
||||||
|
var provisionRequest = new TenantProvisionRequest(tenantId, slug, tier, licenseToken, dbPassword);
|
||||||
ProvisionResult result = tenantProvisioner.provision(provisionRequest);
|
ProvisionResult result = tenantProvisioner.provision(provisionRequest);
|
||||||
|
|
||||||
TenantEntity tenant = tenantRepository.findById(tenantId).orElse(null);
|
TenantEntity tenant = tenantRepository.findById(tenantId).orElse(null);
|
||||||
@@ -302,7 +329,15 @@ public class VendorTenantService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Drop per-tenant PG user + database
|
||||||
|
try {
|
||||||
|
tenantDatabaseService.dropTenantDatabase(tenant.getSlug());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Failed to drop tenant database for {}: {}", tenant.getSlug(), e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
// Erase tenant data from server databases (GDPR)
|
// Erase tenant data from server databases (GDPR)
|
||||||
|
// TODO: split into cleanupClickHouse() in next task
|
||||||
dataCleanupService.cleanup(tenant.getSlug());
|
dataCleanupService.cleanup(tenant.getSlug());
|
||||||
|
|
||||||
// Soft-delete
|
// Soft-delete
|
||||||
|
|||||||
Reference in New Issue
Block a user