ajustes que nao lembro mais

This commit is contained in:
Gabriel Santana de Menezes 2026-05-06 09:57:28 -04:00
parent 4582663fc8
commit e0a3697e5b
22 changed files with 107 additions and 80 deletions

View file

@ -13,6 +13,7 @@ public class Task {
private String description;
private TaskStatus status;
private Project project;
private User user;
private LocalDateTime finishedAt;
private List<TaskHistoryItem> history = new ArrayList<>();
@ -75,6 +76,14 @@ public class Task {
this.project = project;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public List<TaskHistoryItem> getHistory() {
return history;
}
@ -94,18 +103,17 @@ public class Task {
.orElse(null);
}
public void start(User user) {
TaskHistoryItem current = getCurrentRunningItem();
public void start() {
TaskHistoryItem current = getCurrentRunningItem();
if (current != null) {
throw new IllegalStateException("Tarefa já está em execução");
}
if (current != null) {
throw new IllegalStateException("Tarefa já está em execução");
}
this.status = TaskStatus.IN_PROGRESS;
TaskHistoryItem item = new TaskHistoryItem();
item.setStartAt(LocalDateTime.now());
item.setUser(user);
this.history.add(item);
this.status = TaskStatus.IN_PROGRESS;
TaskHistoryItem item = new TaskHistoryItem();
item.setStartAt(LocalDateTime.now());
this.history.add(item);
}
public void stop() {

View file

@ -1,13 +1,11 @@
package net.beetechgroup.beetask.entities.task;
import java.time.LocalDateTime;
import net.beetechgroup.beetask.entities.User;
public class TaskHistoryItem {
private Long id;
private LocalDateTime startAt;
private LocalDateTime endAt;
private User user;
public Long getId() {
return id;
@ -36,12 +34,4 @@ public class TaskHistoryItem {
public boolean isRunning() {
return endAt == null;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}

View file

@ -17,8 +17,8 @@ import net.beetechgroup.beetask.usecase.task.get.GetTaskUseCase;
public class TaskUseCaseConfig {
@Produces
public CreateTaskUseCase createTaskUseCase(TaskRepository taskRepository, ProjectRepository projectRepository) {
return new CreateTaskUseCase(taskRepository, projectRepository);
public CreateTaskUseCase createTaskUseCase(TaskRepository taskRepository, ProjectRepository projectRepository, UserRepository userRepository) {
return new CreateTaskUseCase(taskRepository, projectRepository, userRepository);
}
@Produces
@ -42,8 +42,8 @@ public class TaskUseCaseConfig {
}
@Produces
public UpdateTaskUseCase updateTaskUseCase(TaskRepository taskRepository, ProjectRepository projectRepository) {
return new UpdateTaskUseCase(taskRepository, projectRepository);
public UpdateTaskUseCase updateTaskUseCase(TaskRepository taskRepository, ProjectRepository projectRepository, UserRepository userRepository) {
return new UpdateTaskUseCase(taskRepository, projectRepository, userRepository);
}
@Produces

View file

@ -29,6 +29,9 @@ public class TaskEntity {
@ManyToOne
@JoinColumn(name = "project_id")
private ProjectEntity project;
@ManyToOne
@JoinColumn(name = "user_id")
private UserEntity user;
@OneToMany(mappedBy = "task", cascade = CascadeType.ALL, orphanRemoval = true)
private List<TaskHistoryItemEntity> history;
@ -88,4 +91,11 @@ public class TaskEntity {
this.project = project;
}
public UserEntity getUser() {
return user;
}
public void setUser(UserEntity user) {
this.user = user;
}
}

View file

@ -20,10 +20,6 @@ public class TaskHistoryItemEntity {
private LocalDateTime startAt;
private LocalDateTime endAt;
@ManyToOne
@JoinColumn(name = "user_id")
private UserEntity user;
@ManyToOne
@JoinColumn(name = "task_id")
private TaskEntity task;
@ -52,14 +48,6 @@ public class TaskHistoryItemEntity {
this.endAt = endAt;
}
public UserEntity getUser() {
return user;
}
public void setUser(UserEntity user) {
this.user = user;
}
public TaskEntity getTask() {
return task;
}

View file

@ -7,7 +7,7 @@ import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Entity
@Table(name = "users")
@Table(name = "users_entity")
public class UserEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)

View file

@ -16,6 +16,7 @@ public class TaskEntityMapper {
entity.setStatus(task.getStatus());
entity.setFinishedAt(task.getFinishedAt());
entity.setProject(ProjectEntityMapper.toEntity(task.getProject()));
entity.setUser(UserEntityMapper.toEntity(task.getUser()));
entity.setHistory(new ArrayList<>(task.getHistory().stream().map(h -> {
TaskHistoryItemEntity he = TaskHistoryItemMapper.toEntity(h);
he.setTask(entity);
@ -34,6 +35,7 @@ public class TaskEntityMapper {
task.setStatus(entity.getStatus());
task.setFinishedAt(entity.getFinishedAt());
task.setProject(ProjectEntityMapper.toDomain(entity.getProject()));
task.setUser(UserEntityMapper.toDomain(entity.getUser()));
task.setHistory(new ArrayList<>(entity.getHistory().stream().map(TaskHistoryItemMapper::toDomain).toList()));
return task;

View file

@ -9,9 +9,6 @@ public class TaskHistoryItemMapper {
entity.setId(item.getId());
entity.setStartAt(item.getStartAt());
entity.setEndAt(item.getEndAt());
if (item.getUser() != null) {
entity.setUser(UserEntityMapper.toEntity(item.getUser()));
}
return entity;
}
@ -22,9 +19,6 @@ public class TaskHistoryItemMapper {
item.setId(entity.getId());
item.setStartAt(entity.getStartAt());
item.setEndAt(entity.getEndAt());
if (entity.getUser() != null) {
item.setUser(UserEntityMapper.toDomain(entity.getUser()));
}
return item;
}
}

View file

@ -41,13 +41,16 @@ public class TaskRepositoryImpl implements TaskRepository, PanacheRepository<Tas
@Override
public List<Task> findTasksWorkedByUserInPeriod(String email, LocalDateTime start, LocalDateTime end) {
return find("select distinct t from TaskEntity t join t.history h where h.user.email = ?1 and h.startAt >= ?2 and h.startAt <= ?3",
return find("select distinct t from TaskEntity t join t.history h where t.user.email = ?1 " +
"and h.startAt <= ?3 and (h.endAt is null or h.endAt >= ?2)",
email, start, end).list().stream().map(TaskEntityMapper::toDomain).toList();
}
@Override
public List<Task> findTasksFinishedByUserInPeriod(String email, LocalDateTime start, LocalDateTime end) {
return find("select distinct t from TaskEntity t join t.history h where h.user.email = ?1 and t.finishedAt >= ?2 and t.finishedAt <= ?3",
return find("select distinct t from TaskEntity t where t.user.email = ?1 " +
"and t.status = net.beetechgroup.beetask.entities.task.TaskStatus.COMPLETED " +
"and t.finishedAt >= ?2 and t.finishedAt <= ?3",
email, start, end).list().stream().map(TaskEntityMapper::toDomain).toList();
}
}

View file

@ -41,6 +41,10 @@ public class DashboardController {
? LocalDateTime.parse(endDateStr, DateTimeFormatter.ISO_DATE_TIME)
: LocalDateTime.now();
if (endDateStr != null && end.toLocalTime().equals(java.time.LocalTime.MIDNIGHT)) {
end = end.with(java.time.LocalTime.MAX);
}
DashboardInput input = new DashboardInput(email, start, end);
return dashboardUseCase.execute(input);
}

View file

@ -64,7 +64,8 @@ public class TaskController {
@Operation(summary = "Create a new task", description = "Creates a new task in the system")
@APIResponse(responseCode = "201", description = "Task created successfully")
public CreateTaskResponse createTask(CreateTaskRequest request) {
CreateTaskInput input = TaskControllerMapper.toCreateTaskInput(request);
String email = securityIdentity.getPrincipal().getName();
CreateTaskInput input = TaskControllerMapper.toCreateTaskInput(request, email);
CreateTaskOutput output = createTaskUseCase.execute(input);
return TaskControllerMapper.toCreateTaskResponse(output);
}
@ -74,7 +75,8 @@ public class TaskController {
@Operation(summary = "Update a task", description = "Updates an existing task in the system")
@APIResponse(responseCode = "200", description = "Task updated successfully")
public CreateTaskResponse updateTask(@PathParam("id") Long id, CreateTaskRequest request) {
CreateTaskOutput output = updateTaskUseCase.execute(TaskControllerMapper.toUpdateTaskInput(id, request));
String email = securityIdentity.getPrincipal().getName();
CreateTaskOutput output = updateTaskUseCase.execute(TaskControllerMapper.toUpdateTaskInput(id, request, email));
return TaskControllerMapper.toCreateTaskResponse(output);
}

View file

@ -9,12 +9,13 @@ import net.beetechgroup.beetask.usecase.task.update.UpdateTaskInput;
public class TaskControllerMapper {
public static CreateTaskInput toCreateTaskInput(CreateTaskRequest request) {
public static CreateTaskInput toCreateTaskInput(CreateTaskRequest request, String userEmail) {
return new CreateTaskInput(
request.title(),
request.description(),
request.status(),
request.projectId()
request.projectId(),
userEmail
);
}
@ -38,13 +39,14 @@ public class TaskControllerMapper {
return new StopTaskInput(id);
}
public static UpdateTaskInput toUpdateTaskInput(Long id, CreateTaskRequest request) {
public static UpdateTaskInput toUpdateTaskInput(Long id, CreateTaskRequest request, String userEmail) {
return new UpdateTaskInput(
id,
request.title(),
request.description(),
request.status(),
request.projectId(),
userEmail,
request.history() != null ? request.history().stream()
.map(h -> new TaskHistoryItemInput(h.id(), h.startAt(), h.endAt()))
.toList() : null

View file

@ -23,7 +23,6 @@ public class LoginUseCase {
}
public LoginOutput execute(LoginInput input) {
System.out.println(input.email() + " " + input.password());
Optional<User> userOptional = userRepository.findByEmail(input.email());
if (userOptional.isEmpty()) {
@ -50,14 +49,17 @@ public class LoginUseCase {
.sign();
List<LoginOutput.OrganizationOutput> orgs = userOrganizations.stream()
.collect(Collectors.groupingBy(uo -> uo.getOrganization().getName()))
.collect(Collectors.groupingBy(uo -> uo.getOrganization().getId()))
.entrySet().stream()
.map(entry -> new LoginOutput.OrganizationOutput(
entry.getValue().stream().map(uo -> uo.getOrganization().getId()).findFirst().orElse(null),
entry.getKey(),
entry.getValue().stream().map(uo -> uo.getRole().name()).collect(Collectors.toList())
))
.collect(Collectors.toList());
.map(entry -> {
UserOrganization first = entry.getValue().get(0);
return new LoginOutput.OrganizationOutput(
entry.getKey(),
first.getOrganization().getName(),
entry.getValue().stream().map(uo -> uo.getRole().name()).toList()
);
})
.toList();
String refreshToken = Jwt.issuer(this.issuer)
.upn(user.getEmail())

View file

@ -62,13 +62,17 @@ public class RefreshTokenUseCase {
.sign();
List<LoginOutput.OrganizationOutput> orgs = userOrganizations.stream()
.collect(Collectors.groupingBy(uo -> uo.getOrganization().getName()))
.collect(Collectors.groupingBy(uo -> uo.getOrganization().getId()))
.entrySet().stream()
.map(entry -> new LoginOutput.OrganizationOutput(
entry.getKey(),
entry.getValue().stream().map(uo -> uo.getRole().name()).collect(Collectors.toList())
))
.collect(Collectors.toList());
.map(entry -> {
UserOrganization first = entry.getValue().get(0);
return new LoginOutput.OrganizationOutput(
entry.getKey(),
first.getOrganization().getName(),
entry.getValue().stream().map(uo -> uo.getRole().name()).toList()
);
})
.toList();
return new LoginOutput(
user.getName(),

View file

@ -45,7 +45,6 @@ public class DashboardUseCase {
for (Task task : workedTasks) {
long taskMinutesInPeriod = task.getHistory().stream()
.filter(h -> h.getUser() != null && h.getUser().getEmail().equals(input.userEmail()))
.filter(h -> isWithinPeriod(h, input.startDate(), input.endDate()))
.mapToLong(h -> calculateMinutesInRange(h, input.startDate(), input.endDate()))
.sum();

View file

@ -6,5 +6,6 @@ public record CreateTaskInput(
String title,
String description,
TaskStatus status,
Long projectId) {
Long projectId,
String userEmail) {
}

View file

@ -4,14 +4,17 @@ import net.beetechgroup.beetask.entities.Project;
import net.beetechgroup.beetask.entities.task.Task;
import net.beetechgroup.beetask.usecase.repository.ProjectRepository;
import net.beetechgroup.beetask.usecase.repository.TaskRepository;
import net.beetechgroup.beetask.usecase.repository.UserRepository;
public class CreateTaskUseCase {
private final TaskRepository taskRepository;
private final ProjectRepository projectRepository;
private final UserRepository userRepository;
public CreateTaskUseCase(TaskRepository taskRepository, ProjectRepository projectRepository) {
public CreateTaskUseCase(TaskRepository taskRepository, ProjectRepository projectRepository, UserRepository userRepository) {
this.taskRepository = taskRepository;
this.projectRepository = projectRepository;
this.userRepository = userRepository;
}
public CreateTaskOutput execute(CreateTaskInput input) {
@ -26,6 +29,9 @@ public class CreateTaskUseCase {
task.setProject(project);
}
task.setUser(userRepository.findByEmail(input.userEmail())
.orElseThrow(() -> new IllegalArgumentException("Usuário não encontrado: " + input.userEmail())));
return CreateTaskMapper.toCreateTaskOutput(taskRepository.saveTask(task));
}
}

View file

@ -20,9 +20,12 @@ public class StartTaskUseCase {
public CreateTaskOutput execute(StartTaskInput input) {
Task task = taskRepository.findTaskById(input.id());
User user = userRepository.findByEmail(input.userEmail())
.orElseThrow(() -> new UserNotFoundException(input.userEmail()));
task.start(user);
if (task.getUser() != null && !task.getUser().getEmail().equals(input.userEmail())) {
throw new SecurityException("Você não tem permissão para iniciar esta tarefa.");
}
task.start();
return CreateTaskMapper.toCreateTaskOutput(taskRepository.saveTask(task));
}
}

View file

@ -9,5 +9,6 @@ public record UpdateTaskInput(
String description,
TaskStatus status,
Long projectId,
String userEmail,
List<TaskHistoryItemInput> history) {
}

View file

@ -6,22 +6,29 @@ import net.beetechgroup.beetask.entities.task.Task;
import net.beetechgroup.beetask.entities.task.TaskHistoryItem;
import net.beetechgroup.beetask.usecase.repository.ProjectRepository;
import net.beetechgroup.beetask.usecase.repository.TaskRepository;
import net.beetechgroup.beetask.usecase.repository.UserRepository;
import net.beetechgroup.beetask.usecase.task.create.CreateTaskMapper;
import net.beetechgroup.beetask.usecase.task.create.CreateTaskOutput;
public class UpdateTaskUseCase {
private final TaskRepository taskRepository;
private final ProjectRepository projectRepository;
private final UserRepository userRepository;
public UpdateTaskUseCase(TaskRepository taskRepository, ProjectRepository projectRepository) {
public UpdateTaskUseCase(TaskRepository taskRepository, ProjectRepository projectRepository, UserRepository userRepository) {
this.taskRepository = taskRepository;
this.projectRepository = projectRepository;
this.userRepository = userRepository;
}
public CreateTaskOutput execute(UpdateTaskInput input) {
//TODO deve validar se o usuario logado é o dono da task
Task task = taskRepository.findTaskById(input.id());
if (task.getUser() == null) {
task.setUser(userRepository.findByEmail(input.userEmail())
.orElseThrow(() -> new IllegalArgumentException("Usuário não encontrado: " + input.userEmail())));
}
task.setTitle(input.title());
task.setDescription(input.description());
task.setStatus(input.status());

View file

@ -9,6 +9,7 @@ import {
Send,
XCircle,
} from "lucide-react";
import { format } from "date-fns";
import { useState } from "react";
import type { ChangeEvent, FormEvent } from "react";
import { Link, useNavigate, useParams } from "react-router-dom";
@ -302,7 +303,7 @@ export function CreateTaskPage() {
onClick={() => {
setForm(prev => ({
...prev,
history: [...prev.history, { startAt: new Date().toISOString(), endAt: null }]
history: [...prev.history, { startAt: format(new Date(), "yyyy-MM-dd'T'HH:mm:ss"), endAt: null }]
}))
}}
className="text-xs font-bold text-brand hover:underline"
@ -321,7 +322,7 @@ export function CreateTaskPage() {
value={item.startAt ? item.startAt.slice(0, 16) : ""}
onChange={(e) => {
const newHistory = [...form.history];
newHistory[index] = { ...item, startAt: new Date(e.target.value).toISOString() };
newHistory[index] = { ...item, startAt: e.target.value };
setForm(prev => ({ ...prev, history: newHistory }));
}}
className="w-full rounded-lg border border-border-soft bg-surface px-3 py-2 text-xs outline-none focus:border-brand"
@ -334,7 +335,7 @@ export function CreateTaskPage() {
value={item.endAt ? item.endAt.slice(0, 16) : ""}
onChange={(e) => {
const newHistory = [...form.history];
newHistory[index] = { ...item, endAt: e.target.value ? new Date(e.target.value).toISOString() : null };
newHistory[index] = { ...item, endAt: e.target.value || null };
setForm(prev => ({ ...prev, history: newHistory }));
}}
className="w-full rounded-lg border border-border-soft bg-surface px-3 py-2 text-xs outline-none focus:border-brand"

View file

@ -35,8 +35,8 @@ export const DashboardPage: React.FC = () => {
setIsLoading(true);
try {
const result = await dashboardService.getDashboard(
startDate.toISOString(),
endDate.toISOString(),
format(startDate, "yyyy-MM-dd'T'HH:mm:ss"),
format(endDate, "yyyy-MM-dd'T'HH:mm:ss"),
);
setData(result);
} catch (err) {
@ -80,7 +80,7 @@ export const DashboardPage: React.FC = () => {
<input
type="date"
value={format(startDate, "yyyy-MM-dd")}
onChange={(e) => setStartDate(new Date(e.target.value))}
onChange={(e) => setStartDate(new Date(e.target.value + 'T00:00:00'))}
className="bg-transparent border-none text-sm font-bold text-text-main focus:ring-0 outline-none"
/>
</div>
@ -88,7 +88,7 @@ export const DashboardPage: React.FC = () => {
<input
type="date"
value={format(endDate, "yyyy-MM-dd")}
onChange={(e) => setEndDate(new Date(e.target.value))}
onChange={(e) => setEndDate(new Date(e.target.value + 'T23:59:59'))}
className="bg-transparent border-none text-sm font-bold text-text-main focus:ring-0 outline-none"
/>
</div>