Pizdec
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
package ru.dxunvrs.todo;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class TodoApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(TodoApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package ru.dxunvrs.todo.controller;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import ru.dxunvrs.todo.dto.TaskCreateRequest;
|
||||
import ru.dxunvrs.todo.dto.TaskResponse;
|
||||
import ru.dxunvrs.todo.dto.TaskUpdateRequest;
|
||||
import ru.dxunvrs.todo.service.TaskService;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/tasks")
|
||||
@RequiredArgsConstructor
|
||||
public class TaskController {
|
||||
|
||||
private final TaskService taskService;
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<TaskResponse> getTask(@PathVariable Long id) {
|
||||
TaskResponse response = taskService.getById(id);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<TaskResponse> createTask(@Valid @RequestBody TaskCreateRequest request) {
|
||||
TaskResponse response = taskService.create(request);
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(response);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<TaskResponse> updateTask(
|
||||
@PathVariable Long id,
|
||||
@Valid @RequestBody TaskUpdateRequest request) {
|
||||
TaskResponse response = taskService.update(id, request);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<Void> deleteTask(@PathVariable Long id) {
|
||||
taskService.delete(id);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package ru.dxunvrs.todo.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class TaskCreateRequest {
|
||||
|
||||
@NotBlank(message = "Title is required")
|
||||
private String title;
|
||||
|
||||
private String description;
|
||||
|
||||
private Boolean completed;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package ru.dxunvrs.todo.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class TaskResponse {
|
||||
|
||||
private Long id;
|
||||
private String title;
|
||||
private String description;
|
||||
private Boolean completed;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package ru.dxunvrs.todo.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class TaskUpdateRequest {
|
||||
|
||||
@NotBlank(message = "Title is required")
|
||||
private String title;
|
||||
|
||||
private String description;
|
||||
|
||||
private Boolean completed;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package ru.dxunvrs.todo.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "tasks")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Task {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String title;
|
||||
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String description;
|
||||
|
||||
@Column(nullable = false)
|
||||
private Boolean completed = false;
|
||||
|
||||
@Column(name = "created_at", nullable = false, updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Column(name = "updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
createdAt = LocalDateTime.now();
|
||||
updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
@PreUpdate
|
||||
protected void onUpdate() {
|
||||
updatedAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package ru.dxunvrs.todo.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(TaskNotFoundException.class)
|
||||
public ResponseEntity<ErrorResponse> handleTaskNotFound(TaskNotFoundException ex) {
|
||||
ErrorResponse error = new ErrorResponse(
|
||||
HttpStatus.NOT_FOUND.value(),
|
||||
ex.getMessage(),
|
||||
LocalDateTime.now()
|
||||
);
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
|
||||
}
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ResponseEntity<ValidationErrorResponse> handleValidation(MethodArgumentNotValidException ex) {
|
||||
Map<String, String> errors = new HashMap<>();
|
||||
ex.getBindingResult().getAllErrors().forEach(error -> {
|
||||
String fieldName = ((FieldError) error).getField();
|
||||
String message = error.getDefaultMessage();
|
||||
errors.put(fieldName, message);
|
||||
});
|
||||
|
||||
ValidationErrorResponse response = new ValidationErrorResponse(
|
||||
HttpStatus.BAD_REQUEST.value(),
|
||||
"Validation failed",
|
||||
LocalDateTime.now(),
|
||||
errors
|
||||
);
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<ErrorResponse> handleGeneral(Exception ex) {
|
||||
ErrorResponse error = new ErrorResponse(
|
||||
HttpStatus.INTERNAL_SERVER_ERROR.value(),
|
||||
"Internal server error",
|
||||
LocalDateTime.now()
|
||||
);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
|
||||
}
|
||||
|
||||
record ErrorResponse(int status, String message, LocalDateTime timestamp) {}
|
||||
|
||||
record ValidationErrorResponse(int status, String message, LocalDateTime timestamp, Map<String, String> errors) {}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package ru.dxunvrs.todo.exception;
|
||||
|
||||
public class TaskNotFoundException extends RuntimeException {
|
||||
public TaskNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package ru.dxunvrs.todo.mapper;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import ru.dxunvrs.todo.dto.TaskCreateRequest;
|
||||
import ru.dxunvrs.todo.dto.TaskResponse;
|
||||
import ru.dxunvrs.todo.dto.TaskUpdateRequest;
|
||||
import ru.dxunvrs.todo.entity.Task;
|
||||
|
||||
@Component
|
||||
public class TaskMapper {
|
||||
|
||||
public Task toEntity(TaskCreateRequest request) {
|
||||
Task task = new Task();
|
||||
task.setTitle(request.getTitle());
|
||||
task.setDescription(request.getDescription());
|
||||
task.setCompleted(request.getCompleted() != null ? request.getCompleted() : false);
|
||||
return task;
|
||||
}
|
||||
|
||||
public void updateEntity(Task task, TaskUpdateRequest request) {
|
||||
task.setTitle(request.getTitle());
|
||||
task.setDescription(request.getDescription());
|
||||
if (request.getCompleted() != null) {
|
||||
task.setCompleted(request.getCompleted());
|
||||
}
|
||||
}
|
||||
|
||||
public TaskResponse toResponse(Task task) {
|
||||
return new TaskResponse(
|
||||
task.getId(),
|
||||
task.getTitle(),
|
||||
task.getDescription(),
|
||||
task.getCompleted(),
|
||||
task.getCreatedAt(),
|
||||
task.getUpdatedAt()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package ru.dxunvrs.todo.repository;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import ru.dxunvrs.todo.entity.Task;
|
||||
|
||||
@Repository
|
||||
public interface TaskRepository extends JpaRepository<Task, Long> {
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package ru.dxunvrs.todo.service;
|
||||
|
||||
import ru.dxunvrs.todo.dto.TaskCreateRequest;
|
||||
import ru.dxunvrs.todo.dto.TaskResponse;
|
||||
import ru.dxunvrs.todo.dto.TaskUpdateRequest;
|
||||
|
||||
public interface TaskService {
|
||||
|
||||
TaskResponse getById(Long id);
|
||||
|
||||
TaskResponse create(TaskCreateRequest request);
|
||||
|
||||
TaskResponse update(Long id, TaskUpdateRequest request);
|
||||
|
||||
void delete(Long id);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package ru.dxunvrs.todo.service;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import ru.dxunvrs.todo.dto.TaskCreateRequest;
|
||||
import ru.dxunvrs.todo.dto.TaskResponse;
|
||||
import ru.dxunvrs.todo.dto.TaskUpdateRequest;
|
||||
import ru.dxunvrs.todo.entity.Task;
|
||||
import ru.dxunvrs.todo.exception.TaskNotFoundException;
|
||||
import ru.dxunvrs.todo.mapper.TaskMapper;
|
||||
import ru.dxunvrs.todo.repository.TaskRepository;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class TaskServiceImpl implements TaskService {
|
||||
|
||||
private final TaskRepository taskRepository;
|
||||
private final TaskMapper taskMapper;
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public TaskResponse getById(Long id) {
|
||||
Task task = taskRepository.findById(id)
|
||||
.orElseThrow(() -> new TaskNotFoundException("Task not found with id: " + id));
|
||||
return taskMapper.toResponse(task);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public TaskResponse create(TaskCreateRequest request) {
|
||||
Task task = taskMapper.toEntity(request);
|
||||
Task saved = taskRepository.save(task);
|
||||
return taskMapper.toResponse(saved);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public TaskResponse update(Long id, TaskUpdateRequest request) {
|
||||
Task task = taskRepository.findById(id)
|
||||
.orElseThrow(() -> new TaskNotFoundException("Task not found with id: " + id));
|
||||
taskMapper.updateEntity(task, request);
|
||||
Task updated = taskRepository.save(task);
|
||||
return taskMapper.toResponse(updated);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void delete(Long id) {
|
||||
if (!taskRepository.existsById(id)) {
|
||||
throw new TaskNotFoundException("Task not found with id: " + id);
|
||||
}
|
||||
taskRepository.deleteById(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
spring.application.name=todo
|
||||
|
||||
spring.datasource.url=jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:tododb}
|
||||
spring.datasource.username=${DB_USER:postgres}
|
||||
spring.datasource.password=${DB_PASSWORD:postgres}
|
||||
spring.datasource.driver-class-name=org.postgresql.Driver
|
||||
|
||||
spring.datasource.hikari.maximum-pool-size=10
|
||||
spring.datasource.hikari.minimum-idle=5
|
||||
spring.datasource.hikari.connection-timeout=30000
|
||||
spring.datasource.hikari.idle-timeout=600000
|
||||
spring.datasource.hikari.max-lifetime=1800000
|
||||
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
spring.jpa.show-sql=false
|
||||
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
|
||||
spring.jpa.properties.hibernate.format_sql=true
|
||||
|
||||
management.endpoints.web.exposure.include=health,info,metrics,prometheus
|
||||
management.endpoint.health.show-details=always
|
||||
management.endpoint.prometheus.enabled=true
|
||||
management.metrics.export.prometheus.enabled=true
|
||||
management.metrics.tags.application=${spring.application.name}
|
||||
management.metrics.distribution.percentiles-histogram.http.server.requests=true
|
||||
Reference in New Issue
Block a user