├── .github └── workflows │ ├── api-tests.yml │ └── wait-for-it.sh ├── .gitignore ├── README.md ├── checkstyle.xml ├── docker-compose.yml ├── ewm-main-service-spec.json ├── ewm-service ├── Dockerfile ├── pom.xml └── src │ └── main │ ├── java │ └── ru │ │ └── practicum │ │ ├── ExploreWithMe.java │ │ ├── category │ │ ├── controller │ │ │ ├── CategoryAdminController.java │ │ │ └── CategoryPublicController.java │ │ ├── data │ │ │ ├── Category.java │ │ │ ├── CategoryRepository.java │ │ │ └── dto │ │ │ │ ├── CategoryDto.java │ │ │ │ ├── CategoryMapper.java │ │ │ │ └── NewCategoryDto.java │ │ └── service │ │ │ ├── CategoryService.java │ │ │ └── CategoryServiceImpl.java │ │ ├── comment │ │ ├── controller │ │ │ ├── CommentAdminController.java │ │ │ ├── CommentPrivateController.java │ │ │ └── CommentPublicController.java │ │ ├── data │ │ │ ├── Comment.java │ │ │ ├── CommentMapper.java │ │ │ ├── CommentRepository.java │ │ │ └── dto │ │ │ │ ├── CommentDto.java │ │ │ │ ├── CommentPatchDto.java │ │ │ │ └── NewCommentDto.java │ │ └── service │ │ │ ├── CommentService.java │ │ │ └── CommentServiceImpl.java │ │ ├── compilation │ │ ├── controller │ │ │ ├── CompilationAdminController.java │ │ │ └── CompilationPublicController.java │ │ ├── data │ │ │ ├── Compilation.java │ │ │ ├── CompilationDto.java │ │ │ ├── CompilationMapper.java │ │ │ ├── CompilationRepository.java │ │ │ └── dto │ │ │ │ ├── NewCompilationDto.java │ │ │ │ └── UpdateCompilationRequest.java │ │ └── service │ │ │ ├── CompilationService.java │ │ │ └── CompilationServiceImpl.java │ │ ├── configuration │ │ └── BeanConfiguration.java │ │ ├── event │ │ ├── controller │ │ │ ├── AdminEventController.java │ │ │ ├── PrivateEventController.java │ │ │ └── PublicEventController.java │ │ ├── data │ │ │ ├── AdminEventStateAction.java │ │ │ ├── Event.java │ │ │ ├── EventRepository.java │ │ │ ├── EventState.java │ │ │ ├── Location.java │ │ │ ├── UserEventStateAction.java │ │ │ └── dto │ │ │ │ ├── EventFullDto.java │ │ │ │ ├── EventMapper.java │ │ │ │ ├── EventRequestStatusUpdateRequest.java │ │ │ │ ├── EventRequestStatusUpdateResult.java │ │ │ │ ├── EventShortDto.java │ │ │ │ ├── NewEventDto.java │ │ │ │ ├── SearchParameters.java │ │ │ │ ├── SortProperty.java │ │ │ │ ├── UpdateEventAdminRequest.java │ │ │ │ ├── UpdateEventRequest.java │ │ │ │ └── UpdateEventUserRequest.java │ │ └── service │ │ │ ├── EventService.java │ │ │ └── EventServiceImpl.java │ │ ├── exception │ │ ├── ApiError.java │ │ ├── ChangeEventStatusForbiddenException.java │ │ ├── EventParticipationForbiddenException.java │ │ ├── RequestNotValidException.java │ │ ├── RequestStatusNotPendingException.java │ │ └── controller │ │ │ └── ExceptionController.java │ │ ├── request │ │ ├── controller │ │ │ └── PrivateRequestController.java │ │ ├── data │ │ │ ├── Request.java │ │ │ ├── RequestRepository.java │ │ │ ├── RequestStatus.java │ │ │ └── dto │ │ │ │ ├── ParticipationRequestDto.java │ │ │ │ └── RequestMapper.java │ │ └── service │ │ │ ├── RequestService.java │ │ │ └── RequestServiceImpl.java │ │ ├── user │ │ ├── controller │ │ │ └── UserAdminController.java │ │ ├── data │ │ │ ├── User.java │ │ │ ├── UserRepository.java │ │ │ └── dto │ │ │ │ ├── UserDto.java │ │ │ │ ├── UserMapper.java │ │ │ │ └── UserShortDto.java │ │ └── service │ │ │ ├── UserService.java │ │ │ └── UserServiceImpl.java │ │ └── validation │ │ ├── DateTimeConsistency.java │ │ ├── DateTimeConsistencyValidator.java │ │ ├── InFuture.java │ │ └── InFutureValidator.java │ └── resources │ ├── application.properties │ └── schema.sql ├── ewm-stats-service-spec.json ├── lombok.config ├── pom.xml ├── postman └── feature.json ├── stat ├── pom.xml ├── stat-client │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── ru │ │ └── practicum │ │ └── StatClient.java ├── stat-dto │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── ru │ │ └── practicum │ │ └── dto │ │ ├── EndpointHitDto.java │ │ └── ViewStats.java └── stat-server │ ├── Dockerfile │ ├── pom.xml │ └── src │ └── main │ ├── java │ └── ru │ │ └── practicum │ │ ├── StatApplication.java │ │ ├── controller │ │ ├── ExceptionController.java │ │ └── StatController.java │ │ ├── entity │ │ └── EndpointHit.java │ │ ├── exception │ │ └── DatesNotConsistentException.java │ │ ├── mapper │ │ └── EndpointHitMapper.java │ │ ├── repository │ │ └── StatRepository.java │ │ └── service │ │ ├── StatService.java │ │ └── StatServiceImpl.java │ └── resources │ ├── application.properties │ └── schema.sql └── suppressions.xml /.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 -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | checkstyle.xml 3 | target/ 4 | !.mvn/wrapper/maven-wrapper.jar 5 | !**/src/main/**/target/ 6 | !**/src/test/**/target/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | 17 | ### IntelliJ IDEA ### 18 | .idea 19 | *.iws 20 | *.iml 21 | *.ipr 22 | 23 | ### NetBeans ### 24 | /nbproject/private/ 25 | /nbbuild/ 26 | /dist/ 27 | /nbdist/ 28 | /.nb-gradle/ 29 | build/ 30 | !**/src/main/**/build/ 31 | !**/src/test/**/build/ 32 | 33 | ### VS Code ### 34 | .vscode/ 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## ExploreWithMe - сервис для участия в мероприятиях 2 | 3 | - Основная идея проекта - помогать пользователям находить интересные мероприятия и делиться ими с друзьями 4 | - Приложение содержит два сервиса: 5 | * Основной сервис - предназначен для работы с событиями и их участниками. 6 | * Сервис статистики - хранит информацию об обращениях к основному сервису. Позволяет формировать статистику для основного сервиса. 7 | 8 | ## Дополнительная функциональность 9 | 10 | Комментарии к событиям 11 | - позволяет оставлять и модерировать комментарии к событиям, 12 | - позволяет ставить лайки и дизлайки событию, 13 | - позволяет получать количество комментариев у событий для понимания их популярности. 14 | 15 | - https://github.com/Iregor/java-explore-with-me/pull/9 16 | 17 | ## Технологический стек 18 | 19 | Проект ExploreWithMe разработан с использованием следующих технологий и инструментов: 20 | 21 | - Java 11 22 | - Spring Boot 23 | - Hibernate ORM, Query DSL - маппинг данных 24 | - Apache Maven - управление зависимостями и сборка приложения. 25 | - PostgresSQL, H2 - реляционные базы данных 26 | - Docker, Docker Compose - контейнеризация сервисов приложения. 27 | 28 | ## Микросервисная архитектура 29 | 30 | Приложение состоит из 2 основных модулей: 31 | - Stats - сервер хранения обращений к приложению и формирования статистики 32 | - Service - сервер обработки запросов к приложению 33 | 34 | ## Установка и запуск проекта 35 | 36 | ### 1 вариант: 37 | Необходимо настроенная система виртуализации, установленный Docker Desktop(скачать и установить можно с официального сайта https://www.docker.com/products/docker-desktop/) 38 | 39 | 1. Клонируйте репозиторий проекта на свою локальную машину. 40 | 2. Запустите командную строку и перейдите в корень директории с проектом. 41 | 3. Введите следующую команду, которая подготовит и запустит приложение на вашей локальной машине 42 | ``` 43 | $ docker-compose up 44 | ``` 45 | 4. Приложение будет запущено на порту 8080 и готово принимать http-запросы. 46 | 5. Список доступных эндпоинтов предоставлен ниже 47 | 48 | 49 | ### 2 вариант: 50 | 51 | 1. Установите Java Development Kit (JDK) версии 11 или выше, если у вас его еще нет. 52 | 2. Установите PostgreSQL и создайте базу данных для проекта. 53 | 3. Клонируйте репозиторий проекта на свою локальную машину. 54 | 4. Настройте файл `application.properties`, расположенный в директории `src/main/resources`, чтобы указать данные для подключения к вашей базе данных PostgreSQL. 55 | 5. Запустите приложение, выполнив следующую команду в корневой директории проекта: 56 | ``` 57 | mvn spring-boot:run 58 | ``` 59 | 6. Приложение будет запущено на порту 8080 и готово принимать http-запросы. 60 | 61 | Эндпоинты 62 | --- 63 | - GET /compilations - Получение подборок событий 64 | - GET /compilations/{compId} - Получение подборки событий по его id 65 | --- 66 | - POST /admin/categories Добавление новой категории 67 | - DELETE /admin/categories/{catId} Удаление категории 68 | - GET /admin/categories/{catId} Получение списка бронирований для всех вещей текущего пользователя. 69 | --- 70 | - GET /users/{userId}/events - Получение событий, добавленных текущим пользователем 71 | - POST /users/{userId}/events - Добавление нового события 72 | - GET /users/{userId}/events/{eventId} - Получение полной информации о событии добавленном текущим пользователем 73 | - PATCH /users/{userId}/events/{eventId} - Изменение события добавленного текущим пользователем 74 | - GET /users/{userId}/events/{eventId}/requests - Получение информации о запросах на участие в событии текущего пользователя 75 | - PATCH /users/{userId}/events/{eventId}/requests - Изменение статуса (подтверждена, отменена) заявок на участие в событии текущего пользователя 76 | --- 77 | - GET /categories - Получение категорий 78 | - GET /categories/{catId} - Получение информации о категории по её идентификатору 79 | --- 80 | - GET /admin/events - Поиск событий 81 | - PATCH /admin/events/{eventId} - Редактирование данных события и его статуса (отклонение/публикация). 82 | --- 83 | - GET /events/{id} - Получение подробной информации об опубликованном событии по его идентификатору 84 | - GET /events/{id} - Получение подробной информации об опубликованном событии по его идентификатору 85 | --- 86 | - GET /users/{userId}/requests - Получение информации о заявках текущего пользователя на участие в чужих событиях 87 | - POST /users/{userId}/requests - Добавление запроса от текущего пользователя на участие в событии 88 | - DELETE /users/{userId}/requests/{requestId}/cancel - Отмена своего запроса на участие в событии 89 | --- 90 | - GET /admin/users - Получение информации о пользователях 91 | - POST /admin/users - Добавление нового пользователя 92 | - DELETE /admin/users/{userId} - Удаление пользователя 93 | --- 94 | - POST /admin/compilations - Добавление новой подборки (подборка может не содержать событий) 95 | - DELETE /admin/compilations/{compId} - Удаление подборки 96 | - PATCH /admin/compilations/{compId} - Обновить информацию о подборке 97 | --- 98 | - GET /stats - Получение статистики по посещениям 99 | - POST /hit - Сохранение информации о том, что на uri конкретного сервиса был отправлен запрос пользователем. 100 | --- 101 | - DELETE /comments/{commentId} - удаление комменатрия администратором 102 | - POST /users/{userId}/events/{eventId}/comments - добавление комментария пользователем событию 103 | - PATCH /users/{userId}/events/{eventId}/comments - редактирование пользователем собственного комментария 104 | - DELETE /users/{userId}/events/{eventId}/comments/{commentId} - удаление комментария пользователем 105 | - GET /comments - получение пользователем списка собственных комментариев 106 | - GET /events/{eventId}/comments/{commentId} - получение комментария по его id 107 | - GET /events/{eventId}/comments - получение списка комментариев события 108 | --- 109 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.1' 2 | services: 3 | stats-server: 4 | build: stat/stat-server 5 | container_name: stats-server-container 6 | ports: 7 | - "9090:9090" 8 | depends_on: 9 | - stat-db 10 | environment: 11 | - SPRING_DATASOURCE_URL=jdbc:postgresql://stat-db:5432/stat-db 12 | - SPRING_DATASOURCE_USERNAME=user 13 | - SPRING-DATASOURCE_PASSWORD=password 14 | 15 | stat-db: 16 | image: postgres:15-alpine 17 | container_name: stat-db-container 18 | ports: 19 | - "6542:5432" 20 | environment: 21 | - POSTGRES_DB=stat-db 22 | - POSTGRES_USER=user 23 | - POSTGRES_PASSWORD=password 24 | 25 | ewm-service: 26 | build: ewm-service 27 | container_name: ewm-service-container 28 | ports: 29 | - "8080:8080" 30 | depends_on: 31 | - ewm-db 32 | - stats-server 33 | environment: 34 | - STAT_URL=http://stats-server:9090 35 | - SPRING_DATASOURCE_URL=jdbc:postgresql://ewm-db:5432/ewm-db 36 | - SPRING_DATASOURCE_USERNAME=user 37 | - SPRING-DATASOURCE_PASSWORD=password 38 | 39 | ewm-db: 40 | image: postgres:15-alpine 41 | container_name: emw-db-container 42 | ports: 43 | - "6541:5432" 44 | environment: 45 | - POSTGRES_DB=ewm-db 46 | - POSTGRES_USER=user 47 | - POSTGRES_PASSWORD=password -------------------------------------------------------------------------------- /ewm-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM amazoncorretto:11-alpine-jdk 2 | COPY target/*.jar ewm-service.jar 3 | ENTRYPOINT ["java","-jar","/ewm-service.jar"] -------------------------------------------------------------------------------- /ewm-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | explore-with-me 7 | ru.practicum 8 | 0.0.1-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | service 13 | 14 | 15 | 11 16 | 11 17 | UTF-8 18 | 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-web 24 | 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-data-jpa 29 | 30 | 31 | 32 | com.querydsl 33 | querydsl-apt 34 | 35 | 36 | com.querydsl 37 | querydsl-jpa 38 | 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-validation 43 | 44 | 45 | 46 | org.springframework.boot 47 | spring-boot-starter-actuator 48 | 49 | 50 | 51 | com.h2database 52 | h2 53 | runtime 54 | 55 | 56 | 57 | org.postgresql 58 | postgresql 59 | 60 | 61 | 62 | ru.practicum 63 | stat-client 64 | 0.0.1-SNAPSHOT 65 | compile 66 | 67 | 68 | 69 | 70 | 71 | 72 | org.springframework.boot 73 | spring-boot-maven-plugin 74 | 75 | 76 | com.mysema.maven 77 | apt-maven-plugin 78 | 1.1.3 79 | 80 | 81 | 82 | process 83 | 84 | 85 | target/generated-sources 86 | com.querydsl.apt.jpa.JPAAnnotationProcessor 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/ExploreWithMe.java: -------------------------------------------------------------------------------- 1 | package ru.practicum; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class ExploreWithMe { 8 | public static void main(String[] args) { 9 | SpringApplication.run(ExploreWithMe.class, args); 10 | } 11 | } -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/category/controller/CategoryAdminController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.category.controller; 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.category.data.dto.CategoryDto; 8 | import ru.practicum.category.data.dto.NewCategoryDto; 9 | import ru.practicum.category.service.CategoryService; 10 | 11 | import javax.validation.Valid; 12 | import javax.validation.constraints.Positive; 13 | import java.time.LocalDateTime; 14 | 15 | @RestController 16 | @RequestMapping("/admin/categories") 17 | @Slf4j 18 | @RequiredArgsConstructor 19 | public class CategoryAdminController { 20 | private final CategoryService categoryService; 21 | 22 | @PostMapping 23 | @ResponseStatus(HttpStatus.CREATED) 24 | public CategoryDto createCategory(@RequestBody @Valid NewCategoryDto newCategoryDto) { 25 | CategoryDto dto = categoryService.createCategory(newCategoryDto); 26 | log.info(String.format("%s: category created: %s", LocalDateTime.now(), dto)); 27 | return dto; 28 | } 29 | 30 | @DeleteMapping("/{catId}") 31 | @ResponseStatus(HttpStatus.NO_CONTENT) 32 | public void deleteCategory(@PathVariable @Positive long catId) { 33 | categoryService.deleteCategory(catId); 34 | log.info(String.format("%s: category was deleted by id: %d", LocalDateTime.now(), catId)); 35 | } 36 | 37 | @PatchMapping("/{catId}") 38 | public CategoryDto patchCategory(@RequestBody @Valid CategoryDto categoryDto, @PathVariable long catId) { 39 | CategoryDto dto = categoryService.patchCategory(categoryDto, catId); 40 | log.info(String.format("%s: category was patched: %s", LocalDateTime.now(), dto)); 41 | return dto; 42 | } 43 | } -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/category/controller/CategoryPublicController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.category.controller; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.web.bind.annotation.*; 6 | import ru.practicum.category.data.dto.CategoryDto; 7 | import ru.practicum.category.service.CategoryService; 8 | 9 | import javax.validation.constraints.Positive; 10 | import java.time.LocalDateTime; 11 | import java.util.List; 12 | 13 | @RestController 14 | @RequestMapping("/categories") 15 | @Slf4j 16 | @RequiredArgsConstructor 17 | public class CategoryPublicController { 18 | private final CategoryService categoryService; 19 | 20 | @GetMapping 21 | List getAllCategories(@RequestParam(defaultValue = "0") int from, 22 | @RequestParam(defaultValue = "10") int size) { 23 | List categories = categoryService.getAllCategories(from, size); 24 | log.info(String.format("%s: categories are returned: %s", LocalDateTime.now(), categories.toString())); 25 | return categories; 26 | } 27 | 28 | @GetMapping("/{catId}") 29 | CategoryDto findCategoryById(@PathVariable @Positive long catId) { 30 | CategoryDto category = categoryService.findCategoryById(catId); 31 | log.info(String.format("%s: category is returned: %s", LocalDateTime.now(), category.toString())); 32 | return category; 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/category/data/Category.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.category.data; 2 | 3 | import lombok.*; 4 | 5 | import javax.persistence.*; 6 | import javax.validation.constraints.NotBlank; 7 | import javax.validation.constraints.Size; 8 | 9 | @Entity 10 | @Table(name = "categories") 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | @Getter 14 | @Setter 15 | @ToString 16 | public class Category { 17 | @Id 18 | @GeneratedValue(strategy = GenerationType.IDENTITY) 19 | @Column(name = "id") 20 | private Long id; 21 | @NotBlank 22 | @Size(min = 1, max = 50) 23 | @Column(name = "name") 24 | private String name; 25 | } 26 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/category/data/CategoryRepository.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.category.data; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import org.springframework.stereotype.Repository; 5 | 6 | @Repository 7 | public interface CategoryRepository extends JpaRepository { 8 | } 9 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/category/data/dto/CategoryDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.category.data.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.validation.constraints.NotBlank; 8 | import javax.validation.constraints.Size; 9 | 10 | @Data 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | public class CategoryDto { 14 | private Long id; 15 | @NotBlank 16 | @Size(min = 1, max = 50) 17 | private String name; 18 | } -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/category/data/dto/CategoryMapper.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.category.data.dto; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.NoArgsConstructor; 5 | import ru.practicum.category.data.Category; 6 | 7 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 8 | public class CategoryMapper { 9 | public static Category toCategory(CategoryDto categoryDto) { 10 | return new Category(categoryDto.getId(), categoryDto.getName()); 11 | } 12 | 13 | public static CategoryDto toCategoryDto(Category category) { 14 | return new CategoryDto(category.getId(), category.getName()); 15 | } 16 | 17 | public static Category toCategory(NewCategoryDto newCategoryDto) { 18 | return new Category(null, newCategoryDto.getName()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/category/data/dto/NewCategoryDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.category.data.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.validation.constraints.NotBlank; 8 | import javax.validation.constraints.Size; 9 | 10 | @Data 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | public class NewCategoryDto { 14 | @NotBlank 15 | @Size(min = 1, max = 50) 16 | private String name; 17 | } 18 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/category/service/CategoryService.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.category.service; 2 | 3 | import ru.practicum.category.data.dto.CategoryDto; 4 | import ru.practicum.category.data.dto.NewCategoryDto; 5 | 6 | import java.util.List; 7 | 8 | public interface CategoryService { 9 | CategoryDto createCategory(NewCategoryDto newCategoryDto); 10 | 11 | void deleteCategory(long catId); 12 | 13 | CategoryDto patchCategory(CategoryDto categoryDto, long catId); 14 | 15 | List getAllCategories(int from, int size); 16 | 17 | CategoryDto findCategoryById(long catId); 18 | } 19 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/category/service/CategoryServiceImpl.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.category.service; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.data.domain.PageRequest; 5 | import org.springframework.data.domain.Pageable; 6 | import org.springframework.data.domain.Sort; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.transaction.annotation.Transactional; 9 | import ru.practicum.category.data.Category; 10 | import ru.practicum.category.data.CategoryRepository; 11 | import ru.practicum.category.data.dto.CategoryDto; 12 | import ru.practicum.category.data.dto.CategoryMapper; 13 | import ru.practicum.category.data.dto.NewCategoryDto; 14 | 15 | import java.util.List; 16 | import java.util.stream.Collectors; 17 | 18 | @Service 19 | @RequiredArgsConstructor 20 | public class CategoryServiceImpl implements CategoryService { 21 | private final CategoryRepository categoryRepository; 22 | 23 | @Override 24 | @Transactional 25 | public CategoryDto createCategory(NewCategoryDto newCategoryDto) { 26 | Category category = CategoryMapper.toCategory(newCategoryDto); 27 | return CategoryMapper.toCategoryDto(categoryRepository.save(category)); 28 | } 29 | 30 | @Override 31 | @Transactional 32 | public void deleteCategory(long catId) { 33 | categoryRepository.deleteById(catId); 34 | } 35 | 36 | @Override 37 | @Transactional 38 | public CategoryDto patchCategory(CategoryDto categoryDto, long catId) { 39 | Category category = CategoryMapper.toCategory(categoryDto); 40 | category.setId(catId); 41 | return CategoryMapper.toCategoryDto(categoryRepository.save(category)); 42 | } 43 | 44 | @Override 45 | @Transactional 46 | public List getAllCategories(int from, int size) { 47 | Pageable page = PageRequest.of(from / size, size, Sort.by("id").ascending()); 48 | return categoryRepository.findAll(page) 49 | .stream() 50 | .map(CategoryMapper::toCategoryDto) 51 | .collect(Collectors.toList()); 52 | } 53 | 54 | @Override 55 | @Transactional 56 | public CategoryDto findCategoryById(long catId) { 57 | return categoryRepository.findById(catId).map(CategoryMapper::toCategoryDto).orElseThrow(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/comment/controller/CommentAdminController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.comment.controller; 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.comment.service.CommentService; 8 | 9 | import javax.validation.constraints.Positive; 10 | import java.time.LocalDateTime; 11 | 12 | @RestController 13 | @RequestMapping("/comments") 14 | @RequiredArgsConstructor 15 | @Slf4j 16 | public class CommentAdminController { 17 | private final CommentService commentService; 18 | 19 | @DeleteMapping("/{commentId}") 20 | @ResponseStatus(HttpStatus.NO_CONTENT) 21 | public void deleteCommentById(@PathVariable @Positive long commentId) { 22 | commentService.deleteCommentById(commentId); 23 | log.info("{}: comment with id={} was deleted", LocalDateTime.now(), commentId); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.comment.controller; 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.comment.data.dto.CommentDto; 9 | import ru.practicum.comment.data.dto.CommentPatchDto; 10 | import ru.practicum.comment.data.dto.NewCommentDto; 11 | import ru.practicum.comment.service.CommentService; 12 | 13 | import javax.validation.Valid; 14 | import javax.validation.constraints.Positive; 15 | import javax.validation.constraints.PositiveOrZero; 16 | import java.time.LocalDateTime; 17 | import java.util.List; 18 | 19 | @RestController 20 | @RequestMapping("/users/{userId}") 21 | @Validated 22 | @RequiredArgsConstructor 23 | @Slf4j 24 | public class CommentPrivateController { 25 | private final CommentService commentService; 26 | 27 | @PostMapping("/events/{eventId}/comments") 28 | @ResponseStatus(HttpStatus.CREATED) 29 | public CommentDto createComment(@PathVariable @Positive long userId, @PathVariable @Positive long eventId, @RequestBody @Valid NewCommentDto newCommentDto) { 30 | CommentDto comment = commentService.createComment(userId, eventId, newCommentDto); 31 | log.info("{}: comment created: {}", LocalDateTime.now(), comment); 32 | return comment; 33 | } 34 | 35 | @PatchMapping("/events/{eventId}/comments") 36 | public CommentDto updateComment(@PathVariable @Positive long userId, @PathVariable @Positive long eventId, @RequestBody @Valid CommentPatchDto commentPatchDto) { 37 | CommentDto comment = commentService.updateComment(userId, eventId, commentPatchDto); 38 | log.info("{}: comment updated: {}", LocalDateTime.now(), comment); 39 | return comment; 40 | } 41 | 42 | @DeleteMapping("/events/{eventId}/comments/{commentId}") 43 | @ResponseStatus(HttpStatus.NO_CONTENT) 44 | public void deleteCommentById(@PathVariable @Positive long userId, @PathVariable @Positive long eventId, @PathVariable @Positive long commentId) { 45 | commentService.deleteComment(userId, eventId, commentId); 46 | log.info("{}: comment deleted: userId = {}, eventId = {}, commentId = {}", LocalDateTime.now(), userId, eventId, commentId); 47 | } 48 | 49 | @GetMapping("/comments") 50 | public List getUserComments(@PathVariable @Positive long userId, 51 | @RequestParam(defaultValue = "0") @PositiveOrZero int from, 52 | @RequestParam(defaultValue = "10") @Positive int size) { 53 | List comments = commentService.getUserComments(userId, from, size); 54 | log.info("{}: comments returned: {}", LocalDateTime.now(), comments); 55 | return comments; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/comment/controller/CommentPublicController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.comment.controller; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.web.bind.annotation.*; 6 | import ru.practicum.comment.data.dto.CommentDto; 7 | import ru.practicum.comment.service.CommentService; 8 | 9 | import javax.validation.constraints.Positive; 10 | import javax.validation.constraints.PositiveOrZero; 11 | import java.time.LocalDateTime; 12 | import java.util.List; 13 | 14 | @RestController 15 | @RequestMapping("/events/{eventId}/comments") 16 | @RequiredArgsConstructor 17 | @Slf4j 18 | public class CommentPublicController { 19 | private final CommentService commentService; 20 | 21 | @GetMapping("/{commentId}") 22 | //позволяет получить комментарий по id 23 | public CommentDto getCommentById(@PathVariable @Positive long eventId, @PathVariable @Positive long commentId) { 24 | CommentDto comment = commentService.getCommentById(eventId, commentId); 25 | log.info("{}: comment returned: {}", LocalDateTime.now(), comment); 26 | return comment; 27 | } 28 | 29 | @GetMapping 30 | //Позволяет получить комментарии к событию 31 | public List getEventComments(@PathVariable @Positive long eventId, 32 | @RequestParam(defaultValue = "0") @PositiveOrZero int from, 33 | @RequestParam(defaultValue = "10") @Positive int size) { 34 | List comments = commentService.getEventComments(eventId, from, size); 35 | log.info("{}: comments returned: {}", LocalDateTime.now(), comments); 36 | return comments; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/comment/data/Comment.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.comment.data; 2 | 3 | import lombok.*; 4 | import ru.practicum.event.data.Event; 5 | import ru.practicum.user.data.User; 6 | 7 | import javax.persistence.*; 8 | import javax.validation.constraints.NotBlank; 9 | import javax.validation.constraints.NotNull; 10 | import javax.validation.constraints.Size; 11 | import java.time.LocalDateTime; 12 | 13 | @Entity 14 | @Table(name = "comments") 15 | @Getter 16 | @Setter 17 | @NoArgsConstructor 18 | @AllArgsConstructor 19 | @Builder 20 | @ToString 21 | public class Comment { 22 | @Id 23 | @GeneratedValue(strategy = GenerationType.IDENTITY) 24 | private Long id; 25 | @NotBlank 26 | @Size(min = 5, max = 5000) 27 | private String text; 28 | @NotNull 29 | private LocalDateTime created; 30 | private LocalDateTime editedOn; 31 | @NotNull 32 | @ManyToOne 33 | private User commentator; 34 | @NotNull 35 | @ManyToOne 36 | private Event event; 37 | } 38 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/comment/data/CommentMapper.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.comment.data; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.NoArgsConstructor; 5 | import ru.practicum.comment.data.dto.CommentDto; 6 | import ru.practicum.comment.data.dto.NewCommentDto; 7 | import ru.practicum.event.data.Event; 8 | import ru.practicum.event.data.dto.EventMapper; 9 | import ru.practicum.user.data.User; 10 | import ru.practicum.user.data.dto.UserMapper; 11 | 12 | import java.time.LocalDateTime; 13 | 14 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 15 | public class CommentMapper { 16 | public static Comment toComment(User commentator, Event event, NewCommentDto newCommentDto, LocalDateTime created) { 17 | return Comment.builder() 18 | .text(newCommentDto.getText()) 19 | .commentator(commentator) 20 | .event(event) 21 | .created(created) 22 | .build(); 23 | } 24 | 25 | public static CommentDto toCommentDto(Comment comment) { 26 | return CommentDto.builder() 27 | .id(comment.getId()) 28 | .text(comment.getText()) 29 | .created(comment.getCreated()) 30 | .editedOn(comment.getEditedOn()) 31 | .commentator(UserMapper.toUserShortDto(comment.getCommentator())) 32 | .event(EventMapper.toEventShortDto(comment.getEvent())) 33 | .build(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/comment/data/CommentRepository.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.comment.data; 2 | 3 | import org.springframework.data.domain.Pageable; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.List; 8 | import java.util.Optional; 9 | 10 | @Repository 11 | public interface CommentRepository extends JpaRepository { 12 | Optional findByCommentatorIdAndEventId(long userId, long eventId); 13 | 14 | Optional findByIdAndCommentatorIdAndEventId(long commentId, long userId, long eventId); 15 | 16 | Optional findByIdAndEventId(long commentId, long eventId); 17 | 18 | List findAllByCommentatorId(long userId, Pageable page); 19 | 20 | List findAllByEventId(long eventId, Pageable page); 21 | 22 | long countByEventId(long eventId); 23 | } 24 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/comment/data/dto/CommentDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.comment.data.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import ru.practicum.event.data.dto.EventShortDto; 8 | import ru.practicum.user.data.dto.UserShortDto; 9 | 10 | import javax.validation.constraints.NotBlank; 11 | import javax.validation.constraints.NotNull; 12 | import javax.validation.constraints.Positive; 13 | import java.time.LocalDateTime; 14 | 15 | @Data 16 | @NoArgsConstructor 17 | @AllArgsConstructor 18 | @Builder 19 | public class CommentDto { 20 | @Positive 21 | @NotNull 22 | private Long id; 23 | @NotBlank 24 | private String text; 25 | @NotNull 26 | private LocalDateTime created; 27 | private LocalDateTime editedOn; 28 | @NotNull 29 | private UserShortDto commentator; 30 | @NotNull 31 | private EventShortDto event; 32 | } 33 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/comment/data/dto/CommentPatchDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.comment.data.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.validation.constraints.Size; 9 | 10 | @Data 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | @Builder 14 | public class CommentPatchDto { 15 | @Size(min = 5, max = 300) 16 | private String text; 17 | } 18 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/comment/data/dto/NewCommentDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.comment.data.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.validation.constraints.NotBlank; 9 | import javax.validation.constraints.Size; 10 | 11 | @Data 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | @Builder 15 | public class NewCommentDto { 16 | @NotBlank 17 | @Size(min = 5, max = 500) 18 | private String text; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/comment/service/CommentService.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.comment.service; 2 | 3 | import ru.practicum.comment.data.dto.CommentDto; 4 | import ru.practicum.comment.data.dto.CommentPatchDto; 5 | import ru.practicum.comment.data.dto.NewCommentDto; 6 | 7 | import java.util.List; 8 | 9 | public interface CommentService { 10 | CommentDto createComment(long userId, long eventId, NewCommentDto newCommentDto); 11 | 12 | CommentDto updateComment(long userId, long eventId, CommentPatchDto commentPatchDto); 13 | 14 | void deleteComment(long userId, long eventId, long commentId); 15 | 16 | CommentDto getCommentById(long eventId, long commentId); 17 | 18 | List getUserComments(long userId, int from, int size); 19 | 20 | List getEventComments(long eventId, int from, int size); 21 | 22 | void deleteCommentById(long commentId); 23 | } 24 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/comment/service/CommentServiceImpl.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.comment.service; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.data.domain.PageRequest; 5 | import org.springframework.data.domain.Pageable; 6 | import org.springframework.data.domain.Sort; 7 | import org.springframework.stereotype.Service; 8 | import ru.practicum.comment.data.Comment; 9 | import ru.practicum.comment.data.CommentMapper; 10 | import ru.practicum.comment.data.CommentRepository; 11 | import ru.practicum.comment.data.dto.CommentDto; 12 | import ru.practicum.comment.data.dto.CommentPatchDto; 13 | import ru.practicum.comment.data.dto.NewCommentDto; 14 | import ru.practicum.event.data.Event; 15 | import ru.practicum.event.data.EventRepository; 16 | import ru.practicum.user.data.User; 17 | import ru.practicum.user.data.UserRepository; 18 | 19 | import java.time.LocalDateTime; 20 | import java.util.List; 21 | import java.util.stream.Collectors; 22 | 23 | @Service 24 | @RequiredArgsConstructor 25 | public class CommentServiceImpl implements CommentService { 26 | private final CommentRepository commentRepository; 27 | private final UserRepository userRepository; 28 | private final EventRepository eventRepository; 29 | 30 | @Override 31 | public CommentDto createComment(long userId, long eventId, NewCommentDto newCommentDto) { 32 | User commentator = userRepository.findById(userId).orElseThrow(); 33 | Event event = eventRepository.findById(eventId).orElseThrow(); 34 | Comment comment = CommentMapper.toComment(commentator, event, newCommentDto, LocalDateTime.now()); 35 | return CommentMapper.toCommentDto(commentRepository.save(comment)); 36 | } 37 | 38 | @Override 39 | public CommentDto updateComment(long userId, long eventId, CommentPatchDto commentPatchDto) { 40 | Comment comment = commentRepository.findByCommentatorIdAndEventId(userId, eventId).orElseThrow(); 41 | patchComment(comment, commentPatchDto); 42 | return CommentMapper.toCommentDto(commentRepository.save(comment)); 43 | } 44 | 45 | @Override 46 | public void deleteComment(long userId, long eventId, long commentId) { 47 | Comment comment = commentRepository.findByIdAndCommentatorIdAndEventId(commentId, userId, eventId).orElseThrow(); 48 | commentRepository.delete(comment); 49 | } 50 | 51 | @Override 52 | public CommentDto getCommentById(long eventId, long commentId) { 53 | return CommentMapper.toCommentDto(commentRepository.findByIdAndEventId(commentId, eventId).orElseThrow()); 54 | } 55 | 56 | @Override 57 | public List getUserComments(long userId, int from, int size) { 58 | Pageable page = PageRequest.of(from / size, size, Sort.by("id").descending()); 59 | return commentRepository.findAllByCommentatorId(userId, page) 60 | .stream() 61 | .map(CommentMapper::toCommentDto) 62 | .collect(Collectors.toList()); 63 | } 64 | 65 | @Override 66 | public List getEventComments(long eventId, int from, int size) { 67 | Pageable page = getPageable(from, size); 68 | return commentRepository.findAllByEventId(eventId, page) 69 | .stream() 70 | .map(CommentMapper::toCommentDto) 71 | .collect(Collectors.toList()); 72 | } 73 | 74 | @Override 75 | public void deleteCommentById(long commentId) { 76 | commentRepository.deleteById(commentId); 77 | } 78 | 79 | private Pageable getPageable(int from, int size) { 80 | return PageRequest.of(from / size, size, Sort.by("id").ascending()); 81 | } 82 | 83 | private void patchComment(Comment comment, CommentPatchDto commentPatchDto) { 84 | if (commentPatchDto != null) { 85 | comment.setText(commentPatchDto.getText()); 86 | } 87 | comment.setEditedOn(LocalDateTime.now()); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/compilation/controller/CompilationAdminController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.compilation.controller; 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.compilation.data.CompilationDto; 9 | import ru.practicum.compilation.data.dto.NewCompilationDto; 10 | import ru.practicum.compilation.data.dto.UpdateCompilationRequest; 11 | import ru.practicum.compilation.service.CompilationService; 12 | 13 | import javax.validation.Valid; 14 | import javax.validation.constraints.Positive; 15 | import java.time.LocalDateTime; 16 | 17 | @RestController 18 | @RequestMapping("/admin/compilations") 19 | @RequiredArgsConstructor 20 | @Slf4j 21 | @Validated 22 | public class CompilationAdminController { 23 | private final CompilationService compilationService; 24 | 25 | @PostMapping 26 | @ResponseStatus(HttpStatus.CREATED) 27 | public CompilationDto createCompilation(@RequestBody @Valid NewCompilationDto newCompilationDto) { 28 | CompilationDto compilationDto = compilationService.createCompilation(newCompilationDto); 29 | log.info("{}: compilation created: {}", LocalDateTime.now(), compilationDto.toString()); 30 | return compilationDto; 31 | } 32 | 33 | @DeleteMapping("/{compId}") 34 | @ResponseStatus(HttpStatus.NO_CONTENT) 35 | public void deleteCompilation(@PathVariable @Positive long compId) { 36 | compilationService.deleteCompilation(compId); 37 | log.info("{}: compilation with id={} was deleted.", LocalDateTime.now(), compId); 38 | } 39 | 40 | @PatchMapping("/{compId}") 41 | public CompilationDto patchCompilation(@PathVariable @Positive long compId, @RequestBody @Valid UpdateCompilationRequest updateCompilationRequest) { 42 | CompilationDto compilationDto = compilationService.patchCompilation(compId, updateCompilationRequest); 43 | log.info("{}: compilation updated {}", LocalDateTime.now(), compilationDto); 44 | return compilationDto; 45 | } 46 | 47 | 48 | } 49 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/compilation/controller/CompilationPublicController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.compilation.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.*; 7 | import ru.practicum.compilation.data.CompilationDto; 8 | import ru.practicum.compilation.service.CompilationService; 9 | 10 | import javax.validation.constraints.Positive; 11 | import java.time.LocalDateTime; 12 | import java.util.List; 13 | 14 | @RestController 15 | @RequestMapping("/compilations") 16 | @RequiredArgsConstructor 17 | @Slf4j 18 | @Validated 19 | public class CompilationPublicController { 20 | private final CompilationService compilationService; 21 | 22 | @GetMapping 23 | public List getCompilationsByParams(@RequestParam(required = false) Boolean pinned, @RequestParam(defaultValue = "0") int from, @RequestParam(defaultValue = "10") int size) { 24 | List compilations = compilationService.getCompilationsByParams(pinned, from, size); 25 | log.info("{}: compilations were returned: {}", LocalDateTime.now(), compilations.toString()); 26 | return compilations; 27 | } 28 | 29 | @GetMapping("/{compId}") 30 | public CompilationDto getCompilationById(@PathVariable @Positive long compId) { 31 | CompilationDto compilationDto = compilationService.getCompilationById(compId); 32 | log.info("{}: compilation returned: {}", LocalDateTime.now(), compilationDto); 33 | return compilationDto; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/compilation/data/Compilation.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.compilation.data; 2 | 3 | import lombok.*; 4 | import ru.practicum.event.data.Event; 5 | 6 | import javax.persistence.*; 7 | import java.util.List; 8 | 9 | @Entity 10 | @Table(name = "compilations") 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | @Builder 14 | @Getter 15 | @Setter 16 | @ToString 17 | public class Compilation { 18 | @Id 19 | @GeneratedValue(strategy = GenerationType.IDENTITY) 20 | private long id; 21 | @Column(name = "title") 22 | private String title; 23 | @Column(name = "pinned") 24 | private boolean pinned; 25 | @ManyToMany 26 | @JoinTable(name = "compilation_event", 27 | joinColumns = @JoinColumn(name = "compilation_id"), 28 | inverseJoinColumns = @JoinColumn(name = "event_id")) 29 | private List events; 30 | } 31 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/compilation/data/CompilationDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.compilation.data; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import ru.practicum.event.data.dto.EventShortDto; 8 | 9 | import java.util.List; 10 | 11 | @Data 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | @Builder 15 | public class CompilationDto { 16 | private long id; 17 | private String title; 18 | private boolean pinned; 19 | private List events; 20 | } -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/compilation/data/CompilationMapper.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.compilation.data; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.NoArgsConstructor; 5 | import ru.practicum.compilation.data.dto.NewCompilationDto; 6 | import ru.practicum.compilation.data.dto.UpdateCompilationRequest; 7 | import ru.practicum.event.data.Event; 8 | import ru.practicum.event.data.dto.EventMapper; 9 | 10 | import java.util.List; 11 | import java.util.stream.Collectors; 12 | 13 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 14 | public class CompilationMapper { 15 | public static Compilation toCompilation(NewCompilationDto newCompilationDto, List events) { 16 | return Compilation.builder() 17 | .title(newCompilationDto.getTitle()) 18 | .pinned(newCompilationDto.isPinned()) 19 | .events(events) 20 | .build(); 21 | } 22 | 23 | public static Compilation toCompilation(UpdateCompilationRequest updateCompilationRequest, Compilation compilation, List events) { 24 | return Compilation.builder() 25 | .id(compilation.getId()) 26 | .title(updateCompilationRequest.getTitle() != null ? updateCompilationRequest.getTitle() : compilation.getTitle()) 27 | .pinned(updateCompilationRequest.getPinned() != null ? updateCompilationRequest.getPinned() : compilation.isPinned()) 28 | .events(events) 29 | .build(); 30 | } 31 | 32 | public static CompilationDto toCompilationDto(Compilation compilation) { 33 | return CompilationDto.builder() 34 | .id(compilation.getId()) 35 | .title(compilation.getTitle()) 36 | .pinned(compilation.isPinned()) 37 | .events(compilation.getEvents() != null ? compilation.getEvents().stream().map(EventMapper::toEventShortDto).collect(Collectors.toList()) : null) 38 | .build(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/compilation/data/CompilationRepository.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.compilation.data; 2 | 3 | import org.springframework.data.domain.Pageable; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.List; 8 | 9 | @Repository 10 | public interface CompilationRepository extends JpaRepository { 11 | List findAllByPinned(boolean pinned, Pageable page); 12 | } 13 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/compilation/data/dto/NewCompilationDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.compilation.data.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.validation.constraints.NotBlank; 9 | import javax.validation.constraints.Size; 10 | import java.util.List; 11 | 12 | @Data 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | @Builder 16 | public class NewCompilationDto { 17 | @NotBlank 18 | @Size(min = 1, max = 50) 19 | private String title; 20 | private boolean pinned; 21 | private List events; 22 | } 23 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/compilation/data/dto/UpdateCompilationRequest.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.compilation.data.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.validation.constraints.Size; 8 | import java.util.List; 9 | 10 | @Data 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | public class UpdateCompilationRequest { 14 | @Size(min = 1, max = 50) 15 | private String title; 16 | private Boolean pinned; 17 | private List events; 18 | } 19 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/compilation/service/CompilationService.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.compilation.service; 2 | 3 | import ru.practicum.compilation.data.CompilationDto; 4 | import ru.practicum.compilation.data.dto.NewCompilationDto; 5 | import ru.practicum.compilation.data.dto.UpdateCompilationRequest; 6 | 7 | import java.util.List; 8 | 9 | public interface CompilationService { 10 | CompilationDto createCompilation(NewCompilationDto newCompilationDto); 11 | 12 | void deleteCompilation(long compId); 13 | 14 | CompilationDto patchCompilation(long compId, UpdateCompilationRequest updateCompilationRequest); 15 | 16 | List getCompilationsByParams(Boolean pinned, int from, int size); 17 | 18 | CompilationDto getCompilationById(long compId); 19 | } 20 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/compilation/service/CompilationServiceImpl.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.compilation.service; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 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.stereotype.Service; 9 | import org.springframework.transaction.annotation.Transactional; 10 | import ru.practicum.compilation.data.Compilation; 11 | import ru.practicum.compilation.data.CompilationDto; 12 | import ru.practicum.compilation.data.CompilationMapper; 13 | import ru.practicum.compilation.data.CompilationRepository; 14 | import ru.practicum.compilation.data.dto.NewCompilationDto; 15 | import ru.practicum.compilation.data.dto.UpdateCompilationRequest; 16 | import ru.practicum.event.data.Event; 17 | import ru.practicum.event.data.EventRepository; 18 | 19 | import java.util.List; 20 | import java.util.stream.Collectors; 21 | 22 | @Service 23 | @RequiredArgsConstructor 24 | @Slf4j 25 | public class CompilationServiceImpl implements CompilationService { 26 | private final CompilationRepository compilationRepository; 27 | private final EventRepository eventRepository; 28 | 29 | @Override 30 | @Transactional 31 | public CompilationDto createCompilation(NewCompilationDto newCompilationDto) { 32 | List events = newCompilationDto.getEvents() != null ? 33 | eventRepository.findAllByIdIn(newCompilationDto.getEvents()) : null; 34 | Compilation compilation = CompilationMapper.toCompilation(newCompilationDto, events); 35 | return CompilationMapper.toCompilationDto(compilationRepository.save(compilation)); 36 | } 37 | 38 | @Override 39 | public void deleteCompilation(long compId) { 40 | compilationRepository.deleteById(compId); 41 | } 42 | 43 | @Override 44 | public CompilationDto patchCompilation(long compId, UpdateCompilationRequest updateCompilationRequest) { 45 | Compilation compilation = compilationRepository.findById(compId).orElseThrow(); 46 | List events; 47 | if (updateCompilationRequest.getEvents() != null && updateCompilationRequest.getEvents().size() > 0) { 48 | events = eventRepository.findAllByIdIn(updateCompilationRequest.getEvents()); 49 | } else { 50 | events = compilation.getEvents(); 51 | } 52 | compilation = CompilationMapper.toCompilation(updateCompilationRequest, compilation, events); 53 | return CompilationMapper.toCompilationDto(compilationRepository.save(compilation)); 54 | } 55 | 56 | @Override 57 | public List getCompilationsByParams(Boolean pinned, int from, int size) { 58 | List compilations = pinned != null ? 59 | compilationRepository.findAllByPinned(pinned, getPageable(from, size)) : 60 | compilationRepository.findAll(getPageable(from, size)).stream().collect(Collectors.toList()); 61 | return compilations.stream().map(CompilationMapper::toCompilationDto).collect(Collectors.toList()); 62 | } 63 | 64 | @Override 65 | public CompilationDto getCompilationById(long compId) { 66 | return CompilationMapper.toCompilationDto(compilationRepository.findById(compId).orElseThrow()); 67 | } 68 | 69 | private Pageable getPageable(int from, int size) { 70 | return PageRequest.of(from / size, size, Sort.by("id").ascending()); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/configuration/BeanConfiguration.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.configuration; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import ru.practicum.StatClient; 7 | 8 | @Configuration 9 | @RequiredArgsConstructor 10 | public class BeanConfiguration { 11 | 12 | @Bean 13 | StatClient createStatClient() { 14 | return new StatClient(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/event/controller/AdminEventController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.event.controller; 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.event.data.EventState; 9 | import ru.practicum.event.data.dto.EventFullDto; 10 | import ru.practicum.event.data.dto.SearchParameters; 11 | import ru.practicum.event.data.dto.UpdateEventAdminRequest; 12 | import ru.practicum.event.service.EventService; 13 | 14 | import javax.validation.Valid; 15 | import javax.validation.constraints.Positive; 16 | import javax.validation.constraints.PositiveOrZero; 17 | import java.time.LocalDateTime; 18 | import java.util.List; 19 | 20 | @RestController 21 | @RequestMapping("admin/events") 22 | @Slf4j 23 | @RequiredArgsConstructor 24 | @Validated 25 | public class AdminEventController { 26 | private final EventService eventService; 27 | 28 | @GetMapping 29 | List getAllEventWithParams(@RequestParam(required = false) List users, 30 | @RequestParam(required = false) List states, 31 | @RequestParam(required = false) List categories, 32 | @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime rangeStart, 33 | @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime rangeEnd, 34 | @RequestParam(defaultValue = "0") @PositiveOrZero int from, 35 | @RequestParam(defaultValue = "10") @Positive int size) { 36 | 37 | SearchParameters parameters = new SearchParameters() 38 | .setUsers(users) 39 | .setStates(states) 40 | .setCategories(categories) 41 | .setRangeStart(rangeStart) 42 | .setRangeEnd(rangeEnd) 43 | .setFrom(from) 44 | .setSize(size); 45 | 46 | List events = eventService.getAllEventWithParams(parameters); 47 | log.info(String.format("%s: events were returned: %s", LocalDateTime.now(), events.toString())); 48 | return events; 49 | } 50 | 51 | @PatchMapping("/{eventId}") 52 | public EventFullDto moderateEvent(@PathVariable @Positive long eventId, 53 | @RequestBody @Valid UpdateEventAdminRequest eventAdminRequest) { 54 | EventFullDto eventDto = eventService.moderateEvent(eventId, eventAdminRequest); 55 | log.info(String.format("%s: event was successfully moderated: %s", LocalDateTime.now(), eventDto.toString())); 56 | return eventDto; 57 | } 58 | } -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/event/controller/PrivateEventController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.event.controller; 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.event.data.dto.*; 8 | import ru.practicum.event.service.EventService; 9 | import ru.practicum.request.data.dto.ParticipationRequestDto; 10 | 11 | import javax.validation.Valid; 12 | import javax.validation.constraints.Positive; 13 | import java.time.LocalDateTime; 14 | import java.util.List; 15 | 16 | @RestController 17 | @RequestMapping("/users/{userId}/events") 18 | @Slf4j 19 | @RequiredArgsConstructor 20 | public class PrivateEventController { 21 | private final EventService eventService; 22 | 23 | @GetMapping 24 | public List getUserEvents(@PathVariable @Positive long userId, 25 | @RequestParam(defaultValue = "0") int from, 26 | @RequestParam(defaultValue = "10") int size) { 27 | List userEvents = eventService.getUserEvents(userId, from, size); 28 | log.info(String.format("%s: events by user id=%d were returned: %s", LocalDateTime.now(), userId, userEvents.toString())); 29 | return userEvents; 30 | } 31 | 32 | @PostMapping 33 | @ResponseStatus(HttpStatus.CREATED) 34 | public EventFullDto createEvent(@RequestBody @Valid NewEventDto newEventDto, @PathVariable @Positive long userId) { 35 | EventFullDto eventDto = eventService.createEvent(newEventDto, userId); 36 | log.info(String.format("%s: new event is created: %s", LocalDateTime.now(), eventDto.toString())); 37 | return eventDto; 38 | } 39 | 40 | @GetMapping("/{eventId}") 41 | public EventFullDto findUserEventById(@PathVariable @Positive long userId, @PathVariable @Positive long eventId) { 42 | EventFullDto eventDto = eventService.findUserEventById(userId, eventId); 43 | log.info(String.format("%s: event by id returned: %s", LocalDateTime.now(), eventDto.toString())); 44 | return eventDto; 45 | } 46 | 47 | @PatchMapping("/{eventId}") 48 | public EventFullDto patchEvent(@PathVariable @Positive long userId, 49 | @PathVariable @Positive long eventId, 50 | @RequestBody @Valid UpdateEventUserRequest updateRequest) { 51 | EventFullDto eventDto = eventService.patchEvent(userId, eventId, updateRequest); 52 | log.info(String.format("%s: event was successfully patched: %s", LocalDateTime.now(), eventDto.toString())); 53 | return eventDto; 54 | } 55 | 56 | @GetMapping("/{eventId}/requests") 57 | public List getAllEventRequests(@PathVariable @Positive long userId, @PathVariable @Positive long eventId) { 58 | List eventRequests = eventService.getAllEventRequests(userId, eventId); 59 | log.info(String.format("%s: all event requests returned: %s", LocalDateTime.now(), eventRequests.toString())); 60 | return eventRequests; 61 | } 62 | 63 | 64 | @PatchMapping("/{eventId}/requests") 65 | public EventRequestStatusUpdateResult updateRequestsStatus(@PathVariable @Positive long userId, 66 | @PathVariable @Positive long eventId, 67 | @RequestBody @Valid EventRequestStatusUpdateRequest updateRequest) { 68 | EventRequestStatusUpdateResult result = eventService.updateRequestsStatus(userId, eventId, updateRequest); 69 | log.info(String.format("%s: Requests status updated: %s", LocalDateTime.now(), result.toString())); 70 | return result; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/event/controller/PublicEventController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.event.controller; 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.event.data.dto.EventFullDto; 9 | import ru.practicum.event.data.dto.EventShortDto; 10 | import ru.practicum.event.data.dto.SearchParameters; 11 | import ru.practicum.event.data.dto.SortProperty; 12 | import ru.practicum.event.service.EventService; 13 | import ru.practicum.validation.DateTimeConsistency; 14 | 15 | import javax.servlet.http.HttpServletRequest; 16 | import javax.validation.constraints.Positive; 17 | import javax.validation.constraints.PositiveOrZero; 18 | import java.time.LocalDateTime; 19 | import java.util.List; 20 | 21 | @RestController 22 | @RequestMapping("/events") 23 | @RequiredArgsConstructor 24 | @Slf4j 25 | @Validated 26 | public class PublicEventController { 27 | 28 | private final EventService eventService; 29 | 30 | @GetMapping 31 | @DateTimeConsistency 32 | List getAllPublicEvents(@RequestParam(required = false) String text, 33 | @RequestParam(required = false) List categories, 34 | @RequestParam(required = false) Boolean paid, 35 | @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime rangeStart, 36 | @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime rangeEnd, 37 | @RequestParam(defaultValue = "false") boolean onlyAvailable, 38 | @RequestParam(required = false) SortProperty sortProperty, 39 | @RequestParam(defaultValue = "0") @PositiveOrZero int from, 40 | @RequestParam(defaultValue = "10") @Positive int size, 41 | HttpServletRequest request) { 42 | SearchParameters parameters = new SearchParameters() 43 | .setText(text) 44 | .setCategories(categories) 45 | .setPaid(paid) 46 | .setRangeStart(rangeStart) 47 | .setRangeEnd(rangeEnd) 48 | .setOnlyAvailable(onlyAvailable) 49 | .setSortProperty(sortProperty) 50 | .setFrom(from) 51 | .setSize(size); 52 | List events = eventService.getAllPublicEventsWithParams(parameters, request); 53 | log.info(String.format("%s: events are returned: %s", LocalDateTime.now(), events.toString())); 54 | return events; 55 | } 56 | 57 | @GetMapping("/{eventId}") 58 | EventFullDto getEventById(@PathVariable @Positive long eventId, HttpServletRequest request) { 59 | EventFullDto event = eventService.getEventById(eventId, request); 60 | log.info(String.format("%s: event is returned: %s", LocalDateTime.now(), event.toString())); 61 | return event; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/event/data/AdminEventStateAction.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.event.data; 2 | 3 | public enum AdminEventStateAction { 4 | PUBLISH_EVENT, 5 | REJECT_EVENT 6 | } 7 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/event/data/Event.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.event.data; 2 | 3 | import lombok.*; 4 | import ru.practicum.category.data.Category; 5 | import ru.practicum.user.data.User; 6 | 7 | import javax.persistence.*; 8 | import javax.validation.constraints.NotBlank; 9 | import javax.validation.constraints.NotNull; 10 | import javax.validation.constraints.PositiveOrZero; 11 | import java.time.LocalDateTime; 12 | 13 | @Entity 14 | @Table(name = "events") 15 | @Getter 16 | @Setter 17 | @NoArgsConstructor 18 | @AllArgsConstructor 19 | @Builder 20 | @ToString 21 | public class Event { 22 | @NotBlank 23 | private String annotation; 24 | @NotNull 25 | @ManyToOne 26 | private Category category; 27 | @NotNull 28 | private LocalDateTime createdOn; 29 | @NotBlank 30 | private String description; 31 | @NotNull 32 | private LocalDateTime eventDate; 33 | @Id 34 | @GeneratedValue(strategy = GenerationType.IDENTITY) 35 | private Long id; 36 | @NotNull 37 | @ManyToOne 38 | private User initiator; 39 | private float lat; 40 | private float lon; 41 | @NotNull 42 | private Boolean paid; 43 | @PositiveOrZero 44 | private int participantLimit; 45 | private LocalDateTime publishedOn; 46 | @NotNull 47 | private Boolean requestModeration; 48 | @NotNull 49 | @Enumerated(EnumType.STRING) 50 | private EventState state; 51 | @NotBlank 52 | private String title; 53 | } -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/event/data/EventRepository.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.event.data; 2 | 3 | import org.springframework.data.domain.Pageable; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.data.querydsl.QuerydslPredicateExecutor; 6 | import org.springframework.stereotype.Repository; 7 | 8 | import java.util.List; 9 | import java.util.Optional; 10 | 11 | @Repository 12 | public interface EventRepository extends JpaRepository, QuerydslPredicateExecutor { 13 | List findAllByInitiatorId(long initiatorId, Pageable page); 14 | 15 | Optional findByInitiatorIdAndId(long initiatorId, long eventId); 16 | 17 | Optional findByIdAndState(long eventId, EventState eventState); 18 | 19 | List findAllByIdIn(List eventIds); 20 | } 21 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/event/data/EventState.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.event.data; 2 | 3 | public enum EventState { 4 | PENDING, 5 | PUBLISHED, 6 | CANCELED 7 | } 8 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/event/data/Location.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.event.data; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | @Data 7 | @AllArgsConstructor 8 | public class Location { 9 | private float lat; 10 | private float lon; 11 | } 12 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/event/data/UserEventStateAction.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.event.data; 2 | 3 | public enum UserEventStateAction { 4 | SEND_TO_REVIEW, 5 | CANCEL_REVIEW 6 | } 7 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/event/data/dto/EventFullDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.event.data.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.*; 5 | import ru.practicum.category.data.dto.CategoryDto; 6 | import ru.practicum.event.data.EventState; 7 | import ru.practicum.event.data.Location; 8 | import ru.practicum.user.data.dto.UserShortDto; 9 | 10 | import javax.validation.constraints.NotBlank; 11 | import javax.validation.constraints.NotNull; 12 | import javax.validation.constraints.PositiveOrZero; 13 | import java.time.LocalDateTime; 14 | 15 | @Data 16 | @EqualsAndHashCode(callSuper = true) 17 | @NoArgsConstructor 18 | @AllArgsConstructor 19 | public class EventFullDto extends EventShortDto { 20 | @NotNull 21 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") 22 | private LocalDateTime createdOn; 23 | @NotBlank 24 | private String description; 25 | @NotNull 26 | private Location location; 27 | @PositiveOrZero 28 | private int participantLimit; 29 | @NotNull 30 | private LocalDateTime publishedOn; 31 | @NotNull 32 | private Boolean requestModeration; 33 | @NotNull 34 | private EventState state; 35 | 36 | @Builder(builderMethodName = "fullBuilder") 37 | public EventFullDto(String annotation, 38 | CategoryDto category, 39 | long confirmedRequests, 40 | LocalDateTime eventDate, long id, 41 | UserShortDto initiator, 42 | Boolean paid, 43 | String title, 44 | long views, 45 | long comments, 46 | LocalDateTime createdOn, 47 | String description, 48 | Location location, 49 | int participantLimit, 50 | LocalDateTime publishedOn, 51 | Boolean requestModeration, 52 | EventState state) { 53 | super(annotation, category, confirmedRequests, eventDate, id, initiator, paid, title, views, comments); 54 | this.createdOn = createdOn; 55 | this.description = description; 56 | this.location = location; 57 | this.participantLimit = participantLimit; 58 | this.publishedOn = publishedOn; 59 | this.requestModeration = requestModeration; 60 | this.state = state; 61 | } 62 | } 63 | 64 | 65 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/event/data/dto/EventMapper.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.event.data.dto; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.NoArgsConstructor; 5 | import ru.practicum.category.data.Category; 6 | import ru.practicum.category.data.dto.CategoryMapper; 7 | import ru.practicum.event.data.Event; 8 | import ru.practicum.event.data.Location; 9 | import ru.practicum.user.data.User; 10 | import ru.practicum.user.data.dto.UserMapper; 11 | 12 | import java.time.format.DateTimeFormatter; 13 | 14 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 15 | public class EventMapper { 16 | public static final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); 17 | 18 | public static EventShortDto toEventShortDto(Event event) { 19 | return EventShortDto.builder() 20 | .annotation(event.getAnnotation()) 21 | .category(CategoryMapper.toCategoryDto(event.getCategory())) 22 | .confirmedRequests(0) 23 | .eventDate(event.getEventDate()) 24 | .id(event.getId()) 25 | .initiator(UserMapper.toUserShortDto(event.getInitiator())) 26 | .paid(event.getPaid()) 27 | .title(event.getTitle()) 28 | .views(0) 29 | .build(); 30 | } 31 | 32 | public static EventShortDto toEventShortDto(Event event, long confirmedRequests, long views) { 33 | return EventShortDto.builder() 34 | .annotation(event.getAnnotation()) 35 | .category(CategoryMapper.toCategoryDto(event.getCategory())) 36 | .confirmedRequests(confirmedRequests) 37 | .eventDate(event.getEventDate()) 38 | .id(event.getId()) 39 | .initiator(UserMapper.toUserShortDto(event.getInitiator())) 40 | .paid(event.getPaid()) 41 | .title(event.getTitle()) 42 | .views(views) 43 | .build(); 44 | } 45 | 46 | public static EventFullDto toEventFullDto(Event event) { 47 | return EventFullDto 48 | .fullBuilder() 49 | .annotation(event.getAnnotation()) 50 | .category(CategoryMapper.toCategoryDto(event.getCategory())) 51 | .createdOn(event.getCreatedOn()) 52 | .description(event.getDescription()) 53 | .eventDate(event.getEventDate()) 54 | .id(event.getId()) 55 | .initiator(UserMapper.toUserShortDto(event.getInitiator())) 56 | .location(new Location(event.getLat(), event.getLon())) 57 | .paid(event.getPaid()) 58 | .participantLimit(event.getParticipantLimit()) 59 | .publishedOn(event.getPublishedOn()) 60 | .requestModeration(event.getRequestModeration()) 61 | .state(event.getState()) 62 | .title(event.getTitle()) 63 | .build(); 64 | } 65 | 66 | public static EventFullDto toEventFullDto(Event event, long confirmedRequests, long views, long comments) { 67 | return EventFullDto 68 | .fullBuilder() 69 | .annotation(event.getAnnotation()) 70 | .category(CategoryMapper.toCategoryDto(event.getCategory())) 71 | .confirmedRequests(confirmedRequests) 72 | .createdOn(event.getCreatedOn()) 73 | .description(event.getDescription()) 74 | .eventDate(event.getEventDate()) 75 | .id(event.getId()) 76 | .initiator(UserMapper.toUserShortDto(event.getInitiator())) 77 | .location(new Location(event.getLat(), event.getLon())) 78 | .paid(event.getPaid()) 79 | .participantLimit(event.getParticipantLimit()) 80 | .requestModeration(event.getRequestModeration()) 81 | .state(event.getState()) 82 | .title(event.getTitle()) 83 | .views(views) 84 | .comments(comments) 85 | .build(); 86 | } 87 | 88 | public static Event toEvent(NewEventDto newEventDto, User initiator, Category category) { 89 | return Event.builder() 90 | .annotation(newEventDto.getAnnotation()) 91 | .category(category) 92 | .createdOn(null) 93 | .description(newEventDto.getDescription()) 94 | .eventDate(newEventDto.getEventDate()) 95 | .initiator(initiator) 96 | .lat(newEventDto.getLocation().getLat()) 97 | .lon(newEventDto.getLocation().getLon()) 98 | .paid(newEventDto.isPaid()) 99 | .participantLimit(newEventDto.getParticipantLimit()) 100 | .requestModeration(newEventDto.isRequestModeration()) 101 | .state(null) 102 | .title(newEventDto.getTitle()) 103 | .build(); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/event/data/dto/EventRequestStatusUpdateRequest.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.event.data.dto; 2 | 3 | import lombok.Data; 4 | import ru.practicum.request.data.RequestStatus; 5 | 6 | import javax.validation.constraints.NotNull; 7 | import java.util.List; 8 | 9 | @Data 10 | public class EventRequestStatusUpdateRequest { 11 | @NotNull 12 | private List requestIds; 13 | @NotNull 14 | private RequestStatus status; 15 | } 16 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/event/data/dto/EventRequestStatusUpdateResult.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.event.data.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import ru.practicum.request.data.dto.ParticipationRequestDto; 6 | 7 | import java.util.List; 8 | 9 | @Data 10 | @AllArgsConstructor 11 | public class EventRequestStatusUpdateResult { 12 | List confirmedRequests; 13 | List rejectedRequests; 14 | } 15 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/event/data/dto/EventShortDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.event.data.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import ru.practicum.category.data.dto.CategoryDto; 9 | import ru.practicum.user.data.dto.UserShortDto; 10 | 11 | import javax.validation.constraints.NotBlank; 12 | import javax.validation.constraints.NotNull; 13 | import javax.validation.constraints.PositiveOrZero; 14 | import java.time.LocalDateTime; 15 | 16 | @Data 17 | @NoArgsConstructor 18 | @Builder 19 | @AllArgsConstructor 20 | public class EventShortDto { 21 | @NotBlank 22 | private String annotation; 23 | @NotNull 24 | private CategoryDto category; 25 | @PositiveOrZero 26 | private long confirmedRequests; 27 | @NotNull 28 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") 29 | private LocalDateTime eventDate; 30 | private long id; 31 | @NotNull 32 | private UserShortDto initiator; 33 | @NotNull 34 | private Boolean paid; 35 | @NotBlank 36 | private String title; 37 | @PositiveOrZero 38 | private long views; 39 | @PositiveOrZero 40 | private long comments; 41 | } 42 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/event/data/dto/NewEventDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.event.data.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import ru.practicum.event.data.Location; 7 | import ru.practicum.validation.InFuture; 8 | 9 | import javax.validation.constraints.*; 10 | import java.time.LocalDateTime; 11 | 12 | @Data 13 | @NoArgsConstructor 14 | public class NewEventDto { 15 | @NotBlank 16 | @Size(min = 20, max = 2000) 17 | private String annotation; 18 | @NotNull 19 | @Positive 20 | private Long category; 21 | @NotBlank 22 | @Size(min = 20, max = 7000) 23 | private String description; 24 | @NotNull 25 | @InFuture(2) 26 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") 27 | private LocalDateTime eventDate; 28 | @NotNull 29 | private Location location; 30 | private boolean paid = false; 31 | @PositiveOrZero 32 | private int participantLimit; 33 | private boolean requestModeration = true; 34 | @NotBlank 35 | @Size(min = 3, max = 120) 36 | private String title; 37 | } 38 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/event/data/dto/SearchParameters.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.event.data.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.ToString; 5 | import ru.practicum.event.data.EventState; 6 | 7 | import java.time.LocalDateTime; 8 | import java.util.List; 9 | 10 | @Getter 11 | @ToString 12 | public class SearchParameters { 13 | private String text; 14 | private List users; 15 | private List states; 16 | private List categories; 17 | private Boolean paid; 18 | private LocalDateTime rangeStart; 19 | private LocalDateTime rangeEnd; 20 | private Boolean onlyAvailable; 21 | private Integer from; 22 | private Integer size; 23 | private SortProperty sortProperty; 24 | 25 | 26 | public SearchParameters setSortProperty(SortProperty sortProperty) { 27 | this.sortProperty = sortProperty; 28 | return this; 29 | } 30 | 31 | public SearchParameters setUsers(List users) { 32 | this.users = users; 33 | return this; 34 | } 35 | 36 | public SearchParameters setText(String text) { 37 | this.text = text; 38 | return this; 39 | } 40 | 41 | public SearchParameters setPaid(Boolean paid) { 42 | this.paid = paid; 43 | return this; 44 | } 45 | 46 | public SearchParameters setOnlyAvailable(Boolean onlyAvailable) { 47 | this.onlyAvailable = onlyAvailable; 48 | return this; 49 | } 50 | 51 | public SearchParameters setStates(List states) { 52 | this.states = states; 53 | return this; 54 | } 55 | 56 | public SearchParameters setCategories(List categories) { 57 | this.categories = categories; 58 | return this; 59 | } 60 | 61 | public SearchParameters setRangeStart(LocalDateTime rangeStart) { 62 | this.rangeStart = rangeStart; 63 | return this; 64 | } 65 | 66 | public SearchParameters setRangeEnd(LocalDateTime rangeEnd) { 67 | this.rangeEnd = rangeEnd; 68 | return this; 69 | } 70 | 71 | public SearchParameters setFrom(int from) { 72 | this.from = from; 73 | return this; 74 | } 75 | 76 | public SearchParameters setSize(int size) { 77 | this.size = size; 78 | return this; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/event/data/dto/SortProperty.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.event.data.dto; 2 | 3 | public enum SortProperty { 4 | EVENT_DATE, 5 | VIEWS 6 | } 7 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/event/data/dto/UpdateEventAdminRequest.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.event.data.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.NoArgsConstructor; 7 | import ru.practicum.event.data.AdminEventStateAction; 8 | import ru.practicum.validation.InFuture; 9 | 10 | import java.time.LocalDateTime; 11 | 12 | @Data 13 | @EqualsAndHashCode(callSuper = true) 14 | @NoArgsConstructor 15 | public class UpdateEventAdminRequest extends UpdateEventRequest { 16 | private AdminEventStateAction stateAction; 17 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") 18 | @InFuture(1) 19 | private LocalDateTime eventDate; 20 | } -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/event/data/dto/UpdateEventRequest.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.event.data.dto; 2 | 3 | import lombok.Data; 4 | import lombok.NoArgsConstructor; 5 | import ru.practicum.event.data.Location; 6 | 7 | import javax.validation.constraints.Size; 8 | 9 | @Data 10 | @NoArgsConstructor 11 | public class UpdateEventRequest { 12 | @Size(min = 20, max = 2000) 13 | private String annotation; 14 | private Long category; 15 | @Size(min = 20, max = 7000) 16 | private String description; 17 | private Location location; 18 | private Boolean paid; 19 | private Integer participantLimit; 20 | private Boolean requestModeration; 21 | @Size(min = 3, max = 120) 22 | private String title; 23 | } 24 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/event/data/dto/UpdateEventUserRequest.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.event.data.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.NoArgsConstructor; 7 | import ru.practicum.event.data.UserEventStateAction; 8 | import ru.practicum.validation.InFuture; 9 | 10 | import java.time.LocalDateTime; 11 | 12 | @Data 13 | @EqualsAndHashCode(callSuper = true) 14 | @NoArgsConstructor 15 | public class UpdateEventUserRequest extends UpdateEventRequest { 16 | private UserEventStateAction stateAction; 17 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") 18 | @InFuture(2) 19 | private LocalDateTime eventDate; 20 | } 21 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/event/service/EventService.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.event.service; 2 | 3 | import ru.practicum.event.data.dto.*; 4 | import ru.practicum.request.data.dto.ParticipationRequestDto; 5 | 6 | import javax.servlet.http.HttpServletRequest; 7 | import java.util.List; 8 | 9 | public interface EventService { 10 | List getUserEvents(long userId, int from, int size); 11 | 12 | EventFullDto createEvent(NewEventDto newEventDto, long userId); 13 | 14 | EventFullDto findUserEventById(long userId, long eventId); 15 | 16 | EventFullDto patchEvent(long userId, long eventId, UpdateEventUserRequest updateRequest); 17 | 18 | EventFullDto moderateEvent(long eventId, UpdateEventAdminRequest eventAdminRequest); 19 | 20 | List getAllEventRequests(long userId, long eventId); 21 | 22 | EventRequestStatusUpdateResult updateRequestsStatus(long userId, long eventId, EventRequestStatusUpdateRequest updateRequest); 23 | 24 | List getAllEventWithParams(SearchParameters parameters); 25 | 26 | List getAllPublicEventsWithParams(SearchParameters parameters, HttpServletRequest request); 27 | 28 | EventFullDto getEventById(long eventId, HttpServletRequest request); 29 | } -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/event/service/EventServiceImpl.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.event.service; 2 | 3 | import com.querydsl.core.BooleanBuilder; 4 | import com.querydsl.core.types.Predicate; 5 | import com.querydsl.core.types.dsl.NumberExpression; 6 | import com.querydsl.jpa.impl.JPAQuery; 7 | import lombok.RequiredArgsConstructor; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.data.domain.PageRequest; 10 | import org.springframework.data.domain.Pageable; 11 | import org.springframework.data.domain.Sort; 12 | import org.springframework.stereotype.Service; 13 | import org.springframework.transaction.annotation.Transactional; 14 | import ru.practicum.StatClient; 15 | import ru.practicum.category.data.Category; 16 | import ru.practicum.category.data.CategoryRepository; 17 | import ru.practicum.comment.data.CommentRepository; 18 | import ru.practicum.comment.data.QComment; 19 | import ru.practicum.dto.ViewStats; 20 | import ru.practicum.event.data.*; 21 | import ru.practicum.event.data.dto.*; 22 | import ru.practicum.exception.ChangeEventStatusForbiddenException; 23 | import ru.practicum.exception.EventParticipationForbiddenException; 24 | import ru.practicum.exception.RequestNotValidException; 25 | import ru.practicum.exception.RequestStatusNotPendingException; 26 | import ru.practicum.request.data.QRequest; 27 | import ru.practicum.request.data.Request; 28 | import ru.practicum.request.data.RequestRepository; 29 | import ru.practicum.request.data.RequestStatus; 30 | import ru.practicum.request.data.dto.ParticipationRequestDto; 31 | import ru.practicum.request.data.dto.RequestMapper; 32 | import ru.practicum.user.data.User; 33 | import ru.practicum.user.data.UserRepository; 34 | 35 | import javax.persistence.EntityManager; 36 | import javax.servlet.http.HttpServletRequest; 37 | import java.time.LocalDateTime; 38 | import java.util.*; 39 | import java.util.stream.Collectors; 40 | 41 | @Service 42 | @RequiredArgsConstructor 43 | @Slf4j 44 | public class EventServiceImpl implements EventService { 45 | private final EventRepository eventRepository; 46 | private final UserRepository userRepository; 47 | private final CategoryRepository categoryRepository; 48 | private final RequestRepository requestRepository; 49 | private final CommentRepository commentRepository; 50 | private final EntityManager entityManager; 51 | private final StatClient statClient; 52 | 53 | 54 | @Override 55 | @Transactional 56 | public List getUserEvents(long userId, int from, int size) { 57 | Pageable page = PageRequest.of(from / size, size, Sort.by("id").ascending()); 58 | List events = eventRepository.findAllByInitiatorId(userId, page) 59 | .stream() 60 | .map(EventMapper::toEventShortDto) 61 | .collect(Collectors.toList()); 62 | fillEventListWithViews(events); 63 | fillEventListWithConfirmedRequests(events); 64 | fillEventListWithComments(events); 65 | return events; 66 | } 67 | 68 | @Override 69 | @Transactional 70 | public EventFullDto createEvent(NewEventDto newEventDto, long userId) { 71 | User initiator = userRepository.findById(userId).orElseThrow(); 72 | Category category = categoryRepository.findById(newEventDto.getCategory()).orElseThrow(); 73 | Event event = EventMapper.toEvent(newEventDto, initiator, category); 74 | event.setCreatedOn(LocalDateTime.now()); 75 | event.setState(EventState.PENDING); 76 | return EventMapper.toEventFullDto(eventRepository.save(event)); 77 | } 78 | 79 | @Override 80 | @Transactional 81 | public EventFullDto findUserEventById(long userId, long eventId) { 82 | return EventMapper.toEventFullDto( 83 | eventRepository.findByInitiatorIdAndId(userId, eventId).orElseThrow(), 84 | getConfirmedRequests(eventId), 85 | getSingleEventViews(eventId), 86 | getSingleEventComments(eventId)); 87 | } 88 | 89 | @Override 90 | @Transactional 91 | public EventFullDto patchEvent(long userId, long eventId, UpdateEventUserRequest updateRequest) { 92 | Event event = eventRepository.findByInitiatorIdAndId(userId, eventId).orElseThrow(); 93 | patchEventState(event, updateRequest); 94 | patchEventDate(event, updateRequest); 95 | patchEventFields(event, updateRequest); 96 | return EventMapper.toEventFullDto( 97 | eventRepository.save(event), 98 | getConfirmedRequests(eventId), 99 | getSingleEventViews(eventId), 100 | getSingleEventComments(eventId)); 101 | } 102 | 103 | @Override 104 | @Transactional 105 | public EventFullDto moderateEvent(long eventId, UpdateEventAdminRequest updateRequest) { 106 | Event event = eventRepository.findById(eventId).orElseThrow(); 107 | patchEventState(event, updateRequest); 108 | patchEventDate(event, updateRequest); 109 | patchEventFields(event, updateRequest); 110 | 111 | return EventMapper.toEventFullDto( 112 | eventRepository.save(event), 113 | getConfirmedRequests(eventId), 114 | getSingleEventViews(eventId), 115 | getSingleEventComments(eventId)); 116 | } 117 | 118 | @Override 119 | @Transactional 120 | public List getAllEventRequests(long userId, long eventId) { 121 | eventRepository.findByInitiatorIdAndId(userId, eventId).orElseThrow(); //just to validate event exists 122 | return requestRepository.findAllByEventId(eventId) 123 | .stream() 124 | .map(RequestMapper::toParticipationRequestDto) 125 | .collect(Collectors.toList()); 126 | } 127 | 128 | @Override 129 | @Transactional 130 | public EventRequestStatusUpdateResult updateRequestsStatus(long userId, long eventId, EventRequestStatusUpdateRequest updateRequest) { 131 | Event event = eventRepository.findByInitiatorIdAndId(userId, eventId).orElseThrow(); 132 | List requestsToConfirm = requestRepository.findAllByIdIn(updateRequest.getRequestIds()); 133 | 134 | if (updateRequest.getStatus() == RequestStatus.REJECTED) { 135 | return rejectAll(requestsToConfirm); 136 | } 137 | validateParticipationLimitNotReached(event); 138 | if (!event.getRequestModeration() || updateRequest.getStatus() == RequestStatus.CONFIRMED) { //if no pre-moderation required - return all as confirmed 139 | return confirmAllUntilLimitReached(event, requestsToConfirm); 140 | } 141 | throw new RequestNotValidException("Request status not valid."); 142 | } 143 | 144 | @Override 145 | @Transactional 146 | public List getAllEventWithParams(SearchParameters parameters) { 147 | Predicate predicate = getCommonPredicate(parameters); 148 | Pageable page = getPageable(parameters.getFrom(), parameters.getSize()); 149 | List events = eventRepository.findAll(predicate, page) 150 | .stream() 151 | .map(EventMapper::toEventFullDto) 152 | .collect(Collectors.toList()); 153 | fillEventListWithConfirmedRequests(events); 154 | fillEventListWithViews(events); 155 | fillEventListWithComments(events); 156 | return events; 157 | } 158 | 159 | @Override 160 | @Transactional 161 | public List getAllPublicEventsWithParams(SearchParameters parameters, HttpServletRequest request) { 162 | Pageable page = getPageable(parameters.getFrom(), parameters.getSize(), parameters.getSortProperty()); 163 | JPAQuery query = new JPAQuery(entityManager); 164 | statClient.saveEndpointHit("ewm-main-service", "/events", request.getRemoteAddr(), LocalDateTime.now().format(EventMapper.dateTimeFormatter)); 165 | 166 | List events = eventRepository.findAll(getPublicPredicate(query, parameters), page) 167 | .stream() 168 | .map(EventMapper::toEventShortDto) 169 | .collect(Collectors.toList()); 170 | fillEventListWithConfirmedRequests(events); 171 | fillEventListWithViews(events); 172 | fillEventListWithComments(events); 173 | if (parameters.getSortProperty() == SortProperty.VIEWS) { 174 | events.sort(Comparator.comparingLong(EventShortDto::getViews)); 175 | } 176 | return events; 177 | } 178 | 179 | @Override 180 | @Transactional 181 | public EventFullDto getEventById(long eventId, HttpServletRequest request) { 182 | Event event = eventRepository.findByIdAndState(eventId, EventState.PUBLISHED).orElseThrow(); 183 | statClient.saveEndpointHit("ewm-main-service", String.format("/events/%d", eventId), request.getRemoteAddr(), LocalDateTime.now().format(EventMapper.dateTimeFormatter)); 184 | return EventMapper.toEventFullDto( 185 | event, 186 | getConfirmedRequests(eventId), 187 | getSingleEventViews(eventId), 188 | getSingleEventComments(eventId)); 189 | } 190 | 191 | private Event patchEventFields(Event event, UpdateEventRequest updateRequest) { 192 | if (updateRequest.getAnnotation() != null) { 193 | event.setAnnotation(updateRequest.getAnnotation()); 194 | } 195 | if (updateRequest.getCategory() != null) { 196 | Category category = categoryRepository.findById(updateRequest.getCategory()).orElseThrow(); 197 | event.setCategory(category); 198 | } 199 | if (updateRequest.getDescription() != null) { 200 | event.setDescription(updateRequest.getDescription()); 201 | } 202 | if (updateRequest.getLocation() != null) { 203 | event.setLat(updateRequest.getLocation().getLat()); 204 | event.setLon(updateRequest.getLocation().getLon()); 205 | } 206 | if (updateRequest.getPaid() != null) { 207 | event.setPaid(updateRequest.getPaid()); 208 | } 209 | if (updateRequest.getParticipantLimit() != null) { 210 | event.setParticipantLimit(updateRequest.getParticipantLimit()); 211 | } 212 | if (updateRequest.getRequestModeration() != null) { 213 | event.setRequestModeration(updateRequest.getRequestModeration()); 214 | } 215 | if (updateRequest.getTitle() != null) { 216 | event.setTitle(updateRequest.getTitle()); 217 | } 218 | return event; 219 | } 220 | 221 | private void patchEventDate(Event event, UpdateEventUserRequest updateRequest) { 222 | if (updateRequest.getEventDate() != null) { 223 | event.setEventDate(updateRequest.getEventDate()); 224 | } 225 | } 226 | 227 | private void patchEventDate(Event event, UpdateEventAdminRequest updateRequest) { 228 | if (event.getEventDate().isBefore(LocalDateTime.now().plusHours(1))) { 229 | throw new ChangeEventStatusForbiddenException("Event will start within 1 hour."); 230 | } 231 | if (updateRequest.getEventDate() != null) { 232 | event.setEventDate(updateRequest.getEventDate()); 233 | } 234 | } 235 | 236 | private void patchEventState(Event event, UpdateEventUserRequest updateRequest) { 237 | EventState eventState = event.getState(); 238 | if (eventState == EventState.PUBLISHED) { 239 | throw new ChangeEventStatusForbiddenException("Event is already published."); 240 | } 241 | UserEventStateAction stateAction; 242 | if ((stateAction = updateRequest.getStateAction()) != null) { 243 | event.setState(determineEventState(eventState, stateAction)); 244 | } 245 | } 246 | 247 | private void patchEventState(Event event, UpdateEventAdminRequest updateRequest) { 248 | EventState eventState = event.getState(); 249 | AdminEventStateAction stateAction; 250 | if ((stateAction = updateRequest.getStateAction()) != null) { 251 | event.setState(determineEventState(eventState, stateAction)); 252 | } 253 | if (stateAction == AdminEventStateAction.PUBLISH_EVENT) { 254 | event.setPublishedOn(LocalDateTime.now()); //unreachable if event was published earlier (exception in snippet above) 255 | } 256 | } 257 | 258 | private EventState determineEventState(EventState eventState, UserEventStateAction stateAction) { 259 | switch (stateAction) { 260 | case SEND_TO_REVIEW: 261 | if (eventState == EventState.CANCELED || eventState == EventState.PENDING) { 262 | return EventState.PENDING; 263 | } 264 | break; 265 | case CANCEL_REVIEW: 266 | if (eventState == EventState.PENDING) { 267 | return EventState.CANCELED; 268 | } 269 | break; 270 | } 271 | return eventState; 272 | } 273 | 274 | private EventState determineEventState(EventState eventState, AdminEventStateAction stateAction) { 275 | switch (stateAction) { 276 | case PUBLISH_EVENT: 277 | if (eventState == EventState.PENDING) { 278 | return EventState.PUBLISHED; 279 | } else { 280 | throw new ChangeEventStatusForbiddenException("Event is already published or canceled."); 281 | } 282 | case REJECT_EVENT: 283 | if (eventState != EventState.PUBLISHED) { 284 | return EventState.CANCELED; 285 | } else { 286 | throw new ChangeEventStatusForbiddenException("Event is already published."); 287 | } 288 | default: 289 | return null; //unreachable 290 | } 291 | } 292 | 293 | private void validateParticipationLimitNotReached(Event event) { 294 | long confirmedRequestsCount = requestRepository.countByStatusAndEventId(RequestStatus.CONFIRMED, event.getId()); 295 | if (confirmedRequestsCount >= event.getParticipantLimit() && event.getParticipantLimit() != 0) { 296 | throw new EventParticipationForbiddenException("Event participation limit reached."); 297 | } 298 | } 299 | 300 | private EventRequestStatusUpdateResult confirmAllUntilLimitReached(Event event, List requestsToConfirm) { 301 | long confirmedRequestsCount = getConfirmedRequests(event.getId()); 302 | long participationLimit = event.getParticipantLimit(); 303 | int requestPointer = 0; 304 | List confirmedRequests = new ArrayList<>(); 305 | List rejectedRequests = new ArrayList<>(); 306 | 307 | while ((confirmedRequestsCount < participationLimit || participationLimit == 0) && requestPointer < requestsToConfirm.size()) { 308 | Request request = requestsToConfirm.get(requestPointer); 309 | validateRequestStatusIsPendingOrDefined(request, RequestStatus.CONFIRMED); 310 | request.setStatus(RequestStatus.CONFIRMED); 311 | confirmedRequests.add(request); 312 | requestPointer++; 313 | confirmedRequestsCount++; 314 | } 315 | while (requestPointer < requestsToConfirm.size()) { 316 | Request request = requestsToConfirm.get(requestPointer); 317 | validateRequestStatusIsPendingOrDefined(request, RequestStatus.REJECTED); 318 | request.setStatus(RequestStatus.REJECTED); 319 | rejectedRequests.add(request); 320 | } 321 | List requestsToSave = new ArrayList<>(); 322 | requestsToSave.addAll(confirmedRequests); 323 | requestsToSave.addAll(rejectedRequests); 324 | requestRepository.saveAll(requestsToSave); 325 | return new EventRequestStatusUpdateResult( 326 | confirmedRequests.stream().map(RequestMapper::toParticipationRequestDto).collect(Collectors.toList()), 327 | rejectedRequests.stream().map(RequestMapper::toParticipationRequestDto).collect(Collectors.toList())); 328 | } 329 | 330 | private EventRequestStatusUpdateResult rejectAll(List requestsToReject) { 331 | List rejectedRequests = new ArrayList<>(); 332 | 333 | for (Request request : requestsToReject) { 334 | validateRequestStatusIsPendingOrDefined(request, RequestStatus.REJECTED); 335 | request.setStatus(RequestStatus.REJECTED); 336 | rejectedRequests.add(request); 337 | } 338 | requestRepository.saveAll(rejectedRequests); 339 | return new EventRequestStatusUpdateResult( 340 | new ArrayList<>(), 341 | rejectedRequests.stream().map(RequestMapper::toParticipationRequestDto).collect(Collectors.toList())); 342 | } 343 | 344 | private void validateRequestStatusIsPendingOrDefined(Request request, RequestStatus status) { 345 | if (request.getStatus() != RequestStatus.PENDING && request.getStatus() != status) { 346 | throw new RequestStatusNotPendingException(String.format("Request status is %s", request.getStatus().name())); 347 | } 348 | } 349 | 350 | private BooleanBuilder getCommonPredicate(SearchParameters parameters) { 351 | QEvent event = QEvent.event; 352 | BooleanBuilder predicate = new BooleanBuilder(); 353 | 354 | if (parameters.getUsers() != null) { 355 | predicate.and(event.initiator.id.in(parameters.getUsers())); 356 | } 357 | if (parameters.getText() != null) { 358 | predicate.and(event.annotation.containsIgnoreCase(parameters.getText()).or(event.description.containsIgnoreCase(parameters.getText()))); 359 | } 360 | if (parameters.getPaid() != null) { 361 | predicate.and(event.paid.eq(parameters.getPaid())); 362 | } 363 | if (parameters.getStates() != null) { 364 | predicate.and(event.state.in(parameters.getStates())); 365 | } 366 | if (parameters.getCategories() != null) { 367 | predicate.and(event.category.id.in(parameters.getCategories())); 368 | } 369 | if (parameters.getRangeStart() != null) { 370 | predicate.and(event.eventDate.after(parameters.getRangeStart())); 371 | } 372 | if (parameters.getRangeEnd() != null) { 373 | predicate.and(event.eventDate.before(parameters.getRangeEnd())); 374 | } 375 | return predicate; 376 | } 377 | 378 | private BooleanBuilder getPublicPredicate(JPAQuery query, SearchParameters parameters) { 379 | BooleanBuilder predicate = getCommonPredicate(parameters); 380 | QEvent event = QEvent.event; 381 | 382 | if (parameters.getOnlyAvailable()) { 383 | predicate.and(event.id.in(getAvailableEventsIds(query))); 384 | } 385 | 386 | if (parameters.getRangeStart() == null) { 387 | predicate.and(event.eventDate.after(LocalDateTime.now())); 388 | } 389 | 390 | predicate.and(event.state.eq(EventState.PUBLISHED)); //to return only published events 391 | return predicate; 392 | } 393 | 394 | private Pageable getPageable(int from, int size) { 395 | return PageRequest.of(from / size, size, Sort.by("id").ascending()); 396 | } 397 | 398 | private Pageable getPageable(int from, int size, SortProperty sortProperty) { 399 | if (sortProperty == null || sortProperty == SortProperty.VIEWS) { 400 | return getPageable(from, size); 401 | } 402 | return PageRequest.of(from / size, size, Sort.by("event_date").ascending()); 403 | } 404 | 405 | List getAvailableEventsIds(JPAQuery query) { 406 | QRequest request = QRequest.request; 407 | NumberExpression id = request.event.id.as("id"); 408 | NumberExpression limit = request.event.participantLimit.as("limit"); 409 | NumberExpression confirmed = request.status.count().as("confirmed"); 410 | 411 | return query.select(id, limit, confirmed) //get all event ids with participation available 412 | .from(request) 413 | .where(request.status.eq(RequestStatus.CONFIRMED)) 414 | .groupBy(id) 415 | .having(limit.gt(confirmed)) 416 | .fetch() 417 | .stream() 418 | .map(tuple -> tuple.get(id)) 419 | .collect(Collectors.toList()); 420 | } 421 | 422 | private void fillEventListWithConfirmedRequests(List events) { 423 | HashMap eventsMap = new HashMap<>(); 424 | for (EventShortDto dto : events) { 425 | eventsMap.put(dto.getId(), dto); 426 | } 427 | 428 | List ids = events.stream().map(EventShortDto::getId).collect(Collectors.toList()); 429 | JPAQuery query = new JPAQuery(entityManager); 430 | QRequest request = QRequest.request; 431 | query 432 | .select(request.event.id, request.status.count()) 433 | .from(request) 434 | .where(request.event.id.in(ids).and(request.status.eq(RequestStatus.CONFIRMED))) 435 | .groupBy(request.event.id) 436 | .fetch() 437 | .forEach(tuple -> { 438 | long eventId = tuple.get(request.event.id); 439 | eventsMap.get(eventId).setConfirmedRequests(tuple.get(request.status.count())); 440 | }); 441 | } 442 | 443 | private long getConfirmedRequests(long eventId) { 444 | return requestRepository.countByStatusAndEventId(RequestStatus.CONFIRMED, eventId); 445 | } 446 | 447 | private void fillEventListWithComments(List events) { 448 | HashMap eventsMap = new HashMap<>(); 449 | for (EventShortDto dto : events) { 450 | eventsMap.put(dto.getId(), dto); 451 | } 452 | 453 | List ids = events.stream().map(EventShortDto::getId).collect(Collectors.toList()); 454 | 455 | JPAQuery query = new JPAQuery(entityManager); 456 | QComment comment = QComment.comment; 457 | query 458 | .select(comment.event.id, comment.id.count()) 459 | .from(comment) 460 | .where(comment.event.id.in(ids)) 461 | .groupBy(comment.event.id) 462 | .fetch() 463 | .forEach(tuple -> { 464 | long eventId = tuple.get(comment.event.id); 465 | eventsMap.get(eventId).setComments(tuple.get(comment.id.count())); 466 | }); 467 | } 468 | 469 | private long getSingleEventComments(long eventId) { 470 | return commentRepository.countByEventId(eventId); 471 | } 472 | 473 | private void fillEventListWithViews(List events) { 474 | List eventIds = events.stream().map(EventShortDto::getId).collect(Collectors.toList()); 475 | Map eventViewsMap = getEventViewsMap(eventIds); 476 | 477 | for (EventShortDto event : events) { 478 | Long views = eventViewsMap.get(event.getId()); 479 | event.setViews(views != null ? views : 0); 480 | } 481 | } 482 | 483 | private Map getEventViewsMap(List eventIds) { 484 | List uris = eventIds.stream().map(id -> String.format("/events/%d", id)).collect(Collectors.toList()); 485 | ViewStats[] viewStats = statClient.getStat(LocalDateTime.of(2020, 1, 1, 1, 1), LocalDateTime.now(), uris, true).getBody(); 486 | HashMap eventsViews = new HashMap<>(); 487 | for (ViewStats viewStat : viewStats) { 488 | long eventId = Long.parseLong(viewStat.getUri().split("/")[2]); 489 | eventsViews.put(eventId, viewStat.getHits()); 490 | } 491 | return eventsViews; 492 | } 493 | 494 | private long getSingleEventViews(long eventId) { 495 | return getSingleEventViews(eventId, null, null); 496 | } 497 | 498 | private long getSingleEventViews(long eventId, LocalDateTime start, LocalDateTime end) { 499 | start = start != null ? start : LocalDateTime.of(2020, 1, 1, 1, 1); 500 | end = end != null ? end : LocalDateTime.now(); 501 | ViewStats[] viewStats = statClient.getStat(start, end, List.of(String.format("/events/%d", eventId)), true).getBody(); 502 | return viewStats.length > 0 ? viewStats[0].getHits() : 0; 503 | } 504 | } -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/exception/ApiError.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.exception; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.time.LocalDateTime; 9 | import java.util.List; 10 | 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | @Data 14 | public class ApiError { 15 | private List errors; 16 | private String message; 17 | private String reason; 18 | private String status; 19 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") 20 | LocalDateTime timestamp; 21 | } -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/exception/ChangeEventStatusForbiddenException.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.exception; 2 | 3 | public class ChangeEventStatusForbiddenException extends RuntimeException { 4 | public ChangeEventStatusForbiddenException() { 5 | } 6 | 7 | public ChangeEventStatusForbiddenException(String message) { 8 | super(message); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/exception/EventParticipationForbiddenException.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.exception; 2 | 3 | public class EventParticipationForbiddenException extends RuntimeException { 4 | public EventParticipationForbiddenException() { 5 | } 6 | 7 | public EventParticipationForbiddenException(String message) { 8 | super(message); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/exception/RequestNotValidException.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.exception; 2 | 3 | public class RequestNotValidException extends RuntimeException { 4 | public RequestNotValidException() { 5 | } 6 | 7 | public RequestNotValidException(String message) { 8 | super(message); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/exception/RequestStatusNotPendingException.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.exception; 2 | 3 | public class RequestStatusNotPendingException extends RuntimeException { 4 | public RequestStatusNotPendingException() { 5 | } 6 | 7 | public RequestStatusNotPendingException(String message) { 8 | super(message); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/exception/controller/ExceptionController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.exception.controller; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.dao.DataIntegrityViolationException; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.web.bind.annotation.ExceptionHandler; 7 | import org.springframework.web.bind.annotation.ResponseStatus; 8 | import org.springframework.web.bind.annotation.RestControllerAdvice; 9 | import ru.practicum.exception.*; 10 | 11 | import javax.validation.ConstraintViolationException; 12 | import java.time.LocalDateTime; 13 | import java.util.NoSuchElementException; 14 | 15 | @RestControllerAdvice 16 | @Slf4j 17 | public class ExceptionController { 18 | 19 | @ExceptionHandler(NoSuchElementException.class) 20 | @ResponseStatus(HttpStatus.NOT_FOUND) 21 | public ApiError handle(NoSuchElementException exc) { 22 | return new ApiError( 23 | null, 24 | exc.getMessage(), 25 | "Entity wasn't created or was deleted", 26 | "404", 27 | LocalDateTime.now()); 28 | } 29 | 30 | @ExceptionHandler(DataIntegrityViolationException.class) 31 | @ResponseStatus(HttpStatus.CONFLICT) 32 | public ApiError handle(DataIntegrityViolationException exc) { 33 | return new ApiError( 34 | null, 35 | exc.getMessage(), 36 | "Entity already exists.", 37 | "409", 38 | LocalDateTime.now() 39 | ); 40 | } 41 | 42 | @ExceptionHandler(EventParticipationForbiddenException.class) 43 | @ResponseStatus(HttpStatus.CONFLICT) 44 | public ApiError handle(EventParticipationForbiddenException exc) { 45 | return new ApiError( 46 | null, 47 | exc.getMessage(), 48 | "Participation is forbidden.", 49 | "409", 50 | LocalDateTime.now() 51 | ); 52 | } 53 | 54 | @ExceptionHandler(ChangeEventStatusForbiddenException.class) 55 | @ResponseStatus(HttpStatus.CONFLICT) 56 | public ApiError handle(ChangeEventStatusForbiddenException exc) { 57 | return new ApiError( 58 | null, 59 | exc.getMessage(), 60 | "Changing event status is forbidden.", 61 | "409", 62 | LocalDateTime.now() 63 | ); 64 | } 65 | 66 | 67 | @ExceptionHandler(RequestStatusNotPendingException.class) 68 | @ResponseStatus(HttpStatus.CONFLICT) 69 | public ApiError handle(RequestStatusNotPendingException exc) { 70 | return new ApiError( 71 | null, 72 | exc.getMessage(), 73 | "Changing event status is forbidden.", 74 | "409", 75 | LocalDateTime.now() 76 | ); 77 | } 78 | 79 | 80 | @ExceptionHandler(RequestNotValidException.class) 81 | @ResponseStatus(HttpStatus.BAD_REQUEST) 82 | public ApiError handle(RequestNotValidException exc) { 83 | return new ApiError( 84 | null, 85 | exc.getMessage(), 86 | "Request not valid.", 87 | "400", 88 | LocalDateTime.now() 89 | ); 90 | } 91 | 92 | @ExceptionHandler(ConstraintViolationException.class) 93 | @ResponseStatus(HttpStatus.BAD_REQUEST) 94 | public ApiError handle(ConstraintViolationException exc) { 95 | return new ApiError( 96 | null, 97 | exc.getMessage(), 98 | "Request not valid.", 99 | "400", 100 | LocalDateTime.now() 101 | ); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/request/controller/PrivateRequestController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.request.controller; 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.request.data.dto.ParticipationRequestDto; 8 | import ru.practicum.request.service.RequestService; 9 | 10 | import javax.validation.constraints.Positive; 11 | import java.time.LocalDateTime; 12 | import java.util.List; 13 | 14 | @RestController 15 | @RequestMapping("users/{userId}/requests") 16 | @Slf4j 17 | @RequiredArgsConstructor 18 | public class PrivateRequestController { 19 | private final RequestService requestService; 20 | 21 | @GetMapping 22 | public List getAllUserRequests(@PathVariable @Positive long userId) { 23 | List userRequests = requestService.getAllUserRequests(userId); 24 | log.info(String.format("%s: user requests are returned: %s", LocalDateTime.now(), userRequests.toString())); 25 | return userRequests; 26 | } 27 | 28 | @PostMapping 29 | @ResponseStatus(HttpStatus.CREATED) 30 | public ParticipationRequestDto createRequest(@PathVariable @Positive long userId, @RequestParam @Positive long eventId) { 31 | ParticipationRequestDto requestDto = requestService.createRequest(userId, eventId); 32 | log.info(String.format("%s: request successfully created: %s", LocalDateTime.now(), requestDto.toString())); 33 | return requestDto; 34 | } 35 | 36 | @PatchMapping("/{requestId}/cancel") 37 | public ParticipationRequestDto cancelRequest(@PathVariable @Positive long userId, @PathVariable @Positive long requestId) { 38 | ParticipationRequestDto participationDto = requestService.cancelRequest(userId, requestId); 39 | log.info(String.format("%s: user request canceled: %s", LocalDateTime.now(), participationDto.toString())); 40 | return participationDto; 41 | } 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/request/data/Request.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.request.data; 2 | 3 | import lombok.*; 4 | import ru.practicum.event.data.Event; 5 | import ru.practicum.user.data.User; 6 | 7 | import javax.persistence.*; 8 | import javax.validation.constraints.NotNull; 9 | import java.time.LocalDateTime; 10 | 11 | @Entity 12 | @Table(name = "requests") 13 | @Getter 14 | @Setter 15 | @Builder 16 | @NoArgsConstructor 17 | @AllArgsConstructor 18 | @ToString 19 | public class Request { 20 | @Id 21 | @GeneratedValue(strategy = GenerationType.IDENTITY) 22 | private Long id; 23 | @NotNull 24 | @ManyToOne 25 | private User requestor; 26 | @NotNull 27 | @ManyToOne 28 | private Event event; 29 | @NotNull 30 | @Enumerated(EnumType.STRING) 31 | private RequestStatus status; 32 | @NotNull 33 | private LocalDateTime created; 34 | } 35 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/request/data/RequestRepository.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.request.data; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import org.springframework.stereotype.Repository; 5 | 6 | import java.util.List; 7 | import java.util.Optional; 8 | 9 | @Repository 10 | public interface RequestRepository extends JpaRepository { 11 | long countByStatusAndEventId(RequestStatus status, long eventId); 12 | 13 | List findAllByEventId(long eventId); 14 | 15 | List findAllByIdIn(List ids); 16 | 17 | List findAllByRequestorId(long userId); 18 | 19 | Optional getByIdAndRequestorId(long requestId, long userId); 20 | 21 | 22 | } 23 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/request/data/RequestStatus.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.request.data; 2 | 3 | public enum RequestStatus { 4 | PENDING, 5 | CANCELED, 6 | CONFIRMED, 7 | REJECTED 8 | } 9 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/request/data/dto/ParticipationRequestDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.request.data.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import ru.practicum.request.data.RequestStatus; 9 | 10 | import java.time.LocalDateTime; 11 | 12 | @Data 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | @Builder 16 | public class ParticipationRequestDto { 17 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") 18 | private LocalDateTime created; 19 | private long event; 20 | private long id; 21 | private long requester; 22 | private RequestStatus status; 23 | } 24 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/request/data/dto/RequestMapper.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.request.data.dto; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.NoArgsConstructor; 5 | import ru.practicum.request.data.Request; 6 | 7 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 8 | public class RequestMapper { 9 | 10 | public static ParticipationRequestDto toParticipationRequestDto(Request request) { 11 | return ParticipationRequestDto.builder() 12 | .id(request.getId()) 13 | .created(request.getCreated()) 14 | .event(request.getEvent().getId()) 15 | .requester(request.getRequestor().getId()) 16 | .status(request.getStatus()) 17 | .build(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/request/service/RequestService.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.request.service; 2 | 3 | import ru.practicum.request.data.dto.ParticipationRequestDto; 4 | 5 | import java.util.List; 6 | 7 | public interface RequestService { 8 | ParticipationRequestDto createRequest(long userId, long eventId); 9 | 10 | List getAllUserRequests(long userId); 11 | 12 | ParticipationRequestDto cancelRequest(long userId, long requestId); 13 | } 14 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/request/service/RequestServiceImpl.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.request.service; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.stereotype.Service; 5 | import org.springframework.transaction.annotation.Transactional; 6 | import ru.practicum.event.data.Event; 7 | import ru.practicum.event.data.EventRepository; 8 | import ru.practicum.event.data.EventState; 9 | import ru.practicum.exception.EventParticipationForbiddenException; 10 | import ru.practicum.request.data.Request; 11 | import ru.practicum.request.data.RequestRepository; 12 | import ru.practicum.request.data.RequestStatus; 13 | import ru.practicum.request.data.dto.ParticipationRequestDto; 14 | import ru.practicum.request.data.dto.RequestMapper; 15 | import ru.practicum.user.data.User; 16 | import ru.practicum.user.data.UserRepository; 17 | 18 | import java.time.LocalDateTime; 19 | import java.util.List; 20 | import java.util.stream.Collectors; 21 | 22 | @Service 23 | @RequiredArgsConstructor 24 | public class RequestServiceImpl implements RequestService { 25 | private final RequestRepository requestRepository; 26 | private final UserRepository userRepository; 27 | private final EventRepository eventRepository; 28 | 29 | @Override 30 | @Transactional 31 | public ParticipationRequestDto createRequest(long userId, long eventId) { 32 | User requestor = userRepository.findById(userId).orElseThrow(); 33 | Event event = eventRepository.findById(eventId).orElseThrow(); 34 | validateRequestorNotInitiator(event, requestor); 35 | validateEventIsPublished(event); 36 | validateParticipationLimitNotReached(event); 37 | Request request = Request.builder() 38 | .requestor(requestor) 39 | .event(event) 40 | .status(event.getRequestModeration() && event.getParticipantLimit() != 0 ? RequestStatus.PENDING : RequestStatus.CONFIRMED) 41 | .created(LocalDateTime.now()) 42 | .build(); 43 | return RequestMapper.toParticipationRequestDto(requestRepository.save(request)); 44 | } 45 | 46 | @Override 47 | @Transactional 48 | public List getAllUserRequests(long userId) { 49 | return requestRepository.findAllByRequestorId(userId) 50 | .stream() 51 | .map(RequestMapper::toParticipationRequestDto) 52 | .collect(Collectors.toList()); 53 | } 54 | 55 | @Override 56 | @Transactional 57 | public ParticipationRequestDto cancelRequest(long userId, long requestId) { 58 | Request request = requestRepository.getByIdAndRequestorId(requestId, userId).orElseThrow(); 59 | request.setStatus(RequestStatus.CANCELED); 60 | return RequestMapper.toParticipationRequestDto(requestRepository.save(request)); 61 | } 62 | 63 | private void validateRequestorNotInitiator(Event event, User requestor) { 64 | if (event.getInitiator().getId().equals(requestor.getId())) { 65 | throw new EventParticipationForbiddenException("Initiator can not be requestor himself."); 66 | } 67 | } 68 | 69 | private void validateEventIsPublished(Event event) { 70 | if (event.getState() != EventState.PUBLISHED) { 71 | throw new EventParticipationForbiddenException("Event is not published."); 72 | } 73 | } 74 | 75 | private void validateParticipationLimitNotReached(Event event) { 76 | long acceptedRequestsCount = requestRepository.countByStatusAndEventId(RequestStatus.CONFIRMED, event.getId()); 77 | if (acceptedRequestsCount >= event.getParticipantLimit() && event.getParticipantLimit() != 0) { 78 | throw new EventParticipationForbiddenException("Event participation limit reached."); 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/user/controller/UserAdminController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.user.controller; 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.user.data.dto.UserDto; 9 | import ru.practicum.user.service.UserServiceImpl; 10 | 11 | import javax.validation.Valid; 12 | import javax.validation.constraints.Positive; 13 | import javax.validation.constraints.PositiveOrZero; 14 | import java.time.LocalDateTime; 15 | import java.util.List; 16 | 17 | @RestController 18 | @RequestMapping("/admin/users") 19 | @Slf4j 20 | @RequiredArgsConstructor 21 | @Validated 22 | public class UserAdminController { 23 | private final UserServiceImpl userService; 24 | 25 | @PostMapping 26 | @ResponseStatus(HttpStatus.CREATED) 27 | public UserDto createUser(@RequestBody @Valid UserDto userDto) { 28 | UserDto dto = userService.createUser(userDto); 29 | log.info(String.format("%s: user was created: %s", LocalDateTime.now(), userDto)); 30 | return dto; 31 | } 32 | 33 | @GetMapping 34 | public List getUsers(@RequestParam(required = false) List ids, 35 | @RequestParam(defaultValue = "0") @PositiveOrZero int from, 36 | @RequestParam(defaultValue = "10") @Positive int size) { 37 | List users = userService.getUsers(ids, from, size); 38 | log.info(String.format("%s: users were found for request: ids: %s; from=%d; size=%d", LocalDateTime.now(), ids == null ? "null" : ids.toString(), from, size)); 39 | return users; 40 | } 41 | 42 | @DeleteMapping("/{userId}") 43 | @ResponseStatus(HttpStatus.NO_CONTENT) 44 | public void deleteUserById(@PathVariable @Positive long userId) { 45 | userService.deleteUserById(userId); 46 | log.info(String.format("%s: user was deleted by id: %d", LocalDateTime.now(), userId)); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/user/data/User.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.user.data; 2 | 3 | import lombok.*; 4 | 5 | import javax.persistence.*; 6 | import javax.validation.constraints.Email; 7 | import javax.validation.constraints.NotBlank; 8 | import javax.validation.constraints.NotNull; 9 | import javax.validation.constraints.Size; 10 | 11 | @Entity 12 | @Table(name = "users") 13 | @Getter 14 | @Setter 15 | @Builder 16 | @AllArgsConstructor 17 | @NoArgsConstructor 18 | @ToString 19 | public class User { 20 | @Id 21 | @GeneratedValue(strategy = GenerationType.IDENTITY) 22 | @Column(name = "id") 23 | private Long id; 24 | @Email 25 | @NotNull 26 | @Column(name = "email") 27 | @Size(min = 6, max = 254) 28 | private String email; 29 | @NotBlank 30 | @Column(name = "name") 31 | @Size(min = 2, max = 250) 32 | private String name; 33 | } 34 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/user/data/UserRepository.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.user.data; 2 | 3 | import org.springframework.data.domain.Pageable; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.List; 8 | 9 | @Repository 10 | public interface UserRepository extends JpaRepository { 11 | List findAllByIdIn(Iterable iterable, Pageable page); 12 | } 13 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/user/data/dto/UserDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.user.data.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.validation.constraints.Email; 8 | import javax.validation.constraints.NotBlank; 9 | import javax.validation.constraints.NotNull; 10 | import javax.validation.constraints.Size; 11 | 12 | @Data 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | public class UserDto { 16 | private Long id; 17 | @Email 18 | @NotNull 19 | @Size(min = 6, max = 254) 20 | private String email; 21 | @NotBlank 22 | @Size(min = 2, max = 250) 23 | private String name; 24 | } 25 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/user/data/dto/UserMapper.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.user.data.dto; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.NoArgsConstructor; 5 | import ru.practicum.user.data.User; 6 | 7 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 8 | public class UserMapper { 9 | public static UserDto toUserDto(User user) { 10 | return new UserDto(user.getId(), user.getEmail(), user.getName()); 11 | } 12 | 13 | public static User toUser(UserDto userDto) { 14 | return new User(userDto.getId(), userDto.getEmail(), userDto.getName()); 15 | } 16 | 17 | public static UserShortDto toUserShortDto(User user) { 18 | return new UserShortDto(user.getId(), user.getName()); 19 | } 20 | } -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/user/data/dto/UserShortDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.user.data.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.validation.constraints.NotBlank; 8 | import javax.validation.constraints.NotNull; 9 | import javax.validation.constraints.Size; 10 | 11 | @Data 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | public class UserShortDto { 15 | @NotNull 16 | private Long id; 17 | @NotBlank 18 | @Size(min = 2, max = 250) 19 | private String name; 20 | } 21 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/user/service/UserService.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.user.service; 2 | 3 | import ru.practicum.user.data.dto.UserDto; 4 | 5 | import java.util.List; 6 | 7 | public interface UserService { 8 | UserDto createUser(UserDto userDto); 9 | 10 | List getUsers(List ids, int from, int size); 11 | 12 | void deleteUserById(long userId); 13 | } 14 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/user/service/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.user.service; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.data.domain.PageRequest; 5 | import org.springframework.data.domain.Pageable; 6 | import org.springframework.data.domain.Sort; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.transaction.annotation.Transactional; 9 | import ru.practicum.user.data.User; 10 | import ru.practicum.user.data.UserRepository; 11 | import ru.practicum.user.data.dto.UserDto; 12 | import ru.practicum.user.data.dto.UserMapper; 13 | 14 | import java.util.List; 15 | import java.util.stream.Collectors; 16 | 17 | @Service 18 | @RequiredArgsConstructor 19 | public class UserServiceImpl implements UserService { 20 | private final UserRepository userRepository; 21 | 22 | @Override 23 | @Transactional 24 | public UserDto createUser(UserDto userDto) { 25 | User user = UserMapper.toUser(userDto); 26 | return UserMapper.toUserDto(userRepository.save(user)); 27 | } 28 | 29 | @Override 30 | @Transactional 31 | public List getUsers(List ids, int from, int size) { 32 | Pageable page = PageRequest.of(from / size, size, Sort.by("id").ascending()); 33 | return ids == null ? 34 | userRepository.findAll(page).stream().map(UserMapper::toUserDto).collect(Collectors.toList()) : 35 | userRepository.findAllByIdIn(ids, page).stream().map(UserMapper::toUserDto).collect(Collectors.toList()); 36 | } 37 | 38 | @Override 39 | @Transactional 40 | public void deleteUserById(long userId) { 41 | userRepository.deleteById(userId); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/validation/DateTimeConsistency.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.validation; 2 | 3 | import javax.validation.Constraint; 4 | import javax.validation.Payload; 5 | import java.lang.annotation.Documented; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.Target; 8 | 9 | import static java.lang.annotation.ElementType.CONSTRUCTOR; 10 | import static java.lang.annotation.ElementType.METHOD; 11 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 12 | 13 | @Constraint(validatedBy = DateTimeConsistencyValidator.class) 14 | @Target({METHOD, CONSTRUCTOR}) 15 | @Retention(RUNTIME) 16 | @Documented 17 | public @interface DateTimeConsistency { 18 | String message() default 19 | "End date must be after begin date"; 20 | 21 | Class[] groups() default {}; 22 | 23 | Class[] payload() default {}; 24 | } 25 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/validation/DateTimeConsistencyValidator.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.validation; 2 | 3 | import javax.validation.ConstraintValidator; 4 | import javax.validation.ConstraintValidatorContext; 5 | import javax.validation.constraintvalidation.SupportedValidationTarget; 6 | import javax.validation.constraintvalidation.ValidationTarget; 7 | import java.time.LocalDateTime; 8 | 9 | @SupportedValidationTarget(ValidationTarget.PARAMETERS) 10 | public class DateTimeConsistencyValidator implements ConstraintValidator { 11 | @Override 12 | public boolean isValid(Object[] value, ConstraintValidatorContext context) { 13 | if (value[3] == null || value[4] == null) { 14 | return true; 15 | } else { 16 | return ((LocalDateTime) value[4]).isAfter((LocalDateTime) value[3]); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/validation/InFuture.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.validation; 2 | 3 | import javax.validation.Constraint; 4 | import javax.validation.Payload; 5 | import java.lang.annotation.*; 6 | 7 | @Target(ElementType.FIELD) 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Documented 10 | @Constraint(validatedBy = InFutureValidator.class) 11 | public @interface InFuture { 12 | int value() default 0; 13 | 14 | String message() default "{Date should be after now with value offset}"; 15 | 16 | Class[] groups() default {}; 17 | 18 | Class[] payload() default {}; 19 | } 20 | -------------------------------------------------------------------------------- /ewm-service/src/main/java/ru/practicum/validation/InFutureValidator.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.validation; 2 | 3 | import javax.validation.ConstraintValidator; 4 | import javax.validation.ConstraintValidatorContext; 5 | import java.time.LocalDateTime; 6 | 7 | public class InFutureValidator implements ConstraintValidator { 8 | private int offset; 9 | 10 | @Override 11 | public void initialize(InFuture constraintAnnotation) { 12 | offset = constraintAnnotation.value(); 13 | } 14 | 15 | @Override 16 | public boolean isValid(LocalDateTime value, ConstraintValidatorContext context) { 17 | return value == null || value.isAfter(LocalDateTime.now().plusHours(offset)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ewm-service/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8080 2 | spring.jpa.hibernate.ddl-auto=none 3 | spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQL10Dialect 4 | spring.jpa.properties.hibernate.format_sql=true 5 | spring.sql.init.mode=always 6 | logging.level.org.springframework.orm.jpa=INFO 7 | logging.level.org.springframework.transaction=INFO 8 | logging.level.org.springframework.transaction.interceptor=TRACE 9 | logging.level.org.springframework.orm.jpa.JpaTransactionManager=DEBUG 10 | server.error.include-message=always 11 | 12 | stat.url=http://localhost:9090 13 | 14 | spring.datasource.url=jdbc:h2:mem:testdb 15 | spring.datasource.username=sa 16 | spring.datasource.password=password -------------------------------------------------------------------------------- /ewm-service/src/main/resources/schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS users ( 2 | id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 3 | email varchar(255) NOT NULL, 4 | name varchar(255) NOT NULL, 5 | UNIQUE (email, name) 6 | ); 7 | 8 | CREATE TABLE IF NOT EXISTS categories ( 9 | id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 10 | name varchar(255) NOT NULL UNIQUE 11 | ); 12 | 13 | CREATE TABLE IF NOT EXISTS events ( 14 | annotation varchar(2000) NOT NULL, 15 | category_id BIGINT REFERENCES categories(id) ON DELETE RESTRICT, 16 | created_on TIMESTAMP NOT NULL, 17 | description varchar(7000) NOT NULL, 18 | event_date TIMESTAMP NOT NULL, 19 | id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 20 | initiator_id BIGINT REFERENCES users(id) ON DELETE RESTRICT, 21 | lat float NOT NULL, 22 | lon float NOT NULL, 23 | paid boolean NOT NULL, 24 | participant_limit int NOT NULL, 25 | published_on TIMESTAMP, 26 | request_moderation boolean NOT NULL, 27 | state varchar(32), 28 | title varchar(120) 29 | ); 30 | 31 | CREATE TABLE IF NOT EXISTS requests( 32 | id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 33 | requestor_id BIGINT REFERENCES users(id) ON DELETE CASCADE, 34 | event_id BIGINT REFERENCES events(id) ON DELETE CASCADE, 35 | status varchar(64) NOT NULL, 36 | created TIMESTAMP NOT NULL, 37 | UNIQUE(requestor_id, event_id) 38 | ); 39 | 40 | CREATE TABLE IF NOT EXISTS compilations( 41 | id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 42 | title varchar(128) NOT NULL, 43 | pinned boolean NOT NULL 44 | ); 45 | 46 | CREATE TABLE IF NOT EXISTS compilation_event( 47 | compilation_id BIGINT NOT NULL, 48 | event_id BIGINT NOT NULL, 49 | PRIMARY KEY (compilation_id, event_id) 50 | ); 51 | 52 | CREATE TABLE IF NOT EXISTS comments( 53 | id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 54 | text varchar(300) NOT NULL, 55 | created TIMESTAMP NOT NULL, 56 | edited_on TIMESTAMP, 57 | commentator_id BIGINT REFERENCES users(id) ON DELETE CASCADE, 58 | event_id BIGINT REFERENCES events(id) ON DELETE CASCADE, 59 | UNIQUE(commentator_id, event_id) 60 | ); -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lombok.config: -------------------------------------------------------------------------------- 1 | config.stopBubbling = true 2 | lombok.anyconstructor.addconstructorproperties = false 3 | lombok.addLombokGeneratedAnnotation = true 4 | lombok.addSuppressWarnings = false -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | ewm-service 7 | stat 8 | 9 | 10 | 11 | org.springframework.boot 12 | spring-boot-starter-parent 13 | 2.7.5 14 | 15 | 16 | 17 | Explore With Me 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 | org.projectlombok 32 | lombok 33 | true 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | org.apache.maven.plugins 42 | maven-surefire-plugin 43 | 44 | 45 | test 46 | 47 | 48 | 49 | 50 | org.apache.maven.plugins 51 | maven-checkstyle-plugin 52 | 3.1.2 53 | 54 | checkstyle.xml 55 | true 56 | true 57 | true 58 | 59 | 60 | 61 | 62 | check 63 | 64 | compile 65 | 66 | 67 | 68 | 69 | com.puppycrawl.tools 70 | checkstyle 71 | 10.3 72 | 73 | 74 | 75 | 76 | com.github.spotbugs 77 | spotbugs-maven-plugin 78 | 4.7.0.0 79 | 80 | Max 81 | High 82 | 83 | 84 | 85 | 86 | check 87 | 88 | 89 | 90 | 91 | 92 | org.jacoco 93 | jacoco-maven-plugin 94 | 0.8.8 95 | 96 | file 97 | 98 | 99 | 100 | jacoco-initialize 101 | 102 | prepare-agent 103 | 104 | 105 | 106 | jacoco-check 107 | 108 | check 109 | 110 | 111 | 112 | 113 | BUNDLE 114 | 115 | 116 | INSTRUCTION 117 | COVEREDRATIO 118 | 0.01 119 | 120 | 121 | LINE 122 | COVEREDRATIO 123 | 0.2 124 | 125 | 126 | BRANCH 127 | COVEREDRATIO 128 | 0.2 129 | 130 | 131 | COMPLEXITY 132 | COVEREDRATIO 133 | 0.2 134 | 135 | 136 | METHOD 137 | COVEREDRATIO 138 | 0.2 139 | 140 | 141 | CLASS 142 | MISSEDCOUNT 143 | 1 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | jacoco-site 152 | install 153 | 154 | report 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | check 165 | 166 | 167 | 168 | org.apache.maven.plugins 169 | maven-checkstyle-plugin 170 | 171 | 172 | com.github.spotbugs 173 | spotbugs-maven-plugin 174 | 175 | 176 | 177 | 178 | 179 | 180 | com.github.spotbugs 181 | spotbugs-maven-plugin 182 | 183 | 184 | 185 | 186 | 187 | coverage 188 | 189 | 190 | 191 | org.jacoco 192 | jacoco-maven-plugin 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | -------------------------------------------------------------------------------- /stat/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | explore-with-me 7 | ru.practicum 8 | 0.0.1-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | stat 13 | pom 14 | 15 | stat-server 16 | stat-dto 17 | stat-client 18 | 19 | 20 | 21 | 11 22 | 11 23 | UTF-8 24 | 25 | 26 | -------------------------------------------------------------------------------- /stat/stat-client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | stat 7 | ru.practicum 8 | 0.0.1-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | stat-client 13 | 14 | 15 | 11 16 | 11 17 | UTF-8 18 | 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-web 24 | 25 | 26 | 27 | ru.practicum 28 | stat-dto 29 | 0.0.1-SNAPSHOT 30 | compile 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /stat/stat-client/src/main/java/ru/practicum/StatClient.java: -------------------------------------------------------------------------------- 1 | package ru.practicum; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.http.*; 5 | import org.springframework.web.client.RestTemplate; 6 | import ru.practicum.dto.EndpointHitDto; 7 | import ru.practicum.dto.ViewStats; 8 | 9 | import java.time.LocalDateTime; 10 | import java.time.format.DateTimeFormatter; 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | public class StatClient { 15 | public final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); 16 | private final RestTemplate restTemplate = new RestTemplate(); 17 | @Value("${stat.url}") 18 | private String statServerUrl; 19 | 20 | public void saveEndpointHit(String app, String uri, String ip, String timestamp) { 21 | EndpointHitDto dto = new EndpointHitDto(app, uri, ip, timestamp); 22 | HttpEntity httpEntity = new HttpEntity<>(dto, defaultHeaders()); 23 | restTemplate.exchange(statServerUrl + "/hit", HttpMethod.POST, httpEntity, Void.class); 24 | } 25 | 26 | public ResponseEntity getStat(LocalDateTime start, LocalDateTime end, List uris, boolean unique) { 27 | String endpointPath = buildURI(uris); 28 | Map parameters = Map.of("start", start.format(dateTimeFormatter), "end", end.format(dateTimeFormatter), "unique", unique); 29 | HttpEntity httpEntity = new HttpEntity<>(defaultHeaders()); 30 | return restTemplate.exchange(statServerUrl + endpointPath, HttpMethod.GET, httpEntity, ViewStats[].class, parameters); 31 | } 32 | 33 | private HttpHeaders defaultHeaders() { 34 | HttpHeaders httpHeaders = new HttpHeaders(); 35 | httpHeaders.setContentType(MediaType.APPLICATION_JSON); 36 | httpHeaders.setAccept(List.of(MediaType.APPLICATION_JSON)); 37 | return httpHeaders; 38 | } 39 | 40 | private String buildURI(List uris) { 41 | StringBuilder sb = new StringBuilder(); 42 | sb.append("/stats?start={start}&end={end}"); 43 | for (String uri : uris) { 44 | sb.append(String.format("&uris=%s", uri)); 45 | } 46 | sb.append("&unique={unique}"); 47 | return sb.toString(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /stat/stat-dto/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | stat 7 | ru.practicum 8 | 0.0.1-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | stat-dto 13 | 14 | 15 | 11 16 | 11 17 | UTF-8 18 | 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-validation 24 | 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-data-jpa 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /stat/stat-dto/src/main/java/ru/practicum/dto/EndpointHitDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | import javax.validation.constraints.NotBlank; 7 | import javax.validation.constraints.Pattern; 8 | 9 | @Data 10 | @AllArgsConstructor 11 | public class EndpointHitDto { 12 | @NotBlank 13 | private String app; 14 | @NotBlank 15 | private String uri; 16 | @NotBlank 17 | private String ip; 18 | @Pattern(regexp = 19 | "^((19|2[0-9])[0-9]{2})-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])" + 20 | "\\s([0-1][0-9]|[2][0-3]):([0-5][0-9]):([0-5][0-9])$") 21 | private String timestamp; 22 | } -------------------------------------------------------------------------------- /stat/stat-dto/src/main/java/ru/practicum/dto/ViewStats.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.validation.constraints.NotBlank; 9 | import javax.validation.constraints.PositiveOrZero; 10 | 11 | @Builder 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | @Data 15 | public class ViewStats { 16 | @NotBlank 17 | private String app; 18 | @NotBlank 19 | private String uri; 20 | @PositiveOrZero 21 | private long hits; 22 | } -------------------------------------------------------------------------------- /stat/stat-server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM amazoncorretto:11-alpine-jdk 2 | COPY target/*.jar stat-server.jar 3 | ENTRYPOINT ["java","-jar","/stat-server.jar"] -------------------------------------------------------------------------------- /stat/stat-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | stat 7 | ru.practicum 8 | 0.0.1-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | stat-server 13 | 0.0.1-SNAPSHOT 14 | 15 | 16 | 11 17 | 11 18 | UTF-8 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-web 25 | 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter-data-jpa 30 | 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-validation 35 | 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-actuator 40 | 41 | 42 | 43 | com.h2database 44 | h2 45 | runtime 46 | 47 | 48 | 49 | org.postgresql 50 | postgresql 51 | 52 | 53 | 54 | ru.practicum 55 | stat-dto 56 | 0.0.1-SNAPSHOT 57 | compile 58 | 59 | 60 | 61 | 62 | 63 | 64 | org.springframework.boot 65 | spring-boot-maven-plugin 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /stat/stat-server/src/main/java/ru/practicum/StatApplication.java: -------------------------------------------------------------------------------- 1 | package ru.practicum; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class StatApplication { 8 | public static void main(String[] args) { 9 | SpringApplication.run(StatApplication.class, args); 10 | } 11 | } -------------------------------------------------------------------------------- /stat/stat-server/src/main/java/ru/practicum/controller/ExceptionController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.controller; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.web.bind.annotation.ExceptionHandler; 7 | import org.springframework.web.bind.annotation.ResponseStatus; 8 | import org.springframework.web.bind.annotation.RestControllerAdvice; 9 | import ru.practicum.exception.DatesNotConsistentException; 10 | 11 | import java.io.PrintWriter; 12 | import java.io.StringWriter; 13 | 14 | @RestControllerAdvice 15 | @Slf4j 16 | public class ExceptionController { 17 | 18 | @ExceptionHandler(DatesNotConsistentException.class) 19 | @ResponseStatus(HttpStatus.BAD_REQUEST) 20 | public String handleException(DatesNotConsistentException exception) { 21 | return exception.getMessage(); 22 | } 23 | 24 | @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) 25 | public Error handleException(Exception exception) { 26 | log.error("Error", exception); 27 | StringWriter out = new StringWriter(); 28 | exception.printStackTrace(new PrintWriter(out)); 29 | String stackTrace = out.toString(); 30 | return new Error(exception.getMessage(), stackTrace); 31 | } 32 | 33 | @AllArgsConstructor 34 | private static class Error { 35 | private String message; 36 | private String stackTrace; 37 | } 38 | } -------------------------------------------------------------------------------- /stat/stat-server/src/main/java/ru/practicum/controller/StatController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.controller; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.format.annotation.DateTimeFormat; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.validation.annotation.Validated; 7 | import org.springframework.web.bind.annotation.*; 8 | import ru.practicum.dto.EndpointHitDto; 9 | import ru.practicum.dto.ViewStats; 10 | import ru.practicum.service.StatService; 11 | 12 | import javax.validation.Valid; 13 | import java.time.LocalDateTime; 14 | import java.util.List; 15 | 16 | @RestController 17 | @Validated 18 | @RequiredArgsConstructor 19 | public class StatController { 20 | 21 | private final StatService statService; 22 | 23 | @PostMapping("/hit") 24 | @ResponseStatus(HttpStatus.CREATED) 25 | public void postHit(@RequestBody @Valid EndpointHitDto endpointHitDto) { 26 | statService.postHit(endpointHitDto); 27 | } 28 | 29 | @GetMapping("/stats") 30 | public List getStat(@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime start, 31 | @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime end, 32 | @RequestParam(required = false) List uris, 33 | @RequestParam(defaultValue = "false") Boolean unique) { 34 | return statService.getStat(start, end, uris, unique); 35 | } 36 | } -------------------------------------------------------------------------------- /stat/stat-server/src/main/java/ru/practicum/entity/EndpointHit.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.entity; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | 6 | import javax.persistence.*; 7 | import javax.validation.constraints.NotBlank; 8 | import javax.validation.constraints.NotNull; 9 | import java.time.LocalDateTime; 10 | 11 | @Entity 12 | @Builder 13 | @Table(name = "endpoint_hits") 14 | @Getter 15 | public class EndpointHit { 16 | @Id 17 | @GeneratedValue(strategy = GenerationType.IDENTITY) 18 | Long id; 19 | @NotBlank 20 | @Column(name = "app") 21 | private String app; 22 | @NotBlank 23 | @Column(name = "uri") 24 | private String uri; 25 | @NotBlank 26 | @Column(name = "ip") 27 | private String ip; 28 | @NotNull 29 | @Column(name = "timestamp") 30 | private LocalDateTime timestamp; 31 | } -------------------------------------------------------------------------------- /stat/stat-server/src/main/java/ru/practicum/exception/DatesNotConsistentException.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.exception; 2 | 3 | public class DatesNotConsistentException extends RuntimeException { 4 | 5 | public DatesNotConsistentException() { 6 | } 7 | 8 | public DatesNotConsistentException(String message) { 9 | super(message); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /stat/stat-server/src/main/java/ru/practicum/mapper/EndpointHitMapper.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.mapper; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.NoArgsConstructor; 5 | import ru.practicum.dto.EndpointHitDto; 6 | import ru.practicum.entity.EndpointHit; 7 | 8 | import java.time.LocalDateTime; 9 | import java.time.format.DateTimeFormatter; 10 | 11 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 12 | public class EndpointHitMapper { 13 | public static final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); 14 | 15 | public static EndpointHit toEndpointHit(EndpointHitDto dto) { 16 | return EndpointHit.builder() 17 | .id(null) 18 | .app(dto.getApp()) 19 | .uri(dto.getUri()) 20 | .ip(dto.getIp()) 21 | .timestamp(LocalDateTime.parse(dto.getTimestamp(), dateTimeFormatter)) 22 | .build(); 23 | } 24 | } -------------------------------------------------------------------------------- /stat/stat-server/src/main/java/ru/practicum/repository/StatRepository.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import org.springframework.data.jpa.repository.Query; 5 | import org.springframework.stereotype.Repository; 6 | import ru.practicum.dto.ViewStats; 7 | import ru.practicum.entity.EndpointHit; 8 | 9 | import java.time.LocalDateTime; 10 | import java.util.List; 11 | 12 | @Repository 13 | public interface StatRepository extends JpaRepository { 14 | @Query("SELECT new ru.practicum.dto.ViewStats(eh.app, eh.uri, COUNT(DISTINCT eh.ip)) " + 15 | "FROM EndpointHit AS eh " + 16 | "WHERE eh.timestamp BETWEEN ?1 AND ?2 AND (eh.uri IN ?3 OR ?3 = null) " + 17 | "GROUP BY eh.app, eh.uri " + 18 | "ORDER BY COUNT(DISTINCT eh.ip) DESC") 19 | List findViewStatsByUniqueIP(LocalDateTime start, LocalDateTime end, List uris); 20 | 21 | @Query("SELECT new ru.practicum.dto.ViewStats(eh.app, eh.uri, COUNT(eh.ip)) " + 22 | "FROM EndpointHit as eh " + 23 | "WHERE eh.timestamp BETWEEN ?1 AND ?2 AND (eh.uri IN ?3 OR ?3 = null) " + 24 | "GROUP BY eh.app, eh.uri " + 25 | "ORDER BY COUNT(eh.ip) DESC") 26 | List findViewStats(LocalDateTime start, LocalDateTime end, List uris); 27 | } -------------------------------------------------------------------------------- /stat/stat-server/src/main/java/ru/practicum/service/StatService.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.service; 2 | 3 | import ru.practicum.dto.EndpointHitDto; 4 | import ru.practicum.dto.ViewStats; 5 | 6 | import java.time.LocalDateTime; 7 | import java.util.List; 8 | 9 | public interface StatService { 10 | void postHit(EndpointHitDto endpointHitDto); 11 | 12 | List getStat(LocalDateTime start, LocalDateTime end, List uris, boolean unique); 13 | } -------------------------------------------------------------------------------- /stat/stat-server/src/main/java/ru/practicum/service/StatServiceImpl.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.service; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.stereotype.Service; 6 | import ru.practicum.dto.EndpointHitDto; 7 | import ru.practicum.dto.ViewStats; 8 | import ru.practicum.exception.DatesNotConsistentException; 9 | import ru.practicum.mapper.EndpointHitMapper; 10 | import ru.practicum.repository.StatRepository; 11 | 12 | import java.time.LocalDateTime; 13 | import java.util.List; 14 | 15 | @Service 16 | @RequiredArgsConstructor 17 | @Slf4j 18 | public class StatServiceImpl implements StatService { 19 | private final StatRepository statRepository; 20 | 21 | @Override 22 | public void postHit(EndpointHitDto endpointHitDto) { 23 | statRepository.save(EndpointHitMapper.toEndpointHit(endpointHitDto)); 24 | log.info(String.format("Endpoint saved: %s", endpointHitDto)); 25 | } 26 | 27 | @Override 28 | public List getStat(LocalDateTime start, LocalDateTime end, List uris, boolean unique) { 29 | validateDates(start, end); 30 | List response = unique ? 31 | statRepository.findViewStatsByUniqueIP(start, end, uris) : 32 | statRepository.findViewStats(start, end, uris); 33 | log.info(String.format("Stat for request params is resolved: start = %s, end = %s, uris = %s, unique = %b", start, end, getUrisList(uris), unique)); 34 | return response; 35 | } 36 | 37 | private String getUrisList(List uris) { 38 | if (uris == null) { 39 | return "null"; 40 | } 41 | StringBuilder sb = new StringBuilder(); 42 | for (String uri : uris) { 43 | sb.append(uri).append("&"); 44 | } 45 | return sb.toString(); 46 | } 47 | 48 | private void validateDates(LocalDateTime start, LocalDateTime end) { 49 | if (start != null && end != null && start.isAfter(end)) { 50 | throw new DatesNotConsistentException("Start date must be before end date"); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /stat/stat-server/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=9090 2 | spring.jpa.hibernate.ddl-auto=none 3 | spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQL10Dialect 4 | spring.jpa.properties.hibernate.format_sql=true 5 | spring.sql.init.mode=always 6 | logging.level.org.springframework.orm.jpa=INFO 7 | logging.level.org.springframework.transaction=INFO 8 | logging.level.org.springframework.transaction.interceptor=TRACE 9 | logging.level.org.springframework.orm.jpa.JpaTransactionManager=DEBUG 10 | server.error.include-message=always 11 | #spring.datasource.driverClassName=org.postgresql.Driver 12 | #spring.datasource.url=jdbc:postgresql://${DB_HOST}:${DB_PORT}/${DB_NAME} 13 | #spring.datasource.username=${POSTGRES_USER} 14 | #spring.datasource.password=${POSTGRES_PASSWORD} 15 | #spring.datasource.driverClassName=org.h2.Driver 16 | spring.datasource.url=jdbc:h2:mem:testdb 17 | spring.datasource.username=sa 18 | spring.datasource.password=password 19 | #spring.jpa.database-platform=org.hibernate.dialect.H2Dialect -------------------------------------------------------------------------------- /stat/stat-server/src/main/resources/schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS endpoint_hits( 2 | id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 3 | app VARCHAR(127) NOT NULL, 4 | uri VARCHAR(255) NOT NULL, 5 | ip VARCHAR(255) NOT NULL, 6 | timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL 7 | ); -------------------------------------------------------------------------------- /suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | --------------------------------------------------------------------------------