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);
+ }
+}