Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,13 @@
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package ru.practicum.shareit.exception;

public class DuplicateDataException extends RuntimeException {
public DuplicateDataException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package ru.practicum.shareit.exception;

public class ForbiddenAccessException extends RuntimeException {
public ForbiddenAccessException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package ru.practicum.shareit.exception;

import java.util.function.Supplier;

public class NotFoundException extends RuntimeException {
public NotFoundException(String message) {
super(message);
}

public static Supplier<NotFoundException> supplier(String message, Object... args) {
return () -> new NotFoundException(message.formatted(args));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package ru.practicum.shareit.exception.dto;

public record ErrorResponse(String name, String message) {
}
Comment thread
LightInTheFire marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package ru.practicum.shareit.exception.dto;

import java.util.List;

public record ValidationErrorResponse(List<Violation> violations) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package ru.practicum.shareit.exception.dto;

public record Violation(String fieldName, String message) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package ru.practicum.shareit.exception.handler;

import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import ru.practicum.shareit.exception.DuplicateDataException;
import ru.practicum.shareit.exception.ForbiddenAccessException;
import ru.practicum.shareit.exception.NotFoundException;
import ru.practicum.shareit.exception.dto.ErrorResponse;
import ru.practicum.shareit.exception.dto.ValidationErrorResponse;
import ru.practicum.shareit.exception.dto.Violation;

import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@RestControllerAdvice
class GlobalExceptionHandler {

@ExceptionHandler
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResponse onException(Exception ex) {
log.error("Error occurred while processing request {}", ex.getMessage());
return new ErrorResponse("internal server error",
"An error occurred while processing request");
}

@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ValidationErrorResponse onConstraintValidationException(
ConstraintViolationException ex
) {
final List<Violation> violations = ex.getConstraintViolations().stream()
.map(
violation -> new Violation(
violation.getPropertyPath().toString(),
violation.getMessage()
)
)
.collect(Collectors.toList());
log.warn(violations.toString());
return new ValidationErrorResponse(violations);
}

@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ValidationErrorResponse onMethodArgumentNotValidException(
MethodArgumentNotValidException ex
) {
final List<Violation> violations = ex.getBindingResult().getFieldErrors().stream()
.map(error -> new Violation(error.getField(), error.getDefaultMessage()))
.collect(Collectors.toList());
log.warn(violations.toString());
return new ValidationErrorResponse(violations);
}

@ExceptionHandler(NotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse onNotFoundException(NotFoundException ex) {
log.warn("Not found exception occurred while processing request {}", ex.getMessage());
return new ErrorResponse("not found", ex.getMessage());
}

@ExceptionHandler(DuplicateDataException.class)
@ResponseStatus(HttpStatus.CONFLICT)
public ErrorResponse onDuplicateDataException(DuplicateDataException ex) {
log.warn("Duplicate data exception occurred while processing request {}", ex.getMessage());
return new ErrorResponse("duplicate data", ex.getMessage());
}

@ExceptionHandler(ForbiddenAccessException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
public ErrorResponse onForbiddenAccessException(ForbiddenAccessException ex) {
log.warn("Forbidden access exception occurred while processing request {}", ex.getMessage());
return new ErrorResponse("forbidden", ex.getMessage());
}
}
59 changes: 54 additions & 5 deletions src/main/java/ru/practicum/shareit/item/ItemController.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,61 @@
package ru.practicum.shareit.item;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import ru.practicum.shareit.item.dto.ItemDto;
import ru.practicum.shareit.item.dto.NewItemDto;
import ru.practicum.shareit.item.dto.UpdateItemDto;

/**
* TODO Sprint add-controllers.
*/
import java.util.Collection;

@Slf4j
@Validated
@RestController
@RequestMapping("/items")
@RequiredArgsConstructor
public class ItemController {
private static final String SHARER_USER_ID_HEADER = "X-Sharer-User-Id";
private final ItemService itemService;

@GetMapping("/{itemId}")
public ItemDto getItem(
@RequestHeader(SHARER_USER_ID_HEADER) long userId,
@PathVariable long itemId
) {
return itemService.getItemOfUserById(userId, itemId);
}

@GetMapping
public Collection<ItemDto> getItems(
@RequestHeader(SHARER_USER_ID_HEADER) long userId
) {
return itemService.getAllItemsOfUser(userId);
}

@PostMapping
public ItemDto createItem(
@RequestHeader(SHARER_USER_ID_HEADER) long userId,
@RequestBody @Valid NewItemDto newItemDto
) {
return itemService.saveItem(userId, newItemDto);
}

@PatchMapping("/{itemId}")
public ItemDto updateItem(
@RequestHeader(SHARER_USER_ID_HEADER) long userId,
@PathVariable long itemId,
@RequestBody @Valid UpdateItemDto updatedItem
) {
return itemService.updateItem(userId, itemId, updatedItem);
}

@GetMapping("/search")
public Collection<ItemDto> searchItems(
@RequestParam(name = "text") String query
) {
return itemService.searchItems(query);
}
}
45 changes: 45 additions & 0 deletions src/main/java/ru/practicum/shareit/item/ItemMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package ru.practicum.shareit.item;

import lombok.experimental.UtilityClass;
import ru.practicum.shareit.item.dto.ItemDto;
import ru.practicum.shareit.item.dto.NewItemDto;
import ru.practicum.shareit.item.dto.UpdateItemDto;
import ru.practicum.shareit.item.model.Item;

@UtilityClass
public class ItemMapper {

public ItemDto toItemDto(Item item) {
return new ItemDto(
item.getId(),
item.getName(),
item.getDescription(),
item.isAvailable(),
item.getRequest() != null ? item.getRequest().getId() : null
);
}

public Item toItem(NewItemDto newItemDto) {
Item item = new Item();
item.setName(newItemDto.name());
item.setDescription(newItemDto.description());
item.setAvailable(newItemDto.available());
return item;
}

public Item updateItem(Item item, UpdateItemDto updateItemDto) {
if (updateItemDto.hasName()) {
item.setName(updateItemDto.name());
}

if (updateItemDto.hasDescription()) {
item.setDescription(updateItemDto.description());
}

if (updateItemDto.hasAvailable()) {
item.setAvailable(updateItemDto.available());
}

return item;
}
}
19 changes: 19 additions & 0 deletions src/main/java/ru/practicum/shareit/item/ItemService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package ru.practicum.shareit.item;

import ru.practicum.shareit.item.dto.ItemDto;
import ru.practicum.shareit.item.dto.NewItemDto;
import ru.practicum.shareit.item.dto.UpdateItemDto;

import java.util.Collection;

public interface ItemService {
ItemDto getItemOfUserById(long userId, long itemId);

Collection<ItemDto> getAllItemsOfUser(long userId);

ItemDto saveItem(long userId, NewItemDto newItem);

ItemDto updateItem(long userId, long itemId, UpdateItemDto updatedItem);

Collection<ItemDto> searchItems(String query);
}
90 changes: 90 additions & 0 deletions src/main/java/ru/practicum/shareit/item/ItemServiceImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package ru.practicum.shareit.item;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import ru.practicum.shareit.exception.ForbiddenAccessException;
import ru.practicum.shareit.exception.NotFoundException;
import ru.practicum.shareit.item.dto.ItemDto;
import ru.practicum.shareit.item.dto.NewItemDto;
import ru.practicum.shareit.item.dto.UpdateItemDto;
import ru.practicum.shareit.item.model.Item;
import ru.practicum.shareit.item.repository.ItemRepository;
import ru.practicum.shareit.user.model.User;
import ru.practicum.shareit.user.repository.UserRepository;

import java.util.Collection;
import java.util.Collections;

@Slf4j
@Service
@RequiredArgsConstructor
public class ItemServiceImpl implements ItemService {
private final ItemRepository itemRepository;
private final UserRepository userRepository;

@Override
public ItemDto getItemOfUserById(long userId, long itemId) {
getUserOrThrow(userId);

return itemRepository.findById(itemId)
.map(ItemMapper::toItemDto)
.orElseThrow(NotFoundException.supplier("item with id %d not found", itemId));
}

@Override
public Collection<ItemDto> getAllItemsOfUser(long userId) {
getUserOrThrow(userId);

return itemRepository.findAllByUserId(userId)
.stream()
.map(ItemMapper::toItemDto)
.toList();
}

@Override
public ItemDto saveItem(long userId, NewItemDto newItem) {
User owner = getUserOrThrow(userId);
Item item = ItemMapper.toItem(newItem);
item.setOwner(owner);
item = itemRepository.save(item);
return ItemMapper.toItemDto(item);
}

@Override
public ItemDto updateItem(long userId, long itemId, UpdateItemDto newItem) {
getUserOrThrow(userId);
Item item = getItemOrThrow(itemId);
if (item.getOwner().getId() != userId) {
throw new ForbiddenAccessException("Only owner of item can update it");
}
Item updatedItem = ItemMapper.updateItem(item, newItem);
itemRepository.save(updatedItem);
return ItemMapper.toItemDto(updatedItem);
}


@Override
public Collection<ItemDto> searchItems(String query) {
if (query.isBlank()) {
return Collections.emptyList();
}

return itemRepository.searchItems(query)
.stream()
.map(ItemMapper::toItemDto)
.toList();
}

private Item getItemOrThrow(long itemId) {
return itemRepository.findById(itemId).orElseThrow(
NotFoundException.supplier("Item with id %d not found", itemId)
);
}

private User getUserOrThrow(long userId) {
return userRepository.findById(userId).orElseThrow(
NotFoundException.supplier("User with id %d not found", userId)
);
}
}
11 changes: 7 additions & 4 deletions src/main/java/ru/practicum/shareit/item/dto/ItemDto.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package ru.practicum.shareit.item.dto;

/**
* TODO Sprint add-controllers.
*/
public class ItemDto {
public record ItemDto(
Long id,
String name,
String description,
boolean available,
Long requestId
) {
}
11 changes: 11 additions & 0 deletions src/main/java/ru/practicum/shareit/item/dto/NewItemDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package ru.practicum.shareit.item.dto;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

public record NewItemDto(
@NotBlank String name,
@NotBlank String description,
@NotNull Boolean available
) {
}
Loading