feat: criar workspace padrao no cadastro de usuario e impedir organizacoes com nomes duplicados
This commit is contained in:
parent
a603b80a4c
commit
b1441285a1
9 changed files with 83 additions and 7 deletions
|
|
@ -9,7 +9,9 @@ import net.beetechgroup.beetask.usecase.user.create.CreateUserUseCase;
|
||||||
import net.beetechgroup.beetask.usecase.user.profile.GetUserProfileUseCase;
|
import net.beetechgroup.beetask.usecase.user.profile.GetUserProfileUseCase;
|
||||||
import net.beetechgroup.beetask.usecase.user.update.UpdateUserProfileUseCase;
|
import net.beetechgroup.beetask.usecase.user.update.UpdateUserProfileUseCase;
|
||||||
import net.beetechgroup.beetask.usecase.user.uploadphoto.UploadUserPhotoUseCase;
|
import net.beetechgroup.beetask.usecase.user.uploadphoto.UploadUserPhotoUseCase;
|
||||||
|
import net.beetechgroup.beetask.usecase.repository.OrganizationRepository;
|
||||||
import net.beetechgroup.beetask.usecase.repository.StorageRepository;
|
import net.beetechgroup.beetask.usecase.repository.StorageRepository;
|
||||||
|
import net.beetechgroup.beetask.usecase.repository.UserOrganizationRepository;
|
||||||
import net.beetechgroup.beetask.usecase.repository.UserRepository;
|
import net.beetechgroup.beetask.usecase.repository.UserRepository;
|
||||||
import io.smallrye.jwt.auth.principal.JWTParser;
|
import io.smallrye.jwt.auth.principal.JWTParser;
|
||||||
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||||
|
|
@ -25,8 +27,10 @@ public class UserUseCaseConfig {
|
||||||
String issuer;
|
String issuer;
|
||||||
|
|
||||||
@Produces
|
@Produces
|
||||||
public CreateUserUseCase createUserUseCase(UserRepository userRepository) {
|
public CreateUserUseCase createUserUseCase(UserRepository userRepository,
|
||||||
return new CreateUserUseCase(userRepository);
|
OrganizationRepository organizationRepository,
|
||||||
|
UserOrganizationRepository userOrganizationRepository) {
|
||||||
|
return new CreateUserUseCase(userRepository, organizationRepository, userOrganizationRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Produces
|
@Produces
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,16 @@ public class OrganizationRepositoryImpl implements OrganizationRepository, Panac
|
||||||
return organization;
|
return organization;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Organization> findByNameIgnoreCase(String name) {
|
||||||
|
if (Objects.isNull(name) || name.isBlank()) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
return find("lower(trim(name)) = ?1", name.trim().toLowerCase())
|
||||||
|
.firstResultOptional()
|
||||||
|
.map(OrganizationEntityMapper::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long countAll() {
|
public long countAll() {
|
||||||
return count();
|
return count();
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||||
|
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
import net.beetechgroup.beetask.usecase.auth.login.LoginInput;
|
import net.beetechgroup.beetask.usecase.auth.login.LoginInput;
|
||||||
import net.beetechgroup.beetask.usecase.auth.login.LoginOutput;
|
import net.beetechgroup.beetask.usecase.auth.login.LoginOutput;
|
||||||
import net.beetechgroup.beetask.usecase.auth.login.LoginUseCase;
|
import net.beetechgroup.beetask.usecase.auth.login.LoginUseCase;
|
||||||
|
|
@ -129,6 +130,7 @@ public class AuthController {
|
||||||
|
|
||||||
@Path("/register")
|
@Path("/register")
|
||||||
@POST
|
@POST
|
||||||
|
@Transactional
|
||||||
@Operation(summary = "Register", description = "Creates a new user account")
|
@Operation(summary = "Register", description = "Creates a new user account")
|
||||||
public CreateAccountResponse register(CreateAccountRequest request) {
|
public CreateAccountResponse register(CreateAccountRequest request) {
|
||||||
LOGGER.infof("Registration requested for email %s", request.email());
|
LOGGER.infof("Registration requested for email %s", request.email());
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import io.quarkus.security.Authenticated;
|
||||||
import io.quarkus.security.identity.SecurityIdentity;
|
import io.quarkus.security.identity.SecurityIdentity;
|
||||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
import jakarta.ws.rs.GET;
|
import jakarta.ws.rs.GET;
|
||||||
import jakarta.ws.rs.POST;
|
import jakarta.ws.rs.POST;
|
||||||
import jakarta.ws.rs.PATCH;
|
import jakarta.ws.rs.PATCH;
|
||||||
|
|
@ -63,6 +64,7 @@ public class OrganizationController {
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Authenticated
|
@Authenticated
|
||||||
|
@Transactional
|
||||||
@Operation(summary = "Create organization", description = "Creates a new organization")
|
@Operation(summary = "Create organization", description = "Creates a new organization")
|
||||||
public CreateOrganizationResponse createOrganization(CreateOrganizationRequest request) {
|
public CreateOrganizationResponse createOrganization(CreateOrganizationRequest request) {
|
||||||
String userEmail = securityIdentity.getPrincipal().getName();
|
String userEmail = securityIdentity.getPrincipal().getName();
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,17 @@ public class CreateOrganizationUseCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public CreateOrganizationOutput execute(CreateOrganizationInput input) {
|
public CreateOrganizationOutput execute(CreateOrganizationInput input) {
|
||||||
|
String trimmedName = input.name() != null ? input.name().trim() : "";
|
||||||
|
if (trimmedName.isBlank()) {
|
||||||
|
LOGGER.warnf("Organization creation failed because name is blank");
|
||||||
|
throw new IllegalArgumentException("O nome da organização é obrigatório.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (organizationRepository.findByNameIgnoreCase(trimmedName).isPresent()) {
|
||||||
|
LOGGER.warnf("Organization creation rejected because name '%s' already exists", trimmedName);
|
||||||
|
throw new IllegalStateException("Já existe uma organização com o nome '" + trimmedName + "'.");
|
||||||
|
}
|
||||||
|
|
||||||
Optional<User> userOptional = userRepository.findByEmail(input.userEmail());
|
Optional<User> userOptional = userRepository.findByEmail(input.userEmail());
|
||||||
if (userOptional.isEmpty()) {
|
if (userOptional.isEmpty()) {
|
||||||
LOGGER.warnf("Organization creation failed because user %s was not found", input.userEmail());
|
LOGGER.warnf("Organization creation failed because user %s was not found", input.userEmail());
|
||||||
|
|
@ -34,9 +45,9 @@ public class CreateOrganizationUseCase {
|
||||||
}
|
}
|
||||||
User user = userOptional.get();
|
User user = userOptional.get();
|
||||||
|
|
||||||
LOGGER.infof("Creating organization '%s' for owner %s", input.name(), input.userEmail());
|
LOGGER.infof("Creating organization '%s' for owner %s", trimmedName, input.userEmail());
|
||||||
Organization organization = new Organization();
|
Organization organization = new Organization();
|
||||||
organization.setName(input.name());
|
organization.setName(trimmedName);
|
||||||
organization = organizationRepository.saveOrganization(organization);
|
organization = organizationRepository.saveOrganization(organization);
|
||||||
|
|
||||||
UserOrganization userOrganization = new UserOrganization();
|
UserOrganization userOrganization = new UserOrganization();
|
||||||
|
|
|
||||||
|
|
@ -9,5 +9,6 @@ public interface OrganizationRepository {
|
||||||
Organization saveOrganization(Organization organization);
|
Organization saveOrganization(Organization organization);
|
||||||
List<Organization> search(String query);
|
List<Organization> search(String query);
|
||||||
Optional<Organization> findOrganizationById(Long id);
|
Optional<Organization> findOrganizationById(Long id);
|
||||||
|
Optional<Organization> findByNameIgnoreCase(String name);
|
||||||
long countAll();
|
long countAll();
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,12 @@
|
||||||
package net.beetechgroup.beetask.usecase.user.create;
|
package net.beetechgroup.beetask.usecase.user.create;
|
||||||
|
|
||||||
import net.beetechgroup.beetask.entities.User;
|
import net.beetechgroup.beetask.entities.User;
|
||||||
|
import net.beetechgroup.beetask.entities.organization.Organization;
|
||||||
|
import net.beetechgroup.beetask.entities.organization.UserOrganization;
|
||||||
|
import net.beetechgroup.beetask.entities.organization.UserOrganizationRole;
|
||||||
|
import net.beetechgroup.beetask.entities.organization.UserOrganizationStatus;
|
||||||
|
import net.beetechgroup.beetask.usecase.repository.OrganizationRepository;
|
||||||
|
import net.beetechgroup.beetask.usecase.repository.UserOrganizationRepository;
|
||||||
import net.beetechgroup.beetask.usecase.repository.UserRepository;
|
import net.beetechgroup.beetask.usecase.repository.UserRepository;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
|
|
@ -8,9 +14,15 @@ public class CreateUserUseCase {
|
||||||
private static final Logger LOGGER = Logger.getLogger(CreateUserUseCase.class);
|
private static final Logger LOGGER = Logger.getLogger(CreateUserUseCase.class);
|
||||||
|
|
||||||
private final UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
|
private final OrganizationRepository organizationRepository;
|
||||||
|
private final UserOrganizationRepository userOrganizationRepository;
|
||||||
|
|
||||||
public CreateUserUseCase(UserRepository userRepository) {
|
public CreateUserUseCase(UserRepository userRepository,
|
||||||
|
OrganizationRepository organizationRepository,
|
||||||
|
UserOrganizationRepository userOrganizationRepository) {
|
||||||
this.userRepository = userRepository;
|
this.userRepository = userRepository;
|
||||||
|
this.organizationRepository = organizationRepository;
|
||||||
|
this.userOrganizationRepository = userOrganizationRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CreateUserOutput execute(CreateUserInput input) {
|
public CreateUserOutput execute(CreateUserInput input) {
|
||||||
|
|
@ -27,6 +39,33 @@ public class CreateUserUseCase {
|
||||||
User savedUser = userRepository.save(user);
|
User savedUser = userRepository.save(user);
|
||||||
LOGGER.infof("User %d created successfully with email %s", savedUser.getId(), savedUser.getEmail());
|
LOGGER.infof("User %d created successfully with email %s", savedUser.getId(), savedUser.getEmail());
|
||||||
|
|
||||||
|
createDefaultWorkspaceForUser(savedUser);
|
||||||
|
|
||||||
return CreateUserMapper.toCreateUserOutput(savedUser);
|
return CreateUserMapper.toCreateUserOutput(savedUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void createDefaultWorkspaceForUser(User user) {
|
||||||
|
String userName = user.getName() != null && !user.getName().isBlank() ? user.getName().trim() : "Meu";
|
||||||
|
String baseWorkspaceName = "Workspace de " + userName;
|
||||||
|
String uniqueWorkspaceName = baseWorkspaceName;
|
||||||
|
int counter = 2;
|
||||||
|
|
||||||
|
while (organizationRepository.findByNameIgnoreCase(uniqueWorkspaceName).isPresent()) {
|
||||||
|
uniqueWorkspaceName = baseWorkspaceName + " (" + counter + ")";
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.infof("Creating initial workspace '%s' for newly registered user %d (%s)", uniqueWorkspaceName, user.getId(), user.getEmail());
|
||||||
|
Organization organization = new Organization();
|
||||||
|
organization.setName(uniqueWorkspaceName);
|
||||||
|
organization = organizationRepository.saveOrganization(organization);
|
||||||
|
|
||||||
|
UserOrganization userOrganization = new UserOrganization();
|
||||||
|
userOrganization.setUser(user);
|
||||||
|
userOrganization.setOrganization(organization);
|
||||||
|
userOrganization.setRole(UserOrganizationRole.OWNER);
|
||||||
|
userOrganization.setStatus(UserOrganizationStatus.ACTIVE);
|
||||||
|
userOrganizationRepository.save(userOrganization);
|
||||||
|
LOGGER.infof("Initial workspace %d ('%s') successfully linked to user %d", organization.getId(), uniqueWorkspaceName, user.getId());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
-- Add unique index for organization name (case-insensitive and trimmed), ignoring soft-deleted records
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS uk_organizations_name
|
||||||
|
ON organizations (lower(trim(name)))
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
|
|
@ -53,8 +53,11 @@ export async function apiFetch<T>(path: string, options: RequestOptions = {}): P
|
||||||
let errorMessage = text || 'Não foi possível concluir a requisição.'
|
let errorMessage = text || 'Não foi possível concluir a requisição.'
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(text)
|
const parsed = JSON.parse(text)
|
||||||
if (parsed.message) errorMessage = parsed.message
|
if (parsed.message) {
|
||||||
if (parsed.error) errorMessage = parsed.error
|
errorMessage = parsed.message
|
||||||
|
} else if (parsed.error) {
|
||||||
|
errorMessage = parsed.error
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Usar texto puro
|
// Usar texto puro
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue