feat: validate slug uniqueness during onboarding
Add GET /api/onboarding/slug-available endpoint to check if a slug is already taken. Frontend checks availability with 400ms debounce as the user types and shows inline feedback. Submit button disabled when slug is taken. POST /api/onboarding/tenant now returns 409 instead of 500 for duplicate slugs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,15 +1,20 @@
|
||||
package net.siegeln.cameleer.saas.onboarding;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import java.util.Map;
|
||||
import net.siegeln.cameleer.saas.tenant.TenantEntity;
|
||||
import net.siegeln.cameleer.saas.tenant.dto.TenantResponse;
|
||||
import net.siegeln.cameleer.saas.tenant.TenantRepository;
|
||||
import net.siegeln.cameleer.saas.tenant.TenantStatus;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@@ -17,9 +22,11 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
public class OnboardingController {
|
||||
|
||||
private final OnboardingService onboardingService;
|
||||
private final TenantRepository tenantRepository;
|
||||
|
||||
public OnboardingController(OnboardingService onboardingService) {
|
||||
public OnboardingController(OnboardingService onboardingService, TenantRepository tenantRepository) {
|
||||
this.onboardingService = onboardingService;
|
||||
this.tenantRepository = tenantRepository;
|
||||
}
|
||||
|
||||
public record CreateTrialTenantRequest(
|
||||
@@ -35,13 +42,23 @@ public class OnboardingController {
|
||||
String slug
|
||||
) {}
|
||||
|
||||
@GetMapping("/slug-available")
|
||||
public ResponseEntity<Map<String, Boolean>> slugAvailable(@RequestParam String slug) {
|
||||
boolean taken = tenantRepository.existsBySlugAndStatusNot(slug, TenantStatus.DELETED);
|
||||
return ResponseEntity.ok(Map.of("available", !taken));
|
||||
}
|
||||
|
||||
@PostMapping("/tenant")
|
||||
public ResponseEntity<TenantResponse> createTrialTenant(
|
||||
@Valid @RequestBody CreateTrialTenantRequest request,
|
||||
@AuthenticationPrincipal Jwt jwt) {
|
||||
|
||||
String userId = jwt.getSubject();
|
||||
TenantEntity tenant = onboardingService.createTrialTenant(request.name(), request.slug(), userId);
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(TenantResponse.from(tenant));
|
||||
try {
|
||||
TenantEntity tenant = onboardingService.createTrialTenant(request.name(), request.slug(), userId);
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(TenantResponse.from(tenant));
|
||||
} catch (IllegalArgumentException e) {
|
||||
return ResponseEntity.status(HttpStatus.CONFLICT).build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user