diff --git a/pom.xml b/pom.xml index 2db888c..6ddbe30 100644 --- a/pom.xml +++ b/pom.xml @@ -60,6 +60,17 @@ org.springframework.boot spring-boot-starter-validation + + org.mapstruct + mapstruct + 1.5.5.Final + + + org.mapstruct + mapstruct-processor + 1.5.5.Final + provided + diff --git a/src/main/java/ru/practicum/shareit/booking/Booking.java b/src/main/java/ru/practicum/shareit/booking/Booking.java deleted file mode 100644 index 2d9c666..0000000 --- a/src/main/java/ru/practicum/shareit/booking/Booking.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.practicum.shareit.booking; - -/** - * TODO Sprint add-bookings. - */ -public class Booking { -} diff --git a/src/main/java/ru/practicum/shareit/booking/BookingStatus.java b/src/main/java/ru/practicum/shareit/booking/BookingStatus.java new file mode 100644 index 0000000..1be1bce --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/BookingStatus.java @@ -0,0 +1,11 @@ +package ru.practicum.shareit.booking; + +public enum BookingStatus { + WAITING, + + APPROVED, + + REJECTED, + + CANCELED +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/BookingController.java b/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java similarity index 73% rename from src/main/java/ru/practicum/shareit/booking/BookingController.java rename to src/main/java/ru/practicum/shareit/booking/controller/BookingController.java index b94493d..a3e0ebb 100644 --- a/src/main/java/ru/practicum/shareit/booking/BookingController.java +++ b/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java @@ -1,12 +1,10 @@ -package ru.practicum.shareit.booking; +package ru.practicum.shareit.booking.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -/** - * TODO Sprint add-bookings. - */ + @RestController @RequestMapping(path = "/bookings") public class BookingController { -} +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java b/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java index 861de9e..393e544 100644 --- a/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java +++ b/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java @@ -1,7 +1,28 @@ package ru.practicum.shareit.booking.dto; -/** - * TODO Sprint add-bookings. - */ +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import ru.practicum.shareit.booking.BookingStatus; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.user.dto.UserDto; + +import java.time.LocalDateTime; + + +@Getter +@Setter +@Builder(toBuilder = true) public class BookingDto { -} + private Long id; + + private LocalDateTime start; + + private LocalDateTime end; + + private ItemDto item; + + private UserDto booker; + + private BookingStatus status; +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/exception/ShareItException.java b/src/main/java/ru/practicum/shareit/booking/exception/ShareItException.java new file mode 100644 index 0000000..db4ad68 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/exception/ShareItException.java @@ -0,0 +1,31 @@ +package ru.practicum.shareit.booking.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +public class ShareItException extends RuntimeException { + private final HttpStatus status; + + public ShareItException(String message, HttpStatus status) { + super(message); + this.status = status; + } + + public HttpStatus getStatus() { + return status; + } + + @ResponseStatus(HttpStatus.NOT_FOUND) + public static class NotFoundException extends ShareItException { + public NotFoundException(String message) { + super(message, HttpStatus.NOT_FOUND); + } + } + + @ResponseStatus(HttpStatus.CONFLICT) + public static class ConflictException extends ShareItException { + public ConflictException(String message) { + super(message, HttpStatus.CONFLICT); + } + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/model/Booking.java b/src/main/java/ru/practicum/shareit/booking/model/Booking.java new file mode 100644 index 0000000..5382265 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/model/Booking.java @@ -0,0 +1,26 @@ +package ru.practicum.shareit.booking.model; + +import lombok.Getter; +import lombok.Setter; +import ru.practicum.shareit.booking.BookingStatus; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.user.model.User; + +import java.time.LocalDateTime; + + +@Getter +@Setter +public class Booking { + private Long id; + + private LocalDateTime start; + + private LocalDateTime end; + + private Item item; + + private User booker; + + private BookingStatus status; +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/Impl/ItemRepositoryImpl.java b/src/main/java/ru/practicum/shareit/item/Impl/ItemRepositoryImpl.java new file mode 100644 index 0000000..8bb6974 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/Impl/ItemRepositoryImpl.java @@ -0,0 +1,34 @@ +package ru.practicum.shareit.item.Impl; + +import org.springframework.stereotype.Component; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.item.repository.AbstractRepository; +import ru.practicum.shareit.item.repository.ItemRepository; + +@Component +public class ItemRepositoryImpl extends AbstractRepository implements ItemRepository { + + @Override + public Item create(Item item) { + setEntityId(item, nextId); + nextId++; + entities.put(item.getId(), item); + return item; + } + + @Override + public Item update(Item item) { + entities.put(item.getId(), item); + return item; + } + + @Override + protected void setEntityId(Item entity, Long id) { + entity.setId(id); + } + + @Override + protected Long getEntityId(Item entity) { + return entity.getId(); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/Impl/ItemServiceImpl.java b/src/main/java/ru/practicum/shareit/item/Impl/ItemServiceImpl.java new file mode 100644 index 0000000..0527ae9 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/Impl/ItemServiceImpl.java @@ -0,0 +1,90 @@ +package ru.practicum.shareit.item.Impl; + +import org.springframework.stereotype.Service; +import ru.practicum.shareit.booking.exception.ShareItException; +import ru.practicum.shareit.item.ItemMapper; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.item.repository.ItemRepository; +import ru.practicum.shareit.item.service.ItemService; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.repository.UserRepository; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class ItemServiceImpl implements ItemService { + private final ItemRepository itemRepository; + private final UserRepository userRepository; + private final ItemMapper itemMapper; + + public ItemServiceImpl(ItemRepository itemRepository, UserRepository userRepository, ItemMapper itemMapper) { + this.itemRepository = itemRepository; + this.userRepository = userRepository; + this.itemMapper = itemMapper; + } + + @Override + public List getAll(Long userId) { + return itemRepository.findAll().stream() + .filter(item -> item.getOwner().getId().equals(userId)) + .map(itemMapper::toItemDto) + .collect(Collectors.toList()); + } + + @Override + public ItemDto getById(Long id) { + Item item = itemRepository.findById(id) + .orElseThrow(() -> new ShareItException.NotFoundException("Не найдена вещь с id: " + id)); + + return itemMapper.toItemDto(item); + } + + @Override + public ItemDto create(ItemDto itemDto, Long userId) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new ShareItException.NotFoundException("Невозможно создать вещь - " + + "не найден пользователь с id: " + userId)); + Item item = itemMapper.toItem(itemDto); + item.setOwner(user); + itemRepository.create(item); + + return itemMapper.toItemDto(item); + } + + @Override + public ItemDto update(ItemDto itemDto, Long id, Long userId) { + Item item = itemRepository.findById(id) + .orElseThrow(() -> new ShareItException.NotFoundException("Не найдена вещь с id: " + id)); + + if (!item.getOwner().getId().equals(userId)) { + throw new ShareItException.NotFoundException("Невозможно обновить вещь - у пользователя с id: " + userId + " нет такой вещи"); + } + + Item updatedItem = itemMapper.updateItemFields(item, itemDto); + return itemMapper.toItemDto(itemRepository.update(updatedItem)); + } + + @Override + public void delete(Long id) { + getById(id); + itemRepository.delete(id); + } + + @Override + public List search(String text) { + if (text.isBlank()) { + return new ArrayList<>(); + } + + String searchText = text.toLowerCase(); + return itemRepository.findAll().stream() + .filter(item -> item.getAvailable() && + (item.getName().toLowerCase().contains(searchText) || + item.getDescription().toLowerCase().contains(searchText))) + .map(itemMapper::toItemDto) + .collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/ItemController.java b/src/main/java/ru/practicum/shareit/item/ItemController.java deleted file mode 100644 index bb17668..0000000 --- a/src/main/java/ru/practicum/shareit/item/ItemController.java +++ /dev/null @@ -1,12 +0,0 @@ -package ru.practicum.shareit.item; - -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * TODO Sprint add-controllers. - */ -@RestController -@RequestMapping("/items") -public class ItemController { -} diff --git a/src/main/java/ru/practicum/shareit/item/ItemMapper.java b/src/main/java/ru/practicum/shareit/item/ItemMapper.java new file mode 100644 index 0000000..ece92a4 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/ItemMapper.java @@ -0,0 +1,20 @@ +package ru.practicum.shareit.item; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.model.Item; + + +@Mapper(componentModel = "spring", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) +public interface ItemMapper { + + @Mapping(target = "request", expression = "java(item.getRequest() != null ? item.getRequest() : null)") + ItemDto toItemDto(Item item); + + Item toItem(ItemDto itemDto); + + Item updateItemFields(@MappingTarget Item targetItem, ItemDto sourceItemDto); +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/controller/ItemController.java b/src/main/java/ru/practicum/shareit/item/controller/ItemController.java new file mode 100644 index 0000000..d00b6fb --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/controller/ItemController.java @@ -0,0 +1,59 @@ +package ru.practicum.shareit.item.controller; + +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.service.ItemService; +import ru.practicum.shareit.item.util.Constants; + +import java.util.List; + +@RestController +@RequestMapping("/items") +public class ItemController { + private final ItemService itemService; + + public ItemController(ItemService itemService) { + this.itemService = itemService; + } + + @GetMapping + public List getAll(@RequestHeader(Constants.USER_ID_HEADER) Long userId) { + return itemService.getAll(userId); + } + + @GetMapping("/{id}") + public ItemDto getById(@PathVariable Long id) { + return itemService.getById(id); + } + + @PostMapping + public ItemDto create(@RequestHeader(Constants.USER_ID_HEADER) Long userId, @Valid @RequestBody ItemDto itemDto) { + return itemService.create(itemDto, userId); + } + + @PatchMapping("/{id}") + public ItemDto update(@RequestBody ItemDto itemDto, @PathVariable Long id, + @RequestHeader(Constants.USER_ID_HEADER) Long userId) { + return itemService.update(itemDto, id, userId); + } + + @DeleteMapping("/{id}") + public void delete(@PathVariable Long id) { + itemService.delete(id); + } + + @GetMapping("/search") + public List search(@RequestParam String text) { + return itemService.search(text); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java b/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java index 9319d7d..ed1ea71 100644 --- a/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java +++ b/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java @@ -1,7 +1,27 @@ package ru.practicum.shareit.item.dto; -/** - * TODO Sprint add-controllers. - */ +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import ru.practicum.shareit.request.model.ItemRequest; + + +@Getter +@Setter +@Builder(toBuilder = true) public class ItemDto { -} + private Long id; + + @NotBlank + private String name; + + @NotBlank + private String description; + + @NotNull + private Boolean available; + + private ItemRequest request; +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/model/Item.java b/src/main/java/ru/practicum/shareit/item/model/Item.java index 44eb73d..7021c25 100644 --- a/src/main/java/ru/practicum/shareit/item/model/Item.java +++ b/src/main/java/ru/practicum/shareit/item/model/Item.java @@ -1,7 +1,30 @@ package ru.practicum.shareit.item.model; -/** - * TODO Sprint add-controllers. - */ +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.user.model.User; + + +@Getter +@Setter +@Builder public class Item { -} + private Long id; + + @NotBlank + private String name; + + @NotBlank + private String description; + + @NotNull + private Boolean available; + + private User owner; + + private ItemRequest request; +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/repository/AbstractRepository.java b/src/main/java/ru/practicum/shareit/item/repository/AbstractRepository.java new file mode 100644 index 0000000..4f5dd72 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/repository/AbstractRepository.java @@ -0,0 +1,28 @@ +package ru.practicum.shareit.item.repository; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public abstract class AbstractRepository { + protected final Map entities = new HashMap<>(); + protected Long nextId = 1L; + + public List findAll() { + return new ArrayList<>(entities.values()); + } + + public Optional findById(K id) { + return entities.containsKey(id) ? Optional.of(entities.get(id)) : Optional.empty(); + } + + public void delete(K id) { + entities.remove(id); + } + + protected abstract void setEntityId(T entity, Long id); + + protected abstract K getEntityId(T entity); +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java b/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java new file mode 100644 index 0000000..9bf5801 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java @@ -0,0 +1,18 @@ +package ru.practicum.shareit.item.repository; + +import ru.practicum.shareit.item.model.Item; + +import java.util.List; +import java.util.Optional; + +public interface ItemRepository { + List findAll(); + + Optional findById(Long id); + + Item create(Item item); + + Item update(Item item); + + void delete(Long id); +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/service/ItemService.java b/src/main/java/ru/practicum/shareit/item/service/ItemService.java new file mode 100644 index 0000000..07f58b1 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/service/ItemService.java @@ -0,0 +1,19 @@ +package ru.practicum.shareit.item.service; + +import ru.practicum.shareit.item.dto.ItemDto; + +import java.util.List; + +public interface ItemService { + List getAll(Long userId); + + ItemDto getById(Long id); + + ItemDto create(ItemDto itemDto, Long userId); + + ItemDto update(ItemDto itemDto, Long id, Long userId); + + void delete(Long id); + + List search(String text); +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/util/Constants.java b/src/main/java/ru/practicum/shareit/item/util/Constants.java new file mode 100644 index 0000000..d2d31b8 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/util/Constants.java @@ -0,0 +1,5 @@ +package ru.practicum.shareit.item.util; + +public class Constants { + public static final String USER_ID_HEADER = "X-Sharer-User-Id"; +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/request/ItemRequest.java b/src/main/java/ru/practicum/shareit/request/ItemRequest.java deleted file mode 100644 index 95d6f23..0000000 --- a/src/main/java/ru/practicum/shareit/request/ItemRequest.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.practicum.shareit.request; - -/** - * TODO Sprint add-item-requests. - */ -public class ItemRequest { -} diff --git a/src/main/java/ru/practicum/shareit/request/ItemRequestController.java b/src/main/java/ru/practicum/shareit/request/controller/ItemRequestController.java similarity index 72% rename from src/main/java/ru/practicum/shareit/request/ItemRequestController.java rename to src/main/java/ru/practicum/shareit/request/controller/ItemRequestController.java index 064e2e9..5eaee82 100644 --- a/src/main/java/ru/practicum/shareit/request/ItemRequestController.java +++ b/src/main/java/ru/practicum/shareit/request/controller/ItemRequestController.java @@ -1,12 +1,10 @@ -package ru.practicum.shareit.request; +package ru.practicum.shareit.request.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -/** - * TODO Sprint add-item-requests. - */ + @RestController @RequestMapping(path = "/requests") public class ItemRequestController { -} +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java b/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java index 7b3ed54..d87f428 100644 --- a/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java +++ b/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java @@ -1,7 +1,26 @@ package ru.practicum.shareit.request.dto; -/** - * TODO Sprint add-item-requests. - */ +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.user.dto.UserDto; + +import java.time.LocalDateTime; +import java.util.List; + + +@Getter +@Setter +@Builder(toBuilder = true) public class ItemRequestDto { -} + private Long id; + + private String description; + + private UserDto requestor; + + private LocalDateTime created; + + private List items; +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java b/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java new file mode 100644 index 0000000..5612dda --- /dev/null +++ b/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java @@ -0,0 +1,22 @@ +package ru.practicum.shareit.request.model; + +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; +import ru.practicum.shareit.user.model.User; + +import java.time.LocalDateTime; + + +@Getter +@Setter +public class ItemRequest { + private Long id; + + @NotBlank + private String description; + + private User requestor; + + private LocalDateTime created; +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/user/Impl/UserRepositoryImpl.java b/src/main/java/ru/practicum/shareit/user/Impl/UserRepositoryImpl.java new file mode 100644 index 0000000..5f895be --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/Impl/UserRepositoryImpl.java @@ -0,0 +1,34 @@ +package ru.practicum.shareit.user.Impl; + +import org.springframework.stereotype.Component; +import ru.practicum.shareit.item.repository.AbstractRepository; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.repository.UserRepository; + +@Component +public class UserRepositoryImpl extends AbstractRepository implements UserRepository { + + @Override + public User create(User user) { + setEntityId(user, nextId); + nextId++; + entities.put(user.getId(), user); + return user; + } + + @Override + public User update(User user) { + entities.put(user.getId(), user); + return user; + } + + @Override + protected void setEntityId(User entity, Long id) { + entity.setId(id); + } + + @Override + protected Long getEntityId(User entity) { + return entity.getId(); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/user/Impl/UserServiceImpl.java b/src/main/java/ru/practicum/shareit/user/Impl/UserServiceImpl.java new file mode 100644 index 0000000..ac0e48b --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/Impl/UserServiceImpl.java @@ -0,0 +1,84 @@ +package ru.practicum.shareit.user.Impl; + +import org.springframework.stereotype.Service; +import ru.practicum.shareit.booking.exception.ShareItException; +import ru.practicum.shareit.user.UserMapper; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.repository.UserRepository; +import ru.practicum.shareit.user.service.UserService; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +public class UserServiceImpl implements UserService { + private final UserRepository userRepository; + private final UserMapper userMapper; + private final Set emailSet = new HashSet<>(); + + public UserServiceImpl(UserRepository userRepository, UserMapper userMapper) { + this.userRepository = userRepository; + this.userMapper = userMapper; + } + + @Override + public List getAll() { + return userRepository.findAll().stream() + .map(userMapper::toUserDto) + .collect(Collectors.toList()); + } + + @Override + public UserDto getById(Long id) { + User user = userRepository.findById(id) + .orElseThrow(() -> new ShareItException.NotFoundException("Не найден пользователь с id: " + id)); + + return userMapper.toUserDto(user); + } + + @Override + public UserDto create(User user) { + checkEmailUniqueness(user); + User createdUser = userRepository.create(user); + emailSet.add(createdUser.getEmail().toLowerCase()); + return userMapper.toUserDto(createdUser); + } + + @Override + public UserDto update(User user, Long id) { + User updatedUser = userRepository.findById(id) + .orElseThrow(() -> new ShareItException.NotFoundException("Невозможно обновить данные пользователя. " + + "Не найден пользователь с id: " + id)); + + if (user.getEmail() != null && !user.getEmail().equals(updatedUser.getEmail())) { + checkEmailUniqueness(user); + emailSet.remove(updatedUser.getEmail().toLowerCase()); + emailSet.add(user.getEmail().toLowerCase()); + } + + User updated = userMapper.updateUserFields(updatedUser, userMapper.toUserDto(user)); + return userMapper.toUserDto(userRepository.update(updated)); + } + + @Override + public void delete(Long id) { + User user = userRepository.findById(id) + .orElseThrow(() -> new ShareItException.NotFoundException("Не найден пользователь с id: " + id)); + emailSet.remove(user.getEmail().toLowerCase()); + userRepository.delete(id); + } + + private void checkEmailUniqueness(User user) { + if (user.getEmail() == null) { + return; + } + + String email = user.getEmail().toLowerCase(); + if (emailSet.contains(email)) { + throw new ShareItException.ConflictException("Пользователь с таким email уже зарегистрирован"); + } + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/user/User.java b/src/main/java/ru/practicum/shareit/user/User.java deleted file mode 100644 index ae6e7f3..0000000 --- a/src/main/java/ru/practicum/shareit/user/User.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.practicum.shareit.user; - -/** - * TODO Sprint add-controllers. - */ -public class User { -} diff --git a/src/main/java/ru/practicum/shareit/user/UserController.java b/src/main/java/ru/practicum/shareit/user/UserController.java deleted file mode 100644 index 03039b9..0000000 --- a/src/main/java/ru/practicum/shareit/user/UserController.java +++ /dev/null @@ -1,12 +0,0 @@ -package ru.practicum.shareit.user; - -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * TODO Sprint add-controllers. - */ -@RestController -@RequestMapping(path = "/users") -public class UserController { -} diff --git a/src/main/java/ru/practicum/shareit/user/UserMapper.java b/src/main/java/ru/practicum/shareit/user/UserMapper.java new file mode 100644 index 0000000..6dcdc52 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/UserMapper.java @@ -0,0 +1,18 @@ +package ru.practicum.shareit.user; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.model.User; + + +@Mapper(componentModel = "spring", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) +public interface UserMapper { + + UserDto toUserDto(User user); + + User toUser(UserDto userDto); + + User updateUserFields(@MappingTarget User targetUser, UserDto sourceUserDto); +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/user/controller/UserController.java b/src/main/java/ru/practicum/shareit/user/controller/UserController.java new file mode 100644 index 0000000..09f47e1 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/controller/UserController.java @@ -0,0 +1,52 @@ +package ru.practicum.shareit.user.controller; + +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import ru.practicum.shareit.user.Impl.UserServiceImpl; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.model.User; + +import java.util.List; + + +@RestController +@RequestMapping(path = "/users") +public class UserController { + private final UserServiceImpl userService; + + public UserController(UserServiceImpl userService) { + this.userService = userService; + } + + @GetMapping + public List getAll() { + return userService.getAll(); + } + + @GetMapping("/{id}") + public UserDto getById(@PathVariable Long id) { + return userService.getById(id); + } + + @PostMapping + public UserDto create(@Valid @RequestBody User user) { + return userService.create(user); + } + + @PatchMapping("/{id}") + public UserDto update(@RequestBody User user, @PathVariable Long id) { + return userService.update(user, id); + } + + @DeleteMapping("/{id}") + public void delete(@PathVariable Long id) { + userService.delete(id); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/user/dto/UserDto.java b/src/main/java/ru/practicum/shareit/user/dto/UserDto.java new file mode 100644 index 0000000..781f1d1 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/dto/UserDto.java @@ -0,0 +1,22 @@ +package ru.practicum.shareit.user.dto; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + + + +@Getter +@Setter +@Builder(toBuilder = true) +public class UserDto { + private Long id; + + @NotBlank + private String name; + + @Email + private String email; +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/user/model/User.java b/src/main/java/ru/practicum/shareit/user/model/User.java new file mode 100644 index 0000000..9647105 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/model/User.java @@ -0,0 +1,22 @@ +package ru.practicum.shareit.user.model; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + + +@Getter +@Setter +@Builder +public class User { + private Long id; + + @NotBlank + private String name; + + @Email + @NotBlank + private String email; +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java b/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java new file mode 100644 index 0000000..34465a7 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java @@ -0,0 +1,18 @@ +package ru.practicum.shareit.user.repository; + +import ru.practicum.shareit.user.model.User; + +import java.util.List; +import java.util.Optional; + +public interface UserRepository { + List findAll(); + + Optional findById(Long id); + + User create(User user); + + User update(User user); + + void delete(Long id); +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/user/service/UserService.java b/src/main/java/ru/practicum/shareit/user/service/UserService.java new file mode 100644 index 0000000..f5f23d4 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/service/UserService.java @@ -0,0 +1,18 @@ +package ru.practicum.shareit.user.service; + +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.model.User; + +import java.util.List; + +public interface UserService { + List getAll(); + + UserDto getById(Long id); + + UserDto create(User user); + + UserDto update(User user, Long id); + + void delete(Long id); +} \ No newline at end of file diff --git a/src/test/java/ru/practicum/shareit/item/controller/ItemControllerTest.java b/src/test/java/ru/practicum/shareit/item/controller/ItemControllerTest.java new file mode 100644 index 0000000..ead826a --- /dev/null +++ b/src/test/java/ru/practicum/shareit/item/controller/ItemControllerTest.java @@ -0,0 +1,255 @@ +package ru.practicum.shareit.item.controller; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; +import ru.practicum.shareit.booking.exception.ShareItException; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.user.UserMapper; +import ru.practicum.shareit.user.controller.UserController; +import ru.practicum.shareit.user.dto.UserDto; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +class ItemControllerTest { + @Autowired + private ItemController itemController; + + @Autowired + private UserController userController; + + @Autowired + private UserMapper userMapper; + + private ItemDto itemDto; + private UserDto userDto; + private Long userId; + + @BeforeEach + void setUp() { + userDto = UserDto.builder() + .name("Test Owner") + .email("owner@email.com") + .build(); + UserDto createdUser = userController.create(userMapper.toUser(userDto)); + userId = createdUser.getId(); + + itemDto = ItemDto.builder() + .name("Test Item") + .description("Test Description") + .available(true) + .build(); + } + + @Nested // Тесты на создание предметов + @DisplayName("Creating items") + class CreateItemTests { + @Test + @DisplayName("Successful creation of an item") + void createItemTest() { + ItemDto createdItem = itemController.create(userId, itemDto); + + assertNotNull(createdItem); + assertEquals(1L, createdItem.getId()); + assertEquals(itemDto.getName(), createdItem.getName()); + assertEquals(itemDto.getDescription(), createdItem.getDescription()); + assertEquals(itemDto.getAvailable(), createdItem.getAvailable()); + } + + @Test + @DisplayName("Error when creating an item with a non-existent user") + void createItemWithNonExistentUserTest() { + assertThrows(ShareItException.NotFoundException.class, + () -> itemController.create(999L, itemDto)); + } + } + + @Nested // Тесты на получение предметов + @DisplayName("Getting items") + class GetItemTests { + @Test + @DisplayName("Getting an item by ID") + void getItemByIdTest() { + ItemDto createdItem = itemController.create(userId, itemDto); + ItemDto retrievedItem = itemController.getById(createdItem.getId()); + + assertNotNull(retrievedItem); + assertEquals(createdItem.getId(), retrievedItem.getId()); + assertEquals(createdItem.getName(), retrievedItem.getName()); + assertEquals(createdItem.getDescription(), retrievedItem.getDescription()); + assertEquals(createdItem.getAvailable(), retrievedItem.getAvailable()); + } + + @Test + @DisplayName("Getting all the user's items") + void getAllUserItemsTest() { + itemController.create(userId, itemDto); + + ItemDto secondItem = ItemDto.builder() + .name("Second Item") + .description("Another Description") + .available(true) + .build(); + itemController.create(userId, secondItem); + + List items = itemController.getAll(userId); + + assertEquals(2, items.size()); + assertEquals("Test Item", items.get(0).getName()); + assertEquals("Second Item", items.get(1).getName()); + } + + @Test + @DisplayName("Error when receiving a non-existent item") + void getNonExistentItemTest() { + assertThrows(ShareItException.NotFoundException.class, + () -> itemController.getById(999L)); + } + } + + @Nested // Тесты на обновление предметов + @DisplayName("Updating items") + class UpdateItemTests { + @Test + @DisplayName("Full item update") + void updateItemTest() { + ItemDto createdItem = itemController.create(userId, itemDto); + + ItemDto updateRequest = ItemDto.builder() + .name("Updated Item") + .description("Updated Description") + .available(false) + .build(); + + ItemDto updatedItem = itemController.update(updateRequest, createdItem.getId(), userId); + + assertEquals("Updated Item", updatedItem.getName()); + assertEquals("Updated Description", updatedItem.getDescription()); + assertEquals(false, updatedItem.getAvailable()); + + ItemDto retrievedItem = itemController.getById(createdItem.getId()); + assertEquals("Updated Item", retrievedItem.getName()); + assertEquals("Updated Description", retrievedItem.getDescription()); + assertEquals(false, retrievedItem.getAvailable()); + } + + @Test + @DisplayName("Partial item update") + void partialUpdateItemTest() { + ItemDto createdItem = itemController.create(userId, itemDto); + ItemDto result; + + result = itemController.update( + ItemDto.builder().name("New Name").build(), + createdItem.getId(), + userId + ); + assertEquals("New Name", result.getName()); + assertEquals(itemDto.getDescription(), result.getDescription()); + assertEquals(itemDto.getAvailable(), result.getAvailable()); + + result = itemController.update( + ItemDto.builder().description("New Description").build(), + createdItem.getId(), + userId + ); + assertEquals("New Name", result.getName()); + assertEquals("New Description", result.getDescription()); + assertEquals(itemDto.getAvailable(), result.getAvailable()); + + result = itemController.update( + ItemDto.builder().available(false).build(), + createdItem.getId(), + userId + ); + assertEquals("New Name", result.getName()); + assertEquals("New Description", result.getDescription()); + assertEquals(false, result.getAvailable()); + } + + @Test + @DisplayName("Error when updating an item by a non-owner") + void updateItemByNonOwnerTest() { + ItemDto createdItem = itemController.create(userId, itemDto); + + UserDto anotherUserDto = UserDto.builder() + .name("Another User") + .email("another@email.com") + .build(); + UserDto anotherUser = userController.create(userMapper.toUser(anotherUserDto)); + + ItemDto updateRequest = ItemDto.builder() + .name("Unauthorized Update") + .build(); + + assertThrows(ShareItException.NotFoundException.class, + () -> itemController.update(updateRequest, createdItem.getId(), anotherUser.getId())); + } + } + + @Nested // Тесты на удаление предметов + @DisplayName("Deleting items") + class DeleteItemTests { + @Test + @DisplayName("Successful removal of an item") + void deleteItemTest() { + ItemDto createdItem = itemController.create(userId, itemDto); + assertEquals(1, itemController.getAll(userId).size()); + + itemController.delete(createdItem.getId()); + + assertEquals(0, itemController.getAll(userId).size()); + } + } + + @Nested // Тесты на поиск предметов + @DisplayName("Searching for items") + class SearchItemTests { + @Test + @DisplayName("Search for items based on various criteria") + void searchItemsTest() { + itemController.create(userId, itemDto); + + ItemDto secondItem = ItemDto.builder() + .name("Special Item") + .description("Unique features") + .available(true) + .build(); + itemController.create(userId, secondItem); + + ItemDto unavailableItem = ItemDto.builder() + .name("Unavailable Special") + .description("Not for rent") + .available(false) + .build(); + itemController.create(userId, unavailableItem); + + List results = itemController.search("Test"); + assertEquals(1, results.size()); + assertEquals("Test Item", results.get(0).getName()); + + results = itemController.search("Unique"); + assertEquals(1, results.size()); + assertEquals("Special Item", results.get(0).getName()); + + results = itemController.search("Item"); + assertEquals(2, results.size()); + + results = itemController.search("Unavailable"); + assertEquals(0, results.size()); + + results = itemController.search(""); + assertEquals(0, results.size()); + } + } +} \ No newline at end of file diff --git a/src/test/java/ru/practicum/shareit/user/controller/UserControllerTest.java b/src/test/java/ru/practicum/shareit/user/controller/UserControllerTest.java new file mode 100644 index 0000000..c1c5962 --- /dev/null +++ b/src/test/java/ru/practicum/shareit/user/controller/UserControllerTest.java @@ -0,0 +1,176 @@ +package ru.practicum.shareit.user.controller; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; +import ru.practicum.shareit.booking.exception.ShareItException; +import ru.practicum.shareit.user.UserMapper; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.model.User; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +class UserControllerTest { + @Autowired + private UserController userController; + + @Autowired + private UserMapper userMapper; + + private UserDto userDto; + private User user; + + @BeforeEach + void setUp() { + userDto = UserDto.builder() + .name("Test User") + .email("test@email.com") + .build(); + + user = userMapper.toUser(userDto); + } + + @Nested // Тесты на создание пользователей + @DisplayName("Creating Users") + class CreateUserTests { + @Test + @DisplayName("Successful user creation") + void createUserTest() { + UserDto createdUser = userController.create(user); + + assertNotNull(createdUser); + assertEquals(1L, createdUser.getId()); + assertEquals(userDto.getName(), createdUser.getName()); + assertEquals(userDto.getEmail(), createdUser.getEmail()); + } + + @Test + @DisplayName("Error when creating a user with a duplicate email") + void createUserWithDuplicateEmailTest() { + userController.create(user); + + User duplicateUser = User.builder() + .name("Another User") + .email("test@email.com") // Тот же email + .build(); + + assertThrows(ShareItException.ConflictException.class, + () -> userController.create(duplicateUser)); + } + } + + @Nested // Тесты на получение пользователей + @DisplayName("Getting Users") + class GetUserTests { + @Test + @DisplayName("Getting a user by ID") + void getUserByIdTest() { + UserDto createdUser = userController.create(user); + UserDto retrievedUser = userController.getById(createdUser.getId()); + + assertNotNull(retrievedUser); + assertEquals(createdUser.getId(), retrievedUser.getId()); + assertEquals(createdUser.getName(), retrievedUser.getName()); + assertEquals(createdUser.getEmail(), retrievedUser.getEmail()); + } + + @Test + @DisplayName("Getting all users") + void getAllUsersTest() { + userController.create(user); + + // Создаем второго пользователя + UserDto secondUser = UserDto.builder() + .name("Second User") + .email("second@email.com") + .build(); + userController.create(userMapper.toUser(secondUser)); + + List users = userController.getAll(); + + assertEquals(2, users.size()); + assertEquals("Test User", users.get(0).getName()); + assertEquals("Second User", users.get(1).getName()); + } + + @Test + @DisplayName("Error when receiving a non-existent user") + void getNonExistentUserTest() { + assertThrows(ShareItException.NotFoundException.class, + () -> userController.getById(999L)); + } + } + + @Nested // Тесты на обновление пользователей + @DisplayName("Updating users") + class UpdateUserTests { + @Test + @DisplayName("Full user update") + void updateUserTest() { + UserDto createdUser = userController.create(user); + + User updatedUser = User.builder() + .name("Updated Name") + .email("updated@email.com") + .build(); + + UserDto result = userController.update(updatedUser, createdUser.getId()); + + assertEquals("Updated Name", result.getName()); + assertEquals("updated@email.com", result.getEmail()); + + UserDto retrievedUser = userController.getById(createdUser.getId()); + assertEquals("Updated Name", retrievedUser.getName()); + assertEquals("updated@email.com", retrievedUser.getEmail()); + } + + @Test // Тест на частичное обновление пользователя + @DisplayName("Partial user update") + void partialUpdateUserTest() { + UserDto createdUser = userController.create(user); + UserDto result; + + User nameUpdate = User.builder() + .name("New Name") + .build(); + result = userController.update(nameUpdate, createdUser.getId()); + assertEquals("New Name", result.getName()); + assertEquals(userDto.getEmail(), result.getEmail()); + + User emailUpdate = User.builder() + .email("new@email.com") + .build(); + result = userController.update(emailUpdate, createdUser.getId()); + assertEquals("New Name", result.getName()); + assertEquals("new@email.com", result.getEmail()); + } + } + + @Nested // Тесты на удаление пользователей + @DisplayName("Deleting Users") + class DeleteUserTests { + @Test + @DisplayName("Successful user deletion") + void deleteUserTest() { + UserDto createdUser = userController.create(user); + assertEquals(1, userController.getAll().size()); + + userController.delete(createdUser.getId()); + + assertEquals(0, userController.getAll().size()); + + assertThrows(ShareItException.NotFoundException.class, + () -> userController.getById(createdUser.getId())); + } + } +} \ No newline at end of file