diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 16415da7..3039bcc0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,11 @@ on: - develop - 'weekly/**' +permissions: + contents: write + issues: write + pull-requests: write + jobs: build-and-test: runs-on: ubuntu-22.04 @@ -36,17 +41,6 @@ jobs: - name: Checkout Repository uses: actions/checkout@v2 -# - name: Cache Gradle dependencies -# uses: actions/cache@v3 -# with: -# path: | -# ~/.gradle/caches/modules-2/files-2.1 -# ~/.gradle/wrapper -# key: gradle-${{ runner.os }}-${{ hashFiles('build.gradle', 'settings.gradle') }} -# restore-keys: | -# gradle-${{ runner.os }}- - - - name: Decode env.properties from GitHub Secrets run: | echo "${{ secrets.ENV_FILE }}" | base64 --decode > ./src/main/resources/env.properties @@ -87,4 +81,33 @@ jobs: run: ./gradlew clean build -Dspring.profiles.active=test - name: Run Tests - run: ./gradlew test -Dspring.profiles.active=test \ No newline at end of file + run: ./gradlew test -Dspring.profiles.active=test + + - name: Generate JaCoCo Coverage Report + run: ./gradlew jacocoTestReport + + - name: Generate Coverage Badge and Summary + uses: cicirello/jacoco-badge-generator@v2 + with: + jacoco-csv-file: 'build/reports/jacoco/test/jacocoTestReport.csv' + badges-directory: 'badges' + generate-branches-badge: true + generate-summary: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Display Coverage Summary JSON + run: | + cat badges/coverage-summary.json + + - name: Extract Coverage Information + if: ${{ github.event_name == 'pull_request' }} + run: | + COVERAGE=$(jq '.coverage' badges/coverage-summary.json) + BRANCH_COVERAGE=$(jq '.branches' badges/coverage-summary.json) + FORMATTED_COVERAGE=$(printf "%.2f" $COVERAGE) + FORMATTED_BRANCH_COVERAGE=$(printf "%.2f" $BRANCH_COVERAGE) + curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + -X POST \ + -d "{\"body\": \"### 코드 커버리지\n- Coverage: ${FORMATTED_COVERAGE}%\n- Branches: ${FORMATTED_BRANCH_COVERAGE}%\n\"}" \ + "https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments" diff --git a/.gitignore b/.gitignore index b502fe8e..f8c40ac3 100644 --- a/.gitignore +++ b/.gitignore @@ -56,4 +56,5 @@ splanet-db ### env ### .env /src/main/resources/env.properties +/src/main/resources/env.properties.base64 splanet-firebase.json \ No newline at end of file diff --git a/build.gradle b/build.gradle index 388226ec..d5def0b7 100644 --- a/build.gradle +++ b/build.gradle @@ -3,9 +3,10 @@ plugins { id 'org.springframework.boot' version '3.3.3' id 'io.spring.dependency-management' version '1.1.6' id 'com.google.protobuf' version '0.9.4' - + id 'jacoco' } + group = 'com.splanet' version = '0.0.1-SNAPSHOT' @@ -18,6 +19,7 @@ java { test { useJUnitPlatform() systemProperty 'spring.profiles.active', 'test' + finalizedBy jacocoTestReport } configurations { @@ -93,6 +95,7 @@ dependencies { tasks.named('test') { useJUnitPlatform() + finalizedBy jacocoTestReport } protobuf { @@ -119,4 +122,30 @@ sourceSets { srcDirs 'build/generated/source/proto/main/java', 'build/generated/source/proto/main/grpc' } } +} + +jacoco { + toolVersion = "0.8.10" +} + +jacocoTestReport { + dependsOn test + reports { + xml.required.set(true) + html.required.set(true) + csv.required.set(true) + } + classDirectories.setFrom( + files(classDirectories.files.collect { + fileTree(dir: it, exclude: [ + '**/dto/**', // dto 패키지 제외 + '**/config/**', // config 패키지 제외 + '**/exception/**', // exception 패키지 제외 + '**/core/**', // core 패키지 제외 + '**/mapper/**', // mapper 패키지 제외 + '**/grpc/**', // grpc 패키지 제외 + '**/log/**', // log 패키지 제외 + ]) + }) + ) } \ No newline at end of file diff --git a/splanet-test.log b/splanet-test.log new file mode 100644 index 00000000..f9ca94fa --- /dev/null +++ b/splanet-test.log @@ -0,0 +1,503 @@ +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T01:30:47.1860528, requestPath: /api/teams/292/invite, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T01:30:47.8163936, requestPath: /api/teams/invitation/56/cancel, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T01:30:47.942391, requestPath: /api/teams/294/invite, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T01:30:48.0655639, requestPath: /api/teams/295/users/2361/role, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T01:30:48.3306743, requestPath: /api/teams/invitations, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T01:30:48.6284571, requestPath: /api/teams/297/members, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T01:30:48.8827197, requestPath: /api/teams/invitation/58/response, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T01:30:49.072135, requestPath: /api/teams, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T01:34:15.9980698, requestPath: /api/teams/invitation/59/response, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:12:52.5109433, requestPath: /api/teams/invitation/60/response, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:15:28.7888321, requestPath: /api/teams/invitation/61/response, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:15:28.9399327, requestPath: /api/teams/303/users/2375/role, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:18:22.4189859, requestPath: /api/teams/304/invite, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:18:23.3406885, requestPath: /api/teams/invitation/63/cancel, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:18:23.4573551, requestPath: /api/teams/306/invite, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:18:23.6584028, requestPath: /api/teams/invitation/64/response, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:18:23.7225922, requestPath: /api/teams/307/users/2384/role, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:18:23.9879624, requestPath: /api/teams/invitations, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:18:24.2387469, requestPath: /api/teams/309/members, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:18:24.6542278, requestPath: /api/teams/invitation/66/response, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:18:24.909439, requestPath: /api/teams, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:22:58.4115848, requestPath: /api/teams/invitation/67/response, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:24:55.3411523, requestPath: /api/teams/invitations, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:26:39.7535168, requestPath: /api/teams/315/invite, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:26:40.6483468, requestPath: /api/teams/invitation/70/cancel, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:26:40.7956308, requestPath: /api/teams/317/invite, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:26:41.0087913, requestPath: /api/teams/invitation/71/response, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:26:41.1112843, requestPath: /api/teams/318/users/2405/role, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:26:41.4204024, requestPath: /api/teams/invitations, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:26:41.6102337, requestPath: /api/teams/320/members, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:26:41.8940681, requestPath: /api/teams/invitation/73/response, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:26:42.063906, requestPath: /api/teams, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:28:02.5629894, requestPath: /api/teams/324/invite, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:28:03.7140563, requestPath: /api/teams/invitation/75/cancel, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:28:03.8805958, requestPath: /api/teams/326/invite, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:28:04.0587861, requestPath: /api/teams/invitation/76/response, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:28:04.1264443, requestPath: /api/teams/327/users/2422/role, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:28:04.4360911, requestPath: /api/teams/invitations, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:28:04.7011395, requestPath: /api/teams/329/members, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:28:05.0007518, requestPath: /api/teams/invitation/78/response, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:28:05.1655281, requestPath: /api/teams, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:30:27.9648354, requestPath: /api/teams/333/users/2432, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:34:28.3032912, requestPath: /api/teams/334/users/2435, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:36:50.5522316, requestPath: /api/comments/999, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:36:50.7912981, requestPath: /api/comments/999, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:36:51.1415171, requestPath: /api/comments, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:36:51.2849148, requestPath: /api/comments/2442, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:36:51.3890202, requestPath: /api/comments/999, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:36:51.4803386, requestPath: /api/comments/449, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:36:51.5921475, requestPath: /api/comments/2448, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:36:51.8367186, requestPath: /api/comments/452, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:36:51.9090987, requestPath: /api/comments, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:36:51.9884984, requestPath: /api/comments/454, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:36:54.3298593, requestPath: /api/friends/999/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:36:54.5153576, requestPath: /api/friends/999, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:36:54.6435791, requestPath: /api/friends/1/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:36:54.8633012, requestPath: /api/friends/2460, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:36:55.140562, requestPath: /api/friends, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:36:55.2711714, requestPath: /api/friends/1, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:36:55.428424, requestPath: /api/friends, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:36:56.7492194, requestPath: /api/friends/requests, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:36:56.837646, requestPath: /api/friends/requests/999/cancel, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:36:56.9837246, requestPath: /api/friends/requests/173/accept, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:36:57.0676166, requestPath: /api/friends/requests, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:36:57.1943555, requestPath: /api/friends/requests/174/cancel, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:36:57.2395291, requestPath: /api/friends/requests/sent, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:36:57.4009325, requestPath: /api/friends/requests/175/cancel, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:36:57.5241516, requestPath: /api/friends/requests/176/reject, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:36:57.6440745, requestPath: /api/friends/requests, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:36:57.7926852, requestPath: /api/friends/requests/received, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:36:58.0291299, requestPath: /api/friends/requests/sent, headers: +eventType: API_REQUEST, userId: 2, deviceId: apiTestDeviceId, timestamp: 2024-11-13T02:37:13.8183349, requestPath: /test/api, headers: User-Agent: ApiTestAgent, Accept: application/json +eventType: LOGIN_SUCCESS, userId: 1, deviceId: testDeviceId, timestamp: 2024-11-13T02:37:13.8369525, requestPath: /test/login, headers: User-Agent: TestAgent, Accept: */* +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:14.5721839, requestPath: /api/payment/999, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:14.6576132, requestPath: /api/payment/69, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:14.7227753, requestPath: /api/payment/999, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:14.7976534, requestPath: /api/payment/71, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:15.2281971, requestPath: /api/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:15.3237317, requestPath: /api/plans/33, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:15.3477231, requestPath: /api/plans/33, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:15.4191482, requestPath: /api/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:15.446887, requestPath: /api/plans/34, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:15.5068766, requestPath: /api/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:15.5390804, requestPath: /api/plans/35, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:15.5970357, requestPath: /api/plans/9999, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:15.6759658, requestPath: /api/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:15.7452256, requestPath: /api/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:15.8438973, requestPath: /api/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:15.9202902, requestPath: /api/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:16.003572, requestPath: /api/plans/38, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:16.0815436, requestPath: /api/plans/9999, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:16.183495, requestPath: /api/plans/39, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:16.2673954, requestPath: /api/plans/40, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:16.3543681, requestPath: /api/plans/9999, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:16.414049, requestPath: /api/plans/9999, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:16.4925541, requestPath: /api/plans/41, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:16.5542434, requestPath: /api/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:16.6219232, requestPath: /api/plans/42, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:16.6792633, requestPath: /api/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:17.6532471, requestPath: /api/subscription/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:17.7544701, requestPath: /api/subscription/me/subscribe, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:17.8432613, requestPath: /api/subscription/me/subscribe, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:17.9700391, requestPath: /api/subscriptions/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:18.3358253, requestPath: /api/teams/335/users/2508, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:18.4795394, requestPath: /api/teams/336/invite, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:18.7015124, requestPath: /api/teams/invitation/82/cancel, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:18.7911217, requestPath: /api/teams/338/invite, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:18.9105623, requestPath: /api/teams/invitation/83/response, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:18.9434427, requestPath: /api/teams/339/users/2517/role, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:19.0684144, requestPath: /api/teams/340/members, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:19.2275766, requestPath: /api/teams/invitation/84/response, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:19.316441, requestPath: /api/teams/342/users/2523, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:19.3852945, requestPath: /api/teams, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:19.939926, requestPath: /api/teams/345/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:20.0092834, requestPath: /api/teams/345/plans/74, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:20.0801684, requestPath: /api/teams/346/plans/999, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:20.15345, requestPath: /api/teams/347/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:20.2434092, requestPath: /api/teams/348/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:20.3664733, requestPath: /api/teams/349/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:20.4449252, requestPath: /api/teams/350/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:20.476685, requestPath: /api/teams/350/plans/76, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:20.5037028, requestPath: /api/teams/350/plans/76, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:20.5699068, requestPath: /api/teams/351/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:20.6012895, requestPath: /api/teams/351/plans/77, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:20.6669845, requestPath: /api/teams/352/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:20.6979221, requestPath: /api/teams/352/plans/78, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:20.7756559, requestPath: /api/teams/353/plans/999, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:20.843362, requestPath: /api/teams/354/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:20.871979, requestPath: /api/teams/354/plans/79, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:21.6428745, requestPath: /api/users/create, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:21.6821109, requestPath: /api/users/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:21.7020622, requestPath: /api/users/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:21.7494743, requestPath: /api/users/create, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:21.7823246, requestPath: /api/users/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:21.7966717, requestPath: /api/users/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:21.8213417, requestPath: /api/users/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:21.8599923, requestPath: /api/users/create, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:21.8904398, requestPath: /api/users/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:21.954743, requestPath: /api/users/create, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:22.1368969, requestPath: /api/users/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:22.1845135, requestPath: /api/users/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:22.2501266, requestPath: /api/users/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:22.3077124, requestPath: /api/users/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:22.3624514, requestPath: /api/users/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:22.4165367, requestPath: /api/users/create, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:37:22.4884116, requestPath: /api/users/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:40:48.1118797, requestPath: /api/teams/355/users/2558, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:40:49.666562, requestPath: /api/teams/356/users/2560, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:40:50.3471282, requestPath: /api/teams/999/users/2562, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:40:50.5004763, requestPath: /api/teams/358/invite, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:40:51.1205584, requestPath: /api/teams/invitation/87/cancel, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:40:51.2688202, requestPath: /api/teams/360/invite, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:40:51.4627571, requestPath: /api/teams/invitation/88/response, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:40:51.5286431, requestPath: /api/teams/361/users/2571/role, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:40:51.7166468, requestPath: /api/teams/362/members, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:40:52.0240492, requestPath: /api/teams/invitation/89/response, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:40:52.2275458, requestPath: /api/teams/364/users/2577, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:40:52.3973095, requestPath: /api/teams, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:43:10.141278, requestPath: /api/teams/367/users/2583, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:43:10.5845694, requestPath: /api/teams/invitation/90/cancel, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:43:10.7718762, requestPath: /api/teams/369/users/2588, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:43:11.1713409, requestPath: /api/teams/999/users/2590, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:43:11.2874055, requestPath: /api/teams/371/invite, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:43:11.6443585, requestPath: /api/teams, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:43:11.8102735, requestPath: /api/teams/invitation/93/cancel, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:43:11.9397144, requestPath: /api/teams/374/invite, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:43:12.1831742, requestPath: /api/teams/invitation/94/response, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:43:12.2290732, requestPath: /api/teams/375/users/2601/role, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:43:12.3742489, requestPath: /api/teams/376/users/2603/role, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:43:12.5241722, requestPath: /api/teams/invitation/95/response, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:43:12.6334208, requestPath: /api/teams/378/members, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:43:12.9206027, requestPath: /api/teams/379/members, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:43:13.2195216, requestPath: /api/teams/invitation/96/response, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:43:13.3670059, requestPath: /api/teams, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:43:13.5130763, requestPath: /api/teams/382/users/2617, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:43:13.9793759, requestPath: /api/teams, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:48:10.1257236, requestPath: /api/teams/386/members, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:50:57.1546558, requestPath: /api/teams/388/members, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:25.5370199, requestPath: /api/comments/999, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:25.7850735, requestPath: /api/comments/999, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:26.0530337, requestPath: /api/comments, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:26.1651293, requestPath: /api/comments/2631, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:26.2416298, requestPath: /api/comments/999, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:26.3179979, requestPath: /api/comments/462, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:26.4235611, requestPath: /api/comments/2637, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:26.6689509, requestPath: /api/comments/465, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:26.7399988, requestPath: /api/comments, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:26.8317109, requestPath: /api/comments/467, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:28.4352026, requestPath: /api/friends/999/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:28.5943521, requestPath: /api/friends/999, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:28.7506151, requestPath: /api/friends/1/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:28.9479387, requestPath: /api/friends/2649, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:29.215656, requestPath: /api/friends, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:29.3351501, requestPath: /api/friends/1, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:29.4761274, requestPath: /api/friends, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:30.5109494, requestPath: /api/friends/requests, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:30.6056946, requestPath: /api/friends/requests/999/cancel, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:30.749297, requestPath: /api/friends/requests/178/accept, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:30.8284083, requestPath: /api/friends/requests, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:30.9686757, requestPath: /api/friends/requests/179/cancel, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:31.0109352, requestPath: /api/friends/requests/sent, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:31.1440963, requestPath: /api/friends/requests/180/cancel, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:31.2375536, requestPath: /api/friends/requests/181/reject, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:31.3177624, requestPath: /api/friends/requests, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:31.4011777, requestPath: /api/friends/requests/received, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:31.497779, requestPath: /api/friends/requests/sent, headers: +eventType: API_REQUEST, userId: 2, deviceId: apiTestDeviceId, timestamp: 2024-11-13T02:53:46.5413504, requestPath: /test/api, headers: User-Agent: ApiTestAgent, Accept: application/json +eventType: LOGIN_SUCCESS, userId: 1, deviceId: testDeviceId, timestamp: 2024-11-13T02:53:46.5688795, requestPath: /test/login, headers: User-Agent: TestAgent, Accept: */* +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:47.5292803, requestPath: /api/payment/999, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:47.6540046, requestPath: /api/payment/73, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:47.7441847, requestPath: /api/payment/999, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:47.8529949, requestPath: /api/payment/75, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:48.5008018, requestPath: /api/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:48.5979283, requestPath: /api/plans/43, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:48.641395, requestPath: /api/plans/43, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:48.7292402, requestPath: /api/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:48.7620267, requestPath: /api/plans/44, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:48.8589573, requestPath: /api/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:48.9026338, requestPath: /api/plans/45, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:48.978235, requestPath: /api/plans/9999, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:49.1260509, requestPath: /api/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:49.2023644, requestPath: /api/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:49.3139543, requestPath: /api/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:49.4024525, requestPath: /api/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:49.4824209, requestPath: /api/plans/48, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:49.5447553, requestPath: /api/plans/9999, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:49.6196053, requestPath: /api/plans/49, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:49.6853723, requestPath: /api/plans/50, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:49.7410549, requestPath: /api/plans/9999, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:49.7960073, requestPath: /api/plans/9999, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:49.8660332, requestPath: /api/plans/51, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:49.9463483, requestPath: /api/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:50.0057485, requestPath: /api/plans/52, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:50.0714845, requestPath: /api/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:50.9529861, requestPath: /api/subscription/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:51.039119, requestPath: /api/subscription/me/subscribe, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:51.1105475, requestPath: /api/subscription/me/subscribe, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:51.222783, requestPath: /api/subscriptions/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:51.4566731, requestPath: /api/teams/389/users/2698, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:51.6296005, requestPath: /api/teams/invitation/97/cancel, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:51.7881189, requestPath: /api/teams/391/users/2703, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:51.9306289, requestPath: /api/teams/999/users/2705, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:52.0144251, requestPath: /api/teams/393/invite, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:52.2015531, requestPath: /api/teams, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:52.3759522, requestPath: /api/teams/invitation/100/cancel, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:52.4778886, requestPath: /api/teams/396/invite, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:52.6109664, requestPath: /api/teams/invitation/101/response, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:52.6432484, requestPath: /api/teams/397/users/2716/role, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:52.776377, requestPath: /api/teams/398/users/2718/role, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:52.9149526, requestPath: /api/teams/invitation/102/response, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:52.97981, requestPath: /api/teams/400/members, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:53.1359649, requestPath: /api/teams/invitation/103/response, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:53.2320442, requestPath: /api/teams/402/users/2727, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:53.3065474, requestPath: /api/teams, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:53.9409898, requestPath: /api/teams/405/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:54.0107706, requestPath: /api/teams/405/plans/80, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:54.0891041, requestPath: /api/teams/406/plans/999, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:54.1886601, requestPath: /api/teams/407/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:54.2782075, requestPath: /api/teams/408/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:54.4353482, requestPath: /api/teams/409/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:54.5566504, requestPath: /api/teams/410/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:54.5964185, requestPath: /api/teams/410/plans/82, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:54.6237363, requestPath: /api/teams/410/plans/82, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:54.7297431, requestPath: /api/teams/411/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:54.7699229, requestPath: /api/teams/411/plans/83, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:54.8652319, requestPath: /api/teams/412/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:54.9067464, requestPath: /api/teams/412/plans/84, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:55.0012801, requestPath: /api/teams/413/plans/999, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:55.0901562, requestPath: /api/teams/414/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:55.1259617, requestPath: /api/teams/414/plans/85, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:55.9602196, requestPath: /api/users/create, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:56.007865, requestPath: /api/users/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:56.0389543, requestPath: /api/users/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:56.0952887, requestPath: /api/users/create, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:56.130556, requestPath: /api/users/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:56.153634, requestPath: /api/users/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:56.1809809, requestPath: /api/users/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:56.2803682, requestPath: /api/users/create, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:56.3320244, requestPath: /api/users/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:56.4042785, requestPath: /api/users/create, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:56.6270754, requestPath: /api/users/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:56.691206, requestPath: /api/users/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:56.7573221, requestPath: /api/users/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:56.8168091, requestPath: /api/users/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:56.8780105, requestPath: /api/users/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:56.9342054, requestPath: /api/users/create, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T02:53:57.0028144, requestPath: /api/users/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:22:42.2161333, requestPath: /api/preview-plan/card, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:22:42.958393, requestPath: /api/preview-plan/card/testDevice/testGroup/testCard, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:22:43.1620282, requestPath: /api/preview-plan/card/testDevice/testGroup/testCard, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:22:43.2908867, requestPath: /api/preview-plan/card/testDevice/testGroup/testCard, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:24:42.502575, requestPath: /api/preview-plan/card, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:24:43.4650389, requestPath: /api/preview-plan/card/testDevice/testGroup/testCard, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:24:43.7014496, requestPath: /api/preview-plan/card/testDevice/testGroup/testCard, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:24:43.8507348, requestPath: /api/preview-plan/delete-all, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:24:44.0984697, requestPath: /api/preview-plan/card/testDevice/testGroup/testCard, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:36:53.0030602, requestPath: /api/preview-plan/card/testDevice/testGroup/nonExistentCard, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:36:54.0315003, requestPath: /api/preview-plan/card, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:36:54.3487901, requestPath: /api/preview-plan/card/testDevice/testGroup/testCard, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:36:54.6069956, requestPath: /api/preview-plan/card/testDevice/testGroup/testCard, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:36:54.7283961, requestPath: /api/preview-plan/card/testDevice/testGroup/nonExistentCard, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:36:54.8689791, requestPath: /api/preview-plan/card, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:36:55.0174004, requestPath: /api/preview-plan/card/testDevice/testGroup/nonExistentCard, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:36:55.1318441, requestPath: /api/preview-plan/card/testDevice/testGroup/testCard, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:38:53.1884276, requestPath: /api/preview-plan/card/testDevice/testGroup/nonExistentCard, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:38:53.6818705, requestPath: /api/preview-plan/card, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:38:53.9347176, requestPath: /api/preview-plan/card/testDevice/testGroup/testCard, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:38:54.1172187, requestPath: /api/preview-plan/card/testDevice/testGroup/testCard, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:38:54.2194285, requestPath: /api/preview-plan/card/testDevice/testGroup/nonExistentCard, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:38:54.3150695, requestPath: /api/preview-plan/card, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:38:54.4497181, requestPath: /api/preview-plan/card/testDevice/testGroup/nonExistentCard, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:38:54.5501698, requestPath: /api/preview-plan/card/testDevice/testGroup/testCard, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:36.6721799, requestPath: /api/comments/999, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:36.8617225, requestPath: /api/comments/999, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:37.1072613, requestPath: /api/comments, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:37.2259553, requestPath: /api/comments/2766, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:37.3091239, requestPath: /api/comments/999, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:37.3913257, requestPath: /api/comments/475, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:37.4745601, requestPath: /api/comments/2772, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:37.649729, requestPath: /api/comments/478, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:37.7168988, requestPath: /api/comments, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:37.7750013, requestPath: /api/comments/480, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:39.7134411, requestPath: /api/friends/999/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:39.8724832, requestPath: /api/friends/999, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:39.9933656, requestPath: /api/friends/1/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:40.1535194, requestPath: /api/friends/2784, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:40.3819408, requestPath: /api/friends, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:40.4765353, requestPath: /api/friends/1, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:40.5727592, requestPath: /api/friends, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:41.6025093, requestPath: /api/friends/requests, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:41.6856534, requestPath: /api/friends/requests/999/cancel, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:41.8487633, requestPath: /api/friends/requests/183/accept, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:41.9584669, requestPath: /api/friends/requests, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:42.1048632, requestPath: /api/friends/requests/184/cancel, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:42.1537167, requestPath: /api/friends/requests/sent, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:42.3025632, requestPath: /api/friends/requests/185/cancel, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:42.4167708, requestPath: /api/friends/requests/186/reject, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:42.5190009, requestPath: /api/friends/requests, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:42.6171823, requestPath: /api/friends/requests/received, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:42.7401364, requestPath: /api/friends/requests/sent, headers: +eventType: API_REQUEST, userId: 2, deviceId: apiTestDeviceId, timestamp: 2024-11-13T16:45:58.0386012, requestPath: /test/api, headers: User-Agent: ApiTestAgent, Accept: application/json +eventType: LOGIN_SUCCESS, userId: 1, deviceId: testDeviceId, timestamp: 2024-11-13T16:45:58.0578632, requestPath: /test/login, headers: User-Agent: TestAgent, Accept: */* +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:58.9247368, requestPath: /api/payment/999, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:59.0068309, requestPath: /api/payment/77, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:59.0696013, requestPath: /api/payment/999, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:59.15817, requestPath: /api/payment/79, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:59.7691924, requestPath: /api/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:59.8401586, requestPath: /api/plans/53, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:59.8663223, requestPath: /api/plans/53, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:59.9258984, requestPath: /api/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:45:59.9575042, requestPath: /api/plans/54, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:00.0156165, requestPath: /api/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:00.0426377, requestPath: /api/plans/55, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:00.0938499, requestPath: /api/plans/9999, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:00.1726955, requestPath: /api/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:00.3185267, requestPath: /api/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:00.4389994, requestPath: /api/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:00.5263014, requestPath: /api/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:00.6067312, requestPath: /api/plans/58, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:00.6927198, requestPath: /api/plans/9999, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:00.7752503, requestPath: /api/plans/59, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:00.855168, requestPath: /api/plans/60, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:00.9153744, requestPath: /api/plans/9999, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:00.9817942, requestPath: /api/plans/9999, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:01.1996999, requestPath: /api/plans/61, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:01.2742533, requestPath: /api/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:01.3596318, requestPath: /api/plans/62, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:01.4290989, requestPath: /api/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:04.0060578, requestPath: /api/preview-plan/card/testDevice/testGroup/nonExistentCard, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:04.1910285, requestPath: /api/preview-plan/card, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:04.3827562, requestPath: /api/preview-plan/card/testDevice/testGroup/testCard, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:04.553435, requestPath: /api/preview-plan/card/testDevice/testGroup/testCard, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:04.6244138, requestPath: /api/preview-plan/card/testDevice/testGroup/nonExistentCard, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:04.6776178, requestPath: /api/preview-plan/card/testDevice/testGroup/nonExistentCard, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:04.7583688, requestPath: /api/preview-plan/card/testDevice/testGroup/testCard, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:05.2488392, requestPath: /api/subscription/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:05.3377159, requestPath: /api/subscription/me/subscribe, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:05.4277046, requestPath: /api/subscription/me/subscribe, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:05.5648555, requestPath: /api/subscriptions/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:06.0787939, requestPath: /api/teams/415/users/2833, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:06.3426506, requestPath: /api/teams/invitation/104/cancel, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:06.7117655, requestPath: /api/teams/417/users/2838, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:07.0857495, requestPath: /api/teams/999/users/2840, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:07.2659447, requestPath: /api/teams/419/invite, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:07.5055229, requestPath: /api/teams, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:07.7313356, requestPath: /api/teams/invitation/107/cancel, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:07.9114666, requestPath: /api/teams/422/invite, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:08.3960094, requestPath: /api/teams/invitation/108/response, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:08.4596387, requestPath: /api/teams/423/users/2851/role, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:08.5958579, requestPath: /api/teams/424/users/2853/role, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:08.7722243, requestPath: /api/teams/invitation/109/response, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:08.8795601, requestPath: /api/teams/426/members, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:09.1158543, requestPath: /api/teams/invitation/110/response, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:09.2372138, requestPath: /api/teams/428/users/2862, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:09.3680782, requestPath: /api/teams, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:10.4578669, requestPath: /api/teams/431/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:10.5431723, requestPath: /api/teams/431/plans/86, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:10.6119373, requestPath: /api/teams/432/plans/999, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:10.690798, requestPath: /api/teams/433/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:10.7573024, requestPath: /api/teams/434/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:10.8943745, requestPath: /api/teams/435/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:11.001092, requestPath: /api/teams/436/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:11.0556572, requestPath: /api/teams/436/plans/88, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:11.0879185, requestPath: /api/teams/436/plans/88, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:11.1886776, requestPath: /api/teams/437/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:11.233035, requestPath: /api/teams/437/plans/89, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:11.3689649, requestPath: /api/teams/438/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:11.4419392, requestPath: /api/teams/438/plans/90, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:11.5460027, requestPath: /api/teams/439/plans/999, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:11.6581052, requestPath: /api/teams/440/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:11.6911924, requestPath: /api/teams/440/plans/91, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:12.6422076, requestPath: /api/users/create, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:12.6797423, requestPath: /api/users/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:12.6982733, requestPath: /api/users/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:12.7368973, requestPath: /api/users/create, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:12.7627302, requestPath: /api/users/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:12.7780556, requestPath: /api/users/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:12.7963611, requestPath: /api/users/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:12.8359576, requestPath: /api/users/create, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:12.8646735, requestPath: /api/users/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:12.9163884, requestPath: /api/users/create, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:13.0513373, requestPath: /api/users/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:13.1005287, requestPath: /api/users/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:13.1546985, requestPath: /api/users/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:13.20467, requestPath: /api/users/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:13.2553336, requestPath: /api/users/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:13.3017336, requestPath: /api/users/create, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-13T16:46:13.3631375, requestPath: /api/users/me, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-14T13:24:48.1165219, requestPath: /api/friends/requests, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-14T13:27:04.2760546, requestPath: /api/friends/requests, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-14T13:35:34.8788792, requestPath: /api/friends/999/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-14T13:41:42.1459963, requestPath: /api/friends/2902/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-14T22:26:31.2012127, requestPath: /api/friends/2904/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-14T22:41:54.3386745, requestPath: /api/friends/2906/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-14T22:51:34.0332552, requestPath: /api/friends/2908/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-14T22:58:03.5637471, requestPath: /api/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-14T22:58:03.9411426, requestPath: /api/friends/2910/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-14T23:11:42.6327312, requestPath: /api/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-14T23:11:43.2196307, requestPath: /api/friends/2912/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-14T23:41:03.1896894, requestPath: /api/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-14T23:41:03.5499993, requestPath: /api/plans/65, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-14T23:41:03.5930102, requestPath: /api/plans/65/comments, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-14T23:51:25.9435048, requestPath: /api/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-14T23:51:26.2220829, requestPath: /api/plans/66, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-14T23:51:26.2510476, requestPath: /api/comments, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-14T23:52:35.2958618, requestPath: /api/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-14T23:52:35.6262677, requestPath: /api/plans/67, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-14T23:52:35.6602526, requestPath: /api/comments, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-14T23:57:44.8987963, requestPath: /api/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-14T23:57:45.2414632, requestPath: /api/plans/68, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-14T23:57:45.2734635, requestPath: /api/comments, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T00:07:18.0954846, requestPath: /api/teams/441/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T00:07:18.4066708, requestPath: /api/teams/441/plans/92, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T00:07:18.4476294, requestPath: /api/teams/441/plans/92, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T00:07:18.4834075, requestPath: /api/teams/441/plans/92, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T00:39:20.0699321, requestPath: /api/teams, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T00:39:20.6226807, requestPath: /api/teams/442/invite, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T00:39:21.2271203, requestPath: /api/teams/invitation/111/response, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T00:39:21.3397947, requestPath: /api/teams/442/users/2924, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T01:27:49.6244116, requestPath: /api/friends/requests, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T01:27:49.9321973, requestPath: /api/friends/requests/200/reject, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T01:27:50.042428, requestPath: /api/friends/requests, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T01:27:50.120066, requestPath: /api/friends/requests/201/accept, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T01:37:48.0193452, requestPath: /api/friends/requests, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T01:37:48.2984272, requestPath: /api/friends/requests/202/reject, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T01:43:07.5555823, requestPath: /api/friends/requests, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T01:43:08.2500464, requestPath: /api/friends/requests/203/reject, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T01:43:08.5059392, requestPath: /api/friends/requests, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T01:43:08.666134, requestPath: /api/friends/requests/204/accept, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T01:45:33.0785423, requestPath: /api/friends/requests, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T01:45:33.3879218, requestPath: /api/friends/requests/205/reject, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T01:45:33.4996167, requestPath: /api/friends/requests, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T01:45:33.5762156, requestPath: /api/friends/requests/206/accept, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T01:49:09.5148297, requestPath: /api/friends/requests, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T01:49:09.9149935, requestPath: /api/friends/requests/received, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T01:49:10.0541672, requestPath: /api/friends/requests, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T01:49:10.1099951, requestPath: /api/friends/requests/208/accept, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T01:51:25.2533392, requestPath: /api/friends/requests, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T01:51:25.7725847, requestPath: /api/friends/requests/209/reject, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T01:51:25.9678684, requestPath: /api/friends/requests, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T01:51:26.0741538, requestPath: /api/friends/requests/210/accept, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T01:53:29.5559943, requestPath: /api/friends/requests, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T01:53:30.551043, requestPath: /api/friends/requests/211/reject, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T01:53:30.7611028, requestPath: /api/friends/requests, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T01:53:30.85865, requestPath: /api/friends/requests/212/accept, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T03:01:33.0965042, requestPath: /api/friends, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T03:01:33.4535829, requestPath: /api/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T03:01:33.6291489, requestPath: /api/plans/69, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T03:01:33.6750939, requestPath: /api/comments, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T03:03:52.9544924, requestPath: /api/friends, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T03:03:53.4180404, requestPath: /api/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T03:03:53.6649917, requestPath: /api/plans/70, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T03:03:53.7259943, requestPath: /api/comments, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T03:05:08.4801025, requestPath: /api/friends, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T03:05:08.7888016, requestPath: /api/plans, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T03:05:08.9704432, requestPath: /api/plans/71, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T03:05:09.0113407, requestPath: /api/comments, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T03:21:41.9436992, requestPath: /api/preview-plan/card, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T03:21:42.5546871, requestPath: /api/preview-plan/card/testDevice/testGroup/testCard, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T03:21:42.6185005, requestPath: /api/preview-plan/card/testDevice/testGroup/testCard, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T03:21:42.6644158, requestPath: /api/preview-plan/card/testDevice/testGroup/testCard, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T04:47:11.0257337, requestPath: /api/subscription/me/subscribe, headers: +eventType: API_REQUEST, userId: null, deviceId: null, timestamp: 2024-11-15T04:47:11.5584523, requestPath: /api/subscription/me, headers: diff --git a/src/main/java/com/splanet/splanet/core/exception/ErrorCode.java b/src/main/java/com/splanet/splanet/core/exception/ErrorCode.java index 145a5913..b96e6769 100644 --- a/src/main/java/com/splanet/splanet/core/exception/ErrorCode.java +++ b/src/main/java/com/splanet/splanet/core/exception/ErrorCode.java @@ -54,6 +54,10 @@ public enum ErrorCode { // redis REDIS_SCAN_FAILED("Redis 키 스캔 중 오류가 발생했습니다.", HttpStatus.SERVICE_UNAVAILABLE), + // fcm + TOKEN_NOT_FOUND("해당 FCM 토큰을 찾을 수 없습니다.", HttpStatus.NOT_FOUND), + + // ect INVALID_PLAN_FORMAT("", HttpStatus.BAD_REQUEST); diff --git a/src/main/java/com/splanet/splanet/friend/repository/FriendRepository.java b/src/main/java/com/splanet/splanet/friend/repository/FriendRepository.java index 03b87d14..7106f7bd 100644 --- a/src/main/java/com/splanet/splanet/friend/repository/FriendRepository.java +++ b/src/main/java/com/splanet/splanet/friend/repository/FriendRepository.java @@ -9,10 +9,10 @@ import java.util.List; - @Repository public interface FriendRepository extends JpaRepository { List findByUserId(Long userId); + boolean existsByUserIdAndFriendId(Long userId, Long friendId); @Modifying diff --git a/src/main/java/com/splanet/splanet/friend/service/FriendService.java b/src/main/java/com/splanet/splanet/friend/service/FriendService.java index cfcbe7c3..d01ac0f4 100644 --- a/src/main/java/com/splanet/splanet/friend/service/FriendService.java +++ b/src/main/java/com/splanet/splanet/friend/service/FriendService.java @@ -84,8 +84,6 @@ public ResponseEntity> getFriendPlan(Long friendId, Long u return ResponseEntity.ok(planResponseDtos); } - // 친구 삭제(취소)하기 - // 친구 삭제(취소)하기 @Transactional public ResponseEntity> unfriend(Long friendId, Long userId) { diff --git a/src/main/java/com/splanet/splanet/gpt/service/GptService.java b/src/main/java/com/splanet/splanet/gpt/service/GptService.java index a4fc07f2..89275423 100644 --- a/src/main/java/com/splanet/splanet/gpt/service/GptService.java +++ b/src/main/java/com/splanet/splanet/gpt/service/GptService.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.splanet.splanet.core.properties.GptProperties; import com.splanet.splanet.plan.dto.PlanResponseDto; +import com.splanet.splanet.plan.dto.PlanTimeDto; import com.splanet.splanet.plan.service.PlanService; import org.springframework.ai.openai.api.OpenAiApi; import org.springframework.http.ResponseEntity; @@ -25,9 +26,9 @@ public class GptService { private static final double RESPONSE_TEMPERATURE = 0.8; private static final Map PROMPT_TEMPLATES = Map.of( - 3, "사용자 입력: \"%s\" (deviceId: %s) (groupId: %s) 기존 일정이 있다면 해당 시간과는 겹치지 않게 해 줘 기존일정:%s 현재 시간 이후로 가능한 자주 반복하여 짧고 집중적으로 일정을 완수할 수 있도록 계획을 세워줘. 시험이 포함된 경우, 시험 당일이 아닌 전날까지 준비가 완료되도록 해줘 (%s 기준). 모든 일정은 한국 시간(UTC+9)을 기준으로 설정해줘.", - 2, "사용자 입력: \"%s\" (deviceId: %s) (groupId: %s) 기존 일정이 있다면 해당 시간과는 겹치지 않게 해 줘 기존일정:%s 현재 시간 이후로 적당한 간격을 두고 모든 일정을 완수할 수 있도록 계획해줘. 시험이 포함된 경우, 시험 당일이 아닌 전날까지 준비가 완료되도록 해줘 (%s 기준). 모든 일정은 한국 시간(UTC+9)을 기준으로 설정해줘.", - 1, "사용자 입력: \"%s\" (deviceId: %s) (groupId: %s) 기존 일정이 있다면 해당 시간과는 겹치지 않게 해 줘 기존일정:%s 현재 시간 이후로 여유 있게 모든 일정을 완수할 수 있도록 계획해줘. 시험이 포함된 경우, 시험 당일이 아닌 전날까지 준비가 완료되도록 해줘 (%s 기준). 모든 일정은 한국 시간(UTC+9)을 기준으로 설정해줘." + 3, "사용자 입력: \"%s\" (deviceId: %s) (groupId: %s) 제공된 가이드라인을 따르지 않으면 페널티가 부과될 것입니다. 모든 지침을 주의깊게 읽고 그에 따라 행동하세요. 기존 startDate와 endDate사이에 일정을 생성하지 말아줘. 기존 startDate, endDate:%s 현재 시간 이후로 가능한 자주 반복하여 짧고 집중적으로 일정을 완수할 수 있도록 계획을 세워줘. 현재 시간 : (%s 기준). 모든 일정은 한국 시간(UTC+9)을 기준으로 설정해줘. 또한, 기존 일정을 생각하고 새로운 일정을 적당한 간격을 두고 배치해줘.", + 2, "사용자 입력: \"%s\" (deviceId: %s) (groupId: %s) 제공된 가이드라인을 따르지 않으면 페널티가 부과될 것입니다. 모든 지침을 주의깊게 읽고 그에 따라 행동하세요. 기존 startDate와 endDate사이에 일정을 생성하지 말아줘. 기존 startDate, endDate:%s 현재 시간 이후로 적당한 간격을 두고 모든 일정을 완수할 수 있도록 계획해줘. 현재 시간 : (%s 기준). 모든 일정은 한국 시간(UTC+9)을 기준으로 설정해줘. 또한, 기존 일정을 생각하고 새로운 일정을 적당한 간격을 두고 배치해줘.", + 1, "사용자 입력: \"%s\" (deviceId: %s) (groupId: %s) 제공된 가이드라인을 따르지 않으면 페널티가 부과될 것입니다. 모든 지침을 주의깊게 읽고 그에 따라 행동하세요. 기존 startDate와 endDate사이에 일정을 생성하지 말아줘. 기존 startDate, endDate:%s 현재 시간 이후로 여유 있게 모든 일정을 완수할 수 있도록 계획해줘. 현재 시간 : (%s 기준). 모든 일정은 한국 시간(UTC+9)을 기준으로 설정해줘. 또한, 기존 일정을 생각하고 새로운 일정을 적당한 간격을 두고 배치해줘." ); public GptService(OpenAiApi openAiApi, GptProperties gptProperties, PlanService planService, ObjectMapper objectMapper) { @@ -39,7 +40,7 @@ public GptService(OpenAiApi openAiApi, GptProperties gptProperties, PlanService public String generateResponse(String userInput, Long userId, String deviceId, int groupId) { String currentTime = getCurrentTime(); - List futurePlans = (userId != null) ? planService.getAllFuturePlansByUserId(userId) : List.of(); + List futurePlans = (userId != null) ? planService.getAllFuturePlanTimesByUserId(userId) : List.of(); String planJson = convertPlansToJson(futurePlans); String promptTemplate = PROMPT_TEMPLATES.get(groupId); String fullPrompt = String.format(promptTemplate, userInput, deviceId, groupId, planJson, currentTime); @@ -54,7 +55,7 @@ private String getCurrentTime() { return LocalDateTime.now(ZoneId.of("Asia/Seoul")).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); } - private String convertPlansToJson(List futurePlans) { + private String convertPlansToJson(List futurePlans) { try { return objectMapper.writeValueAsString(futurePlans); } catch (JsonProcessingException e) { @@ -85,3 +86,4 @@ private String getGptResponse(OpenAiApi.ChatCompletionRequest chatRequest) { } } } + diff --git a/src/main/java/com/splanet/splanet/notification/controller/FcmTokenApi.java b/src/main/java/com/splanet/splanet/notification/controller/FcmTokenApi.java index 5eb4ead0..9b14df7c 100644 --- a/src/main/java/com/splanet/splanet/notification/controller/FcmTokenApi.java +++ b/src/main/java/com/splanet/splanet/notification/controller/FcmTokenApi.java @@ -16,35 +16,25 @@ public interface FcmTokenApi { @PostMapping("/register") - @Operation(summary = "FCM 토큰 등록", description = "유저가 FCM 토큰을 등록합니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "FCM 토큰이 성공적으로 등록되었습니다."), - @ApiResponse(responseCode = "404", description = "유저를 찾을 수 없습니다.", content = @Content) - }) ResponseEntity registerFcmToken( @AuthenticationPrincipal Long userId, @RequestBody FcmTokenRequest fcmTokenRequest ); - @PutMapping("/update") - @Operation(summary = "FCM 토큰 설정 수정", description = "알림 설정 및 알림 오프셋을 수정합니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "FCM 토큰 설정이 성공적으로 수정되었습니다."), - @ApiResponse(responseCode = "404", description = "유저를 찾을 수 없습니다.", content = @Content) - }) - ResponseEntity updateFcmTokenSettings( - @AuthenticationPrincipal Long userId, - @RequestBody FcmTokenUpdateRequest fcmTokenUpdateRequest + @PutMapping("/update/notification-enabled") + ResponseEntity updateNotificationEnabled( + @RequestParam String token, + @RequestParam Boolean isNotificationEnabled + ); + + @PutMapping("/update/notification-offset") + ResponseEntity updateNotificationOffset( + @RequestParam String token, + @RequestParam Integer notificationOffset ); @DeleteMapping("/delete") - @Operation(summary = "FCM 토큰 삭제", description = "유저의 FCM 토큰을 삭제합니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "FCM 토큰이 성공적으로 삭제되었습니다."), - @ApiResponse(responseCode = "404", description = "해당 토큰을 찾을 수 없습니다.", content = @Content) - }) ResponseEntity deleteFcmToken( - @AuthenticationPrincipal Long userId, @RequestParam String token ); } diff --git a/src/main/java/com/splanet/splanet/notification/controller/FcmTokenController.java b/src/main/java/com/splanet/splanet/notification/controller/FcmTokenController.java index 38523c35..d4b66f01 100644 --- a/src/main/java/com/splanet/splanet/notification/controller/FcmTokenController.java +++ b/src/main/java/com/splanet/splanet/notification/controller/FcmTokenController.java @@ -1,7 +1,6 @@ package com.splanet.splanet.notification.controller; import com.splanet.splanet.notification.dto.FcmTokenRequest; -import com.splanet.splanet.notification.dto.FcmTokenUpdateRequest; import com.splanet.splanet.notification.service.FcmTokenService; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -21,14 +20,20 @@ public ResponseEntity registerFcmToken(Long userId, FcmTokenRequest fcmT } @Override - public ResponseEntity updateFcmTokenSettings(Long userId, FcmTokenUpdateRequest fcmTokenUpdateRequest) { - fcmTokenService.updateFcmTokenSettings(userId, fcmTokenUpdateRequest); - return ResponseEntity.ok("FCM token 수정 완료"); + public ResponseEntity updateNotificationEnabled(String token, Boolean isNotificationEnabled) { + fcmTokenService.updateNotificationEnabled(token, isNotificationEnabled); + return ResponseEntity.ok("FCM 알림 여부 수정 완료"); } @Override - public ResponseEntity deleteFcmToken(Long userId, String token) { - fcmTokenService.deleteFcmToken(userId, token); + public ResponseEntity updateNotificationOffset(String token, Integer notificationOffset) { + fcmTokenService.updateNotificationOffset(token, notificationOffset); + return ResponseEntity.ok("FCM 알림 오프셋 수정 완료"); + } + + @Override + public ResponseEntity deleteFcmToken(String token) { + fcmTokenService.deleteFcmToken(token); return ResponseEntity.ok("FCM token 삭제 완료"); } } diff --git a/src/main/java/com/splanet/splanet/notification/repository/FcmTokenRepository.java b/src/main/java/com/splanet/splanet/notification/repository/FcmTokenRepository.java index 1f26d161..6fea10f3 100644 --- a/src/main/java/com/splanet/splanet/notification/repository/FcmTokenRepository.java +++ b/src/main/java/com/splanet/splanet/notification/repository/FcmTokenRepository.java @@ -12,4 +12,5 @@ public interface FcmTokenRepository extends JpaRepository { Optional findByUserIdAndToken(Long userId, String token); List findByUserId(Long userId); List findByUserIdIn(Collection userIds); + Optional findByToken(String token); } diff --git a/src/main/java/com/splanet/splanet/notification/service/FcmTokenService.java b/src/main/java/com/splanet/splanet/notification/service/FcmTokenService.java index 29190f26..fea0ddc0 100644 --- a/src/main/java/com/splanet/splanet/notification/service/FcmTokenService.java +++ b/src/main/java/com/splanet/splanet/notification/service/FcmTokenService.java @@ -1,4 +1,3 @@ -// src/main/java/com/splanet/splanet/notification/service/FcmTokenService.java package com.splanet.splanet.notification.service; import com.splanet.splanet.core.exception.BusinessException; @@ -34,26 +33,33 @@ public void registerFcmToken(Long userId, String token) { } @Transactional - public void updateFcmTokenSettings(Long userId, FcmTokenUpdateRequest request) { - FcmToken fcmToken = fcmTokenRepository.findByUserIdAndToken(userId, request.token()) - .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND)); + public void updateNotificationEnabled(String token, Boolean isNotificationEnabled) { + FcmToken fcmToken = fcmTokenRepository.findByToken(token) + .orElseThrow(() -> new BusinessException(ErrorCode.TOKEN_NOT_FOUND)); fcmToken = fcmToken.toBuilder() - .isNotificationEnabled(request.isNotificationEnabled() != null - ? request.isNotificationEnabled() - : fcmToken.getIsNotificationEnabled()) - .notificationOffset(request.notificationOffset() != null - ? request.notificationOffset() - : fcmToken.getNotificationOffset()) + .isNotificationEnabled(isNotificationEnabled != null ? isNotificationEnabled : fcmToken.getIsNotificationEnabled()) .build(); fcmTokenRepository.save(fcmToken); } @Transactional - public void deleteFcmToken(Long userId, String token) { - FcmToken fcmToken = fcmTokenRepository.findByUserIdAndToken(userId, token) - .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND)); + public void updateNotificationOffset(String token, Integer notificationOffset) { + FcmToken fcmToken = fcmTokenRepository.findByToken(token) + .orElseThrow(() -> new BusinessException(ErrorCode.TOKEN_NOT_FOUND)); + + fcmToken = fcmToken.toBuilder() + .notificationOffset(notificationOffset != null ? notificationOffset : fcmToken.getNotificationOffset()) + .build(); + + fcmTokenRepository.save(fcmToken); + } + + @Transactional + public void deleteFcmToken(String token) { + FcmToken fcmToken = fcmTokenRepository.findByToken(token) + .orElseThrow(() -> new BusinessException(ErrorCode.TOKEN_NOT_FOUND)); fcmTokenRepository.delete(fcmToken); } } diff --git a/src/main/java/com/splanet/splanet/notification/service/NotificationService.java b/src/main/java/com/splanet/splanet/notification/service/NotificationService.java index 500289ac..828d0237 100644 --- a/src/main/java/com/splanet/splanet/notification/service/NotificationService.java +++ b/src/main/java/com/splanet/splanet/notification/service/NotificationService.java @@ -42,7 +42,16 @@ public void sendNotification(FcmToken fcmToken, Plan plan) { Notification notification = new Notification(title, body); - Message message = Message.builder().setToken(fcmToken.getToken()).setNotification(notification).putData("title", plan.getTitle()).putData("title", plan.getDescription()).putData("startDate", plan.getStartDate().toString()).build(); + String clickActionUrl = "https://www.splanet.co.kr"; + + Message message = Message.builder().setToken(fcmToken.getToken()) + .setNotification(notification) + .putData("click_action", clickActionUrl) + .putData("title", plan.getTitle()) + .putData("description", plan.getDescription()) + .putData("startDate", plan.getStartDate().toString()) + .build(); + try { String response = firebaseMessaging.send(message); log.info("알림을 정상적으로 전송하였습니다. : {}", response); diff --git a/src/main/java/com/splanet/splanet/plan/dto/PlanResponseDto.java b/src/main/java/com/splanet/splanet/plan/dto/PlanResponseDto.java index 49279433..33ab0619 100644 --- a/src/main/java/com/splanet/splanet/plan/dto/PlanResponseDto.java +++ b/src/main/java/com/splanet/splanet/plan/dto/PlanResponseDto.java @@ -14,11 +14,9 @@ public class PlanResponseDto { private String description; private LocalDateTime startDate; private LocalDateTime endDate; - - @JsonIgnore private Boolean accessibility; - @JsonIgnore private Boolean isCompleted; + @JsonIgnore private LocalDateTime createdAt; @JsonIgnore diff --git a/src/main/java/com/splanet/splanet/plan/dto/PlanTimeDto.java b/src/main/java/com/splanet/splanet/plan/dto/PlanTimeDto.java new file mode 100644 index 00000000..09a951a6 --- /dev/null +++ b/src/main/java/com/splanet/splanet/plan/dto/PlanTimeDto.java @@ -0,0 +1,21 @@ +package com.splanet.splanet.plan.dto; + +import java.time.LocalDateTime; + +public class PlanTimeDto { + private LocalDateTime startDate; + private LocalDateTime endDate; + + public PlanTimeDto(LocalDateTime startDate, LocalDateTime endDate) { + this.startDate = startDate; + this.endDate = endDate; + } + + public LocalDateTime getStartDate() { + return startDate; + } + + public LocalDateTime getEndDate() { + return endDate; + } +} diff --git a/src/main/java/com/splanet/splanet/plan/repository/PlanRepository.java b/src/main/java/com/splanet/splanet/plan/repository/PlanRepository.java index 731c1f0c..5a57110d 100644 --- a/src/main/java/com/splanet/splanet/plan/repository/PlanRepository.java +++ b/src/main/java/com/splanet/splanet/plan/repository/PlanRepository.java @@ -18,4 +18,7 @@ public interface PlanRepository extends JpaRepository { @Query("SELECT p FROM Plan p JOIN FETCH p.user WHERE p.startDate > :now AND p.isCompleted = false") List findUpcomingPlans(@Param("now") LocalDateTime now); + @Query("SELECT p FROM Plan p WHERE p.user.id = :userId AND p.startDate > :currentTime") + List findAllFuturePlansByUserId(@Param("userId") Long userId, @Param("currentTime") LocalDateTime currentTime); + } \ No newline at end of file diff --git a/src/main/java/com/splanet/splanet/plan/service/PlanService.java b/src/main/java/com/splanet/splanet/plan/service/PlanService.java index d40d1796..f7dfa42c 100644 --- a/src/main/java/com/splanet/splanet/plan/service/PlanService.java +++ b/src/main/java/com/splanet/splanet/plan/service/PlanService.java @@ -2,6 +2,7 @@ import com.splanet.splanet.plan.dto.PlanRequestDto; import com.splanet.splanet.plan.dto.PlanResponseDto; +import com.splanet.splanet.plan.dto.PlanTimeDto; import com.splanet.splanet.plan.entity.Plan; import com.splanet.splanet.plan.mapper.PlanMapper; import com.splanet.splanet.plan.repository.PlanRepository; @@ -16,6 +17,7 @@ import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; +import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Set; @@ -118,11 +120,11 @@ private Plan convertToPlan(PlanCardResponseDto previewCard, User user, DateTimeF } @Transactional(readOnly = true) - public List getAllFuturePlansByUserId(Long userId) { - LocalDateTime now = LocalDateTime.now(); - List futurePlans = planRepository.findAllByUserIdAndStartDateAfter(userId, now); - return futurePlans.stream() - .map(planMapper::toResponseDto) + public List getAllFuturePlanTimesByUserId(Long userId) { + LocalDateTime currentTime = LocalDateTime.now(ZoneId.of("Asia/Seoul")); + return planRepository.findAllFuturePlansByUserId(userId, currentTime) + .stream() + .map(plan -> new PlanTimeDto(plan.getStartDate(), plan.getEndDate())) .collect(Collectors.toList()); } diff --git a/src/main/java/com/splanet/splanet/team/repository/TeamInvitationRepository.java b/src/main/java/com/splanet/splanet/team/repository/TeamInvitationRepository.java index ebf47868..8f1a7e28 100644 --- a/src/main/java/com/splanet/splanet/team/repository/TeamInvitationRepository.java +++ b/src/main/java/com/splanet/splanet/team/repository/TeamInvitationRepository.java @@ -13,4 +13,5 @@ public interface TeamInvitationRepository extends JpaRepository findAllByUserAndStatus(User user, InvitationStatus status); List findAllByTeamAndStatus(Team team, InvitationStatus status); Optional findByTeamAndUserAndStatus(Team team, User user, InvitationStatus status); + void deleteAllByTeam(Team team); } \ No newline at end of file diff --git a/src/main/java/com/splanet/splanet/team/repository/TeamUserRelationRepository.java b/src/main/java/com/splanet/splanet/team/repository/TeamUserRelationRepository.java index 45b3beb1..6da4fb1a 100644 --- a/src/main/java/com/splanet/splanet/team/repository/TeamUserRelationRepository.java +++ b/src/main/java/com/splanet/splanet/team/repository/TeamUserRelationRepository.java @@ -25,5 +25,6 @@ public interface TeamUserRelationRepository extends JpaRepository findByTeamIdAndUserId(@Param("teamId") Long teamId, @Param("userId") Long userId); int countByTeam(Team team); + void deleteAllByTeam(Team team); } \ No newline at end of file diff --git a/src/main/java/com/splanet/splanet/team/service/TeamService.java b/src/main/java/com/splanet/splanet/team/service/TeamService.java index 3c432c52..e09c21d7 100644 --- a/src/main/java/com/splanet/splanet/team/service/TeamService.java +++ b/src/main/java/com/splanet/splanet/team/service/TeamService.java @@ -293,7 +293,13 @@ public void deleteTeam(Long teamId, Long adminId) { throw new BusinessException(ErrorCode.ACCESS_DENIED); } - // 팀 삭제 - 연관된 엔티티도 함께 삭제되도록 설정 + // 관련된 초대 데이터 삭제 + teamInvitationRepository.deleteAllByTeam(team); + + // 관련된 사용자 관계 삭제 + teamUserRelationRepository.deleteAllByTeam(team); + + // 팀 삭제 teamRepository.delete(team); } diff --git a/src/test/java/com/splanet/splanet/friend/controller/FriendControllerAcceptanceTest.java b/src/test/java/com/splanet/splanet/friend/controller/FriendControllerAcceptanceTest.java new file mode 100644 index 00000000..e3faadf8 --- /dev/null +++ b/src/test/java/com/splanet/splanet/friend/controller/FriendControllerAcceptanceTest.java @@ -0,0 +1,128 @@ +package com.splanet.splanet.friend.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.splanet.splanet.comment.dto.CommentRequest; +import com.splanet.splanet.comment.entity.Comment; +import com.splanet.splanet.comment.repository.CommentRepository; +import com.splanet.splanet.friendRequest.entity.FriendRequest; +import com.splanet.splanet.friendRequest.repository.FriendRequestRepository; +import com.splanet.splanet.jwt.JwtTokenProvider; +import com.splanet.splanet.plan.dto.PlanRequestDto; +import com.splanet.splanet.plan.dto.PlanResponseDto; +import com.splanet.splanet.user.entity.User; +import com.splanet.splanet.user.repository.UserRepository; +import jakarta.transaction.Transactional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@SpringBootTest +@AutoConfigureMockMvc +@Transactional +class FriendControllerAcceptanceTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private UserRepository userRepository; + + @Autowired + private FriendRequestRepository friendRequestRepository; + + @Autowired + private CommentRepository commentRepository; + + @Autowired + private JwtTokenProvider jwtTokenProvider; + + private String userAToken; + private String userBToken; + private Long userAId; + private Long userBId; + private User userA; + private User userB; + + @BeforeEach + void setUp() { + userA = User.builder() + .nickname("testUser") + .kakaoId(123456789L) + .profileImage("http://example.com/profile.jpg") + .isPremium(false) + .build(); + userB = User.builder() + .nickname("testWriter") + .kakaoId(987654321L) + .profileImage("http://example.com/profile.jpg") + .isPremium(false) + .build(); + + userRepository.saveAll(List.of(userA, userB)); + + userAId = userA.getId(); + userBId = userB.getId(); + + userAToken = jwtTokenProvider.createAccessToken(userAId); + userBToken = jwtTokenProvider.createAccessToken(userBId); + + FriendRequest friendRequest = new FriendRequest(userA, userB, FriendRequest.Status.ACCEPTED); + friendRequestRepository.save(friendRequest); + } + + @Test + @WithMockUser + void 친구_플랜조회_후_댓글작성() throws Exception { + PlanRequestDto requestDto = PlanRequestDto.builder() + .title("테스트 플랜") + .description("테스트 설명") + .startDate(LocalDateTime.now().plusDays(1)) + .endDate(LocalDateTime.now().plusDays(2)) + .build(); + + String planResponse = mockMvc.perform(post("/api/plans") + .header("Authorization", "Bearer " + userAToken) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(requestDto))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.title").value("테스트 플랜")) + .andExpect(jsonPath("$.description").value("테스트 설명")) + .andReturn().getResponse().getContentAsString(); + + Long planId = objectMapper.readValue(planResponse, PlanResponseDto.class).getId(); + + mockMvc.perform(get("/api/plans/" + planId) + .header("Authorization", "Bearer " + userAToken)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.title").value("테스트 플랜")) + .andExpect(jsonPath("$.description").value("테스트 설명")); + + CommentRequest commentRequest = new CommentRequest(userAId, "테스트 댓글"); + + mockMvc.perform(post("/api/comments") + .header("Authorization", "Bearer " + userAToken) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(commentRequest))) + .andExpect(status().isOk()) + .andExpect(content().string("댓글이 성공적으로 작성되었습니다.")); + + List comments = commentRepository.findByUserId(userAId); + assertThat(comments).hasSize(1); + assertThat(comments.get(0).getContent()).isEqualTo("테스트 댓글"); + } +} \ No newline at end of file diff --git a/src/test/java/com/splanet/splanet/friendRequest/controller/FriendRequestControllerAcceptanceTest.java b/src/test/java/com/splanet/splanet/friendRequest/controller/FriendRequestControllerAcceptanceTest.java new file mode 100644 index 00000000..ccb17188 --- /dev/null +++ b/src/test/java/com/splanet/splanet/friendRequest/controller/FriendRequestControllerAcceptanceTest.java @@ -0,0 +1,97 @@ +package com.splanet.splanet.friendRequest.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.splanet.splanet.core.exception.BusinessException; +import com.splanet.splanet.friend.repository.FriendRepository; +import com.splanet.splanet.friendRequest.dto.FriendRequestCreateRequest; +import com.splanet.splanet.friendRequest.entity.FriendRequest; +import com.splanet.splanet.friendRequest.repository.FriendRequestRepository; +import com.splanet.splanet.jwt.JwtTokenProvider; +import com.splanet.splanet.user.entity.User; +import com.splanet.splanet.user.repository.UserRepository; +import jakarta.transaction.Transactional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +@Transactional +class FriendRequestControllerAcceptanceTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private JwtTokenProvider jwtTokenProvider; + + @Autowired + private UserRepository userRepository; + + @Autowired + private FriendRepository friendRepository; + + @Autowired + private FriendRequestRepository friendRequestRepository; + + private String accessToken; + private Long requesterId; + private Long receiverId; + + @BeforeEach + void setUp() { + User requester = User.builder() + .nickname("requester") + .profileImage("requesterImage.png") + .build(); + userRepository.save(requester); + requesterId = requester.getId(); + + User receiver = User.builder() + .nickname("receiver") + .profileImage("receiverImage.png") + .build(); + userRepository.save(receiver); + receiverId = receiver.getId(); + + accessToken = "Bearer " + jwtTokenProvider.createAccessToken(requesterId); + } + + @Test + void 내가_친구_요청_보내면_친구가_수락하기() throws Exception { + FriendRequestCreateRequest request = new FriendRequestCreateRequest(receiverId); + mockMvc.perform(post("/api/friends/requests") + .header("Authorization", accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(MockMvcResultMatchers.jsonPath("$.message").value("친구 요청이 성공적으로 전송되었습니다.")); + + List pendingRequests = friendRequestRepository.findPendingRequestsByReceiverId(receiverId, requesterId, FriendRequest.Status.PENDING); + FriendRequest friendRequest = pendingRequests.get(0); + + String receiverAccessToken = "Bearer " + jwtTokenProvider.createAccessToken(receiverId); + + mockMvc.perform(post("/api/friends/requests/{requestId}/accept", friendRequest.getId()) + .header("Authorization", receiverAccessToken) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(MockMvcResultMatchers.jsonPath("$.status").value("ACCEPTED")); + } +} \ No newline at end of file diff --git a/src/test/java/com/splanet/splanet/notification/service/FcmTokenServiceTest.java b/src/test/java/com/splanet/splanet/notification/service/FcmTokenServiceTest.java index 1390e6ff..766a0791 100644 --- a/src/test/java/com/splanet/splanet/notification/service/FcmTokenServiceTest.java +++ b/src/test/java/com/splanet/splanet/notification/service/FcmTokenServiceTest.java @@ -1,6 +1,7 @@ package com.splanet.splanet.notification.service; import com.splanet.splanet.core.exception.BusinessException; +import com.splanet.splanet.core.exception.ErrorCode; import com.splanet.splanet.notification.dto.FcmTokenUpdateRequest; import com.splanet.splanet.notification.entity.FcmToken; import com.splanet.splanet.notification.repository.FcmTokenRepository; @@ -8,9 +9,10 @@ import com.splanet.splanet.user.repository.UserRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.jupiter.MockitoExtension; import java.util.Optional; @@ -19,6 +21,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; +@ExtendWith(MockitoExtension.class) class FcmTokenServiceTest { @Mock @@ -30,11 +33,6 @@ class FcmTokenServiceTest { @InjectMocks private FcmTokenService fcmTokenService; - @BeforeEach - void setUp() { - MockitoAnnotations.openMocks(this); - } - @Test void FCM토큰_등록_성공() { Long userId = 1L; @@ -58,68 +56,85 @@ void setUp() { when(userRepository.findById(userId)).thenReturn(Optional.empty()); - BusinessException exception = assertThrows(BusinessException.class, () -> + assertThrows(BusinessException.class, () -> fcmTokenService.registerFcmToken(userId, token)); verify(fcmTokenRepository, never()).save(any(FcmToken.class)); } @Test - void FCM토큰_설정_수정_성공() { - Long userId = 1L; + void FCM알림여부_수정_성공() { String token = "testToken"; - FcmTokenUpdateRequest request = new FcmTokenUpdateRequest(token, true, 30); + Boolean isNotificationEnabled = true; FcmToken fcmToken = FcmToken.builder() - .user(User.builder().id(userId).build()) .token(token) .isNotificationEnabled(false) - .notificationOffset(15) .build(); - when(fcmTokenRepository.findByUserIdAndToken(userId, token)).thenReturn(Optional.of(fcmToken)); + when(fcmTokenRepository.findByToken(token)).thenReturn(Optional.of(fcmToken)); - assertDoesNotThrow(() -> fcmTokenService.updateFcmTokenSettings(userId, request)); + assertDoesNotThrow(() -> fcmTokenService.updateNotificationEnabled(token, isNotificationEnabled)); verify(fcmTokenRepository, times(1)).save(any(FcmToken.class)); } @Test - void FCM토큰_설정_수정_토큰없음_예외발생() { - Long userId = 1L; + void FCM알림여부_수정_토큰없음_예외발생() { String token = "testToken"; - FcmTokenUpdateRequest request = new FcmTokenUpdateRequest(token, true, 30); - when(fcmTokenRepository.findByUserIdAndToken(userId, token)).thenReturn(Optional.empty()); + when(fcmTokenRepository.findByToken(token)).thenReturn(Optional.empty()); - BusinessException exception = assertThrows(BusinessException.class, () -> - fcmTokenService.updateFcmTokenSettings(userId, request)); + assertThrows(BusinessException.class, () -> + fcmTokenService.updateNotificationEnabled(token, true)); verify(fcmTokenRepository, never()).save(any(FcmToken.class)); } @Test - void FCM토큰_삭제_성공() { - Long userId = 1L; + void FCM알림오프셋_수정_성공() { String token = "testToken"; + Integer notificationOffset = 30; FcmToken fcmToken = FcmToken.builder() - .user(User.builder().id(userId).build()) .token(token) + .notificationOffset(15) .build(); - when(fcmTokenRepository.findByUserIdAndToken(userId, token)).thenReturn(Optional.of(fcmToken)); + when(fcmTokenRepository.findByToken(token)).thenReturn(Optional.of(fcmToken)); - assertDoesNotThrow(() -> fcmTokenService.deleteFcmToken(userId, token)); + assertDoesNotThrow(() -> fcmTokenService.updateNotificationOffset(token, notificationOffset)); + verify(fcmTokenRepository, times(1)).save(any(FcmToken.class)); + } + + @Test + void FCM알림오프셋_수정_토큰없음_예외발생() { + String token = "testToken"; + + when(fcmTokenRepository.findByToken(token)).thenReturn(Optional.empty()); + + assertThrows(BusinessException.class, () -> + fcmTokenService.updateNotificationOffset(token, 30)); + verify(fcmTokenRepository, never()).save(any(FcmToken.class)); + } + + @Test + void FCM토큰_삭제_성공() { + String token = "testToken"; + + FcmToken fcmToken = FcmToken.builder().token(token).build(); + + when(fcmTokenRepository.findByToken(token)).thenReturn(Optional.of(fcmToken)); + + assertDoesNotThrow(() -> fcmTokenService.deleteFcmToken(token)); verify(fcmTokenRepository, times(1)).delete(fcmToken); } @Test void FCM토큰_삭제_토큰없음_예외발생() { - Long userId = 1L; String token = "testToken"; - when(fcmTokenRepository.findByUserIdAndToken(userId, token)).thenReturn(Optional.empty()); + when(fcmTokenRepository.findByToken(token)).thenReturn(Optional.empty()); - BusinessException exception = assertThrows(BusinessException.class, () -> - fcmTokenService.deleteFcmToken(userId, token)); + assertThrows(BusinessException.class, () -> + fcmTokenService.deleteFcmToken(token)); verify(fcmTokenRepository, never()).delete(any(FcmToken.class)); } -} \ No newline at end of file +} diff --git a/src/test/java/com/splanet/splanet/previewplan/controller/PreviewPlanControllerAccecptanceTest.java b/src/test/java/com/splanet/splanet/previewplan/controller/PreviewPlanControllerAccecptanceTest.java new file mode 100644 index 00000000..801bce51 --- /dev/null +++ b/src/test/java/com/splanet/splanet/previewplan/controller/PreviewPlanControllerAccecptanceTest.java @@ -0,0 +1,97 @@ +package com.splanet.splanet.previewplan.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.splanet.splanet.previewplan.dto.PlanCardRequestDto; +import com.splanet.splanet.previewplan.entity.PlanCard; +import com.splanet.splanet.previewplan.repository.PlanCardRepository; +import com.splanet.splanet.previewplan.repository.PlanGroupRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +@Transactional +class PreviewPlanControllerAccecptanceTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private RedisTemplate redisTemplate; + + @Autowired + private PlanCardRepository planCardRepository; + + @Autowired + private PlanGroupRepository planGroupRepository; + + private static final String BASE_URL = "/api/preview-plan"; + + @BeforeEach + void setup() { + redisTemplate.getConnectionFactory().getConnection().flushAll(); + } + + private void saveSamplePlanCard(String deviceId, String groupId, String cardId) { + PlanCard planCard = PlanCard.builder() + .customKey(deviceId + ":" + groupId + ":" + cardId) + .deviceId(deviceId) + .groupId(groupId) + .cardId(cardId) + .title("Sample Title") + .description("Sample Description") + .startDate("2024-01-01") + .endDate("2024-12-31") + .expiration(3600L) + .build(); + planCardRepository.save(planCard); + } + + @Test + void 플랜카드_생성후_조회후_수정후_삭제() throws Exception { + String deviceId = "testDevice"; + String groupId = "testGroup"; + String cardId = "testCard"; + + PlanCardRequestDto requestDto = new PlanCardRequestDto("Title", "Description", "2024-01-01", "2024-12-31"); + mockMvc.perform(post(BASE_URL + "/card") + .param("deviceId", deviceId) + .param("groupId", groupId) + .contentType(MediaType.APPLICATION_JSON) + .content(new ObjectMapper().writeValueAsString(requestDto))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.title").value("Title")) + .andExpect(jsonPath("$.description").value("Description")); + + saveSamplePlanCard(deviceId, groupId, cardId); + + mockMvc.perform(get(BASE_URL + "/card/{deviceId}/{groupId}/{cardId}", deviceId, groupId, cardId)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.title").value("Sample Title")); + + PlanCardRequestDto updateRequest = new PlanCardRequestDto("Updated Title", "Updated Description", "2024-01-01", "2024-12-31"); + mockMvc.perform(put(BASE_URL + "/card/{deviceId}/{groupId}/{cardId}", deviceId, groupId, cardId) + .contentType(MediaType.APPLICATION_JSON) + .content(new ObjectMapper().writeValueAsString(updateRequest))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.title").value("Updated Title")) + .andExpect(jsonPath("$.description").value("Updated Description")); + + mockMvc.perform(delete(BASE_URL + "/card/{deviceId}/{groupId}/{cardId}", deviceId, groupId, cardId)) + .andExpect(status().isOk()); + + assertFalse(planCardRepository.findById(deviceId + ":" + groupId + ":" + cardId).isPresent()); + } +} \ No newline at end of file diff --git a/src/test/java/com/splanet/splanet/subscription/controller/SubscriptionControllerAccecptanceTest.java b/src/test/java/com/splanet/splanet/subscription/controller/SubscriptionControllerAccecptanceTest.java new file mode 100644 index 00000000..944f0244 --- /dev/null +++ b/src/test/java/com/splanet/splanet/subscription/controller/SubscriptionControllerAccecptanceTest.java @@ -0,0 +1,99 @@ +package com.splanet.splanet.subscription.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.splanet.splanet.core.exception.ErrorCode; +import com.splanet.splanet.subscription.dto.SubscriptionRequest; +import com.splanet.splanet.subscription.entity.Subscription; +import com.splanet.splanet.subscription.repository.SubscriptionRepository; +import com.splanet.splanet.user.entity.User; +import com.splanet.splanet.user.repository.UserRepository; +import com.splanet.splanet.jwt.JwtTokenProvider; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; + +@SpringBootTest +@AutoConfigureMockMvc +@Transactional +class SubscriptionControllerAccecptanceTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private UserRepository userRepository; + + @Autowired + private SubscriptionRepository subscriptionRepository; + + @Autowired + private JwtTokenProvider jwtTokenProvider; + + private String token; + private User testUser; + + @BeforeEach + void setUp() { + testUser = User.builder() + .nickname("구독 테스트 사용자") + .profileImage("http://example.com/profile.jpg") + .kakaoId(123456789L) + .isPremium(false) + .build(); + userRepository.save(testUser); + + token = "Bearer " + jwtTokenProvider.createAccessToken(testUser.getId()); + } + + @Test + void 구독_생성후_조회() throws Exception { + SubscriptionRequest request = new SubscriptionRequest(); + request.setType(Subscription.Type.MONTHLY); + + mockMvc.perform(post("/api/subscription/me/subscribe") + .header("Authorization", token) + .contentType(MediaType.APPLICATION_JSON) + .content(new ObjectMapper().writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("구독이 성공적으로 완료되었습니다.")) + .andExpect(jsonPath("$.subscription.id").exists()) + .andExpect(jsonPath("$.subscription.startDate").exists()) + .andExpect(jsonPath("$.subscription.endDate").doesNotExist()); + + Subscription newSubscription = subscriptionRepository.findTopByUserIdAndStatusOrderByStartDateDesc(testUser.getId(), Subscription.Status.ACTIVE) + .orElseThrow(() -> new AssertionError("구독 정보가 저장되지 않았습니다.")); + + assertThat(newSubscription.getType()).isEqualTo(request.getType()); + assertThat(newSubscription.getStatus()).isEqualTo(Subscription.Status.ACTIVE); + + Subscription subscription = Subscription.builder() + .user(testUser) + .type(Subscription.Type.MONTHLY) + .startDate(LocalDateTime.now()) + .endDate(LocalDateTime.now().plusMonths(1)) + .build(); + subscriptionRepository.save(subscription); + + mockMvc.perform(get("/api/subscription/me") + .header("Authorization", token) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("구독 정보가 성공적으로 조회되었습니다.")) + .andExpect(jsonPath("$.subscription.id").exists()) + .andExpect(jsonPath("$.subscription.startDate").exists()) + .andExpect(jsonPath("$.subscription.endDate").exists()); + } +} diff --git a/src/test/java/com/splanet/splanet/team/controller/TeamControllerAcceptanceTest.java b/src/test/java/com/splanet/splanet/team/controller/TeamControllerAcceptanceTest.java new file mode 100644 index 00000000..5c8d4f3f --- /dev/null +++ b/src/test/java/com/splanet/splanet/team/controller/TeamControllerAcceptanceTest.java @@ -0,0 +1,123 @@ +package com.splanet.splanet.team.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.splanet.splanet.team.entity.*; +import com.splanet.splanet.team.repository.TeamInvitationRepository; +import com.splanet.splanet.team.repository.TeamRepository; +import com.splanet.splanet.team.repository.TeamUserRelationRepository; +import com.splanet.splanet.jwt.JwtTokenProvider; +import com.splanet.splanet.user.entity.User; +import com.splanet.splanet.user.repository.UserRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +@Transactional +class TeamControllerAcceptanceTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private TeamRepository teamRepository; + + @Autowired + private TeamInvitationRepository teamInvitationRepository; + + @Autowired + private TeamUserRelationRepository teamUserRelationRepository; + + @Autowired + private UserRepository userRepository; + + @Autowired + private JwtTokenProvider jwtTokenProvider; + + private String adminAccessToken; + private String invitedUserAccessToken; + private Long teamId; + private Long adminUserId; + private Long invitedUserId; + + @BeforeEach + void setUp() { + User adminUser = User.builder() + .nickname("adminUser") + .profileImage("admin.png") + .build(); + userRepository.save(adminUser); + adminUserId = adminUser.getId(); + + User invitedUser = User.builder() + .nickname("invitedUser") + .profileImage("invited.png") + .build(); + userRepository.save(invitedUser); + invitedUserId = invitedUser.getId(); + + Team team = new Team("Test Team", adminUser); + teamRepository.save(team); + teamId = team.getId(); + + TeamUserRelation adminRelation = new TeamUserRelation(team, adminUser, UserTeamRole.ADMIN); + teamUserRelationRepository.save(adminRelation); + + adminAccessToken = "Bearer " + jwtTokenProvider.createAccessToken(adminUserId); + invitedUserAccessToken = "Bearer " + jwtTokenProvider.createAccessToken(invitedUserId); + } + + @Test + void 팀생성후_유저초대_유저가_수락후_내보내기() throws Exception { + mockMvc.perform(post("/api/teams") + .header("Authorization", adminAccessToken) + .param("teamName", "새로운 팀")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.teamName").value("새로운 팀")) + .andExpect(jsonPath("$.user.nickname").value("adminUser")); + + mockMvc.perform(post("/api/teams/{teamId}/invite", teamId) + .header("Authorization", adminAccessToken) + .param("nickname", "invitedUser")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.nickname").value("invitedUser")) + .andExpect(jsonPath("$.teamId").value(teamId)) + .andExpect(jsonPath("$.status").value("PENDING")); + + TeamInvitation invitation = teamInvitationRepository.findAllByUserAndStatus(userRepository.findById(invitedUserId).get(), InvitationStatus.PENDING).get(0); + assertThat(invitation.getUser().getId()).isEqualTo(invitedUserId); + assertThat(invitation.getTeam().getId()).isEqualTo(teamId); + assertThat(invitation.getStatus()).isEqualTo(InvitationStatus.PENDING); + + mockMvc.perform(put("/api/teams/invitation/{invitationId}/response", invitation.getId()) + .header("Authorization", invitedUserAccessToken) + .param("isAccepted", "true")) + .andExpect(status().isNoContent()); + + TeamInvitation updatedInvitation = teamInvitationRepository.findById(invitation.getId()).orElseThrow(); + assertThat(updatedInvitation.getStatus()).isEqualTo(InvitationStatus.ACCEPTED); + + TeamUserRelation userRelation = teamUserRelationRepository.findByTeamAndUser(teamRepository.findById(teamId).get(), userRepository.findById(invitedUserId).get()).orElseThrow(); + assertThat(userRelation.getUser().getId()).isEqualTo(invitedUserId); + + mockMvc.perform(delete("/api/teams/{teamId}/users/{userId}", teamId, invitedUserId) + .header("Authorization", adminAccessToken)) + .andExpect(status().isNoContent()); + + TeamUserRelation removedUserRelation = teamUserRelationRepository.findByTeamAndUser(teamRepository.findById(teamId).get(), userRepository.findById(invitedUserId).get()).orElse(null); + assertThat(removedUserRelation).isNull(); + } +} \ No newline at end of file diff --git a/src/test/java/com/splanet/splanet/team/service/TeamServiceTest.java b/src/test/java/com/splanet/splanet/team/service/TeamServiceTest.java index 1edf52fe..92333fd7 100644 --- a/src/test/java/com/splanet/splanet/team/service/TeamServiceTest.java +++ b/src/test/java/com/splanet/splanet/team/service/TeamServiceTest.java @@ -83,28 +83,6 @@ public void testCreateTeam_InvalidInput() { assertEquals(ErrorCode.TEAM_NAME_NOT_FOUND, exception.getErrorCode()); } - @Test - public void testDeleteTeam_Success() { - when(teamUserRelationRepository.findByTeamAndUser(any(Team.class), any(User.class))) - .thenReturn(Optional.of(testRelation)); - - teamService.deleteTeam(1L, 1L); - - verify(teamRepository, times(1)).delete(testTeam); - } - - @Test - public void testDeleteTeam_AccessDenied() { - TeamUserRelation memberRelation = new TeamUserRelation(testTeam, testUser, UserTeamRole.MEMBER); - when(teamUserRelationRepository.findByTeamAndUser(any(Team.class), any(User.class))) - .thenReturn(Optional.of(memberRelation)); - - BusinessException exception = assertThrows(BusinessException.class, () -> - teamService.deleteTeam(1L, 1L) - ); - - assertEquals(ErrorCode.ACCESS_DENIED, exception.getErrorCode()); - } @Test public void testLeaveTeam_Success() { @@ -238,5 +216,49 @@ public void testInviteUserToTeam_InvitationAlreadySent() { assertEquals(ErrorCode.INVITATION_ALREADY_SENT, exception.getErrorCode()); } + @Test + public void testDeleteTeam_Success() { + when(teamUserRelationRepository.findByTeamAndUser(testTeam, testUser)) + .thenReturn(Optional.of(testRelation)); + + // 팀 삭제 시 관련 데이터 삭제 확인 + doNothing().when(teamInvitationRepository).deleteAllByTeam(testTeam); + doNothing().when(teamUserRelationRepository).deleteAllByTeam(testTeam); + doNothing().when(teamRepository).delete(testTeam); + + teamService.deleteTeam(1L, 1L); + + verify(teamInvitationRepository, times(1)).deleteAllByTeam(testTeam); + verify(teamUserRelationRepository, times(1)).deleteAllByTeam(testTeam); + verify(teamRepository, times(1)).delete(testTeam); + } + + @Test + public void testDeleteTeam_AccessDenied() { + TeamUserRelation memberRelation = new TeamUserRelation(testTeam, testUser, UserTeamRole.MEMBER); + when(teamUserRelationRepository.findByTeamAndUser(testTeam, testUser)) + .thenReturn(Optional.of(memberRelation)); + + BusinessException exception = assertThrows(BusinessException.class, () -> + teamService.deleteTeam(1L, 1L) + ); + + assertEquals(ErrorCode.ACCESS_DENIED, exception.getErrorCode()); + verify(teamRepository, never()).delete(testTeam); + } + + @Test + public void testDeleteTeam_TeamNotFound() { + when(teamRepository.findById(1L)).thenReturn(Optional.empty()); + + BusinessException exception = assertThrows(BusinessException.class, () -> + teamService.deleteTeam(1L, 1L) + ); + + assertEquals(ErrorCode.TEAM_NOT_FOUND, exception.getErrorCode()); + verify(teamInvitationRepository, never()).deleteAllByTeam(any()); + verify(teamUserRelationRepository, never()).deleteAllByTeam(any()); + verify(teamRepository, never()).delete(any()); + } } \ No newline at end of file diff --git a/src/test/java/com/splanet/splanet/teamplan/controller/TeamPlanControllerAcceptanceTest.java b/src/test/java/com/splanet/splanet/teamplan/controller/TeamPlanControllerAcceptanceTest.java new file mode 100644 index 00000000..1370bb7a --- /dev/null +++ b/src/test/java/com/splanet/splanet/teamplan/controller/TeamPlanControllerAcceptanceTest.java @@ -0,0 +1,146 @@ +package com.splanet.splanet.teamplan.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.jayway.jsonpath.JsonPath; +import com.splanet.splanet.team.entity.Team; +import com.splanet.splanet.team.entity.TeamUserRelation; +import com.splanet.splanet.team.entity.UserTeamRole; +import com.splanet.splanet.team.repository.TeamRepository; +import com.splanet.splanet.team.repository.TeamUserRelationRepository; +import com.splanet.splanet.teamplan.dto.TeamPlanRequestDto; +import com.splanet.splanet.teamplan.repository.TeamPlanRepository; +import com.splanet.splanet.jwt.JwtTokenProvider; +import com.splanet.splanet.user.entity.User; +import com.splanet.splanet.user.repository.UserRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +@Transactional +class TeamPlanControllerAcceptanceTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private JwtTokenProvider jwtTokenProvider; + + @Autowired + private TeamRepository teamRepository; + + @Autowired + private UserRepository userRepository; + + @Autowired + private TeamUserRelationRepository teamUserRelationRepository; + + @Autowired + private TeamPlanRepository teamPlanRepository; + + private String userAccessToken; + private Long userId; + private Long friendId; + private Long teamId; + private Long planId; + + @BeforeEach + void setUp() { + User user = User.builder() + .nickname("testuser") + .profileImage("testimage.png") + .build(); + userRepository.save(user); + userId = user.getId(); + + User friend = User.builder() + .nickname("frienduser") + .profileImage("friendimage.png") + .build(); + userRepository.save(friend); + friendId = friend.getId(); + + Team team = new Team("친구팀", user); + teamRepository.save(team); + teamId = team.getId(); + + TeamUserRelation userRelation = new TeamUserRelation(team, user, UserTeamRole.ADMIN); + TeamUserRelation friendRelation = new TeamUserRelation(team, friend, UserTeamRole.MEMBER); + teamUserRelationRepository.save(userRelation); + teamUserRelationRepository.save(friendRelation); + + userAccessToken = "Bearer " + jwtTokenProvider.createAccessToken(userId); + } + + @Test + void 팀플랜_생성후_수정후_삭제() throws Exception { + TeamPlanRequestDto requestDto = new TeamPlanRequestDto( + "친구 팀 플랜", + "친구 팀에서 만든 플랜", + LocalDateTime.parse("2024-11-12T10:00:00"), + LocalDateTime.parse("2024-11-12T11:00:00"), + true, + true + ); + + String response = mockMvc.perform(post("/api/teams/{teamId}/plans", teamId) + .header("Authorization", userAccessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(requestDto))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.title").value("친구 팀 플랜")) + .andExpect(jsonPath("$.description").value("친구 팀에서 만든 플랜")) + .andExpect(jsonPath("$.startDate").value("2024-11-12T10:00:00")) + .andExpect(jsonPath("$.endDate").value("2024-11-12T11:00:00")) + .andExpect(jsonPath("$.accessibility").value(true)) + .andExpect(jsonPath("$.isCompleted").value(true)) + .andReturn().getResponse().getContentAsString(); + + planId = JsonPath.parse(response).read("$.id", Long.class); + + TeamPlanRequestDto updateRequestDto = new TeamPlanRequestDto( + "수정된 친구 팀 플랜", + "수정된 플랜 설명", + LocalDateTime.parse("2024-11-12T11:00:00"), + LocalDateTime.parse("2024-11-12T12:00:00"), + false, + false + ); + + mockMvc.perform(put("/api/teams/{teamId}/plans/{planId}", teamId, planId) + .header("Authorization", userAccessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(updateRequestDto))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.title").value("수정된 친구 팀 플랜")) + .andExpect(jsonPath("$.description").value("수정된 플랜 설명")) + .andExpect(jsonPath("$.startDate").value("2024-11-12T11:00:00")) + .andExpect(jsonPath("$.endDate").value("2024-11-12T12:00:00")) + .andExpect(jsonPath("$.accessibility").value(false)) + .andExpect(jsonPath("$.isCompleted").value(false)); + + mockMvc.perform(delete("/api/teams/{teamId}/plans/{planId}", teamId, planId) + .header("Authorization", userAccessToken)) + .andExpect(status().isNoContent()); + + mockMvc.perform(get("/api/teams/{teamId}/plans/{planId}", teamId, planId) + .header("Authorization", userAccessToken)) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.message").value("플랜이 존재하지 않습니다.")); + } +}