diff --git a/.img/ewm-main-event-service.png b/.img/ewm-main-event-service.png new file mode 100644 index 0000000..a600c6f Binary files /dev/null and b/.img/ewm-main-event-service.png differ diff --git a/.img/ewm-main-rating-service.png b/.img/ewm-main-rating-service.png new file mode 100644 index 0000000..f889525 Binary files /dev/null and b/.img/ewm-main-rating-service.png differ diff --git a/.img/ewm-main-request-service.png b/.img/ewm-main-request-service.png new file mode 100644 index 0000000..d35f401 Binary files /dev/null and b/.img/ewm-main-request-service.png differ diff --git a/.img/ewm-main-user-service.png b/.img/ewm-main-user-service.png new file mode 100644 index 0000000..2c4e1cd Binary files /dev/null and b/.img/ewm-main-user-service.png differ diff --git a/.img/ewm-stats-stats-server.png b/.img/ewm-stats-stats-server.png new file mode 100644 index 0000000..5fd9297 Binary files /dev/null and b/.img/ewm-stats-stats-server.png differ diff --git a/.img/external-api-2.png b/.img/external-api-2.png new file mode 100644 index 0000000..1c13636 Binary files /dev/null and b/.img/external-api-2.png differ diff --git a/.img/external-api.png b/.img/external-api.png new file mode 100644 index 0000000..bfb4bfe Binary files /dev/null and b/.img/external-api.png differ diff --git a/.img/internal-api.png b/.img/internal-api.png new file mode 100644 index 0000000..32ab3b5 Binary files /dev/null and b/.img/internal-api.png differ diff --git a/.img/microservices.png b/.img/microservices.png new file mode 100644 index 0000000..7db5bf5 Binary files /dev/null and b/.img/microservices.png differ diff --git a/.postman/feature.json b/.postman/ewm-rating-service.json similarity index 99% rename from .postman/feature.json rename to .postman/ewm-rating-service.json index f75e49e..459a353 100644 --- a/.postman/feature.json +++ b/.postman/ewm-rating-service.json @@ -770,7 +770,7 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + "pm.test(\"Ответ должен содержать код статуса 404 и данные в формате json\", function () {\r", " pm.response.to.have.status(404);\r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", @@ -1043,7 +1043,7 @@ "variable": [ { "key": "baseUrl", - "value": "http://localhost:8080" + "value": "" }, { "key": "uid", diff --git a/core/aggregation-api/pom.xml b/core/aggregation-api/pom.xml new file mode 100644 index 0000000..3d1e4eb --- /dev/null +++ b/core/aggregation-api/pom.xml @@ -0,0 +1,54 @@ + + + 4.0.0 + + + ru.practicum + core + 0.0.1-SNAPSHOT + + + aggregation-api + jar + + + + ru.practicum + stat-dto + 0.0.1-SNAPSHOT + + + + jakarta.servlet + jakarta.servlet-api + + + jakarta.validation + jakarta.validation-api + + + jakarta.persistence + jakarta.persistence-api + + + + org.hibernate.orm + hibernate-core + + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + + \ No newline at end of file diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/client/RemoteCallExecutor.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/client/RemoteCallExecutor.java new file mode 100644 index 0000000..3dfa43f --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/client/RemoteCallExecutor.java @@ -0,0 +1,39 @@ +package ru.practicum.aggregation.client; + +import lombok.experimental.UtilityClass; +import org.springframework.lang.NonNull; +import ru.practicum.aggregation.error.exception.bussines.ServiceProcessingException; +import ru.practicum.aggregation.error.exception.unavailable.ServiceUnavailableException; + +import java.util.function.Supplier; + +@UtilityClass +public class RemoteCallExecutor { + + @NonNull + public RemoteCallResult execute(@NonNull Supplier action) { + try { + return new RemoteCallResult.Success<>(action.get()); + + } catch (ServiceProcessingException ex) { + return new RemoteCallResult.Failure<>(ex); + + } catch (ServiceUnavailableException ex) { + return new RemoteCallResult.Degraded<>(ex); + } + } + + @NonNull + public static RemoteCallResult executeVoid(@NonNull Runnable function) { + try { + function.run(); + return new RemoteCallResult.Success<>(null); + + } catch (ServiceProcessingException ex) { + return new RemoteCallResult.Failure<>(ex); + + } catch (ServiceUnavailableException ex) { + return new RemoteCallResult.Degraded<>(ex); + } + } +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/client/RemoteCallResult.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/client/RemoteCallResult.java new file mode 100644 index 0000000..2ba2f77 --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/client/RemoteCallResult.java @@ -0,0 +1,22 @@ +package ru.practicum.aggregation.client; + +import ru.practicum.aggregation.error.exception.bussines.ServiceProcessingException; + +@SuppressWarnings("unused") +public sealed interface RemoteCallResult + permits RemoteCallResult.Success, + RemoteCallResult.Failure, + RemoteCallResult.Degraded { + + record Success(T value) + implements RemoteCallResult { + } + + record Failure(ServiceProcessingException ex) + implements RemoteCallResult { + } + + record Degraded(Throwable cause) + implements RemoteCallResult { + } +} \ No newline at end of file diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/client/RemoteExceptionMapper.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/client/RemoteExceptionMapper.java new file mode 100644 index 0000000..b30f1da --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/client/RemoteExceptionMapper.java @@ -0,0 +1,52 @@ +package ru.practicum.aggregation.client; + +import feign.FeignException; +import lombok.NonNull; +import lombok.experimental.UtilityClass; +import ru.practicum.aggregation.error.exception.bussines.cause.BadRequestException; +import ru.practicum.aggregation.error.exception.bussines.cause.ConflictException; +import ru.practicum.aggregation.error.exception.bussines.cause.NotFoundException; +import ru.practicum.aggregation.error.exception.unavailable.EventServiceUnavailableException; +import ru.practicum.aggregation.error.exception.unavailable.RequestServiceUnavailableException; +import ru.practicum.aggregation.error.exception.unavailable.UserServiceUnavailableException; + +@UtilityClass +public class RemoteExceptionMapper { + + public RuntimeException mapUserException(@NonNull Throwable cause) { + if (cause instanceof FeignException ex) { + return switch (ex.status()) { + case 400 -> new BadRequestException(ex.getMessage()); + case 404 -> new NotFoundException(ex.getMessage()); + case 409 -> new ConflictException(ex.getMessage()); + default -> new EventServiceUnavailableException(ex); + }; + } + return new UserServiceUnavailableException(cause); + } + + public RuntimeException mapEventException(@NonNull Throwable cause) { + if (cause instanceof FeignException ex) { + return switch (ex.status()) { + case 400 -> new BadRequestException(ex.getMessage()); + case 404 -> new NotFoundException(ex.getMessage()); + case 409 -> new ConflictException(ex.getMessage()); + default -> new EventServiceUnavailableException(ex); + }; + } + return new EventServiceUnavailableException(cause); + } + + public RuntimeException mapRequestException(@NonNull Throwable cause) { + if (cause instanceof FeignException ex) { + return switch (ex.status()) { + case 400 -> new BadRequestException(ex.getMessage()); + case 404 -> new NotFoundException(ex.getMessage()); + case 409 -> new ConflictException(ex.getMessage()); + default -> new EventServiceUnavailableException(ex); + }; + } + return new RequestServiceUnavailableException(cause); + } + +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/client/event/EventClient.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/client/event/EventClient.java new file mode 100644 index 0000000..cbf7c0a --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/client/event/EventClient.java @@ -0,0 +1,25 @@ +package ru.practicum.aggregation.client.event; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.*; +import ru.practicum.aggregation.dto.event.output.EventFullDto; +import ru.practicum.aggregation.dto.rating.come.update.RatingUpdateRequest; + +@FeignClient( + name = "event-service", + fallbackFactory = EventFallbackFactory.class +) +public interface EventClient { + + @PatchMapping("/system/events/ratings") + void systemUpdateRating(@RequestBody @NotNull @Valid RatingUpdateRequest request); + + @GetMapping("/system/events/{eventId}") + EventFullDto systemFindEventById(@PathVariable Long eventId, @RequestParam Long confirmets); + + @GetMapping("/system/events/{eventId}/initiator") + Long systemGetInitiatorIfPublished(@PathVariable Long eventId); + +} \ No newline at end of file diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/client/event/EventFallbackFactory.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/client/event/EventFallbackFactory.java new file mode 100644 index 0000000..2f64581 --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/client/event/EventFallbackFactory.java @@ -0,0 +1,34 @@ +package ru.practicum.aggregation.client.event; + +import org.springframework.cloud.openfeign.FallbackFactory; +import org.springframework.stereotype.Component; +import ru.practicum.aggregation.client.RemoteExceptionMapper; +import ru.practicum.aggregation.dto.event.output.EventFullDto; +import ru.practicum.aggregation.dto.rating.come.update.RatingUpdateRequest; + +@Component +public class EventFallbackFactory implements FallbackFactory { + + @Override + public EventClient create(Throwable cause) { + + return new EventClient() { + + @Override + public void systemUpdateRating(RatingUpdateRequest request) { + throw RemoteExceptionMapper.mapEventException(cause); + } + + @Override + public EventFullDto systemFindEventById(Long eventId, Long confirmets) { + throw RemoteExceptionMapper.mapEventException(cause); + } + + @Override + public Long systemGetInitiatorIfPublished(Long eventId) { + throw RemoteExceptionMapper.mapEventException(cause); + } + }; + } + +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/client/request/RequestClient.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/client/request/RequestClient.java new file mode 100644 index 0000000..1e03c4f --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/client/request/RequestClient.java @@ -0,0 +1,33 @@ +package ru.practicum.aggregation.client.request; + +import jakarta.validation.constraints.Positive; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.*; +import ru.practicum.aggregation.dto.participation.come.EventRequestStatusUpdateRequest; +import ru.practicum.aggregation.dto.participation.output.EventRequestStatusUpdateResult; +import ru.practicum.aggregation.dto.participation.output.ParticipationRequestDto; +import ru.practicum.aggregation.model.repository.EventRequestCount; + +import java.util.List; + +@FeignClient( + name = "request-service", + fallbackFactory = RequestFallbackFactory.class +) +public interface RequestClient { + + @GetMapping("/system/requests/users/{userId}/events/{eventId}") + List findByUserIdAndEventId(@PathVariable @Positive Long userId, + @PathVariable @Positive Long eventId); + + @PatchMapping("/system/requests/events/{eventId}/status") + EventRequestStatusUpdateResult updateStatusRequest( + @PathVariable Long eventId, + @RequestParam Integer participantLimit, + @RequestParam Boolean requestModeration, + @RequestBody EventRequestStatusUpdateRequest request + ); + + @GetMapping("/system/requests/count") + List getConfirmedRequestsCount(@RequestParam List eventIds); +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/client/request/RequestFallbackFactory.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/client/request/RequestFallbackFactory.java new file mode 100644 index 0000000..a4cd1d0 --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/client/request/RequestFallbackFactory.java @@ -0,0 +1,43 @@ +package ru.practicum.aggregation.client.request; + +import org.springframework.cloud.openfeign.FallbackFactory; +import org.springframework.stereotype.Component; +import ru.practicum.aggregation.client.RemoteExceptionMapper; +import ru.practicum.aggregation.dto.participation.come.EventRequestStatusUpdateRequest; +import ru.practicum.aggregation.dto.participation.output.EventRequestStatusUpdateResult; +import ru.practicum.aggregation.dto.participation.output.ParticipationRequestDto; +import ru.practicum.aggregation.model.repository.EventRequestCount; + +import java.util.List; + +@Component +public class RequestFallbackFactory implements FallbackFactory { + + @Override + public RequestClient create(Throwable cause) { + + return new RequestClient() { + + @Override + public List findByUserIdAndEventId(Long userId, Long eventId) { + throw RemoteExceptionMapper.mapRequestException(cause); + } + + @Override + public EventRequestStatusUpdateResult updateStatusRequest( + Long eventId, + Integer participantLimit, + Boolean requestModeration, + EventRequestStatusUpdateRequest request + ) { + throw RemoteExceptionMapper.mapRequestException(cause); + } + + @Override + public List getConfirmedRequestsCount(List eventIds) { + throw RemoteExceptionMapper.mapRequestException(cause); + } + }; + } + +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/client/stats/StatsClient.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/client/stats/StatsClient.java new file mode 100644 index 0000000..77dee9b --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/client/stats/StatsClient.java @@ -0,0 +1,31 @@ +package ru.practicum.aggregation.client.stats; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import ru.practicum.stat.dto.EndpointHitDto; +import ru.practicum.stat.dto.ViewStatsDto; + +import java.time.LocalDateTime; +import java.util.List; + +@FeignClient( + name = "stats-server", + fallbackFactory = StatsFallbackFactory.class +) +public interface StatsClient { + + @PostMapping("/hit") + void hit(@RequestBody EndpointHitDto endpointHitDto); + + @GetMapping("/stats") + List getStats( + @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime start, + @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime end, + @RequestParam List uris, + @RequestParam boolean unique); + +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/client/stats/StatsFallbackFactory.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/client/stats/StatsFallbackFactory.java new file mode 100644 index 0000000..5b1afe3 --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/client/stats/StatsFallbackFactory.java @@ -0,0 +1,36 @@ +package ru.practicum.aggregation.client.stats; + +import org.springframework.cloud.openfeign.FallbackFactory; +import org.springframework.stereotype.Component; +import ru.practicum.aggregation.error.exception.unavailable.StatsServerUnavailableException; +import ru.practicum.stat.dto.EndpointHitDto; +import ru.practicum.stat.dto.ViewStatsDto; + +import java.time.LocalDateTime; +import java.util.List; + +@Component +public class StatsFallbackFactory implements FallbackFactory { + + @Override + public StatsClient create(Throwable cause) { + + return new StatsClient() { + + @Override + public void hit(EndpointHitDto endpointHitDto) { + throw new StatsServerUnavailableException("", cause); + } + + @Override + public List getStats(LocalDateTime start, + LocalDateTime end, + List uris, + boolean unique) { + + throw new StatsServerUnavailableException("", cause); + } + }; + } + +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/client/user/UserClient.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/client/user/UserClient.java new file mode 100644 index 0000000..fe1e429 --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/client/user/UserClient.java @@ -0,0 +1,27 @@ +package ru.practicum.aggregation.client.user; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestParam; +import ru.practicum.aggregation.dto.user.output.UserDto; +import ru.practicum.aggregation.dto.user.output.UserShortDto; + +import java.util.List; + +@FeignClient( + name = "user-service", + fallbackFactory = UserFallbackFactory.class +) +public interface UserClient { + + @GetMapping("/system/users/{userId}") + UserDto getUser(@PathVariable Long userId); + + @GetMapping("/system/users/{userId}/check") + void checkUser(@PathVariable Long userId); + + @GetMapping("/system/users/short") + List getShortUsersByIds(@RequestParam List userIds); + +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/client/user/UserFallbackFactory.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/client/user/UserFallbackFactory.java new file mode 100644 index 0000000..30273d2 --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/client/user/UserFallbackFactory.java @@ -0,0 +1,36 @@ +package ru.practicum.aggregation.client.user; + +import org.springframework.cloud.openfeign.FallbackFactory; +import org.springframework.stereotype.Component; +import ru.practicum.aggregation.client.RemoteExceptionMapper; +import ru.practicum.aggregation.dto.user.output.UserDto; +import ru.practicum.aggregation.dto.user.output.UserShortDto; + +import java.util.List; + +@Component +public class UserFallbackFactory implements FallbackFactory { + + @Override + public UserClient create(Throwable cause) { + + return new UserClient() { + + @Override + public UserDto getUser(Long userId) { + throw RemoteExceptionMapper.mapUserException(cause); + } + + @Override + public void checkUser(Long userId) { + throw RemoteExceptionMapper.mapUserException(cause); + } + + @Override + public List getShortUsersByIds(List userIds) { + throw RemoteExceptionMapper.mapUserException(cause); + } + }; + } + +} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/util/config/MainServiceConfig.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/config/JacksonConfiguration.java similarity index 91% rename from core/main-service/src/main/java/ru/practicum/ewm/util/config/MainServiceConfig.java rename to core/aggregation-api/src/main/java/ru/practicum/aggregation/config/JacksonConfiguration.java index a39ef11..b87276d 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/util/config/MainServiceConfig.java +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/config/JacksonConfiguration.java @@ -1,4 +1,4 @@ -package ru.practicum.ewm.util.config; +package ru.practicum.aggregation.config; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; @@ -9,7 +9,7 @@ import java.time.format.DateTimeFormatter; @SpringBootConfiguration -public class MainServiceConfig { +public class JacksonConfiguration { @Bean public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() { diff --git a/core/main-service/src/main/java/ru/practicum/ewm/dto/category/NewCategoryDto.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/category/come/NewCategoryDto.java similarity index 70% rename from core/main-service/src/main/java/ru/practicum/ewm/dto/category/NewCategoryDto.java rename to core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/category/come/NewCategoryDto.java index 2d5e215..e3252bf 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/dto/category/NewCategoryDto.java +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/category/come/NewCategoryDto.java @@ -1,8 +1,12 @@ -package ru.practicum.ewm.dto.category; +package ru.practicum.aggregation.dto.category.come; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; +/** + * + * @param name + */ public record NewCategoryDto( @NotBlank diff --git a/core/main-service/src/main/java/ru/practicum/ewm/dto/category/CategoryDto.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/category/output/CategoryDto.java similarity index 51% rename from core/main-service/src/main/java/ru/practicum/ewm/dto/category/CategoryDto.java rename to core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/category/output/CategoryDto.java index 3ff7521..4a00a95 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/dto/category/CategoryDto.java +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/category/output/CategoryDto.java @@ -1,9 +1,15 @@ -package ru.practicum.ewm.dto.category; +package ru.practicum.aggregation.dto.category.output; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; import lombok.Builder; +/** + * Данные категории для изменения категории + * @apiNote + * Long id
+ * String name + */ @Builder public record CategoryDto( diff --git a/core/main-service/src/main/java/ru/practicum/ewm/dto/compilation/NewCompilationDto.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/compilation/come/create/NewCompilationDto.java similarity index 64% rename from core/main-service/src/main/java/ru/practicum/ewm/dto/compilation/NewCompilationDto.java rename to core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/compilation/come/create/NewCompilationDto.java index f6ded8b..50ae05f 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/dto/compilation/NewCompilationDto.java +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/compilation/come/create/NewCompilationDto.java @@ -1,4 +1,4 @@ -package ru.practicum.ewm.dto.compilation; +package ru.practicum.aggregation.dto.compilation.come.create; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; @@ -9,6 +9,13 @@ import java.util.Set; +/** + * данные новой подборки + * @apiNote + * {@link Set}<{@link Long}> events
+ * boolean pinned
+ * {@link String} title + */ @Data @Builder @AllArgsConstructor diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/compilation/come/get/CompilationSearchFilter.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/compilation/come/get/CompilationSearchFilter.java new file mode 100644 index 0000000..23818ac --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/compilation/come/get/CompilationSearchFilter.java @@ -0,0 +1,28 @@ +package ru.practicum.aggregation.dto.compilation.come.get; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * {@link Boolean} pinned - искать только закрепленные/не закрепленные подборки
+ * {@link Integer} from - количество элементов, которые нужно пропустить для формирования текущего набора
+ * Default value : 0
+ * {@link Integer} size - количество элементов в наборе
+ * Default value : 10 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CompilationSearchFilter { + + Boolean pinned; + + @Builder.Default + Integer from = 0; + + @Builder.Default + Integer size = 10; +} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/dto/compilation/CompilationUpdateDto.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/compilation/come/update/CompilationUpdateDto.java similarity index 58% rename from core/main-service/src/main/java/ru/practicum/ewm/dto/compilation/CompilationUpdateDto.java rename to core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/compilation/come/update/CompilationUpdateDto.java index 2f98b0e..da9971c 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/dto/compilation/CompilationUpdateDto.java +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/compilation/come/update/CompilationUpdateDto.java @@ -1,4 +1,4 @@ -package ru.practicum.ewm.dto.compilation; +package ru.practicum.aggregation.dto.compilation.come.update; import jakarta.validation.constraints.Size; import lombok.AllArgsConstructor; @@ -8,6 +8,13 @@ import java.util.Set; +/** + * данные для обновления подборки + * @apiNote + * {@link Set}<{@link Long}> events
+ * {@link Boolean} pinned
+ * {@link String} title + */ @Data @Builder @AllArgsConstructor diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/compilation/output/CompilationDto.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/compilation/output/CompilationDto.java new file mode 100644 index 0000000..9723569 --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/compilation/output/CompilationDto.java @@ -0,0 +1,22 @@ +package ru.practicum.aggregation.dto.compilation.output; + +import lombok.Builder; +import ru.practicum.aggregation.dto.event.output.EventShortDto; + +import java.util.List; + +/** + * @apiNote + * Long id
+ * boolean pinned
+ * String title
+ * List<{@link EventShortDto}> events + */ +@Builder +public record CompilationDto( + Long id, + boolean pinned, + String title, + List events +) { +} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/dto/event/NewEventDto.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/event/come/create/NewEventDto.java similarity index 82% rename from core/main-service/src/main/java/ru/practicum/ewm/dto/event/NewEventDto.java rename to core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/event/come/create/NewEventDto.java index fe9ff80..70efdec 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/dto/event/NewEventDto.java +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/event/come/create/NewEventDto.java @@ -1,4 +1,4 @@ -package ru.practicum.ewm.dto.event; +package ru.practicum.aggregation.dto.event.come.create; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; @@ -7,10 +7,13 @@ import lombok.Data; import lombok.experimental.Accessors; import lombok.experimental.FieldDefaults; -import ru.practicum.ewm.model.Location; +import ru.practicum.aggregation.model.entity.Location; import java.time.LocalDateTime; +/** + * данные добавляемого события + */ @Data @Builder() @Accessors(fluent = true) diff --git a/core/main-service/src/main/java/ru/practicum/ewm/dto/event/UpdateEventAdminRequest.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/event/come/update/UpdateEventAdminRequest.java similarity index 74% rename from core/main-service/src/main/java/ru/practicum/ewm/dto/event/UpdateEventAdminRequest.java rename to core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/event/come/update/UpdateEventAdminRequest.java index 14c73ec..a011313 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/dto/event/UpdateEventAdminRequest.java +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/event/come/update/UpdateEventAdminRequest.java @@ -1,9 +1,9 @@ -package ru.practicum.ewm.dto.event; +package ru.practicum.aggregation.dto.event.come.update; import jakarta.validation.constraints.Size; import lombok.Builder; -import ru.practicum.ewm.model.Location; -import ru.practicum.ewm.model.enums.AdminStateAction; +import ru.practicum.aggregation.enums.AdminStateAction; +import ru.practicum.aggregation.model.entity.Location; import java.time.LocalDateTime; diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/event/come/update/UpdateEventUserRequest.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/event/come/update/UpdateEventUserRequest.java new file mode 100644 index 0000000..e9fa32d --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/event/come/update/UpdateEventUserRequest.java @@ -0,0 +1,49 @@ +package ru.practicum.aggregation.dto.event.come.update; + +import jakarta.validation.constraints.Size; +import lombok.Builder; +import ru.practicum.aggregation.enums.UserStateAction; +import ru.practicum.aggregation.model.entity.Location; + +import java.time.LocalDateTime; + +/** + * данные для обновления события текущим пользователем + * @param annotation + * @param category + * @param description + * @param eventDate + * @param location + * @param paid + * @param participantLimit + * @param requestModeration + * @param stateAction + * @param title + */ +@Builder +public record UpdateEventUserRequest( + + @Size(min = 20, max = 2000) + String annotation, + + Long category, + + @Size(min = 20, max = 7000) + String description, + + LocalDateTime eventDate, + + Location location, + + Boolean paid, + + Integer participantLimit, + + Boolean requestModeration, + + UserStateAction stateAction, + + @Size(min = 3, max = 120) + String title +) { +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/event/output/EventFullDto.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/event/output/EventFullDto.java new file mode 100644 index 0000000..492adf3 --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/event/output/EventFullDto.java @@ -0,0 +1,75 @@ +package ru.practicum.aggregation.dto.event.output; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import ru.practicum.aggregation.dto.category.output.CategoryDto; +import ru.practicum.aggregation.dto.user.output.UserShortDto; +import ru.practicum.aggregation.enums.EventState; +import ru.practicum.aggregation.model.entity.Location; + +import java.time.LocalDateTime; + +/** + * @apiNote Long id
+ * String annotation
+ * CategoryDto category
+ * Long confirmedRequests
+ * LocalDateTime createdOn
+ * String description
+ * LocalDateTime eventDate
+ * UserShortDto initiator
+ * Location location
+ * boolean paid
+ * Integer participantLimit
+ * LocalDateTime publishedOn
+ * boolean requestModeration
+ * EventState state
+ * String title
+ * Long views
+ * long rate + */ +@Builder +public record EventFullDto( + + Long id, + + @NotBlank + String annotation, + + @NotNull + CategoryDto category, + + Long confirmedRequests, + + LocalDateTime createdOn, + + String description, + + @NotNull + LocalDateTime eventDate, + + @NotNull + UserShortDto initiator, + + @NotNull + Location location, + + boolean paid, + + Integer participantLimit, + + LocalDateTime publishedOn, + + boolean requestModeration, + + EventState state, + + @NotBlank + String title, + + Long views, + + long rate +) { +} \ No newline at end of file diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/event/output/EventShortDto.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/event/output/EventShortDto.java new file mode 100644 index 0000000..d074fe0 --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/event/output/EventShortDto.java @@ -0,0 +1,35 @@ +package ru.practicum.aggregation.dto.event.output; + +import lombok.Builder; +import ru.practicum.aggregation.dto.category.output.CategoryDto; +import ru.practicum.aggregation.dto.user.output.UserShortDto; + +import java.time.LocalDateTime; + +/** + * @apiNote + * long id
+ * {@link String} annotation
+ * {@link CategoryDto} category
+ * long confirmedRequests
+ * {@link LocalDateTime} eventDate
+ * {@link UserShortDto} initiator
+ * boolean paid
+ * {@link String} title
+ * long views
+ * long rate + */ +@Builder +public record EventShortDto( + long id, + String annotation, + CategoryDto category, + long confirmedRequests, + LocalDateTime eventDate, + UserShortDto initiator, + boolean paid, + String title, + long views, + long rate +) { +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/participation/come/EventRequestStatusUpdateRequest.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/participation/come/EventRequestStatusUpdateRequest.java new file mode 100644 index 0000000..7c4235a --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/participation/come/EventRequestStatusUpdateRequest.java @@ -0,0 +1,24 @@ +package ru.practicum.aggregation.dto.participation.come; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import lombok.Builder; +import ru.practicum.aggregation.enums.RequestUpdateStatus; + +import java.util.List; + +/** Новый статус для заявок на участие в событии текущего пользователя + * @apiNote {@link List}<{@link Long}> requestIds
+ * {@link RequestUpdateStatus} status + */ +@Builder +public record EventRequestStatusUpdateRequest( + + @NotEmpty + List<@Positive(message = "id запроса на участие должен быть положительным числом") Long> requestIds, + + @NotNull + RequestUpdateStatus status +) { +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/participation/output/EventRequestStatusUpdateResult.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/participation/output/EventRequestStatusUpdateResult.java new file mode 100644 index 0000000..7a927f3 --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/participation/output/EventRequestStatusUpdateResult.java @@ -0,0 +1,17 @@ +package ru.practicum.aggregation.dto.participation.output; + +import lombok.Builder; + +import java.util.List; + +/** + * @apiNote + * {@link List}<{@link ParticipationRequestDto}> confirmedRequests
+ * {@link List}<{@link ParticipationRequestDto}> rejectedRequests + */ +@Builder +public record EventRequestStatusUpdateResult( + List confirmedRequests, + List rejectedRequests +) { +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/participation/output/ParticipationRequestDto.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/participation/output/ParticipationRequestDto.java new file mode 100644 index 0000000..3d8ece5 --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/participation/output/ParticipationRequestDto.java @@ -0,0 +1,24 @@ +package ru.practicum.aggregation.dto.participation.output; + +import lombok.Builder; +import ru.practicum.aggregation.enums.ParticipationStatus; + +import java.time.LocalDateTime; + +/** + * @apiNote + * {@link Long} id
+ * {@link String} created
+ * {@link Long} event
+ * {@link Long} requester
+ * {@link ParticipationStatus} status + */ +@Builder +public record ParticipationRequestDto( + Long id, + LocalDateTime created, + Long event, + Long requester, + ParticipationStatus status +) { +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/rating/come/create/RatingCreateRequest.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/rating/come/create/RatingCreateRequest.java new file mode 100644 index 0000000..3665164 --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/rating/come/create/RatingCreateRequest.java @@ -0,0 +1,17 @@ +package ru.practicum.aggregation.dto.rating.come.create; + +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import ru.practicum.aggregation.enums.Reaction; + +/** + * @apiNote + * {@link Reaction} reaction + */ +@Builder +public record RatingCreateRequest( + + @NotNull + Reaction reaction +) { +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/rating/come/update/RatingUpdateRequest.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/rating/come/update/RatingUpdateRequest.java new file mode 100644 index 0000000..02cad12 --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/rating/come/update/RatingUpdateRequest.java @@ -0,0 +1,22 @@ +package ru.practicum.aggregation.dto.rating.come.update; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import lombok.Builder; + +/** + * @apiNote + * {@link Long} eventId
+ * {@link Long} rate + */ +@Builder +public record RatingUpdateRequest( + + @Positive + Long eventId, + + @NotNull + Long rate + +) { +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/rating/output/RatingResponse.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/rating/output/RatingResponse.java new file mode 100644 index 0000000..072a729 --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/rating/output/RatingResponse.java @@ -0,0 +1,20 @@ +package ru.practicum.aggregation.dto.rating.output; + +import lombok.Builder; +import ru.practicum.aggregation.enums.Reaction; + +/** + * @apiNote + * Long id
+ * Long userId
+ * Long eventId
+ * {@link Reaction} reaction + */ +@Builder +public record RatingResponse( + Long id, + Long userId, + Long eventId, + Reaction reaction +) { +} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/dto/user/NewUserRequest.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/user/come/NewUserRequest.java similarity index 65% rename from core/main-service/src/main/java/ru/practicum/ewm/dto/user/NewUserRequest.java rename to core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/user/come/NewUserRequest.java index c1820d7..8e959e2 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/dto/user/NewUserRequest.java +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/user/come/NewUserRequest.java @@ -1,10 +1,16 @@ -package ru.practicum.ewm.dto.user; +package ru.practicum.aggregation.dto.user.come; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; import lombok.Builder; +/** + * Данные добавляемого пользователя + * @apiNote + * String name
+ * String email + */ @Builder public record NewUserRequest( diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/user/output/UserDto.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/user/output/UserDto.java new file mode 100644 index 0000000..5c381ff --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/user/output/UserDto.java @@ -0,0 +1,17 @@ +package ru.practicum.aggregation.dto.user.output; + +import lombok.Builder; + +/** + * @apiNote + * {@link Long} id
+ * {@link String} name
+ * {@link String} email + */ +@Builder +public record UserDto( + Long id, + String name, + String email +) { +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/user/output/UserShortDto.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/user/output/UserShortDto.java new file mode 100644 index 0000000..6a4398e --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/dto/user/output/UserShortDto.java @@ -0,0 +1,15 @@ +package ru.practicum.aggregation.dto.user.output; + +import lombok.Builder; + +/** + * @apiNote + * Long id
+ * String name + */ +@Builder +public record UserShortDto( + Long id, + String name +) { +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/enums/AdminStateAction.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/enums/AdminStateAction.java new file mode 100644 index 0000000..248e447 --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/enums/AdminStateAction.java @@ -0,0 +1,10 @@ +package ru.practicum.aggregation.enums; + +/** + * PUBLISH_EVENT, + * REJECT_EVENT + */ +public enum AdminStateAction { + PUBLISH_EVENT, + REJECT_EVENT +} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/model/enums/EventState.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/enums/EventState.java similarity index 60% rename from core/main-service/src/main/java/ru/practicum/ewm/model/enums/EventState.java rename to core/aggregation-api/src/main/java/ru/practicum/aggregation/enums/EventState.java index 73987d3..fedcf90 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/model/enums/EventState.java +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/enums/EventState.java @@ -1,4 +1,4 @@ -package ru.practicum.ewm.model.enums; +package ru.practicum.aggregation.enums; public enum EventState { PENDING, PUBLISHED, CANCELED diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/enums/ParticipationStatus.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/enums/ParticipationStatus.java new file mode 100644 index 0000000..8f09c57 --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/enums/ParticipationStatus.java @@ -0,0 +1,8 @@ +package ru.practicum.aggregation.enums; + +/** + * PENDING, CONFIRMED, REJECTED, CANCELED + */ +public enum ParticipationStatus { + PENDING, CONFIRMED, REJECTED, CANCELED +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/enums/Reaction.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/enums/Reaction.java new file mode 100644 index 0000000..b34bd86 --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/enums/Reaction.java @@ -0,0 +1,8 @@ +package ru.practicum.aggregation.enums; + +/** + * LIKE, DISLIKE + */ +public enum Reaction { + LIKE, DISLIKE +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/enums/RequestUpdateStatus.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/enums/RequestUpdateStatus.java new file mode 100644 index 0000000..863e5f0 --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/enums/RequestUpdateStatus.java @@ -0,0 +1,8 @@ +package ru.practicum.aggregation.enums; + +/** + * CONFIRMED, REJECTED + */ +public enum RequestUpdateStatus { + CONFIRMED, REJECTED +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/enums/UserStateAction.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/enums/UserStateAction.java new file mode 100644 index 0000000..8f5e6a2 --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/enums/UserStateAction.java @@ -0,0 +1,8 @@ +package ru.practicum.aggregation.enums; + +public enum UserStateAction { + SEND_TO_REVIEW, + CANCEL_REVIEW, + PUBLISH_EVENT, + REJECT_EVENT +} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/util/error/ApiError.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/error/ApiError.java similarity index 87% rename from core/main-service/src/main/java/ru/practicum/ewm/util/error/ApiError.java rename to core/aggregation-api/src/main/java/ru/practicum/aggregation/error/ApiError.java index 3d8f3f9..37fc981 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/util/error/ApiError.java +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/error/ApiError.java @@ -1,5 +1,6 @@ -package ru.practicum.ewm.util.error; +package ru.practicum.aggregation.error; +import lombok.Builder; import org.springframework.http.HttpStatus; import java.time.LocalDateTime; @@ -11,6 +12,7 @@ * @param errors Список стектрейсов или описания ошибок * @param timestamp Дата и время когда произошла ошибка (в формате "yyyy-MM-dd HH:mm:ss") */ +@Builder(toBuilder = true) public record ApiError( HttpStatus status, String reason, diff --git a/core/main-service/src/main/java/ru/practicum/ewm/util/error/MainServiceErrorHandler.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/error/GeneralErrorHandler.java similarity index 50% rename from core/main-service/src/main/java/ru/practicum/ewm/util/error/MainServiceErrorHandler.java rename to core/aggregation-api/src/main/java/ru/practicum/aggregation/error/GeneralErrorHandler.java index a31a2c5..fe23e0d 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/util/error/MainServiceErrorHandler.java +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/error/GeneralErrorHandler.java @@ -1,4 +1,4 @@ -package ru.practicum.ewm.util.error; +package ru.practicum.aggregation.error; import jakarta.validation.ValidationException; import lombok.extern.slf4j.Slf4j; @@ -10,10 +10,10 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.method.annotation.HandlerMethodValidationException; -import ru.practicum.ewm.util.error.exception.ConflictException; -import ru.practicum.ewm.util.error.exception.HitRequestException; -import ru.practicum.ewm.util.error.exception.NotFoundException; -import ru.practicum.ewm.util.error.exception.StatResponseException; +import ru.practicum.aggregation.error.exception.bussines.cause.ConflictException; +import ru.practicum.aggregation.error.exception.bussines.cause.NotFoundException; +import ru.practicum.aggregation.error.exception.stats.HitRequestException; +import ru.practicum.aggregation.error.exception.stats.StatsResponseException; import java.io.PrintWriter; import java.io.StringWriter; @@ -24,153 +24,154 @@ @Slf4j @RestControllerAdvice -public class MainServiceErrorHandler { +public class GeneralErrorHandler { @ExceptionHandler @ResponseStatus(INTERNAL_SERVER_ERROR) public ApiError handleSomeError(final Exception ex) { String stackTrace = getStackTrace(ex); - return new ApiError( - INTERNAL_SERVER_ERROR, - "Непредвиденная ошибка", - ex.getMessage(), - stackTrace, - LocalDateTime.now() - ); + return ApiError.builder() + .status(INTERNAL_SERVER_ERROR) + .reason("Непредвиденная ошибка") + .message(ex.getMessage()) + .errors(stackTrace) + .timestamp(LocalDateTime.now()) + .build(); } @ExceptionHandler @ResponseStatus(INTERNAL_SERVER_ERROR) - public ApiError handleHitRequestException(HitRequestException e) { - log.info("500 {}", e.getMessage(), e); - String stackTrace = getStackTrace(e); - return new ApiError( - INTERNAL_SERVER_ERROR, - "Stat-server error ....", - e.getMessage(), - stackTrace, - LocalDateTime.now() - ); + public ApiError handleHitRequestException(HitRequestException ex) { + log.info("500 {}", ex.getMessage(), ex); + String stackTrace = getStackTrace(ex); + return ApiError.builder() + .status(INTERNAL_SERVER_ERROR) + .reason("Stat-server error ....") + .message(ex.getMessage()) + .errors(stackTrace) + .timestamp(LocalDateTime.now()) + .build(); } @ExceptionHandler @ResponseStatus(INTERNAL_SERVER_ERROR) - public ApiError handleStatResponseException(StatResponseException ex) { + public ApiError handleStatResponseException(StatsResponseException ex) { log.info("500 {}", ex.getMessage(), ex); String stackTrace = getStackTrace(ex); - return new ApiError( - INTERNAL_SERVER_ERROR, - "Stat-server error ....", - ex.getMessage(), - stackTrace, - LocalDateTime.now() - ); + return ApiError.builder() + .status(INTERNAL_SERVER_ERROR) + .reason("Stat-server error ....") + .message(ex.getMessage()) + .errors(stackTrace) + .timestamp(LocalDateTime.now()) + .build(); } @ExceptionHandler @ResponseStatus(BAD_REQUEST) - public ApiError handleMissingServletRequestParameter(@NonNull final MissingServletRequestParameterException ex) { + public ApiError handleMissingServletRequestParameter( + @NonNull final MissingServletRequestParameterException ex) { log.warn("400 Bad Request (MissingServletRequestParameter): {}", ex.getMessage()); String stackTrace = getStackTrace(ex); - return new ApiError( - BAD_REQUEST, - "MissingServletRequestParameter", - ex.getMessage(), - stackTrace, - LocalDateTime.now() - ); + return ApiError.builder() + .status(BAD_REQUEST) + .reason("MissingServletRequestParameter") + .message(ex.getMessage()) + .errors(stackTrace) + .timestamp(LocalDateTime.now()) + .build(); } @ExceptionHandler @ResponseStatus(BAD_REQUEST) public ApiError handleMethodArgumentNotValid(@NonNull MethodArgumentNotValidException ex) { log.warn("400 Bad Request (ArgumentNotValid): {}", ex.getMessage()); - return new ApiError( - BAD_REQUEST, - "Incorrectly made request.", - "Field: " + Objects.requireNonNull(ex.getBindingResult().getFieldError()).getField() - + ". Error: " + ex.getBindingResult().getFieldError().getDefaultMessage(), - getStackTrace(ex), - LocalDateTime.now() - ); + return ApiError.builder() + .status(BAD_REQUEST) + .reason("Incorrectly made request.") + .message("Field: " + Objects.requireNonNull(ex.getBindingResult().getFieldError()).getField() + + ". Error: " + ex.getBindingResult().getFieldError().getDefaultMessage()) + .errors(getStackTrace(ex)) + .timestamp(LocalDateTime.now()) + .build(); } @ExceptionHandler @ResponseStatus(BAD_REQUEST) public ApiError handleHandlerMethodValidationException(@NonNull HandlerMethodValidationException ex) { log.warn("400 Bad Request (HandlerMethodValidationException): {}", ex.getMessage()); - return new ApiError( - BAD_REQUEST, - "HandlerMethodValidationException", - ex.getMessage(), - getStackTrace(ex), - LocalDateTime.now() - ); + return ApiError.builder() + .status(BAD_REQUEST) + .reason("HandlerMethodValidationException") + .message(ex.getMessage()) + .errors(getStackTrace(ex)) + .timestamp(LocalDateTime.now()) + .build(); } @ExceptionHandler @ResponseStatus(BAD_REQUEST) public ApiError handleIllegalArgumentException(@NonNull IllegalArgumentException ex) { log.warn("400 Bad Request (IllegalArgument): {}", ex.getMessage()); - return new ApiError( - BAD_REQUEST, - "Incorrectly made request.", - ex.getMessage(), - getStackTrace(ex), - LocalDateTime.now() - ); + return ApiError.builder() + .status(BAD_REQUEST) + .reason("Incorrectly made request.") + .message(ex.getMessage()) + .errors(getStackTrace(ex)) + .timestamp(LocalDateTime.now()) + .build(); } @ExceptionHandler @ResponseStatus(BAD_REQUEST) public ApiError handleValidationException(@NonNull ValidationException ex) { log.warn("400 Bad Request (ValidationException): {}", ex.getMessage()); - return new ApiError( - BAD_REQUEST, - "Incorrectly made request.", - ex.getMessage(), - getStackTrace(ex), - LocalDateTime.now() - ); + return ApiError.builder() + .status(BAD_REQUEST) + .reason("Incorrectly made request.") + .message(ex.getMessage()) + .errors(getStackTrace(ex)) + .timestamp(LocalDateTime.now()) + .build(); } @ExceptionHandler @ResponseStatus(NOT_FOUND) public ApiError handleNotFound(@NonNull NotFoundException ex) { log.warn("404 Not Found: {}", ex.getMessage()); - return new ApiError( - NOT_FOUND, - "The required object was not found.", - ex.getMessage(), - getStackTrace(ex), - LocalDateTime.now() - ); + return ApiError.builder() + .status(NOT_FOUND) + .reason("The required object was not found.") + .message(ex.getMessage()) + .errors(getStackTrace(ex)) + .timestamp(LocalDateTime.now()) + .build(); } @ExceptionHandler @ResponseStatus(CONFLICT) public ApiError handleConflict(@NonNull ConflictException ex) { log.warn("409 Conflict: {}", ex.getMessage()); - return new ApiError( - CONFLICT, - "For the requested operation the conditions are not met.", - ex.getMessage(), - getStackTrace(ex), - LocalDateTime.now() - ); + return ApiError.builder() + .status(CONFLICT) + .reason("For the requested operation the conditions are not met.") + .message(ex.getMessage()) + .errors(getStackTrace(ex)) + .timestamp(LocalDateTime.now()) + .build(); } @ExceptionHandler @ResponseStatus(CONFLICT) public ApiError handleHttpMessageNotReadableException(@NonNull final HttpMessageNotReadableException ex) { log.warn("409 CONFLICT: Required request body is missing or invalid"); - return new ApiError( - BAD_REQUEST, - "Incorrectly made request.", - ex.getMessage(), - getStackTrace(ex), - LocalDateTime.now() - ); + return ApiError.builder() + .status(CONFLICT) + .reason("Incorrectly made request.") + .message(ex.getMessage()) + .errors(getStackTrace(ex)) + .timestamp(LocalDateTime.now()) + .build(); } private String getStackTrace(@NonNull Exception e) { diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/error/exception/bussines/ServiceProcessingException.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/error/exception/bussines/ServiceProcessingException.java new file mode 100644 index 0000000..be4f363 --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/error/exception/bussines/ServiceProcessingException.java @@ -0,0 +1,7 @@ +package ru.practicum.aggregation.error.exception.bussines; + +public class ServiceProcessingException extends RuntimeException { + public ServiceProcessingException(String message) { + super(message); + } +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/error/exception/bussines/cause/BadRequestException.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/error/exception/bussines/cause/BadRequestException.java new file mode 100644 index 0000000..fe513ae --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/error/exception/bussines/cause/BadRequestException.java @@ -0,0 +1,9 @@ +package ru.practicum.aggregation.error.exception.bussines.cause; + +import ru.practicum.aggregation.error.exception.bussines.ServiceProcessingException; + +public class BadRequestException extends ServiceProcessingException { + public BadRequestException(String message) { + super(message); + } +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/error/exception/bussines/cause/ConflictException.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/error/exception/bussines/cause/ConflictException.java new file mode 100644 index 0000000..afaac10 --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/error/exception/bussines/cause/ConflictException.java @@ -0,0 +1,10 @@ +package ru.practicum.aggregation.error.exception.bussines.cause; + +import ru.practicum.aggregation.error.exception.bussines.ServiceProcessingException; + +public class ConflictException extends ServiceProcessingException { + + public ConflictException(String message) { + super(message); + } +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/error/exception/bussines/cause/NotFoundException.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/error/exception/bussines/cause/NotFoundException.java new file mode 100644 index 0000000..358d7e0 --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/error/exception/bussines/cause/NotFoundException.java @@ -0,0 +1,10 @@ +package ru.practicum.aggregation.error.exception.bussines.cause; + +import ru.practicum.aggregation.error.exception.bussines.ServiceProcessingException; + +public class NotFoundException extends ServiceProcessingException { + + public NotFoundException(String message) { + super(message); + } +} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/util/error/exception/HitRequestException.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/error/exception/stats/HitRequestException.java similarity index 78% rename from core/main-service/src/main/java/ru/practicum/ewm/util/error/exception/HitRequestException.java rename to core/aggregation-api/src/main/java/ru/practicum/aggregation/error/exception/stats/HitRequestException.java index a0232af..6ce1164 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/util/error/exception/HitRequestException.java +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/error/exception/stats/HitRequestException.java @@ -1,4 +1,4 @@ -package ru.practicum.ewm.util.error.exception; +package ru.practicum.aggregation.error.exception.stats; public class HitRequestException extends RuntimeException { diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/error/exception/stats/StatsResponseException.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/error/exception/stats/StatsResponseException.java new file mode 100644 index 0000000..53da671 --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/error/exception/stats/StatsResponseException.java @@ -0,0 +1,10 @@ +package ru.practicum.aggregation.error.exception.stats; + +public class StatsResponseException extends RuntimeException { + + private static final String MESSAGE = "Не удалось получить статистику"; + + public StatsResponseException() { + super(MESSAGE); + } +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/error/exception/unavailable/EventServiceUnavailableException.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/error/exception/unavailable/EventServiceUnavailableException.java new file mode 100644 index 0000000..1699e94 --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/error/exception/unavailable/EventServiceUnavailableException.java @@ -0,0 +1,14 @@ +package ru.practicum.aggregation.error.exception.unavailable; + +public class EventServiceUnavailableException extends ServiceUnavailableException { + + public EventServiceUnavailableException(Throwable cause) { + super(""" + Сервис событий временно недоступен. + Повторите попытку позже. + """, + cause + ); + } + +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/error/exception/unavailable/RequestServiceUnavailableException.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/error/exception/unavailable/RequestServiceUnavailableException.java new file mode 100644 index 0000000..f4a092e --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/error/exception/unavailable/RequestServiceUnavailableException.java @@ -0,0 +1,14 @@ +package ru.practicum.aggregation.error.exception.unavailable; + +public class RequestServiceUnavailableException extends ServiceUnavailableException { + + public RequestServiceUnavailableException(Throwable cause) { + super(""" + Сервис запросов на участие временно недоступен. + Повторите попытку позже. + """, + cause + ); + } + +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/error/exception/unavailable/ServiceUnavailableException.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/error/exception/unavailable/ServiceUnavailableException.java new file mode 100644 index 0000000..c964a1c --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/error/exception/unavailable/ServiceUnavailableException.java @@ -0,0 +1,7 @@ +package ru.practicum.aggregation.error.exception.unavailable; + +public class ServiceUnavailableException extends RuntimeException { + public ServiceUnavailableException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/error/exception/unavailable/StatsServerUnavailableException.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/error/exception/unavailable/StatsServerUnavailableException.java new file mode 100644 index 0000000..3bb526c --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/error/exception/unavailable/StatsServerUnavailableException.java @@ -0,0 +1,7 @@ +package ru.practicum.aggregation.error.exception.unavailable; + +public class StatsServerUnavailableException extends ServiceUnavailableException { + public StatsServerUnavailableException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/error/exception/unavailable/UserServiceUnavailableException.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/error/exception/unavailable/UserServiceUnavailableException.java new file mode 100644 index 0000000..a615002 --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/error/exception/unavailable/UserServiceUnavailableException.java @@ -0,0 +1,14 @@ +package ru.practicum.aggregation.error.exception.unavailable; + +public class UserServiceUnavailableException extends ServiceUnavailableException { + + public UserServiceUnavailableException(Throwable cause) { + super(""" + Сервис пользователей временно недоступен. + Повторите попытку позже. + """, + cause + ); + } + +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/model/data/AdminGetData.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/model/data/AdminGetData.java new file mode 100644 index 0000000..abd277f --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/model/data/AdminGetData.java @@ -0,0 +1,31 @@ +package ru.practicum.aggregation.model.data; + +import lombok.Builder; +import ru.practicum.aggregation.enums.EventState; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * + * @param users список id пользователей, чьи события нужно найти + * @param states список состояний в которых находятся искомые события + * @param categories список id категорий в которых будет вестись поиск + * @param rangeStart дата и время не раньше которых должно произойти событие + * @param rangeEnd дата и время не позже которых должно произойти событие + * @param from количество событий, которые нужно пропустить для формирования текущего набора
+ * Default value : 0 + * @param size количество событий в наборе
+ * Default value : 10 + */ +@Builder +public record AdminGetData( + List users, + List states, + List categories, + LocalDateTime rangeStart, + LocalDateTime rangeEnd, + Integer from, + Integer size +) { +} \ No newline at end of file diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/model/data/FreeGetData.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/model/data/FreeGetData.java new file mode 100644 index 0000000..7f6d6e6 --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/model/data/FreeGetData.java @@ -0,0 +1,42 @@ +package ru.practicum.aggregation.model.data; + +import jakarta.validation.constraints.NotNull; +import lombok.Builder; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * + * @param text текст для поиска в содержимом аннотации и подробном описании события + * @param categories список идентификаторов категорий в которых будет вестись поиск + * @param paid поиск только платных/бесплатных событий + * @param rangeStart дата и время не раньше которых должно произойти событие + * @param rangeEnd дата и время не позже которых должно произойти событие + * @param onlyAvailable только события у которых не исчерпан лимит запросов на участие
+ * Default value : false + * @param sort Вариант сортировки: по дате события или по количеству просмотров
+ * Available values : EVENT_DATE, VIEWS + * @param from количество событий, которые нужно пропустить для формирования текущего набора
+ * Default value : 0 + * @param size количество событий в наборе
+ * Default value : 10 + */ +@NotNull +@Builder +public record FreeGetData( + String text, + List categories, + Boolean paid, + LocalDateTime rangeStart, + LocalDateTime rangeEnd, + Boolean onlyAvailable, + FreeEventSort sort, + Integer from, + Integer size +) { + + public enum FreeEventSort { + EVENT_DATE, VIEWS + } +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/model/data/StatsRequestData.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/model/data/StatsRequestData.java new file mode 100644 index 0000000..ac17417 --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/model/data/StatsRequestData.java @@ -0,0 +1,24 @@ +package ru.practicum.aggregation.model.data; + +import lombok.Builder; +import lombok.Data; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; + +@Data +@Builder +public class StatsRequestData { + + private List uris; + + @Builder.Default + private LocalDateTime start = LocalDateTime.parse("1970-01-01T00:00:00"); + + @Builder.Default + private LocalDateTime end = LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS); + + @Builder.Default + private Boolean unique = false; +} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/ewm/util/entity/BaseEntity.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/model/entity/BaseEntity.java similarity index 95% rename from core/main-service/src/main/java/ru/practicum/ewm/util/entity/BaseEntity.java rename to core/aggregation-api/src/main/java/ru/practicum/aggregation/model/entity/BaseEntity.java index 15f4e23..f68622e 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/util/entity/BaseEntity.java +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/model/entity/BaseEntity.java @@ -1,4 +1,4 @@ -package ru.practicum.ewm.util.entity; +package ru.practicum.aggregation.model.entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; diff --git a/core/main-service/src/main/java/ru/practicum/ewm/model/Location.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/model/entity/Location.java similarity index 58% rename from core/main-service/src/main/java/ru/practicum/ewm/model/Location.java rename to core/aggregation-api/src/main/java/ru/practicum/aggregation/model/entity/Location.java index fb54d09..b524bbc 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/model/Location.java +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/model/entity/Location.java @@ -1,10 +1,15 @@ -package ru.practicum.ewm.model; +package ru.practicum.aggregation.model.entity; import lombok.Builder; import lombok.Data; import java.io.Serializable; +/** + * @apiNote + * {@link Float} lat
+ * {@link Float} lon + */ @Data @Builder public class Location implements Serializable { diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/model/repository/EventRequestCount.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/model/repository/EventRequestCount.java new file mode 100644 index 0000000..3a9d103 --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/model/repository/EventRequestCount.java @@ -0,0 +1,15 @@ +package ru.practicum.aggregation.model.repository; + +import lombok.Builder; + +/** + * @apiNote + * {@link Long} eventId
+ * {@link Integer} count + */ +@Builder +public record EventRequestCount( + Long eventId, + Long count +) { +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/repository/EventFeignRepository.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/repository/EventFeignRepository.java new file mode 100644 index 0000000..c30ce08 --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/repository/EventFeignRepository.java @@ -0,0 +1,13 @@ +package ru.practicum.aggregation.repository; + +import ru.practicum.aggregation.dto.event.output.EventFullDto; +import ru.practicum.aggregation.dto.rating.come.update.RatingUpdateRequest; + +public interface EventFeignRepository { + + void updateRating(RatingUpdateRequest request); + + EventFullDto systemFindEventById(long eventId, long confirmets); + + Long getInitiatorIfPublished(Long eventId); +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/repository/EventFeignRepositoryImpl.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/repository/EventFeignRepositoryImpl.java new file mode 100644 index 0000000..3505879 --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/repository/EventFeignRepositoryImpl.java @@ -0,0 +1,93 @@ +package ru.practicum.aggregation.repository; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import lombok.extern.slf4j.Slf4j; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; +import ru.practicum.aggregation.client.RemoteCallExecutor; +import ru.practicum.aggregation.client.RemoteCallResult; +import ru.practicum.aggregation.client.event.EventClient; +import ru.practicum.aggregation.dto.event.output.EventFullDto; +import ru.practicum.aggregation.dto.rating.come.update.RatingUpdateRequest; +import ru.practicum.aggregation.error.exception.unavailable.UserServiceUnavailableException; + +@Slf4j +@Component +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class EventFeignRepositoryImpl implements EventFeignRepository { + + EventClient eventClient; + + @SuppressWarnings("unused") + @Override + public void updateRating(@NonNull RatingUpdateRequest request) { + log.info(""" + PLEASE WAITING + Система обновляет рейтинг + request: {}""", request); + var eventId = request.eventId(); + switch (RemoteCallExecutor.executeVoid(() -> eventClient.systemUpdateRating(request))) { + case RemoteCallResult.Success(var nullable) -> { + } + case RemoteCallResult.Failure(var exeption) -> { + log.warn(""" + Бизнес-исключение + Не удалось обновить рейтинг события с id={}""", eventId); + throw exeption; + } + case RemoteCallResult.Degraded(var cause) -> log.warn(""" + Деградация + Не удалось обновить рейтинг события с id={}""", eventId, cause); + } + } + + @Override + public EventFullDto systemFindEventById(long eventId, long confirmets) { + log.info(""" + PLEASE WAITING + Система ищет событие с id={}""", eventId); + return switch (RemoteCallExecutor.execute(() -> + eventClient.systemFindEventById(eventId, confirmets))) { + case RemoteCallResult.Success(var response) -> response; + case RemoteCallResult.Failure(var exeption) -> { + log.warn(""" + Бизнес-исключение + Не удалось получить событие с id={}""", eventId); + throw exeption; + } + case RemoteCallResult.Degraded(var cause) -> { + log.warn(""" + Деградация + Не удалось получить событие с id={}""", eventId); + throw new UserServiceUnavailableException(cause); + } + }; + } + + @Override + public Long getInitiatorIfPublished(Long eventId) { + log.info(""" + PLEASE WAITING + Система ищет инициатора события с id={}""", eventId); + return switch (RemoteCallExecutor.execute(() -> + eventClient.systemGetInitiatorIfPublished(eventId))) { + case RemoteCallResult.Success(var response) -> response; + case RemoteCallResult.Failure(var exeption) -> { + log.warn(""" + Бизнес-исключение + Не удалось получить инициатора события с id={}""", eventId); + throw exeption; + } + case RemoteCallResult.Degraded(var cause) -> { + log.warn(""" + Деградация + Не удалось получить инициатора события с id={}""", eventId); + throw new UserServiceUnavailableException(cause); + } + }; + } + +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/repository/RequestFeignRepository.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/repository/RequestFeignRepository.java new file mode 100644 index 0000000..378845c --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/repository/RequestFeignRepository.java @@ -0,0 +1,20 @@ +package ru.practicum.aggregation.repository; + +import ru.practicum.aggregation.dto.participation.come.EventRequestStatusUpdateRequest; +import ru.practicum.aggregation.dto.participation.output.EventRequestStatusUpdateResult; +import ru.practicum.aggregation.dto.participation.output.ParticipationRequestDto; +import ru.practicum.aggregation.model.repository.EventRequestCount; + +import java.util.List; + +public interface RequestFeignRepository { + + List getConfirmedRequestsCount(List eventIds); + + List findByUserIdAndEventId(long userId, long eventId); + + EventRequestStatusUpdateResult updateStatusRequest(Long eventId, + Integer participantLimit, + Boolean requestModeration, + EventRequestStatusUpdateRequest status); +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/repository/RequestFeignRepositoryImpl.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/repository/RequestFeignRepositoryImpl.java new file mode 100644 index 0000000..8b558b9 --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/repository/RequestFeignRepositoryImpl.java @@ -0,0 +1,119 @@ +package ru.practicum.aggregation.repository; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import ru.practicum.aggregation.client.RemoteCallExecutor; +import ru.practicum.aggregation.client.RemoteCallResult; +import ru.practicum.aggregation.client.request.RequestClient; +import ru.practicum.aggregation.dto.participation.come.EventRequestStatusUpdateRequest; +import ru.practicum.aggregation.dto.participation.output.EventRequestStatusUpdateResult; +import ru.practicum.aggregation.dto.participation.output.ParticipationRequestDto; +import ru.practicum.aggregation.model.repository.EventRequestCount; + +import java.util.Collections; +import java.util.List; + +@Slf4j +@Component +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class RequestFeignRepositoryImpl implements RequestFeignRepository { + + RequestClient requestClient; + + @Override + public List getConfirmedRequestsCount(List eventIds) { + log.info(""" + PLEASE WAITING + Система получает количество подтвержденных запросов на участие в событиях + eventIds: {}""", eventIds); + return switch (RemoteCallExecutor.execute(() -> + requestClient.getConfirmedRequestsCount(eventIds))) { + case RemoteCallResult.Success(var eventRequestCounts) -> eventRequestCounts; + case RemoteCallResult.Failure(var exeption) -> { + log.warn(""" + Бизнес-исключение + Не удалось получить количество подтвержденных заявок на участие в событиях + eventIds: {}""", eventIds); + throw exeption; + } + case RemoteCallResult.Degraded(var cause) -> { + log.warn(""" + Деградация + Не удалось получить количество подтвержденных заявок на участие в событиях + eventIds: {}""", eventIds, cause); + + yield eventIds.stream() + .map(id -> EventRequestCount.builder() + .eventId(id) + .count(0L) + .build() + ) + .toList(); + } + }; + } + + @Override + public List findByUserIdAndEventId(long userId, long eventId) { + log.info(""" + PLEASE WAITING + Система ищет запрос на участие пользователя с id={} в событии с id={}""", userId, eventId); + return switch (RemoteCallExecutor.execute(() -> requestClient + .findByUserIdAndEventId(userId, eventId))) { + case RemoteCallResult.Success(var participationRequestDtos) -> participationRequestDtos; + case RemoteCallResult.Failure(var exeption) -> { + log.warn(""" + Бизнес-исключение + Пользователю с id={} + Не удалось получить запросы на участие в событии с id={}""", userId, eventId); + throw exeption; + } + case RemoteCallResult.Degraded(var cause) -> { + log.warn(""" + Деградация + Пользователю с id={} + Не удалось получить запросы на участие в событии с id={}""", userId, eventId, cause); + + yield Collections.emptyList(); + } + }; + } + + @Override + public EventRequestStatusUpdateResult updateStatusRequest(Long eventId, + Integer participantLimit, + Boolean requestModeration, + EventRequestStatusUpdateRequest request) { + log.info(""" + PLEASE WAITING + Система обновляет статус запросов на участие в событии с id={} + {}""", eventId, request); + return switch (RemoteCallExecutor.execute(() -> + requestClient.updateStatusRequest(eventId, participantLimit, requestModeration, request))) { + case RemoteCallResult.Success(var updateResult) -> updateResult; + case RemoteCallResult.Failure(var exeption) -> { + log.warn(""" + Бизнес-исключение + Не удалось обновить статус запросов на участие в событии с id={} + request: {}""", eventId, request); + throw exeption; + } + case RemoteCallResult.Degraded(var cause) -> { + log.warn(""" + Деградация + Не удалось обновить статус запросов на участие в событии с id={} + {}""", eventId, request, cause); + + yield EventRequestStatusUpdateResult.builder() + .confirmedRequests(Collections.emptyList()) + .rejectedRequests(Collections.emptyList()) + .build(); + } + }; + } + +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/repository/StatsFeignRepository.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/repository/StatsFeignRepository.java new file mode 100644 index 0000000..0ff5776 --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/repository/StatsFeignRepository.java @@ -0,0 +1,16 @@ +package ru.practicum.aggregation.repository; + +import jakarta.servlet.http.HttpServletRequest; +import ru.practicum.aggregation.model.data.StatsRequestData; +import ru.practicum.stat.dto.ViewStatsDto; + +import java.util.List; +import java.util.Optional; + +public interface StatsFeignRepository { + + void sendHitRequest(HttpServletRequest request); + + Optional> getStatList(StatsRequestData request); + +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/repository/StatsFeignRepositoryImpl.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/repository/StatsFeignRepositoryImpl.java new file mode 100644 index 0000000..6f090e7 --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/repository/StatsFeignRepositoryImpl.java @@ -0,0 +1,96 @@ +package ru.practicum.aggregation.repository; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.env.Environment; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; +import ru.practicum.aggregation.client.RemoteCallExecutor; +import ru.practicum.aggregation.client.RemoteCallResult; +import ru.practicum.aggregation.client.stats.StatsClient; +import ru.practicum.aggregation.error.exception.stats.StatsResponseException; +import ru.practicum.stat.dto.EndpointHitDto; +import ru.practicum.aggregation.model.data.StatsRequestData; +import ru.practicum.stat.dto.ViewStatsDto; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Optional; + +@Slf4j +@Component +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class StatsFeignRepositoryImpl implements StatsFeignRepository { + + Environment environment; + StatsClient statsClient; + + @Override + public void sendHitRequest(@NonNull HttpServletRequest request) { + hit(EndpointHitDto.builder() + .ip(request.getRemoteAddr()) + .uri(request.getRequestURI()) + .app(environment.getProperty("spring.application.name")) + .timestamp(LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS)) + .build()); + } + + @Override + public Optional> getStatList(@NonNull StatsRequestData request) { + + var statUris = request.getUris(); + if (statUris.isEmpty()) { + throw new StatsResponseException(); + } + + return getStatistic(request.getStart(), request.getEnd(), statUris, request.getUnique()); + } + + @SuppressWarnings("unused") + private void hit(EndpointHitDto endpointHitDto) { + log.info(""" + PLEASE WAITING + Система регистрирует статистику + {}""", endpointHitDto); + switch (RemoteCallExecutor.executeVoid(() -> statsClient.hit(endpointHitDto))) { + + case RemoteCallResult.Success(var nullable) -> { + } + case RemoteCallResult.Failure(var exeptionNullable) -> { + } + case RemoteCallResult.Degraded(var cause) -> log.warn(""" + Деградация + Не удалось зарегистрировать статистику + {}""", endpointHitDto, cause); + + } + } + + @NonNull + private Optional> getStatistic(LocalDateTime start, + LocalDateTime end, + List uris, + boolean unique) { + log.info(""" + PLEASE WAITING + Система получает статистику + uris: {}""", uris); + return switch (RemoteCallExecutor.execute(() -> statsClient.getStats(start, end, uris, unique))) { + case RemoteCallResult.Success(var response) -> Optional.of(response); + case RemoteCallResult.Failure(var exeptionNullable) -> throw exeptionNullable; + case RemoteCallResult.Degraded(var cause) -> { + log.warn(""" + Деградация + Не удалось получить статистику + uris: {}""", uris, cause); + + yield Optional.empty(); + } + }; + } +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/repository/UserFeignRepository.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/repository/UserFeignRepository.java new file mode 100644 index 0000000..9ce82eb --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/repository/UserFeignRepository.java @@ -0,0 +1,16 @@ +package ru.practicum.aggregation.repository; + +import ru.practicum.aggregation.dto.user.output.UserDto; +import ru.practicum.aggregation.dto.user.output.UserShortDto; + +import java.util.List; + +public interface UserFeignRepository { + + UserDto getUserDtoById(long userId); + + List getUsersByIds(List userIds); + + void checkUser(Long userId); + +} diff --git a/core/aggregation-api/src/main/java/ru/practicum/aggregation/repository/UserFeignRepositoryImpl.java b/core/aggregation-api/src/main/java/ru/practicum/aggregation/repository/UserFeignRepositoryImpl.java new file mode 100644 index 0000000..f3bd2b9 --- /dev/null +++ b/core/aggregation-api/src/main/java/ru/practicum/aggregation/repository/UserFeignRepositoryImpl.java @@ -0,0 +1,97 @@ +package ru.practicum.aggregation.repository; + +import lombok.AccessLevel; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import ru.practicum.aggregation.client.RemoteCallExecutor; +import ru.practicum.aggregation.client.RemoteCallResult; +import ru.practicum.aggregation.client.user.UserClient; +import ru.practicum.aggregation.dto.user.output.UserDto; +import ru.practicum.aggregation.dto.user.output.UserShortDto; +import ru.practicum.aggregation.error.exception.unavailable.UserServiceUnavailableException; + +import java.util.List; + +@Slf4j +@Component +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class UserFeignRepositoryImpl implements UserFeignRepository { + + UserClient userClient; + + @Override + public @NonNull UserDto getUserDtoById(long userId) { + log.info(""" + PLEASE WAITING + Система получает пользователя с id={}""", userId); + return switch (RemoteCallExecutor.execute(() -> userClient.getUser(userId))) { + case RemoteCallResult.Success(var response) -> response; + case RemoteCallResult.Failure(var exeption) -> { + log.warn(""" + Бизнес-исключение + Не удалось получить пользователя с id={}""", userId); + throw exeption; + } + case RemoteCallResult.Degraded(var cause) -> { + log.warn(""" + Деградация + Не удалось получить пользователя с id={}""", userId); + throw new UserServiceUnavailableException(cause); + } + }; + } + + @Override + public List getUsersByIds(List userIds) { + log.info(""" + PLEASE WAITING + Система получает пользователей по ID + userids: {}""", userIds); + return switch (RemoteCallExecutor.execute(() -> userClient.getShortUsersByIds(userIds))) { + case RemoteCallResult.Success(var response) -> response; + case RemoteCallResult.Failure(var exeption) -> { + log.warn(""" + Бизнес-исключение + Не удалось получить краткий список пользователей + userIds: {}""", userIds); + throw exeption; + } + case RemoteCallResult.Degraded(var cause) -> { + log.warn(""" + Деградация + Не удалось получить краткий список пользователей + userIds: {}""", userIds); + throw new UserServiceUnavailableException(cause); + } + }; + } + + @SuppressWarnings("unused") + @Override + public void checkUser(Long userId) { + log.info(""" + PLEASE WAITING + Система проверяет наличие пользователя с id={}""", userId); + switch (RemoteCallExecutor.executeVoid(() -> userClient.checkUser(userId))) { + case RemoteCallResult.Success(var nullable) -> { + } + case RemoteCallResult.Failure(var exeption) -> { + log.warn(""" + Бизнес-исключение + Не удалось проверить наличие пользователя с id={}""", userId); + throw exeption; + } + case RemoteCallResult.Degraded(var cause) -> { + log.warn(""" + Деградация + Не удалось проверить наличие пользователя с id={}""", userId); + throw new UserServiceUnavailableException(cause); + } + } + } + +} diff --git a/core/main-service/pom.xml b/core/event-service/pom.xml similarity index 79% rename from core/main-service/pom.xml rename to core/event-service/pom.xml index 2098d21..86abe85 100644 --- a/core/main-service/pom.xml +++ b/core/event-service/pom.xml @@ -10,15 +10,9 @@ 0.0.1-SNAPSHOT - main-service + event-service jar - - 21 - 21 - UTF-8 - - ru.practicum @@ -27,15 +21,10 @@ ru.practicum - stat-client + aggregation-api 0.0.1-SNAPSHOT - - org.projectlombok - lombok - - - + org.springframework.boot spring-boot-starter-web @@ -53,11 +42,14 @@ spring-boot-starter-test test + - org.zalando - logbook-spring-boot-starter - 4.0.3 + io.hypersistence + hypersistence-utils-hibernate-63 + 3.15.2 + compile + org.postgresql postgresql @@ -68,12 +60,6 @@ h2 runtime - - io.hypersistence - hypersistence-utils-hibernate-63 - 3.15.2 - compile - org.springframework.cloud @@ -110,6 +96,18 @@ io.micrometer micrometer-registry-prometheus + + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + + + org.springframework.cloud + spring-cloud-starter-circuitbreaker-resilience4j + @@ -118,13 +116,6 @@ org.springframework.boot spring-boot-maven-plugin - - org.apache.maven.plugins - maven-surefire-plugin - - -Duser.language=ru -Duser.country=RU - - diff --git a/core/event-service/src/main/java/ru/practicum/ewm/events/EventServiceApp.java b/core/event-service/src/main/java/ru/practicum/ewm/events/EventServiceApp.java new file mode 100644 index 0000000..d7cdb4d --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/ewm/events/EventServiceApp.java @@ -0,0 +1,33 @@ +package ru.practicum.ewm.events; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.ComponentScan; +import ru.practicum.aggregation.client.event.EventClient; +import ru.practicum.aggregation.client.request.RequestClient; +import ru.practicum.aggregation.client.stats.StatsClient; +import ru.practicum.aggregation.client.user.UserClient; +import ru.practicum.aggregation.config.JacksonConfiguration; + +@SpringBootApplication +@EnableFeignClients(clients = { + UserClient.class, + EventClient.class, + RequestClient.class, + StatsClient.class +}) +@ComponentScan(value = { + "ru.practicum.ewm.events", + "ru.practicum.aggregation.config", + "ru.practicum.aggregation.client", + "ru.practicum.aggregation.error", + "ru.practicum.aggregation.repository" +}) +@ConfigurationPropertiesScan(basePackageClasses = JacksonConfiguration.class) +public class EventServiceApp { + public static void main(String[] args) { + SpringApplication.run(EventServiceApp.class, args); + } +} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminCategoryController.java b/core/event-service/src/main/java/ru/practicum/ewm/events/controller/admin/AdminCategoryController.java similarity index 50% rename from core/main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminCategoryController.java rename to core/event-service/src/main/java/ru/practicum/ewm/events/controller/admin/AdminCategoryController.java index 341ae49..f7504db 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminCategoryController.java +++ b/core/event-service/src/main/java/ru/practicum/ewm/events/controller/admin/AdminCategoryController.java @@ -1,13 +1,17 @@ -package ru.practicum.ewm.controller.admin; +package ru.practicum.ewm.events.controller.admin; +import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; +import lombok.NonNull; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; -import ru.practicum.ewm.dto.category.CategoryDto; -import ru.practicum.ewm.dto.category.NewCategoryDto; -import ru.practicum.ewm.service.category.CategoryService; +import ru.practicum.aggregation.dto.category.output.CategoryDto; +import ru.practicum.aggregation.dto.category.come.NewCategoryDto; +import ru.practicum.ewm.events.service.category.CategoryService; +@Slf4j @RestController @RequestMapping(path = "/admin/categories") @RequiredArgsConstructor @@ -16,42 +20,48 @@ public class AdminCategoryController { private final CategoryService categoryService; /** - * Добавление новой категории. Обратите внимание: имя категории должно быть уникальным - * + * Обратите внимание: имя категории должно быть уникальным * @param newCategoryDto данные добавляемой категории - * @return CategoryDto */ @PostMapping @ResponseStatus(HttpStatus.CREATED) - public CategoryDto addNewCategory(@RequestBody @Valid NewCategoryDto newCategoryDto) { + public CategoryDto addNewCategory(@RequestBody @Valid NewCategoryDto newCategoryDto, + @NonNull HttpServletRequest request) { + log.info(""" + ENDPOINT + Добавление новой категории + {} {}""", request.getMethod(), request.getRequestURI()); + return categoryService.adminAddNewCategory(newCategoryDto); } /** - * Изменение категории - *

* Обратите внимание: имя категории должно быть уникальным - * - * @param catId id категории - * @param categoryDto Данные категории для изменения - * @return CategoryDto */ @PatchMapping("/{catId}") public CategoryDto updateCategory(@PathVariable Long catId, - @RequestBody @Valid CategoryDto categoryDto) { + @RequestBody @Valid CategoryDto categoryDto, + @NonNull HttpServletRequest request) { + log.info(""" + ENDPOINT + Изменение категории + {} {}""", request.getMethod(), request.getRequestURI()); + return categoryService.updateCategory(catId, categoryDto); } /** - * Удаление категории - *

* Обратите внимание: с категорией не должно быть связано ни одного события. - * - * @param catId id категории */ @DeleteMapping("/{catId}") @ResponseStatus(HttpStatus.NO_CONTENT) - public void deleteCategory(@PathVariable Long catId) { + public void deleteCategory(@PathVariable Long catId, + @NonNull HttpServletRequest request) { + log.info(""" + ENDPOINT + Удаление категории + {} {}""", request.getMethod(), request.getRequestURI()); + categoryService.deleteCategory(catId); } } diff --git a/core/event-service/src/main/java/ru/practicum/ewm/events/controller/admin/AdminCompilationController.java b/core/event-service/src/main/java/ru/practicum/ewm/events/controller/admin/AdminCompilationController.java new file mode 100644 index 0000000..f212281 --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/ewm/events/controller/admin/AdminCompilationController.java @@ -0,0 +1,61 @@ +package ru.practicum.ewm.events.controller.admin; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import ru.practicum.aggregation.dto.compilation.output.CompilationDto; +import ru.practicum.aggregation.dto.compilation.come.update.CompilationUpdateDto; +import ru.practicum.aggregation.dto.compilation.come.create.NewCompilationDto; +import ru.practicum.ewm.events.service.compilation.CompilationService; + +@Slf4j +@RestController +@RequestMapping(path = "/admin/compilations") +@RequiredArgsConstructor +public class AdminCompilationController { + + private final CompilationService compilationService; + + /** + * подборка может не содержать событий + */ + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public CompilationDto createCompilation(@RequestBody @Valid NewCompilationDto newCompilationDto, + @NonNull HttpServletRequest request) { + log.info(""" + ENDPOINT + Добавление новой подборки + {} {}""", request.getMethod(), request.getRequestURI()); + + return compilationService.addCompilation(newCompilationDto); + } + + @PatchMapping("/{compId}") + public CompilationDto updateCompilation(@RequestBody @Valid CompilationUpdateDto compilationUpdateDto, + @PathVariable Long compId, + @NonNull HttpServletRequest request) { + log.info(""" + ENDPOINT + Обновление подборки + {} {}""", request.getMethod(), request.getRequestURI()); + + return compilationService.updateCompilation(compId, compilationUpdateDto); + } + + @DeleteMapping("/{compId}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void delById(@PathVariable Long compId, + @NonNull HttpServletRequest request) { + log.info(""" + ENDPOINT + Удаление подборки + {} {}""", request.getMethod(), request.getRequestURI()); + + compilationService.delById(compId); + } +} diff --git a/core/event-service/src/main/java/ru/practicum/ewm/events/controller/admin/AdminEventController.java b/core/event-service/src/main/java/ru/practicum/ewm/events/controller/admin/AdminEventController.java new file mode 100644 index 0000000..7b57395 --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/ewm/events/controller/admin/AdminEventController.java @@ -0,0 +1,102 @@ +package ru.practicum.ewm.events.controller.admin; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.web.bind.annotation.*; +import ru.practicum.aggregation.model.data.AdminGetData; +import ru.practicum.aggregation.dto.event.output.EventFullDto; +import ru.practicum.aggregation.dto.event.come.update.UpdateEventAdminRequest; +import ru.practicum.aggregation.enums.EventState; +import ru.practicum.ewm.events.service.event.EventService; + +import java.time.LocalDateTime; +import java.util.List; + +@Slf4j +@RestController +@RequestMapping(path = "/admin/events") +@RequiredArgsConstructor +public class AdminEventController { + + private final EventService eventService; + + /** + * Эндпоинт возвращает полную информацию обо всех событиях подходящих под переданные условия + * В случае, если по заданным фильтрам не найдено ни одного события, возвращает пустой список + */ + @GetMapping + public List adminGetEvents( + @RequestParam(required = false) + List users, + + @RequestParam(required = false) + List states, + + @RequestParam(required = false) + List categories, + + @RequestParam(required = false) + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + LocalDateTime rangeStart, + + @RequestParam(required = false) + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + LocalDateTime rangeEnd, + + @RequestParam(required = false, defaultValue = "0") + Integer from, + + @RequestParam(required = false, defaultValue = "10") + Integer size, + + @NonNull HttpServletRequest request + ) { + + log.info(""" + ENDPOINT + Поиск событий + {} {}""", request.getMethod(), request.getRequestURI()); + + AdminGetData getDto = AdminGetData.builder() + .users(users) + .states(states) + .categories(categories) + .rangeStart(rangeStart) + .rangeEnd(rangeEnd) + .from(from) + .size(size) + .build(); + + return eventService.adminGetEvents(getDto); + } + + /** + * Редактирование данных любого события администратором.
+ * Валидация данных не требуется.
+ * Обратите внимание:
+ * - дата начала изменяемого события должна быть не ранее чем за час от даты публикации.
+ * (Ожидается код ошибки 409)
+ * - событие можно публиковать, только если оно в состоянии ожидания публикации
+ * (Ожидается код ошибки 409)
+ * - событие можно отклонить, только если оно еще не опубликовано
+ * (Ожидается код ошибки 409) + */ + @PatchMapping("/{eventId}") + public EventFullDto adminUpdateEvent( + @PathVariable Long eventId, + @RequestBody @Valid UpdateEventAdminRequest updateRequest, + @NonNull HttpServletRequest request) { + + log.info(""" + ENDPOINT + Редактирование данных события и его статуса (отклонение/публикация) + {} {}""", request.getMethod(), request.getRequestURI()); + + return eventService.adminUpdateEvent(eventId, updateRequest); + } + +} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/controller/free/FreeCategoryController.java b/core/event-service/src/main/java/ru/practicum/ewm/events/controller/free/FreeCategoryController.java similarity index 62% rename from core/main-service/src/main/java/ru/practicum/ewm/controller/free/FreeCategoryController.java rename to core/event-service/src/main/java/ru/practicum/ewm/events/controller/free/FreeCategoryController.java index f3eb20b..a282d43 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/controller/free/FreeCategoryController.java +++ b/core/event-service/src/main/java/ru/practicum/ewm/events/controller/free/FreeCategoryController.java @@ -1,12 +1,14 @@ -package ru.practicum.ewm.controller.free; +package ru.practicum.ewm.events.controller.free; +import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.constraints.Positive; import jakarta.validation.constraints.PositiveOrZero; +import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; -import ru.practicum.ewm.dto.category.CategoryDto; -import ru.practicum.ewm.service.category.CategoryService; +import ru.practicum.aggregation.dto.category.output.CategoryDto; +import ru.practicum.ewm.events.service.category.CategoryService; import java.util.List; @@ -19,32 +21,38 @@ public class FreeCategoryController { private final CategoryService categoryService; /** - * Получение категорий. - *

* В случае, если по заданным фильтрам не найдено ни одной категории, возвращает пустой список * * @param from количество категорий, которые нужно пропустить для формирования текущего набора * Default value : 0 * @param size количество категорий в наборе * Default value : 10 - * @return List<{@link CategoryDto}> */ @GetMapping public List findAll(@RequestParam(defaultValue = "0") @PositiveOrZero Integer from, - @RequestParam(defaultValue = "10") @Positive Integer size) { + @RequestParam(defaultValue = "10") @Positive Integer size, + @NonNull HttpServletRequest request) { + + log.info(""" + ENDPOINT + Получение категорий + {} {}""", request.getMethod(), request.getRequestURI()); + return categoryService.findAll(from, size); } /** - * Получение информации о категории по её идентификатору. - *

* В случае, если категории с заданным id не найдено, возвращает статус код 404 - * - * @param catId id категории - * @return {@link CategoryDto} */ @GetMapping("/{catId}") - public CategoryDto findById(@PathVariable @Positive Long catId) { + public CategoryDto findById(@PathVariable @Positive Long catId, + @NonNull HttpServletRequest request) { + log.info(""" + ENDPOINT + Получение информации о категории по её идентификатору + {} {}""", request.getMethod(), request.getRequestURI()); + return categoryService.findById(catId); } + } diff --git a/core/event-service/src/main/java/ru/practicum/ewm/events/controller/free/FreeCompilationController.java b/core/event-service/src/main/java/ru/practicum/ewm/events/controller/free/FreeCompilationController.java new file mode 100644 index 0000000..691086e --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/ewm/events/controller/free/FreeCompilationController.java @@ -0,0 +1,58 @@ +package ru.practicum.ewm.events.controller.free; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; +import ru.practicum.aggregation.dto.compilation.output.CompilationDto; +import ru.practicum.aggregation.dto.compilation.come.get.CompilationSearchFilter; +import ru.practicum.ewm.events.service.compilation.CompilationService; + +import java.util.List; + +@Slf4j +@RestController +@RequestMapping(path = "/compilations") +@RequiredArgsConstructor +public class FreeCompilationController { + + private final CompilationService compilationService; + + /** + * В случае, если по заданным фильтрам не найдено ни одной подборки, возвращает пустой список + */ + @GetMapping + public List getCompilations( + @RequestParam(required = false) Boolean pinned, + @RequestParam(defaultValue = "0") Integer from, + @RequestParam(defaultValue = "10") Integer size, + @NonNull HttpServletRequest request + ) { + log.info(""" + ENDPOINT + Получение подборок событий + {} {}""", request.getMethod(), request.getRequestURI()); + + CompilationSearchFilter filter = CompilationSearchFilter.builder() + .pinned(pinned) + .from(from) + .size(size) + .build(); + + return compilationService.getByFilter(filter, request); + } + + @GetMapping("/{compId}") + public CompilationDto getCompilationById(@PathVariable Long compId, + @NonNull HttpServletRequest request) { + + log.info(""" + ENDPOINT + Получение подборки событий по её id + {} {}""", request.getMethod(), request.getRequestURI()); + + return compilationService.getById(compId, request); + } + +} diff --git a/core/event-service/src/main/java/ru/practicum/ewm/events/controller/free/FreeEventController.java b/core/event-service/src/main/java/ru/practicum/ewm/events/controller/free/FreeEventController.java new file mode 100644 index 0000000..728027a --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/ewm/events/controller/free/FreeEventController.java @@ -0,0 +1,114 @@ +package ru.practicum.ewm.events.controller.free; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.AccessLevel; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import lombok.extern.slf4j.Slf4j; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.web.bind.annotation.*; +import ru.practicum.aggregation.dto.event.output.EventFullDto; +import ru.practicum.aggregation.dto.event.output.EventShortDto; +import ru.practicum.aggregation.model.data.FreeGetData; +import ru.practicum.aggregation.repository.StatsFeignRepository; +import ru.practicum.ewm.events.service.event.EventService; + +import java.time.LocalDateTime; +import java.util.List; + +@Slf4j +@RestController +@RequestMapping(path = "/events") +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class FreeEventController { + + EventService eventService; + StatsFeignRepository statsFeignRepository; + + /** + * Обратите внимание:
+ * - это публичный эндпоинт, соответственно в выдаче должны быть только опубликованные события
+ * - текстовый поиск (по аннотации и подробному описанию) должен быть без учета регистра букв
+ * - если в запросе не указан диапазон дат [rangeStart-rangeEnd], то нужно выгружать события, + * которые произойдут позже текущей даты и времени
+ * - информация о каждом событии должна включать в себя количество просмотров и количество + * уже одобренных заявок на участие
+ * - информацию о том, что по этому эндпоинту был осуществлен и обработан запрос, нужно + * сохранить в сервисе статистики
+ * В случае, если по заданным фильтрам не найдено ни одного события, возвращает пустой список + */ + @GetMapping + public List getFreeEvents( + + @RequestParam(required = false) + String text, + + @RequestParam(required = false) + List categories, + + @RequestParam(required = false) + Boolean paid, + + @RequestParam(required = false) + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + LocalDateTime rangeStart, + + @RequestParam(required = false) + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + LocalDateTime rangeEnd, + + @RequestParam(required = false, defaultValue = "false") + Boolean onlyAvailable, + + @RequestParam(required = false) + FreeGetData.FreeEventSort sort, + + @RequestParam(required = false, defaultValue = "0") + Integer from, + + @RequestParam(required = false, defaultValue = "10") + Integer size, + + @NonNull HttpServletRequest request + ) { + FreeGetData freeGetData = FreeGetData.builder() + .text(text) + .categories(categories) + .paid(paid) + .rangeStart(rangeStart) + .rangeEnd(rangeEnd) + .onlyAvailable(onlyAvailable) + .sort(sort) + .from(from) + .size(size) + .build(); + + log.info(""" + ENDPOINT + Получение событий с возможностью фильтрации + {} {}""", request.getMethod(), request.getRequestURI()); + + var result = eventService.getFreeEvents(freeGetData, request); + statsFeignRepository.sendHitRequest(request); + + return result; + } + + @GetMapping(value = "/{eventId}") + public EventFullDto getFreeEventById(@PathVariable Long eventId, + @NonNull HttpServletRequest request) { + + log.info(""" + ENDPOINT + Получение подробной информации об опубликованном событии по его идентификатору. + {} {}""", request.getMethod(), request.getRequestURI()); + + var result = eventService.getFreeEventById(eventId, request); + statsFeignRepository.sendHitRequest(request); + + return result; + } + +} diff --git a/core/event-service/src/main/java/ru/practicum/ewm/events/controller/system/SystemEventController.java b/core/event-service/src/main/java/ru/practicum/ewm/events/controller/system/SystemEventController.java new file mode 100644 index 0000000..35987b1 --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/ewm/events/controller/system/SystemEventController.java @@ -0,0 +1,32 @@ +package ru.practicum.ewm.events.controller.system; + +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; +import ru.practicum.aggregation.dto.event.output.EventFullDto; +import ru.practicum.ewm.events.service.event.EventService; + +@Slf4j +@RestController +@RequestMapping("/system/events") +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class SystemEventController { + + EventService eventService; + + @GetMapping("/{eventId}") + public EventFullDto systemFindEventById(@PathVariable @Positive Long eventId, + @RequestParam @PositiveOrZero Long confirmets) { + return eventService.findEventById(eventId, confirmets); + } + + @GetMapping("/{eventId}/initiator") + public Long systemGetInitiatorIfPublished(@PathVariable @Positive Long eventId) { + return eventService.getInitiatorIfPublished(eventId); + } +} diff --git a/core/event-service/src/main/java/ru/practicum/ewm/events/controller/system/SystemRatingController.java b/core/event-service/src/main/java/ru/practicum/ewm/events/controller/system/SystemRatingController.java new file mode 100644 index 0000000..6bd9ceb --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/ewm/events/controller/system/SystemRatingController.java @@ -0,0 +1,28 @@ +package ru.practicum.ewm.events.controller.system; + +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import ru.practicum.aggregation.dto.rating.come.update.RatingUpdateRequest; +import ru.practicum.ewm.events.service.rating.EventRatingService; + +@Slf4j +@RestController +@RequestMapping(path = "/system/events/ratings") +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class SystemRatingController { + + EventRatingService eventRatingService; + + @PatchMapping + public void updateRating(@RequestBody @NotNull RatingUpdateRequest request) { + eventRatingService.updateRating(request); + } +} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/controller/user/UserEventController.java b/core/event-service/src/main/java/ru/practicum/ewm/events/controller/user/UserEventController.java similarity index 52% rename from core/main-service/src/main/java/ru/practicum/ewm/controller/user/UserEventController.java rename to core/event-service/src/main/java/ru/practicum/ewm/events/controller/user/UserEventController.java index 005aaad..591c2da 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/controller/user/UserEventController.java +++ b/core/event-service/src/main/java/ru/practicum/ewm/events/controller/user/UserEventController.java @@ -1,119 +1,121 @@ -package ru.practicum.ewm.controller.user; +package ru.practicum.ewm.events.controller.user; +import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; import jakarta.validation.constraints.PositiveOrZero; +import lombok.NonNull; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; -import ru.practicum.ewm.dto.event.EventFullDto; -import ru.practicum.ewm.dto.event.EventShortDto; -import ru.practicum.ewm.dto.event.NewEventDto; -import ru.practicum.ewm.dto.event.UpdateEventUserRequest; -import ru.practicum.ewm.dto.request.EventRequestStatusUpdateRequest; -import ru.practicum.ewm.dto.request.EventRequestStatusUpdateResult; -import ru.practicum.ewm.dto.request.ParticipationRequestDto; -import ru.practicum.ewm.service.event.EventService; -import ru.practicum.ewm.service.request.RequestService; +import ru.practicum.aggregation.dto.event.come.create.NewEventDto; +import ru.practicum.aggregation.dto.event.come.update.UpdateEventUserRequest; +import ru.practicum.aggregation.dto.event.output.EventFullDto; +import ru.practicum.aggregation.dto.event.output.EventShortDto; +import ru.practicum.aggregation.dto.participation.come.EventRequestStatusUpdateRequest; +import ru.practicum.aggregation.dto.participation.output.EventRequestStatusUpdateResult; +import ru.practicum.aggregation.dto.participation.output.ParticipationRequestDto; +import ru.practicum.ewm.events.service.event.EventService; +import ru.practicum.ewm.events.service.request.EventRequestService; import java.util.List; +@Slf4j @RestController @RequestMapping(path = "/users/{userId}/events") @RequiredArgsConstructor public class UserEventController { private final EventService eventService; - private final RequestService requestService; + private final EventRequestService eventRequestService; /** - * Получение событий, добавленных текущим пользователем - * - * @param userId id текущего пользователя - * @param from количество элементов, которые нужно пропустить для формирования текущего набора + * @param from количество элементов, которые нужно пропустить для формирования текущего набора
* Default value : 0 - * @param size количество элементов в наборе + * @param size количество элементов в наборе
* Default value : 10 - * @return List<{@link EventShortDto}> */ @GetMapping - public List findEventsByUserId(@PathVariable @Positive Long userId, - @RequestParam(defaultValue = "0") @PositiveOrZero Integer from, - @RequestParam(defaultValue = "10") @Positive Integer size) { + public List findEventsByUserId( + @PathVariable @Positive Long userId, + @RequestParam(defaultValue = "0") @PositiveOrZero Integer from, + @RequestParam(defaultValue = "10") @Positive Integer size, + @NonNull HttpServletRequest request + ) { + log.info(""" + ENDPOINT + Получение событий, добавленных текущим пользователем + {} {}""", request.getMethod(), request.getRequestURI()); + return eventService.findByUserId(userId, from, size); } - /** - * Получение полной информации о событии добавленном текущим пользователем - * - * @param userId id текущего пользователя - * @param eventId id события - * @return {@link EventFullDto} - */ @GetMapping("/{eventId}") public EventFullDto findEventById(@PathVariable @Positive Long userId, - @PathVariable @Positive Long eventId) { - return eventService.findEventById(userId, eventId); + @PathVariable @Positive Long eventId, + @NonNull HttpServletRequest request) { + log.info(""" + ENDPOINT + Получение полной информации о событии добавленном текущим пользователем + {} {}""", request.getMethod(), request.getRequestURI()); + return eventService.findEventByUserIdAndEventId(userId, eventId); } /** - * Добавление нового события - *

* Обратите внимание: дата и время на которые намечено событие не может быть раньше, * чем через два часа от текущего момента - * - * @param userId id текущего пользователя - * @param newEventDto данные добавляемого события - * @return {@link EventFullDto} */ @PostMapping @ResponseStatus(HttpStatus.CREATED) public EventFullDto addEvent(@PathVariable @Positive Long userId, - @RequestBody @Valid NewEventDto newEventDto) { - + @RequestBody @Valid NewEventDto newEventDto, + @NonNull HttpServletRequest request) { + log.info(""" + ENDPOINT + Добавление нового события + {} {}""", request.getMethod(), request.getRequestURI()); return eventService.userAddNewEvent(userId, newEventDto); } /** - * Изменение события добавленного текущим пользователем - *

* Обратите внимание: *

- * - изменить можно только отмененные события или события в состоянии ожидания модерации (Ожидается код ошибки 409) + * - изменить можно только отмененные события или события в состоянии ожидания модерации
+ * (Ожидается код ошибки 409) *

- * - дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента + * - дата и время на которые намечено событие не может быть раньше, + * чем через два часа от текущего момента
* (Ожидается код ошибки 409) - * - * @param userId id текущего пользователя - * @param eventId id редактируемого события - * @param request Данные HTTP-запроса - * @return {@link EventFullDto} */ @PatchMapping("/{eventId}") public EventFullDto patchEvent(@PathVariable @Positive Long userId, @PathVariable @Positive Long eventId, - @RequestBody @Valid UpdateEventUserRequest request) { - return eventService.patchEvent(userId, eventId, request); + @RequestBody @Valid UpdateEventUserRequest updateRequest, + @NonNull HttpServletRequest request) { + log.info(""" + ENDPOINT + Изменение события добавленного текущим пользователем + {} {}""", request.getMethod(), request.getRequestURI()); + return eventService.patchEvent(userId, eventId, updateRequest); } /** - * Получение информации о запросах на участие в событии текущего пользователя - *

* В случае, если по заданным фильтрам не найдено ни одной заявки, возвращает пустой список - * - * @param userId id текущего пользователя - * @param eventId id события - * @return List<{@link ParticipationRequestDto}> */ @GetMapping("/{eventId}/requests") public List getRequests(@PathVariable @Positive Long userId, - @PathVariable @Positive Long eventId) { - return requestService.findByEventId(userId, eventId); + @PathVariable @Positive Long eventId, + @NonNull HttpServletRequest request) { + log.info(""" + ENDPOINT + Получение информации о запросах на участие в событии текущего пользователя + {} {}""", request.getMethod(), request.getRequestURI()); + return eventRequestService.findByEventId(userId, eventId); } /** - * Изменение статуса (подтверждена, отменена) заявок на участие в событии текущего пользователя - *

* Обратите внимание: *

* - если для события лимит заявок равен 0 или отключена пре-модерация заявок, то подтверждение заявок не требуется @@ -124,16 +126,19 @@ public List getRequests(@PathVariable @Positive Long us *

* - если при подтверждении данной заявки, лимит заявок для события исчерпан, то все неподтверждённые заявки * необходимо отклонить - * - * @param userId id текущего пользователя - * @param eventId id события текущего пользователя - * @param status Новый статус для заявок на участие в событии текущего пользователя - * @return {@link EventRequestStatusUpdateResult} */ @PatchMapping("/{eventId}/requests") - public EventRequestStatusUpdateResult patchRequests(@PathVariable @Positive Long userId, - @PathVariable @Positive Long eventId, - @RequestBody @Valid EventRequestStatusUpdateRequest status) { - return requestService.updateStatusRequest(userId, eventId, status); + public EventRequestStatusUpdateResult patchRequests( + @PathVariable @Positive Long userId, + @PathVariable @Positive Long eventId, + @RequestBody @NotNull @Valid EventRequestStatusUpdateRequest updateRequest, + @NonNull HttpServletRequest request + ) { + log.info(""" + ENDPOINT + Изменение статуса (подтверждена, отменена) заявок на участие в событии текущего пользователя + {} {}""", request.getMethod(), request.getRequestURI()); + return eventRequestService.updateStatusRequest(userId, eventId, updateRequest); } + } diff --git a/core/main-service/src/main/java/ru/practicum/ewm/model/Category.java b/core/event-service/src/main/java/ru/practicum/ewm/events/entity/Category.java similarity index 81% rename from core/main-service/src/main/java/ru/practicum/ewm/model/Category.java rename to core/event-service/src/main/java/ru/practicum/ewm/events/entity/Category.java index d21838c..9d849e2 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/model/Category.java +++ b/core/event-service/src/main/java/ru/practicum/ewm/events/entity/Category.java @@ -1,4 +1,4 @@ -package ru.practicum.ewm.model; +package ru.practicum.ewm.events.entity; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -7,7 +7,7 @@ import lombok.NoArgsConstructor; import lombok.Setter; import lombok.experimental.SuperBuilder; -import ru.practicum.ewm.util.entity.BaseEntity; +import ru.practicum.aggregation.model.entity.BaseEntity; @Getter @Setter diff --git a/core/main-service/src/main/java/ru/practicum/ewm/model/Compilation.java b/core/event-service/src/main/java/ru/practicum/ewm/events/entity/Compilation.java similarity index 87% rename from core/main-service/src/main/java/ru/practicum/ewm/model/Compilation.java rename to core/event-service/src/main/java/ru/practicum/ewm/events/entity/Compilation.java index 5d02b70..f24e433 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/model/Compilation.java +++ b/core/event-service/src/main/java/ru/practicum/ewm/events/entity/Compilation.java @@ -1,10 +1,10 @@ -package ru.practicum.ewm.model; +package ru.practicum.ewm.events.entity; import jakarta.persistence.*; import lombok.*; import lombok.experimental.FieldDefaults; import lombok.experimental.SuperBuilder; -import ru.practicum.ewm.util.entity.BaseEntity; +import ru.practicum.aggregation.model.entity.BaseEntity; import java.util.HashSet; import java.util.Set; diff --git a/core/main-service/src/main/java/ru/practicum/ewm/model/Event.java b/core/event-service/src/main/java/ru/practicum/ewm/events/entity/Event.java similarity index 75% rename from core/main-service/src/main/java/ru/practicum/ewm/model/Event.java rename to core/event-service/src/main/java/ru/practicum/ewm/events/entity/Event.java index 32f8cf0..4d09417 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/model/Event.java +++ b/core/event-service/src/main/java/ru/practicum/ewm/events/entity/Event.java @@ -1,4 +1,4 @@ -package ru.practicum.ewm.model; +package ru.practicum.ewm.events.entity; import io.hypersistence.utils.hibernate.type.json.JsonBinaryType; import jakarta.persistence.*; @@ -11,12 +11,31 @@ import org.hibernate.annotations.JdbcTypeCode; import org.hibernate.annotations.Type; import org.hibernate.type.SqlTypes; -import ru.practicum.ewm.model.enums.EventState; -import ru.practicum.ewm.util.entity.BaseEntity; +import ru.practicum.aggregation.enums.EventState; +import ru.practicum.aggregation.model.entity.BaseEntity; +import ru.practicum.aggregation.model.entity.Location; import java.time.LocalDateTime; import java.util.List; +/** + * @apiNote + * {@link String} annotation
+ * {@link Category} category
+ * {@link LocalDateTime} createdOn
+ * {@link String} description
+ * {@link LocalDateTime} eventDate
+ * {@link Long} initiatorId
+ * {@link Location} location
+ * boolean paid
+ * int participantLimit
+ * {@link LocalDateTime} publishedOn
+ * boolean requestModeration
+ * {@link EventState} state
+ * {@link String} title
+ * {@link List}<{@link Compilation}> compilations
+ * long rate + */ @Getter @Setter @Entity @@ -48,9 +67,8 @@ public class Event extends BaseEntity { LocalDateTime eventDate; /// Пользователь инициатор события - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "initiator_id") - User initiator; + @JoinColumn(name = "initiator_id", nullable = false) + Long initiatorId; /// Широта и долгота места проведения события @Type(JsonBinaryType.class) diff --git a/core/main-service/src/main/java/ru/practicum/ewm/mapper/CategoryMapper.java b/core/event-service/src/main/java/ru/practicum/ewm/events/mapper/CategoryMapper.java similarity index 58% rename from core/main-service/src/main/java/ru/practicum/ewm/mapper/CategoryMapper.java rename to core/event-service/src/main/java/ru/practicum/ewm/events/mapper/CategoryMapper.java index 448c6d4..6a7a38d 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/mapper/CategoryMapper.java +++ b/core/event-service/src/main/java/ru/practicum/ewm/events/mapper/CategoryMapper.java @@ -1,10 +1,10 @@ -package ru.practicum.ewm.mapper; +package ru.practicum.ewm.events.mapper; import lombok.experimental.UtilityClass; import org.springframework.lang.NonNull; -import ru.practicum.ewm.dto.category.CategoryDto; -import ru.practicum.ewm.dto.category.NewCategoryDto; -import ru.practicum.ewm.model.Category; +import ru.practicum.aggregation.dto.category.output.CategoryDto; +import ru.practicum.aggregation.dto.category.come.NewCategoryDto; +import ru.practicum.ewm.events.entity.Category; @UtilityClass public class CategoryMapper { @@ -15,12 +15,6 @@ public CategoryDto toDto(@NonNull Category category) { .build(); } - public Category toEntity(@NonNull CategoryDto categoryDto) { - return Category.builder() - .name(categoryDto.name()) - .build(); - } - public Category toEntity(@NonNull NewCategoryDto newCategoryDto) { return Category.builder() .name(newCategoryDto.name()) diff --git a/core/main-service/src/main/java/ru/practicum/ewm/mapper/CompilationMapper.java b/core/event-service/src/main/java/ru/practicum/ewm/events/mapper/CompilationMapper.java similarity index 56% rename from core/main-service/src/main/java/ru/practicum/ewm/mapper/CompilationMapper.java rename to core/event-service/src/main/java/ru/practicum/ewm/events/mapper/CompilationMapper.java index db94788..3078c24 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/mapper/CompilationMapper.java +++ b/core/event-service/src/main/java/ru/practicum/ewm/events/mapper/CompilationMapper.java @@ -1,11 +1,13 @@ -package ru.practicum.ewm.mapper; +package ru.practicum.ewm.events.mapper; import lombok.experimental.UtilityClass; import org.springframework.lang.NonNull; -import ru.practicum.ewm.dto.compilation.CompilationDto; -import ru.practicum.ewm.dto.compilation.NewCompilationDto; -import ru.practicum.ewm.model.Compilation; -import ru.practicum.ewm.model.Event; +import ru.practicum.aggregation.dto.compilation.output.CompilationDto; +import ru.practicum.aggregation.dto.compilation.come.create.NewCompilationDto; +import ru.practicum.aggregation.dto.user.output.UserShortDto; +import ru.practicum.ewm.events.entity.Compilation; +import ru.practicum.ewm.events.entity.Event; +import ru.practicum.ewm.events.model.EventData; import java.util.HashSet; import java.util.Map; @@ -15,6 +17,7 @@ public class CompilationMapper { public CompilationDto toCompilationDto(@NonNull Compilation compilation, + @NonNull Map eventIdToInitiator, @NonNull Map confirmedRequests, @NonNull Map views) { return CompilationDto.builder() @@ -24,8 +27,12 @@ public CompilationDto toCompilationDto(@NonNull Compilation compilation, .events(compilation.getEvents().stream() .map(event -> EventMapper.toEventShortDto( event, - confirmedRequests.getOrDefault(event.getId(), 0L), - views.getOrDefault(event.getId(), 0L) + EventData.builder() + .initiator(eventIdToInitiator.get(event.getId())) + .confirmedRequests(confirmedRequests + .getOrDefault(event.getId(), 0L)) + .views(views.getOrDefault(event.getId(), 0L)) + .build() )) .toList()) .build(); @@ -38,4 +45,5 @@ public Compilation toEntity(@NonNull NewCompilationDto dto, Set events) { .events(events != null ? events : new HashSet<>()) .build(); } + } diff --git a/core/main-service/src/main/java/ru/practicum/ewm/mapper/EventMapper.java b/core/event-service/src/main/java/ru/practicum/ewm/events/mapper/EventMapper.java similarity index 72% rename from core/main-service/src/main/java/ru/practicum/ewm/mapper/EventMapper.java rename to core/event-service/src/main/java/ru/practicum/ewm/events/mapper/EventMapper.java index de4cbbd..a6d885e 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/mapper/EventMapper.java +++ b/core/event-service/src/main/java/ru/practicum/ewm/events/mapper/EventMapper.java @@ -1,12 +1,16 @@ -package ru.practicum.ewm.mapper; +package ru.practicum.ewm.events.mapper; import lombok.experimental.UtilityClass; import org.springframework.lang.NonNull; -import ru.practicum.ewm.dto.event.*; -import ru.practicum.ewm.model.Category; -import ru.practicum.ewm.model.Event; -import ru.practicum.ewm.model.User; -import ru.practicum.ewm.model.enums.EventState; +import ru.practicum.aggregation.dto.event.come.create.NewEventDto; +import ru.practicum.aggregation.dto.event.come.update.UpdateEventAdminRequest; +import ru.practicum.aggregation.dto.event.come.update.UpdateEventUserRequest; +import ru.practicum.aggregation.dto.event.output.EventFullDto; +import ru.practicum.aggregation.dto.event.output.EventShortDto; +import ru.practicum.aggregation.enums.EventState; +import ru.practicum.ewm.events.entity.Category; +import ru.practicum.ewm.events.entity.Event; +import ru.practicum.ewm.events.model.EventData; import java.time.LocalDateTime; import java.util.Optional; @@ -14,31 +18,33 @@ @UtilityClass public class EventMapper { - public EventShortDto toEventShortDto(@NonNull Event event, long confirmedRequests, long views) { + public EventShortDto toEventShortDto(@NonNull Event event, + @NonNull EventData eventData) { return EventShortDto.builder() .annotation(event.getAnnotation()) .category(CategoryMapper.toDto(event.getCategory())) - .confirmedRequests(confirmedRequests) + .confirmedRequests(eventData.confirmedRequests()) .eventDate(event.getEventDate()) .id(event.getId()) - .initiator(UserMapper.toUserShortDto(event.getInitiator())) + .initiator(eventData.initiator()) .paid(event.isPaid()) .title(event.getTitle()) - .views(views) + .views(eventData.views()) .rate(event.getRate()) .build(); } - public EventFullDto toEventFullDto(@NonNull Event event, long confirmedRequests, long views) { + public EventFullDto toEventFullDto(@NonNull Event event, + @NonNull EventData eventData) { return EventFullDto.builder() .id(event.getId()) .annotation(event.getAnnotation()) .category(CategoryMapper.toDto(event.getCategory())) - .confirmedRequests(confirmedRequests) + .confirmedRequests(eventData.confirmedRequests()) .createdOn(event.getCreatedOn()) .description(event.getDescription()) .eventDate(event.getEventDate()) - .initiator(UserMapper.toUserShortDto(event.getInitiator())) + .initiator(eventData.initiator()) .location(event.getLocation()) .paid(event.isPaid()) .participantLimit(event.getParticipantLimit()) @@ -46,7 +52,7 @@ public EventFullDto toEventFullDto(@NonNull Event event, long confirmedRequests, .requestModeration(event.isRequestModeration()) .state(event.getState()) .title(event.getTitle()) - .views(views) + .views(eventData.views()) .rate(event.getRate()) .build(); } @@ -54,7 +60,7 @@ public EventFullDto toEventFullDto(@NonNull Event event, long confirmedRequests, public Event toEntity(@NonNull NewEventDto newEventDto, Category category, LocalDateTime createdOn, - User initiator, + Long initiatorId, LocalDateTime publishedOn, EventState state) { return Event.builder() @@ -63,18 +69,21 @@ public Event toEntity(@NonNull NewEventDto newEventDto, .createdOn(createdOn) .description(newEventDto.description()) .eventDate(newEventDto.eventDate()) - .initiator(initiator) + .initiatorId(initiatorId) .location(newEventDto.location()) .paid(newEventDto.paid() != null && newEventDto.paid()) - .participantLimit(newEventDto.participantLimit() == null ? 0 : newEventDto.participantLimit()) + .participantLimit(newEventDto.participantLimit() == null + ? 0 : newEventDto.participantLimit()) .publishedOn(publishedOn) - .requestModeration(newEventDto.requestModeration() == null || newEventDto.requestModeration()) + .requestModeration(newEventDto.requestModeration() == null + || newEventDto.requestModeration()) .state(state) .title(newEventDto.title()) .rate(0) .build(); } + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") public static Event update(@NonNull Event oldEvent, @NonNull UpdateEventAdminRequest request, EventState state, @@ -143,4 +152,5 @@ public void merge(Event event, UpdateEventUserRequest dto) { event.setRequestModeration(dto.requestModeration()); } } + } diff --git a/core/main-service/src/main/java/ru/practicum/ewm/mapper/StateMapper.java b/core/event-service/src/main/java/ru/practicum/ewm/events/mapper/StateMapper.java similarity index 72% rename from core/main-service/src/main/java/ru/practicum/ewm/mapper/StateMapper.java rename to core/event-service/src/main/java/ru/practicum/ewm/events/mapper/StateMapper.java index 3acbea8..4448b98 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/mapper/StateMapper.java +++ b/core/event-service/src/main/java/ru/practicum/ewm/events/mapper/StateMapper.java @@ -1,12 +1,14 @@ -package ru.practicum.ewm.mapper; +package ru.practicum.ewm.events.mapper; +import org.springframework.lang.Nullable; import org.springframework.stereotype.Component; -import ru.practicum.ewm.model.enums.EventState; -import ru.practicum.ewm.model.enums.UserStateAction; +import ru.practicum.aggregation.enums.EventState; +import ru.practicum.aggregation.enums.UserStateAction; @Component public class StateMapper { + @Nullable public static EventState mapUserEventAction(UserStateAction action) { return switch (action) { case CANCEL_REVIEW -> EventState.CANCELED; @@ -15,6 +17,7 @@ public static EventState mapUserEventAction(UserStateAction action) { }; } + @Nullable public static EventState mapAdminEventAction(UserStateAction action) { return switch (action) { case PUBLISH_EVENT -> EventState.PUBLISHED; @@ -22,4 +25,5 @@ public static EventState mapAdminEventAction(UserStateAction action) { case null, default -> null; }; } + } diff --git a/core/event-service/src/main/java/ru/practicum/ewm/events/mapper/UserMapper.java b/core/event-service/src/main/java/ru/practicum/ewm/events/mapper/UserMapper.java new file mode 100644 index 0000000..555f175 --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/ewm/events/mapper/UserMapper.java @@ -0,0 +1,18 @@ +package ru.practicum.ewm.events.mapper; + +import lombok.experimental.UtilityClass; +import org.springframework.lang.NonNull; +import ru.practicum.aggregation.dto.user.output.UserDto; +import ru.practicum.aggregation.dto.user.output.UserShortDto; + +@UtilityClass +public class UserMapper { + + public UserShortDto toUserShortDto(@NonNull UserDto userDto) { + return UserShortDto.builder() + .id(userDto.id()) + .name(userDto.name()) + .build(); + } + +} \ No newline at end of file diff --git a/core/event-service/src/main/java/ru/practicum/ewm/events/model/EventData.java b/core/event-service/src/main/java/ru/practicum/ewm/events/model/EventData.java new file mode 100644 index 0000000..49c8111 --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/ewm/events/model/EventData.java @@ -0,0 +1,18 @@ +package ru.practicum.ewm.events.model; + +import lombok.Builder; +import ru.practicum.aggregation.dto.user.output.UserShortDto; + +/** + * @apiNote + * UserShortDto initiator
+ * long confirmedRequests
+ * long views + */ +@Builder +public record EventData( + UserShortDto initiator, + long confirmedRequests, + long views +) { +} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/ewm/dao/CategoryRepository.java b/core/event-service/src/main/java/ru/practicum/ewm/events/repository/CategoryRepository.java similarity index 66% rename from core/main-service/src/main/java/ru/practicum/ewm/dao/CategoryRepository.java rename to core/event-service/src/main/java/ru/practicum/ewm/events/repository/CategoryRepository.java index 48dac91..49b7fec 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/dao/CategoryRepository.java +++ b/core/event-service/src/main/java/ru/practicum/ewm/events/repository/CategoryRepository.java @@ -1,7 +1,7 @@ -package ru.practicum.ewm.dao; +package ru.practicum.ewm.events.repository; import org.springframework.data.jpa.repository.JpaRepository; -import ru.practicum.ewm.model.Category; +import ru.practicum.ewm.events.entity.Category; public interface CategoryRepository extends JpaRepository { diff --git a/core/main-service/src/main/java/ru/practicum/ewm/dao/CompilationRepository.java b/core/event-service/src/main/java/ru/practicum/ewm/events/repository/CompilationRepository.java similarity index 76% rename from core/main-service/src/main/java/ru/practicum/ewm/dao/CompilationRepository.java rename to core/event-service/src/main/java/ru/practicum/ewm/events/repository/CompilationRepository.java index 0bbb5aa..c860baa 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/dao/CompilationRepository.java +++ b/core/event-service/src/main/java/ru/practicum/ewm/events/repository/CompilationRepository.java @@ -1,9 +1,9 @@ -package ru.practicum.ewm.dao; +package ru.practicum.ewm.events.repository; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; -import ru.practicum.ewm.model.Compilation; +import ru.practicum.ewm.events.entity.Compilation; public interface CompilationRepository extends JpaRepository { diff --git a/core/event-service/src/main/java/ru/practicum/ewm/events/repository/EventRepository.java b/core/event-service/src/main/java/ru/practicum/ewm/events/repository/EventRepository.java new file mode 100644 index 0000000..f4c4d98 --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/ewm/events/repository/EventRepository.java @@ -0,0 +1,25 @@ +package ru.practicum.ewm.events.repository; + +import org.springframework.data.domain.PageRequest; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; +import ru.practicum.aggregation.enums.EventState; +import ru.practicum.ewm.events.entity.Event; + +import java.util.List; + +public interface EventRepository extends JpaRepository, JpaSpecificationExecutor { + + List findByInitiatorId(Long userId, PageRequest pageRequest); + + @Query(""" + select e.initiatorId from Event e + where e.id = :eventId + """) + Long getInitiatorIdByEventId(Long eventId); + + boolean existsByCategoryId(Long categoryId); + + boolean existsByIdAndState(Long eventId, EventState state); +} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/service/category/CategoryService.java b/core/event-service/src/main/java/ru/practicum/ewm/events/service/category/CategoryService.java similarity index 59% rename from core/main-service/src/main/java/ru/practicum/ewm/service/category/CategoryService.java rename to core/event-service/src/main/java/ru/practicum/ewm/events/service/category/CategoryService.java index c1843cf..714399a 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/service/category/CategoryService.java +++ b/core/event-service/src/main/java/ru/practicum/ewm/events/service/category/CategoryService.java @@ -1,23 +1,23 @@ -package ru.practicum.ewm.service.category; +package ru.practicum.ewm.events.service.category; import org.springframework.transaction.annotation.Transactional; -import ru.practicum.ewm.dto.category.CategoryDto; -import ru.practicum.ewm.dto.category.NewCategoryDto; +import ru.practicum.aggregation.dto.category.output.CategoryDto; +import ru.practicum.aggregation.dto.category.come.NewCategoryDto; import java.util.List; +@Transactional public interface CategoryService { - @Transactional CategoryDto adminAddNewCategory(NewCategoryDto newCategoryDto); + @Transactional(readOnly = true) List findAll(Integer from, Integer size); + @Transactional(readOnly = true) CategoryDto findById(Long catId); - @Transactional CategoryDto updateCategory(Long catId, CategoryDto categoryDto); - @Transactional void deleteCategory(Long catId); } diff --git a/core/main-service/src/main/java/ru/practicum/ewm/service/category/CategoryServiceImpl.java b/core/event-service/src/main/java/ru/practicum/ewm/events/service/category/CategoryServiceImpl.java similarity index 81% rename from core/main-service/src/main/java/ru/practicum/ewm/service/category/CategoryServiceImpl.java rename to core/event-service/src/main/java/ru/practicum/ewm/events/service/category/CategoryServiceImpl.java index 28e435e..b0919b0 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/service/category/CategoryServiceImpl.java +++ b/core/event-service/src/main/java/ru/practicum/ewm/events/service/category/CategoryServiceImpl.java @@ -1,4 +1,4 @@ -package ru.practicum.ewm.service.category; +package ru.practicum.ewm.events.service.category; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; @@ -7,14 +7,14 @@ import org.springframework.lang.NonNull; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import ru.practicum.ewm.dao.CategoryRepository; -import ru.practicum.ewm.dao.EventRepository; -import ru.practicum.ewm.dto.category.CategoryDto; -import ru.practicum.ewm.dto.category.NewCategoryDto; -import ru.practicum.ewm.mapper.CategoryMapper; -import ru.practicum.ewm.model.Category; -import ru.practicum.ewm.util.error.exception.ConflictException; -import ru.practicum.ewm.util.error.exception.NotFoundException; +import ru.practicum.aggregation.dto.category.output.CategoryDto; +import ru.practicum.aggregation.dto.category.come.NewCategoryDto; +import ru.practicum.aggregation.error.exception.bussines.cause.ConflictException; +import ru.practicum.aggregation.error.exception.bussines.cause.NotFoundException; +import ru.practicum.ewm.events.mapper.CategoryMapper; +import ru.practicum.ewm.events.entity.Category; +import ru.practicum.ewm.events.repository.CategoryRepository; +import ru.practicum.ewm.events.repository.EventRepository; import java.util.List; import java.util.stream.Collectors; @@ -84,4 +84,5 @@ private Category getCategoryById(long categoryId) { () -> new NotFoundException("Категория с id=" + categoryId + " не найдена") ); } + } diff --git a/core/event-service/src/main/java/ru/practicum/ewm/events/service/compilation/CompilationService.java b/core/event-service/src/main/java/ru/practicum/ewm/events/service/compilation/CompilationService.java new file mode 100644 index 0000000..217f2be --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/ewm/events/service/compilation/CompilationService.java @@ -0,0 +1,26 @@ +package ru.practicum.ewm.events.service.compilation; + +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.aggregation.dto.compilation.output.CompilationDto; +import ru.practicum.aggregation.dto.compilation.come.get.CompilationSearchFilter; +import ru.practicum.aggregation.dto.compilation.come.update.CompilationUpdateDto; +import ru.practicum.aggregation.dto.compilation.come.create.NewCompilationDto; + +import java.util.List; + +@Transactional +public interface CompilationService { + + @Transactional(readOnly = true) + CompilationDto getById(Long compilationId, HttpServletRequest request); + + void delById(Long compilationId); + + CompilationDto addCompilation(NewCompilationDto compilation); + + CompilationDto updateCompilation(Long compilationId, CompilationUpdateDto compilation); + + @Transactional(readOnly = true) + List getByFilter(CompilationSearchFilter filter, HttpServletRequest request); +} diff --git a/core/event-service/src/main/java/ru/practicum/ewm/events/service/compilation/CompilationServiceImpl.java b/core/event-service/src/main/java/ru/practicum/ewm/events/service/compilation/CompilationServiceImpl.java new file mode 100644 index 0000000..9ae7429 --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/ewm/events/service/compilation/CompilationServiceImpl.java @@ -0,0 +1,235 @@ +package ru.practicum.ewm.events.service.compilation; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Service; +import ru.practicum.aggregation.dto.compilation.come.create.NewCompilationDto; +import ru.practicum.aggregation.dto.compilation.come.get.CompilationSearchFilter; +import ru.practicum.aggregation.dto.compilation.come.update.CompilationUpdateDto; +import ru.practicum.aggregation.dto.compilation.output.CompilationDto; +import ru.practicum.aggregation.dto.user.output.UserShortDto; +import ru.practicum.aggregation.error.exception.bussines.cause.NotFoundException; +import ru.practicum.aggregation.model.entity.BaseEntity; +import ru.practicum.aggregation.model.repository.EventRequestCount; +import ru.practicum.aggregation.repository.RequestFeignRepository; +import ru.practicum.aggregation.repository.StatsFeignRepository; +import ru.practicum.aggregation.repository.UserFeignRepository; +import ru.practicum.ewm.events.entity.Compilation; +import ru.practicum.ewm.events.entity.Event; +import ru.practicum.ewm.events.mapper.CompilationMapper; +import ru.practicum.ewm.events.repository.CompilationRepository; +import ru.practicum.ewm.events.repository.EventRepository; +import ru.practicum.aggregation.model.data.StatsRequestData; +import ru.practicum.stat.dto.ViewStatsDto; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class CompilationServiceImpl implements CompilationService { + + EventRepository eventRepository; + CompilationRepository compilationRepository; + + StatsFeignRepository statsFeignRepository; + + UserFeignRepository userFeignRepository; + RequestFeignRepository requestFeignRepository; + + @Override + public CompilationDto getById(Long compilationId, HttpServletRequest request) { + statsFeignRepository.sendHitRequest(request); + + var compilation = getCompilationById(compilationId); + var confirmedRequests = getConfirmedRequests(List.of(compilation)); + var views = getViews(List.of(compilation)); + var events = compilation.getEvents(); + var eventIdToInitiator = eventIdToInitiatorMap(events); + + return CompilationMapper.toCompilationDto(compilation, eventIdToInitiator, confirmedRequests, views); + } + + @Override + public void delById(Long compilationId) { + getCompilationById(compilationId); + compilationRepository.deleteById(compilationId); + } + + @Override + public CompilationDto addCompilation(@NonNull NewCompilationDto compilationDto) { + Set events = new HashSet<>(); + + if (compilationDto.getEvents() != null && !compilationDto.getEvents().isEmpty()) { + events = new HashSet<>(eventRepository.findAllById(compilationDto.getEvents())); + + if (events.size() < compilationDto.getEvents().size()) { + throw new NotFoundException("Одно или несколько событий не найдены"); + } + } + + var savedCompilation = compilationRepository.save(CompilationMapper.toEntity(compilationDto, events)); + + Map confirmedRequests = new HashMap<>(); + savedCompilation.getEvents().forEach(event -> confirmedRequests.put(event.getId(), 0L)); + + Map views = new HashMap<>(); + savedCompilation.getEvents().forEach(event -> views.put(event.getId(), 0L)); + + var eventIdToInitiator = eventIdToInitiatorMap(events); + + return CompilationMapper.toCompilationDto( + savedCompilation, eventIdToInitiator, confirmedRequests, views); + } + + @Override + public CompilationDto updateCompilation(Long compilationId, + @NonNull CompilationUpdateDto compilationUpdateDto) { + var compilationInDb = getCompilationById(compilationId); + + if (compilationUpdateDto.getEvents() != null) { + List eventsUpdate = new ArrayList<>(); + + if (!compilationUpdateDto.getEvents().isEmpty()) { + eventsUpdate = eventRepository.findAllById(compilationUpdateDto.getEvents()); + if (eventsUpdate.size() < compilationUpdateDto.getEvents().size()) { + throw new NotFoundException("Одно или несколько событий не найдены"); + } + } + compilationInDb.setEvents(new HashSet<>(eventsUpdate)); + } + + if (compilationUpdateDto.getTitle() != null && !compilationUpdateDto.getTitle().isBlank()) { + compilationInDb.setTitle(compilationUpdateDto.getTitle()); + } + + if (compilationUpdateDto.getPinned() != null) { + compilationInDb.setPinned(compilationUpdateDto.getPinned()); + } + + var confirmedRequests = getConfirmedRequests(List.of(compilationInDb)); + var views = getViews(List.of(compilationInDb)); + var compilation = getCompilationById(compilationId); + var events = compilation.getEvents(); + var eventIdToInitiator = eventIdToInitiatorMap(events); + + return CompilationMapper.toCompilationDto( + compilationInDb, eventIdToInitiator, confirmedRequests, views); + } + + @NonNull + private Map eventIdToInitiatorMap(@NonNull Set events) { + var initiatorIds = events.stream().map(Event::getInitiatorId).toList(); + var initiatorMap = userFeignRepository.getUsersByIds(initiatorIds) + .stream() + .collect(Collectors.toMap(UserShortDto::id, Function.identity())); + + return events.stream() + .collect(Collectors.toMap(BaseEntity::getId, e -> + initiatorMap.get(e.getInitiatorId()) + ) + ); + } + + @Override + public List getByFilter(@NonNull CompilationSearchFilter filter, + HttpServletRequest request) { + var pageable = PageRequest.of(filter.getFrom() / filter.getSize(), filter.getSize()); + Page compilationsPage; + + if (filter.getPinned() != null) { + compilationsPage = compilationRepository.findAllByPinned(filter.getPinned(), pageable); + } else { + compilationsPage = compilationRepository.findAll(pageable); + } + + List compilations = compilationsPage.getContent(); + + if (compilations.isEmpty()) { + return Collections.emptyList(); + } + + Map allConfirmedRequests = getConfirmedRequests(compilations); + Map allViews = getViews(compilations); + + var result = new ArrayList(); + for (Compilation compilation : compilations) { + + var events = compilation.getEvents(); + var eventIdToInitiator = eventIdToInitiatorMap(events); + + result.add(CompilationMapper.toCompilationDto( + compilation, eventIdToInitiator, allConfirmedRequests, allViews)); + } + + return result; + } + + @NonNull + private Compilation getCompilationById(long compilationId) { + return compilationRepository.findById(compilationId).orElseThrow( + () -> new NotFoundException("Подборка с id=" + compilationId + " не найдена") + ); + } + + /// Map + @NonNull + private Map getConfirmedRequests(@NonNull Collection compilations) { + List events = compilations.stream() + .flatMap(c -> c.getEvents().stream()) + .toList(); + + if (events.isEmpty()) return Collections.emptyMap(); + + List eventRequestCountList = requestFeignRepository + .getConfirmedRequestsCount(events.stream().map(Event::getId).toList()); + + return eventRequestCountList.stream() + .collect(Collectors.toMap( + EventRequestCount::eventId, + EventRequestCount::count, + (existing, replacement) -> existing + ) + ); + } + + /// Map + @NonNull + private Map getViews(@NonNull Collection compilations) { + + // Все уникальные события + List allEvents = compilations.stream() + .flatMap(c -> c.getEvents().stream()) + .distinct() + .toList(); + + if (allEvents.isEmpty()) return Collections.emptyMap(); + + // Все URIs + List uris = allEvents.stream().map(e -> "/events/" + e.getId()).toList(); + + var statsOptional = statsFeignRepository.getStatList( + StatsRequestData.builder() + .uris(uris) + .unique(false) + .build() + ); + + // Map + return statsOptional.map(viewStatsDtos -> viewStatsDtos.stream() + .collect(Collectors.toMap(statsDto -> + Long.parseLong(statsDto.getUri().replace("/events/", "")), + ViewStatsDto::getHits, + (a, b) -> a + )) + ).orElse(Collections.emptyMap()); + } + +} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/service/event/EventService.java b/core/event-service/src/main/java/ru/practicum/ewm/events/service/event/EventService.java similarity index 60% rename from core/main-service/src/main/java/ru/practicum/ewm/service/event/EventService.java rename to core/event-service/src/main/java/ru/practicum/ewm/events/service/event/EventService.java index af8549f..eac622a 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/service/event/EventService.java +++ b/core/event-service/src/main/java/ru/practicum/ewm/events/service/event/EventService.java @@ -1,16 +1,23 @@ -package ru.practicum.ewm.service.event; +package ru.practicum.ewm.events.service.event; import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.constraints.Positive; import org.springframework.transaction.annotation.Transactional; -import ru.practicum.ewm.dto.event.*; +import ru.practicum.aggregation.dto.event.come.create.NewEventDto; +import ru.practicum.aggregation.dto.event.come.update.UpdateEventAdminRequest; +import ru.practicum.aggregation.dto.event.come.update.UpdateEventUserRequest; +import ru.practicum.aggregation.dto.event.output.EventFullDto; +import ru.practicum.aggregation.dto.event.output.EventShortDto; +import ru.practicum.aggregation.model.data.AdminGetData; +import ru.practicum.aggregation.model.data.FreeGetData; import java.util.List; -@Transactional +@Transactional(readOnly = true) public interface EventService { /// Получение событий незарегистрированным пользователем с возможностью фильтрации - List getFreeEvents(FreeGetDto freeGetDto, HttpServletRequest request); + List getFreeEvents(FreeGetData freeGetData, HttpServletRequest request); /** * Получение незарегистрированным пользователем подробной информации об опубликованном событии @@ -19,21 +26,28 @@ public interface EventService { EventFullDto getFreeEventById(Long eventId, HttpServletRequest request); /// Добавление нового события зарегистрированным пользователем + @Transactional EventFullDto userAddNewEvent(Long userId, NewEventDto newEventDto); /// Поиск событий администратором - @Transactional(readOnly = true) - List adminGetEvents(AdminGetDto adminGetDto); + List adminGetEvents(AdminGetData adminGetData); /// Редактирование администратором данных события и его статуса (отклонение/публикация) + @Transactional EventFullDto adminUpdateEvent(Long eventId, UpdateEventAdminRequest request); /// Получение событий, добавленных текущим пользователем List findByUserId(Long userId, Integer from, Integer size); /// Получение полной информации о событии добавленном текущим пользователем - EventFullDto findEventById(Long userId, Long eventId); + EventFullDto findEventByUserIdAndEventId(Long userId, Long eventId); + + /// Получение полной информации о событии по ID + EventFullDto findEventById(long eventId, long confirmets); /// Изменение события добавленного текущим пользователем + @Transactional EventFullDto patchEvent(Long userId, Long eventId, UpdateEventUserRequest request); + + long getInitiatorIfPublished(@Positive Long eventId); } diff --git a/core/event-service/src/main/java/ru/practicum/ewm/events/service/event/EventServiceImpl.java b/core/event-service/src/main/java/ru/practicum/ewm/events/service/event/EventServiceImpl.java new file mode 100644 index 0000000..0f1a1e5 --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/ewm/events/service/event/EventServiceImpl.java @@ -0,0 +1,602 @@ +package ru.practicum.ewm.events.service.event; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.ValidationException; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Service; +import ru.practicum.aggregation.dto.event.come.create.NewEventDto; +import ru.practicum.aggregation.dto.event.come.update.UpdateEventAdminRequest; +import ru.practicum.aggregation.dto.event.come.update.UpdateEventUserRequest; +import ru.practicum.aggregation.dto.event.output.EventFullDto; +import ru.practicum.aggregation.dto.event.output.EventShortDto; +import ru.practicum.aggregation.dto.user.output.UserDto; +import ru.practicum.aggregation.dto.user.output.UserShortDto; +import ru.practicum.aggregation.enums.AdminStateAction; +import ru.practicum.aggregation.enums.EventState; +import ru.practicum.aggregation.enums.UserStateAction; +import ru.practicum.aggregation.error.exception.bussines.cause.ConflictException; +import ru.practicum.aggregation.error.exception.bussines.cause.NotFoundException; +import ru.practicum.aggregation.model.data.AdminGetData; +import ru.practicum.aggregation.model.data.FreeGetData; +import ru.practicum.aggregation.model.data.StatsRequestData; +import ru.practicum.aggregation.model.repository.EventRequestCount; +import ru.practicum.aggregation.repository.RequestFeignRepositoryImpl; +import ru.practicum.aggregation.repository.StatsFeignRepository; +import ru.practicum.aggregation.repository.UserFeignRepositoryImpl; +import ru.practicum.ewm.events.entity.Category; +import ru.practicum.ewm.events.entity.Event; +import ru.practicum.ewm.events.mapper.EventMapper; +import ru.practicum.ewm.events.mapper.StateMapper; +import ru.practicum.ewm.events.mapper.UserMapper; +import ru.practicum.ewm.events.model.EventData; +import ru.practicum.ewm.events.repository.CategoryRepository; +import ru.practicum.ewm.events.repository.EventRepository; +import ru.practicum.ewm.events.specification.EventSpecifications; +import ru.practicum.ewm.events.specification.SpecBuilder; +import ru.practicum.stat.dto.ViewStatsDto; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class EventServiceImpl implements EventService { + + StatsFeignRepository statsFeignRepository; + EventRepository eventRepository; + CategoryRepository categoryRepository; + + UserFeignRepositoryImpl userFeignRepository; + RequestFeignRepositoryImpl requestFeignRepository; + + private static final String EVENTS_PATH = "/events/"; + private static final int HOURS_BEFORE_START = 2; + + @Override + public List getFreeEvents(@NonNull FreeGetData dto, HttpServletRequest request) { + + if (dto.rangeStart() != null && dto.rangeEnd() != null) { + if (dto.rangeEnd().isBefore(dto.rangeStart())) { + throw new ValidationException("Окончание события не может быть раньше начала"); + } + } + + SpecBuilder builder = SpecBuilder.builder() + .and(EventSpecifications.isPublished()) + .andIf(dto.text() != null && !dto.text().isBlank(), + () -> EventSpecifications.textContains(dto.text())) + .andIf(dto.categories() != null && !dto.categories().isEmpty(), + () -> EventSpecifications.hasCategories(dto.categories())) + .andIf(dto.paid() != null, + () -> EventSpecifications.isPaid(dto.paid())) + .andIf(Boolean.TRUE.equals(dto.onlyAvailable()), + () -> EventSpecifications.onlyAvailable(true)); + + boolean hasStart = dto.rangeStart() != null; + boolean hasEnd = dto.rangeEnd() != null; + + if (!hasStart && !hasEnd) { + builder.and(EventSpecifications.eventDateAfterNow(LocalDateTime.now())); + } else { + builder + .andIf(hasStart, + () -> EventSpecifications.dateAfter(dto.rangeStart())) + .andIf(hasEnd, + () -> EventSpecifications.dateBefore(dto.rangeEnd())); + } + + Specification spec = builder.build(); + + Sort sort = Sort.unsorted(); + + if (dto.sort() != null) { + switch (dto.sort()) { + case EVENT_DATE -> sort = Sort.by("eventDate").ascending(); + case VIEWS -> sort = Sort.by("views").descending(); + } + } + + Pageable pageable = PageRequest.of((dto.from() / dto.size()), dto.size(), sort); + + List events = eventRepository.findAll(spec, pageable).getContent(); + if (events.isEmpty()) { + return Collections.emptyList(); + } + + var confirmedRequestsCounts = fetchConfirmedRequestsCount( + events.stream().map(Event::getId).toList()); + + Map eventIdToRequestCount = getRequestCountMap(confirmedRequestsCounts); + + var statsData = StatsRequestData.builder() + .uris(getUris(events)) + .start(dto.rangeStart()) + .end(dto.rangeEnd()) + .unique(false) + .build(); + + var statsOptional = statsFeignRepository.getStatList(statsData); + + Map userIdToInitiator = getUserShortDtoMap(events); + + return events.stream() + .map(event -> { + var userId = event.getInitiatorId(); + var confirmets = eventIdToRequestCount.getOrDefault(userId, 0L); + AtomicLong views = new AtomicLong(); + statsOptional.ifPresentOrElse(stats -> + views.set(getViewsFromStatsList(stats, event.getId())), + () -> views.set(1L) + ); + var eventData = EventData.builder() + .initiator(userIdToInitiator.get(userId)) + .confirmedRequests(confirmets) + .views(views.get()) + .build(); + return EventMapper.toEventShortDto(event, eventData); + }) + .toList(); + } + + @Override + public EventFullDto getFreeEventById(Long eventId, @NonNull HttpServletRequest request) { + checkEvent(eventId); + + var event = getEventById(eventId); + var initiator = UserMapper.toUserShortDto(getUserById(event.getInitiatorId())); + var uris = List.of(request.getRequestURI()); + var statsOptional = statsFeignRepository.getStatList( + StatsRequestData.builder() + .uris(uris) + .unique(true) + .build() + ); + + long views = statsOptional.map(viewStatsDtos -> viewStatsDtos.stream() + .findFirst() + .map(ViewStatsDto::getHits) + .orElse(0L) + ) + .orElse(1L); + + var confirmedRequestsCount = getConfirmedRequestsCountByEventId(eventId); + + var eventData = EventData.builder() + .initiator(initiator) + .confirmedRequests(confirmedRequestsCount) + .views(views) + .build(); + + return EventMapper.toEventFullDto(event, eventData); + } + + @Override + public EventFullDto userAddNewEvent(Long userId, @NonNull NewEventDto newEventDto) { + if (newEventDto.participantLimit() != null && newEventDto.participantLimit() < 0) { + throw new ValidationException( + "Ограничение на количество участников должно быть положительным числом"); + } + + if (newEventDto.eventDate() != null + && newEventDto.eventDate().isBefore(LocalDateTime.now().plusHours(2))) { + throw new ValidationException( + "Начало события не может быть раньше, чем через два часа от текущего момента"); + } + + userFeignRepository.checkUser(userId); + + var category = getCategoryById(newEventDto.category()); + var event = eventRepository.save( + EventMapper.toEntity( + newEventDto, + category, + LocalDateTime.now(), + userId, + null, + EventState.PENDING + ) + ); + var initiator = UserMapper.toUserShortDto(getUserById(event.getInitiatorId())); + + var eventData = EventData.builder() + .initiator(initiator) + .confirmedRequests(0) + .views(0) + .build(); + + return EventMapper.toEventFullDto(event, eventData); + } + + @Override + public List adminGetEvents(@NonNull AdminGetData dto) { + Specification spec = SpecBuilder.builder() + .andIf(dto.users() != null && !dto.users().isEmpty(), + () -> EventSpecifications.hasUsers(dto.users())) + .andIf(dto.states() != null && !dto.states().isEmpty(), + () -> EventSpecifications.hasStates(dto.states())) + .andIf(dto.categories() != null && !dto.categories().isEmpty(), + () -> EventSpecifications.hasCategories(dto.categories())) + .andIf(dto.rangeStart() != null, + () -> EventSpecifications.dateAfter(dto.rangeStart())) + .andIf(dto.rangeEnd() != null, + () -> EventSpecifications.dateBefore(dto.rangeEnd())) + .build(); + + Pageable pageable = PageRequest.of( + dto.from() / dto.size(), + dto.size() + ); + + List events = eventRepository.findAll(spec, pageable).getContent(); + if (events.isEmpty()) { + return Collections.emptyList(); + } + + List confirmedRequestsCounts = fetchConfirmedRequestsCount( + events.stream().map(Event::getId).toList()); + + Map eventIdToRequestCount = getRequestCountMap(confirmedRequestsCounts); + + var statsData = StatsRequestData.builder() + .uris(getUris(events)) + .start(dto.rangeStart()) + .end(dto.rangeEnd()) + .unique(false) + .build(); + + var statsOptional = statsFeignRepository.getStatList(statsData); + + Map initiators = getUserShortDtoMap(events); + + return events.stream() + .map(event -> { + var userId = event.getInitiatorId(); + var count = getConfirmedRequestsCountFromMap(eventIdToRequestCount, event.getId()); + AtomicLong views = new AtomicLong(); + statsOptional.ifPresentOrElse(stats -> + views.set(getViewsFromStatsList(stats, event.getId())), + () -> views.set(1L) + ); + var eventData = EventData.builder() + .initiator(initiators.get(userId)) + .confirmedRequests(count) + .views(views.get()) + .build(); + return EventMapper.toEventFullDto(event, eventData); + }) + .toList(); + } + + @Override + public EventFullDto adminUpdateEvent(Long eventId, @NonNull UpdateEventAdminRequest request) { + Event oldEvent = getEventById(eventId); + Event newEvent; + + if (request.eventDate() != null && request.eventDate() + .isBefore(LocalDateTime.now().plusHours(HOURS_BEFORE_START))) { + throw new ValidationException( + "Дата события не может быть раньше, чем через два часа от текущего момента"); + } + + if (request.stateAction() == null) { + newEvent = EventMapper.update( + oldEvent, + request, + oldEvent.getState(), + oldEvent.getPublishedOn(), + request.category() == null ? + Optional.empty() : + Optional.of(getCategoryById(request.category())) + ); + } else { + + if (request.stateAction().equals(AdminStateAction.PUBLISH_EVENT) && + oldEvent.getState().equals(EventState.PUBLISHED)) { + throw new ConflictException("Событие с id=%s уже опубликовано" + .formatted(oldEvent.getId()) + ); + } + + if (request.stateAction().equals(AdminStateAction.PUBLISH_EVENT) && + oldEvent.getState().equals(EventState.CANCELED)) { + throw new ConflictException("Публикация события с id=%s уже отменена пользователем" + .formatted(oldEvent.getId()) + ); + } + + if (request.stateAction().equals(AdminStateAction.REJECT_EVENT) && + oldEvent.getState().equals(EventState.PUBLISHED)) { + throw new ConflictException("Событие с id=%s уже опубликовано, отмена не возможна" + .formatted(oldEvent.getId()) + ); + } + + newEvent = EventMapper.update( + oldEvent, + request, + request.stateAction().equals(AdminStateAction.REJECT_EVENT) + ? EventState.CANCELED : EventState.PUBLISHED, + request.stateAction().equals(AdminStateAction.REJECT_EVENT) + ? null : LocalDateTime.now(), + request.category() == null + ? Optional.empty() : Optional.of(getCategoryById(request.category())) + ); + } + + var event = eventRepository.save(newEvent); + + return EventMapper.toEventFullDto(event, getEventData(eventId, event.getInitiatorId())); + } + + @Override + public List findByUserId(Long userId, Integer from, Integer size) { + userFeignRepository.checkUser(userId); + + PageRequest pageRequest = PageRequest.of(from / size, size); + List events = eventRepository.findByInitiatorId(userId, pageRequest); + if (events.isEmpty()) { + return Collections.emptyList(); + } + + List confirmedRequestsCounts = fetchConfirmedRequestsCount( + events.stream().map(Event::getId).toList()); + + Map eventIdToRequestCount = getRequestCountMap(confirmedRequestsCounts); + + List uris = getUris(events); + var statsOptional = statsFeignRepository.getStatList( + StatsRequestData.builder() + .uris(uris) + .unique(true) + .build() + ); + + Map initiators = getUserShortDtoMap(events); + + return events.stream() + .map(event -> { + var count = getConfirmedRequestsCountFromMap(eventIdToRequestCount, event.getId()); + AtomicLong views = new AtomicLong(); + statsOptional.ifPresentOrElse(stats -> + views.set(getViewsFromStatsList(stats, event.getId())), + () -> views.set(1L) + ); + var eventData = EventData.builder() + .initiator(initiators.get(userId)) + .confirmedRequests(count) + .views(views.get()) + .build(); + return EventMapper.toEventShortDto(event, eventData); + }) + .toList(); + } + + @Override + public EventFullDto findEventByUserIdAndEventId(Long userId, Long eventId) { + userFeignRepository.checkUser(userId); + + Event event = getEventById(eventId); + var initiatorId = event.getInitiatorId(); + + if (!initiatorId.equals(userId)) { + throw new ConflictException("Пользователь должен быть инициатором"); + } + + return EventMapper.toEventFullDto(event, getEventData(eventId, initiatorId)); + } + + @Override + public EventFullDto findEventById(long eventId, long confirmets) { + Event event = getEventById(eventId); + var eventData = EventData.builder() + .initiator(UserMapper.toUserShortDto(getUserById(event.getInitiatorId()))) + .confirmedRequests(confirmets) + .views(getViews(event.getId())) + .build(); + + return EventMapper.toEventFullDto(event, eventData); + } + + @Override + public EventFullDto patchEvent(Long userId, Long eventId, @NonNull UpdateEventUserRequest request) { + if (request.participantLimit() != null && request.participantLimit() < 0) { + throw new ValidationException( + "Ограничение на количество участников должно быть положительным числом"); + } + + userFeignRepository.checkUser(userId); + return patchEvent(eventId, request, false); + } + + @Override + public long getInitiatorIfPublished(Long eventId) { + checkEvent(eventId); + long initiatorId = eventRepository.getInitiatorIdByEventId(eventId); + userFeignRepository.checkUser(initiatorId); + return initiatorId; + } + + private void checkEvent(Long eventId) { + if (!eventRepository.existsByIdAndState(eventId, EventState.PUBLISHED)) { + throw new NotFoundException( + "Событие с id=%s не существует или не опубликовано.".formatted(eventId)); + } + } + + @SuppressWarnings("SameParameterValue") + private EventFullDto patchEvent(Long eventId, + @NonNull UpdateEventUserRequest request, + boolean isAdmin) { + try { + Event event = getEventById(eventId); + + if (!isAdmin && event.getState() == EventState.PUBLISHED) { + throw new ConflictException("Нельзя редактировать опубликованное событие"); + } + + if (request.eventDate() != null) { + LocalDateTime eventDateTime = request.eventDate(); + LocalDateTime minDateTime = LocalDateTime.now().plusHours(HOURS_BEFORE_START); + + if (!isAdmin && eventDateTime.isBefore(minDateTime)) { + throw new ValidationException( + String.format( + "Дата события должна быть не ранее чем за %d часа(ов) до начала", + HOURS_BEFORE_START + ) + ); + } + } + + UserStateAction action = request.stateAction(); + + if (action != null) { + EventState newState = isAdmin + ? StateMapper.mapAdminEventAction(action) + : StateMapper.mapUserEventAction(action); + + if (EventState.PUBLISHED.equals(newState)) { + event.setPublishedOn(LocalDateTime.from(Instant.now())); + } + if (newState != null) { + event.setState(newState); + } + } + + if (request.category() != null) { + Category category = getCategoryById(request.category()); + event.setCategory(category); + } + + EventMapper.merge(event, request); + + Event patched = eventRepository.save(event); + + log.info("Событие обновлено: {}", patched.getId()); + + return EventMapper.toEventFullDto(event, getEventData(eventId, event.getInitiatorId())); + + } catch (DataIntegrityViolationException e) { + log.debug("Конфликт во время обновления события {}", request, e); + throw new ConflictException("Конфликт с другим событием"); + } + } + + private List fetchConfirmedRequestsCount(List events) { + return requestFeignRepository.getConfirmedRequestsCount(events); + } + + @NonNull + private List getUris(@NonNull List events) { + return events.stream().map(event -> EVENTS_PATH + event.getId()).toList(); + } + + private Map getRequestCountMap(@NonNull List eventRequestCountList) { + return eventRequestCountList.stream() + .collect(Collectors.toMap(EventRequestCount::eventId, EventRequestCount::count, + (a, b) -> a)); + } + + @NonNull + private Map getUserShortDtoMap(@NonNull List events) { + List userIds = events.stream().map(Event::getInitiatorId).toList(); + List users = userFeignRepository.getUsersByIds(userIds); + return users.stream() + .collect(Collectors.toMap(UserShortDto::id, user -> user)); + } + + private EventData getEventData(long eventId, long initiatorId) { + return EventData.builder() + .initiator(UserMapper.toUserShortDto(getUserById(initiatorId))) + .confirmedRequests(getConfirmedRequestsCountByEventId(eventId)) + .views(getViews(eventId)) + .build(); + } + + @NonNull + private UserDto getUserById(long userId) { + return userFeignRepository.getUserDtoById(userId); + } + + @NonNull + private Event getEventById(long eventId) { + return eventRepository.findById(eventId).orElseThrow( + () -> new NotFoundException("Событие с id=%s не найдено".formatted(eventId)) + ); + } + + @NonNull + private Category getCategoryById(long categoryId) { + return categoryRepository.findById(categoryId).orElseThrow( + () -> new NotFoundException("Категория с id=%s не найдена".formatted(categoryId)) + ); + } + + private long getConfirmedRequestsCountByEventId(long eventId) { + var counts = fetchConfirmedRequestsCount(List.of(eventId)); + if (counts.isEmpty()) { + return 0; + } + + return counts.getFirst().count(); + } + + private long getViews(long eventId) { + var uris = List.of(EVENTS_PATH + eventId); + var statsOptional = statsFeignRepository.getStatList( + StatsRequestData.builder() + .uris(uris) + .unique(true) + .build() + ); + + if (statsOptional.isEmpty()) { + return 1; + } + + List stats = statsOptional.get(); + + if (stats.isEmpty()) { + return 0; + } + return stats.getFirst().getHits(); + } + + private long getConfirmedRequestsCountFromMap(@NonNull + Map requestCountMap, + long eventId) { + if (requestCountMap.isEmpty()) { + return 0; + } + if (requestCountMap.containsKey(eventId)) { + return requestCountMap.get(eventId); + } + return 0; + } + + private long getViewsFromStatsList(@NonNull List viewStatsMap, long eventId) { + return viewStatsMap.stream() + .filter(statsDto -> statsDto.getUri().equals(EVENTS_PATH + eventId)) + .map(ViewStatsDto::getHits) + .findAny() + .orElse(0L); + } + +} diff --git a/core/event-service/src/main/java/ru/practicum/ewm/events/service/rating/EventRatingService.java b/core/event-service/src/main/java/ru/practicum/ewm/events/service/rating/EventRatingService.java new file mode 100644 index 0000000..aecad37 --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/ewm/events/service/rating/EventRatingService.java @@ -0,0 +1,10 @@ +package ru.practicum.ewm.events.service.rating; + +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.aggregation.dto.rating.come.update.RatingUpdateRequest; + +@Transactional +public interface EventRatingService { + + void updateRating(RatingUpdateRequest request); +} diff --git a/core/event-service/src/main/java/ru/practicum/ewm/events/service/rating/EventRatingServiceImpl.java b/core/event-service/src/main/java/ru/practicum/ewm/events/service/rating/EventRatingServiceImpl.java new file mode 100644 index 0000000..a31ca86 --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/ewm/events/service/rating/EventRatingServiceImpl.java @@ -0,0 +1,27 @@ +package ru.practicum.ewm.events.service.rating; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Service; +import ru.practicum.aggregation.dto.rating.come.update.RatingUpdateRequest; +import ru.practicum.aggregation.error.exception.bussines.cause.NotFoundException; +import ru.practicum.ewm.events.entity.Event; +import ru.practicum.ewm.events.repository.EventRepository; + +@Service +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class EventRatingServiceImpl implements EventRatingService { + + EventRepository eventRepository; + + @Override + public void updateRating(@NonNull RatingUpdateRequest request) { + Event event = eventRepository.findById(request.eventId()).orElseThrow(() -> + new NotFoundException("Событие с id=%s не найдено".formatted(request.eventId()))); + event.setRate(request.rate()); + eventRepository.save(event); + } +} diff --git a/core/event-service/src/main/java/ru/practicum/ewm/events/service/request/EventRequestService.java b/core/event-service/src/main/java/ru/practicum/ewm/events/service/request/EventRequestService.java new file mode 100644 index 0000000..09f9154 --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/ewm/events/service/request/EventRequestService.java @@ -0,0 +1,19 @@ +package ru.practicum.ewm.events.service.request; + +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.aggregation.dto.participation.come.EventRequestStatusUpdateRequest; +import ru.practicum.aggregation.dto.participation.output.EventRequestStatusUpdateResult; +import ru.practicum.aggregation.dto.participation.output.ParticipationRequestDto; + +import java.util.List; + +@Transactional +public interface EventRequestService { + + @Transactional(readOnly = true) + List findByEventId(long userId, long eventId); + + EventRequestStatusUpdateResult updateStatusRequest(long userId, + long eventId, + EventRequestStatusUpdateRequest status); +} diff --git a/core/event-service/src/main/java/ru/practicum/ewm/events/service/request/EventRequestServiceImpl.java b/core/event-service/src/main/java/ru/practicum/ewm/events/service/request/EventRequestServiceImpl.java new file mode 100644 index 0000000..c715bf3 --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/ewm/events/service/request/EventRequestServiceImpl.java @@ -0,0 +1,62 @@ +package ru.practicum.ewm.events.service.request; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import org.springframework.stereotype.Service; +import ru.practicum.aggregation.dto.participation.come.EventRequestStatusUpdateRequest; +import ru.practicum.aggregation.dto.participation.output.EventRequestStatusUpdateResult; +import ru.practicum.aggregation.dto.participation.output.ParticipationRequestDto; +import ru.practicum.aggregation.error.exception.bussines.cause.ConflictException; +import ru.practicum.aggregation.error.exception.bussines.cause.NotFoundException; +import ru.practicum.aggregation.repository.RequestFeignRepository; +import ru.practicum.aggregation.repository.UserFeignRepositoryImpl; +import ru.practicum.ewm.events.repository.EventRepository; + +import java.util.List; + +@Service +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class EventRequestServiceImpl implements EventRequestService { + + EventRepository eventRepository; + + RequestFeignRepository requestFeignRepository; + UserFeignRepositoryImpl userFeignRepository; + + @Override + public List findByEventId(long userId, long eventId) { + checkEvent(eventId); + userFeignRepository.checkUser(userId); + + return requestFeignRepository.findByUserIdAndEventId(userId, eventId); + } + + @Override + public EventRequestStatusUpdateResult updateStatusRequest(long userId, + long eventId, + EventRequestStatusUpdateRequest request + ) { + userFeignRepository.checkUser(userId); + var event = eventRepository.findById(eventId).orElseThrow( + () -> new NotFoundException("Событие с id=%s не найдено".formatted(eventId)) + ); + + if (!event.getInitiatorId().equals(userId)) { + throw new ConflictException("Пользователь с id=%s не является инициатором события с id=%s" + .formatted(eventId, userId) + ); + } + + return requestFeignRepository.updateStatusRequest( + eventId, event.getParticipantLimit(), event.isRequestModeration(), request); + } + + private void checkEvent(long eventId) { + if (!eventRepository.existsById(eventId)) { + throw new NotFoundException("Событие с id=%s не найдено".formatted(eventId)); + } + } + +} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/util/specification/EventSpecifications.java b/core/event-service/src/main/java/ru/practicum/ewm/events/specification/EventSpecifications.java similarity index 90% rename from core/main-service/src/main/java/ru/practicum/ewm/util/specification/EventSpecifications.java rename to core/event-service/src/main/java/ru/practicum/ewm/events/specification/EventSpecifications.java index fe01617..9687d4b 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/util/specification/EventSpecifications.java +++ b/core/event-service/src/main/java/ru/practicum/ewm/events/specification/EventSpecifications.java @@ -1,12 +1,12 @@ -package ru.practicum.ewm.util.specification; +package ru.practicum.ewm.events.specification; import jakarta.persistence.criteria.Join; import lombok.experimental.UtilityClass; import org.springframework.data.jpa.domain.Specification; -import ru.practicum.ewm.model.Category; -import ru.practicum.ewm.model.Event; -import ru.practicum.ewm.model.User; -import ru.practicum.ewm.model.enums.EventState; +import org.springframework.lang.NonNull; +import ru.practicum.aggregation.enums.EventState; +import ru.practicum.ewm.events.entity.Category; +import ru.practicum.ewm.events.entity.Event; import java.time.LocalDateTime; import java.util.List; @@ -106,18 +106,19 @@ public Specification eventDateAfterNow(LocalDateTime now) { } /// initiator + @NonNull public static Specification hasUsers(List userIds) { return (root, query, cb) -> { if (userIds == null || userIds.isEmpty()) { return null; } - Join userJoin = root.join("initiator"); - return userJoin.get("id").in(userIds); + return root.get("initiatorId").in(userIds); }; } /// По статусам + @NonNull public static Specification hasStates(List states) { return (root, query, cb) -> { if (states == null || states.isEmpty()) { @@ -127,4 +128,5 @@ public static Specification hasStates(List states) { return root.get("state").in(states); }; } + } \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/ewm/util/specification/SpecBuilder.java b/core/event-service/src/main/java/ru/practicum/ewm/events/specification/SpecBuilder.java similarity index 93% rename from core/main-service/src/main/java/ru/practicum/ewm/util/specification/SpecBuilder.java rename to core/event-service/src/main/java/ru/practicum/ewm/events/specification/SpecBuilder.java index 1ec02c1..e7d546f 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/util/specification/SpecBuilder.java +++ b/core/event-service/src/main/java/ru/practicum/ewm/events/specification/SpecBuilder.java @@ -1,4 +1,4 @@ -package ru.practicum.ewm.util.specification; +package ru.practicum.ewm.events.specification; import org.springframework.data.jpa.domain.Specification; @@ -33,4 +33,5 @@ public SpecBuilder and(Specification other) { public Specification build() { return spec; } + } diff --git a/core/main-service/src/main/resources/application-test.yaml b/core/event-service/src/main/resources/application-test.yaml.bak similarity index 100% rename from core/main-service/src/main/resources/application-test.yaml rename to core/event-service/src/main/resources/application-test.yaml.bak diff --git a/core/main-service/src/main/resources/application.yml b/core/event-service/src/main/resources/application.yml similarity index 97% rename from core/main-service/src/main/resources/application.yml rename to core/event-service/src/main/resources/application.yml index 8c9a66f..18d7df8 100644 --- a/core/main-service/src/main/resources/application.yml +++ b/core/event-service/src/main/resources/application.yml @@ -3,7 +3,7 @@ server: spring: application: - name: main-service + name: event-service config: import: "optional:configserver:" cloud: diff --git a/core/main-service/src/main/resources/schema-h2.sql b/core/event-service/src/main/resources/schema-h2.sql.bak similarity index 100% rename from core/main-service/src/main/resources/schema-h2.sql rename to core/event-service/src/main/resources/schema-h2.sql.bak diff --git a/core/event-service/src/main/resources/schema-postgres.sql b/core/event-service/src/main/resources/schema-postgres.sql new file mode 100644 index 0000000..1345dd0 --- /dev/null +++ b/core/event-service/src/main/resources/schema-postgres.sql @@ -0,0 +1,47 @@ +CREATE TABLE IF NOT EXISTS categories ( + id bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + name varchar(50) NOT NULL, + CONSTRAINT pk_categories PRIMARY KEY (id) +); +CREATE UNIQUE INDEX IF NOT EXISTS idx_categories_name ON categories (name); + +CREATE TABLE IF NOT EXISTS events ( + id bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + annotation varchar(2000) NOT NULL, + created_on timestamp(3) without time zone NOT NULL, + description varchar(7000) NOT NULL, + event_date timestamp(3) without time zone NOT NULL, + location jsonb, + paid boolean NOT NULL, + participant_limit integer NOT NULL, + published_on timestamp(3) without time zone, + request_moderation boolean NOT NULL, + state varchar(255) check (state in ('PENDING','PUBLISHED','CANCELED')) NOT NULL, + title varchar(120) NOT NULL, + category_id bigint, + initiator_id bigint, + rate bigint NOT NULL DEFAULT 0, + CONSTRAINT pk_events PRIMARY KEY (id), + CONSTRAINT fk_events_categories FOREIGN KEY (category_id) + REFERENCES categories (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE NO ACTION +); + +CREATE TABLE IF NOT EXISTS compilations ( + id bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + pinned boolean NOT NULL, + title varchar(50) NOT NULL, + CONSTRAINT pk_compilations PRIMARY KEY (id) +); + +CREATE TABLE IF NOT EXISTS compilation_events ( + compilations_id bigint NOT NULL, + events_id bigint NOT NULL, + CONSTRAINT fk_events_compilations FOREIGN KEY (compilations_id) + REFERENCES compilations (id) MATCH SIMPLE + ON DELETE CASCADE, + CONSTRAINT fk_compilations_events FOREIGN KEY (events_id) + REFERENCES events (id) MATCH SIMPLE + ON DELETE CASCADE +); \ No newline at end of file diff --git a/core/main-service/src/main/resources/schema-postgres.sql b/core/event-service/src/main/resources/schema-postgres.sql.bak similarity index 100% rename from core/main-service/src/main/resources/schema-postgres.sql rename to core/event-service/src/main/resources/schema-postgres.sql.bak diff --git a/core/main-service/src/main/java/ru/practicum/ewm/MainServiceApp.java b/core/main-service/src/main/java/ru/practicum/ewm/MainServiceApp.java deleted file mode 100644 index eab9033..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/MainServiceApp.java +++ /dev/null @@ -1,13 +0,0 @@ -package ru.practicum.ewm; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.ComponentScan; - -@SpringBootApplication -@ComponentScan(value = {"ru.practicum.ewm", "ru.practicum.stat.client"}) -public class MainServiceApp { - public static void main(String[] args) { - SpringApplication.run(MainServiceApp.class, args); - } -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminCompilationController.java b/core/main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminCompilationController.java deleted file mode 100644 index a68b957..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminCompilationController.java +++ /dev/null @@ -1,53 +0,0 @@ -package ru.practicum.ewm.controller.admin; - -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.*; -import ru.practicum.ewm.dto.compilation.CompilationDto; -import ru.practicum.ewm.dto.compilation.CompilationUpdateDto; -import ru.practicum.ewm.dto.compilation.NewCompilationDto; -import ru.practicum.ewm.service.compilation.CompilationService; - -@RestController -@RequestMapping(path = "/admin/compilations") -@RequiredArgsConstructor -public class AdminCompilationController { - - private final CompilationService compilationService; - - /** - * Добавление новой подборки (подборка может не содержать событий) - * - * @param newCompilationDto данные новой подборки - * @return {@link CompilationDto} - */ - @PostMapping - @ResponseStatus(HttpStatus.CREATED) - public CompilationDto createCompilation(@RequestBody @Valid NewCompilationDto newCompilationDto) { - return compilationService.addCompilation(newCompilationDto); - } - - /** - * - * @param compilationUpdateDto данные для обновления подборки - * @param compId id подборки - * @return {@link CompilationDto} - */ - @PatchMapping("/{compId}") - public CompilationDto updateCompilation(@RequestBody @Valid CompilationUpdateDto compilationUpdateDto, - @PathVariable Long compId) { - return compilationService.updateCompilation(compId, compilationUpdateDto); - } - - /** - * Удаление подборки - * - * @param compId id подборки - */ - @DeleteMapping("/{compId}") - @ResponseStatus(HttpStatus.NO_CONTENT) - public void delById(@PathVariable Long compId) { - compilationService.delById(compId); - } -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminEventController.java b/core/main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminEventController.java deleted file mode 100644 index 9aafe96..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminEventController.java +++ /dev/null @@ -1,82 +0,0 @@ -package ru.practicum.ewm.controller.admin; - -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import org.springframework.format.annotation.DateTimeFormat; -import org.springframework.web.bind.annotation.*; -import ru.practicum.ewm.dto.event.AdminGetDto; -import ru.practicum.ewm.dto.event.EventFullDto; -import ru.practicum.ewm.dto.event.UpdateEventAdminRequest; -import ru.practicum.ewm.model.enums.EventState; -import ru.practicum.ewm.service.event.EventService; - -import java.time.LocalDateTime; -import java.util.List; - -@RestController -@RequestMapping(path = "/admin/events") -@RequiredArgsConstructor -public class AdminEventController { - - private final EventService eventService; - - /** - * Поиск событий - *

- * Эндпоинт возвращает полную информацию обо всех событиях подходящих под переданные условия - * В случае, если по заданным фильтрам не найдено ни одного события, возвращает пустой список - * - * @param users список id пользователей, чьи события нужно найти - * @param states список состояний в которых находятся искомые события - * @param categories список id категорий в которых будет вестись поиск - * @param rangeStart дата и время не раньше которых должно произойти событие - * @param rangeEnd дата и время не позже которых должно произойти событие - * @param from количество событий, которые нужно пропустить для формирования текущего набора - * Default value : 0 - * @param size количество событий в наборе - * Default value : 10 - * @return List<{@link EventFullDto}> - */ - @GetMapping - public List adminGetEvents( - @RequestParam(required = false) List users, - @RequestParam(required = false) List states, - @RequestParam(required = false) List categories, - @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime rangeStart, - @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime rangeEnd, - @RequestParam(required = false, defaultValue = "0") Integer from, - @RequestParam(required = false, defaultValue = "10") Integer size) { - - AdminGetDto getDto = AdminGetDto.builder() - .users(users) - .states(states) - .categories(categories) - .rangeStart(rangeStart) - .rangeEnd(rangeEnd) - .from(from) - .size(size) - .build(); - - return eventService.adminGetEvents(getDto); - } - - /** - * Редактирование данных события и его статуса (отклонение/публикация) - *

- * Редактирование данных любого события администратором. Валидация данных не требуется. Обратите внимание: - * - дата начала изменяемого события должна быть не ранее чем за час от даты публикации. (Ожидается код ошибки 409) - * - событие можно публиковать, только если оно в состоянии ожидания публикации (Ожидается код ошибки 409) - * - событие можно отклонить, только если оно еще не опубликовано (Ожидается код ошибки 409) - * - * @param eventId id события - * @param request Данные HTTP-запроса - * @return {@link EventFullDto} - */ - @PatchMapping("/{eventId}") - public EventFullDto adminUpdateEvent( - @PathVariable Long eventId, - @RequestBody @Valid UpdateEventAdminRequest request) { - - return eventService.adminUpdateEvent(eventId, request); - } -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminUserController.java b/core/main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminUserController.java deleted file mode 100644 index cc5a305..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminUserController.java +++ /dev/null @@ -1,65 +0,0 @@ -package ru.practicum.ewm.controller.admin; - -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.*; -import ru.practicum.ewm.dto.user.NewUserRequest; -import ru.practicum.ewm.dto.user.UserDto; -import ru.practicum.ewm.service.user.UserService; - -import java.util.List; - -@RestController -@RequestMapping(path = "/admin/users") -@RequiredArgsConstructor -public class AdminUserController { - - private final UserService userService; - - /** - * Добавление нового пользователя - * - * @param newUserRequest Данные добавляемого пользователя - * @return {@link UserDto} - */ - @PostMapping - @ResponseStatus(HttpStatus.CREATED) - public UserDto addNewUser(@RequestBody @Valid NewUserRequest newUserRequest) { - return userService.adminAddNewUser(newUserRequest); - } - - /** - * Получение информации о пользователях - *

- * Возвращает информацию обо всех пользователях (учитываются параметры ограничения выборки), - * либо о конкретных (учитываются указанные идентификаторы) - * В случае, если по заданным фильтрам не найдено ни одного пользователя, возвращает пустой список - * - * @param ids id пользователей - * @param from количество элементов, которые нужно пропустить для формирования текущего набора - * Default value : 0 - * @param size количество элементов в наборе - * Default value : 10 - * @return List<{@link UserDto}> - */ - @GetMapping - public List getUsers( - @RequestParam(required = false) List ids, - @RequestParam(defaultValue = "0") int from, - @RequestParam(defaultValue = "10") int size - ) { - return userService.getUsers(ids, from, size); - } - - /** - * Удаление пользователя - * - * @param userId id пользователя - */ - @DeleteMapping("/{userId}") - @ResponseStatus(HttpStatus.NO_CONTENT) - public void deleteUser(@PathVariable Long userId) { - userService.deleteUser(userId); - } -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/controller/free/FreeCompilationController.java b/core/main-service/src/main/java/ru/practicum/ewm/controller/free/FreeCompilationController.java deleted file mode 100644 index 40450ac..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/controller/free/FreeCompilationController.java +++ /dev/null @@ -1,60 +0,0 @@ -package ru.practicum.ewm.controller.free; - -import jakarta.servlet.http.HttpServletRequest; -import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.*; -import ru.practicum.ewm.dto.compilation.CompilationDto; -import ru.practicum.ewm.dto.compilation.CompilationSearchFilter; -import ru.practicum.ewm.service.compilation.CompilationService; - -import java.util.List; - -@RestController -@RequestMapping(path = "/compilations") -@RequiredArgsConstructor -public class FreeCompilationController { - - private final CompilationService compilationService; - - /** - * Получение подборок событий - *

- * В случае, если по заданным фильтрам не найдено ни одной подборки, возвращает пустой список - * - * @param pinned искать только закрепленные/не закрепленные подборки - * @param from количество элементов, которые нужно пропустить для формирования текущего набора - * Default value : 0 - * @param size количество элементов в наборе - * Default value : 10 - * @param request Данные HTTP-запроса - * @return List<{@link CompilationDto}> - */ - @GetMapping - public List getCompilations( - @RequestParam(required = false) Boolean pinned, - @RequestParam(defaultValue = "0") Integer from, - @RequestParam(defaultValue = "10") Integer size, - HttpServletRequest request) { - - CompilationSearchFilter filter = CompilationSearchFilter.builder() - .pinned(pinned) - .from(from) - .size(size) - .build(); - - return compilationService.getByFilter(filter, request); - } - - /** - * Получение подборки событий по его id - * - * @param compId id подборки - * @param request Данные HTTP-запроса - * @return {@link CompilationDto} - */ - @GetMapping("/{compId}") - public CompilationDto getCompilationById(@PathVariable Long compId, - HttpServletRequest request) { - return compilationService.getById(compId, request); - } -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/controller/free/FreeEventController.java b/core/main-service/src/main/java/ru/practicum/ewm/controller/free/FreeEventController.java deleted file mode 100644 index 996383e..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/controller/free/FreeEventController.java +++ /dev/null @@ -1,89 +0,0 @@ -package ru.practicum.ewm.controller.free; - -import jakarta.servlet.http.HttpServletRequest; -import lombok.RequiredArgsConstructor; -import org.springframework.format.annotation.DateTimeFormat; -import org.springframework.web.bind.annotation.*; -import ru.practicum.ewm.dto.event.EventFullDto; -import ru.practicum.ewm.dto.event.EventShortDto; -import ru.practicum.ewm.dto.event.FreeGetDto; -import ru.practicum.ewm.service.event.EventService; - -import java.time.LocalDateTime; -import java.util.List; - -@RestController -@RequestMapping(path = "/events") -@RequiredArgsConstructor -public class FreeEventController { - - private final EventService eventService; - - /** - * Получение событий с возможностью фильтрации - *

- * Обратите внимание: - * - это публичный эндпоинт, соответственно в выдаче должны быть только опубликованные события - * - текстовый поиск (по аннотации и подробному описанию) должен быть без учета регистра букв - * - если в запросе не указан диапазон дат [rangeStart-rangeEnd], то нужно выгружать события, - * которые произойдут позже текущей даты и времени - * - информация о каждом событии должна включать в себя количество просмотров и количество - * уже одобренных заявок на участие - * - информацию о том, что по этому эндпоинту был осуществлен и обработан запрос, нужно - * сохранить в сервисе статистики - * В случае, если по заданным фильтрам не найдено ни одного события, возвращает пустой список - * - * @param text текст для поиска в содержимом аннотации и подробном описании события - * @param categories список идентификаторов категорий в которых будет вестись поиск - * @param paid поиск только платных/бесплатных событий - * @param rangeStart дата и время не раньше которых должно произойти событие - * @param rangeEnd дата и время не позже которых должно произойти событие - * @param onlyAvailable только события у которых не исчерпан лимит запросов на участие Default value : false - * @param sort Вариант сортировки: по дате события или по количеству просмотров Available values : EVENT_DATE, VIEWS - * @param from количество событий, которые нужно пропустить для формирования текущего набора Default value : 0 - * @param size количество событий в наборе Default value : 10 - * @param request Данные HTTP-запроса - * @return List<{@link EventShortDto}> - */ - @GetMapping - public List getFreeEvents( - @RequestParam(required = false) String text, - @RequestParam(required = false) List categories, - @RequestParam(required = false) Boolean paid, - @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime rangeStart, - @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime rangeEnd, - @RequestParam(required = false, defaultValue = "false") Boolean onlyAvailable, - @RequestParam(required = false) FreeGetDto.FreeEventSort sort, - @RequestParam(required = false, defaultValue = "0") Integer from, - @RequestParam(required = false, defaultValue = "10") Integer size, - HttpServletRequest request - ) { - - FreeGetDto freeGetDto = FreeGetDto.builder() - .text(text) - .categories(categories) - .paid(paid) - .rangeStart(rangeStart) - .rangeEnd(rangeEnd) - .onlyAvailable(onlyAvailable) - .sort(sort) - .from(from) - .size(size) - .build(); - - return eventService.getFreeEvents(freeGetDto, request); - } - - /** - * Получение подробной информации об опубликованном событии по его идентификатору. - * - * @param eventId id события - * @param request Данные HTTP-запроса - * @return {@link EventFullDto} - */ - @GetMapping(value = "/{eventId}") - public EventFullDto getFreeEventById(@PathVariable Long eventId, - HttpServletRequest request) { - return eventService.getFreeEventById(eventId, request); - } -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/controller/rating/RatingController.java b/core/main-service/src/main/java/ru/practicum/ewm/controller/rating/RatingController.java deleted file mode 100644 index 0940438..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/controller/rating/RatingController.java +++ /dev/null @@ -1,32 +0,0 @@ -package ru.practicum.ewm.controller.rating; - -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.*; -import ru.practicum.ewm.dto.rating.RatingRequest; -import ru.practicum.ewm.dto.rating.RatingResponse; -import ru.practicum.ewm.service.rating.RatingService; - -@RestController -@RequestMapping("/users/{userId}/events/{eventId}/likes") -@RequiredArgsConstructor -public class RatingController { - - private final RatingService ratingService; - - @PostMapping - @ResponseStatus(HttpStatus.CREATED) - public RatingResponse addReaction(@PathVariable Long userId, - @PathVariable Long eventId, - @Valid @RequestBody RatingRequest request) { - return ratingService.addOrUpdateReaction(userId, eventId, request); - } - - @DeleteMapping - @ResponseStatus(HttpStatus.NO_CONTENT) - public void removeReaction(@PathVariable Long userId, - @PathVariable Long eventId) { - ratingService.removeReaction(userId, eventId); - } -} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/ewm/dao/EventRepository.java b/core/main-service/src/main/java/ru/practicum/ewm/dao/EventRepository.java deleted file mode 100644 index 452a0bc..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/dao/EventRepository.java +++ /dev/null @@ -1,22 +0,0 @@ -package ru.practicum.ewm.dao; - -import org.springframework.data.domain.PageRequest; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; -import ru.practicum.ewm.model.Event; -import ru.practicum.ewm.model.enums.EventState; - -import java.util.Collection; -import java.util.Optional; - -public interface EventRepository extends JpaRepository, - JpaSpecificationExecutor { - - Collection findByInitiatorId(Long userId, PageRequest pageRequest); - - Optional findByIdAndState(Long eventId, EventState state); - - boolean existsByCategoryId(Long categoryId); - - boolean existsByIdAndState(Long eventId, EventState state); -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/dto/compilation/CompilationDto.java b/core/main-service/src/main/java/ru/practicum/ewm/dto/compilation/CompilationDto.java deleted file mode 100644 index 127b712..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/dto/compilation/CompilationDto.java +++ /dev/null @@ -1,20 +0,0 @@ -package ru.practicum.ewm.dto.compilation; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; -import ru.practicum.ewm.dto.event.EventShortDto; - -import java.util.List; - -@Data -@Builder -@AllArgsConstructor -@NoArgsConstructor -public class CompilationDto { - private Long id; - private boolean pinned; - private String title; - private List events; -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/dto/compilation/CompilationSearchFilter.java b/core/main-service/src/main/java/ru/practicum/ewm/dto/compilation/CompilationSearchFilter.java deleted file mode 100644 index 68cf761..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/dto/compilation/CompilationSearchFilter.java +++ /dev/null @@ -1,21 +0,0 @@ -package ru.practicum.ewm.dto.compilation; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class CompilationSearchFilter { - - Boolean pinned; - - @Builder.Default - Integer from = 0; - - @Builder.Default - Integer size = 10; -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/dto/event/AdminGetDto.java b/core/main-service/src/main/java/ru/practicum/ewm/dto/event/AdminGetDto.java deleted file mode 100644 index c165994..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/dto/event/AdminGetDto.java +++ /dev/null @@ -1,21 +0,0 @@ -package ru.practicum.ewm.dto.event; - -import jakarta.validation.constraints.NotNull; -import lombok.Builder; -import ru.practicum.ewm.model.enums.EventState; - -import java.time.LocalDateTime; -import java.util.List; - -@NotNull -@Builder -public record AdminGetDto( - List users, - List states, - List categories, - LocalDateTime rangeStart, - LocalDateTime rangeEnd, - Integer from, - Integer size -) { -} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/ewm/dto/event/EventFullDto.java b/core/main-service/src/main/java/ru/practicum/ewm/dto/event/EventFullDto.java deleted file mode 100644 index 7fc1da1..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/dto/event/EventFullDto.java +++ /dev/null @@ -1,56 +0,0 @@ -package ru.practicum.ewm.dto.event; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.Builder; -import ru.practicum.ewm.dto.category.CategoryDto; -import ru.practicum.ewm.dto.user.UserShortDto; -import ru.practicum.ewm.model.Location; -import ru.practicum.ewm.model.enums.EventState; - -import java.time.LocalDateTime; - -@Builder -public record EventFullDto( - - Long id, - - @NotBlank - String annotation, - - @NotNull - CategoryDto category, - - Long confirmedRequests, - - LocalDateTime createdOn, - - String description, - - @NotNull - LocalDateTime eventDate, - - @NotNull - UserShortDto initiator, - - @NotNull - Location location, - - boolean paid, - - Integer participantLimit, - - LocalDateTime publishedOn, - - boolean requestModeration, - - EventState state, - - @NotBlank - String title, - - Long views, - - long rate -) { -} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/ewm/dto/event/EventShortDto.java b/core/main-service/src/main/java/ru/practicum/ewm/dto/event/EventShortDto.java deleted file mode 100644 index 6f2be95..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/dto/event/EventShortDto.java +++ /dev/null @@ -1,22 +0,0 @@ -package ru.practicum.ewm.dto.event; - -import lombok.Builder; -import ru.practicum.ewm.dto.category.CategoryDto; -import ru.practicum.ewm.dto.user.UserShortDto; - -import java.time.LocalDateTime; - -@Builder -public record EventShortDto( - long id, - String annotation, - CategoryDto category, - long confirmedRequests, - LocalDateTime eventDate, - UserShortDto initiator, - boolean paid, - String title, - long views, - long rate -) { -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/dto/event/FreeGetDto.java b/core/main-service/src/main/java/ru/practicum/ewm/dto/event/FreeGetDto.java deleted file mode 100644 index 0343925..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/dto/event/FreeGetDto.java +++ /dev/null @@ -1,26 +0,0 @@ -package ru.practicum.ewm.dto.event; - -import jakarta.validation.constraints.NotNull; -import lombok.Builder; - -import java.time.LocalDateTime; -import java.util.List; - -@NotNull -@Builder -public record FreeGetDto( - String text, - List categories, - Boolean paid, - LocalDateTime rangeStart, - LocalDateTime rangeEnd, - Boolean onlyAvailable, - FreeEventSort sort, - Integer from, - Integer size -) { - - public enum FreeEventSort { - EVENT_DATE, VIEWS - } -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/dto/event/UpdateEventUserRequest.java b/core/main-service/src/main/java/ru/practicum/ewm/dto/event/UpdateEventUserRequest.java deleted file mode 100644 index 45aa833..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/dto/event/UpdateEventUserRequest.java +++ /dev/null @@ -1,34 +0,0 @@ -package ru.practicum.ewm.dto.event; - -import jakarta.validation.constraints.Size; -import ru.practicum.ewm.model.Location; -import ru.practicum.ewm.model.enums.UserStateAction; - -import java.time.LocalDateTime; - -public record UpdateEventUserRequest( - - @Size(min = 20, max = 2000) - String annotation, - - Long category, - - @Size(min = 20, max = 7000) - String description, - - LocalDateTime eventDate, - - Location location, - - Boolean paid, - - Integer participantLimit, - - Boolean requestModeration, - - @Size(min = 3, max = 120) - String title, - - UserStateAction stateAction -) { -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/dto/rating/RatingRequest.java b/core/main-service/src/main/java/ru/practicum/ewm/dto/rating/RatingRequest.java deleted file mode 100644 index 1a46e1a..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/dto/rating/RatingRequest.java +++ /dev/null @@ -1,18 +0,0 @@ -package ru.practicum.ewm.dto.rating; - -import jakarta.validation.constraints.NotNull; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; -import ru.practicum.ewm.model.enums.Reaction; - -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class RatingRequest { - - @NotNull - private Reaction reaction; -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/dto/rating/RatingResponse.java b/core/main-service/src/main/java/ru/practicum/ewm/dto/rating/RatingResponse.java deleted file mode 100644 index 3118cfd..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/dto/rating/RatingResponse.java +++ /dev/null @@ -1,18 +0,0 @@ -package ru.practicum.ewm.dto.rating; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; -import ru.practicum.ewm.model.enums.Reaction; - -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class RatingResponse { - private Long id; - private Long userId; - private Long eventId; - private Reaction reaction; -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/dto/request/EventRequestStatusUpdateRequest.java b/core/main-service/src/main/java/ru/practicum/ewm/dto/request/EventRequestStatusUpdateRequest.java deleted file mode 100644 index f2809f8..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/dto/request/EventRequestStatusUpdateRequest.java +++ /dev/null @@ -1,17 +0,0 @@ -package ru.practicum.ewm.dto.request; - -import jakarta.validation.constraints.NotNull; -import lombok.Builder; -import ru.practicum.ewm.model.enums.RequestUpdateStatus; - -import java.util.List; - -@Builder -public record EventRequestStatusUpdateRequest( - - List requestIds, - - @NotNull - RequestUpdateStatus status -) { -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/dto/request/EventRequestStatusUpdateResult.java b/core/main-service/src/main/java/ru/practicum/ewm/dto/request/EventRequestStatusUpdateResult.java deleted file mode 100644 index 42af240..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/dto/request/EventRequestStatusUpdateResult.java +++ /dev/null @@ -1,12 +0,0 @@ -package ru.practicum.ewm.dto.request; - -import lombok.Builder; - -import java.util.List; - -@Builder -public record EventRequestStatusUpdateResult( - List confirmedRequests, - List rejectedRequests -) { -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/dto/request/ParticipationRequestDto.java b/core/main-service/src/main/java/ru/practicum/ewm/dto/request/ParticipationRequestDto.java deleted file mode 100644 index 336e79d..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/dto/request/ParticipationRequestDto.java +++ /dev/null @@ -1,14 +0,0 @@ -package ru.practicum.ewm.dto.request; - -import lombok.Builder; -import ru.practicum.ewm.model.enums.ParticipationStatus; - -@Builder -public record ParticipationRequestDto( - Long id, - String created, - Long event, - Long requester, - ParticipationStatus status -) { -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/dto/user/UserDto.java b/core/main-service/src/main/java/ru/practicum/ewm/dto/user/UserDto.java deleted file mode 100644 index 80725ca..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/dto/user/UserDto.java +++ /dev/null @@ -1,11 +0,0 @@ -package ru.practicum.ewm.dto.user; - -import lombok.Builder; - -@Builder -public record UserDto( - Long id, - String name, - String email -) { -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/dto/user/UserShortDto.java b/core/main-service/src/main/java/ru/practicum/ewm/dto/user/UserShortDto.java deleted file mode 100644 index 7c8dc62..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/dto/user/UserShortDto.java +++ /dev/null @@ -1,23 +0,0 @@ -package ru.practicum.ewm.dto.user; - -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; -import lombok.Builder; - -@NotNull -@Builder -public record UserShortDto( - - Long id, - - @NotBlank - String name, - - @Email - @Size(min = 6, max = 255) - @NotBlank - String email -) { -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/mapper/RequestMapper.java b/core/main-service/src/main/java/ru/practicum/ewm/mapper/RequestMapper.java deleted file mode 100644 index dc76c23..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/mapper/RequestMapper.java +++ /dev/null @@ -1,31 +0,0 @@ -package ru.practicum.ewm.mapper; - -import org.springframework.stereotype.Component; -import ru.practicum.ewm.dto.request.ParticipationRequestDto; -import ru.practicum.ewm.model.ParticipationRequest; - -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; - - -@Component -public class RequestMapper { - - public static ParticipationRequestDto toParticipationRequestDto(ParticipationRequest participationRequest) { - if (participationRequest == null) return null; - return ParticipationRequestDto.builder() - .event(participationRequest.getEvent().getId()) - .requester(participationRequest.getRequester().getId()) - .status(participationRequest.getStatus()) - .created(formatDateTime(participationRequest.getCreated())) - .id(participationRequest.getId()) - .build(); - } - - private static String formatDateTime(LocalDateTime dateTime) { - if (dateTime == null) return null; - - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS"); - return dateTime.format(formatter); - } -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/model/enums/AdminStateAction.java b/core/main-service/src/main/java/ru/practicum/ewm/model/enums/AdminStateAction.java deleted file mode 100644 index 88f2fa6..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/model/enums/AdminStateAction.java +++ /dev/null @@ -1,5 +0,0 @@ -package ru.practicum.ewm.model.enums; - -public enum AdminStateAction { - PUBLISH_EVENT, REJECT_EVENT -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/model/enums/ParticipationStatus.java b/core/main-service/src/main/java/ru/practicum/ewm/model/enums/ParticipationStatus.java deleted file mode 100644 index b16afe2..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/model/enums/ParticipationStatus.java +++ /dev/null @@ -1,5 +0,0 @@ -package ru.practicum.ewm.model.enums; - -public enum ParticipationStatus { - PENDING, CONFIRMED, REJECTED, CANCELED -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/model/enums/Reaction.java b/core/main-service/src/main/java/ru/practicum/ewm/model/enums/Reaction.java deleted file mode 100644 index d379a2c..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/model/enums/Reaction.java +++ /dev/null @@ -1,5 +0,0 @@ -package ru.practicum.ewm.model.enums; - -public enum Reaction { - LIKE, DISLIKE -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/model/enums/RequestUpdateStatus.java b/core/main-service/src/main/java/ru/practicum/ewm/model/enums/RequestUpdateStatus.java deleted file mode 100644 index 37d71e0..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/model/enums/RequestUpdateStatus.java +++ /dev/null @@ -1,5 +0,0 @@ -package ru.practicum.ewm.model.enums; - -public enum RequestUpdateStatus { - CONFIRMED, REJECTED -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/model/enums/UserStateAction.java b/core/main-service/src/main/java/ru/practicum/ewm/model/enums/UserStateAction.java deleted file mode 100644 index ce9624b..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/model/enums/UserStateAction.java +++ /dev/null @@ -1,5 +0,0 @@ -package ru.practicum.ewm.model.enums; - -public enum UserStateAction { - SEND_TO_REVIEW, CANCEL_REVIEW, PUBLISH_EVENT, REJECT_EVENT -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/service/compilation/CompilationService.java b/core/main-service/src/main/java/ru/practicum/ewm/service/compilation/CompilationService.java deleted file mode 100644 index 8af94ca..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/service/compilation/CompilationService.java +++ /dev/null @@ -1,22 +0,0 @@ -package ru.practicum.ewm.service.compilation; - -import jakarta.servlet.http.HttpServletRequest; -import ru.practicum.ewm.dto.compilation.CompilationDto; -import ru.practicum.ewm.dto.compilation.CompilationSearchFilter; -import ru.practicum.ewm.dto.compilation.CompilationUpdateDto; -import ru.practicum.ewm.dto.compilation.NewCompilationDto; - -import java.util.List; - -public interface CompilationService { - - CompilationDto getById(Long compilationId, HttpServletRequest request); - - void delById(Long compilationId); - - CompilationDto addCompilation(NewCompilationDto compilation); - - CompilationDto updateCompilation(Long compilationId, CompilationUpdateDto compilation); - - List getByFilter(CompilationSearchFilter filter, HttpServletRequest request); -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/service/compilation/CompilationServiceImpl.java b/core/main-service/src/main/java/ru/practicum/ewm/service/compilation/CompilationServiceImpl.java deleted file mode 100644 index 90f4cff..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/service/compilation/CompilationServiceImpl.java +++ /dev/null @@ -1,206 +0,0 @@ -package ru.practicum.ewm.service.compilation; - -import jakarta.servlet.http.HttpServletRequest; -import lombok.AccessLevel; -import lombok.RequiredArgsConstructor; -import lombok.experimental.FieldDefaults; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.lang.NonNull; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import ru.practicum.ewm.dao.CompilationRepository; -import ru.practicum.ewm.dao.EventRepository; -import ru.practicum.ewm.dao.RequestRepository; -import ru.practicum.ewm.dto.compilation.CompilationDto; -import ru.practicum.ewm.dto.compilation.CompilationSearchFilter; -import ru.practicum.ewm.dto.compilation.CompilationUpdateDto; -import ru.practicum.ewm.dto.compilation.NewCompilationDto; -import ru.practicum.ewm.mapper.CompilationMapper; -import ru.practicum.ewm.model.Compilation; -import ru.practicum.ewm.model.Event; -import ru.practicum.ewm.model.enums.ParticipationStatus; -import ru.practicum.ewm.service.request.EventRequestCount; -import ru.practicum.ewm.util.error.exception.NotFoundException; -import ru.practicum.ewm.util.statistic.StatRepository; -import ru.practicum.stat.dto.ViewStatsDto; - -import java.util.*; -import java.util.stream.Collectors; - -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) -public class CompilationServiceImpl implements CompilationService { - - CompilationRepository compilationRepository; - EventRepository eventRepository; - StatRepository statRepository; - RequestRepository requestRepository; - - @Override - public CompilationDto getById(Long compilationId, HttpServletRequest request) { - statRepository.sendHitRequest(request); - - Compilation compilation = getCompilationById(compilationId); - - return CompilationMapper.toCompilationDto( - compilation, - getConfirmedRequests(List.of(compilation)), - getViews(List.of(compilation)) - ); - } - - @Override - @Transactional - public void delById(Long compilationId) { - getCompilationById(compilationId); - compilationRepository.deleteById(compilationId); - } - - @Override - @Transactional - public CompilationDto addCompilation(@NonNull NewCompilationDto compilationDto) { - Set events = new HashSet<>(); - - if (compilationDto.getEvents() != null && !compilationDto.getEvents().isEmpty()) { - events = new HashSet<>(eventRepository.findAllById(compilationDto.getEvents())); - - if (events.size() < compilationDto.getEvents().size()) { - throw new NotFoundException("Одно или несколько событий не найдены"); - } - } - - Compilation compilation = CompilationMapper.toEntity(compilationDto, events); - Compilation savedCompilation = compilationRepository.save(compilation); - - Map confirmedRequests = new HashMap<>(); - savedCompilation.getEvents().forEach(event -> confirmedRequests.put(event.getId(), 0L)); - - Map views = new HashMap<>(); - savedCompilation.getEvents().forEach(event -> views.put(event.getId(), 0L)); - - return CompilationMapper.toCompilationDto( - savedCompilation, - confirmedRequests, - views - ); - } - - @Override - @Transactional - public CompilationDto updateCompilation(Long compilationId, @NonNull CompilationUpdateDto compilationUpdateDto) { - Compilation compilationInDb = getCompilationById(compilationId); - - if (compilationUpdateDto.getEvents() != null) { - List eventsUpdate = new ArrayList<>(); - - if (!compilationUpdateDto.getEvents().isEmpty()) { - eventsUpdate = eventRepository.findAllById(compilationUpdateDto.getEvents()); - if (eventsUpdate.size() < compilationUpdateDto.getEvents().size()) { - throw new NotFoundException("Одно или несколько событий не найдены"); - } - } - compilationInDb.setEvents(new HashSet<>(eventsUpdate)); - - } - - if (compilationUpdateDto.getTitle() != null && !compilationUpdateDto.getTitle().isBlank()) { - compilationInDb.setTitle(compilationUpdateDto.getTitle()); - } - - if (compilationUpdateDto.getPinned() != null) { - compilationInDb.setPinned(compilationUpdateDto.getPinned()); - } - - return CompilationMapper.toCompilationDto( - compilationInDb, getConfirmedRequests(List.of(compilationInDb)), getViews(List.of(compilationInDb))); - } - - @Override - public List getByFilter(@NonNull CompilationSearchFilter filter, HttpServletRequest request) { - Pageable pageable = PageRequest.of(filter.getFrom() / filter.getSize(), filter.getSize()); - Page compilationsPage; - - if (filter.getPinned() != null) { - compilationsPage = compilationRepository.findAllByPinned(filter.getPinned(), pageable); - } else { - compilationsPage = compilationRepository.findAll(pageable); - } - - List compilations = compilationsPage.getContent(); - - if (compilations.isEmpty()) { - return Collections.emptyList(); - } - - Map allConfirmedRequests = getConfirmedRequests(compilations); - Map allViews = getViews(compilations); - - // Теперь маппим, просто подставляя готовые Map - return compilations.stream() - .map(compilation -> CompilationMapper.toCompilationDto( - compilation, - allConfirmedRequests, - allViews - )) - .toList(); - } - - @NonNull - private Compilation getCompilationById(long compilationId) { - return compilationRepository.findById(compilationId).orElseThrow( - () -> new NotFoundException("Подборка с id=" + compilationId + " не найдена") - ); - } - - /// Map - @NonNull - private Map getConfirmedRequests(Collection compilations) { - List events = compilations.stream() - .flatMap(c -> c.getEvents().stream()) - .toList(); - - if (events.isEmpty()) return Collections.emptyMap(); - - List eventRequestCountList = requestRepository.countConfirmedRequestsByEventIds( - events.stream().map(Event::getId).toList(), - ParticipationStatus.CONFIRMED); - - return eventRequestCountList.stream() - .collect(Collectors.toMap( - EventRequestCount::getEventId, - EventRequestCount::getCount, - (existing, replacement) -> existing - )); - } - - /// Map - @NonNull - private Map getViews(Collection compilations) { - // Все уникальные события - List allEvents = compilations.stream() - .flatMap(c -> c.getEvents().stream()) - .distinct() - .toList(); - - if (allEvents.isEmpty()) return Collections.emptyMap(); - - // Все URIs - List uris = allEvents.stream() - .map(e -> "/events/" + e.getId()) - .toList(); - - List stats = statRepository.getStat(uris, false); - - // Map - return stats.stream() - .collect(Collectors.toMap( - s -> Long.parseLong(s.getUri().replace("/events/", "")), - ViewStatsDto::getHits, - (a, b) -> a - )); - } -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/service/event/EventServiceImpl.java b/core/main-service/src/main/java/ru/practicum/ewm/service/event/EventServiceImpl.java deleted file mode 100644 index 038df7a..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/service/event/EventServiceImpl.java +++ /dev/null @@ -1,465 +0,0 @@ -package ru.practicum.ewm.service.event; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.validation.ValidationException; -import lombok.AccessLevel; -import lombok.RequiredArgsConstructor; -import lombok.experimental.FieldDefaults; -import lombok.extern.slf4j.Slf4j; -import org.springframework.dao.DataIntegrityViolationException; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.data.jpa.domain.Specification; -import org.springframework.lang.NonNull; -import org.springframework.stereotype.Service; -import ru.practicum.ewm.dao.CategoryRepository; -import ru.practicum.ewm.dao.EventRepository; -import ru.practicum.ewm.dao.RequestRepository; -import ru.practicum.ewm.dao.UserRepository; -import ru.practicum.ewm.dto.event.*; -import ru.practicum.ewm.mapper.EventMapper; -import ru.practicum.ewm.mapper.StateMapper; -import ru.practicum.ewm.model.Category; -import ru.practicum.ewm.model.Event; -import ru.practicum.ewm.model.User; -import ru.practicum.ewm.model.enums.AdminStateAction; -import ru.practicum.ewm.model.enums.EventState; -import ru.practicum.ewm.model.enums.ParticipationStatus; -import ru.practicum.ewm.model.enums.UserStateAction; -import ru.practicum.ewm.service.request.EventRequestCount; -import ru.practicum.ewm.util.error.exception.ConflictException; -import ru.practicum.ewm.util.error.exception.NotFoundException; -import ru.practicum.ewm.util.specification.EventSpecifications; -import ru.practicum.ewm.util.specification.SpecBuilder; -import ru.practicum.ewm.util.statistic.StatRepository; -import ru.practicum.stat.dto.ViewStatsDto; - -import java.time.Instant; -import java.time.LocalDateTime; -import java.util.*; - -@Slf4j -@Service -@RequiredArgsConstructor -@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) -public class EventServiceImpl implements EventService { - - StatRepository statRepository; - EventRepository eventRepository; - UserRepository userRepository; - CategoryRepository categoryRepository; - RequestRepository requestRepository; - - @Override - public List getFreeEvents(@NonNull FreeGetDto dto, HttpServletRequest request) { - - if (dto.rangeStart() != null && dto.rangeEnd() != null) { - if (dto.rangeEnd().isBefore(dto.rangeStart())) { - throw new ValidationException("Окончание события не может быть раньше начала"); - } - } - - statRepository.sendHitRequest(request); - - SpecBuilder builder = SpecBuilder.builder() - .and(EventSpecifications.isPublished()) - .andIf(dto.text() != null && !dto.text().isBlank(), - () -> EventSpecifications.textContains(dto.text())) - .andIf(dto.categories() != null && !dto.categories().isEmpty(), - () -> EventSpecifications.hasCategories(dto.categories())) - .andIf(dto.paid() != null, - () -> EventSpecifications.isPaid(dto.paid())) - .andIf(Boolean.TRUE.equals(dto.onlyAvailable()), - () -> EventSpecifications.onlyAvailable(true)); - - boolean hasStart = dto.rangeStart() != null; - boolean hasEnd = dto.rangeEnd() != null; - - if (!hasStart && !hasEnd) { - builder.and(EventSpecifications.eventDateAfterNow(LocalDateTime.now())); - } else { - builder - .andIf(hasStart, - () -> EventSpecifications.dateAfter(dto.rangeStart())) - .andIf(hasEnd, - () -> EventSpecifications.dateBefore(dto.rangeEnd())); - } - - Specification spec = builder.build(); - - Sort sort = Sort.unsorted(); - - if (dto.sort() != null) { - switch (dto.sort()) { - case EVENT_DATE -> sort = Sort.by("eventDate").ascending(); - case VIEWS -> sort = Sort.by("views").descending(); - } - } - - Pageable pageable = PageRequest.of( - dto.from() / dto.size(), - dto.size(), - sort - ); - - List events = eventRepository.findAll(spec, pageable).getContent(); - if (events.isEmpty()) { - return Collections.emptyList(); - } - - List eventRequestCountList = requestRepository.countConfirmedRequestsByEventIds( - events.stream().map(Event::getId).toList(), ParticipationStatus.CONFIRMED); - - Map requestCountMap = new HashMap<>(); - if (!eventRequestCountList.isEmpty()) { - eventRequestCountList.forEach(eventRequestCount -> { - requestCountMap.put(eventRequestCount.getEventId(), eventRequestCount.getCount()); - }); - } - - List uris = events.stream().map(event -> "/events/" + event.getId()).toList(); - List stats = statRepository.getStat( - uris, - dto.rangeStart(), - dto.rangeEnd(), - false); - - return events.stream() - .map(event -> EventMapper.toEventShortDto( - event, - requestCountMap.get(event.getId()) == null ? 0L : requestCountMap.get(event.getId()), - stats.size() - ) - ).toList(); - } - - @Override - public EventFullDto getFreeEventById(Long eventId, HttpServletRequest request) { - if (!eventRepository.existsByIdAndState(eventId, EventState.PUBLISHED)) { - throw new NotFoundException("Событие с id=" + eventId + " не существует или не опубликовано."); - } - statRepository.sendHitRequest(request); - Event event = getEventById(eventId); - - long views = statRepository.getStat(List.of(request.getRequestURI()), true).getFirst().getHits(); - - long confirmedRequests = getConfirmedRequests(eventId); - - return EventMapper.toEventFullDto(event, confirmedRequests, views); - } - - @Override - public EventFullDto userAddNewEvent(Long userId, @NonNull NewEventDto newEventDto) { - if (newEventDto.participantLimit() != null && newEventDto.participantLimit() < 0) { - throw new ValidationException("Ограничение на количество участников должно быть положительным числом"); - } - - if (newEventDto.eventDate() != null && newEventDto.eventDate().isBefore(LocalDateTime.now().plusHours(2))) { - throw new ValidationException("Начало события не может быть раньше, " + - "чем через два часа от текущего момента"); - } - - User initiator = getUserById(userId); - Category category = getCategoryById(newEventDto.category()); - - Event event = EventMapper.toEntity( - newEventDto, - category, - LocalDateTime.now(), - initiator, - null, - EventState.PENDING - ); - - return EventMapper.toEventFullDto(eventRepository.save(event), 0L, 0L); - } - - @Override - public List adminGetEvents(@NonNull AdminGetDto dto) { - Specification spec = SpecBuilder.builder() - .andIf(dto.users() != null && !dto.users().isEmpty(), - () -> EventSpecifications.hasUsers(dto.users())) - .andIf(dto.states() != null && !dto.states().isEmpty(), - () -> EventSpecifications.hasStates(dto.states())) - .andIf(dto.categories() != null && !dto.categories().isEmpty(), - () -> EventSpecifications.hasCategories(dto.categories())) - .andIf(dto.rangeStart() != null, - () -> EventSpecifications.dateAfter(dto.rangeStart())) - .andIf(dto.rangeEnd() != null, - () -> EventSpecifications.dateBefore(dto.rangeEnd())) - .build(); - - Pageable pageable = PageRequest.of( - dto.from() / dto.size(), - dto.size() - ); - - List events = eventRepository.findAll(spec, pageable).getContent(); - if (events.isEmpty()) { - return Collections.emptyList(); - } - - List eventRequestCountList = requestRepository.countConfirmedRequestsByEventIds( - events.stream().map(Event::getId).toList(), ParticipationStatus.CONFIRMED); - - Map requestCountMap = new HashMap<>(); - if (!eventRequestCountList.isEmpty()) { - eventRequestCountList.forEach(eventRequestCount -> - requestCountMap.put(eventRequestCount.getEventId(), eventRequestCount.getCount()) - ); - } - - List uris = events.stream().map(event -> "/events/" + event.getId()).toList(); - List stats = statRepository.getStat( - uris, - dto.rangeStart(), - dto.rangeEnd(), - false); - - return events.stream() - .map(event -> EventMapper.toEventFullDto( - event, - getConfirmedRequests(requestCountMap, event.getId()), - getHits(stats, event.getId()) - ) - ).toList(); - } - - @Override - public EventFullDto adminUpdateEvent(Long eventId, @NonNull UpdateEventAdminRequest request) { - Event oldEvent = getEventById(eventId); - Event newEvent; - - if (request.eventDate() != null && request.eventDate().isBefore(LocalDateTime.now().plusHours(2))) { - throw new ValidationException("Дата события должна быть не может быть раньше, " + - "чем через два часа от текущего момента"); - } - - if (request.stateAction() == null) { - newEvent = EventMapper.update( - oldEvent, - request, - oldEvent.getState(), - oldEvent.getPublishedOn(), - request.category() == null ? - Optional.empty() : - Optional.of(getCategoryById(request.category())) - ); - } else { - - if (request.stateAction().equals(AdminStateAction.PUBLISH_EVENT) && - oldEvent.getState().equals(EventState.PUBLISHED)) { - throw new ConflictException("Событие с id=" + oldEvent.getId() + " уже опубликовано"); - } - - if (request.stateAction().equals(AdminStateAction.PUBLISH_EVENT) && - oldEvent.getState().equals(EventState.CANCELED)) { - throw new ConflictException("Публикация события с id=" + oldEvent.getId() + - " уже отменена пользователем"); - } - - if (request.stateAction().equals(AdminStateAction.REJECT_EVENT) && - oldEvent.getState().equals(EventState.PUBLISHED)) { - throw new ConflictException("Событие с id=" + oldEvent.getId() + - " уже опубликовано, отмена не возможна"); - } - - newEvent = EventMapper.update( - oldEvent, - request, - request.stateAction().equals(AdminStateAction.REJECT_EVENT) ? - EventState.CANCELED : EventState.PUBLISHED, - request.stateAction().equals(AdminStateAction.REJECT_EVENT) ? - null : LocalDateTime.now(), - request.category() == null ? - Optional.empty() : - Optional.of(getCategoryById(request.category())) - ); - } - - return EventMapper.toEventFullDto( - eventRepository.save(newEvent), - getConfirmedRequests(oldEvent.getId()), - getHits(oldEvent.getId()) - ); - } - - @Override - public List findByUserId(Long userId, Integer from, Integer size) { - checkUser(userId); - - PageRequest pageRequest = PageRequest.of(from / size, size); - Collection events = eventRepository.findByInitiatorId(userId, pageRequest); - if (events.isEmpty()) { - return Collections.emptyList(); - } - - List eventRequestCountList = requestRepository.countConfirmedRequestsByEventIds( - events.stream().map(Event::getId).toList(), ParticipationStatus.CONFIRMED); - - Map requestCountMap = new HashMap<>(); - if (!eventRequestCountList.isEmpty()) { - eventRequestCountList.forEach(eventRequestCount -> - requestCountMap.put(eventRequestCount.getEventId(), eventRequestCount.getCount()) - ); - } - - List uris = events.stream().map(event -> "/events/" + event.getId()).toList(); - List stats = statRepository.getStat(uris, true); - - return events.stream() - .map(event -> EventMapper.toEventShortDto( - event, - getConfirmedRequests(requestCountMap, event.getId()), - getHits(stats, event.getId()) - ) - ).toList(); - } - - @Override - public EventFullDto findEventById(Long userId, Long eventId) { - checkUser(userId); - - Event event = getEventById(eventId); - - if (!event.getInitiator().getId().equals(userId)) { - throw new ConflictException("Пользователь должен быть инициатором"); - } - - return EventMapper.toEventFullDto( - event, - getConfirmedRequests(event.getId()), - getHits(event.getId()) - ); - } - - @Override - public EventFullDto patchEvent(Long userId, Long eventId, @NonNull UpdateEventUserRequest request) { - if (request.participantLimit() != null && request.participantLimit() < 0) { - throw new ValidationException("Ограничение на количество участников должно быть положительным числом"); - } - - checkUser(userId); - return patchEvent(eventId, request, 2, false); - } - - private EventFullDto patchEvent(Long eventId, @NonNull UpdateEventUserRequest request, long hoursBeforeStart, - boolean isAdmin) { - try { - Event event = getEventById(eventId); - - if (!isAdmin && event.getState() == EventState.PUBLISHED) { - throw new ConflictException("Нельзя редактировать опубликованное событие"); - } - - if (request.eventDate() != null) { - LocalDateTime eventDateTime = request.eventDate(); - LocalDateTime minDateTime = LocalDateTime.now().plusHours(hoursBeforeStart); - - if (!isAdmin && eventDateTime.isBefore(minDateTime)) { - throw new ValidationException( - String.format("Дата события должна быть не ранее чем за %d часа(ов) до начала", - hoursBeforeStart) - ); - } - } - - UserStateAction action = request.stateAction(); - - if (action != null) { - EventState newState = isAdmin - ? StateMapper.mapAdminEventAction(action) - : StateMapper.mapUserEventAction(action); - - if (EventState.PUBLISHED.equals(newState)) { - event.setPublishedOn(LocalDateTime.from(Instant.now())); - } - if (newState != null) { - event.setState(newState); - } - } - - if (request.category() != null) { - Category category = getCategoryById(request.category()); - event.setCategory(category); - } - - EventMapper.merge(event, request); - - Event patched = eventRepository.save(event); - - log.info("Ивент обновлен: {}", patched.getId()); - - return EventMapper.toEventFullDto( - event, - getConfirmedRequests(event.getId()), - getHits(event.getId()) - ); - - } catch (DataIntegrityViolationException e) { - log.debug("Конфликт вовремя обновления ивента {}", request, e); - throw new ConflictException("Конфликт с другим ивентом"); - } - } - - private void checkUser(Long userId) { - if (!userRepository.existsById(userId)) { - throw new NotFoundException("Пользователь с id=" + userId + " не найден"); - } - } - - @NonNull - private User getUserById(long userId) { - return userRepository.findById(userId) - .orElseThrow(() -> new NotFoundException("Пользователь с id=" + userId + " не найден")); - } - - @NonNull - private Event getEventById(long eventId) { - return eventRepository.findById(eventId).orElseThrow( - () -> new NotFoundException("Событие с id=" + eventId + " не найдено") - ); - } - - @NonNull - private Category getCategoryById(long categoryId) { - return categoryRepository.findById(categoryId).orElseThrow( - () -> new NotFoundException("Категория с id=" + categoryId + " не найдена") - ); - } - - private long getConfirmedRequests(Long eventId) { - return requestRepository.countByEventIdAndStatus(eventId, ParticipationStatus.CONFIRMED); - } - - private long getHits(long eventId) { - List stats = statRepository.getStat(List.of("/events/" + eventId), true); - if (stats.isEmpty()) { - return 0; - } - return stats.getFirst().getHits(); - } - - private long getHits(@NonNull List stats, long eventId) { - if (stats.isEmpty()) { - return 0; - } - for (ViewStatsDto stat : stats) { - if (stat.getUri().equals("/events/" + eventId)) { - return stat.getHits(); - } - } - return 0; - } - - private long getConfirmedRequests(@NonNull Map requestCountMap, long eventId) { - if (requestCountMap.isEmpty()) { - return 0; - } - if (requestCountMap.containsKey(eventId)) { - return requestCountMap.get(eventId); - } - return 0; - } -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/service/rating/RatingService.java b/core/main-service/src/main/java/ru/practicum/ewm/service/rating/RatingService.java deleted file mode 100644 index 27f61d9..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/service/rating/RatingService.java +++ /dev/null @@ -1,11 +0,0 @@ -package ru.practicum.ewm.service.rating; - -import ru.practicum.ewm.dto.rating.RatingRequest; -import ru.practicum.ewm.dto.rating.RatingResponse; - -public interface RatingService { - - RatingResponse addOrUpdateReaction(Long userId, Long eventId, RatingRequest request); - - void removeReaction(Long userId, Long eventId); -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/service/rating/RatingServiceImpl.java b/core/main-service/src/main/java/ru/practicum/ewm/service/rating/RatingServiceImpl.java deleted file mode 100644 index df506dc..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/service/rating/RatingServiceImpl.java +++ /dev/null @@ -1,94 +0,0 @@ -package ru.practicum.ewm.service.rating; - -import jakarta.validation.ValidationException; -import lombok.RequiredArgsConstructor; -import org.springframework.lang.NonNull; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import ru.practicum.ewm.dao.EventRepository; -import ru.practicum.ewm.dao.RatingRepository; -import ru.practicum.ewm.dao.UserRepository; -import ru.practicum.ewm.dto.rating.RatingRequest; -import ru.practicum.ewm.dto.rating.RatingResponse; -import ru.practicum.ewm.model.Event; -import ru.practicum.ewm.model.Rating; -import ru.practicum.ewm.model.User; -import ru.practicum.ewm.model.enums.EventState; -import ru.practicum.ewm.model.enums.Reaction; -import ru.practicum.ewm.util.error.exception.ConflictException; -import ru.practicum.ewm.util.error.exception.NotFoundException; - -import java.util.Objects; - -@Service -@RequiredArgsConstructor -@Transactional -public class RatingServiceImpl implements RatingService { - - private final RatingRepository ratingRepository; - private final UserRepository userRepository; - private final EventRepository eventRepository; - - @Override - public RatingResponse addOrUpdateReaction(Long userId, Long eventId, RatingRequest request) { - User user = userRepository.findById(userId) - .orElseThrow(() -> new NotFoundException("Пользователь с id=" + userId + " не найден")); - Event event = eventRepository.findByIdAndState(eventId, EventState.PUBLISHED) - .orElseThrow(() -> - new NotFoundException("Событие с id=" + eventId + " не существует или не опубликовано.") - ); - User initiator = event.getInitiator(); - if (Objects.equals(user.getId(), initiator.getId())) { - throw new ValidationException("Нельзя ставить реакции своим событиям"); - } - - Rating rating = ratingRepository.findByUserIdAndEventId(userId, eventId).orElse(null); - - if (rating != null) { - if (rating.getReaction() == request.getReaction()) { - ratingRepository.delete(rating); - updateEventRate(event); - throw new ConflictException("Reaction removed"); - } else { - rating.setReaction(request.getReaction()); - ratingRepository.save(rating); - updateEventRate(event); - return mapToResponse(rating); - } - } else { - rating = Rating.builder() - .user(user) - .event(event) - .reaction(request.getReaction()) - .build(); - ratingRepository.save(rating); - updateEventRate(event); - return mapToResponse(rating); - } - } - - @Override - public void removeReaction(Long userId, Long eventId) { - Rating rating = ratingRepository.findByUserIdAndEventId(userId, eventId) - .orElseThrow(() -> new NotFoundException("Реакция не найдена")); - ratingRepository.delete(rating); - Event event = rating.getEvent(); - updateEventRate(event); - } - - private void updateEventRate(@NonNull Event event) { - long likes = ratingRepository.countByEventIdAndReaction(event.getId(), Reaction.LIKE); - long dislikes = ratingRepository.countByEventIdAndReaction(event.getId(), Reaction.DISLIKE); - event.setRate(likes - dislikes); - eventRepository.save(event); - } - - private RatingResponse mapToResponse(@NonNull Rating rating) { - return RatingResponse.builder() - .id(rating.getId()) - .userId(rating.getUser().getId()) - .eventId(rating.getEvent().getId()) - .reaction(rating.getReaction()) - .build(); - } -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/service/request/EventRequestCount.java b/core/main-service/src/main/java/ru/practicum/ewm/service/request/EventRequestCount.java deleted file mode 100644 index a7d0684..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/service/request/EventRequestCount.java +++ /dev/null @@ -1,9 +0,0 @@ -package ru.practicum.ewm.service.request; - - -public interface EventRequestCount { - - Long getEventId(); - - Long getCount(); -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/service/request/RequestService.java b/core/main-service/src/main/java/ru/practicum/ewm/service/request/RequestService.java deleted file mode 100644 index 50cff58..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/service/request/RequestService.java +++ /dev/null @@ -1,23 +0,0 @@ -package ru.practicum.ewm.service.request; - -import org.springframework.transaction.annotation.Transactional; -import ru.practicum.ewm.dto.request.EventRequestStatusUpdateRequest; -import ru.practicum.ewm.dto.request.EventRequestStatusUpdateResult; -import ru.practicum.ewm.dto.request.ParticipationRequestDto; - -import java.util.List; - -@Transactional -public interface RequestService { - - List findByEventId(Long userId, Long eventId); - - EventRequestStatusUpdateResult updateStatusRequest(Long userId, Long eventId, - EventRequestStatusUpdateRequest request); - - List findByRequesterId(Long userId); - - ParticipationRequestDto addParticipationRequest(Long userId, Long eventId); - - ParticipationRequestDto cancelParticipationRequest(Long userId, Long requestId); -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/service/request/RequestServiceImpl.java b/core/main-service/src/main/java/ru/practicum/ewm/service/request/RequestServiceImpl.java deleted file mode 100644 index bed101c..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/service/request/RequestServiceImpl.java +++ /dev/null @@ -1,198 +0,0 @@ -package ru.practicum.ewm.service.request; - -import lombok.AccessLevel; -import lombok.RequiredArgsConstructor; -import lombok.experimental.FieldDefaults; -import lombok.extern.slf4j.Slf4j; -import org.springframework.lang.NonNull; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import ru.practicum.ewm.dao.EventRepository; -import ru.practicum.ewm.dao.RequestRepository; -import ru.practicum.ewm.dao.UserRepository; -import ru.practicum.ewm.dto.request.EventRequestStatusUpdateRequest; -import ru.practicum.ewm.dto.request.EventRequestStatusUpdateResult; -import ru.practicum.ewm.dto.request.ParticipationRequestDto; -import ru.practicum.ewm.mapper.RequestMapper; -import ru.practicum.ewm.model.Event; -import ru.practicum.ewm.model.ParticipationRequest; -import ru.practicum.ewm.model.User; -import ru.practicum.ewm.model.enums.EventState; -import ru.practicum.ewm.model.enums.ParticipationStatus; -import ru.practicum.ewm.util.error.exception.ConflictException; -import ru.practicum.ewm.util.error.exception.NotFoundException; - -import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -@Slf4j -@Service -@RequiredArgsConstructor -@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) -public class RequestServiceImpl implements RequestService { - - RequestRepository requestRepository; - UserRepository userRepository; - EventRepository eventRepository; - - public List findByEventId(Long userId, Long eventId) { - Event event = getEventById(eventId); - if (!event.getInitiator().getId().equals(userId)) { - throw new NotFoundException("Событие не найдено"); - } - - return requestRepository.findByEventId(eventId) - .stream() - .map(RequestMapper::toParticipationRequestDto) - .toList(); - } - - public EventRequestStatusUpdateResult updateStatusRequest(Long userId, Long eventId, - EventRequestStatusUpdateRequest request) { - Event event = getEventById(eventId); - if (!event.getInitiator().getId().equals(userId)) { - throw new NotFoundException("Событие не найдено"); - } - - int limit = event.getParticipantLimit(); - List confirmedRequests = new ArrayList<>(); - List rejectedRequests = new ArrayList<>(); - - boolean isModerationOff = !event.isRequestModeration() || limit == 0; - boolean idsEmpty = request.requestIds() == null || request.requestIds().isEmpty(); - - if (isModerationOff || idsEmpty) { - return EventRequestStatusUpdateResult.builder() - .confirmedRequests(Collections.emptyList()) - .rejectedRequests(Collections.emptyList()) - .build(); - } - - int countConfirmed = requestRepository.countByEventIdAndStatus(eventId, ParticipationStatus.CONFIRMED); - List requests = requestRepository.findAllByIdIn(request.requestIds()); - - if (request.status().name().equals(ParticipationStatus.CONFIRMED.name()) && countConfirmed >= limit) { - throw new ConflictException("Достигнут лимит подтвержденных заявок"); - } - - for (ParticipationRequest pr : requests) { - if (!pr.getStatus().equals(ParticipationStatus.PENDING)) { - throw new ConflictException("Статус можно изменить только у заявок в состоянии рассмотрения"); - } - - if (request.status().name().equals(ParticipationStatus.CONFIRMED.name()) && countConfirmed < limit) { - pr.setStatus(ParticipationStatus.CONFIRMED); - countConfirmed++; - confirmedRequests.add(RequestMapper.toParticipationRequestDto(pr)); - } else { - pr.setStatus(ParticipationStatus.REJECTED); - rejectedRequests.add(RequestMapper.toParticipationRequestDto(pr)); - } - } - - requestRepository.saveAll(requests); - - // если в процессе лимит превышен - отклоняем все оставшиеся заявки - if (request.status().name().equals(ParticipationStatus.CONFIRMED.name()) && countConfirmed >= limit) { - if (requestRepository.rejectPendingRequests(eventId, ParticipationStatus.PENDING) < 0) { - throw new RuntimeException("Не удалось отклонить заявку"); - } - } - - return EventRequestStatusUpdateResult.builder() - .confirmedRequests(confirmedRequests) - .rejectedRequests(rejectedRequests) - .build(); - } - - public List findByRequesterId(Long userId) { - return requestRepository.findByRequesterId(userId) - .stream() - .map(RequestMapper::toParticipationRequestDto) - .toList(); - } - - @Transactional - public ParticipationRequestDto addParticipationRequest(Long userId, Long eventId) { - User requester = getUserById(userId); - Event event = getEventById(eventId); - - if (!EventState.PUBLISHED.equals(event.getState())) { - throw new ConflictException("Нельзя участвовать в неопубликованном событии"); - } - - if (requestRepository.existsByRequesterIdAndEventId(userId, eventId)) { - throw new ConflictException("Запрос уже существует"); - } - - if (event.getInitiator().getId().equals(userId)) { - throw new ConflictException("Инициатор события не может добавить запрос на участие в своём событии"); - } - - int limit = event.getParticipantLimit(); - if (limit != 0) { - long confirmedCount = requestRepository.countByEventIdAndStatus(eventId, ParticipationStatus.CONFIRMED); - - if (event.isRequestModeration()) { - long pendingCount = requestRepository.countByEventIdAndStatus(eventId, ParticipationStatus.PENDING); - if (confirmedCount + pendingCount >= limit) { - throw new ConflictException("Достигнут лимит запросов на участие"); - } - } else { - if (confirmedCount >= limit) { - throw new ConflictException("Достигнут лимит запросов на участие"); - } - } - } - - ParticipationStatus status; - - if (!event.isRequestModeration() || limit == 0) { - status = ParticipationStatus.CONFIRMED; - } else { - status = ParticipationStatus.PENDING; - } - - ParticipationRequest request = ParticipationRequest.builder() - .requester(requester) - .event(event) - .status(status) - .created(LocalDateTime.now().truncatedTo(ChronoUnit.MILLIS)) - .build(); - - return RequestMapper.toParticipationRequestDto(requestRepository.save(request)); - } - - public ParticipationRequestDto cancelParticipationRequest(Long userId, Long requestId) { - ParticipationRequest request = getRequestById(requestId); - - if (!request.getRequester().getId().equals(userId)) { - throw new ConflictException("Нельзя отменить чужую заявку"); - } - request.setStatus(ParticipationStatus.CANCELED); - return RequestMapper.toParticipationRequestDto(requestRepository.save(request)); - } - - @NonNull - private User getUserById(long userId) { - return userRepository.findById(userId) - .orElseThrow(() -> new NotFoundException("Пользователь с id=" + userId + " не найден")); - } - - @NonNull - private Event getEventById(long eventId) { - return eventRepository.findById(eventId).orElseThrow( - () -> new NotFoundException("Событие с id=" + eventId + " не найдено") - ); - } - - @NonNull - private ParticipationRequest getRequestById(Long requestId) { - return requestRepository.findById(requestId).orElseThrow( - () -> new NotFoundException("Заявка с id=" + requestId + " не найдена") - ); - } -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/service/user/UserService.java b/core/main-service/src/main/java/ru/practicum/ewm/service/user/UserService.java deleted file mode 100644 index d10ee41..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/service/user/UserService.java +++ /dev/null @@ -1,18 +0,0 @@ -package ru.practicum.ewm.service.user; - -import jakarta.transaction.Transactional; -import ru.practicum.ewm.dto.user.NewUserRequest; -import ru.practicum.ewm.dto.user.UserDto; - -import java.util.List; - -public interface UserService { - - @Transactional - UserDto adminAddNewUser(NewUserRequest newUserRequest); - - List getUsers(List ids, int from, int size); - - @Transactional - void deleteUser(Long userId); -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/service/user/UserServiceImpl.java b/core/main-service/src/main/java/ru/practicum/ewm/service/user/UserServiceImpl.java deleted file mode 100644 index 31cd7cd..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/service/user/UserServiceImpl.java +++ /dev/null @@ -1,60 +0,0 @@ -package ru.practicum.ewm.service.user; - -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.PageRequest; -import org.springframework.lang.NonNull; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import ru.practicum.ewm.dao.UserRepository; -import ru.practicum.ewm.dto.user.NewUserRequest; -import ru.practicum.ewm.dto.user.UserDto; -import ru.practicum.ewm.mapper.UserMapper; -import ru.practicum.ewm.model.User; -import ru.practicum.ewm.util.error.exception.ConflictException; -import ru.practicum.ewm.util.error.exception.NotFoundException; - -import java.util.List; -import java.util.stream.Collectors; - -@Service -@RequiredArgsConstructor -public class UserServiceImpl implements UserService { - - private final UserRepository userRepository; - - @Override - public UserDto adminAddNewUser(@NonNull NewUserRequest newUserRequest) { - if (userRepository.existsByEmail(newUserRequest.email())) { - throw new ConflictException("Пользователь с такой почтой " + newUserRequest.email() + " уже существует"); - } - User user = UserMapper.toEntity(newUserRequest); - return UserMapper.toUserDto(userRepository.save(user)); - } - - @Override - public List getUsers(List ids, int from, int size) { - PageRequest page = PageRequest.of(from / size, size); - List users; - if (ids == null || ids.isEmpty()) { - users = userRepository.findAll(page).getContent(); - } else { - users = userRepository.findAllByIdIn(ids, page); - } - return users.stream() - .map(UserMapper::toUserDto) - .collect(Collectors.toList()); - } - - @Override - @Transactional - public void deleteUser(Long userId) { - checkUser(userId); - userRepository.deleteById(userId); - } - - private void checkUser(Long userId) { - if (!userRepository.existsById(userId)) { - throw new NotFoundException("Пользователь с id=" + userId + " не найден"); - } - } -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/util/error/exception/ConflictException.java b/core/main-service/src/main/java/ru/practicum/ewm/util/error/exception/ConflictException.java deleted file mode 100644 index 8848991..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/util/error/exception/ConflictException.java +++ /dev/null @@ -1,8 +0,0 @@ -package ru.practicum.ewm.util.error.exception; - -public class ConflictException extends RuntimeException { - - public ConflictException(String message) { - super(message); - } -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/util/error/exception/NotFoundException.java b/core/main-service/src/main/java/ru/practicum/ewm/util/error/exception/NotFoundException.java deleted file mode 100644 index a9d3922..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/util/error/exception/NotFoundException.java +++ /dev/null @@ -1,8 +0,0 @@ -package ru.practicum.ewm.util.error.exception; - -public class NotFoundException extends RuntimeException { - - public NotFoundException(String message) { - super(message); - } -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/util/error/exception/StatResponseException.java b/core/main-service/src/main/java/ru/practicum/ewm/util/error/exception/StatResponseException.java deleted file mode 100644 index f2b13cb..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/util/error/exception/StatResponseException.java +++ /dev/null @@ -1,14 +0,0 @@ -package ru.practicum.ewm.util.error.exception; - -public class StatResponseException extends RuntimeException { - - private static final String MESSAGE = "Не удалось получить статистику"; - - public StatResponseException(Exception ex) { - super(MESSAGE, ex); - } - - public StatResponseException() { - super(MESSAGE); - } -} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/util/statistic/StatRepository.java b/core/main-service/src/main/java/ru/practicum/ewm/util/statistic/StatRepository.java deleted file mode 100644 index 0f9d137..0000000 --- a/core/main-service/src/main/java/ru/practicum/ewm/util/statistic/StatRepository.java +++ /dev/null @@ -1,85 +0,0 @@ -package ru.practicum.ewm.util.statistic; - -import jakarta.servlet.http.HttpServletRequest; -import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; -import ru.practicum.ewm.util.error.exception.HitRequestException; -import ru.practicum.ewm.util.error.exception.StatResponseException; -import ru.practicum.stat.client.StatClient; -import ru.practicum.stat.dto.EndpointHitDto; -import ru.practicum.stat.dto.StatsRequest; -import ru.practicum.stat.dto.ViewStatsDto; - -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.time.temporal.ChronoUnit; -import java.util.List; - -@Component -@RequiredArgsConstructor -public class StatRepository { - - private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); - private final LocalDateTime startUnixEpoch = LocalDateTime.parse("1970-01-01T00:00:00"); - - @Value("${spring.application.name}") - private String appName; - - private final StatClient statClient; - - public void sendHitRequest(HttpServletRequest request) { - try { - statClient.hit(EndpointHitDto.builder() - .ip(request.getRemoteAddr()) - .uri(request.getRequestURI()) - .app(appName) - .timestamp(LocalDateTime.now()) - .build() - ); - } catch (Exception ex) { - throw new HitRequestException(ex); - } - } - - public List getStat(List statUris, - LocalDateTime rangeStart, - LocalDateTime rangeEnd, - boolean uniqe) { - if (statUris == null || statUris.isEmpty()) { - throw new StatResponseException(); - } - - if (rangeStart == null) { - rangeStart = startUnixEpoch; - } - - if (rangeEnd == null) { - rangeEnd = LocalDateTime.now(); - } - - StatsRequest request = StatsRequest.builder() - .uris(statUris) - .start(rangeStart.truncatedTo(ChronoUnit.MILLIS).format(formatter)) - .end(rangeEnd.truncatedTo(ChronoUnit.MILLIS).format(formatter)) - .unique(uniqe) - .build(); - - List stats; - try { - stats = statClient.getStat(request); - } catch (Exception ex) { - throw new StatResponseException(ex); - } - - if (stats == null) { - throw new StatResponseException(); - } - - return stats; - } - - public List getStat(List statUris, boolean uniqe) { - return getStat(statUris, null, null, uniqe); - } -} diff --git a/core/main-service/src/test/java/ru/practicum/ewm/TestUtils.java b/core/main-service/src/test/java/ru/practicum/ewm/TestUtils.java deleted file mode 100644 index 80e8ba9..0000000 --- a/core/main-service/src/test/java/ru/practicum/ewm/TestUtils.java +++ /dev/null @@ -1,103 +0,0 @@ -package ru.practicum.ewm; - -import org.springframework.boot.test.context.TestComponent; -import ru.practicum.ewm.model.Category; -import ru.practicum.ewm.model.Event; -import ru.practicum.ewm.model.Location; -import ru.practicum.ewm.model.User; -import ru.practicum.ewm.model.enums.EventState; - -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Random; - -@SuppressWarnings("unused") -@TestComponent -public class TestUtils { - - public final LocalDateTime pastDate = LocalDateTime.parse("2023-01-01T00:00:01"); - public final LocalDateTime futureDate = LocalDateTime.parse("2028-01-01T00:00:01"); - - private final Random random = new Random(); - - public String generateRandomText(int length, String addAfterStr, Character... addSynbols) { - java.util.List symbols = new ArrayList<>(List.of( - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', - 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', - 'u', 'v', 'w', 'x', 'y', 'z' - )); - symbols.addAll(Arrays.asList(addSynbols)); - StringBuilder sb = new StringBuilder(length); - - for (int i = 0; i < length; i++) { - int index = random.nextInt(symbols.size()); - sb.append(symbols.get(index)); - } - - return sb.append(addAfterStr).toString(); - } - - public Location generateLocation() { - return Location.builder() - .lat(random.nextFloat()) - .lon(random.nextFloat()) - .build(); - } - - public User generateUser() { - return User.builder() - .name(generateRandomText(15, "", ' ')) - .email(generateRandomText(15, "@mail.ru")) - .build(); - } - - public Category generateCategory() { - return Category.builder() - .name(generateRandomText(10, "")) - .build(); - } - - public Event generateEvent(User initiator, - Category category, - LocalDateTime eventDate, - EventState eventState) { - return Event.builder() - .annotation(generateRandomText(100, "", ' ')) - .category(category) - .createdOn(eventDate.minusDays(1)) - .description(generateRandomText(200, "", ' ')) - .eventDate(eventDate) - .initiator(initiator) - .location(generateLocation()) - .paid(random.nextBoolean()) - .participantLimit(random.nextInt(100)) - .publishedOn(eventDate.plusHours(3)) - .requestModeration(random.nextBoolean()) - .state(eventState) - .title(generateRandomText(15, "", ' ')) - .rate(0) - .build(); - } - - public Event generateEvent(LocalDateTime eventDate, EventState eventState) { - return generateEvent(generateUser(), generateCategory(), eventDate, eventState); - } - - public Event generateEvent(EventState eventState) { - return generateEvent(pastDate, eventState); - } - - public Event generateEvent() { - return generateEvent(pastDate, EventState.PUBLISHED); - } - - public Event getNewCopyOfEvent(Event event) { - return event.toBuilder().id(null).build(); - } - - public Event getNewCopyOfEvent(Event event, EventState eventState) { - return event.toBuilder().id(null).state(eventState).build(); - } -} \ No newline at end of file diff --git a/core/main-service/src/test/java/ru/practicum/ewm/controller/admin/AdminCategoryControllerTest.java b/core/main-service/src/test/java/ru/practicum/ewm/controller/admin/AdminCategoryControllerTest.java deleted file mode 100644 index 58fb804..0000000 --- a/core/main-service/src/test/java/ru/practicum/ewm/controller/admin/AdminCategoryControllerTest.java +++ /dev/null @@ -1,67 +0,0 @@ -package ru.practicum.ewm.controller.admin; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import ru.practicum.ewm.dto.category.CategoryDto; -import ru.practicum.ewm.dto.category.NewCategoryDto; -import ru.practicum.ewm.service.category.CategoryService; - -import static org.mockito.Mockito.*; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - -@ExtendWith(MockitoExtension.class) -class AdminCategoryControllerTest { - - @Mock - private CategoryService categoryService; - - @InjectMocks - private AdminCategoryController controller; - - private MockMvc mockMvc; - - private final ObjectMapper objectMapper = new ObjectMapper(); - - @BeforeEach - void setUp() { - mockMvc = MockMvcBuilders.standaloneSetup(controller).build(); - } - - @Test - void createCategory_blankName_shouldReturn400() throws Exception { - NewCategoryDto newCat = new NewCategoryDto(""); - - mockMvc.perform(post("/admin/categories") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(newCat))) - .andExpect(status().isBadRequest()); - } - - @Test - void updateCategory_validData_shouldReturn200() throws Exception { - CategoryDto update = CategoryDto.builder().id(1L).name("Выставки").build(); - CategoryDto updated = CategoryDto.builder().id(1L).name("Выставки").build(); - when(categoryService.updateCategory(1L, update)).thenReturn(updated); - - mockMvc.perform(patch("/admin/categories/1") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(update))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.name").value("Выставки")); - } - - @Test - void deleteCategory_existing_shouldReturn204() throws Exception { - mockMvc.perform(delete("/admin/categories/1")) - .andExpect(status().isNoContent()); - } -} \ No newline at end of file diff --git a/core/main-service/src/test/java/ru/practicum/ewm/controller/admin/AdminUserControllerTest.java b/core/main-service/src/test/java/ru/practicum/ewm/controller/admin/AdminUserControllerTest.java deleted file mode 100644 index 65dc166..0000000 --- a/core/main-service/src/test/java/ru/practicum/ewm/controller/admin/AdminUserControllerTest.java +++ /dev/null @@ -1,85 +0,0 @@ -package ru.practicum.ewm.controller.admin; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import ru.practicum.ewm.dto.user.NewUserRequest; -import ru.practicum.ewm.dto.user.UserDto; -import ru.practicum.ewm.service.user.UserService; - -import java.util.List; - -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - -@ExtendWith(MockitoExtension.class) -class AdminUserControllerTest { - - @Mock - private UserService userService; - - @InjectMocks - private AdminUserController controller; - - private MockMvc mockMvc; - - private final ObjectMapper objectMapper = new ObjectMapper(); - - @BeforeEach - void setUp() { - mockMvc = MockMvcBuilders.standaloneSetup(controller).build(); - } - - @Test - void createUser_validRequest_shouldReturn201() throws Exception { - NewUserRequest request = new NewUserRequest("Test User", "user@example.com"); - UserDto userDto = new UserDto(1L, "Test User", "user@example.com"); - when(userService.adminAddNewUser(any(NewUserRequest.class))).thenReturn(userDto); - - mockMvc.perform(post("/admin/users") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request))) - .andExpect(status().isCreated()) - .andExpect(jsonPath("$.id").value(1L)) - .andExpect(jsonPath("$.name").value("Test User")) - .andExpect(jsonPath("$.email").value("user@example.com")); - } - - @Test - void createUser_blankName_shouldReturn400() throws Exception { - NewUserRequest request = new NewUserRequest("", "user@example.com"); - - mockMvc.perform(post("/admin/users") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request))) - .andExpect(status().isBadRequest()); - } - - @Test - void getUsers_shouldReturnList() throws Exception { - UserDto user = new UserDto(1L, "Test User", "user@example.com"); - when(userService.getUsers(any(), anyInt(), anyInt())).thenReturn(List.of(user)); - - mockMvc.perform(get("/admin/users") - .param("from", "0") - .param("size", "10")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$[0].id").value(1L)) - .andExpect(jsonPath("$[0].name").value("Test User")); - } - - @Test - void deleteUser_existingUser_shouldReturn204() throws Exception { - mockMvc.perform(delete("/admin/users/1")) - .andExpect(status().isNoContent()); - } -} \ No newline at end of file diff --git a/core/main-service/src/test/java/ru/practicum/ewm/dto/NewEventDtoTest.java b/core/main-service/src/test/java/ru/practicum/ewm/dto/NewEventDtoTest.java deleted file mode 100644 index bec8633..0000000 --- a/core/main-service/src/test/java/ru/practicum/ewm/dto/NewEventDtoTest.java +++ /dev/null @@ -1,200 +0,0 @@ -package ru.practicum.ewm.dto; - -import jakarta.validation.ConstraintViolation; -import jakarta.validation.Validation; -import jakarta.validation.Validator; -import jakarta.validation.ValidatorFactory; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import ru.practicum.ewm.dto.event.NewEventDto; -import ru.practicum.ewm.model.Location; - -import java.time.LocalDateTime; - -import static org.junit.jupiter.api.Assertions.*; - -/// generated by Alisa -class NewEventDtoTest { - - private Validator validator; - - private final String validAnnotation = "Valid annotation text with more than 20 characters"; - private final String validDescription = "Valid description with more than 20 characters and less than 7000 chars"; - - @BeforeEach - void setUp() { - try (ValidatorFactory factory = Validation.buildDefaultValidatorFactory()) { - validator = factory.getValidator(); - } - } - - @Test - void whenValidDto_thenNoViolations() { - NewEventDto dto = NewEventDto.builder() - .annotation(validAnnotation) - .category(1L) - .description(validDescription) - .eventDate(LocalDateTime.now().plusDays(1)) - .location(Location.builder() - .lat(55.7558F) - .lon(37.6173F) - .build()) - .paid(true) - .participantLimit(100) - .requestModeration(true) - .title("Valid title") - .build(); - - var violations = validator.validate(dto); - - assertEquals(0, violations.size()); - } - - @Test - void whenAnnotationTooShort_thenViolation() { - NewEventDto dto = NewEventDto.builder() - .annotation("short") - .category(1L) - .description(validDescription) - .eventDate(LocalDateTime.now().plusDays(1)) - .location(Location.builder() - .lat(55.7558F) - .lon(37.6173F) - .build()) - .paid(true) - .participantLimit(100) - .requestModeration(true) - .title("Valid title") - .build(); - - var violations = validator.validate(dto); - - assertEquals(1, violations.size()); - ConstraintViolation violation = violations.iterator().next(); - assertEquals("annotation", violation.getPropertyPath().toString()); - assertEquals("размер должен находиться в диапазоне от 20 до 2000", violation.getMessage()); - } - - @Test - void whenCategoryIsNull_thenViolation() { - NewEventDto dto = NewEventDto.builder() - .annotation(validAnnotation) - .category(null) - .description(validDescription) - .eventDate(LocalDateTime.now().plusDays(1)) - .location(Location.builder() - .lat(55.7558F) - .lon(37.6173F) - .build()) - .paid(true) - .participantLimit(100) - .requestModeration(true) - .title("Valid title") - .build(); - - var violations = validator.validate(dto); - - assertEquals(1, violations.size()); - ConstraintViolation violation = violations.iterator().next(); - assertEquals("category", violation.getPropertyPath().toString()); - assertEquals("не должно равняться null", violation.getMessage()); - } - - @Test - void whenDescriptionTooLong_thenViolation() { - String longDescription = "A".repeat(7001); - NewEventDto dto = NewEventDto.builder() - .annotation(validAnnotation) - .category(1L) - .description(longDescription) - .eventDate(LocalDateTime.now().plusDays(1)) - .location(Location.builder() - .lat(55.7558F) - .lon(37.6173F) - .build()) - .paid(true) - .participantLimit(100) - .requestModeration(true) - .title("Valid title") - .build(); - - var violations = validator.validate(dto); - - assertEquals(1, violations.size()); - ConstraintViolation violation = violations.iterator().next(); - assertEquals("description", violation.getPropertyPath().toString()); - assertEquals("размер должен находиться в диапазоне от 20 до 7000", violation.getMessage()); - } - - @Test - void whenEventDateIsNull_thenViolation() { - NewEventDto dto = NewEventDto.builder() - .annotation(validAnnotation) - .category(1L) - .description(validDescription) - .eventDate(null) - .location(Location.builder() - .lat(55.7558F) - .lon(37.6173F) - .build()) - .paid(true) - .participantLimit(100) - .requestModeration(true) - .title("Valid title") - .build(); - - var violations = validator.validate(dto); - - assertEquals(1, violations.size()); - ConstraintViolation violation = violations.iterator().next(); - assertEquals("eventDate", violation.getPropertyPath().toString()); - assertEquals("не должно равняться null", violation.getMessage()); - } - - @Test - void whenLocationIsNull_thenViolation() { - NewEventDto dto = NewEventDto.builder() - .annotation(validAnnotation) - .category(1L) - .description(validDescription) - .eventDate(LocalDateTime.now().plusDays(1)) - .location(null) - .paid(true) - .participantLimit(100) - .requestModeration(true) - .title("Valid title") - .build(); - - var violations = validator.validate(dto); - - assertEquals(1, violations.size()); - ConstraintViolation violation = violations.iterator().next(); - assertEquals("location", violation.getPropertyPath().toString()); - assertEquals("не должно равняться null", violation.getMessage()); - } - - @Test - void whenTitleTooShort_thenViolation() { - NewEventDto dto = NewEventDto.builder() - .annotation(validAnnotation) - .category(1L) - .description(validDescription) - .eventDate(LocalDateTime.now().plusDays(1)) - .location(Location.builder() - .lat(55.7558F) - .lon(37.6173F) - .build()) - .paid(true) - .participantLimit(100) - .requestModeration(true) - .title("ab") - .build(); - - var violations = validator.validate(dto); - - assertEquals(1, violations.size()); - ConstraintViolation violation = violations.iterator().next(); - assertEquals("title", violation.getPropertyPath().toString()); - assertEquals("размер должен находиться в диапазоне от 3 до 120", violation.getMessage()); - } -} diff --git a/core/main-service/src/test/java/ru/practicum/ewm/service/AdminCategoryServiceTest.java b/core/main-service/src/test/java/ru/practicum/ewm/service/AdminCategoryServiceTest.java deleted file mode 100644 index 3da291e..0000000 --- a/core/main-service/src/test/java/ru/practicum/ewm/service/AdminCategoryServiceTest.java +++ /dev/null @@ -1,112 +0,0 @@ -package ru.practicum.ewm.service; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import ru.practicum.ewm.dao.CategoryRepository; -import ru.practicum.ewm.dao.EventRepository; -import ru.practicum.ewm.dto.category.CategoryDto; -import ru.practicum.ewm.dto.category.NewCategoryDto; -import ru.practicum.ewm.model.Category; -import ru.practicum.ewm.service.category.CategoryServiceImpl; -import ru.practicum.ewm.util.error.exception.ConflictException; -import ru.practicum.ewm.util.error.exception.NotFoundException; - -import java.util.Optional; - -import static org.assertj.core.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -class AdminCategoryServiceTest { - - @Mock - private CategoryRepository categoryRepository; - @Mock - private EventRepository eventRepository; - - @InjectMocks - private CategoryServiceImpl categoryService; - - private Category category; - private NewCategoryDto newCategoryDto; - private CategoryDto updateDto; - - @BeforeEach - void setUp() { - category = Category.builder() - .id(1L) - .name("Концерты") - .build(); - newCategoryDto = new NewCategoryDto("Концерты"); - updateDto = CategoryDto.builder() - .id(1L) - .name("Выставки") - .build(); - } - - @Test - void createCategory_success() { - when(categoryRepository.existsByName("Концерты")).thenReturn(false); - when(categoryRepository.save(any(Category.class))).thenReturn(category); - - CategoryDto result = categoryService.adminAddNewCategory(newCategoryDto); - - assertThat(result.id()).isEqualTo(1L); - assertThat(result.name()).isEqualTo("Концерты"); - } - - @Test - void createCategory_duplicateName_shouldThrowConflict() { - when(categoryRepository.existsByName("Концерты")).thenReturn(true); - - assertThatThrownBy(() -> categoryService.adminAddNewCategory(newCategoryDto)) - .isInstanceOf(ConflictException.class) - .hasMessageContaining("уже существует"); - } - - @Test - void updateCategory_success() { - when(categoryRepository.findById(1L)).thenReturn(Optional.of(category)); - when(categoryRepository.existsByName("Выставки")).thenReturn(false); - when(categoryRepository.save(any(Category.class))).thenReturn(category); - - CategoryDto result = categoryService.updateCategory(1L, updateDto); - - assertThat(result.name()).isEqualTo("Выставки"); - verify(categoryRepository).save(category); - } - - @Test - void updateCategory_notFound_shouldThrowNotFound() { - when(categoryRepository.findById(999L)).thenReturn(Optional.empty()); - - assertThatThrownBy(() -> categoryService.updateCategory(999L, updateDto)) - .isInstanceOf(NotFoundException.class); - } - - @Test - void deleteCategory_noEvents_success() { - when(categoryRepository.findById(1L)).thenReturn(Optional.of(category)); - when(eventRepository.existsByCategoryId(1L)).thenReturn(false); - - categoryService.deleteCategory(1L); - - verify(categoryRepository).delete(category); - } - - @Test - void deleteCategory_withEvents_shouldThrowConflict() { - when(categoryRepository.findById(1L)).thenReturn(Optional.of(category)); - when(eventRepository.existsByCategoryId(1L)).thenReturn(true); - - assertThatThrownBy(() -> categoryService.deleteCategory(1L)) - .isInstanceOf(ConflictException.class) - .hasMessageContaining("The category is not empty"); - verify(categoryRepository, never()).delete(any()); - } -} \ No newline at end of file diff --git a/core/main-service/src/test/java/ru/practicum/ewm/service/AdminUserServiceTest.java b/core/main-service/src/test/java/ru/practicum/ewm/service/AdminUserServiceTest.java deleted file mode 100644 index ec176d8..0000000 --- a/core/main-service/src/test/java/ru/practicum/ewm/service/AdminUserServiceTest.java +++ /dev/null @@ -1,101 +0,0 @@ -package ru.practicum.ewm.service; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.data.domain.PageRequest; -import ru.practicum.ewm.dao.UserRepository; -import ru.practicum.ewm.dto.user.NewUserRequest; -import ru.practicum.ewm.dto.user.UserDto; -import ru.practicum.ewm.model.User; -import ru.practicum.ewm.service.user.UserServiceImpl; -import ru.practicum.ewm.util.error.exception.NotFoundException; - -import java.util.List; - -import static org.assertj.core.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; -import static org.mockito.Mockito.eq; - -@ExtendWith(MockitoExtension.class) -class AdminUserServiceTest { - - @Mock - private UserRepository userRepository; - - @InjectMocks - private UserServiceImpl userService; - - private User user; - private NewUserRequest newUserRequest; - - @BeforeEach - void setUp() { - user = User.builder() - .id(1L) - .name("Test User") - .email("user@example.com") - .build(); - newUserRequest = NewUserRequest.builder() - .name("Test User") - .email("user@example.com") - .build(); - } - - @Test - void createUser_shouldReturnUserDto() { - when(userRepository.save(any(User.class))).thenReturn(user); - - UserDto result = userService.adminAddNewUser(newUserRequest); - - assertThat(result.id()).isEqualTo(1L); - assertThat(result.name()).isEqualTo("Test User"); - assertThat(result.email()).isEqualTo("user@example.com"); - verify(userRepository).save(any(User.class)); - } - - @Test - void getUsers_withoutIds_shouldReturnAll() { - when(userRepository.findAll(any(PageRequest.class))) - .thenReturn(org.springframework.data.domain.Page.empty()); - - List result = userService.getUsers(null, 0, 10); - - assertThat(result).isEmpty(); - verify(userRepository).findAll(any(PageRequest.class)); - } - - @Test - void getUsers_withIds_shouldFilterByIds() { - when(userRepository.findAllByIdIn(eq(List.of(1L)), any(PageRequest.class))) - .thenReturn(List.of(user)); - - List result = userService.getUsers(List.of(1L), 0, 10); - - assertThat(result).hasSize(1); - assertThat(result.getFirst().id()).isEqualTo(1L); - } - - @Test - void deleteUser_existingUser_shouldSucceed() { - when(userRepository.existsById(1L)).thenReturn(true); - doNothing().when(userRepository).deleteById(1L); - - assertThatCode(() -> userService.deleteUser(1L)).doesNotThrowAnyException(); - verify(userRepository).deleteById(1L); - } - - @Test - void deleteUser_notFound_shouldThrowNotFoundException() { - when(userRepository.existsById(999L)).thenReturn(false); - - assertThatThrownBy(() -> userService.deleteUser(999L)) - .isInstanceOf(NotFoundException.class) - .hasMessageContaining("Пользователь с id=999 не найден"); - verify(userRepository, never()).deleteById(any()); - } -} \ No newline at end of file diff --git a/core/main-service/src/test/java/ru/practicum/ewm/service/EventServiceImplTest.java b/core/main-service/src/test/java/ru/practicum/ewm/service/EventServiceImplTest.java deleted file mode 100644 index 9c97663..0000000 --- a/core/main-service/src/test/java/ru/practicum/ewm/service/EventServiceImplTest.java +++ /dev/null @@ -1,162 +0,0 @@ -package ru.practicum.ewm.service; - -import jakarta.servlet.http.HttpServletRequest; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentMatchers; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.domain.Specification; -import ru.practicum.ewm.dao.EventRepository; -import ru.practicum.ewm.dao.RequestRepository; -import ru.practicum.ewm.dto.event.EventFullDto; -import ru.practicum.ewm.dto.event.EventShortDto; -import ru.practicum.ewm.dto.event.FreeGetDto; -import ru.practicum.ewm.mapper.EventMapper; -import ru.practicum.ewm.model.Category; -import ru.practicum.ewm.model.Event; -import ru.practicum.ewm.model.Location; -import ru.practicum.ewm.model.User; -import ru.practicum.ewm.model.enums.EventState; -import ru.practicum.ewm.model.enums.ParticipationStatus; -import ru.practicum.ewm.service.event.EventServiceImpl; -import ru.practicum.ewm.util.statistic.StatRepository; -import ru.practicum.stat.dto.ViewStatsDto; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; - -import static org.hamcrest.MatcherAssert.*; -import static org.hamcrest.Matchers.*; -import static org.hamcrest.Matchers.contains; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -class EventServiceImplTest { - - @Mock - StatRepository statRepository; - - @Mock - EventRepository eventRepository; - - @Mock - RequestRepository requestRepository; - - @Mock - HttpServletRequest httpServletRequest; - - @InjectMocks - EventServiceImpl eventService; - - private static Event event; - - @BeforeAll - static void setup() { - event = Event.builder() - .id(1L) - .annotation("annotation".repeat(10)) - .category(Category.builder().id(1L).name("category").build()) - .createdOn(LocalDateTime.parse("2026-11-10T19:00:00")) - .description("description".repeat(10)) - .eventDate(LocalDateTime.parse("2027-01-01T00:00:01")) - .initiator(User.builder().id(1L).name("User").email("user@email.ry").build()) - .location(Location.builder().lat(25.55F).lon(35.77F).build()) - .paid(true) - .participantLimit(30) - .publishedOn(LocalDateTime.parse("2026-12-10T21:00:00")) - .requestModeration(true) - .state(EventState.PUBLISHED) - .title("title") - .rate(0) - .build(); - } - - @Nested - @DisplayName("Получение списка публичных событий") - class GetFreeEvents { - - @Test - void basicFlow() { - // region setup - FreeGetDto getDto = FreeGetDto.builder() - .text("0") - .categories(List.of(0)) - .paid(true) - .onlyAvailable(true) - .rangeStart(LocalDateTime.parse("2026-12-31T23:59:00")) - .rangeEnd(LocalDateTime.parse("2027-01-01T02:00:00")) - .sort(FreeGetDto.FreeEventSort.EVENT_DATE) - .from(0) - .size(1) - .build(); - - Page page = new PageImpl<>(List.of(event)); - // endregion setup - - doNothing().when(statRepository).sendHitRequest(httpServletRequest); - when(eventRepository.findAll( - ArgumentMatchers.>any(), - ArgumentMatchers.any(Pageable.class) - )).thenReturn(page); - - List result = eventService.getFreeEvents(getDto, httpServletRequest); - - assertThat(result, contains(EventMapper.toEventShortDto(event, 0L, 0L))); - verify(statRepository).sendHitRequest(httpServletRequest); - } - - @Test - void whenNoDateRangeProvided() { - FreeGetDto dto = FreeGetDto.builder() - .from(0) - .size(10) - .build(); - - when(eventRepository.findAll( - ArgumentMatchers.>any(), - ArgumentMatchers.any(Pageable.class) - )).thenReturn(Page.empty()); - - eventService.getFreeEvents(dto, httpServletRequest); - - verify(eventRepository).findAll( - ArgumentMatchers.>any(), - ArgumentMatchers.any(Pageable.class) - ); - } - } - - @Nested - @DisplayName("Получение публичного события по id") - class GetFreeEventById { - - @Test - void basicFlow() { - doNothing().when(statRepository).sendHitRequest(httpServletRequest); - when(eventRepository.existsByIdAndState(event.getId(), EventState.PUBLISHED)).thenReturn(Boolean.TRUE); - when(eventRepository.findById(event.getId())).thenReturn(Optional.of(event)); - when(httpServletRequest.getRequestURI()).thenReturn("/events"); - when(statRepository.getStat(List.of("/events"), true)) - .thenReturn(List.of(ViewStatsDto.builder() - .hits(0L) - .build() - )); - when(requestRepository.countByEventIdAndStatus(event.getId(), ParticipationStatus.CONFIRMED)) - .thenReturn(0); - - EventFullDto result = eventService.getFreeEventById(event.getId(), httpServletRequest); - - assertThat(result, is(EventMapper.toEventFullDto(event, 0L, 0L))); - verify(statRepository).sendHitRequest(httpServletRequest); - } - } -} \ No newline at end of file diff --git a/core/pom.xml b/core/pom.xml index 144fd02..c91243d 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -14,7 +14,39 @@ pom - main-service + aggregation-api + event-service + request-service + user-service + rating-service + + + + org.projectlombok + lombok + provided + true + + + + + io.github.openfeign + feign-okhttp + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + -Duser.language=ru -Duser.country=RU + + + + + \ No newline at end of file diff --git a/core/rating-service/pom.xml b/core/rating-service/pom.xml new file mode 100644 index 0000000..72e6f4a --- /dev/null +++ b/core/rating-service/pom.xml @@ -0,0 +1,116 @@ + + + 4.0.0 + + + ru.practicum + core + 0.0.1-SNAPSHOT + + + rating-service + + + + ru.practicum + aggregation-api + 0.0.1-SNAPSHOT + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-test + test + + + + io.hypersistence + hypersistence-utils-hibernate-63 + 3.15.2 + compile + + + + org.postgresql + postgresql + runtime + + + com.h2database + h2 + runtime + + + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-client + + + org.springframework.cloud + spring-cloud-starter-config + + + + org.springframework.cloud + spring-cloud-starter-loadbalancer + + + com.github.ben-manes.caffeine + caffeine + + + + org.springframework.retry + spring-retry + + + org.springframework.boot + spring-boot-starter-aop + + + + org.springframework.boot + spring-boot-starter-actuator + + + io.micrometer + micrometer-registry-prometheus + + + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + + + org.springframework.cloud + spring-cloud-starter-circuitbreaker-resilience4j + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/core/rating-service/src/main/java/ru/practicum/ewm/ratings/RatingServiceApp.java b/core/rating-service/src/main/java/ru/practicum/ewm/ratings/RatingServiceApp.java new file mode 100644 index 0000000..64ea6eb --- /dev/null +++ b/core/rating-service/src/main/java/ru/practicum/ewm/ratings/RatingServiceApp.java @@ -0,0 +1,33 @@ +package ru.practicum.ewm.ratings; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.ComponentScan; +import ru.practicum.aggregation.client.event.EventClient; +import ru.practicum.aggregation.client.request.RequestClient; +import ru.practicum.aggregation.client.stats.StatsClient; +import ru.practicum.aggregation.client.user.UserClient; +import ru.practicum.aggregation.config.JacksonConfiguration; + +@SpringBootApplication +@EnableFeignClients(clients = { + UserClient.class, + EventClient.class, + RequestClient.class, + StatsClient.class +}) +@ComponentScan(value = { + "ru.practicum.ewm.ratings", + "ru.practicum.aggregation.config", + "ru.practicum.aggregation.client", + "ru.practicum.aggregation.error", + "ru.practicum.aggregation.repository" +}) +@ConfigurationPropertiesScan(basePackageClasses = JacksonConfiguration.class) +public class RatingServiceApp { + public static void main(String[] args) { + SpringApplication.run(RatingServiceApp.class, args); + } +} diff --git a/core/rating-service/src/main/java/ru/practicum/ewm/ratings/controller/UserRatingController.java b/core/rating-service/src/main/java/ru/practicum/ewm/ratings/controller/UserRatingController.java new file mode 100644 index 0000000..95a662e --- /dev/null +++ b/core/rating-service/src/main/java/ru/practicum/ewm/ratings/controller/UserRatingController.java @@ -0,0 +1,50 @@ +package ru.practicum.ewm.ratings.controller; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import ru.practicum.aggregation.dto.rating.come.create.RatingCreateRequest; +import ru.practicum.aggregation.dto.rating.output.RatingResponse; +import ru.practicum.ewm.ratings.service.RatingService; + +@Slf4j +@RestController +@RequestMapping("/users/{userId}/events/{eventId}/likes") +@RequiredArgsConstructor +public class UserRatingController { + + private final RatingService ratingService; + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public RatingResponse addReaction(@PathVariable Long userId, + @PathVariable Long eventId, + @Valid @RequestBody RatingCreateRequest createRequest, + @NonNull HttpServletRequest request) { + + log.info(""" + ENDPOINT + Добавление новой реакции + {} {}""", request.getMethod(), request.getRequestURI()); + + return ratingService.addOrUpdateReaction(userId, eventId, createRequest); + } + + @DeleteMapping + @ResponseStatus(HttpStatus.NO_CONTENT) + public void removeReaction(@PathVariable Long userId, + @PathVariable Long eventId, + @NonNull HttpServletRequest request) { + log.info(""" + ENDPOINT + Удаление реакции + {} {}""", request.getMethod(), request.getRequestURI()); + + ratingService.removeReaction(userId, eventId); + } + +} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/ewm/model/Rating.java b/core/rating-service/src/main/java/ru/practicum/ewm/ratings/entity/Rating.java similarity index 70% rename from core/main-service/src/main/java/ru/practicum/ewm/model/Rating.java rename to core/rating-service/src/main/java/ru/practicum/ewm/ratings/entity/Rating.java index f10552c..ad15357 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/model/Rating.java +++ b/core/rating-service/src/main/java/ru/practicum/ewm/ratings/entity/Rating.java @@ -1,4 +1,4 @@ -package ru.practicum.ewm.model; +package ru.practicum.ewm.ratings.entity; import jakarta.persistence.*; import lombok.AccessLevel; @@ -7,8 +7,8 @@ import lombok.Setter; import lombok.experimental.FieldDefaults; import lombok.experimental.SuperBuilder; -import ru.practicum.ewm.model.enums.Reaction; -import ru.practicum.ewm.util.entity.BaseEntity; +import ru.practicum.aggregation.enums.Reaction; +import ru.practicum.aggregation.model.entity.BaseEntity; @Getter @Setter @@ -21,15 +21,13 @@ @FieldDefaults(level = AccessLevel.PRIVATE) public class Rating extends BaseEntity { - @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) - private User user; + Long userId; - @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "event_id", nullable = false) - private Event event; + Long eventId; @Enumerated(EnumType.STRING) @Column(nullable = false) - private Reaction reaction; + Reaction reaction; } diff --git a/core/rating-service/src/main/java/ru/practicum/ewm/ratings/mapper/RatingMapper.java b/core/rating-service/src/main/java/ru/practicum/ewm/ratings/mapper/RatingMapper.java new file mode 100644 index 0000000..fc293e9 --- /dev/null +++ b/core/rating-service/src/main/java/ru/practicum/ewm/ratings/mapper/RatingMapper.java @@ -0,0 +1,19 @@ +package ru.practicum.ewm.ratings.mapper; + +import lombok.NonNull; +import lombok.experimental.UtilityClass; +import ru.practicum.aggregation.dto.rating.output.RatingResponse; +import ru.practicum.ewm.ratings.entity.Rating; + +@UtilityClass +public class RatingMapper { + + public RatingResponse mapToResponse(@NonNull Rating rating) { + return RatingResponse.builder() + .id(rating.getId()) + .userId(rating.getUserId()) + .eventId(rating.getEventId()) + .reaction(rating.getReaction()) + .build(); + } +} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/dao/RatingRepository.java b/core/rating-service/src/main/java/ru/practicum/ewm/ratings/repository/RatingRepository.java similarity index 63% rename from core/main-service/src/main/java/ru/practicum/ewm/dao/RatingRepository.java rename to core/rating-service/src/main/java/ru/practicum/ewm/ratings/repository/RatingRepository.java index 418c9cd..91753f2 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/dao/RatingRepository.java +++ b/core/rating-service/src/main/java/ru/practicum/ewm/ratings/repository/RatingRepository.java @@ -1,8 +1,8 @@ -package ru.practicum.ewm.dao; +package ru.practicum.ewm.ratings.repository; import org.springframework.data.jpa.repository.JpaRepository; -import ru.practicum.ewm.model.Rating; -import ru.practicum.ewm.model.enums.Reaction; +import ru.practicum.aggregation.enums.Reaction; +import ru.practicum.ewm.ratings.entity.Rating; import java.util.Optional; @@ -10,7 +10,5 @@ public interface RatingRepository extends JpaRepository { Optional findByUserIdAndEventId(Long userId, Long eventId); - boolean existsByUserIdAndEventId(Long userId, Long eventId); - long countByEventIdAndReaction(Long eventId, Reaction reaction); } diff --git a/core/rating-service/src/main/java/ru/practicum/ewm/ratings/service/RatingService.java b/core/rating-service/src/main/java/ru/practicum/ewm/ratings/service/RatingService.java new file mode 100644 index 0000000..2266e02 --- /dev/null +++ b/core/rating-service/src/main/java/ru/practicum/ewm/ratings/service/RatingService.java @@ -0,0 +1,13 @@ +package ru.practicum.ewm.ratings.service; + +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.aggregation.dto.rating.come.create.RatingCreateRequest; +import ru.practicum.aggregation.dto.rating.output.RatingResponse; + +@Transactional +public interface RatingService { + + RatingResponse addOrUpdateReaction(long userId, long eventId, RatingCreateRequest request); + + void removeReaction(long userId, long eventId); +} diff --git a/core/rating-service/src/main/java/ru/practicum/ewm/ratings/service/RatingServiceImpl.java b/core/rating-service/src/main/java/ru/practicum/ewm/ratings/service/RatingServiceImpl.java new file mode 100644 index 0000000..7bd9d23 --- /dev/null +++ b/core/rating-service/src/main/java/ru/practicum/ewm/ratings/service/RatingServiceImpl.java @@ -0,0 +1,101 @@ +package ru.practicum.ewm.ratings.service; + +import jakarta.validation.ValidationException; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Service; +import ru.practicum.aggregation.dto.rating.come.create.RatingCreateRequest; +import ru.practicum.aggregation.dto.rating.come.update.RatingUpdateRequest; +import ru.practicum.aggregation.dto.rating.output.RatingResponse; +import ru.practicum.aggregation.enums.Reaction; +import ru.practicum.aggregation.error.exception.bussines.cause.ConflictException; +import ru.practicum.aggregation.error.exception.bussines.cause.NotFoundException; +import ru.practicum.aggregation.repository.EventFeignRepository; +import ru.practicum.ewm.ratings.entity.Rating; +import ru.practicum.ewm.ratings.mapper.RatingMapper; +import ru.practicum.ewm.ratings.repository.RatingRepository; + +import java.util.Objects; + +@Service +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class RatingServiceImpl implements RatingService { + + RatingRepository ratingRepository; + + EventFeignRepository eventFeignRepository; + + @Override + public RatingResponse addOrUpdateReaction(long userId, + long eventId, + @NonNull RatingCreateRequest request) { + + var initiatorId = eventFeignRepository.getInitiatorIfPublished(eventId); + + if (Objects.equals(userId, initiatorId)) { + throw new ValidationException("Нельзя ставить реакции своим событиям"); + } + + var rating = ratingRepository + .findByUserIdAndEventId(userId, eventId) + .orElse(null); + var requestReaction = request.reaction(); + + if (rating != null) { + if (rating.getReaction() == requestReaction) { + ratingRepository.delete(rating); + updateEventRate(eventId); + throw new ConflictException(""" + Реакция пользователя с id=%s событию с id=%s, удалена""" + .formatted(userId, eventId)); + } else { + rating.setReaction(requestReaction); + ratingRepository.save(rating); + updateEventRate(eventId); + return RatingMapper.mapToResponse(rating); + } + } else { + rating = Rating.builder() + .userId(userId) + .eventId(eventId) + .reaction(requestReaction) + .build(); + ratingRepository.save(rating); + updateEventRate(eventId); + return RatingMapper.mapToResponse(rating); + } + } + + @Override + public void removeReaction(long userId, long eventId) { + ratingRepository.findByUserIdAndEventId(userId, eventId) + .ifPresentOrElse(ratingRepository::delete, + () -> { + updateEventRate(eventId); + throw new NotFoundException( + "Реакция пользователя с id=%s событию с id=%s, не найдена" + .formatted(userId, eventId) + ); + } + ); + + updateEventRate(eventId); + } + + private void updateEventRate(long eventId) { + long likes = ratingRepository.countByEventIdAndReaction(eventId, Reaction.LIKE); + long dislikes = ratingRepository.countByEventIdAndReaction(eventId, Reaction.DISLIKE); + long rate = likes - dislikes; + + var request = RatingUpdateRequest.builder() + .eventId(eventId) + .rate(rate) + .build(); + + eventFeignRepository.updateRating(request); + } + +} diff --git a/core/rating-service/src/main/resources/application.yml b/core/rating-service/src/main/resources/application.yml new file mode 100644 index 0000000..338ad89 --- /dev/null +++ b/core/rating-service/src/main/resources/application.yml @@ -0,0 +1,31 @@ +server: + port: 0 + +spring: + application: + name: rating-service + config: + import: "optional:configserver:" + cloud: + config: + discovery: + enabled: true + service-id: config-server + fail-fast: true + retry: + initial-interval: 1000 + max-interval: 10000 + multiplier: 2.0 + max-attempts: 20 + +eureka: + client: + serviceUrl: + defaultZone: http://localhost:8761/eureka/ + register-with-eureka: true + fetch-registry: true + instance: + prefer-ip-address: true + instance-id: ${spring.application.name}:${random.value} + lease-renewal-interval-in-seconds: 5 + lease-expiration-duration-in-seconds: 10 diff --git a/core/rating-service/src/main/resources/schema-postgres.sql b/core/rating-service/src/main/resources/schema-postgres.sql new file mode 100644 index 0000000..83fc95d --- /dev/null +++ b/core/rating-service/src/main/resources/schema-postgres.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS ratings ( + id bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + user_id bigint NOT NULL, + event_id bigint NOT NULL, + reaction varchar(10) NOT NULL CHECK (reaction IN ('LIKE', 'DISLIKE')), + CONSTRAINT pk_ratings PRIMARY KEY (id), + CONSTRAINT uq_ratings_user_event UNIQUE (user_id, event_id) +); diff --git a/core/request-service/pom.xml b/core/request-service/pom.xml new file mode 100644 index 0000000..589db77 --- /dev/null +++ b/core/request-service/pom.xml @@ -0,0 +1,116 @@ + + + 4.0.0 + + + ru.practicum + core + 0.0.1-SNAPSHOT + + + request-service + + + + ru.practicum + aggregation-api + 0.0.1-SNAPSHOT + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-test + test + + + + io.hypersistence + hypersistence-utils-hibernate-63 + 3.15.2 + compile + + + + org.postgresql + postgresql + runtime + + + com.h2database + h2 + runtime + + + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-client + + + org.springframework.cloud + spring-cloud-starter-config + + + + org.springframework.cloud + spring-cloud-starter-loadbalancer + + + com.github.ben-manes.caffeine + caffeine + + + + org.springframework.retry + spring-retry + + + org.springframework.boot + spring-boot-starter-aop + + + + org.springframework.boot + spring-boot-starter-actuator + + + io.micrometer + micrometer-registry-prometheus + + + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + + + org.springframework.cloud + spring-cloud-starter-circuitbreaker-resilience4j + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/core/request-service/src/main/java/ru/practicum/ewm/requests/RequestServiceApp.java b/core/request-service/src/main/java/ru/practicum/ewm/requests/RequestServiceApp.java new file mode 100644 index 0000000..f5824cf --- /dev/null +++ b/core/request-service/src/main/java/ru/practicum/ewm/requests/RequestServiceApp.java @@ -0,0 +1,33 @@ +package ru.practicum.ewm.requests; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.ComponentScan; +import ru.practicum.aggregation.client.event.EventClient; +import ru.practicum.aggregation.client.request.RequestClient; +import ru.practicum.aggregation.client.stats.StatsClient; +import ru.practicum.aggregation.client.user.UserClient; +import ru.practicum.aggregation.config.JacksonConfiguration; + +@SpringBootApplication +@EnableFeignClients(clients = { + UserClient.class, + EventClient.class, + RequestClient.class, + StatsClient.class +}) +@ComponentScan(value = { + "ru.practicum.ewm.requests", + "ru.practicum.aggregation.config", + "ru.practicum.aggregation.client", + "ru.practicum.aggregation.error", + "ru.practicum.aggregation.repository" +}) +@ConfigurationPropertiesScan(basePackageClasses = JacksonConfiguration.class) +public class RequestServiceApp { + public static void main(String[] args) { + SpringApplication.run(RequestServiceApp.class, args); + } +} diff --git a/core/request-service/src/main/java/ru/practicum/ewm/requests/controller/SystemRequestController.java b/core/request-service/src/main/java/ru/practicum/ewm/requests/controller/SystemRequestController.java new file mode 100644 index 0000000..ef3ace6 --- /dev/null +++ b/core/request-service/src/main/java/ru/practicum/ewm/requests/controller/SystemRequestController.java @@ -0,0 +1,57 @@ +package ru.practicum.ewm.requests.controller; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; +import ru.practicum.aggregation.dto.participation.come.EventRequestStatusUpdateRequest; +import ru.practicum.aggregation.dto.participation.output.EventRequestStatusUpdateResult; +import ru.practicum.aggregation.dto.participation.output.ParticipationRequestDto; +import ru.practicum.aggregation.model.repository.EventRequestCount; +import ru.practicum.ewm.requests.service.RequestService; + +import java.util.List; + +@Slf4j +@RestController +@RequestMapping(path = "/system/requests") +@RequiredArgsConstructor +public class SystemRequestController { + + private final RequestService requestService; + + /** + * Найти количество подтвержденных запросов на участие по ID событий + * @param eventIds ID событий + * @return List.of({@link EventRequestCount}) + */ + @GetMapping("/count") + public List getConfirmedRequestsCount(@RequestParam List eventIds) { + return requestService.getRequestsCount(eventIds); + } + + @PatchMapping("/events/{eventId}/status") + public EventRequestStatusUpdateResult updateStatusRequest( + @PathVariable @Positive Long eventId, + @RequestParam @PositiveOrZero Integer participantLimit, + @RequestParam @NotNull Boolean requestModeration, + @RequestBody @NotNull EventRequestStatusUpdateRequest request + ) { + return requestService.updateStatusRequest(eventId, participantLimit, requestModeration, request); + } + + /** + * + * @param userId id текущего пользователя + * @param eventId id события + * @return List<{@link ParticipationRequestDto}> + */ + @GetMapping("/users/{userId}/events/{eventId}") + public List findByUserIdAndEventId(@PathVariable @Positive Long userId, + @PathVariable @Positive Long eventId) { + return requestService.findByUserIdAndEventId(userId, eventId); + } + +} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/controller/user/UserRequestController.java b/core/request-service/src/main/java/ru/practicum/ewm/requests/controller/UserRequestController.java similarity index 61% rename from core/main-service/src/main/java/ru/practicum/ewm/controller/user/UserRequestController.java rename to core/request-service/src/main/java/ru/practicum/ewm/requests/controller/UserRequestController.java index d970679..0e237cf 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/controller/user/UserRequestController.java +++ b/core/request-service/src/main/java/ru/practicum/ewm/requests/controller/UserRequestController.java @@ -1,12 +1,14 @@ -package ru.practicum.ewm.controller.user; +package ru.practicum.ewm.requests.controller; +import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.constraints.Positive; +import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; -import ru.practicum.ewm.dto.request.ParticipationRequestDto; -import ru.practicum.ewm.service.request.RequestService; +import ru.practicum.aggregation.dto.participation.output.ParticipationRequestDto; +import ru.practicum.ewm.requests.service.RequestService; import java.util.List; @@ -15,24 +17,24 @@ @RequestMapping(path = "/users/{userId}/requests") @RequiredArgsConstructor public class UserRequestController { - private final RequestService service; + + private final RequestService requestService; /** - * Получение информации о заявках текущего пользователя на участие в чужих событиях - *

* В случае, если по заданным фильтрам не найдено ни одной заявки, возвращает пустой список - * - * @param userId id текущего пользователя - * @return List<{@link ParticipationRequestDto}> */ @GetMapping - public List findByRequesterId(@PathVariable @Positive Long userId) { - return service.findByRequesterId(userId); + public List findByRequesterId(@PathVariable @Positive Long userId, + @NonNull HttpServletRequest request) { + log.info(""" + ENDPOINT + Получение информации о заявках текущего пользователя на участие в чужих событиях + {} {}""", request.getMethod(), request.getRequestURI()); + + return requestService.findByRequesterId(userId); } /** - * Добавление запроса от текущего пользователя на участие в событии - *

* Обратите внимание: *

* нельзя добавить повторный запрос (Ожидается код ошибки 409) @@ -45,28 +47,30 @@ public List findByRequesterId(@PathVariable @Positive L *

* если для события отключена пре-модерация запросов на участие, то запрос должен автоматически перейти в * состояние подтвержденного - * - * @param userId id текущего пользователя - * @param eventId id события - * @return {@link ParticipationRequestDto} */ @PostMapping @ResponseStatus(HttpStatus.CREATED) public ParticipationRequestDto addParticipationRequest(@PathVariable @Positive Long userId, - @RequestParam @Positive Long eventId) { - return service.addParticipationRequest(userId, eventId); + @RequestParam @Positive Long eventId, + @NonNull HttpServletRequest request) { + log.info(""" + ENDPOINT + Добавление запроса от текущего пользователя на участие в событии + {} {}""", request.getMethod(), request.getRequestURI()); + + return requestService.addParticipationRequest(userId, eventId); } - /** - * Отмена своего запроса на участие в событии - * - * @param userId id текущего пользователя - * @param requestId id запроса на участие - * @return {@link ParticipationRequestDto} - */ @PatchMapping("/{requestId}/cancel") public ParticipationRequestDto cancelParticipationRequest(@PathVariable @Positive Long userId, - @PathVariable @Positive Long requestId) { - return service.cancelParticipationRequest(userId, requestId); + @PathVariable @Positive Long requestId, + @NonNull HttpServletRequest request) { + log.info(""" + ENDPOINT + Отмена своего запроса на участие в событии + {} {}""", request.getMethod(), request.getRequestURI()); + + return requestService.cancelParticipationRequest(userId, requestId); } + } diff --git a/core/main-service/src/main/java/ru/practicum/ewm/model/ParticipationRequest.java b/core/request-service/src/main/java/ru/practicum/ewm/requests/entity/ParticipationRequest.java similarity index 73% rename from core/main-service/src/main/java/ru/practicum/ewm/model/ParticipationRequest.java rename to core/request-service/src/main/java/ru/practicum/ewm/requests/entity/ParticipationRequest.java index 67bf763..02d2b3b 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/model/ParticipationRequest.java +++ b/core/request-service/src/main/java/ru/practicum/ewm/requests/entity/ParticipationRequest.java @@ -1,4 +1,4 @@ -package ru.practicum.ewm.model; +package ru.practicum.ewm.requests.entity; import jakarta.persistence.*; import lombok.AccessLevel; @@ -7,8 +7,8 @@ import lombok.Setter; import lombok.experimental.FieldDefaults; import lombok.experimental.SuperBuilder; -import ru.practicum.ewm.model.enums.ParticipationStatus; -import ru.practicum.ewm.util.entity.BaseEntity; +import ru.practicum.aggregation.enums.ParticipationStatus; +import ru.practicum.aggregation.model.entity.BaseEntity; import java.time.LocalDateTime; @@ -23,13 +23,11 @@ public class ParticipationRequest extends BaseEntity { LocalDateTime created; - @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "event_id", nullable = false) - Event event; + Long eventId; - @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "requester_id", nullable = false) - User requester; + Long requesterId; @Enumerated(EnumType.STRING) @Column(nullable = false) diff --git a/core/request-service/src/main/java/ru/practicum/ewm/requests/mapper/RequestMapper.java b/core/request-service/src/main/java/ru/practicum/ewm/requests/mapper/RequestMapper.java new file mode 100644 index 0000000..2968e48 --- /dev/null +++ b/core/request-service/src/main/java/ru/practicum/ewm/requests/mapper/RequestMapper.java @@ -0,0 +1,26 @@ +package ru.practicum.ewm.requests.mapper; + +import lombok.NonNull; +import lombok.experimental.UtilityClass; +import ru.practicum.aggregation.dto.participation.output.ParticipationRequestDto; +import ru.practicum.ewm.requests.entity.ParticipationRequest; + +@UtilityClass +public class RequestMapper { + + public ParticipationRequestDto toParticipationRequestDto( + @NonNull ParticipationRequest participationRequest) { + + var dateTime = participationRequest.getCreated() == null ? + null : participationRequest.getCreated(); + + return ParticipationRequestDto.builder() + .event(participationRequest.getEventId()) + .requester(participationRequest.getRequesterId()) + .status(participationRequest.getStatus()) + .created(dateTime) + .id(participationRequest.getId()) + .build(); + } + +} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/dao/RequestRepository.java b/core/request-service/src/main/java/ru/practicum/ewm/requests/repository/RequestRepository.java similarity index 60% rename from core/main-service/src/main/java/ru/practicum/ewm/dao/RequestRepository.java rename to core/request-service/src/main/java/ru/practicum/ewm/requests/repository/RequestRepository.java index 73c41a5..265b668 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/dao/RequestRepository.java +++ b/core/request-service/src/main/java/ru/practicum/ewm/requests/repository/RequestRepository.java @@ -1,12 +1,12 @@ -package ru.practicum.ewm.dao; +package ru.practicum.ewm.requests.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.transaction.annotation.Transactional; -import ru.practicum.ewm.model.ParticipationRequest; -import ru.practicum.ewm.model.enums.ParticipationStatus; -import ru.practicum.ewm.service.request.EventRequestCount; +import ru.practicum.aggregation.enums.ParticipationStatus; +import ru.practicum.aggregation.model.repository.EventRequestCount; +import ru.practicum.ewm.requests.entity.ParticipationRequest; import java.util.List; @@ -21,21 +21,20 @@ public interface RequestRepository extends JpaRepository countConfirmedRequestsByEventIds(List eventIds, ParticipationStatus status); + List getCountByEventIdsAndStatus(List eventIds, ParticipationStatus status); @Modifying @Transactional @Query(""" UPDATE ParticipationRequest pr SET pr.status = 'REJECTED' - WHERE pr.event.id = :eventId + WHERE pr.eventId = :eventId AND pr.status=:status """) int rejectPendingRequests(Long eventId, ParticipationStatus status); diff --git a/core/request-service/src/main/java/ru/practicum/ewm/requests/service/RequestService.java b/core/request-service/src/main/java/ru/practicum/ewm/requests/service/RequestService.java new file mode 100644 index 0000000..c6c48db --- /dev/null +++ b/core/request-service/src/main/java/ru/practicum/ewm/requests/service/RequestService.java @@ -0,0 +1,31 @@ +package ru.practicum.ewm.requests.service; + +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.aggregation.dto.participation.come.EventRequestStatusUpdateRequest; +import ru.practicum.aggregation.dto.participation.output.EventRequestStatusUpdateResult; +import ru.practicum.aggregation.dto.participation.output.ParticipationRequestDto; +import ru.practicum.aggregation.model.repository.EventRequestCount; + +import java.util.List; + +@Transactional +public interface RequestService { + + @Transactional(readOnly = true) + List findByUserIdAndEventId(long userId, long eventId); + + @Transactional(readOnly = true) + List findByRequesterId(long userId); + + @Transactional(readOnly = true) + List getRequestsCount(List eventIds); + + EventRequestStatusUpdateResult updateStatusRequest(long eventId, + int participantLimit, + boolean requestModeration, + EventRequestStatusUpdateRequest request); + + ParticipationRequestDto addParticipationRequest(long userId, long eventId); + + ParticipationRequestDto cancelParticipationRequest(long userId, long requestId); +} diff --git a/core/request-service/src/main/java/ru/practicum/ewm/requests/service/RequestServiceImpl.java b/core/request-service/src/main/java/ru/practicum/ewm/requests/service/RequestServiceImpl.java new file mode 100644 index 0000000..ee38821 --- /dev/null +++ b/core/request-service/src/main/java/ru/practicum/ewm/requests/service/RequestServiceImpl.java @@ -0,0 +1,206 @@ +package ru.practicum.ewm.requests.service; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import lombok.extern.slf4j.Slf4j; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Service; +import ru.practicum.aggregation.dto.event.output.EventFullDto; +import ru.practicum.aggregation.dto.participation.come.EventRequestStatusUpdateRequest; +import ru.practicum.aggregation.dto.participation.output.EventRequestStatusUpdateResult; +import ru.practicum.aggregation.dto.participation.output.ParticipationRequestDto; +import ru.practicum.aggregation.dto.user.output.UserDto; +import ru.practicum.aggregation.enums.EventState; +import ru.practicum.aggregation.enums.ParticipationStatus; +import ru.practicum.aggregation.enums.RequestUpdateStatus; +import ru.practicum.aggregation.error.exception.bussines.cause.ConflictException; +import ru.practicum.aggregation.error.exception.bussines.cause.NotFoundException; +import ru.practicum.aggregation.model.repository.EventRequestCount; +import ru.practicum.aggregation.repository.EventFeignRepository; +import ru.practicum.aggregation.repository.UserFeignRepository; +import ru.practicum.ewm.requests.entity.ParticipationRequest; +import ru.practicum.ewm.requests.mapper.RequestMapper; +import ru.practicum.ewm.requests.repository.RequestRepository; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@Slf4j +@Service +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class RequestServiceImpl implements RequestService { + + RequestRepository requestRepository; + + UserFeignRepository userFeignRepository; + EventFeignRepository eventFeignRepository; + + public List findByUserIdAndEventId(long userId, long eventId) { + var event = getEventFullDtoById(eventId); + if (!event.initiator().id().equals(userId)) { + throw new NotFoundException("Событие не найдено"); + } + + var requests = requestRepository.findByEventId(eventId); + + return requests.stream() + .map(RequestMapper::toParticipationRequestDto) + .toList(); + } + + public EventRequestStatusUpdateResult updateStatusRequest( + long eventId, + int participantLimit, + boolean requestModeration, + @NonNull EventRequestStatusUpdateRequest request + ) { + var confirmedRequests = new ArrayList(); + var rejectedRequests = new ArrayList(); + + boolean isModerationOff = !requestModeration || participantLimit == 0; + + if (isModerationOff || request.requestIds().isEmpty()) { + return EventRequestStatusUpdateResult.builder() + .confirmedRequests(Collections.emptyList()) + .rejectedRequests(Collections.emptyList()) + .build(); + } + + var countConfirmed = requestRepository + .countByEventIdAndStatus(eventId, ParticipationStatus.CONFIRMED); + + var requests = requestRepository.findAllByIdIn(request.requestIds()); + + boolean isConfirmed = request.status().equals(RequestUpdateStatus.CONFIRMED); + if (isConfirmed && countConfirmed >= participantLimit) { + throw new ConflictException("Достигнут лимит подтвержденных заявок"); + } + + for (var pr : requests) { + if (!pr.getStatus().equals(ParticipationStatus.PENDING)) { + throw new ConflictException("Статус можно изменить только у заявок в состоянии рассмотрения"); + } + + if (isConfirmed && countConfirmed < participantLimit) { + pr.setStatus(ParticipationStatus.CONFIRMED); + countConfirmed++; + confirmedRequests.add(RequestMapper.toParticipationRequestDto(pr)); + } else { + pr.setStatus(ParticipationStatus.REJECTED); + rejectedRequests.add(RequestMapper.toParticipationRequestDto(pr)); + } + } + + requestRepository.saveAll(requests); + + // если в процессе лимит превышен - отклоняем все оставшиеся заявки + if (isConfirmed && countConfirmed >= participantLimit) { + if (requestRepository.rejectPendingRequests(eventId, ParticipationStatus.PENDING) < 0) { + throw new RuntimeException("Не удалось отклонить заявку"); + } + } + + return EventRequestStatusUpdateResult.builder() + .confirmedRequests(confirmedRequests) + .rejectedRequests(rejectedRequests) + .build(); + } + + public List findByRequesterId(long userId) { + return requestRepository.findByRequesterId(userId) + .stream() + .map(RequestMapper::toParticipationRequestDto) + .toList(); + } + + public ParticipationRequestDto addParticipationRequest(long userId, long eventId) { + var requester = getUserDtoById(userId); + var event = getEventFullDtoById(eventId); + + if (!EventState.PUBLISHED.equals(event.state())) { + throw new ConflictException("Нельзя участвовать в неопубликованном событии"); + } + + if (requestRepository.existsByRequesterIdAndEventId(userId, eventId)) { + throw new ConflictException("Запрос уже существует"); + } + + if (event.initiator().id().equals(userId)) { + throw new ConflictException( + "Инициатор события не может добавить запрос на участие в своём событии"); + } + + int limit = event.participantLimit(); + if (limit != 0) { + var confirmedCount = requestRepository + .countByEventIdAndStatus(eventId, ParticipationStatus.CONFIRMED); + + if (event.requestModeration()) { + var pendingCount = requestRepository + .countByEventIdAndStatus(eventId, ParticipationStatus.PENDING); + if (confirmedCount + pendingCount >= limit) { + throw new ConflictException("Достигнут лимит запросов на участие"); + } + } else { + if (confirmedCount >= limit) { + throw new ConflictException("Достигнут лимит запросов на участие"); + } + } + } + + ParticipationStatus status; + if (!event.requestModeration() || limit == 0) { + status = ParticipationStatus.CONFIRMED; + } else { + status = ParticipationStatus.PENDING; + } + + ParticipationRequest request = ParticipationRequest.builder() + .requesterId(requester.id()) + .eventId(event.id()) + .status(status) + .created(LocalDateTime.now().truncatedTo(ChronoUnit.MILLIS)) + .build(); + + return RequestMapper.toParticipationRequestDto(requestRepository.save(request)); + } + + public ParticipationRequestDto cancelParticipationRequest(long userId, long requestId) { + ParticipationRequest request = getRequestById(requestId); + + if (!request.getRequesterId().equals(userId)) { + throw new ConflictException("Нельзя отменить чужую заявку"); + } + request.setStatus(ParticipationStatus.CANCELED); + return RequestMapper.toParticipationRequestDto(requestRepository.save(request)); + } + + @Override + public List getRequestsCount(List eventIds) { + return requestRepository.getCountByEventIdsAndStatus(eventIds, ParticipationStatus.CONFIRMED); + } + + @NonNull + private UserDto getUserDtoById(long userId) { + return userFeignRepository.getUserDtoById(userId); + } + + @NonNull + private EventFullDto getEventFullDtoById(long eventId) { + int confirmets = requestRepository.countByEventIdAndStatus(eventId, ParticipationStatus.CONFIRMED); + return eventFeignRepository.systemFindEventById(eventId, confirmets); + } + + @NonNull + private ParticipationRequest getRequestById(long requestId) { + return requestRepository.findById(requestId).orElseThrow( + () -> new NotFoundException("Заявка с id=" + requestId + " не найдена") + ); + } + +} diff --git a/core/request-service/src/main/resources/application.yml b/core/request-service/src/main/resources/application.yml new file mode 100644 index 0000000..a4f1bec --- /dev/null +++ b/core/request-service/src/main/resources/application.yml @@ -0,0 +1,31 @@ +server: + port: 0 + +spring: + application: + name: request-service + config: + import: "optional:configserver:" + cloud: + config: + discovery: + enabled: true + service-id: config-server + fail-fast: true + retry: + initial-interval: 1000 + max-interval: 10000 + multiplier: 2.0 + max-attempts: 20 + +eureka: + client: + serviceUrl: + defaultZone: http://localhost:8761/eureka/ + register-with-eureka: true + fetch-registry: true + instance: + prefer-ip-address: true + instance-id: ${spring.application.name}:${random.value} + lease-renewal-interval-in-seconds: 5 + lease-expiration-duration-in-seconds: 10 diff --git a/core/request-service/src/main/resources/schema-postgres.sql b/core/request-service/src/main/resources/schema-postgres.sql new file mode 100644 index 0000000..e46e196 --- /dev/null +++ b/core/request-service/src/main/resources/schema-postgres.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS requests ( + id bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + created timestamp(3) without time zone, + status varchar(255) check (status in ('PENDING', 'CONFIRMED', 'REJECTED', 'CANCELED')), + event_id bigint, + requester_id bigint, + CONSTRAINT pk_requests PRIMARY KEY (id) +); + diff --git a/core/user-service/pom.xml b/core/user-service/pom.xml new file mode 100644 index 0000000..408bf83 --- /dev/null +++ b/core/user-service/pom.xml @@ -0,0 +1,103 @@ + + + 4.0.0 + + ru.practicum + core + 0.0.1-SNAPSHOT + + + user-service + + + + ru.practicum + aggregation-api + 0.0.1-SNAPSHOT + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-test + test + + + + io.hypersistence + hypersistence-utils-hibernate-63 + 3.15.2 + compile + + + + org.postgresql + postgresql + runtime + + + com.h2database + h2 + runtime + + + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-client + + + org.springframework.cloud + spring-cloud-starter-config + + + + org.springframework.cloud + spring-cloud-starter-loadbalancer + + + com.github.ben-manes.caffeine + caffeine + + + + org.springframework.retry + spring-retry + + + org.springframework.boot + spring-boot-starter-aop + + + + org.springframework.boot + spring-boot-starter-actuator + + + io.micrometer + micrometer-registry-prometheus + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/core/user-service/src/main/java/ru/practicum/ewm/users/UserServiceApp.java b/core/user-service/src/main/java/ru/practicum/ewm/users/UserServiceApp.java new file mode 100644 index 0000000..06801fa --- /dev/null +++ b/core/user-service/src/main/java/ru/practicum/ewm/users/UserServiceApp.java @@ -0,0 +1,33 @@ +package ru.practicum.ewm.users; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.ComponentScan; +import ru.practicum.aggregation.client.event.EventClient; +import ru.practicum.aggregation.client.request.RequestClient; +import ru.practicum.aggregation.client.stats.StatsClient; +import ru.practicum.aggregation.client.user.UserClient; +import ru.practicum.aggregation.config.JacksonConfiguration; + +@SpringBootApplication +@EnableFeignClients(clients = { + UserClient.class, + EventClient.class, + RequestClient.class, + StatsClient.class +}) +@ComponentScan(value = { + "ru.practicum.ewm.users", + "ru.practicum.aggregation.config", + "ru.practicum.aggregation.client", + "ru.practicum.aggregation.error", + "ru.practicum.aggregation.repository" +}) +@ConfigurationPropertiesScan(basePackageClasses = JacksonConfiguration.class) +public class UserServiceApp { + public static void main(String[] args) { + SpringApplication.run(UserServiceApp.class, args); + } +} diff --git a/core/user-service/src/main/java/ru/practicum/ewm/users/controller/AdminUserController.java b/core/user-service/src/main/java/ru/practicum/ewm/users/controller/AdminUserController.java new file mode 100644 index 0000000..5cd10ca --- /dev/null +++ b/core/user-service/src/main/java/ru/practicum/ewm/users/controller/AdminUserController.java @@ -0,0 +1,83 @@ +package ru.practicum.ewm.users.controller; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import ru.practicum.aggregation.dto.user.come.NewUserRequest; +import ru.practicum.aggregation.dto.user.output.UserDto; +import ru.practicum.ewm.users.service.UserService; + +import java.util.List; + +@Slf4j +@RestController +@RequestMapping(path = "/admin/users") +@RequiredArgsConstructor +public class AdminUserController { + + private final UserService userService; + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public UserDto createUser(@RequestBody @Valid NewUserRequest newUserRequest, + @NonNull HttpServletRequest request) { + log.info(""" + ENDPOINT + Добавление нового пользователя + {} {}""", request.getMethod(), request.getRequestURI()); + + return userService.createUser(newUserRequest); + } + + @GetMapping("/{userId}") + public UserDto getUser(@PathVariable Long userId, @NonNull HttpServletRequest request) { + log.info(""" + ENDPOINT + Получение пользователя по ID + {} {}""", request.getMethod(), request.getRequestURI()); + + return userService.getUser(userId); + } + + /** + * Возвращает информацию обо всех пользователях (учитываются параметры ограничения выборки), + * либо о конкретных (учитываются указанные идентификаторы) + * В случае, если по заданным фильтрам не найдено ни одного пользователя, возвращает пустой список + * + * @param ids id пользователей + * @param from количество элементов, которые нужно пропустить для формирования текущего набора + * Default value : 0 + * @param size количество элементов в наборе + * Default value : 10 + */ + @GetMapping + public List findUsers( + @RequestParam(required = false) List ids, + @RequestParam(defaultValue = "0") int from, + @RequestParam(defaultValue = "10") int size, + @NonNull HttpServletRequest request + ) { + log.info(""" + ENDPOINT + Получение информации о пользователях + {} {}""", request.getMethod(), request.getRequestURI()); + + return userService.findUsers(ids, from, size); + } + + @DeleteMapping("/{userId}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteUser(@PathVariable Long userId, @NonNull HttpServletRequest request) { + log.info(""" + ENDPOINT + Удаление пользователя + {} {}""", request.getMethod(), request.getRequestURI()); + + userService.deleteUser(userId); + } + +} diff --git a/core/user-service/src/main/java/ru/practicum/ewm/users/controller/SystemUserController.java b/core/user-service/src/main/java/ru/practicum/ewm/users/controller/SystemUserController.java new file mode 100644 index 0000000..7453c2a --- /dev/null +++ b/core/user-service/src/main/java/ru/practicum/ewm/users/controller/SystemUserController.java @@ -0,0 +1,50 @@ +package ru.practicum.ewm.users.controller; + +import jakarta.validation.constraints.Positive; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; +import ru.practicum.aggregation.dto.user.output.UserDto; +import ru.practicum.aggregation.dto.user.output.UserShortDto; +import ru.practicum.ewm.users.service.UserService; + +import java.util.List; + +@RestController +@RequestMapping(path = "/system/users") +@RequiredArgsConstructor +public class SystemUserController { + + private final UserService userService; + + /** + * Проверка наличия пользователя по ID + * + * @param userId ID пользователя + */ + @GetMapping("/{userId}/check") + public void checkUser(@PathVariable @Positive Long userId) { + userService.remoteCheckUser(userId); + } + + /** + * Получение пользователя по ID + * @param userId ID пользователя + * @return {@link UserDto} + */ + @GetMapping("/{userId}") + UserDto getUser(@PathVariable @Positive Long userId) { + return userService.getUser(userId); + } + + /** + * Получение краткого списка пользователей + * + * @param userIds id пользователей + * @return {@link UserShortDto} + */ + @GetMapping("/short") + public List getShortUsersByIds(@RequestParam List<@Positive Long> userIds) { + return userService.getShortUsersByIds(userIds); + } + +} diff --git a/core/main-service/src/main/java/ru/practicum/ewm/model/User.java b/core/user-service/src/main/java/ru/practicum/ewm/users/entity/User.java similarity index 85% rename from core/main-service/src/main/java/ru/practicum/ewm/model/User.java rename to core/user-service/src/main/java/ru/practicum/ewm/users/entity/User.java index a2815eb..bf2a932 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/model/User.java +++ b/core/user-service/src/main/java/ru/practicum/ewm/users/entity/User.java @@ -1,4 +1,4 @@ -package ru.practicum.ewm.model; +package ru.practicum.ewm.users.entity; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -9,7 +9,7 @@ import lombok.Setter; import lombok.experimental.FieldDefaults; import lombok.experimental.SuperBuilder; -import ru.practicum.ewm.util.entity.BaseEntity; +import ru.practicum.aggregation.model.entity.BaseEntity; @Getter @Setter diff --git a/core/main-service/src/main/java/ru/practicum/ewm/mapper/UserMapper.java b/core/user-service/src/main/java/ru/practicum/ewm/users/mapper/UserMapper.java similarity index 70% rename from core/main-service/src/main/java/ru/practicum/ewm/mapper/UserMapper.java rename to core/user-service/src/main/java/ru/practicum/ewm/users/mapper/UserMapper.java index cfa070f..c705582 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/mapper/UserMapper.java +++ b/core/user-service/src/main/java/ru/practicum/ewm/users/mapper/UserMapper.java @@ -1,11 +1,11 @@ -package ru.practicum.ewm.mapper; +package ru.practicum.ewm.users.mapper; import lombok.experimental.UtilityClass; import org.springframework.lang.NonNull; -import ru.practicum.ewm.dto.user.NewUserRequest; -import ru.practicum.ewm.dto.user.UserDto; -import ru.practicum.ewm.dto.user.UserShortDto; -import ru.practicum.ewm.model.User; +import ru.practicum.aggregation.dto.user.come.NewUserRequest; +import ru.practicum.aggregation.dto.user.output.UserDto; +import ru.practicum.aggregation.dto.user.output.UserShortDto; +import ru.practicum.ewm.users.entity.User; @UtilityClass public class UserMapper { @@ -14,7 +14,6 @@ public UserShortDto toUserShortDto(@NonNull User user) { return UserShortDto.builder() .id(user.getId()) .name(user.getName()) - .email(user.getEmail()) .build(); } @@ -32,4 +31,5 @@ public User toEntity(@NonNull NewUserRequest newUserRequest) { .email(newUserRequest.email()) .build(); } + } \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/ewm/dao/UserRepository.java b/core/user-service/src/main/java/ru/practicum/ewm/users/repository/UserRepository.java similarity index 78% rename from core/main-service/src/main/java/ru/practicum/ewm/dao/UserRepository.java rename to core/user-service/src/main/java/ru/practicum/ewm/users/repository/UserRepository.java index b312916..ad71e36 100644 --- a/core/main-service/src/main/java/ru/practicum/ewm/dao/UserRepository.java +++ b/core/user-service/src/main/java/ru/practicum/ewm/users/repository/UserRepository.java @@ -1,8 +1,8 @@ -package ru.practicum.ewm.dao; +package ru.practicum.ewm.users.repository; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; -import ru.practicum.ewm.model.User; +import ru.practicum.ewm.users.entity.User; import java.util.List; diff --git a/core/user-service/src/main/java/ru/practicum/ewm/users/service/UserService.java b/core/user-service/src/main/java/ru/practicum/ewm/users/service/UserService.java new file mode 100644 index 0000000..5531170 --- /dev/null +++ b/core/user-service/src/main/java/ru/practicum/ewm/users/service/UserService.java @@ -0,0 +1,26 @@ +package ru.practicum.ewm.users.service; + +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.aggregation.dto.user.come.NewUserRequest; +import ru.practicum.aggregation.dto.user.output.UserDto; +import ru.practicum.aggregation.dto.user.output.UserShortDto; + +import java.util.List; + +@Transactional(readOnly = true) +public interface UserService { + + @Transactional + UserDto createUser(NewUserRequest newUserRequest); + + UserDto getUser(long userId); + + void remoteCheckUser(long userId); + + List getShortUsersByIds(List userIds); + + List findUsers(List ids, int from, int size); + + @Transactional + void deleteUser(long userId); +} diff --git a/core/user-service/src/main/java/ru/practicum/ewm/users/service/UserServiceImpl.java b/core/user-service/src/main/java/ru/practicum/ewm/users/service/UserServiceImpl.java new file mode 100644 index 0000000..fad9fe6 --- /dev/null +++ b/core/user-service/src/main/java/ru/practicum/ewm/users/service/UserServiceImpl.java @@ -0,0 +1,81 @@ +package ru.practicum.ewm.users.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Service; +import ru.practicum.aggregation.dto.user.come.NewUserRequest; +import ru.practicum.aggregation.dto.user.output.UserDto; +import ru.practicum.aggregation.dto.user.output.UserShortDto; +import ru.practicum.aggregation.error.exception.bussines.cause.ConflictException; +import ru.practicum.aggregation.error.exception.bussines.cause.NotFoundException; +import ru.practicum.ewm.users.entity.User; +import ru.practicum.ewm.users.mapper.UserMapper; +import ru.practicum.ewm.users.repository.UserRepository; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class UserServiceImpl implements UserService { + + private final UserRepository userRepository; + + @Override + public UserDto createUser(@NonNull NewUserRequest newUserRequest) { + if (userRepository.existsByEmail(newUserRequest.email())) { + throw new ConflictException( + "Пользователь с такой почтой " + newUserRequest.email() + " уже существует"); + } + User user = UserMapper.toEntity(newUserRequest); + return UserMapper.toUserDto(userRepository.save(user)); + } + + @Override + public UserDto getUser(long userId) { + return UserMapper.toUserDto(userRepository.findById(userId).orElseThrow(() -> + new NotFoundException("Пользователь с id=%s не найден".formatted(userId)) + ) + ); + } + + @Override + public void remoteCheckUser(long userId) { + checkUser(userId); + } + + @Override + public List getShortUsersByIds(List userIds) { + return userRepository.findAllById(userIds).stream() + .map(UserMapper::toUserShortDto) + .toList(); + } + + @Override + public List findUsers(List ids, int from, int size) { + PageRequest page = PageRequest.of(from / size, size); + List users; + if (ids == null || ids.isEmpty()) { + users = userRepository.findAll(page).getContent(); + } else { + users = userRepository.findAllByIdIn(ids, page); + } + return users.stream() + .map(UserMapper::toUserDto) + .collect(Collectors.toList()); + } + + @Override + public void deleteUser(long userId) { + checkUser(userId); + userRepository.deleteById(userId); + } + + private void checkUser(long userId) { + if (!userRepository.existsById(userId)) { + throw new NotFoundException("Пользователь с id=%s не найден".formatted(userId)); + } + } + +} diff --git a/core/user-service/src/main/resources/application.yml b/core/user-service/src/main/resources/application.yml new file mode 100644 index 0000000..1ef89d8 --- /dev/null +++ b/core/user-service/src/main/resources/application.yml @@ -0,0 +1,31 @@ +server: + port: 0 + +spring: + application: + name: user-service + config: + import: "optional:configserver:" + cloud: + config: + discovery: + enabled: true + service-id: config-server + fail-fast: true + retry: + initial-interval: 1000 + max-interval: 10000 + multiplier: 2.0 + max-attempts: 20 + +eureka: + client: + serviceUrl: + defaultZone: http://localhost:8761/eureka/ + register-with-eureka: true + fetch-registry: true + instance: + prefer-ip-address: true + instance-id: ${spring.application.name}:${random.value} + lease-renewal-interval-in-seconds: 5 + lease-expiration-duration-in-seconds: 10 diff --git a/core/user-service/src/main/resources/schema-postgres.sql b/core/user-service/src/main/resources/schema-postgres.sql new file mode 100644 index 0000000..63894cb --- /dev/null +++ b/core/user-service/src/main/resources/schema-postgres.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS users ( + id bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + email varchar(255) NOT NULL, + name varchar(255) NOT NULL, + CONSTRAINT pk_users PRIMARY KEY (id) +); +CREATE UNIQUE INDEX IF NOT EXISTS idx_users_email ON users (email); diff --git a/docker-compose.yml b/docker-compose.yml index 7245a40..4a7e415 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,3 +1,7 @@ +volumes: + postgres_main-db_data: + postgres_stats-db_data: + services: main-db: image: postgres:16.1 @@ -8,10 +12,13 @@ services: - POSTGRES_DB=ewm-main - POSTGRES_USER=dbuser - POSTGRES_PASSWORD=12345 + volumes: + - ./docker-init.sql:/docker-entrypoint-initdb.d/docker-init.sql:ro + - postgres_main-db_data:/var/lib/postgresql/data healthcheck: - test: pg_isready -q -d $$POSTGRES_DB -U $$POSTGRES_USER - timeout: 5s + test: [ "CMD-SHELL", "pg_isready -U dbuser -d ewm-main" ] interval: 5s + timeout: 5s retries: 10 stats-db: @@ -23,6 +30,8 @@ services: - POSTGRES_DB=ewm-stats - POSTGRES_USER=dbuser - POSTGRES_PASSWORD=12345 + volumes: + - postgres_stats-db_data:/var/lib/postgresql/data healthcheck: test: pg_isready -q -d $$POSTGRES_DB -U $$POSTGRES_USER timeout: 5s diff --git a/docker-init.sql b/docker-init.sql new file mode 100644 index 0000000..731876d --- /dev/null +++ b/docker-init.sql @@ -0,0 +1,20 @@ +CREATE DATABASE "ewm-users"; +CREATE DATABASE "ewm-events"; +CREATE DATABASE "ewm-requests"; +CREATE DATABASE "ewm-ratings"; + +\c "ewm-users" +CREATE SCHEMA IF NOT EXISTS user_service; +GRANT ALL PRIVILEGES ON SCHEMA user_service TO dbuser; + +\c "ewm-events" +CREATE SCHEMA IF NOT EXISTS event_service; +GRANT ALL PRIVILEGES ON SCHEMA event_service TO dbuser; + +\c "ewm-requests" +CREATE SCHEMA IF NOT EXISTS request_service; +GRANT ALL PRIVILEGES ON SCHEMA request_service TO dbuser; + +\c "ewm-ratings" +CREATE SCHEMA IF NOT EXISTS rating_service; +GRANT ALL PRIVILEGES ON SCHEMA rating_service TO dbuser; diff --git a/image.png b/image.png deleted file mode 100644 index 318bfe5..0000000 Binary files a/image.png and /dev/null differ diff --git a/infra/config-repo/application.yml b/infra/config-repo/application.yml index 9b5ac24..b560087 100644 --- a/infra/config-repo/application.yml +++ b/infra/config-repo/application.yml @@ -1,18 +1,19 @@ spring: + output: + ansi: + enabled: ALWAYS main: banner-mode: off log-startup-info: false - jackson: - serialization.INDENT_OUTPUT: true logging: level: root: info org: - springframework: error + springframework: info management: endpoints: web: exposure: - include: health, metrics, prometheus, info, mappings \ No newline at end of file + include: health, metrics, prometheus, info, mappings, refresh \ No newline at end of file diff --git a/infra/config-repo/core/event-service.yml b/infra/config-repo/core/event-service.yml new file mode 100644 index 0000000..d9ff588 --- /dev/null +++ b/infra/config-repo/core/event-service.yml @@ -0,0 +1,49 @@ +spring: + sql.init: + mode: ALWAYS + schema-locations: classpath:schema-postgres.sql + jackson: + serialization.INDENT_OUTPUT: true + datasource: + driver-class-name: org.postgresql.Driver + url: jdbc:postgresql://localhost:6541/ewm-events?currentSchema=event_service + username: dbuser + password: 12345 + jpa: + generate-ddl: false + hibernate.ddl-auto: validate + show-sql: false + properties: + hibernate: + format_sql: true + cloud: + openfeign: + okhttp: + enabled: true + client: + config: + default: + connectTimeout: 3000 + readTimeout: 4000 + loggerLevel: basic + circuitbreaker: + enabled: true + +resilience4j: + circuitbreaker: + configs: + default: + sliding-window-size: 5 + failure-rate-threshold: 50 + minimum-number-of-calls: 3 + slow-call-duration-threshold: 3s + slow-call-rate-threshold: 50 + wait-duration-in-open-state: 10s + permitted-number-of-calls-in-half-open-state: 3 + ignore-exceptions: + - ru.practicum.aggregation.error.exception.bussines.ServiceProcessingException + - jakarta.validation.ValidationException + timelimiter: + configs: + default: + timeout-duration: 3s diff --git a/infra/config-repo/core/main-service.yaml b/infra/config-repo/core/main-service.yaml deleted file mode 100644 index 15b541d..0000000 --- a/infra/config-repo/core/main-service.yaml +++ /dev/null @@ -1,21 +0,0 @@ -spring: - sql.init: - mode: ALWAYS - schema-locations: classpath:schema-postgres.sql - datasource: - driver-class-name: org.postgresql.Driver - url: jdbc:postgresql://localhost:6541/ewm-main - username: dbuser - password: 12345 - jpa: - generate-ddl: false - hibernate.ddl-auto: validate - show-sql: true - properties: - hibernate: - format_sql: true - -logging: - org: - zalando: - logbook: trace diff --git a/infra/config-repo/core/rating-service.yml b/infra/config-repo/core/rating-service.yml new file mode 100644 index 0000000..d1bc18a --- /dev/null +++ b/infra/config-repo/core/rating-service.yml @@ -0,0 +1,50 @@ +spring: + sql.init: + mode: ALWAYS + schema-locations: classpath:schema-postgres.sql + jackson: + serialization.INDENT_OUTPUT: true + datasource: + driver-class-name: org.postgresql.Driver + url: jdbc:postgresql://localhost:6541/ewm-ratings?currentSchema=rating_service + username: dbuser + password: 12345 + jpa: + generate-ddl: false + hibernate.ddl-auto: validate + show-sql: false + properties: + hibernate: + format_sql: true + cloud: + openfeign: + okhttp: + enabled: true + client: + config: + default: + connectTimeout: 2000 + readTimeout: 3000 + loggerLevel: basic + circuitbreaker: + enabled: true + +resilience4j: + circuitbreaker: + configs: + default: + sliding-window-size: 5 + failure-rate-threshold: 50 + minimum-number-of-calls: 3 + slow-call-duration-threshold: 3s + slow-call-rate-threshold: 50 + wait-duration-in-open-state: 10s + permitted-number-of-calls-in-half-open-state: 3 + ignore-exceptions: + - ru.practicum.aggregation.error.exception.bussines.ServiceProcessingException + - jakarta.validation.ValidationException + timelimiter: + configs: + default: + timeout-duration: 3s + diff --git a/infra/config-repo/core/request-service.yml b/infra/config-repo/core/request-service.yml new file mode 100644 index 0000000..70d5efd --- /dev/null +++ b/infra/config-repo/core/request-service.yml @@ -0,0 +1,50 @@ +spring: + sql.init: + mode: ALWAYS + schema-locations: classpath:schema-postgres.sql + jackson: + serialization.INDENT_OUTPUT: true + datasource: + driver-class-name: org.postgresql.Driver + url: jdbc:postgresql://localhost:6541/ewm-requests?currentSchema=request_service + username: dbuser + password: 12345 + jpa: + generate-ddl: false + hibernate.ddl-auto: validate + show-sql: false + properties: + hibernate: + format_sql: true + cloud: + openfeign: + okhttp: + enabled: true + client: + config: + default: + connectTimeout: 2000 + readTimeout: 3000 + loggerLevel: basic + circuitbreaker: + enabled: true + +resilience4j: + circuitbreaker: + configs: + default: + sliding-window-size: 5 + failure-rate-threshold: 50 + minimum-number-of-calls: 3 + slow-call-duration-threshold: 3s + slow-call-rate-threshold: 50 + wait-duration-in-open-state: 10s + permitted-number-of-calls-in-half-open-state: 3 + ignore-exceptions: + - ru.practicum.aggregation.error.exception.bussines.ServiceProcessingException + - jakarta.validation.ValidationException + timelimiter: + configs: + default: + timeout-duration: 3s + diff --git a/infra/config-repo/core/user-service.yml b/infra/config-repo/core/user-service.yml new file mode 100644 index 0000000..663a106 --- /dev/null +++ b/infra/config-repo/core/user-service.yml @@ -0,0 +1,50 @@ +spring: + sql.init: + mode: ALWAYS + schema-locations: classpath:schema-postgres.sql + jackson: + serialization.INDENT_OUTPUT: true + datasource: + driver-class-name: org.postgresql.Driver + url: jdbc:postgresql://localhost:6541/ewm-users?currentSchema=user_service + username: dbuser + password: 12345 + jpa: + generate-ddl: false + hibernate.ddl-auto: validate + show-sql: false + properties: + hibernate: + format_sql: true + cloud: + openfeign: + okhttp: + enabled: true + client: + config: + default: + connectTimeout: 2000 + readTimeout: 3000 + loggerLevel: basic + circuitbreaker: + enabled: true + +resilience4j: + circuitbreaker: + configs: + default: + sliding-window-size: 5 + failure-rate-threshold: 50 + minimum-number-of-calls: 3 + slow-call-duration-threshold: 3s + slow-call-rate-threshold: 50 + wait-duration-in-open-state: 10s + permitted-number-of-calls-in-half-open-state: 3 + ignore-exceptions: + - ru.practicum.aggregation.error.exception.bussines.ServiceProcessingException + - jakarta.validation.ValidationException + timelimiter: + configs: + default: + timeout-duration: 3s + diff --git a/infra/config-repo/infra/gateway-server.yml b/infra/config-repo/infra/gateway-server.yml index 279928a..108cb4d 100644 --- a/infra/config-repo/infra/gateway-server.yml +++ b/infra/config-repo/infra/gateway-server.yml @@ -1,82 +1,94 @@ -server: - port: 8080 - spring: cloud: gateway: - discovery: - locator: - enabled: false - routes: + server: + webflux: + httpclient: + wiretap: true + httpserver: + wiretap: true + discovery: + locator: + enabled: false + routes: + + # ==================== ADMIN ==================== + + - id: admin-users + order: 0 + uri: lb://user-service + predicates: + - Path=/admin/users/** - - id: ratings - uri: lb://main-service - predicates: - - Path=/users/*/events/*/likes/** + - id: admin-categories + order: 0 + uri: lb://event-service + predicates: + - Path=/admin/categories/** - - id: user-requests - uri: lb://main-service - predicates: - - Path=/users/*/requests/** + - id: admin-compilations + order: 0 + uri: lb://event-service + predicates: + - Path=/admin/compilations/** - - id: user-events - uri: lb://main-service - predicates: - - Path=/users/*/events/** + - id: admin-events + order: 0 + uri: lb://event-service + predicates: + - Path=/admin/events/** - - id: admin-users - uri: lb://main-service - predicates: - - Path=/admin/users/** + # ==================== USER ==================== - - id: admin-categories - uri: lb://main-service - predicates: - - Path=/admin/categories/** + - id: user-ratings + order: -2 + uri: lb://rating-service + predicates: + - Path=/users/{userId}/events/{eventId}/likes/** - - id: admin-compilations - uri: lb://main-service - predicates: - - Path=/admin/compilations/** + - id: user-events-requests + order: -1 + uri: lb://event-service + predicates: + - Path=/users/{userId}/events/{eventId}/requests/** - - id: admin-events - uri: lb://main-service - predicates: - - Path=/admin/events/** + - id: user-events + order: 0 + uri: lb://event-service + predicates: + - Path=/users/{userId}/events/** - - id: categories - uri: lb://main-service - predicates: - - Path=/categories/** + - id: user-requests + order: 0 + uri: lb://request-service + predicates: + - Path=/users/{userId}/requests/** - - id: compilations - uri: lb://main-service - predicates: - - Path=/compilations/** + # ==================== FREE ==================== - - id: events - uri: lb://main-service - predicates: - - Path=/events/** + - id: free-events + order: 0 + uri: lb://event-service + predicates: + - Path=/events/** - globalcors: - add-to-simple-url-handler-mapping: true - cors-configurations: - '[/**]': - allowed-origins: - - http://localhost:3000 - - http://localhost:5173 - - http://localhost:8443 - allowed-methods: - - GET - - POST - - PUT - - PATCH - - DELETE - - OPTIONS - allowed-headers: - - '*' - exposed-headers: - - Location - allow-credentials: true - max-age: 3600 + - id: free-categories + order: 0 + uri: lb://event-service + predicates: + - Path=/categories/** + + - id: free-compilations + order: 0 + uri: lb://event-service + predicates: + - Path=/compilations/** + +management: + endpoint: + gateway: + access: read-only + endpoints: + web: + exposure: + include: gateway, health, metrics, prometheus, info, mappings, refresh diff --git a/infra/config-repo/stats/stats-server.yml b/infra/config-repo/stats/stats-server.yml index 1192cf2..92bfb29 100644 --- a/infra/config-repo/stats/stats-server.yml +++ b/infra/config-repo/stats/stats-server.yml @@ -1,16 +1,18 @@ spring: - sql: - init: - mode: always + sql.init: + mode: ALWAYS + schema-locations: classpath:schema-postgres.sql + jackson: + serialization.INDENT_OUTPUT: true datasource: driver-class-name: org.postgresql.Driver url: jdbc:postgresql://localhost:6542/ewm-stats username: dbuser password: 12345 jpa: - hibernate: - ddl-auto: none + generate-ddl: false + hibernate.ddl-auto: validate + show-sql: false properties: hibernate: format_sql: true - show-sql: true diff --git a/infra/config-server/pom.xml b/infra/config-server/pom.xml index 65ffdf8..7220a8e 100644 --- a/infra/config-server/pom.xml +++ b/infra/config-server/pom.xml @@ -22,18 +22,6 @@ org.springframework.cloud spring-cloud-starter-netflix-eureka-client - - org.springframework.cloud - spring-cloud-starter-loadbalancer - - - com.github.ben-manes.caffeine - caffeine - - - org.springframework.retry - spring-retry - diff --git a/infra/config-server/src/main/resources/application.yml b/infra/config-server/src/main/resources/application.yml index 35e6da8..de85a5e 100644 --- a/infra/config-server/src/main/resources/application.yml +++ b/infra/config-server/src/main/resources/application.yml @@ -2,13 +2,14 @@ server: port: 0 spring: + application: + name: config-server + output: + ansi: + enabled: ALWAYS main: banner-mode: off log-startup-info: false - jackson: - serialization.INDENT_OUTPUT: true - application: - name: config-server profiles: active: native cloud: @@ -35,6 +36,12 @@ eureka: lease-renewal-interval-in-seconds: 5 lease-expiration-duration-in-seconds: 10 +management: + endpoints: + web: + exposure: + include: health, metrics, prometheus, info, mappings, refresh + logging: level: root: info diff --git a/infra/discovery-server/pom.xml b/infra/discovery-server/pom.xml index 98616f4..5f2fb1c 100644 --- a/infra/discovery-server/pom.xml +++ b/infra/discovery-server/pom.xml @@ -17,18 +17,6 @@ org.springframework.cloud spring-cloud-starter-netflix-eureka-server - - org.springframework.retry - spring-retry - - - org.springframework.cloud - spring-cloud-starter-loadbalancer - - - com.github.ben-manes.caffeine - caffeine - diff --git a/infra/discovery-server/src/main/resources/application.yml b/infra/discovery-server/src/main/resources/application.yml index 4d26970..277b2e1 100644 --- a/infra/discovery-server/src/main/resources/application.yml +++ b/infra/discovery-server/src/main/resources/application.yml @@ -2,13 +2,14 @@ server: port: 8761 spring: + application: + name: discovery-server + output: + ansi: + enabled: ALWAYS main: banner-mode: off log-startup-info: false - jackson: - serialization.INDENT_OUTPUT: true - application: - name: discovery-server eureka: client: @@ -17,14 +18,14 @@ eureka: register-with-eureka: false fetch-registry: false -logging: - level: - root: info - org: - springframework: error - management: endpoints: web: exposure: - include: health, metrics, prometheus, info, mappings \ No newline at end of file + include: health, metrics, prometheus, info, mappings + +logging: + level: + root: info + org: + springframework: error \ No newline at end of file diff --git a/infra/gateway-server/pom.xml b/infra/gateway-server/pom.xml index e260520..c13b18a 100644 --- a/infra/gateway-server/pom.xml +++ b/infra/gateway-server/pom.xml @@ -18,10 +18,6 @@ org.springframework.cloud spring-cloud-starter-gateway-server-webflux - - org.springframework.cloud - spring-cloud-starter-loadbalancer - org.springframework.cloud spring-cloud-starter-netflix-eureka-client @@ -30,10 +26,6 @@ org.springframework.cloud spring-cloud-starter-config - - com.github.ben-manes.caffeine - caffeine - org.projectlombok lombok diff --git a/infra/gateway-server/src/main/java/ru/practicum/ewm/infra/gateway/error/ApiError.java b/infra/gateway-server/src/main/java/ru/practicum/ewm/infra/gateway/error/ApiError.java new file mode 100644 index 0000000..968c7f2 --- /dev/null +++ b/infra/gateway-server/src/main/java/ru/practicum/ewm/infra/gateway/error/ApiError.java @@ -0,0 +1,24 @@ +package ru.practicum.ewm.infra.gateway.error; + +import lombok.Builder; +import org.springframework.http.HttpStatus; + +import java.time.LocalDateTime; + +/** + * @param status Код статуса HTTP-ответа + * @param reason Общее описание причины ошибки + * @param message Сообщение об ошибке + * @param errors Список стектрейсов или описания ошибок + * @param timestamp Дата и время когда произошла ошибка (в формате "yyyy-MM-dd HH:mm:ss") + */ +@Builder(toBuilder = true) +public record ApiError( + HttpStatus status, + String reason, + String message, + String errors, + LocalDateTime timestamp +) { +} + diff --git a/infra/gateway-server/src/main/java/ru/practicum/ewm/infra/gateway/error/GeneralErrorHandler.java b/infra/gateway-server/src/main/java/ru/practicum/ewm/infra/gateway/error/GeneralErrorHandler.java new file mode 100644 index 0000000..8659b4a --- /dev/null +++ b/infra/gateway-server/src/main/java/ru/practicum/ewm/infra/gateway/error/GeneralErrorHandler.java @@ -0,0 +1,40 @@ +package ru.practicum.ewm.infra.gateway.error; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.lang.NonNull; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.reactive.resource.NoResourceFoundException; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.time.LocalDateTime; + +import static org.springframework.http.HttpStatus.*; + +@Slf4j +@RestControllerAdvice +public class GeneralErrorHandler { + + @ExceptionHandler + @ResponseStatus(INTERNAL_SERVER_ERROR) + public ApiError handleNoResourceFoundException(NoResourceFoundException ex) { + log.info("500 Internal Server Error (NoResourceFoundException) {}", ex.getMessage(), ex); + String stackTrace = getStackTrace(ex); + return ApiError.builder() + .status(INTERNAL_SERVER_ERROR) + .reason("Resource not found") + .message(ex.getMessage()) + .errors(stackTrace) + .timestamp(LocalDateTime.now()) + .build(); + } + + private String getStackTrace(@NonNull Exception e) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + e.printStackTrace(pw); + return sw.toString(); + } +} diff --git a/infra/gateway-server/src/main/resources/application.yml b/infra/gateway-server/src/main/resources/application.yml index 3b2e0a2..f5cf8f5 100644 --- a/infra/gateway-server/src/main/resources/application.yml +++ b/infra/gateway-server/src/main/resources/application.yml @@ -1,8 +1,11 @@ +server: + port: 8080 + spring: application: name: gateway-server config: - import: "optional:configserver:" + import: "configserver:" cloud: config: discovery: @@ -10,9 +13,9 @@ spring: service-id: config-server retry: initial-interval: 1000 - max-interval: 5000 - multiplier: 1.5 - max-attempts: 6 + max-interval: 10000 + multiplier: 2.0 + max-attempts: 20 eureka: client: diff --git a/infra/pom.xml b/infra/pom.xml index e6130e1..3d41116 100644 --- a/infra/pom.xml +++ b/infra/pom.xml @@ -20,6 +20,19 @@ + + org.springframework.cloud + spring-cloud-starter-loadbalancer + + + com.github.ben-manes.caffeine + caffeine + + + org.springframework.retry + spring-retry + + org.springframework.boot spring-boot-starter-actuator diff --git a/pom.xml b/pom.xml index 343ca5b..73091ef 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,7 @@ stats infra core - + ru.practicum explore-with-me @@ -167,6 +167,7 @@ + check diff --git a/readme.md b/readme.md index 5a2a350..11a93aa 100644 --- a/readme.md +++ b/readme.md @@ -4,19 +4,41 @@ ![Static Badge](https://img.shields.io/badge/Spring_Boot-3.5.9-green) ![Static Badge](https://img.shields.io/badge/Lombok-red) ![Static Badge](https://img.shields.io/badge/JPA--specification-8A2BE2) -![Static Badge](https://img.shields.io/badge/RestTemplate-538681) +![Static Badge](https://img.shields.io/badge/SpringCloud-538681) ![Static Badge](https://img.shields.io/badge/PostgreSQL-16.1-blue) ![Static Badge](https://img.shields.io/badge/docker_compose-blue) -![Static Badge](https://img.shields.io/badge/H2_database-blue) -![Static Badge](https://img.shields.io/badge/JUnit-5-orange) -![Static Badge](https://img.shields.io/badge/Mockito-green) -![Static Badge](https://img.shields.io/badge/Jacoco-red) ![Static Badge](https://img.shields.io/badge/Maven-orange) -![Static Badge](https://img.shields.io/badge/Multi--module--project-8A2BE2) +![Static Badge](https://img.shields.io/badge/Microservices-8A2BE2) ## Бэкэнд для сервиса поиска мероприятий -![](image.png) + +### Configuration +configuration files location: infra/config-repo + +### Microservice map +![ms](.img/microservices.png) + +### External API +![external-api.png](.img/external-api.png) +![external-api-2.png](.img/external-api-2.png) + +### Internal API +![internal-api.png](.img/internal-api.png) ### Database map -![db](db-schema.png) \ No newline at end of file + +ewm-main/event-service +![ewm-main-event-service.png](.img/ewm-main-event-service.png) + +ewm-main/rating-service +![ewm-main-rating-service.png](.img/ewm-main-rating-service.png) + +ewm-main/request-service +![ewm-main-request-service.png](.img/ewm-main-request-service.png) + +ewm-main/user-service +![ewm-main-user-service.png](.img/ewm-main-user-service.png) + +ewm-stats/stats-server +![ewm-stats-stats-server.png](.img/ewm-stats-stats-server.png) \ No newline at end of file diff --git a/stats/pom.xml b/stats/pom.xml index 007e33b..494a796 100644 --- a/stats/pom.xml +++ b/stats/pom.xml @@ -16,7 +16,6 @@ stat-dto stats-server - stat-client \ No newline at end of file diff --git a/stats/stat-client/pom.xml b/stats/stat-client/pom.xml deleted file mode 100644 index ea100b1..0000000 --- a/stats/stat-client/pom.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - 4.0.0 - - ru.practicum - stats - 0.0.1-SNAPSHOT - - - stat-client - jar - - - - ru.practicum - stat-dto - 0.0.1-SNAPSHOT - - - org.projectlombok - lombok - true - - - org.springframework.boot - spring-boot-starter-web - - - org.apache.httpcomponents.client5 - httpclient5 - - - org.springframework.cloud - spring-cloud-commons - - - org.springframework.retry - spring-retry - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - - org.projectlombok - lombok - ${lombok.version} - - - - - - - - \ No newline at end of file diff --git a/stats/stat-client/src/main/java/ru/practicum/stat/client/StatClient.java b/stats/stat-client/src/main/java/ru/practicum/stat/client/StatClient.java deleted file mode 100644 index 579f3a5..0000000 --- a/stats/stat-client/src/main/java/ru/practicum/stat/client/StatClient.java +++ /dev/null @@ -1,127 +0,0 @@ -package ru.practicum.stat.client; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.web.client.RestTemplateBuilder; -import org.springframework.cloud.client.ServiceInstance; -import org.springframework.cloud.client.discovery.DiscoveryClient; -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.MediaType; -import org.springframework.lang.NonNull; -import org.springframework.retry.backoff.FixedBackOffPolicy; -import org.springframework.retry.policy.MaxAttemptsRetryPolicy; -import org.springframework.retry.support.RetryTemplate; -import org.springframework.stereotype.Component; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.util.UriComponentsBuilder; -import ru.practicum.stat.client.exception.StatsServerUnavailable; -import ru.practicum.stat.dto.EndpointHitDto; -import ru.practicum.stat.dto.StatsRequest; -import ru.practicum.stat.dto.ViewStatsDto; - -import java.net.URI; -import java.util.List; - -@Slf4j -@Component -public class StatClient { - - private final RestTemplate rest; - - private final RetryTemplate retryTemplate; - - private final DiscoveryClient discoveryClient; - - public StatClient(@NonNull RestTemplateBuilder builder, - @NonNull DiscoveryClient discoveryClient) { - this.discoveryClient = discoveryClient; - this.retryTemplate = createRetryTemplate(); - this.rest = builder.build(); - } - - public void hit(EndpointHitDto endpointHitDto) { - try { - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - - HttpEntity requestEntity = new HttpEntity<>(endpointHitDto, headers); - - rest.exchange( - makeUri("/hit"), - HttpMethod.POST, - requestEntity, - Void.class - ); - } catch (Exception e) { - log.error("Ошибка записи: {}", endpointHitDto, e); - } - - } - - public List getStat(StatsRequest statsRequest) { - try { - UriComponentsBuilder builder = UriComponentsBuilder - .fromUri(makeUri("/stats")) - .queryParam("start", statsRequest.getStart()) - .queryParam("end", statsRequest.getEnd()) - .queryParam("unique", statsRequest.getUnique()); - - List uris = statsRequest.getUris(); - - if (uris != null && !uris.isEmpty()) { - builder.queryParam("uris", uris); - } - - URI uri = builder.encode().build().toUri(); - - return rest.exchange( - uri, - HttpMethod.GET, - null, - new ParameterizedTypeReference>() { - } - ).getBody(); - - } catch (Exception e) { - log.error("Ошибка записи: {}", statsRequest, e); - return null; - } - } - - private ServiceInstance getInstance() { - String statsServiceId = "stats-server"; - try { - return discoveryClient - .getInstances(statsServiceId) - .getFirst(); - } catch (Exception exception) { - throw new StatsServerUnavailable( - "Ошибка обнаружения адреса сервиса статистики с id: " + statsServiceId, - exception - ); - } - } - - @NonNull - private URI makeUri(@NonNull String path) { - ServiceInstance instance = retryTemplate.execute(cxt -> getInstance()); - return URI.create("http://" + instance.getHost() + ":" + instance.getPort() + path); - } - - @NonNull - private RetryTemplate createRetryTemplate() { - RetryTemplate retryTemplate = new RetryTemplate(); - - FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy(); - fixedBackOffPolicy.setBackOffPeriod(3000L); - retryTemplate.setBackOffPolicy(fixedBackOffPolicy); - - MaxAttemptsRetryPolicy retryPolicy = new MaxAttemptsRetryPolicy(); - retryPolicy.setMaxAttempts(3); - retryTemplate.setRetryPolicy(retryPolicy); - - return retryTemplate; - } -} diff --git a/stats/stat-client/src/main/java/ru/practicum/stat/client/exception/StatsServerUnavailable.java b/stats/stat-client/src/main/java/ru/practicum/stat/client/exception/StatsServerUnavailable.java deleted file mode 100644 index 47d4df3..0000000 --- a/stats/stat-client/src/main/java/ru/practicum/stat/client/exception/StatsServerUnavailable.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.practicum.stat.client.exception; - -public class StatsServerUnavailable extends RuntimeException { - public StatsServerUnavailable(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/stats/stat-dto/src/main/java/ru/practicum/stat/dto/EndpointHitDto.java b/stats/stat-dto/src/main/java/ru/practicum/stat/dto/EndpointHitDto.java index c480d43..4883b3a 100644 --- a/stats/stat-dto/src/main/java/ru/practicum/stat/dto/EndpointHitDto.java +++ b/stats/stat-dto/src/main/java/ru/practicum/stat/dto/EndpointHitDto.java @@ -16,6 +16,7 @@ @NoArgsConstructor @AllArgsConstructor public class EndpointHitDto { + @NotBlank(message = "Название приложения не может быть пустым") @Size(max = 255, message = "APP слишком длинный (макс. 255 символов)") private String app; diff --git a/stats/stat-dto/src/main/java/ru/practicum/stat/dto/StatsRequest.java b/stats/stat-dto/src/main/java/ru/practicum/stat/dto/StatsRequest.java deleted file mode 100644 index f2fca59..0000000 --- a/stats/stat-dto/src/main/java/ru/practicum/stat/dto/StatsRequest.java +++ /dev/null @@ -1,27 +0,0 @@ -package ru.practicum.stat.dto; - -import jakarta.validation.constraints.NotNull; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.util.List; - -@Data -@Builder -@AllArgsConstructor -@NoArgsConstructor -public class StatsRequest { - - @NotNull - private String start; - - @NotNull - private String end; - - private List uris; - - @Builder.Default - private Boolean unique = false; -} \ No newline at end of file diff --git a/stats/stats-server/pom.xml b/stats/stats-server/pom.xml index 6d5223e..4f59218 100644 --- a/stats/stats-server/pom.xml +++ b/stats/stats-server/pom.xml @@ -21,8 +21,7 @@ org.projectlombok lombok - - + provided org.springframework.boot @@ -87,7 +86,6 @@ io.micrometer micrometer-registry-prometheus - diff --git a/stats/stats-server/src/main/java/ru/practicum/stat/server/StatServerApp.java b/stats/stats-server/src/main/java/ru/practicum/stat/server/StatsServerApp.java similarity index 73% rename from stats/stats-server/src/main/java/ru/practicum/stat/server/StatServerApp.java rename to stats/stats-server/src/main/java/ru/practicum/stat/server/StatsServerApp.java index 9a0911f..31068cb 100644 --- a/stats/stats-server/src/main/java/ru/practicum/stat/server/StatServerApp.java +++ b/stats/stats-server/src/main/java/ru/practicum/stat/server/StatsServerApp.java @@ -4,8 +4,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication -public class StatServerApp { +public class StatsServerApp { public static void main(String[] args) { - SpringApplication.run(StatServerApp.class, args); + SpringApplication.run(StatsServerApp.class, args); } } diff --git a/stats/stats-server/src/main/java/ru/practicum/stat/server/util/config/StatServerConfiguration.java b/stats/stats-server/src/main/java/ru/practicum/stat/server/config/JacksonConfiguration.java similarity index 91% rename from stats/stats-server/src/main/java/ru/practicum/stat/server/util/config/StatServerConfiguration.java rename to stats/stats-server/src/main/java/ru/practicum/stat/server/config/JacksonConfiguration.java index 669e26a..dec31a4 100644 --- a/stats/stats-server/src/main/java/ru/practicum/stat/server/util/config/StatServerConfiguration.java +++ b/stats/stats-server/src/main/java/ru/practicum/stat/server/config/JacksonConfiguration.java @@ -1,4 +1,4 @@ -package ru.practicum.stat.server.util.config; +package ru.practicum.stat.server.config; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; @@ -9,7 +9,7 @@ import java.time.format.DateTimeFormatter; @SpringBootConfiguration -public class StatServerConfiguration { +public class JacksonConfiguration { @Bean public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() { diff --git a/stats/stats-server/src/main/java/ru/practicum/stat/server/config/WebConfig.java b/stats/stats-server/src/main/java/ru/practicum/stat/server/config/WebConfig.java new file mode 100644 index 0000000..61bd1da --- /dev/null +++ b/stats/stats-server/src/main/java/ru/practicum/stat/server/config/WebConfig.java @@ -0,0 +1,20 @@ +package ru.practicum.stat.server.config; + +import org.jspecify.annotations.NonNull; +import org.springframework.context.annotation.Configuration; +import org.springframework.format.FormatterRegistry; +import org.springframework.format.datetime.standard.DateTimeFormatterRegistrar; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import java.time.format.DateTimeFormatter; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + + @Override + public void addFormatters(@NonNull FormatterRegistry registry) { + DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar(); + registrar.setUseIsoFormat(false); + registrar.setDateTimeFormatter(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + registrar.registerFormatters(registry); + } +} \ No newline at end of file diff --git a/stats/stats-server/src/main/java/ru/practicum/stat/server/controller/StatController.java b/stats/stats-server/src/main/java/ru/practicum/stat/server/controller/StatController.java index dc37f5b..8434216 100644 --- a/stats/stats-server/src/main/java/ru/practicum/stat/server/controller/StatController.java +++ b/stats/stats-server/src/main/java/ru/practicum/stat/server/controller/StatController.java @@ -1,6 +1,7 @@ package ru.practicum.stat.server.controller; import jakarta.validation.Valid; +import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.format.annotation.DateTimeFormat; @@ -13,16 +14,16 @@ import java.time.LocalDateTime; import java.util.List; +@Slf4j @RestController @RequiredArgsConstructor -@Slf4j public class StatController { private final StatService statService; @PostMapping("/hit") @ResponseStatus(HttpStatus.CREATED) - public void hit(@Valid @RequestBody EndpointHitDto endpointHitDto) { + public void hit(@NonNull @Valid @RequestBody EndpointHitDto endpointHitDto) { log.info("Сохранение статистики: сервис={}, uri={}, ip={}", endpointHitDto.getApp(), endpointHitDto.getUri(), endpointHitDto.getIp()); statService.saveHit(endpointHitDto); diff --git a/stats/stats-server/src/main/java/ru/practicum/stat/server/model/EndpointHit.java b/stats/stats-server/src/main/java/ru/practicum/stat/server/entity/EndpointHit.java similarity index 68% rename from stats/stats-server/src/main/java/ru/practicum/stat/server/model/EndpointHit.java rename to stats/stats-server/src/main/java/ru/practicum/stat/server/entity/EndpointHit.java index d8181dc..120b5cb 100644 --- a/stats/stats-server/src/main/java/ru/practicum/stat/server/model/EndpointHit.java +++ b/stats/stats-server/src/main/java/ru/practicum/stat/server/entity/EndpointHit.java @@ -1,4 +1,4 @@ -package ru.practicum.stat.server.model; +package ru.practicum.stat.server.entity; import jakarta.persistence.*; import lombok.*; @@ -13,6 +13,7 @@ @AllArgsConstructor @NoArgsConstructor public class EndpointHit { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @@ -28,4 +29,16 @@ public class EndpointHit { @Column(name = "hit_timestamp", nullable = false) private LocalDateTime timestamp; + + @Override + public String toString() { + return """ + { + "id": "%s", + "app": "%s", + "uri": "%s", + "ip": "%s", + "timestamp": "%s" + }""".formatted(id, app, uri, ip, timestamp); + } } diff --git a/stats/stats-server/src/main/java/ru/practicum/stat/server/util/error/ApiError.java b/stats/stats-server/src/main/java/ru/practicum/stat/server/error/ApiError.java similarity index 73% rename from stats/stats-server/src/main/java/ru/practicum/stat/server/util/error/ApiError.java rename to stats/stats-server/src/main/java/ru/practicum/stat/server/error/ApiError.java index d619347..389e94d 100644 --- a/stats/stats-server/src/main/java/ru/practicum/stat/server/util/error/ApiError.java +++ b/stats/stats-server/src/main/java/ru/practicum/stat/server/error/ApiError.java @@ -1,10 +1,13 @@ -package ru.practicum.stat.server.util.error; +package ru.practicum.stat.server.error; +import lombok.AccessLevel; import lombok.Getter; +import lombok.experimental.FieldDefaults; import org.springframework.http.HttpStatus; import java.time.LocalDateTime; @Getter +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class ApiError { private HttpStatus status; private String reason; diff --git a/stats/stats-server/src/main/java/ru/practicum/stat/server/util/error/StatErrorHandler.java b/stats/stats-server/src/main/java/ru/practicum/stat/server/error/StatErrorHandler.java similarity index 66% rename from stats/stats-server/src/main/java/ru/practicum/stat/server/util/error/StatErrorHandler.java rename to stats/stats-server/src/main/java/ru/practicum/stat/server/error/StatErrorHandler.java index 98e75d8..73c0155 100644 --- a/stats/stats-server/src/main/java/ru/practicum/stat/server/util/error/StatErrorHandler.java +++ b/stats/stats-server/src/main/java/ru/practicum/stat/server/error/StatErrorHandler.java @@ -1,11 +1,15 @@ -package ru.practicum.stat.server.util.error; +package ru.practicum.stat.server.error; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; +import org.springframework.lang.NonNull; import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; + import java.io.PrintWriter; import java.io.StringWriter; @@ -13,15 +17,14 @@ @RestControllerAdvice public class StatErrorHandler { - // Обработка 400 Bad Request @ExceptionHandler({ MethodArgumentNotValidException.class, IllegalArgumentException.class, - org.springframework.web.bind.MissingServletRequestParameterException.class, // Отсутствие параметра - org.springframework.web.method.annotation.MethodArgumentTypeMismatchException.class // Неверный формат + MissingServletRequestParameterException.class, + MethodArgumentTypeMismatchException.class }) @ResponseStatus(HttpStatus.BAD_REQUEST) - public ApiError handleBadRequest(final Exception e) { // Здесь Exception + public ApiError handleBadRequest(@NonNull final Exception e) { log.error("400 Bad Request: {}", e.getMessage()); return new ApiError( HttpStatus.BAD_REQUEST, @@ -31,7 +34,7 @@ public ApiError handleBadRequest(final Exception e) { // Здесь Exception ); } - private String getStackTrace(Exception e) { + private String getStackTrace(@NonNull Exception e) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); e.printStackTrace(pw); @@ -43,6 +46,7 @@ private String getStackTrace(Exception e) { public ApiError handleException(Exception e) { log.info("500 {}", e.getMessage(), e); String stackTrace = getStackTrace(e); - return new ApiError(HttpStatus.INTERNAL_SERVER_ERROR, "Error ....", e.getMessage(), stackTrace); + return new ApiError(HttpStatus.INTERNAL_SERVER_ERROR, + "Error ....", e.getMessage(), stackTrace); } } diff --git a/stats/stats-server/src/main/java/ru/practicum/stat/server/mapper/EndpointHitMapper.java b/stats/stats-server/src/main/java/ru/practicum/stat/server/mapper/EndpointHitMapper.java index fb45b19..dfe0e33 100644 --- a/stats/stats-server/src/main/java/ru/practicum/stat/server/mapper/EndpointHitMapper.java +++ b/stats/stats-server/src/main/java/ru/practicum/stat/server/mapper/EndpointHitMapper.java @@ -2,7 +2,9 @@ import org.springframework.lang.NonNull; import ru.practicum.stat.dto.EndpointHitDto; -import ru.practicum.stat.server.model.EndpointHit; +import ru.practicum.stat.server.entity.EndpointHit; + +import java.time.temporal.ChronoUnit; public class EndpointHitMapper { @@ -11,7 +13,7 @@ public static EndpointHit toEntity(@NonNull EndpointHitDto dto) { .app(dto.getApp()) .uri(dto.getUri()) .ip(dto.getIp()) - .timestamp(dto.getTimestamp()) + .timestamp(dto.getTimestamp().truncatedTo(ChronoUnit.SECONDS)) .build(); } } diff --git a/stats/stats-server/src/main/java/ru/practicum/stat/server/dao/StatRepository.java b/stats/stats-server/src/main/java/ru/practicum/stat/server/repository/StatRepository.java similarity index 93% rename from stats/stats-server/src/main/java/ru/practicum/stat/server/dao/StatRepository.java rename to stats/stats-server/src/main/java/ru/practicum/stat/server/repository/StatRepository.java index c76d2dc..18b4294 100644 --- a/stats/stats-server/src/main/java/ru/practicum/stat/server/dao/StatRepository.java +++ b/stats/stats-server/src/main/java/ru/practicum/stat/server/repository/StatRepository.java @@ -1,10 +1,10 @@ -package ru.practicum.stat.server.dao; +package ru.practicum.stat.server.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import ru.practicum.stat.dto.ViewStatsDto; -import ru.practicum.stat.server.model.EndpointHit; +import ru.practicum.stat.server.entity.EndpointHit; import java.time.LocalDateTime; import java.util.List; diff --git a/stats/stats-server/src/main/java/ru/practicum/stat/server/service/StatService.java b/stats/stats-server/src/main/java/ru/practicum/stat/server/service/StatService.java index aaed2ce..48bd30f 100644 --- a/stats/stats-server/src/main/java/ru/practicum/stat/server/service/StatService.java +++ b/stats/stats-server/src/main/java/ru/practicum/stat/server/service/StatService.java @@ -7,14 +7,14 @@ import org.springframework.transaction.annotation.Transactional; import ru.practicum.stat.dto.EndpointHitDto; import ru.practicum.stat.dto.ViewStatsDto; -import ru.practicum.stat.server.dao.StatRepository; import ru.practicum.stat.server.mapper.EndpointHitMapper; +import ru.practicum.stat.server.repository.StatRepository; import java.time.LocalDateTime; import java.util.List; -@Service @Slf4j +@Service @RequiredArgsConstructor @Transactional(readOnly = true) public class StatService { @@ -27,7 +27,10 @@ public void saveHit(@NonNull EndpointHitDto endpointHitDto) { statRepository.save(EndpointHitMapper.toEntity(endpointHitDto)); } - public List getStats(LocalDateTime start, LocalDateTime end, List uris, boolean unique) { + public List getStats(LocalDateTime start, + LocalDateTime end, + List uris, + boolean unique) { if (unique) { log.debug("Получение статистики (уникальные IP) для URI: {}", uris); return statRepository.getStatsUnique(start, end, uris); diff --git a/stats/stats-server/src/main/resources/application-test.yaml b/stats/stats-server/src/main/resources/application-test.yaml deleted file mode 100644 index 10090ac..0000000 --- a/stats/stats-server/src/main/resources/application-test.yaml +++ /dev/null @@ -1,26 +0,0 @@ -spring: - config: - import: "optional:configserver:" - cloud: - config: - enabled: false - fail-fast: false - discovery: - enabled: false - application: - name: stats-server - datasource: - driver-class-name: org.h2.Driver - url: jdbc:h2:mem:ewm-stats - jpa: - hibernate: - ddl-auto: none - show-sql: true - -eureka: - client: - enabled: false - -logging: - level: - org.springframework.cloud: DEBUG \ No newline at end of file diff --git a/stats/stats-server/src/main/resources/application-test.yaml.bak b/stats/stats-server/src/main/resources/application-test.yaml.bak new file mode 100644 index 0000000..0a6d29e --- /dev/null +++ b/stats/stats-server/src/main/resources/application-test.yaml.bak @@ -0,0 +1,12 @@ +spring: + datasource: + driver-class-name: org.h2.Driver + url: jdbc:h2:mem:ewm-stats + jpa: + hibernate: + ddl-auto: none + show-sql: true + +logging: + level: + org.springframework.cloud: DEBUG \ No newline at end of file diff --git a/stats/stats-server/src/main/resources/application.yaml b/stats/stats-server/src/main/resources/application.yaml index 26f3ca3..e4f93c2 100644 --- a/stats/stats-server/src/main/resources/application.yaml +++ b/stats/stats-server/src/main/resources/application.yaml @@ -13,10 +13,10 @@ spring: service-id: config-server fail-fast: true retry: - initial-interval: 1000 # начальная задержка (мс) - max-interval: 10000 # максимальная задержка (мс) - multiplier: 2.0 # множитель увеличения задержки - max-attempts: 20 # количество попыток (0 = бесконечно) + initial-interval: 1000 + max-interval: 10000 + multiplier: 2.0 + max-attempts: 20 eureka: client: diff --git a/stats/stats-server/src/main/resources/schema.sql b/stats/stats-server/src/main/resources/schema-postgres.sql similarity index 74% rename from stats/stats-server/src/main/resources/schema.sql rename to stats/stats-server/src/main/resources/schema-postgres.sql index 09ae1f7..37cf885 100644 --- a/stats/stats-server/src/main/resources/schema.sql +++ b/stats/stats-server/src/main/resources/schema-postgres.sql @@ -4,4 +4,6 @@ CREATE TABLE IF NOT EXISTS stats ( uri VARCHAR(512) NOT NULL, ip VARCHAR(45) NOT NULL, hit_timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL - ); \ No newline at end of file + ); + +CREATE INDEX IF NOT EXISTS idx_endpoint_hits_stats ON stats(hit_timestamp, uri, ip); \ No newline at end of file diff --git a/stats/stats-server/src/test/java/ru/practicum/stat/server/mapper/EndpointHitMapperTest.java b/stats/stats-server/src/test/java/ru/practicum/stat/server/mapper/EndpointHitMapperTest.java deleted file mode 100644 index 479ba67..0000000 --- a/stats/stats-server/src/test/java/ru/practicum/stat/server/mapper/EndpointHitMapperTest.java +++ /dev/null @@ -1,30 +0,0 @@ -package ru.practicum.stat.server.mapper; - -import org.junit.jupiter.api.Test; -import ru.practicum.stat.dto.EndpointHitDto; -import ru.practicum.stat.server.model.EndpointHit; - -import java.time.LocalDateTime; - -import static org.junit.jupiter.api.Assertions.*; - -class EndpointHitMapperTest { - - @Test - void toEntity_shouldMapAllFieldsCorrectly() { - LocalDateTime now = LocalDateTime.now(); - EndpointHitDto dto = EndpointHitDto.builder() - .app("ewm-main-service") - .uri("/events/1") - .ip("192.168.1.1") - .timestamp(now) - .build(); - EndpointHit entity = EndpointHitMapper.toEntity(dto); - - assertNotNull(entity); - assertEquals(dto.getApp(), entity.getApp()); - assertEquals(dto.getUri(), entity.getUri()); - assertEquals(dto.getIp(), entity.getIp()); - assertEquals(dto.getTimestamp(), entity.getTimestamp()); - } -} \ No newline at end of file