From 9c59bf65f9d782a49eada74b601e4eaf7d791fed Mon Sep 17 00:00:00 2001 From: Just Roma Date: Sat, 26 Jul 2025 21:13:27 +0500 Subject: [PATCH 1/2] main work --- docker-compose.yml | 9 +- main-service/Dockerfile | 6 +- main-service/pom.xml | 6 + .../src/main/java/ewm/DateFormatConfig.java | 34 ++ .../main/java/ewm/MainServiceApplication.java | 2 + .../main/java/ewm/categories/Category.java | 21 + .../java/ewm/categories/CategoryMapper.java | 24 ++ .../ewm/categories/CategoryRepository.java | 6 + .../controller/CategoriesAdminController.java | 37 ++ .../CategoriesPublicController.java | 30 ++ .../ewm/categories/dto/CategoryCreateDto.java | 14 + .../java/ewm/categories/dto/CategoryDto.java | 15 + .../categories/service/CategoryService.java | 21 + .../service/CategoryServiceImpl.java | 60 +++ .../java/ewm/compilations/Compilation.java | 39 ++ .../ewm/compilations/CompilationMapper.java | 23 ++ .../compilations/CompilationRepository.java | 10 + .../CompilationAdminController.java | 35 ++ .../CompilationPublicController.java | 32 ++ .../ewm/compilations/dto/CompilationDto.java | 24 ++ .../dto/CreateCompilationDto.java | 27 ++ .../dto/UpdateCompilationRequest.java | 23 ++ .../service/CompilationService.java | 19 + .../service/CompilationServiceImpl.java | 154 +++++++ .../src/main/java/ewm/events/Event.java | 70 ++++ .../src/main/java/ewm/events/EventMapper.java | 98 +++++ .../main/java/ewm/events/EventRepository.java | 25 ++ .../controller/EventControllerAdmin.java | 46 +++ .../controller/EventControllerPrivate.java | 68 +++ .../controller/EventControllerPublic.java | 49 +++ .../java/ewm/events/dto/EventCreateDto.java | 54 +++ .../java/ewm/events/dto/EventFullDto.java | 34 ++ .../java/ewm/events/dto/EventShortDto.java | 32 ++ .../ewm/events/dto/EventUpdateAdminDto.java | 44 ++ .../ewm/events/dto/EventUpdateUserDto.java | 44 ++ .../ewm/events/dto/EventViewsFullDto.java | 51 +++ .../ewm/events/dto/EventViewsShortDto.java | 34 ++ .../src/main/java/ewm/events/enums/State.java | 7 + .../ewm/events/enums/StateActionAdmin.java | 6 + .../ewm/events/enums/StateActionPrivate.java | 6 + .../java/ewm/events/service/EventService.java | 31 ++ .../ewm/events/service/EventServiceImpl.java | 390 ++++++++++++++++++ .../ewm/exception/BadRequestException.java | 7 + .../main/java/ewm/exception/ErrorHandler.java | 73 ++++ .../java/ewm/exception/ErrorResponse.java | 18 + .../ewm/exception/ForbiddenException.java | 7 + .../InternalServerErrorException.java | 0 .../java/ewm/exception/NotFoundException.java | 0 .../ewm/exception/ValidationException.java | 7 + .../src/main/java/ewm/locations/Location.java | 24 ++ .../main/java/ewm/locations/LocationDto.java | 19 + .../java/ewm/locations/LocationMapper.java | 22 + .../ewm/locations/LocationRepository.java | 9 + .../locations/service/LocationService.java | 8 + .../service/LocationServiceImpl.java | 26 ++ .../src/main/java/ewm/requests/Request.java | 37 ++ .../java/ewm/requests/RequestController.java | 32 ++ .../main/java/ewm/requests/RequestMapper.java | 19 + .../java/ewm/requests/RequestRepository.java | 28 ++ .../main/java/ewm/requests/RequestStatus.java | 8 + .../requests/dto/ConfirmedRequestsDto.java | 14 + .../dto/EventRequestStatusUpdateRequest.java | 16 + .../dto/EventRequestStatusUpdateResult.java | 15 + .../java/ewm/requests/dto/RequestDto.java | 23 ++ .../ewm/requests/service/RequestService.java | 20 + .../requests/service/RequestServiceImpl.java | 131 ++++++ main-service/src/main/java/ewm/user/User.java | 26 ++ .../main/java/ewm/user/UserController.java | 40 ++ .../src/main/java/ewm/user/UserMapper.java | 46 +++ .../main/java/ewm/user/UserRepository.java | 13 + .../main/java/ewm/user/dto/UserCreateDto.java | 20 + .../src/main/java/ewm/user/dto/UserDto.java | 21 + .../main/java/ewm/user/dto/UserShortDto.java | 11 + .../java/ewm/user/service/UserService.java | 18 + .../ewm/user/service/UserServiceImpl.java | 57 +++ .../src/main/resources/application.properties | 24 ++ main-service/src/main/resources/schema.sql | 63 +++ .../src/test/resources/application.properties | 7 + stats/stats-client/pom.xml | 40 +- .../src/main/java/client/StatsClient.java | 6 +- stats/stats-dto/pom.xml | 3 +- .../{CreateDto.java => EndpointHitDto.java} | 5 +- .../src/main/java/dto/ViewStatsDto.java | 16 +- stats/stats-server/Dockerfile | 6 +- stats/stats-server/pom.xml | 3 +- .../src/main/java/ewm/DateFormatConfig.java | 34 ++ .../java/ewm/exception/ConflictException.java | 8 - .../main/java/ewm/exception/ErrorHandler.java | 40 +- .../java/ewm/exception/ErrorResponse.java | 28 +- .../exception/InternalServerException.java | 7 + .../java/ewm/stat/EndpointHitController.java | 6 +- .../main/java/ewm/stat/EndpointHitMapper.java | 5 +- .../java/ewm/stat/EndpointHitRepository.java | 48 ++- .../ewm/stat/service/EndpointHitService.java | 4 +- .../stat/service/EndpointHitServiceImpl.java | 20 +- .../src/main/resources/schema.sql | 7 +- 96 files changed, 2808 insertions(+), 147 deletions(-) create mode 100644 main-service/src/main/java/ewm/DateFormatConfig.java create mode 100644 main-service/src/main/java/ewm/categories/Category.java create mode 100644 main-service/src/main/java/ewm/categories/CategoryMapper.java create mode 100644 main-service/src/main/java/ewm/categories/CategoryRepository.java create mode 100644 main-service/src/main/java/ewm/categories/controller/CategoriesAdminController.java create mode 100644 main-service/src/main/java/ewm/categories/controller/CategoriesPublicController.java create mode 100644 main-service/src/main/java/ewm/categories/dto/CategoryCreateDto.java create mode 100644 main-service/src/main/java/ewm/categories/dto/CategoryDto.java create mode 100644 main-service/src/main/java/ewm/categories/service/CategoryService.java create mode 100644 main-service/src/main/java/ewm/categories/service/CategoryServiceImpl.java create mode 100644 main-service/src/main/java/ewm/compilations/Compilation.java create mode 100644 main-service/src/main/java/ewm/compilations/CompilationMapper.java create mode 100644 main-service/src/main/java/ewm/compilations/CompilationRepository.java create mode 100644 main-service/src/main/java/ewm/compilations/controller/CompilationAdminController.java create mode 100644 main-service/src/main/java/ewm/compilations/controller/CompilationPublicController.java create mode 100644 main-service/src/main/java/ewm/compilations/dto/CompilationDto.java create mode 100644 main-service/src/main/java/ewm/compilations/dto/CreateCompilationDto.java create mode 100644 main-service/src/main/java/ewm/compilations/dto/UpdateCompilationRequest.java create mode 100644 main-service/src/main/java/ewm/compilations/service/CompilationService.java create mode 100644 main-service/src/main/java/ewm/compilations/service/CompilationServiceImpl.java create mode 100644 main-service/src/main/java/ewm/events/Event.java create mode 100644 main-service/src/main/java/ewm/events/EventMapper.java create mode 100644 main-service/src/main/java/ewm/events/EventRepository.java create mode 100644 main-service/src/main/java/ewm/events/controller/EventControllerAdmin.java create mode 100644 main-service/src/main/java/ewm/events/controller/EventControllerPrivate.java create mode 100644 main-service/src/main/java/ewm/events/controller/EventControllerPublic.java create mode 100644 main-service/src/main/java/ewm/events/dto/EventCreateDto.java create mode 100644 main-service/src/main/java/ewm/events/dto/EventFullDto.java create mode 100644 main-service/src/main/java/ewm/events/dto/EventShortDto.java create mode 100644 main-service/src/main/java/ewm/events/dto/EventUpdateAdminDto.java create mode 100644 main-service/src/main/java/ewm/events/dto/EventUpdateUserDto.java create mode 100644 main-service/src/main/java/ewm/events/dto/EventViewsFullDto.java create mode 100644 main-service/src/main/java/ewm/events/dto/EventViewsShortDto.java create mode 100644 main-service/src/main/java/ewm/events/enums/State.java create mode 100644 main-service/src/main/java/ewm/events/enums/StateActionAdmin.java create mode 100644 main-service/src/main/java/ewm/events/enums/StateActionPrivate.java create mode 100644 main-service/src/main/java/ewm/events/service/EventService.java create mode 100644 main-service/src/main/java/ewm/events/service/EventServiceImpl.java create mode 100644 main-service/src/main/java/ewm/exception/BadRequestException.java create mode 100644 main-service/src/main/java/ewm/exception/ErrorHandler.java create mode 100644 main-service/src/main/java/ewm/exception/ErrorResponse.java create mode 100644 main-service/src/main/java/ewm/exception/ForbiddenException.java rename {stats/stats-server => main-service}/src/main/java/ewm/exception/InternalServerErrorException.java (100%) rename {stats/stats-server => main-service}/src/main/java/ewm/exception/NotFoundException.java (100%) create mode 100644 main-service/src/main/java/ewm/exception/ValidationException.java create mode 100644 main-service/src/main/java/ewm/locations/Location.java create mode 100644 main-service/src/main/java/ewm/locations/LocationDto.java create mode 100644 main-service/src/main/java/ewm/locations/LocationMapper.java create mode 100644 main-service/src/main/java/ewm/locations/LocationRepository.java create mode 100644 main-service/src/main/java/ewm/locations/service/LocationService.java create mode 100644 main-service/src/main/java/ewm/locations/service/LocationServiceImpl.java create mode 100644 main-service/src/main/java/ewm/requests/Request.java create mode 100644 main-service/src/main/java/ewm/requests/RequestController.java create mode 100644 main-service/src/main/java/ewm/requests/RequestMapper.java create mode 100644 main-service/src/main/java/ewm/requests/RequestRepository.java create mode 100644 main-service/src/main/java/ewm/requests/RequestStatus.java create mode 100644 main-service/src/main/java/ewm/requests/dto/ConfirmedRequestsDto.java create mode 100644 main-service/src/main/java/ewm/requests/dto/EventRequestStatusUpdateRequest.java create mode 100644 main-service/src/main/java/ewm/requests/dto/EventRequestStatusUpdateResult.java create mode 100644 main-service/src/main/java/ewm/requests/dto/RequestDto.java create mode 100644 main-service/src/main/java/ewm/requests/service/RequestService.java create mode 100644 main-service/src/main/java/ewm/requests/service/RequestServiceImpl.java create mode 100644 main-service/src/main/java/ewm/user/User.java create mode 100644 main-service/src/main/java/ewm/user/UserController.java create mode 100644 main-service/src/main/java/ewm/user/UserMapper.java create mode 100644 main-service/src/main/java/ewm/user/UserRepository.java create mode 100644 main-service/src/main/java/ewm/user/dto/UserCreateDto.java create mode 100644 main-service/src/main/java/ewm/user/dto/UserDto.java create mode 100644 main-service/src/main/java/ewm/user/dto/UserShortDto.java create mode 100644 main-service/src/main/java/ewm/user/service/UserService.java create mode 100644 main-service/src/main/java/ewm/user/service/UserServiceImpl.java create mode 100644 main-service/src/main/resources/application.properties create mode 100644 main-service/src/main/resources/schema.sql create mode 100644 main-service/src/test/resources/application.properties rename stats/stats-dto/src/main/java/dto/{CreateDto.java => EndpointHitDto.java} (75%) create mode 100644 stats/stats-server/src/main/java/ewm/DateFormatConfig.java delete mode 100644 stats/stats-server/src/main/java/ewm/exception/ConflictException.java create mode 100644 stats/stats-server/src/main/java/ewm/exception/InternalServerException.java diff --git a/docker-compose.yml b/docker-compose.yml index 1e26a3e..ab52e69 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,16 +2,17 @@ version: '3.1' services: stats-server: - build: stats/stats-server + build: ./stats/stats-server container_name: stat-server ports: - "9090:9090" + depends_on: + - stats-db environment: - SPRING_DATASOURCE_URL=jdbc:postgresql://stats-db:5432/ewm-stats - SPRING_DATASOURCE_USERNAME=stat - SPRING_DATASOURCE_PASSWORD=stat - depends_on: - - stats-db + stats-db: image: postgres:16.1 @@ -24,7 +25,7 @@ services: - POSTGRES_DB=ewm-stats ewm-service: - build: main-service + build: ./main-service ports: - "8080:8080" environment: diff --git a/main-service/Dockerfile b/main-service/Dockerfile index 0ff1817..7ae68e5 100644 --- a/main-service/Dockerfile +++ b/main-service/Dockerfile @@ -1,5 +1,3 @@ FROM eclipse-temurin:21-jre-jammy -VOLUME /tmp -ARG JAR_FILE=target/*.jar -COPY ${JAR_FILE} app.jar -ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"] \ No newline at end of file +COPY ./target/*.jar main-service.jar +ENTRYPOINT ["java","-jar","/main-service.jar"] \ No newline at end of file diff --git a/main-service/pom.xml b/main-service/pom.xml index dea6df8..2711657 100644 --- a/main-service/pom.xml +++ b/main-service/pom.xml @@ -18,6 +18,12 @@ + + ru.practicum + stats-client + 0.0.1-SNAPSHOT + compile + org.zalando logbook-spring-boot-starter diff --git a/main-service/src/main/java/ewm/DateFormatConfig.java b/main-service/src/main/java/ewm/DateFormatConfig.java new file mode 100644 index 0000000..414a0f1 --- /dev/null +++ b/main-service/src/main/java/ewm/DateFormatConfig.java @@ -0,0 +1,34 @@ +package ewm; + +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.time.format.DateTimeFormatter; + +@Configuration +public class DateFormatConfig { + + @Bean + public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() { + + return builder -> { + + // formatter + DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + // deserializers + builder.deserializers(new LocalDateDeserializer(dateFormatter)); + builder.deserializers(new LocalDateTimeDeserializer(dateTimeFormatter)); + + // serializers + builder.serializers(new LocalDateSerializer(dateFormatter)); + builder.serializers(new LocalDateTimeSerializer(dateTimeFormatter)); + }; + } +} diff --git a/main-service/src/main/java/ewm/MainServiceApplication.java b/main-service/src/main/java/ewm/MainServiceApplication.java index fce1434..0788c03 100644 --- a/main-service/src/main/java/ewm/MainServiceApplication.java +++ b/main-service/src/main/java/ewm/MainServiceApplication.java @@ -2,7 +2,9 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; +@ComponentScan(basePackages = {"ewm", "client"}) @SpringBootApplication public class MainServiceApplication { public static void main(String[] args) { diff --git a/main-service/src/main/java/ewm/categories/Category.java b/main-service/src/main/java/ewm/categories/Category.java new file mode 100644 index 0000000..dee827e --- /dev/null +++ b/main-service/src/main/java/ewm/categories/Category.java @@ -0,0 +1,21 @@ +package ewm.categories; + +import jakarta.persistence.*; +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Getter +@Setter +@ToString +@Entity +@Table(name = "categories") +public class Category { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + @Column(nullable = false, unique = true) + private String name; +} diff --git a/main-service/src/main/java/ewm/categories/CategoryMapper.java b/main-service/src/main/java/ewm/categories/CategoryMapper.java new file mode 100644 index 0000000..bd7b5e8 --- /dev/null +++ b/main-service/src/main/java/ewm/categories/CategoryMapper.java @@ -0,0 +1,24 @@ +package ewm.categories; + +import ewm.categories.dto.CategoryCreateDto; +import ewm.categories.dto.CategoryDto; +import org.springframework.stereotype.Component; + +@Component +public class CategoryMapper { + + public Category categoryCreateDtoToModel(CategoryCreateDto dto) { + return Category.builder() + .name(dto.getName()) + .build(); + } + + public CategoryDto modelToDto(Category category) { + return CategoryDto.builder() + .id(category.getId()) + .name(category.getName()) + .build(); + } + + +} diff --git a/main-service/src/main/java/ewm/categories/CategoryRepository.java b/main-service/src/main/java/ewm/categories/CategoryRepository.java new file mode 100644 index 0000000..acdd9e3 --- /dev/null +++ b/main-service/src/main/java/ewm/categories/CategoryRepository.java @@ -0,0 +1,6 @@ +package ewm.categories; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CategoryRepository extends JpaRepository { +} diff --git a/main-service/src/main/java/ewm/categories/controller/CategoriesAdminController.java b/main-service/src/main/java/ewm/categories/controller/CategoriesAdminController.java new file mode 100644 index 0000000..50e8b39 --- /dev/null +++ b/main-service/src/main/java/ewm/categories/controller/CategoriesAdminController.java @@ -0,0 +1,37 @@ +package ewm.categories.controller; + +import ewm.categories.dto.CategoryCreateDto; +import ewm.categories.dto.CategoryDto; +import ewm.categories.service.CategoryService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +@Validated +@RestController +@RequiredArgsConstructor +@RequestMapping("/admin/categories") +public class CategoriesAdminController { + private final CategoryService service; + + @PostMapping + @ResponseStatus(value = HttpStatus.CREATED) + public CategoryDto create(@RequestBody @Valid CategoryCreateDto createDto) { + return service.create(createDto); + } + + @PatchMapping("/{catId}") + public CategoryDto update(@PathVariable Long catId, + @RequestBody @Valid CategoryCreateDto createDto) { + return service.update(catId, createDto); + } + + @DeleteMapping("/{catId}") + @ResponseStatus(value = HttpStatus.NO_CONTENT) + public void deleteById(@PathVariable Long catId) { + service.deleteById(catId); + } + +} diff --git a/main-service/src/main/java/ewm/categories/controller/CategoriesPublicController.java b/main-service/src/main/java/ewm/categories/controller/CategoriesPublicController.java new file mode 100644 index 0000000..f8240fd --- /dev/null +++ b/main-service/src/main/java/ewm/categories/controller/CategoriesPublicController.java @@ -0,0 +1,30 @@ +package ewm.categories.controller; + +import ewm.categories.dto.CategoryDto; +import ewm.categories.service.CategoryService; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Validated +@RestController +@RequiredArgsConstructor +@RequestMapping("/categories") +public class CategoriesPublicController { + private final CategoryService service; + + @GetMapping + public List getCategories( + @RequestParam(value = "from", defaultValue = "0") Integer from, + @RequestParam(value = "size", defaultValue = "10") Integer size) { + return service.getCategories(from, size); + } + + @GetMapping("/{catId}") + public CategoryDto getById(@PathVariable Long catId) { + return service.getById(catId); + } + +} diff --git a/main-service/src/main/java/ewm/categories/dto/CategoryCreateDto.java b/main-service/src/main/java/ewm/categories/dto/CategoryCreateDto.java new file mode 100644 index 0000000..d16d387 --- /dev/null +++ b/main-service/src/main/java/ewm/categories/dto/CategoryCreateDto.java @@ -0,0 +1,14 @@ +package ewm.categories.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +@Data +public class CategoryCreateDto { + @NotNull + @NotBlank + @Size(min = 1, max = 50) + private String name; +} diff --git a/main-service/src/main/java/ewm/categories/dto/CategoryDto.java b/main-service/src/main/java/ewm/categories/dto/CategoryDto.java new file mode 100644 index 0000000..7341704 --- /dev/null +++ b/main-service/src/main/java/ewm/categories/dto/CategoryDto.java @@ -0,0 +1,15 @@ +package ewm.categories.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class CategoryDto { + private long id; + private String name; +} diff --git a/main-service/src/main/java/ewm/categories/service/CategoryService.java b/main-service/src/main/java/ewm/categories/service/CategoryService.java new file mode 100644 index 0000000..9bee337 --- /dev/null +++ b/main-service/src/main/java/ewm/categories/service/CategoryService.java @@ -0,0 +1,21 @@ +package ewm.categories.service; + +import ewm.categories.Category; +import ewm.categories.dto.CategoryCreateDto; +import ewm.categories.dto.CategoryDto; + +import java.util.List; + +public interface CategoryService { + CategoryDto create(CategoryCreateDto createDto); + + CategoryDto update(long categoryId, CategoryCreateDto createDto); + + void deleteById(long categoryId); + + List getCategories(Integer from, Integer size); + + CategoryDto getById(long categoryId); + + Category checkAndReturnCategory(long id); +} diff --git a/main-service/src/main/java/ewm/categories/service/CategoryServiceImpl.java b/main-service/src/main/java/ewm/categories/service/CategoryServiceImpl.java new file mode 100644 index 0000000..9afc30c --- /dev/null +++ b/main-service/src/main/java/ewm/categories/service/CategoryServiceImpl.java @@ -0,0 +1,60 @@ +package ewm.categories.service; + +import ewm.categories.Category; +import ewm.categories.CategoryMapper; +import ewm.categories.CategoryRepository; +import ewm.categories.dto.CategoryCreateDto; +import ewm.categories.dto.CategoryDto; +import ewm.exception.NotFoundException; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Transactional +@Service +@RequiredArgsConstructor +public class CategoryServiceImpl implements CategoryService { + private final CategoryRepository repository; + private final CategoryMapper mapper; + + @Override + public CategoryDto create(CategoryCreateDto createDto) { + Category category = mapper.categoryCreateDtoToModel(createDto); + return mapper.modelToDto(repository.save(category)); + } + + @Override + public CategoryDto update(long categoryId, CategoryCreateDto createDto) { + Category savedCategory = checkAndReturnCategory(categoryId); + savedCategory.setName(createDto.getName()); + return mapper.modelToDto(repository.save(savedCategory)); + } + + @Override + public void deleteById(long categoryId) { + checkAndReturnCategory(categoryId); + repository.deleteById(categoryId); + } + + @Override + public List getCategories(Integer from, Integer size) { + Pageable pageable = PageRequest.of(from / size, size); + return repository.findAll(pageable).map(mapper::modelToDto).toList(); + } + + @Override + public CategoryDto getById(long categoryId) { + Category category = checkAndReturnCategory(categoryId); + return mapper.modelToDto(category); + } + + public Category checkAndReturnCategory(long id) { + return repository.findById(id).orElseThrow(() -> + new NotFoundException("Category with id=" + id + " was not found")); + } + +} diff --git a/main-service/src/main/java/ewm/compilations/Compilation.java b/main-service/src/main/java/ewm/compilations/Compilation.java new file mode 100644 index 0000000..0ef17e4 --- /dev/null +++ b/main-service/src/main/java/ewm/compilations/Compilation.java @@ -0,0 +1,39 @@ +package ewm.compilations; + +import ewm.events.Event; +import jakarta.persistence.*; +import lombok.*; + +import java.util.Set; + +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Getter +@Setter +@ToString +@Entity +@Table(name = "compilations") +public class Compilation { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + @Column(name = "title", nullable = false) + private String title; + + @Column(name = "pinned", nullable = false) + private Boolean pinned; + + @ManyToMany + @JoinTable(name = "compilation_event", + joinColumns = @JoinColumn(name = "compilation_id"), + inverseJoinColumns = @JoinColumn(name = "event_id")) + private Set events; + + public Compilation(String title, Boolean pinned) { + this.title = title; + this.pinned = pinned; + } + +} diff --git a/main-service/src/main/java/ewm/compilations/CompilationMapper.java b/main-service/src/main/java/ewm/compilations/CompilationMapper.java new file mode 100644 index 0000000..c118cb8 --- /dev/null +++ b/main-service/src/main/java/ewm/compilations/CompilationMapper.java @@ -0,0 +1,23 @@ +package ewm.compilations; + +import ewm.compilations.dto.CompilationDto; +import ewm.compilations.dto.CreateCompilationDto; +import org.springframework.stereotype.Component; + +@Component +public class CompilationMapper { + public Compilation toCompilationEntity(CreateCompilationDto createCompilationDto) { + return new Compilation( + createCompilationDto.getTitle(), + createCompilationDto.getPinned() + ); + } + + public CompilationDto toCompilationDto(Compilation compilation) { + return new CompilationDto( + compilation.getId(), + compilation.getPinned(), + compilation.getTitle() + ); + } +} diff --git a/main-service/src/main/java/ewm/compilations/CompilationRepository.java b/main-service/src/main/java/ewm/compilations/CompilationRepository.java new file mode 100644 index 0000000..7b587e5 --- /dev/null +++ b/main-service/src/main/java/ewm/compilations/CompilationRepository.java @@ -0,0 +1,10 @@ +package ewm.compilations; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface CompilationRepository extends JpaRepository { + List findAllByPinned(Boolean pinned, Pageable pageable); +} diff --git a/main-service/src/main/java/ewm/compilations/controller/CompilationAdminController.java b/main-service/src/main/java/ewm/compilations/controller/CompilationAdminController.java new file mode 100644 index 0000000..872490d --- /dev/null +++ b/main-service/src/main/java/ewm/compilations/controller/CompilationAdminController.java @@ -0,0 +1,35 @@ +package ewm.compilations.controller; + +import ewm.compilations.dto.CompilationDto; +import ewm.compilations.dto.CreateCompilationDto; +import ewm.compilations.dto.UpdateCompilationRequest; +import ewm.compilations.service.CompilationService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/admin/compilations") +@RequiredArgsConstructor +public class CompilationAdminController { + private final CompilationService compilationService; + + @PostMapping + @ResponseStatus(value = HttpStatus.CREATED) + public CompilationDto addCompilation(@RequestBody @Valid CreateCompilationDto createCompilationDto) { + return compilationService.addCompilation(createCompilationDto); + } + + @PatchMapping("/{compilationId}") + public CompilationDto updateCompilation(@PathVariable Long compilationId, + @RequestBody @Valid UpdateCompilationRequest updateCompilation) { + return compilationService.updateCompilation(compilationId, updateCompilation); + } + + @DeleteMapping("/{compilationId}") + @ResponseStatus(value = HttpStatus.NO_CONTENT) + public void deleteCompilation(@PathVariable long compilationId) { + compilationService.deleteCompilation(compilationId); + } +} \ No newline at end of file diff --git a/main-service/src/main/java/ewm/compilations/controller/CompilationPublicController.java b/main-service/src/main/java/ewm/compilations/controller/CompilationPublicController.java new file mode 100644 index 0000000..8b535d7 --- /dev/null +++ b/main-service/src/main/java/ewm/compilations/controller/CompilationPublicController.java @@ -0,0 +1,32 @@ +package ewm.compilations.controller; + +import ewm.compilations.dto.CompilationDto; +import ewm.compilations.service.CompilationService; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Validated +@RestController +@RequestMapping("/compilations") +@RequiredArgsConstructor +public class CompilationPublicController { + private final CompilationService compilationService; + + @GetMapping + public List getCompilations(@RequestParam(required = false) Boolean pinned, + @RequestParam(defaultValue = "0") @PositiveOrZero Integer from, + @RequestParam(defaultValue = "10") @Positive Integer size) { + return compilationService.getCompilations(pinned, from, size); + } + + @GetMapping("/{compilationId}") + public CompilationDto getCompilationById(@PathVariable Long compilationId) { + return compilationService.getCompilationById(compilationId); + } + +} diff --git a/main-service/src/main/java/ewm/compilations/dto/CompilationDto.java b/main-service/src/main/java/ewm/compilations/dto/CompilationDto.java new file mode 100644 index 0000000..8924015 --- /dev/null +++ b/main-service/src/main/java/ewm/compilations/dto/CompilationDto.java @@ -0,0 +1,24 @@ +package ewm.compilations.dto; + +import ewm.events.dto.EventShortDto; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class CompilationDto { + private Long id; + private List events; + private Boolean pinned; + private String title; + + public CompilationDto(Long id, boolean pinned, String title) { + this.id = id; + this.pinned = pinned; + this.title = title; + } +} diff --git a/main-service/src/main/java/ewm/compilations/dto/CreateCompilationDto.java b/main-service/src/main/java/ewm/compilations/dto/CreateCompilationDto.java new file mode 100644 index 0000000..ffa0223 --- /dev/null +++ b/main-service/src/main/java/ewm/compilations/dto/CreateCompilationDto.java @@ -0,0 +1,27 @@ +package ewm.compilations.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class CreateCompilationDto { + + private List events; + + private boolean pinned = false; + + @NotBlank + @Size(min = 1, max = 50) + private String title; + + public Boolean getPinned() { + return pinned; + } +} diff --git a/main-service/src/main/java/ewm/compilations/dto/UpdateCompilationRequest.java b/main-service/src/main/java/ewm/compilations/dto/UpdateCompilationRequest.java new file mode 100644 index 0000000..579c5f2 --- /dev/null +++ b/main-service/src/main/java/ewm/compilations/dto/UpdateCompilationRequest.java @@ -0,0 +1,23 @@ +package ewm.compilations.dto; + +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class UpdateCompilationRequest { + private List events; + + @Getter + private Boolean pinned; + + @Size(min = 1, max = 50) + private String title; + +} \ No newline at end of file diff --git a/main-service/src/main/java/ewm/compilations/service/CompilationService.java b/main-service/src/main/java/ewm/compilations/service/CompilationService.java new file mode 100644 index 0000000..57777ad --- /dev/null +++ b/main-service/src/main/java/ewm/compilations/service/CompilationService.java @@ -0,0 +1,19 @@ +package ewm.compilations.service; + +import ewm.compilations.dto.CompilationDto; +import ewm.compilations.dto.CreateCompilationDto; +import ewm.compilations.dto.UpdateCompilationRequest; + +import java.util.List; + +public interface CompilationService { + List getCompilations(Boolean pinned, Integer from, Integer size); + + CompilationDto getCompilationById(Long compilationId); + + CompilationDto addCompilation(CreateCompilationDto newCompilationDto); + + CompilationDto updateCompilation(Long compId, UpdateCompilationRequest updateCompilation); + + void deleteCompilation(Long compilationId); +} diff --git a/main-service/src/main/java/ewm/compilations/service/CompilationServiceImpl.java b/main-service/src/main/java/ewm/compilations/service/CompilationServiceImpl.java new file mode 100644 index 0000000..e507f68 --- /dev/null +++ b/main-service/src/main/java/ewm/compilations/service/CompilationServiceImpl.java @@ -0,0 +1,154 @@ +package ewm.compilations.service; + +import ewm.compilations.Compilation; +import ewm.compilations.CompilationMapper; +import ewm.compilations.CompilationRepository; +import ewm.compilations.dto.CompilationDto; +import ewm.compilations.dto.CreateCompilationDto; +import ewm.compilations.dto.UpdateCompilationRequest; +import ewm.events.Event; +import ewm.events.EventMapper; +import ewm.events.EventRepository; +import ewm.events.dto.EventShortDto; +import ewm.exception.NotFoundException; +import ewm.requests.RequestRepository; +import ewm.requests.dto.ConfirmedRequestsDto; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static ewm.requests.RequestStatus.CONFIRMED; + +@Transactional +@Service +@RequiredArgsConstructor +public class CompilationServiceImpl implements CompilationService { + private final CompilationRepository compilationRepository; + private final EventRepository eventRepository; + private final RequestRepository requestRepository; + private final EventMapper eventMapper; + private final CompilationMapper compilationMapper; + + @Override + public List getCompilations(Boolean pinned, Integer from, Integer size) { + Pageable pageable = PageRequest.of(from / size, size); + List compilations; + + if (pinned != null) { + compilations = compilationRepository.findAllByPinned(pinned, pageable); + } else { + compilations = compilationRepository.findAll(pageable).getContent(); + } + + List compilationIds = compilations.stream() + .map(Compilation::getId) + .toList(); + + List events = eventRepository.findAllByInitiatorIdIn(compilationIds); + + List eventIds = events.stream() + .map(Event::getId) + .toList(); + + Map confirmedRequests = requestRepository.findAllByEventIdInAndStatus(eventIds, CONFIRMED) + .stream() + .collect(Collectors.toMap(ConfirmedRequestsDto::getEvent, ConfirmedRequestsDto::getCount)); + + List result = compilations.stream() + .map(compilation -> { + CompilationDto compilationDto = compilationMapper.toCompilationDto(compilation); + List eventShortDtos = compilation.getEvents().stream() + .map(event -> eventMapper.toEventShortDto(event, confirmedRequests.get(event.getId()))) + .collect(Collectors.toList()); + compilationDto.setEvents(eventShortDtos); + return compilationDto; + }) + .toList(); + return result; + } + + @Override + @Transactional(readOnly = true) + public CompilationDto getCompilationById(Long compilationId) { + Compilation compilation = getCompilation(compilationId); + CompilationDto compilationDto = compilationMapper.toCompilationDto(compilation); + if (compilation.getEvents() != null) { + List ids = compilation.getEvents().stream().map(Event::getId).collect(Collectors.toList()); + Map confirmedRequests = requestRepository.findAllByEventIdInAndStatus(ids, CONFIRMED) + .stream() + .collect(Collectors.toMap(ConfirmedRequestsDto::getEvent, ConfirmedRequestsDto::getCount)); + compilationDto.setEvents(compilation.getEvents().stream() + .map(event -> eventMapper.toEventShortDto(event, confirmedRequests.get(event.getId()))) + .collect(Collectors.toList())); + } + return compilationDto; + } + + @Override + public CompilationDto addCompilation(CreateCompilationDto newCompilationDto) { + Compilation compilation = compilationMapper.toCompilationEntity(newCompilationDto); + setEvents(compilation, newCompilationDto.getEvents()); + return setCompilationDto(compilation); + } + + @Override + public CompilationDto updateCompilation(Long compId, UpdateCompilationRequest updateCompilation) { + Compilation compilation = getCompilation(compId); + if (updateCompilation.getEvents() != null) { + Set events = updateCompilation.getEvents().stream() + .map(id -> { + Event event = new Event(); + event.setId(id); + return event; + }).collect(Collectors.toSet()); + compilation.setEvents(events); + } + if (updateCompilation.getPinned() != null) { + compilation.setPinned(updateCompilation.getPinned()); + } + String title = updateCompilation.getTitle(); + if (title != null && !title.isBlank()) { + compilation.setTitle(title); + } + return setCompilationDto(compilation); + } + + @Override + public void deleteCompilation(Long compilationId) { + getCompilation(compilationId); + compilationRepository.deleteById(compilationId); + } + + private void setEvents(Compilation compilation, List eventIds) { + if (eventIds != null) { + compilation.setEvents(eventRepository.findAllByIdIn(eventIds)); + } + } + + private CompilationDto setCompilationDto(Compilation compilation) { + CompilationDto compilationDto = compilationMapper.toCompilationDto(compilationRepository.save(compilation)); + if (compilation.getEvents() != null) { + List ids = compilation.getEvents().stream().map(Event::getId).collect(Collectors.toList()); + Map confirmedRequests = requestRepository.findAllByEventIdInAndStatus(ids, CONFIRMED) + .stream() + .collect(Collectors.toMap(ConfirmedRequestsDto::getEvent, ConfirmedRequestsDto::getCount)); + compilationDto.setEvents(compilation.getEvents().stream() + .map(event -> eventMapper.toEventShortDto(event, confirmedRequests.get(event.getId()))) + .collect(Collectors.toList())); + } + return compilationDto; + } + + private Compilation getCompilation(Long compilationId) { + return compilationRepository.findById(compilationId).orElseThrow(() -> + new NotFoundException("Compilation id=" + compilationId + " not found")); + } + +} diff --git a/main-service/src/main/java/ewm/events/Event.java b/main-service/src/main/java/ewm/events/Event.java new file mode 100644 index 0000000..986464f --- /dev/null +++ b/main-service/src/main/java/ewm/events/Event.java @@ -0,0 +1,70 @@ +package ewm.events; + +import ewm.categories.Category; +import ewm.events.enums.State; +import ewm.locations.Location; +import ewm.user.User; +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDateTime; + +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Getter +@Setter +@ToString +@Entity +@Table(name = "events") +public class Event { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + @Column(nullable = false) + private String annotation; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "category_id", nullable = false) + @ToString.Exclude + private Category category; + + @Column(name = "created_on", nullable = false, columnDefinition = "TIMESTAMP") + private LocalDateTime createdOn; + + @Column(nullable = false) + private String description; + + @Column(name = "event_date", nullable = false) + private LocalDateTime eventDate; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "initiator_id", nullable = false) + @ToString.Exclude + private User initiator; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "location_id", nullable = false) + @ToString.Exclude + private Location location; + + private Boolean paid; + + @Column(name = "participant_limit") + private Integer participantLimit; + + @Column(name = "published_on") + private LocalDateTime publishedOn; + + @Column(name = "request_moderation") + private Boolean requestModeration; + + @Enumerated(EnumType.STRING) + private State state; + + @Column(nullable = false) + private String title; + + +} diff --git a/main-service/src/main/java/ewm/events/EventMapper.java b/main-service/src/main/java/ewm/events/EventMapper.java new file mode 100644 index 0000000..5c6e51b --- /dev/null +++ b/main-service/src/main/java/ewm/events/EventMapper.java @@ -0,0 +1,98 @@ +package ewm.events; + +import ewm.categories.CategoryMapper; +import ewm.events.dto.*; +import ewm.locations.LocationMapper; +import ewm.user.UserMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class EventMapper { + private final CategoryMapper categoryMapper; + private final UserMapper userMapper; + private final LocationMapper locationMapper; + + public Event eventCreateDtoToModel(EventCreateDto dto) { + return Event.builder() + .annotation(dto.getAnnotation()) + .description(dto.getDescription()) + .eventDate(dto.getEventDate()) + .paid(dto.getPaid()) + .participantLimit(dto.getParticipantLimit()) + .requestModeration(dto.getRequestModeration()) + .title(dto.getTitle()) + .build(); + } + + public EventFullDto modelToEventFullDto(Event event, Long confirmedRequests) { + return EventFullDto.builder() + .id(event.getId()) + .annotation(event.getAnnotation()) + .category(categoryMapper.modelToDto(event.getCategory())) + .confirmedRequests(confirmedRequests) + .createdOn(event.getCreatedOn()) + .description(event.getDescription()) + .eventDate(event.getEventDate()) + .initiator(userMapper.modelToUserShortDto(event.getInitiator())) + .location(locationMapper.modelToDto(event.getLocation())) + .paid(event.getPaid()) + .participantLimit(event.getParticipantLimit()) + .publishedOn(event.getPublishedOn()) + .requestModeration(event.getRequestModeration()) + .state(event.getState()) + .title(event.getTitle()) + .build(); + } + + public EventShortDto toEventShortDto(Event event, Long confirmedRequests) { + return EventShortDto.builder() + .id(event.getId()) + .annotation(event.getAnnotation()) + .category(categoryMapper.modelToDto(event.getCategory())) + .confirmedRequests(confirmedRequests) + .eventDate(event.getEventDate()) + .initiator(userMapper.modelToUserShortDto(event.getInitiator())) + .paid(event.getPaid()) + .title(event.getTitle()) + .build(); + } + + + public EventViewsFullDto toEventFullDtoWithViews(Event event, Long views, Long confirmedRequests) { + return EventViewsFullDto.builder() + .id(event.getId()) + .annotation(event.getAnnotation()) + .category(categoryMapper.modelToDto(event.getCategory())) + .confirmedRequests(confirmedRequests) + .createdOn(event.getCreatedOn()) + .description(event.getDescription()) + .eventDate(event.getEventDate()) + .initiator(userMapper.modelToUserShortDto(event.getInitiator())) + .location(locationMapper.modelToDto(event.getLocation())) + .paid(event.getPaid()) + .participantLimit(event.getParticipantLimit()) + .publishedOn(event.getPublishedOn()) + .requestModeration(event.getRequestModeration()) + .state(event.getState()) + .title(event.getTitle()) + .views(views) + .build(); + } + + public EventViewsShortDto toEventShortDtoWithViews(Event event, Long views, Long confirmedRequests) { + return EventViewsShortDto.builder() + .id(event.getId()) + .annotation(event.getAnnotation()) + .category(categoryMapper.modelToDto(event.getCategory())) + .confirmedRequests(confirmedRequests) + .eventDate(event.getEventDate()) + .initiator(userMapper.modelToUserShortDto(event.getInitiator())) + .paid(event.getPaid()) + .title(event.getTitle()) + .views(views) + .build(); + } + +} diff --git a/main-service/src/main/java/ewm/events/EventRepository.java b/main-service/src/main/java/ewm/events/EventRepository.java new file mode 100644 index 0000000..226f9c2 --- /dev/null +++ b/main-service/src/main/java/ewm/events/EventRepository.java @@ -0,0 +1,25 @@ +package ewm.events; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.util.List; +import java.util.Optional; +import java.util.Set; + +public interface EventRepository extends JpaRepository { + + List findAllByInitiatorId(Long userId, Pageable pageable); + + Optional findByIdAndInitiatorId(Long eventId, Long userId); + + @Query("SELECT e FROM Event e WHERE e.initiator.id IN :initiatorIds") + List findAllByInitiatorIdIn(List initiatorIds); + + Page findAll(Specification specification, Pageable pageable); + + Set findAllByIdIn(List events); +} diff --git a/main-service/src/main/java/ewm/events/controller/EventControllerAdmin.java b/main-service/src/main/java/ewm/events/controller/EventControllerAdmin.java new file mode 100644 index 0000000..66301c5 --- /dev/null +++ b/main-service/src/main/java/ewm/events/controller/EventControllerAdmin.java @@ -0,0 +1,46 @@ +package ewm.events.controller; + +import ewm.events.dto.EventFullDto; +import ewm.events.dto.EventUpdateAdminDto; +import ewm.events.dto.EventViewsFullDto; +import ewm.events.service.EventService; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDateTime; +import java.util.List; + +@Slf4j +@Validated +@RestController +@RequiredArgsConstructor +@RequestMapping("/admin/events") +public class EventControllerAdmin { + private final EventService eventService; + + @PatchMapping("/{eventId}") + public EventFullDto updateEventByAdmin(@PathVariable Long eventId, + @RequestBody @Valid EventUpdateAdminDto eventUpdateAdminDto) { + return eventService.updateEventByAdmin(eventId, eventUpdateAdminDto); + } + + @GetMapping + public List getEvents( + @RequestParam(required = false) List users, + @RequestParam(required = false) List states, + @RequestParam(required = false) List categories, + @RequestParam(required = false) LocalDateTime rangeStart, + @RequestParam(required = false) LocalDateTime rangeEnd, + @RequestParam(value = "from", defaultValue = "0") + @PositiveOrZero Integer from, + @RequestParam(value = "size", defaultValue = "10") + @Positive Integer size) { + return eventService.getEventsByAdmin(users, states, categories, rangeStart, rangeEnd, from, size); + } + +} diff --git a/main-service/src/main/java/ewm/events/controller/EventControllerPrivate.java b/main-service/src/main/java/ewm/events/controller/EventControllerPrivate.java new file mode 100644 index 0000000..8c45312 --- /dev/null +++ b/main-service/src/main/java/ewm/events/controller/EventControllerPrivate.java @@ -0,0 +1,68 @@ +package ewm.events.controller; + +import ewm.events.dto.EventCreateDto; +import ewm.events.dto.EventFullDto; +import ewm.events.dto.EventShortDto; +import ewm.events.dto.EventUpdateUserDto; +import ewm.events.service.EventService; +import ewm.requests.dto.EventRequestStatusUpdateRequest; +import ewm.requests.dto.EventRequestStatusUpdateResult; +import ewm.requests.dto.RequestDto; +import ewm.requests.service.RequestService; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Slf4j +@Validated +@RestController +@RequestMapping("/users/{userId}/events") +@RequiredArgsConstructor +public class EventControllerPrivate { + private final EventService eventService; + private final RequestService requestService; + + @PostMapping + @ResponseStatus(value = HttpStatus.CREATED) + public EventFullDto addEvent(@PathVariable Long userId, @RequestBody @Valid EventCreateDto dto) { + return eventService.create(userId, dto); + } + + @PatchMapping("/{eventId}") + public EventFullDto updateEventByOwner(@PathVariable Long userId, + @PathVariable Long eventId, + @RequestBody @Valid EventUpdateUserDto updateEvent) { + return eventService.updateEventByOwner(userId, eventId, updateEvent); + } + + @PatchMapping("/{eventId}/requests") + public EventRequestStatusUpdateResult updateRequestsStatus(@PathVariable Long userId, + @PathVariable Long eventId, + @RequestBody EventRequestStatusUpdateRequest request) { + return requestService.updateRequestsStatus(userId, eventId, request); + } + + @GetMapping + List getEventsByOwner(@PathVariable Long userId, + @RequestParam(value = "from", defaultValue = "0") @PositiveOrZero Integer from, + @RequestParam(value = "size", defaultValue = "10") @Positive Integer size) { + return eventService.getEventsByOwnerId(userId, from, size); + } + + @GetMapping("/{eventId}") + public EventFullDto getEventByOwner(@PathVariable Long userId, @PathVariable Long eventId) { + return eventService.getEventByOwner(userId, eventId); + } + + @GetMapping("/{eventId}/requests") + public List getRequestsByEventOwner(@PathVariable Long userId, @PathVariable Long eventId) { + return requestService.getRequestsByEventOwner(userId, eventId); + } +} diff --git a/main-service/src/main/java/ewm/events/controller/EventControllerPublic.java b/main-service/src/main/java/ewm/events/controller/EventControllerPublic.java new file mode 100644 index 0000000..299a959 --- /dev/null +++ b/main-service/src/main/java/ewm/events/controller/EventControllerPublic.java @@ -0,0 +1,49 @@ +package ewm.events.controller; + + +import ewm.events.dto.EventViewsFullDto; +import ewm.events.dto.EventViewsShortDto; +import ewm.events.service.EventService; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDateTime; +import java.util.List; + +@Validated +@RestController +@RequiredArgsConstructor +@RequestMapping("/events") +public class EventControllerPublic { + private final EventService eventService; + + @GetMapping + public List getEvents( + @RequestParam(required = false) String text, + @RequestParam(required = false) List categories, + @RequestParam(required = false) Boolean paid, + @RequestParam(required = false) + LocalDateTime rangeStart, + @RequestParam(required = false) + LocalDateTime rangeEnd, + @RequestParam(defaultValue = "false") Boolean onlyAvailable, + @RequestParam(defaultValue = "EVENT_DATE") String sort, + @RequestParam(value = "from", defaultValue = "0") @PositiveOrZero + Integer from, + @RequestParam(value = "size", defaultValue = "10") @Positive + Integer size, + HttpServletRequest request) { + return eventService.getEvents(text, categories, paid, rangeStart, rangeEnd, onlyAvailable, + sort, from, size, request); + } + + @GetMapping("/{eventId}") + public EventViewsFullDto getEventById(@PathVariable Long eventId, HttpServletRequest request) { + return eventService.getEventById(eventId, request); + } + +} \ No newline at end of file diff --git a/main-service/src/main/java/ewm/events/dto/EventCreateDto.java b/main-service/src/main/java/ewm/events/dto/EventCreateDto.java new file mode 100644 index 0000000..ab8d46f --- /dev/null +++ b/main-service/src/main/java/ewm/events/dto/EventCreateDto.java @@ -0,0 +1,54 @@ +package ewm.events.dto; + +import ewm.locations.LocationDto; +import jakarta.validation.Valid; +import jakarta.validation.constraints.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class EventCreateDto { + @Size(min = 20, max = 2000) + @NotBlank + private String annotation; + + @NotNull + private Long category; + + @Size(min = 20, max = 7000) + @NotBlank + private String description; + + @NotNull + @Future + private LocalDateTime eventDate; + + @NotNull + @Valid + private LocationDto location; + + private boolean paid = false; + + @PositiveOrZero + private int participantLimit = 0; + + private boolean requestModeration = true; + + @Size(min = 3, max = 120) + @NotBlank + private String title; + + public Boolean getPaid() { + return paid; + } + + public Boolean getRequestModeration() { + return requestModeration; + } + +} diff --git a/main-service/src/main/java/ewm/events/dto/EventFullDto.java b/main-service/src/main/java/ewm/events/dto/EventFullDto.java new file mode 100644 index 0000000..817aa6c --- /dev/null +++ b/main-service/src/main/java/ewm/events/dto/EventFullDto.java @@ -0,0 +1,34 @@ +package ewm.events.dto; + +import ewm.categories.dto.CategoryDto; +import ewm.events.enums.State; +import ewm.locations.LocationDto; +import ewm.user.dto.UserShortDto; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Builder +@Data +@AllArgsConstructor +@NoArgsConstructor +public class EventFullDto { + private Long id; + private String annotation; + private CategoryDto category; + private Long confirmedRequests; + private LocalDateTime createdOn; + private String description; + private LocalDateTime eventDate; + private UserShortDto initiator; + private LocationDto location; + private Boolean paid; + private Integer participantLimit; + private LocalDateTime publishedOn; + private Boolean requestModeration; + private State state; + private String title; +} diff --git a/main-service/src/main/java/ewm/events/dto/EventShortDto.java b/main-service/src/main/java/ewm/events/dto/EventShortDto.java new file mode 100644 index 0000000..0c99401 --- /dev/null +++ b/main-service/src/main/java/ewm/events/dto/EventShortDto.java @@ -0,0 +1,32 @@ +package ewm.events.dto; + +import ewm.categories.dto.CategoryDto; +import ewm.user.dto.UserShortDto; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Builder +@Data +@AllArgsConstructor +@NoArgsConstructor +public class EventShortDto { + private Long id; + + private String annotation; + + private CategoryDto category; + + private Long confirmedRequests; + + private LocalDateTime eventDate; + + private UserShortDto initiator; + + private Boolean paid; + + private String title; +} diff --git a/main-service/src/main/java/ewm/events/dto/EventUpdateAdminDto.java b/main-service/src/main/java/ewm/events/dto/EventUpdateAdminDto.java new file mode 100644 index 0000000..469282a --- /dev/null +++ b/main-service/src/main/java/ewm/events/dto/EventUpdateAdminDto.java @@ -0,0 +1,44 @@ +package ewm.events.dto; + +import ewm.locations.LocationDto; +import jakarta.validation.constraints.Future; +import jakarta.validation.constraints.PositiveOrZero; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Builder +@Data +@AllArgsConstructor +@NoArgsConstructor +public class EventUpdateAdminDto { + + @Size(min = 20, max = 2000) + private String annotation; + + private Long category; + + @Size(min = 20, max = 7000) + private String description; + + @Future + private LocalDateTime eventDate; + + private LocationDto location; + + private Boolean paid; + + @PositiveOrZero + private Integer participantLimit; + + private Boolean requestModeration; + + private String stateAction; + + @Size(min = 3, max = 120) + private String title; +} diff --git a/main-service/src/main/java/ewm/events/dto/EventUpdateUserDto.java b/main-service/src/main/java/ewm/events/dto/EventUpdateUserDto.java new file mode 100644 index 0000000..c11dc45 --- /dev/null +++ b/main-service/src/main/java/ewm/events/dto/EventUpdateUserDto.java @@ -0,0 +1,44 @@ +package ewm.events.dto; + +import ewm.locations.LocationDto; +import jakarta.validation.constraints.Future; +import jakarta.validation.constraints.PositiveOrZero; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Builder +@Data +@AllArgsConstructor +@NoArgsConstructor +public class EventUpdateUserDto { + + @Size(min = 20, max = 2000) + private String annotation; + + private Long category; + + @Size(min = 20, max = 7000) + private String description; + + @Future + private LocalDateTime eventDate; + + private LocationDto location; + + private Boolean paid; + + @PositiveOrZero + private Integer participantLimit; + + private Boolean requestModeration; + + private String stateAction; + + @Size(min = 3, max = 120) + private String title; +} diff --git a/main-service/src/main/java/ewm/events/dto/EventViewsFullDto.java b/main-service/src/main/java/ewm/events/dto/EventViewsFullDto.java new file mode 100644 index 0000000..4d86921 --- /dev/null +++ b/main-service/src/main/java/ewm/events/dto/EventViewsFullDto.java @@ -0,0 +1,51 @@ +package ewm.events.dto; + +import ewm.categories.dto.CategoryDto; +import ewm.events.enums.State; +import ewm.locations.LocationDto; +import ewm.user.dto.UserShortDto; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Builder +@Data +@AllArgsConstructor +@NoArgsConstructor +public class EventViewsFullDto { + private Long id; + + private String title; + + private String annotation; + + private CategoryDto category; + + private boolean paid; + + private LocalDateTime eventDate; + + private UserShortDto initiator; + + private Long views; + + private Long confirmedRequests; + + private String description; + + private Integer participantLimit; + + private State state; + + private LocalDateTime createdOn; + + private LocalDateTime publishedOn; + + private LocationDto location; + + private boolean requestModeration; + +} diff --git a/main-service/src/main/java/ewm/events/dto/EventViewsShortDto.java b/main-service/src/main/java/ewm/events/dto/EventViewsShortDto.java new file mode 100644 index 0000000..abebf31 --- /dev/null +++ b/main-service/src/main/java/ewm/events/dto/EventViewsShortDto.java @@ -0,0 +1,34 @@ +package ewm.events.dto; + +import ewm.categories.dto.CategoryDto; +import ewm.user.dto.UserShortDto; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Builder +@Data +@AllArgsConstructor +@NoArgsConstructor +public class EventViewsShortDto { + private Long id; + + private String annotation; + + private CategoryDto category; + + private Long confirmedRequests; + + private LocalDateTime eventDate; + + private UserShortDto initiator; + + private Boolean paid; + + private String title; + + private Long views; +} diff --git a/main-service/src/main/java/ewm/events/enums/State.java b/main-service/src/main/java/ewm/events/enums/State.java new file mode 100644 index 0000000..5cf3232 --- /dev/null +++ b/main-service/src/main/java/ewm/events/enums/State.java @@ -0,0 +1,7 @@ +package ewm.events.enums; + +public enum State { + PENDING, + PUBLISHED, + CANCELED; +} diff --git a/main-service/src/main/java/ewm/events/enums/StateActionAdmin.java b/main-service/src/main/java/ewm/events/enums/StateActionAdmin.java new file mode 100644 index 0000000..7f01a8c --- /dev/null +++ b/main-service/src/main/java/ewm/events/enums/StateActionAdmin.java @@ -0,0 +1,6 @@ +package ewm.events.enums; + +public enum StateActionAdmin { + PUBLISH_EVENT, + REJECT_EVENT +} diff --git a/main-service/src/main/java/ewm/events/enums/StateActionPrivate.java b/main-service/src/main/java/ewm/events/enums/StateActionPrivate.java new file mode 100644 index 0000000..499b713 --- /dev/null +++ b/main-service/src/main/java/ewm/events/enums/StateActionPrivate.java @@ -0,0 +1,6 @@ +package ewm.events.enums; + +public enum StateActionPrivate { + SEND_TO_REVIEW, + CANCEL_REVIEW +} diff --git a/main-service/src/main/java/ewm/events/service/EventService.java b/main-service/src/main/java/ewm/events/service/EventService.java new file mode 100644 index 0000000..516dc45 --- /dev/null +++ b/main-service/src/main/java/ewm/events/service/EventService.java @@ -0,0 +1,31 @@ +package ewm.events.service; + +import ewm.events.dto.*; +import jakarta.servlet.http.HttpServletRequest; + +import java.time.LocalDateTime; +import java.util.List; + +public interface EventService { + EventFullDto create(long userId, EventCreateDto dto); + + EventFullDto updateEventByOwner(Long userId, Long eventId, EventUpdateUserDto dto); + + EventFullDto updateEventByAdmin(Long eventId, EventUpdateAdminDto dto); + + List getEventsByOwnerId(Long userId, Integer from, Integer size); + + EventFullDto getEventByOwner(Long userId, Long eventId); + + List getEventsByAdmin(List users, List states, List categories, + LocalDateTime rangeStart, LocalDateTime rangeEnd, + Integer from, Integer size); + + List getEvents(String text, List categories, Boolean paid, LocalDateTime rangeStart, + LocalDateTime rangeEnd, Boolean onlyAvailable, String sort, Integer from, + Integer size, HttpServletRequest request); + + EventViewsFullDto getEventById(Long eventId, HttpServletRequest request); + + +} diff --git a/main-service/src/main/java/ewm/events/service/EventServiceImpl.java b/main-service/src/main/java/ewm/events/service/EventServiceImpl.java new file mode 100644 index 0000000..c6b1b84 --- /dev/null +++ b/main-service/src/main/java/ewm/events/service/EventServiceImpl.java @@ -0,0 +1,390 @@ +package ewm.events.service; + +import client.StatsClient; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import dto.EndpointHitDto; +import dto.ViewStatsDto; +import ewm.categories.Category; +import ewm.categories.service.CategoryService; +import ewm.events.Event; +import ewm.events.EventMapper; +import ewm.events.EventRepository; +import ewm.events.dto.*; +import ewm.events.enums.State; +import ewm.events.enums.StateActionAdmin; +import ewm.events.enums.StateActionPrivate; +import ewm.exception.BadRequestException; +import ewm.exception.ForbiddenException; +import ewm.exception.NotFoundException; +import ewm.locations.Location; +import ewm.locations.service.LocationService; +import ewm.requests.RequestRepository; +import ewm.requests.dto.ConfirmedRequestsDto; +import ewm.user.User; +import ewm.user.service.UserService; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.ValidationException; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +import static ewm.events.enums.State.*; +import static ewm.events.enums.StateActionAdmin.PUBLISH_EVENT; +import static ewm.events.enums.StateActionAdmin.REJECT_EVENT; +import static ewm.events.enums.StateActionPrivate.CANCEL_REVIEW; +import static ewm.events.enums.StateActionPrivate.SEND_TO_REVIEW; +import static ewm.requests.RequestStatus.CONFIRMED; + +@Transactional +@Service +@RequiredArgsConstructor +public class EventServiceImpl implements EventService { + private final EventRepository eventRepository; + private final EventMapper eventMapper; + private final UserService userService; + private final CategoryService categoryService; + private final LocationService locationService; + private final RequestRepository requestRepository; + private final StatsClient statsClient; + @Value("${app}") + String app; + + @Override + public EventFullDto create(long userId, EventCreateDto dto) { + validateEventTime(dto.getEventDate()); + User user = userService.getModelById(userId); + Category category = categoryService.checkAndReturnCategory(dto.getCategory()); + Location location = locationService.getOrSave(dto.getLocation()); + + Event event = eventMapper.eventCreateDtoToModel(dto); + event.setInitiator(user); + event.setCategory(category); + event.setLocation(location); + event.setCreatedOn(LocalDateTime.now()); + event.setState(PENDING); + + return eventMapper.modelToEventFullDto(eventRepository.save(event), 0L); + } + + @Override + public EventFullDto updateEventByOwner(Long userId, Long eventId, EventUpdateUserDto dto) { + Event event = checkAndReturnEvent(eventId); + if (event.getInitiator().getId() != userId) { + throw new BadRequestException("Event must not be published"); + } + if (event.getState() == PUBLISHED) { + throw new ForbiddenException("Cannot update the event because it's not in the right state: PUBLISHED"); + } + String annotation = dto.getAnnotation(); + if (annotation != null && !annotation.isBlank()) { + event.setAnnotation(annotation); + } + if (dto.getCategory() != null) { + event.setCategory(categoryService.checkAndReturnCategory(dto.getCategory())); + } + String description = dto.getDescription(); + if (description != null && !description.isBlank()) { + event.setDescription(description); + } + LocalDateTime eventDate = dto.getEventDate(); + if (eventDate != null) { + validateEventTime(eventDate); + event.setEventDate(eventDate); + } + if (dto.getLocation() != null) { + event.setLocation(locationService.getOrSave(dto.getLocation())); + } + if (dto.getPaid() != null) { + event.setPaid(dto.getPaid()); + } + if (dto.getParticipantLimit() != null) { + event.setParticipantLimit(dto.getParticipantLimit()); + } + if (dto.getRequestModeration() != null) { + event.setRequestModeration(dto.getRequestModeration()); + } + String title = dto.getTitle(); + if (title != null && !title.isBlank()) { + event.setTitle(title); + } + if (dto.getStateAction() != null) { + StateActionPrivate stateActionPrivate = StateActionPrivate.valueOf(dto.getStateAction()); + if (stateActionPrivate.equals(SEND_TO_REVIEW)) { + event.setState(PENDING); + } else if (stateActionPrivate.equals(CANCEL_REVIEW)) { + event.setState(CANCELED); + } + } + event = eventRepository.save(event); + return eventMapper.modelToEventFullDto(event, requestRepository.countByEventIdAndStatus(eventId, CONFIRMED)); + } + + @Override + public EventFullDto updateEventByAdmin(Long eventId, EventUpdateAdminDto dto) { + Event event = checkAndReturnEvent(eventId); + + if (dto.getStateAction() != null) { + StateActionAdmin stateAction = StateActionAdmin.valueOf(dto.getStateAction()); + if (!event.getState().equals(PENDING) && stateAction.equals(PUBLISH_EVENT)) { + throw new ForbiddenException("Cannot publish the event because it's not in the right state: not PENDING"); + } + if (event.getState().equals(PUBLISHED) && stateAction.equals(REJECT_EVENT)) { + throw new ForbiddenException("Cannot reject the event because it's not in the right state: PUBLISHED"); + } + if (stateAction.equals(PUBLISH_EVENT)) { + event.setState(PUBLISHED); + event.setPublishedOn(LocalDateTime.now()); + } else if (stateAction.equals(REJECT_EVENT)) { + event.setState(State.CANCELED); + } + } + + String annotation = dto.getAnnotation(); + if (annotation != null && !annotation.isBlank()) { + event.setAnnotation(annotation); + } + if (dto.getCategory() != null) { + event.setCategory(categoryService.checkAndReturnCategory(dto.getCategory())); + } + String description = dto.getDescription(); + if (description != null && !description.isBlank()) { + event.setDescription(description); + } + LocalDateTime eventDate = dto.getEventDate(); + if (eventDate != null) { + validateEventTime(eventDate); + event.setEventDate(eventDate); + } + if (dto.getLocation() != null) { + event.setLocation(locationService.getOrSave(dto.getLocation())); + } + if (dto.getPaid() != null) { + event.setPaid(dto.getPaid()); + } + if (dto.getParticipantLimit() != null) { + event.setParticipantLimit(dto.getParticipantLimit()); + } + if (dto.getRequestModeration() != null) { + event.setRequestModeration(dto.getRequestModeration()); + } + String title = dto.getTitle(); + if (title != null && !title.isBlank()) { + event.setTitle(title); + } + + event = eventRepository.save(event); + return eventMapper.modelToEventFullDto(event, requestRepository.countByEventIdAndStatus(eventId, CONFIRMED)); + } + + @Override + @Transactional(readOnly = true) + public List getEventsByOwnerId(Long userId, Integer from, Integer size) { + List events = eventRepository.findAllByInitiatorId(userId, PageRequest.of(from / size, size)); + List ids = events.stream().map(Event::getId).collect(Collectors.toList()); + Map confirmedRequests = requestRepository.findAllByEventIdInAndStatus(ids, CONFIRMED) + .stream() + .collect(Collectors.toMap(ConfirmedRequestsDto::getEvent, ConfirmedRequestsDto::getCount)); + return events.stream() + .map(event -> eventMapper.toEventShortDto(event, confirmedRequests.getOrDefault(event.getId(), 0L))) + .collect(Collectors.toList()); + } + + @Override + @Transactional(readOnly = true) + public EventFullDto getEventByOwner(Long userId, Long eventId) { + Event event = checkAndReturnEvent(eventId); + if (event.getInitiator().getId() == userId) { + return eventMapper.modelToEventFullDto(event, requestRepository.countByEventIdAndStatus(eventId, CONFIRMED)); + } else { + throw new NotFoundException("userId не верный"); + } + } + + @Override + @Transactional(readOnly = true) + public List getEventsByAdmin(List users, List states, List categories, + LocalDateTime rangeStart, LocalDateTime rangeEnd, + Integer from, Integer size) { + if (rangeStart != null && rangeEnd != null && rangeStart.isAfter(rangeEnd)) { + throw new ValidationException("Incorrectly made request, Start is after End"); + } + Specification specification = Specification.where(null); + if (users != null) { + specification = specification.and((root, query, criteriaBuilder) -> + root.get("initiator").get("id").in(users)); + } + if (states != null) { + specification = specification.and((root, query, criteriaBuilder) -> + root.get("state").as(String.class).in(states)); + } + if (categories != null) { + specification = specification.and((root, query, criteriaBuilder) -> + root.get("category").get("id").in(categories)); + } + if (rangeStart != null) { + specification = specification.and((root, query, criteriaBuilder) -> + criteriaBuilder.greaterThanOrEqualTo(root.get("eventDate"), rangeStart)); + } + if (rangeEnd != null) { + specification = specification.and((root, query, criteriaBuilder) -> + criteriaBuilder.lessThanOrEqualTo(root.get("eventDate"), rangeEnd)); + } + List events = eventRepository.findAll(specification, PageRequest.of(from / size, size)).getContent(); + List result = new ArrayList<>(); + List uris = events.stream() + .map(event -> String.format("/events/%s", event.getId())) + .collect(Collectors.toList()); + LocalDateTime start = events.stream() + .map(Event::getCreatedOn) + .min(LocalDateTime::compareTo) + .orElseThrow(() -> new NotFoundException("Start not found")); + ResponseEntity response = statsClient.getStats(start, LocalDateTime.now(), uris, true); + + List ids = events.stream().map(Event::getId).collect(Collectors.toList()); + Map confirmedRequests = requestRepository.findAllByEventIdInAndStatus(ids, CONFIRMED).stream() + .collect(Collectors.toMap(ConfirmedRequestsDto::getEvent, ConfirmedRequestsDto::getCount)); + for (Event event : events) { + ObjectMapper mapper = new ObjectMapper(); + List statsDto = mapper.convertValue(response.getBody(), new TypeReference<>() { + }); + if (!statsDto.isEmpty()) { + result.add(eventMapper.toEventFullDtoWithViews(event, statsDto.getFirst().getHits(), + confirmedRequests.getOrDefault(event.getId(), 0L))); + } else { + result.add(eventMapper.toEventFullDtoWithViews(event, 0L, + confirmedRequests.getOrDefault(event.getId(), 0L))); + } + } + return result; + } + + @Override + @Transactional(readOnly = true) + public List getEvents(String text, List categories, Boolean paid, LocalDateTime rangeStart, + LocalDateTime rangeEnd, Boolean onlyAvailable, String sort, Integer from, + Integer size, HttpServletRequest request) { + if (rangeStart != null && rangeEnd != null && rangeStart.isAfter(rangeEnd)) { + throw new BadRequestException("Incorrectly made request, Start is after End"); + } + Specification specification = Specification.where(null); + if (text != null) { + specification = specification.and((root, query, criteriaBuilder) -> + criteriaBuilder.or( + criteriaBuilder.like(criteriaBuilder.lower(root.get("annotation")), "%" + text.toLowerCase() + "%"), + criteriaBuilder.like(criteriaBuilder.lower(root.get("description")), "%" + text.toLowerCase() + "%") + )); + } + if (categories != null) { + specification = specification.and((root, query, criteriaBuilder) -> + root.get("category").get("id").in(categories)); + } + if (paid != null) { + specification = specification.and((root, query, criteriaBuilder) -> + criteriaBuilder.equal(root.get("paid"), paid)); + } + LocalDateTime now = LocalDateTime.now(); + LocalDateTime startDateTime = Objects.requireNonNullElseGet(rangeStart, () -> now); + specification = specification.and((root, query, criteriaBuilder) -> + criteriaBuilder.greaterThan(root.get("eventDate"), startDateTime)); + if (rangeEnd != null) { + specification = specification.and((root, query, criteriaBuilder) -> + criteriaBuilder.lessThan(root.get("eventDate"), rangeEnd)); + } + if (onlyAvailable != null && onlyAvailable) { + specification = specification.and((root, query, criteriaBuilder) -> + criteriaBuilder.greaterThanOrEqualTo(root.get("participantLimit"), 0)); + } + specification = specification.and((root, query, criteriaBuilder) -> + criteriaBuilder.equal(root.get("state"), PUBLISHED)); + PageRequest pageRequest; + if (sort.equals("EVENT_DATE")) { + pageRequest = PageRequest.of(from / size, size, Sort.by("eventDate")); + } else if (sort.equals("VIEWS")) { + pageRequest = PageRequest.of(from / size, size, Sort.by("views").descending()); + } else { + throw new ValidationException("Unknown sort: " + sort); + } + List events = eventRepository.findAll(specification, pageRequest).getContent(); + List result = new ArrayList<>(); + List uris = events.stream() + .map(event -> String.format("/events/%s", event.getId())) + .collect(Collectors.toList()); + LocalDateTime start = events.stream() + .map(Event::getCreatedOn) + .min(LocalDateTime::compareTo) + .orElseThrow(() -> new NotFoundException("Start not found")); + ResponseEntity response = statsClient.getStats(start, LocalDateTime.now(), uris, true); + System.out.println(response.getBody()); + List ids = events.stream().map(Event::getId).collect(Collectors.toList()); + Map confirmedRequests = requestRepository.findAllByEventIdInAndStatus(ids, CONFIRMED) + .stream() + .collect(Collectors.toMap(ConfirmedRequestsDto::getEvent, ConfirmedRequestsDto::getCount)); + for (Event event : events) { + ObjectMapper mapper = new ObjectMapper(); + List statsDto = mapper.convertValue(response.getBody(), new TypeReference<>() { + }); + if (!statsDto.isEmpty()) { + result.add(eventMapper.toEventShortDtoWithViews(event, statsDto.getFirst().getHits(), + confirmedRequests.getOrDefault(event.getId(), 0L))); + } else { + result.add(eventMapper.toEventShortDtoWithViews(event, 0L, + confirmedRequests.getOrDefault(event.getId(), 0L))); + } + } + EndpointHitDto endpointHitDto = new EndpointHitDto(app, request.getRequestURI(), request.getRemoteAddr(), + LocalDateTime.now()); + statsClient.save(endpointHitDto); + return result; + } + + @Override + @Transactional(readOnly = true) + public EventViewsFullDto getEventById(Long eventId, HttpServletRequest request) { + Event event = checkAndReturnEvent(eventId); + if (event.getState() != PUBLISHED) { + throw new NotFoundException("Event is not PUBLISHED"); + } + ResponseEntity response = statsClient.getStats(event.getCreatedOn(), LocalDateTime.now(), + List.of(request.getRequestURI()), true); + ObjectMapper mapper = new ObjectMapper(); + List statsDto = mapper.convertValue(response.getBody(), new TypeReference<>() { + }); + EventViewsFullDto result; + if (!statsDto.isEmpty()) { + result = eventMapper.toEventFullDtoWithViews(event, statsDto.getFirst().getHits(), + requestRepository.countByEventIdAndStatus(eventId, CONFIRMED)); + } else { + result = eventMapper.toEventFullDtoWithViews(event, 0L, + requestRepository.countByEventIdAndStatus(eventId, CONFIRMED)); + } + EndpointHitDto endpointHitDto = new EndpointHitDto(app, request.getRequestURI(), request.getRemoteAddr(), + LocalDateTime.now()); + statsClient.save(endpointHitDto); + return result; + } + + private void validateEventTime(LocalDateTime eventTime) { + if (eventTime.isBefore(LocalDateTime.now().plusHours(2))) { + throw new ForbiddenException(" должно содержать дату, которая еще не наступила. Value: " + eventTime); + } + } + + private Event checkAndReturnEvent(Long eventId) { + return eventRepository.findById(eventId).orElseThrow(() -> + new NotFoundException("Event with id=" + eventId + " was not found")); + } + +} diff --git a/main-service/src/main/java/ewm/exception/BadRequestException.java b/main-service/src/main/java/ewm/exception/BadRequestException.java new file mode 100644 index 0000000..4bf52b6 --- /dev/null +++ b/main-service/src/main/java/ewm/exception/BadRequestException.java @@ -0,0 +1,7 @@ +package ewm.exception; + +public class BadRequestException extends RuntimeException { + public BadRequestException(String message) { + super(message); + } +} diff --git a/main-service/src/main/java/ewm/exception/ErrorHandler.java b/main-service/src/main/java/ewm/exception/ErrorHandler.java new file mode 100644 index 0000000..9bfefd5 --- /dev/null +++ b/main-service/src/main/java/ewm/exception/ErrorHandler.java @@ -0,0 +1,73 @@ +package ewm.exception; + +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.time.LocalDateTime; + + +@RestControllerAdvice +public class ErrorHandler { + + @ExceptionHandler + @ResponseStatus(HttpStatus.NOT_FOUND) + public ErrorResponse handleNotFoundException(final NotFoundException e) { + return ErrorResponse.builder() + .message(e.getMessage()) + .reason("The required object was not found.") + .status("NOT_FOUND") + .timestamp(LocalDateTime.now()) + .build(); + } + + @ExceptionHandler + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ErrorResponse handleInternalServerErrorException(final InternalServerErrorException e) { + return ErrorResponse.builder() + .message(e.getMessage()) + .status("INTERNAL_SERVER_ERROR") + .timestamp(LocalDateTime.now()) + .build(); + } + + @ExceptionHandler + @ResponseStatus(HttpStatus.CONFLICT) + public ErrorResponse handleForbiddenException(ForbiddenException e) { + return ErrorResponse.builder() + .message(e.getMessage()) + .reason("For the requested operation the conditions are not met.") + .status("FORBIDDEN") + .timestamp(LocalDateTime.now()) + .build(); + } + + @ExceptionHandler + @ResponseStatus(HttpStatus.CONFLICT) + public ErrorResponse handleСonflictDataIntegrityViolationException(DataIntegrityViolationException e) { + return ErrorResponse.builder() + .message(e.getMessage()) + .reason("Integrity constraint has been violated.") + .status("CONFLICT") + .timestamp(LocalDateTime.now()) + .build(); + } + + @ExceptionHandler({ValidationException.class, NumberFormatException.class, BadRequestException.class}) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleBadRequestException(RuntimeException e) { + return ErrorResponse.builder() + .message(e.getMessage()) + .reason("Incorrectly made request.") + .status("BAD_REQUEST") + .timestamp(LocalDateTime.now()) + .build(); + } + + +} + + + diff --git a/main-service/src/main/java/ewm/exception/ErrorResponse.java b/main-service/src/main/java/ewm/exception/ErrorResponse.java new file mode 100644 index 0000000..00547fe --- /dev/null +++ b/main-service/src/main/java/ewm/exception/ErrorResponse.java @@ -0,0 +1,18 @@ +package ewm.exception; + + +import lombok.Builder; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Data +@Builder +public class ErrorResponse { + private final List errors; + private final String message; + private final String reason; + private final String status; + private final LocalDateTime timestamp; +} \ No newline at end of file diff --git a/main-service/src/main/java/ewm/exception/ForbiddenException.java b/main-service/src/main/java/ewm/exception/ForbiddenException.java new file mode 100644 index 0000000..968e1b9 --- /dev/null +++ b/main-service/src/main/java/ewm/exception/ForbiddenException.java @@ -0,0 +1,7 @@ +package ewm.exception; + +public class ForbiddenException extends RuntimeException { + public ForbiddenException(String message) { + super(message); + } +} diff --git a/stats/stats-server/src/main/java/ewm/exception/InternalServerErrorException.java b/main-service/src/main/java/ewm/exception/InternalServerErrorException.java similarity index 100% rename from stats/stats-server/src/main/java/ewm/exception/InternalServerErrorException.java rename to main-service/src/main/java/ewm/exception/InternalServerErrorException.java diff --git a/stats/stats-server/src/main/java/ewm/exception/NotFoundException.java b/main-service/src/main/java/ewm/exception/NotFoundException.java similarity index 100% rename from stats/stats-server/src/main/java/ewm/exception/NotFoundException.java rename to main-service/src/main/java/ewm/exception/NotFoundException.java diff --git a/main-service/src/main/java/ewm/exception/ValidationException.java b/main-service/src/main/java/ewm/exception/ValidationException.java new file mode 100644 index 0000000..c36284c --- /dev/null +++ b/main-service/src/main/java/ewm/exception/ValidationException.java @@ -0,0 +1,7 @@ +package ewm.exception; + +public class ValidationException extends Exception { + public ValidationException(String message) { + super(message); + } +} diff --git a/main-service/src/main/java/ewm/locations/Location.java b/main-service/src/main/java/ewm/locations/Location.java new file mode 100644 index 0000000..c1b6859 --- /dev/null +++ b/main-service/src/main/java/ewm/locations/Location.java @@ -0,0 +1,24 @@ +package ewm.locations; + +import jakarta.persistence.*; +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Getter +@Setter +@ToString +@Entity +@Table(name = "locations") +public class Location { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + @Column(nullable = false) + private Float lat; + + @Column(nullable = false) + private Float lon; +} diff --git a/main-service/src/main/java/ewm/locations/LocationDto.java b/main-service/src/main/java/ewm/locations/LocationDto.java new file mode 100644 index 0000000..f5d256b --- /dev/null +++ b/main-service/src/main/java/ewm/locations/LocationDto.java @@ -0,0 +1,19 @@ +package ewm.locations; + +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class LocationDto { + @NotNull + private float lat; + + @NotNull + private float lon; +} diff --git a/main-service/src/main/java/ewm/locations/LocationMapper.java b/main-service/src/main/java/ewm/locations/LocationMapper.java new file mode 100644 index 0000000..6215246 --- /dev/null +++ b/main-service/src/main/java/ewm/locations/LocationMapper.java @@ -0,0 +1,22 @@ +package ewm.locations; + +import org.springframework.stereotype.Component; + +@Component +public class LocationMapper { + + public Location locationDtoToModel(LocationDto dto) { + return Location.builder() + .lat(dto.getLat()) + .lon(dto.getLon()) + .build(); + } + + public LocationDto modelToDto(Location location) { + return LocationDto.builder() + .lat(location.getLat()) + .lon(location.getLon()) + .build(); + } + +} diff --git a/main-service/src/main/java/ewm/locations/LocationRepository.java b/main-service/src/main/java/ewm/locations/LocationRepository.java new file mode 100644 index 0000000..eb8c511 --- /dev/null +++ b/main-service/src/main/java/ewm/locations/LocationRepository.java @@ -0,0 +1,9 @@ +package ewm.locations; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface LocationRepository extends JpaRepository { + + Location findByLatAndLon(Float lat, Float lon); + +} diff --git a/main-service/src/main/java/ewm/locations/service/LocationService.java b/main-service/src/main/java/ewm/locations/service/LocationService.java new file mode 100644 index 0000000..3567f3c --- /dev/null +++ b/main-service/src/main/java/ewm/locations/service/LocationService.java @@ -0,0 +1,8 @@ +package ewm.locations.service; + +import ewm.locations.Location; +import ewm.locations.LocationDto; + +public interface LocationService { + Location getOrSave(LocationDto dto); +} diff --git a/main-service/src/main/java/ewm/locations/service/LocationServiceImpl.java b/main-service/src/main/java/ewm/locations/service/LocationServiceImpl.java new file mode 100644 index 0000000..21585ea --- /dev/null +++ b/main-service/src/main/java/ewm/locations/service/LocationServiceImpl.java @@ -0,0 +1,26 @@ +package ewm.locations.service; + +import ewm.locations.Location; +import ewm.locations.LocationDto; +import ewm.locations.LocationMapper; +import ewm.locations.LocationRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Objects; + +@Transactional +@Service +@RequiredArgsConstructor +public class LocationServiceImpl implements LocationService { + private final LocationMapper mapper; + private final LocationRepository repository; + + @Override + public Location getOrSave(LocationDto dto) { + Location location = repository.findByLatAndLon(dto.getLat(), dto.getLon()); + return Objects.requireNonNullElseGet(location, () -> repository.save(mapper.locationDtoToModel(dto))); + } + +} diff --git a/main-service/src/main/java/ewm/requests/Request.java b/main-service/src/main/java/ewm/requests/Request.java new file mode 100644 index 0000000..7980055 --- /dev/null +++ b/main-service/src/main/java/ewm/requests/Request.java @@ -0,0 +1,37 @@ +package ewm.requests; + +import ewm.events.Event; +import ewm.user.User; +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDateTime; + +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Getter +@Setter +@ToString +@Entity +@Table(name = "requests") +public class Request { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + private LocalDateTime created; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "event_id") + @ToString.Exclude + private Event event; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "requester_id") + @ToString.Exclude + private User requester; + + @Enumerated(EnumType.STRING) + private RequestStatus status; +} diff --git a/main-service/src/main/java/ewm/requests/RequestController.java b/main-service/src/main/java/ewm/requests/RequestController.java new file mode 100644 index 0000000..d3a41ef --- /dev/null +++ b/main-service/src/main/java/ewm/requests/RequestController.java @@ -0,0 +1,32 @@ +package ewm.requests; + +import ewm.requests.dto.RequestDto; +import ewm.requests.service.RequestService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/users/{userId}/requests") +public class RequestController { + private final RequestService requestService; + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public RequestDto addRequest(@PathVariable Long userId, @RequestParam Long eventId) { + return requestService.addRequest(userId, eventId); + } + + @PatchMapping("/{requestId}/cancel") + public RequestDto cancelRequest(@PathVariable Long userId, @PathVariable Long requestId) { + return requestService.cancelRequest(userId, requestId); + } + + @GetMapping + public List getRequestsByUser(@PathVariable Long userId) { + return requestService.getRequestsByUser(userId); + } +} \ No newline at end of file diff --git a/main-service/src/main/java/ewm/requests/RequestMapper.java b/main-service/src/main/java/ewm/requests/RequestMapper.java new file mode 100644 index 0000000..b596a81 --- /dev/null +++ b/main-service/src/main/java/ewm/requests/RequestMapper.java @@ -0,0 +1,19 @@ +package ewm.requests; + +import ewm.requests.dto.RequestDto; +import org.springframework.stereotype.Component; + +@Component +public class RequestMapper { + + public RequestDto modelToDto(Request request) { + return new RequestDto( + request.getId(), + request.getCreated(), + request.getEvent().getId(), + request.getRequester().getId(), + request.getStatus() + ); + } + +} \ No newline at end of file diff --git a/main-service/src/main/java/ewm/requests/RequestRepository.java b/main-service/src/main/java/ewm/requests/RequestRepository.java new file mode 100644 index 0000000..44dcaa6 --- /dev/null +++ b/main-service/src/main/java/ewm/requests/RequestRepository.java @@ -0,0 +1,28 @@ +package ewm.requests; + +import ewm.requests.dto.ConfirmedRequestsDto; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface RequestRepository extends JpaRepository { + Request findByIdAndRequesterId(Long requestId, Long userId); + + List findAllByEventId(Long eventId); + + List findAllByRequesterId(Long userId); + + List findAllByEventIdAndIdInAndStatus(Long eventId, List requestId, RequestStatus status); + + Boolean existsByRequesterIdAndEventId(Long userId, Long eventId); + + Long countByEventIdAndStatus(Long eventId, RequestStatus status); + + @Query("SELECT new ewm.requests.dto.ConfirmedRequestsDto(COUNT(DISTINCT r.id), r.event.id) " + + "FROM Request AS r " + + "WHERE r.event.id IN (:ids) AND r.status = :status " + + "GROUP BY (r.event)") + List findAllByEventIdInAndStatus(@Param("ids") List ids, @Param("status") RequestStatus status); +} diff --git a/main-service/src/main/java/ewm/requests/RequestStatus.java b/main-service/src/main/java/ewm/requests/RequestStatus.java new file mode 100644 index 0000000..a14b991 --- /dev/null +++ b/main-service/src/main/java/ewm/requests/RequestStatus.java @@ -0,0 +1,8 @@ +package ewm.requests; + +public enum RequestStatus { + PENDING, + CONFIRMED, + REJECTED, + CANCELED +} diff --git a/main-service/src/main/java/ewm/requests/dto/ConfirmedRequestsDto.java b/main-service/src/main/java/ewm/requests/dto/ConfirmedRequestsDto.java new file mode 100644 index 0000000..0fd43ad --- /dev/null +++ b/main-service/src/main/java/ewm/requests/dto/ConfirmedRequestsDto.java @@ -0,0 +1,14 @@ +package ewm.requests.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +public class ConfirmedRequestsDto { + private Long count; + private Long event; + +} diff --git a/main-service/src/main/java/ewm/requests/dto/EventRequestStatusUpdateRequest.java b/main-service/src/main/java/ewm/requests/dto/EventRequestStatusUpdateRequest.java new file mode 100644 index 0000000..9772441 --- /dev/null +++ b/main-service/src/main/java/ewm/requests/dto/EventRequestStatusUpdateRequest.java @@ -0,0 +1,16 @@ +package ewm.requests.dto; + +import ewm.requests.RequestStatus; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class EventRequestStatusUpdateRequest { + private List requestIds; + private RequestStatus status; +} diff --git a/main-service/src/main/java/ewm/requests/dto/EventRequestStatusUpdateResult.java b/main-service/src/main/java/ewm/requests/dto/EventRequestStatusUpdateResult.java new file mode 100644 index 0000000..7038721 --- /dev/null +++ b/main-service/src/main/java/ewm/requests/dto/EventRequestStatusUpdateResult.java @@ -0,0 +1,15 @@ +package ewm.requests.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class EventRequestStatusUpdateResult { + private List confirmedRequests; + private List rejectedRequests; +} \ No newline at end of file diff --git a/main-service/src/main/java/ewm/requests/dto/RequestDto.java b/main-service/src/main/java/ewm/requests/dto/RequestDto.java new file mode 100644 index 0000000..9c55ee0 --- /dev/null +++ b/main-service/src/main/java/ewm/requests/dto/RequestDto.java @@ -0,0 +1,23 @@ +package ewm.requests.dto; + +import ewm.requests.RequestStatus; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class RequestDto { + private Long id; + + private LocalDateTime created; + + private Long event; + + private Long requester; + + private RequestStatus status; +} diff --git a/main-service/src/main/java/ewm/requests/service/RequestService.java b/main-service/src/main/java/ewm/requests/service/RequestService.java new file mode 100644 index 0000000..72e61c0 --- /dev/null +++ b/main-service/src/main/java/ewm/requests/service/RequestService.java @@ -0,0 +1,20 @@ +package ewm.requests.service; + +import ewm.requests.dto.EventRequestStatusUpdateRequest; +import ewm.requests.dto.EventRequestStatusUpdateResult; +import ewm.requests.dto.RequestDto; + +import java.util.List; + +public interface RequestService { + RequestDto addRequest(Long userId, Long eventId); + + EventRequestStatusUpdateResult updateRequestsStatus(Long userId, Long eventId, + EventRequestStatusUpdateRequest statusUpdateRequest); + + RequestDto cancelRequest(Long userId, Long requestId); + + List getRequestsByEventOwner(Long userId, Long eventId); + + List getRequestsByUser(Long userId); +} diff --git a/main-service/src/main/java/ewm/requests/service/RequestServiceImpl.java b/main-service/src/main/java/ewm/requests/service/RequestServiceImpl.java new file mode 100644 index 0000000..bb2b408 --- /dev/null +++ b/main-service/src/main/java/ewm/requests/service/RequestServiceImpl.java @@ -0,0 +1,131 @@ +package ewm.requests.service; + +import ewm.events.Event; +import ewm.events.EventRepository; +import ewm.events.enums.State; +import ewm.exception.ForbiddenException; +import ewm.exception.NotFoundException; +import ewm.requests.Request; +import ewm.requests.RequestMapper; +import ewm.requests.RequestRepository; +import ewm.requests.RequestStatus; +import ewm.requests.dto.EventRequestStatusUpdateRequest; +import ewm.requests.dto.EventRequestStatusUpdateResult; +import ewm.requests.dto.RequestDto; +import ewm.user.User; +import ewm.user.service.UserService; +import jakarta.validation.ValidationException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import static ewm.requests.RequestStatus.*; + +@Transactional +@Service +@RequiredArgsConstructor +public class RequestServiceImpl implements RequestService { + private final RequestRepository requestRepository; + private final EventRepository eventRepository; + private final UserService userService; + private final RequestMapper requestMapper; + + @Override + public RequestDto addRequest(Long userId, Long eventId) { + Event event = eventRepository.findById(eventId) + .orElseThrow(() -> new NotFoundException("Event with id=" + eventId + " was not found")); + User user = userService.getModelById(userId); + if (requestRepository.existsByRequesterIdAndEventId(userId, eventId)) { + throw new ForbiddenException("Request is already exist."); + } + if (userId.equals(event.getInitiator().getId())) { + throw new ForbiddenException("Initiator can't send request to his own event."); + } + if (!event.getState().equals(State.PUBLISHED)) { + throw new ForbiddenException("Participation is possible only in published event."); + } + if (event.getParticipantLimit() != 0 && event.getParticipantLimit() <= + requestRepository.countByEventIdAndStatus(eventId, CONFIRMED)) { + throw new ForbiddenException("Participant limit has been reached."); + } + Request request = new Request(); + request.setCreated(LocalDateTime.now()); + request.setEvent(event); + request.setRequester(user); + + if (event.getRequestModeration() && event.getParticipantLimit() != 0) { + request.setStatus(PENDING); + } else { + request.setStatus(CONFIRMED); + } + return requestMapper.modelToDto(requestRepository.save(request)); + } + + @Override + public EventRequestStatusUpdateResult updateRequestsStatus(Long userId, Long eventId, + EventRequestStatusUpdateRequest statusUpdateRequest) { + User initiator = userService.getModelById(userId); + Event event = eventRepository.findByIdAndInitiatorId(eventId, userId).orElseThrow(() -> + new NotFoundException("Event with id=" + eventId + " was not found.")); + if (!event.getInitiator().equals(initiator)) { + throw new ValidationException("User isn't initiator."); + } + long confirmedRequests = requestRepository.countByEventIdAndStatus(eventId, CONFIRMED); + if (event.getParticipantLimit() > 0 && event.getParticipantLimit() <= confirmedRequests) { + throw new ForbiddenException("The participant limit has been reached."); + } + List confirmed = new ArrayList<>(); + List rejected = new ArrayList<>(); + List requests = requestRepository.findAllByEventIdAndIdInAndStatus(eventId, + statusUpdateRequest.getRequestIds(), PENDING); + for (int i = 0; i < requests.size(); i++) { + Request request = requests.get(i); + if (statusUpdateRequest.getStatus() == REJECTED) { + request.setStatus(REJECTED); + rejected.add(requestMapper.modelToDto(request)); + } + if (statusUpdateRequest.getStatus() == CONFIRMED && event.getParticipantLimit() > 0 && + (confirmedRequests + i) < event.getParticipantLimit()) { + request.setStatus(CONFIRMED); + confirmed.add(requestMapper.modelToDto(request)); + } else { + request.setStatus(REJECTED); + rejected.add(requestMapper.modelToDto(request)); + } + } + return new EventRequestStatusUpdateResult(confirmed, rejected); + } + + @Override + public RequestDto cancelRequest(Long userId, Long requestId) { + Request request = requestRepository.findByIdAndRequesterId(requestId, userId); + request.setStatus(RequestStatus.CANCELED); + + return requestMapper.modelToDto(requestRepository.save(request)); + } + + @Override + @Transactional(readOnly = true) + public List getRequestsByEventOwner(Long userId, Long eventId) { + userService.getModelById(userId); + eventRepository.findByIdAndInitiatorId(eventId, userId).orElseThrow(() -> + new NotFoundException("Event with id=" + eventId + " was not found")); + return requestRepository.findAllByEventId(eventId).stream() + .map(requestMapper::modelToDto).collect(Collectors.toList()); + } + + @Override + @Transactional(readOnly = true) + public List getRequestsByUser(Long userId) { + userService.getModelById(userId); + return requestRepository.findAllByRequesterId(userId).stream() + .map(requestMapper::modelToDto).toList(); + } + + +} diff --git a/main-service/src/main/java/ewm/user/User.java b/main-service/src/main/java/ewm/user/User.java new file mode 100644 index 0000000..b1f1995 --- /dev/null +++ b/main-service/src/main/java/ewm/user/User.java @@ -0,0 +1,26 @@ +package ewm.user; + +import jakarta.persistence.*; +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Getter +@Setter +@ToString +@Entity +@Table(name = "users") +public class User { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + @Column(nullable = false) + private String name; + + @Column(nullable = false, unique = true) + private String email; + + +} diff --git a/main-service/src/main/java/ewm/user/UserController.java b/main-service/src/main/java/ewm/user/UserController.java new file mode 100644 index 0000000..386afa2 --- /dev/null +++ b/main-service/src/main/java/ewm/user/UserController.java @@ -0,0 +1,40 @@ +package ewm.user; + +import ewm.user.dto.UserCreateDto; +import ewm.user.dto.UserDto; +import ewm.user.service.UserService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Validated +@RestController +@RequiredArgsConstructor +@RequestMapping("/admin/users") +public class UserController { + private final UserService userService; + + @PostMapping + @ResponseStatus(value = HttpStatus.CREATED) + public UserDto create(@RequestBody @Valid UserCreateDto userDto) { + return userService.create(userDto); + } + + @DeleteMapping("/{userId}") + @ResponseStatus(value = HttpStatus.NO_CONTENT) + public void deleteUser(@PathVariable long userId) { + userService.delete(userId); + } + + @GetMapping + public List getUsers(@RequestParam(value = "ids", required = false) List ids, + @RequestParam(value = "from", defaultValue = "0") Integer from, + @RequestParam(value = "size", defaultValue = "10") Integer size) { + return userService.getUsers(ids, from, size); + } + +} diff --git a/main-service/src/main/java/ewm/user/UserMapper.java b/main-service/src/main/java/ewm/user/UserMapper.java new file mode 100644 index 0000000..ba270d3 --- /dev/null +++ b/main-service/src/main/java/ewm/user/UserMapper.java @@ -0,0 +1,46 @@ +package ewm.user; + +import ewm.user.dto.UserCreateDto; +import ewm.user.dto.UserDto; +import ewm.user.dto.UserShortDto; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +@Component +public class UserMapper { + + public User createDtoToModel(UserCreateDto dto) { + return User.builder() + .name(dto.getName()) + .email(dto.getEmail()) + .build(); + } + + public UserDto modelToDto(User user) { + return UserDto.builder() + .id(user.getId()) + .name(user.getName()) + .email(user.getEmail()) + .build(); + } + + public UserShortDto modelToUserShortDto(User user) { + return UserShortDto.builder() + .id(user.getId()) + .name(user.getName()) + .build(); + } + + public List listModelToDto(Collection users) { + List list = new ArrayList<>(); + for (User user : users) { + list.add(modelToDto(user)); + } + return list; + } + +} + diff --git a/main-service/src/main/java/ewm/user/UserRepository.java b/main-service/src/main/java/ewm/user/UserRepository.java new file mode 100644 index 0000000..d7a74b1 --- /dev/null +++ b/main-service/src/main/java/ewm/user/UserRepository.java @@ -0,0 +1,13 @@ +package ewm.user; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface UserRepository extends JpaRepository { + + List findAllByIdIn(List ids, Pageable pageable); + +} + diff --git a/main-service/src/main/java/ewm/user/dto/UserCreateDto.java b/main-service/src/main/java/ewm/user/dto/UserCreateDto.java new file mode 100644 index 0000000..7e90642 --- /dev/null +++ b/main-service/src/main/java/ewm/user/dto/UserCreateDto.java @@ -0,0 +1,20 @@ +package ewm.user.dto; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +@Data +public class UserCreateDto { + @NotNull + @NotBlank + @Size(min = 2, max = 250) + private String name; + @Email + @NotNull + @NotBlank + @Size(min = 6, max = 254) + private String email; +} diff --git a/main-service/src/main/java/ewm/user/dto/UserDto.java b/main-service/src/main/java/ewm/user/dto/UserDto.java new file mode 100644 index 0000000..6093ce0 --- /dev/null +++ b/main-service/src/main/java/ewm/user/dto/UserDto.java @@ -0,0 +1,21 @@ +package ewm.user.dto; + +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +@NoArgsConstructor +@Data +@Builder +public class UserDto { + private Long id; + private String name; + private String email; + + public UserDto(Long id, String name, String email) { + this.id = id; + this.name = name; + this.email = email; + } +} \ No newline at end of file diff --git a/main-service/src/main/java/ewm/user/dto/UserShortDto.java b/main-service/src/main/java/ewm/user/dto/UserShortDto.java new file mode 100644 index 0000000..b66ab02 --- /dev/null +++ b/main-service/src/main/java/ewm/user/dto/UserShortDto.java @@ -0,0 +1,11 @@ +package ewm.user.dto; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class UserShortDto { + private long id; + private String name; +} diff --git a/main-service/src/main/java/ewm/user/service/UserService.java b/main-service/src/main/java/ewm/user/service/UserService.java new file mode 100644 index 0000000..4379fb5 --- /dev/null +++ b/main-service/src/main/java/ewm/user/service/UserService.java @@ -0,0 +1,18 @@ +package ewm.user.service; + + +import ewm.user.User; +import ewm.user.dto.UserCreateDto; +import ewm.user.dto.UserDto; + +import java.util.List; + +public interface UserService { + UserDto create(UserCreateDto userDto); + + void delete(long userId); + + User getModelById(long userId); + + List getUsers(List userIds, Integer from, Integer size); +} diff --git a/main-service/src/main/java/ewm/user/service/UserServiceImpl.java b/main-service/src/main/java/ewm/user/service/UserServiceImpl.java new file mode 100644 index 0000000..a32ac4c --- /dev/null +++ b/main-service/src/main/java/ewm/user/service/UserServiceImpl.java @@ -0,0 +1,57 @@ +package ewm.user.service; + +import ewm.exception.NotFoundException; +import ewm.user.User; +import ewm.user.UserMapper; +import ewm.user.UserRepository; +import ewm.user.dto.UserCreateDto; +import ewm.user.dto.UserDto; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +@Transactional +@Service +@RequiredArgsConstructor +public class UserServiceImpl implements UserService { + private final UserRepository repository; + private final UserMapper mapper; + + @Override + public UserDto create(UserCreateDto userDto) { + User user = mapper.createDtoToModel(userDto); + return mapper.modelToDto(repository.save(user)); + } + + @Override + public void delete(long userId) { + if (!repository.existsById(userId)) { + throw new NotFoundException("User with id=" + userId + " was not found"); + } + repository.deleteById(userId); + } + + @Override + public User getModelById(long userId) { + return repository.findById(userId).orElseThrow(() -> + new NotFoundException("User with id=" + userId + " was not found")); + } + + @Override + public List getUsers(List userIds, Integer from, Integer size) { + Pageable pageable = PageRequest.of(from / size, size); + if (userIds != null) { + return repository.findAllByIdIn(userIds, pageable).stream() + .map(mapper::modelToDto) + .collect(Collectors.toList()); + } else { + return repository.findAll(pageable).map(mapper::modelToDto).toList(); + } + } + +} diff --git a/main-service/src/main/resources/application.properties b/main-service/src/main/resources/application.properties new file mode 100644 index 0000000..8f2a8b7 --- /dev/null +++ b/main-service/src/main/resources/application.properties @@ -0,0 +1,24 @@ +server.port=8080 + +client.url=http://localhost:9090 +app=ewm-main-service + +spring.output.ansi.enabled=ALWAYS +spring.jpa.hibernate.ddl-auto=none +spring.jpa.properties.hibernate.format_sql=true +spring.sql.init.mode=always + +spring.mvc.format.date-time=yyyy-MM-dd HH:mm:ss + +logging.level.org.zalando.logbook= TRACE + +logging.level.org.springframework.orm.jpa=INFO +logging.level.org.springframework.transaction=INFO +logging.level.org.springframework.transaction.interceptor=TRACE +logging.level.org.springframework.orm.jpa.JpaTransactionManager=DEBUG + +spring.datasource.driverClassName=org.postgresql.Driver +spring.datasource.url=jdbc:postgresql://localhost:7542/ewm-main +spring.datasource.username=main +spring.datasource.password=main +spring.jpa.properties.hibernate.default_schema=public \ No newline at end of file diff --git a/main-service/src/main/resources/schema.sql b/main-service/src/main/resources/schema.sql new file mode 100644 index 0000000..a53aca3 --- /dev/null +++ b/main-service/src/main/resources/schema.sql @@ -0,0 +1,63 @@ +DROP TABLE IF EXISTS users CASCADE; +DROP TABLE IF EXISTS categories CASCADE; +DROP TABLE IF EXISTS locations CASCADE; +DROP TABLE IF EXISTS events CASCADE; +DROP TABLE IF EXISTS compilations CASCADE; +DROP TABLE IF EXISTS compilation_event CASCADE; +DROP TABLE IF EXISTS requests CASCADE; + + + +CREATE TABLE IF NOT EXISTS users ( + id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + name VARCHAR(250) NOT NULL, + email VARCHAR(254) NOT NULL UNIQUE +); + +CREATE TABLE IF NOT EXISTS categories ( + id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + name VARCHAR(50) NOT NULL UNIQUE +); + +CREATE TABLE IF NOT EXISTS locations ( + id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + lat FLOAT NOT NULL, + lon FLOAT NOT NULL +); + +CREATE TABLE IF NOT EXISTS events( + id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + annotation VARCHAR(2000) UNIQUE NOT NULL, + category_id INTEGER NOT NULL REFERENCES categories(id), + created_on TIMESTAMP, + description VARCHAR(7000) NOT NULL, + event_date TIMESTAMP NOT NULL, + initiator_id INTEGER REFERENCES users(id), + location_id INTEGER NOT NULL REFERENCES locations(id), + paid BOOLEAN, + participant_limit INTEGER, + published_on TIMESTAMP, + request_moderation BOOLEAN, + state VARCHAR(10), + title VARCHAR(120) NOT NULL +); + +CREATE TABLE IF NOT EXISTS compilations( + id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + pinned BOOLEAN, + title VARCHAR(50) NOT NULL UNIQUE +); + +CREATE TABLE IF NOT EXISTS compilation_event( + compilation_id INTEGER REFERENCES compilations(id), + event_id INTEGER REFERENCES events(id), + PRIMARY KEY (compilation_id, event_id) +); + +CREATE TABLE IF NOT EXISTS requests( + id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + created TIMESTAMP, + event_id INTEGER REFERENCES events(id), + requester_id INTEGER REFERENCES users(id), + status VARCHAR(50) +); diff --git a/main-service/src/test/resources/application.properties b/main-service/src/test/resources/application.properties new file mode 100644 index 0000000..83d218c --- /dev/null +++ b/main-service/src/test/resources/application.properties @@ -0,0 +1,7 @@ +spring.output.ansi.enabled=ALWAYS +spring.jpa.defer-datasource-initialization=true +spring.config.activate.on-profile=test +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.url=jdbc:h2:mem:main +spring.datasource.username=main +spring.datasource.password=main diff --git a/stats/stats-client/pom.xml b/stats/stats-client/pom.xml index c65653f..275e43a 100644 --- a/stats/stats-client/pom.xml +++ b/stats/stats-client/pom.xml @@ -5,9 +5,8 @@ 4.0.0 ru.practicum - explore-with-me + stats 0.0.1-SNAPSHOT - ../../pom.xml stats-client @@ -18,14 +17,10 @@ logbook-spring-boot-starter 3.7.2 - - org.springframework.boot - spring-boot-starter-web - - org.springframework.boot - spring-boot-starter-validation + org.springframework + spring-web @@ -34,8 +29,9 @@ - org.hibernate.validator - hibernate-validator + org.apache.httpcomponents + httpclient + 4.5.14 @@ -43,35 +39,13 @@ httpclient5 - - org.springframework.boot - spring-boot-configuration-processor - true - - - - org.projectlombok - lombok - true - - - - org.springframework.boot - spring-boot-starter-test - test - ru.practicum stats-dto 0.0.1-SNAPSHOT compile - - ru.practicum - stats-server - 0.0.1-SNAPSHOT - compile - + diff --git a/stats/stats-client/src/main/java/client/StatsClient.java b/stats/stats-client/src/main/java/client/StatsClient.java index 79a3337..2d4c6e0 100644 --- a/stats/stats-client/src/main/java/client/StatsClient.java +++ b/stats/stats-client/src/main/java/client/StatsClient.java @@ -1,6 +1,6 @@ package client; -import dto.CreateDto; +import dto.EndpointHitDto; import org.apache.hc.client5.http.classic.HttpClient; import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; import org.springframework.beans.factory.annotation.Value; @@ -28,10 +28,10 @@ public StatsClient() { rest.setRequestFactory(requestFactory); } - public ResponseEntity save(CreateDto createDto) { + public ResponseEntity save(EndpointHitDto endpointHitDto) { ResponseEntity response; try { - response = rest.postForEntity(serverUrl + "/createDto", createDto, Object.class); + response = rest.postForEntity(serverUrl + "/hit", endpointHitDto, Object.class); } catch (HttpStatusCodeException e) { return ResponseEntity.status(e.getStatusCode()).body(e.getResponseBodyAsByteArray()); } diff --git a/stats/stats-dto/pom.xml b/stats/stats-dto/pom.xml index 26d9ae0..259fc97 100644 --- a/stats/stats-dto/pom.xml +++ b/stats/stats-dto/pom.xml @@ -5,9 +5,8 @@ 4.0.0 ru.practicum - explore-with-me + stats 0.0.1-SNAPSHOT - ../../pom.xml stats-dto diff --git a/stats/stats-dto/src/main/java/dto/CreateDto.java b/stats/stats-dto/src/main/java/dto/EndpointHitDto.java similarity index 75% rename from stats/stats-dto/src/main/java/dto/CreateDto.java rename to stats/stats-dto/src/main/java/dto/EndpointHitDto.java index e2f6159..6e1ba88 100644 --- a/stats/stats-dto/src/main/java/dto/CreateDto.java +++ b/stats/stats-dto/src/main/java/dto/EndpointHitDto.java @@ -1,6 +1,5 @@ package dto; -import com.fasterxml.jackson.annotation.JsonFormat; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Past; import lombok.*; @@ -12,7 +11,7 @@ @Getter @NoArgsConstructor @AllArgsConstructor -public class CreateDto { +public class EndpointHitDto { @NotNull private String app; @@ -24,7 +23,7 @@ public class CreateDto { @NotNull @Past - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + // @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime timestamp; } diff --git a/stats/stats-dto/src/main/java/dto/ViewStatsDto.java b/stats/stats-dto/src/main/java/dto/ViewStatsDto.java index 23044f8..9295c89 100644 --- a/stats/stats-dto/src/main/java/dto/ViewStatsDto.java +++ b/stats/stats-dto/src/main/java/dto/ViewStatsDto.java @@ -1,19 +1,15 @@ package dto; -import lombok.Getter; -import lombok.Setter; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; -@Getter -@Setter +@Data +@AllArgsConstructor +@NoArgsConstructor public class ViewStatsDto { private String app; private String uri; private Long hits; - - public ViewStatsDto(String app, String uri, Long hits) { - this.app = app; - this.uri = uri; - this.hits = hits; - } } diff --git a/stats/stats-server/Dockerfile b/stats/stats-server/Dockerfile index 0ff1817..63576b9 100644 --- a/stats/stats-server/Dockerfile +++ b/stats/stats-server/Dockerfile @@ -1,5 +1,3 @@ FROM eclipse-temurin:21-jre-jammy -VOLUME /tmp -ARG JAR_FILE=target/*.jar -COPY ${JAR_FILE} app.jar -ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"] \ No newline at end of file +COPY ./target/*.jar stats-server.jar +ENTRYPOINT ["java","-jar","/stats-server.jar"] \ No newline at end of file diff --git a/stats/stats-server/pom.xml b/stats/stats-server/pom.xml index af87956..8764a27 100644 --- a/stats/stats-server/pom.xml +++ b/stats/stats-server/pom.xml @@ -5,9 +5,8 @@ 4.0.0 ru.practicum - explore-with-me + stats 0.0.1-SNAPSHOT - ../../pom.xml stats-server diff --git a/stats/stats-server/src/main/java/ewm/DateFormatConfig.java b/stats/stats-server/src/main/java/ewm/DateFormatConfig.java new file mode 100644 index 0000000..414a0f1 --- /dev/null +++ b/stats/stats-server/src/main/java/ewm/DateFormatConfig.java @@ -0,0 +1,34 @@ +package ewm; + +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.time.format.DateTimeFormatter; + +@Configuration +public class DateFormatConfig { + + @Bean + public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() { + + return builder -> { + + // formatter + DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + // deserializers + builder.deserializers(new LocalDateDeserializer(dateFormatter)); + builder.deserializers(new LocalDateTimeDeserializer(dateTimeFormatter)); + + // serializers + builder.serializers(new LocalDateSerializer(dateFormatter)); + builder.serializers(new LocalDateTimeSerializer(dateTimeFormatter)); + }; + } +} diff --git a/stats/stats-server/src/main/java/ewm/exception/ConflictException.java b/stats/stats-server/src/main/java/ewm/exception/ConflictException.java deleted file mode 100644 index 6bcc2f4..0000000 --- a/stats/stats-server/src/main/java/ewm/exception/ConflictException.java +++ /dev/null @@ -1,8 +0,0 @@ -package ewm.exception; - -public class ConflictException extends RuntimeException { - public ConflictException(String message) { - super(message); - } -} - diff --git a/stats/stats-server/src/main/java/ewm/exception/ErrorHandler.java b/stats/stats-server/src/main/java/ewm/exception/ErrorHandler.java index 3154594..dfcf251 100644 --- a/stats/stats-server/src/main/java/ewm/exception/ErrorHandler.java +++ b/stats/stats-server/src/main/java/ewm/exception/ErrorHandler.java @@ -1,39 +1,35 @@ package ewm.exception; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; +import java.time.LocalDateTime; + +@Slf4j @RestControllerAdvice public class ErrorHandler { - @ExceptionHandler - @ResponseStatus(HttpStatus.NOT_FOUND) - public ErrorResponse handleNotFoundException(final NotFoundException e) { - return new ErrorResponse("Error", e.getMessage()); + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleValidationException(final BadRequestException e) { + return ErrorResponse.builder() + .message(e.getMessage()) + .reason("Incorrectly made request.") + .status("BAD_REQUEST") + .timestamp(LocalDateTime.now()) + .build(); } @ExceptionHandler @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) - public ErrorResponse handleInternalServerErrorException(final InternalServerErrorException e) { - return new ErrorResponse("Error", e.getMessage()); - } - - @ExceptionHandler - @ResponseStatus(HttpStatus.CONFLICT) - public ErrorResponse handleConflictException(final ConflictException e) { - return new ErrorResponse("Error", e.getMessage()); - } - - @ExceptionHandler - @ResponseStatus(HttpStatus.BAD_REQUEST) - public ErrorResponse handleBadRequestException(final BadRequestException e) { - return new ErrorResponse("Error", e.getMessage()); + public ErrorResponse handleThrowable(final InternalServerException e) { + return ErrorResponse.builder() + .message(e.getMessage()) + .status("INTERNAL_SERVER_ERROR") + .timestamp(LocalDateTime.now()) + .build(); } - } - - - diff --git a/stats/stats-server/src/main/java/ewm/exception/ErrorResponse.java b/stats/stats-server/src/main/java/ewm/exception/ErrorResponse.java index 2fdf34c..1ff7622 100644 --- a/stats/stats-server/src/main/java/ewm/exception/ErrorResponse.java +++ b/stats/stats-server/src/main/java/ewm/exception/ErrorResponse.java @@ -1,19 +1,17 @@ package ewm.exception; -public class ErrorResponse { - private final String error; - private final String description; - - public ErrorResponse(String error, String description) { - this.error = error; - this.description = description; - } +import lombok.Builder; +import lombok.Data; - public String getError() { - return error; - } +import java.time.LocalDateTime; +import java.util.List; - public String getDescription() { - return description; - } -} \ No newline at end of file +@Data +@Builder +public class ErrorResponse { + private final List errors; + private final String message; + private final String reason; + private final String status; + private final LocalDateTime timestamp; +} diff --git a/stats/stats-server/src/main/java/ewm/exception/InternalServerException.java b/stats/stats-server/src/main/java/ewm/exception/InternalServerException.java new file mode 100644 index 0000000..e3aa867 --- /dev/null +++ b/stats/stats-server/src/main/java/ewm/exception/InternalServerException.java @@ -0,0 +1,7 @@ +package ewm.exception; + +public class InternalServerException extends RuntimeException { + public InternalServerException(String message) { + super(message); + } +} diff --git a/stats/stats-server/src/main/java/ewm/stat/EndpointHitController.java b/stats/stats-server/src/main/java/ewm/stat/EndpointHitController.java index c0d0752..add7b04 100644 --- a/stats/stats-server/src/main/java/ewm/stat/EndpointHitController.java +++ b/stats/stats-server/src/main/java/ewm/stat/EndpointHitController.java @@ -1,6 +1,6 @@ package ewm.stat; -import dto.CreateDto; +import dto.EndpointHitDto; import dto.ViewStatsDto; import ewm.stat.service.EndpointHitService; import jakarta.validation.Valid; @@ -21,8 +21,8 @@ public class EndpointHitController { @ResponseStatus(HttpStatus.CREATED) @PostMapping("/hit") - public void create(@RequestBody @Valid CreateDto createDto) { - service.create(createDto); + public void create(@RequestBody @Valid EndpointHitDto endpointHitDto) { + service.create(endpointHitDto); } @GetMapping("/stats") diff --git a/stats/stats-server/src/main/java/ewm/stat/EndpointHitMapper.java b/stats/stats-server/src/main/java/ewm/stat/EndpointHitMapper.java index fe03da4..5a9789e 100644 --- a/stats/stats-server/src/main/java/ewm/stat/EndpointHitMapper.java +++ b/stats/stats-server/src/main/java/ewm/stat/EndpointHitMapper.java @@ -1,6 +1,6 @@ package ewm.stat; -import dto.CreateDto; +import dto.EndpointHitDto; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; @@ -8,7 +8,7 @@ @Component public class EndpointHitMapper { - public EndpointHit dtoToModel(CreateDto dto) { + public EndpointHit dtoToModel(EndpointHitDto dto) { return EndpointHit.builder() .app(dto.getApp()) .uri(dto.getUri()) @@ -16,4 +16,5 @@ public EndpointHit dtoToModel(CreateDto dto) { .timestamp(dto.getTimestamp()) .build(); } + } diff --git a/stats/stats-server/src/main/java/ewm/stat/EndpointHitRepository.java b/stats/stats-server/src/main/java/ewm/stat/EndpointHitRepository.java index 5075d2b..6ae95bd 100644 --- a/stats/stats-server/src/main/java/ewm/stat/EndpointHitRepository.java +++ b/stats/stats-server/src/main/java/ewm/stat/EndpointHitRepository.java @@ -3,34 +3,38 @@ import dto.ViewStatsDto; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; import java.time.LocalDateTime; import java.util.List; public interface EndpointHitRepository extends JpaRepository { - @Query("SELECT new dto.ViewStatsDto(e.app, e.uri, COUNT(e.ip) as hits) " + - "FROM EndpointHit e " + - "WHERE e.timestamp BETWEEN :start AND :end " + - "AND (:uris IS NULL OR e.uri IN :uris) " + - "GROUP BY e.app, e.uri " + - "ORDER BY COUNT(e.ip) DESC") - List getStats( - @Param("start") LocalDateTime start, - @Param("end") LocalDateTime end, - @Param("uris") List uris); - - @Query("SELECT new dto.ViewStatsDto(e.app, e.uri, COUNT(DISTINCT e.ip) as hits) " + - "FROM EndpointHit e " + - "WHERE e.timestamp BETWEEN :start AND :end " + - "AND (:uris IS NULL OR e.uri IN :uris) " + - "GROUP BY e.app, e.uri " + - "ORDER BY COUNT(DISTINCT e.ip) DESC") - List getUniqueStats( - @Param("start") LocalDateTime start, - @Param("end") LocalDateTime end, - @Param("uris") List uris); + @Query("SELECT new dto.ViewStatsDto(h.app, h.uri, COUNT(h.uri)) " + + "FROM EndpointHit AS h " + + "WHERE h.timestamp BETWEEN ?1 AND ?2 " + + "GROUP BY h.app, h.uri " + + "ORDER BY COUNT (h.uri) DESC") + List getAllHitsWithoutUris(LocalDateTime start, LocalDateTime end); + @Query("SELECT new dto.ViewStatsDto(h.app, h.uri, COUNT(h.uri)) " + + "FROM EndpointHit AS h " + + "WHERE h.uri IN (?1) AND h.timestamp BETWEEN ?2 AND ?3 " + + "GROUP BY h.app, h.uri " + + "ORDER BY COUNT (h.uri) DESC") + List getAllHitsWithUris(List uris, LocalDateTime start, LocalDateTime end); + + @Query("SELECT new dto.ViewStatsDto(h.app, h.uri, COUNT(DISTINCT h.ip)) " + + "FROM EndpointHit AS h " + + "WHERE h.timestamp BETWEEN ?1 AND ?2 " + + "GROUP BY h.app, h.uri " + + "ORDER BY COUNT(DISTINCT h.ip) DESC") + List getHitsWithoutUrisWithUniqueIp(LocalDateTime start, LocalDateTime end); + + @Query("SELECT new dto.ViewStatsDto(h.app, h.uri, COUNT(DISTINCT h.ip)) " + + "FROM EndpointHit AS h " + + "WHERE h.uri IN (?1) AND h.timestamp BETWEEN ?2 AND ?3 " + + "GROUP BY h.app, h.uri " + + "ORDER BY COUNT(DISTINCT h.ip) DESC") + List getHitsWithUrisWithUniqueIp(List uris, LocalDateTime start, LocalDateTime end); } diff --git a/stats/stats-server/src/main/java/ewm/stat/service/EndpointHitService.java b/stats/stats-server/src/main/java/ewm/stat/service/EndpointHitService.java index 8ea6ec3..d8e1763 100644 --- a/stats/stats-server/src/main/java/ewm/stat/service/EndpointHitService.java +++ b/stats/stats-server/src/main/java/ewm/stat/service/EndpointHitService.java @@ -1,6 +1,6 @@ package ewm.stat.service; -import dto.CreateDto; +import dto.EndpointHitDto; import dto.ViewStatsDto; import java.time.LocalDateTime; @@ -8,7 +8,7 @@ public interface EndpointHitService { - void create(CreateDto createDto); + void create(EndpointHitDto endpointHitDto); List getStats(LocalDateTime start, LocalDateTime end, List uris, boolean unique); } diff --git a/stats/stats-server/src/main/java/ewm/stat/service/EndpointHitServiceImpl.java b/stats/stats-server/src/main/java/ewm/stat/service/EndpointHitServiceImpl.java index 5059a7f..b169639 100644 --- a/stats/stats-server/src/main/java/ewm/stat/service/EndpointHitServiceImpl.java +++ b/stats/stats-server/src/main/java/ewm/stat/service/EndpointHitServiceImpl.java @@ -1,7 +1,8 @@ package ewm.stat.service; -import dto.CreateDto; +import dto.EndpointHitDto; import dto.ViewStatsDto; +import ewm.exception.BadRequestException; import ewm.stat.EndpointHit; import ewm.stat.EndpointHitMapper; import ewm.stat.EndpointHitRepository; @@ -20,17 +21,26 @@ public class EndpointHitServiceImpl implements EndpointHitService { private final EndpointHitMapper mapper; @Override - public void create(CreateDto createDto) { - EndpointHit endpointHit = mapper.dtoToModel(createDto); + public void create(EndpointHitDto endpointHitDto) { + EndpointHit endpointHit = mapper.dtoToModel(endpointHitDto); repository.save(endpointHit); } @Override public List getStats(LocalDateTime start, LocalDateTime end, List uris, boolean unique) { + if (start.isAfter(end)) { + throw new BadRequestException("Время указанно не верно"); + } if (unique) { - return repository.getUniqueStats(start, end, uris); + if (uris != null) { + return repository.getHitsWithUrisWithUniqueIp(uris, start, end); + } + return repository.getHitsWithoutUrisWithUniqueIp(start, end); } else { - return repository.getStats(start, end, uris); + if (uris != null) { + return repository.getAllHitsWithUris(uris, start, end); + } + return repository.getAllHitsWithoutUris(start, end); } } diff --git a/stats/stats-server/src/main/resources/schema.sql b/stats/stats-server/src/main/resources/schema.sql index cb63d1d..1c82b34 100644 --- a/stats/stats-server/src/main/resources/schema.sql +++ b/stats/stats-server/src/main/resources/schema.sql @@ -1,8 +1,9 @@ +DROP TABLE IF EXISTS endpoint_hits CASCADE; CREATE TABLE IF NOT EXISTS endpoint_hits ( id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - app VARCHAR(200) NOT NULL, - uri VARCHAR(200) NOT NULL, - ip VARCHAR(200) NOT NULL, + app VARCHAR(200), + uri VARCHAR(200), + ip VARCHAR(200), timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL ); From 338becbd02825672f56a27fa620b59fd445f6c04 Mon Sep 17 00:00:00 2001 From: Just Roma Date: Sun, 27 Jul 2025 19:46:06 +0500 Subject: [PATCH 2/2] edit --- .../ewm/categories/service/CategoryServiceImpl.java | 2 ++ .../java/ewm/compilations/CompilationMapper.java | 2 +- .../ewm/compilations/dto/CreateCompilationDto.java | 6 ++---- .../compilations/dto/UpdateCompilationRequest.java | 2 -- .../compilations/service/CompilationServiceImpl.java | 1 + .../src/main/java/ewm/events/EventMapper.java | 4 ++-- .../src/main/java/ewm/events/dto/EventCreateDto.java | 10 +--------- .../java/ewm/events/service/EventServiceImpl.java | 12 +++++------- .../ewm/locations/service/LocationServiceImpl.java | 1 + .../main/java/ewm/user/service/UserServiceImpl.java | 1 + 10 files changed, 16 insertions(+), 25 deletions(-) diff --git a/main-service/src/main/java/ewm/categories/service/CategoryServiceImpl.java b/main-service/src/main/java/ewm/categories/service/CategoryServiceImpl.java index 9afc30c..0fe731d 100644 --- a/main-service/src/main/java/ewm/categories/service/CategoryServiceImpl.java +++ b/main-service/src/main/java/ewm/categories/service/CategoryServiceImpl.java @@ -41,12 +41,14 @@ public void deleteById(long categoryId) { } @Override + @Transactional(readOnly = true) public List getCategories(Integer from, Integer size) { Pageable pageable = PageRequest.of(from / size, size); return repository.findAll(pageable).map(mapper::modelToDto).toList(); } @Override + @Transactional(readOnly = true) public CategoryDto getById(long categoryId) { Category category = checkAndReturnCategory(categoryId); return mapper.modelToDto(category); diff --git a/main-service/src/main/java/ewm/compilations/CompilationMapper.java b/main-service/src/main/java/ewm/compilations/CompilationMapper.java index c118cb8..0c3d3ea 100644 --- a/main-service/src/main/java/ewm/compilations/CompilationMapper.java +++ b/main-service/src/main/java/ewm/compilations/CompilationMapper.java @@ -9,7 +9,7 @@ public class CompilationMapper { public Compilation toCompilationEntity(CreateCompilationDto createCompilationDto) { return new Compilation( createCompilationDto.getTitle(), - createCompilationDto.getPinned() + createCompilationDto.isPinned() ); } diff --git a/main-service/src/main/java/ewm/compilations/dto/CreateCompilationDto.java b/main-service/src/main/java/ewm/compilations/dto/CreateCompilationDto.java index ffa0223..e1ca206 100644 --- a/main-service/src/main/java/ewm/compilations/dto/CreateCompilationDto.java +++ b/main-service/src/main/java/ewm/compilations/dto/CreateCompilationDto.java @@ -15,13 +15,11 @@ public class CreateCompilationDto { private List events; - private boolean pinned = false; + private boolean pinned; @NotBlank @Size(min = 1, max = 50) private String title; - public Boolean getPinned() { - return pinned; - } + } diff --git a/main-service/src/main/java/ewm/compilations/dto/UpdateCompilationRequest.java b/main-service/src/main/java/ewm/compilations/dto/UpdateCompilationRequest.java index 579c5f2..1367bca 100644 --- a/main-service/src/main/java/ewm/compilations/dto/UpdateCompilationRequest.java +++ b/main-service/src/main/java/ewm/compilations/dto/UpdateCompilationRequest.java @@ -3,7 +3,6 @@ import jakarta.validation.constraints.Size; import lombok.AllArgsConstructor; import lombok.Data; -import lombok.Getter; import lombok.NoArgsConstructor; import java.util.List; @@ -14,7 +13,6 @@ public class UpdateCompilationRequest { private List events; - @Getter private Boolean pinned; @Size(min = 1, max = 50) diff --git a/main-service/src/main/java/ewm/compilations/service/CompilationServiceImpl.java b/main-service/src/main/java/ewm/compilations/service/CompilationServiceImpl.java index e507f68..51b3986 100644 --- a/main-service/src/main/java/ewm/compilations/service/CompilationServiceImpl.java +++ b/main-service/src/main/java/ewm/compilations/service/CompilationServiceImpl.java @@ -37,6 +37,7 @@ public class CompilationServiceImpl implements CompilationService { private final CompilationMapper compilationMapper; @Override + @Transactional(readOnly = true) public List getCompilations(Boolean pinned, Integer from, Integer size) { Pageable pageable = PageRequest.of(from / size, size); List compilations; diff --git a/main-service/src/main/java/ewm/events/EventMapper.java b/main-service/src/main/java/ewm/events/EventMapper.java index 5c6e51b..be9ef68 100644 --- a/main-service/src/main/java/ewm/events/EventMapper.java +++ b/main-service/src/main/java/ewm/events/EventMapper.java @@ -19,9 +19,9 @@ public Event eventCreateDtoToModel(EventCreateDto dto) { .annotation(dto.getAnnotation()) .description(dto.getDescription()) .eventDate(dto.getEventDate()) - .paid(dto.getPaid()) + .paid(dto.isPaid()) .participantLimit(dto.getParticipantLimit()) - .requestModeration(dto.getRequestModeration()) + .requestModeration(dto.isRequestModeration()) .title(dto.getTitle()) .build(); } diff --git a/main-service/src/main/java/ewm/events/dto/EventCreateDto.java b/main-service/src/main/java/ewm/events/dto/EventCreateDto.java index ab8d46f..3c08c62 100644 --- a/main-service/src/main/java/ewm/events/dto/EventCreateDto.java +++ b/main-service/src/main/java/ewm/events/dto/EventCreateDto.java @@ -32,7 +32,7 @@ public class EventCreateDto { @Valid private LocationDto location; - private boolean paid = false; + private boolean paid; @PositiveOrZero private int participantLimit = 0; @@ -43,12 +43,4 @@ public class EventCreateDto { @NotBlank private String title; - public Boolean getPaid() { - return paid; - } - - public Boolean getRequestModeration() { - return requestModeration; - } - } diff --git a/main-service/src/main/java/ewm/events/service/EventServiceImpl.java b/main-service/src/main/java/ewm/events/service/EventServiceImpl.java index c6b1b84..16c643b 100644 --- a/main-service/src/main/java/ewm/events/service/EventServiceImpl.java +++ b/main-service/src/main/java/ewm/events/service/EventServiceImpl.java @@ -59,8 +59,9 @@ public class EventServiceImpl implements EventService { private final LocationService locationService; private final RequestRepository requestRepository; private final StatsClient statsClient; + private final ObjectMapper objectMapper = new ObjectMapper(); @Value("${app}") - String app; + private String app; @Override public EventFullDto create(long userId, EventCreateDto dto) { @@ -257,8 +258,7 @@ public List getEventsByAdmin(List users, List s Map confirmedRequests = requestRepository.findAllByEventIdInAndStatus(ids, CONFIRMED).stream() .collect(Collectors.toMap(ConfirmedRequestsDto::getEvent, ConfirmedRequestsDto::getCount)); for (Event event : events) { - ObjectMapper mapper = new ObjectMapper(); - List statsDto = mapper.convertValue(response.getBody(), new TypeReference<>() { + List statsDto = objectMapper.convertValue(response.getBody(), new TypeReference<>() { }); if (!statsDto.isEmpty()) { result.add(eventMapper.toEventFullDtoWithViews(event, statsDto.getFirst().getHits(), @@ -333,8 +333,7 @@ public List getEvents(String text, List categories, Bo .stream() .collect(Collectors.toMap(ConfirmedRequestsDto::getEvent, ConfirmedRequestsDto::getCount)); for (Event event : events) { - ObjectMapper mapper = new ObjectMapper(); - List statsDto = mapper.convertValue(response.getBody(), new TypeReference<>() { + List statsDto = objectMapper.convertValue(response.getBody(), new TypeReference<>() { }); if (!statsDto.isEmpty()) { result.add(eventMapper.toEventShortDtoWithViews(event, statsDto.getFirst().getHits(), @@ -359,8 +358,7 @@ public EventViewsFullDto getEventById(Long eventId, HttpServletRequest request) } ResponseEntity response = statsClient.getStats(event.getCreatedOn(), LocalDateTime.now(), List.of(request.getRequestURI()), true); - ObjectMapper mapper = new ObjectMapper(); - List statsDto = mapper.convertValue(response.getBody(), new TypeReference<>() { + List statsDto = objectMapper.convertValue(response.getBody(), new TypeReference<>() { }); EventViewsFullDto result; if (!statsDto.isEmpty()) { diff --git a/main-service/src/main/java/ewm/locations/service/LocationServiceImpl.java b/main-service/src/main/java/ewm/locations/service/LocationServiceImpl.java index 21585ea..dd5eb37 100644 --- a/main-service/src/main/java/ewm/locations/service/LocationServiceImpl.java +++ b/main-service/src/main/java/ewm/locations/service/LocationServiceImpl.java @@ -18,6 +18,7 @@ public class LocationServiceImpl implements LocationService { private final LocationRepository repository; @Override + @Transactional(readOnly = true) public Location getOrSave(LocationDto dto) { Location location = repository.findByLatAndLon(dto.getLat(), dto.getLon()); return Objects.requireNonNullElseGet(location, () -> repository.save(mapper.locationDtoToModel(dto))); diff --git a/main-service/src/main/java/ewm/user/service/UserServiceImpl.java b/main-service/src/main/java/ewm/user/service/UserServiceImpl.java index a32ac4c..6ec87ed 100644 --- a/main-service/src/main/java/ewm/user/service/UserServiceImpl.java +++ b/main-service/src/main/java/ewm/user/service/UserServiceImpl.java @@ -43,6 +43,7 @@ public User getModelById(long userId) { } @Override + @Transactional(readOnly = true) public List getUsers(List userIds, Integer from, Integer size) { Pageable pageable = PageRequest.of(from / size, size); if (userIds != null) {