diff --git a/src/main/java/ru/practicum/shareit/ShareItApp.java b/src/main/java/ru/practicum/shareit/ShareItApp.java index a00ad56..0151768 100644 --- a/src/main/java/ru/practicum/shareit/ShareItApp.java +++ b/src/main/java/ru/practicum/shareit/ShareItApp.java @@ -6,8 +6,7 @@ @SpringBootApplication public class ShareItApp { - public static void main(String[] args) { - SpringApplication.run(ShareItApp.class, args); - } - -} + public static void main(String[] args) { + SpringApplication.run(ShareItApp.class, args); + } +} \ 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 83% 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..427a4b6 100644 --- a/src/main/java/ru/practicum/shareit/booking/BookingController.java +++ b/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java @@ -1,4 +1,4 @@ -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; diff --git a/src/main/java/ru/practicum/shareit/booking/Booking.java b/src/main/java/ru/practicum/shareit/booking/model/Booking.java similarity index 58% rename from src/main/java/ru/practicum/shareit/booking/Booking.java rename to src/main/java/ru/practicum/shareit/booking/model/Booking.java index 2d9c666..71f189c 100644 --- a/src/main/java/ru/practicum/shareit/booking/Booking.java +++ b/src/main/java/ru/practicum/shareit/booking/model/Booking.java @@ -1,4 +1,4 @@ -package ru.practicum.shareit.booking; +package ru.practicum.shareit.booking.model; /** * TODO Sprint add-bookings. diff --git a/src/main/java/ru/practicum/shareit/exception/DuplicateException.java b/src/main/java/ru/practicum/shareit/exception/DuplicateException.java new file mode 100644 index 0000000..bc84828 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/DuplicateException.java @@ -0,0 +1,7 @@ +package ru.practicum.shareit.exception; + +public class DuplicateException extends RuntimeException { + public DuplicateException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java b/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java new file mode 100644 index 0000000..2db34ea --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java @@ -0,0 +1,39 @@ +package ru.practicum.shareit.exception; + +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 +public class ErrorHandler { + @ExceptionHandler + @ResponseStatus(HttpStatus.CONFLICT) + public ErrorResponse handleDuplicateException(final DuplicateException e) { + return new ErrorResponse(e.getMessage(), "Дублирование данных"); + } + + @ExceptionHandler + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleValidationException(final ValidationException e) { + return new ErrorResponse(e.getMessage(), "Ошибка валидации"); + } + + @ExceptionHandler + @ResponseStatus(HttpStatus.NOT_FOUND) + public ErrorResponse handleNotFoundException(final NotFoundException e) { + return new ErrorResponse(e.getMessage(), "Ресурс не найден"); + } + + @ExceptionHandler + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ErrorResponse handleIllegalArgumentException(final IllegalArgumentException e) { + return new ErrorResponse(e.getMessage(), "Ошибка на стороне сервера"); + } + + @ExceptionHandler + @ResponseStatus(HttpStatus.FORBIDDEN) + public ErrorResponse handleForbiddenException(final ForbiddenException e) { + return new ErrorResponse(e.getMessage(), "Ошибка прав пользователя"); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/exception/ErrorResponse.java b/src/main/java/ru/practicum/shareit/exception/ErrorResponse.java new file mode 100644 index 0000000..736d166 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/ErrorResponse.java @@ -0,0 +1,11 @@ +package ru.practicum.shareit.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class ErrorResponse { + private final String error; + private final String description; +} \ No newline at end of file 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..d8df923 --- /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); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/exception/IllegalArgumentException.java b/src/main/java/ru/practicum/shareit/exception/IllegalArgumentException.java new file mode 100644 index 0000000..6fda831 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/IllegalArgumentException.java @@ -0,0 +1,7 @@ +package ru.practicum.shareit.exception; + +public class IllegalArgumentException extends RuntimeException { + public IllegalArgumentException(String message) { + super(message); + } +} \ No newline at end of file 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..98610d2 --- /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); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/exception/ValidationException.java b/src/main/java/ru/practicum/shareit/exception/ValidationException.java new file mode 100644 index 0000000..067eded --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/ValidationException.java @@ -0,0 +1,7 @@ +package ru.practicum.shareit.exception; + +public class ValidationException extends RuntimeException { + public ValidationException(String message) { + super(message); + } +} \ 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/controller/ItemController.java b/src/main/java/ru/practicum/shareit/item/controller/ItemController.java new file mode 100644 index 0000000..3fb7b92 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/controller/ItemController.java @@ -0,0 +1,62 @@ +package ru.practicum.shareit.item.controller; + +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.service.ItemService; + +import java.util.Collection; + +/** + * TODO Sprint add-controllers. + */ +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/items") +public class ItemController { + + private final ItemService itemService; + + @GetMapping + public Collection findAllByUserId(@NonNull @RequestHeader("X-Sharer-User-Id") Long userId) { + log.info("Запрос на получение всех вещей пользователя c Id:{}", userId); + return itemService.findAllByUserId(userId); + } + + @GetMapping("/{itemId}") + public ItemDto findById(@NonNull @PathVariable Long itemId) { + log.info("Запрос на получение вещи с Id:{}", itemId); + return itemService.findById(itemId); + } + + @GetMapping("/search") + public Collection searchByText(@NonNull @RequestParam("text") String searchText) { + log.info("Запрос на поиск вещи в названии или в описании"); + return itemService.searchByText(searchText); + } + + @PostMapping + public ItemDto create(@NonNull @RequestHeader("X-Sharer-User-Id") Long userId, + @NonNull @RequestBody ItemDto newItem) { + log.info("Запрос на добавление новой вещи пользователем с Id:{}", userId); + return itemService.create(userId, newItem); + } + + @PatchMapping("/{itemId}") + public ItemDto update(@NonNull @RequestHeader("X-Sharer-User-Id") Long ownerId, + @NonNull @PathVariable Long itemId, + @NonNull @RequestBody ItemDto updatedItem) { + log.info("Запрос на обновление вещи с Id:{} пользователем с Id:{}", itemId, ownerId); + return itemService.update(ownerId, itemId, updatedItem); + } + + @DeleteMapping("/{itemId}") + public void delete(@NonNull @RequestHeader("X-Sharer-User-Id") Long ownerId, + @NonNull @PathVariable Long itemId) { + log.info("Запрос на удаление вещи с Id:{} пользователем с Id:{}", itemId, ownerId); + itemService.delete(ownerId, itemId); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/dao/ItemRepository.java b/src/main/java/ru/practicum/shareit/item/dao/ItemRepository.java new file mode 100644 index 0000000..fb3fb1e --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/dao/ItemRepository.java @@ -0,0 +1,20 @@ +package ru.practicum.shareit.item.dao; + +import ru.practicum.shareit.item.model.Item; + +import java.util.Collection; +import java.util.Optional; + +public interface ItemRepository { + Collection findAllByUserId(Long userId); + + Optional findById(Long itemId); + + Collection searchByText(String searchText); + + Item create(Item item, Long userId); + + Item update(Item item); + + void delete(Long itemId); +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/dao/ItemRepositoryImpl.java b/src/main/java/ru/practicum/shareit/item/dao/ItemRepositoryImpl.java new file mode 100644 index 0000000..ef17a63 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/dao/ItemRepositoryImpl.java @@ -0,0 +1,61 @@ +package ru.practicum.shareit.item.dao; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; +import ru.practicum.shareit.item.model.Item; + +import java.util.*; + +@Slf4j +@Repository +public class ItemRepositoryImpl implements ItemRepository { + + private final Map items = new HashMap<>(); + private final Map> userItemIndex = new HashMap<>(); + private long idCounter = 0L; + + @Override + public Collection findAllByUserId(Long userId) { + return items.values() + .stream() + .filter(item -> item.getOwnerId().equals(userId)) + .toList(); + } + + @Override + public Optional findById(Long itemId) { + return Optional.ofNullable(items.get(itemId)); + } + + @Override + public Collection searchByText(String searchText) { + String text = searchText.toLowerCase(); + return items.values() + .stream() + .filter(item -> (item.getName().toLowerCase().contains(text)) + || (item.getDescription().toLowerCase().contains(text)) + && (item.getAvailable().equals(true))) + .toList(); + } + + @Override + public Item create(Item item, Long userId) { + item.setItemId(++idCounter); + item.setOwnerId(userId); + items.put(item.getItemId(), item); + final List itemsByOwner = userItemIndex.computeIfAbsent(item.getOwnerId(), k -> new ArrayList<>()); + itemsByOwner.add(item); + return item; + } + + @Override + public Item update(Item item) { + items.put(item.getItemId(), item); + return item; + } + + @Override + public void delete(Long itemId) { + items.remove(itemId); + } +} \ 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..84900c1 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,14 @@ package ru.practicum.shareit.item.dto; -/** - * TODO Sprint add-controllers. - */ +import lombok.Builder; +import lombok.Data; + +@Data +@Builder(toBuilder = true) public class ItemDto { -} + private Long id; + private String name; + private String description; + private Boolean available; + private Long ownerId; +} \ No newline at end of file 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..dfdf6b9 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java @@ -0,0 +1,29 @@ +package ru.practicum.shareit.item.mapper; + +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.model.Item; + +public class ItemMapper { + + // Преобразование Item в Dto + public static ItemDto mapToDto(Item item) { + return ItemDto.builder() + .id(item.getItemId()) + .name(item.getName()) + .description(item.getDescription()) + .available(item.getAvailable()) + .ownerId(item.getOwnerId()) + .build(); + } + + // Преобразование ItemDto в Item + public static Item mapToItem(ItemDto itemDto) { + return Item.builder() + .itemId(itemDto.getId()) + .name(itemDto.getName()) + .description(itemDto.getDescription()) + .available(itemDto.getAvailable()) + .ownerId(itemDto.getOwnerId()) + .build(); + } +} \ 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..640eee5 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,17 @@ package ru.practicum.shareit.item.model; +import lombok.Builder; +import lombok.Data; + /** * TODO Sprint add-controllers. */ +@Data +@Builder(toBuilder = true) public class Item { -} + private Long itemId; + private String name; + private String description; + private Boolean available; + private Long ownerId; +} \ 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..cac40a9 --- /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.Collection; + +public interface ItemService { + Collection findAllByUserId(Long userId); + + ItemDto findById(Long itemId); + + Collection searchByText(String searchText); + + ItemDto create(Long ownerId, ItemDto newItem); + + ItemDto update(Long ownerId, Long itemId, ItemDto updatedItem); + + void delete(Long ownerID, Long itemId); +} \ No newline at end of file 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..146ce24 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java @@ -0,0 +1,107 @@ +package ru.practicum.shareit.item.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import ru.practicum.shareit.exception.*; +import ru.practicum.shareit.item.dao.ItemRepository; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.mapper.ItemMapper; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.item.utils.ItemValidate; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.service.UserService; + +import java.util.Collection; +import java.util.Collections; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +public class ItemServiceImpl implements ItemService { + + private final ItemRepository itemRepository; + private final UserService userService; + + // Получение всех вещей пользователя + @Override + public Collection findAllByUserId(Long userId) { + return itemRepository.findAllByUserId(userId) + .stream() + .map(ItemMapper::mapToDto) + .toList(); + } + + // Получение вещи по id + @Override + public ItemDto findById(Long itemId) { + return itemRepository.findById(itemId) + .map(ItemMapper::mapToDto) + .orElseThrow(() -> new NotFoundException("Вещь не найдена")); + } + + // Поиск вещи по тексту в названии или описании + @Override + public Collection searchByText(String searchText) { + if (searchText == null || searchText.trim().isEmpty()) { + return Collections.emptyList(); + } + + return itemRepository.searchByText(searchText.toLowerCase()) + .stream() + .filter(item -> item.getAvailable() != null && item.getAvailable()) + .map(ItemMapper::mapToDto) + .collect(Collectors.toList()); + } + + // Добавление вещи + @Override + public ItemDto create(Long ownerId, ItemDto itemDto) { + UserDto user = userService.findById(ownerId); + + if (user == null) { + throw new NotFoundException("Пользователь с ID" + ownerId + " не найден"); + } + + Item item = ItemMapper.mapToItem(itemDto); + item.setOwnerId(ownerId); + ItemValidate.validateForCreate(item); + + Item createdItem = itemRepository.create(item, ownerId); + return ItemMapper.mapToDto(createdItem); + } + + // Обновление вещи + @Override + public ItemDto update(Long ownerId, Long itemId, ItemDto itemDto) { + userService.findById(ownerId); + + Item existingItem = itemRepository.findById(itemId) + .orElseThrow(() -> new NotFoundException("Вещь с ID " + itemId + "не найдена")); + + if (!existingItem.getOwnerId().equals(ownerId)) { + throw new ForbiddenException("Пользователь не является владельцем вещи"); + } + + if (itemDto.getName() != null) { + existingItem.setName(itemDto.getName()); + } + if (itemDto.getDescription() != null) { + existingItem.setDescription(itemDto.getDescription()); + } + if (itemDto.getAvailable() != null) { + existingItem.setAvailable(itemDto.getAvailable()); + } + + ItemValidate.validateForUpdate(existingItem); + Item updatedItem = itemRepository.update(existingItem); + return ItemMapper.mapToDto(updatedItem); + } + + // Удаление вещи + @Override + public void delete(Long ownerId, Long itemId) { + itemRepository.delete(itemId); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/utils/ItemValidate.java b/src/main/java/ru/practicum/shareit/item/utils/ItemValidate.java new file mode 100644 index 0000000..5b8d629 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/utils/ItemValidate.java @@ -0,0 +1,78 @@ +package ru.practicum.shareit.item.utils; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import ru.practicum.shareit.exception.ValidationException; +import ru.practicum.shareit.item.model.Item; + +/** + * Утилитарный класс для валидации сущности Item + */ + +@Slf4j +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ItemValidate { + + // Валидация при создании вещи + public static void validateForCreate(Item item) { + validateNotNull(item); + validateName(item.getName()); + validateDescription(item.getDescription()); + validateAvailability(item.getAvailable()); + validateOwner(item.getOwnerId()); + } + + // Валидация при изменении вещи + public static void validateForUpdate(Item item) { + validateNotNull(item); + + if (item.getName() != null) { + validateName(item.getName()); + } + + if (item.getDescription() != null) { + validateDescription(item.getDescription()); + } + + if (item.getAvailable() != null) { + validateAvailability(item.getAvailable()); + } + } + + // Вспомогательные методы для валидации полей Item + private static void validateNotNull(Item item) { + if (item == null) { + log.error("Попытка добавить или изменить null, а не вещь"); + throw new ValidationException("Вещь не может быть null"); + } + } + + private static void validateName(String name) { + if (name == null || name.isBlank()) { + log.error("Попытка создать вещь без названия"); + throw new ValidationException("Название вещи не может быть пустым"); + } + } + + private static void validateDescription(String description) { + if (description == null || description.isBlank()) { + log.error("Попытка добавить вещь без описания"); + throw new ValidationException("Описание вещи не может быть пустым"); + } + } + + private static void validateAvailability(Boolean available) { + if (available == null) { + log.error("Попытка добавить вещь без статуса доступности"); + throw new ValidationException("Статус доступности вещи должен быть указан"); + } + } + + private static void validateOwner(Long ownerId) { + if (ownerId == null || ownerId <= 0) { + log.error("Попытка добавить вещь с некорректным Id владельца"); + throw new ValidationException("Некорректный Id владельца"); + } + } +} \ No newline at end of file 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 84% 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..02e6a98 100644 --- a/src/main/java/ru/practicum/shareit/request/ItemRequestController.java +++ b/src/main/java/ru/practicum/shareit/request/controller/ItemRequestController.java @@ -1,4 +1,4 @@ -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; diff --git a/src/main/java/ru/practicum/shareit/request/ItemRequest.java b/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java similarity index 62% rename from src/main/java/ru/practicum/shareit/request/ItemRequest.java rename to src/main/java/ru/practicum/shareit/request/model/ItemRequest.java index 95d6f23..0abeaa7 100644 --- a/src/main/java/ru/practicum/shareit/request/ItemRequest.java +++ b/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java @@ -1,4 +1,4 @@ -package ru.practicum.shareit.request; +package ru.practicum.shareit.request.model; /** * TODO Sprint add-item-requests. 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/controller/UserController.java b/src/main/java/ru/practicum/shareit/user/controller/UserController.java new file mode 100644 index 0000000..ba53c47 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/controller/UserController.java @@ -0,0 +1,53 @@ +package ru.practicum.shareit.user.controller; + +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.service.UserService; + +import java.util.Collection; + +/** + * TODO Sprint add-controllers. + */ +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping(path = "/users") +public class UserController { + + private final UserService userService; + + @GetMapping + public Collection findAll() { + log.info("Запрос на получение всех пользователей"); + return userService.findAll(); + } + + @GetMapping("/{id}") + public UserDto findById(@NonNull @PathVariable("id") Long userId) { + log.info("Запрос на получение пользователя с Id:{}", userId); + return userService.findById(userId); + } + + @PostMapping + public UserDto create(@NonNull @RequestBody UserDto newUser) { + log.info("Запрос на добавление нового пользователя"); + return userService.create(newUser); + } + + @PatchMapping("/{id}") + public UserDto update(@NonNull @PathVariable("id") Long userId, + @NonNull @RequestBody UserDto updatedUser) { + log.info("Запрос на обновление пользователя с id:{}", updatedUser.getId()); + return userService.update(userId, updatedUser); + } + + @DeleteMapping("/{id}") + public void delete(@NonNull @PathVariable("id") Long userId) { + log.info("Запрос на удаление пользователя с id:{}", userId); + userService.delete(userId); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/user/dao/UserRepository.java b/src/main/java/ru/practicum/shareit/user/dao/UserRepository.java new file mode 100644 index 0000000..4e4d535 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/dao/UserRepository.java @@ -0,0 +1,18 @@ +package ru.practicum.shareit.user.dao; + +import ru.practicum.shareit.user.model.User; + +import java.util.Collection; +import java.util.Optional; + +public interface UserRepository { + Collection findAll(); + + Optional findById(Long userId); + + User create(User user); + + User update(Long userId, User user); + + void delete(Long userId); +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/user/dao/UserRepositoryImpl.java b/src/main/java/ru/practicum/shareit/user/dao/UserRepositoryImpl.java new file mode 100644 index 0000000..6343bbc --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/dao/UserRepositoryImpl.java @@ -0,0 +1,46 @@ +package ru.practicum.shareit.user.dao; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; +import ru.practicum.shareit.user.model.User; + +import java.util.*; + +@Slf4j +@Repository +public class UserRepositoryImpl implements UserRepository { + + private final Map users = new HashMap<>(); + private final Set usersEmail = new HashSet<>(); + private long idCounter = 0L; + + @Override + public Collection findAll() { + return List.copyOf(users.values()); + } + + @Override + public Optional findById(Long userId) { + return Optional.ofNullable(users.get(userId)); + } + + @Override + public User create(User user) { + user.setUserId(++idCounter); + usersEmail.add(user.getEmail().toLowerCase()); + users.put(user.getUserId(), user); + return user; + } + + @Override + public User update(Long userId, User user) { + users.put(user.getUserId(), user); + return user; + } + + @Override + public void delete(Long userId) { + usersEmail.remove(users.get(userId).getEmail()); + users.remove(userId); + } +} \ 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..cfbdbf3 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/dto/UserDto.java @@ -0,0 +1,12 @@ +package ru.practicum.shareit.user.dto; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder(toBuilder = true) +public class UserDto { + private Long id; + private String name; + private String email; +} \ No newline at end of file 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..130cba6 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java @@ -0,0 +1,25 @@ +package ru.practicum.shareit.user.mapper; + +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.model.User; + +public class UserMapper { + + // Преобразование User в Dto + public static UserDto mapToDto(User user) { + return UserDto.builder() + .id(user.getUserId()) + .name(user.getName()) + .email(user.getEmail()) + .build(); + } + + // Преобразование Dto в User + public static User mapToUser(UserDto userDto) { + return User.builder() + .userId(userDto.getId()) + .name(userDto.getName()) + .email(userDto.getEmail()) + .build(); + } +} \ 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..a1b5375 --- /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.Builder; +import lombok.Data; + +/** + * TODO Sprint add-controllers. + */ +@Data +@Builder(toBuilder = true) +public class User { + private Long userId; + private String name; + private String email; +} \ 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..6fa8bd2 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/service/UserService.java @@ -0,0 +1,17 @@ +package ru.practicum.shareit.user.service; + +import ru.practicum.shareit.user.dto.UserDto; + +import java.util.Collection; + +public interface UserService { + Collection findAll(); + + UserDto findById(Long userId); + + UserDto create(UserDto newUser); + + UserDto update(Long userId, UserDto updatedUser); + + void delete(Long userId); +} \ No newline at end of file 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..3476523 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java @@ -0,0 +1,93 @@ +package ru.practicum.shareit.user.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import ru.practicum.shareit.exception.*; +import ru.practicum.shareit.user.dao.UserRepository; +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.utils.UserValidate; + +import java.util.Collection; + +@Slf4j +@Service +@RequiredArgsConstructor +public class UserServiceImpl implements UserService { + + private final UserRepository userRepository; + + // Получение всех пользователей + @Override + public Collection findAll() { + return userRepository.findAll().stream() + .map(UserMapper::mapToDto) + .toList(); + } + + // Получение пользователя по id + @Override + public UserDto findById(Long userId) { + return userRepository.findById(userId) + .map(UserMapper::mapToDto) + .orElseThrow(() -> new NotFoundException("Пользователь не найден")); + } + + // Добавление пользователя + @Override + public UserDto create(UserDto userDto) { + User user = UserMapper.mapToUser(userDto); + UserValidate.validateForCreate(user); + checkUniqueEmail(userDto.getEmail()); + + User createdUser = userRepository.create(user); + return UserMapper.mapToDto(createdUser); + } + + // Обновление пользователя + @Override + public UserDto update(Long userId, UserDto userDto) { + if (userId == null || userId <= 0) { + throw new ValidationException("Некорректный ID пользователя"); + } + + if (userDto == null) { + throw new ValidationException("Пользователь не может быть null"); + } + + User existingUser = userRepository.findById(userId) + .orElseThrow(() -> new NotFoundException("Пользователь с ID " + userId + " не найден")); + + if (userDto.getName() != null) { + existingUser.setName(userDto.getName()); + } + + if (userDto.getEmail() != null && !userDto.getEmail().equalsIgnoreCase(existingUser.getEmail())) { + checkUniqueEmail(userDto.getEmail()); + existingUser.setEmail(userDto.getEmail()); + } + + UserValidate.validateForEdit(existingUser); + + User updatedUser = userRepository.update(userId, existingUser); + return UserMapper.mapToDto(updatedUser); + } + + // Удаление пользователя + @Override + public void delete(Long userId) { + userRepository.delete(userId); + } + + // Проверки уникальности email + private void checkUniqueEmail(String email) { + boolean emailExists = userRepository.findAll().stream() + .anyMatch(u -> u.getEmail().equalsIgnoreCase(email)); + + if (emailExists) { + throw new DuplicateException("Email " + email + " уже занят"); + } + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/user/utils/UserValidate.java b/src/main/java/ru/practicum/shareit/user/utils/UserValidate.java new file mode 100644 index 0000000..fc2e208 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/utils/UserValidate.java @@ -0,0 +1,51 @@ +package ru.practicum.shareit.user.utils; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import ru.practicum.shareit.exception.ValidationException; +import ru.practicum.shareit.user.model.User; + +/** + * Утилитарный класс для валидации данных сущности User + */ + +@Slf4j +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class UserValidate { + + // Валидация при создании пользователя + public static void validateForCreate(User user) { + validateNotNull(user); + validateEmail(user.getEmail()); + } + + // Валидация при изменении пользователя + public static void validateForEdit(User user) { + validateNotNull(user); + + if (user.getEmail() != null) { + validateEmail(user.getEmail()); + } + } + + // Вспомогательный метод валидации email + private static void validateEmail(String email) { + if (email == null || email.isEmpty()) { + log.error("Попытка добавить пустой email"); + throw new ValidationException("email не может быть пустым"); + } + if (!email.contains("@")) { + log.error("Попытка добавить некорректный email"); + throw new ValidationException("email не содержит @"); + } + } + + // Вспомогательный метод проверки пользователя на null + private static void validateNotNull(User user) { + if (user == null) { + log.error("Попытка добавить или изменить null, а не пользователя"); + throw new ValidationException("Пользователь не может быть null"); + } + } +} \ No newline at end of file