criando orgnizacao e associando ao usuario logado e o /me

This commit is contained in:
Gabriel Santana de Menezes 2026-05-02 23:07:28 -04:00
parent 4a2f4371e0
commit 92f00ab0a1
16 changed files with 172 additions and 18 deletions

View file

@ -4,12 +4,16 @@ import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Produces; import jakarta.enterprise.inject.Produces;
import net.beetechgroup.beetask.usecase.organization.create.CreateOrganizationUseCase; import net.beetechgroup.beetask.usecase.organization.create.CreateOrganizationUseCase;
import net.beetechgroup.beetask.usecase.repository.OrganizationRepository; import net.beetechgroup.beetask.usecase.repository.OrganizationRepository;
import net.beetechgroup.beetask.usecase.repository.UserOrganizationRepository;
import net.beetechgroup.beetask.usecase.repository.UserRepository;
@ApplicationScoped @ApplicationScoped
public class OrganizationUseCaseConfig { public class OrganizationUseCaseConfig {
@Produces @Produces
public CreateOrganizationUseCase createOrganizationUseCase(OrganizationRepository organizationRepository) { public CreateOrganizationUseCase createOrganizationUseCase(OrganizationRepository organizationRepository,
return new CreateOrganizationUseCase(organizationRepository); UserRepository userRepository,
UserOrganizationRepository userOrganizationRepository) {
return new CreateOrganizationUseCase(organizationRepository, userRepository, userOrganizationRepository);
} }
} }

View file

@ -6,8 +6,10 @@ import jakarta.inject.Inject;
import net.beetechgroup.beetask.usecase.auth.login.LoginUseCase; import net.beetechgroup.beetask.usecase.auth.login.LoginUseCase;
import net.beetechgroup.beetask.usecase.auth.refresh.RefreshTokenUseCase; import net.beetechgroup.beetask.usecase.auth.refresh.RefreshTokenUseCase;
import net.beetechgroup.beetask.usecase.user.create.CreateUserUseCase; import net.beetechgroup.beetask.usecase.user.create.CreateUserUseCase;
import net.beetechgroup.beetask.usecase.user.profile.GetUserProfileUseCase;
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;
@ApplicationScoped @ApplicationScoped
public class UserUseCaseConfig { public class UserUseCaseConfig {
@ -15,6 +17,10 @@ public class UserUseCaseConfig {
@Inject @Inject
JWTParser jwtParser; JWTParser jwtParser;
@Inject
@ConfigProperty(name = "mp.jwt.verify.issuer")
String issuer;
@Produces @Produces
public CreateUserUseCase createUserUseCase(UserRepository userRepository) { public CreateUserUseCase createUserUseCase(UserRepository userRepository) {
return new CreateUserUseCase(userRepository); return new CreateUserUseCase(userRepository);
@ -22,11 +28,16 @@ public class UserUseCaseConfig {
@Produces @Produces
public LoginUseCase loginUseCase(UserRepository userRepository) { public LoginUseCase loginUseCase(UserRepository userRepository) {
return new LoginUseCase(userRepository); return new LoginUseCase(userRepository, issuer);
} }
@Produces @Produces
public RefreshTokenUseCase refreshTokenUseCase(UserRepository userRepository) { public RefreshTokenUseCase refreshTokenUseCase(UserRepository userRepository) {
return new RefreshTokenUseCase(userRepository, jwtParser); return new RefreshTokenUseCase(userRepository, jwtParser, issuer);
}
@Produces
public GetUserProfileUseCase getUserProfileUseCase(UserRepository userRepository) {
return new GetUserProfileUseCase(userRepository);
} }
} }

View file

@ -13,4 +13,13 @@ public class UserOrganizationEntityMapper {
domain.setRole(entity.getRole()); domain.setRole(entity.getRole());
return domain; return domain;
} }
public static UserOrganizationEntity toEntity(UserOrganization domain) {
if (domain == null) return null;
UserOrganizationEntity entity = new UserOrganizationEntity();
entity.setUser(UserEntityMapper.toEntity(domain.getUser()));
entity.setOrganization(OrganizationEntityMapper.toEntity(domain.getOrganization()));
entity.setRole(domain.getRole());
return entity;
}
} }

View file

@ -2,6 +2,7 @@ package net.beetechgroup.beetask.frameworks.persistence.repository;
import io.quarkus.hibernate.orm.panache.PanacheRepository; import io.quarkus.hibernate.orm.panache.PanacheRepository;
import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.ApplicationScoped;
import jakarta.transaction.Transactional;
import net.beetechgroup.beetask.entities.organization.UserOrganization; import net.beetechgroup.beetask.entities.organization.UserOrganization;
import net.beetechgroup.beetask.frameworks.persistence.entities.UserOrganizationEntity; import net.beetechgroup.beetask.frameworks.persistence.entities.UserOrganizationEntity;
import net.beetechgroup.beetask.frameworks.persistence.mapper.UserOrganizationEntityMapper; import net.beetechgroup.beetask.frameworks.persistence.mapper.UserOrganizationEntityMapper;
@ -18,4 +19,11 @@ public class UserOrganizationRepositoryImpl implements UserOrganizationRepositor
.map(UserOrganizationEntityMapper::toDomain) .map(UserOrganizationEntityMapper::toDomain)
.toList(); .toList();
} }
@Override
@Transactional
public void save(UserOrganization userOrganization) {
UserOrganizationEntity entity = UserOrganizationEntityMapper.toEntity(userOrganization);
persist(entity);
}
} }

View file

@ -22,6 +22,12 @@ import net.beetechgroup.beetask.usecase.user.create.CreateUserUseCase;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import io.quarkus.security.Authenticated;
import io.quarkus.security.identity.SecurityIdentity;
import jakarta.ws.rs.GET;
import net.beetechgroup.beetask.usecase.user.profile.GetUserProfileUseCase;
import net.beetechgroup.beetask.usecase.user.profile.UserProfileOutput;
@Path("/auth") @Path("/auth")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
@ -37,6 +43,21 @@ public class AuthController {
@Inject @Inject
RefreshTokenUseCase refreshTokenUseCase; RefreshTokenUseCase refreshTokenUseCase;
@Inject
GetUserProfileUseCase getUserProfileUseCase;
@Inject
SecurityIdentity securityIdentity;
@Path("/me")
@GET
@Authenticated
@Operation(summary = "Get current user profile", description = "Returns details of the logged in user")
public UserProfileOutput me() {
String email = securityIdentity.getPrincipal().getName();
return getUserProfileUseCase.execute(email);
}
@Path("/register") @Path("/register")
@POST @POST
@Operation(summary = "Register", description = "Creates a new user account") @Operation(summary = "Register", description = "Creates a new user account")

View file

@ -1,5 +1,7 @@
package net.beetechgroup.beetask.interfaceadapters.controllers.organization; package net.beetechgroup.beetask.interfaceadapters.controllers.organization;
import io.quarkus.security.Authenticated;
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.ws.rs.POST; import jakarta.ws.rs.POST;
@ -14,11 +16,15 @@ public class OrganizationController {
@Inject @Inject
CreateOrganizationUseCase createOrganizationUseCase; CreateOrganizationUseCase createOrganizationUseCase;
@Inject
SecurityIdentity securityIdentity;
@POST @POST
@Authenticated
@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) {
CreateOrganizationInput input = OrganizationControllerMapper.toCreateOrganizationInput(request); String userEmail = securityIdentity.getPrincipal().getName();
CreateOrganizationInput input = OrganizationControllerMapper.toCreateOrganizationInput(request, userEmail);
CreateOrganizationOutput output = createOrganizationUseCase.execute(input); CreateOrganizationOutput output = createOrganizationUseCase.execute(input);
return OrganizationControllerMapper.toCreateOrganizationResponse(output); return OrganizationControllerMapper.toCreateOrganizationResponse(output);
} }

View file

@ -4,8 +4,8 @@ import net.beetechgroup.beetask.usecase.organization.create.CreateOrganizationIn
import net.beetechgroup.beetask.usecase.organization.create.CreateOrganizationOutput; import net.beetechgroup.beetask.usecase.organization.create.CreateOrganizationOutput;
public class OrganizationControllerMapper { public class OrganizationControllerMapper {
public static CreateOrganizationInput toCreateOrganizationInput(CreateOrganizationRequest request) { public static CreateOrganizationInput toCreateOrganizationInput(CreateOrganizationRequest request, String userEmail) {
return new CreateOrganizationInput(request.name()); return new CreateOrganizationInput(request.name(), userEmail);
} }
public static CreateOrganizationResponse toCreateOrganizationResponse(CreateOrganizationOutput output) { public static CreateOrganizationResponse toCreateOrganizationResponse(CreateOrganizationOutput output) {

View file

@ -17,7 +17,6 @@ import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.tags.Tag; import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import net.beetechgroup.beetask.entities.task.Task;
import net.beetechgroup.beetask.entities.task.TaskStatus; import net.beetechgroup.beetask.entities.task.TaskStatus;
import net.beetechgroup.beetask.usecase.task.create.CreateTaskInput; import net.beetechgroup.beetask.usecase.task.create.CreateTaskInput;
import net.beetechgroup.beetask.usecase.task.create.CreateTaskOutput; import net.beetechgroup.beetask.usecase.task.create.CreateTaskOutput;

View file

@ -15,9 +15,11 @@ import java.util.stream.Collectors;
public class LoginUseCase { public class LoginUseCase {
private final UserRepository userRepository; private final UserRepository userRepository;
private final String issuer;
public LoginUseCase(UserRepository userRepository) { public LoginUseCase(UserRepository userRepository, String issuer) {
this.userRepository = userRepository; this.userRepository = userRepository;
this.issuer = issuer;
} }
public LoginOutput execute(LoginInput input) { public LoginOutput execute(LoginInput input) {
@ -39,7 +41,7 @@ public class LoginUseCase {
Set<String> groups = new HashSet<>(); Set<String> groups = new HashSet<>();
userOrganizations.forEach(uo -> groups.add(uo.getRole().name())); userOrganizations.forEach(uo -> groups.add(uo.getRole().name()));
String token = Jwt.issuer("https://beetech.net") String token = Jwt.issuer(this.issuer)
.upn(user.getEmail()) .upn(user.getEmail())
.groups(groups) .groups(groups)
.claim("name", user.getName()) .claim("name", user.getName())
@ -55,7 +57,7 @@ public class LoginUseCase {
)) ))
.collect(Collectors.toList()); .collect(Collectors.toList());
String refreshToken = Jwt.issuer("https://beetech.net") String refreshToken = Jwt.issuer(this.issuer)
.upn(user.getEmail()) .upn(user.getEmail())
.expiresIn(86400) // 24 hours .expiresIn(86400) // 24 hours
.sign(); .sign();

View file

@ -20,10 +20,12 @@ public class RefreshTokenUseCase {
private final UserRepository userRepository; private final UserRepository userRepository;
private final JWTParser jwtParser; private final JWTParser jwtParser;
private final String issuer;
public RefreshTokenUseCase(UserRepository userRepository, JWTParser jwtParser) { public RefreshTokenUseCase(UserRepository userRepository, JWTParser jwtParser, String issuer) {
this.userRepository = userRepository; this.userRepository = userRepository;
this.jwtParser = jwtParser; this.jwtParser = jwtParser;
this.issuer = issuer;
} }
public LoginOutput execute(RefreshTokenInput input) { public LoginOutput execute(RefreshTokenInput input) {
@ -46,7 +48,7 @@ public class RefreshTokenUseCase {
Set<String> groups = new HashSet<>(); Set<String> groups = new HashSet<>();
userOrganizations.forEach(uo -> groups.add(uo.getRole().name())); userOrganizations.forEach(uo -> groups.add(uo.getRole().name()));
String newToken = Jwt.issuer("https://beetech.net") String newToken = Jwt.issuer(this.issuer)
.upn(user.getEmail()) .upn(user.getEmail())
.groups(groups) .groups(groups)
.claim("name", user.getName()) .claim("name", user.getName())
@ -54,7 +56,7 @@ public class RefreshTokenUseCase {
.sign(); .sign();
// Generate a new refresh token (also a JWT but with longer expiration) // Generate a new refresh token (also a JWT but with longer expiration)
String newRefreshToken = Jwt.issuer("https://beetech.net") String newRefreshToken = Jwt.issuer(this.issuer)
.upn(user.getEmail()) .upn(user.getEmail())
.expiresIn(86400) // 24 hours .expiresIn(86400) // 24 hours
.sign(); .sign();

View file

@ -1,4 +1,4 @@
package net.beetechgroup.beetask.usecase.organization.create; package net.beetechgroup.beetask.usecase.organization.create;
public record CreateOrganizationInput(String name) { public record CreateOrganizationInput(String name, String userEmail) {
} }

View file

@ -1,19 +1,45 @@
package net.beetechgroup.beetask.usecase.organization.create; package net.beetechgroup.beetask.usecase.organization.create;
import net.beetechgroup.beetask.entities.User;
import net.beetechgroup.beetask.entities.organization.Organization; 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.usecase.repository.OrganizationRepository; import net.beetechgroup.beetask.usecase.repository.OrganizationRepository;
import net.beetechgroup.beetask.usecase.repository.UserOrganizationRepository;
import net.beetechgroup.beetask.usecase.repository.UserRepository;
import java.util.Optional;
public class CreateOrganizationUseCase { public class CreateOrganizationUseCase {
private final OrganizationRepository organizationRepository; private final OrganizationRepository organizationRepository;
private final UserRepository userRepository;
private final UserOrganizationRepository userOrganizationRepository;
public CreateOrganizationUseCase(OrganizationRepository organizationRepository) { public CreateOrganizationUseCase(OrganizationRepository organizationRepository,
UserRepository userRepository,
UserOrganizationRepository userOrganizationRepository) {
this.organizationRepository = organizationRepository; this.organizationRepository = organizationRepository;
this.userRepository = userRepository;
this.userOrganizationRepository = userOrganizationRepository;
} }
public CreateOrganizationOutput execute(CreateOrganizationInput input) { public CreateOrganizationOutput execute(CreateOrganizationInput input) {
Optional<User> userOptional = userRepository.findByEmail(input.userEmail());
if (userOptional.isEmpty()) {
throw new RuntimeException("User not found: " + input.userEmail());
}
User user = userOptional.get();
Organization organization = new Organization(); Organization organization = new Organization();
organization.setName(input.name()); organization.setName(input.name());
organizationRepository.saveOrganization(organization); organization = organizationRepository.saveOrganization(organization);
UserOrganization userOrganization = new UserOrganization();
userOrganization.setUser(user);
userOrganization.setOrganization(organization);
userOrganization.setRole(UserOrganizationRole.OWNER);
userOrganizationRepository.save(userOrganization);
return CreateOrganizationMapper.toCreateOrganizationOutput(organization); return CreateOrganizationMapper.toCreateOrganizationOutput(organization);
} }
} }

View file

@ -5,4 +5,5 @@ import java.util.List;
public interface UserOrganizationRepository { public interface UserOrganizationRepository {
List<UserOrganization> findByUserId(Long userId); List<UserOrganization> findByUserId(Long userId);
void save(UserOrganization userOrganization);
} }

View file

@ -0,0 +1,45 @@
package net.beetechgroup.beetask.usecase.user.profile;
import net.beetechgroup.beetask.entities.User;
import net.beetechgroup.beetask.entities.organization.UserOrganization;
import net.beetechgroup.beetask.usecase.repository.UserRepository;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class GetUserProfileUseCase {
private final UserRepository userRepository;
public GetUserProfileUseCase(UserRepository userRepository) {
this.userRepository = userRepository;
}
public UserProfileOutput execute(String email) {
Optional<User> userOptional = userRepository.findByEmail(email);
if (userOptional.isEmpty()) {
throw new RuntimeException("User not found");
}
User user = userOptional.get();
List<UserOrganization> userOrganizations = userRepository.findUserOrganizations(user.getId());
List<UserProfileOutput.OrganizationProfileOutput> orgs = userOrganizations.stream()
.collect(Collectors.groupingBy(uo -> uo.getOrganization().getName()))
.entrySet().stream()
.map(entry -> new UserProfileOutput.OrganizationProfileOutput(
entry.getKey(),
entry.getValue().stream().map(uo -> uo.getRole().name()).collect(Collectors.toList())
))
.collect(Collectors.toList());
return new UserProfileOutput(
user.getName(),
user.getEmail(),
user.getPhoto() != null ? user.getPhoto() : "https://ui-avatars.com/api/?name=" + user.getName().replace(" ", "+") + "&background=random",
orgs
);
}
}

View file

@ -0,0 +1,15 @@
package net.beetechgroup.beetask.usecase.user.profile;
import java.util.List;
public record UserProfileOutput(
String name,
String email,
String photo,
List<OrganizationProfileOutput> organizations
) {
public record OrganizationProfileOutput(
String name,
List<String> roles
) {}
}

View file

@ -18,3 +18,8 @@ quarkus.hibernate-orm.database.generation=validate
quarkus.smallrye-openapi.path=/openapi quarkus.smallrye-openapi.path=/openapi
quarkus.swagger-ui.path=/swagger quarkus.swagger-ui.path=/swagger
quarkus.swagger-ui.always-include=true quarkus.swagger-ui.always-include=true
# JWT Security
mp.jwt.verify.issuer=https://beetech.net
smallrye.jwt.sign.key.location=privateKey.pem
mp.jwt.verify.publickey.location=publicKey.pem