diff --git a/pom.xml b/pom.xml
index 2db888c..a3b4a77 100644
--- a/pom.xml
+++ b/pom.xml
@@ -17,6 +17,8 @@
21
+ 1.6.3
+ 0.2.0
@@ -60,6 +62,19 @@
org.springframework.boot
spring-boot-starter-validation
+
+
+ org.mapstruct
+ mapstruct
+ ${org.mapstruct.version}
+
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ ${lombok-mapstruct-binding.version}
+
+
@@ -120,6 +135,32 @@
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.1
+
+ 1.8
+ 1.8
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+
+
+ org.mapstruct
+ mapstruct-processor
+ ${org.mapstruct.version}
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ ${lombok-mapstruct-binding.version}
+
+
+
+
com.github.spotbugs
spotbugs-maven-plugin
diff --git a/src/main/java/ru/practicum/shareit/booking/Booking.java b/src/main/java/ru/practicum/shareit/booking/Booking.java
index 2d9c666..8facd50 100644
--- a/src/main/java/ru/practicum/shareit/booking/Booking.java
+++ b/src/main/java/ru/practicum/shareit/booking/Booking.java
@@ -1,7 +1,16 @@
package ru.practicum.shareit.booking;
+import java.time.LocalDate;
+
/**
* TODO Sprint add-bookings.
*/
public class Booking {
+
+ private Long bookingId;
+ private LocalDate start;
+ private LocalDate end;
+ private Long itemId;
+ private Long bookerId;
+ BookingStatus status;
}
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/exception/AnotherUserException.java b/src/main/java/ru/practicum/shareit/exception/AnotherUserException.java
new file mode 100644
index 0000000..6fc8434
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/exception/AnotherUserException.java
@@ -0,0 +1,8 @@
+package ru.practicum.shareit.exception;
+
+public class AnotherUserException extends RuntimeException {
+
+ public AnotherUserException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/ru/practicum/shareit/exception/DuplicatedDataException.java b/src/main/java/ru/practicum/shareit/exception/DuplicatedDataException.java
new file mode 100644
index 0000000..3aef12f
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/exception/DuplicatedDataException.java
@@ -0,0 +1,8 @@
+package ru.practicum.shareit.exception;
+
+public class DuplicatedDataException extends RuntimeException {
+
+ public DuplicatedDataException(String message) {
+ super(message);
+ }
+}
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..d118158
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java
@@ -0,0 +1,33 @@
+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 ErrorHandler {
+
+ @ExceptionHandler(DuplicatedDataException.class)
+ @ResponseStatus(HttpStatus.CONFLICT)
+ public ErrorResponse handleDuplicatedDataException(final DuplicatedDataException e) {
+ log.error("Duplicated: {}", e.getMessage());
+ return new ErrorResponse(e.getMessage(), HttpStatus.CONFLICT);
+ }
+
+ @ExceptionHandler(NotFoundException.class)
+ @ResponseStatus(HttpStatus.NOT_FOUND)
+ public ErrorResponse handleNotFoundException(final NotFoundException e) {
+ log.error("Not found: {}", e.getMessage());
+ return new ErrorResponse(e.getMessage(), HttpStatus.NOT_FOUND);
+ }
+
+ @ExceptionHandler
+ @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+ public ErrorResponse handleException(final RuntimeException e) {
+ log.error("ServerError: {}", e.getMessage());
+ return new ErrorResponse(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
+ }
+}
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..40e1fb8
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/exception/ErrorResponse.java
@@ -0,0 +1,16 @@
+package ru.practicum.shareit.exception;
+
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+
+@Getter
+public class ErrorResponse {
+ String error;
+ HttpStatus status;
+
+ public ErrorResponse(String error, HttpStatus status) {
+ this.error = error;
+ this.status = status;
+ }
+
+}
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..89a2ad0
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/exception/NotFoundException.java
@@ -0,0 +1,10 @@
+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/exception/ValidationException.java b/src/main/java/ru/practicum/shareit/exception/ValidationException.java
new file mode 100644
index 0000000..56cb5a8
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/exception/ValidationException.java
@@ -0,0 +1,8 @@
+package ru.practicum.shareit.exception;
+
+public class ValidationException extends RuntimeException {
+
+ public ValidationException(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..d033939 100644
--- a/src/main/java/ru/practicum/shareit/item/ItemController.java
+++ b/src/main/java/ru/practicum/shareit/item/ItemController.java
@@ -1,12 +1,53 @@
package ru.practicum.shareit.item;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-/**
- * TODO Sprint add-controllers.
- */
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import ru.practicum.shareit.item.dto.ItemRequestDto;
+import ru.practicum.shareit.item.dto.ItemResponseDto;
+
+import java.util.Collection;
+
@RestController
@RequestMapping("/items")
+@RequiredArgsConstructor
+@Slf4j
+@Validated
public class ItemController {
+
+ public static final String OWNER_ID = "X-Sharer-User-Id";
+ private final ItemService itemService;
+
+ @PostMapping
+ public ItemResponseDto create(@RequestHeader(OWNER_ID) Long ownerId,
+ @Valid @RequestBody ItemRequestDto item) {
+ return itemService.createItem(item, ownerId);
+ }
+
+ @PatchMapping("/{itemId}")
+ public ItemResponseDto update(@RequestBody ItemRequestDto item,
+ @PathVariable Long itemId,
+ @RequestHeader(OWNER_ID) Long ownerId) {
+ return itemService.updateItem(itemId, item, ownerId);
+ }
+
+ @GetMapping("/{itemId}")
+ public ItemResponseDto getItemById(@PathVariable Long itemId) {
+ return itemService.getItemById(itemId);
+ }
+
+ @GetMapping
+ public Collection getAllItemsForOwner(
+ @RequestHeader(OWNER_ID) Long ownerId) {
+ return itemService.getAllItemsForOwner(ownerId);
+ }
+
+ @GetMapping("/search")
+ public Collection searchItems(@RequestParam String text) {
+ return itemService.searchByText(text);
+ }
+
}
diff --git a/src/main/java/ru/practicum/shareit/item/ItemService.java b/src/main/java/ru/practicum/shareit/item/ItemService.java
new file mode 100644
index 0000000..9dd0d0c
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/item/ItemService.java
@@ -0,0 +1,77 @@
+package ru.practicum.shareit.item;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import ru.practicum.shareit.exception.AnotherUserException;
+import ru.practicum.shareit.exception.NotFoundException;
+import ru.practicum.shareit.exception.ValidationException;
+import ru.practicum.shareit.item.dto.ItemRequestDto;
+import ru.practicum.shareit.item.dto.ItemResponseDto;
+import ru.practicum.shareit.item.interfaces.ItemServiceInterface;
+import ru.practicum.shareit.item.mapper.ItemMapper;
+import ru.practicum.shareit.item.model.Item;
+import ru.practicum.shareit.user.UserStorage;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.stream.Collectors;
+
+@Service
+@RequiredArgsConstructor
+public class ItemService implements ItemServiceInterface {
+
+ private final ItemStorage itemStorage;
+ private final UserStorage userStorage;
+ private final ItemMapper itemMapper;
+
+ public ItemResponseDto createItem(ItemRequestDto item, Long ownerId) {
+ if (ownerId == null) {
+ throw new ValidationException("Owner id cannot be null");
+ }
+ userStorage.getUserById(ownerId)
+ .orElseThrow(() -> new NotFoundException("User with id " + ownerId + " not found"));
+ Item itemSaved = itemMapper.toItem(item);
+ itemSaved.setOwnerId(ownerId);
+ return itemMapper.toItemResponseDto(itemStorage.createItem(itemSaved));
+ }
+
+ public ItemResponseDto getItemById(Long id) {
+ return itemMapper.toItemResponseDto(itemStorage.getItemById(id)
+ .orElseThrow(() -> new NotFoundException("Item with id " + id + " not found")));
+ }
+
+ public ItemResponseDto updateItem(Long itemId,
+ ItemRequestDto item, Long ownerId) {
+ findUserById(ownerId);
+ Item foundItem = itemStorage.getItemById(itemId)
+ .orElseThrow(() -> new NotFoundException("Item with id " + itemId + " not found"));
+ if (foundItem.getOwnerId().equals(ownerId)) {
+ item.setItemId(itemId);
+ foundItem = itemMapper.toItem(item);
+ } else {
+ throw new AnotherUserException("User with id " + ownerId + " is not owner of this Item");
+ }
+ return itemMapper.toItemResponseDto(itemStorage.updateItem(foundItem));
+ }
+
+ public Collection getAllItemsForOwner(Long ownerId) {
+ return itemStorage.getAllItemsForOwner(ownerId).stream()
+ .map(itemMapper::toItemResponseDto)
+ .collect(Collectors.toList());
+ }
+
+ public Collection searchByText(String text) {
+ if (text == null || text.isEmpty()) {
+ return new ArrayList<>();
+ }
+ return itemStorage.searchByText(text).stream()
+ .filter(Item::getIsAvailable)
+ .map(itemMapper::toItemResponseDto)
+ .collect(Collectors.toList());
+ }
+
+ private void findUserById(Long userId) {
+ userStorage.getUserById(userId)
+ .orElseThrow(() -> new NotFoundException("User with id = " + userId + " not found"));
+ }
+}
diff --git a/src/main/java/ru/practicum/shareit/item/ItemStorage.java b/src/main/java/ru/practicum/shareit/item/ItemStorage.java
new file mode 100644
index 0000000..6b234d5
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/item/ItemStorage.java
@@ -0,0 +1,67 @@
+package ru.practicum.shareit.item;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Repository;
+import ru.practicum.shareit.item.interfaces.ItemStorageInterface;
+import ru.practicum.shareit.item.model.Item;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Repository
+@RequiredArgsConstructor
+public class ItemStorage implements ItemStorageInterface {
+
+ private Long nextItemId = 1L;
+ private final Map items = new HashMap<>();
+
+ public Item createItem(Item item) {
+ item.setItemId(nextItemId++);
+ items.put(item.getItemId(), item);
+ return item;
+ }
+
+ public Optional- getItemById(Long id) {
+ return Optional.ofNullable(items.get(id));
+ }
+
+ public Item updateItem(Item item) {
+ Item oldItem = items.get(item.getItemId());
+
+ if (oldItem != null) {
+
+ if (item.getItemName() != null) {
+ oldItem.setItemName(item.getItemName());
+ }
+
+ if (item.getItemDescription() != null) {
+ oldItem.setItemDescription(item.getItemDescription());
+ }
+
+ if (item.getIsAvailable() != null) {
+ oldItem.setIsAvailable(item.getIsAvailable());
+ }
+
+ }
+ return oldItem;
+ }
+
+ public Collection
- getAllItemsForOwner(Long ownerId) {
+ return items.values()
+ .stream()
+ .filter(item -> Objects.equals(item.getOwnerId(), ownerId))
+ .collect(Collectors.toList());
+ }
+
+ public Collection
- searchByText(String text) {
+ Collection
- itemCollection = new ArrayList<>();
+ for (Item item : items.values()) {
+ if (item.getItemName().toLowerCase().contains(text.toLowerCase())) {
+ itemCollection.add(item);
+ } else if (item.getItemDescription().toLowerCase().contains(text.toLowerCase())) {
+ itemCollection.add(item);
+ }
+ }
+ return itemCollection;
+ }
+}
diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java b/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java
deleted file mode 100644
index 9319d7d..0000000
--- a/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package ru.practicum.shareit.item.dto;
-
-/**
- * TODO Sprint add-controllers.
- */
-public class ItemDto {
-}
diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemRequestDto.java b/src/main/java/ru/practicum/shareit/item/dto/ItemRequestDto.java
new file mode 100644
index 0000000..3b77b82
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/item/dto/ItemRequestDto.java
@@ -0,0 +1,33 @@
+package ru.practicum.shareit.item.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.hibernate.validator.constraints.Length;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class ItemRequestDto {
+ @JsonProperty("id")
+ private Long itemId;
+
+ @NotEmpty
+ @JsonProperty("name")
+ private String itemName;
+
+ @NotEmpty
+ @Length(max = 150)
+ @JsonProperty("description")
+ private String itemDescription;
+
+ @NotNull
+ @JsonProperty("available")
+ private Boolean isAvailable;
+
+ private Long requestId;
+
+}
diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemResponseDto.java b/src/main/java/ru/practicum/shareit/item/dto/ItemResponseDto.java
new file mode 100644
index 0000000..9b50b2b
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/item/dto/ItemResponseDto.java
@@ -0,0 +1,31 @@
+package ru.practicum.shareit.item.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class ItemResponseDto {
+
+ @NotNull
+ @JsonProperty("id")
+ private Long itemId;
+
+ @JsonProperty("name")
+ private String itemName;
+
+ @JsonProperty("description")
+ private String itemDescription;
+
+ @JsonProperty("available")
+ private Boolean isAvailable;
+
+ @JsonProperty("userId")
+ private Long ownerId;
+
+ private Long requestId;
+}
diff --git a/src/main/java/ru/practicum/shareit/item/interfaces/ItemServiceInterface.java b/src/main/java/ru/practicum/shareit/item/interfaces/ItemServiceInterface.java
new file mode 100644
index 0000000..053d40c
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/item/interfaces/ItemServiceInterface.java
@@ -0,0 +1,19 @@
+package ru.practicum.shareit.item.interfaces;
+
+import ru.practicum.shareit.item.dto.ItemRequestDto;
+import ru.practicum.shareit.item.dto.ItemResponseDto;
+
+import java.util.Collection;
+
+public interface ItemServiceInterface {
+ ItemResponseDto createItem(ItemRequestDto item, Long ownerId);
+
+ ItemResponseDto getItemById(Long id);
+
+ ItemResponseDto updateItem(Long itemId, ItemRequestDto item, Long ownerId);
+
+ Collection getAllItemsForOwner(Long ownerId);
+
+ Collection searchByText(String text);
+
+}
diff --git a/src/main/java/ru/practicum/shareit/item/interfaces/ItemStorageInterface.java b/src/main/java/ru/practicum/shareit/item/interfaces/ItemStorageInterface.java
new file mode 100644
index 0000000..0376243
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/item/interfaces/ItemStorageInterface.java
@@ -0,0 +1,19 @@
+package ru.practicum.shareit.item.interfaces;
+
+import ru.practicum.shareit.item.model.Item;
+
+import java.util.Collection;
+import java.util.Optional;
+
+public interface ItemStorageInterface {
+ Item createItem(Item item);
+
+ Optional
- getItemById(Long id);
+
+ Item updateItem(Item item);
+
+ Collection
- getAllItemsForOwner(Long ownerId);
+
+ Collection
- searchByText(String text);
+
+}
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..6835d2c
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java
@@ -0,0 +1,19 @@
+package ru.practicum.shareit.item.mapper;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.MappingConstants;
+import ru.practicum.shareit.item.dto.ItemRequestDto;
+import ru.practicum.shareit.item.dto.ItemResponseDto;
+import ru.practicum.shareit.item.model.Item;
+
+@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
+public interface ItemMapper {
+ ItemResponseDto toItemResponseDto(ItemRequestDto itemRequestDto);
+
+ ItemRequestDto toItemRequestDto(ItemResponseDto itemDtoResponseDto);
+
+ Item toItem(ItemRequestDto itemRequestDto);
+
+ ItemResponseDto toItemResponseDto(Item item);
+
+}
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..6fcf2d1 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.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
public class Item {
+
+ private Long itemId;
+ private String itemName;
+ private String itemDescription;
+ private Boolean isAvailable;
+ private Long ownerId;
+ private Long requestId;
+
}
diff --git a/src/main/java/ru/practicum/shareit/request/ItemRequest.java b/src/main/java/ru/practicum/shareit/request/ItemRequest.java
index 95d6f23..c12fab1 100644
--- a/src/main/java/ru/practicum/shareit/request/ItemRequest.java
+++ b/src/main/java/ru/practicum/shareit/request/ItemRequest.java
@@ -1,7 +1,13 @@
package ru.practicum.shareit.request;
+import java.time.LocalDate;
+
/**
* TODO Sprint add-item-requests.
*/
public class ItemRequest {
+ private Long itemRequestId;
+ private String requestDescription;
+ private Long requesterId;
+ private LocalDate requestDate;
}
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..4c334a4 100644
--- a/src/main/java/ru/practicum/shareit/user/UserController.java
+++ b/src/main/java/ru/practicum/shareit/user/UserController.java
@@ -1,12 +1,42 @@
package ru.practicum.shareit.user;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import ru.practicum.shareit.user.dto.UserRequestDto;
+import ru.practicum.shareit.user.dto.UserResponseDto;
-/**
- * TODO Sprint add-controllers.
- */
@RestController
+@Validated
@RequestMapping(path = "/users")
+@RequiredArgsConstructor
public class UserController {
+ private final UserService userService;
+
+ @PostMapping
+ public UserResponseDto createUser(@Valid @RequestBody UserRequestDto user) {
+ return userService.createUser(user);
+ }
+
+ @GetMapping("/{id}")
+ public UserResponseDto getUser(@PathVariable long id) {
+ return userService.getUserById(id);
+ }
+
+ @PatchMapping("/{id}")
+ public UserResponseDto updateUser(@PathVariable long id, @RequestBody UserRequestDto user) {
+ return userService.updateUser(id, user);
+ }
+
+ @DeleteMapping("/{id}")
+ public void deleteUser(@PathVariable long id) {
+ userService.deleteUser(id);
+ }
+
+ @DeleteMapping
+ public void deleteUser() {
+ userService.deleteAllUsers();
+ }
+
}
diff --git a/src/main/java/ru/practicum/shareit/user/UserService.java b/src/main/java/ru/practicum/shareit/user/UserService.java
new file mode 100644
index 0000000..e4c8802
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/user/UserService.java
@@ -0,0 +1,63 @@
+package ru.practicum.shareit.user;
+
+import lombok.AllArgsConstructor;
+import org.springframework.stereotype.Service;
+import ru.practicum.shareit.exception.DuplicatedDataException;
+import ru.practicum.shareit.exception.NotFoundException;
+import ru.practicum.shareit.user.dto.UserRequestDto;
+import ru.practicum.shareit.user.dto.UserResponseDto;
+import ru.practicum.shareit.user.interfaces.UserServiceInterface;
+import ru.practicum.shareit.user.mapper.UserMapper;
+import ru.practicum.shareit.user.model.User;
+
+import java.util.Collection;
+import java.util.Optional;
+
+@Service
+@AllArgsConstructor
+public class UserService implements UserServiceInterface {
+
+ private final UserStorage userStorage;
+ private final UserMapper userMapper;
+
+ public UserResponseDto createUser(UserRequestDto user) {
+ validateEmail(user.getUserEmail());
+ User newUser = userMapper.toUser(user);
+ return userMapper.toUserResponseDto(userStorage.createUser(newUser));
+ }
+
+ public UserResponseDto getUserById(Long userId) throws NotFoundException {
+ return userMapper.toUserResponseDto(userStorage.getUserById(userId)
+ .orElseThrow(() -> new NotFoundException("User not found, id = " + userId)));
+ }
+
+ public UserResponseDto updateUser(Long userId, UserRequestDto user) throws NotFoundException {
+ validateEmail(user.getUserEmail());
+ return userMapper.toUserResponseDto(userStorage.updateUser(userId, userMapper.toUser(user)));
+ }
+
+ public void deleteUser(Long userId) throws NotFoundException {
+ getUserById(userId);
+ userStorage.deleteUser(userId);
+ }
+
+ public void deleteAllUsers() {
+ userStorage.deleteAllUsers();
+ }
+
+ private void validateEmail(String email) {
+ Collection users = userStorage.getAllUsers();
+
+ if (!users.isEmpty()) {
+ Optional first =
+ userStorage.getAllUsers().stream()
+ .map(User::getUserEmail)
+ .filter(userEmail -> userEmail.equals(email))
+ .findFirst();
+
+ if (first.isPresent()) {
+ throw new DuplicatedDataException("User with email " + email + " already exists");
+ }
+ }
+ }
+}
diff --git a/src/main/java/ru/practicum/shareit/user/UserStorage.java b/src/main/java/ru/practicum/shareit/user/UserStorage.java
new file mode 100644
index 0000000..040839d
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/user/UserStorage.java
@@ -0,0 +1,53 @@
+package ru.practicum.shareit.user;
+
+import org.springframework.stereotype.Repository;
+import ru.practicum.shareit.user.interfaces.UserStorageInterface;
+import ru.practicum.shareit.user.model.User;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+@Repository
+public class UserStorage implements UserStorageInterface {
+
+ private final Map users = new HashMap<>();
+ private Long nextUserId = 1L;
+
+ public User createUser(User user) {
+ user.setUserId(nextUserId++);
+ users.put(user.getUserId(), user);
+ return user;
+ }
+
+ public Optional getUserById(Long userId) {
+ return Optional.ofNullable(users.get(userId));
+ }
+
+ public User updateUser(Long userId, User user) {
+ User oldUser = users.get(userId);
+
+ if (user.getUserName() != null) {
+ oldUser.setUserName(user.getUserName());
+ }
+
+ if (user.getUserEmail() != null) {
+ oldUser.setUserEmail(user.getUserEmail());
+ }
+
+ return oldUser;
+ }
+
+ public Collection getAllUsers() {
+ return users.values();
+ }
+
+ public void deleteUser(Long userId) {
+ users.remove(userId);
+ }
+
+ public void deleteAllUsers() {
+ users.clear();
+ }
+}
diff --git a/src/main/java/ru/practicum/shareit/user/dto/UserRequestDto.java b/src/main/java/ru/practicum/shareit/user/dto/UserRequestDto.java
new file mode 100644
index 0000000..b396c14
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/user/dto/UserRequestDto.java
@@ -0,0 +1,27 @@
+package ru.practicum.shareit.user.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class UserRequestDto {
+
+ @JsonProperty("id")
+ private Long userId;
+
+ @NotBlank
+ @JsonProperty("name")
+ private String userName;
+
+ @NotNull
+ @Email
+ @JsonProperty("email")
+ private String userEmail;
+}
diff --git a/src/main/java/ru/practicum/shareit/user/dto/UserResponseDto.java b/src/main/java/ru/practicum/shareit/user/dto/UserResponseDto.java
new file mode 100644
index 0000000..bed79f2
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/user/dto/UserResponseDto.java
@@ -0,0 +1,22 @@
+package ru.practicum.shareit.user.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class UserResponseDto {
+ @NotNull
+ @JsonProperty("id")
+ private Long userId;
+
+ @JsonProperty("name")
+ private String userName;
+
+ @JsonProperty("email")
+ private String userEmail;
+}
diff --git a/src/main/java/ru/practicum/shareit/user/interfaces/UserServiceInterface.java b/src/main/java/ru/practicum/shareit/user/interfaces/UserServiceInterface.java
new file mode 100644
index 0000000..1ca30dc
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/user/interfaces/UserServiceInterface.java
@@ -0,0 +1,16 @@
+package ru.practicum.shareit.user.interfaces;
+
+import ru.practicum.shareit.user.dto.UserRequestDto;
+import ru.practicum.shareit.user.dto.UserResponseDto;
+
+public interface UserServiceInterface {
+ UserResponseDto createUser(UserRequestDto user);
+
+ UserResponseDto getUserById(Long userId);
+
+ UserResponseDto updateUser(Long userId, UserRequestDto user);
+
+ void deleteUser(Long userId);
+
+ void deleteAllUsers();
+}
diff --git a/src/main/java/ru/practicum/shareit/user/interfaces/UserStorageInterface.java b/src/main/java/ru/practicum/shareit/user/interfaces/UserStorageInterface.java
new file mode 100644
index 0000000..dafdbf4
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/user/interfaces/UserStorageInterface.java
@@ -0,0 +1,20 @@
+package ru.practicum.shareit.user.interfaces;
+
+import ru.practicum.shareit.user.model.User;
+
+import java.util.Collection;
+import java.util.Optional;
+
+public interface UserStorageInterface {
+ User createUser(User user);
+
+ Optional getUserById(Long userId);
+
+ User updateUser(Long userId, User user);
+
+ Collection getAllUsers();
+
+ void deleteUser(Long userId);
+
+ void deleteAllUsers();
+}
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..086a773
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java
@@ -0,0 +1,20 @@
+package ru.practicum.shareit.user.mapper;
+
+import org.mapstruct.InjectionStrategy;
+import org.mapstruct.Mapper;
+import ru.practicum.shareit.user.dto.UserRequestDto;
+import ru.practicum.shareit.user.dto.UserResponseDto;
+import ru.practicum.shareit.user.model.User;
+
+@Mapper(componentModel = "spring", uses = UserMapper
+ .class, injectionStrategy = InjectionStrategy.CONSTRUCTOR)
+public interface UserMapper {
+
+ UserRequestDto toUserRequestDto(User user);
+
+ User toUser(UserRequestDto userDto);
+
+ UserResponseDto toUserResponseDto(User user);
+
+ User toUser(UserResponseDto userDto);
+}
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..c7c00cc
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/user/model/User.java
@@ -0,0 +1,14 @@
+package ru.practicum.shareit.user.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class User {
+ private Long userId;
+ private String userName;
+ private String userEmail;
+}
diff --git a/src/test/java/ru/practicum/shareit/ItemMapperTest.java b/src/test/java/ru/practicum/shareit/ItemMapperTest.java
new file mode 100644
index 0000000..05ba9ca
--- /dev/null
+++ b/src/test/java/ru/practicum/shareit/ItemMapperTest.java
@@ -0,0 +1,81 @@
+package ru.practicum.shareit;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import ru.practicum.shareit.item.dto.ItemRequestDto;
+import ru.practicum.shareit.item.dto.ItemResponseDto;
+import ru.practicum.shareit.item.mapper.ItemMapper;
+import ru.practicum.shareit.item.model.Item;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@SpringBootTest
+public class ItemMapperTest {
+
+ @Autowired
+ private ItemMapper itemMapper;
+
+ @Test
+ public void itemRequestDtoToItemTest() {
+ ItemRequestDto itemRequestDto = new ItemRequestDto(
+ 1L, "TestName", "Test description", true, 1L);
+
+ Item item = itemMapper.toItem(itemRequestDto);
+
+ assertAll(() -> {
+ assertEquals(1L, item.getItemId());
+ assertEquals("TestName", item.getItemName());
+ assertEquals("Test description", item.getItemDescription());
+ assertEquals(true, item.getIsAvailable());
+ assertNull(item.getOwnerId());
+ assertEquals(1L, item.getRequestId());
+ });
+ }
+
+ @Test
+ public void itemToItemResponseDtoTest() {
+ Item item = new Item(2L, "TestName2", "Test description2", true, 2L, 2L);
+
+ ItemResponseDto itemResponseDto = itemMapper.toItemResponseDto(item);
+ assertAll(() -> {
+ assertEquals(2L, itemResponseDto.getItemId());
+ assertEquals("TestName2", itemResponseDto.getItemName());
+ assertEquals("Test description2", itemResponseDto.getItemDescription());
+ assertEquals(true, itemResponseDto.getIsAvailable());
+ assertEquals(2L, itemResponseDto.getOwnerId());
+ assertEquals(2L, itemResponseDto.getRequestId());
+ });
+ }
+
+ @Test
+ public void itemRequestDtoToItemResponseDtoTest() {
+ ItemRequestDto item = new ItemRequestDto(
+ 3L, "TestName3", "Test description3", false, 3L);
+
+ ItemResponseDto itemResponseDto = itemMapper.toItemResponseDto(item);
+ assertAll(() -> {
+ assertEquals(3L, itemResponseDto.getItemId());
+ assertEquals("TestName3", itemResponseDto.getItemName());
+ assertEquals("Test description3", itemResponseDto.getItemDescription());
+ assertEquals(false, itemResponseDto.getIsAvailable());
+ assertNull(itemResponseDto.getOwnerId());
+ assertEquals(3L, itemResponseDto.getRequestId());
+ });
+ }
+
+ @Test
+ public void itemResponseDtoToItemRequestDtoTest() {
+ ItemResponseDto itemResponseDto = new ItemResponseDto(
+ 4L, "TestName4", "Test description4", false, 4L, 4L);
+ ItemRequestDto itemRequestDto = itemMapper.toItemRequestDto(itemResponseDto);
+ assertAll(() -> {
+ assertEquals(4L, itemRequestDto.getItemId());
+ assertEquals("TestName4", itemRequestDto.getItemName());
+ assertEquals("Test description4", itemRequestDto.getItemDescription());
+ assertEquals(false, itemRequestDto.getIsAvailable());
+ assertEquals(4L, itemRequestDto.getRequestId());
+ });
+ }
+
+}
diff --git a/src/test/java/ru/practicum/shareit/UserMapperTest.java b/src/test/java/ru/practicum/shareit/UserMapperTest.java
new file mode 100644
index 0000000..0bd6da2
--- /dev/null
+++ b/src/test/java/ru/practicum/shareit/UserMapperTest.java
@@ -0,0 +1,65 @@
+package ru.practicum.shareit;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import ru.practicum.shareit.user.dto.UserRequestDto;
+import ru.practicum.shareit.user.dto.UserResponseDto;
+import ru.practicum.shareit.user.mapper.UserMapper;
+import ru.practicum.shareit.user.model.User;
+
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@SpringBootTest
+public class UserMapperTest {
+
+ @Autowired
+ private UserMapper userMapper;
+
+ @Test
+ public void userToUserRequestDtoTest() {
+ User user = new User(1L, "TestName", "test@test.com");
+
+ UserRequestDto userRequestDto = userMapper.toUserRequestDto(user);
+
+ assertAll(() -> {
+ assertEquals(1L, userRequestDto.getUserId());
+ assertEquals("TestName", userRequestDto.getUserName());
+ assertEquals("test@test.com", userRequestDto.getUserEmail());
+ });
+ }
+
+ @Test
+ public void userRequestDtoToUserTest() {
+ UserRequestDto userRequestDto = new UserRequestDto(2L, "TestName2", "test2@test.com");
+ User user = userMapper.toUser(userRequestDto);
+ assertAll(() -> {
+ assertEquals(2L, user.getUserId());
+ assertEquals("TestName2", user.getUserName());
+ assertEquals("test2@test.com", user.getUserEmail());
+ });
+ }
+
+ @Test
+ public void userResponseDtoToUserTest() {
+ UserResponseDto userResponseDto = new UserResponseDto(3L, "TestName3", "test3@test.com");
+ User user = userMapper.toUser(userResponseDto);
+ assertAll(() -> {
+ assertEquals(3L, user.getUserId());
+ assertEquals("TestName3", user.getUserName());
+ assertEquals("test3@test.com", user.getUserEmail());
+ });
+ }
+
+ @Test
+ public void userToUserResponseDtoTest() {
+ User user = new User(4L, "TestName4", "test4@test.com");
+ UserResponseDto userResponseDto = userMapper.toUserResponseDto(user);
+ assertAll(() -> {
+ assertEquals(4L, userResponseDto.getUserId());
+ assertEquals("TestName4", userResponseDto.getUserName());
+ assertEquals("test4@test.com", userResponseDto.getUserEmail());
+ });
+ }
+}