Files
cameleer-saas/src/main/java/net/siegeln/cameleer/saas/onboarding/OnboardingService.java
2026-04-27 14:46:20 +02:00

86 lines
3.7 KiB
Java

package net.siegeln.cameleer.saas.onboarding;
import net.siegeln.cameleer.saas.account.AccountService;
import net.siegeln.cameleer.saas.identity.LogtoManagementClient;
import net.siegeln.cameleer.saas.tenant.TenantEntity;
import net.siegeln.cameleer.saas.tenant.dto.CreateTenantRequest;
import net.siegeln.cameleer.saas.vendor.VendorTenantService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.util.UUID;
/**
* Self-service onboarding: lets a newly registered user create their own trial tenant.
* Reuses VendorTenantService for the heavy lifting (Logto org, license, Docker provisioning)
* but adds the calling user as the tenant owner instead of creating a new admin user.
*/
@Service
public class OnboardingService {
private static final Logger log = LoggerFactory.getLogger(OnboardingService.class);
private final VendorTenantService vendorTenantService;
private final LogtoManagementClient logtoClient;
private final AccountService accountService;
public OnboardingService(VendorTenantService vendorTenantService,
LogtoManagementClient logtoClient,
AccountService accountService) {
this.vendorTenantService = vendorTenantService;
this.logtoClient = logtoClient;
this.accountService = accountService;
}
public TenantEntity createTrialTenant(String name, String slug, String logtoUserId) {
// Guard: check if user already has a tenant (prevent abuse)
if (logtoClient.isAvailable()) {
var orgs = logtoClient.getUserOrganizations(logtoUserId);
if (!orgs.isEmpty()) {
throw new IllegalStateException("You already have a tenant. Only one trial tenant per account.");
}
}
// Create tenant via the existing vendor flow (no admin user — we'll add the caller)
UUID actorId = resolveActorId(logtoUserId);
var request = new CreateTenantRequest(name, slug, "STARTER", null, null);
TenantEntity tenant = vendorTenantService.createAndProvision(request, actorId);
// Add the calling user to the Logto org as owner
if (tenant.getLogtoOrgId() != null && logtoClient.isAvailable()) {
try {
String ownerRoleId = logtoClient.findOrgRoleIdByName("owner");
logtoClient.addUserToOrganization(tenant.getLogtoOrgId(), logtoUserId);
if (ownerRoleId != null) {
logtoClient.assignOrganizationRole(tenant.getLogtoOrgId(), logtoUserId, ownerRoleId);
}
log.info("Added user {} as owner of tenant {}", logtoUserId, slug);
// Set display name from email if not already set (email-registered users have no name)
var profile = accountService.getProfile(logtoUserId);
if (profile.name() == null || profile.name().isBlank()) {
String email = profile.email();
if (!email.isBlank() && email.contains("@")) {
String displayName = email.substring(0, email.indexOf('@'));
accountService.updateDisplayName(logtoUserId, displayName);
log.info("Set display name '{}' for user {}", displayName, logtoUserId);
}
}
} catch (Exception e) {
log.warn("Failed to add user {} to org for tenant {}: {}", logtoUserId, slug, e.getMessage());
}
}
return tenant;
}
private UUID resolveActorId(String subject) {
try {
return UUID.fromString(subject);
} catch (IllegalArgumentException e) {
return UUID.nameUUIDFromBytes(subject.getBytes());
}
}
}