From 1868d069c2fce7db5dd05b33e2f1fda7a11ddea0 Mon Sep 17 00:00:00 2001 From: Gabriel Santana de Menezes Date: Sat, 9 May 2026 15:57:40 -0400 Subject: [PATCH] chore: enforce coding standards and add project guidelines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace JPQL queries in TaskRepositoryImpl with Panache shorthand and Java filtering - Replace == null / != null with Objects.isNull() / Objects.nonNull() across all layers - Add CLAUDE.md with project conventions, architecture rules, and coding standards - Add .github/PULL_REQUEST_TEMPLATE.md for standardized PR descriptions in Portuguese - Add .claude/skills/new-task.md workflow guide (issue reading → branch → commit → PR) Co-Authored-By: Claude Sonnet 4.6 --- .claude/skills/new-task.md | 92 +++++++++++ .github/PULL_REQUEST_TEMPLATE.md | 38 +++++ CLAUDE.md | 145 ++++++++++++++++++ .../beetask/entities/Project.java | 3 +- .../beetask/entities/task/Task.java | 28 ++-- .../entities/task/TaskHistoryItem.java | 3 +- .../mapper/OrganizationEntityMapper.java | 3 +- .../mapper/ProjectEntityMapper.java | 5 +- .../persistence/mapper/TaskEntityMapper.java | 5 +- .../mapper/TaskHistoryItemMapper.java | 5 +- .../persistence/mapper/UserEntityMapper.java | 5 +- .../mapper/UserOrganizationEntityMapper.java | 5 +- .../OrganizationRepositoryImpl.java | 2 +- .../repository/ProjectRepositoryImpl.java | 3 +- .../repository/TaskRepositoryImpl.java | 23 +-- .../UserOrganizationRepositoryImpl.java | 6 +- .../repository/UserRepositoryImpl.java | 3 +- .../task/TaskControllerMapper.java | 3 +- .../usecase/auth/login/LoginUseCase.java | 3 +- .../auth/refresh/RefreshTokenUseCase.java | 5 +- .../usecase/dashboard/DashboardUseCase.java | 22 ++- .../usecase/task/create/CreateTaskMapper.java | 3 +- .../task/create/CreateTaskUseCase.java | 3 +- .../usecase/task/start/StartTaskUseCase.java | 9 +- .../task/update/UpdateTaskUseCase.java | 13 +- .../user/profile/GetUserProfileUseCase.java | 3 +- 26 files changed, 363 insertions(+), 75 deletions(-) create mode 100644 .claude/skills/new-task.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 CLAUDE.md diff --git a/.claude/skills/new-task.md b/.claude/skills/new-task.md new file mode 100644 index 0000000..d38ad0d --- /dev/null +++ b/.claude/skills/new-task.md @@ -0,0 +1,92 @@ +--- +trigger: manual +description: Workflow completo para iniciar e concluir uma nova tarefa a partir de uma issue do GitHub +--- + +# Workflow de Nova Tarefa + +Siga estes passos em ordem para executar uma tarefa do início ao PR. + +## Passo 1 — Ler a issue + +```bash +gh issue view --repo +``` + +Leia o **título** e a **descrição** completa da issue antes de qualquer ação. + +## Passo 2 — Criar a branch + +Escolha o prefixo de acordo com o tipo de mudança: + +| Tipo | Prefixo | Quando usar | +|------|---------|-------------| +| Nova funcionalidade | `feat/` | Algo novo que o usuário percebe | +| Correção de bug | `fix/` | Comportamento errado corrigido | +| Manutenção/chore | `chore/` | Refactor, config, padrões, docs | +| Teste | `test/` | Apenas adição/correção de testes | + +Formato do nome: `` + +```bash +git checkout main +git pull origin main +git checkout -b feat/nome-descritivo-da-tarefa +``` + +## Passo 3 — Executar a tarefa + +Implemente as mudanças seguindo os padrões definidos no `CLAUDE.md` do projeto. + +Lembre-se de: +- Não usar SQL nativo nem JPQL nas repositories +- Usar `Objects.isNull()` / `Objects.nonNull()` ao invés de `== null` / `!= null` +- Seguir a Clean Architecture (entities → use cases → interface adapters → frameworks) +- Não importar frameworks nas camadas de domínio + +## Passo 4 — Commit + +Formato obrigatório: `: ` + +```bash +git add +git commit -m "feat: add null check using Objects utility methods" +``` + +Tipos válidos: `feat`, `fix`, `chore`, `docs`, `test`, `refactor` + +Exemplos: +- `feat: add task filtering by organization` +- `fix: prevent duplicate user organization entries` +- `chore: replace JPQL queries with Panache shorthand` + +## Passo 5 — Criar o Pull Request + +```bash +git push origin +gh pr create \ + --title ": " \ + --body "$(cat <<'EOF' +## Descrição +... + +## Tipo de mudança +- [x] Nova funcionalidade + +## Issue relacionada +Fecha # + +## O que foi feito +- ... + +## Como testar +1. ... + +## Checklist +- [x] Código segue padrões do projeto +- [x] Build passa sem erros +EOF +)" +``` + +A descrição do PR deve ser **em português**, detalhada e seguir o template em `.github/PULL_REQUEST_TEMPLATE.md`. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..097efeb --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,38 @@ +## Descrição + + + +## Tipo de mudança + +- [ ] Nova funcionalidade (`feat`) +- [ ] Correção de bug (`fix`) +- [ ] Melhoria/refatoração (`chore`) +- [ ] Documentação (`docs`) +- [ ] Testes (`test`) + +## Issue relacionada + +Fecha # + +## O que foi feito + + + +- +- +- + +## Como testar + + + +1. +2. +3. + +## Checklist + +- [ ] O código segue os padrões do projeto (sem SQL nativo, sem JPQL direto, usando `Objects.isNull`/`nonNull`) +- [ ] Foram criados/atualizados testes para as mudanças +- [ ] O build passa sem erros (`./mvnw verify` ou `quarkus dev`) +- [ ] Foi revisado o impacto em outras partes do sistema diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..0202e82 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,145 @@ +# Bee Task — Guia de Desenvolvimento + +## Stack + +- **Backend**: Quarkus + Hibernate ORM + Panache (Java 17+) +- **Frontend**: React + TypeScript + Tailwind CSS +- **Banco de dados**: PostgreSQL (via Docker Compose) + +## Arquitetura + +O backend segue **Clean Architecture** adaptada: + +``` +entities/ → Domínio puro: entidades e regras de negócio +usecase/ → Casos de uso: orquestram entidades e repositórios (interfaces) +interfaceadapters/ → Controllers REST (JAX-RS), mappers de entrada/saída +frameworks/ → Implementações: Panache repositories, JPA entities, mappers de persistência +``` + +**Regras de dependência (nunca viole):** +- `entities` não importa nada externo (sem frameworks, sem Jakarta, sem Quarkus) +- `usecase` só importa `entities` e interfaces próprias +- `interfaceadapters` importa `usecase` e `entities`, nunca `frameworks` +- `frameworks` implementa as interfaces de `usecase` + +## Padrões de Código Java + +### Repositories — Sem SQL nativo e sem JPQL explícito + +Nas implementações de repository (`frameworks/persistence/repository/`), use **sempre** o estilo shorthand do Panache: + +```java +// CORRETO — shorthand Panache (propriedades da entidade) +find("user.email = ?1 and status = ?2", email, status).list(); +find("organization.id", organizationId).stream().toList(); +findByIdOptional(id).map(mapper::toDomain); + +// ERRADO — JPQL explícito +find("SELECT t FROM TaskEntity t WHERE t.user.email = ?1", email); +find("select distinct t from TaskEntity t join t.history h where ...", ...); + +// ERRADO — SQL nativo +getEntityManager().createNativeQuery("SELECT * FROM tasks WHERE ..."); +``` + +Para consultas complexas que envolvem JOINs com coleções, prefira buscar via shorthand e filtrar em Java: + +```java +// Busca por propriedade simples + filtragem em Java +return find("user.email = ?1", email).list().stream() + .filter(entity -> entity.getHistory().stream().anyMatch(h -> + Objects.nonNull(h.getStartAt()) && + !h.getStartAt().isAfter(end) && + (Objects.isNull(h.getEndAt()) || !h.getEndAt().isBefore(start)))) + .map(mapper::toDomain) + .toList(); +``` + +### Verificações de nulo — `Objects.isNull` e `Objects.nonNull` + +Use sempre `Objects.isNull(x)` e `Objects.nonNull(x)` ao invés dos operadores `== null` e `!= null`: + +```java +// CORRETO +if (Objects.isNull(entity.getId())) { + persist(entity); +} + +if (Objects.nonNull(input.projectId())) { + task.setProject(projectRepository.findProjectById(input.projectId()).orElseThrow()); +} + +// ERRADO +if (entity.getId() == null) { ... } +if (input.projectId() != null) { ... } +``` + +Isso se aplica a: repositories, mappers, use cases e entities. +Exceção tolerada: ternários simples inline onde a legibilidade é melhor com `!=`. + +### Mappers de persistência + +Sempre verifique nulo na entrada de métodos de mapeamento: + +```java +public static MyEntity toEntity(MyDomain domain) { + if (Objects.isNull(domain)) return null; + // ... +} +``` + +### Padrão de save nos repositories + +```java +if (Objects.isNull(entity.getId())) { + persist(entity); +} else { + entity = getEntityManager().merge(entity); +} +``` + +## Workflow de Tarefas + +Use o skill `/new-task` para seguir o guia completo de início ao PR. + +### Branches + +| Tipo | Prefixo | +|------|---------| +| Funcionalidade | `feat/` | +| Bug | `fix/` | +| Manutenção | `chore/` | +| Testes | `test/` | + +### Commits + +Formato: `: ` + +``` +feat: add task filtering by user organization +fix: prevent null pointer in task history mapper +chore: replace JPQL with Panache shorthand queries +``` + +### Pull Requests + +- Título em inglês, curto (< 70 chars) +- Descrição em **português**, seguindo `.github/PULL_REQUEST_TEMPLATE.md` +- Vincular a issue com `Fecha #` + +## Rodando o projeto + +```bash +# Backend +cd backend +./mvnw quarkus:dev + +# Frontend +cd frontend +npm install +npm run dev + +# Banco de dados +docker-compose up -d +``` diff --git a/backend/src/main/java/net/beetechgroup/beetask/entities/Project.java b/backend/src/main/java/net/beetechgroup/beetask/entities/Project.java index 13e4349..7d9b0cd 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/entities/Project.java +++ b/backend/src/main/java/net/beetechgroup/beetask/entities/Project.java @@ -1,6 +1,7 @@ package net.beetechgroup.beetask.entities; import java.util.List; +import java.util.Objects; import net.beetechgroup.beetask.entities.organization.Organization; @@ -47,7 +48,7 @@ public class Project { return "Project{" + "id=" + id + ", name='" + name + '\'' + - ", organization=" + (organization != null ? organization.getName() : "null") + + ", organization=" + (Objects.nonNull(organization) ? organization.getName() : "null") + '}'; } } diff --git a/backend/src/main/java/net/beetechgroup/beetask/entities/task/Task.java b/backend/src/main/java/net/beetechgroup/beetask/entities/task/Task.java index 4289123..380334e 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/entities/task/Task.java +++ b/backend/src/main/java/net/beetechgroup/beetask/entities/task/Task.java @@ -3,6 +3,7 @@ package net.beetechgroup.beetask.entities.task; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import net.beetechgroup.beetask.entities.Project; import net.beetechgroup.beetask.entities.User; @@ -48,9 +49,6 @@ public class Task { public void setStatus(TaskStatus status) { if (this.status == status) return; - // Note: Automatic start/stop removed from setStatus to avoid - // compilation errors and ensure user attribution via StartTaskUseCase. - if (status == TaskStatus.COMPLETED) { this.finishedAt = LocalDateTime.now(); } else { @@ -99,14 +97,14 @@ public class Task { private TaskHistoryItem getCurrentRunningItem() { return history.stream() .filter(TaskHistoryItem::isRunning) - .findFirst() - .orElse(null); -} + .findFirst() + .orElse(null); + } public void start() { TaskHistoryItem current = getCurrentRunningItem(); - - if (current != null) { + + if (Objects.nonNull(current)) { throw new IllegalStateException("Tarefa já está em execução"); } @@ -117,14 +115,12 @@ public class Task { } public void stop() { - TaskHistoryItem current = getCurrentRunningItem(); + TaskHistoryItem current = getCurrentRunningItem(); - if (current == null) { - throw new IllegalStateException("Task não está em execução"); + if (Objects.isNull(current)) { + throw new IllegalStateException("Task não está em execução"); + } + + current.setEndAt(LocalDateTime.now()); } - - current.setEndAt(LocalDateTime.now()); -} - - } diff --git a/backend/src/main/java/net/beetechgroup/beetask/entities/task/TaskHistoryItem.java b/backend/src/main/java/net/beetechgroup/beetask/entities/task/TaskHistoryItem.java index fa0bbe2..623535e 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/entities/task/TaskHistoryItem.java +++ b/backend/src/main/java/net/beetechgroup/beetask/entities/task/TaskHistoryItem.java @@ -1,6 +1,7 @@ package net.beetechgroup.beetask.entities.task; import java.time.LocalDateTime; +import java.util.Objects; public class TaskHistoryItem { private Long id; @@ -32,6 +33,6 @@ public class TaskHistoryItem { } public boolean isRunning() { - return endAt == null; + return Objects.isNull(endAt); } } diff --git a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/mapper/OrganizationEntityMapper.java b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/mapper/OrganizationEntityMapper.java index dd12fb7..90ddde3 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/mapper/OrganizationEntityMapper.java +++ b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/mapper/OrganizationEntityMapper.java @@ -1,5 +1,6 @@ package net.beetechgroup.beetask.frameworks.persistence.mapper; +import java.util.Objects; import net.beetechgroup.beetask.entities.organization.Organization; import net.beetechgroup.beetask.frameworks.persistence.entities.OrganizationEntity; @@ -12,7 +13,7 @@ public class OrganizationEntityMapper { } public static Organization toDomain(OrganizationEntity entity) { - if (entity == null) return null; + if (Objects.isNull(entity)) return null; Organization organization = new Organization(); organization.setId(entity.getId()); diff --git a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/mapper/ProjectEntityMapper.java b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/mapper/ProjectEntityMapper.java index e199a46..fce7f28 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/mapper/ProjectEntityMapper.java +++ b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/mapper/ProjectEntityMapper.java @@ -1,12 +1,13 @@ package net.beetechgroup.beetask.frameworks.persistence.mapper; +import java.util.Objects; import net.beetechgroup.beetask.entities.Project; import net.beetechgroup.beetask.frameworks.persistence.entities.ProjectEntity; public class ProjectEntityMapper { public static ProjectEntity toEntity(Project project) { - if (project == null) return null; + if (Objects.isNull(project)) return null; ProjectEntity entity = new ProjectEntity(); entity.setId(project.getId()); entity.setName(project.getName()); @@ -15,7 +16,7 @@ public class ProjectEntityMapper { } public static Project toDomain(ProjectEntity entity) { - if (entity == null) return null; + if (Objects.isNull(entity)) return null; Project project = new Project(); project.setId(entity.getId()); diff --git a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/mapper/TaskEntityMapper.java b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/mapper/TaskEntityMapper.java index 5af709b..d64b094 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/mapper/TaskEntityMapper.java +++ b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/mapper/TaskEntityMapper.java @@ -1,6 +1,7 @@ package net.beetechgroup.beetask.frameworks.persistence.mapper; import java.util.ArrayList; +import java.util.Objects; import net.beetechgroup.beetask.entities.task.Task; import net.beetechgroup.beetask.frameworks.persistence.entities.TaskEntity; @@ -26,7 +27,7 @@ public class TaskEntityMapper { } public static Task toDomain(TaskEntity entity) { - if (entity == null) return null; + if (Objects.isNull(entity)) return null; Task task = new Task(); task.setId(entity.getId()); @@ -40,4 +41,4 @@ public class TaskEntityMapper { return task; } -} \ No newline at end of file +} diff --git a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/mapper/TaskHistoryItemMapper.java b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/mapper/TaskHistoryItemMapper.java index 42f6ea8..dbbd11b 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/mapper/TaskHistoryItemMapper.java +++ b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/mapper/TaskHistoryItemMapper.java @@ -1,5 +1,6 @@ package net.beetechgroup.beetask.frameworks.persistence.mapper; +import java.util.Objects; import net.beetechgroup.beetask.entities.task.TaskHistoryItem; import net.beetechgroup.beetask.frameworks.persistence.entities.TaskHistoryItemEntity; @@ -13,7 +14,7 @@ public class TaskHistoryItemMapper { } public static TaskHistoryItem toDomain(TaskHistoryItemEntity entity) { - if (entity == null) return null; + if (Objects.isNull(entity)) return null; TaskHistoryItem item = new TaskHistoryItem(); item.setId(entity.getId()); @@ -21,4 +22,4 @@ public class TaskHistoryItemMapper { item.setEndAt(entity.getEndAt()); return item; } -} \ No newline at end of file +} 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 index b5c7192..50397ec 100644 --- 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 @@ -1,12 +1,13 @@ package net.beetechgroup.beetask.frameworks.persistence.mapper; +import java.util.Objects; 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; + if (Objects.isNull(user)) return null; UserEntity entity = new UserEntity(); entity.setId(user.getId()); entity.setName(user.getName()); @@ -17,7 +18,7 @@ public class UserEntityMapper { } public static User toDomain(UserEntity entity) { - if (entity == null) return null; + if (Objects.isNull(entity)) return null; User user = new User(); user.setId(entity.getId()); user.setName(entity.getName()); 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 index e127b51..010718d 100644 --- 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 @@ -1,12 +1,13 @@ package net.beetechgroup.beetask.frameworks.persistence.mapper; +import java.util.Objects; 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; + if (Objects.isNull(entity)) return null; UserOrganization domain = new UserOrganization(); domain.setUser(UserEntityMapper.toDomain(entity.getUser())); domain.setOrganization(OrganizationEntityMapper.toDomain(entity.getOrganization())); @@ -16,7 +17,7 @@ public class UserOrganizationEntityMapper { } public static UserOrganizationEntity toEntity(UserOrganization domain) { - if (domain == null) return null; + if (Objects.isNull(domain)) return null; UserOrganizationEntity entity = new UserOrganizationEntity(); entity.setUser(UserEntityMapper.toEntity(domain.getUser())); entity.setOrganization(OrganizationEntityMapper.toEntity(domain.getOrganization())); diff --git a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/OrganizationRepositoryImpl.java b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/OrganizationRepositoryImpl.java index 7d2562a..037fd89 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/OrganizationRepositoryImpl.java +++ b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/OrganizationRepositoryImpl.java @@ -19,7 +19,7 @@ public class OrganizationRepositoryImpl implements OrganizationRepository, Panac @Transactional public Organization saveOrganization(Organization organization) { OrganizationEntity entity = OrganizationEntityMapper.toEntity(organization); - if (entity.getId() == null) { + if (Objects.isNull(entity.getId())) { persist(entity); } else { entity = getEntityManager().merge(entity); diff --git a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/ProjectRepositoryImpl.java b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/ProjectRepositoryImpl.java index f652aa3..fcbb823 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/ProjectRepositoryImpl.java +++ b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/ProjectRepositoryImpl.java @@ -9,6 +9,7 @@ import net.beetechgroup.beetask.frameworks.persistence.mapper.ProjectEntityMappe import net.beetechgroup.beetask.usecase.repository.ProjectRepository; import java.util.List; +import java.util.Objects; import java.util.Optional; @ApplicationScoped @@ -18,7 +19,7 @@ public class ProjectRepositoryImpl implements ProjectRepository, PanacheReposito @Transactional public Project saveProject(Project project) { ProjectEntity entity = ProjectEntityMapper.toEntity(project); - if (entity.getId() == null) { + if (Objects.isNull(entity.getId())) { persist(entity); } else { entity = getEntityManager().merge(entity); diff --git a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/TaskRepositoryImpl.java b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/TaskRepositoryImpl.java index 20257fd..d89fd8b 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/TaskRepositoryImpl.java +++ b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/TaskRepositoryImpl.java @@ -1,12 +1,14 @@ package net.beetechgroup.beetask.frameworks.persistence.repository; import java.util.List; +import java.util.Objects; import java.time.LocalDateTime; import io.quarkus.hibernate.orm.panache.PanacheRepository; import jakarta.enterprise.context.ApplicationScoped; import jakarta.transaction.Transactional; import net.beetechgroup.beetask.entities.task.Task; +import net.beetechgroup.beetask.entities.task.TaskStatus; import net.beetechgroup.beetask.frameworks.persistence.entities.TaskEntity; import net.beetechgroup.beetask.frameworks.persistence.mapper.TaskEntityMapper; import net.beetechgroup.beetask.usecase.exceptions.TaskNotFoundException; @@ -19,7 +21,7 @@ public class TaskRepositoryImpl implements TaskRepository, PanacheRepository findTasksWorkedByUserInPeriod(String email, LocalDateTime start, LocalDateTime end) { - 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(); + return find("user.email = ?1", email).list().stream() + .filter(entity -> entity.getHistory().stream().anyMatch(h -> + Objects.nonNull(h.getStartAt()) && + !h.getStartAt().isAfter(end) && + (Objects.isNull(h.getEndAt()) || !h.getEndAt().isBefore(start)))) + .map(TaskEntityMapper::toDomain) + .toList(); } @Override public List findTasksFinishedByUserInPeriod(String email, LocalDateTime start, LocalDateTime end) { - 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(); + return find("user.email = ?1 and status = ?2 and finishedAt >= ?3 and finishedAt <= ?4", + email, TaskStatus.COMPLETED, start, end) + .list().stream().map(TaskEntityMapper::toDomain).toList(); } @Override public List findTasksByUser(String email) { return find("user.email = ?1", email).list().stream().map(TaskEntityMapper::toDomain).toList(); } -} \ No newline at end of file +} 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 index 1113b12..d83a94e 100644 --- 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 @@ -10,6 +10,7 @@ import net.beetechgroup.beetask.frameworks.persistence.mapper.UserOrganizationEn import net.beetechgroup.beetask.usecase.repository.UserOrganizationRepository; import java.util.List; +import java.util.Objects; import java.util.Optional; @ApplicationScoped @@ -47,9 +48,8 @@ public class UserOrganizationRepositoryImpl implements UserOrganizationRepositor @Transactional public void save(UserOrganization userOrganization) { UserOrganizationEntity entity = UserOrganizationEntityMapper.toEntity(userOrganization); - if (entity.getUser().getId() != null && entity.getOrganization().getId() != null) { - // Find existing by user and org to update instead of creating new if it exists - Optional existing = find("user.id = ?1 and organization.id = ?2", + if (Objects.nonNull(entity.getUser().getId()) && Objects.nonNull(entity.getOrganization().getId())) { + Optional existing = find("user.id = ?1 and organization.id = ?2", entity.getUser().getId(), entity.getOrganization().getId()).firstResultOptional(); if (existing.isPresent()) { UserOrganizationEntity existingEntity = existing.get(); 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 index 63af959..4410295 100644 --- 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 @@ -4,6 +4,7 @@ import io.quarkus.hibernate.orm.panache.PanacheRepository; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.transaction.Transactional; +import java.util.Objects; import net.beetechgroup.beetask.entities.User; import net.beetechgroup.beetask.frameworks.persistence.entities.UserEntity; import net.beetechgroup.beetask.frameworks.persistence.mapper.UserEntityMapper; @@ -25,7 +26,7 @@ public class UserRepositoryImpl implements UserRepository, PanacheRepository new TaskHistoryItemInput(h.id(), h.startAt(), h.endAt())) .toList() : null ); 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 index 9ab8ba7..09c8951 100644 --- 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 @@ -8,6 +8,7 @@ import net.beetechgroup.beetask.usecase.repository.UserRepository; import java.time.LocalDateTime; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -69,7 +70,7 @@ public class LoginUseCase { return new LoginOutput( user.getName(), user.getEmail(), - user.getPhoto() != null ? user.getPhoto() : "https://ui-avatars.com/api/?name=" + user.getName().replace(" ", "+") + "&background=random", + Objects.nonNull(user.getPhoto()) ? user.getPhoto() : "https://ui-avatars.com/api/?name=" + user.getName().replace(" ", "+") + "&background=random", token, refreshToken, 3600L, 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 index 2321bef..ccb874a 100644 --- 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 @@ -12,6 +12,7 @@ import org.eclipse.microprofile.jwt.JsonWebToken; import java.time.LocalDateTime; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -33,7 +34,7 @@ public class RefreshTokenUseCase { JsonWebToken token = jwtParser.parse(input.refreshToken()); String email = token.getSubject(); // Or token.getName() / token.getClaim(Claims.upn.name()) - if (email == null) { + if (Objects.isNull(email)) { throw new RuntimeException("Invalid refresh token: email not found"); } @@ -77,7 +78,7 @@ public class RefreshTokenUseCase { return new LoginOutput( user.getName(), user.getEmail(), - user.getPhoto() != null ? user.getPhoto() : "https://ui-avatars.com/api/?name=" + user.getName().replace(" ", "+") + "&background=random", + Objects.nonNull(user.getPhoto()) ? user.getPhoto() : "https://ui-avatars.com/api/?name=" + user.getName().replace(" ", "+") + "&background=random", newToken, newRefreshToken, 3600L, diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/dashboard/DashboardUseCase.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/dashboard/DashboardUseCase.java index eef4365..f7ea128 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/usecase/dashboard/DashboardUseCase.java +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/dashboard/DashboardUseCase.java @@ -7,6 +7,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import net.beetechgroup.beetask.entities.task.Task; import net.beetechgroup.beetask.entities.task.TaskHistoryItem; @@ -22,24 +23,20 @@ public class DashboardUseCase { } public DashboardOutput execute(DashboardInput input) { - // 1. Fetch tasks worked by user in period List workedTasks = taskRepository.findTasksWorkedByUserInPeriod( input.userEmail(), input.startDate(), input.endDate() ); - // 2. Fetch tasks finished by user in period List finishedTasks = taskRepository.findTasksFinishedByUserInPeriod( input.userEmail(), input.startDate(), input.endDate() ); - // 3. Yesterday's tasks LocalDateTime startOfYesterday = LocalDateTime.now().minusDays(1).with(LocalTime.MIN); LocalDateTime endOfYesterday = LocalDateTime.now().minusDays(1).with(LocalTime.MAX); List yesterdayTasks = taskRepository.findTasksWorkedByUserInPeriod( input.userEmail(), startOfYesterday, endOfYesterday ); - // Calculate total minutes and project stats long totalMinutes = 0; Map projectStatsMap = new HashMap<>(); @@ -51,14 +48,14 @@ public class DashboardUseCase { totalMinutes += taskMinutesInPeriod; - if (task.getProject() != null) { + if (Objects.nonNull(task.getProject())) { Long projectId = task.getProject().getId(); - DashboardProjectStats stats = projectStatsMap.getOrDefault(projectId, + DashboardProjectStats stats = projectStatsMap.getOrDefault(projectId, new DashboardProjectStats(projectId, task.getProject().getName(), 0L)); - + projectStatsMap.put(projectId, new DashboardProjectStats( - projectId, - stats.projectName(), + projectId, + stats.projectName(), stats.totalMinutes() + taskMinutesInPeriod )); } @@ -73,16 +70,15 @@ public class DashboardUseCase { } private boolean isWithinPeriod(TaskHistoryItem item, LocalDateTime start, LocalDateTime end) { - if (item.getStartAt() == null) return false; - // If it started after end or ended before start, it's out + if (Objects.isNull(item.getStartAt())) return false; if (item.getStartAt().isAfter(end)) return false; - if (item.getEndAt() != null && item.getEndAt().isBefore(start)) return false; + if (Objects.nonNull(item.getEndAt()) && item.getEndAt().isBefore(start)) return false; return true; } private long calculateMinutesInRange(TaskHistoryItem item, LocalDateTime start, LocalDateTime end) { LocalDateTime effectiveStart = item.getStartAt().isBefore(start) ? start : item.getStartAt(); - LocalDateTime effectiveEnd = item.getEndAt() == null || item.getEndAt().isAfter(end) ? end : item.getEndAt(); + LocalDateTime effectiveEnd = Objects.isNull(item.getEndAt()) || item.getEndAt().isAfter(end) ? end : item.getEndAt(); if (effectiveStart.isAfter(effectiveEnd)) return 0; diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/task/create/CreateTaskMapper.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/task/create/CreateTaskMapper.java index b366238..d93d096 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/usecase/task/create/CreateTaskMapper.java +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/task/create/CreateTaskMapper.java @@ -1,5 +1,6 @@ package net.beetechgroup.beetask.usecase.task.create; +import java.util.Objects; import net.beetechgroup.beetask.entities.task.Task; import net.beetechgroup.beetask.entities.task.TaskHistoryItem; @@ -10,7 +11,7 @@ public class CreateTaskMapper { task.getTitle(), task.getDescription(), task.getStatus(), - task.getProject() != null ? task.getProject().getName() : null, + Objects.nonNull(task.getProject()) ? task.getProject().getName() : null, task.getFinishedAt(), task.getHistory().stream().map(CreateTaskMapper::toTaskHistoryItemOutput).toList()); } diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/task/create/CreateTaskUseCase.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/task/create/CreateTaskUseCase.java index 61a2762..c6c1732 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/usecase/task/create/CreateTaskUseCase.java +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/task/create/CreateTaskUseCase.java @@ -1,5 +1,6 @@ package net.beetechgroup.beetask.usecase.task.create; +import java.util.Objects; import net.beetechgroup.beetask.entities.Project; import net.beetechgroup.beetask.entities.task.Task; import net.beetechgroup.beetask.usecase.repository.ProjectRepository; @@ -23,7 +24,7 @@ public class CreateTaskUseCase { task.setDescription(input.description()); task.setStatus(input.status()); - if (input.projectId() != null) { + if (Objects.nonNull(input.projectId())) { Project project = projectRepository.findProjectById(input.projectId()) .orElseThrow(() -> new IllegalArgumentException("Projeto não encontrado com ID: " + input.projectId())); task.setProject(project); diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/task/start/StartTaskUseCase.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/task/start/StartTaskUseCase.java index b536802..4a94ede 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/usecase/task/start/StartTaskUseCase.java +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/task/start/StartTaskUseCase.java @@ -1,12 +1,11 @@ package net.beetechgroup.beetask.usecase.task.start; -import net.beetechgroup.beetask.entities.User; +import java.util.Objects; import net.beetechgroup.beetask.entities.task.Task; 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; -import net.beetechgroup.beetask.usecase.exceptions.UserNotFoundException; public class StartTaskUseCase { @@ -20,11 +19,11 @@ public class StartTaskUseCase { public CreateTaskOutput execute(StartTaskInput input) { Task task = taskRepository.findTaskById(input.id()); - - if (task.getUser() != null && !task.getUser().getEmail().equals(input.userEmail())) { + + if (Objects.nonNull(task.getUser()) && !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)); } diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/task/update/UpdateTaskUseCase.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/task/update/UpdateTaskUseCase.java index baae9f0..7eafc15 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/usecase/task/update/UpdateTaskUseCase.java +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/task/update/UpdateTaskUseCase.java @@ -1,6 +1,7 @@ package net.beetechgroup.beetask.usecase.task.update; import java.util.List; +import java.util.Objects; import net.beetechgroup.beetask.entities.Project; import net.beetechgroup.beetask.entities.task.Task; import net.beetechgroup.beetask.entities.task.TaskHistoryItem; @@ -23,8 +24,8 @@ public class UpdateTaskUseCase { public CreateTaskOutput execute(UpdateTaskInput input) { Task task = taskRepository.findTaskById(input.id()); - - if (task.getUser() == null) { + + if (Objects.isNull(task.getUser())) { task.setUser(userRepository.findByEmail(input.userEmail()) .orElseThrow(() -> new IllegalArgumentException("Usuário não encontrado: " + input.userEmail()))); } @@ -33,7 +34,7 @@ public class UpdateTaskUseCase { task.setDescription(input.description()); task.setStatus(input.status()); - if (input.projectId() != null) { + if (Objects.nonNull(input.projectId())) { Project project = projectRepository.findProjectById(input.projectId()) .orElseThrow(() -> new IllegalArgumentException("Projeto não encontrado com ID: " + input.projectId())); task.setProject(project); @@ -41,9 +42,9 @@ public class UpdateTaskUseCase { task.setProject(null); } - if (input.history() != null) { + if (Objects.nonNull(input.history())) { List updatedHistory = input.history().stream().map(hInput -> { - if (hInput.id() != null) { + if (Objects.nonNull(hInput.id())) { return task.getHistory().stream() .filter(existing -> existing.getId().equals(hInput.id())) .findFirst() @@ -65,7 +66,7 @@ public class UpdateTaskUseCase { return newItem; } }).toList(); - + task.getHistory().clear(); task.getHistory().addAll(updatedHistory); } diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/user/profile/GetUserProfileUseCase.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/user/profile/GetUserProfileUseCase.java index a41d720..53bf753 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/usecase/user/profile/GetUserProfileUseCase.java +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/user/profile/GetUserProfileUseCase.java @@ -5,6 +5,7 @@ import net.beetechgroup.beetask.entities.organization.UserOrganization; import net.beetechgroup.beetask.usecase.repository.UserRepository; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; @@ -42,7 +43,7 @@ public class GetUserProfileUseCase { return new UserProfileOutput( user.getName(), user.getEmail(), - user.getPhoto() != null ? user.getPhoto() : "https://ui-avatars.com/api/?name=" + user.getName().replace(" ", "+") + "&background=random", + Objects.nonNull(user.getPhoto()) ? user.getPhoto() : "https://ui-avatars.com/api/?name=" + user.getName().replace(" ", "+") + "&background=random", orgs ); }