├── explore-with-me-stats ├── stats-client │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ └── application.properties │ │ │ └── java │ │ │ └── ru │ │ │ └── practicum │ │ │ └── explorewithme │ │ │ ├── BaseClient.java │ │ │ └── StatsClient.java │ └── pom.xml ├── stats-service │ ├── Dockerfile-stats │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ ├── schema.sql │ │ │ └── application.properties │ │ │ └── java │ │ │ └── ru │ │ │ └── practicum │ │ │ └── explorewithme │ │ │ ├── controller │ │ │ ├── exception │ │ │ │ ├── LocalDateTimeException.java │ │ │ │ ├── ErrorResponse.java │ │ │ │ └── ErrorHandler.java │ │ │ └── StatsController.java │ │ │ ├── ExploreWithMeStats.java │ │ │ ├── service │ │ │ ├── StatsService.java │ │ │ └── StatsServiceImpl.java │ │ │ ├── model │ │ │ ├── Stat.java │ │ │ └── StatMapper.java │ │ │ └── repository │ │ │ └── StatsRepository.java │ └── pom.xml ├── stats-dto │ ├── src │ │ └── main │ │ │ └── java │ │ │ └── ru │ │ │ └── practicum │ │ │ └── explorewithme │ │ │ └── dto │ │ │ ├── StatDtoWithHitsProjection.java │ │ │ ├── StatDtoWithHits.java │ │ │ └── StatDto.java │ ├── .gitignore │ └── pom.xml └── pom.xml ├── Dockerfile-postgres-main ├── Dockerfile-postgres-stats ├── explore-wth-me-main ├── Dockerfile-main ├── src │ └── main │ │ ├── java │ │ └── ru │ │ │ └── practicum │ │ │ └── explorewithme │ │ │ ├── model │ │ │ ├── enums │ │ │ │ ├── EventState.java │ │ │ │ ├── RequestStatus.java │ │ │ │ └── LocationStatus.java │ │ │ ├── Category.java │ │ │ ├── User.java │ │ │ ├── Compilation.java │ │ │ ├── Location.java │ │ │ ├── ParticipationRequest.java │ │ │ └── Event.java │ │ │ ├── dto │ │ │ ├── user │ │ │ │ ├── UserShortDto.java │ │ │ │ ├── UserDto.java │ │ │ │ ├── NewUserRequest.java │ │ │ │ └── mapper │ │ │ │ │ └── UserMapper.java │ │ │ ├── location │ │ │ │ ├── CoordinatesResponseDto.java │ │ │ │ ├── LocationDtoWithEvents.java │ │ │ │ ├── LocationDto.java │ │ │ │ ├── LocationUpdateRequest.java │ │ │ │ ├── NewCoordinatesDto.java │ │ │ │ ├── NewLocationDtoAdmin.java │ │ │ │ └── mapper │ │ │ │ │ └── LocationMapper.java │ │ │ ├── category │ │ │ │ ├── NewCategoryDto.java │ │ │ │ ├── CategoryDto.java │ │ │ │ └── mapper │ │ │ │ │ └── CategoryMapper.java │ │ │ ├── request │ │ │ │ ├── EventRequestStatusUpdateRequest.java │ │ │ │ ├── EventRequestStatusUpdateResult.java │ │ │ │ ├── ParticipationRequestDto.java │ │ │ │ └── mapper │ │ │ │ │ └── RequestMapper.java │ │ │ ├── compilation │ │ │ │ ├── UpdateCompilationRequest.java │ │ │ │ ├── NewCompilationDto.java │ │ │ │ ├── CompilationDto.java │ │ │ │ └── mapper │ │ │ │ │ └── CompilationMapper.java │ │ │ └── event │ │ │ │ ├── annotations │ │ │ │ ├── AfterNowValidator.java │ │ │ │ ├── AfterNow.java │ │ │ │ ├── DateTime.java │ │ │ │ └── DateTimeValidator.java │ │ │ │ ├── EventShortDto.java │ │ │ │ ├── UpdateEventUserRequest.java │ │ │ │ ├── NewEventDto.java │ │ │ │ ├── EventFullDto.java │ │ │ │ └── mapper │ │ │ │ └── EventMapper.java │ │ │ ├── util │ │ │ └── exception │ │ │ │ ├── ClientErrorException.java │ │ │ │ ├── EntityNotFoundException.java │ │ │ │ └── handler │ │ │ │ ├── ErrorResponse.java │ │ │ │ └── ErrorHandler.java │ │ │ ├── repository │ │ │ ├── CategoryRepository.java │ │ │ ├── UserRepository.java │ │ │ ├── CompilationRepository.java │ │ │ ├── ParticipationRequestRepository.java │ │ │ ├── LocationRepository.java │ │ │ └── EventRepository.java │ │ │ ├── ExploreWithMeMain.java │ │ │ ├── service │ │ │ ├── user │ │ │ │ ├── UserService.java │ │ │ │ └── UserServiceImpl.java │ │ │ ├── request │ │ │ │ ├── ParticipationRequestService.java │ │ │ │ └── ParticipationRequestServiceImpl.java │ │ │ ├── category │ │ │ │ ├── CategoryService.java │ │ │ │ └── CategoryServiceImpl.java │ │ │ ├── location │ │ │ │ ├── LocationService.java │ │ │ │ └── LocationServiceImpl.java │ │ │ ├── compilation │ │ │ │ ├── CompilationService.java │ │ │ │ └── CompilationServiceImpl.java │ │ │ └── event │ │ │ │ ├── StatsServiceClient.java │ │ │ │ ├── EventService.java │ │ │ │ ├── UpdateEventOperations.java │ │ │ │ └── EventServiceImpl.java │ │ │ └── controller │ │ │ ├── CategoriesController.java │ │ │ ├── LocationsController.java │ │ │ ├── CompilationsController.java │ │ │ ├── admin │ │ │ ├── AdminLocationsController.java │ │ │ ├── AdminUsersController.java │ │ │ ├── AdminCategoriesController.java │ │ │ ├── AdminCompilationsController.java │ │ │ └── AdminEventsController.java │ │ │ ├── users │ │ │ ├── UsersRequestsController.java │ │ │ └── UsersEventsController.java │ │ │ └── EventsController.java │ │ └── resources │ │ ├── application.properties │ │ └── schema.sql └── pom.xml ├── lombok.config ├── .github └── workflows │ ├── api-tests.yml │ └── wait-for-it.sh ├── suppressions.xml ├── .gitignore ├── docker-compose.yml ├── README.md ├── ewm-stats-service-spec.json ├── pom.xml └── checkstyle.xml /explore-with-me-stats/stats-client/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | explore-with-me-stats-service.url=http://localhost:9090 -------------------------------------------------------------------------------- /Dockerfile-postgres-main: -------------------------------------------------------------------------------- 1 | FROM postgres:14-alpine 2 | COPY explore-wth-me-main/src/main/resources/schema.sql /docker-entrypoint-initdb.d/schema.sql -------------------------------------------------------------------------------- /Dockerfile-postgres-stats: -------------------------------------------------------------------------------- 1 | FROM postgres:14-alpine 2 | COPY explore-with-me-stats/stats-service/src/main/resources/schema.sql /docker-entrypoint-initdb.d/ -------------------------------------------------------------------------------- /explore-wth-me-main/Dockerfile-main: -------------------------------------------------------------------------------- 1 | FROM amazoncorretto:11 2 | COPY target/*.jar main-service.jar 3 | ENTRYPOINT ["java", "-jar", "/main-service.jar"] -------------------------------------------------------------------------------- /explore-with-me-stats/stats-service/Dockerfile-stats: -------------------------------------------------------------------------------- 1 | FROM amazoncorretto:11 2 | COPY target/*.jar stats-service.jar 3 | ENTRYPOINT ["java", "-jar", "/stats-service.jar"] -------------------------------------------------------------------------------- /lombok.config: -------------------------------------------------------------------------------- 1 | config.stopBubbling = true 2 | lombok.anyconstructor.addconstructorproperties = false 3 | lombok.addLombokGeneratedAnnotation = true 4 | lombok.addSuppressWarnings = false -------------------------------------------------------------------------------- /.github/workflows/api-tests.yml: -------------------------------------------------------------------------------- 1 | name: Explore With Me API Tests 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | build: 8 | uses: yandex-praktikum/java-explore-with-me/.github/workflows/api-tests.yml@ci -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/model/enums/EventState.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.model.enums; 2 | 3 | public enum EventState { 4 | PENDING, PUBLISHED, CANCELED 5 | } 6 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/model/enums/RequestStatus.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.model.enums; 2 | 3 | public enum RequestStatus { 4 | PENDING, CONFIRMED, REJECTED, CANCELED 5 | } 6 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/model/enums/LocationStatus.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.model.enums; 2 | 3 | public enum LocationStatus { 4 | SUGGESTED_BY_USER, 5 | APPROVED_BY_ADMIN, 6 | CANCELED_BY_ADMIN 7 | } 8 | -------------------------------------------------------------------------------- /explore-with-me-stats/stats-dto/src/main/java/ru/practicum/explorewithme/dto/StatDtoWithHitsProjection.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto; 2 | 3 | public interface StatDtoWithHitsProjection { 4 | String getApp(); 5 | 6 | String getUri(); 7 | 8 | Long getHits(); 9 | } 10 | -------------------------------------------------------------------------------- /suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/dto/user/UserShortDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.user; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | @Data 7 | @Builder 8 | public class UserShortDto { 9 | private Long id; 10 | private String name; 11 | } 12 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/util/exception/ClientErrorException.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.util.exception; 2 | 3 | public class ClientErrorException extends RuntimeException { 4 | public ClientErrorException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/util/exception/EntityNotFoundException.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.util.exception; 2 | 3 | public class EntityNotFoundException extends RuntimeException { 4 | public EntityNotFoundException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /explore-with-me-stats/stats-service/src/main/resources/schema.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS stats CASCADE; 2 | 3 | CREATE TABLE stats ( 4 | id BIGSERIAL PRIMARY KEY, 5 | app VARCHAR(100) NOT NULL, 6 | uri VARCHAR(100) NOT NULL, 7 | ip VARCHAR(100) NOT NULL, 8 | timestamp TIMESTAMP WITH TIME ZONE NOT NULL 9 | ); 10 | -------------------------------------------------------------------------------- /explore-with-me-stats/stats-service/src/main/java/ru/practicum/explorewithme/controller/exception/LocalDateTimeException.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.controller.exception; 2 | 3 | public class LocalDateTimeException extends RuntimeException { 4 | public LocalDateTimeException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/dto/location/CoordinatesResponseDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.location; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | @Data 7 | @Builder 8 | public class CoordinatesResponseDto { 9 | private long id; 10 | private double lat; 11 | private double lon; 12 | } -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/dto/location/LocationDtoWithEvents.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.location; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | @Getter 7 | @AllArgsConstructor 8 | public class LocationDtoWithEvents { 9 | private String name; 10 | private Long events; 11 | } 12 | -------------------------------------------------------------------------------- /explore-with-me-stats/stats-service/src/main/java/ru/practicum/explorewithme/controller/exception/ErrorResponse.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.controller.exception; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | class ErrorResponse { 7 | private final String message; 8 | 9 | public ErrorResponse(String message) { 10 | this.message = message; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/util/exception/handler/ErrorResponse.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.util.exception.handler; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public class ErrorResponse { 7 | 8 | private final String error; 9 | 10 | public ErrorResponse(String error) { 11 | this.error = error; 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/dto/category/NewCategoryDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.category; 2 | 3 | import lombok.Data; 4 | 5 | import javax.validation.constraints.NotBlank; 6 | import javax.validation.constraints.Size; 7 | 8 | @Data 9 | public class NewCategoryDto { 10 | @NotBlank 11 | @Size(max = 50) 12 | private String name; 13 | } -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/repository/CategoryRepository.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import ru.practicum.explorewithme.model.Category; 5 | 6 | public interface CategoryRepository extends JpaRepository { 7 | boolean existsCategoriesByName(String name); 8 | } 9 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/dto/request/EventRequestStatusUpdateRequest.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.request; 2 | 3 | import lombok.Data; 4 | import ru.practicum.explorewithme.model.enums.RequestStatus; 5 | 6 | import java.util.List; 7 | 8 | @Data 9 | public class EventRequestStatusUpdateRequest { 10 | private List requestIds; 11 | private RequestStatus status; 12 | } 13 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/dto/user/UserDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.user; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | import javax.validation.constraints.NotNull; 7 | 8 | @Data 9 | @Builder 10 | public class UserDto { 11 | @NotNull 12 | private String email; 13 | @NotNull 14 | private Long id; 15 | @NotNull 16 | private String name; 17 | } 18 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/dto/request/EventRequestStatusUpdateResult.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.request; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | import java.util.List; 7 | 8 | @Data 9 | @Builder 10 | public class EventRequestStatusUpdateResult { 11 | List confirmedRequests; 12 | List rejectedRequests; 13 | } 14 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/ExploreWithMeMain.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class ExploreWithMeMain { 8 | public static void main(String[] args) { 9 | SpringApplication.run(ExploreWithMeMain.class, args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/dto/compilation/UpdateCompilationRequest.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.compilation; 2 | 3 | import lombok.Data; 4 | 5 | import javax.validation.constraints.Size; 6 | import java.util.List; 7 | 8 | @Data 9 | public class UpdateCompilationRequest { 10 | private List events; 11 | private Boolean pinned; 12 | @Size(max = 50) 13 | private String title; 14 | } 15 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/dto/category/CategoryDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.category; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | import javax.validation.constraints.NotBlank; 7 | import javax.validation.constraints.Size; 8 | 9 | @Data 10 | @Builder 11 | public class CategoryDto { 12 | private Long id; 13 | @NotBlank 14 | @Size(max = 50) 15 | private String name; 16 | } 17 | -------------------------------------------------------------------------------- /explore-with-me-stats/stats-service/src/main/java/ru/practicum/explorewithme/ExploreWithMeStats.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class ExploreWithMeStats { 8 | public static void main(String[] args) { 9 | SpringApplication.run(ExploreWithMeStats.class, args); 10 | } 11 | } -------------------------------------------------------------------------------- /explore-with-me-stats/stats-dto/src/main/java/ru/practicum/explorewithme/dto/StatDtoWithHits.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto; 2 | 3 | import lombok.*; 4 | 5 | @Getter 6 | @Setter 7 | @ToString 8 | public class StatDtoWithHits { 9 | private String app; 10 | private String uri; 11 | private Long hits; 12 | 13 | public StatDtoWithHits(String app, String uri, Long hits) { 14 | this.app = app; 15 | this.uri = uri; 16 | this.hits = hits; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/dto/compilation/NewCompilationDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.compilation; 2 | 3 | import lombok.Data; 4 | 5 | import javax.validation.constraints.NotBlank; 6 | import javax.validation.constraints.Size; 7 | import java.util.List; 8 | 9 | @Data 10 | public class NewCompilationDto { 11 | private List events; 12 | private Boolean pinned; 13 | @NotBlank 14 | @Size(max = 50) 15 | private String title; 16 | } 17 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/dto/location/LocationDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.location; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | import ru.practicum.explorewithme.model.enums.LocationStatus; 6 | 7 | @Data 8 | @Builder 9 | public class LocationDto { 10 | private String name; 11 | private Double lat; 12 | private Double lon; 13 | private Double rad; 14 | private Long id; 15 | private LocationStatus status; 16 | } 17 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/dto/location/LocationUpdateRequest.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.location; 2 | 3 | import lombok.Data; 4 | 5 | import javax.validation.constraints.Size; 6 | 7 | @Data 8 | public class LocationUpdateRequest { 9 | @Size(min = 5, max = 100) 10 | private String name; 11 | private Double rad; 12 | private AdminStatusUpdate status; 13 | 14 | enum AdminStatusUpdate { 15 | APPROVED, CANCELED 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/dto/location/NewCoordinatesDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.location; 2 | 3 | import lombok.Data; 4 | 5 | import javax.validation.constraints.Max; 6 | import javax.validation.constraints.Min; 7 | import javax.validation.constraints.NotNull; 8 | 9 | @Data 10 | public class NewCoordinatesDto { 11 | @NotNull 12 | @Min(-90) @Max(90) 13 | private Double lat; 14 | @NotNull 15 | @Min(-180) @Max(180) 16 | private Double lon; 17 | } 18 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/service/user/UserService.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.user; 2 | 3 | import ru.practicum.explorewithme.dto.user.NewUserRequest; 4 | import ru.practicum.explorewithme.dto.user.UserDto; 5 | 6 | import java.util.List; 7 | 8 | public interface UserService { 9 | List getUsersByAdmin(List ids, int from, int size); 10 | 11 | UserDto addNewUserByAdmin(NewUserRequest userRequest); 12 | 13 | void deleteUserByAdmin(long userId); 14 | } 15 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/dto/user/NewUserRequest.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.user; 2 | 3 | import lombok.Data; 4 | 5 | import javax.validation.constraints.Email; 6 | import javax.validation.constraints.NotBlank; 7 | import javax.validation.constraints.Size; 8 | 9 | @Data 10 | public class NewUserRequest { 11 | @NotBlank 12 | @Email 13 | @Size(min = 6, max = 254) 14 | private String email; 15 | @NotBlank 16 | @Size(min = 2, max = 250) 17 | private String name; 18 | } 19 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import ru.practicum.explorewithme.model.User; 5 | 6 | import org.springframework.data.domain.Pageable; 7 | import java.util.List; 8 | 9 | public interface UserRepository extends JpaRepository { 10 | List findAllByIdIn(List ids, Pageable pageable); 11 | 12 | boolean existsUserByName(String name); 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/model/Category.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.model; 2 | 3 | import lombok.*; 4 | 5 | import javax.persistence.*; 6 | import javax.validation.constraints.NotBlank; 7 | 8 | @Entity 9 | @Getter 10 | @Setter 11 | @Builder 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | @Table(name = "categories") 15 | public class Category { 16 | @Id 17 | @GeneratedValue(strategy = GenerationType.IDENTITY) 18 | private Long id; 19 | @NotBlank 20 | private String name; 21 | } 22 | -------------------------------------------------------------------------------- /explore-with-me-stats/stats-service/src/main/java/ru/practicum/explorewithme/service/StatsService.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service; 2 | 3 | 4 | import ru.practicum.explorewithme.dto.StatDto; 5 | import ru.practicum.explorewithme.dto.StatDtoWithHits; 6 | 7 | import java.time.LocalDateTime; 8 | import java.util.List; 9 | 10 | public interface StatsService { 11 | 12 | StatDto saveStats(StatDto statDto); 13 | 14 | List getStats(LocalDateTime start, LocalDateTime end, List uris, Boolean unique); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/dto/request/ParticipationRequestDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.request; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | 7 | import java.time.LocalDateTime; 8 | 9 | @Data 10 | @Builder 11 | public class ParticipationRequestDto { 12 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 13 | private LocalDateTime created; 14 | private Long event; 15 | private Long id; 16 | private Long requester; 17 | private String status; 18 | } -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/service/request/ParticipationRequestService.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.request; 2 | 3 | import ru.practicum.explorewithme.dto.request.ParticipationRequestDto; 4 | 5 | import java.util.List; 6 | 7 | public interface ParticipationRequestService { 8 | List getRequestsByUserId(long userId); 9 | 10 | ParticipationRequestDto addNewParticipationRequest(long userId, long eventId); 11 | 12 | ParticipationRequestDto cancelParticipationRequest(long userId, long requestId); 13 | } 14 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/dto/compilation/CompilationDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.compilation; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | import ru.practicum.explorewithme.dto.event.EventShortDto; 6 | 7 | import javax.validation.constraints.NotNull; 8 | import java.util.List; 9 | 10 | @Data 11 | @Builder 12 | public class CompilationDto { 13 | private List events; 14 | @NotNull 15 | private Long id; 16 | @NotNull 17 | private Boolean pinned; 18 | @NotNull 19 | private String title; 20 | } 21 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/dto/event/annotations/AfterNowValidator.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.event.annotations; 2 | 3 | import javax.validation.ConstraintValidator; 4 | import javax.validation.ConstraintValidatorContext; 5 | import java.time.LocalDateTime; 6 | 7 | public class AfterNowValidator implements ConstraintValidator { 8 | 9 | @Override 10 | public boolean isValid(LocalDateTime value, ConstraintValidatorContext context) { 11 | return value == null || value.isAfter(LocalDateTime.now()); 12 | } 13 | } 14 | 15 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/dto/location/NewLocationDtoAdmin.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.location; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | import javax.validation.constraints.*; 7 | 8 | @Data 9 | @Builder 10 | public class NewLocationDtoAdmin { 11 | @NotBlank 12 | @Size(min = 5, max = 100) 13 | private String name; 14 | @NotNull 15 | @Min(-90) @Max(90) 16 | private Double lat; 17 | @NotNull 18 | @Min(-180) @Max(180) 19 | private Double lon; 20 | @PositiveOrZero 21 | private Double rad; 22 | } 23 | -------------------------------------------------------------------------------- /explore-with-me-stats/stats-service/src/main/java/ru/practicum/explorewithme/model/Stat.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.model; 2 | 3 | import lombok.*; 4 | 5 | import javax.persistence.*; 6 | import java.time.LocalDateTime; 7 | 8 | @Entity 9 | @Table(name = "stats") 10 | @Getter 11 | @Setter 12 | @Builder 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | public class Stat { 16 | @Id 17 | @GeneratedValue(strategy = GenerationType.IDENTITY) 18 | private Long id; 19 | private String app; 20 | private String uri; 21 | private String ip; 22 | private LocalDateTime timestamp; 23 | } -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/model/User.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.model; 2 | 3 | import lombok.*; 4 | 5 | import javax.persistence.*; 6 | import javax.validation.constraints.Email; 7 | import javax.validation.constraints.NotNull; 8 | 9 | @Entity 10 | @Getter 11 | @Setter 12 | @Builder 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | @Table(name = "users") 16 | public class User { 17 | @Id 18 | @GeneratedValue(strategy = GenerationType.IDENTITY) 19 | private Long id; 20 | @NotNull 21 | @Email 22 | private String email; 23 | private String name; 24 | } 25 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/dto/event/annotations/AfterNow.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.event.annotations; 2 | 3 | import javax.validation.Constraint; 4 | import javax.validation.Payload; 5 | import java.lang.annotation.*; 6 | 7 | @Documented 8 | @Constraint(validatedBy = AfterNowValidator.class) 9 | @Target({ElementType.FIELD}) 10 | @Retention(RetentionPolicy.RUNTIME) 11 | public @interface AfterNow { 12 | String message() default "Date and time must be after current date and time"; 13 | Class[] groups() default {}; 14 | Class[] payload() default {}; 15 | } 16 | 17 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/service/category/CategoryService.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.category; 2 | 3 | import ru.practicum.explorewithme.dto.category.CategoryDto; 4 | import ru.practicum.explorewithme.dto.category.NewCategoryDto; 5 | 6 | import java.util.List; 7 | 8 | public interface CategoryService { 9 | CategoryDto addNewCategory(NewCategoryDto dto); 10 | 11 | void deleteCategory(long catId); 12 | 13 | CategoryDto updateCategory(long catId, CategoryDto dto); 14 | 15 | List getCategories(int from, int size); 16 | 17 | CategoryDto getCategory(long catId); 18 | } 19 | -------------------------------------------------------------------------------- /explore-with-me-stats/stats-dto/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### IntelliJ IDEA ### 7 | .idea/modules.xml 8 | .idea/jarRepositories.xml 9 | .idea/compiler.xml 10 | .idea/libraries/ 11 | *.iws 12 | *.iml 13 | *.ipr 14 | 15 | ### Eclipse ### 16 | .apt_generated 17 | .classpath 18 | .factorypath 19 | .project 20 | .settings 21 | .springBeans 22 | .sts4-cache 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | build/ 31 | !**/src/main/**/build/ 32 | !**/src/test/**/build/ 33 | 34 | ### VS Code ### 35 | .vscode/ 36 | 37 | ### Mac OS ### 38 | .DS_Store -------------------------------------------------------------------------------- /explore-with-me-stats/stats-dto/src/main/java/ru/practicum/explorewithme/dto/StatDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.*; 5 | 6 | import javax.validation.constraints.NotBlank; 7 | import javax.validation.constraints.NotNull; 8 | import java.time.LocalDateTime; 9 | 10 | @Data 11 | @Builder 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | public class StatDto { 15 | @NotBlank 16 | private String app; 17 | @NotBlank 18 | private String uri; 19 | @NotBlank 20 | private String ip; 21 | @NotNull 22 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 23 | private LocalDateTime timestamp; 24 | } -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/repository/CompilationRepository.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.repository; 2 | 3 | import org.springframework.data.domain.Pageable; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import ru.practicum.explorewithme.model.Compilation; 6 | 7 | import java.util.List; 8 | import java.util.Optional; 9 | 10 | public interface CompilationRepository extends JpaRepository { 11 | 12 | List findAllByPinned(Boolean pinned, Pageable pageable); 13 | 14 | Optional getCompilationById(long compId); 15 | 16 | Optional findCompilationByTitle(String title); 17 | } 18 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/dto/event/annotations/DateTime.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.event.annotations; 2 | 3 | import javax.validation.Constraint; 4 | import javax.validation.Payload; 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Target({ElementType.TYPE}) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Constraint(validatedBy = DateTimeValidator.class) 13 | public @interface DateTime { 14 | String message() default "Invalid date range"; 15 | 16 | Class[] groups() default {}; 17 | 18 | Class[] payload() default {}; 19 | } 20 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/model/Compilation.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.model; 2 | 3 | import lombok.*; 4 | 5 | import javax.persistence.*; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | @Entity 10 | @Getter 11 | @Setter 12 | @Builder 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | @Table(name = "compilations") 16 | public class Compilation { 17 | @Id 18 | @GeneratedValue(strategy = GenerationType.IDENTITY) 19 | private Long id; 20 | private String title; 21 | @Column(columnDefinition = "BOOLEAN DEFAULT false") 22 | private Boolean pinned; 23 | 24 | @ManyToMany(mappedBy = "compilationList") 25 | private List eventList = new ArrayList<>(); 26 | } 27 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/service/location/LocationService.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.location; 2 | 3 | import ru.practicum.explorewithme.dto.location.LocationDto; 4 | import ru.practicum.explorewithme.dto.location.LocationUpdateRequest; 5 | import ru.practicum.explorewithme.dto.location.NewLocationDtoAdmin; 6 | import ru.practicum.explorewithme.dto.location.LocationDtoWithEvents; 7 | 8 | import java.util.List; 9 | 10 | public interface LocationService { 11 | 12 | LocationDto addNewLocation(NewLocationDtoAdmin locationDto); 13 | 14 | List getLocations(int from, int size); 15 | 16 | LocationDto updateLocation(long id, LocationUpdateRequest request); 17 | 18 | void deleteLocation(long locationId); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/service/compilation/CompilationService.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.compilation; 2 | 3 | import ru.practicum.explorewithme.dto.compilation.CompilationDto; 4 | import ru.practicum.explorewithme.dto.compilation.NewCompilationDto; 5 | import ru.practicum.explorewithme.dto.compilation.UpdateCompilationRequest; 6 | 7 | import java.util.List; 8 | 9 | public interface CompilationService { 10 | 11 | CompilationDto addNewCompilation(NewCompilationDto newCompilationDto); 12 | 13 | void deleteCompilation(long compId); 14 | 15 | CompilationDto updateCompilation(long compId, UpdateCompilationRequest updateRequest); 16 | 17 | List getCompilations(Boolean pinned, int from, int size); 18 | 19 | CompilationDto getCompilation(long compId); 20 | } 21 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/repository/ParticipationRequestRepository.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import ru.practicum.explorewithme.model.ParticipationRequest; 5 | import ru.practicum.explorewithme.model.enums.RequestStatus; 6 | 7 | import java.util.List; 8 | 9 | public interface ParticipationRequestRepository extends JpaRepository { 10 | List findAllByRequesterId(long userId); 11 | 12 | List findAllByEventIdAndStatus(long eventId, RequestStatus status); 13 | 14 | List findAllByEventInitiatorIdAndEventId(long userId, long eventId); 15 | 16 | boolean existsByRequesterIdAndEventId(long userId, long eventId); 17 | } 18 | -------------------------------------------------------------------------------- /explore-with-me-stats/stats-service/src/main/java/ru/practicum/explorewithme/model/StatMapper.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.model; 2 | 3 | import lombok.experimental.UtilityClass; 4 | import ru.practicum.explorewithme.dto.StatDto; 5 | 6 | @UtilityClass 7 | public class StatMapper { 8 | 9 | public Stat fromStatDto(StatDto statDto) { 10 | return Stat.builder() 11 | .app(statDto.getApp()) 12 | .uri(statDto.getUri()) 13 | .ip(statDto.getIp()) 14 | .timestamp(statDto.getTimestamp()) 15 | .build(); 16 | } 17 | 18 | public StatDto toStatDto(Stat stat) { 19 | return StatDto.builder() 20 | .app(stat.getApp()) 21 | .uri(stat.getUri()) 22 | .ip(stat.getIp()) 23 | .timestamp(stat.getTimestamp()) 24 | .build(); 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/model/Location.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.model; 2 | 3 | import lombok.*; 4 | import ru.practicum.explorewithme.model.enums.LocationStatus; 5 | 6 | import javax.persistence.*; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | @Entity 11 | @Getter 12 | @Setter 13 | @Builder 14 | @ToString 15 | @NoArgsConstructor 16 | @AllArgsConstructor 17 | @Table(name = "locations") 18 | public class Location { 19 | @Id 20 | @GeneratedValue(strategy = GenerationType.IDENTITY) 21 | private Long id; 22 | private Double lat; 23 | private Double lon; 24 | private Double rad; 25 | private String name; 26 | @Enumerated(EnumType.STRING) 27 | @Column(name = "status", nullable = false) 28 | private LocationStatus status; 29 | 30 | @ManyToMany(mappedBy = "locationList") 31 | private List eventList = new ArrayList<>(); 32 | } 33 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/dto/event/EventShortDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.event; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import ru.practicum.explorewithme.dto.category.CategoryDto; 7 | import ru.practicum.explorewithme.dto.user.UserShortDto; 8 | 9 | import javax.validation.constraints.NotNull; 10 | import java.time.LocalDateTime; 11 | 12 | @Data 13 | @Builder 14 | public class EventShortDto { 15 | @NotNull 16 | private String annotation; 17 | @NotNull 18 | private CategoryDto category; 19 | private Integer confirmedRequests; 20 | @NotNull 21 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 22 | private LocalDateTime eventDate; 23 | private Long id; 24 | @NotNull 25 | private UserShortDto initiator; 26 | private Boolean paid; 27 | @NotNull 28 | private String title; 29 | private Integer views; 30 | } 31 | -------------------------------------------------------------------------------- /explore-with-me-stats/stats-service/src/main/java/ru/practicum/explorewithme/controller/exception/ErrorHandler.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.controller.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ExceptionHandler; 5 | import org.springframework.web.bind.annotation.ResponseStatus; 6 | import org.springframework.web.bind.annotation.RestControllerAdvice; 7 | 8 | @RestControllerAdvice 9 | public class ErrorHandler { 10 | @ExceptionHandler 11 | @ResponseStatus(HttpStatus.BAD_REQUEST) 12 | public ErrorResponse handleLocalDateTimeException(final LocalDateTimeException e) { 13 | e.printStackTrace(); 14 | return new ErrorResponse(e.getMessage()); 15 | } 16 | 17 | @ExceptionHandler 18 | @ResponseStatus(HttpStatus.BAD_REQUEST) 19 | public ErrorResponse handleThrowableException(final Throwable e) { 20 | e.printStackTrace(); 21 | return new ErrorResponse(e.getMessage()); 22 | } 23 | } -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/dto/event/UpdateEventUserRequest.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.event; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.Data; 5 | import ru.practicum.explorewithme.dto.event.annotations.AfterNow; 6 | import ru.practicum.explorewithme.dto.location.NewCoordinatesDto; 7 | 8 | import javax.validation.constraints.Size; 9 | import java.time.LocalDateTime; 10 | 11 | @Data 12 | public class UpdateEventUserRequest { 13 | @Size(min = 20, max = 2000) 14 | private String annotation; 15 | private Long category; 16 | @Size(min = 20, max = 7000) 17 | private String description; 18 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 19 | @AfterNow 20 | private LocalDateTime eventDate; 21 | private NewCoordinatesDto location; 22 | private Boolean paid; 23 | private Integer participantLimit; 24 | private Boolean requestModeration; 25 | private String stateAction; 26 | @Size(min = 3, max = 120) 27 | private String title; 28 | } -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/model/ParticipationRequest.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.*; 5 | import ru.practicum.explorewithme.model.enums.RequestStatus; 6 | 7 | import javax.persistence.*; 8 | import javax.validation.constraints.NotNull; 9 | import java.time.LocalDateTime; 10 | 11 | @Entity 12 | @Getter 13 | @Setter 14 | @Builder 15 | @NoArgsConstructor 16 | @AllArgsConstructor 17 | @Table(name = "requests") 18 | public class ParticipationRequest { 19 | @NotNull 20 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 21 | private LocalDateTime created; 22 | @ManyToOne(fetch = FetchType.LAZY) 23 | private Event event; 24 | @Id 25 | @GeneratedValue(strategy = GenerationType.IDENTITY) 26 | private Long id; 27 | @ManyToOne(fetch = FetchType.LAZY) 28 | private User requester; 29 | @Enumerated(EnumType.STRING) 30 | @Column(name = "status", nullable = false) 31 | private RequestStatus status; 32 | } -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/dto/event/annotations/DateTimeValidator.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.event.annotations; 2 | 3 | import ru.practicum.explorewithme.dto.event.NewEventDto; 4 | 5 | import javax.validation.ConstraintValidator; 6 | import javax.validation.ConstraintValidatorContext; 7 | import java.time.LocalDateTime; 8 | 9 | public class DateTimeValidator implements ConstraintValidator { 10 | 11 | @Override 12 | public boolean isValid(Object value, ConstraintValidatorContext context) { 13 | if (value == null) { 14 | return true; 15 | } 16 | 17 | if (!(value instanceof NewEventDto)) { 18 | throw new IllegalArgumentException("Invalid validation type"); 19 | } 20 | 21 | NewEventDto entity = (NewEventDto) value; 22 | LocalDateTime now = LocalDateTime.now(); 23 | LocalDateTime eventDate = entity.getEventDate(); 24 | 25 | return eventDate != null && eventDate.minusHours(2L).isAfter(now); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /explore-with-me-stats/stats-service/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=9090 2 | 3 | spring.jpa.hibernate.ddl-auto=none 4 | spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQL10Dialect 5 | spring.jpa.properties.hibernate.format_sql=true 6 | spring.sql.init.mode=always 7 | spring.jpa.show-sql=true 8 | 9 | logging.level.org.springframework.orm.jpa=INFO 10 | logging.level.org.springframework.transaction=INFO 11 | logging.level.org.springframework.transaction.interceptor=TRACE 12 | logging.level.org.springframework.orm.jpa.JpaTransactionManager=DEBUG 13 | 14 | spring.datasource.driverClassName=org.postgresql.Driver 15 | spring.datasource.url=${SPRING_DATASOURCE_URL} 16 | spring.datasource.username=${USERNAME_DB} 17 | spring.datasource.password=${PASSWORD_DB} 18 | 19 | #--- 20 | spring.config.activate.on-profile=ci,test 21 | spring.datasource.driverClassName=org.h2.Driver 22 | spring.datasource.url=jdbc:h2:mem:emw 23 | spring.datasource.username=test 24 | spring.datasource.password=test 25 | spring.h2.console.enabled=true 26 | spring.h2.console.path=/h2-console -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/dto/request/mapper/RequestMapper.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.request.mapper; 2 | 3 | import lombok.experimental.UtilityClass; 4 | import ru.practicum.explorewithme.dto.request.ParticipationRequestDto; 5 | import ru.practicum.explorewithme.model.ParticipationRequest; 6 | 7 | import java.util.List; 8 | import java.util.stream.Collectors; 9 | 10 | @UtilityClass 11 | public class RequestMapper { 12 | public ParticipationRequestDto mapToDto(ParticipationRequest entity) { 13 | return ParticipationRequestDto.builder() 14 | .id(entity.getId()) 15 | .requester(entity.getRequester().getId()) 16 | .event(entity.getEvent().getId()) 17 | .created(entity.getCreated()) 18 | .status(String.valueOf(entity.getStatus())) 19 | .build(); 20 | } 21 | 22 | public List mapToDto(List entities) { 23 | return entities.stream() 24 | .map(RequestMapper::mapToDto) 25 | .collect(Collectors.toList()); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/util/exception/handler/ErrorHandler.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.util.exception.handler; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ExceptionHandler; 5 | import org.springframework.web.bind.annotation.ResponseStatus; 6 | import org.springframework.web.bind.annotation.RestControllerAdvice; 7 | import ru.practicum.explorewithme.util.exception.ClientErrorException; 8 | import ru.practicum.explorewithme.util.exception.EntityNotFoundException; 9 | 10 | @RestControllerAdvice 11 | public class ErrorHandler { 12 | 13 | @ExceptionHandler 14 | @ResponseStatus(HttpStatus.NOT_FOUND) 15 | public ErrorResponse handleEventNotFoundException(final EntityNotFoundException e) { 16 | e.printStackTrace(); 17 | return new ErrorResponse(e.getMessage()); 18 | } 19 | 20 | @ExceptionHandler 21 | @ResponseStatus(HttpStatus.CONFLICT) 22 | public ErrorResponse handleClientErrorException(final ClientErrorException e) { 23 | e.printStackTrace(); 24 | return new ErrorResponse(e.getMessage()); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/dto/user/mapper/UserMapper.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.user.mapper; 2 | 3 | import lombok.experimental.UtilityClass; 4 | import ru.practicum.explorewithme.dto.user.NewUserRequest; 5 | import ru.practicum.explorewithme.dto.user.UserDto; 6 | import ru.practicum.explorewithme.dto.user.UserShortDto; 7 | import ru.practicum.explorewithme.model.User; 8 | 9 | @UtilityClass 10 | public class UserMapper { 11 | 12 | public User mapFromUserDto(NewUserRequest dto) { 13 | return User.builder() 14 | .name(dto.getName()) 15 | .email(dto.getEmail()) 16 | .build(); 17 | } 18 | 19 | public UserDto mapToUserDto(User user) { 20 | return UserDto.builder() 21 | .id(user.getId()) 22 | .email(user.getEmail()) 23 | .name(user.getName()) 24 | .build(); 25 | } 26 | 27 | public UserShortDto mapToUserShortDto(User user) { 28 | return UserShortDto.builder() 29 | .id(user.getId()) 30 | .name(user.getName()) 31 | .build(); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/repository/LocationRepository.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import org.springframework.data.jpa.repository.Query; 5 | import org.springframework.data.repository.query.Param; 6 | import ru.practicum.explorewithme.model.Location; 7 | 8 | import org.springframework.data.domain.Pageable; 9 | import java.util.List; 10 | 11 | public interface LocationRepository extends JpaRepository { 12 | 13 | List findAllByNameNotNull(Pageable pageable); 14 | 15 | @Query("SELECT l FROM Location l " + 16 | "WHERE FUNCTION('distance', :lat, :lon, l.lat, l.lon) <= l.rad " + 17 | "ORDER BY FUNCTION('distance', :lat, :lon, l.lat, l.lon) ASC ") 18 | List findLocationsWithinRadius( 19 | @Param("lat") Double lat, 20 | @Param("lon") Double lon 21 | ); 22 | 23 | Location findByLatAndLon(Double lat, Double lon); 24 | 25 | boolean existsByLatAndLon(Double lat, Double lon); 26 | 27 | boolean existsByNameAndLatAndLon(String name, Double lat, Double lon); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/dto/category/mapper/CategoryMapper.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.category.mapper; 2 | 3 | import lombok.experimental.UtilityClass; 4 | import ru.practicum.explorewithme.dto.category.CategoryDto; 5 | import ru.practicum.explorewithme.dto.category.NewCategoryDto; 6 | import ru.practicum.explorewithme.model.Category; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | @UtilityClass 12 | public class CategoryMapper { 13 | public Category mapEntityFromDto(NewCategoryDto dto) { 14 | return Category.builder() 15 | .name(dto.getName()) 16 | .build(); 17 | } 18 | 19 | public CategoryDto mapDtoFromEntity(Category entity) { 20 | return CategoryDto.builder() 21 | .id(entity.getId()) 22 | .name(entity.getName()) 23 | .build(); 24 | } 25 | 26 | public List mapDtoFromEntity(Iterable entities) { 27 | List ans = new ArrayList<>(); 28 | for (Category c : entities) { 29 | ans.add(mapDtoFromEntity(c)); 30 | } 31 | return ans; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/controller/CategoriesController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.controller; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.web.bind.annotation.*; 6 | import ru.practicum.explorewithme.dto.category.CategoryDto; 7 | import ru.practicum.explorewithme.service.category.CategoryService; 8 | 9 | import java.util.List; 10 | 11 | @Slf4j 12 | @RestController 13 | @RequiredArgsConstructor 14 | @RequestMapping(path = "/categories") 15 | public class CategoriesController { 16 | 17 | private final CategoryService categoryService; 18 | 19 | @GetMapping 20 | public List getCategories( 21 | @RequestParam(required = false, defaultValue = "0") int from, 22 | @RequestParam(required = false, defaultValue = "10") int size 23 | ) { 24 | log.info("GET | Get Categories"); 25 | return categoryService.getCategories(from, size); 26 | } 27 | 28 | @GetMapping(path = "/{catId}") 29 | public CategoryDto getCategory( 30 | @PathVariable(name = "catId") long catId 31 | ) { 32 | return categoryService.getCategory(catId); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/dto/event/NewEventDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.event; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.Data; 5 | import ru.practicum.explorewithme.dto.event.annotations.DateTime; 6 | import ru.practicum.explorewithme.dto.location.NewCoordinatesDto; 7 | 8 | import javax.validation.Valid; 9 | import javax.validation.constraints.NotBlank; 10 | import javax.validation.constraints.NotNull; 11 | import javax.validation.constraints.PositiveOrZero; 12 | import javax.validation.constraints.Size; 13 | import java.time.LocalDateTime; 14 | 15 | @Data 16 | @DateTime 17 | public class NewEventDto { 18 | @NotBlank 19 | @Size(min = 20, max = 2000) 20 | private String annotation; 21 | @NotNull 22 | private Long category; 23 | @NotBlank 24 | @Size(min = 20) 25 | private String description; 26 | @NotNull 27 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 28 | private LocalDateTime eventDate; 29 | @NotNull 30 | @Valid 31 | private NewCoordinatesDto location; 32 | private Boolean paid; 33 | @PositiveOrZero 34 | private Integer participantLimit; 35 | private Boolean requestModeration; 36 | @NotNull 37 | @Size(min = 3, max = 120) 38 | private String title; 39 | } 40 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | logging.level.org.springframework.web.client.RestTemplate=DEBUG 2 | server.port=8080 3 | explore-with-me-stats-service.url=${EWM_SERVER_URL} 4 | app-name=ewm-main-service 5 | 6 | spring.jpa.hibernate.ddl-auto=none 7 | spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQL10Dialect 8 | spring.jpa.properties.hibernate.format_sql=true 9 | spring.sql.init.mode=always 10 | spring.jpa.show-sql=true 11 | 12 | logging.level.org.springframework.orm.jpa=INFO 13 | logging.level.org.springframework.transaction=INFO 14 | logging.level.org.springframework.transaction.interceptor=TRACE 15 | logging.level.org.springframework.orm.jpa.JpaTransactionManager=DEBUG 16 | 17 | spring.datasource.driverClassName=org.postgresql.Driver 18 | spring.datasource.url=${SPRING_DATASOURCE_URL} 19 | spring.datasource.username=${USERNAME_DB} 20 | spring.datasource.password=${PASSWORD_DB} 21 | 22 | #--- 23 | explore-with-me-stats-service.url=http://localhost:9090 24 | spring.config.activate.on-profile=ci,test 25 | spring.datasource.url=jdbc:postgresql://localhost:5432/ewmmain 26 | spring.datasource.username=postgres 27 | spring.datasource.password=iamroot 28 | #spring.datasource.driverClassName=org.h2.Driver 29 | #spring.datasource.url=jdbc:h2:mem:emw-main 30 | #spring.datasource.username=test 31 | #spring.datasource.password=test 32 | #spring.h2.console.enabled=true 33 | #spring.h2.console.path=/h2-console -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/dto/event/EventFullDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.event; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import ru.practicum.explorewithme.dto.category.CategoryDto; 7 | import ru.practicum.explorewithme.dto.location.CoordinatesResponseDto; 8 | import ru.practicum.explorewithme.dto.user.UserShortDto; 9 | 10 | import javax.validation.constraints.NotNull; 11 | import java.time.LocalDateTime; 12 | 13 | @Data 14 | @Builder 15 | public class EventFullDto { 16 | @NotNull 17 | private String annotation; 18 | @NotNull 19 | private CategoryDto category; 20 | private Integer confirmedRequests; 21 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 22 | private LocalDateTime createdOn; 23 | @NotNull 24 | private String description; 25 | @NotNull 26 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 27 | private LocalDateTime eventDate; 28 | private Long id; 29 | private UserShortDto initiator; 30 | @NotNull 31 | private CoordinatesResponseDto location; 32 | private Boolean paid; 33 | private Integer participantLimit; 34 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 35 | private LocalDateTime publishedOn; 36 | private Boolean requestModeration; 37 | private String state; 38 | @NotNull 39 | private String title; 40 | private Integer views; 41 | } -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/dto/location/mapper/LocationMapper.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.location.mapper; 2 | 3 | import lombok.experimental.UtilityClass; 4 | import ru.practicum.explorewithme.dto.location.LocationDto; 5 | import ru.practicum.explorewithme.dto.location.NewLocationDtoAdmin; 6 | import ru.practicum.explorewithme.dto.location.NewCoordinatesDto; 7 | import ru.practicum.explorewithme.model.Location; 8 | 9 | @UtilityClass 10 | public class LocationMapper { 11 | 12 | public Location mapFromLocationDto(NewLocationDtoAdmin dto) { 13 | return Location.builder() 14 | .name(dto.getName()) 15 | .lon(dto.getLon()) 16 | .lat(dto.getLat()) 17 | .rad(dto.getRad()) 18 | .build(); 19 | } 20 | 21 | public Location mapFromLocationShortDto(NewCoordinatesDto dto) { 22 | return Location.builder() 23 | .lat(dto.getLat()) 24 | .lon(dto.getLon()) 25 | .build(); 26 | } 27 | 28 | public LocationDto mapFullDtoFromEntity(Location entity) { 29 | return LocationDto.builder() 30 | .id(entity.getId()) 31 | .name((entity.getName())) 32 | .lat(entity.getLat()) 33 | .lon(entity.getLon()) 34 | .rad(entity.getRad()) 35 | .status(entity.getStatus()) 36 | .build(); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/controller/LocationsController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.controller; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.validation.annotation.Validated; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RequestParam; 9 | import org.springframework.web.bind.annotation.RestController; 10 | import ru.practicum.explorewithme.dto.location.LocationDtoWithEvents; 11 | import ru.practicum.explorewithme.service.location.LocationService; 12 | 13 | import javax.validation.constraints.Positive; 14 | import javax.validation.constraints.PositiveOrZero; 15 | import java.util.List; 16 | 17 | @Slf4j 18 | @Validated 19 | @RestController 20 | @RequiredArgsConstructor 21 | @RequestMapping(path = "/locations") 22 | public class LocationsController { 23 | 24 | private final LocationService locationService; 25 | 26 | @GetMapping 27 | public List getLocations( 28 | @RequestParam(defaultValue = "0") @PositiveOrZero int from, 29 | @RequestParam(defaultValue = "10") @Positive int size 30 | ) { 31 | log.info("GET | Locations"); 32 | List response = locationService.getLocations(from, size); 33 | log.info("{}", response); 34 | return response; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.1' 2 | services: 3 | stats-server: 4 | build: 5 | context: ./explore-with-me-stats/stats-service 6 | dockerfile: Dockerfile-stats 7 | image: ewm-stats-service 8 | ports: 9 | - "9090:9090" 10 | depends_on: 11 | - stats-db 12 | environment: 13 | - SPRING_DATASOURCE_URL=jdbc:postgresql://stats-db:5432/ewmstats 14 | - USERNAME_DB=postgres 15 | - PASSWORD_DB=iamroot 16 | 17 | stats-db: 18 | image: postgres:14-alpine 19 | build: 20 | context: ./ 21 | dockerfile: Dockerfile-postgres-stats 22 | volumes: 23 | - /var/lib/postgresql/data/ 24 | ports: 25 | - "6541:5432" 26 | environment: 27 | - POSTGRES_DB=ewmstats 28 | - POSTGRES_USER=postgres 29 | - POSTGRES_PASSWORD=iamroot 30 | 31 | ewm-service: 32 | build: 33 | context: ./explore-wth-me-main 34 | dockerfile: Dockerfile-main 35 | image: ewm-main-service 36 | ports: 37 | - "8080:8080" 38 | depends_on: 39 | - stats-server 40 | - ewm-db 41 | environment: 42 | - EWM_SERVER_URL=http://stats-server:9090 43 | - SPRING_DATASOURCE_URL=jdbc:postgresql://ewm-db:5432/ewmmain 44 | - USERNAME_DB=postgres 45 | - PASSWORD_DB=iamroot 46 | 47 | ewm-db: 48 | image: postgres:14-alpine 49 | build: 50 | context: ./ 51 | dockerfile: Dockerfile-postgres-main 52 | volumes: 53 | - /var/lib/postgresql/data/ 54 | ports: 55 | - "6542:5432" 56 | environment: 57 | - POSTGRES_DB=ewmmain 58 | - POSTGRES_USER=postgres 59 | - POSTGRES_PASSWORD=iamroot -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/controller/CompilationsController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.controller; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.web.bind.annotation.*; 6 | import ru.practicum.explorewithme.dto.compilation.CompilationDto; 7 | import ru.practicum.explorewithme.service.compilation.CompilationService; 8 | 9 | import java.util.List; 10 | 11 | @Slf4j 12 | @RestController 13 | @RequiredArgsConstructor 14 | @RequestMapping(path = "/compilations") 15 | public class CompilationsController { 16 | 17 | private final CompilationService compilationService; 18 | 19 | @GetMapping 20 | public List getCompilations( 21 | @RequestParam(required = false, name = "pinned") Boolean pinned, 22 | @RequestParam(required = false, defaultValue = "0") int from, 23 | @RequestParam(required = false, defaultValue = "10") int size 24 | ) { 25 | log.info("GET | Get Compilations"); 26 | List response = compilationService.getCompilations(pinned, from, size); 27 | log.info("Get Compilations: {}", response); 28 | return response; 29 | } 30 | 31 | @GetMapping(path = "/{compId}") 32 | public CompilationDto getCompilation( 33 | @PathVariable(name = "compId") long compId 34 | ) { 35 | log.info("GET | Get Compilation"); 36 | CompilationDto response = compilationService.getCompilation(compId); 37 | log.info("Get Compilation: {}", response); 38 | return response; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/dto/compilation/mapper/CompilationMapper.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.compilation.mapper; 2 | 3 | import lombok.experimental.UtilityClass; 4 | import ru.practicum.explorewithme.dto.compilation.CompilationDto; 5 | import ru.practicum.explorewithme.dto.compilation.NewCompilationDto; 6 | import ru.practicum.explorewithme.dto.event.mapper.EventMapper; 7 | import ru.practicum.explorewithme.model.Compilation; 8 | import ru.practicum.explorewithme.model.Event; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | import java.util.stream.Collectors; 13 | 14 | @UtilityClass 15 | public class CompilationMapper { 16 | 17 | public CompilationDto mapEntityToDto(Compilation compilation) { 18 | List eventList = compilation.getEventList() != null ? compilation.getEventList() : new ArrayList<>(); 19 | 20 | return CompilationDto.builder() 21 | .events(EventMapper.mapToEventShortDto(eventList)) 22 | .id(compilation.getId()) 23 | .pinned(compilation.getPinned()) 24 | .title(compilation.getTitle()) 25 | .build(); 26 | } 27 | 28 | public List mapEntityToDto(List compilations) { 29 | return compilations.stream() 30 | .map(CompilationMapper::mapEntityToDto) 31 | .collect(Collectors.toList()); 32 | } 33 | 34 | public Compilation mapFromNewCompilationDto(NewCompilationDto dto) { 35 | return Compilation.builder() 36 | .title(dto.getTitle()) 37 | .pinned(dto.getPinned() != null ? dto.getPinned() : false) 38 | .build(); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /explore-with-me-stats/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | ru.practicum 9 | explore-with-me 10 | 0.0.1-SNAPSHOT 11 | ../pom.xml 12 | 13 | 14 | pom 15 | 16 | Explore With Me Statistics 17 | 18 | stats-dto 19 | stats-client 20 | stats-service 21 | 22 | explore-with-me-stats 23 | 24 | 25 | 11 26 | 11 27 | UTF-8 28 | 29 | 30 | 31 | 32 | 33 | 34 | com.fasterxml.jackson.core 35 | jackson-databind 36 | 2.13.4.1 37 | 38 | 39 | 40 | ru.practicum 41 | stats-dto 42 | ${project.version} 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /explore-with-me-stats/stats-dto/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | ru.practicum 8 | explore-with-me-stats 9 | 0.0.1-SNAPSHOT 10 | ../pom.xml 11 | 12 | 13 | stats-dto 14 | jar 15 | 16 | 17 | 11 18 | 11 19 | UTF-8 20 | 21 | 22 | 23 | 24 | org.projectlombok 25 | lombok 26 | true 27 | 28 | 29 | 30 | com.fasterxml.jackson.core 31 | jackson-databind 32 | 33 | 34 | 35 | jakarta.validation 36 | jakarta.validation-api 37 | 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-starter-test 42 | test 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /explore-with-me-stats/stats-service/src/main/java/ru/practicum/explorewithme/controller/StatsController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.controller; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.format.annotation.DateTimeFormat; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.web.bind.annotation.*; 8 | import ru.practicum.explorewithme.dto.StatDto; 9 | import ru.practicum.explorewithme.dto.StatDtoWithHits; 10 | import ru.practicum.explorewithme.service.StatsService; 11 | 12 | import java.time.LocalDateTime; 13 | import java.util.List; 14 | 15 | @RestController 16 | @Slf4j 17 | @RequiredArgsConstructor 18 | public class StatsController { 19 | 20 | private final StatsService service; 21 | 22 | @PostMapping(path = "/hit") 23 | @ResponseStatus(HttpStatus.CREATED) 24 | public StatDto saveStats(@RequestBody StatDto statDto) { 25 | log.info("POST request for stats received: {}", statDto); 26 | StatDto response = service.saveStats(statDto); 27 | log.info("Stats saved: {}", response); 28 | return response; 29 | } 30 | 31 | @GetMapping(path = "/stats") 32 | public List getStats( 33 | @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime start, 34 | @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime end, 35 | @RequestParam(required = false) List uris, 36 | @RequestParam(required = false, defaultValue = "false") Boolean unique 37 | ) { 38 | log.info("GET request for stats received from {} to {} with uris: {}", start, end, uris); 39 | List response = service.getStats(start, end, uris, unique); 40 | log.info("{}", response); 41 | return response; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /explore-with-me-stats/stats-service/src/main/java/ru/practicum/explorewithme/repository/StatsRepository.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.repository; 2 | 3 | import org.springframework.data.jpa.repository.Query; 4 | import org.springframework.data.repository.query.Param; 5 | import ru.practicum.explorewithme.dto.StatDtoWithHitsProjection; 6 | import ru.practicum.explorewithme.model.Stat; 7 | import org.springframework.data.jpa.repository.JpaRepository; 8 | 9 | import java.time.LocalDateTime; 10 | import java.util.List; 11 | 12 | public interface StatsRepository extends JpaRepository { 13 | @Query(value = 14 | "SELECT st.app AS app, st.uri AS uri, count(DISTINCT st.ip) AS hits " + 15 | "FROM stats st " + 16 | "WHERE (:uris IS NULL OR st.uri IN :uris) " + 17 | "AND (CAST(st.timestamp AS DATE) BETWEEN :start AND :end) " + 18 | "GROUP BY st.app, st.uri " + 19 | "ORDER BY hits DESC", 20 | nativeQuery = true) 21 | List getStatsForTimeIntervalUnique( 22 | @Param("start") LocalDateTime start, 23 | @Param("end") LocalDateTime end, 24 | @Param("uris") List uris 25 | ); 26 | 27 | @Query(value = 28 | "SELECT st.app AS app, st.uri AS uri, count(st.ip) AS hits " + 29 | "FROM stats st " + 30 | "WHERE (:uris IS NULL OR st.uri IN :uris) " + 31 | "AND (CAST(st.timestamp AS DATE) BETWEEN :start AND :end) " + 32 | "GROUP BY st.app, st.uri " + 33 | "ORDER BY hits DESC", 34 | nativeQuery = true) 35 | List getStatsForTimeInterval( 36 | @Param("start") LocalDateTime start, 37 | @Param("end") LocalDateTime end, 38 | @Param("uris") List uris 39 | ); 40 | } -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/service/event/StatsServiceClient.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.event; 2 | 3 | import com.fasterxml.jackson.core.type.TypeReference; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import lombok.NonNull; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.stereotype.Component; 9 | import ru.practicum.explorewithme.StatsClient; 10 | 11 | import javax.servlet.http.HttpServletRequest; 12 | import java.time.LocalDateTime; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | @Component 17 | @RequiredArgsConstructor 18 | public class StatsServiceClient { 19 | 20 | private final StatsClient statsClient; 21 | 22 | protected int getHits(@NonNull ResponseEntity responseEntity) { 23 | ObjectMapper objectMapper = new ObjectMapper(); 24 | List> responseList 25 | = objectMapper.convertValue(responseEntity.getBody(), new TypeReference<>() {}); 26 | 27 | if (!responseList.isEmpty()) { 28 | Map firstResponseMap = responseList.get(0); 29 | if (firstResponseMap.containsKey("hits")) { 30 | Object hitsValue = firstResponseMap.get("hits"); 31 | if (hitsValue instanceof Integer) { 32 | return (int) hitsValue; 33 | } 34 | } 35 | } 36 | 37 | return 0; 38 | } 39 | 40 | protected ResponseEntity getStatistics(long id) { 41 | LocalDateTime start = LocalDateTime.of(2020, 1, 1, 10, 0,0); 42 | LocalDateTime end = LocalDateTime.of(2035, 1, 1, 10, 0,0); 43 | return statsClient.getStats( 44 | start, 45 | end, 46 | List.of("/events/" + id), 47 | true 48 | ); 49 | } 50 | 51 | protected void sendHit(String app, HttpServletRequest request) { 52 | statsClient.sendHit(app, request); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /explore-with-me-stats/stats-client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | ru.practicum 8 | explore-with-me-stats 9 | 0.0.1-SNAPSHOT 10 | ../pom.xml 11 | 12 | 13 | stats-client 14 | jar 15 | 16 | 17 | 11 18 | 11 19 | UTF-8 20 | 21 | 22 | 23 | 24 | ru.practicum 25 | stats-dto 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-web 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-actuator 36 | 37 | 38 | 39 | org.apache.httpcomponents 40 | httpclient 41 | 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-configuration-processor 46 | true 47 | 48 | 49 | 50 | org.springframework.boot 51 | spring-boot-starter-test 52 | test 53 | 54 | 55 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/controller/admin/AdminLocationsController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.controller.admin; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.validation.annotation.Validated; 7 | import org.springframework.web.bind.annotation.*; 8 | import ru.practicum.explorewithme.dto.location.LocationDto; 9 | import ru.practicum.explorewithme.dto.location.LocationUpdateRequest; 10 | import ru.practicum.explorewithme.dto.location.NewLocationDtoAdmin; 11 | import ru.practicum.explorewithme.service.location.LocationService; 12 | 13 | import javax.validation.Valid; 14 | 15 | @Slf4j 16 | @Validated 17 | @RestController 18 | @RequiredArgsConstructor 19 | @RequestMapping(path = "/admin/locations") 20 | public class AdminLocationsController { 21 | 22 | private final LocationService locationService; 23 | 24 | @PostMapping 25 | @ResponseStatus(HttpStatus.CREATED) 26 | public LocationDto addNewLocation( 27 | @RequestBody @Valid NewLocationDtoAdmin locationDto 28 | ) { 29 | log.info("POST | Add new Location"); 30 | LocationDto response = locationService.addNewLocation(locationDto); 31 | log.info("{}", response); 32 | return response; 33 | } 34 | 35 | @PatchMapping(path = "/{locationId}") 36 | public LocationDto updateLocation( 37 | @PathVariable(name = "locationId") long id, 38 | @RequestBody @Valid LocationUpdateRequest request 39 | ) { 40 | log.info("PATCH | Update location"); 41 | LocationDto response = locationService.updateLocation(id, request); 42 | log.info("{}", response); 43 | return response; 44 | } 45 | 46 | @DeleteMapping(path = "/{locationId}") 47 | public void deleteLocation( 48 | @PathVariable(name = "locationId") long id 49 | ) { 50 | log.info("DELETE | Delete Location"); 51 | locationService.deleteLocation(id); 52 | log.info("Delete Location successful"); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/repository/EventRepository.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.repository; 2 | 3 | import org.springframework.data.domain.Pageable; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.data.jpa.repository.Query; 6 | import org.springframework.lang.Nullable; 7 | import ru.practicum.explorewithme.model.Event; 8 | import ru.practicum.explorewithme.model.enums.EventState; 9 | 10 | import java.time.LocalDateTime; 11 | import java.util.List; 12 | 13 | public interface EventRepository extends JpaRepository { 14 | 15 | List findAllByInitiatorId(long userId, Pageable pageable); 16 | 17 | List findByInitiator_IdInAndStateInAndCategory_IdInAndEventDateGreaterThanEqualAndEventDateLessThanEqual( 18 | @Nullable List users, 19 | @Nullable List states, 20 | @Nullable List categories, 21 | @Nullable LocalDateTime rangeStart, 22 | @Nullable LocalDateTime rangeEnd, 23 | Pageable pageable 24 | ); 25 | 26 | List findByInitiator_IdInAndCategory_IdIn( 27 | @Nullable List users, 28 | @Nullable List categories, 29 | Pageable pageable 30 | ); 31 | 32 | List findByAnnotationContainingIgnoreCaseOrDescriptionContainingIgnoreCaseAndCategory_IdInAndPaidAndEventDateGreaterThanEqualAndEventDateLessThanEqual( 33 | String annotation, String description, List categories, Boolean paid, LocalDateTime rangeStart, LocalDateTime rangeEnd, Pageable pageable 34 | ); 35 | 36 | 37 | List findAllByCategoryId(long catId); 38 | 39 | 40 | @Query("SELECT e FROM Event e " + 41 | "WHERE FUNCTION('distance', :lat, :lon, e.location.lat, e.location.lon) " + 42 | "<= :rad " + 43 | "AND e.state = :state " + 44 | "ORDER BY e.eventDate DESC ") 45 | List findEventsWithLocationRadius(Double lat, Double lon, Double rad, EventState state, Pageable pageable); 46 | 47 | List findAllByLocationId(long locationId); 48 | } 49 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/controller/admin/AdminUsersController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.controller.admin; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.validation.annotation.Validated; 7 | import org.springframework.web.bind.annotation.*; 8 | import ru.practicum.explorewithme.dto.user.NewUserRequest; 9 | import ru.practicum.explorewithme.dto.user.UserDto; 10 | import ru.practicum.explorewithme.service.user.UserService; 11 | 12 | import javax.validation.Valid; 13 | import java.util.List; 14 | 15 | @Slf4j 16 | @Validated 17 | @RestController 18 | @RequiredArgsConstructor 19 | @RequestMapping(path = "/admin/users") 20 | public class AdminUsersController { 21 | 22 | private final UserService userService; 23 | 24 | @GetMapping 25 | public List getUsersByAdmin( 26 | @RequestParam(required = false) List ids, 27 | @RequestParam(required = false, defaultValue = "0") int from, 28 | @RequestParam(required = false, defaultValue = "10") int size 29 | ) { 30 | log.info("GET Admin | Get Users"); 31 | List response = userService.getUsersByAdmin(ids, from, size); 32 | log.info("Get Users: {}", response); 33 | return response; 34 | } 35 | 36 | @PostMapping 37 | @ResponseStatus(HttpStatus.CREATED) 38 | public UserDto addNewUserByAdmin( 39 | @RequestBody @Valid NewUserRequest userRequest 40 | ) { 41 | log.info("POST Admin | New User Request: {}", userRequest); 42 | UserDto response = userService.addNewUserByAdmin(userRequest); 43 | log.info("New User added: {}", response); 44 | return response; 45 | } 46 | 47 | @DeleteMapping(path = "/{userId}") 48 | @ResponseStatus(HttpStatus.NO_CONTENT) 49 | public void deleteUserByAdmin( 50 | @PathVariable(name = "userId") long userId 51 | ) { 52 | log.info("DELETE Admin | UserId: {}", userId); 53 | userService.deleteUserByAdmin(userId); 54 | log.info("User {} deleted", userId); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/controller/admin/AdminCategoriesController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.controller.admin; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.validation.annotation.Validated; 7 | import org.springframework.web.bind.annotation.*; 8 | import ru.practicum.explorewithme.dto.category.CategoryDto; 9 | import ru.practicum.explorewithme.dto.category.NewCategoryDto; 10 | import ru.practicum.explorewithme.service.category.CategoryService; 11 | 12 | import javax.validation.Valid; 13 | import javax.validation.constraints.NotNull; 14 | 15 | @Slf4j 16 | @Validated 17 | @RestController 18 | @RequiredArgsConstructor 19 | @RequestMapping(path = "/admin/categories") 20 | public class AdminCategoriesController { 21 | 22 | private final CategoryService categoryService; 23 | 24 | @PostMapping 25 | @ResponseStatus(HttpStatus.CREATED) 26 | public CategoryDto addNewCategory( 27 | @RequestBody @Valid NewCategoryDto dto 28 | ) { 29 | log.info("POST | Add new Category: {}", dto); 30 | CategoryDto response = categoryService.addNewCategory(dto); 31 | log.info("Add new Category successfully: {}", response); 32 | return response; 33 | } 34 | 35 | @DeleteMapping(path = "/{catId}") 36 | @ResponseStatus(HttpStatus.NO_CONTENT) 37 | public void deleteCategory( 38 | @PathVariable(name = "catId") long catId 39 | ) { 40 | log.info("DELETE | CatId: {} | Delete Category", catId); 41 | categoryService.deleteCategory(catId); 42 | log.info("Delete Category successfully"); 43 | } 44 | 45 | @PatchMapping(path = "/{catId}") 46 | public CategoryDto updateCategory( 47 | @PathVariable(name = "catId") long catId, 48 | @RequestBody @NotNull @Valid CategoryDto dto 49 | ) { 50 | log.info("PATCH | CatId: {} | Update Category", catId); 51 | CategoryDto response = categoryService.updateCategory(catId, dto); 52 | log.info("Update Category successfully: {}", response); 53 | return response; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/controller/admin/AdminCompilationsController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.controller.admin; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.validation.annotation.Validated; 7 | import org.springframework.web.bind.annotation.*; 8 | import ru.practicum.explorewithme.dto.compilation.CompilationDto; 9 | import ru.practicum.explorewithme.dto.compilation.NewCompilationDto; 10 | import ru.practicum.explorewithme.dto.compilation.UpdateCompilationRequest; 11 | import ru.practicum.explorewithme.service.compilation.CompilationService; 12 | 13 | import javax.validation.Valid; 14 | 15 | @Slf4j 16 | @Validated 17 | @RestController 18 | @RequiredArgsConstructor 19 | @RequestMapping(path = "/admin/compilations") 20 | public class AdminCompilationsController { 21 | 22 | private final CompilationService compilationService; 23 | 24 | @PostMapping 25 | @ResponseStatus(HttpStatus.CREATED) 26 | public CompilationDto addNewCompilation( 27 | @RequestBody @Valid NewCompilationDto newCompilationDto 28 | ) { 29 | log.info("POST | Add new Compilation"); 30 | CompilationDto response = compilationService.addNewCompilation(newCompilationDto); 31 | log.info("Add new Compilation: "); 32 | return response; 33 | } 34 | 35 | @DeleteMapping(path = "/{compId}") 36 | @ResponseStatus(HttpStatus.NO_CONTENT) 37 | public void deleteCompilation( 38 | @PathVariable(name = "compId") long compId 39 | ) { 40 | log.info("DELETE | Delete Compilation"); 41 | compilationService.deleteCompilation(compId); 42 | log.info("Delete Compilation successfully"); 43 | } 44 | 45 | @PatchMapping(path = "/{compId}") 46 | public CompilationDto updateCompilation( 47 | @PathVariable(name = "compId") long compId, 48 | @RequestBody @Valid UpdateCompilationRequest updateRequest 49 | ) { 50 | log.info("PATCH | Update Compilation"); 51 | CompilationDto response = compilationService.updateCompilation(compId, updateRequest); 52 | log.info("Update Compilation: {}", response); 53 | return response; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/model/Event.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.*; 5 | import ru.practicum.explorewithme.model.enums.EventState; 6 | 7 | import javax.persistence.*; 8 | import javax.validation.constraints.Min; 9 | import javax.validation.constraints.NotNull; 10 | import java.time.LocalDateTime; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | @Entity 15 | @Getter 16 | @Setter 17 | @Builder 18 | @NoArgsConstructor 19 | @AllArgsConstructor 20 | @Table(name = "events") 21 | public class Event { 22 | @Id 23 | @GeneratedValue(strategy = GenerationType.IDENTITY) 24 | private Long id; 25 | private String annotation; 26 | @ManyToOne(fetch = FetchType.LAZY) 27 | private Category category; 28 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 29 | @Column(name = "created_on") 30 | private LocalDateTime createdOn; 31 | private String description; 32 | @Column(name = "event_date") 33 | private LocalDateTime eventDate; 34 | @ManyToOne(fetch = FetchType.LAZY) 35 | private User initiator; 36 | @ManyToOne(fetch = FetchType.EAGER) 37 | private Location location; 38 | @Min(0) 39 | @Column(name = "participant_limit") 40 | private Integer participantLimit; 41 | @Column(name = "published_on") 42 | private LocalDateTime publishedOn; 43 | @Enumerated(EnumType.STRING) 44 | @Column(name = "state", nullable = false) 45 | private EventState state; 46 | private Boolean paid; 47 | @NotNull 48 | private String title; 49 | @Column(name = "request_moderation") 50 | private Boolean requestModeration; 51 | 52 | @ManyToMany(cascade = CascadeType.ALL) 53 | @JoinTable( 54 | name = "events_compilations", 55 | joinColumns = @JoinColumn(name = "event_id"), 56 | inverseJoinColumns = @JoinColumn(name = "compilation_id") 57 | ) 58 | private List compilationList = new ArrayList<>(); 59 | 60 | @ManyToMany(cascade = CascadeType.ALL) 61 | @JoinTable( 62 | name = "events_locations", 63 | joinColumns = @JoinColumn(name = "event_id"), 64 | inverseJoinColumns = @JoinColumn(name = "location_id") 65 | ) 66 | private List locationList = new ArrayList<>(); 67 | } -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/controller/users/UsersRequestsController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.controller.users; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.web.bind.annotation.*; 7 | import ru.practicum.explorewithme.dto.request.ParticipationRequestDto; 8 | import ru.practicum.explorewithme.service.request.ParticipationRequestService; 9 | 10 | import java.util.List; 11 | 12 | @Slf4j 13 | @RestController 14 | @RequiredArgsConstructor 15 | @RequestMapping(path = "/users/{userId}/requests") 16 | public class UsersRequestsController { 17 | 18 | private final ParticipationRequestService participationRequestService; 19 | 20 | @GetMapping 21 | public List getRequestsByUserId( 22 | @PathVariable(name = "userId") long userId 23 | ) { 24 | log.info("GET | UserId: {} | Participation Requests", userId); 25 | List response 26 | = participationRequestService.getRequestsByUserId(userId); 27 | log.info("Participation Requests: {}", response); 28 | return response; 29 | } 30 | 31 | @PostMapping 32 | @ResponseStatus(HttpStatus.CREATED) 33 | public ParticipationRequestDto addNewParticipationRequest( 34 | @PathVariable(name = "userId") long userId, 35 | @RequestParam(name = "eventId") long eventId 36 | ) { 37 | log.info("POST | UserId: {} | EventId: {} | Add Request", userId, eventId); 38 | ParticipationRequestDto response 39 | = participationRequestService.addNewParticipationRequest(userId, eventId); 40 | log.info("Add Request: {}", response); 41 | return response; 42 | } 43 | 44 | @PatchMapping(path = "/{requestId}/cancel") 45 | public ParticipationRequestDto cancelParticipationRequest( 46 | @PathVariable(name = "userId") long userId, 47 | @PathVariable(name = "requestId") long requestId 48 | ) { 49 | log.info("DELETE | UserId: {} | RequestId: {} | Cancel Request", userId, requestId); 50 | ParticipationRequestDto response 51 | = participationRequestService.cancelParticipationRequest(userId, requestId); 52 | log.info("Cancel Request: {}", response); 53 | return response; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/controller/admin/AdminEventsController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.controller.admin; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.format.annotation.DateTimeFormat; 6 | import org.springframework.validation.annotation.Validated; 7 | import org.springframework.web.bind.annotation.*; 8 | import ru.practicum.explorewithme.dto.event.EventFullDto; 9 | import ru.practicum.explorewithme.dto.event.UpdateEventUserRequest; 10 | import ru.practicum.explorewithme.model.enums.EventState; 11 | import ru.practicum.explorewithme.service.event.EventService; 12 | 13 | import javax.validation.Valid; 14 | import java.time.LocalDateTime; 15 | import java.util.List; 16 | 17 | @Slf4j 18 | @Validated 19 | @RestController 20 | @RequiredArgsConstructor 21 | @RequestMapping(path = "/admin/events") 22 | public class AdminEventsController { 23 | 24 | private final EventService eventService; 25 | 26 | @GetMapping 27 | public List getEventsByAdmin( 28 | @RequestParam(required = false) List users, 29 | @RequestParam(required = false) List states, 30 | @RequestParam(required = false) List categories, 31 | @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime rangeStart, 32 | @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime rangeEnd, 33 | @RequestParam(required = false, defaultValue = "0") int from, 34 | @RequestParam(required = false, defaultValue = "10") int size 35 | ) { 36 | log.info("GET Admin | Get Events"); 37 | List response 38 | = eventService.getEventsByAdmin(users, states, categories, rangeStart, rangeEnd, from, size); 39 | log.info("Get Events: {}", response); 40 | return response; 41 | } 42 | 43 | @PatchMapping(path = "/{eventId}") 44 | public EventFullDto updateEventByAdmin( 45 | @PathVariable(name = "eventId") long eventId, 46 | @RequestBody @Valid UpdateEventUserRequest request 47 | ) { 48 | log.info("PATCH | Update Event"); 49 | EventFullDto response = eventService.updateEventRequest(eventId, request); 50 | log.info("Update Event: {}", response); 51 | return response; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/service/event/EventService.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.event; 2 | 3 | import ru.practicum.explorewithme.dto.event.EventFullDto; 4 | import ru.practicum.explorewithme.dto.event.EventShortDto; 5 | import ru.practicum.explorewithme.dto.event.NewEventDto; 6 | import ru.practicum.explorewithme.dto.event.UpdateEventUserRequest; 7 | import ru.practicum.explorewithme.dto.request.EventRequestStatusUpdateRequest; 8 | import ru.practicum.explorewithme.dto.request.EventRequestStatusUpdateResult; 9 | import ru.practicum.explorewithme.dto.request.ParticipationRequestDto; 10 | import ru.practicum.explorewithme.model.enums.EventState; 11 | 12 | import javax.servlet.http.HttpServletRequest; 13 | import java.time.LocalDateTime; 14 | import java.util.List; 15 | 16 | public interface EventService { 17 | List getEventsByUser(long userId, int from, int size); 18 | 19 | EventFullDto addNewEvent(long userId, NewEventDto newEventDto); 20 | 21 | EventFullDto getEventFullInfo(long userId, long eventId); 22 | 23 | EventFullDto updateEventRequest(long eventId, UpdateEventUserRequest request); 24 | 25 | List getParticipationRequests(long userId, long eventId); 26 | 27 | List getEventsByAdmin( 28 | List users, 29 | List states, 30 | List categories, 31 | LocalDateTime rangeStart, 32 | LocalDateTime rangeEnd, 33 | int from, 34 | int size 35 | ); 36 | 37 | EventRequestStatusUpdateResult updateStatusesOfRequestsForEvent( 38 | long userId, 39 | long eventId, 40 | EventRequestStatusUpdateRequest eventRequestStatusUpdateRequest 41 | ); 42 | 43 | List getEvents( 44 | String text, 45 | List categories, 46 | Boolean paid, 47 | LocalDateTime rangeStart, 48 | LocalDateTime rangeEnd, 49 | Boolean onlyAvailable, 50 | String sort, 51 | int from, 52 | int size, 53 | String app, 54 | HttpServletRequest request 55 | ); 56 | 57 | EventFullDto getEvent(long eventId, String app, HttpServletRequest request); 58 | 59 | List getEventsInLocation(Long locationId, Double lat, Double lon, Double rad, int from, int size); 60 | } 61 | -------------------------------------------------------------------------------- /explore-with-me-stats/stats-service/src/main/java/ru/practicum/explorewithme/service/StatsServiceImpl.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import ru.practicum.explorewithme.controller.exception.LocalDateTimeException; 5 | import ru.practicum.explorewithme.dto.StatDto; 6 | import ru.practicum.explorewithme.dto.StatDtoWithHits; 7 | import ru.practicum.explorewithme.model.Stat; 8 | import ru.practicum.explorewithme.model.StatMapper; 9 | import org.springframework.stereotype.Service; 10 | import org.springframework.transaction.annotation.Transactional; 11 | import ru.practicum.explorewithme.repository.StatsRepository; 12 | 13 | import java.time.LocalDateTime; 14 | import java.util.*; 15 | import java.util.stream.Collectors; 16 | 17 | @Service 18 | @RequiredArgsConstructor 19 | @Transactional(readOnly = true) 20 | public class StatsServiceImpl implements StatsService { 21 | 22 | private final StatsRepository statsRepository; 23 | 24 | @Override 25 | @Transactional 26 | public StatDto saveStats(StatDto statDto) { 27 | Stat stat = StatMapper.fromStatDto(statDto); 28 | return StatMapper.toStatDto(statsRepository.save(stat)); 29 | } 30 | 31 | @Override 32 | @Transactional(readOnly = true) 33 | public List getStats(LocalDateTime start, LocalDateTime end, List uris, Boolean unique) { 34 | checkLocalDateTimeOrThrow(start, end); 35 | List ans = new ArrayList<>(); 36 | 37 | for (String u : uris) { 38 | if (!unique) { 39 | List results = statsRepository.getStatsForTimeInterval(start, end, List.of(u)) 40 | .stream() 41 | .map(s -> new StatDtoWithHits(s.getApp(), s.getUri(), s.getHits())) 42 | .collect(Collectors.toList()); 43 | ans.addAll(results); 44 | } else { 45 | List results = statsRepository.getStatsForTimeIntervalUnique(start, end, List.of(u)) 46 | .stream() 47 | .map(s -> new StatDtoWithHits(s.getApp(), s.getUri(), s.getHits())) 48 | .collect(Collectors.toList()); 49 | ans.addAll(results); 50 | } 51 | } 52 | 53 | ans.sort(Comparator.comparing(StatDtoWithHits::getHits).reversed()); 54 | return ans; 55 | } 56 | 57 | private void checkLocalDateTimeOrThrow(LocalDateTime start, LocalDateTime end) { 58 | if (start.isAfter(end) || end == null) { 59 | throw new LocalDateTimeException("Start must be after end"); 60 | } 61 | } 62 | } 63 | 64 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/service/user/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.user; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.data.domain.PageRequest; 5 | import org.springframework.data.domain.Pageable; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.transaction.annotation.Transactional; 8 | import ru.practicum.explorewithme.dto.user.NewUserRequest; 9 | import ru.practicum.explorewithme.dto.user.UserDto; 10 | import ru.practicum.explorewithme.dto.user.mapper.UserMapper; 11 | import ru.practicum.explorewithme.model.User; 12 | import ru.practicum.explorewithme.repository.UserRepository; 13 | import ru.practicum.explorewithme.util.exception.ClientErrorException; 14 | import ru.practicum.explorewithme.util.exception.EntityNotFoundException; 15 | 16 | import java.util.List; 17 | import java.util.Optional; 18 | import java.util.stream.Collectors; 19 | 20 | @Service 21 | @RequiredArgsConstructor 22 | @Transactional(readOnly = true) 23 | public class UserServiceImpl implements UserService { 24 | 25 | private final UserRepository userRepository; 26 | 27 | @Override 28 | @Transactional(readOnly = true) 29 | public List getUsersByAdmin(List ids, int from, int size) { 30 | Pageable page = PageRequest.of(from / size, size); 31 | List response; 32 | 33 | if (ids == null || ids.isEmpty()) { 34 | response = userRepository.findAll(page).toList(); 35 | } else { 36 | response = userRepository.findAllByIdIn(ids, page); 37 | } 38 | 39 | return response.stream() 40 | .map(UserMapper::mapToUserDto) 41 | .collect(Collectors.toList()); 42 | } 43 | 44 | @Override 45 | @Transactional 46 | public UserDto addNewUserByAdmin(NewUserRequest userRequest) { 47 | User request = UserMapper.mapFromUserDto(userRequest); 48 | checkIfUserWithNameExists(userRequest.getName()); 49 | User response = userRepository.save(request); 50 | return UserMapper.mapToUserDto(response); 51 | } 52 | 53 | @Override 54 | @Transactional 55 | public void deleteUserByAdmin(long userId) { 56 | Optional mayBeUser = userRepository.findById(userId); 57 | if (mayBeUser.isEmpty()) { 58 | throw new EntityNotFoundException("User with id " + userId + " not found"); 59 | } 60 | userRepository.deleteById(userId); 61 | } 62 | 63 | private void checkIfUserWithNameExists(String name) { 64 | if (userRepository.existsUserByName(name)) { 65 | throw new ClientErrorException("User with name " + name + " already exists"); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /explore-with-me-stats/stats-client/src/main/java/ru/practicum/explorewithme/BaseClient.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme; 2 | 3 | import org.springframework.http.*; 4 | import org.springframework.lang.Nullable; 5 | import org.springframework.web.client.HttpStatusCodeException; 6 | import org.springframework.web.client.RestTemplate; 7 | 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | public class BaseClient { 12 | protected final RestTemplate rest; 13 | 14 | public BaseClient(RestTemplate rest) { 15 | this.rest = rest; 16 | } 17 | 18 | protected ResponseEntity post(String path, @Nullable T body) { 19 | return makeAndSendRequest(HttpMethod.POST, path, null, body); 20 | } 21 | 22 | protected ResponseEntity get(String path, @Nullable Map parameters) { 23 | return makeAndSendRequest(HttpMethod.GET, path, parameters, null); 24 | } 25 | 26 | private ResponseEntity makeAndSendRequest(HttpMethod method, 27 | String path, 28 | @Nullable Map parameters, 29 | @Nullable T body) { 30 | HttpEntity requestEntity = new HttpEntity<>(body, defaultHeaders()); 31 | 32 | ResponseEntity ewmStatsServerResponse; 33 | try { 34 | if (parameters != null) { 35 | ewmStatsServerResponse = rest.exchange(path, method, requestEntity, Object.class, parameters); 36 | } else { 37 | ewmStatsServerResponse = rest.exchange(path, method, requestEntity, Object.class); 38 | } 39 | } catch (HttpStatusCodeException e) { 40 | return ResponseEntity.status(e.getStatusCode()).body(e.getResponseBodyAsByteArray()); 41 | } 42 | return prepareGatewayResponse(ewmStatsServerResponse); 43 | } 44 | 45 | private HttpHeaders defaultHeaders() { 46 | HttpHeaders headers = new HttpHeaders(); 47 | headers.setContentType(MediaType.APPLICATION_JSON); 48 | headers.setAccept(List.of(MediaType.APPLICATION_JSON)); 49 | return headers; 50 | } 51 | 52 | private static ResponseEntity prepareGatewayResponse(ResponseEntity response) { 53 | if (response.getStatusCode().is2xxSuccessful()) { 54 | return response; 55 | } 56 | 57 | ResponseEntity.BodyBuilder responseBuilder = ResponseEntity.status(response.getStatusCode()); 58 | 59 | if (response.hasBody()) { 60 | return responseBuilder.body(response.getBody()); 61 | } 62 | 63 | return responseBuilder.build(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /explore-with-me-stats/stats-client/src/main/java/ru/practicum/explorewithme/StatsClient.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.boot.web.client.RestTemplateBuilder; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; 8 | import org.springframework.stereotype.Service; 9 | import org.springframework.web.util.DefaultUriBuilderFactory; 10 | import ru.practicum.explorewithme.dto.StatDto; 11 | 12 | import javax.servlet.http.HttpServletRequest; 13 | import java.time.LocalDateTime; 14 | import java.time.format.DateTimeFormatter; 15 | import java.util.List; 16 | import java.util.Map; 17 | 18 | @Service 19 | public class StatsClient extends BaseClient { 20 | private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); 21 | 22 | @Autowired 23 | public StatsClient(@Value("${explore-with-me-stats-service.url}") String serverUrl, RestTemplateBuilder builder) { 24 | super( 25 | builder 26 | .uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl)) 27 | .requestFactory(HttpComponentsClientHttpRequestFactory::new) 28 | .build() 29 | ); 30 | } 31 | 32 | public void sendHit(String appName, HttpServletRequest request) { 33 | StatDto statDto = StatDto.builder() 34 | .app(appName) 35 | .uri(request.getRequestURI()) 36 | .ip(request.getRemoteAddr()) 37 | .timestamp(LocalDateTime.now()) 38 | .build(); 39 | post("/hit", statDto); 40 | } 41 | 42 | public ResponseEntity getStats(LocalDateTime start, 43 | LocalDateTime end, 44 | List uris, 45 | Boolean unique) { 46 | 47 | String concatenatedUris = concatenateUris(uris); 48 | 49 | Map parameters = Map.of( 50 | "start", start.format(FORMATTER), 51 | "end", end.format(FORMATTER), 52 | "uris", concatenatedUris, 53 | "unique", unique 54 | ); 55 | 56 | return get("/stats?start={start}&end={end}&uris={uris}&unique={unique}", parameters); 57 | } 58 | 59 | private String concatenateUris(List uris) { 60 | StringBuilder sb = new StringBuilder(); 61 | for (int i = 0; i < uris.size() - 1; i++) { 62 | sb.append(uris.get(i)).append("&uris="); 63 | } 64 | sb.append(uris.get(uris.size() - 1)); 65 | return sb.toString(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /explore-with-me-stats/stats-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | ru.practicum 8 | explore-with-me-stats 9 | 0.0.1-SNAPSHOT 10 | ../pom.xml 11 | 12 | 13 | stats-service 14 | 15 | 16 | 11 17 | 11 18 | UTF-8 19 | 20 | 21 | 22 | 23 | ru.practicum 24 | stats-dto 25 | 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter-web 30 | 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-data-jpa 35 | 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-validation 40 | 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-starter-actuator 45 | 46 | 47 | 48 | org.apache.httpcomponents 49 | httpclient 50 | 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-configuration-processor 55 | true 56 | 57 | 58 | 59 | org.projectlombok 60 | lombok 61 | true 62 | 63 | 64 | 65 | org.springframework.boot 66 | spring-boot-starter-test 67 | test 68 | 69 | 70 | 71 | org.postgresql 72 | postgresql 73 | runtime 74 | 75 | 76 | 77 | com.h2database 78 | h2 79 | runtime 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | org.springframework.boot 88 | spring-boot-maven-plugin 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /explore-wth-me-main/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | ru.practicum 8 | explore-with-me 9 | 0.0.1-SNAPSHOT 10 | ../pom.xml 11 | 12 | 13 | explore-wth-me-main 14 | 0.0.1-SNAPSHOT 15 | Explore with me Main 16 | 17 | 18 | 11 19 | 11 20 | UTF-8 21 | 22 | 23 | 24 | 25 | ru.practicum 26 | stats-dto 27 | ${project.version} 28 | 29 | 30 | 31 | ru.practicum 32 | stats-client 33 | ${project.version} 34 | 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-web 39 | 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-starter 44 | 45 | 46 | 47 | org.springframework.boot 48 | spring-boot-starter-data-jpa 49 | 50 | 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-starter-validation 55 | 56 | 57 | 58 | org.springframework.boot 59 | spring-boot-starter-actuator 60 | 61 | 62 | 63 | org.apache.httpcomponents 64 | httpclient 65 | 66 | 67 | 68 | org.springframework.boot 69 | spring-boot-configuration-processor 70 | true 71 | 72 | 73 | 74 | org.projectlombok 75 | lombok 76 | true 77 | 78 | 79 | 80 | org.springframework.boot 81 | spring-boot-starter-test 82 | test 83 | 84 | 85 | 86 | org.postgresql 87 | postgresql 88 | runtime 89 | 90 | 91 | 92 | com.h2database 93 | h2 94 | runtime 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | org.springframework.boot 103 | spring-boot-maven-plugin 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/controller/EventsController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.controller; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.format.annotation.DateTimeFormat; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.validation.annotation.Validated; 9 | import org.springframework.web.bind.annotation.*; 10 | import org.springframework.web.server.ResponseStatusException; 11 | import ru.practicum.explorewithme.dto.event.EventFullDto; 12 | import ru.practicum.explorewithme.dto.event.EventShortDto; 13 | import ru.practicum.explorewithme.service.event.EventService; 14 | 15 | import javax.servlet.http.HttpServletRequest; 16 | import javax.validation.constraints.Min; 17 | import javax.validation.constraints.Positive; 18 | import javax.validation.constraints.PositiveOrZero; 19 | import java.time.LocalDateTime; 20 | import java.util.List; 21 | 22 | @Slf4j 23 | @Validated 24 | @RestController 25 | @RequiredArgsConstructor 26 | @RequestMapping(path = "/events") 27 | public class EventsController { 28 | 29 | private final EventService eventService; 30 | 31 | @GetMapping 32 | public List getEvents( 33 | @RequestParam(required = false) String text, 34 | @RequestParam(required = false) List categories, 35 | @RequestParam(required = false) Boolean paid, 36 | @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime rangeStart, 37 | @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime rangeEnd, 38 | @RequestParam(required = false, defaultValue = "false") Boolean onlyAvailable, 39 | @RequestParam(required = false) String sort, 40 | @RequestParam(required = false, defaultValue = "0") @Min(0) int from, 41 | @RequestParam(required = false, defaultValue = "10") @Min(0) int size, 42 | @Value("${app-name}") String app, 43 | HttpServletRequest request 44 | ) { 45 | if (rangeStart != null && rangeEnd != null && rangeStart.isAfter(rangeEnd)) { 46 | throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Start must be before end"); 47 | } 48 | log.info("GET | Get Events Public"); 49 | List response = eventService.getEvents( 50 | text, categories, paid, rangeStart, rangeEnd, onlyAvailable, sort, from, size, app, request 51 | ); 52 | log.info("Get Events Public: {}", response); 53 | return response; 54 | } 55 | 56 | @GetMapping(path = "/{id}") 57 | public EventFullDto getEvent( 58 | @PathVariable(name = "id") long eventId, 59 | @Value("${app-name}") String app, 60 | HttpServletRequest request 61 | ) { 62 | log.info("GET | Event with id {}", eventId); 63 | EventFullDto response = eventService.getEvent(eventId, app, request); 64 | log.info("{}", response); 65 | return response; 66 | } 67 | 68 | @GetMapping(path = "/locations") 69 | public List getEventsInLocation( 70 | @RequestParam(required = false) @Positive Long locationId, 71 | @RequestParam(required = false) Double lat, 72 | @RequestParam(required = false) Double lon, 73 | @RequestParam(defaultValue = "0.0") double rad, 74 | @RequestParam(defaultValue = "0") @PositiveOrZero int from, 75 | @RequestParam(defaultValue = "10") @Positive int size 76 | ) { 77 | log.info("GET | Events in location"); 78 | List response = eventService.getEventsInLocation( 79 | locationId, lat, lon, rad, from, size); 80 | log.info("{}", response); 81 | return response; 82 | } 83 | 84 | } 85 | 86 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/dto/event/mapper/EventMapper.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.event.mapper; 2 | 3 | import lombok.experimental.UtilityClass; 4 | import ru.practicum.explorewithme.dto.category.mapper.CategoryMapper; 5 | import ru.practicum.explorewithme.dto.event.EventFullDto; 6 | import ru.practicum.explorewithme.dto.event.EventShortDto; 7 | import ru.practicum.explorewithme.dto.event.NewEventDto; 8 | import ru.practicum.explorewithme.dto.location.CoordinatesResponseDto; 9 | import ru.practicum.explorewithme.dto.location.mapper.LocationMapper; 10 | import ru.practicum.explorewithme.dto.user.mapper.UserMapper; 11 | import ru.practicum.explorewithme.model.Category; 12 | import ru.practicum.explorewithme.model.Event; 13 | 14 | import java.util.List; 15 | import java.util.stream.Collectors; 16 | 17 | @UtilityClass 18 | public class EventMapper { 19 | 20 | public Event mapFromNewEventDto(NewEventDto dto) { 21 | return Event.builder() 22 | .annotation(dto.getAnnotation()) 23 | .category(Category.builder().id(dto.getCategory()).build()) 24 | .description(dto.getDescription()) 25 | .eventDate(dto.getEventDate()) 26 | .location(LocationMapper.mapFromLocationShortDto(dto.getLocation())) 27 | .paid(dto.getPaid() != null && dto.getPaid()) 28 | .participantLimit(dto.getParticipantLimit() == null ? 0 : dto.getParticipantLimit()) 29 | .requestModeration(dto.getRequestModeration() == null || dto.getRequestModeration()) 30 | .title(dto.getTitle()) 31 | .build(); 32 | } 33 | 34 | public EventFullDto mapEventFullDtoFromEntity(Event event) { 35 | CoordinatesResponseDto location = 36 | CoordinatesResponseDto.builder() 37 | .id(event.getLocation().getId()) 38 | .lon(event.getLocation().getLon()) 39 | .lat(event.getLocation().getLat()) 40 | .build(); 41 | 42 | return EventFullDto.builder() 43 | .annotation(event.getAnnotation()) 44 | .category(CategoryMapper.mapDtoFromEntity(event.getCategory())) 45 | .createdOn(event.getCreatedOn()) 46 | .description(event.getDescription()) 47 | .eventDate(event.getEventDate()) 48 | .id(event.getId()) 49 | .initiator(UserMapper.mapToUserShortDto(event.getInitiator())) 50 | .location(location) 51 | .paid(event.getPaid()) 52 | .participantLimit(event.getParticipantLimit()) 53 | .publishedOn(event.getPublishedOn()) 54 | .requestModeration(event.getRequestModeration()) 55 | .state(String.valueOf(event.getState())) 56 | .title(event.getTitle()) 57 | .build(); 58 | } 59 | 60 | public EventShortDto mapToEventShortDto(Event event) { 61 | return EventShortDto.builder() 62 | .annotation(event.getAnnotation()) 63 | .category(CategoryMapper.mapDtoFromEntity(event.getCategory())) 64 | .eventDate(event.getEventDate()) 65 | .initiator(UserMapper.mapToUserShortDto(event.getInitiator())) 66 | .paid(event.getPaid()) 67 | .title(event.getTitle()) 68 | .id(event.getId()) 69 | .build(); 70 | } 71 | 72 | public List mapToEventShortDto(List events) { 73 | return events.stream() 74 | .map(EventMapper::mapToEventShortDto) 75 | .collect(Collectors.toList()); 76 | } 77 | 78 | public List mapToEventFullDto(List events) { 79 | return events.stream() 80 | .map(EventMapper::mapEventFullDtoFromEntity) 81 | .collect(Collectors.toList()); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/service/category/CategoryServiceImpl.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.category; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.data.domain.Page; 5 | import org.springframework.data.domain.PageRequest; 6 | import org.springframework.data.domain.Pageable; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.transaction.annotation.Transactional; 9 | import ru.practicum.explorewithme.dto.category.CategoryDto; 10 | import ru.practicum.explorewithme.dto.category.NewCategoryDto; 11 | import ru.practicum.explorewithme.dto.category.mapper.CategoryMapper; 12 | import ru.practicum.explorewithme.model.Category; 13 | import ru.practicum.explorewithme.repository.CategoryRepository; 14 | import ru.practicum.explorewithme.repository.EventRepository; 15 | import ru.practicum.explorewithme.util.exception.ClientErrorException; 16 | import ru.practicum.explorewithme.util.exception.EntityNotFoundException; 17 | 18 | import java.util.List; 19 | 20 | @Service 21 | @RequiredArgsConstructor 22 | @Transactional(readOnly = true) 23 | public class CategoryServiceImpl implements CategoryService { 24 | 25 | private final CategoryRepository categoryRepository; 26 | private final EventRepository eventRepository; 27 | 28 | @Override 29 | @Transactional 30 | public CategoryDto addNewCategory(NewCategoryDto dto) { 31 | Category request = CategoryMapper.mapEntityFromDto(dto); 32 | checkIfCategoryNameExists(dto.getName()); 33 | Category response = categoryRepository.save(request); 34 | return CategoryMapper.mapDtoFromEntity(response); 35 | } 36 | 37 | @Override 38 | @Transactional 39 | public void deleteCategory(long catId) { 40 | if (!categoryRepository.existsById(catId)) { 41 | throw new EntityNotFoundException("Category with id " + catId + " not found."); 42 | } else { 43 | if (!eventRepository.findAllByCategoryId(catId).isEmpty()) { 44 | throw new ClientErrorException("For the requested operation the conditions are not met."); 45 | } else { 46 | categoryRepository.deleteById(catId); 47 | } 48 | } 49 | } 50 | 51 | @Override 52 | @Transactional 53 | public CategoryDto updateCategory(long catId, CategoryDto updateCategoryRequest) { 54 | Category existingCategory = getCategoryOrThrowException(catId); 55 | if (!isUpdatedCategoryTheSame(existingCategory, updateCategoryRequest)) { 56 | checkIfCategoryNameExists(updateCategoryRequest.getName()); 57 | } 58 | existingCategory.setName(updateCategoryRequest.getName()); 59 | categoryRepository.save(existingCategory); 60 | return CategoryMapper.mapDtoFromEntity(existingCategory); 61 | } 62 | 63 | @Override 64 | @Transactional(readOnly = true) 65 | public List getCategories(int from, int size) { 66 | Pageable page = PageRequest.of(from / size, size); 67 | Page response = categoryRepository.findAll(page); 68 | return CategoryMapper.mapDtoFromEntity(response); 69 | } 70 | 71 | @Override 72 | @Transactional(readOnly = true) 73 | public CategoryDto getCategory(long catId) { 74 | Category category = getCategoryOrThrowException(catId); 75 | return CategoryMapper.mapDtoFromEntity(category); 76 | } 77 | 78 | private boolean isUpdatedCategoryTheSame(Category existingCategory, CategoryDto updateCategoryRequest) { 79 | return existingCategory.getName().equals(updateCategoryRequest.getName()); 80 | } 81 | 82 | private void checkIfCategoryNameExists(String name) { 83 | if (categoryRepository.existsCategoriesByName(name)) { 84 | throw new ClientErrorException("Category with name " + name + " already exists"); 85 | } 86 | } 87 | 88 | private Category getCategoryOrThrowException(long catId) { 89 | return categoryRepository.findById(catId).orElseThrow( 90 | () -> new EntityNotFoundException("Category with id: " + catId + " was not found") 91 | ); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/resources/schema.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS users CASCADE; 2 | DROP TABLE IF EXISTS categories CASCADE; 3 | DROP TABLE IF EXISTS locations CASCADE; 4 | DROP TABLE IF EXISTS events CASCADE; 5 | DROP TABLE IF EXISTS requests CASCADE; 6 | DROP TABLE IF EXISTS compilations CASCADE; 7 | DROP TABLE IF EXISTS events_compilations CASCADE; 8 | DROP TABLE IF EXISTS events_locations CASCADE; 9 | 10 | CREATE TABLE users ( 11 | id BIGSERIAL PRIMARY KEY, 12 | name VARCHAR(250) NOT NULL, 13 | email VARCHAR(254) NOT NULL 14 | ); 15 | 16 | CREATE TABLE categories ( 17 | id BIGSERIAL PRIMARY KEY, 18 | name VARCHAR(100) NOT NULL 19 | ); 20 | 21 | CREATE TABLE locations ( 22 | id BIGSERIAL PRIMARY KEY, 23 | lat DOUBLE PRECISION NOT NULL, 24 | lon DOUBLE PRECISION NOT NULL, 25 | rad DOUBLE PRECISION, 26 | name VARCHAR(100), 27 | status VARCHAR(100) 28 | ); 29 | 30 | CREATE TABLE events ( 31 | id BIGSERIAL PRIMARY KEY, 32 | annotation VARCHAR(2000) NOT NULL, 33 | category_id BIGINT NOT NULL, 34 | created_on TIMESTAMP WITH TIME ZONE NOT NULL, 35 | description VARCHAR(7000) NOT NULL, 36 | event_date TIMESTAMP WITH TIME ZONE NOT NULL, 37 | initiator_id BIGINT NOT NULL, 38 | participant_limit BIGINT, 39 | location_id BIGINT NOT NULL, 40 | published_on TIMESTAMP WITH TIME ZONE, 41 | state VARCHAR(100), 42 | title VARCHAR(120) NOT NULL, 43 | request_moderation BOOLEAN, 44 | paid BOOLEAN, 45 | CONSTRAINT fk_events_to_users FOREIGN KEY (initiator_id) REFERENCES users (id) ON DELETE CASCADE, 46 | CONSTRAINT fk_events_to_categories FOREIGN KEY (category_id) REFERENCES categories (id), 47 | CONSTRAINT fk_events_to_locations FOREIGN KEY (location_id) REFERENCES locations (id) 48 | ); 49 | 50 | CREATE TABLE requests ( 51 | id BIGSERIAL PRIMARY KEY, 52 | created TIMESTAMP WITH TIME ZONE NOT NULL, 53 | event_id BIGINT NOT NULL, 54 | requester_id BIGINT NOT NULL, 55 | status VARCHAR(100) NOT NULL, 56 | CONSTRAINT fk_requests_to_users FOREIGN KEY (requester_id) REFERENCES users (id) ON DELETE CASCADE, 57 | CONSTRAINT fk_requests_to_events FOREIGN KEY (event_id) REFERENCES events (id) ON DELETE CASCADE 58 | ); 59 | 60 | CREATE TABLE compilations ( 61 | id BIGSERIAL PRIMARY KEY, 62 | title VARCHAR(225) NOT NULL, 63 | pinned BOOLEAN NOT NULL 64 | ); 65 | 66 | CREATE TABLE events_compilations ( 67 | event_id BIGINT NOT NULL, 68 | compilation_id BIGINT NOT NULL, 69 | CONSTRAINT fk_events_compilations_to_events FOREIGN KEY (event_id) REFERENCES events (id) ON DELETE CASCADE, 70 | CONSTRAINT fk_events_compilations_to_compilations FOREIGN KEY (compilation_id) REFERENCES compilations (id) ON DELETE CASCADE 71 | ); 72 | 73 | CREATE TABLE events_locations ( 74 | event_id BIGINT NOT NULL, 75 | location_id BIGINT NOT NULL, 76 | CONSTRAINT fk_events_locations_to_events FOREIGN KEY (event_id) REFERENCES events (id) ON DELETE CASCADE, 77 | CONSTRAINT fk_events_locations_to_locations FOREIGN KEY (location_id) REFERENCES locations (id) ON DELETE CASCADE 78 | ); 79 | 80 | CREATE OR REPLACE FUNCTION distance(lat1 float, lon1 float, lat2 float, lon2 float) 81 | RETURNS float 82 | AS 83 | ' 84 | declare 85 | dist float = 0; 86 | rad_lat1 float; 87 | rad_lat2 float; 88 | theta float; 89 | rad_theta float; 90 | BEGIN 91 | IF lat1 = lat2 AND lon1 = lon2 92 | THEN 93 | RETURN dist; 94 | ELSE 95 | -- переводим градусы широты в радианы 96 | rad_lat1 = pi() * lat1 / 180; 97 | -- переводим градусы долготы в радианы 98 | rad_lat2 = pi() * lat2 / 180; 99 | -- находим разность долгот 100 | theta = lon1 - lon2; 101 | -- переводим градусы в радианы 102 | rad_theta = pi() * theta / 180; 103 | -- находим длину ортодромии 104 | dist = sin(rad_lat1) * sin(rad_lat2) + cos(rad_lat1) * cos(rad_lat2) * cos(rad_theta); 105 | 106 | IF dist > 1 107 | THEN dist = 1; 108 | END IF; 109 | 110 | dist = acos(dist); 111 | -- переводим радианы в градусы 112 | dist = dist * 180 / pi(); 113 | -- переводим градусы в километры 114 | dist = dist * 60 * 1.8524; 115 | 116 | RETURN dist; 117 | END IF; 118 | END; 119 | ' 120 | LANGUAGE PLPGSQL; -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/service/event/UpdateEventOperations.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.event; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import ru.practicum.explorewithme.dto.event.UpdateEventUserRequest; 5 | import ru.practicum.explorewithme.dto.location.NewCoordinatesDto; 6 | import ru.practicum.explorewithme.model.Category; 7 | import ru.practicum.explorewithme.model.Event; 8 | import ru.practicum.explorewithme.model.Location; 9 | import ru.practicum.explorewithme.model.enums.EventState; 10 | import ru.practicum.explorewithme.repository.CategoryRepository; 11 | import ru.practicum.explorewithme.repository.LocationRepository; 12 | import ru.practicum.explorewithme.util.exception.ClientErrorException; 13 | 14 | import java.time.LocalDateTime; 15 | import java.util.Optional; 16 | 17 | public class UpdateEventOperations { 18 | 19 | @Autowired 20 | protected CategoryRepository categoryRepository; 21 | @Autowired 22 | protected LocationRepository locationRepository; 23 | 24 | protected void updateEvent(Event existingEvent, UpdateEventUserRequest updateEventRequest) { 25 | if (existingEvent != null) { 26 | if (updateEventRequest.getAnnotation() != null) { 27 | existingEvent.setAnnotation(updateEventRequest.getAnnotation()); 28 | } 29 | if (updateEventRequest.getCategory() != null) { 30 | Optional category = categoryRepository.findById(updateEventRequest.getCategory()); 31 | category.ifPresent(existingEvent::setCategory); 32 | } 33 | if (updateEventRequest.getDescription() != null) { 34 | existingEvent.setDescription(updateEventRequest.getDescription()); 35 | } 36 | if (updateEventRequest.getEventDate() != null) { 37 | existingEvent.setEventDate(updateEventRequest.getEventDate()); 38 | } 39 | if (updateEventRequest.getLocation() != null) { 40 | existingEvent.setLocation(updateLocation(existingEvent, updateEventRequest.getLocation())); 41 | } 42 | if (updateEventRequest.getPaid() != null) { 43 | existingEvent.setPaid(updateEventRequest.getPaid()); 44 | } 45 | if (updateEventRequest.getParticipantLimit() != null) { 46 | existingEvent.setParticipantLimit(updateEventRequest.getParticipantLimit()); 47 | } 48 | if (updateEventRequest.getRequestModeration() != null) { 49 | existingEvent.setRequestModeration(updateEventRequest.getRequestModeration()); 50 | } 51 | if (updateEventRequest.getStateAction() != null) { 52 | updateEventState(existingEvent, updateEventRequest); 53 | } 54 | if (updateEventRequest.getTitle() != null) { 55 | existingEvent.setTitle(updateEventRequest.getTitle()); 56 | } 57 | } 58 | } 59 | 60 | protected void updateEventState(Event existingEvent, UpdateEventUserRequest updateEventRequest) { 61 | if (updateEventRequest.getStateAction().equals("SEND_TO_REVIEW")) { 62 | existingEvent.setState(EventState.PENDING); 63 | } else if (updateEventRequest.getStateAction().equals("PUBLISH_EVENT")) { 64 | if (existingEvent.getState().equals(EventState.PUBLISHED) 65 | || existingEvent.getState().equals(EventState.CANCELED)) { 66 | throw new ClientErrorException("For the requested operation the conditions are not met."); 67 | } else { 68 | existingEvent.setState(EventState.PUBLISHED); 69 | existingEvent.setPublishedOn(LocalDateTime.now()); 70 | } 71 | } else { 72 | if (existingEvent.getState().equals(EventState.PUBLISHED)) { 73 | throw new ClientErrorException("For the requested operation the conditions are not met."); 74 | } else { 75 | existingEvent.setState(EventState.CANCELED); 76 | } 77 | } 78 | } 79 | 80 | private Location updateLocation(Event existingEvent, NewCoordinatesDto locationDtoUser) { 81 | Double newLat = locationDtoUser.getLat(); 82 | Double newLon = locationDtoUser.getLon(); 83 | Location existingLocation = existingEvent.getLocation(); 84 | existingLocation.setLat(newLat); 85 | existingLocation.setLon(newLon); 86 | return locationRepository.save(existingLocation); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Event Listings (java-explore-with-me) 2 | This application empowers users to post events, register for participation, and browse event lists by location. 3 | The backend for this web application is developed using Java/SpringBoot and leverages Docker, PostgreSQL, and Hibernate. 4 | 5 | The application's microservices infrastructure is implemented using Docker and docker-compose.yml: 6 | - The primary service resides in the ```main``` module and handles the core business logic of the application 7 | - The statistics service, located in the ```stats``` module, collects data on user event views 8 | - Each service has its PostgreSQL database, configured in the ```application.properties``` files 9 | 10 | ___ 11 | ### Key Features of ```explore-with-me-main``` 12 | API is divided into three parts: 13 | - ```Public```: Accessible to any network user without registration. 14 | - ```Private```: Accessible only to authorized users. 15 | - ```Admin```: Intended for service administrators. 16 | 17 | **```Public``` API functionality** 18 | - Sorting event lists by view count or event dates 19 | - Viewing detailed information about specific events 20 | - Retrieving all available event categories and collections 21 | - **Requests for event lists or complete event information are logged in the statistics service** 22 | 23 | **```Private``` API functionality** 24 | - Adding/modifying/viewing events 25 | - Submitting applications for participation in events of interest 26 | - Event creators have the ability to confirm applications submitted by other service users 27 | 28 | **```Admin``` API functionality** 29 | - Adding/modifying/removing event categories. 30 | - Adding/removing/pinning event collections on the homepage. 31 | - Moderation capabilities for user-posted events, including publication or rejection. 32 | - Adding/activating/viewing/removing users. 33 | 34 | **Database Structure** 35 | - ```User```: Users are categorized as ```admin```, ```public```, or ```private```. 36 | - ```Event```: Events posted by registered ```private``` users. 37 | - ```Location```: Locations where events take place (see "Additional Functionality" for more details). 38 | - ```Category```: Event categories. 39 | - ```Compilation```: Event collections created by administrators. 40 | - ```ParticipationRequest```: Participation requests for events, if this option is enabled for the event. 41 | 42 | Screenshot 2023-09-07 at 19 27 13 43 | 44 | **Http-client** 45 | 46 | To send requests to record statistics from the ```main``` service to ```stats```, ```RestTemplate``` is used. 47 | The ```StatsClient``` class is implemented in a separate submodule called ```stats``` and is imported as a dependency in ```main```. 48 | ___ 49 | 50 | ### Stats Service ```explore-with-me-stats``` Functionality 51 | - Recording information about processed API endpoint requests 52 | - Providing statistics for selected dates and endpoints 53 | ___ 54 | 55 | #### Additional Functionality ```location_processing``` 56 | 1. Administrators can add new locations ```Location``` 57 | 2. Registered users, when posting events, specify their coordinates (```lat```, ```lon```): 58 | - Based on geolocation, the nearest locations where events occur are calculated (```locationList```) 59 | - Each location also has a list of events associated with it (```eventList```), creating a ```many-to-many``` relationship. 60 | 3. Regular users can view a list of ```APPROVED_BY_ADMIN``` locations and lists of ```PUBLISHED``` events in those locations: 61 | - The list of ```APPROVED_BY_ADMIN``` locations includes an event counter (```events```) 62 | - Lists of events that users can view must be approved by an administrator 63 | 64 | ![Untitled](https://github.com/mdemidkin1992/java-explore-with-me/assets/118021621/e41ad2ab-cd7a-4232-b1b1-67df8ae2c545) 65 | 66 | #### Newly Added Controllers 67 | #### Admin 68 | * ```POST /admin/locations``` 69 | * ```UPDATE /admin/locations/{id}``` 70 | * ```DELETE /admin/locations/{id}``` 71 | 72 | #### Public 73 | * ```GET /locations``` – Retrieve a list of all approved locations 74 | * ```GET /events/{locationId}/locations``` – Retrieve a list of published events in a location 75 | 76 | #### Updated Data Model 77 | 78 | Location, as added by an administrator ```Location```: ```name```, ```lat```, ```lon```, ```rad```. 79 | 80 | When viewing the list of locations, a ```LocationDtoWithEventsCount``` DTO is returned, including: 81 | * ```name``` Location name 82 | * ```events``` Number of events in the location 83 | 84 | Only locations approved by administrators with the status ```APPROVED_BY_ADMIN``` are displayed. 85 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/controller/users/UsersEventsController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.controller.users; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.validation.annotation.Validated; 7 | import org.springframework.web.bind.annotation.*; 8 | import ru.practicum.explorewithme.dto.event.EventFullDto; 9 | import ru.practicum.explorewithme.dto.event.EventShortDto; 10 | import ru.practicum.explorewithme.dto.event.NewEventDto; 11 | import ru.practicum.explorewithme.dto.event.UpdateEventUserRequest; 12 | import ru.practicum.explorewithme.dto.request.EventRequestStatusUpdateRequest; 13 | import ru.practicum.explorewithme.dto.request.EventRequestStatusUpdateResult; 14 | import ru.practicum.explorewithme.dto.request.ParticipationRequestDto; 15 | import ru.practicum.explorewithme.service.event.EventService; 16 | 17 | import javax.validation.Valid; 18 | import java.util.List; 19 | 20 | @Slf4j 21 | @Validated 22 | @RestController 23 | @RequiredArgsConstructor 24 | @RequestMapping(path = "/users/{userId}/events") 25 | public class UsersEventsController { 26 | 27 | private final EventService eventService; 28 | 29 | @PostMapping 30 | @ResponseStatus(HttpStatus.CREATED) 31 | public EventFullDto addNewEvent( 32 | @PathVariable long userId, 33 | @RequestBody @Valid NewEventDto newEventDto 34 | ) { 35 | log.info("POST request for new Event received: {}", newEventDto); 36 | EventFullDto response = eventService.addNewEvent(userId, newEventDto); 37 | log.info("New event added: {}", response); 38 | return response; 39 | } 40 | 41 | @GetMapping 42 | public List getEventsByUser( 43 | @PathVariable long userId, 44 | @RequestParam(name = "from", defaultValue = "0") Integer from, 45 | @RequestParam(name = "size", defaultValue = "10") Integer size 46 | ) { 47 | log.info("GET request for Events from user {}", userId); 48 | List response = eventService.getEventsByUser(userId, from, size); 49 | log.info("Events published by user: {}", response); 50 | return response; 51 | } 52 | 53 | @GetMapping(path = "/{eventId}") 54 | public EventFullDto getEventFullInfo( 55 | @PathVariable(name = "userId") long userId, 56 | @PathVariable(name = "eventId") long eventId 57 | ) { 58 | log.info("GET request for full Event {} info received from user {}", eventId, userId); 59 | EventFullDto response = eventService.getEventFullInfo(userId, eventId); 60 | log.info("{}", response); 61 | return response; 62 | } 63 | 64 | @PatchMapping(path = "/{eventId}") 65 | public EventFullDto updateEventUserRequest( 66 | @PathVariable(name = "userId") long userId, 67 | @PathVariable(name = "eventId") long eventId, 68 | @RequestBody @Valid UpdateEventUserRequest request 69 | ) { 70 | log.info("PATCH | UserId: {} | EventId: {} | Update Event: {}", userId, eventId, request); 71 | EventFullDto response = eventService.updateEventRequest(eventId, request); 72 | log.info("Updated Event: {}", response); 73 | return response; 74 | } 75 | 76 | @GetMapping(path = "/{eventId}/requests") 77 | public List getParticipationRequests( 78 | @PathVariable(name = "userId") long userId, 79 | @PathVariable(name = "eventId") long eventId 80 | ) { 81 | log.info("GET | UserId: {} | EventId: {} | Participation Requests", userId, eventId); 82 | List response = eventService.getParticipationRequests(userId, eventId); 83 | log.info("Participation Requests: {}", response); 84 | return response; 85 | } 86 | 87 | @PatchMapping(path = "/{eventId}/requests") 88 | public EventRequestStatusUpdateResult updateStatusesOfRequestsForEvent( 89 | @PathVariable(name = "userId") long userId, 90 | @PathVariable(name = "eventId") long eventId, 91 | @RequestBody EventRequestStatusUpdateRequest eventRequestStatusUpdateRequest 92 | ) { 93 | log.info("PATCH | Update status requests"); 94 | EventRequestStatusUpdateResult response 95 | = eventService.updateStatusesOfRequestsForEvent(userId, eventId, eventRequestStatusUpdateRequest); 96 | log.info("Update status requests: {}", response); 97 | return response; 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/service/compilation/CompilationServiceImpl.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.compilation; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.data.domain.PageRequest; 5 | import org.springframework.data.domain.Pageable; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.transaction.annotation.Transactional; 8 | import ru.practicum.explorewithme.dto.compilation.CompilationDto; 9 | import ru.practicum.explorewithme.dto.compilation.NewCompilationDto; 10 | import ru.practicum.explorewithme.dto.compilation.UpdateCompilationRequest; 11 | import ru.practicum.explorewithme.dto.compilation.mapper.CompilationMapper; 12 | import ru.practicum.explorewithme.model.Compilation; 13 | import ru.practicum.explorewithme.model.Event; 14 | import ru.practicum.explorewithme.repository.CompilationRepository; 15 | import ru.practicum.explorewithme.repository.EventRepository; 16 | import ru.practicum.explorewithme.util.exception.ClientErrorException; 17 | import ru.practicum.explorewithme.util.exception.EntityNotFoundException; 18 | 19 | import java.util.List; 20 | 21 | @Service 22 | @RequiredArgsConstructor 23 | @Transactional(readOnly = true) 24 | public class CompilationServiceImpl implements CompilationService { 25 | 26 | private final CompilationRepository compilationRepository; 27 | private final EventRepository eventRepository; 28 | 29 | @Override 30 | @Transactional 31 | public CompilationDto addNewCompilation(NewCompilationDto newCompilationDto) { 32 | Compilation request = CompilationMapper.mapFromNewCompilationDto(newCompilationDto); 33 | List eventsIds = newCompilationDto.getEvents(); 34 | 35 | if (eventsIds != null && !eventsIds.isEmpty()) { 36 | List eventList = eventRepository.findAllById(eventsIds); 37 | request.setEventList(eventList); 38 | } 39 | 40 | Compilation response = compilationRepository.save(request); 41 | return CompilationMapper.mapEntityToDto(response); 42 | } 43 | 44 | @Override 45 | @Transactional 46 | public void deleteCompilation(long compId) { 47 | getCompilationOrThrow(compId); 48 | compilationRepository.deleteById(compId); 49 | } 50 | 51 | @Override 52 | @Transactional 53 | public CompilationDto updateCompilation(long compId, UpdateCompilationRequest updateRequest) { 54 | Compilation existingCompilation = getCompilationOrThrow(compId); 55 | 56 | if (updateRequest.getTitle() != null) { 57 | if (compilationRepository.findCompilationByTitle(updateRequest.getTitle()).isPresent()) { 58 | throw new ClientErrorException("Compilation with name " + updateRequest.getTitle() + " already exists"); 59 | } 60 | existingCompilation.setTitle(updateRequest.getTitle()); 61 | } 62 | if (updateRequest.getPinned() != null) { 63 | existingCompilation.setPinned(updateRequest.getPinned()); 64 | } 65 | if (updateRequest.getEvents() != null) { 66 | List eventsIds = updateRequest.getEvents(); 67 | List eventList = eventRepository.findAllById(eventsIds); 68 | 69 | existingCompilation.getEventList().clear(); 70 | existingCompilation.getEventList().addAll(eventList); 71 | 72 | for (Event event : eventList) { 73 | event.getCompilationList().add(existingCompilation); 74 | } 75 | } 76 | 77 | compilationRepository.save(existingCompilation); 78 | 79 | return CompilationMapper.mapEntityToDto(existingCompilation); 80 | } 81 | 82 | @Override 83 | @Transactional(readOnly = true) 84 | public List getCompilations(Boolean pinned, int from, int size) { 85 | Pageable page = PageRequest.of(from / size, size); 86 | List response; 87 | 88 | if (pinned == null) { 89 | response = compilationRepository.findAll(page).toList(); 90 | } else { 91 | response = compilationRepository.findAllByPinned(pinned, page); 92 | } 93 | 94 | return CompilationMapper.mapEntityToDto(response); 95 | } 96 | 97 | @Override 98 | @Transactional(readOnly = true) 99 | public CompilationDto getCompilation(long compId) { 100 | Compilation response = getCompilationOrThrow(compId); 101 | return CompilationMapper.mapEntityToDto(response); 102 | } 103 | 104 | private Compilation getCompilationOrThrow(long compId) { 105 | return compilationRepository.findById(compId).orElseThrow( 106 | () -> new EntityNotFoundException("Compilation with id " + compId + " not found") 107 | ); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/service/location/LocationServiceImpl.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.location; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.data.domain.PageRequest; 5 | import org.springframework.data.domain.Pageable; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.transaction.annotation.Transactional; 8 | import ru.practicum.explorewithme.dto.location.LocationDto; 9 | import ru.practicum.explorewithme.dto.location.LocationUpdateRequest; 10 | import ru.practicum.explorewithme.dto.location.NewLocationDtoAdmin; 11 | import ru.practicum.explorewithme.dto.location.LocationDtoWithEvents; 12 | import ru.practicum.explorewithme.dto.location.mapper.LocationMapper; 13 | import ru.practicum.explorewithme.model.Event; 14 | import ru.practicum.explorewithme.model.Location; 15 | import ru.practicum.explorewithme.model.enums.EventState; 16 | import ru.practicum.explorewithme.model.enums.LocationStatus; 17 | import ru.practicum.explorewithme.repository.EventRepository; 18 | import ru.practicum.explorewithme.repository.LocationRepository; 19 | import ru.practicum.explorewithme.util.exception.ClientErrorException; 20 | import ru.practicum.explorewithme.util.exception.EntityNotFoundException; 21 | 22 | import java.util.ArrayList; 23 | import java.util.Comparator; 24 | import java.util.List; 25 | 26 | @Service 27 | @RequiredArgsConstructor 28 | @Transactional(readOnly = true) 29 | public class LocationServiceImpl implements LocationService { 30 | 31 | private final LocationRepository locationRepository; 32 | private final EventRepository eventRepository; 33 | 34 | @Override 35 | @Transactional 36 | public LocationDto addNewLocation(NewLocationDtoAdmin locationDto) { 37 | Location request = LocationMapper.mapFromLocationDto(locationDto); 38 | checkIfLocationExistsOrThrowException(locationDto); 39 | request.setStatus(LocationStatus.APPROVED_BY_ADMIN); 40 | Location response = locationRepository.save(request); 41 | return LocationMapper.mapFullDtoFromEntity(response); 42 | } 43 | 44 | @Override 45 | @Transactional(readOnly = true) 46 | public List getLocations(int from, int size) { 47 | Pageable page = PageRequest.of(from / size, size); 48 | 49 | List locations = locationRepository.findAllByNameNotNull(page); 50 | List response = new ArrayList<>(); 51 | 52 | for (Location location : locations) { 53 | String locationName = location.getName(); 54 | List eventList = location.getEventList(); 55 | 56 | long eventsCount = eventList.stream() 57 | .filter(e -> e.getState().equals(EventState.PUBLISHED)) 58 | .count(); 59 | 60 | response.add(new LocationDtoWithEvents(locationName, eventsCount)); 61 | } 62 | 63 | response.sort(Comparator.comparing(LocationDtoWithEvents::getEvents).reversed()); 64 | return response; 65 | } 66 | 67 | @Override 68 | @Transactional 69 | public LocationDto updateLocation(long id, LocationUpdateRequest request) { 70 | Location existingLocation = getLocationOrThrowException(id); 71 | if (String.valueOf(request.getStatus()).equals("APPROVED")) { 72 | existingLocation.setStatus(LocationStatus.APPROVED_BY_ADMIN); 73 | 74 | if (request.getName() != null) { 75 | existingLocation.setName(request.getName()); 76 | } 77 | if (request.getRad() != null) { 78 | existingLocation.setRad(request.getRad()); 79 | } 80 | 81 | } else { 82 | existingLocation.setStatus(LocationStatus.CANCELED_BY_ADMIN); 83 | } 84 | Location updatedLocation = locationRepository.save(existingLocation); 85 | return LocationMapper.mapFullDtoFromEntity(updatedLocation); 86 | } 87 | 88 | @Override 89 | @Transactional 90 | public void deleteLocation(long locationId) { 91 | getLocationOrThrowException(locationId); 92 | checkIfLocationTiedToEvents(locationId); 93 | locationRepository.deleteById(locationId); 94 | } 95 | 96 | private void checkIfLocationExistsOrThrowException(NewLocationDtoAdmin dto) { 97 | if (locationRepository.existsByNameAndLatAndLon( 98 | dto.getName(), dto.getLat(), dto.getLon() 99 | )) { 100 | throw new ClientErrorException("Location already exists"); 101 | } 102 | } 103 | 104 | private Location getLocationOrThrowException(long locationId) { 105 | return locationRepository.findById(locationId).orElseThrow( 106 | () -> new EntityNotFoundException("Location not found") 107 | ); 108 | } 109 | 110 | private void checkIfLocationTiedToEvents(long locationId) { 111 | if (!eventRepository.findAllByLocationId(locationId).isEmpty()) { 112 | throw new ClientErrorException("Conditions are not met"); 113 | } 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /ewm-stats-service-spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "openapi": "3.0.1", 3 | "info": { 4 | "title": "Stat service API", 5 | "version": "v0" 6 | }, 7 | "servers": [ 8 | { 9 | "url": "http://localhost:9090", 10 | "description": "Generated server url" 11 | } 12 | ], 13 | "tags": [ 14 | { 15 | "name": "StatsController", 16 | "description": "API для работы со статистикой посещений" 17 | } 18 | ], 19 | "paths": { 20 | "/hit": { 21 | "post": { 22 | "tags": [ 23 | "StatsController" 24 | ], 25 | "summary": "Сохранение информации о том, что к эндпоинту был запрос", 26 | "description": "Сохранение информации о том, что на uri конкретного сервиса был отправлен запрос пользователем. Название сервиса, uri и ip пользователя указаны в теле запроса.", 27 | "operationId": "hit", 28 | "requestBody": { 29 | "description": "данные запроса", 30 | "content": { 31 | "application/json": { 32 | "schema": { 33 | "$ref": "#/components/schemas/EndpointHit" 34 | } 35 | } 36 | }, 37 | "required": true 38 | }, 39 | "responses": { 40 | "201": { 41 | "description": "Информация сохранена" 42 | } 43 | } 44 | } 45 | }, 46 | "/stats": { 47 | "get": { 48 | "tags": [ 49 | "StatsController" 50 | ], 51 | "summary": "Получение статистики по посещениям. Обратите внимание: значение даты и времени нужно закодировать (например используя java.net.URLEncoder.encode) ", 52 | "operationId": "getStats", 53 | "parameters": [ 54 | { 55 | "name": "start", 56 | "in": "query", 57 | "description": "Дата и время начала диапазона за который нужно выгрузить статистику (в формате \"yyyy-MM-dd HH:mm:ss\")", 58 | "required": true, 59 | "schema": { 60 | "type": "string" 61 | } 62 | }, 63 | { 64 | "name": "end", 65 | "in": "query", 66 | "description": "Дата и время конца диапазона за который нужно выгрузить статистику (в формате \"yyyy-MM-dd HH:mm:ss\")", 67 | "required": true, 68 | "schema": { 69 | "type": "string" 70 | } 71 | }, 72 | { 73 | "name": "uris", 74 | "in": "query", 75 | "description": "Список uri для которых нужно выгрузить статистику", 76 | "required": false, 77 | "schema": { 78 | "type": "array", 79 | "items": { 80 | "type": "string" 81 | } 82 | } 83 | }, 84 | { 85 | "name": "unique", 86 | "in": "query", 87 | "description": "Нужно ли учитывать только уникальные посещения (только с уникальным ip)", 88 | "required": false, 89 | "schema": { 90 | "type": "boolean", 91 | "default": false 92 | } 93 | } 94 | ], 95 | "responses": { 96 | "200": { 97 | "description": "Статистика собрана", 98 | "content": { 99 | "application/json": { 100 | "schema": { 101 | "type": "array", 102 | "items": { 103 | "$ref": "#/components/schemas/ViewStats" 104 | } 105 | } 106 | } 107 | } 108 | } 109 | } 110 | } 111 | } 112 | }, 113 | "components": { 114 | "schemas": { 115 | "EndpointHit": { 116 | "type": "object", 117 | "properties": { 118 | "id": { 119 | "type": "integer", 120 | "description": "Идентификатор записи", 121 | "format": "int64", 122 | "readOnly": true, 123 | "example": 1 124 | }, 125 | "app": { 126 | "type": "string", 127 | "description": "Идентификатор сервиса для которого записывается информация", 128 | "example": "ewm-main-service" 129 | }, 130 | "uri": { 131 | "type": "string", 132 | "description": "URI для которого был осуществлен запрос", 133 | "example": "/events/1" 134 | }, 135 | "ip": { 136 | "type": "string", 137 | "description": "IP-адрес пользователя, осуществившего запрос", 138 | "example": "192.163.0.1" 139 | }, 140 | "timestamp": { 141 | "type": "string", 142 | "description": "Дата и время, когда был совершен запрос к эндпоинту (в формате \"yyyy-MM-dd HH:mm:ss\")", 143 | "example": "2022-09-06 11:00:23" 144 | } 145 | } 146 | }, 147 | "ViewStats": { 148 | "type": "object", 149 | "properties": { 150 | "app": { 151 | "type": "string", 152 | "description": "Название сервиса", 153 | "example": "ewm-main-service" 154 | }, 155 | "uri": { 156 | "type": "string", 157 | "description": "URI сервиса", 158 | "example": "/events/1" 159 | }, 160 | "hits": { 161 | "type": "integer", 162 | "description": "Количество просмотров", 163 | "format": "int64", 164 | "example": 6 165 | } 166 | } 167 | } 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | org.springframework.boot 8 | spring-boot-starter-parent 9 | 2.7.5 10 | 11 | 12 | 13 | Explore With Me 14 | 15 | explore-with-me-stats 16 | explore-wth-me-main 17 | 18 | 19 | ru.practicum 20 | explore-with-me 21 | 0.0.1-SNAPSHOT 22 | pom 23 | 24 | 25 | 11 26 | UTF-8 27 | 28 | 29 | 30 | 31 | 32 | 33 | org.apache.maven.plugins 34 | maven-surefire-plugin 35 | 36 | 37 | test 38 | 39 | 40 | 41 | 42 | org.apache.maven.plugins 43 | maven-checkstyle-plugin 44 | 3.1.2 45 | 46 | checkstyle.xml 47 | true 48 | true 49 | true 50 | 51 | 52 | 53 | 54 | check 55 | 56 | compile 57 | 58 | 59 | 60 | 61 | com.puppycrawl.tools 62 | checkstyle 63 | 10.3 64 | 65 | 66 | 67 | 68 | com.github.spotbugs 69 | spotbugs-maven-plugin 70 | 4.7.0.0 71 | 72 | Max 73 | High 74 | 75 | 76 | 77 | 78 | check 79 | 80 | 81 | 82 | 83 | 84 | org.jacoco 85 | jacoco-maven-plugin 86 | 0.8.8 87 | 88 | file 89 | 90 | 91 | 92 | jacoco-initialize 93 | 94 | prepare-agent 95 | 96 | 97 | 98 | jacoco-check 99 | 100 | check 101 | 102 | 103 | 104 | 105 | BUNDLE 106 | 107 | 108 | INSTRUCTION 109 | COVEREDRATIO 110 | 0.01 111 | 112 | 113 | LINE 114 | COVEREDRATIO 115 | 0.2 116 | 117 | 118 | BRANCH 119 | COVEREDRATIO 120 | 0.2 121 | 122 | 123 | COMPLEXITY 124 | COVEREDRATIO 125 | 0.2 126 | 127 | 128 | METHOD 129 | COVEREDRATIO 130 | 0.2 131 | 132 | 133 | CLASS 134 | MISSEDCOUNT 135 | 1 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | jacoco-site 144 | install 145 | 146 | report 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | check 157 | 158 | 159 | 160 | org.apache.maven.plugins 161 | maven-checkstyle-plugin 162 | 163 | 164 | com.github.spotbugs 165 | spotbugs-maven-plugin 166 | 167 | 168 | 169 | 170 | 171 | 172 | com.github.spotbugs 173 | spotbugs-maven-plugin 174 | 175 | 176 | 177 | 178 | 179 | coverage 180 | 181 | 182 | 183 | org.jacoco 184 | jacoco-maven-plugin 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/service/request/ParticipationRequestServiceImpl.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.request; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.stereotype.Service; 5 | import org.springframework.transaction.annotation.Transactional; 6 | import ru.practicum.explorewithme.dto.request.ParticipationRequestDto; 7 | import ru.practicum.explorewithme.dto.request.mapper.RequestMapper; 8 | import ru.practicum.explorewithme.model.Event; 9 | import ru.practicum.explorewithme.model.ParticipationRequest; 10 | import ru.practicum.explorewithme.model.User; 11 | import ru.practicum.explorewithme.model.enums.EventState; 12 | import ru.practicum.explorewithme.model.enums.RequestStatus; 13 | import ru.practicum.explorewithme.repository.EventRepository; 14 | import ru.practicum.explorewithme.repository.ParticipationRequestRepository; 15 | import ru.practicum.explorewithme.repository.UserRepository; 16 | import ru.practicum.explorewithme.util.exception.ClientErrorException; 17 | import ru.practicum.explorewithme.util.exception.EntityNotFoundException; 18 | 19 | import java.time.LocalDateTime; 20 | import java.util.List; 21 | import java.util.stream.Collectors; 22 | 23 | @Service 24 | @RequiredArgsConstructor 25 | @Transactional(readOnly = true) 26 | public class ParticipationRequestServiceImpl implements ParticipationRequestService { 27 | 28 | private final ParticipationRequestRepository repository; 29 | private final EventRepository eventRepository; 30 | private final UserRepository userRepository; 31 | 32 | @Override 33 | @Transactional(readOnly = true) 34 | public List getRequestsByUserId(long userId) { 35 | return repository.findAllByRequesterId(userId).stream() 36 | .map(RequestMapper::mapToDto) 37 | .collect(Collectors.toList()); 38 | } 39 | 40 | @Override 41 | @Transactional 42 | public ParticipationRequestDto addNewParticipationRequest(long userId, long eventId) { 43 | makeParticipationRequestChecks(userId, eventId); 44 | User requester = getUserByIdOrThrowException(userId); 45 | Event event = getEventByIdOrThrowException(eventId); 46 | 47 | ParticipationRequest request = buildParticipationRequest(requester, event); 48 | ParticipationRequest response = repository.save(request); 49 | return RequestMapper.mapToDto(response); 50 | } 51 | 52 | @Override 53 | @Transactional 54 | public ParticipationRequestDto cancelParticipationRequest(long userId, long requestId) { 55 | ParticipationRequest existingRequest = getParticipationRequestOrThrow(requestId); 56 | existingRequest.setStatus(RequestStatus.CANCELED); 57 | repository.save(existingRequest); 58 | return RequestMapper.mapToDto(existingRequest); 59 | } 60 | 61 | private Event getEventByIdOrThrowException(long eventId) { 62 | return eventRepository.findById(eventId).orElseThrow( 63 | () -> new EntityNotFoundException("Event with id: " + eventId + " was not found") 64 | ); 65 | } 66 | 67 | private User getUserByIdOrThrowException(long userId) { 68 | return userRepository.findById(userId).orElseThrow( 69 | () -> new EntityNotFoundException("User with id: " + userId + " was not found") 70 | ); 71 | } 72 | 73 | private ParticipationRequest getParticipationRequestOrThrow(long requestId) { 74 | return repository.findById(requestId).orElseThrow( 75 | () -> new EntityNotFoundException("Request with id not found") 76 | ); 77 | } 78 | 79 | private ParticipationRequest buildParticipationRequest(User requester, Event event) { 80 | RequestStatus status; 81 | 82 | if (!event.getRequestModeration() || event.getParticipantLimit() == 0) { 83 | status = RequestStatus.CONFIRMED; 84 | } else { 85 | status = RequestStatus.PENDING; 86 | } 87 | 88 | return ParticipationRequest.builder() 89 | .requester(requester) 90 | .event(event) 91 | .created(LocalDateTime.now()) 92 | .status(status) 93 | .build(); 94 | } 95 | 96 | private void makeParticipationRequestChecks(long userId, long eventId) { 97 | checkIfParticipationRequestExists(userId, eventId); 98 | checkIfRequesterIsTheEventInitiator(userId, eventId); 99 | checkIfEventIsPublished(eventId); 100 | checkIfParticipationLimitIsOK(eventId); 101 | } 102 | 103 | private void checkIfParticipationRequestExists(long userId, long eventId) { 104 | if (repository.existsByRequesterIdAndEventId(userId, eventId)) { 105 | throw new ClientErrorException("Participation request already exists"); 106 | } 107 | } 108 | 109 | private void checkIfRequesterIsTheEventInitiator(long userId, long eventId) { 110 | Event event = getEventByIdOrThrowException(eventId); 111 | if (event.getInitiator().getId().equals(userId)) { 112 | throw new ClientErrorException("Participant can't be event initiator"); 113 | } 114 | } 115 | 116 | private void checkIfEventIsPublished(long eventId) { 117 | Event event = getEventByIdOrThrowException(eventId); 118 | if (!event.getState().equals(EventState.PUBLISHED)) { 119 | throw new ClientErrorException("Event state must be published"); 120 | } 121 | } 122 | 123 | private void checkIfParticipationLimitIsOK(long eventId) { 124 | Event event = getEventByIdOrThrowException(eventId); 125 | int confirmedRequests = repository.findAllByEventIdAndStatus(eventId, RequestStatus.CONFIRMED).size(); 126 | int participationLimit = event.getParticipantLimit(); 127 | 128 | if (confirmedRequests >= participationLimit && participationLimit != 0) { 129 | throw new ClientErrorException("Participation limit is exceeded"); 130 | } 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /.github/workflows/wait-for-it.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Use this script to test if a given TCP host/port are available 3 | 4 | WAITFORIT_cmdname=${0##*/} 5 | 6 | echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi } 7 | 8 | usage() 9 | { 10 | cat << USAGE >&2 11 | Usage: 12 | $WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args] 13 | -h HOST | --host=HOST Host or IP under test 14 | -p PORT | --port=PORT TCP port under test 15 | Alternatively, you specify the host and port as host:port 16 | -s | --strict Only execute subcommand if the test succeeds 17 | -q | --quiet Don't output any status messages 18 | -t TIMEOUT | --timeout=TIMEOUT 19 | Timeout in seconds, zero for no timeout 20 | -- COMMAND ARGS Execute command with args after the test finishes 21 | USAGE 22 | exit 1 23 | } 24 | 25 | wait_for() 26 | { 27 | if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then 28 | echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" 29 | else 30 | echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout" 31 | fi 32 | WAITFORIT_start_ts=$(date +%s) 33 | while : 34 | do 35 | if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then 36 | nc -z $WAITFORIT_HOST $WAITFORIT_PORT 37 | WAITFORIT_result=$? 38 | else 39 | # (echo -n > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1 40 | (curl --fail --silent $WAITFORIT_HOST:$WAITFORIT_PORT/actuator/health | grep UP) >/dev/null 2>&1 41 | WAITFORIT_result=$? 42 | fi 43 | if [[ $WAITFORIT_result -eq 0 ]]; then 44 | WAITFORIT_end_ts=$(date +%s) 45 | echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds" 46 | break 47 | fi 48 | sleep 1 49 | done 50 | return $WAITFORIT_result 51 | } 52 | 53 | wait_for_wrapper() 54 | { 55 | # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 56 | if [[ $WAITFORIT_QUIET -eq 1 ]]; then 57 | timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & 58 | else 59 | timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & 60 | fi 61 | WAITFORIT_PID=$! 62 | trap "kill -INT -$WAITFORIT_PID" INT 63 | wait $WAITFORIT_PID 64 | WAITFORIT_RESULT=$? 65 | if [[ $WAITFORIT_RESULT -ne 0 ]]; then 66 | echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" 67 | fi 68 | return $WAITFORIT_RESULT 69 | } 70 | 71 | # process arguments 72 | while [[ $# -gt 0 ]] 73 | do 74 | case "$1" in 75 | *:* ) 76 | WAITFORIT_hostport=(${1//:/ }) 77 | WAITFORIT_HOST=${WAITFORIT_hostport[0]} 78 | WAITFORIT_PORT=${WAITFORIT_hostport[1]} 79 | shift 1 80 | ;; 81 | --child) 82 | WAITFORIT_CHILD=1 83 | shift 1 84 | ;; 85 | -q | --quiet) 86 | WAITFORIT_QUIET=1 87 | shift 1 88 | ;; 89 | -s | --strict) 90 | WAITFORIT_STRICT=1 91 | shift 1 92 | ;; 93 | -h) 94 | WAITFORIT_HOST="$2" 95 | if [[ $WAITFORIT_HOST == "" ]]; then break; fi 96 | shift 2 97 | ;; 98 | --host=*) 99 | WAITFORIT_HOST="${1#*=}" 100 | shift 1 101 | ;; 102 | -p) 103 | WAITFORIT_PORT="$2" 104 | if [[ $WAITFORIT_PORT == "" ]]; then break; fi 105 | shift 2 106 | ;; 107 | --port=*) 108 | WAITFORIT_PORT="${1#*=}" 109 | shift 1 110 | ;; 111 | -t) 112 | WAITFORIT_TIMEOUT="$2" 113 | if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi 114 | shift 2 115 | ;; 116 | --timeout=*) 117 | WAITFORIT_TIMEOUT="${1#*=}" 118 | shift 1 119 | ;; 120 | --) 121 | shift 122 | WAITFORIT_CLI=("$@") 123 | break 124 | ;; 125 | --help) 126 | usage 127 | ;; 128 | *) 129 | echoerr "Unknown argument: $1" 130 | usage 131 | ;; 132 | esac 133 | done 134 | 135 | if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then 136 | echoerr "Error: you need to provide a host and port to test." 137 | usage 138 | fi 139 | 140 | WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15} 141 | WAITFORIT_STRICT=${WAITFORIT_STRICT:-0} 142 | WAITFORIT_CHILD=${WAITFORIT_CHILD:-0} 143 | WAITFORIT_QUIET=${WAITFORIT_QUIET:-0} 144 | 145 | # Check to see if timeout is from busybox? 146 | WAITFORIT_TIMEOUT_PATH=$(type -p timeout) 147 | WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH) 148 | 149 | WAITFORIT_BUSYTIMEFLAG="" 150 | if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then 151 | WAITFORIT_ISBUSY=1 152 | # Check if busybox timeout uses -t flag 153 | # (recent Alpine versions don't support -t anymore) 154 | if timeout &>/dev/stdout | grep -q -e '-t '; then 155 | WAITFORIT_BUSYTIMEFLAG="-t" 156 | fi 157 | else 158 | WAITFORIT_ISBUSY=0 159 | fi 160 | 161 | if [[ $WAITFORIT_CHILD -gt 0 ]]; then 162 | wait_for 163 | WAITFORIT_RESULT=$? 164 | exit $WAITFORIT_RESULT 165 | else 166 | if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then 167 | wait_for_wrapper 168 | WAITFORIT_RESULT=$? 169 | else 170 | wait_for 171 | WAITFORIT_RESULT=$? 172 | fi 173 | fi 174 | 175 | if [[ $WAITFORIT_CLI != "" ]]; then 176 | if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then 177 | echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess" 178 | exit $WAITFORIT_RESULT 179 | fi 180 | exec "${WAITFORIT_CLI[@]}" 181 | else 182 | exit $WAITFORIT_RESULT 183 | fi -------------------------------------------------------------------------------- /checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | -------------------------------------------------------------------------------- /explore-wth-me-main/src/main/java/ru/practicum/explorewithme/service/event/EventServiceImpl.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.event; 2 | 3 | import lombok.NonNull; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.data.domain.PageRequest; 6 | import org.springframework.data.domain.Pageable; 7 | import org.springframework.data.domain.Sort; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.stereotype.Service; 10 | import org.springframework.transaction.annotation.Transactional; 11 | import ru.practicum.explorewithme.dto.event.EventFullDto; 12 | import ru.practicum.explorewithme.dto.event.EventShortDto; 13 | import ru.practicum.explorewithme.dto.event.NewEventDto; 14 | import ru.practicum.explorewithme.dto.event.UpdateEventUserRequest; 15 | import ru.practicum.explorewithme.dto.event.mapper.EventMapper; 16 | import ru.practicum.explorewithme.dto.location.NewCoordinatesDto; 17 | import ru.practicum.explorewithme.dto.location.mapper.LocationMapper; 18 | import ru.practicum.explorewithme.dto.request.EventRequestStatusUpdateRequest; 19 | import ru.practicum.explorewithme.dto.request.EventRequestStatusUpdateResult; 20 | import ru.practicum.explorewithme.dto.request.ParticipationRequestDto; 21 | import ru.practicum.explorewithme.dto.request.mapper.RequestMapper; 22 | import ru.practicum.explorewithme.model.*; 23 | import ru.practicum.explorewithme.model.enums.EventState; 24 | import ru.practicum.explorewithme.model.enums.LocationStatus; 25 | import ru.practicum.explorewithme.model.enums.RequestStatus; 26 | import ru.practicum.explorewithme.repository.*; 27 | import ru.practicum.explorewithme.util.exception.ClientErrorException; 28 | import ru.practicum.explorewithme.util.exception.EntityNotFoundException; 29 | 30 | import javax.servlet.http.HttpServletRequest; 31 | import javax.validation.constraints.NotNull; 32 | import java.time.LocalDateTime; 33 | import java.util.*; 34 | 35 | @Service 36 | @RequiredArgsConstructor 37 | @Transactional(readOnly = true) 38 | public class EventServiceImpl extends UpdateEventOperations implements EventService { 39 | 40 | private final UserRepository userRepository; 41 | private final EventRepository eventRepository; 42 | private final ParticipationRequestRepository requestRepository; 43 | private final StatsServiceClient statsServiceClient; 44 | 45 | @Override 46 | @Transactional(readOnly = true) 47 | public List getEventsByUser(long userId, int from, int size) { 48 | Pageable page = PageRequest.of(from / size, size, Sort.by("eventDate").descending()); 49 | List eventsByUserId = eventRepository.findAllByInitiatorId(userId, page); 50 | List response = EventMapper.mapToEventShortDto(eventsByUserId); 51 | 52 | for (EventShortDto dto : response) { 53 | int confirmedRequests = getConfirmedRequests(dto.getId()); 54 | dto.setConfirmedRequests(confirmedRequests); 55 | } 56 | 57 | return response; 58 | } 59 | 60 | @Override 61 | @Transactional 62 | public EventFullDto addNewEvent(long userId, NewEventDto newEventDto) { 63 | Event request = EventMapper.mapFromNewEventDto(newEventDto); 64 | 65 | Category category = getCategoryByIdOrThrowException(newEventDto.getCategory()); 66 | request.setCategory(category); 67 | 68 | User initiator = getUserByIdOrThrowException(userId); 69 | request.setInitiator(initiator); 70 | request.setCreatedOn(LocalDateTime.now()); 71 | request.setState(EventState.PENDING); 72 | 73 | Location location = getExistingLocationOrCreateNewOne(newEventDto); 74 | request.setLocation(location); 75 | 76 | Set locationList = getClosestLocations(newEventDto); 77 | locationList.add(location); 78 | request.setLocationList(new ArrayList<>(locationList)); 79 | 80 | Event response = eventRepository.save(request); 81 | return EventMapper.mapEventFullDtoFromEntity(response); 82 | } 83 | 84 | @Override 85 | @Transactional(readOnly = true) 86 | public EventFullDto getEventFullInfo(long userId, long eventId) { 87 | Event response = getEventByIdOrThrowException(eventId); 88 | return EventMapper.mapEventFullDtoFromEntity(response); 89 | } 90 | 91 | @Override 92 | @Transactional 93 | public EventFullDto updateEventRequest(long eventId, UpdateEventUserRequest updateEventUserRequest) { 94 | Event existingEvent = getEventByIdOrThrowException(eventId); 95 | checkIfEventIsAlreadyPublished(existingEvent); 96 | updateEvent(existingEvent, updateEventUserRequest); 97 | eventRepository.save(existingEvent); 98 | return EventMapper.mapEventFullDtoFromEntity(existingEvent); 99 | } 100 | 101 | @Override 102 | @Transactional(readOnly = true) 103 | public List getParticipationRequests(long userId, long eventId) { 104 | List response 105 | = requestRepository.findAllByEventInitiatorIdAndEventId(userId, eventId); 106 | return RequestMapper.mapToDto(response); 107 | } 108 | 109 | @Override 110 | @Transactional(readOnly = true) 111 | public List getEventsByAdmin(List users, 112 | List states, 113 | List categories, 114 | LocalDateTime rangeStart, 115 | LocalDateTime rangeEnd, 116 | int from, 117 | int size 118 | ) { 119 | Pageable page = PageRequest.of(from / size, size, Sort.by("eventDate").descending()); 120 | List eventList; 121 | 122 | if (users == null && states == null && categories == null && rangeStart == null && rangeEnd == null) { 123 | eventList = eventRepository.findAll(page).toList(); 124 | } else if (users != null && states == null && categories != null && rangeStart == null && rangeEnd == null) { 125 | eventList = eventRepository.findByInitiator_IdInAndCategory_IdIn(users, categories, page); 126 | } else { 127 | eventList = eventRepository.findByInitiator_IdInAndStateInAndCategory_IdInAndEventDateGreaterThanEqualAndEventDateLessThanEqual( 128 | users, states, categories, rangeStart, rangeEnd, page); 129 | } 130 | 131 | List response = EventMapper.mapToEventFullDto(eventList); 132 | 133 | for (EventFullDto dto : response) { 134 | int confirmedRequests = getConfirmedRequests(dto.getId()); 135 | dto.setConfirmedRequests(confirmedRequests); 136 | } 137 | 138 | return response; 139 | } 140 | 141 | @Override 142 | @Transactional 143 | public EventRequestStatusUpdateResult updateStatusesOfRequestsForEvent( 144 | long userId, 145 | long eventId, 146 | EventRequestStatusUpdateRequest eventRequestStatusUpdateRequest 147 | ) { 148 | List requestIds = eventRequestStatusUpdateRequest.getRequestIds(); 149 | RequestStatus status = eventRequestStatusUpdateRequest.getStatus(); 150 | Event event = getEventByIdOrThrowException(eventId); 151 | 152 | int participationLimit = event.getParticipantLimit(); 153 | boolean requestModeration = event.getRequestModeration(); 154 | int confirmedParticipants = getConfirmedRequests(eventId); 155 | 156 | List confirmedRequests = new ArrayList<>(); 157 | List rejectedRequests = new ArrayList<>(); 158 | 159 | if (participationLimit == 0 || !requestModeration) { 160 | updateRequestsStatusWhenModerationNotRequired(requestIds); 161 | } else { 162 | updateRequestsStatusWhenModerationRequired( 163 | requestIds, 164 | confirmedParticipants, 165 | participationLimit, 166 | confirmedRequests, 167 | rejectedRequests, 168 | status 169 | ); 170 | } 171 | 172 | return EventRequestStatusUpdateResult.builder() 173 | .confirmedRequests(confirmedRequests) 174 | .rejectedRequests(rejectedRequests) 175 | .build(); 176 | } 177 | 178 | private void updateRequestsStatusWhenModerationNotRequired(List requestIds) { 179 | for (Long requestId : requestIds) { 180 | ParticipationRequest existingRequest = getRequestByIdOrThrowException(requestId); 181 | existingRequest.setStatus(RequestStatus.CONFIRMED); 182 | requestRepository.save(existingRequest); 183 | } 184 | } 185 | 186 | private void updateRequestsStatusWhenModerationRequired( 187 | List requestIds, 188 | int confirmedParticipants, 189 | int participationLimit, 190 | List confirmedRequests, 191 | List rejectedRequests, 192 | RequestStatus status 193 | ) { 194 | for (Long requestId : requestIds) { 195 | ParticipationRequest existingRequest = getRequestByIdOrThrowException(requestId); 196 | 197 | if (existingRequest.getStatus().equals(RequestStatus.CONFIRMED) 198 | && status.equals(RequestStatus.REJECTED)) { 199 | throw new ClientErrorException("Request can't be REJECTED, has already been CONFIRMED"); 200 | } 201 | 202 | if (confirmedParticipants < participationLimit) { 203 | setStatusIfLimitNotReached( 204 | existingRequest, 205 | confirmedRequests, 206 | rejectedRequests, 207 | status 208 | ); 209 | if (status.equals(RequestStatus.CONFIRMED)) { 210 | confirmedParticipants++; 211 | } 212 | } else { 213 | rejectIfLimitReached( 214 | existingRequest, 215 | rejectedRequests, 216 | status 217 | ); 218 | } 219 | } 220 | } 221 | 222 | private void setStatusIfLimitNotReached( 223 | ParticipationRequest existingRequest, 224 | List confirmedRequests, 225 | List rejectedRequests, 226 | RequestStatus status 227 | ) { 228 | if (existingRequest.getStatus().equals(RequestStatus.PENDING)) { 229 | existingRequest.setStatus(status); 230 | requestRepository.save(existingRequest); 231 | if (Objects.requireNonNull(status) == RequestStatus.CONFIRMED) { 232 | confirmedRequests.add(RequestMapper.mapToDto(existingRequest)); 233 | } else { 234 | rejectedRequests.add(RequestMapper.mapToDto(existingRequest)); 235 | } 236 | } else { 237 | throw new ClientErrorException("RequestStatus is not PENDING"); 238 | } 239 | } 240 | 241 | private void rejectIfLimitReached( 242 | ParticipationRequest existingRequest, 243 | List rejectedRequests, 244 | RequestStatus status 245 | ) { 246 | if (status.equals(RequestStatus.CONFIRMED)) { 247 | throw new ClientErrorException("Limit has been reached"); 248 | } else { 249 | existingRequest.setStatus(RequestStatus.REJECTED); 250 | requestRepository.save(existingRequest); 251 | rejectedRequests.add(RequestMapper.mapToDto(existingRequest)); 252 | } 253 | } 254 | 255 | @Override 256 | @Transactional(readOnly = true) 257 | public List getEvents(String text, 258 | List categories, 259 | Boolean paid, 260 | LocalDateTime rangeStart, 261 | LocalDateTime rangeEnd, 262 | Boolean onlyAvailable, 263 | String sort, 264 | int from, 265 | int size, 266 | String app, 267 | HttpServletRequest request 268 | ) { 269 | statsServiceClient.sendHit(app, request); 270 | 271 | Sort sorting = Sort.by("eventDate").descending(); 272 | Pageable page = PageRequest.of(from / size, size, sorting); 273 | 274 | List eventList 275 | = eventRepository.findByAnnotationContainingIgnoreCaseOrDescriptionContainingIgnoreCaseAndCategory_IdInAndPaidAndEventDateGreaterThanEqualAndEventDateLessThanEqual( 276 | text != null ? text.toLowerCase() : null, 277 | text != null ? text.toLowerCase() : null, 278 | categories, 279 | paid, 280 | rangeStart, 281 | rangeEnd, 282 | page 283 | ); 284 | 285 | List response = EventMapper.mapToEventShortDto(eventList); 286 | 287 | for (EventShortDto eventShortDto : response) { 288 | long eventId = eventShortDto.getId(); 289 | eventShortDto.setConfirmedRequests(getConfirmedRequests(eventId)); 290 | eventShortDto.setViews(getViewsStats(eventId)); 291 | } 292 | 293 | if (sort != null && sort.equals("VIEWS")) { 294 | response.sort(Comparator.comparing(EventShortDto::getViews)); 295 | } else if (sort != null && sort.equals("EVENT_DATE")) { 296 | response.sort(Comparator.comparing(EventShortDto::getEventDate)); 297 | } 298 | 299 | 300 | return response; 301 | } 302 | 303 | @Override 304 | @Transactional(readOnly = true) 305 | public EventFullDto getEvent(long eventId, String app, HttpServletRequest request) { 306 | statsServiceClient.sendHit(app, request); 307 | Event event = getEventByIdOrThrowException(eventId); 308 | checkIfEventIsPublishedForPublic(event); 309 | EventFullDto response = EventMapper.mapEventFullDtoFromEntity(event); 310 | response.setConfirmedRequests(getConfirmedRequests(eventId)); 311 | response.setViews(getViewsStats(eventId)); 312 | return response; 313 | } 314 | 315 | @Override 316 | @Transactional(readOnly = true) 317 | public List getEventsInLocation( 318 | Long locationId, Double lat, Double lon, Double rad, int from, int size 319 | ) { 320 | Pageable page = PageRequest.of(from / size, size, Sort.by("eventDate").descending()); 321 | List eventList; 322 | 323 | if (locationId != null) { 324 | Location location = getExistingLocationOrThrowException(locationId); 325 | eventList = eventRepository.findEventsWithLocationRadius( 326 | location.getLat(), 327 | location.getLon(), 328 | location.getRad(), 329 | EventState.PUBLISHED, 330 | page 331 | ); 332 | } else { 333 | if (lat == null || lon == null) { 334 | throw new ClientErrorException("Conditions are not met"); 335 | } else { 336 | eventList = eventRepository.findEventsWithLocationRadius( 337 | lat, lon, rad, EventState.PUBLISHED, page 338 | ); 339 | } 340 | } 341 | 342 | return EventMapper.mapToEventShortDto(eventList); 343 | } 344 | 345 | private int getConfirmedRequests(long id) { 346 | return requestRepository.findAllByEventIdAndStatus(id, RequestStatus.CONFIRMED).size(); 347 | } 348 | 349 | private int getViewsStats(long eventId) { 350 | ResponseEntity statsObject = statsServiceClient.getStatistics(eventId); 351 | return statsServiceClient.getHits(statsObject); 352 | } 353 | 354 | private Event getEventByIdOrThrowException(long eventId) { 355 | return eventRepository.findById(eventId).orElseThrow( 356 | () -> new EntityNotFoundException("Event with id: " + eventId + " was not found") 357 | ); 358 | } 359 | 360 | private Category getCategoryByIdOrThrowException(long catId) { 361 | return categoryRepository.findById(catId).orElseThrow( 362 | () -> new EntityNotFoundException("Category with id: " + catId + " was not found") 363 | ); 364 | } 365 | 366 | private User getUserByIdOrThrowException(long userId) { 367 | return userRepository.findById(userId).orElseThrow( 368 | () -> new EntityNotFoundException("User with id: " + userId + " was not found") 369 | ); 370 | } 371 | 372 | private ParticipationRequest getRequestByIdOrThrowException(long requestId) { 373 | return requestRepository.findById(requestId).orElseThrow( 374 | () -> new EntityNotFoundException("Participation request with id: " 375 | + requestId + " was not found") 376 | ); 377 | } 378 | 379 | private Set getClosestLocations(NewEventDto newEventDto) { 380 | NewCoordinatesDto coordinates = newEventDto.getLocation(); 381 | Double lat = coordinates.getLat(); 382 | Double lon = coordinates.getLon(); 383 | 384 | return new HashSet<>(locationRepository.findLocationsWithinRadius(lat, lon)); 385 | } 386 | 387 | private Location getExistingLocationOrCreateNewOne(NewEventDto newEventDto) { 388 | NewCoordinatesDto coordinates = newEventDto.getLocation(); 389 | Double lat = coordinates.getLat(); 390 | Double lon = coordinates.getLon(); 391 | 392 | if (!locationRepository.existsByLatAndLon(lat, lon)) { 393 | Location newLocation = LocationMapper.mapFromLocationShortDto(coordinates); 394 | newLocation.setStatus(LocationStatus.SUGGESTED_BY_USER); 395 | return locationRepository.save(newLocation); 396 | } else { 397 | return locationRepository.findByLatAndLon(lat, lon); 398 | } 399 | } 400 | 401 | private Location getExistingLocationOrThrowException(long locationId) { 402 | return locationRepository.findById(locationId).orElseThrow( 403 | () -> new EntityNotFoundException("Location with id " + locationId + " not found") 404 | ); 405 | } 406 | 407 | private void checkIfEventIsAlreadyPublished(@NonNull Event event) { 408 | if (event.getState().equals(EventState.PUBLISHED)) { 409 | throw new ClientErrorException("Published event can't be updated"); 410 | } 411 | } 412 | 413 | private void checkIfEventIsPublishedForPublic(@NotNull Event event) { 414 | if (!event.getState().equals(EventState.PUBLISHED)) { 415 | throw new EntityNotFoundException("Event is not published"); 416 | } 417 | } 418 | 419 | } 420 | --------------------------------------------------------------------------------