diff --git a/src/main/java/ru/practicum/shareit/ShareItApp.java b/src/main/java/ru/practicum/shareit/ShareItApp.java index a00ad56..1137780 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); } - -} +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/Booking.java b/src/main/java/ru/practicum/shareit/booking/Booking.java index 2d9c666..bbb99f8 100644 --- a/src/main/java/ru/practicum/shareit/booking/Booking.java +++ b/src/main/java/ru/practicum/shareit/booking/Booking.java @@ -1,7 +1,22 @@ package ru.practicum.shareit.booking; -/** - * TODO Sprint add-bookings. - */ +import lombok.Data; +import java.time.LocalDateTime; + +@Data public class Booking { -} + + public enum Status { + WAITING, + APPROVED, + REJECTED, + CANCELED + } + + private Long id; + private LocalDateTime start; + private LocalDateTime end; + private Long itemId; + private Long bookerId; + private Status status; +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/exception/ConflictException.java b/src/main/java/ru/practicum/shareit/exception/ConflictException.java new file mode 100644 index 0000000..12f8fe0 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/ConflictException.java @@ -0,0 +1,7 @@ +package ru.practicum.shareit.exception; + +public class ConflictException extends RuntimeException { + public ConflictException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/exception/InternalServerException.java b/src/main/java/ru/practicum/shareit/exception/InternalServerException.java new file mode 100644 index 0000000..f8bc39e --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/InternalServerException.java @@ -0,0 +1,11 @@ +package ru.practicum.shareit.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) +public class InternalServerException extends RuntimeException { + public InternalServerException(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/exception/handler/GlobalExceptionHandler.java b/src/main/java/ru/practicum/shareit/exception/handler/GlobalExceptionHandler.java new file mode 100644 index 0000000..dec3dd0 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/handler/GlobalExceptionHandler.java @@ -0,0 +1,87 @@ +package ru.practicum.shareit.exception.handler; + +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import ru.practicum.shareit.exception.ConflictException; +import ru.practicum.shareit.exception.NotFoundException; +import ru.practicum.shareit.exception.ValidationException; +import ru.practicum.shareit.response.ErrorResponse; + +import java.time.LocalDateTime; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(NotFoundException.class) + public ResponseEntity handleNotFound(NotFoundException e, HttpServletRequest request) { + ErrorResponse errorResponse = new ErrorResponse( + LocalDateTime.now(), + HttpStatus.NOT_FOUND.value(), + "Not Found", + e.getMessage(), + request.getRequestURI() + ); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse); + } + + @ExceptionHandler({ValidationException.class}) + public ResponseEntity handleValidation( + ValidationException e, HttpServletRequest request) { + ErrorResponse errorResponse = new ErrorResponse( + LocalDateTime.now(), + HttpStatus.BAD_REQUEST.value(), + "Bad Request", + e.getMessage(), + request.getRequestURI() + ); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse); + } + + @ExceptionHandler({ConflictException.class}) + public ResponseEntity handleConflict( + ConflictException e, HttpServletRequest request) { + ErrorResponse errorResponse = new ErrorResponse( + LocalDateTime.now(), + HttpStatus.CONFLICT.value(), + "Conflict", + e.getMessage(), + request.getRequestURI() + ); + return ResponseEntity.status(HttpStatus.CONFLICT).body(errorResponse); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleOtherExceptions(Exception e, HttpServletRequest request) { + ErrorResponse errorResponse = new ErrorResponse( + LocalDateTime.now(), + HttpStatus.INTERNAL_SERVER_ERROR.value(), + "Internal Server Error", + "Произошла ошибка: " + e.getMessage(), + request.getRequestURI() + ); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException e, + HttpServletRequest request) { + String message = e.getBindingResult().getFieldErrors().stream() + .map(error -> error.getField() + ": " + error.getDefaultMessage()) + .findFirst() + .orElse("Ошибка валидации"); + + ErrorResponse errorResponse = new ErrorResponse( + LocalDateTime.now(), + HttpStatus.BAD_REQUEST.value(), + "Bad Request", + message, + request.getRequestURI() + ); + + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse); + } +} \ 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 index bb17668..4c9b176 100644 --- a/src/main/java/ru/practicum/shareit/item/ItemController.java +++ b/src/main/java/ru/practicum/shareit/item/ItemController.java @@ -1,12 +1,52 @@ package ru.practicum.shareit.item; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.mapper.ItemMapper; +import ru.practicum.shareit.item.service.ItemService; + +import java.util.List; -/** - * TODO Sprint add-controllers. - */ @RestController @RequestMapping("/items") +@RequiredArgsConstructor public class ItemController { -} + + private final ItemService itemService; + + private static final String USER_HEADER = "X-Sharer-User-Id"; + + @PostMapping + public ItemDto create(@RequestHeader(USER_HEADER) Long userId, + @Valid @RequestBody ItemDto itemDto) { + return ItemMapper.toItemDto(itemService.create(userId, itemDto)); + } + + @PatchMapping("/{itemId}") + public ItemDto update(@RequestHeader(USER_HEADER) Long userId, + @PathVariable Long itemId, + @RequestBody ItemDto dto) { + return ItemMapper.toItemDto(itemService.update(userId, itemId, dto)); + } + + @GetMapping("/{itemId}") + public ItemDto getById(@PathVariable Long itemId) { + return ItemMapper.toItemDto(itemService.getItemById(itemId)); + } + + @GetMapping + public List getAllByOwner(@RequestHeader(USER_HEADER) Long userId) { + return itemService.getAllByOwner(userId).stream() + .map(ItemMapper::toItemDto) + .toList(); + } + + @GetMapping("/search") + public List search(@RequestParam String text) { + return itemService.search(text).stream() + .map(ItemMapper::toItemDto) + .toList(); + } +} \ 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..31c26a4 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,21 @@ package ru.practicum.shareit.item.dto; -/** - * TODO Sprint add-controllers. - */ +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder public class ItemDto { -} + private Long id; + + @NotNull + @NotBlank + private String name; + @NotNull + private String description; + @NotNull + private Boolean available; + private Long requestId; +} \ 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..c7ce218 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java @@ -0,0 +1,42 @@ +package ru.practicum.shareit.item.mapper; + +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.request.ItemRequest; +import ru.practicum.shareit.user.model.User; + +public class ItemMapper { + + public static ItemDto toItemDto(Item item) { + return ItemDto.builder() + .id(item.getId()) + .name(item.getName()) + .description(item.getDescription()) + .available(item.getAvailable()) + .requestId(item.getRequest() != null ? item.getRequest().getId() : null) + .build(); + } + + public static Item toItem(ItemDto itemDto, User owner, ItemRequest itemRequest) { + return Item.builder() + .name(itemDto.getName()) + .description(itemDto.getDescription()) + .available(itemDto.getAvailable()) + .ownerId(owner.getId()) + .request(itemRequest) + .build(); + } + + public static Item updateItemFields(Item item, ItemDto itemDto) { + if (itemDto.getDescription() != null) { + item.setDescription(itemDto.getDescription()); + } + if (itemDto.getName() != null) { + item.setName(itemDto.getName()); + } + if (itemDto.getAvailable() != null) { + item.setAvailable(itemDto.getAvailable()); + } + return item; + } +} \ 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..f0e6ad2 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,16 @@ package ru.practicum.shareit.item.model; -/** - * TODO Sprint add-controllers. - */ +import lombok.Builder; +import lombok.Data; +import ru.practicum.shareit.request.ItemRequest; + +@Data +@Builder public class Item { -} + private Long id; + private String name; + private String description; + private Boolean available; + private Long ownerId; + private ItemRequest request; +} \ 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..4cba349 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/service/ItemService.java @@ -0,0 +1,18 @@ +package ru.practicum.shareit.item.service; + +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.model.Item; + +import java.util.List; + +public interface ItemService { + Item create(Long userId, ItemDto itemDto); + + Item update(Long userId, Long itemId, ItemDto itemDto); + + Item getItemById(Long itemId); + + List getAllByOwner(Long userId); + + List search(String text); +} \ 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..42542c7 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java @@ -0,0 +1,90 @@ +package ru.practicum.shareit.item.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import ru.practicum.shareit.exception.NotFoundException; +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.user.model.User; +import ru.practicum.shareit.user.service.UserService; + +import java.util.*; +import java.util.concurrent.atomic.AtomicLong; + +@Service +@Slf4j +@RequiredArgsConstructor +public class ItemServiceImpl implements ItemService { + private final UserService userService; + private final Map items = new HashMap<>(); + private final AtomicLong itemIdCounter = new AtomicLong(0); + + @Override + public Item create(Long userId, ItemDto itemDto) { + User owner = userService.getUserById(userId); + Item item = ItemMapper.toItem(itemDto, owner, null); // пока request == null + item.setId(getNextId()); + items.put(item.getId(), item); + + log.info("Вещь создана: {}", item); + return item; + } + + @Override + public Item update(Long userId, Long itemId, ItemDto itemDto) { + Item item = getItemById(itemId); + if (!Objects.equals(item.getOwnerId(), userId)) { + throw new NotFoundException("Редактировать может только владелец."); + } + + Item update = ItemMapper.updateItemFields(item, itemDto); + log.info("Вещь обновлена: {}", item); + return update; + } + + @Override + public Item getItemById(Long itemId) { + Item item = items.get(itemId); + if (item == null) { + log.error("Вещь с id {} не найдена.", itemId); + throw new NotFoundException("Вещь с id " + itemId + " не найдена."); + } + + log.info("Вещь с id {} получена.", itemId); + return item; + } + + @Override + public List getAllByOwner(Long userId) { + List itemsByOwner = items.values().stream() + .filter(i -> i.getOwnerId().equals(userId)) + .toList(); + + log.info("Получен список всех вещей, сдаваемых пользователем с ID {}", userId); + return itemsByOwner; + } + + @Override + public List search(String text) { + if (text == null || text.isBlank()) { + log.info("Пустой запрос для поиска"); + return List.of(); + } + return findAvailableItemsByText(text); + } + + public List findAvailableItemsByText(String text) { + String lower = text.toLowerCase(); + return items.values().stream() + .filter(i -> Boolean.TRUE.equals(i.getAvailable())) + .filter(i -> i.getName().toLowerCase().contains(lower) || + i.getDescription().toLowerCase().contains(lower)) + .toList(); + } + + public long getNextId() { + return itemIdCounter.incrementAndGet(); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/request/ItemRequest.java b/src/main/java/ru/practicum/shareit/request/ItemRequest.java index 95d6f23..fe7a595 100644 --- a/src/main/java/ru/practicum/shareit/request/ItemRequest.java +++ b/src/main/java/ru/practicum/shareit/request/ItemRequest.java @@ -1,7 +1,12 @@ package ru.practicum.shareit.request; -/** - * TODO Sprint add-item-requests. - */ +import lombok.Data; +import java.time.LocalDateTime; + +@Data public class ItemRequest { -} + private Long id; + private String description; + private Long requesterId; + private LocalDateTime created; +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/response/ErrorResponse.java b/src/main/java/ru/practicum/shareit/response/ErrorResponse.java new file mode 100644 index 0000000..21b3444 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/response/ErrorResponse.java @@ -0,0 +1,18 @@ +package ru.practicum.shareit.response; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; + +@Setter +@Getter +@AllArgsConstructor +public class ErrorResponse { + private LocalDateTime timestamp; + private int status; + private String error; + private String message; + private String path; +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/user/User.java b/src/main/java/ru/practicum/shareit/user/User.java deleted file mode 100644 index ae6e7f3..0000000 --- a/src/main/java/ru/practicum/shareit/user/User.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.practicum.shareit.user; - -/** - * TODO Sprint add-controllers. - */ -public class User { -} diff --git a/src/main/java/ru/practicum/shareit/user/UserController.java b/src/main/java/ru/practicum/shareit/user/UserController.java index 03039b9..4244c8a 100644 --- a/src/main/java/ru/practicum/shareit/user/UserController.java +++ b/src/main/java/ru/practicum/shareit/user/UserController.java @@ -1,12 +1,49 @@ 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.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.mapper.UserMapper; +import ru.practicum.shareit.user.service.UserService; + +import java.util.List; -/** - * TODO Sprint add-controllers. - */ @RestController -@RequestMapping(path = "/users") +@RequestMapping("/users") +@RequiredArgsConstructor public class UserController { -} + + private final UserService userService; + + @PostMapping + public UserDto create(@Valid @RequestBody UserDto userDto) { + return UserMapper.toUserDto(userService.create(userDto)); + } + + @PatchMapping("/{id}") + public UserDto update(@PathVariable Long id, + @RequestBody UserDto userDto) { + userDto.setId(id); + return UserMapper.toUserDto(userService.update(userDto)); + } + + @GetMapping("/{id}") + public UserDto getById(@PathVariable Long id) { + return UserMapper.toUserDto(userService.getUserById(id)); + } + + @GetMapping + public List getAll() { + return userService.getAll().stream() + .map(UserMapper::toUserDto) + .toList(); + } + + @DeleteMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void delete(@PathVariable Long id) { + userService.delete(id); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/user/dto/UserDto.java b/src/main/java/ru/practicum/shareit/user/dto/UserDto.java new file mode 100644 index 0000000..e956bd0 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/dto/UserDto.java @@ -0,0 +1,17 @@ +package ru.practicum.shareit.user.dto; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class UserDto { + private Long id; + + @Email(message = "Некорректный email") + @NotNull + private String email; + private String name; +} \ 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..b673bc4 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java @@ -0,0 +1,28 @@ +package ru.practicum.shareit.user.mapper; + +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.dto.UserDto; + +public class UserMapper { + + public static UserDto toUserDto(User user) { + return UserDto.builder() + .id(user.getId()) + .name(user.getName()) + .email(user.getEmail()) + .build(); + } + + public static User toUser(UserDto userDto) { + return User.builder() + .name(userDto.getName()) + .email(userDto.getEmail()) + .build(); + } + + public static void updateUserFields(User user, UserDto userDto) { + if (userDto.getName() != null) { + user.setName(userDto.getName()); + } + } +} \ 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..9a04c58 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/model/User.java @@ -0,0 +1,12 @@ +package ru.practicum.shareit.user.model; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class User { + private Long id; + private String email; + private String name; +} \ 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..658346a --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/service/UserService.java @@ -0,0 +1,18 @@ +package ru.practicum.shareit.user.service; + +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.dto.UserDto; + +import java.util.List; + +public interface UserService { + User create(UserDto userDto); + + User update(UserDto userDto); + + User getUserById(Long id); + + List getAll(); + + void delete(Long id); +} \ 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..19d63c8 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java @@ -0,0 +1,99 @@ +package ru.practicum.shareit.user.service; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import ru.practicum.shareit.exception.ConflictException; +import ru.practicum.shareit.exception.NotFoundException; +import ru.practicum.shareit.exception.ValidationException; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.mapper.UserMapper; + +import java.util.*; +import java.util.concurrent.atomic.AtomicLong; + +@Service +@Slf4j +public class UserServiceImpl implements UserService { + private final Map users = new HashMap<>(); + private final Map usersByEmail = new HashMap<>(); + private final AtomicLong userIdCounter = new AtomicLong(0); + + @Override + public User create(UserDto userDto) { + if (usersByEmail.containsKey(userDto.getEmail())) { + log.error("Ошибка создания пользователя: email {} уже используется", userDto.getEmail()); + throw new ConflictException("Данный email уже используется"); + } + + User user = UserMapper.toUser(userDto); + user.setId(getNextId()); + users.put(user.getId(), user); + usersByEmail.put(user.getEmail(), user); + + log.info("Пользователь успешно создан с id: {}, email: {}", + user.getId(), user.getEmail()); + return user; + } + + @Override + public User update(UserDto userDto) { + if (userDto.getId() == null || userDto.getId() == 0) { + throw new ValidationException("id должен быть указан"); + } + User existingUser = users.get(userDto.getId()); + if (existingUser == null) { + log.error("Пользователь не найден"); + throw new NotFoundException("Пользователь не найден"); + } + // проверяем доступность email + if (!Objects.equals(userDto.getEmail(), existingUser.getEmail())) { + if (usersByEmail.containsKey(userDto.getEmail())) { + User existingUserByEmail = usersByEmail.get(userDto.getEmail()); + if (!Objects.equals(existingUserByEmail.getId(), userDto.getId())) { + log.error("Ошибка обновления: email {} уже используется", userDto.getEmail()); + throw new ConflictException("Этот e-mail уже используется"); + } + } + usersByEmail.remove(existingUser.getEmail()); + existingUser.setEmail(userDto.getEmail()); + usersByEmail.put(existingUser.getEmail(), existingUser); + } + + // Обновляем только изменяемые поля + UserMapper.updateUserFields(existingUser, userDto); + + log.info("Пользователь с id {} успешно обновлён", existingUser.getId()); + return existingUser; + } + + @Override + public User getUserById(Long userId) { + User user = users.get(userId); + if (user == null) { + log.error("Пользователь с id {} не найден.", userId); + throw new NotFoundException("Пользователь с id " + userId + " не найден."); + } + + log.info("Пользователь с id {} получен.", userId); + return user; + } + + @Override + public List getAll() { + return users.values().stream() + .toList(); + } + + @Override + public void delete(Long userId) { + User user = getUserById(userId); // один вызов + users.remove(userId); + usersByEmail.remove(user.getEmail()); + log.info("Пользователь с id {} удален", userId); + } + + public long getNextId() { + return userIdCounter.incrementAndGet(); + } +} \ No newline at end of file