diff --git a/backend/pom.xml b/backend/pom.xml
index 22dbe26..d7ba451 100644
--- a/backend/pom.xml
+++ b/backend/pom.xml
@@ -39,6 +39,14 @@
io.quarkus
quarkus-rest-jackson
+
+ io.quarkus
+ quarkus-smallrye-jwt
+
+
+ io.quarkus
+ quarkus-smallrye-jwt-build
+
io.quarkus
quarkus-smallrye-openapi
diff --git a/backend/src/main/java/net/beetechgroup/beetask/entities/User.java b/backend/src/main/java/net/beetechgroup/beetask/entities/User.java
index 06bfd16..bafa69b 100644
--- a/backend/src/main/java/net/beetechgroup/beetask/entities/User.java
+++ b/backend/src/main/java/net/beetechgroup/beetask/entities/User.java
@@ -4,6 +4,8 @@ public class User {
private Long id;
private String name;
private String email;
+ private String password;
+ private String photo;
public Long getId() {
return id;
@@ -28,4 +30,20 @@ public class User {
public void setEmail(String email) {
this.email = email;
}
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public String getPhoto() {
+ return photo;
+ }
+
+ public void setPhoto(String photo) {
+ this.photo = photo;
+ }
}
diff --git a/backend/src/main/java/net/beetechgroup/beetask/entities/organization/UserOrganization.java b/backend/src/main/java/net/beetechgroup/beetask/entities/organization/UserOrganization.java
index 8e90f7f..6120b60 100644
--- a/backend/src/main/java/net/beetechgroup/beetask/entities/organization/UserOrganization.java
+++ b/backend/src/main/java/net/beetechgroup/beetask/entities/organization/UserOrganization.java
@@ -6,4 +6,28 @@ public class UserOrganization {
private User user;
private Organization organization;
private UserOrganizationRole role;
+
+ public User getUser() {
+ return user;
+ }
+
+ public void setUser(User user) {
+ this.user = user;
+ }
+
+ public Organization getOrganization() {
+ return organization;
+ }
+
+ public void setOrganization(Organization organization) {
+ this.organization = organization;
+ }
+
+ public UserOrganizationRole getRole() {
+ return role;
+ }
+
+ public void setRole(UserOrganizationRole role) {
+ this.role = role;
+ }
}
diff --git a/backend/src/main/java/net/beetechgroup/beetask/frameworks/config/UserUseCaseConfig.java b/backend/src/main/java/net/beetechgroup/beetask/frameworks/config/UserUseCaseConfig.java
new file mode 100644
index 0000000..8da2010
--- /dev/null
+++ b/backend/src/main/java/net/beetechgroup/beetask/frameworks/config/UserUseCaseConfig.java
@@ -0,0 +1,32 @@
+package net.beetechgroup.beetask.frameworks.config;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.inject.Produces;
+import jakarta.inject.Inject;
+import net.beetechgroup.beetask.usecase.auth.login.LoginUseCase;
+import net.beetechgroup.beetask.usecase.auth.refresh.RefreshTokenUseCase;
+import net.beetechgroup.beetask.usecase.user.create.CreateUserUseCase;
+import net.beetechgroup.beetask.usecase.repository.UserRepository;
+import io.smallrye.jwt.auth.principal.JWTParser;
+
+@ApplicationScoped
+public class UserUseCaseConfig {
+
+ @Inject
+ JWTParser jwtParser;
+
+ @Produces
+ public CreateUserUseCase createUserUseCase(UserRepository userRepository) {
+ return new CreateUserUseCase(userRepository);
+ }
+
+ @Produces
+ public LoginUseCase loginUseCase(UserRepository userRepository) {
+ return new LoginUseCase(userRepository);
+ }
+
+ @Produces
+ public RefreshTokenUseCase refreshTokenUseCase(UserRepository userRepository) {
+ return new RefreshTokenUseCase(userRepository, jwtParser);
+ }
+}
diff --git a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/entities/UserEntity.java b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/entities/UserEntity.java
index d1bd525..74e8f1a 100644
--- a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/entities/UserEntity.java
+++ b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/entities/UserEntity.java
@@ -14,6 +14,8 @@ public class UserEntity {
private Long id;
private String name;
private String email;
+ private String password;
+ private String photo;
public Long getId() {
return id;
@@ -38,4 +40,20 @@ public class UserEntity {
public void setEmail(String email) {
this.email = email;
}
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public String getPhoto() {
+ return photo;
+ }
+
+ public void setPhoto(String photo) {
+ this.photo = photo;
+ }
}
diff --git a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/mapper/UserEntityMapper.java b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/mapper/UserEntityMapper.java
new file mode 100644
index 0000000..b5c7192
--- /dev/null
+++ b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/mapper/UserEntityMapper.java
@@ -0,0 +1,29 @@
+package net.beetechgroup.beetask.frameworks.persistence.mapper;
+
+import net.beetechgroup.beetask.entities.User;
+import net.beetechgroup.beetask.frameworks.persistence.entities.UserEntity;
+
+public class UserEntityMapper {
+
+ public static UserEntity toEntity(User user) {
+ if (user == null) return null;
+ UserEntity entity = new UserEntity();
+ entity.setId(user.getId());
+ entity.setName(user.getName());
+ entity.setEmail(user.getEmail());
+ entity.setPassword(user.getPassword());
+ entity.setPhoto(user.getPhoto());
+ return entity;
+ }
+
+ public static User toDomain(UserEntity entity) {
+ if (entity == null) return null;
+ User user = new User();
+ user.setId(entity.getId());
+ user.setName(entity.getName());
+ user.setEmail(entity.getEmail());
+ user.setPassword(entity.getPassword());
+ user.setPhoto(entity.getPhoto());
+ return user;
+ }
+}
diff --git a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/mapper/UserOrganizationEntityMapper.java b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/mapper/UserOrganizationEntityMapper.java
new file mode 100644
index 0000000..ee38027
--- /dev/null
+++ b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/mapper/UserOrganizationEntityMapper.java
@@ -0,0 +1,16 @@
+package net.beetechgroup.beetask.frameworks.persistence.mapper;
+
+import net.beetechgroup.beetask.entities.organization.UserOrganization;
+import net.beetechgroup.beetask.frameworks.persistence.entities.UserOrganizationEntity;
+
+public class UserOrganizationEntityMapper {
+
+ public static UserOrganization toDomain(UserOrganizationEntity entity) {
+ if (entity == null) return null;
+ UserOrganization domain = new UserOrganization();
+ domain.setUser(UserEntityMapper.toDomain(entity.getUser()));
+ domain.setOrganization(OrganizationEntityMapper.toDomain(entity.getOrganization()));
+ domain.setRole(entity.getRole());
+ return domain;
+ }
+}
diff --git a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/UserOrganizationRepositoryImpl.java b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/UserOrganizationRepositoryImpl.java
new file mode 100644
index 0000000..4817814
--- /dev/null
+++ b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/UserOrganizationRepositoryImpl.java
@@ -0,0 +1,21 @@
+package net.beetechgroup.beetask.frameworks.persistence.repository;
+
+import io.quarkus.hibernate.orm.panache.PanacheRepository;
+import jakarta.enterprise.context.ApplicationScoped;
+import net.beetechgroup.beetask.entities.organization.UserOrganization;
+import net.beetechgroup.beetask.frameworks.persistence.entities.UserOrganizationEntity;
+import net.beetechgroup.beetask.frameworks.persistence.mapper.UserOrganizationEntityMapper;
+import net.beetechgroup.beetask.usecase.repository.UserOrganizationRepository;
+
+import java.util.List;
+
+@ApplicationScoped
+public class UserOrganizationRepositoryImpl implements UserOrganizationRepository, PanacheRepository {
+
+ @Override
+ public List findByUserId(Long userId) {
+ return find("user.id", userId).stream()
+ .map(UserOrganizationEntityMapper::toDomain)
+ .toList();
+ }
+}
diff --git a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/UserRepositoryImpl.java b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/UserRepositoryImpl.java
new file mode 100644
index 0000000..79fbb98
--- /dev/null
+++ b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/UserRepositoryImpl.java
@@ -0,0 +1,51 @@
+package net.beetechgroup.beetask.frameworks.persistence.repository;
+
+import io.quarkus.hibernate.orm.panache.PanacheRepository;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.transaction.Transactional;
+import net.beetechgroup.beetask.entities.User;
+import net.beetechgroup.beetask.frameworks.persistence.entities.UserEntity;
+import net.beetechgroup.beetask.frameworks.persistence.mapper.UserEntityMapper;
+import net.beetechgroup.beetask.entities.organization.UserOrganization;
+import net.beetechgroup.beetask.usecase.repository.UserRepository;
+import net.beetechgroup.beetask.usecase.repository.UserOrganizationRepository;
+
+import java.util.List;
+import java.util.Optional;
+
+@ApplicationScoped
+public class UserRepositoryImpl implements UserRepository, PanacheRepository {
+
+ @Inject
+ UserOrganizationRepository userOrganizationRepository;
+
+ @Override
+ @Transactional
+ public User save(User user) {
+ UserEntity entity = UserEntityMapper.toEntity(user);
+ if (entity.getId() == null) {
+ persist(entity);
+ } else {
+ entity = getEntityManager().merge(entity);
+ }
+ return UserEntityMapper.toDomain(entity);
+ }
+
+ @Override
+ public Optional findByEmail(String email) {
+ return find("email", email).firstResultOptional()
+ .map(UserEntityMapper::toDomain);
+ }
+
+ @Override
+ public Optional findUserById(Long id) {
+ return findByIdOptional(id)
+ .map(UserEntityMapper::toDomain);
+ }
+
+ @Override
+ public List findUserOrganizations(Long userId) {
+ return userOrganizationRepository.findByUserId(userId);
+ }
+}
diff --git a/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/auth/AuthController.java b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/auth/AuthController.java
index 35e68f8..4bd5885 100644
--- a/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/auth/AuthController.java
+++ b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/auth/AuthController.java
@@ -9,50 +9,58 @@ import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
+import jakarta.inject.Inject;
+import net.beetechgroup.beetask.usecase.auth.login.LoginInput;
+import net.beetechgroup.beetask.usecase.auth.login.LoginOutput;
+import net.beetechgroup.beetask.usecase.auth.login.LoginUseCase;
+import net.beetechgroup.beetask.usecase.auth.refresh.RefreshTokenInput;
+import net.beetechgroup.beetask.usecase.auth.refresh.RefreshTokenUseCase;
+import net.beetechgroup.beetask.usecase.user.create.CreateUserInput;
+import net.beetechgroup.beetask.usecase.user.create.CreateUserOutput;
+import net.beetechgroup.beetask.usecase.user.create.CreateUserUseCase;
+
import java.time.LocalDateTime;
import java.util.List;
@Path("/auth")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
-@Tag(name = "Authentication", description = "Authentication operations (Mocked)")
+@Tag(name = "Authentication", description = "Authentication operations")
public class AuthController {
+ @Inject
+ CreateUserUseCase createUserUseCase;
+
+ @Inject
+ LoginUseCase loginUseCase;
+
+ @Inject
+ RefreshTokenUseCase refreshTokenUseCase;
+
+ @Path("/register")
+ @POST
+ @Operation(summary = "Register", description = "Creates a new user account")
+ public CreateAccountResponse register(CreateAccountRequest request) {
+ CreateUserInput input = AuthControllerMapper.toCreateUserInput(request);
+ CreateUserOutput output = createUserUseCase.execute(input);
+ return AuthControllerMapper.toCreateAccountResponse(output);
+ }
+
@Path("/login")
@POST
- @Operation(summary = "Login", description = "Mocked login that returns user data")
- public LoginResponse login() {
- return new LoginResponse(
- "Gabriel Menezes",
- "gabriel@beetech.net",
- "https://ui-avatars.com/api/?name=Gabriel+Menezes&background=random",
- "mocked-jwt-token-abcd-1234",
- "mocked-refresh-token-wxyz-5678",
- 3600L, // 1 hour
- LocalDateTime.now().toString(),
- List.of(
- new LoginResponse.OrganizationDTO("BeeTech Group", List.of("ADMIN", "OWNER")),
- new LoginResponse.OrganizationDTO("SkyMob Dev", List.of("DEVELOPER"))
- )
- );
+ @Operation(summary = "Login", description = "Login that returns real user data and JWT")
+ public LoginResponse login(LoginRequest request) {
+ LoginInput input = AuthControllerMapper.toLoginInput(request);
+ LoginOutput output = loginUseCase.execute(input);
+ return AuthControllerMapper.toLoginResponse(output);
}
@Path("/refresh")
@POST
- @Operation(summary = "Refresh token", description = "Mocked token refresh")
- public LoginResponse refresh() {
- return new LoginResponse(
- "Gabriel Menezes",
- "gabriel@beetech.net",
- "https://ui-avatars.com/api/?name=Gabriel+Menezes&background=random",
- "refreshed-jwt-token-" + System.currentTimeMillis(),
- "refreshed-refresh-token-" + System.currentTimeMillis(),
- 3600L,
- LocalDateTime.now().toString(),
- List.of(
- new LoginResponse.OrganizationDTO("BeeTech Group", List.of("ADMIN", "OWNER")),
- new LoginResponse.OrganizationDTO("SkyMob Dev", List.of("DEVELOPER"))
- )
- );
+ @Operation(summary = "Refresh token", description = "Token refresh using refresh token")
+ public LoginResponse refresh(RefreshTokenRequest request) {
+ RefreshTokenInput input = AuthControllerMapper.toRefreshTokenInput(request);
+ LoginOutput output = refreshTokenUseCase.execute(input);
+ return AuthControllerMapper.toLoginResponse(output);
}
}
diff --git a/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/auth/AuthControllerMapper.java b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/auth/AuthControllerMapper.java
new file mode 100644
index 0000000..b9a705d
--- /dev/null
+++ b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/auth/AuthControllerMapper.java
@@ -0,0 +1,42 @@
+package net.beetechgroup.beetask.interfaceadapters.controllers.auth;
+
+import net.beetechgroup.beetask.usecase.auth.login.LoginInput;
+import net.beetechgroup.beetask.usecase.auth.login.LoginOutput;
+import net.beetechgroup.beetask.usecase.auth.refresh.RefreshTokenInput;
+import net.beetechgroup.beetask.usecase.user.create.CreateUserInput;
+import net.beetechgroup.beetask.usecase.user.create.CreateUserOutput;
+import java.util.stream.Collectors;
+
+public class AuthControllerMapper {
+
+ public static CreateUserInput toCreateUserInput(CreateAccountRequest request) {
+ return new CreateUserInput(request.name(), request.email(), request.password());
+ }
+
+ public static CreateAccountResponse toCreateAccountResponse(CreateUserOutput output) {
+ return new CreateAccountResponse(output.id(), output.name(), output.email());
+ }
+
+ public static LoginInput toLoginInput(LoginRequest request) {
+ return new LoginInput(request.email(), request.password());
+ }
+
+ public static RefreshTokenInput toRefreshTokenInput(RefreshTokenRequest request) {
+ return new RefreshTokenInput(request.refreshToken());
+ }
+
+ public static LoginResponse toLoginResponse(LoginOutput output) {
+ return new LoginResponse(
+ output.name(),
+ output.email(),
+ output.photo(),
+ output.jwt(),
+ output.refreshToken(),
+ output.expiresIn(),
+ output.issuedAt(),
+ output.organizations().stream()
+ .map(org -> new LoginResponse.Organization(org.name(), org.roles()))
+ .collect(Collectors.toList())
+ );
+ }
+}
diff --git a/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/auth/CreateAccountRequest.java b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/auth/CreateAccountRequest.java
new file mode 100644
index 0000000..37f1f7b
--- /dev/null
+++ b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/auth/CreateAccountRequest.java
@@ -0,0 +1,4 @@
+package net.beetechgroup.beetask.interfaceadapters.controllers.auth;
+
+public record CreateAccountRequest(String name, String email, String password) {
+}
diff --git a/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/auth/CreateAccountResponse.java b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/auth/CreateAccountResponse.java
new file mode 100644
index 0000000..af765f1
--- /dev/null
+++ b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/auth/CreateAccountResponse.java
@@ -0,0 +1,4 @@
+package net.beetechgroup.beetask.interfaceadapters.controllers.auth;
+
+public record CreateAccountResponse(Long id, String name, String email) {
+}
diff --git a/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/auth/LoginRequest.java b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/auth/LoginRequest.java
new file mode 100644
index 0000000..da8a8bb
--- /dev/null
+++ b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/auth/LoginRequest.java
@@ -0,0 +1,4 @@
+package net.beetechgroup.beetask.interfaceadapters.controllers.auth;
+
+public record LoginRequest(String email, String password) {
+}
diff --git a/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/auth/LoginResponse.java b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/auth/LoginResponse.java
index 2226c4b..67bc0f4 100644
--- a/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/auth/LoginResponse.java
+++ b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/auth/LoginResponse.java
@@ -10,9 +10,9 @@ public record LoginResponse(
String refreshToken,
Long expiresIn,
String issuedAt,
- List organizations
+ List organizations
) {
- public record OrganizationDTO(
+ public record Organization(
String name,
List roles
) {}
diff --git a/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/auth/RefreshTokenRequest.java b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/auth/RefreshTokenRequest.java
new file mode 100644
index 0000000..7f6c7af
--- /dev/null
+++ b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/auth/RefreshTokenRequest.java
@@ -0,0 +1,4 @@
+package net.beetechgroup.beetask.interfaceadapters.controllers.auth;
+
+public record RefreshTokenRequest(String refreshToken) {
+}
diff --git a/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/task/CreateTaskResponse.java b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/task/CreateTaskResponse.java
index 6777a5b..4d53dc4 100644
--- a/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/task/CreateTaskResponse.java
+++ b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/task/CreateTaskResponse.java
@@ -1,5 +1,6 @@
package net.beetechgroup.beetask.interfaceadapters.controllers.task;
+import java.time.LocalDateTime;
import java.util.List;
import net.beetechgroup.beetask.entities.task.TaskStatus;
@@ -11,4 +12,9 @@ public record CreateTaskResponse(
TaskStatus status,
String project,
List history) {
-}
\ No newline at end of file
+}
+
+record TaskHistoryItemResponse(
+ LocalDateTime startAt,
+ LocalDateTime endAt) {
+}
diff --git a/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/task/TaskHistoryItemResponse.java b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/task/TaskHistoryItemResponse.java
deleted file mode 100644
index dfca8f5..0000000
--- a/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/task/TaskHistoryItemResponse.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package net.beetechgroup.beetask.interfaceadapters.controllers.task;
-
-import java.time.LocalDateTime;
-
-public record TaskHistoryItemResponse(
- LocalDateTime startAt,
- LocalDateTime endAt) {
-}
diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/auth/login/LoginInput.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/auth/login/LoginInput.java
new file mode 100644
index 0000000..b984767
--- /dev/null
+++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/auth/login/LoginInput.java
@@ -0,0 +1,4 @@
+package net.beetechgroup.beetask.usecase.auth.login;
+
+public record LoginInput(String email, String password) {
+}
diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/auth/login/LoginOutput.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/auth/login/LoginOutput.java
new file mode 100644
index 0000000..2553434
--- /dev/null
+++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/auth/login/LoginOutput.java
@@ -0,0 +1,19 @@
+package net.beetechgroup.beetask.usecase.auth.login;
+
+import java.util.List;
+
+public record LoginOutput(
+ String name,
+ String email,
+ String photo,
+ String jwt,
+ String refreshToken,
+ Long expiresIn,
+ String issuedAt,
+ List organizations
+) {
+ public record OrganizationOutput(
+ String name,
+ List roles
+ ) {}
+}
diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/auth/login/LoginUseCase.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/auth/login/LoginUseCase.java
new file mode 100644
index 0000000..aca3955
--- /dev/null
+++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/auth/login/LoginUseCase.java
@@ -0,0 +1,74 @@
+package net.beetechgroup.beetask.usecase.auth.login;
+
+import io.smallrye.jwt.build.Jwt;
+import net.beetechgroup.beetask.entities.User;
+import net.beetechgroup.beetask.entities.organization.UserOrganization;
+import net.beetechgroup.beetask.usecase.repository.UserRepository;
+
+import java.time.LocalDateTime;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class LoginUseCase {
+
+ private final UserRepository userRepository;
+
+ public LoginUseCase(UserRepository userRepository) {
+ this.userRepository = userRepository;
+ }
+
+ public LoginOutput execute(LoginInput input) {
+ Optional userOptional = userRepository.findByEmail(input.email());
+
+ if (userOptional.isEmpty()) {
+ throw new RuntimeException("Invalid credentials");
+ }
+
+ User user = userOptional.get();
+
+ // Simple password check for now (should use Bcrypt)
+ if (!user.getPassword().equals(input.password())) {
+ throw new RuntimeException("Invalid credentials");
+ }
+
+ List userOrganizations = userRepository.findUserOrganizations(user.getId());
+
+ Set groups = new HashSet<>();
+ userOrganizations.forEach(uo -> groups.add(uo.getRole().name()));
+
+ String token = Jwt.issuer("https://beetech.net")
+ .upn(user.getEmail())
+ .groups(groups)
+ .claim("name", user.getName())
+ .expiresIn(3600)
+ .sign();
+
+ List orgs = userOrganizations.stream()
+ .collect(Collectors.groupingBy(uo -> uo.getOrganization().getName()))
+ .entrySet().stream()
+ .map(entry -> new LoginOutput.OrganizationOutput(
+ entry.getKey(),
+ entry.getValue().stream().map(uo -> uo.getRole().name()).collect(Collectors.toList())
+ ))
+ .collect(Collectors.toList());
+
+ String refreshToken = Jwt.issuer("https://beetech.net")
+ .upn(user.getEmail())
+ .expiresIn(86400) // 24 hours
+ .sign();
+
+ return new LoginOutput(
+ user.getName(),
+ user.getEmail(),
+ user.getPhoto() != null ? user.getPhoto() : "https://ui-avatars.com/api/?name=" + user.getName().replace(" ", "+") + "&background=random",
+ token,
+ refreshToken,
+ 3600L,
+ LocalDateTime.now().toString(),
+ orgs
+ );
+ }
+}
diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/auth/refresh/RefreshTokenInput.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/auth/refresh/RefreshTokenInput.java
new file mode 100644
index 0000000..3e7ff0f
--- /dev/null
+++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/auth/refresh/RefreshTokenInput.java
@@ -0,0 +1,4 @@
+package net.beetechgroup.beetask.usecase.auth.refresh;
+
+public record RefreshTokenInput(String refreshToken) {
+}
diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/auth/refresh/RefreshTokenUseCase.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/auth/refresh/RefreshTokenUseCase.java
new file mode 100644
index 0000000..bf80e9d
--- /dev/null
+++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/auth/refresh/RefreshTokenUseCase.java
@@ -0,0 +1,85 @@
+package net.beetechgroup.beetask.usecase.auth.refresh;
+
+import io.smallrye.jwt.auth.principal.JWTParser;
+import io.smallrye.jwt.auth.principal.ParseException;
+import io.smallrye.jwt.build.Jwt;
+import net.beetechgroup.beetask.entities.User;
+import net.beetechgroup.beetask.entities.organization.UserOrganization;
+import net.beetechgroup.beetask.usecase.auth.login.LoginOutput;
+import net.beetechgroup.beetask.usecase.repository.UserRepository;
+import org.eclipse.microprofile.jwt.JsonWebToken;
+
+import java.time.LocalDateTime;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class RefreshTokenUseCase {
+
+ private final UserRepository userRepository;
+ private final JWTParser jwtParser;
+
+ public RefreshTokenUseCase(UserRepository userRepository, JWTParser jwtParser) {
+ this.userRepository = userRepository;
+ this.jwtParser = jwtParser;
+ }
+
+ public LoginOutput execute(RefreshTokenInput input) {
+ try {
+ JsonWebToken token = jwtParser.parse(input.refreshToken());
+ String email = token.getSubject(); // Or token.getName() / token.getClaim(Claims.upn.name())
+
+ if (email == null) {
+ throw new RuntimeException("Invalid refresh token: email not found");
+ }
+
+ Optional userOptional = userRepository.findByEmail(email);
+ if (userOptional.isEmpty()) {
+ throw new RuntimeException("User not found for the provided refresh token");
+ }
+
+ User user = userOptional.get();
+ List userOrganizations = userRepository.findUserOrganizations(user.getId());
+
+ Set groups = new HashSet<>();
+ userOrganizations.forEach(uo -> groups.add(uo.getRole().name()));
+
+ String newToken = Jwt.issuer("https://beetech.net")
+ .upn(user.getEmail())
+ .groups(groups)
+ .claim("name", user.getName())
+ .expiresIn(3600)
+ .sign();
+
+ // Generate a new refresh token (also a JWT but with longer expiration)
+ String newRefreshToken = Jwt.issuer("https://beetech.net")
+ .upn(user.getEmail())
+ .expiresIn(86400) // 24 hours
+ .sign();
+
+ List orgs = userOrganizations.stream()
+ .collect(Collectors.groupingBy(uo -> uo.getOrganization().getName()))
+ .entrySet().stream()
+ .map(entry -> new LoginOutput.OrganizationOutput(
+ entry.getKey(),
+ entry.getValue().stream().map(uo -> uo.getRole().name()).collect(Collectors.toList())
+ ))
+ .collect(Collectors.toList());
+
+ return new LoginOutput(
+ user.getName(),
+ user.getEmail(),
+ user.getPhoto() != null ? user.getPhoto() : "https://ui-avatars.com/api/?name=" + user.getName().replace(" ", "+") + "&background=random",
+ newToken,
+ newRefreshToken,
+ 3600L,
+ LocalDateTime.now().toString(),
+ orgs
+ );
+ } catch (ParseException e) {
+ throw new RuntimeException("Invalid refresh token: " + e.getMessage());
+ }
+ }
+}
diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/repository/UserOrganizationRepository.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/repository/UserOrganizationRepository.java
new file mode 100644
index 0000000..2746dd5
--- /dev/null
+++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/repository/UserOrganizationRepository.java
@@ -0,0 +1,8 @@
+package net.beetechgroup.beetask.usecase.repository;
+
+import net.beetechgroup.beetask.entities.organization.UserOrganization;
+import java.util.List;
+
+public interface UserOrganizationRepository {
+ List findByUserId(Long userId);
+}
diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/repository/UserRepository.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/repository/UserRepository.java
new file mode 100644
index 0000000..858c4b8
--- /dev/null
+++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/repository/UserRepository.java
@@ -0,0 +1,13 @@
+package net.beetechgroup.beetask.usecase.repository;
+
+import net.beetechgroup.beetask.entities.User;
+import java.util.List;
+import java.util.Optional;
+import net.beetechgroup.beetask.entities.organization.UserOrganization;
+
+public interface UserRepository {
+ User save(User user);
+ Optional findByEmail(String email);
+ Optional findUserById(Long id);
+ List findUserOrganizations(Long userId);
+}
diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/user/create/CreateUserInput.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/user/create/CreateUserInput.java
new file mode 100644
index 0000000..0edb2b8
--- /dev/null
+++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/user/create/CreateUserInput.java
@@ -0,0 +1,4 @@
+package net.beetechgroup.beetask.usecase.user.create;
+
+public record CreateUserInput(String name, String email, String password) {
+}
diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/user/create/CreateUserMapper.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/user/create/CreateUserMapper.java
new file mode 100644
index 0000000..4a5afaf
--- /dev/null
+++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/user/create/CreateUserMapper.java
@@ -0,0 +1,18 @@
+package net.beetechgroup.beetask.usecase.user.create;
+
+import net.beetechgroup.beetask.entities.User;
+
+public class CreateUserMapper {
+
+ public static User toUser(CreateUserInput input) {
+ User user = new User();
+ user.setName(input.name());
+ user.setEmail(input.email());
+ user.setPassword(input.password());
+ return user;
+ }
+
+ public static CreateUserOutput toCreateUserOutput(User user) {
+ return new CreateUserOutput(user.getId(), user.getName(), user.getEmail());
+ }
+}
diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/user/create/CreateUserOutput.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/user/create/CreateUserOutput.java
new file mode 100644
index 0000000..71c643b
--- /dev/null
+++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/user/create/CreateUserOutput.java
@@ -0,0 +1,4 @@
+package net.beetechgroup.beetask.usecase.user.create;
+
+public record CreateUserOutput(Long id, String name, String email) {
+}
diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/user/create/CreateUserUseCase.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/user/create/CreateUserUseCase.java
new file mode 100644
index 0000000..e9b00bb
--- /dev/null
+++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/user/create/CreateUserUseCase.java
@@ -0,0 +1,24 @@
+package net.beetechgroup.beetask.usecase.user.create;
+
+import net.beetechgroup.beetask.entities.User;
+import net.beetechgroup.beetask.usecase.repository.UserRepository;
+
+public class CreateUserUseCase {
+
+ private final UserRepository userRepository;
+
+ public CreateUserUseCase(UserRepository userRepository) {
+ this.userRepository = userRepository;
+ }
+
+ public CreateUserOutput execute(CreateUserInput input) {
+ if (userRepository.findByEmail(input.email()).isPresent()) {
+ throw new RuntimeException("User already exists with this email");
+ }
+
+ User user = CreateUserMapper.toUser(input);
+ User savedUser = userRepository.save(user);
+
+ return CreateUserMapper.toCreateUserOutput(savedUser);
+ }
+}