chore: enforce coding standards and add project guidelines
- 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 <noreply@anthropic.com>
This commit is contained in:
parent
6800688284
commit
1868d069c2
26 changed files with 363 additions and 75 deletions
92
.claude/skills/new-task.md
Normal file
92
.claude/skills/new-task.md
Normal file
|
|
@ -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 <número> --repo <owner/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: `<prefixo><descricao-em-kebab-case>`
|
||||||
|
|
||||||
|
```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: `<tipo>: <descrição em inglês no imperativo>`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add <arquivos específicos>
|
||||||
|
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 <nome-da-branch>
|
||||||
|
gh pr create \
|
||||||
|
--title "<tipo>: <descrição curta em inglês>" \
|
||||||
|
--body "$(cat <<'EOF'
|
||||||
|
## Descrição
|
||||||
|
...
|
||||||
|
|
||||||
|
## Tipo de mudança
|
||||||
|
- [x] Nova funcionalidade
|
||||||
|
|
||||||
|
## Issue relacionada
|
||||||
|
Fecha #<número>
|
||||||
|
|
||||||
|
## 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`.
|
||||||
38
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
38
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
## Descrição
|
||||||
|
|
||||||
|
<!-- Descreva de forma clara e objetiva o que foi implementado, corrigido ou alterado neste PR. -->
|
||||||
|
|
||||||
|
## Tipo de mudança
|
||||||
|
|
||||||
|
- [ ] Nova funcionalidade (`feat`)
|
||||||
|
- [ ] Correção de bug (`fix`)
|
||||||
|
- [ ] Melhoria/refatoração (`chore`)
|
||||||
|
- [ ] Documentação (`docs`)
|
||||||
|
- [ ] Testes (`test`)
|
||||||
|
|
||||||
|
## Issue relacionada
|
||||||
|
|
||||||
|
Fecha #<!-- número da issue -->
|
||||||
|
|
||||||
|
## O que foi feito
|
||||||
|
|
||||||
|
<!-- Liste as principais mudanças realizadas: -->
|
||||||
|
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-
|
||||||
|
|
||||||
|
## Como testar
|
||||||
|
|
||||||
|
<!-- Descreva o passo a passo para validar as mudanças: -->
|
||||||
|
|
||||||
|
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
|
||||||
145
CLAUDE.md
Normal file
145
CLAUDE.md
Normal file
|
|
@ -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: `<tipo>: <descrição em inglês no imperativo>`
|
||||||
|
|
||||||
|
```
|
||||||
|
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 #<número>`
|
||||||
|
|
||||||
|
## 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
|
||||||
|
```
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package net.beetechgroup.beetask.entities;
|
package net.beetechgroup.beetask.entities;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import net.beetechgroup.beetask.entities.organization.Organization;
|
import net.beetechgroup.beetask.entities.organization.Organization;
|
||||||
|
|
||||||
|
|
@ -47,7 +48,7 @@ public class Project {
|
||||||
return "Project{" +
|
return "Project{" +
|
||||||
"id=" + id +
|
"id=" + id +
|
||||||
", name='" + name + '\'' +
|
", name='" + name + '\'' +
|
||||||
", organization=" + (organization != null ? organization.getName() : "null") +
|
", organization=" + (Objects.nonNull(organization) ? organization.getName() : "null") +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package net.beetechgroup.beetask.entities.task;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import net.beetechgroup.beetask.entities.Project;
|
import net.beetechgroup.beetask.entities.Project;
|
||||||
import net.beetechgroup.beetask.entities.User;
|
import net.beetechgroup.beetask.entities.User;
|
||||||
|
|
@ -48,9 +49,6 @@ public class Task {
|
||||||
public void setStatus(TaskStatus status) {
|
public void setStatus(TaskStatus status) {
|
||||||
if (this.status == status) return;
|
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) {
|
if (status == TaskStatus.COMPLETED) {
|
||||||
this.finishedAt = LocalDateTime.now();
|
this.finishedAt = LocalDateTime.now();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -99,14 +97,14 @@ public class Task {
|
||||||
private TaskHistoryItem getCurrentRunningItem() {
|
private TaskHistoryItem getCurrentRunningItem() {
|
||||||
return history.stream()
|
return history.stream()
|
||||||
.filter(TaskHistoryItem::isRunning)
|
.filter(TaskHistoryItem::isRunning)
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start() {
|
public void start() {
|
||||||
TaskHistoryItem current = getCurrentRunningItem();
|
TaskHistoryItem current = getCurrentRunningItem();
|
||||||
|
|
||||||
if (current != null) {
|
if (Objects.nonNull(current)) {
|
||||||
throw new IllegalStateException("Tarefa já está em execução");
|
throw new IllegalStateException("Tarefa já está em execução");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -117,14 +115,12 @@ public class Task {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stop() {
|
public void stop() {
|
||||||
TaskHistoryItem current = getCurrentRunningItem();
|
TaskHistoryItem current = getCurrentRunningItem();
|
||||||
|
|
||||||
if (current == null) {
|
if (Objects.isNull(current)) {
|
||||||
throw new IllegalStateException("Task não está em execução");
|
throw new IllegalStateException("Task não está em execução");
|
||||||
|
}
|
||||||
|
|
||||||
|
current.setEndAt(LocalDateTime.now());
|
||||||
}
|
}
|
||||||
|
|
||||||
current.setEndAt(LocalDateTime.now());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package net.beetechgroup.beetask.entities.task;
|
package net.beetechgroup.beetask.entities.task;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public class TaskHistoryItem {
|
public class TaskHistoryItem {
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
@ -32,6 +33,6 @@ public class TaskHistoryItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isRunning() {
|
public boolean isRunning() {
|
||||||
return endAt == null;
|
return Objects.isNull(endAt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package net.beetechgroup.beetask.frameworks.persistence.mapper;
|
package net.beetechgroup.beetask.frameworks.persistence.mapper;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
import net.beetechgroup.beetask.entities.organization.Organization;
|
import net.beetechgroup.beetask.entities.organization.Organization;
|
||||||
import net.beetechgroup.beetask.frameworks.persistence.entities.OrganizationEntity;
|
import net.beetechgroup.beetask.frameworks.persistence.entities.OrganizationEntity;
|
||||||
|
|
||||||
|
|
@ -12,7 +13,7 @@ public class OrganizationEntityMapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Organization toDomain(OrganizationEntity entity) {
|
public static Organization toDomain(OrganizationEntity entity) {
|
||||||
if (entity == null) return null;
|
if (Objects.isNull(entity)) return null;
|
||||||
|
|
||||||
Organization organization = new Organization();
|
Organization organization = new Organization();
|
||||||
organization.setId(entity.getId());
|
organization.setId(entity.getId());
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
package net.beetechgroup.beetask.frameworks.persistence.mapper;
|
package net.beetechgroup.beetask.frameworks.persistence.mapper;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
import net.beetechgroup.beetask.entities.Project;
|
import net.beetechgroup.beetask.entities.Project;
|
||||||
import net.beetechgroup.beetask.frameworks.persistence.entities.ProjectEntity;
|
import net.beetechgroup.beetask.frameworks.persistence.entities.ProjectEntity;
|
||||||
|
|
||||||
public class ProjectEntityMapper {
|
public class ProjectEntityMapper {
|
||||||
|
|
||||||
public static ProjectEntity toEntity(Project project) {
|
public static ProjectEntity toEntity(Project project) {
|
||||||
if (project == null) return null;
|
if (Objects.isNull(project)) return null;
|
||||||
ProjectEntity entity = new ProjectEntity();
|
ProjectEntity entity = new ProjectEntity();
|
||||||
entity.setId(project.getId());
|
entity.setId(project.getId());
|
||||||
entity.setName(project.getName());
|
entity.setName(project.getName());
|
||||||
|
|
@ -15,7 +16,7 @@ public class ProjectEntityMapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Project toDomain(ProjectEntity entity) {
|
public static Project toDomain(ProjectEntity entity) {
|
||||||
if (entity == null) return null;
|
if (Objects.isNull(entity)) return null;
|
||||||
|
|
||||||
Project project = new Project();
|
Project project = new Project();
|
||||||
project.setId(entity.getId());
|
project.setId(entity.getId());
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package net.beetechgroup.beetask.frameworks.persistence.mapper;
|
package net.beetechgroup.beetask.frameworks.persistence.mapper;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import net.beetechgroup.beetask.entities.task.Task;
|
import net.beetechgroup.beetask.entities.task.Task;
|
||||||
import net.beetechgroup.beetask.frameworks.persistence.entities.TaskEntity;
|
import net.beetechgroup.beetask.frameworks.persistence.entities.TaskEntity;
|
||||||
|
|
@ -26,7 +27,7 @@ public class TaskEntityMapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Task toDomain(TaskEntity entity) {
|
public static Task toDomain(TaskEntity entity) {
|
||||||
if (entity == null) return null;
|
if (Objects.isNull(entity)) return null;
|
||||||
|
|
||||||
Task task = new Task();
|
Task task = new Task();
|
||||||
task.setId(entity.getId());
|
task.setId(entity.getId());
|
||||||
|
|
@ -40,4 +41,4 @@ public class TaskEntityMapper {
|
||||||
|
|
||||||
return task;
|
return task;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package net.beetechgroup.beetask.frameworks.persistence.mapper;
|
package net.beetechgroup.beetask.frameworks.persistence.mapper;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
import net.beetechgroup.beetask.entities.task.TaskHistoryItem;
|
import net.beetechgroup.beetask.entities.task.TaskHistoryItem;
|
||||||
import net.beetechgroup.beetask.frameworks.persistence.entities.TaskHistoryItemEntity;
|
import net.beetechgroup.beetask.frameworks.persistence.entities.TaskHistoryItemEntity;
|
||||||
|
|
||||||
|
|
@ -13,7 +14,7 @@ public class TaskHistoryItemMapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TaskHistoryItem toDomain(TaskHistoryItemEntity entity) {
|
public static TaskHistoryItem toDomain(TaskHistoryItemEntity entity) {
|
||||||
if (entity == null) return null;
|
if (Objects.isNull(entity)) return null;
|
||||||
|
|
||||||
TaskHistoryItem item = new TaskHistoryItem();
|
TaskHistoryItem item = new TaskHistoryItem();
|
||||||
item.setId(entity.getId());
|
item.setId(entity.getId());
|
||||||
|
|
@ -21,4 +22,4 @@ public class TaskHistoryItemMapper {
|
||||||
item.setEndAt(entity.getEndAt());
|
item.setEndAt(entity.getEndAt());
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
package net.beetechgroup.beetask.frameworks.persistence.mapper;
|
package net.beetechgroup.beetask.frameworks.persistence.mapper;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
import net.beetechgroup.beetask.entities.User;
|
import net.beetechgroup.beetask.entities.User;
|
||||||
import net.beetechgroup.beetask.frameworks.persistence.entities.UserEntity;
|
import net.beetechgroup.beetask.frameworks.persistence.entities.UserEntity;
|
||||||
|
|
||||||
public class UserEntityMapper {
|
public class UserEntityMapper {
|
||||||
|
|
||||||
public static UserEntity toEntity(User user) {
|
public static UserEntity toEntity(User user) {
|
||||||
if (user == null) return null;
|
if (Objects.isNull(user)) return null;
|
||||||
UserEntity entity = new UserEntity();
|
UserEntity entity = new UserEntity();
|
||||||
entity.setId(user.getId());
|
entity.setId(user.getId());
|
||||||
entity.setName(user.getName());
|
entity.setName(user.getName());
|
||||||
|
|
@ -17,7 +18,7 @@ public class UserEntityMapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static User toDomain(UserEntity entity) {
|
public static User toDomain(UserEntity entity) {
|
||||||
if (entity == null) return null;
|
if (Objects.isNull(entity)) return null;
|
||||||
User user = new User();
|
User user = new User();
|
||||||
user.setId(entity.getId());
|
user.setId(entity.getId());
|
||||||
user.setName(entity.getName());
|
user.setName(entity.getName());
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
package net.beetechgroup.beetask.frameworks.persistence.mapper;
|
package net.beetechgroup.beetask.frameworks.persistence.mapper;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
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;
|
||||||
|
|
||||||
public class UserOrganizationEntityMapper {
|
public class UserOrganizationEntityMapper {
|
||||||
|
|
||||||
public static UserOrganization toDomain(UserOrganizationEntity entity) {
|
public static UserOrganization toDomain(UserOrganizationEntity entity) {
|
||||||
if (entity == null) return null;
|
if (Objects.isNull(entity)) return null;
|
||||||
UserOrganization domain = new UserOrganization();
|
UserOrganization domain = new UserOrganization();
|
||||||
domain.setUser(UserEntityMapper.toDomain(entity.getUser()));
|
domain.setUser(UserEntityMapper.toDomain(entity.getUser()));
|
||||||
domain.setOrganization(OrganizationEntityMapper.toDomain(entity.getOrganization()));
|
domain.setOrganization(OrganizationEntityMapper.toDomain(entity.getOrganization()));
|
||||||
|
|
@ -16,7 +17,7 @@ public class UserOrganizationEntityMapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static UserOrganizationEntity toEntity(UserOrganization domain) {
|
public static UserOrganizationEntity toEntity(UserOrganization domain) {
|
||||||
if (domain == null) return null;
|
if (Objects.isNull(domain)) return null;
|
||||||
UserOrganizationEntity entity = new UserOrganizationEntity();
|
UserOrganizationEntity entity = new UserOrganizationEntity();
|
||||||
entity.setUser(UserEntityMapper.toEntity(domain.getUser()));
|
entity.setUser(UserEntityMapper.toEntity(domain.getUser()));
|
||||||
entity.setOrganization(OrganizationEntityMapper.toEntity(domain.getOrganization()));
|
entity.setOrganization(OrganizationEntityMapper.toEntity(domain.getOrganization()));
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ public class OrganizationRepositoryImpl implements OrganizationRepository, Panac
|
||||||
@Transactional
|
@Transactional
|
||||||
public Organization saveOrganization(Organization organization) {
|
public Organization saveOrganization(Organization organization) {
|
||||||
OrganizationEntity entity = OrganizationEntityMapper.toEntity(organization);
|
OrganizationEntity entity = OrganizationEntityMapper.toEntity(organization);
|
||||||
if (entity.getId() == null) {
|
if (Objects.isNull(entity.getId())) {
|
||||||
persist(entity);
|
persist(entity);
|
||||||
} else {
|
} else {
|
||||||
entity = getEntityManager().merge(entity);
|
entity = getEntityManager().merge(entity);
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import net.beetechgroup.beetask.frameworks.persistence.mapper.ProjectEntityMappe
|
||||||
import net.beetechgroup.beetask.usecase.repository.ProjectRepository;
|
import net.beetechgroup.beetask.usecase.repository.ProjectRepository;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
|
|
@ -18,7 +19,7 @@ public class ProjectRepositoryImpl implements ProjectRepository, PanacheReposito
|
||||||
@Transactional
|
@Transactional
|
||||||
public Project saveProject(Project project) {
|
public Project saveProject(Project project) {
|
||||||
ProjectEntity entity = ProjectEntityMapper.toEntity(project);
|
ProjectEntity entity = ProjectEntityMapper.toEntity(project);
|
||||||
if (entity.getId() == null) {
|
if (Objects.isNull(entity.getId())) {
|
||||||
persist(entity);
|
persist(entity);
|
||||||
} else {
|
} else {
|
||||||
entity = getEntityManager().merge(entity);
|
entity = getEntityManager().merge(entity);
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
package net.beetechgroup.beetask.frameworks.persistence.repository;
|
package net.beetechgroup.beetask.frameworks.persistence.repository;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
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 jakarta.transaction.Transactional;
|
||||||
import net.beetechgroup.beetask.entities.task.Task;
|
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.entities.TaskEntity;
|
||||||
import net.beetechgroup.beetask.frameworks.persistence.mapper.TaskEntityMapper;
|
import net.beetechgroup.beetask.frameworks.persistence.mapper.TaskEntityMapper;
|
||||||
import net.beetechgroup.beetask.usecase.exceptions.TaskNotFoundException;
|
import net.beetechgroup.beetask.usecase.exceptions.TaskNotFoundException;
|
||||||
|
|
@ -19,7 +21,7 @@ public class TaskRepositoryImpl implements TaskRepository, PanacheRepository<Tas
|
||||||
@Transactional
|
@Transactional
|
||||||
public Task saveTask(Task task) {
|
public Task saveTask(Task task) {
|
||||||
TaskEntity entity = TaskEntityMapper.toEntity(task);
|
TaskEntity entity = TaskEntityMapper.toEntity(task);
|
||||||
if (entity.getId() == null) {
|
if (Objects.isNull(entity.getId())) {
|
||||||
persist(entity);
|
persist(entity);
|
||||||
} else {
|
} else {
|
||||||
entity = getEntityManager().merge(entity);
|
entity = getEntityManager().merge(entity);
|
||||||
|
|
@ -41,21 +43,24 @@ public class TaskRepositoryImpl implements TaskRepository, PanacheRepository<Tas
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Task> findTasksWorkedByUserInPeriod(String email, LocalDateTime start, LocalDateTime end) {
|
public List<Task> findTasksWorkedByUserInPeriod(String email, LocalDateTime start, LocalDateTime end) {
|
||||||
return find("select distinct t from TaskEntity t join t.history h where t.user.email = ?1 " +
|
return find("user.email = ?1", email).list().stream()
|
||||||
"and h.startAt <= ?3 and (h.endAt is null or h.endAt >= ?2)",
|
.filter(entity -> entity.getHistory().stream().anyMatch(h ->
|
||||||
email, start, end).list().stream().map(TaskEntityMapper::toDomain).toList();
|
Objects.nonNull(h.getStartAt()) &&
|
||||||
|
!h.getStartAt().isAfter(end) &&
|
||||||
|
(Objects.isNull(h.getEndAt()) || !h.getEndAt().isBefore(start))))
|
||||||
|
.map(TaskEntityMapper::toDomain)
|
||||||
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Task> findTasksFinishedByUserInPeriod(String email, LocalDateTime start, LocalDateTime end) {
|
public List<Task> findTasksFinishedByUserInPeriod(String email, LocalDateTime start, LocalDateTime end) {
|
||||||
return find("select distinct t from TaskEntity t where t.user.email = ?1 " +
|
return find("user.email = ?1 and status = ?2 and finishedAt >= ?3 and finishedAt <= ?4",
|
||||||
"and t.status = net.beetechgroup.beetask.entities.task.TaskStatus.COMPLETED " +
|
email, TaskStatus.COMPLETED, start, end)
|
||||||
"and t.finishedAt >= ?2 and t.finishedAt <= ?3",
|
.list().stream().map(TaskEntityMapper::toDomain).toList();
|
||||||
email, start, end).list().stream().map(TaskEntityMapper::toDomain).toList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Task> findTasksByUser(String email) {
|
public List<Task> findTasksByUser(String email) {
|
||||||
return find("user.email = ?1", email).list().stream().map(TaskEntityMapper::toDomain).toList();
|
return find("user.email = ?1", email).list().stream().map(TaskEntityMapper::toDomain).toList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import net.beetechgroup.beetask.frameworks.persistence.mapper.UserOrganizationEn
|
||||||
import net.beetechgroup.beetask.usecase.repository.UserOrganizationRepository;
|
import net.beetechgroup.beetask.usecase.repository.UserOrganizationRepository;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
|
|
@ -47,9 +48,8 @@ public class UserOrganizationRepositoryImpl implements UserOrganizationRepositor
|
||||||
@Transactional
|
@Transactional
|
||||||
public void save(UserOrganization userOrganization) {
|
public void save(UserOrganization userOrganization) {
|
||||||
UserOrganizationEntity entity = UserOrganizationEntityMapper.toEntity(userOrganization);
|
UserOrganizationEntity entity = UserOrganizationEntityMapper.toEntity(userOrganization);
|
||||||
if (entity.getUser().getId() != null && entity.getOrganization().getId() != null) {
|
if (Objects.nonNull(entity.getUser().getId()) && Objects.nonNull(entity.getOrganization().getId())) {
|
||||||
// Find existing by user and org to update instead of creating new if it exists
|
Optional<UserOrganizationEntity> existing = find("user.id = ?1 and organization.id = ?2",
|
||||||
Optional<UserOrganizationEntity> existing = find("user.id = ?1 and organization.id = ?2",
|
|
||||||
entity.getUser().getId(), entity.getOrganization().getId()).firstResultOptional();
|
entity.getUser().getId(), entity.getOrganization().getId()).firstResultOptional();
|
||||||
if (existing.isPresent()) {
|
if (existing.isPresent()) {
|
||||||
UserOrganizationEntity existingEntity = existing.get();
|
UserOrganizationEntity existingEntity = existing.get();
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import io.quarkus.hibernate.orm.panache.PanacheRepository;
|
||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import jakarta.transaction.Transactional;
|
import jakarta.transaction.Transactional;
|
||||||
|
import java.util.Objects;
|
||||||
import net.beetechgroup.beetask.entities.User;
|
import net.beetechgroup.beetask.entities.User;
|
||||||
import net.beetechgroup.beetask.frameworks.persistence.entities.UserEntity;
|
import net.beetechgroup.beetask.frameworks.persistence.entities.UserEntity;
|
||||||
import net.beetechgroup.beetask.frameworks.persistence.mapper.UserEntityMapper;
|
import net.beetechgroup.beetask.frameworks.persistence.mapper.UserEntityMapper;
|
||||||
|
|
@ -25,7 +26,7 @@ public class UserRepositoryImpl implements UserRepository, PanacheRepository<Use
|
||||||
@Transactional
|
@Transactional
|
||||||
public User save(User user) {
|
public User save(User user) {
|
||||||
UserEntity entity = UserEntityMapper.toEntity(user);
|
UserEntity entity = UserEntityMapper.toEntity(user);
|
||||||
if (entity.getId() == null) {
|
if (Objects.isNull(entity.getId())) {
|
||||||
persist(entity);
|
persist(entity);
|
||||||
} else {
|
} else {
|
||||||
entity = getEntityManager().merge(entity);
|
entity = getEntityManager().merge(entity);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package net.beetechgroup.beetask.interfaceadapters.controllers.task;
|
package net.beetechgroup.beetask.interfaceadapters.controllers.task;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
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;
|
||||||
import net.beetechgroup.beetask.usecase.task.start.StartTaskInput;
|
import net.beetechgroup.beetask.usecase.task.start.StartTaskInput;
|
||||||
|
|
@ -47,7 +48,7 @@ public class TaskControllerMapper {
|
||||||
request.status(),
|
request.status(),
|
||||||
request.projectId(),
|
request.projectId(),
|
||||||
userEmail,
|
userEmail,
|
||||||
request.history() != null ? request.history().stream()
|
Objects.nonNull(request.history()) ? request.history().stream()
|
||||||
.map(h -> new TaskHistoryItemInput(h.id(), h.startAt(), h.endAt()))
|
.map(h -> new TaskHistoryItemInput(h.id(), h.startAt(), h.endAt()))
|
||||||
.toList() : null
|
.toList() : null
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import net.beetechgroup.beetask.usecase.repository.UserRepository;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
@ -69,7 +70,7 @@ public class LoginUseCase {
|
||||||
return new LoginOutput(
|
return new LoginOutput(
|
||||||
user.getName(),
|
user.getName(),
|
||||||
user.getEmail(),
|
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,
|
token,
|
||||||
refreshToken,
|
refreshToken,
|
||||||
3600L,
|
3600L,
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import org.eclipse.microprofile.jwt.JsonWebToken;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
@ -33,7 +34,7 @@ public class RefreshTokenUseCase {
|
||||||
JsonWebToken token = jwtParser.parse(input.refreshToken());
|
JsonWebToken token = jwtParser.parse(input.refreshToken());
|
||||||
String email = token.getSubject(); // Or token.getName() / token.getClaim(Claims.upn.name())
|
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");
|
throw new RuntimeException("Invalid refresh token: email not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,7 +78,7 @@ public class RefreshTokenUseCase {
|
||||||
return new LoginOutput(
|
return new LoginOutput(
|
||||||
user.getName(),
|
user.getName(),
|
||||||
user.getEmail(),
|
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,
|
newToken,
|
||||||
newRefreshToken,
|
newRefreshToken,
|
||||||
3600L,
|
3600L,
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import net.beetechgroup.beetask.entities.task.Task;
|
import net.beetechgroup.beetask.entities.task.Task;
|
||||||
import net.beetechgroup.beetask.entities.task.TaskHistoryItem;
|
import net.beetechgroup.beetask.entities.task.TaskHistoryItem;
|
||||||
|
|
@ -22,24 +23,20 @@ public class DashboardUseCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public DashboardOutput execute(DashboardInput input) {
|
public DashboardOutput execute(DashboardInput input) {
|
||||||
// 1. Fetch tasks worked by user in period
|
|
||||||
List<Task> workedTasks = taskRepository.findTasksWorkedByUserInPeriod(
|
List<Task> workedTasks = taskRepository.findTasksWorkedByUserInPeriod(
|
||||||
input.userEmail(), input.startDate(), input.endDate()
|
input.userEmail(), input.startDate(), input.endDate()
|
||||||
);
|
);
|
||||||
|
|
||||||
// 2. Fetch tasks finished by user in period
|
|
||||||
List<Task> finishedTasks = taskRepository.findTasksFinishedByUserInPeriod(
|
List<Task> finishedTasks = taskRepository.findTasksFinishedByUserInPeriod(
|
||||||
input.userEmail(), input.startDate(), input.endDate()
|
input.userEmail(), input.startDate(), input.endDate()
|
||||||
);
|
);
|
||||||
|
|
||||||
// 3. Yesterday's tasks
|
|
||||||
LocalDateTime startOfYesterday = LocalDateTime.now().minusDays(1).with(LocalTime.MIN);
|
LocalDateTime startOfYesterday = LocalDateTime.now().minusDays(1).with(LocalTime.MIN);
|
||||||
LocalDateTime endOfYesterday = LocalDateTime.now().minusDays(1).with(LocalTime.MAX);
|
LocalDateTime endOfYesterday = LocalDateTime.now().minusDays(1).with(LocalTime.MAX);
|
||||||
List<Task> yesterdayTasks = taskRepository.findTasksWorkedByUserInPeriod(
|
List<Task> yesterdayTasks = taskRepository.findTasksWorkedByUserInPeriod(
|
||||||
input.userEmail(), startOfYesterday, endOfYesterday
|
input.userEmail(), startOfYesterday, endOfYesterday
|
||||||
);
|
);
|
||||||
|
|
||||||
// Calculate total minutes and project stats
|
|
||||||
long totalMinutes = 0;
|
long totalMinutes = 0;
|
||||||
Map<Long, DashboardProjectStats> projectStatsMap = new HashMap<>();
|
Map<Long, DashboardProjectStats> projectStatsMap = new HashMap<>();
|
||||||
|
|
||||||
|
|
@ -51,14 +48,14 @@ public class DashboardUseCase {
|
||||||
|
|
||||||
totalMinutes += taskMinutesInPeriod;
|
totalMinutes += taskMinutesInPeriod;
|
||||||
|
|
||||||
if (task.getProject() != null) {
|
if (Objects.nonNull(task.getProject())) {
|
||||||
Long projectId = task.getProject().getId();
|
Long projectId = task.getProject().getId();
|
||||||
DashboardProjectStats stats = projectStatsMap.getOrDefault(projectId,
|
DashboardProjectStats stats = projectStatsMap.getOrDefault(projectId,
|
||||||
new DashboardProjectStats(projectId, task.getProject().getName(), 0L));
|
new DashboardProjectStats(projectId, task.getProject().getName(), 0L));
|
||||||
|
|
||||||
projectStatsMap.put(projectId, new DashboardProjectStats(
|
projectStatsMap.put(projectId, new DashboardProjectStats(
|
||||||
projectId,
|
projectId,
|
||||||
stats.projectName(),
|
stats.projectName(),
|
||||||
stats.totalMinutes() + taskMinutesInPeriod
|
stats.totalMinutes() + taskMinutesInPeriod
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
@ -73,16 +70,15 @@ public class DashboardUseCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isWithinPeriod(TaskHistoryItem item, LocalDateTime start, LocalDateTime end) {
|
private boolean isWithinPeriod(TaskHistoryItem item, LocalDateTime start, LocalDateTime end) {
|
||||||
if (item.getStartAt() == null) return false;
|
if (Objects.isNull(item.getStartAt())) return false;
|
||||||
// If it started after end or ended before start, it's out
|
|
||||||
if (item.getStartAt().isAfter(end)) 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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private long calculateMinutesInRange(TaskHistoryItem item, LocalDateTime start, LocalDateTime end) {
|
private long calculateMinutesInRange(TaskHistoryItem item, LocalDateTime start, LocalDateTime end) {
|
||||||
LocalDateTime effectiveStart = item.getStartAt().isBefore(start) ? start : item.getStartAt();
|
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;
|
if (effectiveStart.isAfter(effectiveEnd)) return 0;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package net.beetechgroup.beetask.usecase.task.create;
|
package net.beetechgroup.beetask.usecase.task.create;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
import net.beetechgroup.beetask.entities.task.Task;
|
import net.beetechgroup.beetask.entities.task.Task;
|
||||||
import net.beetechgroup.beetask.entities.task.TaskHistoryItem;
|
import net.beetechgroup.beetask.entities.task.TaskHistoryItem;
|
||||||
|
|
||||||
|
|
@ -10,7 +11,7 @@ public class CreateTaskMapper {
|
||||||
task.getTitle(),
|
task.getTitle(),
|
||||||
task.getDescription(),
|
task.getDescription(),
|
||||||
task.getStatus(),
|
task.getStatus(),
|
||||||
task.getProject() != null ? task.getProject().getName() : null,
|
Objects.nonNull(task.getProject()) ? task.getProject().getName() : null,
|
||||||
task.getFinishedAt(),
|
task.getFinishedAt(),
|
||||||
task.getHistory().stream().map(CreateTaskMapper::toTaskHistoryItemOutput).toList());
|
task.getHistory().stream().map(CreateTaskMapper::toTaskHistoryItemOutput).toList());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package net.beetechgroup.beetask.usecase.task.create;
|
package net.beetechgroup.beetask.usecase.task.create;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
import net.beetechgroup.beetask.entities.Project;
|
import net.beetechgroup.beetask.entities.Project;
|
||||||
import net.beetechgroup.beetask.entities.task.Task;
|
import net.beetechgroup.beetask.entities.task.Task;
|
||||||
import net.beetechgroup.beetask.usecase.repository.ProjectRepository;
|
import net.beetechgroup.beetask.usecase.repository.ProjectRepository;
|
||||||
|
|
@ -23,7 +24,7 @@ public class CreateTaskUseCase {
|
||||||
task.setDescription(input.description());
|
task.setDescription(input.description());
|
||||||
task.setStatus(input.status());
|
task.setStatus(input.status());
|
||||||
|
|
||||||
if (input.projectId() != null) {
|
if (Objects.nonNull(input.projectId())) {
|
||||||
Project project = projectRepository.findProjectById(input.projectId())
|
Project project = projectRepository.findProjectById(input.projectId())
|
||||||
.orElseThrow(() -> new IllegalArgumentException("Projeto não encontrado com ID: " + input.projectId()));
|
.orElseThrow(() -> new IllegalArgumentException("Projeto não encontrado com ID: " + input.projectId()));
|
||||||
task.setProject(project);
|
task.setProject(project);
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
package net.beetechgroup.beetask.usecase.task.start;
|
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.entities.task.Task;
|
||||||
import net.beetechgroup.beetask.usecase.repository.TaskRepository;
|
import net.beetechgroup.beetask.usecase.repository.TaskRepository;
|
||||||
import net.beetechgroup.beetask.usecase.repository.UserRepository;
|
import net.beetechgroup.beetask.usecase.repository.UserRepository;
|
||||||
import net.beetechgroup.beetask.usecase.task.create.CreateTaskMapper;
|
import net.beetechgroup.beetask.usecase.task.create.CreateTaskMapper;
|
||||||
import net.beetechgroup.beetask.usecase.task.create.CreateTaskOutput;
|
import net.beetechgroup.beetask.usecase.task.create.CreateTaskOutput;
|
||||||
import net.beetechgroup.beetask.usecase.exceptions.UserNotFoundException;
|
|
||||||
|
|
||||||
public class StartTaskUseCase {
|
public class StartTaskUseCase {
|
||||||
|
|
||||||
|
|
@ -20,11 +19,11 @@ public class StartTaskUseCase {
|
||||||
|
|
||||||
public CreateTaskOutput execute(StartTaskInput input) {
|
public CreateTaskOutput execute(StartTaskInput input) {
|
||||||
Task task = taskRepository.findTaskById(input.id());
|
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.");
|
throw new SecurityException("Você não tem permissão para iniciar esta tarefa.");
|
||||||
}
|
}
|
||||||
|
|
||||||
task.start();
|
task.start();
|
||||||
return CreateTaskMapper.toCreateTaskOutput(taskRepository.saveTask(task));
|
return CreateTaskMapper.toCreateTaskOutput(taskRepository.saveTask(task));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package net.beetechgroup.beetask.usecase.task.update;
|
package net.beetechgroup.beetask.usecase.task.update;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import net.beetechgroup.beetask.entities.Project;
|
import net.beetechgroup.beetask.entities.Project;
|
||||||
import net.beetechgroup.beetask.entities.task.Task;
|
import net.beetechgroup.beetask.entities.task.Task;
|
||||||
import net.beetechgroup.beetask.entities.task.TaskHistoryItem;
|
import net.beetechgroup.beetask.entities.task.TaskHistoryItem;
|
||||||
|
|
@ -23,8 +24,8 @@ public class UpdateTaskUseCase {
|
||||||
|
|
||||||
public CreateTaskOutput execute(UpdateTaskInput input) {
|
public CreateTaskOutput execute(UpdateTaskInput input) {
|
||||||
Task task = taskRepository.findTaskById(input.id());
|
Task task = taskRepository.findTaskById(input.id());
|
||||||
|
|
||||||
if (task.getUser() == null) {
|
if (Objects.isNull(task.getUser())) {
|
||||||
task.setUser(userRepository.findByEmail(input.userEmail())
|
task.setUser(userRepository.findByEmail(input.userEmail())
|
||||||
.orElseThrow(() -> new IllegalArgumentException("Usuário não encontrado: " + input.userEmail())));
|
.orElseThrow(() -> new IllegalArgumentException("Usuário não encontrado: " + input.userEmail())));
|
||||||
}
|
}
|
||||||
|
|
@ -33,7 +34,7 @@ public class UpdateTaskUseCase {
|
||||||
task.setDescription(input.description());
|
task.setDescription(input.description());
|
||||||
task.setStatus(input.status());
|
task.setStatus(input.status());
|
||||||
|
|
||||||
if (input.projectId() != null) {
|
if (Objects.nonNull(input.projectId())) {
|
||||||
Project project = projectRepository.findProjectById(input.projectId())
|
Project project = projectRepository.findProjectById(input.projectId())
|
||||||
.orElseThrow(() -> new IllegalArgumentException("Projeto não encontrado com ID: " + input.projectId()));
|
.orElseThrow(() -> new IllegalArgumentException("Projeto não encontrado com ID: " + input.projectId()));
|
||||||
task.setProject(project);
|
task.setProject(project);
|
||||||
|
|
@ -41,9 +42,9 @@ public class UpdateTaskUseCase {
|
||||||
task.setProject(null);
|
task.setProject(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.history() != null) {
|
if (Objects.nonNull(input.history())) {
|
||||||
List<TaskHistoryItem> updatedHistory = input.history().stream().map(hInput -> {
|
List<TaskHistoryItem> updatedHistory = input.history().stream().map(hInput -> {
|
||||||
if (hInput.id() != null) {
|
if (Objects.nonNull(hInput.id())) {
|
||||||
return task.getHistory().stream()
|
return task.getHistory().stream()
|
||||||
.filter(existing -> existing.getId().equals(hInput.id()))
|
.filter(existing -> existing.getId().equals(hInput.id()))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
|
|
@ -65,7 +66,7 @@ public class UpdateTaskUseCase {
|
||||||
return newItem;
|
return newItem;
|
||||||
}
|
}
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
task.getHistory().clear();
|
task.getHistory().clear();
|
||||||
task.getHistory().addAll(updatedHistory);
|
task.getHistory().addAll(updatedHistory);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import net.beetechgroup.beetask.entities.organization.UserOrganization;
|
||||||
import net.beetechgroup.beetask.usecase.repository.UserRepository;
|
import net.beetechgroup.beetask.usecase.repository.UserRepository;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
|
@ -42,7 +43,7 @@ public class GetUserProfileUseCase {
|
||||||
return new UserProfileOutput(
|
return new UserProfileOutput(
|
||||||
user.getName(),
|
user.getName(),
|
||||||
user.getEmail(),
|
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
|
orgs
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue