diff --git a/pom.xml b/pom.xml index 2db888c..b771773 100644 --- a/pom.xml +++ b/pom.xml @@ -82,6 +82,32 @@ + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.1.2 + + checkstyle.xml + true + true + true + + + + + check + + compile + + + + + com.puppycrawl.tools + checkstyle + 10.3 + + + diff --git a/src/main/java/ru/practicum/shareit/ShareItApp.java b/src/main/java/ru/practicum/shareit/ShareItApp.java index a00ad56..92c71dc 100644 --- a/src/main/java/ru/practicum/shareit/ShareItApp.java +++ b/src/main/java/ru/practicum/shareit/ShareItApp.java @@ -9,5 +9,4 @@ public class ShareItApp { public static void main(String[] args) { SpringApplication.run(ShareItApp.class, args); } - } 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..51269e0 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/BookingStatus.java @@ -0,0 +1,8 @@ +package ru.practicum.shareit.booking; + +public enum BookingStatus { + WAITING, + APPROVED, + REJECTED, + CANCELED +} 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..848cf61 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,23 @@ package ru.practicum.shareit.booking.dto; -/** - * TODO Sprint add-bookings. - */ +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Data; +import lombok.experimental.FieldDefaults; +import ru.practicum.shareit.booking.BookingStatus; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.user.model.User; + +import java.time.LocalDateTime; + +@Data +@Builder +@FieldDefaults(level = AccessLevel.PRIVATE) public class BookingDto { + Long id; + LocalDateTime start; + LocalDateTime end; + Item item; + User booker; + BookingStatus status; } 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..1fd5353 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/model/Booking.java @@ -0,0 +1,23 @@ +package ru.practicum.shareit.booking.model; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Data; +import lombok.experimental.FieldDefaults; +import ru.practicum.shareit.booking.BookingStatus; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.user.model.User; + +import java.time.LocalDateTime; + +@Data +@Builder +@FieldDefaults(level = AccessLevel.PRIVATE) +public class Booking { + Long id; + LocalDateTime start; + LocalDateTime end; + Item item; + User booker; + BookingStatus status; +} diff --git a/src/main/java/ru/practicum/shareit/exception/DuplicateValidationException.java b/src/main/java/ru/practicum/shareit/exception/DuplicateValidationException.java new file mode 100644 index 0000000..79b0c8b --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/DuplicateValidationException.java @@ -0,0 +1,7 @@ +package ru.practicum.shareit.exception; + +public class DuplicateValidationException extends RuntimeException { + public DuplicateValidationException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/practicum/shareit/exception/ExceptionsHandler.java b/src/main/java/ru/practicum/shareit/exception/ExceptionsHandler.java new file mode 100644 index 0000000..3f9689f --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/ExceptionsHandler.java @@ -0,0 +1,35 @@ +package ru.practicum.shareit.exception; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +@Slf4j +public class ExceptionsHandler { + + @ExceptionHandler(NotFoundException.class) + @ResponseStatus(HttpStatus.NOT_FOUND) + public ErrorResponse handleNotFoundError(NotFoundException exception) { + log.warn("Не найдена сущность для запроса {}", exception.getMessage()); + return new ErrorResponse("Не найдено", exception.getMessage()); + } + + @ExceptionHandler(DuplicateValidationException.class) + @ResponseStatus(HttpStatus.CONFLICT) + public ErrorResponse handleDuplicateError(DuplicateValidationException exception) { + log.error("Конфликт данных {}", exception.getMessage()); + return new ErrorResponse("Дупликация информации:", exception.getMessage()); + } + + @ExceptionHandler(ForbiddenException.class) + @ResponseStatus(HttpStatus.FORBIDDEN) + public ErrorResponse handleForbiddenError(ForbiddenException exception) { + log.error("Попытка запроса на лицом с недостаточными правами {}", exception.getMessage()); + return new ErrorResponse("Доступ запрещен:", exception.getMessage()); + } + + public record ErrorResponse(String error, String details) {} +} diff --git a/src/main/java/ru/practicum/shareit/exception/ForbiddenException.java b/src/main/java/ru/practicum/shareit/exception/ForbiddenException.java new file mode 100644 index 0000000..9266153 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/ForbiddenException.java @@ -0,0 +1,7 @@ +package ru.practicum.shareit.exception; + +public class ForbiddenException extends RuntimeException { + public ForbiddenException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/practicum/shareit/exception/NotFoundException.java b/src/main/java/ru/practicum/shareit/exception/NotFoundException.java new file mode 100644 index 0000000..508b545 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/NotFoundException.java @@ -0,0 +1,7 @@ +package ru.practicum.shareit.exception; + +public class NotFoundException extends RuntimeException { + public NotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/practicum/shareit/item/ItemController.java b/src/main/java/ru/practicum/shareit/item/ItemController.java index bb17668..d024aa8 100644 --- a/src/main/java/ru/practicum/shareit/item/ItemController.java +++ b/src/main/java/ru/practicum/shareit/item/ItemController.java @@ -1,12 +1,51 @@ package ru.practicum.shareit.item; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import lombok.AllArgsConstructor; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.dto.UpdateItemDto; +import ru.practicum.shareit.item.service.ItemService; + +import java.util.Collection; -/** - * TODO Sprint add-controllers. - */ @RestController @RequestMapping("/items") +@AllArgsConstructor public class ItemController { + + private static final String USER_ID_HEADER = "X-Sharer-User-Id"; + private final ItemService itemService; + + @GetMapping + public Collection getAllByUser(@RequestHeader(USER_ID_HEADER) Long userId) { + return itemService.getAllByUser(userId); + } + + @GetMapping("/{itemId}") + public ItemDto getById(@RequestHeader(USER_ID_HEADER) Long userId, + @PathVariable Long itemId) { + return itemService.getById(userId, itemId); + } + + @PostMapping + public ItemDto create(@RequestHeader(USER_ID_HEADER) Long userId, + @RequestBody @Valid ItemDto item) { + return itemService.create(userId, item); + } + + @PatchMapping("/{itemId}") + public ItemDto update(@RequestHeader(USER_ID_HEADER) Long userId, + @PathVariable @Positive Long itemId, + @RequestBody @Valid UpdateItemDto item) { + return itemService.update(userId, itemId, item); + } + + @GetMapping("/search") + public Collection search(@RequestHeader(USER_ID_HEADER) Long userId, + @RequestParam(name = "text") String text) { + return itemService.search(userId, text); + } + } 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..949c43e 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,22 @@ package ru.practicum.shareit.item.dto; -/** - * TODO Sprint add-controllers. - */ +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Data; +import lombok.experimental.FieldDefaults; + +@Data +@Builder +@FieldDefaults(level = AccessLevel.PRIVATE) public class ItemDto { + Long id; + @NotBlank + String name; + @NotBlank + String description; + @NotNull + Boolean available; + Long requestId; } diff --git a/src/main/java/ru/practicum/shareit/item/dto/UpdateItemDto.java b/src/main/java/ru/practicum/shareit/item/dto/UpdateItemDto.java new file mode 100644 index 0000000..2f816a1 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/dto/UpdateItemDto.java @@ -0,0 +1,13 @@ +package ru.practicum.shareit.item.dto; + +import lombok.AccessLevel; +import lombok.Data; +import lombok.experimental.FieldDefaults; + +@Data +@FieldDefaults(level = AccessLevel.PRIVATE) +public class UpdateItemDto { + String name; + String description; + Boolean available; +} diff --git a/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java b/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java new file mode 100644 index 0000000..cf7c9a0 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java @@ -0,0 +1,28 @@ +package ru.practicum.shareit.item.mapper; + +import lombok.experimental.UtilityClass; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.model.Item; + +@UtilityClass +public class ItemMapper { + + public ItemDto toItemDto(Item item) { + return ItemDto.builder() + .id(item.getId()) + .name(item.getName()) + .description(item.getDescription()) + .available(item.isAvailable()) + .requestId(item.getRequest() == null ? null : item.getRequest().getId()) + .build(); + } + + public Item toItem(ItemDto itemDto) { + return Item.builder() + .id(itemDto.getId()) + .name(itemDto.getName()) + .description(itemDto.getDescription()) + .available(itemDto.getAvailable()) + .build(); + } +} 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..3896da0 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,20 @@ package ru.practicum.shareit.item.model; -/** - * TODO Sprint add-controllers. - */ +import lombok.*; +import lombok.experimental.FieldDefaults; +import ru.practicum.shareit.request.ItemRequest; +import ru.practicum.shareit.user.model.User; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE) public class Item { + Long id; + String name; + String description; + boolean available; + User owner; + ItemRequest request; } diff --git a/src/main/java/ru/practicum/shareit/item/repository/InMemoryItemRepository.java b/src/main/java/ru/practicum/shareit/item/repository/InMemoryItemRepository.java new file mode 100644 index 0000000..293cde5 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/repository/InMemoryItemRepository.java @@ -0,0 +1,66 @@ +package ru.practicum.shareit.item.repository; + +import org.springframework.stereotype.Repository; +import ru.practicum.shareit.item.dto.UpdateItemDto; +import ru.practicum.shareit.item.model.Item; + +import java.util.*; + +@Repository +public class InMemoryItemRepository implements ItemRepository { + + private final HashMap items = new HashMap<>(); + + @Override + public Optional getById(Long itemId) { + return Optional.ofNullable(items.get(itemId)); + } + + @Override + public Collection getByUserId(Long id) { + return items.values().stream() + .filter(item -> Objects.equals(item.getOwner().getId(), id)) + .toList(); + } + + @Override + public Item create(Item item) { + item.setId(generateNextId()); + items.put(item.getId(), item); + return item; + } + + @Override + public Item update(Item item, UpdateItemDto updateItem) { + if (updateItem.getName() != null && !updateItem.getName().isBlank()) { + item.setName(updateItem.getName()); + } + + if (updateItem.getDescription() != null && !updateItem.getDescription().isBlank()) { + item.setDescription(updateItem.getDescription()); + } + + if (updateItem.getAvailable() != null) { + item.setAvailable(updateItem.getAvailable()); + } + return item; + } + + @Override + public Collection searchItems(String text) { + if (text.isBlank()) return Collections.emptyList(); + + return items.values().stream() + .filter(Item::isAvailable) + .filter(item -> item.getName().toLowerCase().contains(text.toLowerCase()) || + item.getDescription().toLowerCase().contains(text.toLowerCase())) + .toList(); + } + + private Long generateNextId() { + Long nextId = items.keySet().stream() + .max(Long::compareTo) + .orElse(0L); + return ++nextId; + } +} 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..e016f1b --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java @@ -0,0 +1,20 @@ +package ru.practicum.shareit.item.repository; + +import ru.practicum.shareit.item.dto.UpdateItemDto; +import ru.practicum.shareit.item.model.Item; + +import java.util.Collection; +import java.util.Optional; + +public interface ItemRepository { + + Optional getById(Long itemId); + + Item create(Item item); + + Item update(Item item, UpdateItemDto updateItem); + + Collection getByUserId(Long id); + + Collection searchItems(String text); +} 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..bd796f8 --- /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 ru.practicum.shareit.item.dto.UpdateItemDto; + +import java.util.Collection; + +public interface ItemService { + + Collection getAllByUser(Long userId); + + ItemDto getById(Long userId, Long id); + + ItemDto create(Long userId, ItemDto item); + + ItemDto update(Long userId, Long itemId, UpdateItemDto item); + + Collection search(Long userId, String text); +} diff --git a/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java b/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java new file mode 100644 index 0000000..61c4b67 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java @@ -0,0 +1,77 @@ +package ru.practicum.shareit.item.service; + +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Service; +import ru.practicum.shareit.exception.ForbiddenException; +import ru.practicum.shareit.exception.NotFoundException; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.dto.UpdateItemDto; +import ru.practicum.shareit.item.mapper.ItemMapper; +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; + +@Service +@AllArgsConstructor +public class ItemServiceImpl implements ItemService { + + private final ItemRepository itemRepository; + private final UserRepository userRepository; + + @Override + public Collection getAllByUser(Long userId) { + throwIfUserNotExist(userId); + return itemRepository.getByUserId(userId).stream() + .map(ItemMapper::toItemDto) + .toList(); + } + + @Override + public ItemDto getById(Long userId, Long itemId) { + throwIfUserNotExist(userId); + return itemRepository.getById(itemId) + .map(ItemMapper::toItemDto) + .orElseThrow(() -> new NotFoundException("Вещь с ID: %s не найдена".formatted(itemId))); + } + + @Override + public ItemDto create(Long userId, ItemDto item) { + User owner = throwIfUserNotExist(userId); + Item newItem = ItemMapper.toItem(item); + newItem.setOwner(owner); + newItem = itemRepository.create(newItem); + return ItemMapper.toItemDto(newItem); + } + + @Override + public ItemDto update(Long userId, Long itemId, UpdateItemDto updateItem) { + throwIfUserNotExist(userId); + Item item = throwIfItemNotExist(itemId); + if (!userId.equals(item.getOwner().getId())) { + throw new ForbiddenException("Обновлять вещь может только ее владелец."); + } + Item updatedItem = itemRepository.update(item, updateItem); + return ItemMapper.toItemDto(updatedItem); + } + + @Override + public Collection search(Long userId, String text) { + throwIfUserNotExist(userId); + return itemRepository.searchItems(text).stream() + .map(ItemMapper::toItemDto) + .toList(); + } + + private User throwIfUserNotExist(Long id) { + return userRepository.getById(id) + .orElseThrow(() -> new NotFoundException("Пользователь с ID: %s не найден".formatted(id))); + } + + private Item throwIfItemNotExist(Long id) { + return itemRepository.getById(id) + .orElseThrow(() -> new NotFoundException("Пользователь с ID: %s не найден".formatted(id))); + } +} diff --git a/src/main/java/ru/practicum/shareit/request/ItemRequest.java b/src/main/java/ru/practicum/shareit/request/ItemRequest.java index 95d6f23..614c368 100644 --- a/src/main/java/ru/practicum/shareit/request/ItemRequest.java +++ b/src/main/java/ru/practicum/shareit/request/ItemRequest.java @@ -1,7 +1,19 @@ package ru.practicum.shareit.request; -/** - * TODO Sprint add-item-requests. - */ +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Data; +import lombok.experimental.FieldDefaults; + +import java.time.LocalDateTime; + +@Data +@Builder +@FieldDefaults(level = AccessLevel.PRIVATE) public class ItemRequest { + Long id; + String description; + Long requesterId; + LocalDateTime created; } + 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..308adc5 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,18 @@ package ru.practicum.shareit.request.dto; -/** - * TODO Sprint add-item-requests. - */ +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Data; +import lombok.experimental.FieldDefaults; + +import java.time.LocalDateTime; + +@Data +@Builder +@FieldDefaults(level = AccessLevel.PRIVATE) public class ItemRequestDto { + Long id; + String description; + Long requesterId; + LocalDateTime created; } diff --git a/src/main/java/ru/practicum/shareit/request/mapper/ItemRequestMapper.java b/src/main/java/ru/practicum/shareit/request/mapper/ItemRequestMapper.java new file mode 100644 index 0000000..15d8368 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/request/mapper/ItemRequestMapper.java @@ -0,0 +1,27 @@ +package ru.practicum.shareit.request.mapper; + +import lombok.experimental.UtilityClass; +import ru.practicum.shareit.request.ItemRequest; +import ru.practicum.shareit.request.dto.ItemRequestDto; + +@UtilityClass +public class ItemRequestMapper { + + public ItemRequestDto toItemRequestDto(ItemRequest itemRequest) { + return ItemRequestDto.builder() + .id(itemRequest.getId()) + .description(itemRequest.getDescription()) + .requesterId(itemRequest.getId()) + .created(itemRequest.getCreated()) + .build(); + } + + public ItemRequest toItemRequest(ItemRequestDto itemRequestDto) { + return ItemRequest.builder() + .id(itemRequestDto.getId()) + .description(itemRequestDto.getDescription()) + .requesterId(itemRequestDto.getId()) + .created(itemRequestDto.getCreated()) + .build(); + } +} 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 index 03039b9..674b18f 100644 --- a/src/main/java/ru/practicum/shareit/user/UserController.java +++ b/src/main/java/ru/practicum/shareit/user/UserController.java @@ -1,12 +1,54 @@ package ru.practicum.shareit.user; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.user.dto.UpdateUserDto; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.service.UserService; + +import java.util.Collection; -/** - * TODO Sprint add-controllers. - */ @RestController @RequestMapping(path = "/users") +@RequiredArgsConstructor +@Slf4j public class UserController { + + private final UserService userService; + + @GetMapping + public Collection getAll() { + log.info("Запрошены все пользователи"); + return userService.getAll(); + } + + @GetMapping("/{userId}") + public UserDto getById(@PathVariable @Positive Long userId) { + log.info("Запрошен пользователь с ID: " + userId); + return userService.getById(userId); + } + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public UserDto create(@RequestBody @Valid UserDto user) { + return userService.create(user); + } + + @PatchMapping("/{userId}") + public UserDto update(@RequestBody @Valid UpdateUserDto user, @PathVariable Long userId) { + user.setId(userId); + return userService.update(user); + } + + @DeleteMapping("/{userId}") + public void update(@PathVariable @Positive Long userId) { + userService.delete(userId); + } + + + } diff --git a/src/main/java/ru/practicum/shareit/user/dto/UpdateUserDto.java b/src/main/java/ru/practicum/shareit/user/dto/UpdateUserDto.java new file mode 100644 index 0000000..10a1056 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/dto/UpdateUserDto.java @@ -0,0 +1,17 @@ +package ru.practicum.shareit.user.dto; + +import jakarta.validation.constraints.Email; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Data; +import lombok.experimental.FieldDefaults; + +@Data +@Builder +@FieldDefaults(level = AccessLevel.PRIVATE) +public class UpdateUserDto { + Long id; + String name; + @Email + String email; +} 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..055991f --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/dto/UserDto.java @@ -0,0 +1,19 @@ +package ru.practicum.shareit.user.dto; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Data; +import lombok.experimental.FieldDefaults; + +@Data +@Builder +@FieldDefaults(level = AccessLevel.PRIVATE) +public class UserDto { + Long id; + String name; + @NotBlank + @Email + String email; +} diff --git a/src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java b/src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java new file mode 100644 index 0000000..6bcc305 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java @@ -0,0 +1,38 @@ +package ru.practicum.shareit.user.mapper; + +import lombok.experimental.UtilityClass; +import ru.practicum.shareit.user.dto.UpdateUserDto; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.model.User; + +@UtilityClass +public class UserMapper { + + public UserDto toUserDto(User user) { + return UserDto.builder() + .id(user.getId()) + .name(user.getName()) + .email(user.getEmail()) + .build(); + } + + public User toUser(UserDto userDto) { + return User.builder() + .id(userDto.getId()) + .name(userDto.getName()) + .email(userDto.getEmail()) + .build(); + } + + public User toUser(UpdateUserDto userDto) { + User updateUser = new User(); + updateUser.setId(userDto.getId()); + if (userDto.getEmail() != null) { + updateUser.setEmail(userDto.getEmail()); + } + if (userDto.getName() != null) { + updateUser.setName(userDto.getName()); + } + return updateUser; + } +} 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..8e078bf --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/model/User.java @@ -0,0 +1,15 @@ +package ru.practicum.shareit.user.model; + +import lombok.*; +import lombok.experimental.FieldDefaults; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE) +public class User { + Long id; + String name; + String email; +} diff --git a/src/main/java/ru/practicum/shareit/user/repository/InMemoryUserRepository.java b/src/main/java/ru/practicum/shareit/user/repository/InMemoryUserRepository.java new file mode 100644 index 0000000..0dd6b71 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/repository/InMemoryUserRepository.java @@ -0,0 +1,72 @@ +package ru.practicum.shareit.user.repository; + +import org.springframework.stereotype.Repository; +import ru.practicum.shareit.exception.NotFoundException; +import ru.practicum.shareit.exception.DuplicateValidationException; +import ru.practicum.shareit.user.model.User; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Objects; +import java.util.Optional; + +@Repository +public class InMemoryUserRepository implements UserRepository { + + private final HashMap users = new HashMap<>(); + + @Override + public Collection getAll() { + return users.values(); + } + + @Override + public Optional getById(Long userId) { + return Optional.ofNullable(users.get(userId)); + } + + @Override + public User create(User user) { + user.setId(generateNextId()); + throwIfEmailTaken(user); + users.put(user.getId(), user); + return user; + } + + @Override + public User update(User userUpdate) { + User user = throwIfUserNotExist(userUpdate.getId()); + throwIfEmailTaken(userUpdate); + if (userUpdate.getName() != null) user.setName(userUpdate.getName()); + if (userUpdate.getEmail() != null) user.setEmail(userUpdate.getEmail()); + return user; + } + + @Override + public void delete(Long id) { + throwIfUserNotExist(id); + users.remove(id); + } + + private Long generateNextId() { + Long nextId = users.keySet().stream() + .max(Long::compareTo) + .orElse(0L); + return ++nextId; + } + + private void throwIfEmailTaken(User user) { + boolean isEmailTaken = users.values().stream() + .filter(existedUser -> !Objects.equals(existedUser.getId(), user.getId())) + .anyMatch(existedUser -> Objects.equals(existedUser.getEmail(), user.getEmail())); + if (isEmailTaken) { + throw new DuplicateValidationException("Пользователь с email: '%s' уже существует" + .formatted(user.getEmail())); + } + } + + private User throwIfUserNotExist(Long id) { + return getById(id) + .orElseThrow(() -> new NotFoundException("Пользователь с ID: %s не найден".formatted(id))); + } +} 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..3648312 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java @@ -0,0 +1,19 @@ +package ru.practicum.shareit.user.repository; + +import ru.practicum.shareit.user.model.User; + +import java.util.Collection; +import java.util.Optional; + +public interface UserRepository { + + Collection getAll(); + + Optional getById(Long userId); + + User create(User user); + + User update(User user); + + void delete(Long id); +} 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..509ea13 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/service/UserService.java @@ -0,0 +1,19 @@ +package ru.practicum.shareit.user.service; + +import ru.practicum.shareit.user.dto.UpdateUserDto; +import ru.practicum.shareit.user.dto.UserDto; + +import java.util.Collection; + +public interface UserService { + + Collection getAll(); + + UserDto getById(Long userId); + + UserDto create(UserDto user); + + UserDto update(UpdateUserDto user); + + void delete(Long id); +} diff --git a/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java b/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java new file mode 100644 index 0000000..c7043cb --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java @@ -0,0 +1,52 @@ +package ru.practicum.shareit.user.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import ru.practicum.shareit.exception.NotFoundException; +import ru.practicum.shareit.user.dto.UpdateUserDto; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.mapper.UserMapper; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.repository.UserRepository; + +import java.util.Collection; + +@Service +@RequiredArgsConstructor +public class UserServiceImpl implements UserService { + + private final UserRepository userRepository; + + @Override + public Collection getAll() { + return userRepository.getAll().stream() + .map(UserMapper::toUserDto) + .toList(); + } + + @Override + public UserDto getById(Long userId) { + return userRepository.getById(userId) + .map(UserMapper::toUserDto) + .orElseThrow(() -> new NotFoundException("Пользователь с ID: %s не найден".formatted(userId))); + } + + @Override + public UserDto create(UserDto user) { + User newUser = UserMapper.toUser(user); + newUser = userRepository.create(newUser); + return UserMapper.toUserDto(newUser); + } + + @Override + public UserDto update(UpdateUserDto user) { + User updatedUser = UserMapper.toUser(user); + updatedUser = userRepository.update(updatedUser); + return UserMapper.toUserDto(updatedUser); + } + + @Override + public void delete(Long id) { + userRepository.delete(id); + } +}