feat: add task creation board
This commit is contained in:
@ -5,6 +5,7 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import se.rubble.hemhub.task.InvalidTaskException;
|
||||
import se.rubble.hemhub.user.InvalidUserNameException;
|
||||
import se.rubble.hemhub.user.UserNameAlreadyExistsException;
|
||||
|
||||
@ -26,5 +27,11 @@ public class ApiExceptionHandler {
|
||||
"USER_NAME_ALREADY_EXISTS",
|
||||
"En användare med det namnet finns redan."));
|
||||
}
|
||||
|
||||
@ExceptionHandler(InvalidTaskException.class)
|
||||
public ResponseEntity<ApiError> handleInvalidTask(InvalidTaskException exception) {
|
||||
return ResponseEntity.badRequest()
|
||||
.body(new ApiError("INVALID_TASK", exception.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
package se.rubble.hemhub.task;
|
||||
|
||||
public record CreateTaskRequest(String title, String description) {
|
||||
}
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
package se.rubble.hemhub.task;
|
||||
|
||||
public class InvalidTaskException extends RuntimeException {
|
||||
|
||||
public InvalidTaskException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
69
backend/src/main/java/se/rubble/hemhub/task/Task.java
Normal file
69
backend/src/main/java/se/rubble/hemhub/task/Task.java
Normal file
@ -0,0 +1,69 @@
|
||||
package se.rubble.hemhub.task;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
@Entity
|
||||
@Table(name = "task")
|
||||
class Task {
|
||||
|
||||
@Id
|
||||
private UUID id;
|
||||
|
||||
@Column(nullable = false, length = 100)
|
||||
private String title;
|
||||
|
||||
@Column(length = 500)
|
||||
private String description;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false, length = 20)
|
||||
private TaskStatus status;
|
||||
|
||||
@Column(name = "created_at", nullable = false)
|
||||
private Instant createdAt;
|
||||
|
||||
protected Task() {
|
||||
}
|
||||
|
||||
Task(
|
||||
UUID id,
|
||||
String title,
|
||||
String description,
|
||||
TaskStatus status,
|
||||
Instant createdAt) {
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.description = description;
|
||||
this.status = status;
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
TaskStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
Instant getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
package se.rubble.hemhub.task;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/tasks")
|
||||
public class TaskController {
|
||||
|
||||
private final TaskService taskService;
|
||||
|
||||
TaskController(TaskService taskService) {
|
||||
this.taskService = taskService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public List<TaskResponse> findAll() {
|
||||
return taskService.findAll();
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public TaskResponse create(@RequestBody(required = false) CreateTaskRequest request) {
|
||||
return taskService.create(
|
||||
request == null ? null : request.title(),
|
||||
request == null ? null : request.description());
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
package se.rubble.hemhub.task;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
interface TaskRepository extends JpaRepository<Task, UUID> {
|
||||
|
||||
List<Task> findAllByOrderByCreatedAtAscIdAsc();
|
||||
}
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
package se.rubble.hemhub.task;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
public record TaskResponse(
|
||||
UUID id,
|
||||
String title,
|
||||
String description,
|
||||
TaskStatus status,
|
||||
Instant createdAt) {
|
||||
|
||||
static TaskResponse from(Task task) {
|
||||
return new TaskResponse(
|
||||
task.getId(),
|
||||
task.getTitle(),
|
||||
task.getDescription(),
|
||||
task.getStatus(),
|
||||
task.getCreatedAt());
|
||||
}
|
||||
}
|
||||
|
||||
73
backend/src/main/java/se/rubble/hemhub/task/TaskService.java
Normal file
73
backend/src/main/java/se/rubble/hemhub/task/TaskService.java
Normal file
@ -0,0 +1,73 @@
|
||||
package se.rubble.hemhub.task;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
class TaskService {
|
||||
|
||||
private final TaskRepository taskRepository;
|
||||
private final Clock clock;
|
||||
|
||||
@Autowired
|
||||
TaskService(TaskRepository taskRepository) {
|
||||
this(taskRepository, Clock.systemUTC());
|
||||
}
|
||||
|
||||
TaskService(TaskRepository taskRepository, Clock clock) {
|
||||
this.taskRepository = taskRepository;
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
List<TaskResponse> findAll() {
|
||||
return taskRepository.findAllByOrderByCreatedAtAscIdAsc().stream()
|
||||
.map(TaskResponse::from)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
TaskResponse create(String requestedTitle, String requestedDescription) {
|
||||
String title = requestedTitle == null ? "" : requestedTitle.trim();
|
||||
String description = normalizeDescription(requestedDescription);
|
||||
|
||||
if (title.isEmpty() || codePointLength(title) > 100) {
|
||||
throw new InvalidTaskException(
|
||||
"Titeln måste innehålla mellan 1 och 100 tecken.");
|
||||
}
|
||||
|
||||
if (description != null && codePointLength(description) > 500) {
|
||||
throw new InvalidTaskException(
|
||||
"Beskrivningen får innehålla högst 500 tecken.");
|
||||
}
|
||||
|
||||
Task task = new Task(
|
||||
UUID.randomUUID(),
|
||||
title,
|
||||
description,
|
||||
TaskStatus.WAITING,
|
||||
Instant.now(clock));
|
||||
|
||||
return TaskResponse.from(taskRepository.save(task));
|
||||
}
|
||||
|
||||
private static String normalizeDescription(String requestedDescription) {
|
||||
if (requestedDescription == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String description = requestedDescription.trim();
|
||||
return description.isEmpty() ? null : description;
|
||||
}
|
||||
|
||||
private static int codePointLength(String value) {
|
||||
return value.codePointCount(0, value.length());
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
package se.rubble.hemhub.task;
|
||||
|
||||
public enum TaskStatus {
|
||||
WAITING,
|
||||
IN_PROGRESS,
|
||||
COMPLETED
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user