├── .github └── workflows │ ├── api-tests.yml │ └── wait-for-it.sh ├── .gitignore ├── ER-diagram_EWM-SERVER.png ├── ER-diagram_EWM-STATS.png ├── README.md ├── checkstyle.xml ├── db ├── server.mv.db ├── server.trace.db ├── stats.mv.db └── stats.trace.db ├── docker-compose.yml ├── ewm-main-service-spec.json ├── ewm-stats-service-spec.json ├── lombok.config ├── pom.xml ├── server ├── Dockerfile ├── pom.xml └── src │ ├── main │ ├── java │ │ └── ru │ │ │ └── practicum │ │ │ └── explorewithme │ │ │ ├── ServerApp.java │ │ │ ├── client │ │ │ ├── BaseClient.java │ │ │ ├── EndpointHit.java │ │ │ └── StatsClient.java │ │ │ ├── controller │ │ │ ├── admin │ │ │ │ ├── CategoryAdminController.java │ │ │ │ ├── CommentAdminController.java │ │ │ │ ├── CompilationAdminController.java │ │ │ │ ├── EventAdminController.java │ │ │ │ └── UserAdminController.java │ │ │ ├── priv │ │ │ │ ├── CommentPrivateController.java │ │ │ │ ├── EventPrivateController.java │ │ │ │ └── RequestPrivateController.java │ │ │ └── pub │ │ │ │ ├── CategoriesPublicController.java │ │ │ │ ├── CommentPublicController.java │ │ │ │ ├── CompilationPublicController.java │ │ │ │ └── EventPublicController.java │ │ │ ├── dto │ │ │ ├── category │ │ │ │ ├── CategoryCreateDto.java │ │ │ │ └── CategoryDto.java │ │ │ ├── comment │ │ │ │ ├── CommentCreateDto.java │ │ │ │ ├── CommentDto.java │ │ │ │ └── CommentUpdateDto.java │ │ │ ├── compilation │ │ │ │ ├── CompilationCreateDto.java │ │ │ │ └── CompilationDto.java │ │ │ ├── event │ │ │ │ ├── EventAdminUpdate.java │ │ │ │ ├── EventCreateDto.java │ │ │ │ ├── EventFullDto.java │ │ │ │ ├── EventShortDto.java │ │ │ │ ├── EventUpdateDto.java │ │ │ │ └── LocationDto.java │ │ │ ├── request │ │ │ │ ├── RequestDto.java │ │ │ │ └── RequestStatus.java │ │ │ └── user │ │ │ │ ├── UserCreateDto.java │ │ │ │ ├── UserDto.java │ │ │ │ └── UserShortDto.java │ │ │ ├── exception │ │ │ ├── ApiError.java │ │ │ ├── ErrorHandler.java │ │ │ ├── EventSortException.java │ │ │ ├── EventStateException.java │ │ │ ├── ForbiddenException.java │ │ │ └── NotFoundException.java │ │ │ ├── mapper │ │ │ ├── CategoryMapper.java │ │ │ ├── CommentMapper.java │ │ │ ├── CompilationMapper.java │ │ │ ├── EventMapper.java │ │ │ ├── LocationMapper.java │ │ │ ├── RequestMapper.java │ │ │ └── UserMapper.java │ │ │ ├── model │ │ │ ├── category │ │ │ │ └── Category.java │ │ │ ├── comment │ │ │ │ └── Comment.java │ │ │ ├── compilation │ │ │ │ └── Compilation.java │ │ │ ├── event │ │ │ │ ├── Event.java │ │ │ │ ├── EventSort.java │ │ │ │ ├── EventState.java │ │ │ │ └── Location.java │ │ │ ├── request │ │ │ │ └── Request.java │ │ │ └── user │ │ │ │ └── User.java │ │ │ ├── repository │ │ │ ├── CategoryRepository.java │ │ │ ├── CommentRepository.java │ │ │ ├── CompilationRepository.java │ │ │ ├── EventRepository.java │ │ │ ├── RequestRepository.java │ │ │ └── UserRepository.java │ │ │ └── service │ │ │ ├── admin │ │ │ ├── CategoryAdminService.java │ │ │ ├── CommentAdminService.java │ │ │ ├── CompilationAdminService.java │ │ │ ├── EventAdminService.java │ │ │ ├── UserAdminService.java │ │ │ └── impl │ │ │ │ ├── CategoryAdminServiceImpl.java │ │ │ │ ├── CommentAdminServiceImpl.java │ │ │ │ ├── CompilationAdminServiceImpl.java │ │ │ │ ├── EventAdminServiceImpl.java │ │ │ │ └── UserAdminServiceImpl.java │ │ │ ├── priv │ │ │ ├── CommentPrivateService.java │ │ │ ├── EventPrivateService.java │ │ │ ├── RequestPrivateService.java │ │ │ └── impl │ │ │ │ ├── CommentPrivateServiceImpl.java │ │ │ │ ├── EventPrivateServiceImpl.java │ │ │ │ └── RequestPrivateServiceImpl.java │ │ │ └── pub │ │ │ ├── CategoriesPublicService.java │ │ │ ├── CommentPublicService.java │ │ │ ├── CompilationPublicService.java │ │ │ ├── EventPublicService.java │ │ │ └── impl │ │ │ ├── CategoriesPublicServiceImpl.java │ │ │ ├── CommentPublicServiceImpl.java │ │ │ ├── CompilationPublicServiceImpl.java │ │ │ └── EventPublicServiceImpl.java │ └── resources │ │ ├── application.properties │ │ └── schema.sql │ └── test │ └── java │ └── ru │ └── practicum │ └── explorewithme │ └── comment │ ├── CommentAdminServiceTest.java │ ├── CommentPrivateServiceTest.java │ └── CommentPublicServiceTest.java └── stats ├── Dockerfile ├── pom.xml └── src └── main ├── java └── ru │ └── practicum │ └── explorewithme │ ├── StatsApp.java │ ├── controller │ └── StatsController.java │ ├── dto │ ├── EndpointHitDto.java │ └── ViewStats.java │ ├── exception │ ├── ApiError.java │ └── ErrorHandler.java │ ├── mapper │ └── StatsMapper.java │ ├── model │ └── EndpointHit.java │ ├── repository │ └── StatsRepository.java │ └── service │ ├── StatsService.java │ └── StatsServiceImpl.java └── resources ├── application.properties └── schema.sql /.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 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### DB ### 8 | *.db 9 | 10 | ### STS ### 11 | .apt_generated 12 | .classpath 13 | .factorypath 14 | .project 15 | .settings 16 | .springBeans 17 | .sts4-cache 18 | 19 | ### IntelliJ IDEA ### 20 | .idea 21 | *.iws 22 | *.iml 23 | *.ipr 24 | 25 | ### NetBeans ### 26 | /nbproject/private/ 27 | /nbbuild/ 28 | /dist/ 29 | /nbdist/ 30 | /.nb-gradle/ 31 | build/ 32 | !**/src/main/**/build/ 33 | !**/src/test/**/build/ 34 | 35 | ### VS Code ### 36 | .vscode/ 37 | -------------------------------------------------------------------------------- /ER-diagram_EWM-SERVER.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Katibat/java-explore-with-me/5d7058ac5d9f135f7bbdb96e7f65d56635417c18/ER-diagram_EWM-SERVER.png -------------------------------------------------------------------------------- /ER-diagram_EWM-STATS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Katibat/java-explore-with-me/5d7058ac5d9f135f7bbdb96e7f65d56635417c18/ER-diagram_EWM-STATS.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## The project Explore-With-Me (programming language - Java): 2 | The application project is a poster where you can offer any event from an exhibition to going 3 | to the cinema and recruit a company to participate in it. 4 | Users can offer their events for publication in the service, as well as send applications for participation in 5 | other published events, confirm or reject other people's applications for participation in user's events. 6 | The service administrator makes decisions about publishing events and pinning collections of events 7 | on the main page of the service. 8 | Each user (including unauthorized ones) can receive information about published events. As a result, 9 | the service administrator collect statistics of event views by category. 10 | 11 | ## PR request link: 12 | https://github.com/Katibat/java-explore-with-me/pull/1#issue-1402033542 13 | 14 | ## Project structure: 15 | - Main service - [Swagger file](https://github.com/Katibat/java-explore-with-me/blob/main/ewm-main-service-spec.json) 16 | - Data Base main service - [ER-diagram](./ER_diagram_EWM-SERVER.png) 17 | - Stats service - [Swagger file](https://github.com/Katibat/java-explore-with-me/blob/main/ewm-stats-service-spec.json) 18 | - Data Base stats service - [ER-diagram](./ER_diagram_EWM-STATS.png) 19 | 20 | ## Technology used: 21 | Amazon Corretto 11.0.14, Spring-Boot 2.7.2, Spring-Web 5.3.22, Lombok 1.18.24, 22 | Postgresql 14, Driver PSQL 42.5.0, Driver H2 2.1.214, Docker-compose 3.8, 23 | GSON 2.9.0, HttpClient 5.0, Hibernate 6.2.3 24 | 25 | ## Project start should using Docker Dekstop: 26 | with script 27 | - for Window: 28 | %~dp0mvnw.cmd clean package -DskipTests 29 | docker-compose up --build 30 | - for Linux / MacOS: 31 | ./mvnw clean package -DskipTests 32 | docker-compose up --build -------------------------------------------------------------------------------- /db/server.mv.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Katibat/java-explore-with-me/5d7058ac5d9f135f7bbdb96e7f65d56635417c18/db/server.mv.db -------------------------------------------------------------------------------- /db/server.trace.db: -------------------------------------------------------------------------------- 1 | 2022-10-10 12:14:32 jdbc[3]: exception 2 | java.sql.SQLClientInfoException: Client info name 'ApplicationName' not supported. 3 | at org.h2.jdbc.JdbcConnection.setClientInfo(JdbcConnection.java:1749) 4 | at com.intellij.database.remote.jdbc.impl.RemoteConnectionImpl.setClientInfo(RemoteConnectionImpl.java:470) 5 | at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 6 | at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 7 | at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 8 | at java.base/java.lang.reflect.Method.invoke(Method.java:566) 9 | at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:359) 10 | at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200) 11 | at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197) 12 | at java.base/java.security.AccessController.doPrivileged(Native Method) 13 | at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196) 14 | at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:562) 15 | at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:796) 16 | at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:677) 17 | at java.base/java.security.AccessController.doPrivileged(Native Method) 18 | at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:676) 19 | at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) 20 | at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) 21 | at java.base/java.lang.Thread.run(Thread.java:829) 22 | 2022-10-10 12:14:44 jdbc[3]: exception 23 | java.sql.SQLClientInfoException: Client info name 'ApplicationName' not supported. 24 | at org.h2.jdbc.JdbcConnection.setClientInfo(JdbcConnection.java:1749) 25 | at com.intellij.database.remote.jdbc.impl.RemoteConnectionImpl.setClientInfo(RemoteConnectionImpl.java:470) 26 | at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 27 | at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 28 | at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 29 | at java.base/java.lang.reflect.Method.invoke(Method.java:566) 30 | at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:359) 31 | at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200) 32 | at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197) 33 | at java.base/java.security.AccessController.doPrivileged(Native Method) 34 | at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196) 35 | at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:562) 36 | at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:796) 37 | at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:677) 38 | at java.base/java.security.AccessController.doPrivileged(Native Method) 39 | at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:676) 40 | at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) 41 | at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) 42 | at java.base/java.lang.Thread.run(Thread.java:829) 43 | -------------------------------------------------------------------------------- /db/stats.mv.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Katibat/java-explore-with-me/5d7058ac5d9f135f7bbdb96e7f65d56635417c18/db/stats.mv.db -------------------------------------------------------------------------------- /db/stats.trace.db: -------------------------------------------------------------------------------- 1 | 2022-10-10 12:12:12 jdbc[3]: exception 2 | java.sql.SQLClientInfoException: Client info name 'ApplicationName' not supported. 3 | at org.h2.jdbc.JdbcConnection.setClientInfo(JdbcConnection.java:1749) 4 | at com.intellij.database.remote.jdbc.impl.RemoteConnectionImpl.setClientInfo(RemoteConnectionImpl.java:470) 5 | at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 6 | at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 7 | at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 8 | at java.base/java.lang.reflect.Method.invoke(Method.java:566) 9 | at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:359) 10 | at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200) 11 | at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197) 12 | at java.base/java.security.AccessController.doPrivileged(Native Method) 13 | at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196) 14 | at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:562) 15 | at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:796) 16 | at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:677) 17 | at java.base/java.security.AccessController.doPrivileged(Native Method) 18 | at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:676) 19 | at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) 20 | at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) 21 | at java.base/java.lang.Thread.run(Thread.java:829) 22 | 2022-10-10 12:12:24 jdbc[3]: exception 23 | java.sql.SQLClientInfoException: Client info name 'ApplicationName' not supported. 24 | at org.h2.jdbc.JdbcConnection.setClientInfo(JdbcConnection.java:1749) 25 | at com.intellij.database.remote.jdbc.impl.RemoteConnectionImpl.setClientInfo(RemoteConnectionImpl.java:470) 26 | at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 27 | at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 28 | at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 29 | at java.base/java.lang.reflect.Method.invoke(Method.java:566) 30 | at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:359) 31 | at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200) 32 | at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197) 33 | at java.base/java.security.AccessController.doPrivileged(Native Method) 34 | at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196) 35 | at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:562) 36 | at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:796) 37 | at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:677) 38 | at java.base/java.security.AccessController.doPrivileged(Native Method) 39 | at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:676) 40 | at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) 41 | at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) 42 | at java.base/java.lang.Thread.run(Thread.java:829) 43 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | services: 3 | 4 | stats-server: 5 | build: ./stats 6 | container_name: stats 7 | ports: 8 | - "9090:9090" 9 | depends_on: 10 | - stats-db 11 | environment: 12 | - SPRING_DATASOURCE_URL=jdbc:postgresql://stats-db:5432/stats 13 | - POSTGRES_USER=Kate 14 | - POSTGRES_PASSWORD=purrrrrr 15 | 16 | stats-db: 17 | image: postgres:14 18 | container_name: stats-db 19 | ports: 20 | - "5432:5432" 21 | environment: 22 | - POSTGRES_DB=stats 23 | - POSTGRES_USER=Kate 24 | - POSTGRES_PASSWORD=purrrrrr 25 | 26 | ewm-service: 27 | build: ./server 28 | container_name: ewm-service 29 | ports: 30 | - "8080:8080" 31 | depends_on: 32 | - ewm-db 33 | environment: 34 | - STATS_SERVER_URL=http://stats-server:9090 35 | - SPRING_DATASOURCE_URL=jdbc:postgresql://ewm-db:5432/server 36 | - POSTGRES_USER=Kate 37 | - POSTGRES_PASSWORD=purrrrrr 38 | 39 | ewm-db: 40 | image: postgres:14 41 | container_name: ewm-service-db 42 | ports: 43 | - "5433:5432" 44 | environment: 45 | - POSTGRES_DB=server 46 | - POSTGRES_USER=Kate 47 | - POSTGRES_PASSWORD=purrrrrr -------------------------------------------------------------------------------- /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 | "200": { 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 | }, 123 | "app": { 124 | "type": "string", 125 | "description": "Идентификатор сервиса для которого записывается информация" 126 | }, 127 | "uri": { 128 | "type": "string", 129 | "description": "URI для которого был осуществлен запрос " 130 | }, 131 | "ip": { 132 | "type": "string", 133 | "description": "IP-адрес пользователя, осуществившего запрос" 134 | }, 135 | "timestamp": { 136 | "type": "string", 137 | "description": "Дата и время, когда был совершен запрос к эндпоинту (в формате \"yyyy-MM-dd HH:mm:ss\")", 138 | "example": "2022-09-06 11:00:23" 139 | } 140 | } 141 | }, 142 | "ViewStats": { 143 | "type": "object", 144 | "properties": { 145 | "app": { 146 | "type": "string", 147 | "description": "Название сервиса" 148 | }, 149 | "uri": { 150 | "type": "string", 151 | "description": "URI сервиса" 152 | }, 153 | "hits": { 154 | "type": "integer", 155 | "description": "Количество просмотров", 156 | "format": "int64" 157 | } 158 | } 159 | } 160 | } 161 | } 162 | } -------------------------------------------------------------------------------- /lombok.config: -------------------------------------------------------------------------------- 1 | config.stopBubbling = true 2 | lombok.anyconstructor.addconstructorproperties = false 3 | lombok.addLombokGeneratedAnnotation = true 4 | lombok.addSuppressWarnings = false -------------------------------------------------------------------------------- /server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM amazoncorretto:11 2 | COPY target/*.jar app.jar 3 | ENTRYPOINT ["java","-jar","/app.jar"] -------------------------------------------------------------------------------- /server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | explore-with-me 7 | ru.practicum 8 | 0.0.1-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | server 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 | org.springframework.boot 27 | spring-boot-starter-data-jpa 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-validation 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-actuator 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-configuration-processor 40 | true 41 | 42 | 43 | org.postgresql 44 | postgresql 45 | 46 | 47 | com.h2database 48 | h2 49 | 50 | 51 | org.projectlombok 52 | lombok 53 | 54 | 55 | org.modelmapper 56 | modelmapper 57 | 58 | 59 | org.springframework.boot 60 | spring-boot-starter-test 61 | test 62 | 63 | 64 | junit 65 | junit 66 | test 67 | 68 | 69 | 70 | 71 | 72 | 73 | org.springframework.boot 74 | spring-boot-maven-plugin 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/ServerApp.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | 8 | public class ServerApp { 9 | 10 | public static void main(String[] args) { 11 | SpringApplication.run(ServerApp.class, args); 12 | } 13 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/client/BaseClient.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.client; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.http.*; 5 | import org.springframework.lang.Nullable; 6 | import org.springframework.web.client.*; 7 | 8 | @RequiredArgsConstructor 9 | public class BaseClient { 10 | protected final RestTemplate rest; 11 | 12 | protected ResponseEntity get(String path) { 13 | return makeAndSendRequest(HttpMethod.GET, path, null); 14 | } 15 | 16 | protected ResponseEntity post(String path, T body) { 17 | return makeAndSendRequest(HttpMethod.POST, path, body); 18 | } 19 | 20 | private ResponseEntity makeAndSendRequest(HttpMethod method, String path, @Nullable T body) { 21 | HttpEntity requestEntity = null; 22 | if (body != null) { 23 | requestEntity = new HttpEntity<>(body); 24 | } 25 | ResponseEntity response; 26 | try { 27 | response = rest.exchange(path, method, requestEntity, Object.class); 28 | } catch (HttpStatusCodeException e) { 29 | return ResponseEntity.status(e.getStatusCode()).body(e.getResponseBodyAsByteArray()); 30 | } 31 | return prepareGatewayResponse(response); 32 | } 33 | 34 | private static ResponseEntity prepareGatewayResponse(ResponseEntity response) { 35 | if (response.getStatusCode().is2xxSuccessful()) { 36 | return response; 37 | } 38 | ResponseEntity.BodyBuilder responseBuilder = ResponseEntity.status(response.getStatusCode()); 39 | if (response.hasBody()) { 40 | return responseBuilder.body(response.getBody()); 41 | } 42 | return responseBuilder.build(); 43 | } 44 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/client/EndpointHit.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.client; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.*; 5 | 6 | import java.time.LocalDateTime; 7 | 8 | /** 9 | * Эндроинт к сервису статистики 10 | */ 11 | 12 | @Data 13 | @NoArgsConstructor 14 | public class EndpointHit { 15 | private String app; 16 | private String uri; 17 | private String ip; 18 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 19 | private LocalDateTime timestamp; 20 | 21 | public EndpointHit(String app, String uri, String ip) { 22 | this.app = app; 23 | this.uri = uri; 24 | this.ip = ip; 25 | this.timestamp = LocalDateTime.now(); 26 | } 27 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/client/StatsClient.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.client; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.boot.web.client.RestTemplateBuilder; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.web.util.DefaultUriBuilderFactory; 8 | 9 | import javax.servlet.http.HttpServletRequest; 10 | 11 | /** 12 | * HTTP client для запросов к сервису статистики 13 | */ 14 | 15 | @Service 16 | public class StatsClient extends BaseClient { 17 | @Value("explore-with-me-server") 18 | private String appName; 19 | 20 | @Autowired 21 | public StatsClient(@Value("http://stats-server:9090") String serverUrl, RestTemplateBuilder builder) { 22 | super( 23 | builder 24 | .uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl)) 25 | .build() 26 | ); 27 | } 28 | 29 | public void save(HttpServletRequest request) { 30 | String uri = request.getRequestURI(); 31 | String ip = request.getRemoteAddr(); 32 | post("/hit", new EndpointHit(appName, uri, ip)); 33 | } 34 | 35 | public Object getViews(String uri) { 36 | return get("/hit?uri=" + uri).getBody(); 37 | } 38 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/controller/admin/CategoryAdminController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.controller.admin; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.web.bind.annotation.*; 5 | import ru.practicum.explorewithme.service.admin.CategoryAdminService; 6 | import ru.practicum.explorewithme.dto.category.CategoryCreateDto; 7 | import ru.practicum.explorewithme.dto.category.CategoryDto; 8 | 9 | import javax.validation.Valid; 10 | 11 | /** 12 | * API для работы с категориями на уровне администратора 13 | */ 14 | 15 | @RestController 16 | @RequiredArgsConstructor 17 | @RequestMapping(path = "/admin/categories") 18 | public class CategoryAdminController { 19 | private final CategoryAdminService service; 20 | 21 | @PatchMapping // изменение категории 22 | public CategoryDto update(@Valid @RequestBody CategoryDto categoryDto) { 23 | return service.update(categoryDto); 24 | } 25 | 26 | @PostMapping // добавление новой категории 27 | public CategoryDto save(@Valid @RequestBody CategoryCreateDto categoryCreateDto) { 28 | return service.save(categoryCreateDto); 29 | } 30 | 31 | @DeleteMapping("/{catId}") // удаление категории 32 | public void deleteCategory(@PathVariable Long catId) { 33 | service.delete(catId); 34 | } 35 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/controller/admin/CommentAdminController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.controller.admin; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.web.bind.annotation.*; 5 | import ru.practicum.explorewithme.model.comment.Comment; 6 | import ru.practicum.explorewithme.service.admin.CommentAdminService; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * API для работы с отзывами на события на уровне администратора 12 | */ 13 | 14 | @RestController 15 | @RequiredArgsConstructor 16 | public class CommentAdminController { 17 | private final CommentAdminService service; 18 | 19 | @DeleteMapping("/admin/comments/{commentId}") // удалить отзыв на событие 20 | public void delete(@PathVariable Long commentId) { 21 | service.deleteById(commentId); 22 | } 23 | 24 | @DeleteMapping ("/admin/events/{eventId}/comments")// удалить все отзывы по идентификатору события 25 | public void deleteAllCommentsByEvent(@PathVariable Long eventId) { 26 | service.deleteAllCommentsByEvent(eventId); 27 | } 28 | 29 | @GetMapping("/admin/events/{eventId}/comments") // получить все отзывы по идентификатору события 30 | public List findAllCommentsByEventId(@PathVariable Long eventId) { 31 | return service.findAllCommentsByEventId(eventId); 32 | } 33 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/controller/admin/CompilationAdminController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.controller.admin; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.web.bind.annotation.*; 5 | import ru.practicum.explorewithme.service.admin.CompilationAdminService; 6 | import ru.practicum.explorewithme.dto.compilation.CompilationCreateDto; 7 | import ru.practicum.explorewithme.dto.compilation.CompilationDto; 8 | 9 | import javax.validation.Valid; 10 | 11 | /** 12 | * API для работы с подборками событий на уровне администратора 13 | */ 14 | 15 | @RestController 16 | @RequiredArgsConstructor 17 | @RequestMapping(path = "/admin/compilations") 18 | public class CompilationAdminController { 19 | private final CompilationAdminService service; 20 | 21 | @PostMapping // добавление новой подборки 22 | public CompilationDto save(@Valid @RequestBody CompilationCreateDto compilationCreateDto) { 23 | return service.save(compilationCreateDto); 24 | } 25 | 26 | @DeleteMapping("/{compId}") // удаление подборки 27 | public void delete(@PathVariable Long compId) { 28 | service.deleteById(compId); 29 | } 30 | 31 | @DeleteMapping("/{compId}/events/{eventId}") // удалить событие из подборки 32 | public void deleteEvent(@PathVariable Long compId, @PathVariable Long eventId) { 33 | service.saveOrDeleteEventInCompilation(compId, eventId, true); 34 | } 35 | 36 | @PatchMapping("/{compId}/events/{eventId}") // добавить событие в подборку 37 | public void saveEvent(@PathVariable Long compId, @PathVariable Long eventId) { 38 | service.saveOrDeleteEventInCompilation(compId, eventId, false); 39 | } 40 | 41 | @DeleteMapping("/{compId}/pin") // открепить подборку на главной странице 42 | public void unpin(@PathVariable Long compId) { 43 | service.changeCompilationPin(compId, true); 44 | } 45 | 46 | @PatchMapping("/{compId}/pin") // закрепить подборку на главной странице 47 | public void pin(@PathVariable Long compId) { 48 | service.changeCompilationPin(compId, false); 49 | } 50 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/controller/admin/EventAdminController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.controller.admin; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.format.annotation.DateTimeFormat; 6 | import org.springframework.web.bind.annotation.*; 7 | import ru.practicum.explorewithme.dto.event.EventAdminUpdate; 8 | import ru.practicum.explorewithme.dto.event.EventFullDto; 9 | import ru.practicum.explorewithme.service.admin.EventAdminService; 10 | 11 | import javax.validation.constraints.*; 12 | import java.time.LocalDateTime; 13 | import java.util.List; 14 | 15 | /** 16 | * API для работы с событиями на уровне администратора 17 | */ 18 | 19 | @Slf4j 20 | @RestController 21 | @RequiredArgsConstructor 22 | @RequestMapping(path = "/admin/events") 23 | public class EventAdminController { 24 | private final EventAdminService service; 25 | 26 | @GetMapping // Поиск событий 27 | private List searchEvents( 28 | @RequestParam(required = false) List users, 29 | @RequestParam(required = false) List states, 30 | @RequestParam(required = false) List categories, 31 | @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime rangeStart, 32 | @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime rangeEnd, 33 | @PositiveOrZero @RequestParam(defaultValue = "0") int from, 34 | @Positive @RequestParam(defaultValue = "10") int size) { 35 | return service.searchEvents(users, states, categories, rangeStart, rangeEnd, from, size); 36 | } 37 | 38 | @PutMapping("/{eventId}") // Редактирование события 39 | private EventFullDto updateEventById(@RequestBody EventAdminUpdate eventAdminUpdate, 40 | @PathVariable Long eventId) { 41 | return service.update(eventAdminUpdate, eventId); 42 | } 43 | 44 | @PatchMapping("/{eventId}/publish") // Публикация события 45 | public EventFullDto publishEvent(@PathVariable Long eventId) { 46 | return service.changeEventState(eventId, true); 47 | } 48 | 49 | @PatchMapping("/{eventId}/reject") // Отклонение события 50 | public EventFullDto rejectEvent(@PathVariable Long eventId) { 51 | return service.changeEventState(eventId, false); 52 | } 53 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/controller/admin/UserAdminController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.controller.admin; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.web.bind.annotation.*; 5 | import ru.practicum.explorewithme.dto.user.UserCreateDto; 6 | import ru.practicum.explorewithme.dto.user.UserDto; 7 | import ru.practicum.explorewithme.service.admin.UserAdminService; 8 | 9 | import javax.validation.Valid; 10 | import javax.validation.constraints.*; 11 | import java.util.List; 12 | import java.util.Set; 13 | 14 | /** 15 | * API для работы с пользователями на уровне администратора 16 | */ 17 | 18 | @RestController 19 | @RequiredArgsConstructor 20 | @RequestMapping(path = "/admin/users") 21 | public class UserAdminController { 22 | private final UserAdminService service; 23 | 24 | @GetMapping // Получение информации о пользователях 25 | public List findAll(@RequestParam(required = false) Set ids, 26 | @PositiveOrZero @RequestParam(defaultValue = "0") int from, 27 | @Positive @RequestParam(defaultValue = "10") int size) { 28 | if (ids != null && !ids.isEmpty()) { 29 | return service.getUsersByIds(ids); 30 | } 31 | return service.findAll(ids, from, size); 32 | } 33 | 34 | @PostMapping // Добавление нового пользователя 35 | public UserDto save(@Valid @RequestBody UserCreateDto userCreateDto) { 36 | return service.save(userCreateDto); 37 | } 38 | 39 | @DeleteMapping("/{userId}") // Удаление пользователя 40 | public void delete(@PathVariable Long userId) { 41 | service.deleteById(userId); 42 | } 43 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/controller/priv/CommentPrivateController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.controller.priv; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.web.bind.annotation.*; 5 | import ru.practicum.explorewithme.dto.comment.CommentCreateDto; 6 | import ru.practicum.explorewithme.dto.comment.CommentDto; 7 | import ru.practicum.explorewithme.dto.comment.CommentUpdateDto; 8 | import ru.practicum.explorewithme.service.priv.CommentPrivateService; 9 | 10 | import javax.validation.Valid; 11 | import java.util.List; 12 | 13 | /** 14 | * Закрытый API для работы с отзывами на события (для авторизованных пользователей) 15 | */ 16 | 17 | @RestController 18 | @RequiredArgsConstructor 19 | @RequestMapping(path = "/users") 20 | public class CommentPrivateController { 21 | private final CommentPrivateService service; 22 | 23 | @PostMapping("/{userId}/events/{eventId}/comments") // Добавление нового отзыва о событии текущим пользователем 24 | public CommentDto save(@Valid @RequestBody CommentCreateDto commentCreateDto, 25 | @PathVariable Long userId, 26 | @PathVariable Long eventId) { 27 | return service.save(commentCreateDto, userId, eventId); 28 | } 29 | 30 | // изменение отзыва на событие текущим пользователем 31 | @PatchMapping("/{userId}/events/{eventId}/comments/{commentId}") 32 | public CommentDto update(@Valid @RequestBody CommentUpdateDto commentUpdateDto, 33 | @PathVariable Long userId, 34 | @PathVariable Long eventId, 35 | @PathVariable Long commentId) { 36 | return service.update(commentUpdateDto, userId, eventId, commentId); 37 | } 38 | 39 | @DeleteMapping("/{userId}/events/{eventId}/comments/{commentId}") // удаление отзыва на событие тек.пользователем 40 | public void delete(@PathVariable Long userId, 41 | @PathVariable Long eventId, 42 | @PathVariable Long commentId) { 43 | service.delete(userId, eventId, commentId); 44 | } 45 | 46 | @GetMapping("/{userId}/events/{eventId}/comments") // Получение списка отзывов на событие текущим пользователем 47 | public List findCommentsByEventId(@PathVariable Long userId, 48 | @PathVariable Long eventId) { 49 | return service.findCommentsByEventId(userId, eventId); 50 | } 51 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/controller/priv/EventPrivateController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.controller.priv; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.web.bind.annotation.*; 5 | import ru.practicum.explorewithme.dto.event.EventCreateDto; 6 | import ru.practicum.explorewithme.dto.event.EventFullDto; 7 | import ru.practicum.explorewithme.dto.event.EventUpdateDto; 8 | import ru.practicum.explorewithme.service.priv.EventPrivateService; 9 | import ru.practicum.explorewithme.dto.request.RequestDto; 10 | 11 | import javax.validation.Valid; 12 | import javax.validation.constraints.*; 13 | import java.util.List; 14 | 15 | /** 16 | * Закрытый API для работы с событиями (для авторизованных пользователей) 17 | */ 18 | 19 | @RestController 20 | @RequiredArgsConstructor 21 | @RequestMapping(path = "/users") 22 | public class EventPrivateController { 23 | private final EventPrivateService service; 24 | 25 | @GetMapping("/{userId}/events") // Получение событий добавленных текущим пользователем 26 | public List findEventsForInitiator(@PathVariable Long userId, 27 | @PositiveOrZero @RequestParam(defaultValue = "0") int from, 28 | @Positive @RequestParam(defaultValue = "10") int size) { 29 | return service.findEventByInitiatorId(userId, from, size); 30 | } 31 | 32 | @PatchMapping("/{userId}/events") // Изменение события добавленного текущим пользователем 33 | public EventFullDto update(@Valid @RequestBody EventUpdateDto eventUpdateDto, @PathVariable Long userId) { 34 | return service.update(eventUpdateDto, userId); 35 | } 36 | 37 | @PostMapping("/{userId}/events") // Добавление нового события 38 | public EventFullDto save(@Valid @RequestBody EventCreateDto eventCreateDto, @PathVariable Long userId) { 39 | return service.save(eventCreateDto, userId); 40 | } 41 | 42 | @GetMapping("/{userId}/events/{eventId}") // Получение полной информации о событии добавленном тек.пользователем 43 | public EventFullDto getEventInfo(@PathVariable Long userId, @PathVariable Long eventId) { 44 | return service.findEventInfoByInitiator(userId, eventId); 45 | } 46 | 47 | @PatchMapping("/{userId}/events/{eventId}") // Отмена события добавленного текущим пользователем 48 | public EventFullDto cancelEvent(@PathVariable Long userId, @PathVariable Long eventId) { 49 | return service.cancel(userId, eventId); 50 | } 51 | 52 | @GetMapping("/{userId}/events/{eventId}/requests") // Получение информации о запросах на участие 53 | // в событии текущего пользователя 54 | public List getAllRequests(@PathVariable Long userId, @PathVariable Long eventId) { 55 | return service.getAllRequestsByEventId(userId, eventId); 56 | } 57 | 58 | @PatchMapping("/{userId}/events/{eventId}/requests/{requestId}/confirm") // Подтверждение чужой заявки на участие 59 | public RequestDto confirmRequest(@PathVariable Long userId, 60 | @PathVariable Long eventId, 61 | @PathVariable Long requestId) { 62 | return service.changeRequestStatus(userId, eventId, requestId, true); 63 | } 64 | 65 | @PatchMapping("/{userId}/events/{eventId}/requests/{requestId}/reject") // Отклонение чужой заявки на участие 66 | public RequestDto rejectRequest(@PathVariable Long userId, 67 | @PathVariable Long eventId, 68 | @PathVariable Long requestId) { 69 | return service.changeRequestStatus(userId, eventId, requestId, false); 70 | } 71 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/controller/priv/RequestPrivateController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.controller.priv; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.web.bind.annotation.*; 5 | import ru.practicum.explorewithme.dto.request.RequestDto; 6 | import ru.practicum.explorewithme.service.priv.RequestPrivateService; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * Закрытый API для работы с запросами текущего пользователя на участие в событиях (для авторизованных пользователей) 12 | */ 13 | 14 | @RestController 15 | @RequiredArgsConstructor 16 | @RequestMapping(path = "/users") 17 | public class RequestPrivateController { 18 | private final RequestPrivateService service; 19 | 20 | @GetMapping("/{userId}/requests") // Получение информации о заявках текущего пользователя 21 | // на участие в чужих событиях 22 | public List getRequests(@PathVariable Long userId) { 23 | return service.findRequests(userId); 24 | } 25 | 26 | @PostMapping("/{userId}/requests") // Добавление запроса от текущего пользователя на участие в событии 27 | public RequestDto saveRequest(@PathVariable Long userId, @RequestParam Long eventId) { 28 | return service.save(userId, eventId); 29 | } 30 | 31 | @PatchMapping("/{userId}/requests/{requestId}/cancel") // Отмена своего запроса на участие в событии 32 | public RequestDto cancelRequest(@PathVariable Long userId, @PathVariable Long requestId) { 33 | return service.cancelRequest(userId, requestId); 34 | } 35 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/controller/pub/CategoriesPublicController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.controller.pub; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.web.bind.annotation.*; 5 | import ru.practicum.explorewithme.service.pub.CategoriesPublicService; 6 | import ru.practicum.explorewithme.dto.category.CategoryDto; 7 | 8 | import javax.validation.constraints.*; 9 | import java.util.*; 10 | 11 | /** 12 | * Публичный API для работы с категориями 13 | */ 14 | 15 | @RestController 16 | @RequiredArgsConstructor 17 | @RequestMapping(path = "/categories") 18 | public class CategoriesPublicController { 19 | private final CategoriesPublicService categoryService; 20 | 21 | @GetMapping // Получение категорий 22 | public List findAll(@PositiveOrZero @RequestParam(defaultValue = "0") int from, 23 | @Positive @RequestParam(defaultValue = "10") int size) { 24 | return categoryService.findAll(from, size); 25 | } 26 | 27 | @GetMapping("/{categoryId}") // Получение информации о категории по ее идентификатору 28 | public CategoryDto findById(@PathVariable Long categoryId) { 29 | return categoryService.findById(categoryId); 30 | } 31 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/controller/pub/CommentPublicController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.controller.pub; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.web.bind.annotation.*; 5 | import ru.practicum.explorewithme.dto.comment.CommentDto; 6 | import ru.practicum.explorewithme.service.pub.CommentPublicService; 7 | 8 | import javax.validation.constraints.Positive; 9 | import javax.validation.constraints.PositiveOrZero; 10 | import java.util.List; 11 | 12 | /** 13 | * Публичный API для просмотра отзывов на события 14 | */ 15 | 16 | @RestController 17 | @RequiredArgsConstructor 18 | @RequestMapping(path = "/comments") 19 | public class CommentPublicController { 20 | private final CommentPublicService service; 21 | 22 | @GetMapping // Получение списка отзывов на события 23 | public List findAll(@PositiveOrZero @RequestParam(defaultValue = "0") int from, 24 | @Positive @RequestParam(defaultValue = "10") int size) { 25 | return service.findAll(from, size); 26 | } 27 | 28 | @GetMapping("/{commentId}") // Получить отзыв на событие по идентификатору 29 | public CommentDto getEventComment(@PathVariable Long commentId) { 30 | return service.findCommentById(commentId); 31 | } 32 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/controller/pub/CompilationPublicController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.controller.pub; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.web.bind.annotation.*; 5 | import ru.practicum.explorewithme.service.pub.CompilationPublicService; 6 | import ru.practicum.explorewithme.dto.compilation.CompilationDto; 7 | 8 | import javax.validation.constraints.*; 9 | import java.util.*; 10 | 11 | /** 12 | * Публичный API для работы с подборками событий 13 | */ 14 | 15 | @RestController 16 | @RequiredArgsConstructor 17 | @RequestMapping(path = "/compilations") 18 | public class CompilationPublicController { 19 | private final CompilationPublicService service; 20 | 21 | @GetMapping // Получение подборок событий 22 | public List findAll(@RequestParam(required = false) Boolean pinned, 23 | @PositiveOrZero @RequestParam(defaultValue = "0") int from, 24 | @Positive @RequestParam(defaultValue = "10") int size) { 25 | return service.findAll(pinned, from, size); 26 | } 27 | 28 | @GetMapping("/{compId}") // Получение подборки событий по идентификатору 29 | public CompilationDto findById(@PathVariable Long compId) { 30 | return service.findById(compId); 31 | } 32 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/controller/pub/EventPublicController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.controller.pub; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.format.annotation.DateTimeFormat; 5 | import org.springframework.web.bind.annotation.*; 6 | import ru.practicum.explorewithme.client.StatsClient; 7 | import ru.practicum.explorewithme.dto.event.EventFullDto; 8 | import ru.practicum.explorewithme.dto.event.EventShortDto; 9 | import ru.practicum.explorewithme.service.pub.EventPublicService; 10 | 11 | import javax.servlet.http.HttpServletRequest; 12 | import javax.validation.constraints.*; 13 | import java.time.LocalDateTime; 14 | import java.util.List; 15 | 16 | /** 17 | * Публичный API для работы с событиями 18 | */ 19 | 20 | @RestController 21 | @RequiredArgsConstructor 22 | @RequestMapping(path = "/events") 23 | public class EventPublicController { 24 | private final EventPublicService service; 25 | private final StatsClient client; 26 | 27 | @GetMapping // Получение событий с возможностью фильтрации 28 | public List getEvents( 29 | @RequestParam(required = false) String text, 30 | @RequestParam(required = false) List categories, 31 | @RequestParam(required = false) Boolean paid, 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 = "false") boolean onlyAvailable, 35 | @RequestParam(required = false) String sort, 36 | @PositiveOrZero @RequestParam(defaultValue = "0") int from, 37 | @Positive @RequestParam(defaultValue = "10") int size, 38 | HttpServletRequest request) { 39 | client.save(request); 40 | return service.getEventsSort(text, categories, paid, rangeStart, rangeEnd, onlyAvailable, sort, from, size); 41 | } 42 | 43 | @GetMapping("/{eventId}") // Получение подробной информации об опубликованном событии по его идентификатору 44 | public EventFullDto getEventById(@PathVariable Long eventId, HttpServletRequest request) { 45 | client.save(request); 46 | return service.findEventById(eventId); 47 | } 48 | 49 | @GetMapping("/{eventId}/comments") // Получить событие с отзывами + 1 hit в статистику 50 | public EventFullDto getEventWithComments(@PathVariable Long eventId, 51 | HttpServletRequest request) { 52 | client.save(request); 53 | return service.getEventWithAllComments(eventId); 54 | } 55 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/dto/category/CategoryCreateDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.category; 2 | 3 | import lombok.*; 4 | 5 | import javax.validation.constraints.*; 6 | 7 | /** 8 | * Информация для создания категории события 9 | */ 10 | 11 | @Data 12 | @Builder 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | public class CategoryCreateDto { 16 | @NotNull 17 | @NotBlank 18 | @Size(min = 3, max = 120) 19 | private String name; 20 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/dto/category/CategoryDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.category; 2 | 3 | import lombok.*; 4 | 5 | import javax.validation.constraints.*; 6 | 7 | /** 8 | * Информация о категории события 9 | */ 10 | 11 | @Data 12 | @Builder 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | public class CategoryDto { 16 | private Long id; 17 | @NotNull 18 | @NotBlank 19 | @Size(min = 3, max = 120) 20 | private String name; 21 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/dto/comment/CommentCreateDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.comment; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.*; 5 | import org.hibernate.validator.constraints.Length; 6 | 7 | import javax.validation.constraints.NotBlank; 8 | import java.time.LocalDateTime; 9 | 10 | /** 11 | * Информация для сохранения отзыва на событие 12 | */ 13 | 14 | @Data 15 | @Builder 16 | @NoArgsConstructor 17 | @AllArgsConstructor 18 | public class CommentCreateDto { 19 | @NotBlank 20 | @Length(min = 3, max = 7000) 21 | private String text; 22 | private Boolean edited = false; 23 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 24 | private LocalDateTime created = LocalDateTime.now(); 25 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/dto/comment/CommentDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.comment; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.*; 5 | import org.hibernate.validator.constraints.Length; 6 | 7 | import javax.validation.constraints.NotBlank; 8 | import java.time.LocalDateTime; 9 | 10 | /** 11 | * Информация для отзыва на событие 12 | */ 13 | 14 | @Data 15 | @Builder 16 | @NoArgsConstructor 17 | @AllArgsConstructor 18 | public class CommentDto { 19 | private Long id; 20 | @NotBlank 21 | @Length(min = 3, max = 7000) 22 | private String text; 23 | private Long eventId; 24 | private Long authorId; 25 | private Boolean edited; 26 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 27 | private LocalDateTime created = LocalDateTime.now(); 28 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/dto/comment/CommentUpdateDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.comment; 2 | 3 | import lombok.*; 4 | import org.hibernate.validator.constraints.Length; 5 | 6 | import javax.validation.constraints.NotBlank; 7 | 8 | /** 9 | * Информация для обновления отзыва на событие 10 | */ 11 | 12 | @Data 13 | @Builder 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | public class CommentUpdateDto { 17 | @NotBlank 18 | @Length(min = 3, max = 7000) 19 | private String text; 20 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/dto/compilation/CompilationCreateDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.compilation; 2 | 3 | import lombok.*; 4 | import org.hibernate.validator.constraints.Length; 5 | 6 | import javax.validation.constraints.NotBlank; 7 | import java.util.List; 8 | 9 | /** 10 | * Информация для создания подборки событий 11 | */ 12 | 13 | @Data 14 | @Builder 15 | @NoArgsConstructor 16 | @AllArgsConstructor 17 | public class CompilationCreateDto { 18 | @NotBlank 19 | @Length(min = 3, max = 120) 20 | private String title; 21 | private Boolean pinned; 22 | private List events; 23 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/dto/compilation/CompilationDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.compilation; 2 | 3 | import lombok.*; 4 | import ru.practicum.explorewithme.dto.event.EventShortDto; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Информация о подборке событий 10 | */ 11 | 12 | @Data 13 | @Builder 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | public class CompilationDto { 17 | private Long id; 18 | private String title; 19 | private Boolean pinned; 20 | private List events; 21 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/dto/event/EventAdminUpdate.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.event; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.*; 5 | import ru.practicum.explorewithme.model.event.Location; 6 | 7 | import java.time.LocalDateTime; 8 | 9 | /** 10 | * Информация для редактирования события администратором 11 | */ 12 | 13 | @Data 14 | @Builder 15 | @NoArgsConstructor 16 | @AllArgsConstructor 17 | public class EventAdminUpdate { 18 | private String annotation; 19 | private Long category; 20 | private String description; 21 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 22 | private LocalDateTime eventDate; 23 | private Location location; 24 | private Boolean paid; 25 | private Integer participantLimit; 26 | private Boolean requestModeration; 27 | private String title; 28 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/dto/event/EventCreateDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.event; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.*; 5 | 6 | import javax.validation.constraints.*; 7 | import java.time.LocalDateTime; 8 | 9 | /** 10 | * Информация для создания события 11 | */ 12 | 13 | @Data 14 | @Builder 15 | @NoArgsConstructor 16 | @AllArgsConstructor 17 | public class EventCreateDto { 18 | @NotNull 19 | @NotBlank 20 | @Size(min = 20, max = 2000) 21 | private String annotation; 22 | @NotNull 23 | private Long category; 24 | @NotNull 25 | @NotBlank 26 | @Size(min = 20, max = 7000) 27 | private String description; 28 | @NotNull 29 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 30 | private LocalDateTime eventDate; 31 | @NotNull 32 | private LocationDto location; 33 | private Boolean paid = false; 34 | private Integer participantLimit = 0; 35 | private Boolean requestModeration = true; 36 | @NotNull 37 | @NotBlank 38 | @Size(min = 3, max = 120) 39 | private String title; 40 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/dto/event/EventFullDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.event; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.*; 5 | import ru.practicum.explorewithme.dto.category.CategoryDto; 6 | import ru.practicum.explorewithme.dto.comment.CommentDto; 7 | import ru.practicum.explorewithme.dto.user.UserShortDto; 8 | 9 | import java.time.LocalDateTime; 10 | import java.util.List; 11 | 12 | /** 13 | * Полная информация о событии 14 | */ 15 | 16 | @Data 17 | @Builder 18 | @NoArgsConstructor 19 | @AllArgsConstructor 20 | public class EventFullDto { 21 | private Long id; 22 | private String annotation; 23 | private String description; 24 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 25 | private LocalDateTime createdOn; 26 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 27 | private LocalDateTime eventDate; 28 | private LocationDto location; 29 | private Integer confirmedRequests; 30 | private CategoryDto category; 31 | private UserShortDto initiator; 32 | private Boolean paid; 33 | private Integer participantLimit; 34 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 35 | private LocalDateTime publishedOn; 36 | private Boolean requestModeration; 37 | private String state; 38 | private String title; 39 | private Integer views; 40 | private List comments; 41 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/dto/event/EventShortDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.event; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.*; 5 | import ru.practicum.explorewithme.dto.category.CategoryDto; 6 | import ru.practicum.explorewithme.dto.user.UserShortDto; 7 | 8 | import java.time.LocalDateTime; 9 | 10 | /** 11 | * Краткая информация о событии 12 | */ 13 | 14 | @Data 15 | @Builder 16 | @NoArgsConstructor 17 | @AllArgsConstructor 18 | public class EventShortDto { 19 | private Long id; 20 | private String annotation; 21 | private CategoryDto category; 22 | private Integer confirmedRequests; 23 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 24 | private LocalDateTime eventDate; 25 | private UserShortDto initiator; 26 | private Boolean paid; 27 | private String title; 28 | private Integer views; 29 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/dto/event/EventUpdateDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.event; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.*; 5 | 6 | import javax.validation.constraints.*; 7 | import java.time.LocalDateTime; 8 | 9 | /** 10 | * Информация для обновления события пользователем 11 | */ 12 | 13 | @Data 14 | @Builder 15 | @NoArgsConstructor 16 | @AllArgsConstructor 17 | public class EventUpdateDto { 18 | @NotBlank 19 | @Size(min = 20, max = 2000) 20 | private String annotation; 21 | private Long category; 22 | @NotBlank 23 | @Size(min = 20, max = 7000) 24 | private String description; 25 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 26 | private LocalDateTime eventDate; 27 | @NotNull 28 | private Long eventId; 29 | private Boolean paid; 30 | private Integer participantLimit; 31 | @NotBlank 32 | @Size(min = 3, max = 120) 33 | private String title; 34 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/dto/event/LocationDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.event; 2 | 3 | import lombok.*; 4 | 5 | import javax.persistence.*; 6 | 7 | /** 8 | * Информация о месте проведения события (широта / долгота) 9 | */ 10 | 11 | @Data 12 | @Builder 13 | @Embeddable 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | public class LocationDto { 17 | private Float lat; 18 | private Float lon; 19 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/dto/request/RequestDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.request; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.*; 5 | 6 | import java.time.LocalDateTime; 7 | 8 | /** 9 | * Информация о запросе на участие в событии 10 | */ 11 | 12 | @Data 13 | @Builder 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | public class RequestDto { 17 | private Long id; 18 | @JsonFormat(pattern = "yyyy.MM.dd HH:mm:ss") 19 | private LocalDateTime created; 20 | private Long event; 21 | private Long requester; 22 | private RequestStatus status; 23 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/dto/request/RequestStatus.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.request; 2 | 3 | public enum RequestStatus { 4 | PENDING, 5 | CONFIRMED, 6 | REJECTED, 7 | CANCELED 8 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/dto/user/UserCreateDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.user; 2 | 3 | import lombok.*; 4 | 5 | import javax.validation.constraints.*; 6 | 7 | /** 8 | * Информация для создания пользователя 9 | */ 10 | 11 | @Data 12 | @Builder 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | public class UserCreateDto { 16 | @Email 17 | @NotNull 18 | @NotBlank 19 | private String email; 20 | @NotNull 21 | @NotBlank 22 | @Size(min = 3, max = 120) 23 | private String name; 24 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/dto/user/UserDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.user; 2 | 3 | import lombok.*; 4 | 5 | /** 6 | * Информация о пользователи 7 | */ 8 | 9 | @Data 10 | @Builder 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | public class UserDto { 14 | private Long id; 15 | private String name; 16 | private String email; 17 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/dto/user/UserShortDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto.user; 2 | 3 | import lombok.*; 4 | 5 | /** 6 | * Краткая информация о пользователи 7 | */ 8 | 9 | @Data 10 | @Builder 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | public class UserShortDto { 14 | private Long id; 15 | private String name; 16 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/exception/ApiError.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.exception; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import lombok.*; 5 | import org.springframework.http.HttpStatus; 6 | 7 | import java.time.LocalDateTime; 8 | import java.util.List; 9 | 10 | /** 11 | * Сведения об ошибке 12 | */ 13 | 14 | @Data 15 | @Builder 16 | public class ApiError { 17 | private HttpStatus status; 18 | private String reason; 19 | @JsonInclude(JsonInclude.Include.NON_NULL) 20 | private String message; 21 | @JsonInclude(JsonInclude.Include.NON_NULL) 22 | private List errors; 23 | private LocalDateTime timestamp; 24 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/exception/ErrorHandler.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.exception; 2 | 3 | import org.springframework.core.NestedExceptionUtils; 4 | import org.springframework.dao.DataIntegrityViolationException; 5 | import org.springframework.validation.FieldError; 6 | import org.springframework.web.bind.MethodArgumentNotValidException; 7 | import org.springframework.web.bind.annotation.RestControllerAdvice; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.web.bind.annotation.*; 10 | 11 | import java.time.LocalDateTime; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | @RestControllerAdvice 16 | public class ErrorHandler { 17 | 18 | @ExceptionHandler 19 | @ResponseStatus(HttpStatus.FORBIDDEN) 20 | public ApiError handleForbiddenException(final ForbiddenException e) { 21 | return ApiError.builder() 22 | .status(HttpStatus.FORBIDDEN) 23 | .reason("For the requested operation the conditions are not met.") 24 | .message(e.getMessage()) 25 | .timestamp(LocalDateTime.now()) 26 | .build(); 27 | } 28 | 29 | @ExceptionHandler 30 | @ResponseStatus(HttpStatus.NOT_FOUND) 31 | public ApiError handleNotFoundException(final NotFoundException e) { 32 | return ApiError.builder() 33 | .status(HttpStatus.NOT_FOUND) 34 | .reason("The required object was not found.") 35 | .message(e.getMessage()) 36 | .timestamp(LocalDateTime.now()) 37 | .build(); 38 | } 39 | 40 | @ExceptionHandler 41 | @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) 42 | public ApiError handleInternalServerError(final Throwable e) { 43 | e.printStackTrace(); 44 | return ApiError.builder() 45 | .status(HttpStatus.INTERNAL_SERVER_ERROR) 46 | .reason("Error occurred") 47 | .message(e.getMessage()) 48 | .timestamp(LocalDateTime.now()) 49 | .build(); 50 | } 51 | 52 | @ExceptionHandler 53 | @ResponseStatus(HttpStatus.BAD_REQUEST) 54 | public ApiError handleArgumentNotValid(final MethodArgumentNotValidException e) { 55 | String objectName = e.getObjectName(); 56 | int errorCount = e.getErrorCount(); 57 | return ApiError.builder() 58 | .status(HttpStatus.BAD_REQUEST) 59 | .reason(String.format("During [%s] validation found %s errors", objectName, errorCount)) 60 | .errors(getErrors(e.getFieldErrors())) 61 | .timestamp(LocalDateTime.now()) 62 | .build(); 63 | } 64 | 65 | @ExceptionHandler 66 | @ResponseStatus(HttpStatus.CONFLICT) 67 | public ApiError handleConflictException(final DataIntegrityViolationException e) { 68 | String message = NestedExceptionUtils.getMostSpecificCause(e).getMessage(); 69 | return ApiError.builder() 70 | .status(HttpStatus.CONFLICT) 71 | .reason("Integrity constraint has been violated") 72 | .message(message) 73 | .timestamp(LocalDateTime.now()) 74 | .build(); 75 | } 76 | 77 | private List getErrors(List fieldErrors) { 78 | List errors = new ArrayList<>(); 79 | for (FieldError fieldError : fieldErrors) { 80 | errors.add("Field: " + fieldError.getField() + 81 | ". Error: " + fieldError.getDefaultMessage() + 82 | ". Value: " + fieldError.getRejectedValue()); 83 | } 84 | return errors; 85 | } 86 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/exception/EventSortException.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.exception; 2 | 3 | public class EventSortException extends RuntimeException { 4 | public EventSortException(String message) { 5 | super(message); 6 | } 7 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/exception/EventStateException.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.exception; 2 | 3 | public class EventStateException extends RuntimeException { 4 | public EventStateException(String message) { 5 | super(message); 6 | } 7 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/exception/ForbiddenException.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(HttpStatus.FORBIDDEN) 7 | public class ForbiddenException extends RuntimeException { 8 | public ForbiddenException(String message) { 9 | super(message); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/exception/NotFoundException.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | 7 | @ResponseStatus(HttpStatus.NOT_FOUND) 8 | public class NotFoundException extends RuntimeException { 9 | public NotFoundException(String message) { 10 | super(message); 11 | } 12 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/mapper/CategoryMapper.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.mapper; 2 | 3 | import lombok.*; 4 | import org.springframework.stereotype.Component; 5 | import ru.practicum.explorewithme.dto.category.CategoryCreateDto; 6 | import ru.practicum.explorewithme.dto.category.CategoryDto; 7 | import ru.practicum.explorewithme.model.category.Category; 8 | 9 | 10 | @Component 11 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 12 | public class CategoryMapper { 13 | 14 | public Category toModel(CategoryCreateDto categoryCreateDto) { 15 | return Category.builder() 16 | .name(categoryCreateDto.getName()) 17 | .build(); 18 | } 19 | 20 | public CategoryDto toDto(Category category) { 21 | return CategoryDto.builder() 22 | .id(category.getId()) 23 | .name(category.getName()) 24 | .build(); 25 | } 26 | 27 | public Category toUpdate(CategoryDto categoryDto) { 28 | return Category.builder() 29 | .id(categoryDto.getId()) 30 | .name(categoryDto.getName()) 31 | .build(); 32 | } 33 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/mapper/CommentMapper.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.mapper; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.stereotype.Component; 5 | import ru.practicum.explorewithme.dto.comment.CommentCreateDto; 6 | import ru.practicum.explorewithme.dto.comment.CommentDto; 7 | import ru.practicum.explorewithme.model.comment.Comment; 8 | import ru.practicum.explorewithme.model.event.Event; 9 | import ru.practicum.explorewithme.model.user.User; 10 | import ru.practicum.explorewithme.service.priv.RequestPrivateService; 11 | 12 | @Component 13 | @RequiredArgsConstructor 14 | public class CommentMapper { 15 | private final RequestPrivateService service; 16 | 17 | public CommentDto toDto(Comment comment) { 18 | return CommentDto.builder() 19 | .id(comment.getId()) 20 | .text(comment.getText()) 21 | .eventId(comment.getEvent().getId()) 22 | .authorId(comment.getAuthor().getId()) 23 | .edited(comment.getEdited()) 24 | .created(comment.getCreated()) 25 | .build(); 26 | } 27 | 28 | public Comment toModel(CommentDto commentDto, Long userId) { 29 | User user = service.findUserById(userId); 30 | Event event = service.findEventById(commentDto.getEventId()); 31 | return Comment.builder() 32 | .id(commentDto.getId()) 33 | .text(commentDto.getText()) 34 | .event(event) 35 | .author(user) 36 | .edited(commentDto.getEdited()) 37 | .created(commentDto.getCreated()) 38 | .build(); 39 | } 40 | 41 | public Comment toNewModel(CommentCreateDto commentCreateDto, User user, Event event) { 42 | return Comment.builder() 43 | .text(commentCreateDto.getText()) 44 | .event(event) 45 | .author(user) 46 | .edited(commentCreateDto.getEdited()) 47 | .created(commentCreateDto.getCreated()) 48 | .build(); 49 | } 50 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/mapper/CompilationMapper.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.mapper; 2 | 3 | import lombok.*; 4 | import org.springframework.stereotype.Component; 5 | import ru.practicum.explorewithme.dto.compilation.CompilationCreateDto; 6 | import ru.practicum.explorewithme.dto.compilation.CompilationDto; 7 | import ru.practicum.explorewithme.dto.event.EventShortDto; 8 | import ru.practicum.explorewithme.model.compilation.Compilation; 9 | import ru.practicum.explorewithme.model.event.Event; 10 | import ru.practicum.explorewithme.service.priv.RequestPrivateService; 11 | 12 | import java.util.List; 13 | import java.util.Set; 14 | import java.util.stream.Collectors; 15 | 16 | @Component 17 | @RequiredArgsConstructor 18 | public class CompilationMapper { 19 | private final RequestPrivateService service; 20 | 21 | public Compilation toModel(CompilationCreateDto compilationCreateDto) { 22 | Set events = compilationCreateDto.getEvents() 23 | .stream() 24 | .map(service::findEventById) 25 | .collect(Collectors.toSet()); 26 | return Compilation.builder() 27 | .title(compilationCreateDto.getTitle()) 28 | .pinned(compilationCreateDto.getPinned()) 29 | .events(events) 30 | .build(); 31 | } 32 | 33 | public CompilationDto toDto(Compilation compilation, List events) { 34 | return CompilationDto.builder() 35 | .id(compilation.getId()) 36 | .title(compilation.getTitle()) 37 | .pinned(compilation.isPinned()) 38 | .events(events) 39 | .build(); 40 | } 41 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/mapper/EventMapper.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.mapper; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.stereotype.Component; 5 | import ru.practicum.explorewithme.dto.comment.CommentDto; 6 | import ru.practicum.explorewithme.model.category.Category; 7 | import ru.practicum.explorewithme.dto.category.CategoryDto; 8 | import ru.practicum.explorewithme.client.StatsClient; 9 | import ru.practicum.explorewithme.dto.event.EventCreateDto; 10 | import ru.practicum.explorewithme.dto.event.EventFullDto; 11 | import ru.practicum.explorewithme.dto.event.EventShortDto; 12 | import ru.practicum.explorewithme.model.event.Location; 13 | import ru.practicum.explorewithme.model.event.Event; 14 | import ru.practicum.explorewithme.model.event.EventState; 15 | import ru.practicum.explorewithme.repository.RequestRepository; 16 | import ru.practicum.explorewithme.model.user.User; 17 | import ru.practicum.explorewithme.dto.user.UserShortDto; 18 | 19 | import java.time.LocalDateTime; 20 | import java.util.List; 21 | 22 | @Component 23 | @RequiredArgsConstructor 24 | public class EventMapper { 25 | private final RequestRepository requestRepository; 26 | private final StatsClient client; 27 | 28 | public Event toModel(EventCreateDto eventCreateDto, Long userId) { 29 | return Event.builder() 30 | .id(null) 31 | .title(eventCreateDto.getTitle()) 32 | .annotation(eventCreateDto.getAnnotation()) 33 | .description(eventCreateDto.getDescription()) 34 | .category(new Category(eventCreateDto.getCategory(), null)) 35 | .createdOn(LocalDateTime.now()) 36 | .eventDate(eventCreateDto.getEventDate()) 37 | .initiator(new User(userId, null, null)) 38 | .paid(eventCreateDto.getPaid()) 39 | .participantLimit(eventCreateDto.getParticipantLimit()) 40 | .publishedOn(null) 41 | .requestModeration(eventCreateDto.getRequestModeration()) 42 | .state(EventState.PENDING) 43 | .location(new Location(eventCreateDto.getLocation().getLat(), eventCreateDto.getLocation().getLon())) 44 | .build(); 45 | } 46 | 47 | public EventFullDto toFullDto(Event event) { 48 | return EventFullDto.builder() 49 | .id(event.getId()) 50 | .annotation(event.getAnnotation()) 51 | .description(event.getDescription()) 52 | .createdOn(event.getCreatedOn()) 53 | .eventDate(event.getEventDate()) 54 | .location(LocationMapper.toDto(event.getLocation())) 55 | .confirmedRequests(requestRepository.getConfirmedRequests(event.getId())) 56 | .category(new CategoryDto(event.getCategory().getId(), event.getCategory().getName())) 57 | .initiator(new UserShortDto(event.getInitiator().getId(), event.getInitiator().getName())) 58 | .paid(event.getPaid()) 59 | .participantLimit(event.getParticipantLimit()) 60 | .publishedOn(event.getPublishedOn()) 61 | .requestModeration(event.getRequestModeration()) 62 | .state(event.getState().toString()) 63 | .title(event.getTitle()) 64 | .views(getViews(event.getId())) 65 | .build(); 66 | } 67 | 68 | public EventShortDto toShortDto(Event event) { 69 | return EventShortDto.builder() 70 | .id(event.getId()) 71 | .annotation(event.getAnnotation()) 72 | .category(new CategoryDto(event.getCategory().getId(), event.getCategory().getName())) 73 | .confirmedRequests(requestRepository.getConfirmedRequests(event.getId())) 74 | .eventDate(event.getEventDate()) 75 | .initiator(new UserShortDto(event.getInitiator().getId(), event.getInitiator().getName())) 76 | .paid(event.getPaid()) 77 | .title(event.getTitle()) 78 | .views(getViews(event.getId())) 79 | .build(); 80 | } 81 | 82 | public Event toModelFromShortDto(EventShortDto eventShortDto) { 83 | return Event.builder() 84 | .id(eventShortDto.getId()) 85 | .title(eventShortDto.getTitle()) 86 | .annotation(eventShortDto.getAnnotation()) 87 | .eventDate(eventShortDto.getEventDate()) 88 | .paid(eventShortDto.getPaid()) 89 | .build(); 90 | } 91 | 92 | public EventShortDto toShortDtoFromFullDto(EventFullDto eventFullDto) { 93 | return EventShortDto.builder() 94 | .id(eventFullDto.getId()) 95 | .annotation(eventFullDto.getAnnotation()) 96 | .category(eventFullDto.getCategory()) 97 | .confirmedRequests(eventFullDto.getConfirmedRequests()) 98 | .eventDate(eventFullDto.getEventDate()) 99 | .initiator(eventFullDto.getInitiator()) 100 | .paid(eventFullDto.getPaid()) 101 | .title(eventFullDto.getTitle()) 102 | .views(eventFullDto.getViews()) 103 | .build(); 104 | } 105 | 106 | public EventFullDto toFullDtoWithComments(Event event, List comments) { 107 | return EventFullDto.builder() 108 | .id(event.getId()) 109 | .annotation(event.getAnnotation()) 110 | .description(event.getDescription()) 111 | .createdOn(event.getCreatedOn()) 112 | .eventDate(event.getEventDate()) 113 | .location(LocationMapper.toDto(event.getLocation())) 114 | .confirmedRequests(requestRepository.getConfirmedRequests(event.getId())) 115 | .category(new CategoryDto(event.getCategory().getId(), event.getCategory().getName())) 116 | .initiator(new UserShortDto(event.getInitiator().getId(), event.getInitiator().getName())) 117 | .paid(event.getPaid()) 118 | .participantLimit(event.getParticipantLimit()) 119 | .publishedOn(event.getPublishedOn()) 120 | .requestModeration(event.getRequestModeration()) 121 | .state(event.getState().toString()) 122 | .title(event.getTitle()) 123 | .views(getViews(event.getId())) 124 | .comments(comments) 125 | .build(); 126 | } 127 | 128 | private Integer getViews(Long eventId) { 129 | String uri = "/events/" + eventId; 130 | return (Integer) client.getViews(uri); 131 | } 132 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/mapper/LocationMapper.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.mapper; 2 | 3 | import lombok.*; 4 | import ru.practicum.explorewithme.dto.event.LocationDto; 5 | import ru.practicum.explorewithme.model.event.Location; 6 | 7 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 8 | public class LocationMapper { 9 | 10 | public static Location toModel(LocationDto locationDto) { 11 | return Location.builder() 12 | .lat(locationDto.getLat()) 13 | .lon(locationDto.getLon()) 14 | .build(); 15 | } 16 | 17 | public static LocationDto toDto(Location location) { 18 | return LocationDto.builder() 19 | .lat(location.getLat()) 20 | .lon(location.getLon()) 21 | .build(); 22 | } 23 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/mapper/RequestMapper.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.mapper; 2 | 3 | import lombok.*; 4 | import org.springframework.stereotype.Component; 5 | import ru.practicum.explorewithme.dto.request.RequestDto; 6 | import ru.practicum.explorewithme.model.request.Request; 7 | 8 | @Component 9 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 10 | public class RequestMapper { 11 | 12 | public RequestDto toDto(Request request) { 13 | return RequestDto.builder() 14 | .id(request.getId()) 15 | .created(request.getCreated()) 16 | .event(request.getEventId()) 17 | .requester(request.getRequesterId()) 18 | .status(request.getStatus()) 19 | .build(); 20 | } 21 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/mapper/UserMapper.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.mapper; 2 | 3 | import lombok.*; 4 | import org.springframework.stereotype.Component; 5 | import ru.practicum.explorewithme.dto.user.UserCreateDto; 6 | import ru.practicum.explorewithme.dto.user.UserDto; 7 | import ru.practicum.explorewithme.dto.user.UserShortDto; 8 | import ru.practicum.explorewithme.model.user.User; 9 | 10 | @Component 11 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 12 | public class UserMapper { 13 | 14 | public User toModel(UserCreateDto userCreateDto) { 15 | return User.builder() 16 | .name(userCreateDto.getName()) 17 | .email(userCreateDto.getEmail()) 18 | .build(); 19 | } 20 | 21 | public UserDto toDto(User user) { 22 | return UserDto.builder() 23 | .id(user.getId()) 24 | .name(user.getName()) 25 | .email(user.getEmail()) 26 | .build(); 27 | } 28 | 29 | public UserShortDto toShortDto(User user) { 30 | return UserShortDto.builder() 31 | .id(user.getId()) 32 | .name(user.getName()) 33 | .build(); 34 | } 35 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/model/category/Category.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.model.category; 2 | 3 | import lombok.*; 4 | 5 | import javax.persistence.*; 6 | 7 | /** 8 | * Категория события 9 | */ 10 | 11 | @Data 12 | @Entity 13 | @Builder 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | @Table(name = "categories") 17 | public class Category { 18 | @Id 19 | @GeneratedValue(strategy = GenerationType.IDENTITY) 20 | private Long id; 21 | private String name; 22 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/model/comment/Comment.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.model.comment; 2 | 3 | import lombok.*; 4 | import ru.practicum.explorewithme.model.event.Event; 5 | import ru.practicum.explorewithme.model.user.User; 6 | 7 | import javax.persistence.*; 8 | import javax.persistence.GenerationType; 9 | import java.time.LocalDateTime; 10 | 11 | /** 12 | * Отзыв на событие 13 | */ 14 | 15 | @Data 16 | @Entity 17 | @Builder 18 | @NoArgsConstructor 19 | @AllArgsConstructor 20 | @Table(name = "comments") 21 | public class Comment { 22 | @Id 23 | @GeneratedValue(strategy = GenerationType.IDENTITY) 24 | private Long id; 25 | @Column(name = "text", nullable = false, length = 512) 26 | private String text; 27 | @ManyToOne 28 | @JoinColumn(name = "event_id") 29 | private Event event; 30 | @ManyToOne 31 | @JoinColumn(name = "author_id") 32 | private User author; 33 | @JoinColumn(name = "edited") 34 | private Boolean edited; 35 | @Column(name = "created") 36 | private LocalDateTime created = LocalDateTime.now(); 37 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/model/compilation/Compilation.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.model.compilation; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import lombok.*; 5 | import ru.practicum.explorewithme.model.event.Event; 6 | 7 | import javax.persistence.*; 8 | import java.util.Set; 9 | 10 | /** 11 | * Подборка событий 12 | */ 13 | 14 | @Data 15 | @Entity 16 | @Builder 17 | @NoArgsConstructor 18 | @AllArgsConstructor 19 | @Table(name = "compilations") 20 | public class Compilation { 21 | @Id 22 | @GeneratedValue(strategy = GenerationType.IDENTITY) 23 | private Long id; 24 | private String title; 25 | private boolean pinned; 26 | @ManyToMany 27 | @JoinTable( 28 | name = "events_compilation", 29 | joinColumns = @JoinColumn(name = "compilation_id"), 30 | inverseJoinColumns = @JoinColumn(name = "event_id")) 31 | @ToString.Exclude 32 | @JsonIgnore 33 | private Set events; 34 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/model/event/Event.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.model.event; 2 | 3 | import lombok.*; 4 | import ru.practicum.explorewithme.model.category.Category; 5 | import ru.practicum.explorewithme.model.comment.Comment; 6 | import ru.practicum.explorewithme.model.compilation.Compilation; 7 | import ru.practicum.explorewithme.model.user.User; 8 | 9 | import javax.persistence.*; 10 | import java.time.LocalDateTime; 11 | import java.util.List; 12 | 13 | /** 14 | * Событие 15 | */ 16 | 17 | @Data 18 | @Entity 19 | @Builder 20 | @NoArgsConstructor 21 | @AllArgsConstructor 22 | @Table(name = "events") 23 | public class Event { 24 | @Id 25 | @GeneratedValue(strategy = GenerationType.IDENTITY) 26 | private Long id; 27 | private String title; 28 | private String annotation; 29 | private String description; 30 | @ManyToOne 31 | @JoinColumn(name = "category_id") 32 | private Category category; 33 | @Column(name = "created_on") 34 | private LocalDateTime createdOn; 35 | @Column(name = "event_date") 36 | private LocalDateTime eventDate; 37 | @ManyToOne 38 | @JoinColumn(name = "initiator_id") 39 | private User initiator; 40 | @Column(name = "paid") 41 | private Boolean paid; 42 | @Column(name = "participant_limit") 43 | private Integer participantLimit; 44 | @Column(name = "published_on") 45 | private LocalDateTime publishedOn; 46 | @Column(name = "request_moderation") 47 | private Boolean requestModeration; 48 | @Enumerated(EnumType.STRING) 49 | private EventState state; 50 | @AttributeOverrides({ 51 | @AttributeOverride(name = "lat", column = @Column(name = "location_lat")), 52 | @AttributeOverride(name = "lon", column = @Column(name = "location_lon")) 53 | }) 54 | private Location location; 55 | @ManyToMany(mappedBy = "events") 56 | @ToString.Exclude 57 | private List compilations; 58 | @OneToMany 59 | @JoinColumn(name = "event_id") 60 | private List comments; 61 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/model/event/EventSort.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.model.event; 2 | 3 | public enum EventSort { 4 | EVENT_DATE, 5 | VIEWS 6 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/model/event/EventState.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.model.event; 2 | 3 | public enum EventState { 4 | PENDING, 5 | PUBLISHED, 6 | CANCELED 7 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/model/event/Location.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.model.event; 2 | 3 | import lombok.*; 4 | 5 | import javax.persistence.*; 6 | 7 | /** 8 | * Место проведения события (широта / долгота) 9 | */ 10 | 11 | @Data 12 | @Builder 13 | @Embeddable 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | public class Location { 17 | private Float lat; 18 | private Float lon; 19 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/model/request/Request.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.model.request; 2 | 3 | import lombok.*; 4 | import ru.practicum.explorewithme.dto.request.RequestStatus; 5 | 6 | import javax.persistence.*; 7 | import java.time.LocalDateTime; 8 | 9 | /** 10 | * Запрос на участие в событии 11 | */ 12 | 13 | @Data 14 | @Entity 15 | @Builder 16 | @NoArgsConstructor 17 | @AllArgsConstructor 18 | @Table(name = "requests") 19 | public class Request { 20 | @Id 21 | @GeneratedValue(strategy = GenerationType.IDENTITY) 22 | private Long id; 23 | @JoinColumn(name = "event_id") 24 | @Column(name = "created") 25 | private LocalDateTime created; 26 | private Long eventId; 27 | @JoinColumn(name = "requester_id") 28 | private Long requesterId; 29 | @Enumerated(EnumType.STRING) 30 | @Column(name = "status") 31 | private RequestStatus status; 32 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/model/user/User.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.model.user; 2 | 3 | import lombok.*; 4 | 5 | import javax.persistence.*; 6 | 7 | /** 8 | * Пользователь 9 | */ 10 | 11 | @Data 12 | @Entity 13 | @Builder 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | @Table(name = "users") 17 | public class User { 18 | @Id 19 | @GeneratedValue(strategy = GenerationType.IDENTITY) 20 | private Long id; 21 | private String name; 22 | private String email; 23 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/repository/CategoryRepository.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import org.springframework.stereotype.Repository; 5 | import ru.practicum.explorewithme.model.category.Category; 6 | 7 | @Repository 8 | public interface CategoryRepository extends JpaRepository { 9 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/repository/CommentRepository.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import org.springframework.stereotype.Repository; 5 | import ru.practicum.explorewithme.model.comment.Comment; 6 | 7 | import java.util.List; 8 | 9 | @Repository 10 | public interface CommentRepository extends JpaRepository { 11 | 12 | List findAllCommentsByEventId(Long eventId); 13 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/repository/CompilationRepository.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.repository; 2 | 3 | import org.springframework.data.domain.Pageable; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.data.jpa.repository.*; 6 | import org.springframework.stereotype.Repository; 7 | import ru.practicum.explorewithme.model.compilation.Compilation; 8 | 9 | import java.util.List; 10 | 11 | @Repository 12 | public interface CompilationRepository extends JpaRepository { 13 | 14 | @Query("SELECT c FROM Compilation AS c " + 15 | "WHERE :pinned IS NULL OR c.pinned = :pinned") 16 | List findAllByPinned(Boolean pinned, Pageable pageable); 17 | 18 | @Modifying 19 | @Query("update Compilation as c " + 20 | "set c.pinned = ?1 " + 21 | "where c.id = ?2") 22 | void setCompilationPinned(boolean pinned, Long compId); 23 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/repository/EventRepository.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.repository; 2 | 3 | import org.springframework.data.domain.*; 4 | import org.springframework.data.jpa.repository.*; 5 | import org.springframework.stereotype.Repository; 6 | import ru.practicum.explorewithme.model.event.Event; 7 | import ru.practicum.explorewithme.model.event.EventState; 8 | 9 | import java.time.LocalDateTime; 10 | import java.util.*; 11 | 12 | @Repository 13 | public interface EventRepository extends JpaRepository { 14 | List findAllByInitiatorId(Long userId, Pageable pageable); 15 | 16 | @Modifying 17 | @Query("update Event e set e.state = ?1 where e.id = ?2") 18 | void cancelEvent(EventState state, Long eventId); 19 | 20 | @Query("SELECT e FROM Event AS e " + 21 | "WHERE ((:text) IS NULL " + 22 | "OR UPPER(e.annotation) LIKE UPPER(CONCAT('%', :text, '%')) " + 23 | "OR UPPER(e.description) LIKE UPPER(CONCAT('%', :text, '%'))) " + 24 | "AND ((:categories) IS NULL OR e.category.id IN :categories) " + 25 | "AND ((:paid) IS NULL OR e.paid = :paid) " + 26 | "AND (e.eventDate >= :rangeStart) " + 27 | "AND (CAST(:rangeEnd AS date) IS NULL OR e.eventDate <= :rangeEnd)") 28 | List findEvents(String text, 29 | List categories, 30 | Boolean paid, 31 | LocalDateTime rangeStart, 32 | LocalDateTime rangeEnd); 33 | 34 | @Query("SELECT e FROM Event AS e " + 35 | "WHERE ((:users) IS NULL OR e.initiator.id IN :users) " + 36 | "AND ((:states) IS NULL OR e.state IN :states) " + 37 | "AND ((:categories) IS NULL OR e.category.id IN :categories) " + 38 | "AND (e.eventDate >= :rangeStart) " + 39 | "AND (CAST(:rangeEnd AS date) IS NULL OR e.eventDate <= :rangeEnd)") 40 | List searchEvents(List users, 41 | List states, 42 | List categories, 43 | LocalDateTime rangeStart, 44 | LocalDateTime rangeEnd, 45 | Pageable pageable); 46 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/repository/RequestRepository.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import org.springframework.data.jpa.repository.Modifying; 5 | import org.springframework.data.jpa.repository.Query; 6 | import org.springframework.stereotype.Repository; 7 | import org.springframework.transaction.annotation.Transactional; 8 | import ru.practicum.explorewithme.model.request.Request; 9 | 10 | import java.util.List; 11 | 12 | @Repository 13 | public interface RequestRepository extends JpaRepository { 14 | List findAllByRequesterId(Long userId); 15 | 16 | List findAllByEventId(Long eventId); 17 | 18 | @Query(value = "select count(*) " + 19 | "from requests " + 20 | "where event_id = ?1 " + 21 | "and status = 'CONFIRMED'", 22 | nativeQuery = true) 23 | Integer getConfirmedRequests(Long eventId); 24 | 25 | @Modifying 26 | @Transactional 27 | @Query("UPDATE Request AS r " + 28 | "SET r.status = 'REJECTED' " + 29 | "WHERE r.status = 'PENDING' " + 30 | "AND r.eventId = ?1") 31 | void rejectPendingRequests(Long eventId); 32 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import org.springframework.stereotype.Repository; 5 | import ru.practicum.explorewithme.model.user.User; 6 | 7 | import java.util.List; 8 | import java.util.Set; 9 | 10 | @Repository 11 | public interface UserRepository extends JpaRepository { 12 | List findUsersByIdIn(Set ids); 13 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/service/admin/CategoryAdminService.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.admin; 2 | 3 | import ru.practicum.explorewithme.dto.category.*; 4 | 5 | /** 6 | * Реализация API для работы с категориями на уровне администратора 7 | */ 8 | 9 | public interface CategoryAdminService { 10 | 11 | /** 12 | * Обновить информацию о категории событий 13 | * @param categoryDto 14 | * @return CategoryDto 15 | */ 16 | CategoryDto update(CategoryDto categoryDto); 17 | 18 | /** 19 | * Сохранить новую категорию событий 20 | * @param categoryCreateDto 21 | * @return CategoryDto 22 | */ 23 | CategoryDto save(CategoryCreateDto categoryCreateDto); 24 | 25 | /** 26 | * Удалить категорию событий по идентификатору 27 | * @param categoryId идентификатор категории событий 28 | */ 29 | void delete(Long categoryId); 30 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/service/admin/CommentAdminService.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.admin; 2 | 3 | import ru.practicum.explorewithme.model.comment.Comment; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Реализация API для работы с отзывами на события на уровне администратора 9 | */ 10 | 11 | public interface CommentAdminService { 12 | 13 | /** 14 | * Удалить отзыв на событие пользователя, не соответсвующий правилам сообщества 15 | * @param commentId идентификатор отзыва на событие 16 | */ 17 | void deleteById(Long commentId); 18 | 19 | /** 20 | * Удалить все отзывы по идентификатору события 21 | * @param eventId идентификатор события 22 | */ 23 | void deleteAllCommentsByEvent(Long eventId); 24 | 25 | /** 26 | * Получить список все отзывов по идентификатору события 27 | * @param eventId идентификатор события 28 | * @return List 29 | */ 30 | List findAllCommentsByEventId(Long eventId); 31 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/service/admin/CompilationAdminService.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.admin; 2 | 3 | import ru.practicum.explorewithme.dto.compilation.CompilationCreateDto; 4 | import ru.practicum.explorewithme.dto.compilation.CompilationDto; 5 | 6 | /** 7 | * Реализация API для работы с подборками событий на уровне администратора 8 | */ 9 | 10 | public interface CompilationAdminService { 11 | 12 | /** 13 | * Сохранить новую подборку событий 14 | * @param compilationCreateDto 15 | * @return CompilationDto 16 | */ 17 | CompilationDto save(CompilationCreateDto compilationCreateDto); 18 | 19 | /** 20 | * Удалить подборку событий 21 | * @param compId идентификатор подборки событий 22 | */ 23 | void deleteById(Long compId); 24 | 25 | /** 26 | * Сохранить или удалить событие в подборке событий 27 | * @param compId идентификатор подборки событий 28 | * @param eventId идентификатор события 29 | * @param isDeleting true - удалить, false - сохранить 30 | */ 31 | void saveOrDeleteEventInCompilation(Long compId, Long eventId, boolean isDeleting); 32 | 33 | /** 34 | * Закрепить или открепить подборку событий на главной странице 35 | * @param compId идентификатор подборки событий 36 | * @param isPin true - открепить, false - закрепить 37 | */ 38 | void changeCompilationPin(Long compId, boolean isPin); 39 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/service/admin/EventAdminService.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.admin; 2 | 3 | import ru.practicum.explorewithme.dto.event.EventAdminUpdate; 4 | import ru.practicum.explorewithme.dto.event.EventFullDto; 5 | 6 | import java.time.LocalDateTime; 7 | import java.util.List; 8 | 9 | /** 10 | * Реализация API для работы с событиями на уровне администратора 11 | */ 12 | 13 | public interface EventAdminService { 14 | 15 | /** 16 | * Искать события по параметрам 17 | * @param users список пользователей 18 | * @param states список названий статусов событий (класс EventState) 19 | * @param categories список идентификаторов категорий событий 20 | * @param rangeStart дата и время начала периода поиска 21 | * @param rangeEnd дата и время окончания периода поиска 22 | * @param from количество объектов, default value = 0 23 | * @param size размер страницы default value = 10 24 | * @return List 25 | */ 26 | List searchEvents(List users, 27 | List states, 28 | List categories, 29 | LocalDateTime rangeStart, 30 | LocalDateTime rangeEnd, 31 | int from, 32 | int size); 33 | 34 | /** 35 | * Обновить информацию о событии 36 | * @param eventAdminUpdate 37 | * @param eventId идентификатор события 38 | * @return EventFullDto 39 | */ 40 | EventFullDto update(EventAdminUpdate eventAdminUpdate, Long eventId); 41 | 42 | /** 43 | * Опубликовать или отклонить публикацию события 44 | * @param eventId идентификатор события 45 | * @param isPublish true - публикация, false - отклонение публикации 46 | * @return 47 | */ 48 | EventFullDto changeEventState(Long eventId, boolean isPublish); 49 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/service/admin/UserAdminService.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.admin; 2 | 3 | import ru.practicum.explorewithme.dto.user.UserCreateDto; 4 | import ru.practicum.explorewithme.dto.user.UserDto; 5 | 6 | import java.util.List; 7 | import java.util.Set; 8 | 9 | /** 10 | * Реализация API для работы с пользователями на уровне администратора 11 | */ 12 | 13 | public interface UserAdminService { 14 | 15 | /** 16 | * Получить информацию о пользователях по параметрам 17 | * @param ids сортированный список идентификаторов пользователей 18 | * @param from количество объектов, default value = 0 19 | * @param size размер страницы default value = 10 20 | * @return List 21 | */ 22 | List findAll(Set ids, int from, int size); 23 | 24 | /** 25 | * Сохранить нового пользователя 26 | * @param userCreateDto 27 | * @return UserDto 28 | */ 29 | UserDto save(UserCreateDto userCreateDto); 30 | 31 | /** 32 | * Удалить пользователя по идентификатору 33 | * @param userId идентификатор пользователя 34 | */ 35 | void deleteById(Long userId); 36 | 37 | /** 38 | * Получить список пользователей 39 | * @param ids сортированный список идентификаторов пользователей 40 | * @return List 41 | */ 42 | List getUsersByIds(Set ids); 43 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/service/admin/impl/CategoryAdminServiceImpl.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.admin.impl; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.stereotype.Service; 6 | import org.springframework.transaction.annotation.Transactional; 7 | import ru.practicum.explorewithme.mapper.CategoryMapper; 8 | import ru.practicum.explorewithme.repository.CategoryRepository; 9 | import ru.practicum.explorewithme.dto.category.CategoryCreateDto; 10 | import ru.practicum.explorewithme.dto.category.CategoryDto; 11 | import ru.practicum.explorewithme.exception.ForbiddenException; 12 | import ru.practicum.explorewithme.exception.NotFoundException; 13 | import ru.practicum.explorewithme.model.category.Category; 14 | import ru.practicum.explorewithme.service.admin.CategoryAdminService; 15 | 16 | @Slf4j 17 | @Service 18 | @RequiredArgsConstructor 19 | @Transactional(readOnly = true) 20 | public class CategoryAdminServiceImpl implements CategoryAdminService { 21 | private final CategoryRepository repository; 22 | private final CategoryMapper mapper; 23 | 24 | @Transactional 25 | @Override 26 | public CategoryDto update(CategoryDto categoryDto) { 27 | Category categoryUpdate = mapper.toUpdate(categoryDto); 28 | Category category = findById(categoryUpdate.getId()); 29 | if (categoryUpdate.getName().equals(category.getName())) { 30 | throw new ForbiddenException("CategoryAdminService: Уже создана категория с названием=" + 31 | categoryUpdate.getName()); 32 | } 33 | category.setName(categoryUpdate.getName()); 34 | Category updated = repository.save(category); 35 | log.info("CategoryAdminService: Обновлена информация о категории №={}.", updated.getId()); 36 | return mapper.toDto(updated); 37 | } 38 | 39 | @Transactional 40 | @Override 41 | public CategoryDto save(CategoryCreateDto categoryCreateDto) { 42 | log.info("CategoryAdminService: Сохранение категории с названием={}.", categoryCreateDto.getName()); 43 | Category category = mapper.toModel(categoryCreateDto); 44 | Category saved = repository.save(category); 45 | log.info("CategoryAdminService: Сохранение категории={}.", saved); 46 | return mapper.toDto(saved); 47 | } 48 | 49 | @Transactional 50 | @Override 51 | public void delete(Long categoryId) { 52 | Category category = repository.findById(categoryId) 53 | .orElseThrow(() -> 54 | new NotFoundException("CategoryAdminService: Не найдена категория событий с id=" + categoryId)); 55 | repository.delete(category); 56 | log.info("CategoryAdminService: Удалена информация о категории №={}.", categoryId); 57 | } 58 | 59 | private Category findById(Long catId) { 60 | return repository.findById(catId) 61 | .orElseThrow(() -> 62 | new NotFoundException("CategoryAdminService: Не найдена категория с id=" + catId)); 63 | } 64 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/service/admin/impl/CommentAdminServiceImpl.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.admin.impl; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.stereotype.Service; 6 | import org.springframework.transaction.annotation.Transactional; 7 | import ru.practicum.explorewithme.model.comment.Comment; 8 | import ru.practicum.explorewithme.repository.CommentRepository; 9 | import ru.practicum.explorewithme.service.admin.CommentAdminService; 10 | 11 | import java.util.List; 12 | 13 | @Slf4j 14 | @Service 15 | @RequiredArgsConstructor 16 | @Transactional(readOnly = true) 17 | public class CommentAdminServiceImpl implements CommentAdminService { 18 | private final CommentRepository repository; 19 | 20 | @Transactional 21 | @Override 22 | public void deleteById(Long commentId) { 23 | repository.deleteById(commentId); 24 | log.info("CommentAdminService: Удален отзыв c id={}.", commentId); 25 | } 26 | 27 | @Transactional 28 | @Override 29 | public void deleteAllCommentsByEvent(Long eventId) { 30 | List comments = repository.findAllCommentsByEventId(eventId); 31 | for (Comment c : comments) { 32 | repository.deleteById(c.getId()); 33 | } 34 | log.info("CommentAdminService: Удалены все отзывы на событие c id={}.", eventId); 35 | } 36 | 37 | @Override 38 | public List findAllCommentsByEventId(Long eventId) { 39 | return repository.findAllCommentsByEventId(eventId); 40 | } 41 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/service/admin/impl/CompilationAdminServiceImpl.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.admin.impl; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.stereotype.Service; 6 | import org.springframework.transaction.annotation.Transactional; 7 | import ru.practicum.explorewithme.mapper.CompilationMapper; 8 | import ru.practicum.explorewithme.repository.CompilationRepository; 9 | import ru.practicum.explorewithme.dto.compilation.CompilationCreateDto; 10 | import ru.practicum.explorewithme.dto.compilation.CompilationDto; 11 | import ru.practicum.explorewithme.mapper.EventMapper; 12 | import ru.practicum.explorewithme.model.compilation.Compilation; 13 | import ru.practicum.explorewithme.model.event.Event; 14 | import ru.practicum.explorewithme.dto.event.EventShortDto; 15 | import ru.practicum.explorewithme.repository.EventRepository; 16 | import ru.practicum.explorewithme.exception.*; 17 | import ru.practicum.explorewithme.service.admin.CompilationAdminService; 18 | 19 | import java.util.*; 20 | import java.util.stream.Collectors; 21 | 22 | @Slf4j 23 | @Service 24 | @RequiredArgsConstructor 25 | @Transactional(readOnly = true) 26 | public class CompilationAdminServiceImpl implements CompilationAdminService { 27 | private final CompilationRepository repository; 28 | private final EventRepository eventRepository; 29 | private final CompilationMapper mapper; 30 | private final EventMapper eventMapper; 31 | 32 | @Transactional 33 | @Override 34 | public CompilationDto save(CompilationCreateDto compilationCreateDto) { 35 | Compilation compilation = mapper.toModel(compilationCreateDto); 36 | Compilation saved = repository.save(compilation); 37 | log.info("CompilationAdminService: Сохранена подборка событий с id={}.", saved.getId()); 38 | return mapper.toDto(saved, findCompilationEvents(saved)); 39 | } 40 | 41 | @Transactional 42 | @Override 43 | public void deleteById(Long compId) { 44 | repository.deleteById(compId); 45 | log.info("CompilationAdminService: Удалена информация о подборке событий №={}.", compId); 46 | } 47 | 48 | @Transactional 49 | @Override 50 | public void saveOrDeleteEventInCompilation(Long compId, Long eventId, boolean isDeleting) { 51 | Compilation compilation = findCompilationById(compId); 52 | Event event = findEventById(eventId); 53 | List events = findCompilationEvents(compilation) 54 | .stream().map(eventMapper::toModelFromShortDto).collect(Collectors.toList()); 55 | if (isDeleting) { 56 | if (events.contains(event)) { 57 | compilation.getEvents().remove(event); 58 | log.info("CompilationAdminService: Удалено событие с id={} из подборки событий с id={}.", 59 | eventId, compId); 60 | } 61 | } else { 62 | if (!events.contains(event)) { 63 | compilation.getEvents().add(event); 64 | log.info("CompilationAdminService: Добавлено событие с id={} в подборку событий с id={}.", 65 | eventId, compId); 66 | } 67 | } 68 | repository.save(compilation); 69 | } 70 | 71 | @Transactional 72 | @Override 73 | public void changeCompilationPin(Long compId, boolean isPin) { 74 | Compilation compilation = findCompilationById(compId); 75 | boolean pinned = compilation.isPinned(); 76 | if (isPin) { 77 | if (pinned) { 78 | repository.setCompilationPinned(false, compId); 79 | } else { 80 | throw new ForbiddenException(String.format("CompilationAdminService: Уже откреплена подборка с id=" + 81 | compId)); 82 | } 83 | } else { 84 | if (!pinned) { 85 | repository.setCompilationPinned(true, compId); 86 | } else { 87 | throw new ForbiddenException(String.format("CompilationAdminService: Уже закреплена подборка с id=" + 88 | compId)); 89 | } 90 | } 91 | } 92 | 93 | private Event findEventById(Long eventId) { 94 | return eventRepository.findById(eventId) 95 | .orElseThrow(() -> 96 | new NotFoundException("CompilationService: Не найдено событие с id=" + eventId)); 97 | } 98 | 99 | private List findCompilationEvents(Compilation compilation) { 100 | return compilation.getEvents() 101 | .stream() 102 | .map(eventMapper::toShortDto) 103 | .collect(Collectors.toList()); 104 | } 105 | 106 | private Compilation findCompilationById(Long compilationId) { 107 | return repository.findById(compilationId) 108 | .orElseThrow(() -> 109 | new NotFoundException("CompilationService: Не найдена подборка событий с id=" + compilationId)); 110 | } 111 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/service/admin/impl/EventAdminServiceImpl.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.admin.impl; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.data.domain.*; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.transaction.annotation.Transactional; 8 | import ru.practicum.explorewithme.model.category.Category; 9 | import ru.practicum.explorewithme.dto.event.EventAdminUpdate; 10 | import ru.practicum.explorewithme.dto.event.EventFullDto; 11 | import ru.practicum.explorewithme.mapper.EventMapper; 12 | import ru.practicum.explorewithme.repository.EventRepository; 13 | import ru.practicum.explorewithme.model.event.Location; 14 | import ru.practicum.explorewithme.exception.*; 15 | import ru.practicum.explorewithme.model.event.Event; 16 | import ru.practicum.explorewithme.model.event.EventState; 17 | import ru.practicum.explorewithme.service.admin.EventAdminService; 18 | 19 | import java.time.LocalDateTime; 20 | import java.util.*; 21 | import java.util.stream.Collectors; 22 | 23 | @Slf4j 24 | @Service 25 | @RequiredArgsConstructor 26 | @Transactional(readOnly = true) 27 | public class EventAdminServiceImpl implements EventAdminService { 28 | private final EventRepository repository; 29 | private final EventMapper mapper; 30 | 31 | @Override 32 | public List searchEvents(List users, 33 | List states, 34 | List categories, 35 | LocalDateTime rangeStart, 36 | LocalDateTime rangeEnd, 37 | int from, 38 | int size) { 39 | Pageable pageable = PageRequest.of(from / size, size); 40 | List eventStates = null; 41 | if (states != null && !states.isEmpty()) { 42 | eventStates = new ArrayList<>(); 43 | for (String state : states) { 44 | try { 45 | eventStates.add(EventState.valueOf(state)); 46 | } catch (IllegalArgumentException e) { 47 | throw new EventStateException("EventAdminService: При поиске списка событий передан " + 48 | "некорректный статус события."); 49 | } 50 | } 51 | } 52 | if (rangeStart == null) rangeStart = LocalDateTime.now(); 53 | return repository.searchEvents(users, eventStates, categories, 54 | rangeStart, rangeEnd, pageable) 55 | .stream() 56 | .map(mapper::toFullDto) 57 | .collect(Collectors.toList()); 58 | } 59 | 60 | @Transactional 61 | @Override 62 | public EventFullDto update(EventAdminUpdate eventAdminUpdate, Long eventId) { 63 | Event event = findEventById(eventId); 64 | if (eventAdminUpdate.getTitle() != null) event.setTitle(eventAdminUpdate.getTitle()); 65 | if (eventAdminUpdate.getAnnotation() != null) event.setAnnotation(eventAdminUpdate.getAnnotation()); 66 | if (eventAdminUpdate.getDescription() != null) event.setDescription(eventAdminUpdate.getDescription()); 67 | if (eventAdminUpdate.getEventDate() != null) event.setEventDate(eventAdminUpdate.getEventDate()); 68 | if (eventAdminUpdate.getLocation() != null) { 69 | event.setLocation(new Location(eventAdminUpdate.getLocation().getLat(), 70 | eventAdminUpdate.getLocation().getLon())); 71 | } 72 | if (eventAdminUpdate.getPaid() != null) event.setPaid(eventAdminUpdate.getPaid()); 73 | if (eventAdminUpdate.getCategory() != null) { 74 | event.setCategory(new Category(eventAdminUpdate.getCategory(), null)); 75 | } 76 | if (eventAdminUpdate.getParticipantLimit() != null) { 77 | event.setParticipantLimit(eventAdminUpdate.getParticipantLimit()); 78 | } 79 | if (eventAdminUpdate.getRequestModeration() != null) { 80 | event.setRequestModeration(eventAdminUpdate.getRequestModeration()); 81 | } 82 | Event updated = repository.save(event); 83 | log.info("EventAdminService: Обновлено событие с id={}.", updated.getId()); 84 | return mapper.toFullDto(updated); 85 | } 86 | 87 | @Transactional 88 | @Override 89 | public EventFullDto changeEventState(Long eventId, boolean isPublish) { 90 | Event event = findEventById(eventId); 91 | if (event.getEventDate().isBefore(LocalDateTime.now().plusHours(1))) { 92 | throw new ForbiddenException("EventPrivateService: Событие не может быть опубликовано ранее, " + 93 | "чем за 1 час до начала."); 94 | } 95 | if (!event.getState().equals(EventState.PENDING)) { 96 | throw new ForbiddenException("EventAdminService: Не установлен статус PENDING " + 97 | "для события с id=" + eventId); 98 | } 99 | if (isPublish) { 100 | event.setState(EventState.PUBLISHED); 101 | event.setPublishedOn(LocalDateTime.now()); 102 | } else { 103 | event.setState(EventState.CANCELED); 104 | } 105 | Event changed = repository.save(event); 106 | return mapper.toFullDto(changed); 107 | } 108 | 109 | /** 110 | * Поиск в репозитории события по идентификатору 111 | * если не найден, то вернуть NotFoundException 112 | * @param eventId идентификатор события 113 | * @return Event 114 | */ 115 | private Event findEventById(Long eventId) { // using in CompilationAdminService 116 | return repository.findById(eventId) 117 | .orElseThrow(() -> 118 | new NotFoundException("EventAdminService: Не найдено событие с id=" + eventId)); 119 | } 120 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/service/admin/impl/UserAdminServiceImpl.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.admin.impl; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.data.domain.*; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.transaction.annotation.Transactional; 8 | import ru.practicum.explorewithme.dto.user.UserCreateDto; 9 | import ru.practicum.explorewithme.dto.user.UserDto; 10 | import ru.practicum.explorewithme.exception.NotFoundException; 11 | import ru.practicum.explorewithme.model.user.User; 12 | import ru.practicum.explorewithme.mapper.UserMapper; 13 | import ru.practicum.explorewithme.repository.UserRepository; 14 | import ru.practicum.explorewithme.service.admin.UserAdminService; 15 | 16 | import java.util.List; 17 | import java.util.Set; 18 | import java.util.stream.Collectors; 19 | 20 | @Slf4j 21 | @Service 22 | @RequiredArgsConstructor 23 | @Transactional(readOnly = true) 24 | public class UserAdminServiceImpl implements UserAdminService { 25 | private final UserRepository repository; 26 | private final UserMapper mapper; 27 | 28 | @Override 29 | public List findAll(Set ids, int from, int size) { 30 | log.info("UserAdminService: Получение информации о пользователях с параметрами from={}, size={}", from, size); 31 | return repository.findAll(PageRequest.of(from / size, size)) 32 | .stream() 33 | .map(mapper::toDto) 34 | .collect(Collectors.toList()); 35 | } 36 | 37 | @Transactional 38 | @Override 39 | public UserDto save(UserCreateDto userCreateDto) { 40 | User user = mapper.toModel(userCreateDto); 41 | User saved = repository.save(user); 42 | log.info("UserAdminService: Cохранен пользователь=" + saved); 43 | return mapper.toDto(saved); 44 | } 45 | 46 | @Transactional 47 | @Override 48 | public void deleteById(Long userId) { 49 | boolean isExists = repository.existsById(userId); 50 | if (!isExists) { 51 | throw new NotFoundException("UserAdminService: Не найден пользователь для удаления с id=" + userId); 52 | } 53 | repository.deleteById(userId); 54 | log.info("UserAdminService: Удален пользователь с id=" + userId); 55 | } 56 | 57 | @Override 58 | public List getUsersByIds(Set ids) { 59 | return repository.findUsersByIdIn(ids) 60 | .stream() 61 | .map(mapper::toDto) 62 | .collect(Collectors.toList()); 63 | } 64 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/service/priv/CommentPrivateService.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.priv; 2 | 3 | import ru.practicum.explorewithme.dto.comment.CommentCreateDto; 4 | import ru.practicum.explorewithme.dto.comment.CommentDto; 5 | import ru.practicum.explorewithme.dto.comment.CommentUpdateDto; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * Реализация закрытого API для работы с отзывами на события 11 | * (для авторизованных пользователей) 12 | */ 13 | 14 | public interface CommentPrivateService { 15 | 16 | /** 17 | * Сохранить отзыв на событие 18 | * @param commentCreateDto объект отзыва на событие 19 | * @param userId идентификатор текущего пользователя 20 | * @param eventId идентификатор события 21 | * @return CommentDto 22 | */ 23 | CommentDto save(CommentCreateDto commentCreateDto, Long userId, Long eventId); 24 | 25 | /** 26 | * Редактировать отзыв на событие его автором 27 | * @param commentUpdateDto объект измененного отзыва 28 | * @param userId идентификатор текущего пользователя 29 | * @param eventId идентификатор события 30 | * @param commentId идентификатор отзыва на событие 31 | * @return CommentDto 32 | */ 33 | CommentDto update(CommentUpdateDto commentUpdateDto, Long userId, Long eventId, Long commentId); 34 | 35 | /** 36 | * Удалить отзыв на событие его автором 37 | * @param userId идентификатор автора отзыва 38 | * @param eventId идентификатор события 39 | * @param commentId идентификатор отзыва 40 | */ 41 | void delete(Long userId, Long eventId, Long commentId); 42 | 43 | /** 44 | * Получить список отзывов по идентификатору события 45 | * @param userId идентификатор автора отзыва 46 | * @param eventId идентификатор события 47 | * @return List 48 | */ 49 | List findCommentsByEventId(Long userId, Long eventId); 50 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/service/priv/EventPrivateService.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.priv; 2 | 3 | import ru.practicum.explorewithme.dto.event.EventCreateDto; 4 | import ru.practicum.explorewithme.dto.event.EventFullDto; 5 | import ru.practicum.explorewithme.dto.event.EventUpdateDto; 6 | import ru.practicum.explorewithme.dto.request.RequestDto; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * Реализация закрытого API для работы с событиями 12 | * (для авторизованных пользователей) 13 | */ 14 | public interface EventPrivateService { 15 | 16 | /** 17 | * Получить список событий добавленных текущим пользователем с параметрами 18 | * @param userId идентификатор текущего пользователя 19 | * @param from количество объектов, default value = 0 20 | * @param size размер страницы default value = 10 21 | * @return List 22 | */ 23 | List findEventByInitiatorId(Long userId, int from, int size); 24 | 25 | /** 26 | * Изменить событие добавленное текущим пользователем 27 | * @param eventUpdateDto 28 | * @param userId идентификатор текущего пользователя 29 | * @return EventFullDto 30 | */ 31 | EventFullDto update(EventUpdateDto eventUpdateDto, Long userId); 32 | 33 | /** 34 | * Сохранить новое событие добавленное текущим пользователем 35 | * @param eventCreateDto 36 | * @param userId идентификатор текущего пользователя 37 | * @return EventFullDto 38 | */ 39 | EventFullDto save(EventCreateDto eventCreateDto, Long userId); 40 | 41 | /** 42 | * Получмит полную информацию о событии добавленном текущим пользователем 43 | * @param userId идентификатор текущего пользователя 44 | * @param eventId идентификатор события 45 | * @return EventFullDto 46 | */ 47 | EventFullDto findEventInfoByInitiator(Long userId, Long eventId); 48 | 49 | /** 50 | * Отменить событие добавленное текущим пользователем 51 | * @param userId идентификатор текущего пользователя 52 | * @param eventId идентификатор события 53 | * @return EventFullDto 54 | */ 55 | EventFullDto cancel(Long userId, Long eventId); 56 | 57 | /** 58 | * Получить информацию о запросах на участие в событии текущего пользователя 59 | * @param userId идентификатор текущего пользователя 60 | * @param eventId идентификатор события 61 | * @return List 62 | */ 63 | List getAllRequestsByEventId(Long userId, Long eventId); 64 | 65 | /** 66 | * Подтвердить или отклонить чужую заявку на участие в событии созданном текущим пользователем 67 | * @param userId идентификатор текущего пользователя 68 | * @param eventId идентификатор события 69 | * @param requestId идентификатор заявки на участие другим пользователем 70 | * @param isApproved true - подтвердить, false - отклонить 71 | * @return RequestDto 72 | */ 73 | RequestDto changeRequestStatus(Long userId, Long eventId, Long requestId, boolean isApproved); 74 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/service/priv/RequestPrivateService.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.priv; 2 | 3 | import ru.practicum.explorewithme.dto.request.RequestDto; 4 | import ru.practicum.explorewithme.model.event.Event; 5 | import ru.practicum.explorewithme.model.user.User; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * Реализация закрытого API для работы с запросами текущего пользователя на участие в событиях 11 | * (для авторизованных пользователей) 12 | */ 13 | 14 | public interface RequestPrivateService { 15 | 16 | /** 17 | * Получить список запросов на участие в событиях текущего пользователя 18 | * @param userId идентификатор текущего пользователя 19 | * @return List 20 | */ 21 | List findRequests(Long userId); 22 | 23 | /** 24 | * Сохранить запрос от текущего пользователя на участие в событии 25 | * @param userId идентификатор текущего пользователя 26 | * @param eventId идентификатор события 27 | * @return RequestDto 28 | */ 29 | RequestDto save(Long userId, Long eventId); 30 | 31 | /** 32 | * Отмена запроса на участие в событии текущим пользователем 33 | * @param userId идентификатор текущего пользователя 34 | * @param requestId идентификатор запроса на участие в событии 35 | * @return RequestDto 36 | */ 37 | RequestDto cancelRequest(Long userId, Long requestId); 38 | 39 | /** 40 | * Искать в репозитории пользователя по идентификатору 41 | * если не найден, то вернуть NotFoundException 42 | * @param userId идентификатор пользователя 43 | * @return User 44 | */ 45 | User findUserById(Long userId); 46 | 47 | /** 48 | * Искать в репозитории события по идентификатору 49 | * если не найден, то вернуть NotFoundException 50 | * @param eventId идентификатор события 51 | * @return Event 52 | */ 53 | Event findEventById(Long eventId); 54 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/service/priv/impl/CommentPrivateServiceImpl.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.priv.impl; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.stereotype.Service; 6 | import org.springframework.transaction.annotation.Transactional; 7 | import ru.practicum.explorewithme.dto.comment.CommentCreateDto; 8 | import ru.practicum.explorewithme.dto.comment.CommentDto; 9 | import ru.practicum.explorewithme.dto.comment.CommentUpdateDto; 10 | import ru.practicum.explorewithme.exception.ForbiddenException; 11 | import ru.practicum.explorewithme.exception.NotFoundException; 12 | import ru.practicum.explorewithme.mapper.CommentMapper; 13 | import ru.practicum.explorewithme.model.comment.Comment; 14 | import ru.practicum.explorewithme.model.event.Event; 15 | import ru.practicum.explorewithme.model.user.User; 16 | import ru.practicum.explorewithme.repository.CommentRepository; 17 | import ru.practicum.explorewithme.service.priv.CommentPrivateService; 18 | import ru.practicum.explorewithme.service.priv.RequestPrivateService; 19 | 20 | import java.util.List; 21 | import java.util.Objects; 22 | import java.util.stream.Collectors; 23 | 24 | @Slf4j 25 | @Service 26 | @RequiredArgsConstructor 27 | @Transactional(readOnly = true) 28 | public class CommentPrivateServiceImpl implements CommentPrivateService { 29 | private final CommentRepository repository; 30 | private final CommentMapper mapper; 31 | private final RequestPrivateService requestPrivateService; 32 | 33 | @Override 34 | @Transactional 35 | public CommentDto save(CommentCreateDto commentCreateDto, Long userId, Long eventId) { 36 | Event event = requestPrivateService.findEventById(eventId); 37 | User user = requestPrivateService.findUserById(userId); 38 | Comment comment = mapper.toNewModel(commentCreateDto, user, event); 39 | Comment saved = repository.save(comment); 40 | log.info("Сохранен отзыв с id={} на событие с id={}.", saved.getId(), eventId); 41 | return mapper.toDto(saved); 42 | } 43 | 44 | @Transactional 45 | @Override 46 | public CommentDto update(CommentUpdateDto commentUpdateDto, Long userId, Long eventId, Long commentId) { 47 | Comment comment = findCommentById(commentId); 48 | if (!Objects.equals(comment.getEvent().getId(), eventId)) { 49 | throw new ForbiddenException("CommentPrivateService: Для обновления отзыва неверно указан идентификатор " + 50 | "события id=" + eventId); 51 | } 52 | isAuthor(comment, userId); 53 | if (commentUpdateDto.getText() != null) { 54 | comment.setText(commentUpdateDto.getText()); 55 | } 56 | comment.setEdited(true); 57 | Comment updated = repository.save(comment); 58 | log.info("CommentPrivateService: Обновлен отзыв c id={} на событие c id={}.", updated.getId(), eventId); 59 | return mapper.toDto(updated); 60 | } 61 | 62 | @Override 63 | public void delete(Long userId, Long eventId, Long commentId) { 64 | Comment comment = findCommentById(commentId); 65 | isAuthor(comment, userId); 66 | repository.delete(comment); 67 | log.info("CommentPrivateService: Удален отзыв c id={} на событие c id={}.", commentId, eventId); 68 | 69 | } 70 | 71 | @Override 72 | public List findCommentsByEventId(Long userId, Long eventId) { 73 | Event event = requestPrivateService.findEventById(eventId); 74 | isInitiator(event, userId); 75 | log.info("CommentPrivateService: Запрошен список отзывов автором с id={}.", userId); 76 | return repository.findAllCommentsByEventId(eventId) 77 | .stream() 78 | .map(mapper::toDto) 79 | .collect(Collectors.toList()); 80 | } 81 | 82 | /** 83 | * Проверить является ли пользователь автором отзыва на событие 84 | * если нет, то вернуть ForbiddenException 85 | * @param comment объект отзыва на событие 86 | * @param userId идентификатор проверяемого пользователя 87 | */ 88 | private void isAuthor(Comment comment, Long userId) { 89 | if (!Objects.equals(comment.getAuthor().getId(), userId)) { 90 | throw new ForbiddenException("CommentPrivateService: Попытка не автором внести изменения или получить " + 91 | "информацию об отзыве на событие с id=" + comment.getId()); 92 | } 93 | } 94 | 95 | /** 96 | * Проверить является ли пользователь инициатором события 97 | * если нет, то вернуть ForbiddenException 98 | * @param event объект события 99 | * @param userId идентификатор проверяемого пользователя 100 | */ 101 | private void isInitiator(Event event, Long userId) { 102 | if (!Objects.equals(event.getInitiator().getId(), userId)) { 103 | throw new ForbiddenException("CommentPrivateService: Попытка не инициатором внести изменения или получить " + 104 | "информацию об отзыве на событие с id=" + event.getId()); 105 | } 106 | } 107 | 108 | /** 109 | * Найти в репозитории отзыв на событие по идентификатору 110 | * @param commentId идентификатор отзыва на событие 111 | * @return Comment 112 | */ 113 | private Comment findCommentById(Long commentId) { 114 | return repository.findById(commentId) 115 | .orElseThrow(() -> 116 | new NotFoundException("CommentPrivateService: Не найден отзыв на событие с id=" + commentId)); 117 | } 118 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/service/priv/impl/EventPrivateServiceImpl.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.priv.impl; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.data.domain.*; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.transaction.annotation.Transactional; 8 | import ru.practicum.explorewithme.model.category.Category; 9 | import ru.practicum.explorewithme.dto.request.RequestDto; 10 | import ru.practicum.explorewithme.dto.request.RequestStatus; 11 | import ru.practicum.explorewithme.dto.event.EventCreateDto; 12 | import ru.practicum.explorewithme.dto.event.EventFullDto; 13 | import ru.practicum.explorewithme.dto.event.EventUpdateDto; 14 | import ru.practicum.explorewithme.mapper.EventMapper; 15 | import ru.practicum.explorewithme.mapper.RequestMapper; 16 | import ru.practicum.explorewithme.model.request.Request; 17 | import ru.practicum.explorewithme.repository.EventRepository; 18 | import ru.practicum.explorewithme.exception.*; 19 | import ru.practicum.explorewithme.model.event.Event; 20 | import ru.practicum.explorewithme.model.event.EventState; 21 | import ru.practicum.explorewithme.repository.RequestRepository; 22 | import ru.practicum.explorewithme.repository.UserRepository; 23 | import ru.practicum.explorewithme.service.priv.EventPrivateService; 24 | 25 | import java.util.*; 26 | import java.util.stream.Collectors; 27 | 28 | import static ru.practicum.explorewithme.model.event.EventState.CANCELED; 29 | 30 | @Slf4j 31 | @Service 32 | @RequiredArgsConstructor 33 | @Transactional(readOnly = true) 34 | public class EventPrivateServiceImpl implements EventPrivateService { 35 | private final EventRepository repository; 36 | private final UserRepository userRepository; 37 | private final RequestRepository requestRepository; 38 | private final EventMapper mapper; 39 | private final RequestMapper requestMapper; 40 | 41 | @Override 42 | public List findEventByInitiatorId(Long userId, int from, int size) { 43 | findUserById(userId); 44 | Pageable pageable = PageRequest.of(from / size, size); 45 | return repository.findAllByInitiatorId(userId, pageable) 46 | .stream() 47 | .map(mapper::toFullDto) 48 | .collect(Collectors.toList()); 49 | } 50 | 51 | @Transactional 52 | @Override 53 | public EventFullDto update(EventUpdateDto eventUpdateDto, Long userId) { 54 | Event event = findEventById(eventUpdateDto.getEventId()); 55 | isInitiator(event, userId); 56 | if (eventUpdateDto.getTitle() != null) event.setTitle(eventUpdateDto.getTitle()); 57 | if (eventUpdateDto.getAnnotation() != null) event.setAnnotation(eventUpdateDto.getAnnotation()); 58 | if (eventUpdateDto.getDescription() != null) event.setDescription(eventUpdateDto.getDescription()); 59 | if (eventUpdateDto.getEventDate() != null) event.setEventDate(eventUpdateDto.getEventDate()); 60 | if (eventUpdateDto.getPaid() != null) event.setPaid(eventUpdateDto.getPaid()); 61 | if (eventUpdateDto.getCategory() != null) { 62 | event.setCategory(new Category(eventUpdateDto.getCategory(), null)); 63 | } 64 | if (eventUpdateDto.getParticipantLimit() != null) { 65 | event.setParticipantLimit(eventUpdateDto.getParticipantLimit()); 66 | } 67 | event.setState(EventState.PENDING); 68 | Event updated = repository.save(event); 69 | log.info("EventPrivateService: Обновлено событие с id={}.", updated.getId()); 70 | return mapper.toFullDto(updated); 71 | } 72 | 73 | @Transactional 74 | @Override 75 | public EventFullDto save(EventCreateDto eventCreateDto, Long userId) { 76 | Event event = mapper.toModel(eventCreateDto, userId); 77 | Event saved = repository.save(event); 78 | log.info("EventPrivateService: Сохранено событие={}.", saved); 79 | return mapper.toFullDto(saved); 80 | } 81 | 82 | @Override 83 | public EventFullDto findEventInfoByInitiator(Long userId, Long eventId) { 84 | Event event = findEventById(eventId); 85 | isInitiator(event, userId); 86 | return mapper.toFullDto(event); 87 | } 88 | 89 | @Transactional 90 | @Override 91 | public EventFullDto cancel(Long userId, Long eventId) { 92 | EventFullDto eventFullDto = findEventInfoByInitiator(userId, eventId); 93 | repository.cancelEvent(CANCELED, eventId); 94 | eventFullDto.setState("CANCELED"); 95 | log.info("EventPrivateService: Отменено событие с id={}.", eventFullDto.getId()); 96 | return eventFullDto; 97 | } 98 | 99 | @Override 100 | public List getAllRequestsByEventId(Long userId, Long eventId) { 101 | findEventInfoByInitiator(userId, eventId); 102 | return requestRepository.findAllByEventId(eventId) 103 | .stream() 104 | .map(requestMapper::toDto) 105 | .collect(Collectors.toList()); 106 | } 107 | 108 | @Transactional 109 | @Override 110 | public RequestDto changeRequestStatus(Long userId, Long eventId, Long requestId, boolean isApproved) { 111 | EventFullDto event = findEventInfoByInitiator(userId, eventId); 112 | if (event.getConfirmedRequests() >= event.getParticipantLimit() && event.getParticipantLimit() != 0) { 113 | throw new ForbiddenException("EventPrivateService: Превышен лимит заявок на участие в событии с id=" + 114 | eventId); 115 | } 116 | Request request = findRequestById(requestId); 117 | if (isApproved) { 118 | request.setStatus(RequestStatus.CONFIRMED); 119 | } else { 120 | request.setStatus(RequestStatus.REJECTED); 121 | } 122 | Request changed = requestRepository.save(request); 123 | log.info("EventPrivateService: Изменен статус заявки №{}, статус={} на участие в событии с id={}.", 124 | requestId, changed.getStatus(), changed.getId()); 125 | if (event.getConfirmedRequests() == event.getParticipantLimit() - 1) { 126 | requestRepository.rejectPendingRequests(eventId); 127 | } 128 | return requestMapper.toDto(changed); 129 | } 130 | 131 | /** 132 | * Искать событие по идентификатору 133 | * если не найден, то вернуть NotFoundException 134 | * @param eventId идентификатор события 135 | * @return Event 136 | */ 137 | private Event findEventById(Long eventId) { 138 | return repository.findById(eventId) 139 | .orElseThrow(() -> 140 | new NotFoundException("EventPrivateService: Не найдено событие с id=" + eventId)); 141 | } 142 | 143 | /** 144 | * Искать в репозитории запроса на участие в событии по идентификатору 145 | * если не найден, то вернуть NotFoundException 146 | * @param requestId идентификатор запроса 147 | * @return Request 148 | */ 149 | private Request findRequestById(Long requestId) { 150 | return requestRepository.findById(requestId) 151 | .orElseThrow(() -> 152 | new NotFoundException("EventPrivateService: Не найден запрос на участие в событии с id=" + 153 | requestId)); 154 | } 155 | 156 | /** 157 | * Искать в репозитории пользователя по идентификатору 158 | * если не найден, то вернуть NotFoundException 159 | * @param userId идентификатор пользователя 160 | */ 161 | private void findUserById(Long userId) { 162 | userRepository.findById(userId) 163 | .orElseThrow(() -> 164 | new NotFoundException("EventPrivateService: Не найден пользователь с id=" + userId)); 165 | } 166 | 167 | /** 168 | * Проверить является ли пользователь инициатором события 169 | * если не найден, то вернуть ForbiddenException 170 | * @param event объект события 171 | * @param userId идентификатор проверяемого пользователя 172 | */ 173 | private void isInitiator(Event event, Long userId) { 174 | if (!Objects.equals(event.getInitiator().getId(), userId)) { 175 | throw new ForbiddenException("EventPrivateService: Попытка не инициатором внести изменения или получить " + 176 | "информацию о событии с id=" + event.getId()); 177 | } 178 | } 179 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/service/priv/impl/RequestPrivateServiceImpl.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.priv.impl; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.stereotype.Service; 6 | import org.springframework.transaction.annotation.Transactional; 7 | import ru.practicum.explorewithme.dto.request.RequestDto; 8 | import ru.practicum.explorewithme.dto.request.RequestStatus; 9 | import ru.practicum.explorewithme.mapper.RequestMapper; 10 | import ru.practicum.explorewithme.model.event.EventState; 11 | import ru.practicum.explorewithme.model.event.Event; 12 | import ru.practicum.explorewithme.model.request.Request; 13 | import ru.practicum.explorewithme.repository.EventRepository; 14 | import ru.practicum.explorewithme.exception.*; 15 | import ru.practicum.explorewithme.repository.RequestRepository; 16 | import ru.practicum.explorewithme.repository.UserRepository; 17 | import ru.practicum.explorewithme.model.user.User; 18 | import ru.practicum.explorewithme.service.priv.RequestPrivateService; 19 | 20 | import javax.persistence.EntityManager; 21 | import java.time.LocalDateTime; 22 | import java.util.*; 23 | import java.util.stream.Collectors; 24 | 25 | @Slf4j 26 | @Service 27 | @RequiredArgsConstructor 28 | @Transactional(readOnly = true) 29 | public class RequestPrivateServiceImpl implements RequestPrivateService { 30 | private final RequestRepository repository; 31 | private final EventRepository eventRepository; 32 | private final UserRepository userRepository; 33 | private final EntityManager entityManager; 34 | private final RequestMapper mapper; 35 | 36 | public List findRequests(Long userId) { 37 | log.info("RequestPrivateService: Получение информации о заявках текущего пользователя id={} " + 38 | "на участие в чужих событиях.", userId); 39 | return repository.findAllByRequesterId(userId) 40 | .stream() 41 | .map(mapper::toDto) 42 | .collect(Collectors.toList()); 43 | } 44 | 45 | @Transactional 46 | public RequestDto save(Long userId, Long eventId) { 47 | Event event = findEventById(eventId); 48 | userRepository.existsById(userId); 49 | if (Objects.equals(event.getInitiator().getId(), userId)) { 50 | throw new ForbiddenException("RequestPrivateService: Запрос на участие направлен инициатором событис с id=" 51 | + event.getId()); 52 | } 53 | if (!event.getState().equals(EventState.PUBLISHED)) { 54 | throw new ForbiddenException("RequestPrivateService: Направлен запрос на участие " + 55 | "в неопубликованном событии с id=" + event.getId()); 56 | } 57 | int confirmedRequests = repository.getConfirmedRequests(eventId); 58 | int participantLimit = event.getParticipantLimit(); 59 | if (participantLimit != 0) { 60 | if (participantLimit <= confirmedRequests) { 61 | throw new ForbiddenException("RequestPrivateService: Превышен лимит участников на событие с id=" + 62 | event.getId()); 63 | } 64 | } 65 | Request request = Request.builder() 66 | .created(LocalDateTime.now()) 67 | .eventId(eventId) 68 | .requesterId(userId) 69 | .build(); 70 | if (event.getRequestModeration()) { 71 | request.setStatus(RequestStatus.PENDING); 72 | } else { 73 | request.setStatus(RequestStatus.CONFIRMED); 74 | } 75 | Request saved = repository.save(request); 76 | entityManager.refresh(saved); 77 | log.info("RequestPrivateService: Сохранена заявка на участие c id={} в событии с id={} " + 78 | "текущего пользователя с id={}.", saved.getId(), eventId, userId); 79 | return mapper.toDto(saved); 80 | } 81 | 82 | @Transactional 83 | public RequestDto cancelRequest(Long userId, Long requestId) { 84 | User user = findUserById(userId); 85 | Request request = findRequestById(requestId); 86 | if (!Objects.equals(request.getRequesterId(), user.getId())) { 87 | throw new ForbiddenException("RequestPrivateService: Отменить запрос на участие в мероприятии " + 88 | "может только пользователь его создавший."); 89 | } 90 | if (Objects.equals(request.getStatus(), RequestStatus.CANCELED)) { 91 | throw new ForbiddenException("RequestPrivateService: Запрос на участие в мероприятии уже отменен."); 92 | } 93 | request.setStatus(RequestStatus.CANCELED); 94 | repository.save(request); 95 | log.info("RequestPrivateService: Отменена заявка на участие в событии с id={} текущего пользователя с id={}.", 96 | request.getEventId(), userId); 97 | return mapper.toDto(request); 98 | } 99 | 100 | /** 101 | * Искать в репозитории запроса на участие в событии по идентификатору 102 | * если не найден, то вернуть NotFoundException 103 | * @param requestId идентификатор запроса 104 | * @return Request 105 | */ 106 | private Request findRequestById(Long requestId) { 107 | return repository.findById(requestId) 108 | .orElseThrow(() -> 109 | new NotFoundException("RequestPrivateService: Не найден запрос с id=" + requestId)); 110 | } 111 | 112 | @Override 113 | public User findUserById(Long userId) { 114 | return userRepository.findById(userId) 115 | .orElseThrow(() -> 116 | new NotFoundException("RequestPrivateService: Не найден пользователь с id=" + userId)); 117 | } 118 | 119 | @Override 120 | public Event findEventById(Long eventId) { 121 | return eventRepository.findById(eventId) 122 | .orElseThrow(() -> 123 | new NotFoundException("RequestPrivateService: Не найдено событие с id=" + eventId)); 124 | } 125 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/service/pub/CategoriesPublicService.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.pub; 2 | 3 | import ru.practicum.explorewithme.dto.category.CategoryDto; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Реализация публичного API для работы с категориями 9 | */ 10 | 11 | public interface CategoriesPublicService { 12 | 13 | /** 14 | * Получить категории события по параметрам 15 | * @param from количество объектов, default value = 0 16 | * @param size размер страницы default value = 10 17 | * @return List 18 | */ 19 | List findAll(int from, int size); 20 | 21 | /** 22 | * Получить информацию о категории по идентификатору 23 | * @param categoryId идентификатор категории 24 | * @return CategoryDto 25 | */ 26 | CategoryDto findById(Long categoryId); 27 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/service/pub/CommentPublicService.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.pub; 2 | 3 | import ru.practicum.explorewithme.dto.comment.CommentDto; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Реализация публичного API для просмотра отзывов на события 9 | */ 10 | 11 | public interface CommentPublicService { 12 | 13 | /** 14 | * Получить список отзывов на события по параметрам 15 | * @param from количество объектов, default value = 0 16 | * @param size размер страницы default value = 10 17 | * @return List 18 | */ 19 | List findAll(int from, int size); 20 | 21 | /** 22 | * Получить отзыв на событие по идентификатору 23 | * @param commentId идентификатор отзыва на событие 24 | * @return CommentDto 25 | */ 26 | CommentDto findCommentById(Long commentId); 27 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/service/pub/CompilationPublicService.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.pub; 2 | 3 | import ru.practicum.explorewithme.dto.compilation.CompilationDto; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Реализация публичного API для работы с подборками событий 9 | */ 10 | 11 | public interface CompilationPublicService { 12 | 13 | /** 14 | * Получить подборку событий по параметрам 15 | * @param pinned true - закрепленные на главной странице, false - не закрепленные 16 | * @param from количество объектов, default value = 0 17 | * @param size размер страницы default value = 10 18 | * @return List 19 | */ 20 | List findAll(Boolean pinned, int from, int size); 21 | 22 | /** 23 | * Получить подборку событий по идентификатору 24 | * @param compId идентификатор подборки событий 25 | * @return CompilationDto 26 | */ 27 | CompilationDto findById(Long compId); 28 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/service/pub/EventPublicService.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.pub; 2 | 3 | import ru.practicum.explorewithme.dto.event.EventFullDto; 4 | import ru.practicum.explorewithme.dto.event.EventShortDto; 5 | 6 | import java.time.LocalDateTime; 7 | import java.util.List; 8 | 9 | /** 10 | * Реализация публичного API для работы с событиями 11 | */ 12 | 13 | public interface EventPublicService { 14 | 15 | /** 16 | * Получить событие с возможностью фильтрации по параметрам и с возможностью фильтрации 17 | * StatsClient передает на сервер статистики HttpRequest для сохранения нового EndpointHit в таблице Stats 18 | * ViewStats увеличен на 1 просмотр 19 | * @param text текст для поиска по аннотациями и описанию событий 20 | * @param categories список идентификаторов категорий в которых будет вестись поиск 21 | * @param paid true - платные события, false - бесплатные события 22 | * @param rangeStart дата и время начала периода поиска 23 | * @param rangeEnd дата и время окончания периода поиска 24 | * @param onlyAvailable true - исчерпан лимит запросов на участие, false - не исчерпан лимит запросов на участие 25 | * @param sort сортировка: EVENT_DATE - по дате события, VIEWS - по количеству просмотров 26 | * @param from количество объектов, default value = 0 27 | * @param size размер страницы default value = 10 28 | * @return List 29 | */ 30 | List getEventsSort(String text, 31 | List categories, 32 | Boolean paid, 33 | LocalDateTime rangeStart, 34 | LocalDateTime rangeEnd, 35 | boolean onlyAvailable, 36 | String sort, 37 | int from, 38 | int size); 39 | 40 | /** 41 | * Получить полную информацию об опубликованном событии по идентификатору 42 | * StatsClient передает на сервер статистики HttpRequest для сохранения нового EndpointHit в таблице Stats 43 | * ViewStats увеличен на 1 просмотр 44 | * @param eventId идентификатор события 45 | * @return EventFullDto 46 | */ 47 | EventFullDto findEventById(Long eventId); 48 | 49 | /** 50 | * Получить полную информацию о событии со списком отзывов и добавить 1 hit в Stats 51 | * @param eventId идентификатор события 52 | * @return EventFullDto 53 | */ 54 | EventFullDto getEventWithAllComments(Long eventId); 55 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/service/pub/impl/CategoriesPublicServiceImpl.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.pub.impl; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.data.domain.*; 6 | import org.springframework.transaction.annotation.Transactional; 7 | import org.springframework.stereotype.Service; 8 | import ru.practicum.explorewithme.mapper.CategoryMapper; 9 | import ru.practicum.explorewithme.repository.CategoryRepository; 10 | import ru.practicum.explorewithme.dto.category.CategoryDto; 11 | import ru.practicum.explorewithme.exception.NotFoundException; 12 | import ru.practicum.explorewithme.model.category.Category; 13 | import ru.practicum.explorewithme.service.pub.CategoriesPublicService; 14 | 15 | import java.util.*; 16 | import java.util.stream.Collectors; 17 | 18 | @Slf4j 19 | @Service 20 | @RequiredArgsConstructor 21 | @Transactional(readOnly = true) 22 | public class CategoriesPublicServiceImpl implements CategoriesPublicService { 23 | private final CategoryRepository repository; 24 | private final CategoryMapper mapper; 25 | 26 | @Override 27 | public List findAll(int from, int size) { 28 | log.info("PublicCategoriesService: Получение списка категорий."); 29 | return repository.findAll(PageRequest.of(from / size, size)) 30 | .stream() 31 | .map(mapper::toDto) 32 | .collect(Collectors.toList()); 33 | } 34 | 35 | @Override 36 | public CategoryDto findById(Long categoryId) { 37 | log.info("PublicCategoriesService: Получение категории по ее идентификатору."); 38 | Category category = repository.findById(categoryId) 39 | .orElseThrow(() -> new NotFoundException("PublicCategoriesService: Не найдена категория с id=" + 40 | categoryId)); 41 | return mapper.toDto(category); 42 | } 43 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/service/pub/impl/CommentPublicServiceImpl.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.pub.impl; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.data.domain.PageRequest; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.transaction.annotation.Transactional; 8 | import ru.practicum.explorewithme.dto.comment.CommentDto; 9 | import ru.practicum.explorewithme.exception.NotFoundException; 10 | import ru.practicum.explorewithme.mapper.CommentMapper; 11 | import ru.practicum.explorewithme.model.comment.Comment; 12 | import ru.practicum.explorewithme.repository.CommentRepository; 13 | import ru.practicum.explorewithme.service.pub.CommentPublicService; 14 | 15 | import java.util.List; 16 | import java.util.stream.Collectors; 17 | 18 | @Slf4j 19 | @Service 20 | @RequiredArgsConstructor 21 | @Transactional(readOnly = true) 22 | public class CommentPublicServiceImpl implements CommentPublicService { 23 | private final CommentRepository repository; 24 | private final CommentMapper mapper; 25 | 26 | @Override 27 | public List findAll(int from, int size) { 28 | log.info("PublicCategoriesService: Получение списка отзывов на события, где from={} и size={}.", from, size); 29 | return repository.findAll(PageRequest.of(from / size, size)) 30 | .stream() 31 | .map(mapper::toDto) 32 | .collect(Collectors.toList()); 33 | } 34 | 35 | @Override 36 | public CommentDto findCommentById(Long commentId) { 37 | Comment comment = repository.findById(commentId) 38 | .orElseThrow(() -> 39 | new NotFoundException("CommentPublicService: Не найден отзыв на событие с id=" + commentId)); 40 | return mapper.toDto(comment); 41 | } 42 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/service/pub/impl/CompilationPublicServiceImpl.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.pub.impl; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.data.domain.PageRequest; 5 | import org.springframework.stereotype.Service; 6 | import org.springframework.transaction.annotation.Transactional; 7 | import ru.practicum.explorewithme.mapper.CompilationMapper; 8 | import ru.practicum.explorewithme.repository.CompilationRepository; 9 | import ru.practicum.explorewithme.dto.compilation.CompilationDto; 10 | import ru.practicum.explorewithme.mapper.EventMapper; 11 | import ru.practicum.explorewithme.model.compilation.Compilation; 12 | import ru.practicum.explorewithme.model.event.Event; 13 | import ru.practicum.explorewithme.dto.event.EventShortDto; 14 | import ru.practicum.explorewithme.exception.NotFoundException; 15 | import ru.practicum.explorewithme.service.pub.CompilationPublicService; 16 | 17 | import java.util.*; 18 | import java.util.stream.Collectors; 19 | 20 | @Service 21 | @RequiredArgsConstructor 22 | @Transactional(readOnly = true) 23 | public class CompilationPublicServiceImpl implements CompilationPublicService { 24 | private final CompilationRepository repository; 25 | private final CompilationMapper mapper; 26 | private final EventMapper eventMapper; 27 | 28 | @Override 29 | public List findAll(Boolean pinned, int from, int size) { 30 | return repository.findAllByPinned(pinned, PageRequest.of(from / size, size)) 31 | .stream() 32 | .map(c -> mapper.toDto(c, findCompilationEvents(c))) 33 | .collect(Collectors.toList()); 34 | } 35 | 36 | @Override 37 | public CompilationDto findById(Long compId) { 38 | Compilation compilation = repository.findById(compId) 39 | .orElseThrow(() -> new NotFoundException("CompilationPublicService: Не найдена подборка событий " + 40 | "с id=" + compId)); 41 | return mapper.toDto(compilation, findCompilationEvents(compilation)); 42 | } 43 | 44 | /** 45 | * Получить список событий из подборки событий 46 | * @param compilation объект подборки событий 47 | * @return List 48 | */ 49 | private List findCompilationEvents(Compilation compilation) { 50 | List eventList = new ArrayList<>(compilation.getEvents()); 51 | return eventList.stream().map(eventMapper::toShortDto).collect(Collectors.toList()); 52 | } 53 | } -------------------------------------------------------------------------------- /server/src/main/java/ru/practicum/explorewithme/service/pub/impl/EventPublicServiceImpl.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service.pub.impl; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.stereotype.Service; 6 | import org.springframework.transaction.annotation.Transactional; 7 | import ru.practicum.explorewithme.dto.comment.CommentDto; 8 | import ru.practicum.explorewithme.dto.event.EventFullDto; 9 | import ru.practicum.explorewithme.dto.event.EventShortDto; 10 | import ru.practicum.explorewithme.mapper.CommentMapper; 11 | import ru.practicum.explorewithme.mapper.EventMapper; 12 | import ru.practicum.explorewithme.repository.CommentRepository; 13 | import ru.practicum.explorewithme.repository.EventRepository; 14 | import ru.practicum.explorewithme.exception.*; 15 | import ru.practicum.explorewithme.model.event.Event; 16 | import ru.practicum.explorewithme.model.event.EventSort; 17 | import ru.practicum.explorewithme.model.event.EventState; 18 | import ru.practicum.explorewithme.service.pub.EventPublicService; 19 | 20 | import java.time.LocalDateTime; 21 | import java.util.*; 22 | import java.util.stream.Collectors; 23 | 24 | @Slf4j 25 | @Service 26 | @RequiredArgsConstructor 27 | @Transactional(readOnly = true) 28 | public class EventPublicServiceImpl implements EventPublicService { 29 | private final EventRepository repository; 30 | private final CommentRepository commentRepository; 31 | private final EventMapper mapper; 32 | private final CommentMapper commentMapper; 33 | 34 | @Override 35 | public List getEventsSort(String text, 36 | List categories, 37 | Boolean paid, 38 | LocalDateTime rangeStart, 39 | LocalDateTime rangeEnd, 40 | boolean onlyAvailable, 41 | String sort, 42 | int from, 43 | int size) { 44 | log.info("EventPublicService: Получение списка событий с сортировкой по text={}, start={}, end={}.", 45 | text, rangeStart, rangeEnd); 46 | if (text.isBlank()) return Collections.emptyList(); 47 | if (rangeStart == null) rangeStart = LocalDateTime.now(); 48 | List events = repository.findEvents(text, categories, paid, rangeStart, rangeEnd) 49 | .stream() 50 | .map(mapper::toFullDto) 51 | .collect(Collectors.toList()); 52 | if (onlyAvailable) { 53 | events = events.stream() 54 | .filter(e -> e.getConfirmedRequests() < e.getParticipantLimit() || e.getParticipantLimit() == 0) 55 | .collect(Collectors.toList()); 56 | } 57 | if (sort != null) { 58 | EventSort extractedSort; 59 | try { 60 | extractedSort = EventSort.valueOf(sort); 61 | } catch (IllegalArgumentException e) { 62 | throw new EventSortException("EventPublicService: " + 63 | "При получени списка событий передан некорректный вид сортировки sort=" + sort); 64 | } 65 | switch (extractedSort) { 66 | case EVENT_DATE: 67 | events = events.stream() 68 | .sorted(Comparator.comparing(EventFullDto::getEventDate)) 69 | .collect(Collectors.toList()); 70 | break; 71 | case VIEWS: 72 | events = events.stream() 73 | .sorted(Comparator.comparingLong(EventFullDto::getViews)) 74 | .collect(Collectors.toList()); 75 | break; 76 | } 77 | } 78 | return events.stream() 79 | .skip(from) 80 | .limit(size) 81 | .map(mapper::toShortDtoFromFullDto) 82 | .collect(Collectors.toList()); 83 | } 84 | 85 | @Override 86 | public EventFullDto findEventById(Long eventId) { 87 | log.info("EventPublicService: Получение подробной информации об опубликованном событии с id={}.", eventId); 88 | Event event = repository.findById(eventId) 89 | .orElseThrow(() -> 90 | new NotFoundException("EventPublicService: Не найдено событие с id=" + eventId)); 91 | if (!EventState.PUBLISHED.equals(event.getState())) { 92 | throw new ForbiddenException("EventPublicService: " + 93 | "Запрошена информация о неопубликованом событии с id=" + eventId); 94 | } 95 | return mapper.toFullDto(event); 96 | } 97 | 98 | @Override 99 | public EventFullDto getEventWithAllComments(Long eventId) { 100 | log.info("CommentPrivateService: Запрошено событие с id={} с полным списком отзывов.", eventId); 101 | Event event = repository.findById(eventId) 102 | .orElseThrow(() -> 103 | new NotFoundException("EventPublicService: Не найдено событие с id=" + eventId)); 104 | List comments = commentRepository.findAllCommentsByEventId(eventId) 105 | .stream() 106 | .map(commentMapper::toDto) 107 | .collect(Collectors.toList()); 108 | return mapper.toFullDtoWithComments(event, comments); 109 | } 110 | } -------------------------------------------------------------------------------- /server/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.jpa.hibernate.ddl-auto=none 2 | spring.jpa.properties.hibernate.format_sql=true 3 | spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect 4 | spring.datasource.driverClassName=org.postgresql.Driver 5 | spring.datasource.url=${SPRING_DATASOURCE_URL} 6 | spring.datasource.username=${POSTGRES_USER} 7 | spring.datasource.password=${POSTGRES_PASSWORD} 8 | spring.sql.init.mode=always 9 | 10 | server.port=8080 11 | app-name=explore-with-me-server 12 | stats-server.url=${STATS_SERVER_URL} 13 | #--- 14 | spring.config.activate.on-profile=ci,test 15 | spring.datasource.driverClassName=org.h2.Driver 16 | spring.datasource.url=jdbc:h2:mem:explore-with-me 17 | spring.datasource.username=test 18 | spring.datasource.password=test -------------------------------------------------------------------------------- /server/src/main/resources/schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS USERS 2 | ( 3 | ID BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, 4 | NAME VARCHAR(120) NOT NULL, 5 | EMAIL VARCHAR(254) NOT NULL, 6 | CONSTRAINT PK_USER PRIMARY KEY (ID), 7 | CONSTRAINT UQ_USER_EMAIL UNIQUE (EMAIL) 8 | ); 9 | 10 | CREATE TABLE IF NOT EXISTS CATEGORIES 11 | ( 12 | ID BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, 13 | NAME VARCHAR(120) NOT NULL, 14 | CONSTRAINT PK_CATEGORIES PRIMARY KEY (ID), 15 | CONSTRAINT UQ_CATEGORIES_NAME UNIQUE (NAME) 16 | ); 17 | 18 | CREATE TABLE IF NOT EXISTS EVENTS 19 | ( 20 | ID BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, 21 | TITLE VARCHAR(120) NOT NULL, 22 | ANNOTATION VARCHAR(2000) NOT NULL, 23 | DESCRIPTION VARCHAR(7000) NOT NULL, 24 | CATEGORY_ID BIGINT, 25 | CREATED_ON TIMESTAMP, 26 | EVENT_DATE TIMESTAMP NOT NULL, 27 | INITIATOR_ID BIGINT NOT NULL, 28 | PAID BOOLEAN, 29 | PARTICIPANT_LIMIT BIGINT DEFAULT 0 NOT NULL, 30 | PUBLISHED_ON TIMESTAMP, 31 | REQUEST_MODERATION BOOLEAN, 32 | STATE VARCHAR(12) NOT NULL, 33 | LOCATION_LAT FLOAT, 34 | LOCATION_LON FLOAT, 35 | CONSTRAINT PK_EVENT PRIMARY KEY (ID), 36 | CONSTRAINT FK_EVENT_ON_CATEGORY FOREIGN KEY (CATEGORY_ID) REFERENCES CATEGORIES (ID), 37 | CONSTRAINT FK_EVENT_ON_USER FOREIGN KEY (INITIATOR_ID) REFERENCES USERS (ID) 38 | ); 39 | 40 | CREATE TABLE IF NOT EXISTS REQUESTS 41 | ( 42 | ID BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, 43 | EVENT_ID BIGINT NOT NULL, 44 | REQUESTER_ID BIGINT NOT NULL, 45 | STATUS VARCHAR(12) NOT NULL, 46 | CREATED TIMESTAMP NOT NULL, 47 | CONSTRAINT PK_REQUEST PRIMARY KEY (ID), 48 | UNIQUE (REQUESTER_ID, EVENT_ID), 49 | CONSTRAINT FK_REQUESTER FOREIGN KEY (REQUESTER_ID) REFERENCES USERS (ID), 50 | CONSTRAINT FK_EVENTS_REQUESTS FOREIGN KEY (EVENT_ID) REFERENCES EVENTS (ID) 51 | ); 52 | 53 | CREATE TABLE IF NOT EXISTS COMPILATIONS 54 | ( 55 | ID BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, 56 | TITLE VARCHAR(120) NOT NULL, 57 | PINNED BOOLEAN, 58 | CONSTRAINT PK_COMPILATIONS PRIMARY KEY (ID) 59 | ); 60 | 61 | CREATE TABLE IF NOT EXISTS EVENTS_COMPILATION 62 | ( 63 | EVENT_ID BIGINT NOT NULL, 64 | COMPILATION_ID BIGINT NOT NULL, 65 | CONSTRAINT FK_COMPILATION_EVENTS_ON_COMPILATION FOREIGN KEY (COMPILATION_ID) REFERENCES COMPILATIONS (ID), 66 | CONSTRAINT FK_COMPILATION_EVENTS FOREIGN KEY (EVENT_ID) REFERENCES EVENTS (ID) 67 | ); 68 | 69 | CREATE TABLE IF NOT EXISTS COMMENTS ( 70 | ID BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, 71 | TEXT VARCHAR(7000) NOT NULL, 72 | EVENT_ID BIGINT , 73 | AUTHOR_ID BIGINT , 74 | CREATED TIMESTAMP WITHOUT TIME ZONE NOT NULL, 75 | EDITED BOOLEAN, 76 | CONSTRAINT PK_COMMENTS PRIMARY KEY (ID), 77 | CONSTRAINT FK_COMMENTS_ON_AUTHOR FOREIGN KEY (AUTHOR_ID) REFERENCES USERS (ID), 78 | CONSTRAINT FK_COMMENTS_ON_ITEM FOREIGN KEY (EVENT_ID) REFERENCES EVENTS (ID) 79 | ); -------------------------------------------------------------------------------- /server/src/test/java/ru/practicum/explorewithme/comment/CommentAdminServiceTest.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.comment; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.junit.jupiter.api.BeforeAll; 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; 9 | import org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureTestEntityManager; 10 | import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; 11 | import org.springframework.boot.test.context.SpringBootTest; 12 | import ru.practicum.explorewithme.model.category.Category; 13 | import ru.practicum.explorewithme.model.comment.Comment; 14 | import ru.practicum.explorewithme.model.event.Event; 15 | import ru.practicum.explorewithme.model.event.EventState; 16 | import ru.practicum.explorewithme.model.event.Location; 17 | import ru.practicum.explorewithme.model.user.User; 18 | import ru.practicum.explorewithme.service.admin.CommentAdminService; 19 | 20 | import javax.transaction.Transactional; 21 | 22 | import java.time.LocalDateTime; 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | import java.util.concurrent.atomic.AtomicLong; 26 | 27 | import static org.junit.jupiter.api.Assertions.*; 28 | 29 | @SpringBootTest 30 | @AutoConfigureTestDatabase 31 | @AutoConfigureTestEntityManager 32 | @Transactional 33 | @RequiredArgsConstructor(onConstructor_ = @Autowired) 34 | class CommentAdminServiceTest { 35 | 36 | @Autowired 37 | private final CommentAdminService service; 38 | 39 | @Autowired 40 | private final TestEntityManager testEntityManager; 41 | 42 | private static AtomicLong userIdHolder; 43 | 44 | @BeforeAll 45 | public static void init() { 46 | userIdHolder = new AtomicLong(); 47 | } 48 | 49 | @BeforeEach 50 | public void beforeEachCommentServiceTests() { 51 | testEntityManager.clear(); 52 | } 53 | 54 | @Test 55 | public void deleteCommentByIdTestOk() { 56 | Comment comment = generateAndPersistComment(); 57 | service.deleteById(comment.getId()); 58 | assertNull(testEntityManager.find(Comment.class, comment.getId())); 59 | } 60 | 61 | @Test 62 | public void deleteAllCommentsByEventTestOk() { 63 | int commentCount = 5; 64 | Event event = generateAndPersistEvent(); 65 | List comments = generateAndPersistCommentsForEvent(commentCount, event); 66 | assertEquals(commentCount, comments.size()); 67 | service.deleteAllCommentsByEvent(event.getId()); 68 | List actual = service.findAllCommentsByEventId(event.getId()); 69 | assertEquals(0, actual.size()); 70 | } 71 | 72 | @Test 73 | public void getAllCommentsByEventIdTestOk() { 74 | int commentCount = 5; 75 | Event event = generateAndPersistEvent(); 76 | List comments = generateAndPersistCommentsForEvent(commentCount, event); 77 | assertEquals(commentCount, comments.size()); 78 | List actual = service.findAllCommentsByEventId(event.getId()); 79 | assertEquals(comments.size(), actual.size()); 80 | } 81 | 82 | private User generateAndPersistUser() { 83 | String email = String.format("email-%s@yandex.ru", userIdHolder.incrementAndGet()); 84 | String name = String.format("user-%s", userIdHolder.get()); 85 | User user = User.builder() 86 | .name(name) 87 | .email(email) 88 | .build(); 89 | return testEntityManager.persist(user); 90 | } 91 | 92 | private Event generateAndPersistEvent() { 93 | long stamp = System.nanoTime(); 94 | String annotation = "annotation-" + stamp; 95 | Category category = generateAndPersistEventCategory("Category-" + stamp); 96 | LocalDateTime createOn = LocalDateTime.now(); 97 | String description = "description-" + stamp; 98 | LocalDateTime eventDate = LocalDateTime.now().plusMonths(1); 99 | User initiator = generateAndPersistUser(); 100 | LocalDateTime publishedOn = LocalDateTime.now().plusHours(1); 101 | String title = "title-" + stamp; 102 | Location location = new Location(59.57f, 30.19f); 103 | Event event = Event.builder() 104 | .title(title) 105 | .annotation(annotation) 106 | .description(description) 107 | .category(category) 108 | .createdOn(createOn) 109 | .eventDate(eventDate) 110 | .initiator(initiator) 111 | .paid(false) 112 | .participantLimit(100) 113 | .publishedOn(publishedOn) 114 | .requestModeration(false) 115 | .state(EventState.PUBLISHED) 116 | .location(location) 117 | .build(); 118 | return testEntityManager.persist(event); 119 | } 120 | 121 | private Category generateAndPersistEventCategory(String name) { 122 | Category category = Category.builder() 123 | .name(name) 124 | .build(); 125 | return testEntityManager.persistAndFlush(category); 126 | } 127 | 128 | private Comment generateAndPersistComment() { 129 | Comment comment = Comment.builder() 130 | .text("Comment " + System.nanoTime()) 131 | .event(generateAndPersistEvent()) 132 | .author(generateAndPersistUser()) 133 | .edited(false) 134 | .created(LocalDateTime.now()) 135 | .build(); 136 | return testEntityManager.persist(comment); 137 | } 138 | 139 | private List generateAndPersistCommentsForEvent(int commentCount, Event event) { 140 | List comments = new ArrayList<>(); 141 | for (int i = 0; i < commentCount; i++) { 142 | Comment comment = Comment.builder() 143 | .text("Comment " + System.nanoTime()) 144 | .event(event) 145 | .author(generateAndPersistUser()) 146 | .edited(false) 147 | .created(LocalDateTime.now()) 148 | .build(); 149 | testEntityManager.persist(comment); 150 | comments.add(comment); 151 | } 152 | return comments; 153 | } 154 | } -------------------------------------------------------------------------------- /server/src/test/java/ru/practicum/explorewithme/comment/CommentPrivateServiceTest.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.comment; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.junit.jupiter.api.BeforeAll; 5 | import org.junit.jupiter.api.Test; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; 9 | import org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureTestEntityManager; 10 | import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; 11 | import org.springframework.boot.test.context.SpringBootTest; 12 | import ru.practicum.explorewithme.dto.comment.CommentCreateDto; 13 | import ru.practicum.explorewithme.dto.comment.CommentDto; 14 | import ru.practicum.explorewithme.dto.comment.CommentUpdateDto; 15 | import ru.practicum.explorewithme.exception.ForbiddenException; 16 | import ru.practicum.explorewithme.mapper.CommentMapper; 17 | import ru.practicum.explorewithme.model.category.Category; 18 | import ru.practicum.explorewithme.model.comment.Comment; 19 | import ru.practicum.explorewithme.model.event.Event; 20 | import ru.practicum.explorewithme.model.event.EventState; 21 | import ru.practicum.explorewithme.model.event.Location; 22 | import ru.practicum.explorewithme.model.user.User; 23 | import ru.practicum.explorewithme.service.priv.CommentPrivateService; 24 | 25 | import javax.transaction.Transactional; 26 | import java.time.LocalDateTime; 27 | import java.util.ArrayList; 28 | import java.util.List; 29 | import java.util.concurrent.atomic.AtomicLong; 30 | import java.util.stream.Collectors; 31 | 32 | import static org.junit.jupiter.api.Assertions.*; 33 | 34 | @SpringBootTest 35 | @AutoConfigureTestDatabase 36 | @AutoConfigureTestEntityManager 37 | @Transactional 38 | @RequiredArgsConstructor(onConstructor_ = @Autowired) 39 | public class CommentPrivateServiceTest { 40 | 41 | @Autowired 42 | private final CommentPrivateService service; 43 | 44 | @Autowired 45 | private final CommentMapper mapper; 46 | 47 | @Autowired 48 | private final TestEntityManager testEntityManager; 49 | 50 | private static AtomicLong userIdHolder; 51 | 52 | @BeforeAll 53 | public static void init() { 54 | userIdHolder = new AtomicLong(); 55 | } 56 | 57 | @BeforeEach 58 | public void beforeEachCommentServiceTests() { 59 | testEntityManager.clear(); 60 | } 61 | 62 | @Test 63 | public void saveCommentTest() { 64 | Event event = generateAndPersistEvent(); 65 | User author = generateAndPersistUser(); 66 | CommentCreateDto commentCreateDto = CommentCreateDto.builder() 67 | .text("Comment " + System.nanoTime()) 68 | .edited(false) 69 | .created(LocalDateTime.now()) 70 | .build(); 71 | CommentDto saved = service.save(commentCreateDto, author.getId(), event.getId()); 72 | CommentDto actual = mapper.toDto(testEntityManager.find(Comment.class, saved.getId())); 73 | assertEquals(saved.getText(), actual.getText()); 74 | } 75 | 76 | @Test 77 | public void updateCommentTest() { 78 | Event event = generateAndPersistEvent(); 79 | User author = generateAndPersistUser(); 80 | CommentCreateDto commentCreateDto = CommentCreateDto.builder() 81 | .text("Comment " + System.nanoTime()) 82 | .edited(false) 83 | .created(LocalDateTime.now()) 84 | .build(); 85 | CommentDto saved = service.save(commentCreateDto, author.getId(), event.getId()); 86 | 87 | CommentUpdateDto commentUpdateDto = CommentUpdateDto.builder() 88 | .text("Comment update test") 89 | .build(); 90 | CommentDto updated = service.update(commentUpdateDto, author.getId(), event.getId(), saved.getId()); 91 | Comment found = testEntityManager.find(Comment.class, saved.getId()); 92 | assertEquals(updated.getText(), found.getText()); 93 | } 94 | 95 | @Test 96 | public void updateCommentFailNotAuthorTest() { 97 | Comment comment = generateAndPersistComment(); 98 | User otherUser = generateAndPersistUser(); 99 | CommentUpdateDto commentUpdateDto = CommentUpdateDto.builder() 100 | .text("Comment update test") 101 | .build(); 102 | assertThrows(ForbiddenException.class, 103 | () -> service.update(commentUpdateDto, otherUser.getId(), comment.getEvent().getId(), comment.getId()) 104 | ); 105 | } 106 | 107 | @Test 108 | public void deleteCommentByAuthorTest() { 109 | Comment comment = generateAndPersistComment(); 110 | service.delete(comment.getAuthor().getId(), comment.getEvent().getId(), comment.getId()); 111 | assertNull(testEntityManager.find(Comment.class, comment.getId())); 112 | } 113 | 114 | @Test 115 | public void deleteCommentFailNotAuthorTest() { 116 | Comment comment = generateAndPersistComment(); 117 | User otherUser = generateAndPersistUser(); 118 | assertThrows(ForbiddenException.class, 119 | () -> service.delete(otherUser.getId(), comment.getEvent().getId(), comment.getId()) 120 | ); 121 | } 122 | 123 | @Test 124 | public void getAllCommentsByEventIdTest() { 125 | int commentQuantity = 5; 126 | Event event = generateAndPersistEvent(); 127 | List comments = generateAndPersistCommentsForEvent(commentQuantity, event) 128 | .stream() 129 | .map(mapper::toDto) 130 | .collect(Collectors.toList()); 131 | List expected = service.findCommentsByEventId(event.getInitiator().getId(), event.getId()); 132 | assertEquals(comments.size(), expected.size()); 133 | } 134 | 135 | private User generateAndPersistUser() { 136 | String email = String.format("email-%s@yandex.ru", userIdHolder.incrementAndGet()); 137 | String name = String.format("user-%s", userIdHolder.get()); 138 | User user = User.builder() 139 | .name(name) 140 | .email(email) 141 | .build(); 142 | return testEntityManager.persist(user); 143 | } 144 | 145 | private Event generateAndPersistEvent() { 146 | long stamp = System.nanoTime(); 147 | String annotation = "annotation-" + stamp; 148 | Category category = generateAndPersistEventCategory("Category-" + stamp); 149 | LocalDateTime createOn = LocalDateTime.now(); 150 | String description = "description-" + stamp; 151 | LocalDateTime eventDate = LocalDateTime.now().plusMonths(1); 152 | User initiator = generateAndPersistUser(); 153 | LocalDateTime publishedOn = LocalDateTime.now().plusHours(1); 154 | String title = "title-" + stamp; 155 | Location location = new Location(59.57f, 30.19f); 156 | Event event = Event.builder() 157 | .title(title) 158 | .annotation(annotation) 159 | .description(description) 160 | .category(category) 161 | .createdOn(createOn) 162 | .eventDate(eventDate) 163 | .initiator(initiator) 164 | .paid(false) 165 | .participantLimit(100) 166 | .publishedOn(publishedOn) 167 | .requestModeration(false) 168 | .state(EventState.PUBLISHED) 169 | .location(location) 170 | .build(); 171 | return testEntityManager.persist(event); 172 | } 173 | 174 | private Category generateAndPersistEventCategory(String name) { 175 | Category category = Category.builder() 176 | .name(name) 177 | .build(); 178 | return testEntityManager.persistAndFlush(category); 179 | } 180 | 181 | private Comment generateAndPersistComment() { 182 | Comment comment = Comment.builder() 183 | .text("Comment " + System.nanoTime()) 184 | .event(generateAndPersistEvent()) 185 | .author(generateAndPersistUser()) 186 | .edited(false) 187 | .created(LocalDateTime.now()) 188 | .build(); 189 | return testEntityManager.persist(comment); 190 | } 191 | 192 | private List generateAndPersistCommentsForEvent(int commentCount, Event event) { 193 | List comments = new ArrayList<>(); 194 | for (int i = 0; i < commentCount; i++) { 195 | Comment comment = Comment.builder() 196 | .text("Comment " + System.nanoTime()) 197 | .event(event) 198 | .author(generateAndPersistUser()) 199 | .edited(false) 200 | .created(LocalDateTime.now()) 201 | .build(); 202 | testEntityManager.persist(comment); 203 | comments.add(comment); 204 | } 205 | return comments; 206 | } 207 | } -------------------------------------------------------------------------------- /server/src/test/java/ru/practicum/explorewithme/comment/CommentPublicServiceTest.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.comment; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.junit.jupiter.api.BeforeAll; 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; 9 | import org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureTestEntityManager; 10 | import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; 11 | import org.springframework.boot.test.context.SpringBootTest; 12 | import ru.practicum.explorewithme.dto.comment.CommentDto; 13 | import ru.practicum.explorewithme.mapper.CommentMapper; 14 | import ru.practicum.explorewithme.model.category.Category; 15 | import ru.practicum.explorewithme.model.comment.Comment; 16 | import ru.practicum.explorewithme.model.event.Event; 17 | import ru.practicum.explorewithme.model.event.EventState; 18 | import ru.practicum.explorewithme.model.event.Location; 19 | import ru.practicum.explorewithme.model.user.User; 20 | import ru.practicum.explorewithme.service.pub.CommentPublicService; 21 | 22 | import javax.transaction.Transactional; 23 | 24 | import java.time.LocalDateTime; 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | import java.util.concurrent.atomic.AtomicLong; 28 | import java.util.stream.Collectors; 29 | 30 | import static org.junit.jupiter.api.Assertions.*; 31 | 32 | @SpringBootTest 33 | @AutoConfigureTestDatabase 34 | @AutoConfigureTestEntityManager 35 | @Transactional 36 | @RequiredArgsConstructor(onConstructor_ = @Autowired) 37 | class CommentPublicServiceTest { 38 | 39 | @Autowired 40 | private final CommentPublicService service; 41 | 42 | @Autowired 43 | private final CommentMapper mapper; 44 | 45 | @Autowired 46 | private final TestEntityManager testEntityManager; 47 | 48 | private static AtomicLong userIdHolder; 49 | 50 | @BeforeAll 51 | public static void init() { 52 | userIdHolder = new AtomicLong(); 53 | } 54 | 55 | @BeforeEach 56 | public void beforeEachCommentServiceTests() { 57 | testEntityManager.clear(); 58 | } 59 | 60 | @Test 61 | public void getAllCommentsByEventsTest() { 62 | int commentCount = 5; 63 | Event event = generateAndPersistEvent(); 64 | List comments = generateAndPersistCommentsForEvent(commentCount, event) 65 | .stream() 66 | .map(mapper::toDto) 67 | .collect(Collectors.toList()); 68 | List actual = service.findAll(0, 10); 69 | assertEquals(comments.size(), actual.size()); 70 | } 71 | 72 | @Test 73 | public void getCommentById() { 74 | Comment comment = generateAndPersistComment(); 75 | CommentDto expected = mapper.toDto(comment); 76 | CommentDto actual = service.findCommentById(comment.getId()); 77 | assertEquals(expected, actual); 78 | } 79 | 80 | private User generateAndPersistUser() { 81 | String email = String.format("email-%s@yandex.ru", userIdHolder.incrementAndGet()); 82 | String name = String.format("user-%s", userIdHolder.get()); 83 | User user = User.builder() 84 | .name(name) 85 | .email(email) 86 | .build(); 87 | return testEntityManager.persist(user); 88 | } 89 | 90 | private Event generateAndPersistEvent() { 91 | long stamp = System.nanoTime(); 92 | String annotation = "annotation-" + stamp; 93 | Category category = generateAndPersistEventCategory("Category-" + stamp); 94 | LocalDateTime createOn = LocalDateTime.now(); 95 | String description = "description-" + stamp; 96 | LocalDateTime eventDate = LocalDateTime.now().plusMonths(1); 97 | User initiator = generateAndPersistUser(); 98 | LocalDateTime publishedOn = LocalDateTime.now().plusHours(1); 99 | String title = "title-" + stamp; 100 | Location location = new Location(59.57f, 30.19f); 101 | Event event = Event.builder() 102 | .title(title) 103 | .annotation(annotation) 104 | .description(description) 105 | .category(category) 106 | .createdOn(createOn) 107 | .eventDate(eventDate) 108 | .initiator(initiator) 109 | .paid(false) 110 | .participantLimit(100) 111 | .publishedOn(publishedOn) 112 | .requestModeration(false) 113 | .state(EventState.PUBLISHED) 114 | .location(location) 115 | .build(); 116 | return testEntityManager.persist(event); 117 | } 118 | 119 | private Category generateAndPersistEventCategory(String name) { 120 | Category category = Category.builder() 121 | .name(name) 122 | .build(); 123 | return testEntityManager.persistAndFlush(category); 124 | } 125 | 126 | private Comment generateAndPersistComment() { 127 | Comment comment = Comment.builder() 128 | .text("Comment " + System.nanoTime()) 129 | .event(generateAndPersistEvent()) 130 | .author(generateAndPersistUser()) 131 | .edited(false) 132 | .created(LocalDateTime.now()) 133 | .build(); 134 | return testEntityManager.persist(comment); 135 | } 136 | 137 | private List generateAndPersistCommentsForEvent(int commentCount, Event event) { 138 | List comments = new ArrayList<>(); 139 | for (int i = 0; i < commentCount; i++) { 140 | Comment comment = Comment.builder() 141 | .text("Comment " + System.nanoTime()) 142 | .event(event) 143 | .author(generateAndPersistUser()) 144 | .edited(false) 145 | .created(LocalDateTime.now()) 146 | .build(); 147 | testEntityManager.persist(comment); 148 | comments.add(comment); 149 | } 150 | return comments; 151 | } 152 | } -------------------------------------------------------------------------------- /stats/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM amazoncorretto:11 2 | COPY target/*.jar app.jar 3 | ENTRYPOINT ["java","-jar","/app.jar"] -------------------------------------------------------------------------------- /stats/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | explore-with-me 7 | ru.practicum 8 | 0.0.1-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | stats 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 | org.springframework.boot 27 | spring-boot-starter-data-jpa 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-validation 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-actuator 36 | 37 | 38 | org.postgresql 39 | postgresql 40 | 41 | 42 | org.projectlombok 43 | lombok 44 | 45 | 46 | org.modelmapper 47 | modelmapper 48 | 49 | 50 | com.h2database 51 | h2 52 | 53 | 54 | org.springframework.boot 55 | spring-boot-starter-test 56 | test 57 | 58 | 59 | org.junit.vintage 60 | junit-vintage-engine 61 | test 62 | 63 | 64 | org.hamcrest 65 | hamcrest-core 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | org.springframework.boot 75 | spring-boot-maven-plugin 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /stats/src/main/java/ru/practicum/explorewithme/StatsApp.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class StatsApp { 8 | public static void main(String[] args) { 9 | SpringApplication.run(StatsApp.class, args); 10 | } 11 | } -------------------------------------------------------------------------------- /stats/src/main/java/ru/practicum/explorewithme/controller/StatsController.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.controller; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.format.annotation.DateTimeFormat; 6 | import org.springframework.validation.annotation.Validated; 7 | import org.springframework.web.bind.annotation.*; 8 | import ru.practicum.explorewithme.mapper.StatsMapper; 9 | import ru.practicum.explorewithme.service.StatsService; 10 | import ru.practicum.explorewithme.dto.EndpointHitDto; 11 | import ru.practicum.explorewithme.dto.ViewStats; 12 | import ru.practicum.explorewithme.model.*; 13 | 14 | import javax.validation.Valid; 15 | import java.time.LocalDateTime; 16 | import java.util.List; 17 | 18 | /** 19 | * API для работы с сервисом статистики 20 | */ 21 | 22 | @Slf4j 23 | @Validated 24 | @RestController 25 | @RequiredArgsConstructor 26 | public class StatsController { 27 | private final StatsService service; 28 | 29 | @PostMapping("/hit") // Сохранение информации о том, что к эндпоинту был запрос 30 | public void saveHit(@Valid @RequestBody EndpointHitDto endpointHitDto) { 31 | log.info("StatsController: сохранение просмотра hit={}", endpointHitDto.getUri()); 32 | EndpointHit endpointHit = StatsMapper.toModel(endpointHitDto); 33 | service.save(endpointHit); 34 | } 35 | 36 | @GetMapping("/stats") // Получение статистики по посещениям 37 | public List getStats( 38 | @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime start, 39 | @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime end, 40 | @RequestParam(required = false) String[] uris, 41 | @RequestParam(defaultValue = "false") boolean unique) { 42 | log.info("StatsController: получение статистики по посещениям за период с {} по {}.", start, end); 43 | return service.getStats(start, end, uris, unique); 44 | } 45 | 46 | @GetMapping("/hit") // Получение статистики по просмотрам 47 | public Integer getViews(@RequestParam String uri) { 48 | log.info("StatsController: получение статистики просмотров по uri={}.", uri); 49 | return service.getViews(uri); 50 | } 51 | } -------------------------------------------------------------------------------- /stats/src/main/java/ru/practicum/explorewithme/dto/EndpointHitDto.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.*; 5 | 6 | import java.time.LocalDateTime; 7 | 8 | /** 9 | * Информация об эндроинте сервиса статистики 10 | */ 11 | 12 | @Data 13 | @Builder 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | public class EndpointHitDto { 17 | private Long id; 18 | private String app; 19 | private String uri; 20 | private String ip; 21 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 22 | private LocalDateTime timestamp; 23 | } -------------------------------------------------------------------------------- /stats/src/main/java/ru/practicum/explorewithme/dto/ViewStats.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.dto; 2 | 3 | /** 4 | * Реализация получения данных статистики 5 | */ 6 | 7 | public interface ViewStats { 8 | 9 | /** 10 | * название сервиса 11 | */ 12 | String getApp(); 13 | 14 | /** 15 | * URI сервиса 16 | */ 17 | String getUri(); 18 | 19 | /** 20 | * Количество просмотров 21 | */ 22 | Integer getHits(); 23 | } -------------------------------------------------------------------------------- /stats/src/main/java/ru/practicum/explorewithme/exception/ApiError.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.exception; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import lombok.*; 5 | import org.springframework.http.HttpStatus; 6 | 7 | import java.time.LocalDateTime; 8 | import java.util.List; 9 | 10 | /** 11 | * Сведения об ошибке 12 | */ 13 | 14 | @Data 15 | @Builder 16 | public class ApiError { 17 | private HttpStatus status; 18 | private String reason; 19 | 20 | @JsonInclude(JsonInclude.Include.NON_NULL) 21 | private String message; 22 | 23 | @JsonInclude(JsonInclude.Include.NON_NULL) 24 | private List errors; 25 | private LocalDateTime timestamp; 26 | } -------------------------------------------------------------------------------- /stats/src/main/java/ru/practicum/explorewithme/exception/ErrorHandler.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.MissingServletRequestParameterException; 5 | import org.springframework.web.bind.annotation.*; 6 | 7 | import java.time.LocalDateTime; 8 | 9 | @RestControllerAdvice 10 | public class ErrorHandler { 11 | @ExceptionHandler 12 | @ResponseStatus(HttpStatus.BAD_REQUEST) 13 | public ApiError handleMissingServletRequestParameterException(final MissingServletRequestParameterException e) { 14 | return ApiError.builder() 15 | .status(HttpStatus.BAD_REQUEST) 16 | .reason("Missing request parameters") 17 | .message(e.getMessage()) 18 | .timestamp(LocalDateTime.now()) 19 | .build(); 20 | } 21 | } -------------------------------------------------------------------------------- /stats/src/main/java/ru/practicum/explorewithme/mapper/StatsMapper.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.mapper; 2 | 3 | import lombok.*; 4 | import ru.practicum.explorewithme.dto.EndpointHitDto; 5 | import ru.practicum.explorewithme.model.*; 6 | 7 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 8 | public class StatsMapper { 9 | 10 | public static EndpointHit toModel(EndpointHitDto endpointHitDto) { 11 | return EndpointHit.builder() 12 | .id(endpointHitDto.getId()) 13 | .app(endpointHitDto.getApp()) 14 | .uri(endpointHitDto.getUri()) 15 | .ip(endpointHitDto.getIp()) 16 | .timestamp(endpointHitDto.getTimestamp()) 17 | .build(); 18 | } 19 | } -------------------------------------------------------------------------------- /stats/src/main/java/ru/practicum/explorewithme/model/EndpointHit.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.model; 2 | 3 | import lombok.*; 4 | 5 | import javax.persistence.*; 6 | import java.time.LocalDateTime; 7 | 8 | /** 9 | * Эндроинт к сервису статистики 10 | */ 11 | 12 | @Data 13 | @Entity 14 | @Builder 15 | @NoArgsConstructor 16 | @AllArgsConstructor 17 | @Table (name = "stats") 18 | public class EndpointHit { 19 | @Id 20 | @Column(name = "id") 21 | @GeneratedValue(strategy = GenerationType.IDENTITY) 22 | private Long id; // Идентификатор записи 23 | private String app; // Идентификатор сервиса для которого записывается информация 24 | private String uri; // URI для которого был осуществлен запрос 25 | private String ip; // IP-адрес пользователя, осуществившего запрос 26 | private LocalDateTime timestamp; // Дата и время, когда был совершен запрос к эндпоинту 27 | } -------------------------------------------------------------------------------- /stats/src/main/java/ru/practicum/explorewithme/repository/StatsRepository.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.repository; 2 | 3 | import org.springframework.data.jpa.repository.*; 4 | import ru.practicum.explorewithme.dto.ViewStats; 5 | import ru.practicum.explorewithme.model.*; 6 | 7 | import java.time.LocalDateTime; 8 | import java.util.List; 9 | 10 | public interface StatsRepository extends JpaRepository { 11 | 12 | String QUERY_NON_UNIQUE = "select distinct(e.uri) as uri, " + 13 | "count(e.app) as hits, e.app as app " + 14 | "from EndpointHit e " + 15 | "where e.timestamp > ?1 " + 16 | "and e.timestamp < ?2 " + 17 | "group by e.app, (e.uri)"; 18 | String QUERY_UNIQUE = QUERY_NON_UNIQUE + ", e.ip"; 19 | 20 | @Query(value = "select count(id) from stats where uri = ?1", nativeQuery = true) 21 | Integer getViews(String uri); 22 | 23 | @Query(value = QUERY_NON_UNIQUE) 24 | List findAllNotUnique(LocalDateTime start, LocalDateTime end); 25 | 26 | @Query(value = QUERY_UNIQUE) 27 | List findAllUnique(LocalDateTime start, LocalDateTime end); 28 | } -------------------------------------------------------------------------------- /stats/src/main/java/ru/practicum/explorewithme/service/StatsService.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service; 2 | 3 | import ru.practicum.explorewithme.dto.ViewStats; 4 | import ru.practicum.explorewithme.model.EndpointHit; 5 | 6 | import java.time.LocalDateTime; 7 | import java.util.List; 8 | 9 | /** 10 | * Реализация API для работы с сервисом статистики 11 | */ 12 | 13 | public interface StatsService { 14 | 15 | /** 16 | * Сохранить информацию о том, что к endpointHit был запрос с основного сервиса 17 | * о просмотре событий в публичном доступе 18 | * @param endpointHit 19 | */ 20 | void save(EndpointHit endpointHit); 21 | 22 | /** 23 | * Получить количество просмотров события по URI 24 | * @param uri URI для которого был осуществлен запрос 25 | * @return Integer натуральное число просмотров 26 | */ 27 | Integer getViews(String uri); 28 | 29 | /** 30 | * Получить список посещений события 31 | * @param start дата и время начала периода 32 | * @param end дата и время окончания периода 33 | * @param uris массив URI 34 | * @param unique true - уникальные IP, false - не уникальные IP 35 | * @return List 36 | */ 37 | List getStats(LocalDateTime start, LocalDateTime end, String[] uris, boolean unique); 38 | } -------------------------------------------------------------------------------- /stats/src/main/java/ru/practicum/explorewithme/service/StatsServiceImpl.java: -------------------------------------------------------------------------------- 1 | package ru.practicum.explorewithme.service; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.stereotype.Service; 6 | import org.springframework.transaction.annotation.Transactional; 7 | import ru.practicum.explorewithme.dto.ViewStats; 8 | import ru.practicum.explorewithme.model.*; 9 | import ru.practicum.explorewithme.repository.StatsRepository; 10 | 11 | import java.time.LocalDateTime; 12 | import java.util.*; 13 | import java.util.stream.Collectors; 14 | 15 | @Slf4j 16 | @Service 17 | @RequiredArgsConstructor 18 | @Transactional(readOnly = true) 19 | public class StatsServiceImpl implements StatsService { 20 | private final StatsRepository repository; 21 | 22 | @Override 23 | public void save(EndpointHit endpointHit) { 24 | log.info("StatsService: сохранение нового просмотра {}", endpointHit); 25 | repository.save(endpointHit); 26 | } 27 | 28 | @Override 29 | public Integer getViews(String uri) { 30 | log.info("StatsService: получение статистики просмотров по uri={}.", uri); 31 | return repository.getViews(uri); 32 | } 33 | 34 | @Override 35 | public List getStats(LocalDateTime start, LocalDateTime end, String[] uris, boolean unique) { 36 | log.info("StatsService: получение статистики просмотров в период с {} по {} для uri={}, где unique={},", 37 | start, end, uris, unique); 38 | List viewStats; 39 | if (!unique) { 40 | viewStats = repository.findAllNotUnique(start, end); 41 | } else { 42 | viewStats = repository.findAllUnique(start, end); 43 | } 44 | if (uris != null) { 45 | return viewStats.stream() 46 | .map(view -> filterByUri(view, uris)) 47 | .filter(Objects::nonNull) 48 | .collect(Collectors.toList()); 49 | } else { 50 | return viewStats; 51 | } 52 | } 53 | 54 | private ViewStats filterByUri(ViewStats viewStats, String[] uris) { 55 | for (String uri : uris) { 56 | if (viewStats.getUri().equals(uri)) { 57 | return viewStats; 58 | } 59 | } 60 | return null; 61 | } 62 | } -------------------------------------------------------------------------------- /stats/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | logging.level.org.springframework.web.client.RestTemplate=DEBUG 2 | spring.jpa.hibernate.ddl-auto=none 3 | spring.jpa.properties.hibernate.format_sql=true 4 | spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect 5 | spring.datasource.driverClassName=org.postgresql.Driver 6 | spring.datasource.url=${SPRING_DATASOURCE_URL} 7 | spring.datasource.username=${POSTGRES_USER} 8 | spring.datasource.password=${POSTGRES_PASSWORD} 9 | spring.sql.init.mode=always 10 | 11 | logging.level.ru.practicum=DEBUG 12 | server.port=9090 13 | #--- 14 | spring.config.activate.on-profile=ci,test 15 | spring.datasource.driverClassName=org.h2.Driver 16 | spring.datasource.url=jdbc:h2:mem:stats 17 | spring.datasource.username=test 18 | spring.datasource.password=test -------------------------------------------------------------------------------- /stats/src/main/resources/schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS STATS ( 2 | ID BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, 3 | APP VARCHAR(120) NOT NULL, 4 | URI VARCHAR(8000) NOT NULL, 5 | IP VARCHAR(120) NOT NULL, 6 | TIMESTAMP TIMESTAMP NOT NULL, 7 | CONSTRAINT PK_STATS PRIMARY KEY (ID) 8 | ); --------------------------------------------------------------------------------