From 586731105e4a6102ee65f54c600d6a383d3f16a4 Mon Sep 17 00:00:00 2001 From: Ohrim Date: Mon, 24 Feb 2025 16:38:49 +0300 Subject: [PATCH 1/7] feat: first todo implementation --- pom.xml | 78 ++++++++++++++++- .../configuration/OpenApiConfig.java | 19 ++++ .../springtodo/configuration/RedisConfig.java | 23 +++++ .../controller/GlobalExceptionHandler.java | 72 ++++++++++++++++ .../springtodo/controller/ToDoController.java | 76 ++++++++++++++++ .../emobile/springtodo/dto/ErrorResponse.java | 34 ++++++++ .../emobile/springtodo/dto/ToDoItemDto.java | 31 +++++++ .../emobile/springtodo/entity/ToDoItem.java | 22 +++++ .../exception/InvalidToDoException.java | 7 ++ .../exception/ToDoNotFoundException.java | 7 ++ .../springtodo/mapper/ToDoItemMapper.java | 10 +++ .../springtodo/mapper/ToDoItemMapperImpl.java | 32 +++++++ .../springtodo/repository/ToDoRepository.java | 18 ++++ .../repository/ToDoRepositoryImpl.java | 61 +++++++++++++ .../springtodo/service/ToDoService.java | 17 ++++ .../springtodo/service/ToDoServiceImpl.java | 86 +++++++++++++++++++ src/main/resources/application.properties | 12 +++ .../migration/V1__Create_initial_schema.sql | 8 ++ 18 files changed, 611 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/emobile/springtodo/configuration/OpenApiConfig.java create mode 100644 src/main/java/com/emobile/springtodo/configuration/RedisConfig.java create mode 100644 src/main/java/com/emobile/springtodo/controller/GlobalExceptionHandler.java create mode 100644 src/main/java/com/emobile/springtodo/controller/ToDoController.java create mode 100644 src/main/java/com/emobile/springtodo/dto/ErrorResponse.java create mode 100644 src/main/java/com/emobile/springtodo/dto/ToDoItemDto.java create mode 100644 src/main/java/com/emobile/springtodo/entity/ToDoItem.java create mode 100644 src/main/java/com/emobile/springtodo/exception/InvalidToDoException.java create mode 100644 src/main/java/com/emobile/springtodo/exception/ToDoNotFoundException.java create mode 100644 src/main/java/com/emobile/springtodo/mapper/ToDoItemMapper.java create mode 100644 src/main/java/com/emobile/springtodo/mapper/ToDoItemMapperImpl.java create mode 100644 src/main/java/com/emobile/springtodo/repository/ToDoRepository.java create mode 100644 src/main/java/com/emobile/springtodo/repository/ToDoRepositoryImpl.java create mode 100644 src/main/java/com/emobile/springtodo/service/ToDoService.java create mode 100644 src/main/java/com/emobile/springtodo/service/ToDoServiceImpl.java create mode 100644 src/main/resources/db/migration/V1__Create_initial_schema.sql diff --git a/pom.xml b/pom.xml index 03bc446d..9476f6e5 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent 3.3.5 - + com.emobile SpringToDo @@ -19,18 +19,92 @@ + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-jdbc + + + + + org.postgresql + postgresql + 42.7.4 + + + org.springframework.boot - spring-boot-starter + spring-boot-starter-cache + + org.springframework.boot + spring-boot-starter-data-redis + + + + + org.springframework.boot + spring-boot-starter-validation + + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.5.0 + + + + org.projectlombok + lombok + provided + + + org.springframework.boot spring-boot-starter-test test + + + org.testcontainers + testcontainers + test + + + + + org.testcontainers + postgresql + test + + + + + org.flywaydb + flyway-core + 11.3.2 + + + + org.flywaydb + flyway-database-postgresql + 11.3.2 + + + + diff --git a/src/main/java/com/emobile/springtodo/configuration/OpenApiConfig.java b/src/main/java/com/emobile/springtodo/configuration/OpenApiConfig.java new file mode 100644 index 00000000..d6c2e63a --- /dev/null +++ b/src/main/java/com/emobile/springtodo/configuration/OpenApiConfig.java @@ -0,0 +1,19 @@ +package com.emobile.springtodo.configuration; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class OpenApiConfig { + + @Bean + public OpenAPI customOpenAPI() { + return new OpenAPI() + .info(new Info() + .title("Spring ToDo API") + .version("1.0") + .description("API для управления задачами ToDo")); + } +} diff --git a/src/main/java/com/emobile/springtodo/configuration/RedisConfig.java b/src/main/java/com/emobile/springtodo/configuration/RedisConfig.java new file mode 100644 index 00000000..94f26030 --- /dev/null +++ b/src/main/java/com/emobile/springtodo/configuration/RedisConfig.java @@ -0,0 +1,23 @@ +package com.emobile.springtodo.configuration; + + +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; +import org.springframework.data.redis.connection.RedisConnectionFactory; + +@Configuration +@EnableCaching +public class RedisConfig { + + @Bean + public CacheManager cacheManager(RedisConnectionFactory connectionFactory) { + RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig(); + return RedisCacheManager.builder(connectionFactory) + .cacheDefaults(cacheConfig) + .build(); + } +} diff --git a/src/main/java/com/emobile/springtodo/controller/GlobalExceptionHandler.java b/src/main/java/com/emobile/springtodo/controller/GlobalExceptionHandler.java new file mode 100644 index 00000000..95336ae7 --- /dev/null +++ b/src/main/java/com/emobile/springtodo/controller/GlobalExceptionHandler.java @@ -0,0 +1,72 @@ +package com.emobile.springtodo.controller; + +import com.emobile.springtodo.dto.ErrorResponse; +import com.emobile.springtodo.exception.InvalidToDoException; +import com.emobile.springtodo.exception.ToDoNotFoundException; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +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; + +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler { + + private ResponseEntity buildResponse(Exception ex, HttpStatus status, HttpServletRequest request, Map validationErrors) { + log.error("Exception: {} - {}", ex.getClass().getSimpleName(), ex.getMessage(), ex); + + + ErrorResponse response = ErrorResponse.builder() + .timestamp(LocalDateTime.now()) + .status(status.value()) + .error(status.getReasonPhrase()) + .message(ex.getMessage()) + .path(request.getRequestURI()) + .build(); + + + if (validationErrors != null && !validationErrors.isEmpty()) { + response.setDetails(validationErrors); + } + + return ResponseEntity.status(status).body(response); + } + + @ExceptionHandler(ToDoNotFoundException.class) + public ResponseEntity handleNotFound(ToDoNotFoundException ex, HttpServletRequest request) { + return buildResponse(ex, HttpStatus.NOT_FOUND, request, null); + } + + @ExceptionHandler(InvalidToDoException.class) + public ResponseEntity handleInvalidToDo(InvalidToDoException ex, HttpServletRequest request) { + return buildResponse(ex, HttpStatus.BAD_REQUEST, request, null); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleValidationExceptions(MethodArgumentNotValidException ex, HttpServletRequest request) { + + Map validationErrors = new HashMap<>(); + for (FieldError fieldError : ex.getBindingResult().getFieldErrors()) { + validationErrors.put(fieldError.getField(), fieldError.getDefaultMessage()); + } + + + log.error("Validation failed: {}", validationErrors); + + + return buildResponse(ex, HttpStatus.BAD_REQUEST, request, validationErrors); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleGeneral(Exception ex, HttpServletRequest request) { + return buildResponse(ex, HttpStatus.INTERNAL_SERVER_ERROR, request, null); + } +} diff --git a/src/main/java/com/emobile/springtodo/controller/ToDoController.java b/src/main/java/com/emobile/springtodo/controller/ToDoController.java new file mode 100644 index 00000000..fc43d61c --- /dev/null +++ b/src/main/java/com/emobile/springtodo/controller/ToDoController.java @@ -0,0 +1,76 @@ +package com.emobile.springtodo.controller; + +import com.emobile.springtodo.dto.ToDoItemDto; +import com.emobile.springtodo.service.ToDoService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/todos") +@RequiredArgsConstructor +@Tag(name = "ToDo API", description = "API для управления задачами ToDo") +public class ToDoController { + + private final ToDoService toDoService; + + @GetMapping + @Operation(summary = "Получить все задачи", description = "Получает список всех задач с возможностью пагинации") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Успешное получение списка задач"), + @ApiResponse(responseCode = "400", description = "Ошибка пагинации") + }) + public List getAll(@RequestParam(defaultValue = "10") int limit, + @RequestParam(defaultValue = "0") int offset) { + return toDoService.getAll(limit, offset); + } + + @GetMapping("/{id}") + @Operation(summary = "Получить задачу по ID", description = "Получает задачу по ее уникальному идентификатору") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Задача успешно получена"), + @ApiResponse(responseCode = "404", description = "Задача не найдена") + }) + public ToDoItemDto getById(@PathVariable Long id) { + return toDoService.getById(id); + } + + @PostMapping + @Operation(summary = "Создать новую задачу", description = "Создает новую задачу") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Задача успешно создана"), + @ApiResponse(responseCode = "400", description = "Ошибка валидации данных") + }) + public ResponseEntity create(@Valid @RequestBody ToDoItemDto dto) { + return ResponseEntity.ok(toDoService.create(dto)); + } + + @PutMapping("/{id}") + @Operation(summary = "Обновить задачу", description = "Обновляет существующую задачу по ее ID") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Задача успешно обновлена"), + @ApiResponse(responseCode = "404", description = "Задача не найдена") + }) + public ResponseEntity update(@PathVariable Long id, @Valid @RequestBody ToDoItemDto dto) { + return ResponseEntity.ok(toDoService.update(id, dto)); + } + + @DeleteMapping("/{id}") + @Operation(summary = "Удалить задачу", description = "Удаляет задачу по ее ID") + @ApiResponses(value = { + @ApiResponse(responseCode = "204", description = "Задача успешно удалена"), + @ApiResponse(responseCode = "404", description = "Задача не найдена") + }) + public ResponseEntity delete(@PathVariable Long id) { + toDoService.delete(id); + return ResponseEntity.noContent().build(); + } + +} diff --git a/src/main/java/com/emobile/springtodo/dto/ErrorResponse.java b/src/main/java/com/emobile/springtodo/dto/ErrorResponse.java new file mode 100644 index 00000000..1783f66b --- /dev/null +++ b/src/main/java/com/emobile/springtodo/dto/ErrorResponse.java @@ -0,0 +1,34 @@ +package com.emobile.springtodo.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +import java.time.LocalDateTime; +import java.util.Map; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Schema(description = "Ответ с ошибкой") +public class ErrorResponse { + + @Schema(description = "Время возникновения ошибки", example = "2023-10-24T15:30:00") + private LocalDateTime timestamp; + + @Schema(description = "HTTP статус код ошибки", example = "400") + private int status; + + @Schema(description = "Тип ошибки", example = "Bad Request") + private String error; + + @Schema(description = "Сообщение об ошибке", example = "Задача не найдена") + private String message; + + @Schema(description = "Путь запроса, который вызвал ошибку", example = "/api/todos/1") + private String path; + + @Schema(description = "Подробности ошибок валидации, если есть", example = "{\"field\":\"Title cannot be blank\"}") + private Map details; +} diff --git a/src/main/java/com/emobile/springtodo/dto/ToDoItemDto.java b/src/main/java/com/emobile/springtodo/dto/ToDoItemDto.java new file mode 100644 index 00000000..e5a6c009 --- /dev/null +++ b/src/main/java/com/emobile/springtodo/dto/ToDoItemDto.java @@ -0,0 +1,31 @@ +package com.emobile.springtodo.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.*; + +import java.io.Serializable; + +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ToDoItemDto implements Serializable { + + @Schema(description = "Уникальный идентификатор задачи", example = "1") + private Long id; + + @NotBlank(message = "Title cannot be blank") + @Size(max = 100, message = "Title must be at most 100 characters long") + @Schema(description = "Название задачи", example = "Сделать уборку", maxLength = 100) + private String title; + + @Size(max = 1000, message = "Description must be at most 1000 characters long") + @Schema(description = "Описание задачи", example = "Убрать кухню и зал", maxLength = 1000) + private String description; + + @Schema(description = "Статус выполнения задачи", example = "false") + private boolean completed; +} diff --git a/src/main/java/com/emobile/springtodo/entity/ToDoItem.java b/src/main/java/com/emobile/springtodo/entity/ToDoItem.java new file mode 100644 index 00000000..c38c86fd --- /dev/null +++ b/src/main/java/com/emobile/springtodo/entity/ToDoItem.java @@ -0,0 +1,22 @@ +package com.emobile.springtodo.entity; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.LocalDateTime; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class ToDoItem { + + private Long id; + private String title; + private String description; + private boolean completed; + private LocalDateTime createdAt; + +} diff --git a/src/main/java/com/emobile/springtodo/exception/InvalidToDoException.java b/src/main/java/com/emobile/springtodo/exception/InvalidToDoException.java new file mode 100644 index 00000000..31f2ddee --- /dev/null +++ b/src/main/java/com/emobile/springtodo/exception/InvalidToDoException.java @@ -0,0 +1,7 @@ +package com.emobile.springtodo.exception; + +public class InvalidToDoException extends RuntimeException { + public InvalidToDoException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/src/main/java/com/emobile/springtodo/exception/ToDoNotFoundException.java b/src/main/java/com/emobile/springtodo/exception/ToDoNotFoundException.java new file mode 100644 index 00000000..560c3ef0 --- /dev/null +++ b/src/main/java/com/emobile/springtodo/exception/ToDoNotFoundException.java @@ -0,0 +1,7 @@ +package com.emobile.springtodo.exception; + +public class ToDoNotFoundException extends RuntimeException { + public ToDoNotFoundException(Long id) { + super("ToDo item with id " + id + " not found"); + } +} diff --git a/src/main/java/com/emobile/springtodo/mapper/ToDoItemMapper.java b/src/main/java/com/emobile/springtodo/mapper/ToDoItemMapper.java new file mode 100644 index 00000000..4becf8a8 --- /dev/null +++ b/src/main/java/com/emobile/springtodo/mapper/ToDoItemMapper.java @@ -0,0 +1,10 @@ +package com.emobile.springtodo.mapper; + +import com.emobile.springtodo.dto.ToDoItemDto; +import com.emobile.springtodo.entity.ToDoItem; + +public interface ToDoItemMapper { + ToDoItem toEntity(ToDoItemDto dto); + + ToDoItemDto toDto(ToDoItem entity); +} diff --git a/src/main/java/com/emobile/springtodo/mapper/ToDoItemMapperImpl.java b/src/main/java/com/emobile/springtodo/mapper/ToDoItemMapperImpl.java new file mode 100644 index 00000000..3b2a8bf7 --- /dev/null +++ b/src/main/java/com/emobile/springtodo/mapper/ToDoItemMapperImpl.java @@ -0,0 +1,32 @@ +package com.emobile.springtodo.mapper; + +import com.emobile.springtodo.dto.ToDoItemDto; +import com.emobile.springtodo.entity.ToDoItem; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; + +@Component +public class ToDoItemMapperImpl implements ToDoItemMapper { + + @Override + public ToDoItem toEntity(ToDoItemDto dto) { + return new ToDoItem( + dto.getId(), + dto.getTitle(), + dto.getDescription(), + dto.isCompleted(), + LocalDateTime.now() + ); + } + + @Override + public ToDoItemDto toDto(ToDoItem entity) { + return ToDoItemDto.builder() + .id(entity.getId()) + .title(entity.getTitle()) + .description(entity.getDescription()) + .completed(entity.isCompleted()) + .build(); + } +} diff --git a/src/main/java/com/emobile/springtodo/repository/ToDoRepository.java b/src/main/java/com/emobile/springtodo/repository/ToDoRepository.java new file mode 100644 index 00000000..904bc6bf --- /dev/null +++ b/src/main/java/com/emobile/springtodo/repository/ToDoRepository.java @@ -0,0 +1,18 @@ +package com.emobile.springtodo.repository; + +import com.emobile.springtodo.entity.ToDoItem; + +import java.util.List; +import java.util.Optional; + +public interface ToDoRepository { + List findAll(int limit, int offset); + + Optional findById(Long id); + + void save(ToDoItem toDoItem); + + void update(ToDoItem toDoItem); + + void deleteById(Long id); +} diff --git a/src/main/java/com/emobile/springtodo/repository/ToDoRepositoryImpl.java b/src/main/java/com/emobile/springtodo/repository/ToDoRepositoryImpl.java new file mode 100644 index 00000000..9c40e4fe --- /dev/null +++ b/src/main/java/com/emobile/springtodo/repository/ToDoRepositoryImpl.java @@ -0,0 +1,61 @@ +package com.emobile.springtodo.repository; + +import com.emobile.springtodo.entity.ToDoItem; +import lombok.RequiredArgsConstructor; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Repository +@RequiredArgsConstructor +public class ToDoRepositoryImpl implements ToDoRepository { + private final JdbcTemplate jdbcTemplate; + + + private final RowMapper rowMapper = (rs, rowNum) -> new ToDoItem( + rs.getLong("id"), + rs.getString("title"), + rs.getString("description"), + rs.getBoolean("completed"), + rs.getTimestamp("created_at").toLocalDateTime() + ); + + @Override + public List findAll(int limit, int offset) { + String sql = "SELECT * FROM todo_items ORDER BY created_at DESC LIMIT ? OFFSET ?"; + + return jdbcTemplate.query(sql, rowMapper, limit, offset); + } + + @Override + public Optional findById(Long id) { + String sql = "SELECT * FROM todo_items WHERE id = ?"; + List result = jdbcTemplate.query(sql, rowMapper, id); + return result.isEmpty() ? Optional.empty() : Optional.of(result.getFirst()); + } + + @Override + public void save(ToDoItem toDoItem) { + String sql = "INSERT INTO todo_items (title, description, completed, created_at) VALUES (?,?,?,?) RETURNING id"; + Long generatedId = jdbcTemplate.queryForObject(sql, Long.class, + toDoItem.getTitle(), toDoItem.getDescription(), toDoItem.isCompleted(), toDoItem.getCreatedAt()); + toDoItem.setId(generatedId); + + } + + @Override + public void update(ToDoItem toDoItem) { + String sql = "UPDATE todo_items SET title = ?, description = ?, completed = ? WHERE id = ?"; + jdbcTemplate.update(sql, toDoItem.getTitle(), toDoItem.getDescription(), toDoItem.isCompleted(), toDoItem.getId()); + } + + @Override + public void deleteById(Long id) { + String sql = "DELETE FROM todo_items WHERE id = ?"; + jdbcTemplate.update(sql, id); + + } +} diff --git a/src/main/java/com/emobile/springtodo/service/ToDoService.java b/src/main/java/com/emobile/springtodo/service/ToDoService.java new file mode 100644 index 00000000..52515a8f --- /dev/null +++ b/src/main/java/com/emobile/springtodo/service/ToDoService.java @@ -0,0 +1,17 @@ +package com.emobile.springtodo.service; + +import com.emobile.springtodo.dto.ToDoItemDto; + +import java.util.List; + +public interface ToDoService { + List getAll(int limit, int offset); + + ToDoItemDto getById(Long id); + + ToDoItemDto create(ToDoItemDto dto); + + ToDoItemDto update(Long id, ToDoItemDto dto); + + void delete(Long id); +} \ No newline at end of file diff --git a/src/main/java/com/emobile/springtodo/service/ToDoServiceImpl.java b/src/main/java/com/emobile/springtodo/service/ToDoServiceImpl.java new file mode 100644 index 00000000..349377ee --- /dev/null +++ b/src/main/java/com/emobile/springtodo/service/ToDoServiceImpl.java @@ -0,0 +1,86 @@ +package com.emobile.springtodo.service; + +import com.emobile.springtodo.dto.ToDoItemDto; +import com.emobile.springtodo.entity.ToDoItem; +import com.emobile.springtodo.exception.ToDoNotFoundException; +import com.emobile.springtodo.mapper.ToDoItemMapper; +import com.emobile.springtodo.repository.ToDoRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.CachePut; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +public class ToDoServiceImpl implements ToDoService { + private final ToDoRepository toDoRepository; + private final ToDoItemMapper toDoItemMapper; + + @Override + public List getAll(int limit, int offset) { + log.info("Fetching ToDo items with limit={} and offset={}", limit, offset); + return toDoRepository.findAll(limit, offset) + .stream() + .map(toDoItemMapper::toDto) + .collect(Collectors.toList()); + } + + @Cacheable(value = "todos", key = "#id") + @Override + public ToDoItemDto getById(Long id) { + log.info("Fetching ToDo item with id={}", id); + ToDoItem item = toDoRepository.findById(id) + .orElseThrow(() -> { + log.warn("ToDo item with id={} not found", id); + return new ToDoNotFoundException(id); + }); + return toDoItemMapper.toDto(item); + } + + @Transactional + @Override + public ToDoItemDto create(ToDoItemDto dto) { + log.info("Creating new ToDo item: {}", dto); + ToDoItem entity = toDoItemMapper.toEntity(dto); + toDoRepository.save(entity); + return toDoItemMapper.toDto(entity); + } + + @Transactional + @CachePut(value = "todos", key = "#id") + @Override + public ToDoItemDto update(Long id, ToDoItemDto dto) { + log.info("Updating ToDo item with id={}", id); + ToDoItem item = toDoRepository.findById(id) + .orElseThrow(() -> { + log.warn("Cannot update - ToDo item with id={} not found", id); + return new ToDoNotFoundException(id); + }); + + item.setTitle(dto.getTitle()); + item.setDescription(dto.getDescription()); + item.setCompleted(dto.isCompleted()); + + toDoRepository.update(item); + return toDoItemMapper.toDto(item); + } + + @Transactional + @CacheEvict(value = "todos", key = "#id") + @Override + public void delete(Long id) { + log.info("Deleting ToDo item with id={}", id); + if (toDoRepository.findById(id).isEmpty()) { + log.warn("Cannot delete - ToDo item with id={} not found", id); + throw new ToDoNotFoundException(id); + } + toDoRepository.deleteById(id); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 5c3e5461..ffc59196 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,13 @@ spring.application.name=SpringToDo +spring.datasource.url=jdbc:postgresql://localhost:5432/todo_db +spring.datasource.username=todo_user +spring.datasource.password=todo_pass +spring.datasource.driver-class-name=org.postgresql.Driver +spring.flyway.enabled=true +spring.cache.type=redis +spring.data.redis.host=localhost +spring.data.redis.port=6379 +spring.data.redis.timeout=6000 +spring.data.redis.client-type=lettuce +spring.data.redis.password= +spring.data.redis.database=0 diff --git a/src/main/resources/db/migration/V1__Create_initial_schema.sql b/src/main/resources/db/migration/V1__Create_initial_schema.sql new file mode 100644 index 00000000..2c797986 --- /dev/null +++ b/src/main/resources/db/migration/V1__Create_initial_schema.sql @@ -0,0 +1,8 @@ +CREATE TABLE todo_items +( + id SERIAL PRIMARY KEY, + title VARCHAR(255) NOT NULL, + description TEXT, + completed BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); From 7324776ed6656ee267fc81ce93081aff9d171884 Mon Sep 17 00:00:00 2001 From: Ohrim Date: Mon, 24 Feb 2025 16:39:27 +0300 Subject: [PATCH 2/7] test: tests for repo, service and controller --- .../controller/BaseIntegrationTest.java | 43 ++++++ .../ToDoControllerIntegrationTest.java | 66 +++++++++ .../repository/ToDoRepositoryTest.java | 60 +++++++++ .../springtodo/service/ToDoServiceTest.java | 126 ++++++++++++++++++ 4 files changed, 295 insertions(+) create mode 100644 src/test/java/com/emobile/springtodo/controller/BaseIntegrationTest.java create mode 100644 src/test/java/com/emobile/springtodo/controller/ToDoControllerIntegrationTest.java create mode 100644 src/test/java/com/emobile/springtodo/repository/ToDoRepositoryTest.java create mode 100644 src/test/java/com/emobile/springtodo/service/ToDoServiceTest.java diff --git a/src/test/java/com/emobile/springtodo/controller/BaseIntegrationTest.java b/src/test/java/com/emobile/springtodo/controller/BaseIntegrationTest.java new file mode 100644 index 00000000..9c518325 --- /dev/null +++ b/src/test/java/com/emobile/springtodo/controller/BaseIntegrationTest.java @@ -0,0 +1,43 @@ +package com.emobile.springtodo.controller; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.PostgreSQLContainer; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public abstract class BaseIntegrationTest { + + protected static PostgreSQLContainer postgresContainer = new PostgreSQLContainer<>("postgres:latest") + .withDatabaseName("test_db") + .withUsername("test") + .withPassword("test"); + + protected static GenericContainer redisContainer = new GenericContainer<>("redis:latest") + .withExposedPorts(6379); + + @LocalServerPort + protected int port; + + + @BeforeAll + public static void setup() { + postgresContainer.start(); + redisContainer.start(); + + System.setProperty("spring.datasource.url", postgresContainer.getJdbcUrl()); + System.setProperty("spring.datasource.username", postgresContainer.getUsername()); + System.setProperty("spring.datasource.password", postgresContainer.getPassword()); + + System.setProperty("spring.data.redis.host", redisContainer.getHost()); + System.setProperty("spring.data.redis.port", redisContainer.getMappedPort(6379).toString()); + } + + @AfterAll + public static void tearDown() { + postgresContainer.stop(); + redisContainer.stop(); + } +} \ No newline at end of file diff --git a/src/test/java/com/emobile/springtodo/controller/ToDoControllerIntegrationTest.java b/src/test/java/com/emobile/springtodo/controller/ToDoControllerIntegrationTest.java new file mode 100644 index 00000000..00c761d0 --- /dev/null +++ b/src/test/java/com/emobile/springtodo/controller/ToDoControllerIntegrationTest.java @@ -0,0 +1,66 @@ +package com.emobile.springtodo.controller; + + +import com.emobile.springtodo.dto.ToDoItemDto; +import com.emobile.springtodo.service.ToDoServiceImpl; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.assertj.core.api.Assertions.assertThat; + +@ExtendWith(SpringExtension.class) +@Import(ToDoServiceImpl.class) +class ToDoControllerIntegrationTest extends BaseIntegrationTest { + + @Autowired + private TestRestTemplate restTemplate; + + @Test + void testCreateToDo() { + ToDoItemDto dto = new ToDoItemDto(1L, "New Task", "Description of the task", false); + + + ToDoItemDto response = restTemplate.postForObject("http://localhost:" + port + "/api/todos", dto, ToDoItemDto.class); + + + assertThat(response).isNotNull(); + assertThat(response.getTitle()).isEqualTo(dto.getTitle()); + assertThat(response.getDescription()).isEqualTo(dto.getDescription()); + assertThat(response.isCompleted()).isEqualTo(dto.isCompleted()); + } + + @Test + void testGetAllToDos() { + + ToDoItemDto dto = new ToDoItemDto(1L, "New Task", "Description of the task", false); + restTemplate.postForObject("http://localhost:" + port + "/api/todos", dto, ToDoItemDto.class); + + + ToDoItemDto[] response = restTemplate.getForObject("http://localhost:" + port + "/api/todos", ToDoItemDto[].class); + + + assertThat(response).isNotEmpty(); + } + + @Test + void testGetByIdToDo() { + + ToDoItemDto dto = new ToDoItemDto(1L, "New Task", "Description of the task", false); + ToDoItemDto createdDto = restTemplate.postForObject("http://localhost:" + port + "/api/todos", dto, ToDoItemDto.class); + + + ToDoItemDto response = restTemplate.getForObject("http://localhost:" + port + "/api/todos/" + createdDto.getId(), ToDoItemDto.class); + + + assertThat(response).isNotNull(); + assertThat(response.getTitle()).isEqualTo(dto.getTitle()); + assertThat(response.getDescription()).isEqualTo(dto.getDescription()); + assertThat(response.isCompleted()).isEqualTo(dto.isCompleted()); + } + + +} \ No newline at end of file diff --git a/src/test/java/com/emobile/springtodo/repository/ToDoRepositoryTest.java b/src/test/java/com/emobile/springtodo/repository/ToDoRepositoryTest.java new file mode 100644 index 00000000..3321eed0 --- /dev/null +++ b/src/test/java/com/emobile/springtodo/repository/ToDoRepositoryTest.java @@ -0,0 +1,60 @@ +package com.emobile.springtodo.repository; + +import com.emobile.springtodo.entity.ToDoItem; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.jdbc.core.JdbcTemplate; + +import java.time.LocalDateTime; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class ToDoRepositoryTest { + + @Mock + private JdbcTemplate jdbcTemplate; + + @InjectMocks + private ToDoRepositoryImpl toDoRepository; + + + @Test + void testSave() { + ToDoItem toDoItem = new ToDoItem(null, "Title", "Description", false, LocalDateTime.now()); + String sql = "INSERT INTO todo_items (title, description, completed, created_at) VALUES (?,?,?,?) RETURNING id"; + Long generatedId = 1L; + + when(jdbcTemplate.queryForObject(eq(sql), eq(Long.class), any(), any(), any(), any())) + .thenReturn(generatedId); + + toDoRepository.save(toDoItem); + + assertEquals(generatedId, toDoItem.getId()); + verify(jdbcTemplate).queryForObject(eq(sql), eq(Long.class), any(), any(), any(), any()); + } + + @Test + void testUpdate() { + ToDoItem toDoItem = new ToDoItem(1L, "Updated Title", "Updated Description", true, LocalDateTime.now()); + String sql = "UPDATE todo_items SET title = ?, description = ?, completed = ? WHERE id = ?"; + + toDoRepository.update(toDoItem); + + verify(jdbcTemplate).update(eq(sql), any(), any(), any(), eq(toDoItem.getId())); + } + + @Test + void testDeleteById() { + Long id = 1L; + String sql = "DELETE FROM todo_items WHERE id = ?"; + + toDoRepository.deleteById(id); + + verify(jdbcTemplate).update(eq(sql), eq(id)); + } +} diff --git a/src/test/java/com/emobile/springtodo/service/ToDoServiceTest.java b/src/test/java/com/emobile/springtodo/service/ToDoServiceTest.java new file mode 100644 index 00000000..6888c61c --- /dev/null +++ b/src/test/java/com/emobile/springtodo/service/ToDoServiceTest.java @@ -0,0 +1,126 @@ +package com.emobile.springtodo.service; + + +import com.emobile.springtodo.dto.ToDoItemDto; +import com.emobile.springtodo.entity.ToDoItem; +import com.emobile.springtodo.exception.ToDoNotFoundException; +import com.emobile.springtodo.mapper.ToDoItemMapper; +import com.emobile.springtodo.repository.ToDoRepository; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class ToDoServiceTest { + + @Mock + private ToDoRepository toDoRepository; + + @Mock + private ToDoItemMapper toDoItemMapper; + + @InjectMocks + private ToDoServiceImpl toDoService; + + @Test + void testGetAll() { + ToDoItem item1 = new ToDoItem(1L, "Task 1", "Description 1", false, LocalDateTime.now()); + ToDoItem item2 = new ToDoItem(2L, "Task 2", "Description 2", true, LocalDateTime.now()); + ToDoItemDto dto1 = new ToDoItemDto(1L, "Task 1", "Description 1", false); + ToDoItemDto dto2 = new ToDoItemDto(1L, "Task 2", "Description 2", true); + + when(toDoRepository.findAll(10, 0)).thenReturn(List.of(item1, item2)); + when(toDoItemMapper.toDto(item1)).thenReturn(dto1); + when(toDoItemMapper.toDto(item2)).thenReturn(dto2); + + List result = toDoService.getAll(10, 0); + + assertEquals(2, result.size()); + assertEquals(dto1, result.get(0)); + assertEquals(dto2, result.get(1)); + + verify(toDoRepository).findAll(10, 0); + verify(toDoItemMapper, times(2)).toDto(any(ToDoItem.class)); + } + + @Test + void testGetById_Success() { + Long id = 1L; + ToDoItem item = new ToDoItem(id, "Task", "Description", false, LocalDateTime.now()); + ToDoItemDto dto = new ToDoItemDto(1L, "Task", "Description", false); + + when(toDoRepository.findById(id)).thenReturn(Optional.of(item)); + when(toDoItemMapper.toDto(item)).thenReturn(dto); + + ToDoItemDto result = toDoService.getById(id); + + assertEquals(dto, result); + verify(toDoRepository).findById(id); + verify(toDoItemMapper).toDto(item); + } + + @Test + void testGetById_NotFound() { + Long id = 1L; + + when(toDoRepository.findById(id)).thenReturn(Optional.empty()); + + assertThrows(ToDoNotFoundException.class, () -> toDoService.getById(id)); + verify(toDoRepository).findById(id); + } + + @Test + void testCreate() { + ToDoItemDto dto = new ToDoItemDto(1L, "New Task", "New Description", false); + ToDoItem entity = new ToDoItem(null, "New Task", "New Description", false, LocalDateTime.now()); + ToDoItemDto savedDto = new ToDoItemDto(1L, "New Task", "New Description", false); + + when(toDoItemMapper.toEntity(dto)).thenReturn(entity); + when(toDoItemMapper.toDto(any(ToDoItem.class))).thenReturn(savedDto); + + doAnswer(invocation -> { + entity.setId(1L); + return null; + }).when(toDoRepository).save(entity); + + ToDoItemDto result = toDoService.create(dto); + + assertEquals(savedDto, result); + verify(toDoRepository).save(entity); + verify(toDoItemMapper).toEntity(dto); + verify(toDoItemMapper).toDto(any(ToDoItem.class)); + } + + + @Test + void testDelete_Success() { + Long id = 1L; + ToDoItem item = new ToDoItem(id, "Task", "Description", false, LocalDateTime.now()); + + when(toDoRepository.findById(id)).thenReturn(Optional.of(item)); + + toDoService.delete(id); + + verify(toDoRepository).findById(id); + verify(toDoRepository).deleteById(id); + } + + @Test + void testDelete_NotFound() { + Long id = 1L; + + when(toDoRepository.findById(id)).thenReturn(Optional.empty()); + + assertThrows(ToDoNotFoundException.class, () -> toDoService.delete(id)); + verify(toDoRepository).findById(id); + } +} From 7cd05bd9b1c070559e26d8bcbed99fd55930bbb6 Mon Sep 17 00:00:00 2001 From: Ohrim Date: Wed, 26 Feb 2025 17:13:14 +0300 Subject: [PATCH 3/7] feat: JavaDoc integration --- .../springtodo/SpringToDoApplication.java | 4 ++ .../configuration/OpenApiConfig.java | 9 ++++ .../springtodo/configuration/RedisConfig.java | 11 ++++- .../controller/GlobalExceptionHandler.java | 43 ++++++++++++++++--- .../springtodo/controller/ToDoController.java | 34 ++++++++++++++- .../emobile/springtodo/dto/ErrorResponse.java | 22 ++++++++++ .../emobile/springtodo/dto/ToDoItemDto.java | 19 ++++++++ .../emobile/springtodo/entity/ToDoItem.java | 23 ++++++++++ .../exception/InvalidToDoException.java | 11 ++++- .../exception/ToDoNotFoundException.java | 9 ++++ .../springtodo/mapper/ToDoItemMapper.java | 15 +++++++ .../springtodo/mapper/ToDoItemMapperImpl.java | 14 ++++++ .../springtodo/repository/ToDoRepository.java | 29 +++++++++++++ .../repository/ToDoRepositoryImpl.java | 38 +++++++++++++--- .../springtodo/service/ToDoService.java | 34 ++++++++++++++- .../springtodo/service/ToDoServiceImpl.java | 32 ++++++++++++++ .../controller/BaseIntegrationTest.java | 5 +++ .../ToDoControllerIntegrationTest.java | 4 ++ .../repository/ToDoRepositoryTest.java | 4 ++ .../springtodo/service/ToDoServiceTest.java | 7 ++- 20 files changed, 352 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/emobile/springtodo/SpringToDoApplication.java b/src/main/java/com/emobile/springtodo/SpringToDoApplication.java index 41980514..c9669b5d 100644 --- a/src/main/java/com/emobile/springtodo/SpringToDoApplication.java +++ b/src/main/java/com/emobile/springtodo/SpringToDoApplication.java @@ -3,6 +3,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +/** + * Главный класс приложения Spring Boot для ToDo. + * Запускает приложение. + */ @SpringBootApplication public class SpringToDoApplication { diff --git a/src/main/java/com/emobile/springtodo/configuration/OpenApiConfig.java b/src/main/java/com/emobile/springtodo/configuration/OpenApiConfig.java index d6c2e63a..42e98d61 100644 --- a/src/main/java/com/emobile/springtodo/configuration/OpenApiConfig.java +++ b/src/main/java/com/emobile/springtodo/configuration/OpenApiConfig.java @@ -5,9 +5,18 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +/** + * Конфигурация OpenAPI для документации API. + * Настроена информация о версии и описании API. + */ @Configuration public class OpenApiConfig { + /** + * Создает объект OpenAPI с информацией о приложении. + * + * @return OpenAPI объект с настроенной информацией. + */ @Bean public OpenAPI customOpenAPI() { return new OpenAPI() diff --git a/src/main/java/com/emobile/springtodo/configuration/RedisConfig.java b/src/main/java/com/emobile/springtodo/configuration/RedisConfig.java index 94f26030..dd332519 100644 --- a/src/main/java/com/emobile/springtodo/configuration/RedisConfig.java +++ b/src/main/java/com/emobile/springtodo/configuration/RedisConfig.java @@ -1,6 +1,5 @@ package com.emobile.springtodo.configuration; - import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; @@ -9,10 +8,20 @@ import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; +/** + * Конфигурация Redis кеширования. + * Включает создание и настройку CacheManager для работы с Redis в приложении. + */ @Configuration @EnableCaching public class RedisConfig { + /** + * Создает и настраивает CacheManager для работы с Redis. + * + * @param connectionFactory Фабрика подключения к Redis. + * @return CacheManager, использующий Redis для кеширования. + */ @Bean public CacheManager cacheManager(RedisConnectionFactory connectionFactory) { RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig(); diff --git a/src/main/java/com/emobile/springtodo/controller/GlobalExceptionHandler.java b/src/main/java/com/emobile/springtodo/controller/GlobalExceptionHandler.java index 95336ae7..686ebae0 100644 --- a/src/main/java/com/emobile/springtodo/controller/GlobalExceptionHandler.java +++ b/src/main/java/com/emobile/springtodo/controller/GlobalExceptionHandler.java @@ -16,14 +16,27 @@ import java.util.HashMap; import java.util.Map; +/** + * Глобальный обработчик исключений для всего приложения. + * Обрабатывает различные исключения, такие как ToDoNotFoundException и InvalidToDoException, + * а также ошибки валидации. + */ @Slf4j @RestControllerAdvice public class GlobalExceptionHandler { + /** + * Строит ответ на основе переданного исключения, статуса HTTP и ошибки валидации. + * + * @param ex Исключение, которое было выброшено. + * @param status Статус HTTP-ответа. + * @param request HTTP-запрос, содержащий путь. + * @param validationErrors Ошибки валидации (если имеются). + * @return Ответ с ошибкой. + */ private ResponseEntity buildResponse(Exception ex, HttpStatus status, HttpServletRequest request, Map validationErrors) { log.error("Exception: {} - {}", ex.getClass().getSimpleName(), ex.getMessage(), ex); - ErrorResponse response = ErrorResponse.builder() .timestamp(LocalDateTime.now()) .status(status.value()) @@ -32,7 +45,6 @@ private ResponseEntity buildResponse(Exception ex, HttpStatus sta .path(request.getRequestURI()) .build(); - if (validationErrors != null && !validationErrors.isEmpty()) { response.setDetails(validationErrors); } @@ -40,31 +52,52 @@ private ResponseEntity buildResponse(Exception ex, HttpStatus sta return ResponseEntity.status(status).body(response); } + /** + * Обработчик исключения ToDoNotFoundException. + * @param ex Исключение, выброшенное при отсутствии задачи. + * @param request HTTP-запрос. + * @return Ответ с ошибкой 404. + */ @ExceptionHandler(ToDoNotFoundException.class) public ResponseEntity handleNotFound(ToDoNotFoundException ex, HttpServletRequest request) { return buildResponse(ex, HttpStatus.NOT_FOUND, request, null); } + /** + * Обработчик исключения InvalidToDoException. + * @param ex Исключение, выброшенное при недействительной задаче. + * @param request HTTP-запрос. + * @return Ответ с ошибкой 400. + */ @ExceptionHandler(InvalidToDoException.class) public ResponseEntity handleInvalidToDo(InvalidToDoException ex, HttpServletRequest request) { return buildResponse(ex, HttpStatus.BAD_REQUEST, request, null); } + /** + * Обработчик исключений валидации. + * @param ex Исключение с ошибками валидации. + * @param request HTTP-запрос. + * @return Ответ с ошибками валидации. + */ @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity handleValidationExceptions(MethodArgumentNotValidException ex, HttpServletRequest request) { - Map validationErrors = new HashMap<>(); for (FieldError fieldError : ex.getBindingResult().getFieldErrors()) { validationErrors.put(fieldError.getField(), fieldError.getDefaultMessage()); } - log.error("Validation failed: {}", validationErrors); - return buildResponse(ex, HttpStatus.BAD_REQUEST, request, validationErrors); } + /** + * Обработчик общих исключений. + * @param ex Общее исключение. + * @param request HTTP-запрос. + * @return Ответ с ошибкой 500. + */ @ExceptionHandler(Exception.class) public ResponseEntity handleGeneral(Exception ex, HttpServletRequest request) { return buildResponse(ex, HttpStatus.INTERNAL_SERVER_ERROR, request, null); diff --git a/src/main/java/com/emobile/springtodo/controller/ToDoController.java b/src/main/java/com/emobile/springtodo/controller/ToDoController.java index fc43d61c..f9c59b10 100644 --- a/src/main/java/com/emobile/springtodo/controller/ToDoController.java +++ b/src/main/java/com/emobile/springtodo/controller/ToDoController.java @@ -3,9 +3,9 @@ import com.emobile.springtodo.dto.ToDoItemDto; import com.emobile.springtodo.service.ToDoService; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -13,6 +13,10 @@ import java.util.List; +/** + * Контроллер для работы с задачами ToDo. + * Предоставляет API для получения, создания, обновления и удаления задач. + */ @RestController @RequestMapping("/api/todos") @RequiredArgsConstructor @@ -21,6 +25,13 @@ public class ToDoController { private final ToDoService toDoService; + /** + * Получить все задачи с возможностью пагинации. + * + * @param limit Количество задач для выборки. + * @param offset Смещение для выборки. + * @return Список задач. + */ @GetMapping @Operation(summary = "Получить все задачи", description = "Получает список всех задач с возможностью пагинации") @ApiResponses(value = { @@ -32,6 +43,11 @@ public List getAll(@RequestParam(defaultValue = "10") int limit, return toDoService.getAll(limit, offset); } + /** + * Получить задачу по уникальному идентификатору. + * @param id Идентификатор задачи. + * @return Задача с данным ID. + */ @GetMapping("/{id}") @Operation(summary = "Получить задачу по ID", description = "Получает задачу по ее уникальному идентификатору") @ApiResponses(value = { @@ -42,6 +58,11 @@ public ToDoItemDto getById(@PathVariable Long id) { return toDoService.getById(id); } + /** + * Создать новую задачу. + * @param dto Данные задачи для создания. + * @return Созданная задача. + */ @PostMapping @Operation(summary = "Создать новую задачу", description = "Создает новую задачу") @ApiResponses(value = { @@ -52,6 +73,12 @@ public ResponseEntity create(@Valid @RequestBody ToDoItemDto dto) { return ResponseEntity.ok(toDoService.create(dto)); } + /** + * Обновить задачу по ID. + * @param id Идентификатор задачи для обновления. + * @param dto Данные задачи для обновления. + * @return Обновленная задача. + */ @PutMapping("/{id}") @Operation(summary = "Обновить задачу", description = "Обновляет существующую задачу по ее ID") @ApiResponses(value = { @@ -62,6 +89,11 @@ public ResponseEntity update(@PathVariable Long id, @Valid @Request return ResponseEntity.ok(toDoService.update(id, dto)); } + /** + * Удалить задачу по ID. + * @param id Идентификатор задачи для удаления. + * @return Ответ без содержимого (204). + */ @DeleteMapping("/{id}") @Operation(summary = "Удалить задачу", description = "Удаляет задачу по ее ID") @ApiResponses(value = { diff --git a/src/main/java/com/emobile/springtodo/dto/ErrorResponse.java b/src/main/java/com/emobile/springtodo/dto/ErrorResponse.java index 1783f66b..9607e0d7 100644 --- a/src/main/java/com/emobile/springtodo/dto/ErrorResponse.java +++ b/src/main/java/com/emobile/springtodo/dto/ErrorResponse.java @@ -6,6 +6,10 @@ import java.time.LocalDateTime; import java.util.Map; +/** + * Ответ с ошибкой, возвращаемый при возникновении исключения в приложении. + * Содержит информацию о времени возникновения ошибки, статусе HTTP, типе ошибки, сообщении, пути запроса и деталях ошибки. + */ @Getter @Setter @AllArgsConstructor @@ -14,21 +18,39 @@ @Schema(description = "Ответ с ошибкой") public class ErrorResponse { + /** + * Время возникновения ошибки. + */ @Schema(description = "Время возникновения ошибки", example = "2023-10-24T15:30:00") private LocalDateTime timestamp; + /** + * HTTP статус код ошибки. + */ @Schema(description = "HTTP статус код ошибки", example = "400") private int status; + /** + * Тип ошибки (например, Bad Request). + */ @Schema(description = "Тип ошибки", example = "Bad Request") private String error; + /** + * Сообщение об ошибке. + */ @Schema(description = "Сообщение об ошибке", example = "Задача не найдена") private String message; + /** + * Путь запроса, который вызвал ошибку. + */ @Schema(description = "Путь запроса, который вызвал ошибку", example = "/api/todos/1") private String path; + /** + * Подробности ошибок валидации, если они есть. + */ @Schema(description = "Подробности ошибок валидации, если есть", example = "{\"field\":\"Title cannot be blank\"}") private Map details; } diff --git a/src/main/java/com/emobile/springtodo/dto/ToDoItemDto.java b/src/main/java/com/emobile/springtodo/dto/ToDoItemDto.java index e5a6c009..9f85bb5e 100644 --- a/src/main/java/com/emobile/springtodo/dto/ToDoItemDto.java +++ b/src/main/java/com/emobile/springtodo/dto/ToDoItemDto.java @@ -7,6 +7,10 @@ import java.io.Serializable; +/** + * DTO для задачи (ToDoItem). + * Содержит поля задачи и использует аннотации для валидации данных. + */ @Getter @Setter @Builder @@ -14,18 +18,33 @@ @NoArgsConstructor public class ToDoItemDto implements Serializable { + /** + * Уникальный идентификатор задачи. + */ @Schema(description = "Уникальный идентификатор задачи", example = "1") private Long id; + /** + * Название задачи. + * Не может быть пустым и должно быть не более 100 символов. + */ @NotBlank(message = "Title cannot be blank") @Size(max = 100, message = "Title must be at most 100 characters long") @Schema(description = "Название задачи", example = "Сделать уборку", maxLength = 100) private String title; + /** + * Описание задачи. + * Должно быть не более 1000 символов. + */ @Size(max = 1000, message = "Description must be at most 1000 characters long") @Schema(description = "Описание задачи", example = "Убрать кухню и зал", maxLength = 1000) private String description; + /** + * Статус выполнения задачи. + * true — задача выполнена, false — задача не выполнена. + */ @Schema(description = "Статус выполнения задачи", example = "false") private boolean completed; } diff --git a/src/main/java/com/emobile/springtodo/entity/ToDoItem.java b/src/main/java/com/emobile/springtodo/entity/ToDoItem.java index c38c86fd..29f9c582 100644 --- a/src/main/java/com/emobile/springtodo/entity/ToDoItem.java +++ b/src/main/java/com/emobile/springtodo/entity/ToDoItem.java @@ -7,16 +7,39 @@ import java.time.LocalDateTime; +/** + * Класс, представляющий задачу (ToDoItem) в системе. + * Содержит информацию о задаче, такую как идентификатор, название, описание, статус выполнения и дата создания. + */ @Getter @Setter @AllArgsConstructor @NoArgsConstructor public class ToDoItem { + /** + * Уникальный идентификатор задачи. + */ private Long id; + + /** + * Название задачи. + */ private String title; + + /** + * Описание задачи. + */ private String description; + + /** + * Статус выполнения задачи. + */ private boolean completed; + + /** + * Дата и время создания задачи. + */ private LocalDateTime createdAt; } diff --git a/src/main/java/com/emobile/springtodo/exception/InvalidToDoException.java b/src/main/java/com/emobile/springtodo/exception/InvalidToDoException.java index 31f2ddee..cd1c407c 100644 --- a/src/main/java/com/emobile/springtodo/exception/InvalidToDoException.java +++ b/src/main/java/com/emobile/springtodo/exception/InvalidToDoException.java @@ -1,7 +1,16 @@ package com.emobile.springtodo.exception; +/** + * Исключение, которое выбрасывается, если задача ToDo является недействительной. + */ public class InvalidToDoException extends RuntimeException { + + /** + * Конструктор исключения. + * + * @param message Сообщение об ошибке. + */ public InvalidToDoException(String message) { super(message); } -} \ No newline at end of file +} diff --git a/src/main/java/com/emobile/springtodo/exception/ToDoNotFoundException.java b/src/main/java/com/emobile/springtodo/exception/ToDoNotFoundException.java index 560c3ef0..2c1dd0bf 100644 --- a/src/main/java/com/emobile/springtodo/exception/ToDoNotFoundException.java +++ b/src/main/java/com/emobile/springtodo/exception/ToDoNotFoundException.java @@ -1,6 +1,15 @@ package com.emobile.springtodo.exception; +/** + * Исключение, которое выбрасывается, если задача ToDo не найдена по ID. + */ public class ToDoNotFoundException extends RuntimeException { + + /** + * Конструктор исключения с ID задачи. + * + * @param id Идентификатор задачи. + */ public ToDoNotFoundException(Long id) { super("ToDo item with id " + id + " not found"); } diff --git a/src/main/java/com/emobile/springtodo/mapper/ToDoItemMapper.java b/src/main/java/com/emobile/springtodo/mapper/ToDoItemMapper.java index 4becf8a8..0ed637c8 100644 --- a/src/main/java/com/emobile/springtodo/mapper/ToDoItemMapper.java +++ b/src/main/java/com/emobile/springtodo/mapper/ToDoItemMapper.java @@ -3,8 +3,23 @@ import com.emobile.springtodo.dto.ToDoItemDto; import com.emobile.springtodo.entity.ToDoItem; +/** + * Интерфейс для маппинга между сущностью ToDoItem и DTO ToDoItemDto. + */ public interface ToDoItemMapper { + + /** + * Преобразует DTO в сущность. + * + * @param dto Объект ToDoItemDto, который нужно преобразовать в сущность. + * @return Сущность ToDoItem. + */ ToDoItem toEntity(ToDoItemDto dto); + /** + * Преобразует сущность в DTO. + * @param entity Объект ToDoItem, который нужно преобразовать в DTO. + * @return DTO ToDoItemDto. + */ ToDoItemDto toDto(ToDoItem entity); } diff --git a/src/main/java/com/emobile/springtodo/mapper/ToDoItemMapperImpl.java b/src/main/java/com/emobile/springtodo/mapper/ToDoItemMapperImpl.java index 3b2a8bf7..d40be106 100644 --- a/src/main/java/com/emobile/springtodo/mapper/ToDoItemMapperImpl.java +++ b/src/main/java/com/emobile/springtodo/mapper/ToDoItemMapperImpl.java @@ -6,9 +6,18 @@ import java.time.LocalDateTime; +/** + * Реализация маппера для преобразования сущности ToDoItem в DTO и наоборот. + */ @Component public class ToDoItemMapperImpl implements ToDoItemMapper { + /** + * Преобразует DTO в сущность ToDoItem. + * + * @param dto DTO для преобразования. + * @return Сущность ToDoItem. + */ @Override public ToDoItem toEntity(ToDoItemDto dto) { return new ToDoItem( @@ -20,6 +29,11 @@ public ToDoItem toEntity(ToDoItemDto dto) { ); } + /** + * Преобразует сущность ToDoItem в DTO. + * @param entity Сущность ToDoItem для преобразования. + * @return DTO ToDoItemDto. + */ @Override public ToDoItemDto toDto(ToDoItem entity) { return ToDoItemDto.builder() diff --git a/src/main/java/com/emobile/springtodo/repository/ToDoRepository.java b/src/main/java/com/emobile/springtodo/repository/ToDoRepository.java index 904bc6bf..8fd4130c 100644 --- a/src/main/java/com/emobile/springtodo/repository/ToDoRepository.java +++ b/src/main/java/com/emobile/springtodo/repository/ToDoRepository.java @@ -5,14 +5,43 @@ import java.util.List; import java.util.Optional; +/** + * Репозиторий для работы с задачами ToDo. + * Содержит методы для поиска, сохранения, обновления и удаления задач. + */ public interface ToDoRepository { + + /** + * Получить все задачи с поддержкой пагинации. + * + * @param limit Количество задач для выборки. + * @param offset Смещение для выборки. + * @return Список задач. + */ List findAll(int limit, int offset); + /** + * Найти задачу по ID. + * @param id Идентификатор задачи. + * @return Опциональная задача. + */ Optional findById(Long id); + /** + * Сохранить новую задачу. + * @param toDoItem Задача, которую нужно сохранить. + */ void save(ToDoItem toDoItem); + /** + * Обновить задачу. + * @param toDoItem Задача с обновленными данными. + */ void update(ToDoItem toDoItem); + /** + * Удалить задачу по ID. + * @param id Идентификатор задачи. + */ void deleteById(Long id); } diff --git a/src/main/java/com/emobile/springtodo/repository/ToDoRepositoryImpl.java b/src/main/java/com/emobile/springtodo/repository/ToDoRepositoryImpl.java index 9c40e4fe..ba22bb67 100644 --- a/src/main/java/com/emobile/springtodo/repository/ToDoRepositoryImpl.java +++ b/src/main/java/com/emobile/springtodo/repository/ToDoRepositoryImpl.java @@ -9,12 +9,19 @@ import java.util.List; import java.util.Optional; +/** + * Реализация репозитория для работы с задачами ToDo с использованием JdbcTemplate. + * Реализует методы поиска, сохранения, обновления и удаления задач в базе данных. + */ @Repository @RequiredArgsConstructor public class ToDoRepositoryImpl implements ToDoRepository { - private final JdbcTemplate jdbcTemplate; + private final JdbcTemplate jdbcTemplate; + /** + * Маппер для преобразования строки результата SQL запроса в объект ToDoItem. + */ private final RowMapper rowMapper = (rs, rowNum) -> new ToDoItem( rs.getLong("id"), rs.getString("title"), @@ -23,39 +30,60 @@ public class ToDoRepositoryImpl implements ToDoRepository { rs.getTimestamp("created_at").toLocalDateTime() ); + /** + * Получить все задачи с поддержкой пагинации. + * + * @param limit Количество задач для выборки. + * @param offset Смещение для выборки. + * @return Список задач. + */ @Override public List findAll(int limit, int offset) { String sql = "SELECT * FROM todo_items ORDER BY created_at DESC LIMIT ? OFFSET ?"; - return jdbcTemplate.query(sql, rowMapper, limit, offset); } + /** + * Найти задачу по ID. + * @param id Идентификатор задачи. + * @return Опциональная задача. + */ @Override public Optional findById(Long id) { String sql = "SELECT * FROM todo_items WHERE id = ?"; List result = jdbcTemplate.query(sql, rowMapper, id); - return result.isEmpty() ? Optional.empty() : Optional.of(result.getFirst()); + return result.isEmpty() ? Optional.empty() : Optional.of(result.get(0)); } + /** + * Сохранить новую задачу в базе данных. + * @param toDoItem Задача для сохранения. + */ @Override public void save(ToDoItem toDoItem) { String sql = "INSERT INTO todo_items (title, description, completed, created_at) VALUES (?,?,?,?) RETURNING id"; Long generatedId = jdbcTemplate.queryForObject(sql, Long.class, toDoItem.getTitle(), toDoItem.getDescription(), toDoItem.isCompleted(), toDoItem.getCreatedAt()); toDoItem.setId(generatedId); - } + /** + * Обновить существующую задачу. + * @param toDoItem Задача с обновленными данными. + */ @Override public void update(ToDoItem toDoItem) { String sql = "UPDATE todo_items SET title = ?, description = ?, completed = ? WHERE id = ?"; jdbcTemplate.update(sql, toDoItem.getTitle(), toDoItem.getDescription(), toDoItem.isCompleted(), toDoItem.getId()); } + /** + * Удалить задачу по ID. + * @param id Идентификатор задачи. + */ @Override public void deleteById(Long id) { String sql = "DELETE FROM todo_items WHERE id = ?"; jdbcTemplate.update(sql, id); - } } diff --git a/src/main/java/com/emobile/springtodo/service/ToDoService.java b/src/main/java/com/emobile/springtodo/service/ToDoService.java index 52515a8f..18d72ed8 100644 --- a/src/main/java/com/emobile/springtodo/service/ToDoService.java +++ b/src/main/java/com/emobile/springtodo/service/ToDoService.java @@ -4,14 +4,46 @@ import java.util.List; +/** + * Сервис для управления задачами ToDo. + * Содержит методы для получения, создания, обновления и удаления задач. + */ public interface ToDoService { + + /** + * Получить все задачи с поддержкой пагинации. + * + * @param limit Количество задач для выборки. + * @param offset Смещение для выборки. + * @return Список задач в виде DTO. + */ List getAll(int limit, int offset); + /** + * Получить задачу по ID. + * @param id Идентификатор задачи. + * @return DTO задачи. + */ ToDoItemDto getById(Long id); + /** + * Создать новую задачу. + * @param dto DTO задачи для создания. + * @return Созданная задача в виде DTO. + */ ToDoItemDto create(ToDoItemDto dto); + /** + * Обновить задачу по ID. + * @param id Идентификатор задачи для обновления. + * @param dto DTO с новыми данными задачи. + * @return Обновленная задача в виде DTO. + */ ToDoItemDto update(Long id, ToDoItemDto dto); + /** + * Удалить задачу по ID. + * @param id Идентификатор задачи для удаления. + */ void delete(Long id); -} \ No newline at end of file +} diff --git a/src/main/java/com/emobile/springtodo/service/ToDoServiceImpl.java b/src/main/java/com/emobile/springtodo/service/ToDoServiceImpl.java index 349377ee..e873f931 100644 --- a/src/main/java/com/emobile/springtodo/service/ToDoServiceImpl.java +++ b/src/main/java/com/emobile/springtodo/service/ToDoServiceImpl.java @@ -16,13 +16,25 @@ import java.util.List; import java.util.stream.Collectors; +/** + * Реализация сервиса для управления задачами ToDo. + * Включает методы для получения задач, создания, обновления и удаления задач с логированием. + */ @Slf4j @Service @RequiredArgsConstructor public class ToDoServiceImpl implements ToDoService { + private final ToDoRepository toDoRepository; private final ToDoItemMapper toDoItemMapper; + /** + * Получить все задачи с пагинацией. + * + * @param limit Количество задач для выборки. + * @param offset Смещение для выборки. + * @return Список задач в виде DTO. + */ @Override public List getAll(int limit, int offset) { log.info("Fetching ToDo items with limit={} and offset={}", limit, offset); @@ -32,6 +44,11 @@ public List getAll(int limit, int offset) { .collect(Collectors.toList()); } + /** + * Получить задачу по ID с кешированием. + * @param id Идентификатор задачи. + * @return DTO задачи. + */ @Cacheable(value = "todos", key = "#id") @Override public ToDoItemDto getById(Long id) { @@ -44,6 +61,11 @@ public ToDoItemDto getById(Long id) { return toDoItemMapper.toDto(item); } + /** + * Создание новой задачи. + * @param dto DTO задачи для создания. + * @return Созданная задача в виде DTO. + */ @Transactional @Override public ToDoItemDto create(ToDoItemDto dto) { @@ -53,6 +75,12 @@ public ToDoItemDto create(ToDoItemDto dto) { return toDoItemMapper.toDto(entity); } + /** + * Обновление задачи по ID с кешированием. + * @param id Идентификатор задачи для обновления. + * @param dto DTO с новыми данными задачи. + * @return Обновленная задача в виде DTO. + */ @Transactional @CachePut(value = "todos", key = "#id") @Override @@ -72,6 +100,10 @@ public ToDoItemDto update(Long id, ToDoItemDto dto) { return toDoItemMapper.toDto(item); } + /** + * Удаление задачи по ID с кешированием. + * @param id Идентификатор задачи для удаления. + */ @Transactional @CacheEvict(value = "todos", key = "#id") @Override diff --git a/src/test/java/com/emobile/springtodo/controller/BaseIntegrationTest.java b/src/test/java/com/emobile/springtodo/controller/BaseIntegrationTest.java index 9c518325..bda23a7b 100644 --- a/src/test/java/com/emobile/springtodo/controller/BaseIntegrationTest.java +++ b/src/test/java/com/emobile/springtodo/controller/BaseIntegrationTest.java @@ -7,6 +7,11 @@ import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.PostgreSQLContainer; + +/** + * Базовый класс для интеграционных тестов. + * Настроены контейнеры для PostgreSQL и Redis. + */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public abstract class BaseIntegrationTest { diff --git a/src/test/java/com/emobile/springtodo/controller/ToDoControllerIntegrationTest.java b/src/test/java/com/emobile/springtodo/controller/ToDoControllerIntegrationTest.java index 00c761d0..a009fcb4 100644 --- a/src/test/java/com/emobile/springtodo/controller/ToDoControllerIntegrationTest.java +++ b/src/test/java/com/emobile/springtodo/controller/ToDoControllerIntegrationTest.java @@ -12,6 +12,10 @@ import static org.assertj.core.api.Assertions.assertThat; +/** + * Интеграционные тесты для контроллера ToDo. + * Проверяются основные операции с задачами (создание, получение). + */ @ExtendWith(SpringExtension.class) @Import(ToDoServiceImpl.class) class ToDoControllerIntegrationTest extends BaseIntegrationTest { diff --git a/src/test/java/com/emobile/springtodo/repository/ToDoRepositoryTest.java b/src/test/java/com/emobile/springtodo/repository/ToDoRepositoryTest.java index 3321eed0..7bf01b75 100644 --- a/src/test/java/com/emobile/springtodo/repository/ToDoRepositoryTest.java +++ b/src/test/java/com/emobile/springtodo/repository/ToDoRepositoryTest.java @@ -13,6 +13,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.*; +/** + * Тесты для репозитория ToDo. + * Включает тестирование операций сохранения, обновления и удаления задач. + */ @ExtendWith(MockitoExtension.class) class ToDoRepositoryTest { diff --git a/src/test/java/com/emobile/springtodo/service/ToDoServiceTest.java b/src/test/java/com/emobile/springtodo/service/ToDoServiceTest.java index 6888c61c..661b9a17 100644 --- a/src/test/java/com/emobile/springtodo/service/ToDoServiceTest.java +++ b/src/test/java/com/emobile/springtodo/service/ToDoServiceTest.java @@ -16,9 +16,14 @@ import java.util.List; import java.util.Optional; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.*; +/** + * Тесты для сервиса ToDo. + * Включает тестирование всех CRUD операций (получение, создание, обновление, удаление). + */ @ExtendWith(MockitoExtension.class) class ToDoServiceTest { From 4e7f13d76c3ddbc11efe5771b675641eeaad750a Mon Sep 17 00:00:00 2001 From: Ohrim Date: Wed, 26 Feb 2025 17:51:39 +0300 Subject: [PATCH 4/7] feat: class authorship in the JavaDoc --- src/main/java/com/emobile/springtodo/SpringToDoApplication.java | 2 ++ .../com/emobile/springtodo/configuration/OpenApiConfig.java | 2 ++ .../java/com/emobile/springtodo/configuration/RedisConfig.java | 2 ++ .../emobile/springtodo/controller/GlobalExceptionHandler.java | 2 ++ .../java/com/emobile/springtodo/controller/ToDoController.java | 2 ++ src/main/java/com/emobile/springtodo/dto/ErrorResponse.java | 2 ++ src/main/java/com/emobile/springtodo/dto/ToDoItemDto.java | 2 ++ src/main/java/com/emobile/springtodo/entity/ToDoItem.java | 2 ++ .../com/emobile/springtodo/exception/InvalidToDoException.java | 2 ++ .../com/emobile/springtodo/exception/ToDoNotFoundException.java | 2 ++ src/main/java/com/emobile/springtodo/mapper/ToDoItemMapper.java | 2 ++ .../java/com/emobile/springtodo/mapper/ToDoItemMapperImpl.java | 2 ++ .../java/com/emobile/springtodo/repository/ToDoRepository.java | 2 ++ .../com/emobile/springtodo/repository/ToDoRepositoryImpl.java | 2 ++ src/main/java/com/emobile/springtodo/service/ToDoService.java | 2 ++ .../java/com/emobile/springtodo/service/ToDoServiceImpl.java | 2 ++ .../com/emobile/springtodo/controller/BaseIntegrationTest.java | 2 ++ .../springtodo/controller/ToDoControllerIntegrationTest.java | 2 ++ .../com/emobile/springtodo/repository/ToDoRepositoryTest.java | 2 ++ .../java/com/emobile/springtodo/service/ToDoServiceTest.java | 2 ++ 20 files changed, 40 insertions(+) diff --git a/src/main/java/com/emobile/springtodo/SpringToDoApplication.java b/src/main/java/com/emobile/springtodo/SpringToDoApplication.java index c9669b5d..f9e0190e 100644 --- a/src/main/java/com/emobile/springtodo/SpringToDoApplication.java +++ b/src/main/java/com/emobile/springtodo/SpringToDoApplication.java @@ -6,6 +6,8 @@ /** * Главный класс приложения Spring Boot для ToDo. * Запускает приложение. + * + * @author PavelOkhrimchuk */ @SpringBootApplication public class SpringToDoApplication { diff --git a/src/main/java/com/emobile/springtodo/configuration/OpenApiConfig.java b/src/main/java/com/emobile/springtodo/configuration/OpenApiConfig.java index 42e98d61..097fb9a0 100644 --- a/src/main/java/com/emobile/springtodo/configuration/OpenApiConfig.java +++ b/src/main/java/com/emobile/springtodo/configuration/OpenApiConfig.java @@ -8,6 +8,8 @@ /** * Конфигурация OpenAPI для документации API. * Настроена информация о версии и описании API. + * + * @author PavelOkhrimchuk */ @Configuration public class OpenApiConfig { diff --git a/src/main/java/com/emobile/springtodo/configuration/RedisConfig.java b/src/main/java/com/emobile/springtodo/configuration/RedisConfig.java index dd332519..6138e2b3 100644 --- a/src/main/java/com/emobile/springtodo/configuration/RedisConfig.java +++ b/src/main/java/com/emobile/springtodo/configuration/RedisConfig.java @@ -11,6 +11,8 @@ /** * Конфигурация Redis кеширования. * Включает создание и настройку CacheManager для работы с Redis в приложении. + * + * @author PavelOkhrimchuk */ @Configuration @EnableCaching diff --git a/src/main/java/com/emobile/springtodo/controller/GlobalExceptionHandler.java b/src/main/java/com/emobile/springtodo/controller/GlobalExceptionHandler.java index 686ebae0..21e3d6b6 100644 --- a/src/main/java/com/emobile/springtodo/controller/GlobalExceptionHandler.java +++ b/src/main/java/com/emobile/springtodo/controller/GlobalExceptionHandler.java @@ -20,6 +20,8 @@ * Глобальный обработчик исключений для всего приложения. * Обрабатывает различные исключения, такие как ToDoNotFoundException и InvalidToDoException, * а также ошибки валидации. + * + * @author PavelOkhrimchuk */ @Slf4j @RestControllerAdvice diff --git a/src/main/java/com/emobile/springtodo/controller/ToDoController.java b/src/main/java/com/emobile/springtodo/controller/ToDoController.java index f9c59b10..3aad2d61 100644 --- a/src/main/java/com/emobile/springtodo/controller/ToDoController.java +++ b/src/main/java/com/emobile/springtodo/controller/ToDoController.java @@ -16,6 +16,8 @@ /** * Контроллер для работы с задачами ToDo. * Предоставляет API для получения, создания, обновления и удаления задач. + * + * @author PavelOkhrimchuk */ @RestController @RequestMapping("/api/todos") diff --git a/src/main/java/com/emobile/springtodo/dto/ErrorResponse.java b/src/main/java/com/emobile/springtodo/dto/ErrorResponse.java index 9607e0d7..71d4e075 100644 --- a/src/main/java/com/emobile/springtodo/dto/ErrorResponse.java +++ b/src/main/java/com/emobile/springtodo/dto/ErrorResponse.java @@ -9,6 +9,8 @@ /** * Ответ с ошибкой, возвращаемый при возникновении исключения в приложении. * Содержит информацию о времени возникновения ошибки, статусе HTTP, типе ошибки, сообщении, пути запроса и деталях ошибки. + * + * @author PavelOkhrimchuk */ @Getter @Setter diff --git a/src/main/java/com/emobile/springtodo/dto/ToDoItemDto.java b/src/main/java/com/emobile/springtodo/dto/ToDoItemDto.java index 9f85bb5e..f0e89026 100644 --- a/src/main/java/com/emobile/springtodo/dto/ToDoItemDto.java +++ b/src/main/java/com/emobile/springtodo/dto/ToDoItemDto.java @@ -10,6 +10,8 @@ /** * DTO для задачи (ToDoItem). * Содержит поля задачи и использует аннотации для валидации данных. + * + * @author PavelOkhrimchuk */ @Getter @Setter diff --git a/src/main/java/com/emobile/springtodo/entity/ToDoItem.java b/src/main/java/com/emobile/springtodo/entity/ToDoItem.java index 29f9c582..80fc7ee1 100644 --- a/src/main/java/com/emobile/springtodo/entity/ToDoItem.java +++ b/src/main/java/com/emobile/springtodo/entity/ToDoItem.java @@ -10,6 +10,8 @@ /** * Класс, представляющий задачу (ToDoItem) в системе. * Содержит информацию о задаче, такую как идентификатор, название, описание, статус выполнения и дата создания. + * + * @author PavelOkhrimchuk */ @Getter @Setter diff --git a/src/main/java/com/emobile/springtodo/exception/InvalidToDoException.java b/src/main/java/com/emobile/springtodo/exception/InvalidToDoException.java index cd1c407c..3a2b77ac 100644 --- a/src/main/java/com/emobile/springtodo/exception/InvalidToDoException.java +++ b/src/main/java/com/emobile/springtodo/exception/InvalidToDoException.java @@ -2,6 +2,8 @@ /** * Исключение, которое выбрасывается, если задача ToDo является недействительной. + * + * @author PavelOkhrimchuk */ public class InvalidToDoException extends RuntimeException { diff --git a/src/main/java/com/emobile/springtodo/exception/ToDoNotFoundException.java b/src/main/java/com/emobile/springtodo/exception/ToDoNotFoundException.java index 2c1dd0bf..3efc1180 100644 --- a/src/main/java/com/emobile/springtodo/exception/ToDoNotFoundException.java +++ b/src/main/java/com/emobile/springtodo/exception/ToDoNotFoundException.java @@ -2,6 +2,8 @@ /** * Исключение, которое выбрасывается, если задача ToDo не найдена по ID. + * + * @author PavelOkhrimchuk */ public class ToDoNotFoundException extends RuntimeException { diff --git a/src/main/java/com/emobile/springtodo/mapper/ToDoItemMapper.java b/src/main/java/com/emobile/springtodo/mapper/ToDoItemMapper.java index 0ed637c8..c11d6a07 100644 --- a/src/main/java/com/emobile/springtodo/mapper/ToDoItemMapper.java +++ b/src/main/java/com/emobile/springtodo/mapper/ToDoItemMapper.java @@ -5,6 +5,8 @@ /** * Интерфейс для маппинга между сущностью ToDoItem и DTO ToDoItemDto. + * + * @author PavelOkhrimchuk */ public interface ToDoItemMapper { diff --git a/src/main/java/com/emobile/springtodo/mapper/ToDoItemMapperImpl.java b/src/main/java/com/emobile/springtodo/mapper/ToDoItemMapperImpl.java index d40be106..ef3846a3 100644 --- a/src/main/java/com/emobile/springtodo/mapper/ToDoItemMapperImpl.java +++ b/src/main/java/com/emobile/springtodo/mapper/ToDoItemMapperImpl.java @@ -8,6 +8,8 @@ /** * Реализация маппера для преобразования сущности ToDoItem в DTO и наоборот. + * + * @author PavelOkhrimchuk */ @Component public class ToDoItemMapperImpl implements ToDoItemMapper { diff --git a/src/main/java/com/emobile/springtodo/repository/ToDoRepository.java b/src/main/java/com/emobile/springtodo/repository/ToDoRepository.java index 8fd4130c..3f71a9f4 100644 --- a/src/main/java/com/emobile/springtodo/repository/ToDoRepository.java +++ b/src/main/java/com/emobile/springtodo/repository/ToDoRepository.java @@ -8,6 +8,8 @@ /** * Репозиторий для работы с задачами ToDo. * Содержит методы для поиска, сохранения, обновления и удаления задач. + * + * @author PavelOkhrimchuk */ public interface ToDoRepository { diff --git a/src/main/java/com/emobile/springtodo/repository/ToDoRepositoryImpl.java b/src/main/java/com/emobile/springtodo/repository/ToDoRepositoryImpl.java index ba22bb67..8970ccf1 100644 --- a/src/main/java/com/emobile/springtodo/repository/ToDoRepositoryImpl.java +++ b/src/main/java/com/emobile/springtodo/repository/ToDoRepositoryImpl.java @@ -12,6 +12,8 @@ /** * Реализация репозитория для работы с задачами ToDo с использованием JdbcTemplate. * Реализует методы поиска, сохранения, обновления и удаления задач в базе данных. + * + * @author PavelOkhrimchuk */ @Repository @RequiredArgsConstructor diff --git a/src/main/java/com/emobile/springtodo/service/ToDoService.java b/src/main/java/com/emobile/springtodo/service/ToDoService.java index 18d72ed8..14c3f279 100644 --- a/src/main/java/com/emobile/springtodo/service/ToDoService.java +++ b/src/main/java/com/emobile/springtodo/service/ToDoService.java @@ -7,6 +7,8 @@ /** * Сервис для управления задачами ToDo. * Содержит методы для получения, создания, обновления и удаления задач. + * + * @author PavelOkhrimchuk */ public interface ToDoService { diff --git a/src/main/java/com/emobile/springtodo/service/ToDoServiceImpl.java b/src/main/java/com/emobile/springtodo/service/ToDoServiceImpl.java index e873f931..a03f6b2f 100644 --- a/src/main/java/com/emobile/springtodo/service/ToDoServiceImpl.java +++ b/src/main/java/com/emobile/springtodo/service/ToDoServiceImpl.java @@ -19,6 +19,8 @@ /** * Реализация сервиса для управления задачами ToDo. * Включает методы для получения задач, создания, обновления и удаления задач с логированием. + * + * @author PavelOkhrimchuk */ @Slf4j @Service diff --git a/src/test/java/com/emobile/springtodo/controller/BaseIntegrationTest.java b/src/test/java/com/emobile/springtodo/controller/BaseIntegrationTest.java index bda23a7b..af665ba9 100644 --- a/src/test/java/com/emobile/springtodo/controller/BaseIntegrationTest.java +++ b/src/test/java/com/emobile/springtodo/controller/BaseIntegrationTest.java @@ -11,6 +11,8 @@ /** * Базовый класс для интеграционных тестов. * Настроены контейнеры для PostgreSQL и Redis. + * + * @author PavelOkhrimchuk */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public abstract class BaseIntegrationTest { diff --git a/src/test/java/com/emobile/springtodo/controller/ToDoControllerIntegrationTest.java b/src/test/java/com/emobile/springtodo/controller/ToDoControllerIntegrationTest.java index a009fcb4..cba123fe 100644 --- a/src/test/java/com/emobile/springtodo/controller/ToDoControllerIntegrationTest.java +++ b/src/test/java/com/emobile/springtodo/controller/ToDoControllerIntegrationTest.java @@ -15,6 +15,8 @@ /** * Интеграционные тесты для контроллера ToDo. * Проверяются основные операции с задачами (создание, получение). + * + * @author PavelOkhrimchuk */ @ExtendWith(SpringExtension.class) @Import(ToDoServiceImpl.class) diff --git a/src/test/java/com/emobile/springtodo/repository/ToDoRepositoryTest.java b/src/test/java/com/emobile/springtodo/repository/ToDoRepositoryTest.java index 7bf01b75..266273eb 100644 --- a/src/test/java/com/emobile/springtodo/repository/ToDoRepositoryTest.java +++ b/src/test/java/com/emobile/springtodo/repository/ToDoRepositoryTest.java @@ -16,6 +16,8 @@ /** * Тесты для репозитория ToDo. * Включает тестирование операций сохранения, обновления и удаления задач. + * + * @author PavelOkhrimchuk */ @ExtendWith(MockitoExtension.class) class ToDoRepositoryTest { diff --git a/src/test/java/com/emobile/springtodo/service/ToDoServiceTest.java b/src/test/java/com/emobile/springtodo/service/ToDoServiceTest.java index 661b9a17..3538a8cc 100644 --- a/src/test/java/com/emobile/springtodo/service/ToDoServiceTest.java +++ b/src/test/java/com/emobile/springtodo/service/ToDoServiceTest.java @@ -23,6 +23,8 @@ /** * Тесты для сервиса ToDo. * Включает тестирование всех CRUD операций (получение, создание, обновление, удаление). + * + * @author PavelOkhrimchuk */ @ExtendWith(MockitoExtension.class) class ToDoServiceTest { From 4d6166269e84b861495ac76e8246ac25aca9beeb Mon Sep 17 00:00:00 2001 From: pavel Date: Fri, 11 Apr 2025 19:56:59 +0300 Subject: [PATCH 5/7] feat: jenkins file --- Jenkinsfile | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 Jenkinsfile diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 00000000..c8242d2b --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,42 @@ +pipeline { + agent any + + environment { + IMAGE_NAME = 'ohrimpavel/spring-todo-app' + DOCKER_CREDENTIALS_ID = 'dockerhub-creds' + } + + stages { + stage('Checkout') { + steps { + checkout scm + } + } + + stage('Build') { + steps { + sh 'mvn clean install' + } + } + + stage('Test') { + steps { + sh 'mvn test' + } + } + + stage('Docker Build & Push') { + when { + branch 'main' + } + steps { + script { + docker.withRegistry('https://index.docker.io/v1/', DOCKER_CREDENTIALS_ID) { + def image = docker.build("${IMAGE_NAME}") + image.push("latest") + } + } + } + } + } +} From cb6d11eb840698e7d4ec245f6fc3cf3801a21750 Mon Sep 17 00:00:00 2001 From: pavel Date: Fri, 11 Apr 2025 20:26:09 +0300 Subject: [PATCH 6/7] fix --- Jenkinsfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index c8242d2b..a9100969 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -4,6 +4,7 @@ pipeline { environment { IMAGE_NAME = 'ohrimpavel/spring-todo-app' DOCKER_CREDENTIALS_ID = 'dockerhub-creds' + MAVEN_HOME = tool name: 'Maven 3', type: 'Maven' } stages { @@ -15,13 +16,13 @@ pipeline { stage('Build') { steps { - sh 'mvn clean install' + sh "${MAVEN_HOME}/bin/mvn clean install" } } stage('Test') { steps { - sh 'mvn test' + sh "${MAVEN_HOME}/bin/mvn test" } } From 67e0dd2c7e941abdbba2d22a9afb721c3411fdd7 Mon Sep 17 00:00:00 2001 From: pavel Date: Wed, 4 Jun 2025 11:17:37 +0300 Subject: [PATCH 7/7] refactor --- Jenkinsfile | 43 ------------------------------------------- 1 file changed, 43 deletions(-) delete mode 100644 Jenkinsfile diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index a9100969..00000000 --- a/Jenkinsfile +++ /dev/null @@ -1,43 +0,0 @@ -pipeline { - agent any - - environment { - IMAGE_NAME = 'ohrimpavel/spring-todo-app' - DOCKER_CREDENTIALS_ID = 'dockerhub-creds' - MAVEN_HOME = tool name: 'Maven 3', type: 'Maven' - } - - stages { - stage('Checkout') { - steps { - checkout scm - } - } - - stage('Build') { - steps { - sh "${MAVEN_HOME}/bin/mvn clean install" - } - } - - stage('Test') { - steps { - sh "${MAVEN_HOME}/bin/mvn test" - } - } - - stage('Docker Build & Push') { - when { - branch 'main' - } - steps { - script { - docker.withRegistry('https://index.docker.io/v1/', DOCKER_CREDENTIALS_ID) { - def image = docker.build("${IMAGE_NAME}") - image.push("latest") - } - } - } - } - } -}