diff --git a/.github/release-drafter-config.yml b/.github/release-drafter-config.yml new file mode 100644 index 00000000..5c7227a1 --- /dev/null +++ b/.github/release-drafter-config.yml @@ -0,0 +1,38 @@ +name-template: 'v$RESOLVED_VERSION' +tag-template: 'v$RESOLVED_VERSION' +categories: + - title: '🎁 μƒˆλ‘œμš΄ κΈ°λŠ₯이 μΆ”κ°€λ˜μ—ˆμ–΄μš”' + labels: ['🎁 feature'] + - title: '🐞 κΈ°μ‘΄ 버그가 μˆ˜μ •λ˜μ—ˆμ–΄μš”' + labels: ['🐞 fix'] + - title: '🐬 μ½”λ“œλ₯Ό κ°œμ„ ν–ˆμ–΄μš”' + labels: + - 'πŸ› οΈ refactor' + - 'πŸ§ͺ test' + - 'πŸͺ› chore' + - title: 'βš™οΈ ν”„λ‘œμ νŠΈλ₯Ό κ°œμ„ ν–ˆμ–΄μš”' + labels: + - 'πŸͺ„ setting' + - 'πŸ“š documentation' + - '🏭 environment' + - title: 'πŸš€ 배포' + labels: + - 'πŸš€ deployment' + +change-template: '- $TITLE #$NUMBER @$AUTHOR ' +template: | + ## 이번 λ²„μ „μ˜ 변경사항은 μ•„λž˜μ™€ κ°™μ•„μš” + --- + $CHANGES +no-changes-template: '변경사항이 μ—†μ–΄μš”' +version-resolver: + major: + labels: + - '1️⃣ major' + minor: + labels: + - '2️⃣ minor' + patch: + labels: + - '3️⃣ patch' + default: patch \ No newline at end of file diff --git a/.github/workflows/cd-dev.yml b/.github/workflows/cd-dev.yml index c1f2cb23..d4840b74 100644 --- a/.github/workflows/cd-dev.yml +++ b/.github/workflows/cd-dev.yml @@ -21,7 +21,7 @@ jobs: - name: Create application.properties from secret run: | - echo "${{ secrets.APPLICATION_SECRET_SPRING_DEV }}" > ./main/src/main/resources/application-secret.properties + echo "${{ secrets.APPLICATION_SECRET_SPRING }}" > ./main/src/main/resources/application-secret.properties shell: bash - name: Docker hub에 둜그인 diff --git a/.github/workflows/cd-prod.yml b/.github/workflows/cd-prod.yml index 557ebe72..04fb1e23 100644 --- a/.github/workflows/cd-prod.yml +++ b/.github/workflows/cd-prod.yml @@ -21,7 +21,7 @@ jobs: - name: Create application.properties from secret run: | - echo "${{ secrets.APPLICATION_SECRET_SPRING_PROD }}" > ./main/src/main/resources/application-secret.properties + echo "${{ secrets.APPLICATION_SECRET_SPRING }}" > ./main/src/main/resources/application-secret.properties shell: bash - name: Docker hub에 둜그인 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bf2f5864..9f35178e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: - name: Create application.properties from secret run: | - echo "${{ secrets.APPLICATION_SECRET_SPRING_DEV }}" > ./main/src/main/resources/application-secret.properties + echo "${{ secrets.APPLICATION_SECRET_SPRING }}" > ./main/src/main/resources/application-secret.properties shell: bash - name: Build and analyze (SpringBoot) diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml new file mode 100644 index 00000000..1153c604 --- /dev/null +++ b/.github/workflows/release-drafter.yml @@ -0,0 +1,14 @@ +name: Release Drafter +on: + push: + branches: + - main +jobs: + update_release_draft: + runs-on: ubuntu-latest + steps: + - uses: release-drafter/release-drafter@v5 + with: + config-name: release-drafter-config.yml + env: + GITHUB_TOKEN: ${{ secrets.ACTION_TOKEN }} \ No newline at end of file diff --git a/README.md b/README.md index db32c78d..01bfffac 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,7 @@ # μ„€λͺ… -- NestJS기반으둜 μš΄μ˜λ˜λŠ” λͺ¨μž„(Crew) μ„œλ²„ +- Spring 기반으둜 μš΄μ˜λ˜λŠ” λͺ¨μž„(Crew) μ„œλ²„ - [PlayGround Link](https://playground.sopt.org/group/) -- [PlayGround Dev Link](https://sopt-internal-dev.pages.dev/group/) - -## node_modules μ„€μΉ˜ - -```bash -npm ci -``` - -## μ‹€ν–‰ν•˜λŠ” 법 - -```bash -# development -$ npm run start - -# watch mode -$ npm run start:dev - -# production mode -$ npm run start:prod -``` - -## ν…ŒμŠ€νŠΈ - -```bash -# unit tests -$ npm run test - -# e2e tests -$ npm run test:e2e - -# test coverage -$ npm run test:cov -``` # λ°°κ²½ @@ -45,23 +12,22 @@ $ npm run test:cov # κΈ°μˆ μŠ€νƒ - DB: PostgreSQL -- DB GUI tool: pgAdmin4 -- ORM: TypeORM +- ORM: jpa, TypeORM - API λ¬Έμ„œ: Swagger -- 배포: AWS EC2, Docker Compose +- 배포: AWS EC2, Docker Compose, Docker hub - 인증: JWT -- ν…ŒμŠ€νŠΈ: Jest, Unit5 -- μ„œλ²„ ν”„λ ˆμž„μ›Œν¬: NestJS, Spring +- ν…ŒμŠ€νŠΈ: JUnit5, Jest +- μ„œλ²„ ν”„λ ˆμž„μ›Œν¬: Spring, NestJS - μ›Ήμ„œλ²„ ν”„λ ˆμž„μ›Œν¬: Caddy -- μ–Έμ–΄: typescript, Java +- μ–Έμ–΄: Java, Typescript # μ•„ν‚€ν…μ²˜ ## flow 1. Caddyλ₯Ό 톡해 HTTPSλ₯Ό μ μš©ν•˜κ³ , HTTP둜 λ“€μ–΄μ˜€λŠ” μš”μ²­μ„ HTTPS둜 λ¦¬λ‹€μ΄λ ‰νŠΈν•œλ‹€. -2. HTTPS둜 λ“€μ–΄μ˜¨ μš”μ²­μ„ Caddyκ°€ λ°›μ•„μ„œ, CaddyλŠ” μš”μ²­μ„ 받은 후에 ν•΄λ‹Ή μš”μ²­μ„ NodeJS/Spring μ„œλ²„λ‘œ λ¦¬λ²„μŠ€ν”„λ‘μ‹œν•œλ‹€. -3. NodeJS/Spring μ„œλ²„λŠ” μš”μ²­μ„ λ°›μ•„μ„œ, μš”μ²­μ— λ§žλŠ” Controllerλ₯Ό μ°Ύμ•„μ„œ ν•΄λ‹Ή Controllerμ—μ„œ Serviceλ₯Ό ν˜ΈμΆœν•œλ‹€. +2. HTTPS둜 λ“€μ–΄μ˜¨ μš”μ²­μ„ Caddyκ°€ λ°›μ•„μ„œ, CaddyλŠ” μš”μ²­μ„ 받은 후에 ν•΄λ‹Ή μš”μ²­μ„ NestJS/Spring μ„œλ²„λ‘œ λ¦¬λ²„μŠ€ν”„λ‘μ‹œν•œλ‹€. +3. NestJS/Spring μ„œλ²„λŠ” μš”μ²­μ„ λ°›μ•„μ„œ, μš”μ²­μ— λ§žλŠ” Controllerλ₯Ό μ°Ύμ•„μ„œ ν•΄λ‹Ή Controllerμ—μ„œ Serviceλ₯Ό ν˜ΈμΆœν•œλ‹€. 4. Serviceμ—μ„œ λ‘œμ§μ„ μ²˜λ¦¬ν•œ 후에, Repositoryλ₯Ό 톡해 DB에 μ ‘κ·Όν•œλ‹€. 5. RepositoryλŠ” DB에 μ ‘κ·Όν•΄μ„œ 데이터λ₯Ό κ°€μ Έμ˜¨ 후에, Serviceμ—κ²Œ 데이터λ₯Ό μ „λ‹¬ν•œλ‹€. 6. ServiceλŠ” Repositoryλ‘œλΆ€ν„° 받은 데이터λ₯Ό κ°€κ³΅ν•΄μ„œ Controllerμ—κ²Œ μ „λ‹¬ν•œλ‹€. @@ -71,7 +37,7 @@ $ npm run test:cov ```bash . -β”œβ”€β”€ Dockerfile # docker둜 배포할 λ•Œ μ‚¬μš©ν•˜λŠ” 파일. ν˜„μž¬λŠ” μ‚¬μš©ν•˜μ§€ μ•ŠμŒ +β”œβ”€β”€ Dockerfile β”œβ”€β”€ jest.config.ts β”œβ”€β”€ nest-cli.json β”œβ”€β”€ package-lock.json @@ -161,7 +127,7 @@ bar # μ˜ˆμ‹œ λͺ¨λ“ˆ ## ν™˜κ²½ λ³€μˆ˜ - ν™˜κ²½ λ³€μˆ˜λŠ” dev/prod ν™˜κ²½μ— 따라 λ‹€λ₯΄κ²Œ μ„€μ •λ˜μ–΄μ•Ό ν•œλ‹€. - - Nestjs + - NestJS - dev ν™˜κ²½: .dev.env - prod ν™˜κ²½: .prod.env - Spring @@ -187,8 +153,8 @@ bar # μ˜ˆμ‹œ λͺ¨λ“ˆ ## 배포 μ „λž΅ -- ν˜„μž¬λŠ” μˆ˜λ™λ°°ν¬λ₯Ό 진행쀑이고 Blue-Green λ°©μ‹μ˜ 배포λ₯Ό μ§„ν–‰ν•˜κ³  μžˆμ§€ μ•ŠλŠ”λ‹€. -- μˆ˜λ™λ°°ν¬ 이후 짧은 μˆœλ‹¨(μ„œλ²„ μž¬μ‹œμž‘)이 λ°œμƒν•˜κΈ° λ•Œλ¬Έμ— μƒˆλ²½μ— 배포λ₯Ό ν•˜κ±°λ‚˜, μ‚¬μš©μžκ°€ λͺ°λ¦¬λŠ” μ‹œκ°„λŒ€λŠ” ν”Όν•΄μ„œ 배포λ₯Ό μ§„ν–‰ν•œλ‹€. (Prodν™˜κ²½ κΈ°μ€€) +- Blue-Green λ°©μ‹μ˜ 배포 μžλ™ν™”λ₯Ό κ΅¬μΆ•ν–ˆλ‹€. +- κ·ΈλŸΌμ—λ„ λΆˆκ΅¬ν•˜κ³ , μ‚¬μš©μžκ°€ λͺ°λ¦¬λŠ” μ‹œκ°„λŒ€λŠ” ν”Όν•΄μ„œ 배포λ₯Ό μ§„ν–‰ν•œλ‹€. (Prodν™˜κ²½ κΈ°μ€€) - Prodν™˜κ²½μ˜ κ²½μš°λŠ” `main` 브랜치λ₯Ό, Devν™˜κ²½μ˜ κ²½μš°λŠ” `develop` 브랜치λ₯Ό κΈ°μ€€μœΌλ‘œ 배포λ₯Ό μ§„ν–‰ν•œλ‹€. ## 배포 정보 @@ -196,7 +162,8 @@ bar # μ˜ˆμ‹œ λͺ¨λ“ˆ - 배포 μ„œλ²„: AWS EC2 - 배포 툴: Docker Compose -## 배포 방법 +## μˆ˜λ™ 배포 방법 +- ν˜„μž¬λŠ” 배포 μžλ™ν™”κ°€ λ˜μ–΄μžˆλ‹€. ```bash # Prod 배포 @@ -229,6 +196,19 @@ $ sudo docker image prune 7. 리뷰λ₯Ό 받은 후에 PR을 develop에 mergeν•œλ‹€. 8. develop에 merge된 후에 developν™˜κ²½ 배포λ₯Ό μ§„ν–‰ν•œλ‹€. -## 컀밋 μ»¨λ²€μ…˜ +## πŸ™ Commit Convention +- μœ λ‹€μ‹œν‹° μ»¨λ²€μ…˜ + +``` +feat: μƒˆλ‘œμš΄ κΈ°λŠ₯ κ΅¬ν˜„ +add: κΈ°λŠ₯κ΅¬ν˜„κΉŒμ§€λŠ” μ•„λ‹ˆμ§€λ§Œ μƒˆλ‘œμš΄ 파일이 μΆ”κ°€λœ 경우 +del: κΈ°μ‘΄ μ½”λ“œλ₯Ό μ‚­μ œν•œ 경우 +fix: 버그, 였λ₯˜ ν•΄κ²° +docs: READMEλ‚˜ WIKI λ“±μ˜ λ¬Έμ„œ μž‘μ—… +style: μ½”λ“œκ°€ μ•„λ‹Œ μŠ€νƒ€μΌ 변경을 ν•˜λŠ” 경우 +refactor: λ¦¬νŒ©ν† λ§ μž‘μ—… +test: ν…ŒμŠ€νŠΈ μ½”λ“œ μΆ”κ°€, ν…ŒμŠ€νŠΈ μ½”λ“œ λ¦¬νŒ©ν† λ§ +chore: μ½”λ“œ μˆ˜μ •, λ‚΄λΆ€ 파일 μˆ˜μ • +``` -TBD +## κΈ°μ—¬ diff --git a/docker-compose.yml b/docker-compose.yml index 73adf8a1..30df9779 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -144,6 +144,14 @@ services: caddy.route_9.reverse_proxy: "{{ upstreams 4000 }}" caddy.route_10: /notice/v2 caddy.route_10.reverse_proxy: "{{ upstreams 4000 }}" + caddy.route_11: /advertisement/v2 + caddy.route_11.reverse_proxy: "{{ upstreams 4000 }}" + caddy.route_12: /advertisement/v2/* + caddy.route_12.reverse_proxy: "{{ upstreams 4000 }}" + caddy.route_13: /auth/v2 + caddy.route_13.reverse_proxy: "{{ upstreams 4000 }}" + caddy.route_14: /auth/v2/* + caddy.route_14.reverse_proxy: "{{ upstreams 4000 }}" nestjs-blue: image: makerscrew/server:latest @@ -241,6 +249,14 @@ services: caddy.route_9.reverse_proxy: "{{ upstreams 4000 }}" caddy.route_10: /notice/v2 caddy.route_10.reverse_proxy: "{{ upstreams 4000 }}" + caddy.route_11: /advertisement/v2 + caddy.route_11.reverse_proxy: "{{ upstreams 4000 }}" + caddy.route_12: /advertisement/v2/* + caddy.route_12.reverse_proxy: "{{ upstreams 4000 }}" + caddy.route_13: /auth/v2 + caddy.route_13.reverse_proxy: "{{ upstreams 4000 }}" + caddy.route_14: /auth/v2/* + caddy.route_14.reverse_proxy: "{{ upstreams 4000 }}" networks: caddy: diff --git a/main/.gitignore b/main/.gitignore index 9f8add33..a8f16721 100644 --- a/main/.gitignore +++ b/main/.gitignore @@ -38,3 +38,5 @@ out/ .vscode/ application-secret.properties + +.DS_Store \ No newline at end of file diff --git a/main/build.gradle b/main/build.gradle index b2d70c5b..28eb9001 100644 --- a/main/build.gradle +++ b/main/build.gradle @@ -35,6 +35,7 @@ dependencies { annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' implementation group: 'org.postgresql', name: 'postgresql', version: '42.6.0' + implementation 'org.springframework.boot:spring-boot-starter-validation' // jsonb νƒ€μž… 핸듀링 μœ„ν•¨ implementation 'io.hypersistence:hypersistence-utils-hibernate-62:3.6.0' @@ -71,6 +72,12 @@ dependencies { annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta" annotationProcessor "jakarta.annotation:jakarta.annotation-api" annotationProcessor "jakarta.persistence:jakarta.persistence-api" + + // AWS SDK for S3 + implementation "software.amazon.awssdk:s3:2.27.0" + + // csv κ΄€λ ¨ + implementation 'com.opencsv:opencsv:5.5.2' } tasks.named('test') { diff --git a/main/src/main/java/org/sopt/makers/crew/main/MainApplication.java b/main/src/main/java/org/sopt/makers/crew/main/MainApplication.java index 6d45a179..16a2ab9f 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/MainApplication.java +++ b/main/src/main/java/org/sopt/makers/crew/main/MainApplication.java @@ -2,10 +2,12 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.EnableAspectJAutoProxy; @SpringBootApplication @EnableAspectJAutoProxy +@EnableFeignClients public class MainApplication { public static void main(String[] args) { diff --git a/main/src/main/java/org/sopt/makers/crew/main/advertisement/AdvertisementApi.java b/main/src/main/java/org/sopt/makers/crew/main/advertisement/AdvertisementApi.java new file mode 100644 index 00000000..04354eb7 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/advertisement/AdvertisementApi.java @@ -0,0 +1,29 @@ +package org.sopt.makers.crew.main.advertisement; + +import java.security.Principal; + +import org.sopt.makers.crew.main.advertisement.dto.AdvertisementsGetResponseDto; +import org.sopt.makers.crew.main.entity.advertisement.enums.AdvertisementCategory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; + +@Tag(name = "κ΄‘κ³ ") +public interface AdvertisementApi { + @Operation(summary = "κ΄‘κ³  쑰회", description = "κ²Œμ‹œκΈ€ λͺ©λ‘ νŽ˜μ΄μ§€μΌ 경우, ?category=POST
λͺ¨μž„ λͺ©λ‘ νŽ˜μ΄μ§€μΌ 경우, ?category=MEETING") + @ResponseStatus(HttpStatus.OK) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "성곡"), + }) + ResponseEntity getAdvertisement(@RequestParam(name = "category", required = true) AdvertisementCategory category, + Principal principal); + +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/advertisement/AdvertisementController.java b/main/src/main/java/org/sopt/makers/crew/main/advertisement/AdvertisementController.java new file mode 100644 index 00000000..30a234a3 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/advertisement/AdvertisementController.java @@ -0,0 +1,33 @@ +package org.sopt.makers.crew.main.advertisement; + +import java.security.Principal; + +import org.sopt.makers.crew.main.advertisement.dto.AdvertisementsGetResponseDto; +import org.sopt.makers.crew.main.advertisement.service.AdvertisementService; +import org.sopt.makers.crew.main.entity.advertisement.enums.AdvertisementCategory; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping("/advertisement/v2") +@RequiredArgsConstructor +public class AdvertisementController implements AdvertisementApi { + + private final AdvertisementService advertisementService; + + @Override + @GetMapping + public ResponseEntity getAdvertisement( + @RequestParam(name = "category") AdvertisementCategory category, + Principal principal) { + + AdvertisementsGetResponseDto response = advertisementService.getAdvertisement(category); + + return ResponseEntity.ok().body(response); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementGetDto.java b/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementGetDto.java new file mode 100644 index 00000000..01e9eafe --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementGetDto.java @@ -0,0 +1,31 @@ +package org.sopt.makers.crew.main.advertisement.dto; + +import org.sopt.makers.crew.main.entity.advertisement.Advertisement; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +@Schema(name = "AdvertisementImageDto", description = "κ΄‘κ³  ꡬ쒌 이미지 Dto") +public class AdvertisementGetDto { + + @Schema(description = "[Desktop] κ΄‘κ³  ꡬ쒌 이미지 url", example = "[pc 버전 url ν˜•μ‹]") + @NotNull + private final String desktopImageUrl; + + @Schema(description = "[mobile] κ΄‘κ³  ꡬ쒌 이미지 url", example = "[mobile 버전 url ν˜•μ‹]") + @NotNull + private final String mobileImageUrl; + + @Schema(description = "κ΄‘κ³  ꡬ쒌 링크", example = "https://www.naver.com") + @NotNull + private final String advertisementLink; + + public static AdvertisementGetDto of(Advertisement advertisement) { + return new AdvertisementGetDto(advertisement.getAdvertisementDesktopImageUrl(), + advertisement.getAdvertisementMobileImageUrl(), advertisement.getAdvertisementLink()); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementsGetResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementsGetResponseDto.java new file mode 100644 index 00000000..aa2e79ff --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementsGetResponseDto.java @@ -0,0 +1,18 @@ +package org.sopt.makers.crew.main.advertisement.dto; + +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; + +@Schema(name = "AdvertisementGetResponseDto", description = "κ΄‘κ³  ꡬ쒌 쑰회 응닡 Dto") +public record AdvertisementsGetResponseDto( + @Schema(description = "κ΄‘κ³  ꡬ쒌 이미지 객체", example = "") + @NotNull + List advertisements +) { + public static AdvertisementsGetResponseDto of(List advertisements) { + + return new AdvertisementsGetResponseDto(advertisements); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/advertisement/service/AdvertisementService.java b/main/src/main/java/org/sopt/makers/crew/main/advertisement/service/AdvertisementService.java new file mode 100644 index 00000000..53f6e198 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/advertisement/service/AdvertisementService.java @@ -0,0 +1,37 @@ +package org.sopt.makers.crew.main.advertisement.service; + +import java.util.List; + +import org.sopt.makers.crew.main.advertisement.dto.AdvertisementsGetResponseDto; +import org.sopt.makers.crew.main.advertisement.dto.AdvertisementGetDto; +import org.sopt.makers.crew.main.common.util.Time; +import org.sopt.makers.crew.main.entity.advertisement.Advertisement; +import org.sopt.makers.crew.main.entity.advertisement.AdvertisementRepository; +import org.sopt.makers.crew.main.entity.advertisement.enums.AdvertisementCategory; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class AdvertisementService { + private final AdvertisementRepository advertisementRepository; + + private final Time time; + + public AdvertisementsGetResponseDto getAdvertisement(AdvertisementCategory advertisementCategory) { + List advertisements = advertisementRepository.findTop6ByAdvertisementCategoryAndAdvertisementEndDateAfterAndAdvertisementStartDateBeforeOrderByPriority( + advertisementCategory, time.now(), time.now()); + + if (advertisements.isEmpty()) { + return AdvertisementsGetResponseDto.of(null); + } + + List advertisementDtos = advertisements.stream().map(AdvertisementGetDto::of) + .toList(); + + return AdvertisementsGetResponseDto.of(advertisementDtos); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/auth/v2/AuthV2Api.java b/main/src/main/java/org/sopt/makers/crew/main/auth/v2/AuthV2Api.java new file mode 100644 index 00000000..bde202fb --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/auth/v2/AuthV2Api.java @@ -0,0 +1,26 @@ +package org.sopt.makers.crew.main.auth.v2; + + +import org.sopt.makers.crew.main.auth.v2.dto.request.AuthV2RequestDto; +import org.sopt.makers.crew.main.auth.v2.dto.response.AuthV2ResponseDto; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; + +@Tag(name = "인증") +public interface AuthV2Api { + @Operation(summary = "둜그인/νšŒμ›κ°€μž…") + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "성곡"), + @ApiResponse(responseCode = "401", description = "μœ νš¨ν•˜μ§€ μ•ŠλŠ” ν† ν°μž…λ‹ˆλ‹€.", content = @Content), + @ApiResponse(responseCode = "500", description = "크루 μ„œλ²„ λ˜λŠ” ν”Œλ ˆμ΄κ·ΈλΌμš΄λ“œ μ„œλ²„ 였λ₯˜μž…λ‹ˆλ‹€.", content = @Content), + }) + ResponseEntity loginUser( + @RequestBody @Valid AuthV2RequestDto requestDto); +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/auth/v2/AuthV2Controller.java b/main/src/main/java/org/sopt/makers/crew/main/auth/v2/AuthV2Controller.java new file mode 100644 index 00000000..a3066df6 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/auth/v2/AuthV2Controller.java @@ -0,0 +1,29 @@ +package org.sopt.makers.crew.main.auth.v2; + +import org.sopt.makers.crew.main.auth.v2.dto.request.AuthV2RequestDto; +import org.sopt.makers.crew.main.auth.v2.dto.response.AuthV2ResponseDto; +import org.sopt.makers.crew.main.auth.v2.service.AuthV2Service; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping("/auth/v2") +@RequiredArgsConstructor +public class AuthV2Controller implements AuthV2Api { + + private final AuthV2Service authV2Service; + + @Override + @PostMapping + public ResponseEntity loginUser(@RequestBody @Valid AuthV2RequestDto requestDto) { + AuthV2ResponseDto responseDto = authV2Service.loginUser(requestDto); + + return ResponseEntity.ok(responseDto); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/auth/v2/dto/request/AuthV2RequestDto.java b/main/src/main/java/org/sopt/makers/crew/main/auth/v2/dto/request/AuthV2RequestDto.java new file mode 100644 index 00000000..c760d74c --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/auth/v2/dto/request/AuthV2RequestDto.java @@ -0,0 +1,13 @@ +package org.sopt.makers.crew.main.auth.v2.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; + + +@Schema(description = "인증 κ΄€λ ¨ request body dto") +public record AuthV2RequestDto( + @Schema(example = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxOCIsImV4cCI6MTY3OTYwOTk3OH0.9D_Tc14J3S0VDmQgT5lUJ5i3KJZob3NKVmSS3fPjHAo", required = true, description = "ν”Œλ ˆμ΄κ·ΈλΌμš΄λ“œ 토큰") + @NotNull + String authToken +) { +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/auth/v2/dto/response/AuthV2ResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/auth/v2/dto/response/AuthV2ResponseDto.java new file mode 100644 index 00000000..f866f671 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/auth/v2/dto/response/AuthV2ResponseDto.java @@ -0,0 +1,15 @@ +package org.sopt.makers.crew.main.auth.v2.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; + +@Schema(description = "인증 κ΄€λ ¨ response dto") +public record AuthV2ResponseDto( + @Schema(example = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoi7Iah66-86recIiwiaWQiOjI4MywiaWF0IjoxNzI0MjYxMDg3LCJleHAiOjE3NjAyNjEwODd9.r2ScFqhSdt6pyl7gUvx0qFXHIknhtrXQVGjJavbAVRY", required = true, description = "크루 토큰") + @NotNull + String accessToken +) { + public static AuthV2ResponseDto of(String accessToken){ + return new AuthV2ResponseDto(accessToken); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/auth/v2/service/AuthV2Service.java b/main/src/main/java/org/sopt/makers/crew/main/auth/v2/service/AuthV2Service.java new file mode 100644 index 00000000..75f5bf88 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/auth/v2/service/AuthV2Service.java @@ -0,0 +1,8 @@ +package org.sopt.makers.crew.main.auth.v2.service; + +import org.sopt.makers.crew.main.auth.v2.dto.request.AuthV2RequestDto; +import org.sopt.makers.crew.main.auth.v2.dto.response.AuthV2ResponseDto; + +public interface AuthV2Service { + AuthV2ResponseDto loginUser(AuthV2RequestDto requestDto); +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/auth/v2/service/AuthV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/auth/v2/service/AuthV2ServiceImpl.java new file mode 100644 index 00000000..bedba902 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/auth/v2/service/AuthV2ServiceImpl.java @@ -0,0 +1,64 @@ +package org.sopt.makers.crew.main.auth.v2.service; + +import java.util.Optional; + +import org.sopt.makers.crew.main.auth.v2.dto.request.AuthV2RequestDto; +import org.sopt.makers.crew.main.auth.v2.dto.response.AuthV2ResponseDto; +import org.sopt.makers.crew.main.common.jwt.JwtTokenProvider; +import org.sopt.makers.crew.main.entity.user.User; +import org.sopt.makers.crew.main.entity.user.UserRepository; +import org.sopt.makers.crew.main.external.playground.PlaygroundService; +import org.sopt.makers.crew.main.external.playground.dto.request.PlaygroundUserRequestDto; +import org.sopt.makers.crew.main.external.playground.dto.response.PlaygroundUserResponseDto; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Slf4j +public class AuthV2ServiceImpl implements AuthV2Service { + + private final UserRepository userRepository; + + private final PlaygroundService playgroundService; + private final JwtTokenProvider jwtTokenProvider; + + @Override + @Transactional + public AuthV2ResponseDto loginUser(AuthV2RequestDto requestDto) { + + // ν”Œκ·Έ μ„œλ²„λ‘œμ˜ μš”μ²­ + PlaygroundUserResponseDto responseDto = playgroundService.getUser(PlaygroundUserRequestDto.of(requestDto.authToken())); + Optional user = userRepository.findByOrgId(responseDto.getId()); + + /** + * @note: νšŒμ›κ°€μž… 경우 + * + * */ + if (user.isEmpty()) { + User newUser = responseDto.toEntity(); + userRepository.save(newUser); + + log.info("new user signup : {} {}", newUser.getId(), newUser.getName()); + String accessToken = jwtTokenProvider.generateAccessToken(newUser.getId(), newUser.getName()); + return AuthV2ResponseDto.of(accessToken); + } + + /** + * @note: 둜그인 경우 : κΈ°μ‘΄ μ •λ³΄μ—μ„œ λ³€ν™”μžˆλŠ” 뢀뢄은 μ—…λ°μ΄νŠΈ ν•œλ‹€. + * + * */ + User curUser = user.get(); + curUser.updateUser(responseDto.getName(), responseDto.getId(), responseDto.getUserActivities(), + responseDto.getProfileImage(), responseDto.getPhone()); + + String accessToken = jwtTokenProvider.generateAccessToken(curUser.getId(), curUser.getName()); + log.info("accessToken : {}", accessToken); + + return AuthV2ResponseDto.of(accessToken); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Api.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Api.java index a5b798f3..2ce70189 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Api.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Api.java @@ -1,8 +1,13 @@ package org.sopt.makers.crew.main.comment.v2; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import java.security.Principal; @@ -14,7 +19,9 @@ import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2CreateCommentResponseDto; import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2GetCommentsResponseDto; import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2ReportCommentResponseDto; +import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2SwitchCommentLikeResponseDto; import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2UpdateCommentResponseDto; +import org.sopt.makers.crew.main.common.dto.TempResponseDto; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; @@ -22,12 +29,19 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.ResponseStatus; +@Tag(name = "λŒ“κΈ€/λŒ€λŒ“κΈ€") public interface CommentV2Api { @Operation(summary = "λͺ¨μž„ κ²Œμ‹œκΈ€ λŒ“κΈ€ μž‘μ„±") @ResponseStatus(HttpStatus.CREATED) @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "성곡"), + @ApiResponse( + responseCode = "201", + description = "성곡", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = CommentV2CreateCommentResponseDto.class) + )), }) ResponseEntity createComment( @Valid @RequestBody CommentV2CreateCommentBodyDto requestBody, Principal principal); @@ -35,7 +49,13 @@ ResponseEntity createComment( @Operation(summary = "λͺ¨μž„ κ²Œμ‹œκΈ€ λŒ“κΈ€ μˆ˜μ •") @ResponseStatus(HttpStatus.OK) @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "성곡"), + @ApiResponse( + responseCode = "200", + description = "성곡", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = CommentV2UpdateCommentResponseDto.class) + )), }) ResponseEntity updateComment( @PathVariable Integer commentId, @@ -45,7 +65,14 @@ ResponseEntity updateComment( @Operation(summary = "λŒ“κΈ€ μ‹ κ³ ν•˜κΈ°") @ResponseStatus(HttpStatus.CREATED) @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "성곡"), + @ApiResponse( + responseCode = "201", + description = "성곡", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = CommentV2ReportCommentResponseDto.class) + ) + ), }) ResponseEntity reportComment( @PathVariable Integer commentId, Principal principal); @@ -60,7 +87,9 @@ ResponseEntity reportComment( @Operation(summary = "λŒ“κΈ€μ—μ„œ μœ μ € λ©˜μ…˜") @ResponseStatus(HttpStatus.OK) @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "성곡"), + @ApiResponse( + responseCode = "200", + description = "성곡"), }) ResponseEntity mentionUserInComment( @Valid @RequestBody CommentV2MentionUserInCommentRequestDto requestBody, @@ -69,7 +98,46 @@ ResponseEntity mentionUserInComment( @Operation(summary = "λͺ¨μž„ κ²Œμ‹œκΈ€ λŒ“κΈ€ 리슀트 쑰회") @ResponseStatus(HttpStatus.OK) @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "성곡"), + @ApiResponse( + responseCode = "200", + description = "성곡"), }) - ResponseEntity getComments(@Valid @ModelAttribute CommentV2GetCommentsQueryDto requestBody, Principal principal); + @Parameters( + { + @Parameter(name = "page", description = "νŽ˜μ΄μ§€, default = 1", example = "1", schema = @Schema(type = "integer", format = "int32")), + @Parameter(name = "take", description = "κ°€μ Έμ˜¬ 데이터 개수, default = 12", example = "50", schema = @Schema(type = "integer", format = "int32")), + @Parameter(name = "postId", description = "κ²Œμ‹œκΈ€ id", example = "3", schema = @Schema(type = "integer", format = "int32")) + }) + ResponseEntity getComments( + @Valid @ModelAttribute @Parameter(hidden = true) CommentV2GetCommentsQueryDto request, + Principal principal); + + @Operation(summary = "[TEMP] λͺ¨μž„ κ²Œμ‹œκΈ€ λŒ“κΈ€ 리슀트 쑰회") + @ResponseStatus(HttpStatus.OK) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "성곡"), + }) + @Parameters({ + @Parameter(name = "page", description = "νŽ˜μ΄μ§€, default = 1", example = "1", schema = @Schema(type = "integer", format = "int32")), + @Parameter(name = "take", description = "κ°€μ Έμ˜¬ 데이터 개수, default = 12", example = "50", schema = @Schema(type = "integer", format = "int32")), + @Parameter(name = "postId", description = "κ²Œμ‹œκΈ€ id", example = "3", schema = @Schema(type = "integer", format = "int32"))}) + ResponseEntity> getCommentsTemp( + @Valid @ModelAttribute @Parameter(hidden = true) CommentV2GetCommentsQueryDto request, + Principal principal); + + @Operation(summary = "λͺ¨μž„ κ²Œμ‹œκΈ€ λŒ“κΈ€ μ’‹μ•„μš” ν† κΈ€") + @ResponseStatus(HttpStatus.CREATED) + @ApiResponses(value = { + @ApiResponse( + responseCode = "201", + description = "성곡", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = CommentV2SwitchCommentLikeResponseDto.class) + )), + }) + ResponseEntity switchCommentLike(Principal principal, + @PathVariable Integer commentId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Controller.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Controller.java index 06c0e59d..52c9c097 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Controller.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Controller.java @@ -1,5 +1,6 @@ package org.sopt.makers.crew.main.comment.v2; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; @@ -14,15 +15,17 @@ import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2CreateCommentResponseDto; import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2GetCommentsResponseDto; import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2ReportCommentResponseDto; +import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2SwitchCommentLikeResponseDto; import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2UpdateCommentResponseDto; import org.sopt.makers.crew.main.comment.v2.service.CommentV2Service; +import org.sopt.makers.crew.main.common.dto.TempResponseDto; import org.sopt.makers.crew.main.common.util.UserUtil; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -32,7 +35,6 @@ @RestController @RequestMapping("/comment/v2") @RequiredArgsConstructor -@Tag(name = "λŒ“κΈ€/λŒ€λŒ“κΈ€") public class CommentV2Controller implements CommentV2Api { private final CommentV2Service commentV2Service; @@ -89,13 +91,39 @@ public ResponseEntity mentionUserInComment( @Override @GetMapping public ResponseEntity getComments( - @Valid @ModelAttribute CommentV2GetCommentsQueryDto requestBody, + @Valid @ModelAttribute @Parameter(hidden = true) CommentV2GetCommentsQueryDto request, Principal principal) { Integer userId = UserUtil.getUserId(principal); - CommentV2GetCommentsResponseDto commentDtos = commentV2Service.getComments(requestBody.getPostId(), - requestBody.getPage(), requestBody.getTake(), userId); + CommentV2GetCommentsResponseDto commentDtos = commentV2Service.getComments(request.getPostId(), + request.getPage(), request.getTake(), userId); return ResponseEntity.status(HttpStatus.OK).body(commentDtos); } + + @Override + @GetMapping("/temp") + public ResponseEntity> getCommentsTemp( + @Valid @ModelAttribute @Parameter(hidden = true) CommentV2GetCommentsQueryDto request, + Principal principal) { + + Integer userId = UserUtil.getUserId(principal); + CommentV2GetCommentsResponseDto commentDtos = commentV2Service.getComments(request.getPostId(), + request.getPage(), request.getTake(), userId); + + return ResponseEntity.status(HttpStatus.OK).body(TempResponseDto.of(commentDtos)); + } + + @Override + @PostMapping("/{commentId}/like") + public ResponseEntity switchCommentLike( + Principal principal, + @PathVariable Integer commentId) { + Integer userId = UserUtil.getUserId(principal); + + CommentV2SwitchCommentLikeResponseDto result = commentV2Service.switchCommentToggle(commentId, + userId); + + return ResponseEntity.status(HttpStatus.CREATED).body(result); + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/query/CommentV2GetCommentsQueryDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/query/CommentV2GetCommentsQueryDto.java index a5602c51..54a63d6a 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/query/CommentV2GetCommentsQueryDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/query/CommentV2GetCommentsQueryDto.java @@ -2,13 +2,16 @@ import org.sopt.makers.crew.main.common.pagination.dto.PageOptionsDto; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; import lombok.Builder; import lombok.Getter; @Getter +@Schema(name = "CommentV2GetCommentsQueryDto", description = "λŒ“κΈ€ λͺ©λ‘ μš”μ²­ Dto") public class CommentV2GetCommentsQueryDto extends PageOptionsDto { + @Schema(description = "κ²Œμ‹œκΈ€ id", example = "1") @NotNull private final Integer postId; diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/request/CommentV2CreateCommentBodyDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/request/CommentV2CreateCommentBodyDto.java index bdc26c45..e95b2939 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/request/CommentV2CreateCommentBodyDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/request/CommentV2CreateCommentBodyDto.java @@ -1,5 +1,7 @@ package org.sopt.makers.crew.main.comment.v2.dto.request; +import com.fasterxml.jackson.annotation.JsonProperty; + import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; @@ -11,18 +13,19 @@ @Schema(description = "λŒ“κΈ€ 생성 request body dto") public class CommentV2CreateCommentBodyDto { - @Schema(example = "1", required = true, description = "κ²Œμ‹œκΈ€ ID") + @Schema(example = "1", description = "κ²Œμ‹œκΈ€ ID") @NotNull private Integer postId; - @Schema(example = "μ•Œκ³ λ³΄λ©΄ μ“Έλ°μžˆλŠ” 개발 ν”„λ‘œμ„ΈμŠ€", required = true, description = "λŒ“κΈ€ λ‚΄μš©") + @Schema(example = "μ•Œκ³ λ³΄λ©΄ μ“Έλ°μžˆλŠ” 개발 ν”„λ‘œμ„ΈμŠ€", description = "λŒ“κΈ€ λ‚΄μš©") @NotEmpty private String contents; - @Schema(example = "λŒ“κΈ€/λŒ€λŒ“κΈ€ μ—¬λΆ€", required = true, description = "true") - private boolean isParent; + @Schema(example = "true", description = "λŒ“κΈ€/λŒ€λŒ“κΈ€ μ—¬λΆ€") + @NotNull + private Boolean isParent; - @Schema(example = "λŒ€λŒ“κΈ€μΈ 경우, λŒ“κΈ€μ˜ id", required = true, description = "1") + @Schema(example = "3", description = "λŒ€λŒ“κΈ€μΈ 경우, λŒ“κΈ€μ˜ id") private Integer parentCommentId; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/request/CommentV2MentionUserInCommentRequestDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/request/CommentV2MentionUserInCommentRequestDto.java index 5d059ef6..e7114b41 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/request/CommentV2MentionUserInCommentRequestDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/request/CommentV2MentionUserInCommentRequestDto.java @@ -14,18 +14,15 @@ @Schema(description = "λŒ“κΈ€μ—μ„œ μœ μ € μ–ΈκΈ‰ request body dto") public class CommentV2MentionUserInCommentRequestDto { - /** - * 주의!! : ν•„λ“œλͺ…은 userIds μ΄μ§€λ§Œ μ‹€μ œ μš”μ²­λ°›λŠ” 값은 orgId μž…λ‹ˆλ‹€. - */ - - @Schema(example = "[111, 112, 113]", required = true, description = "μ–ΈκΈ‰ν•  μœ μ € ID") + @Schema(example = "[111, 112, 113]", description = "λ©”μ΄μ»€μŠ€ ν”„λ‘œλ•νŠΈμ—μ„œ λ²”μš©μ μœΌλ‘œ μ‚¬μš©ν•˜λŠ” userId") @NotEmpty - private List userIds; + private List orgIds; - @Schema(example = "1", required = true, description = "κ²Œμ‹œκΈ€ ID") + @Schema(example = "1", description = "κ²Œμ‹œκΈ€ ID") @NotNull private Integer postId; - @Schema(example = "λ©˜μ…˜λ‚΄μš©~~", required = true, description = "λ©˜μ…˜ λ‚΄μš©") + @Schema(example = "λ©˜μ…˜λ‚΄μš©~~", description = "λ©˜μ…˜ λ‚΄μš©") + @NotNull private String content; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/request/CommentV2UpdateCommentBodyDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/request/CommentV2UpdateCommentBodyDto.java index f702ed16..8668ef69 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/request/CommentV2UpdateCommentBodyDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/request/CommentV2UpdateCommentBodyDto.java @@ -9,11 +9,11 @@ @Getter @AllArgsConstructor @NoArgsConstructor -@Schema(description = "λŒ“κΈ€ μ—…λ°μ΄νŠΈ request body dto") +@Schema(description = "λŒ“κΈ€ μˆ˜μ • request body dto") public class CommentV2UpdateCommentBodyDto { - @Schema(example = "μ•Œκ³ λ³΄λ©΄ μ“Έλ°μžˆλŠ” 개발 ν”„λ‘œμ„ΈμŠ€", description = "λŒ“κΈ€ λ‚΄μš©") - @NotEmpty - private String contents; + @Schema(example = "μ•Œκ³ λ³΄λ©΄ μ“Έλ°μžˆλŠ” 개발 ν”„λ‘œμ„ΈμŠ€", description = "λŒ“κΈ€ λ‚΄μš©") + @NotEmpty + private String contents; } \ No newline at end of file diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java index 945b5a2c..7c48f1f4 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java @@ -2,32 +2,65 @@ import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; import org.sopt.makers.crew.main.entity.comment.Comment; +import org.sopt.makers.crew.main.entity.user.User; +import com.fasterxml.jackson.annotation.JsonProperty; import com.querydsl.core.annotations.QueryProjection; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Getter; @Getter +@Schema(name = "CommentDto", description = "λŒ“κΈ€ 객체 응닡 Dto") public class CommentDto { + + @Schema(description = "λŒ“κΈ€ id", example = "1") + @NotNull private final Integer id; + + @Schema(description = "λŒ“κΈ€ λ‚΄μš©", example = "이것은 λŒ“κΈ€ λ‚΄μš©μž…λ‹ˆλ‹€.") + @NotNull private final String contents; + + @Schema(description = "λŒ“κΈ€ μž‘μ„±μž 객체", example = "") + @NotNull private final CommentWriterDto user; - private final LocalDateTime updatedDate; + + @Schema(description = "λŒ“κΈ€ 생성 μ‹œμ ", example = "2024-07-31T15:30:00") + @NotNull + private final LocalDateTime createdDate; + + @Schema(description = "μ’‹μ•„μš” 갯수", example = "20") + @NotNull private final int likeCount; - private final boolean isLiked; - private final boolean isWriter; + + @Schema(description = "λŒ“κΈ€ μ’‹μ•„μš” μ—¬λΆ€", example = "true") + @NotNull + private final Boolean isLiked; + + @Schema(description = "λŒ“κΈ€ μž‘μ„±μž μ—¬λΆ€", example = "true") + @NotNull + private final Boolean isWriter; + + @Schema(description = "λŒ“κΈ€ μˆœμ„œ", example = "2") + @NotNull private final int order; - private final List replies; + + @Schema(description = "λŒ€λŒ“κΈ€ 객체 λͺ©λ‘", example = "") + @NotNull + private final List replies; @QueryProjection - public CommentDto(Integer id, String contents, CommentWriterDto user, LocalDateTime updatedDate, int likeCount, - boolean isLiked, boolean isWriter, int order, List replies) { + public CommentDto(Integer id, String contents, CommentWriterDto user, LocalDateTime createdDate, int likeCount, + boolean isLiked, boolean isWriter, int order, List replies) { this.id = id; this.contents = contents; this.user = user; - this.updatedDate = updatedDate; + this.createdDate = createdDate; this.likeCount = likeCount; this.isLiked = isLiked; this.isWriter = isWriter; @@ -35,10 +68,15 @@ public CommentDto(Integer id, String contents, CommentWriterDto user, LocalDateT this.replies = replies; } - public static CommentDto of(Comment comment, boolean isLiked, boolean isWriter, List replies) { + public static CommentDto of(Comment comment, boolean isLiked, boolean isWriter, List replies) { + Integer userId = comment.getUser() == null ? null : comment.getUser().getId(); + Integer orgId = comment.getUser() == null ? null : comment.getUser().getOrgId(); + String userName = comment.getUser() == null ? null : comment.getUser().getName(); + String profileImage = comment.getUser() == null ? null : comment.getUser().getProfileImage(); + return new CommentDto(comment.getId(), comment.getContents(), - new CommentWriterDto(comment.getUser().getId(), comment.getUser().getOrgId(), comment.getUser().getName(), - comment.getUser().getProfileImage()), comment.getUpdatedDate(), comment.getLikeCount(), + new CommentWriterDto(userId, orgId, userName, + profileImage), comment.getCreatedDate(), comment.getLikeCount(), isLiked, isWriter, comment.getOrder(), replies); } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2CreateCommentResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2CreateCommentResponseDto.java index d40b12f8..3afa311b 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2CreateCommentResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2CreateCommentResponseDto.java @@ -1,14 +1,20 @@ package org.sopt.makers.crew.main.comment.v2.dto.response; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor(staticName = "of") +@Schema(name = "CommentV2CreateCommentResponseDto", description = "λŒ“κΈ€ 생성 응닡 Dto") public class CommentV2CreateCommentResponseDto { - /** - * μƒμ„±λœ λŒ“κΈ€ id - */ - private Integer commentId; + /** + * μƒμ„±λœ λŒ“κΈ€ id + */ + @Schema(description = "μƒμ„±λœ λŒ“κΈ€ id", example = "1") + @NotNull + private Integer commentId; + } diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2GetCommentsResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2GetCommentsResponseDto.java index de6c9dfb..945ac676 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2GetCommentsResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2GetCommentsResponseDto.java @@ -2,11 +2,27 @@ import java.util.List; +import org.sopt.makers.crew.main.common.pagination.dto.PageMetaDto; + +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor(staticName = "of") +@Schema(name = "CommentV2GetCommentsResponseDto", description = "λŒ“κΈ€ λͺ©λ‘ 쑰회 응닡 Dto") public class CommentV2GetCommentsResponseDto { + + @ArraySchema( + schema = @Schema(implementation = CommentDto.class) + ) + @NotNull private final List comments; + + @Schema(description = "νŽ˜μ΄μ§€λ„€μ΄μ…˜", example = "") + @NotNull + private final PageMetaDto meta; + } diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2ReportCommentResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2ReportCommentResponseDto.java index 03340a2c..0f5ed43a 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2ReportCommentResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2ReportCommentResponseDto.java @@ -1,14 +1,20 @@ package org.sopt.makers.crew.main.comment.v2.dto.response; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor(staticName = "of") +@Schema(name = "CommentV2ReportCommentResponseDto", description = "λŒ“κΈ€ μ‹ κ³  응닡 Dto") public class CommentV2ReportCommentResponseDto { - /** - * μƒμ„±λœ μ‹ κ³  id - */ - private Integer reportId; + /** + * μƒμ„±λœ μ‹ κ³  id + */ + @Schema(description = "μ‹ κ³  id", example = "1") + @NotNull + private Integer reportId; + } diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2SwitchCommentLikeResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2SwitchCommentLikeResponseDto.java new file mode 100644 index 00000000..d58b648a --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2SwitchCommentLikeResponseDto.java @@ -0,0 +1,20 @@ +package org.sopt.makers.crew.main.comment.v2.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor(staticName = "of") +@Schema(name = "CommentV2SwitchCommentLikeResponseDto", description = "λŒ“κΈ€ μ’‹μ•„μš” ν† κΈ€ 응닡 Dto") +public class CommentV2SwitchCommentLikeResponseDto { + + /** + * μš”μ²­ ν›„ λ‚΄κ°€ μ’‹μ•„μš”λ₯Ό λˆ„λ₯Έ μƒνƒœ + */ + @Schema(description = "μš”μ²­ ν›„ λ‚΄κ°€ μ’‹μ•„μš”λ₯Ό λˆ„λ₯Έ μƒνƒœ", example = "false") + @NotNull + private Boolean isLiked; + +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2UpdateCommentResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2UpdateCommentResponseDto.java index fe7de072..0bbc0d94 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2UpdateCommentResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2UpdateCommentResponseDto.java @@ -1,24 +1,34 @@ package org.sopt.makers.crew.main.comment.v2.dto.response; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor(staticName = "of") +@Schema(name = "CommentV2UpdateCommentResponseDto", description = "λŒ“κΈ€ μˆ˜μ • 응닡 Dto") public class CommentV2UpdateCommentResponseDto { - /** - * μƒμ„±λœ λŒ“κΈ€ id - */ - private Integer id; + /** + * μƒμ„±λœ λŒ“κΈ€ id + */ + @Schema(description = "μˆ˜μ •λœ λŒ“κΈ€ id", example = "1") + @NotNull + private Integer id; - /** - * λŒ“κΈ€ λ‚΄μš© - */ - private String contents; + /** + * λŒ“κΈ€ λ‚΄μš© + */ + @Schema(description = "μˆ˜μ •λœ λŒ“κΈ€ λ‚΄μš©", example = "λŒ“κΈ€λ‚΄μš©μž…λ‹ˆλ‹€1") + @NotNull + private String contents; + + /** + * μ—…λ°μ΄νŠΈ μ‹œκ° + */ + @Schema(description = "μˆ˜μ •λœ μ‹œκ°„", example = "2024-07-31T15:30:00") + @NotNull + private String updateDate; - /** - * μ—…λ°μ΄νŠΈ μ‹œκ° - */ - private String updateDate; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentWriterDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentWriterDto.java index af69fa40..905be111 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentWriterDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentWriterDto.java @@ -2,13 +2,28 @@ import com.querydsl.core.annotations.QueryProjection; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Getter; @Getter +@Schema(name = "CommentWriterDto", description = "λŒ“κΈ€ μž‘μ„±μž 객체 Dto") public class CommentWriterDto { + + @Schema(description = "ν¬λ£¨μ—μ„œ μ‚¬μš©ν•˜λŠ” userId", example = "1") + @NotNull private final Integer id; + + @Schema(description = "λ©”μ΄μ»€μŠ€ ν”„λ‘œλ•νŠΈμ—μ„œ λ²”μš©μ μœΌλ‘œ μ‚¬μš©ν•˜λŠ” userId", example = "2") + @NotNull private final Integer orgId; + + @Schema(description = "λŒ“κΈ€ μž‘μ„±μž 이름", example = "홍길동") + @NotNull private final String name; + + @Schema(description = "λŒ“κΈ€ μž‘μ„±μž ν”„λ‘œν•„ 사진", example = "[url] ν˜•μ‹") + @NotNull private final String profileImage; @QueryProjection diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/ReplyDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/ReplyDto.java new file mode 100644 index 00000000..479c1986 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/ReplyDto.java @@ -0,0 +1,65 @@ +package org.sopt.makers.crew.main.comment.v2.dto.response; + +import java.time.LocalDateTime; +import org.sopt.makers.crew.main.entity.comment.Comment; +import com.querydsl.core.annotations.QueryProjection; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; + +@Getter +@Schema(name = "ReplyDto", description = "λŒ€λŒ“κΈ€ 객체 응닡 Dto") +public class ReplyDto { + + @Schema(description = "λŒ€λŒ“κΈ€ id", example = "1") + @NotNull + private final Integer id; + + @Schema(description = "λŒ“κΈ€ λ‚΄μš©", example = "이것은 λŒ“κΈ€ λ‚΄μš©μž…λ‹ˆλ‹€.") + @NotNull + private final String contents; + + @Schema(description = "λŒ“κΈ€ μž‘μ„±μž 객체", example = "") + @NotNull + private final CommentWriterDto user; + + @Schema(description = "λŒ“κΈ€ 생성 μ‹œμ ", example = "2024-07-31T15:30:00") + @NotNull + private final LocalDateTime createdDate; + + @Schema(description = "μ’‹μ•„μš” 갯수", example = "20") + @NotNull + private final int likeCount; + + @Schema(description = "λŒ“κΈ€ μ’‹μ•„μš” μ—¬λΆ€", example = "true") + @NotNull + private final Boolean isLiked; + + @Schema(description = "λŒ“κΈ€ μž‘μ„±μž μ—¬λΆ€", example = "true") + @NotNull + private final Boolean isWriter; + + @Schema(description = "λŒ“κΈ€ μˆœμ„œ", example = "2") + @NotNull + private final int order; + + @QueryProjection + public ReplyDto(Integer id, String contents, CommentWriterDto user, LocalDateTime createdDate, int likeCount, + Boolean isLiked, Boolean isWriter, int order) { + this.id = id; + this.contents = contents; + this.user = user; + this.createdDate = createdDate; + this.likeCount = likeCount; + this.isLiked = isLiked; + this.isWriter = isWriter; + this.order = order; + } + + public static ReplyDto of(Comment comment, boolean isLiked, boolean isWriter) { + return new ReplyDto(comment.getId(), comment.getContents(), + new CommentWriterDto(comment.getUser().getId(), comment.getUser().getOrgId(), comment.getUser().getName(), + comment.getUser().getProfileImage()), comment.getCreatedDate(), comment.getLikeCount(), + isLiked, isWriter, comment.getOrder()); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2Service.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2Service.java index 39a95535..3a917969 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2Service.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2Service.java @@ -5,9 +5,9 @@ import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2CreateCommentResponseDto; import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2GetCommentsResponseDto; import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2ReportCommentResponseDto; +import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2SwitchCommentLikeResponseDto; import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2UpdateCommentResponseDto; import org.sopt.makers.crew.main.common.exception.BadRequestException; -import org.sopt.makers.crew.main.common.exception.ForbiddenException; public interface CommentV2Service { @@ -24,5 +24,8 @@ CommentV2ReportCommentResponseDto reportComment(Integer commentId, Integer userI CommentV2UpdateCommentResponseDto updateComment(Integer commentId, String contents, Integer userId); - CommentV2GetCommentsResponseDto getComments(Integer postId, Integer page, Integer take, Integer userId); + CommentV2GetCommentsResponseDto getComments(Integer postId, Integer page, Integer take, + Integer userId); + + CommentV2SwitchCommentLikeResponseDto switchCommentToggle(Integer commentId, Integer userId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java index 6008f8b4..9f0fc855 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java @@ -1,6 +1,8 @@ package org.sopt.makers.crew.main.comment.v2.service; -import static org.sopt.makers.crew.main.internal.notification.PushNotificationEnums.*; +import static org.sopt.makers.crew.main.internal.notification.PushNotificationEnums.NEW_COMMENT_MENTION_PUSH_NOTIFICATION_TITLE; +import static org.sopt.makers.crew.main.internal.notification.PushNotificationEnums.NEW_COMMENT_PUSH_NOTIFICATION_TITLE; +import static org.sopt.makers.crew.main.internal.notification.PushNotificationEnums.PUSH_NOTIFICATION_CATEGORY; import java.util.ArrayList; import java.util.HashMap; @@ -17,15 +19,20 @@ import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2CreateCommentResponseDto; import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2GetCommentsResponseDto; import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2ReportCommentResponseDto; +import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2SwitchCommentLikeResponseDto; import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2UpdateCommentResponseDto; +import org.sopt.makers.crew.main.comment.v2.dto.response.ReplyDto; import org.sopt.makers.crew.main.common.exception.BadRequestException; +import org.sopt.makers.crew.main.common.exception.ErrorStatus; import org.sopt.makers.crew.main.common.exception.ForbiddenException; -import org.sopt.makers.crew.main.common.response.ErrorStatus; +import org.sopt.makers.crew.main.common.pagination.dto.PageMetaDto; +import org.sopt.makers.crew.main.common.pagination.dto.PageOptionsDto; import org.sopt.makers.crew.main.common.util.MentionSecretStringRemover; import org.sopt.makers.crew.main.common.util.Time; import org.sopt.makers.crew.main.entity.comment.Comment; import org.sopt.makers.crew.main.entity.comment.CommentRepository; import org.sopt.makers.crew.main.entity.comment.Comments; +import org.sopt.makers.crew.main.entity.like.Like; import org.sopt.makers.crew.main.entity.like.LikeRepository; import org.sopt.makers.crew.main.entity.like.MyLikes; import org.sopt.makers.crew.main.entity.post.Post; @@ -44,6 +51,7 @@ @RequiredArgsConstructor @Transactional(readOnly = true) public class CommentV2ServiceImpl implements CommentV2Service { + private static final int IS_REPLY_COMMENT = 1; private final PostRepository postRepository; @@ -79,7 +87,7 @@ public CommentV2CreateCommentResponseDto createComment( int order = 0; Integer parentId = 0; - boolean isReplyComment = !requestBody.isParent(); + boolean isReplyComment = !requestBody.getIsParent(); if (isReplyComment) { validateParentCommentId(requestBody); depth = 1; @@ -160,15 +168,15 @@ public CommentV2GetCommentsResponseDto getComments(Integer postId, Integer page, List comments = commentRepository.findAllByPostIdOrderByCreatedDate(postId); - MyLikes myLikes = new MyLikes(likeRepository.findAllByUserIdAndPostIdNotNull(userId)); + MyLikes myLikes = new MyLikes(likeRepository.findAllByUserIdAndCommentIdNotNull(userId)); - Map> replyMap = new HashMap<>(); + Map> replyMap = new HashMap<>(); comments.stream() .filter(comment -> !comment.isParentComment()) .forEach( comment -> replyMap.computeIfAbsent(comment.getParentId(), k -> new ArrayList<>()) - .add(CommentDto.of(comment, myLikes.isLikeComment(comment.getId()), - comment.isWriter(userId), null))); + .add(ReplyDto.of(comment, myLikes.isLikeComment(comment.getId()), + comment.isWriter(userId)))); List commentDtos = comments.stream() .filter(Comment::isParentComment) @@ -177,7 +185,9 @@ public CommentV2GetCommentsResponseDto getComments(Integer postId, Integer page, replyMap.get(comment.getId()))) .toList(); - return CommentV2GetCommentsResponseDto.of(commentDtos); + PageMetaDto pageMetaDto = new PageMetaDto(new PageOptionsDto(1, 12), 30); + + return CommentV2GetCommentsResponseDto.of(commentDtos, pageMetaDto); } /** @@ -191,20 +201,17 @@ public CommentV2GetCommentsResponseDto getComments(Integer postId, Integer page, */ @Override @Transactional - public CommentV2ReportCommentResponseDto reportComment(Integer commentId, Integer userId) - throws BadRequestException { + public CommentV2ReportCommentResponseDto reportComment(Integer commentId, Integer userId) { Comment comment = commentRepository.findByIdOrThrow(commentId); - User user = userRepository.findByIdOrThrow(userId); - Optional existingReport = reportRepository.findByCommentAndUser(comment, user); - - if (existingReport.isPresent()) { + if (reportRepository.existsByCommentIdAndUserId(commentId, userId)) { throw new BadRequestException(ErrorStatus.ALREADY_REPORTED_COMMENT.getErrorCode()); } Report report = Report.builder() + .userId(userId) .comment(comment) - .user(user) + .commentId(commentId) .build(); Report savedReport = reportRepository.save(report); @@ -248,7 +255,7 @@ public void mentionUserInComment(CommentV2MentionUserInCommentRequestDto request String pushNotificationContent = "\"" + requestBody.getContent() + "\""; String pushNotificationWeblink = pushWebUrl + "/post?id=" + post.getId(); - String[] userOrgIds = requestBody.getUserIds().stream() + String[] userOrgIds = requestBody.getOrgIds().stream() .map(Object::toString) .toArray(String[]::new); @@ -265,4 +272,30 @@ public void mentionUserInComment(CommentV2MentionUserInCommentRequestDto request pushNotificationService.sendPushNotification(pushRequestDto); } + + @Override + @Transactional + public CommentV2SwitchCommentLikeResponseDto switchCommentToggle(Integer commentId, + Integer userId) { + + Comment comment = commentRepository.findByIdOrThrow(commentId); + + boolean isLike = likeRepository.existsByUserIdAndCommentId(userId, commentId); + + if (isLike) { + likeRepository.deleteByUserIdAndCommentId(userId, commentId); + comment.decreaseLikeCount(); + return CommentV2SwitchCommentLikeResponseDto.of(false); + } + + Like like = Like.builder() + .userId(userId) + .commentId(commentId) + .build(); + + likeRepository.save(like); + comment.increaseLikeCount(); + + return CommentV2SwitchCommentLikeResponseDto.of(true); + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/advice/ControllerExceptionAdvice.java b/main/src/main/java/org/sopt/makers/crew/main/common/advice/ControllerExceptionAdvice.java index 76b05c84..0afc8a6b 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/advice/ControllerExceptionAdvice.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/advice/ControllerExceptionAdvice.java @@ -1,35 +1,119 @@ package org.sopt.makers.crew.main.common.advice; import org.sopt.makers.crew.main.common.exception.BaseException; -import org.sopt.makers.crew.main.common.response.CommonResponseDto; -import org.sopt.makers.crew.main.common.response.ErrorStatus; +import org.sopt.makers.crew.main.common.exception.ExceptionResponse; +import org.sopt.makers.crew.main.common.exception.ErrorStatus; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingPathVariableException; import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; + +import jakarta.validation.ConstraintViolationException; +import lombok.extern.slf4j.Slf4j; @RestControllerAdvice +@Slf4j public class ControllerExceptionAdvice { - @ExceptionHandler(BaseException.class) - public ResponseEntity handleGlobalException(BaseException ex) { - return ResponseEntity.status(ex.getStatusCode()) - .body(CommonResponseDto.fail(ex.getErrorCode())); - } - - @ExceptionHandler(MissingServletRequestParameterException.class) - public ResponseEntity handleMissingParameter( - MissingServletRequestParameterException ex) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .body(CommonResponseDto.fail( - ErrorStatus.VALIDATION_REQUEST_MISSING_EXCEPTION.getErrorCode())); - } - - @ExceptionHandler(IllegalArgumentException.class) - public ResponseEntity handleIllegalArgument(IllegalArgumentException ex) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .body(CommonResponseDto.fail(ex.getMessage())); - } + @ExceptionHandler(BaseException.class) + public ResponseEntity handleGlobalException(BaseException e) { + log.warn("{}", e.getMessage()); + return ResponseEntity.status(e.getStatusCode()) + .body(ExceptionResponse.fail(e.getErrorCode())); + } + + @ExceptionHandler(MissingServletRequestParameterException.class) + public ResponseEntity handleMissingParameter( + MissingServletRequestParameterException e) { + log.warn("{}", e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ExceptionResponse.fail( + ErrorStatus.VALIDATION_REQUEST_MISSING_EXCEPTION.getErrorCode())); + } + + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity handleIllegalArgument(IllegalArgumentException e) { + log.warn("{}", e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ExceptionResponse.fail( + ErrorStatus.INVALID_INPUT_VALUE.getErrorCode())); + } + + @ExceptionHandler(ConstraintViolationException.class) // @Notnull 였λ₯˜ + public ResponseEntity handleConstraintViolationException(ConstraintViolationException e) { + log.warn("{}", e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ExceptionResponse.fail( + ErrorStatus.INVALID_INPUT_VALUE.getErrorCode())); + } + + @ExceptionHandler(DataIntegrityViolationException.class) // null value 였λ₯˜ + public ResponseEntity handleDataIntegrityViolationException(DataIntegrityViolationException e) { + log.warn("{}", e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ExceptionResponse.fail( + ErrorStatus.INVALID_INPUT_VALUE.getErrorCode())); + } + + @ExceptionHandler(HttpMessageNotReadableException.class) // null value 였λ₯˜ + public ResponseEntity handleHttpMessageNotReadableException(HttpMessageNotReadableException e) { + log.warn("{}", e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ExceptionResponse.fail( + ErrorStatus.INVALID_INPUT_VALUE.getErrorCode())); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + log.warn("{}", e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ExceptionResponse.fail( + ErrorStatus.INVALID_INPUT_VALUE.getErrorCode())); + } + + /** + * path variable errors + */ + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + public ResponseEntity handleMethodArgumentTypeMismatchException( + MethodArgumentTypeMismatchException e) { + log.warn("{}", e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ExceptionResponse.fail( + ErrorStatus.INVALID_INPUT_VALUE.getErrorCode())); + } + + @ExceptionHandler(MissingPathVariableException.class) + public ResponseEntity handleMissingPathVariableException( + MissingPathVariableException e) { + log.warn("{}", e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ExceptionResponse.fail( + ErrorStatus.INVALID_INPUT_VALUE.getErrorCode())); + } + + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + public ResponseEntity handleHttpRequestMethodNotSupportedException( + HttpRequestMethodNotSupportedException e) { + log.warn("{}", e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ExceptionResponse.fail( + ErrorStatus.INVALID_INPUT_VALUE.getErrorCode())); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleException(Exception e) { + log.error("{}", e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ExceptionResponse.fail( + ErrorStatus.INTERNAL_SERVER_ERROR.getErrorCode())); + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/config/FeignClientConfig.java b/main/src/main/java/org/sopt/makers/crew/main/common/config/FeignClientConfig.java deleted file mode 100644 index 963ec155..00000000 --- a/main/src/main/java/org/sopt/makers/crew/main/common/config/FeignClientConfig.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.sopt.makers.crew.main.common.config; - -import org.springframework.cloud.openfeign.EnableFeignClients; -import org.springframework.context.annotation.Configuration; - -@EnableFeignClients(basePackages = "org.sopt.makers.crew.main.internal.notification") -@Configuration -public class FeignClientConfig { - -} diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/config/SecurityConfig.java b/main/src/main/java/org/sopt/makers/crew/main/common/config/SecurityConfig.java index c1b9172e..b3121b7d 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/config/SecurityConfig.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/config/SecurityConfig.java @@ -3,9 +3,9 @@ import java.util.Arrays; import java.util.stream.Stream; import lombok.RequiredArgsConstructor; -import org.sopt.makers.crew.main.common.config.jwt.JwtAuthenticationEntryPoint; -import org.sopt.makers.crew.main.common.config.jwt.JwtAuthenticationFilter; -import org.sopt.makers.crew.main.common.config.jwt.JwtTokenProvider; +import org.sopt.makers.crew.main.common.jwt.JwtAuthenticationEntryPoint; +import org.sopt.makers.crew.main.common.jwt.JwtAuthenticationFilter; +import org.sopt.makers.crew.main.common.jwt.JwtTokenProvider; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; @@ -42,7 +42,9 @@ public class SecurityConfig { private static final String[] AUTH_WHITELIST = { "/health", "/health/v2", - "/meeting/v2/org-user/**" + "/meeting/v2/org-user/**", + "/auth/v2", + "/auth/v2/**" }; @Bean @@ -132,7 +134,7 @@ CorsConfigurationSource corsConfigurationSource() { configuration.setAllowedOrigins( Arrays.asList("https://playground.sopt.org/", "http://localhost:3000/", "https://sopt-internal-dev.pages.dev/", "https://crew.api.dev.sopt.org", "https://crew.api.prod.sopt.org")); - configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PATCH", "DELETE", "OPTIONS")); + configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PATCH", "DELETE", "PUT", "OPTIONS")); configuration.addAllowedHeader("*"); configuration.setAllowCredentials(false); diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/config/jwt/JwtAuthenticationEntryPoint.java b/main/src/main/java/org/sopt/makers/crew/main/common/config/jwt/JwtAuthenticationEntryPoint.java deleted file mode 100644 index fdbb56c8..00000000 --- a/main/src/main/java/org/sopt/makers/crew/main/common/config/jwt/JwtAuthenticationEntryPoint.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.sopt.makers.crew.main.common.config.jwt; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import java.io.IOException; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.web.AuthenticationEntryPoint; -import org.springframework.stereotype.Component; - -@Component -public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { - - @Override - public void commence(HttpServletRequest request, HttpServletResponse response, - AuthenticationException authException) throws IOException { - setResponse(response); - } - - - public void setResponse(HttpServletResponse response) throws IOException { - response.setContentType("application/json;charset=UTF-8"); - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - } - -} diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/config/jwt/JwtAuthenticationFilter.java b/main/src/main/java/org/sopt/makers/crew/main/common/config/jwt/JwtAuthenticationFilter.java deleted file mode 100644 index bd28aee6..00000000 --- a/main/src/main/java/org/sopt/makers/crew/main/common/config/jwt/JwtAuthenticationFilter.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.sopt.makers.crew.main.common.config.jwt; - -import static org.sopt.makers.crew.main.common.config.jwt.JwtExceptionType.VALID_JWT_TOKEN; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import java.io.IOException; -import lombok.RequiredArgsConstructor; -import org.sopt.makers.crew.main.common.response.ErrorStatus; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; -import org.springframework.stereotype.Component; -import org.springframework.web.filter.OncePerRequestFilter; - -@Component -@RequiredArgsConstructor -public class JwtAuthenticationFilter extends OncePerRequestFilter { - - private final JwtTokenProvider jwtTokenProvider; - private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, - FilterChain chain) throws ServletException, IOException { - String accessToken = jwtTokenProvider.resolveToken(request); - - if (accessToken != null) { - // 토큰 검증 - if (jwtTokenProvider.validateToken(accessToken) - == VALID_JWT_TOKEN) { // 토큰이 μ‘΄μž¬ν•˜κ³  μœ νš¨ν•œ 토큰일 λ•Œλ§Œ - Integer userId = jwtTokenProvider.getAccessTokenPayload(accessToken); - UserAuthentication authentication = new UserAuthentication(userId, null, - null); //μ‚¬μš©μž 객체 생성 - authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails( - request)); // request μ •λ³΄λ‘œ μ‚¬μš©μž 객체 λ””ν…ŒμΌ μ„€μ • - SecurityContextHolder.getContext().setAuthentication(authentication); - } else { - jwtAuthenticationEntryPoint.commence(request, response, - new AuthenticationException(ErrorStatus.UNAUTHORIZED_TOKEN.getErrorCode()) { - }); - return; - } - } - chain.doFilter(request, response); - } -} diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/config/jwt/JwtExceptionType.java b/main/src/main/java/org/sopt/makers/crew/main/common/config/jwt/JwtExceptionType.java deleted file mode 100644 index 043887ea..00000000 --- a/main/src/main/java/org/sopt/makers/crew/main/common/config/jwt/JwtExceptionType.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.sopt.makers.crew.main.common.config.jwt; - -public enum JwtExceptionType { - VALID_JWT_TOKEN, // μœ νš¨ν•œ JWT - INVALID_JWT_SIGNATURE, // μœ νš¨ν•˜μ§€ μ•Šμ€ μ„œλͺ… - INVALID_JWT_TOKEN, // μœ νš¨ν•˜μ§€ μ•Šμ€ 토큰 - EXPIRED_JWT_TOKEN, // 만료된 토큰 - UNSUPPORTED_JWT_TOKEN, // μ§€μ›ν•˜μ§€ μ•ŠλŠ” ν˜•μ‹μ˜ 토큰 - EMPTY_JWT // 빈 JWT -} diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/config/jwt/JwtTokenProvider.java b/main/src/main/java/org/sopt/makers/crew/main/common/config/jwt/JwtTokenProvider.java deleted file mode 100644 index 837fe9ad..00000000 --- a/main/src/main/java/org/sopt/makers/crew/main/common/config/jwt/JwtTokenProvider.java +++ /dev/null @@ -1,95 +0,0 @@ -package org.sopt.makers.crew.main.common.config.jwt; - -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.ExpiredJwtException; -import io.jsonwebtoken.Header; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.MalformedJwtException; -import io.jsonwebtoken.SignatureAlgorithm; -import io.jsonwebtoken.UnsupportedJwtException; -import jakarta.servlet.http.HttpServletRequest; -import java.nio.charset.StandardCharsets; -import java.security.Key; -import java.util.Date; -import javax.crypto.spec.SecretKeySpec; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.security.core.Authentication; -import org.springframework.stereotype.Component; - -@Slf4j -@Component -@RequiredArgsConstructor -public class JwtTokenProvider { - - @Value("${jwt.secret}") - private String secretKey; - - @Value("${jwt.access-token.expire-length}") - private Long accessTokenExpireLength; - - private static final String AUTHORIZATION_HEADER = "Authorization"; - - public String generateAccessToken(Authentication authentication) { - Date now = new Date(); - Date expiration = new Date(now.getTime() + accessTokenExpireLength); - - final Claims claims = Jwts.claims() - .setIssuedAt(now) - .setExpiration(expiration); - - claims.put("id", authentication.getPrincipal()); - - return Jwts.builder() - .setHeaderParam(Header.TYPE, Header.JWT_TYPE) - .setClaims(claims) - .signWith(getSignKey(), SignatureAlgorithm.HS256) - .compact(); - } - - public Integer getAccessTokenPayload(String token) { - return Integer.parseInt( - Jwts.parserBuilder().setSigningKey(getSignKey()).build().parseClaimsJws(token) - .getBody().get("id").toString()); - } - - public String resolveToken(HttpServletRequest request) { - - String header = request.getHeader(AUTHORIZATION_HEADER); - - if (header == null || !header.startsWith("Bearer ")) { - return null; - } else { - return header.split(" ")[1]; - } - } - - public JwtExceptionType validateToken(String token) { - try { - Jwts.parserBuilder().setSigningKey(getSignKey()).build().parseClaimsJws(token) - .getBody(); - return JwtExceptionType.VALID_JWT_TOKEN; - } catch (io.jsonwebtoken.security.SignatureException exception) { - log.error("잘λͺ»λœ JWT μ„œλͺ…을 가진 ν† ν°μž…λ‹ˆλ‹€."); - return JwtExceptionType.INVALID_JWT_SIGNATURE; - } catch (MalformedJwtException exception) { - log.error("잘λͺ»λœ JWT ν† ν°μž…λ‹ˆλ‹€."); - return JwtExceptionType.INVALID_JWT_TOKEN; - } catch (ExpiredJwtException exception) { - log.error("만료된 JWT ν† ν°μž…λ‹ˆλ‹€."); - return JwtExceptionType.EXPIRED_JWT_TOKEN; - } catch (UnsupportedJwtException exception) { - log.error("μ§€μ›ν•˜μ§€ μ•ŠλŠ” JWT ν† ν°μž…λ‹ˆλ‹€."); - return JwtExceptionType.UNSUPPORTED_JWT_TOKEN; - } catch (IllegalArgumentException exception) { - log.error("JWT Claimsκ°€ λΉ„μ–΄μžˆμŠ΅λ‹ˆλ‹€."); - return JwtExceptionType.EMPTY_JWT; - } - } - - private Key getSignKey() { - byte[] keyBytes = secretKey.getBytes(StandardCharsets.UTF_8); - return new SecretKeySpec(keyBytes, "HmacSHA256"); - } -} diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/constant/CrewConst.java b/main/src/main/java/org/sopt/makers/crew/main/common/constant/CrewConst.java index 895501fb..67805790 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/constant/CrewConst.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/constant/CrewConst.java @@ -5,11 +5,15 @@ public abstract class CrewConst { /** * 맀 기수 μ‹œμž‘ν•˜κΈ° 전에 μˆ˜μ • ν•„μš” * */ - public final static Integer ACTIVE_GENERATION = 34; + public static final Integer ACTIVE_GENERATION = 34; - public final static String DAY_START_TIME = " 00:00:00"; - public final static String DAY_END_TIME = " 23:59:59"; + public static final String DAY_START_TIME = " 00:00:00"; + public static final String DAY_END_TIME = " 23:59:59"; + + public static final String DAY_FORMAT = "yyyy.MM.dd"; + public static final String DAY_TIME_FORMAT = "yyyy.MM.dd HH:mm:ss"; + + public static final String ORDER_ASC = "asc"; + public static final String ORDER_DESC = "desc"; - public final static String DAY_FORMAT = "yyyy.MM.dd"; - public final static String DAY_TIME_FORMAT = "yyyy.MM.dd HH:mm:ss"; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/dto/MeetingCreatorDto.java b/main/src/main/java/org/sopt/makers/crew/main/common/dto/MeetingCreatorDto.java new file mode 100644 index 00000000..d1fcf8b5 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/common/dto/MeetingCreatorDto.java @@ -0,0 +1,45 @@ +package org.sopt.makers.crew.main.common.dto; + +import java.util.List; + +import org.sopt.makers.crew.main.entity.user.User; +import org.sopt.makers.crew.main.entity.user.vo.UserActivityVO; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +@Schema(name = "MeetingCreatorDto", description = "λͺ¨μž„ κ°œμ„€μž Dto") +public class MeetingCreatorDto { + @Schema(description = "λͺ¨μž„μž₯ id, ν¬λ£¨μ—μ„œ μ‚¬μš©ν•˜λŠ” userId", example = "1") + @NotNull + private final Integer id; + + @Schema(description = "λͺ¨μž„μž₯ 이름", example = "홍길동") + @NotNull + private final String name; + + @Schema(description = "λͺ¨μž„μž₯ org id, λ©”μ΄μ»€μŠ€ ν”„λ‘œλ•νŠΈμ—μ„œ λ²”μš©μ μœΌλ‘œ μ‚¬μš©ν•˜λŠ” userId", example = "1") + @NotNull + private final Integer orgId; + + @Schema(description = "λͺ¨μž„μž₯ ν”„λ‘œν•„ 사진", example = "[url] ν˜•μ‹") + private final String profileImage; + + @Schema(description = "ν™œλ™ 기수", example = "") + @NotNull + private final List activities; + + @Schema(description = "μ „ν™”λ²ˆν˜Έ", example = "01094726796") + @NotNull + private final String phone; + + public static MeetingCreatorDto of(User user) { + return new MeetingCreatorDto(user.getId(), user.getName(), user.getOrgId(), user.getProfileImage(), + user.getActivities(), user.getPhone()); + } + +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/dto/MeetingResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/common/dto/MeetingResponseDto.java new file mode 100644 index 00000000..8477102d --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/common/dto/MeetingResponseDto.java @@ -0,0 +1,114 @@ +package org.sopt.makers.crew.main.common.dto; + +import java.time.LocalDateTime; + +import java.util.List; + +import org.sopt.makers.crew.main.common.constant.CrewConst; +import org.sopt.makers.crew.main.entity.meeting.Meeting; +import org.sopt.makers.crew.main.entity.meeting.enums.MeetingCategory; +import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart; +import org.sopt.makers.crew.main.entity.meeting.vo.ImageUrlVO; +import org.sopt.makers.crew.main.entity.user.User; + +import com.querydsl.core.annotations.QueryProjection; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Schema(name = "MeetingResponseDto", description = "λͺ¨μž„ Dto") +@RequiredArgsConstructor +@Getter +public class MeetingResponseDto { + + @Schema(description = "λͺ¨μž„ id", example = "1") + @NotNull + private final Integer id; + @Schema(description = "λͺ¨μž„ 제λͺ©", example = "λͺ¨μž„ 제λͺ©μž…λ‹ˆλ‹€") + @NotNull + String title; + @Schema(description = "λŒ€μƒ 기수", example = "33") + @NotNull + private final Integer targetActiveGeneration; + @Schema(example = "[\n" + + " \"ANDROID\",\n" + + " \"IOS\"\n" + + " ]", description = "λŒ€μƒ 파트 λͺ©λ‘") + @NotNull + private final MeetingJoinablePart[] joinableParts; + @Schema(example = "μŠ€ν„°λ””", description = "λͺ¨μž„ μΉ΄ν…Œκ³ λ¦¬") + @NotNull + private final String category; + @Schema(example = "false", description = "ν™œλ™κΈ°μˆ˜λ§Œ 지원 κ°€λŠ₯ μ—¬λΆ€") + @NotNull + private final Boolean canJoinOnlyActiveGeneration; + @Schema(example = "2", description = "λͺ¨μž„ ν™œλ™ μƒνƒœ") + @NotNull + private final Integer status; + /** + * 썸넀일 이미지 + * + * @apiNote μ—¬λŸ¬κ°œμ—¬λ„ 첫번째 μ΄λ―Έμ§€λ§Œ μ‚¬μš© + */ + @Schema(description = "λͺ¨μž„ 사진", example = "[url] ν˜•μ‹") + @NotNull + private final List imageURL; + @Schema(example = "false", description = "λ©˜ν†  ν•„μš” μ—¬λΆ€") + @NotNull + private final Boolean isMentorNeeded; + @Schema(description = "λͺ¨μž„ ν™œλ™ μ‹œμž‘μΌ", example = "2024-07-31T15:30:00") + @NotNull + private final LocalDateTime mStartDate; + @Schema(description = "λͺ¨μž„ ν™œλ™ μ’…λ£ŒμΌ", example = "2024-08-25T15:30:00") + @NotNull + private final LocalDateTime mEndDate; + @Schema(description = "λͺ¨μ§‘ 인원", example = "20") + @NotNull + private final int capacity; + @Schema(description = "λͺ¨μž„μž₯ 정보", example = "") + @NotNull + private final MeetingCreatorDto user; + @Schema(description = "μ‹ μ²­ 수", example = "7") + @NotNull + private final int appliedCount; + + @QueryProjection + public MeetingResponseDto(Integer id, String title, Integer targetActiveGeneration, + @NotNull MeetingJoinablePart[] joinableParts, MeetingCategory category, Boolean canJoinOnlyActiveGeneration, + Integer status, + List imageURL, Boolean isMentorNeeded, LocalDateTime mStartDate, LocalDateTime mEndDate, + int capacity, + MeetingCreatorDto user, int appliedCount) { + + boolean processedCanJoinOnlyActiveGeneration = canJoinOnlyActiveGeneration + && (CrewConst.ACTIVE_GENERATION.equals(targetActiveGeneration)); + + this.id = id; + this.title = title; + this.targetActiveGeneration = targetActiveGeneration; + this.joinableParts = joinableParts; + this.category = category.getValue(); + this.canJoinOnlyActiveGeneration = processedCanJoinOnlyActiveGeneration; + this.status = status; + this.imageURL = imageURL; + this.isMentorNeeded = isMentorNeeded; + this.mStartDate = mStartDate; + this.mEndDate = mEndDate; + this.capacity = capacity; + this.user = user; + this.appliedCount = appliedCount; + } + + public static MeetingResponseDto of(Meeting meeting, User meetingCreator, int appliedCount, LocalDateTime now){ + MeetingCreatorDto creatorDto = MeetingCreatorDto.of(meetingCreator); + boolean canJoinOnlyActiveGeneration = meeting.getTargetActiveGeneration() == CrewConst.ACTIVE_GENERATION + && meeting.getCanJoinOnlyActiveGeneration(); + + return new MeetingResponseDto(meeting.getId(), meeting.getTitle(), + meeting.getTargetActiveGeneration(), meeting.getJoinableParts(), meeting.getCategory(), + canJoinOnlyActiveGeneration, meeting.getMeetingStatus(now), meeting.getImageURL(), + meeting.getIsMentorNeeded(), meeting.getMStartDate(), meeting.getMEndDate(), meeting.getCapacity(), creatorDto, appliedCount); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/dto/TempResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/common/dto/TempResponseDto.java new file mode 100644 index 00000000..7f24aa92 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/common/dto/TempResponseDto.java @@ -0,0 +1,20 @@ +package org.sopt.makers.crew.main.common.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Schema(name = "TempResponseDto", description = "μž„μ‹œ 응닡 Dto") +public class TempResponseDto { + + @Schema(description = "μž„μ‹œ 응닡", example = "") + @NotNull + private T data; + + public static TempResponseDto of(T data) { + return new TempResponseDto(data); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/response/ErrorStatus.java b/main/src/main/java/org/sopt/makers/crew/main/common/exception/ErrorStatus.java similarity index 69% rename from main/src/main/java/org/sopt/makers/crew/main/common/response/ErrorStatus.java rename to main/src/main/java/org/sopt/makers/crew/main/common/exception/ErrorStatus.java index 130043d8..a7665cee 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/response/ErrorStatus.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/exception/ErrorStatus.java @@ -1,4 +1,4 @@ -package org.sopt.makers.crew.main.common.response; +package org.sopt.makers.crew.main.common.exception; import lombok.AccessLevel; import lombok.Getter; @@ -17,22 +17,28 @@ public enum ErrorStatus { */ VALIDATION_EXCEPTION("CF-001"), VALIDATION_REQUEST_MISSING_EXCEPTION("μš”μ²­κ°’μ΄ μž…λ ₯λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€."), + INVALID_INPUT_VALUE("μš”μ²­κ°’μ΄ μ˜¬λ°”λ₯΄μ§€ μ•ŠμŠ΅λ‹ˆλ‹€."), + INVALID_INPUT_VALUE_FILTER("μš”μ²­κ°’ λ˜λŠ” 토큰이 μ˜¬λ°”λ₯΄μ§€ μ•ŠμŠ΅λ‹ˆλ‹€."), NOT_FOUND_MEETING("λͺ¨μž„이 μ—†μŠ΅λ‹ˆλ‹€."), NOT_FOUND_POST("μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” κ²Œμ‹œκΈ€μž…λ‹ˆλ‹€."), NOT_FOUND_COMMENT("μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” λŒ“κΈ€μž…λ‹ˆλ‹€."), FULL_MEETING_CAPACITY("정원이 꽉 μ°ΌμŠ΅λ‹ˆλ‹€."), ALREADY_APPLIED_MEETING("이미 μ§€μ›ν•œ λͺ¨μž„μž…λ‹ˆλ‹€."), ALREADY_REPORTED_COMMENT("이미 μ‹ κ³ ν•œ λŒ“κΈ€μž…λ‹ˆλ‹€."), + ALREADY_REPORTED_POST("이미 μ‹ κ³ ν•œ κ²Œμ‹œκΈ€μž…λ‹ˆλ‹€."), NOT_IN_APPLY_PERIOD("λͺ¨μž„ 지원 기간이 μ•„λ‹™λ‹ˆλ‹€."), MISSING_GENERATION_PART("λ‚΄ ν”„λ‘œν•„μ—μ„œ 기수/파트 정보λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."), NOT_ACTIVE_GENERATION("ν™œλ™ κΈ°μˆ˜κ°€ μ•„λ‹™λ‹ˆλ‹€."), NOT_TARGET_PART("지원 κ°€λŠ₯ν•œ νŒŒνŠΈκ°€ μ•„λ‹™λ‹ˆλ‹€."), NOT_FOUND_APPLY("μ‹ μ²­μƒνƒœκ°€ μ•„λ‹Œ λͺ¨μž„μž…λ‹ˆλ‹€."), + ALREADY_PROCESSED_APPLY("이미 ν•΄λ‹Ή μƒνƒœλ‘œ 처리된 μ‹ μ²­ μ •λ³΄μž…λ‹ˆλ‹€."), + MAX_IMAGE_UPLOAD_EXCEEDED("μ΄λ―Έμ§€λŠ” μ΅œλŒ€ 10κ°œκΉŒμ§€λ§Œ μ—…λ‘œλ“œ κ°€λŠ₯ν•©λ‹ˆλ‹€."), /** * 401 UNAUTHORIZED */ UNAUTHORIZED_TOKEN("μœ νš¨ν•˜μ§€ μ•Šμ€ ν† ν°μž…λ‹ˆλ‹€."), + UNAUTHORIZED_USER("μ‘΄μž¬ν•˜μ§€ μ•Šκ±°λ‚˜ μœ νš¨ν•˜μ§€ μ•Šμ€ μœ μ €μž…λ‹ˆλ‹€."), /** * 403 FORBIDDEN @@ -43,6 +49,8 @@ public enum ErrorStatus { * 500 SERVER_ERROR */ NOTIFICATION_SERVER_ERROR("μ•Œλ¦Ό μ„œλ²„μ— μ—λŸ¬κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€."), + CSV_ERROR("csv 처리 과정에 μ—λŸ¬κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€."), + S3_STORAGE_ERROR("s3 μŠ€ν† λ¦¬μ§€μ— μ—λŸ¬κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€."), INTERNAL_SERVER_ERROR("μ˜ˆμƒμΉ˜ λͺ»ν•œ μ„œλ²„ μ—λŸ¬κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€."); private final String errorCode; diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/response/CommonResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/common/exception/ExceptionResponse.java similarity index 57% rename from main/src/main/java/org/sopt/makers/crew/main/common/response/CommonResponseDto.java rename to main/src/main/java/org/sopt/makers/crew/main/common/exception/ExceptionResponse.java index eca1b6f2..98968b3c 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/response/CommonResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/exception/ExceptionResponse.java @@ -1,4 +1,4 @@ -package org.sopt.makers.crew.main.common.response; +package org.sopt.makers.crew.main.common.exception; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Builder; @@ -7,12 +7,12 @@ @Builder @Getter @JsonInclude(JsonInclude.Include.NON_NULL) -public class CommonResponseDto { +public class ExceptionResponse { private final String errorCode; - public static CommonResponseDto fail(String errorCode) { - return CommonResponseDto.builder() + public static ExceptionResponse fail(String errorCode) { + return ExceptionResponse.builder() .errorCode(errorCode) .build(); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtAuthenticationEntryPoint.java b/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtAuthenticationEntryPoint.java new file mode 100644 index 00000000..ec62d7a8 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtAuthenticationEntryPoint.java @@ -0,0 +1,36 @@ +package org.sopt.makers.crew.main.common.jwt; + +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.*; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; + +import org.sopt.makers.crew.main.common.exception.ExceptionResponse; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.databind.ObjectMapper; + +@Component +public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, + AuthenticationException authException) throws IOException { + setResponse(response); + } + + public void setResponse(HttpServletResponse response) throws IOException { + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + + ObjectMapper mapper = new ObjectMapper(); + String jsonResponse = mapper.writeValueAsString(ExceptionResponse.fail(INVALID_INPUT_VALUE_FILTER.getErrorCode())); + + response.getWriter().write(jsonResponse); + } + +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtAuthenticationFilter.java b/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtAuthenticationFilter.java new file mode 100644 index 00000000..0f1d45a2 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtAuthenticationFilter.java @@ -0,0 +1,50 @@ +package org.sopt.makers.crew.main.common.jwt; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; + +import lombok.RequiredArgsConstructor; + +import org.sopt.makers.crew.main.common.exception.ErrorStatus; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +@Component +@RequiredArgsConstructor +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private final JwtTokenProvider jwtTokenProvider; + private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain chain) throws ServletException, IOException { + String accessToken = jwtTokenProvider.resolveToken(request); + + if (accessToken != null) { + // 토큰 검증 + if (jwtTokenProvider.validateToken(accessToken) + == JwtExceptionType.VALID_JWT_TOKEN) { // 토큰이 μ‘΄μž¬ν•˜κ³  μœ νš¨ν•œ 토큰일 λ•Œλ§Œ + Integer userId = jwtTokenProvider.getAccessTokenPayload(accessToken); + UserAuthentication authentication = new UserAuthentication(userId, null, + null); //μ‚¬μš©μž 객체 생성 + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails( + request)); // request μ •λ³΄λ‘œ μ‚¬μš©μž 객체 λ””ν…ŒμΌ μ„€μ • + SecurityContextHolder.getContext().setAuthentication(authentication); + } else { + jwtAuthenticationEntryPoint.commence(request, response, + new AuthenticationException(ErrorStatus.UNAUTHORIZED_TOKEN.getErrorCode()) { + }); + return; + } + } + chain.doFilter(request, response); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtExceptionType.java b/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtExceptionType.java new file mode 100644 index 00000000..3834205a --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtExceptionType.java @@ -0,0 +1,10 @@ +package org.sopt.makers.crew.main.common.jwt; + +public enum JwtExceptionType { + VALID_JWT_TOKEN, // μœ νš¨ν•œ JWT + INVALID_JWT_SIGNATURE, // μœ νš¨ν•˜μ§€ μ•Šμ€ μ„œλͺ… + INVALID_JWT_TOKEN, // μœ νš¨ν•˜μ§€ μ•Šμ€ 토큰 + EXPIRED_JWT_TOKEN, // 만료된 토큰 + UNSUPPORTED_JWT_TOKEN, // μ§€μ›ν•˜μ§€ μ•ŠλŠ” ν˜•μ‹μ˜ 토큰 + EMPTY_JWT // 빈 JWT +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtTokenProvider.java b/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtTokenProvider.java new file mode 100644 index 00000000..1ed5b3d6 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtTokenProvider.java @@ -0,0 +1,99 @@ +package org.sopt.makers.crew.main.common.jwt; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Header; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.UnsupportedJwtException; +import jakarta.servlet.http.HttpServletRequest; + +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.util.Date; + +import javax.crypto.spec.SecretKeySpec; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class JwtTokenProvider { + + @Value("${jwt.secret}") + private String secretKey; + + @Value("${jwt.access-token.expire-length}") + private Long accessTokenExpireLength; + + private static final String AUTHORIZATION_HEADER = "Authorization"; + + public String generateAccessToken(Integer userId, String userName) { + Date now = new Date(); + Date expiration = new Date(now.getTime() + accessTokenExpireLength); + + final Claims claims = Jwts.claims() + .setIssuedAt(now) + .setExpiration(expiration); + + claims.put("id", userId); + claims.put("name", userName); + + return Jwts.builder() + .setHeaderParam(Header.TYPE, Header.JWT_TYPE) + .setClaims(claims) + .signWith(getSignKey(), SignatureAlgorithm.HS256) + .compact(); + } + + public Integer getAccessTokenPayload(String token) { + return Integer.parseInt( + Jwts.parserBuilder().setSigningKey(getSignKey()).build().parseClaimsJws(token) + .getBody().get("id").toString()); + } + + public String resolveToken(HttpServletRequest request) { + + String header = request.getHeader(AUTHORIZATION_HEADER); + + if (header == null || !header.startsWith("Bearer ")) { + return null; + } else { + return header.split(" ")[1]; + } + } + + public JwtExceptionType validateToken(String token) { + try { + Jwts.parserBuilder().setSigningKey(getSignKey()).build().parseClaimsJws(token) + .getBody(); + return JwtExceptionType.VALID_JWT_TOKEN; + } catch (io.jsonwebtoken.security.SignatureException exception) { + log.error("잘λͺ»λœ JWT μ„œλͺ…을 가진 ν† ν°μž…λ‹ˆλ‹€."); + return JwtExceptionType.INVALID_JWT_SIGNATURE; + } catch (MalformedJwtException exception) { + log.error("잘λͺ»λœ JWT ν† ν°μž…λ‹ˆλ‹€."); + return JwtExceptionType.INVALID_JWT_TOKEN; + } catch (ExpiredJwtException exception) { + log.error("만료된 JWT ν† ν°μž…λ‹ˆλ‹€."); + return JwtExceptionType.EXPIRED_JWT_TOKEN; + } catch (UnsupportedJwtException exception) { + log.error("μ§€μ›ν•˜μ§€ μ•ŠλŠ” JWT ν† ν°μž…λ‹ˆλ‹€."); + return JwtExceptionType.UNSUPPORTED_JWT_TOKEN; + } catch (IllegalArgumentException exception) { + log.error("JWT Claimsκ°€ λΉ„μ–΄μžˆμŠ΅λ‹ˆλ‹€."); + return JwtExceptionType.EMPTY_JWT; + } + } + + private Key getSignKey() { + byte[] keyBytes = secretKey.getBytes(StandardCharsets.UTF_8); + return new SecretKeySpec(keyBytes, "HmacSHA256"); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/config/jwt/UserAuthentication.java b/main/src/main/java/org/sopt/makers/crew/main/common/jwt/UserAuthentication.java similarity index 56% rename from main/src/main/java/org/sopt/makers/crew/main/common/config/jwt/UserAuthentication.java rename to main/src/main/java/org/sopt/makers/crew/main/common/jwt/UserAuthentication.java index 1c6cea33..9a2dda09 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/config/jwt/UserAuthentication.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/jwt/UserAuthentication.java @@ -1,16 +1,16 @@ -package org.sopt.makers.crew.main.common.config.jwt; - +package org.sopt.makers.crew.main.common.jwt; import java.util.Collection; + import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.GrantedAuthority; // UsernamePasswordAuthenticationToken: μ‚¬μš©μžμ˜ 인증 정보 μ €μž₯ν•˜κ³  전달 public class UserAuthentication extends UsernamePasswordAuthenticationToken { - // μ‚¬μš©μž 인증 객체 생성 - public UserAuthentication(Object principal, Object credentials, - Collection authorities) { - super(principal, credentials, authorities); - } + // μ‚¬μš©μž 인증 객체 생성 + public UserAuthentication(Object principal, Object credentials, + Collection authorities) { + super(principal, credentials, authorities); + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/pagination/dto/PageMetaDto.java b/main/src/main/java/org/sopt/makers/crew/main/common/pagination/dto/PageMetaDto.java index c987a0c9..ab8bd34e 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/pagination/dto/PageMetaDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/pagination/dto/PageMetaDto.java @@ -1,35 +1,42 @@ package org.sopt.makers.crew.main.common.pagination.dto; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Getter; @Getter public class PageMetaDto { - @Schema(description = "νŽ˜μ΄μ§€ μœ„μΉ˜") - private int page; - - @Schema(description = "κ°€μ Έμ˜¬ 데이터 개수") - private int take; - - @Schema(description = "응닡 데이터 개수") - private int itemCount; - - @Schema(description = "총 νŽ˜μ΄μ§€ 수") - private int pageCount; - - @Schema(description = "이전 νŽ˜μ΄μ§€κ°€ μžˆλŠ”μ§€ 유무") - private boolean hasPreviousPage; - - @Schema(description = "λ‹€μŒ νŽ˜μ΄μ§€κ°€ μžˆλŠ”μ§€ 유무") - private boolean hasNextPage; - - public PageMetaDto(PageOptionsDto pageOptionsDto, int itemCount) { - this.page = pageOptionsDto.getPage(); - this.take = pageOptionsDto.getTake(); - this.itemCount = itemCount; - this.pageCount = (int) Math.ceil((double) this.itemCount / this.take); - this.hasPreviousPage = this.page > 1; - this.hasNextPage = this.page < this.pageCount; - } + @Schema(description = "νŽ˜μ΄μ§€ μœ„μΉ˜") + @NotNull + private int page; + + @Schema(description = "κ°€μ Έμ˜¬ 데이터 개수") + @NotNull + private int take; + + @Schema(description = "응닡 데이터 개수") + @NotNull + private int itemCount; + + @Schema(description = "총 νŽ˜μ΄μ§€ 수") + @NotNull + private int pageCount; + + @Schema(description = "이전 νŽ˜μ΄μ§€κ°€ μžˆλŠ”μ§€ 유무") + @NotNull + private boolean hasPreviousPage; + + @Schema(description = "λ‹€μŒ νŽ˜μ΄μ§€κ°€ μžˆλŠ”μ§€ 유무") + @NotNull + private boolean hasNextPage; + + public PageMetaDto(PageOptionsDto pageOptionsDto, int itemCount) { + this.page = pageOptionsDto.getPage(); + this.take = pageOptionsDto.getTake(); + this.itemCount = itemCount; + this.pageCount = (int)Math.ceil((double)this.itemCount / this.take); + this.hasPreviousPage = this.page > 1; + this.hasNextPage = this.page < this.pageCount; + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/util/CustomPageable.java b/main/src/main/java/org/sopt/makers/crew/main/common/util/CustomPageable.java new file mode 100644 index 00000000..dd61075b --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/common/util/CustomPageable.java @@ -0,0 +1,69 @@ +package org.sopt.makers.crew.main.common.util; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; + +public class CustomPageable implements Pageable { + + private final int page; + private final int size; + private final Sort sort; + + public CustomPageable(int page, Sort sort) { + this.page = page; + this.sort = sort; + this.size = calculateSize(page); + } + + private int calculateSize(int page) { + // 첫 번째 νŽ˜μ΄μ§€λŠ” 11개, κ·Έ μ΄ν›„λΆ€ν„°λŠ” 12개 + return page == 0 ? 11 : 12; + } + + @Override + public int getPageNumber() { + return this.page; + } + + @Override + public int getPageSize() { + return this.size; + } + + @Override + public long getOffset() { + // μ˜€ν”„μ…‹ 계산 + return page == 0 ? 0 : 11 + (page - 1) * 12; + } + + @Override + public Sort getSort() { + return this.sort; + } + + @Override + public Pageable next() { + return new CustomPageable(this.page + 1, this.sort); + } + + @Override + public Pageable previousOrFirst() { + return this.page == 0 ? this : new CustomPageable(this.page - 1, this.sort); + } + + @Override + public Pageable first() { + return new CustomPageable(0, this.sort); + } + + @Override + public boolean hasPrevious() { + return this.page > 0; + } + + @Override + public Pageable withPage(int pageNumber) { + // μƒˆλ‘œμš΄ νŽ˜μ΄μ§€ 번호둜 CustomPageable 생성 + return new CustomPageable(pageNumber, this.sort); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/Advertisement.java b/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/Advertisement.java new file mode 100644 index 00000000..3f5183a6 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/Advertisement.java @@ -0,0 +1,70 @@ +package org.sopt.makers.crew.main.entity.advertisement; + +import static jakarta.persistence.GenerationType.*; + +import java.time.LocalDateTime; + +import org.sopt.makers.crew.main.entity.advertisement.enums.AdvertisementCategory; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import jakarta.persistence.Entity; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@EntityListeners(AuditingEntityListener.class) +@Table(name = "advertisement") +public class Advertisement { + /** + * Primary Key + */ + @Id + @GeneratedValue(strategy = IDENTITY) + private Integer id; + + @NotNull + private String advertisementDesktopImageUrl; + + @NotNull + private String advertisementMobileImageUrl; + + @NotNull + private String advertisementLink; + + @NotNull + @Enumerated(EnumType.STRING) + private AdvertisementCategory advertisementCategory; + + @NotNull + private Long priority; + + @NotNull + private LocalDateTime advertisementStartDate; + + @NotNull + private LocalDateTime advertisementEndDate; + + @Builder + private Advertisement(String advertisementDesktopImageUrl, String advertisementMobileImageUrl, String advertisementLink, + AdvertisementCategory advertisementCategory, Long priority, LocalDateTime advertisementStartDate, + LocalDateTime advertisementEndDate) { + this.advertisementDesktopImageUrl = advertisementDesktopImageUrl; + this.advertisementMobileImageUrl = advertisementMobileImageUrl; + this.advertisementLink = advertisementLink; + this.advertisementCategory = advertisementCategory; + this.priority = priority; + this.advertisementStartDate = advertisementStartDate; + this.advertisementEndDate = advertisementEndDate; + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/AdvertisementRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/AdvertisementRepository.java new file mode 100644 index 00000000..2d907735 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/AdvertisementRepository.java @@ -0,0 +1,12 @@ +package org.sopt.makers.crew.main.entity.advertisement; + +import java.time.LocalDateTime; +import java.util.List; + +import org.sopt.makers.crew.main.entity.advertisement.enums.AdvertisementCategory; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AdvertisementRepository extends JpaRepository { + List findTop6ByAdvertisementCategoryAndAdvertisementEndDateAfterAndAdvertisementStartDateBeforeOrderByPriority( + AdvertisementCategory advertisementCategory, LocalDateTime now1, LocalDateTime now2); +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/enums/AdvertisementCategory.java b/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/enums/AdvertisementCategory.java new file mode 100644 index 00000000..5ae13ded --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/enums/AdvertisementCategory.java @@ -0,0 +1,5 @@ +package org.sopt.makers.crew.main.entity.advertisement.enums; + +public enum AdvertisementCategory { + POST, MEETING +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Applies.java b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Applies.java new file mode 100644 index 00000000..decbcbf6 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Applies.java @@ -0,0 +1,73 @@ +package org.sopt.makers.crew.main.entity.apply; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.sopt.makers.crew.main.entity.apply.enums.EnApplyStatus; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class Applies { + + /** + * Key : MeetingId + * Value : ν•΄λ‹Ή λͺ¨μž„에 μ‹ μ²­ν•œ 신청정보듀 + * + * @note: List 내에 μžˆλŠ” Apply κ°μ²΄λŠ” fetch join 으둜 λ‹€λ₯Έ 객체λ₯Ό λΆˆλŸ¬μ˜€μ§€ μ•Šμ€ μƒνƒœ + * + * */ + private final Map> appliesMap; + + public Applies(List applies) { + this.appliesMap = applies.stream() + .collect(Collectors.groupingBy(Apply::getMeetingId)); + } + + public int getAppliedCount(Integer meetingId) { + List applies = appliesMap.get(meetingId); + + if (applies == null) { + return 0; + } + return applies.size(); + } + + public long getApprovedCount(Integer meetingId) { + List applies = appliesMap.get(meetingId); + + if (applies == null) { + return 0; + } + + return appliesMap.get(meetingId).stream() + .filter(apply -> apply.getStatus().equals(EnApplyStatus.APPROVE)) + .count(); + } + + public Boolean isApply(Integer meetingId, Integer userId) { + List applies = appliesMap.get(meetingId); + + if (applies == null) { + return false; + } + + return appliesMap.get(meetingId).stream() + .anyMatch(apply -> apply.getUserId().equals(userId)); + } + + public Boolean isApproved(Integer meetingId, Integer userId) { + List applies = appliesMap.get(meetingId); + + if (applies == null) { + return false; + } + + return appliesMap.get(meetingId).stream() + .anyMatch(apply -> apply.getUserId().equals(userId) + && apply.getStatus().equals(EnApplyStatus.APPROVE)); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Apply.java b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Apply.java index 92fe4795..706ef03a 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Apply.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Apply.java @@ -1,6 +1,7 @@ package org.sopt.makers.crew.main.entity.apply; import static jakarta.persistence.GenerationType.IDENTITY; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.*; import jakarta.persistence.Column; import jakarta.persistence.Convert; @@ -12,11 +13,15 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; + import java.time.LocalDateTime; + import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; + +import org.sopt.makers.crew.main.common.exception.BadRequestException; import org.sopt.makers.crew.main.entity.apply.enums.ApplyStatusConverter; import org.sopt.makers.crew.main.entity.apply.enums.ApplyTypeConverter; import org.sopt.makers.crew.main.entity.apply.enums.EnApplyStatus; @@ -33,80 +38,94 @@ @Table(name = "apply") public class Apply { - /** - * Primary Key - */ - @Id - @GeneratedValue(strategy = IDENTITY) - private Integer id; - - /** - * 지원 νƒ€μž… - */ - @Column(name = "type", nullable = false, columnDefinition = "integer default 0") - @Convert(converter = ApplyTypeConverter.class) - private EnApplyType type; - - /** - * μ§€μ›ν•œ λͺ¨μž„ - */ - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "meetingId", nullable = false) - private Meeting meeting; - - /** - * μ§€μ›ν•œ λͺ¨μž„ ID - */ - @Column(insertable = false, updatable = false) - private Integer meetingId; - - /** - * μ§€μ›μž - */ - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "userId", nullable = false) - private User user; - - /** - * μ§€μ›μž ID - */ - @Column(insertable = false, updatable = false) - private Integer userId; - - /** - * 지원 동기 - */ - @Column(name = "content", nullable = false) - private String content; - - /** - * μ§€μ›ν•œ λ‚ μ§œ - */ - @Column(name = "appliedDate", nullable = false, columnDefinition = "TIMESTAMP") - @CreatedDate - private LocalDateTime appliedDate; - - /** - * 지원 μƒνƒœ - */ - @Column(name = "status", nullable = false, columnDefinition = "integer default 0") - @Convert(converter = ApplyStatusConverter.class) - private EnApplyStatus status; - - @Builder - public Apply(EnApplyType type, Meeting meeting, Integer meetingId, User user, Integer userId, - String content) { - this.type = type; - this.meeting = meeting; - this.meetingId = meetingId; - this.user = user; - this.userId = userId; - this.content = content; - this.appliedDate = LocalDateTime.now(); - this.status = EnApplyStatus.WAITING; - } - - public void updateApplyStatus(EnApplyStatus status) { - this.status = status; - } + /** + * Primary Key + */ + @Id + @GeneratedValue(strategy = IDENTITY) + private Integer id; + + /** + * 지원 νƒ€μž… + */ + @Column(name = "type", nullable = false, columnDefinition = "integer default 0") + @Convert(converter = ApplyTypeConverter.class) + private EnApplyType type; + + /** + * μ§€μ›ν•œ λͺ¨μž„ + */ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "meetingId", nullable = false) + private Meeting meeting; + + /** + * μ§€μ›ν•œ λͺ¨μž„ ID + */ + @Column(insertable = false, updatable = false) + private Integer meetingId; + + /** + * μ§€μ›μž + */ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "userId", nullable = false) + private User user; + + /** + * μ§€μ›μž ID + */ + @Column(insertable = false, updatable = false) + private Integer userId; + + /** + * 지원 동기 + */ + @Column(name = "content", nullable = false) + private String content; + + /** + * μ§€μ›ν•œ λ‚ μ§œ + */ + @Column(name = "appliedDate", nullable = false, columnDefinition = "TIMESTAMP") + @CreatedDate + private LocalDateTime appliedDate; + + /** + * 지원 μƒνƒœ + */ + @Column(name = "status", nullable = false, columnDefinition = "integer default 0") + @Convert(converter = ApplyStatusConverter.class) + private EnApplyStatus status; + + @Builder + public Apply(EnApplyType type, Meeting meeting, Integer meetingId, User user, Integer userId, + String content) { + this.type = type; + this.meeting = meeting; + this.meetingId = meetingId; + this.user = user; + this.userId = userId; + this.content = content; + this.appliedDate = LocalDateTime.now(); + this.status = EnApplyStatus.WAITING; + } + + public void updateApplyStatus(EnApplyStatus status) { + this.status = status; + } + + public void validateDuplicateUpdateApplyStatus(EnApplyStatus updatedApplyStatus) { + if (updatedApplyStatus.equals(this.getStatus())) { + throw new BadRequestException(ALREADY_PROCESSED_APPLY.getErrorCode()); + } + } + + public String getContent(Integer requestUserId) { + if (!this.userId.equals(requestUserId)) { + return ""; + } + + return this.content; + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/ApplyRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/ApplyRepository.java index 26ca1b56..f9caa7d1 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/ApplyRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/ApplyRepository.java @@ -1,6 +1,6 @@ package org.sopt.makers.crew.main.entity.apply; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.NOT_FOUND_APPLY; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.NOT_FOUND_APPLY; import java.util.List; import java.util.Optional; @@ -18,10 +18,22 @@ public interface ApplyRepository extends JpaRepository, ApplySea List findAllByUserIdAndStatus(@Param("userId") Integer userId, @Param("statusValue") EnApplyStatus statusValue); + @Query("select a from Apply a join fetch a.meeting m join fetch m.user u where a.userId = :userId") + List findAllByUserId(@Param("userId") Integer userId); + + @Query("select a " + + "from Apply a " + + "join fetch a.user u " + + "where a.meetingId = :meetingId " + + "and a.status in :statuses order by :order") + List findAllByMeetingIdWithUser(@Param("meetingId") Integer meetingId, @Param("statuses") List statuses, @Param("order") String order); + List findAllByMeetingIdAndStatus(Integer meetingId, EnApplyStatus statusValue); List findAllByMeetingId(Integer meetingId); + List findAllByMeetingIdIn(List meetingIds); + boolean existsByMeetingIdAndUserId(Integer meetingId, Integer userId); @Transactional @@ -29,11 +41,14 @@ List findAllByUserIdAndStatus(@Param("userId") Integer userId, @Query("delete from Apply a where a.meeting.id = :meetingId and a.userId = :userId") void deleteByMeetingIdAndUserId(@Param("meetingId") Integer meetingId, @Param("userId") Integer userId); - Optional findById(Integer applyId); - default Apply findByIdOrThrow(Integer applyId) { return findById(applyId) .orElseThrow(() -> new BadRequestException(NOT_FOUND_APPLY.getErrorCode())); } + @Modifying(clearAutomatically = true) + @Transactional + @Query("DELETE FROM Apply a WHERE a.meetingId = :meetingId") + void deleteAllByMeetingIdQuery(Integer meetingId); + } \ No newline at end of file diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/ApplySearchRepositoryImpl.java b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/ApplySearchRepositoryImpl.java index f47765ae..ea0303de 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/ApplySearchRepositoryImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/ApplySearchRepositoryImpl.java @@ -1,5 +1,6 @@ package org.sopt.makers.crew.main.entity.apply; +import static org.sopt.makers.crew.main.common.constant.CrewConst.*; import static org.sopt.makers.crew.main.entity.apply.QApply.apply; import static org.sopt.makers.crew.main.entity.user.QUser.user; @@ -47,7 +48,7 @@ private List getContent(MeetingGetAppliesQueryDto queryCommand, Pa apply.meetingId.eq(meetingId), apply.status.in(queryCommand.getStatus()) ) - .orderBy(queryCommand.getDate().equals("desc") ? apply.appliedDate.desc() : apply.appliedDate.asc()) + .orderBy(queryCommand.getDate().equals(ORDER_DESC) ? apply.appliedDate.desc() : apply.appliedDate.asc()) .offset(pageable.getOffset()) .limit(pageable.getPageSize()) .fetch(); diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/enums/EnApplyStatus.java b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/enums/EnApplyStatus.java index 97452538..804aa500 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/enums/EnApplyStatus.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/enums/EnApplyStatus.java @@ -2,25 +2,25 @@ import java.util.Arrays; import org.sopt.makers.crew.main.common.exception.BadRequestException; -import org.sopt.makers.crew.main.common.exception.BaseException; -import org.springframework.http.HttpStatus; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; /** μ‹ μ²­ μƒνƒœ */ +@RequiredArgsConstructor +@Getter public enum EnApplyStatus { /** λŒ€κΈ° */ - WAITING(0), + WAITING(0, "λŒ€κΈ°"), /** 승인된 μ‹ μ²­μž */ - APPROVE(1), + APPROVE(1, "승인"), /** 거절된 μ‹ μ²­μž */ - REJECT(2); + REJECT(2, "거절"); private final int value; - - EnApplyStatus(int value) { - this.value = value; - } + private final String description; public static EnApplyStatus ofValue(int dbData) { return Arrays.stream(EnApplyStatus.values()) @@ -29,8 +29,4 @@ public static EnApplyStatus ofValue(int dbData) { .orElseThrow(() -> new BadRequestException( String.format("EnApplyStatus ν΄λž˜μŠ€μ— value = [%s] 값을 가진 enum 객체가 μ—†μŠ΅λ‹ˆλ‹€.", dbData))); } - - public int getValue() { - return value; - } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/comment/Comment.java b/main/src/main/java/org/sopt/makers/crew/main/entity/comment/Comment.java index 76d44d4d..039c006b 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/comment/Comment.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/comment/Comment.java @@ -1,6 +1,6 @@ package org.sopt.makers.crew.main.entity.comment; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.*; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.*; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -38,7 +38,6 @@ public class Comment { private static final int PARENT_COMMENT = 0; private static final String DELETE_COMMENT_CONTENT = "μ‚­μ œλœ λŒ“κΈ€μž…λ‹ˆλ‹€."; - /** * λŒ“κΈ€μ˜ 고유 μ‹λ³„μž */ @@ -116,6 +115,7 @@ public class Comment { private Integer parentId; public static class CommentListener { + @PostPersist public void setParentId(Comment comment) { if (comment.depth == PARENT_COMMENT) { // λŒ“κΈ€μΌ 경우 @@ -125,7 +125,8 @@ public void setParentId(Comment comment) { } @Builder - public Comment(String contents, int depth, int order, User user, Integer userId, Post post, Integer postId, + public Comment(String contents, int depth, int order, User user, Integer userId, Post post, + Integer postId, int likeCount, Integer parentId) { this.contents = contents; this.depth = depth; @@ -155,10 +156,21 @@ public void validateWriter(Integer userId) { } public boolean isWriter(Integer userId) { - return this.userId.equals(userId); + if (this.userId == null || !this.userId.equals(userId)) { + return false; + } + return true; } public boolean isParentComment() { return this.depth == PARENT_COMMENT; } + + public void increaseLikeCount() { + this.likeCount++; + } + + public void decreaseLikeCount() { + this.likeCount--; + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/comment/CommentRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/comment/CommentRepository.java index e23cb8f5..f41879e0 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/comment/CommentRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/comment/CommentRepository.java @@ -4,8 +4,11 @@ import java.util.Optional; import org.sopt.makers.crew.main.common.exception.BadRequestException; -import org.sopt.makers.crew.main.common.response.ErrorStatus; +import org.sopt.makers.crew.main.common.exception.ErrorStatus; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.transaction.annotation.Transactional; public interface CommentRepository extends JpaRepository, CommentSearchRepository { @@ -26,4 +29,16 @@ default Comment findByIdAndPostIdOrThrow(Integer id, Integer postId){ } List findAllByPostIdOrderByCreatedDate(Integer postId); + + List findAllByPostIdIsIn(List postIds); + + @Modifying(clearAutomatically = true) + @Transactional + @Query("DELETE FROM Comment c WHERE c.postId = :postId") + void deleteAllByPostId(Integer postId); + + @Modifying(clearAutomatically = true) + @Transactional + @Query("DELETE FROM Comment c WHERE c.postId IN :postIds") + void deleteAllByPostIdsInQuery(List postIds); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/like/Like.java b/main/src/main/java/org/sopt/makers/crew/main/entity/like/Like.java index 345f93ff..019b0c99 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/like/Like.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/like/Like.java @@ -7,11 +7,14 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; + import java.time.LocalDateTime; + import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; + import org.springframework.data.annotation.CreatedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; @@ -21,43 +24,44 @@ @Table(name = "like") @EntityListeners(AuditingEntityListener.class) public class Like { - /** - * Primary key - */ - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Integer id; - - /** - * μ’‹μ•„μš” λˆ„λ₯Έ λ‚ μ§œ - */ - @Column(name = "createdDate", nullable = false, columnDefinition = "TIMESTAMP") - @CreatedDate - private LocalDateTime createdDate; - - /** - * μ’‹μ•„μš” λˆ„λ₯Έμ‚¬λžŒ id - */ - @Column(insertable = false, updatable = false) - private Integer userId; - - /** - * κ²Œμ‹œκΈ€ id - κ²Œμ‹œκΈ€ μ’‹μ•„μš”κ°€ 아닐 경우 null - */ - @Column(insertable = false, updatable = false) - private Integer postId; - - /** - * λŒ“κΈ€ id - λŒ“κΈ€ μ’‹μ•„μš”κ°€ 아닐 경우 null - */ - @Column(insertable = false, updatable = false) - private Integer commentId; - - @Builder - public Like(Integer userId, Integer postId, Integer commentId) { - this.userId = userId; - this.postId = postId; - this.commentId = commentId; - } + + /** + * Primary key + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private int id; + + /** + * μ’‹μ•„μš” λˆ„λ₯Έ λ‚ μ§œ + */ + @Column(name = "createdDate", nullable = false, columnDefinition = "TIMESTAMP") + @CreatedDate + private LocalDateTime createdDate; + + /** + * μ’‹μ•„μš” λˆ„λ₯Έμ‚¬λžŒ id + */ + @Column + private int userId; + + /** + * κ²Œμ‹œκΈ€ id - κ²Œμ‹œκΈ€ μ’‹μ•„μš”κ°€ 아닐 경우 null + */ + @Column + private Integer postId; + + /** + * λŒ“κΈ€ id - λŒ“κΈ€ μ’‹μ•„μš”κ°€ 아닐 경우 null + */ + @Column + private Integer commentId; + + @Builder + public Like(Integer userId, Integer postId, Integer commentId) { + this.userId = userId; + this.postId = postId; + this.commentId = commentId; + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/like/LikeRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/like/LikeRepository.java index 142f20e8..a17c8623 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/like/LikeRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/like/LikeRepository.java @@ -3,8 +3,37 @@ import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.transaction.annotation.Transactional; public interface LikeRepository extends JpaRepository { - List findAllByUserIdAndPostIdNotNull(Integer userId); + List findAllByUserIdAndCommentIdNotNull(Integer userId); + + @Modifying(clearAutomatically = true) + @Transactional + @Query("DELETE FROM Like l WHERE l.postId = :postId") + void deleteAllByPostId(Integer postId); + + @Modifying(clearAutomatically = true) + @Transactional + @Query("DELETE FROM Like l WHERE l.postId IN :postIds") + void deleteAllByPostIdsInQuery(List postIds); + + @Modifying(clearAutomatically = true) + @Transactional + @Query("DELETE FROM Like l WHERE l.commentId IN :commentIds") + void deleteAllByCommentIdsInQuery(List commentIds); + + boolean existsByUserIdAndCommentId(Integer userId, Integer commentId); + + void deleteByUserIdAndCommentId(Integer userId, Integer commentId); + + int deleteByUserIdAndPostId(Integer userId, Integer postId); + + List findAllByPostIdIsIn(List postIds); + + List findAllByCommentIdIsIn(List commentIds); + } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/Meeting.java b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/Meeting.java index b2968c1d..fead16d4 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/Meeting.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/Meeting.java @@ -1,9 +1,10 @@ package org.sopt.makers.crew.main.entity.meeting; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.*; + import io.hypersistence.utils.hibernate.type.array.EnumArrayType; import io.hypersistence.utils.hibernate.type.array.internal.AbstractArrayType; import io.hypersistence.utils.hibernate.type.json.JsonBinaryType; -import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Convert; import jakarta.persistence.Entity; @@ -26,7 +27,8 @@ import org.hibernate.annotations.Parameter; import org.hibernate.annotations.Type; -import org.sopt.makers.crew.main.entity.apply.Apply; +import org.sopt.makers.crew.main.common.exception.BadRequestException; +import org.sopt.makers.crew.main.common.exception.ForbiddenException; import org.sopt.makers.crew.main.entity.meeting.converter.MeetingCategoryConverter; import org.sopt.makers.crew.main.entity.meeting.enums.EnMeetingStatus; import org.sopt.makers.crew.main.entity.meeting.enums.MeetingCategory; @@ -77,7 +79,6 @@ public class Meeting { */ @Column(name = "imageURL", columnDefinition = "jsonb") @Type(JsonBinaryType.class) - //@JdbcTypeCode(SqlTypes.JSON) private List imageURL; /** @@ -174,7 +175,7 @@ public class Meeting { private MeetingJoinablePart[] joinableParts; @Builder - public Meeting(User user, Integer userId, List appliedInfo, String title, MeetingCategory category, + public Meeting(User user, Integer userId, String title, MeetingCategory category, List imageURL, LocalDateTime startDate, LocalDateTime endDate, Integer capacity, String desc, String processDesc, LocalDateTime mStartDate, LocalDateTime mEndDate, String leaderDesc, String targetDesc, String note, Boolean isMentorNeeded, @@ -207,8 +208,8 @@ public Meeting(User user, Integer userId, List appliedInfo, String title, * * @return λͺ¨μž„ λͺ¨μ§‘μƒνƒœ */ - public Integer getMeetingStatus() { - LocalDateTime now = LocalDateTime.now(); + public Integer getMeetingStatus(LocalDateTime now) { + if (now.isBefore(startDate)) { return EnMeetingStatus.BEFORE_START.getValue(); } else if (now.isBefore(endDate)) { @@ -217,4 +218,42 @@ public Integer getMeetingStatus() { return EnMeetingStatus.RECRUITMENT_COMPLETE.getValue(); } } + + public void validateMeetingCreator(Integer requestUserId) { + if (Boolean.FALSE.equals(checkMeetingLeader(requestUserId))) { + throw new ForbiddenException(FORBIDDEN_EXCEPTION.getErrorCode()); + } + } + + public Boolean checkMeetingLeader(Integer userId) { + return this.userId.equals(userId); + } + + public void validateCapacity(int approvedCount) { + if (approvedCount >= this.capacity) { + throw new BadRequestException(FULL_MEETING_CAPACITY.getErrorCode()); + } + } + + public void updateMeeting(Meeting updateMeeting) { + + this.title = updateMeeting.getTitle(); + this.category = updateMeeting.getCategory(); + this.imageURL = updateMeeting.getImageURL(); + this.startDate = updateMeeting.getStartDate(); + this.endDate = updateMeeting.getEndDate(); + this.capacity = updateMeeting.getCapacity(); + this.desc = updateMeeting.getDesc(); + this.processDesc = updateMeeting.getProcessDesc(); + this.mStartDate = updateMeeting.mStartDate; + this.mEndDate = updateMeeting.getMEndDate(); + this.leaderDesc = updateMeeting.getLeaderDesc(); + this.targetDesc = updateMeeting.getTargetDesc(); + this.note = updateMeeting.getNote(); + this.isMentorNeeded = updateMeeting.getIsMentorNeeded(); + this.canJoinOnlyActiveGeneration = updateMeeting.getCanJoinOnlyActiveGeneration(); + this.targetActiveGeneration = updateMeeting.getTargetActiveGeneration(); + this.joinableParts = updateMeeting.getJoinableParts(); + } + } \ No newline at end of file diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingRepository.java index fe05afb8..e8a70abe 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingRepository.java @@ -1,20 +1,25 @@ package org.sopt.makers.crew.main.entity.meeting; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.NOT_FOUND_MEETING; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.NOT_FOUND_MEETING; import java.util.List; -import java.util.Optional; + import org.sopt.makers.crew.main.common.exception.BadRequestException; +import org.sopt.makers.crew.main.entity.user.User; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +public interface MeetingRepository extends JpaRepository, MeetingSearchRepository { -public interface MeetingRepository extends JpaRepository { + List findAllByUserId(Integer userId); - List findAllByUserId(int userId); + List findAllByUser(User user); - Optional findById(Integer meetingId); + default Meeting findByIdOrThrow(Integer meetingId) { + return findById(meetingId) + .orElseThrow(() -> new BadRequestException(NOT_FOUND_MEETING.getErrorCode())); + } - default Meeting findByIdOrThrow(Integer meetingId) { - return findById(meetingId) - .orElseThrow(() -> new BadRequestException(NOT_FOUND_MEETING.getErrorCode())); - } + @Query("SELECT m FROM Meeting m JOIN FETCH m.user ORDER BY m.id DESC LIMIT 20") + List findTop20ByOrderByIdDesc(); } \ No newline at end of file diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingSearchRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingSearchRepository.java new file mode 100644 index 00000000..37757b15 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingSearchRepository.java @@ -0,0 +1,10 @@ +package org.sopt.makers.crew.main.entity.meeting; + +import org.sopt.makers.crew.main.common.util.Time; +import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingQueryDto; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface MeetingSearchRepository { + Page findAllByQuery(MeetingV2GetAllMeetingQueryDto queryCommand, Pageable pageable, Time time); +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingSearchRepositoryImpl.java b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingSearchRepositoryImpl.java new file mode 100644 index 00000000..ca888976 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingSearchRepositoryImpl.java @@ -0,0 +1,170 @@ +package org.sopt.makers.crew.main.entity.meeting; + +import static org.sopt.makers.crew.main.entity.meeting.QMeeting.*; +import static org.sopt.makers.crew.main.entity.user.QUser.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import org.sopt.makers.crew.main.common.constant.CrewConst; +import org.sopt.makers.crew.main.common.util.Time; +import org.sopt.makers.crew.main.entity.meeting.enums.EnMeetingStatus; +import org.sopt.makers.crew.main.entity.meeting.enums.MeetingCategory; +import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart; +import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingQueryDto; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.support.PageableExecutionUtils; +import org.springframework.stereotype.Repository; + +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.jpa.impl.JPAQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class MeetingSearchRepositoryImpl implements MeetingSearchRepository { + private final JPAQueryFactory queryFactory; + //private final Time time; + + /** + * @note: canJoinOnlyActiveGeneration 처리 유의 + * @note: status 처리 유의 + * */ + + @Override + public Page findAllByQuery(MeetingV2GetAllMeetingQueryDto queryCommand, Pageable pageable, Time time) { + + List meetings = getMeetings(queryCommand, pageable, time); + JPAQuery countQuery = getCount(queryCommand, time); + + return PageableExecutionUtils.getPage(meetings, + PageRequest.of(pageable.getPageNumber(), pageable.getPageSize()), countQuery::fetchFirst); + } + + private List getMeetings(MeetingV2GetAllMeetingQueryDto queryCommand, Pageable pageable, Time time) { + return queryFactory + .selectFrom(meeting) + .where( + eqCategory(queryCommand.getCategory()), + eqStatus(queryCommand.getStatus(), time), + isOnlyActiveGeneration(queryCommand.getIsOnlyActiveGeneration()), + eqJoinableParts(queryCommand.getJoinableParts()), + eqQuery(queryCommand.getQuery()) + ) + .innerJoin(meeting.user, user) + .fetchJoin() + .orderBy(meeting.id.desc()) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + } + + private JPAQuery getCount(MeetingV2GetAllMeetingQueryDto queryCommand, Time time) { + return queryFactory + .select(meeting.count()) + .from(meeting) + .where( + eqCategory(queryCommand.getCategory()), + eqStatus(queryCommand.getStatus(), time), + isOnlyActiveGeneration(queryCommand.getIsOnlyActiveGeneration()), + eqJoinableParts(queryCommand.getJoinableParts()), + eqQuery(queryCommand.getQuery()) + ); + } + + private BooleanExpression eqCategory(List categories) { + + if (categories == null || categories.isEmpty()) { + return null; + } + + List categoryList = categories.stream() + .map(MeetingCategory::ofValue) + .collect(Collectors.toList()); + + return meeting.category.in(categoryList); + } + + private BooleanExpression eqStatus(List statues, Time time) { + + if (statues == null || statues.isEmpty()) { + return null; + } + + List conditions = new ArrayList<>(); + + List statuesInt = statues.stream() + .map(Integer::parseInt) + .toList(); + + for (Integer status : statuesInt) { + if (status == EnMeetingStatus.BEFORE_START.getValue()) { + BooleanExpression condition = meeting.startDate.after(time.now()); + conditions.add(condition); + } else if (status == EnMeetingStatus.APPLY_ABLE.getValue()) { + BooleanExpression afterStartDate = meeting.startDate.before(time.now()); + BooleanExpression beforeEndDate = meeting.endDate.after(time.now()); + BooleanExpression condition = afterStartDate.and(beforeEndDate); + conditions.add(condition); + } else if (status == EnMeetingStatus.RECRUITMENT_COMPLETE.getValue()) { + BooleanExpression condition = meeting.endDate.before(time.now()); + conditions.add(condition); + } + } + + if (conditions.isEmpty()) { + return null; // No valid conditions + } + + BooleanExpression combinedCondition = conditions.get(0); + for (int i = 1; i < conditions.size(); i++) { + combinedCondition = combinedCondition.or(conditions.get(i)); + } + + return combinedCondition; + } + + private BooleanExpression isOnlyActiveGeneration(boolean isOnlyActiveGeneration) { + + if (isOnlyActiveGeneration) { + return meeting.canJoinOnlyActiveGeneration.eq(true) + .and(meeting.targetActiveGeneration.eq(CrewConst.ACTIVE_GENERATION)); + } + + return null; + } + + private BooleanExpression eqJoinableParts(MeetingJoinablePart[] joinableParts) { + + if (joinableParts == null || joinableParts.length == 0) { + return null; + } + + String joinablePartsToString = Arrays.stream(joinableParts) + .map(Enum::name) // 각 μš”μ†Œλ₯Ό ν°λ”°μ˜΄ν‘œλ‘œ κ°μŒ‰λ‹ˆλ‹€. + .collect(Collectors.joining(",", "'{", "}'")); // μš”μ†Œλ“€μ„ μ‰Όν‘œλ‘œ μ—°κ²°ν•˜κ³  μ€‘κ΄„ν˜Έλ‘œ κ°μŒ‰λ‹ˆλ‹€. + + // SQL ν…œν”Œλ¦Ώμ„ μ‚¬μš©ν•˜μ—¬ BooleanExpression 생성 + return Expressions.booleanTemplate( + "arraycontains({0}, "+ joinablePartsToString + ") || '' = 'true'", + meeting.joinableParts, + joinablePartsToString + ); + } + + private BooleanExpression eqQuery(String query) { + if (query == null) { + return null; + } + + return meeting.title.contains(query); + } + +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/post/Post.java b/main/src/main/java/org/sopt/makers/crew/main/entity/post/Post.java index 55fcb12c..aa8cf1dc 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/post/Post.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/post/Post.java @@ -1,7 +1,8 @@ package org.sopt.makers.crew.main.entity.post; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.FORBIDDEN_EXCEPTION; + import io.hypersistence.utils.hibernate.type.array.StringArrayType; -import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EntityListeners; @@ -21,7 +22,7 @@ import lombok.NoArgsConstructor; import org.hibernate.annotations.Type; -import org.sopt.makers.crew.main.entity.comment.Comment; +import org.sopt.makers.crew.main.common.exception.ForbiddenException; import org.sopt.makers.crew.main.entity.meeting.Meeting; import org.sopt.makers.crew.main.entity.user.User; import org.springframework.data.annotation.CreatedDate; @@ -126,7 +127,9 @@ public Post(String title, String contents, String[] images, User user, Meeting m this.viewCount = 0; this.images = images; this.user = user; + this.userId = user.getId(); this.meeting = meeting; + this.meetingId = meeting.getId(); this.commentCount = 0; this.likeCount = 0; } @@ -138,4 +141,24 @@ public void increaseCommentCount() { public void decreaseCommentCount() { this.commentCount--; } + + public void increaseLikeCount() { + this.likeCount++; + } + + public void decreaseLikeCount() { + this.likeCount--; + } + + public void isWriter(Integer userId) { + if (!this.userId.equals(userId)) { + throw new ForbiddenException(FORBIDDEN_EXCEPTION.getErrorCode()); + } + } + + public void updatePost(String title, String contents, String[] images) { + this.title = title; + this.contents = contents; + this.images = images; + } } \ No newline at end of file diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostRepository.java index ef23d79b..71b37fc4 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostRepository.java @@ -1,19 +1,31 @@ package org.sopt.makers.crew.main.entity.post; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.NOT_FOUND_POST; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.NOT_FOUND_POST; +import java.util.List; import java.util.Optional; + import org.sopt.makers.crew.main.common.exception.BadRequestException; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.transaction.annotation.Transactional; public interface PostRepository extends JpaRepository, PostSearchRepository { - Optional findById(Integer postId); + default Post findByIdOrThrow(Integer postId) { + return findById(postId) + .orElseThrow(() -> new BadRequestException(NOT_FOUND_POST.getErrorCode())); + } + + Integer countByMeetingId(Integer meetingId); + + List findAllByMeetingId(Integer meetingId); - default Post findByIdOrThrow(Integer postId) { - return findById(postId) - .orElseThrow(() -> new BadRequestException(NOT_FOUND_POST.getErrorCode())); - } + List findAllByMeetingIdIn(List meetingIds); - Optional findFirstByMeetingIdOrderByIdDesc(Integer meetingId); + @Modifying(clearAutomatically = true) + @Transactional + @Query("DELETE FROM Post p WHERE p.meetingId = :meetingId") + void deleteAllByMeetingIdQuery(Integer meetingId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepository.java index 9fbf50a6..df3f5db2 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepository.java @@ -1,10 +1,13 @@ package org.sopt.makers.crew.main.entity.post; import org.sopt.makers.crew.main.post.v2.dto.query.PostGetPostsCommand; +import org.sopt.makers.crew.main.post.v2.dto.response.PostDetailBaseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostDetailResponseDto; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; public interface PostSearchRepository { Page findPostList(PostGetPostsCommand queryCommand, Pageable pageable, Integer userId); + + PostDetailBaseDto findPost(Integer userId, Integer postId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepositoryImpl.java b/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepositoryImpl.java index befc4d88..f255113a 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepositoryImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepositoryImpl.java @@ -1,5 +1,6 @@ package org.sopt.makers.crew.main.entity.post; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.NOT_FOUND_POST; import static org.sopt.makers.crew.main.entity.comment.QComment.comment; import static org.sopt.makers.crew.main.entity.like.QLike.like; import static org.sopt.makers.crew.main.entity.meeting.QMeeting.meeting; @@ -18,6 +19,7 @@ import java.util.Map; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; +import org.sopt.makers.crew.main.common.exception.BadRequestException; import org.sopt.makers.crew.main.post.v2.dto.query.PostGetPostsCommand; import org.sopt.makers.crew.main.post.v2.dto.response.CommenterThumbnails; import org.sopt.makers.crew.main.post.v2.dto.response.PostDetailBaseDto; @@ -41,15 +43,59 @@ public Page findPostList(PostGetPostsCommand queryCommand Integer userId) { Integer meetingId = queryCommand.getMeetingId().orElse(null); - List content = getContent(pageable, meetingId, userId); + List content = getContentList(pageable, meetingId, userId); JPAQuery countQuery = getCount(meetingId); return PageableExecutionUtils.getPage(content, PageRequest.of(pageable.getPageNumber(), pageable.getPageSize()), countQuery::fetchFirst); } - private List getContent(Pageable pageable, Integer meetingId, - Integer userId) { + @Override + public PostDetailBaseDto findPost(Integer userId, Integer postId) { + PostDetailBaseDto postDetail = queryFactory + .select(new QPostDetailBaseDto( + post.id, + post.title, + post.contents, + post.createdDate, + post.images, + new QPostWriterInfoDto( + post.user.id, + post.user.orgId, + post.user.name, + post.user.profileImage + ), + post.likeCount, + ExpressionUtils.as( + JPAExpressions.selectFrom(like) + .where(like.postId.eq(post.id).and(like.userId.eq(userId))) + .exists() + , "isLiked" + ), + post.viewCount, + post.commentCount, + new QPostMeetingDto( + post.meeting.id, + post.meeting.title, + post.meeting.category, + post.meeting.imageURL + ) + )) + .from(post) + .innerJoin(post.meeting, meeting) + .innerJoin(post.user, user) + .where(post.id.eq(postId)) + .fetchFirst(); + + if (postDetail == null) { + throw new BadRequestException(NOT_FOUND_POST.getErrorCode()); + } + + return postDetail; + } + + private List getContentList(Pageable pageable, Integer meetingId, + Integer userId) { List responseDtos = new ArrayList<>(); List postDetailList = queryFactory @@ -77,7 +123,8 @@ private List getContent(Pageable pageable, Integer meetin new QPostMeetingDto( post.meeting.id, post.meeting.title, - post.meeting.category + post.meeting.category, + post.meeting.imageURL ) )) .from(post) diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/report/Report.java b/main/src/main/java/org/sopt/makers/crew/main/entity/report/Report.java index 2827aee4..f722a0db 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/report/Report.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/report/Report.java @@ -10,14 +10,16 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; + import java.time.LocalDateTime; + import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; + import org.sopt.makers.crew.main.entity.comment.Comment; import org.sopt.makers.crew.main.entity.post.Post; -import org.sopt.makers.crew.main.entity.user.User; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; @@ -27,67 +29,59 @@ @Table(name = "report") @EntityListeners(AuditingEntityListener.class) public class Report { - /** - * Primary key - */ - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private int id; - - /** - * μž‘μ„±μΌ - */ - @Column(name = "createdDate", nullable = false, columnDefinition = "TIMESTAMP") - @CreatedDate - private LocalDateTime createdDate; + /** + * Primary key + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private int id; - /** - * μ‹ κ³ μž - */ - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "userId", nullable = false) - private User user; + /** + * μž‘μ„±μΌ + */ + @Column(name = "createdDate", nullable = false, columnDefinition = "TIMESTAMP") + @CreatedDate + private LocalDateTime createdDate; - /** - * μ‹ κ³ μž id - */ - @Column(insertable = false, updatable = false) - private int userId; + /** + * μ‹ κ³ μž id + */ + @Column(updatable = false) + private Integer userId; - /** - * κ²Œμ‹œκΈ€ - */ - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "postId") - private Post post; + /** + * κ²Œμ‹œκΈ€ + */ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "postId") + private Post post; - /** - * κ²Œμ‹œκΈ€ id - κ²Œμ‹œκΈ€ μ’‹μ•„μš”κ°€ 아닐 경우 null - */ - @Column(insertable = false, updatable = false) - private Integer postId; + /** + * κ²Œμ‹œκΈ€ id - κ²Œμ‹œκΈ€ μ’‹μ•„μš”κ°€ 아닐 경우 null + */ + @Column(insertable = false, updatable = false) + private Integer postId; - /** - * λŒ“κΈ€ - */ - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "commentId") - private Comment comment; + /** + * λŒ“κΈ€ + */ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "commentId") + private Comment comment; - /** - * λŒ“κΈ€ id - λŒ“κΈ€ μ’‹μ•„μš”κ°€ 아닐 경우 null - */ - @Column(insertable = false, updatable = false) - private int commentId; + /** + * λŒ“κΈ€ id - λŒ“κΈ€ μ’‹μ•„μš”κ°€ 아닐 경우 null + */ + @Column(insertable = false, updatable = false) + private Integer commentId; - @Builder - public Report(User user, int userId, Post post, int postId, Comment comment, - int commentId) { - this.user = user; - this.userId = userId; - this.post = post; - this.postId = postId; - this.comment = comment; - this.commentId = commentId; - } + @Builder + public Report(Integer userId, Post post, Integer postId, Comment comment, + Integer commentId) { + this.userId = userId; + this.post = post; + this.postId = postId; + this.comment = comment; + this.commentId = commentId; + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/report/ReportRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/report/ReportRepository.java index 56071d7d..02a5d4eb 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/report/ReportRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/report/ReportRepository.java @@ -1,11 +1,10 @@ package org.sopt.makers.crew.main.entity.report; -import java.util.Optional; -import org.sopt.makers.crew.main.entity.comment.Comment; -import org.sopt.makers.crew.main.entity.user.User; import org.springframework.data.jpa.repository.JpaRepository; public interface ReportRepository extends JpaRepository { - Optional findByCommentAndUser(Comment comment, User user); + boolean existsByCommentIdAndUserId(Integer commentId, Integer userId); + + boolean existsByPostIdAndUserId(Integer postId, Integer userId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/user/User.java b/main/src/main/java/org/sopt/makers/crew/main/entity/user/User.java index e13ecae5..edb0bb6f 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/user/User.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/user/User.java @@ -1,6 +1,6 @@ package org.sopt.makers.crew.main.entity.user; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.*; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.*; import io.hypersistence.utils.hibernate.type.json.JsonBinaryType; import jakarta.persistence.Column; @@ -84,4 +84,14 @@ public UserActivityVO getRecentActivityVO(){ .max(Comparator.comparingInt(UserActivityVO::getGeneration)) .orElseThrow(() -> new ServerException(INTERNAL_SERVER_ERROR.getErrorCode())); } + + public void updateUser(String name, Integer orgId, List activities, String profileImage, + String phone){ + + this.name = name; + this.orgId = orgId; + this.activities = activities; + this.profileImage = profileImage; + this.phone = phone; + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/user/UserRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/user/UserRepository.java index 0f390f1c..1531436c 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/user/UserRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/user/UserRepository.java @@ -1,11 +1,9 @@ package org.sopt.makers.crew.main.entity.user; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.NO_CONTENT_EXCEPTION; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.*; -import java.util.List; import java.util.Optional; -import org.sopt.makers.crew.main.common.exception.NoContentException; import org.sopt.makers.crew.main.common.exception.UnAuthorizedException; import org.springframework.data.jpa.repository.JpaRepository; @@ -14,14 +12,8 @@ public interface UserRepository extends JpaRepository { Optional findByOrgId(Integer orgId); default User findByIdOrThrow(Integer userId) { - return findById(userId).orElseThrow(() -> new UnAuthorizedException()); + return findById(userId) + .orElseThrow(() -> new UnAuthorizedException(UNAUTHORIZED_USER.getErrorCode())); } - default User findByOrgIdOrThrow(Integer orgUserId) { - return findByOrgId(orgUserId).orElseThrow( - () -> new NoContentException( - NO_CONTENT_EXCEPTION.getErrorCode())); //μœ μ €κ°€ 아직 λͺ¨μž„ μ„œλΉ„μŠ€λ₯Ό 이용 전이기 λ•Œλ¬Έμ— - } - - List findByIdIn(List userIds); } \ No newline at end of file diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/user/vo/UserActivityVO.java b/main/src/main/java/org/sopt/makers/crew/main/entity/user/vo/UserActivityVO.java index 4a06ab06..c2f96ab6 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/user/vo/UserActivityVO.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/user/vo/UserActivityVO.java @@ -1,5 +1,7 @@ package org.sopt.makers.crew.main.entity.user.vo; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.ToString; @@ -9,7 +11,12 @@ @ToString public class UserActivityVO { - private final String part; - private final int generation; + @Schema(description = "파트", example = "μ„œλ²„") + @NotNull + private final String part; + + @Schema(description = "기수", example = "36") + @NotNull + private final int generation; } \ No newline at end of file diff --git a/main/src/main/java/org/sopt/makers/crew/main/external/playground/PlaygroundServer.java b/main/src/main/java/org/sopt/makers/crew/main/external/playground/PlaygroundServer.java new file mode 100644 index 00000000..752dd200 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/external/playground/PlaygroundServer.java @@ -0,0 +1,14 @@ +package org.sopt.makers.crew.main.external.playground; + + +import org.sopt.makers.crew.main.external.playground.dto.response.PlaygroundUserResponseDto; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; + +@FeignClient(name = "playgroundServer", url = "${playground.server.url}") +public interface PlaygroundServer { + @GetMapping("${playground.server.endpoint}") + PlaygroundUserResponseDto getUser(@RequestHeader("Authorization") String accessToken); + +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/external/playground/PlaygroundService.java b/main/src/main/java/org/sopt/makers/crew/main/external/playground/PlaygroundService.java new file mode 100644 index 00000000..e518a1df --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/external/playground/PlaygroundService.java @@ -0,0 +1,33 @@ +package org.sopt.makers.crew.main.external.playground; + +import java.util.HashMap; +import java.util.Map; + +import org.sopt.makers.crew.main.external.playground.dto.request.PlaygroundUserRequestDto; +import org.sopt.makers.crew.main.external.playground.dto.response.PlaygroundUserResponseDto; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class PlaygroundService { + private final PlaygroundServer playgroundServer; + + public PlaygroundUserResponseDto getUser(PlaygroundUserRequestDto requestDto){ + return playgroundServer.getUser(requestDto.accessToken()); + } + + + private Map createAuthorizationHeader(String accessToken) { + Map headers = createDefaultHeader(); + headers.put(HttpHeaders.AUTHORIZATION, accessToken); + return headers; + } + + private Map createDefaultHeader() { + return new HashMap<>(Map.of(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/external/playground/dto/request/PlaygroundUserRequestDto.java b/main/src/main/java/org/sopt/makers/crew/main/external/playground/dto/request/PlaygroundUserRequestDto.java new file mode 100644 index 00000000..2e366c52 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/external/playground/dto/request/PlaygroundUserRequestDto.java @@ -0,0 +1,11 @@ +package org.sopt.makers.crew.main.external.playground.dto.request; + + +public record PlaygroundUserRequestDto( + String accessToken + +) { + public static PlaygroundUserRequestDto of(String accessToken){ + return new PlaygroundUserRequestDto(accessToken); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/external/playground/dto/response/PlaygroundUserActivityResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/external/playground/dto/response/PlaygroundUserActivityResponseDto.java new file mode 100644 index 00000000..54dff740 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/external/playground/dto/response/PlaygroundUserActivityResponseDto.java @@ -0,0 +1,9 @@ +package org.sopt.makers.crew.main.external.playground.dto.response; + +public record PlaygroundUserActivityResponseDto( + Long id, + int generation, + String part, + String team +) { +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/external/playground/dto/response/PlaygroundUserResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/external/playground/dto/response/PlaygroundUserResponseDto.java new file mode 100644 index 00000000..62cac8d1 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/external/playground/dto/response/PlaygroundUserResponseDto.java @@ -0,0 +1,44 @@ +package org.sopt.makers.crew.main.external.playground.dto.response; + +import java.util.List; + +import org.sopt.makers.crew.main.entity.user.User; +import org.sopt.makers.crew.main.entity.user.vo.UserActivityVO; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * @note: idλŠ” orgId 의미 + * + * */ +@RequiredArgsConstructor +@Getter +public class PlaygroundUserResponseDto { + + private final Integer id; + private final String name; + private final String profileImage; + private final String phone; + private final List activities; + + public User toEntity() { + List userActivityVOs = activities.stream() + .map(a -> new UserActivityVO(a.part(), a.generation())) + .toList(); + + return User.builder() + .orgId(this.id) + .name(name) + .profileImage(profileImage) + .phone(phone) + .activities(userActivityVOs) + .build(); + } + + public List getUserActivities(){ + return activities.stream() + .map(a -> new UserActivityVO(a.part(), a.generation())) + .toList(); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/external/s3/config/AwsProperties.java b/main/src/main/java/org/sopt/makers/crew/main/external/s3/config/AwsProperties.java new file mode 100644 index 00000000..a30afbbd --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/external/s3/config/AwsProperties.java @@ -0,0 +1,22 @@ +package org.sopt.makers.crew.main.external.s3.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +@ConfigurationProperties("aws-property") +public final class AwsProperties { + private final String awsRegion; + private final String s3BucketName; + private final String accessKey; + private final String secretKey; + private final long fileMinSize; + private final long fileMaxSize; + private final String algorithm; + private final String contentType; + private final String requestType; + private final String objectUrl; +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/external/s3/config/AwsPropertiesConfiguration.java b/main/src/main/java/org/sopt/makers/crew/main/external/s3/config/AwsPropertiesConfiguration.java new file mode 100644 index 00000000..72805a8f --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/external/s3/config/AwsPropertiesConfiguration.java @@ -0,0 +1,9 @@ +package org.sopt.makers.crew.main.external.s3.config; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableConfigurationProperties(value = {AwsProperties.class}) +public class AwsPropertiesConfiguration { +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/external/s3/config/S3Config.java b/main/src/main/java/org/sopt/makers/crew/main/external/s3/config/S3Config.java new file mode 100644 index 00000000..dd06a63f --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/external/s3/config/S3Config.java @@ -0,0 +1,44 @@ +package org.sopt.makers.crew.main.external.s3.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import lombok.RequiredArgsConstructor; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.presigner.S3Presigner; + +@Configuration +@RequiredArgsConstructor +public class S3Config { + + private final AwsProperties awsProperties; + + @Bean + public S3Client s3Client() { + AwsBasicCredentials awsBasicCredentials = getAwsBasicCredentials(); + + return S3Client.builder() + .region(Region.of(awsProperties.getAwsRegion())) + .credentialsProvider(StaticCredentialsProvider.create(awsBasicCredentials)) + .build(); + } + + @Bean + public S3Presigner s3Presigner() { + AwsBasicCredentials awsBasicCredentials = getAwsBasicCredentials(); + + return S3Presigner.builder() + .region(Region.of(awsProperties.getAwsRegion())) + .credentialsProvider(StaticCredentialsProvider.create(awsBasicCredentials)) + .build(); + } + + private AwsBasicCredentials getAwsBasicCredentials() { + return AwsBasicCredentials.create( + awsProperties.getAccessKey(), + awsProperties.getSecretKey()); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/external/s3/service/S3Service.java b/main/src/main/java/org/sopt/makers/crew/main/external/s3/service/S3Service.java new file mode 100644 index 00000000..762a3604 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/external/s3/service/S3Service.java @@ -0,0 +1,222 @@ +package org.sopt.makers.crew.main.external.s3.service; + +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.*; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Base64; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.SimpleTimeZone; +import java.util.UUID; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +import org.sopt.makers.crew.main.common.exception.ServerException; +import org.sopt.makers.crew.main.external.s3.config.AwsProperties; +import org.sopt.makers.crew.main.meeting.v2.dto.response.PreSignedUrlFieldResponseDto; +import org.sopt.makers.crew.main.meeting.v2.dto.response.PreSignedUrlResponseDto; +import org.springframework.stereotype.Service; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; +import software.amazon.awssdk.utils.BinaryUtils; + +@Service +@RequiredArgsConstructor +@Slf4j +public class S3Service { + private static final String MEETING_PATH = "meeting"; + private static final String S3_SERVCIE = "s3"; + private static final String PRE_SIGNED_URL_PREFIX = "https://s3.ap-northeast-2.amazonaws.com"; + + private final S3Client s3Client; + + private final AwsProperties awsProperties; + + public String uploadCSVFile(String fileName) { + try { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd"); + String curDate = LocalDateTime.now().format(formatter); + + String objectKey = MEETING_PATH + "/" + curDate + "/" + fileName; + + File file = new File(fileName); + PutObjectRequest putObjectRequest = PutObjectRequest.builder() + .bucket(awsProperties.getS3BucketName()) + .key(objectKey) + .build(); + + s3Client.putObject(putObjectRequest, RequestBody.fromFile(file)); + log.info("File uploaded successfully to S3: {}", fileName); + + return awsProperties.getObjectUrl() + objectKey; + } catch (Exception e) { + throw new ServerException(S3_STORAGE_ERROR.getErrorCode()); + } + + } + + public PreSignedUrlResponseDto generatePreSignedUrl(String contentType) { + SimpleDateFormat dateTimeFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"); + dateTimeFormat.setTimeZone(new SimpleTimeZone(0, "UTC")); + SimpleDateFormat dateStampFormat = new SimpleDateFormat("yyyyMMdd"); + dateStampFormat.setTimeZone(new SimpleTimeZone(0, "UTC")); + + Date now = new Date(); + String dateTimeStamp = dateTimeFormat.format(now); + String dateStamp = dateStampFormat.format(now); + + Calendar calendar = Calendar.getInstance(); + calendar.setTime(now); + calendar.add(Calendar.MINUTE, 10); + Date expirationDate = calendar.getTime(); + + String objectKey = getObjectKey(contentType); + String encodedPolicy = generateEncodedPolicy(awsProperties.getS3BucketName(), objectKey, dateTimeStamp, + dateStamp, expirationDate); + + String signature = sign(encodedPolicy, dateStamp, S3_SERVCIE); + String credentials = getCredentials(dateStamp); + + PreSignedUrlFieldResponseDto fieldResponseDto = new PreSignedUrlFieldResponseDto(awsProperties.getContentType(), + objectKey, awsProperties.getS3BucketName(), awsProperties.getAlgorithm(), credentials, dateTimeStamp, + encodedPolicy, + signature); + + return PreSignedUrlResponseDto.of(PRE_SIGNED_URL_PREFIX + "/" + awsProperties.getS3BucketName(), + fieldResponseDto); + } + + /** + * 파일의 경둜λͺ…을 랜덀으둜 생성 + * @author @mikekks + * @param contentType 파일의 컨텐츠 νƒ€μž… + * @returns 파일의 경둜λͺ… + */ + private String getObjectKey(String contentType) { + UUID uuid = UUID.randomUUID(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd"); + String curDate = LocalDateTime.now().format(formatter); + + return MEETING_PATH + "/" + curDate + "/" + uuid + "." + contentType; + } + + /** + * PreSignedUrl μ—…λ‘œλ“œ μ •μ±… 생성 + * @author @mikekks + * @returns base64둜 μΈμ½”λ”©λœ String + */ + public String generateEncodedPolicy(String bucketName, String objectKey, String dateTimeStamp, String dateStamp, + Date expirationDate) { + List conditions = generateConditions(bucketName, objectKey, dateTimeStamp, + dateStamp); + + SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + simpleDateFormat.setTimeZone(new SimpleTimeZone(0, "UTC")); + + Map policy = Map.of( + "expiration", simpleDateFormat.format(expirationDate), + "conditions", conditions + ); + + return encodePolicy(policy); + } + + private List generateConditions(String bucketName, String objectKey, String dateTimeStamp, String dateStamp) { + List contentLengthRange = List.of("content-length-range", awsProperties.getFileMinSize(), + awsProperties.getFileMaxSize()); + + List conditions = new LinkedList<>(); + conditions.add(contentLengthRange); + + Map params = new HashMap<>(); + String credentials = getCredentials(dateStamp); + params.put("Content-Type", awsProperties.getContentType()); + params.put("key", objectKey); + params.put("bucket", bucketName); + params.put("X-Amz-Algorithm", awsProperties.getAlgorithm()); + params.put("X-Amz-Credential", credentials); + params.put("X-Amz-Date", dateTimeStamp); + + Map awsDefaults = generateAwsDefaultParams(bucketName, objectKey, dateTimeStamp, dateStamp); + + conditions.addAll(awsDefaults.entrySet()); + + return conditions; + } + + private Map generateAwsDefaultParams(String bucketName, String objectKey, String dateTimeStamp, + String dateStamp) { + + String credentials = getCredentials(dateStamp); + Map params = new HashMap<>(); + params.put("Content-Type", awsProperties.getContentType()); + params.put("key", objectKey); + params.put("bucket", bucketName); + params.put("X-Amz-Algorithm", awsProperties.getAlgorithm()); + params.put("X-Amz-Credential", credentials); + params.put("X-Amz-Date", dateTimeStamp); + + return params; + } + + private String getCredentials(String dateStamp) { + return String.format("%s/%s/%s/%s/%s", awsProperties.getAccessKey(), dateStamp, awsProperties.getAwsRegion(), + S3_SERVCIE, + awsProperties.getRequestType()); + } + + private String encodePolicy(Map policy) { + try { + ObjectMapper objectMapper = new ObjectMapper(); + String jsonPolicy = objectMapper.writeValueAsString(policy); + + return Base64.getEncoder().encodeToString(jsonPolicy.getBytes()); + } catch (Exception e) { + throw new ServerException(S3_STORAGE_ERROR.getErrorCode()); + } + } + + public String sign(String toSign, String dateStamp, String service) { + try { + String awsSecretKey = awsProperties.getSecretKey(); + + byte[] kSecret = ("AWS4" + awsSecretKey).getBytes(StandardCharsets.UTF_8); + byte[] kDate = hmacSHA256(dateStamp, kSecret); + byte[] kRegion = hmacSHA256("ap-northeast-2", kDate); + byte[] kService = hmacSHA256(service, kRegion); + byte[] kSigning = hmacSHA256("aws4_request", kService); + byte[] signature = hmacSHA256(toSign, kSigning); + + return BinaryUtils.toHex(signature); + } catch (Exception e) { + throw new ServerException(S3_STORAGE_ERROR.getErrorCode()); + } + } + + private byte[] hmacSHA256(String data, byte[] key) { + try { + String algorithm = "HmacSHA256"; + Mac mac = Mac.getInstance(algorithm); + mac.init(new SecretKeySpec(key, algorithm)); + return mac.doFinal(data.getBytes(StandardCharsets.UTF_8)); + } catch (Exception e) { + throw new ServerException(S3_STORAGE_ERROR.getErrorCode()); + } + } + +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/internal/notification/PushNotificationService.java b/main/src/main/java/org/sopt/makers/crew/main/internal/notification/PushNotificationService.java index 947b9b06..830da233 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/internal/notification/PushNotificationService.java +++ b/main/src/main/java/org/sopt/makers/crew/main/internal/notification/PushNotificationService.java @@ -1,6 +1,6 @@ package org.sopt.makers.crew.main.internal.notification; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.*; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.*; import static org.sopt.makers.crew.main.internal.notification.PushNotificationEnums.PUSH_NOTIFICATION_ACTION; import java.util.UUID; diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java index 4331585e..e507c64a 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java @@ -5,26 +5,38 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; +import jakarta.websocket.server.PathParam; + import java.security.Principal; import java.util.List; + +import org.sopt.makers.crew.main.common.dto.TempResponseDto; +import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingGetAppliesCsvQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingGetAppliesQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingByOrgUserQueryDto; +import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingQueryDto; +import org.sopt.makers.crew.main.meeting.v2.dto.request.ApplyV2UpdateStatusBodyDto; import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2ApplyMeetingDto; import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2CreateMeetingBodyDto; +import org.sopt.makers.crew.main.meeting.v2.dto.response.AppliesCsvFileUrlResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingGetApplyListResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2ApplyMeetingResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2CreateMeetingResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetAllMeetingByOrgUserDto; +import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetAllMeetingDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetMeetingBannerResponseDto; +import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetMeetingByIdResponseDto; +import org.sopt.makers.crew.main.meeting.v2.dto.response.PreSignedUrlResponseDto; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; - +import org.springframework.web.bind.annotation.RequestParam; @Tag(name = "λͺ¨μž„") public interface MeetingV2Api { @@ -71,4 +83,69 @@ ResponseEntity applyMeetingCancel(@PathVariable Integer meetingId, ResponseEntity findApplyList(@PathVariable Integer meetingId, @ModelAttribute MeetingGetAppliesQueryDto queryCommand, Principal principal); + + @Operation(summary = "λͺ¨μž„ 전체 쑰회/검색/필터링", description = "λͺ¨μž„ 전체 쑰회/검색/필터링\n") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "λͺ¨μž„ μ§€μ›μž/μ°Έμ—¬μž 쑰회 성곡")}) + @Parameters({ + @Parameter(name = "page", description = "νŽ˜μ΄μ§€, default = 1", example = "1", schema = @Schema(type = "integer", format = "int32")), + @Parameter(name = "take", description = "κ°€μ Έμ˜¬ 데이터 개수, default = 12", example = "50", schema = @Schema(type = "integer", format = "int32")), + @Parameter(name = "category", description = "μΉ΄ν…Œκ³ λ¦¬", example = "μŠ€ν„°λ””,번개", schema = @Schema(type = "string", format = "string")), + @Parameter(name = "status", description = "λͺ¨μž„ λͺ¨μ§‘ μƒνƒœ", example = "0,1", schema = @Schema(type = "string", format = "string")), + @Parameter(name = "isOnlyActiveGeneration", description = "ν™œλ™κΈ°μˆ˜λ§Œ μ°Έμ—¬μ—¬λΆ€", example = "true", schema = @Schema(type = "boolean", format = "boolean")), + @Parameter(name = "joinableParts", description = "검색할 ν™œλ™ 파트 닀쀑 선택. OR 쑰건으둜 검색됨
Available values : PM, DESIGN, IOS, ANDROID, SERVER, WEB", example = "PM,DESIGN,IOS,ANDROID,SERVER,WEB", schema = @Schema(type = "array[string]", format = "array[string]")), + @Parameter(name = "query", description = "검색 λ‚΄μš©", example = "κ³ μˆ˜μŠ€ν„°λ”” 검색", schema = @Schema(type = "string", format = "string")), + }) + ResponseEntity getMeetings(@ModelAttribute MeetingV2GetAllMeetingQueryDto queryCommand, + Principal principal); + + @Operation(summary = "[TEMP] λͺ¨μž„ 전체 쑰회/검색/필터링", description = "λͺ¨μž„ 전체 쑰회/검색/필터링\n") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "λͺ¨μž„ μ§€μ›μž/μ°Έμ—¬μž 쑰회 성곡")}) + @Parameters({ + @Parameter(name = "page", description = "νŽ˜μ΄μ§€, default = 1", example = "1", schema = @Schema(type = "integer", format = "int32")), + @Parameter(name = "take", description = "κ°€μ Έμ˜¬ 데이터 개수, default = 12", example = "50", schema = @Schema(type = "integer", format = "int32")), + @Parameter(name = "category", description = "μΉ΄ν…Œκ³ λ¦¬", example = "μŠ€ν„°λ””,번개", schema = @Schema(type = "string", format = "string")), + @Parameter(name = "status", description = "λͺ¨μž„ λͺ¨μ§‘ μƒνƒœ", example = "0,1", schema = @Schema(type = "string", format = "string")), + @Parameter(name = "isOnlyActiveGeneration", description = "ν™œλ™κΈ°μˆ˜λ§Œ μ°Έμ—¬μ—¬λΆ€", example = "true", schema = @Schema(type = "boolean", format = "boolean")), + @Parameter(name = "joinableParts", description = "검색할 ν™œλ™ 파트 닀쀑 선택. OR 쑰건으둜 검색됨
Available values : PM, DESIGN, IOS, ANDROID, SERVER, WEB", example = "PM,DESIGN,IOS,ANDROID,SERVER,WEB", schema = @Schema(type = "array[string]", format = "array[string]")), + @Parameter(name = "query", description = "검색 λ‚΄μš©", example = "κ³ μˆ˜μŠ€ν„°λ”” 검색", schema = @Schema(type = "string", format = "string")), + }) + ResponseEntity> getMeetingsTemp(@ModelAttribute MeetingV2GetAllMeetingQueryDto queryCommand, + Principal principal); + + @Operation(summary = "λͺ¨μž„ μ‚­μ œ", description = "λͺ¨μž„ μ‚­μ œν•©λ‹ˆλ‹€.") + ResponseEntity deleteMeeting(@PathVariable Integer meetingId, Principal principal); + + @Operation(summary = "λͺ¨μž„ μˆ˜μ •", description = "λͺ¨μž„ λ‚΄μš©μ„ μˆ˜μ •ν•©λ‹ˆλ‹€.") + ResponseEntity updateMeeting(@PathVariable Integer meetingId, @RequestBody @Valid MeetingV2CreateMeetingBodyDto requestBody ,Principal principal); + + @Operation(summary = "λͺ¨μž„ μ§€μ›μž μƒνƒœ λ³€κ²½", description = "λͺ¨μž„ μ§€μ›μžμ˜ 지원 μƒνƒœλ₯Ό λ³€κ²½ν•©λ‹ˆλ‹€.") + ResponseEntity updateApplyStatus(@PathVariable Integer meetingId, @RequestBody @Valid ApplyV2UpdateStatusBodyDto requestBody ,Principal principal); + + @Operation(summary = "Meeting 썸넀일 μ—…λ‘œλ“œμš© Pre-Signed URL λ°œκΈ‰", description = "Meeting 썸넀일 μ—…λ‘œλ“œμš© Pre-Signed URL λ°œκΈ‰ν•©λ‹ˆλ‹€.") + ResponseEntity createPreSignedUrl(@PathParam("contentType") String contentType ,Principal principal); + + @Operation(summary = "λͺ¨μž„ μ§€μ›μž λͺ©λ‘ csv 파일 λ‹€μš΄λ‘œλ“œ", description = "λͺ¨μž„ μ§€μ›μž λͺ©λ‘ csv 파일 λ‹€μš΄λ‘œλ“œ") + @Parameters({ + @Parameter(name = "status", description = "0: λŒ€κΈ°, 1: 승인된 μ‹ μ²­μž, 2: 거절된 μ‹ μ²­μž", example = "0,1", required = true, schema = @Schema(type = "string")), + @Parameter(name = "type", description = "0: 지원, 1: μ΄ˆλŒ€", example = "0,1", required = true, schema = @Schema(type = "string")), + @Parameter(name = "order", description = "μ •λ ¬μˆœ", example = "desc", schema = @Schema(type = "string", format = "string"))}) + ResponseEntity getAppliesCsvFileUrl( + @PathVariable Integer meetingId, + @ModelAttribute @Valid @Parameter(hidden = true) MeetingGetAppliesCsvQueryDto queryCommand, + Principal principal); + + @Operation(summary = "[TEMP] λͺ¨μž„ μ§€μ›μž λͺ©λ‘ csv 파일 λ‹€μš΄λ‘œλ“œ", description = "λͺ¨μž„ μ§€μ›μž λͺ©λ‘ csv 파일 λ‹€μš΄λ‘œλ“œ") + @Parameters({ + @Parameter(name = "status", description = "0: λŒ€κΈ°, 1: 승인된 μ‹ μ²­μž, 2: 거절된 μ‹ μ²­μž", example = "0,1", required = true, schema = @Schema(type = "string")), + @Parameter(name = "type", description = "0: 지원, 1: μ΄ˆλŒ€", example = "0,1", required = true, schema = @Schema(type = "string")), + @Parameter(name = "order", description = "μ •λ ¬μˆœ", example = "desc", schema = @Schema(type = "string", format = "string"))}) + ResponseEntity> getAppliesCsvFileUrlTemp( + @PathVariable Integer meetingId, + @ModelAttribute @Valid @Parameter(hidden = true) MeetingGetAppliesCsvQueryDto queryCommand, + Principal principal); + + @Operation(summary = "λͺ¨μž„ 상세 쑰회", description = "λͺ¨μž„ 상세 쑰회") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "λͺ¨μž„ 상세 쑰회 성곡"), + @ApiResponse(responseCode = "400", description = "λͺ¨μž„이 μ—†μŠ΅λ‹ˆλ‹€.", content = @Content),}) + ResponseEntity getMeetingById(@PathVariable Integer meetingId, Principal principal); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java index e05957c8..f4817628 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java @@ -2,19 +2,31 @@ import io.swagger.v3.oas.annotations.Parameter; import jakarta.validation.Valid; + import java.security.Principal; import java.util.List; + import lombok.RequiredArgsConstructor; + +import org.sopt.makers.crew.main.common.dto.TempResponseDto; import org.sopt.makers.crew.main.common.util.UserUtil; +import org.sopt.makers.crew.main.external.s3.service.S3Service; +import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingGetAppliesCsvQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingGetAppliesQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingByOrgUserQueryDto; +import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingQueryDto; +import org.sopt.makers.crew.main.meeting.v2.dto.request.ApplyV2UpdateStatusBodyDto; import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2ApplyMeetingDto; import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2CreateMeetingBodyDto; +import org.sopt.makers.crew.main.meeting.v2.dto.response.AppliesCsvFileUrlResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingGetApplyListResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2ApplyMeetingResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2CreateMeetingResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetAllMeetingByOrgUserDto; +import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetAllMeetingDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetMeetingBannerResponseDto; +import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetMeetingByIdResponseDto; +import org.sopt.makers.crew.main.meeting.v2.dto.response.PreSignedUrlResponseDto; import org.sopt.makers.crew.main.meeting.v2.service.MeetingV2Service; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -23,8 +35,10 @@ import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; @@ -33,62 +47,170 @@ @RequiredArgsConstructor public class MeetingV2Controller implements MeetingV2Api { - private final MeetingV2Service meetingV2Service; - - @Override - @GetMapping("/org-user") - @ResponseStatus(HttpStatus.OK) - public ResponseEntity getAllMeetingByOrgUser( - @ModelAttribute @Parameter(hidden = true) MeetingV2GetAllMeetingByOrgUserQueryDto queryDto) { - return ResponseEntity.ok(meetingV2Service.getAllMeetingByOrgUser(queryDto)); - } - - @Override - @GetMapping("/banner") - @ResponseStatus(HttpStatus.OK) - public ResponseEntity> getMeetingBanner( - Principal principal) { - UserUtil.getUserId(principal); - return ResponseEntity.ok(meetingV2Service.getMeetingBanner()); - } - - @Override - @PostMapping - public ResponseEntity createMeeting( - @Valid @RequestBody MeetingV2CreateMeetingBodyDto requestBody, - Principal principal) { - Integer userId = UserUtil.getUserId(principal); - return ResponseEntity.status(HttpStatus.CREATED).body(meetingV2Service.createMeeting(requestBody, userId)); - } - - @Override - @PostMapping("/apply") - public ResponseEntity applyMeeting( - @Valid @RequestBody MeetingV2ApplyMeetingDto requestBody, - Principal principal) { - Integer userId = UserUtil.getUserId(principal); - return ResponseEntity.status(HttpStatus.CREATED).body(meetingV2Service.applyMeeting(requestBody, userId)); - } - - @Override - @DeleteMapping("/{meetingId}/apply") - @ResponseStatus(HttpStatus.OK) - public ResponseEntity applyMeetingCancel(@PathVariable Integer meetingId, - Principal principal) { - Integer userId = UserUtil.getUserId(principal); - meetingV2Service.applyMeetingCancel(meetingId, userId); - return ResponseEntity.status(HttpStatus.OK).build(); - } - - @Override - @GetMapping("/{meetingId}/list") - public ResponseEntity findApplyList(@PathVariable Integer meetingId, - @ModelAttribute MeetingGetAppliesQueryDto queryCommand, - Principal principal) { - - Integer userId = UserUtil.getUserId(principal); - - return ResponseEntity.status(HttpStatus.OK) - .body(meetingV2Service.findApplyList(queryCommand, meetingId, userId)); - } + private final MeetingV2Service meetingV2Service; + + private final S3Service s3Service; + + @Override + @GetMapping("/org-user") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity getAllMeetingByOrgUser( + @ModelAttribute @Parameter(hidden = true) MeetingV2GetAllMeetingByOrgUserQueryDto queryDto) { + return ResponseEntity.ok(meetingV2Service.getAllMeetingByOrgUser(queryDto)); + } + + @Override + @GetMapping("/banner") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity> getMeetingBanner( + Principal principal) { + UserUtil.getUserId(principal); + return ResponseEntity.ok(meetingV2Service.getMeetingBanner()); + } + + @Override + @PostMapping + public ResponseEntity createMeeting( + @Valid @RequestBody MeetingV2CreateMeetingBodyDto requestBody, + Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.status(HttpStatus.CREATED).body(meetingV2Service.createMeeting(requestBody, userId)); + } + + @Override + @PostMapping("/apply") + public ResponseEntity applyMeeting( + @Valid @RequestBody MeetingV2ApplyMeetingDto requestBody, + Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.status(HttpStatus.CREATED).body(meetingV2Service.applyMeeting(requestBody, userId)); + } + + @Override + @DeleteMapping("/{meetingId}/apply") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity applyMeetingCancel(@PathVariable Integer meetingId, + Principal principal) { + Integer userId = UserUtil.getUserId(principal); + meetingV2Service.applyMeetingCancel(meetingId, userId); + return ResponseEntity.status(HttpStatus.OK).build(); + } + + @Override + @GetMapping("/{meetingId}/list") + public ResponseEntity findApplyList(@PathVariable Integer meetingId, + @ModelAttribute MeetingGetAppliesQueryDto queryCommand, + Principal principal) { + + Integer userId = UserUtil.getUserId(principal); + + return ResponseEntity.status(HttpStatus.OK) + .body(meetingV2Service.findApplyList(queryCommand, meetingId, userId)); + } + + @Override + @GetMapping + public ResponseEntity getMeetings( + @ModelAttribute @Valid MeetingV2GetAllMeetingQueryDto queryCommand, + Principal principal) { + + MeetingV2GetAllMeetingDto meetings = meetingV2Service.getMeetings(queryCommand); + return ResponseEntity.ok().body(meetings); + } + + @Override + @GetMapping("/temp") + public ResponseEntity> getMeetingsTemp( + MeetingV2GetAllMeetingQueryDto queryCommand, Principal principal) { + MeetingV2GetAllMeetingDto meetings = meetingV2Service.getMeetings(queryCommand); + return ResponseEntity.ok().body(TempResponseDto.of(meetings)); + } + + @Override + @DeleteMapping("/{meetingId}") + public ResponseEntity deleteMeeting(@PathVariable Integer meetingId, Principal principal) { + Integer userId = UserUtil.getUserId(principal); + meetingV2Service.deleteMeeting(meetingId, userId); + + return ResponseEntity.ok().build(); + } + + @Override + @PutMapping("/{meetingId}") + public ResponseEntity updateMeeting( + @PathVariable Integer meetingId, + @RequestBody @Valid MeetingV2CreateMeetingBodyDto requestBody, + Principal principal) { + + Integer userId = UserUtil.getUserId(principal); + meetingV2Service.updateMeeting(meetingId, requestBody, userId); + + return ResponseEntity.ok().build(); + } + + @Override + @PutMapping("/{meetingId}/apply/status") + public ResponseEntity updateApplyStatus( + @PathVariable Integer meetingId, + @RequestBody @Valid ApplyV2UpdateStatusBodyDto requestBody, + Principal principal) { + + Integer userId = UserUtil.getUserId(principal); + meetingV2Service.updateApplyStatus(meetingId, requestBody, userId); + + return ResponseEntity.ok().build(); + } + + @Override + @GetMapping("/presigned-url") + public ResponseEntity createPreSignedUrl( + @RequestParam("contentType") String contentType, Principal principal) { + PreSignedUrlResponseDto responseDto = s3Service.generatePreSignedUrl(contentType); + + return ResponseEntity.ok(responseDto); + } + + @Override + @GetMapping("/{meetingId}/list/csv") + public ResponseEntity getAppliesCsvFileUrl( + @PathVariable Integer meetingId, + @ModelAttribute @Valid MeetingGetAppliesCsvQueryDto queryCommand, + Principal principal) { + + Integer userId = UserUtil.getUserId(principal); + + // TODO: FE μ—μ„œ request κ°’ λ³€κ²½ν•˜λ„λ‘ μš”μ²­ ν•„μš” + List statuses = List.of(0, 1, 2); + + AppliesCsvFileUrlResponseDto responseDto = meetingV2Service.getAppliesCsvFileUrl(meetingId, statuses, + queryCommand.getOrder(), userId); + + return ResponseEntity.ok(responseDto); + } + + @Override + @GetMapping("/{meetingId}/list/csv/temp") + public ResponseEntity> getAppliesCsvFileUrlTemp( + @PathVariable Integer meetingId, + @ModelAttribute @Valid MeetingGetAppliesCsvQueryDto queryCommand, + Principal principal) { + Integer userId = UserUtil.getUserId(principal); + + // TODO: FE μ—μ„œ request κ°’ λ³€κ²½ν•˜λ„λ‘ μš”μ²­ ν•„μš” + List statuses = List.of(0, 1, 2); + + AppliesCsvFileUrlResponseDto responseDto = meetingV2Service.getAppliesCsvFileUrl(meetingId, statuses, + queryCommand.getOrder(), userId); + + return ResponseEntity.ok(TempResponseDto.of(responseDto)); + } + + @Override + @GetMapping("/{meetingId}") + public ResponseEntity getMeetingById(@PathVariable Integer meetingId, + Principal principal) { + Integer userId = UserUtil.getUserId(principal); + + return ResponseEntity.ok(meetingV2Service.getMeetingById(meetingId, userId)); + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/query/MeetingGetAppliesCsvQueryDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/query/MeetingGetAppliesCsvQueryDto.java new file mode 100644 index 00000000..1374f688 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/query/MeetingGetAppliesCsvQueryDto.java @@ -0,0 +1,32 @@ +package org.sopt.makers.crew.main.meeting.v2.dto.query; + +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Schema(description = "λͺ¨μž„ μ§€μ›μž csv 파일 μš”μ²­ dto") +public class MeetingGetAppliesCsvQueryDto { + + @NotNull + @Schema(example = "[0,1]", description = "0: λŒ€κΈ°, 1: 승인된 μ‹ μ²­μž, 2: 거절된 μ‹ μ²­μž") + private List status; + + @NotNull + @Schema(example = "0", description = "0: 지원, 1: μ΄ˆλŒ€") + private List type; + + @NotNull + @Schema(example = "asc", description = "μ •λ ¬μˆœ") + private String order; + + public MeetingGetAppliesCsvQueryDto(List status, List type, String order) { + this.status = status; + this.type = type; + this.order = order; + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/query/MeetingV2GetAllMeetingQueryDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/query/MeetingV2GetAllMeetingQueryDto.java new file mode 100644 index 00000000..26b84e08 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/query/MeetingV2GetAllMeetingQueryDto.java @@ -0,0 +1,30 @@ +package org.sopt.makers.crew.main.meeting.v2.dto.query; + +import java.util.List; + +import org.sopt.makers.crew.main.common.pagination.dto.PageOptionsDto; +import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart; + +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class MeetingV2GetAllMeetingQueryDto extends PageOptionsDto { + + + List category; + + List status; + @NotNull + Boolean isOnlyActiveGeneration; + @NotNull + MeetingJoinablePart[] joinableParts; + @NotNull + String query; + + public MeetingV2GetAllMeetingQueryDto(Integer page, Integer take) { + super(page, take); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/request/ApplyV2UpdateStatusBodyDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/request/ApplyV2UpdateStatusBodyDto.java new file mode 100644 index 00000000..3bf6e289 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/request/ApplyV2UpdateStatusBodyDto.java @@ -0,0 +1,20 @@ +package org.sopt.makers.crew.main.meeting.v2.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Schema(description = "λͺ¨μž„ μ§€μ›μž μƒνƒœ λ³€κ²½ request body dto") +public class ApplyV2UpdateStatusBodyDto { + @Schema(example = "1", description = "μ‹ μ²­/지원 id") + @NotNull + private final Integer applyId; + + @Schema(example = "0", description = "0: λŒ€κΈ°, 1: 승인, 2: 거절") + @NotNull + private final Integer status; + +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/request/MeetingV2CreateMeetingBodyDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/request/MeetingV2CreateMeetingBodyDto.java index dfb14240..b46fc5d7 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/request/MeetingV2CreateMeetingBodyDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/request/MeetingV2CreateMeetingBodyDto.java @@ -4,90 +4,93 @@ import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; + import java.util.List; + import lombok.AllArgsConstructor; import lombok.Getter; + import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart; @Getter @AllArgsConstructor -@Schema(description = "λͺ¨μž„ 생성 request body dto") +@Schema(description = "λͺ¨μž„ 생성 및 μˆ˜μ • request body dto") public class MeetingV2CreateMeetingBodyDto { - @Schema(example = "μ•Œκ³ λ³΄λ©΄ μ“Έλ°μžˆλŠ” 개발 ν”„λ‘œμ„ΈμŠ€", description = "λͺ¨μž„ 제λͺ©") - @NotNull - private String title; - - @Schema(example = "[\n" - + " \"https://makers-web-img.s3.ap-northeast-2.amazonaws.com/meeting/2023/04/12/7bd87736-b557-4b26-a0d5-9b09f1f1d7df\"\n" - + " ]", description = "λͺ¨μž„ 이미지 리슀트, μ΅œλŒ€ 6개") - @NotEmpty - @Size(max=6) - private List files; - - @Schema(example = "μŠ€ν„°λ””", description = "λͺ¨μž„ μΉ΄ν…Œκ³ λ¦¬") - @NotNull - private String category; - - @Schema(example = "2022.10.08", description = "λͺ¨μ§‘ κΈ°κ°„ μ‹œμž‘ λ‚ μ§œ") - @NotNull - private String startDate; - - @Schema(example = "2022.10.09", description = "λͺ¨μ§‘ κΈ°κ°„ 끝 λ‚ μ§œ") - @NotNull - private String endDate; - - @Schema(example = "5", description = "λͺ¨μ§‘ 인원") - @NotNull - private Integer capacity; - - @Schema(example = "api κ°€ ν„°μ‘Œλ‹€κ³ ? 깃이 ν„°μ‘Œλ‹€κ³ ?", description = "λͺ¨μ§‘ 정보") - @NotNull - private String desc; - - @Schema(example = "μ†Œμš” μ‹œκ°„ : 1μ‹œκ°„ μ˜ˆμƒ", description = "진행 방식 μ†Œκ°œ") - @NotNull - private String processDesc; - - @Schema(example = "2022.10.29", description = "λͺ¨μž„ ν™œλ™ μ‹œμž‘ λ‚ μ§œ", name = "mStartDate") - @NotNull - private String mStartDate; - - @Schema(example = "2022.10.30", description = "λͺ¨μž„ ν™œλ™ μ’…λ£Œ λ‚ μ§œ", name = "mEndDate") - @NotNull - private String mEndDate; - - @Schema(example = "μ•ˆλ…•ν•˜μ„Έμš” 기획 파트 000μž…λ‹ˆλ‹€", description = "κ°œμ„€μž μ†Œκ°œ") - @NotNull - private String leaderDesc; - - @Schema(example = "개발 λͺ¨λ₯΄λŠ” μ‚¬λžŒλ„ ν™˜μ˜", description = "λͺ¨μ§‘ λŒ€μƒ μ†Œκ°œ") - @NotNull - private String targetDesc; - - @Schema(example = "μœ μ˜ν•  사항", description = "μœ μ˜ν•  사항") - private String note; - - @Schema(example = "false", description = "λ©˜ν†  ν•„μš” μ—¬λΆ€") - @NotNull - private Boolean isMentorNeeded; - - @Schema(example = "false", description = "ν™œλ™κΈ°μˆ˜λ§Œ 지원 κ°€λŠ₯ μ—¬λΆ€") - @NotNull - private Boolean canJoinOnlyActiveGeneration; - - @Schema(example = "[\n" - + " \"ANDROID\",\n" - + " \"IOS\"\n" - + " ]", description = "λŒ€μƒ 파트 λͺ©λ‘") - @NotNull - private MeetingJoinablePart[] joinableParts; - - public String getmStartDate() { - return mStartDate; - } - - public String getmEndDate() { - return mEndDate; - } + @Schema(example = "μ•Œκ³ λ³΄λ©΄ μ“Έλ°μžˆλŠ” 개발 ν”„λ‘œμ„ΈμŠ€", description = "λͺ¨μž„ 제λͺ©") + @NotNull + private String title; + + @Schema(example = "[\n" + + " \"https://makers-web-img.s3.ap-northeast-2.amazonaws.com/meeting/2023/04/12/7bd87736-b557-4b26-a0d5-9b09f1f1d7df\"\n" + + " ]", description = "λͺ¨μž„ 이미지 리슀트, μ΅œλŒ€ 6개") + @NotEmpty + @Size(max = 6) + private List files; + + @Schema(example = "μŠ€ν„°λ””", description = "λͺ¨μž„ μΉ΄ν…Œκ³ λ¦¬") + @NotNull + private String category; + + @Schema(example = "2022.10.08", description = "λͺ¨μ§‘ κΈ°κ°„ μ‹œμž‘ λ‚ μ§œ") + @NotNull + private String startDate; + + @Schema(example = "2022.10.09", description = "λͺ¨μ§‘ κΈ°κ°„ 끝 λ‚ μ§œ") + @NotNull + private String endDate; + + @Schema(example = "5", description = "λͺ¨μ§‘ 인원") + @NotNull + private Integer capacity; + + @Schema(example = "api κ°€ ν„°μ‘Œλ‹€κ³ ? 깃이 ν„°μ‘Œλ‹€κ³ ?", description = "λͺ¨μ§‘ 정보") + @NotNull + private String desc; + + @Schema(example = "μ†Œμš” μ‹œκ°„ : 1μ‹œκ°„ μ˜ˆμƒ", description = "진행 방식 μ†Œκ°œ") + @NotNull + private String processDesc; + + @Schema(example = "2022.10.29", description = "λͺ¨μž„ ν™œλ™ μ‹œμž‘ λ‚ μ§œ", name = "mStartDate") + @NotNull + private String mStartDate; + + @Schema(example = "2022.10.30", description = "λͺ¨μž„ ν™œλ™ μ’…λ£Œ λ‚ μ§œ", name = "mEndDate") + @NotNull + private String mEndDate; + + @Schema(example = "μ•ˆλ…•ν•˜μ„Έμš” 기획 파트 000μž…λ‹ˆλ‹€", description = "κ°œμ„€μž μ†Œκ°œ") + @NotNull + private String leaderDesc; + + @Schema(example = "개발 λͺ¨λ₯΄λŠ” μ‚¬λžŒλ„ ν™˜μ˜", description = "λͺ¨μ§‘ λŒ€μƒ μ†Œκ°œ") + @NotNull + private String targetDesc; + + @Schema(example = "μœ μ˜ν•  사항", description = "μœ μ˜ν•  사항") + private String note; + + @Schema(example = "false", description = "λ©˜ν†  ν•„μš” μ—¬λΆ€") + @NotNull + private Boolean isMentorNeeded; + + @Schema(example = "false", description = "ν™œλ™κΈ°μˆ˜λ§Œ 지원 κ°€λŠ₯ μ—¬λΆ€") + @NotNull + private Boolean canJoinOnlyActiveGeneration; + + @Schema(example = "[\n" + + " \"ANDROID\",\n" + + " \"IOS\"\n" + + " ]", description = "λŒ€μƒ 파트 λͺ©λ‘") + @NotNull + private MeetingJoinablePart[] joinableParts; + + public String getmStartDate() { + return mStartDate; + } + + public String getmEndDate() { + return mEndDate; + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplicantByMeetingDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplicantByMeetingDto.java new file mode 100644 index 00000000..971f5007 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplicantByMeetingDto.java @@ -0,0 +1,44 @@ +package org.sopt.makers.crew.main.meeting.v2.dto.response; + +import java.util.List; + +import org.sopt.makers.crew.main.entity.user.User; +import org.sopt.makers.crew.main.entity.user.vo.UserActivityVO; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +@Schema(name = "ApplicantByMeetingDto", description = "λͺ¨μž„ μ‹ μ²­μž 객체 Dto") +public class ApplicantByMeetingDto { + + @Schema(description = "μ‹ μ²­μž id, ν¬λ£¨μ—μ„œ μ‚¬μš©ν•˜λŠ” userId", example = "1") + @NotNull + private final Integer id; + + @Schema(description = "μ‹ μ²­μž 이름", example = "μ†‘λ―Όκ·œ") + @NotNull + private final String name; + + @Schema(description = "μ‹ μ²­μž org id, λ©”μ΄μ»€μŠ€ ν”„λ‘œλ•νŠΈμ—μ„œ λ²”μš©μ μœΌλ‘œ μ‚¬μš©ν•˜λŠ” userId", example = "1") + @NotNull + private final Integer orgId; + + @Schema(description = "μ‹ μ²­μž 기수 정보", example = "[{\"part\": \"μ›Ή\", \"generation\": 32}]") + @NotNull + private final List activities; + + @Schema(description = "μ‹ μ²­μž ν”„λ‘œν•„ 사진", example = "[url] ν˜•μ‹") + private final String profileImage; + + @Schema(description = "μ‹ μ²­μž ν•Έλ“œν° 번호", example = "010-1234-5678") + private final String phone; + + public static ApplicantByMeetingDto of(User user){ + return new ApplicantByMeetingDto(user.getId(), user.getName(), user.getOrgId(), user.getActivities(), + user.getProfileImage(), user.getPhone()); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplicantDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplicantDto.java index 4eb516a3..9a23b106 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplicantDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplicantDto.java @@ -1,30 +1,53 @@ package org.sopt.makers.crew.main.meeting.v2.dto.response; import com.querydsl.core.annotations.QueryProjection; + import java.util.Comparator; import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Getter; + import org.sopt.makers.crew.main.common.util.UserUtil; import org.sopt.makers.crew.main.entity.user.vo.UserActivityVO; @Getter +@Schema(name = "ApplicantDto", description = "λͺ¨μž„ μ‹ μ²­μž 객체 Dto") public class ApplicantDto { - private final Integer id; - private final String name; - private final Integer orgId; - private final UserActivityVO recentActivity; - private final String profileImage; - private final String phone; - - @QueryProjection - public ApplicantDto(Integer id, String name, Integer orgId, List userActivityVOs, String profileImage, - String phone) { - - this.id = id; - this.name = name; - this.orgId = orgId; - this.recentActivity = UserUtil.getRecentUserActivity(userActivityVOs); - this.profileImage = profileImage; - this.phone = phone; - } + + @Schema(description = "μ‹ μ²­μž id, ν¬λ£¨μ—μ„œ μ‚¬μš©ν•˜λŠ” userId", example = "1") + @NotNull + private final Integer id; + + @Schema(description = "μ‹ μ²­μž 이름", example = "μ†‘λ―Όκ·œ") + @NotNull + private final String name; + + @Schema(description = "μ‹ μ²­μž org id, λ©”μ΄μ»€μŠ€ ν”„λ‘œλ•νŠΈμ—μ„œ λ²”μš©μ μœΌλ‘œ μ‚¬μš©ν•˜λŠ” userId", example = "1") + @NotNull + private final Integer orgId; + + @Schema(description = "μ‹ μ²­μž 기수 정보", example = "[{\"part\": \"μ›Ή\", \"generation\": 32}]") + @NotNull + private final UserActivityVO recentActivity; + + @Schema(description = "μ‹ μ²­μž ν”„λ‘œν•„ 사진", example = "[url] ν˜•μ‹") + private final String profileImage; + + @Schema(description = "μ‹ μ²­μž ν•Έλ“œν° 번호", example = "010-1234-5678") + private final String phone; + + @QueryProjection + public ApplicantDto(Integer id, String name, Integer orgId, List userActivityVOs, + String profileImage, + String phone) { + + this.id = id; + this.name = name; + this.orgId = orgId; + this.recentActivity = UserUtil.getRecentUserActivity(userActivityVOs); + this.profileImage = profileImage; + this.phone = phone; + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/AppliesCsvFileUrlResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/AppliesCsvFileUrlResponseDto.java new file mode 100644 index 00000000..cd4437be --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/AppliesCsvFileUrlResponseDto.java @@ -0,0 +1,20 @@ +package org.sopt.makers.crew.main.meeting.v2.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +@Schema(name = "AppliesCsvFileUrlResponseDto", description = "csv url Dto") +public class AppliesCsvFileUrlResponseDto { + + @NotNull + @Schema(description = "csv 파일 url", example = "[url] ν˜•μ‹") + private final String url; + + public static AppliesCsvFileUrlResponseDto of(String url){ + return new AppliesCsvFileUrlResponseDto(url); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplyInfoDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplyInfoDto.java index 23148265..4e765c50 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplyInfoDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplyInfoDto.java @@ -2,15 +2,33 @@ import com.querydsl.core.annotations.QueryProjection; import java.time.LocalDateTime; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Getter; import org.sopt.makers.crew.main.entity.apply.enums.EnApplyStatus; @Getter +@Schema(name = "ApplyInfoDto", description = "λͺ¨μž„ μ‹ μ²­ 객체 Dto") public class ApplyInfoDto { + + @Schema(description = "μ‹ μ²­ id", example = "1") + @NotNull private final Integer id; + + @Schema(description = "μ „ν•˜λŠ” 말", example = "μ € λ½‘μ•„μ£Όμ„Έμš”.") private final String content; + + @Schema(description = "μ‹ μ²­ μ‹œκ°„", example = "2024-07-30T15:30:00") + @NotNull private final LocalDateTime appliedDate; + + @Schema(description = "μ‹ μ²­ μƒνƒœ", example = "1") + @NotNull private final EnApplyStatus status; + + @Schema(description = "μ‹ μ²­μž 정보", example = "") + @NotNull private final ApplicantDto user; @QueryProjection diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplyWholeInfoDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplyWholeInfoDto.java new file mode 100644 index 00000000..a57e508d --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplyWholeInfoDto.java @@ -0,0 +1,58 @@ +package org.sopt.makers.crew.main.meeting.v2.dto.response; + +import java.time.LocalDateTime; + +import org.sopt.makers.crew.main.entity.apply.Apply; +import org.sopt.makers.crew.main.entity.user.User; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +@Schema(description = "μ‹ μ²­ 정보 쑰회 dto") +public class ApplyWholeInfoDto { + + @Schema(description = "μ‹ μ²­ id", example = "3") + @NotNull + private final Integer id; + + @Schema(description = "μ‹ μ²­ νƒ€μž…", example = "0") + @NotNull + private final Integer type; + + @Schema(description = "λͺ¨μž„ id", example = "13") + @NotNull + private final Integer meetingId; + + @Schema(description = "μ‹ μ²­μž id", example = "184") + @NotNull + private final Integer userId; + + @Schema(description = "μ‹ μ²­ λ‚΄μš©", example = "λͺ¨μž„μž₯μ—κ²Œ μ „ν•˜λŠ” λ§μž…λ‹ˆλ‹€.") + @NotNull + private final String content; + + @Schema(description = "μ‹ μ²­ λ‚ μ§œ 및 μ‹œκ°„", example = "2024-10-13T23:59:59") + @NotNull + private final LocalDateTime appliedDate; + + @Schema(description = "μ‹ μ²­ μƒνƒœ", example = "1") + @NotNull + private final Integer status; + + @Schema(description = "μ‹ μ²­μž 객체", example = "") + @NotNull + private final ApplicantByMeetingDto user; + + public static ApplyWholeInfoDto of(Apply apply, User user, Integer requestUserId) { + + ApplicantByMeetingDto applicantByMeetingDto = ApplicantByMeetingDto.of(user); + + return new ApplyWholeInfoDto(apply.getId(), apply.getType().getValue(), apply.getMeetingId(), apply.getUserId(), + apply.getContent(requestUserId), apply.getAppliedDate(), apply.getStatus().getValue(), + applicantByMeetingDto); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingGetApplyListResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingGetApplyListResponseDto.java index f1e85883..87e50fca 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingGetApplyListResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingGetApplyListResponseDto.java @@ -1,14 +1,24 @@ package org.sopt.makers.crew.main.meeting.v2.dto.response; import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; + import org.sopt.makers.crew.main.common.pagination.dto.PageMetaDto; @Getter @AllArgsConstructor(staticName = "of") +@Schema(name = "MeetingGetApplyListResponseDto", description = "λͺ¨μž„ μ‹ μ²­ λͺ©λ‘ 응닡 Dto") public class MeetingGetApplyListResponseDto { - private final List apply; - private final PageMetaDto meta; + @Schema(description = "μ‹ μ²­ λͺ©λ‘", example = "") + @NotNull + private final List apply; + + @NotNull + private final PageMetaDto meta; + } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2ApplyMeetingResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2ApplyMeetingResponseDto.java index 70ee5a62..790a264a 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2ApplyMeetingResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2ApplyMeetingResponseDto.java @@ -1,10 +1,17 @@ package org.sopt.makers.crew.main.meeting.v2.dto.response; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor(staticName = "of") +@Schema(name = "MeetingV2ApplyMeetingResponseDto", description = "λͺ¨μž„ μ‹ μ²­ 응닡 Dto") public class MeetingV2ApplyMeetingResponseDto { - private Integer applyId; + + @Schema(description = "μ‹ μ²­ id", example = "1") + @NotNull + private Integer applyId; + } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2CreateMeetingResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2CreateMeetingResponseDto.java index 40015dee..031ae3f8 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2CreateMeetingResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2CreateMeetingResponseDto.java @@ -1,10 +1,16 @@ package org.sopt.makers.crew.main.meeting.v2.dto.response; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor(staticName = "of") +@Schema(name = "MeetingV2CreateMeetingResponseDto", description = "λͺ¨μž„ 생성 응닡 Dto") public class MeetingV2CreateMeetingResponseDto { - private Integer meetingId; + + @Schema(description = "λͺ¨μž„ id", example = "1") + @NotNull + private Integer meetingId; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetAllMeetingByOrgUserDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetAllMeetingByOrgUserDto.java index 17f509a2..2fea813b 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetAllMeetingByOrgUserDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetAllMeetingByOrgUserDto.java @@ -1,15 +1,24 @@ package org.sopt.makers.crew.main.meeting.v2.dto.response; import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; + import org.sopt.makers.crew.main.common.pagination.dto.PageMetaDto; @Getter @AllArgsConstructor(staticName = "of") +@Schema(name = "MeetingV2GetAllMeetingByOrgUserDto", description = "λͺ¨μž„ 쑰회 응닡 Dto") public class MeetingV2GetAllMeetingByOrgUserDto { - private List meetings; + @Schema(description = "λͺ¨μž„ 객체 λͺ©λ‘", example = "") + @NotNull + private List meetings; + + @NotNull + private PageMetaDto meta; - private PageMetaDto meta; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetAllMeetingByOrgUserMeetingDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetAllMeetingByOrgUserMeetingDto.java index 3ea3c420..4640d7b0 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetAllMeetingByOrgUserMeetingDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetAllMeetingByOrgUserMeetingDto.java @@ -1,19 +1,48 @@ package org.sopt.makers.crew.main.meeting.v2.dto.response; import java.time.LocalDateTime; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor(staticName = "of") +@Schema(name = "MeetingV2GetAllMeetingByOrgUserMeetingDto", description = "λͺ¨μž„ 객체 Dto") public class MeetingV2GetAllMeetingByOrgUserMeetingDto { - private Integer id; - private Boolean isMeetingLeader; - private String title; - private String imageUrl; - private String category; - private LocalDateTime mStartDate; - private LocalDateTime mEndDate; - private Boolean isActiveMeeting; + @Schema(description = "λͺ¨μž„ id", example = "1") + @NotNull + private Integer id; + + @Schema(description = "λͺ¨μž„μž₯ μ—¬λΆ€", example = "true") + @NotNull + private Boolean isMeetingLeader; + + @Schema(description = "λͺ¨μž„ 제λͺ©", example = "λͺ¨μž„ 제λͺ©μž…λ‹ˆλ‹€1") + @NotNull + private String title; + + @Schema(description = "λͺ¨μž„ 사진", example = "[url] ν˜•μ‹") + @NotNull + private String imageUrl; + + @Schema(description = "λͺ¨μž„ λΆ„λ₯˜", example = "μŠ€ν„°λ””") + @NotNull + private String category; + + @Schema(description = "ν™œλ™ μ‹œμž‘ λ‚ μ§œ", example = "2024-07-31T15:30:00") + @NotNull + private LocalDateTime mStartDate; + + @Schema(description = "ν™œλ™ μ’…λ£Œ λ‚ μ§œ", example = "2024-08-15T15:30:00") + @NotNull + private LocalDateTime mEndDate; + + @Schema(description = "λͺ¨μž„ ν™œμ„± μ—¬λΆ€", example = "true") + @NotNull + private Boolean isActiveMeeting; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetAllMeetingDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetAllMeetingDto.java new file mode 100644 index 00000000..5d17f6d9 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetAllMeetingDto.java @@ -0,0 +1,23 @@ +package org.sopt.makers.crew.main.meeting.v2.dto.response; + +import java.util.List; + +import org.sopt.makers.crew.main.common.dto.MeetingResponseDto; +import org.sopt.makers.crew.main.common.pagination.dto.PageMetaDto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; + +@Schema(name = "MeetingV2GetAllMeetingDto", description = "λͺ¨μž„ 쑰회 응닡 Dto") +public record MeetingV2GetAllMeetingDto( + @Schema(description = "λͺ¨μž„ 객체 λͺ©λ‘", example = "") + @NotNull + List meetings, + @Schema(description = "νŽ˜μ΄μ§€λ„€μ΄μ…˜ 객체", example = "") + @NotNull + PageMetaDto meta +) { + public static MeetingV2GetAllMeetingDto of(List meetings, PageMetaDto meta){ + return new MeetingV2GetAllMeetingDto(meetings, meta); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingBannerResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingBannerResponseDto.java index 3e8d647a..51356df3 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingBannerResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingBannerResponseDto.java @@ -2,57 +2,123 @@ import java.time.LocalDateTime; import java.util.List; -import java.util.Optional; + +import org.sopt.makers.crew.main.entity.meeting.Meeting; import org.sopt.makers.crew.main.entity.meeting.enums.MeetingCategory; import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart; import org.sopt.makers.crew.main.entity.meeting.vo.ImageUrlVO; -import lombok.AllArgsConstructor; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Getter; +import lombok.RequiredArgsConstructor; @Getter -@AllArgsConstructor(staticName = "of") +@RequiredArgsConstructor +@Schema(name = "MeetingV2GetMeetingBannerResponseDto", description = "λͺ¨μž„ λ°°λ„ˆ 응닡 Dto") public class MeetingV2GetMeetingBannerResponseDto { - /** λͺ¨μž„ ID */ - private Integer id; - /** μœ μ € Crew ID */ - private Integer userId; - /** λͺ¨μž„ 제λͺ© */ - private String title; - /** - * λͺ¨μž„ μΉ΄ν…Œκ³ λ¦¬ - * - * @apiNote 'μŠ€ν„°λ””', '행사' - */ - private MeetingCategory category; - /** - * 썸넀일 이미지 - * - * @apiNote μ—¬λŸ¬κ°œμ—¬λ„ 첫번째 μ΄λ―Έμ§€λ§Œ μ‚¬μš© - */ - private List imageURL; - /** λͺ¨μž„ ν™œλ™ μ‹œμž‘μΌ */ - private LocalDateTime mStartDate; - /** λͺ¨μž„ ν™œλ™ μ’…λ£ŒμΌ */ - private LocalDateTime mEndDate; - /** λͺ¨μž„ λͺ¨μ§‘ μ‹œμž‘μΌ */ - private LocalDateTime startDate; - /** λͺ¨μž„ λͺ¨μ§‘ μ’…λ£ŒμΌ */ - private LocalDateTime endDate; - /** λͺ¨μž„ 인원 */ - private Integer capacity; - /** 졜근 ν™œλ™ 일자 */ - private Optional recentActivityDate; - /** λͺ¨μž„ νƒ€κ²Ÿ 기수 */ - private Integer targetActiveGeneration; - /** λͺ¨μž„ νƒ€κ²Ÿ 파트 */ - private MeetingJoinablePart[] joinableParts; - /** μ§€μ›μž 수 */ - private Integer applicantCount; - /** κ°€μž…λœ μ§€μ›μž 수 */ - private Integer approvedUserCount; - /** κ°œμ„€μž 정보 */ - private MeetingV2GetMeetingBannerResponseUserDto user; - /** λ―ΈνŒ… μƒνƒœ */ - private Integer status; + /** λͺ¨μž„ ID */ + @Schema(description = "λͺ¨μž„ id", example = "1") + @NotNull + private final Integer id; + + /** μœ μ € Crew ID */ + @Schema(description = "ν¬λ£¨μ—μ„œ μ‚¬μš©ν•˜λŠ” userId", example = "1") + @NotNull + private final Integer userId; + + /** λͺ¨μž„ 제λͺ© */ + @Schema(description = "λͺ¨μž„ 제λͺ©", example = "λͺ¨μž„ 제λͺ©μž…λ‹ˆλ‹€1") + @NotNull + private final String title; + + /** + * λͺ¨μž„ μΉ΄ν…Œκ³ λ¦¬ + * + * @apiNote 'μŠ€ν„°λ””', '행사' + */ + @Schema(description = "λͺ¨μž„ μΉ΄ν…Œκ³ λ¦¬", example = "μŠ€ν„°λ””") + @NotNull + private final MeetingCategory category; + + /** + * 썸넀일 이미지 + * + * @apiNote μ—¬λŸ¬κ°œμ—¬λ„ 첫번째 μ΄λ―Έμ§€λ§Œ μ‚¬μš© + */ + @Schema(description = "λͺ¨μž„ 사진", example = "[url] ν˜•μ‹") + @NotNull + private final List imageURL; + + /** λͺ¨μž„ ν™œλ™ μ‹œμž‘μΌ */ + @Schema(description = "λͺ¨μž„ ν™œλ™ μ‹œμž‘μΌ", example = "2024-07-31T15:30:00") + @NotNull + private final LocalDateTime mStartDate; + + /** λͺ¨μž„ ν™œλ™ μ’…λ£ŒμΌ */ + @Schema(description = "λͺ¨μž„ ν™œλ™ μ’…λ£ŒμΌ", example = "2024-08-25T15:30:00") + @NotNull + private final LocalDateTime mEndDate; + + /** λͺ¨μž„ λͺ¨μ§‘ μ‹œμž‘μΌ */ + @Schema(description = "λͺ¨μž„ λͺ¨μ§‘ μ‹œμž‘μΌ", example = "2024-06-11T15:30:00") + @NotNull + private final LocalDateTime startDate; + + /** λͺ¨μž„ λͺ¨μ§‘ μ’…λ£ŒμΌ */ + @Schema(description = "λͺ¨μž„ λͺ¨μ§‘ μ’…λ£ŒμΌ", example = "2024-06-17T15:30:00") + @NotNull + private final LocalDateTime endDate; + + /** λͺ¨μž„ 인원 */ + @Schema(description = "λͺ¨μ§‘ 인원", example = "20") + @NotNull + private final Integer capacity; + + /** 졜근 ν™œλ™ 일자 */ + @Schema(description = "졜근 ν™œλ™ 일자", example = "2024-06-11T15:30:00") + private final LocalDateTime recentActivityDate; + + /** λͺ¨μž„ νƒ€κ²Ÿ 기수 */ + @Schema(description = "λͺ¨μž„ νƒ€κ²Ÿ 기수", example = "33") + @NotNull + private final Integer targetActiveGeneration; + + /** λͺ¨μž„ νƒ€κ²Ÿ 파트 */ + @Schema(description = "λͺ¨μž„ νƒ€κ²Ÿ 파트", example = "[\"PM\", \"SERVER\"]") + @NotNull + private final MeetingJoinablePart[] joinableParts; + + /** μ§€μ›μž 수 */ + @Schema(description = "μ§€μ›μž 수", example = "50") + @NotNull + private final long applicantCount; + + /** κ°€μž…λœ μ§€μ›μž 수 */ + @Schema(description = "κ°€μž…λœ μ§€μ›μž 수", example = "9") + @NotNull + private final long approvedUserCount; + + /** κ°œμ„€μž 정보 */ + @Schema(description = "λͺ¨μž„μž₯ 정보", example = "") + @NotNull + private final MeetingV2GetMeetingBannerResponseUserDto user; + + /** λ―ΈνŒ… μƒνƒœ */ + @Schema(description = "λͺ¨μž„ μƒνƒœ", example = "1") + @NotNull + private final Integer status; + + public static MeetingV2GetMeetingBannerResponseDto of(Meeting meeting, LocalDateTime recentActivityDate, + long applicantCount, long approvedUserCount, MeetingV2GetMeetingBannerResponseUserDto meetingCreator, LocalDateTime now) { + + return new MeetingV2GetMeetingBannerResponseDto(meeting.getId(), meeting.getUserId(), meeting.getTitle(), + meeting.getCategory(), + meeting.getImageURL(), meeting.getMStartDate(), meeting.getMEndDate(), meeting.getStartDate(), + meeting.getEndDate(), meeting.getCapacity(), recentActivityDate, meeting.getTargetActiveGeneration(), + meeting.getJoinableParts(), applicantCount, approvedUserCount, + meetingCreator, meeting.getMeetingStatus(now)); + } + } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingBannerResponseUserDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingBannerResponseUserDto.java index 712fad0c..608d8427 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingBannerResponseUserDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingBannerResponseUserDto.java @@ -1,17 +1,38 @@ package org.sopt.makers.crew.main.meeting.v2.dto.response; -import lombok.AllArgsConstructor; +import org.sopt.makers.crew.main.entity.user.User; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Getter; +import lombok.RequiredArgsConstructor; @Getter -@AllArgsConstructor(staticName = "of") +@RequiredArgsConstructor +@Schema(name = "MeetingV2GetMeetingBannerResponseUserDto", description = "λͺ¨μž„ λ°°λ„ˆ μœ μ € Dto") public class MeetingV2GetMeetingBannerResponseUserDto { - /** κ°œμ„€μž crew ID */ - private Integer id; - /** κ°œμ„€μž */ - private String name; - /** κ°œμ„€μž playground ID */ - private Integer orgId; - /** ν”„λ‘œν•„ 사진 */ - private String profileImage; + + /** κ°œμ„€μž crew ID */ + @Schema(description = "λͺ¨μž„μž₯ id, ν¬λ£¨μ—μ„œ μ‚¬μš©ν•˜λŠ” userId", example = "1") + @NotNull + private final Integer id; + + /** κ°œμ„€μž */ + @Schema(description = "λͺ¨μž„μž₯ 이름", example = "홍길동") + @NotNull + private final String name; + + /** κ°œμ„€μž playground ID */ + @Schema(description = "λͺ¨μž„μž₯ org id, λ©”μ΄μ»€μŠ€ ν”„λ‘œλ•νŠΈμ—μ„œ λ²”μš©μ μœΌλ‘œ μ‚¬μš©ν•˜λŠ” userId", example = "1") + @NotNull + private final Integer orgId; + + /** ν”„λ‘œν•„ 사진 */ + @Schema(description = "λͺ¨μž„μž₯ ν”„λ‘œν•„ 사진", example = "[url] ν˜•μ‹") + private final String profileImage; + + public static MeetingV2GetMeetingBannerResponseUserDto of(User meetingCreator) { + return new MeetingV2GetMeetingBannerResponseUserDto(meetingCreator.getId(), meetingCreator.getName(), + meetingCreator.getOrgId(), meetingCreator.getProfileImage()); + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingByIdResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingByIdResponseDto.java new file mode 100644 index 00000000..2be1f924 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingByIdResponseDto.java @@ -0,0 +1,161 @@ +package org.sopt.makers.crew.main.meeting.v2.dto.response; + +import java.time.LocalDateTime; +import java.util.List; + +import org.sopt.makers.crew.main.common.dto.MeetingCreatorDto; +import org.sopt.makers.crew.main.entity.meeting.Meeting; +import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart; +import org.sopt.makers.crew.main.entity.meeting.vo.ImageUrlVO; +import org.sopt.makers.crew.main.entity.user.User; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +@Schema(description = "λͺ¨μž„ 상세 쑰회 dto") +public class MeetingV2GetMeetingByIdResponseDto { + + @Schema(description = "λͺ¨μž„ id", example = "2") + @NotNull + private final Integer id; + + @Schema(description = "λͺ¨μž„μž₯ id", example = "184") + @NotNull + private final Integer userId; + + @Schema(description = "λͺ¨μž„ 제λͺ©", example = "λͺ¨μž„ 제λͺ©μž…λ‹ˆλ‹€.") + @NotNull + private final String title; + + @Schema(description = "λͺ¨μž„ μΉ΄ν…Œκ³ λ¦¬", example = "μŠ€ν„°λ””") + @NotNull + private final String category; + + @Schema(description = "λͺ¨μž„ 이미지", example = "[url ν˜•μ‹]") + @NotNull + private final List imageURL; + + @Schema(description = "λͺ¨μž„ μ‹ μ²­ μ‹œμž‘ μ‹œκ°„", example = "2024-07-10T15:30:00") + @NotNull + private final LocalDateTime startDate; + + @Schema(description = "λͺ¨μž„ μ‹ μ²­ μ’…λ£Œ μ‹œκ°„", example = "2024-07-30T23:59:59") + @NotNull + private final LocalDateTime endDate; + + @Schema(description = "λͺ¨μ§‘ 인원", example = "23") + @NotNull + private final int capacity; + + @Schema(description = "λͺ¨μž„ μ†Œκ°œ", example = "λͺ¨μž„ μ†Œκ°œ μž…λ‹ˆλ‹€.") + @NotNull + private final String desc; + + @Schema(description = "진행방식 μ†Œκ°œ", example = "진행방식 μ„€λͺ…μž…λ‹ˆλ‹€.") + @NotNull + private final String processDesc; + + @Schema(description = "λͺ¨μž„ ν™œλ™ μ‹œμž‘ μ‹œκ°„", example = "2024-08-13T15:30:00") + @NotNull + private final LocalDateTime mStartDate; + + @Schema(description = "λͺ¨μž„ ν™œλ™ μ’…λ£Œ μ‹œκ°„", example = "2024-10-13T23:59:59") + @NotNull + private final LocalDateTime mEndDate; + + @Schema(description = "κ°œμ„€μž μ†Œκ°œ", example = "κ°œμ„€μž μ†Œκ°œ μž…λ‹ˆλ‹€.") + @NotNull + private final String leaderDesc; + + @Schema(description = "λͺ¨μ§‘ λŒ€μƒ μ†Œκ°œ", example = "λͺ¨μ§‘ λŒ€μƒ μ†Œκ°œμž…λ‹ˆλ‹€.") + @NotNull + private final String targetDesc; + + @Schema(description = "μœ μ˜μ‚¬ν•­", example = "μœ μ˜μ‚¬ν•­μž…λ‹ˆλ‹€.") + @NotNull + private final String note; + + @Schema(description = "λ©˜ν†  ν•„μš” μ—¬λΆ€", example = "true") + @NotNull + private final Boolean isMentorNeeded; + + @Schema(description = "ν™œλ™ 기수만 μ‹ μ²­κ°€λŠ₯ν•œ μ—¬λΆ€", example = "false") + @NotNull + private final Boolean canJoinOnlyActiveGeneration; + + @Schema(description = "κ°œμ„€ 기수", example = "36") + @NotNull + private final Integer createdGeneration; + + @Schema(description = "λͺ¨μ§‘ λŒ€μƒ 기수", example = "36") + @NotNull + private final Integer targetActiveGeneration; + + @Schema(description = "λͺ¨μ§‘ λŒ€μƒ 파트", example = "[\n" + + " \"PM\",\n" + + " \"DESIGN\",\n" + + " \"WEB\",\n" + + " \"ANDROID\",\n" + + " \"IOS\",\n" + + " \"SERVER\"\n" + + " ]") + @NotNull + private final MeetingJoinablePart[] joinableParts; + + @Schema(description = "λͺ¨μž„ μƒνƒœ, 0: λͺ¨μ§‘μ „, 1: λͺ¨μ§‘쀑, 2: λͺ¨μ§‘μ’…λ£Œ", example = "1") + @NotNull + private final Integer status; + + @Schema(description = "승인된 μ‹ μ²­ 수", example = "7") + @NotNull + private final long approvedApplyCount; + + @Schema(description = "λͺ¨μž„ κ°œμ„€μž μ—¬λΆ€", example = "true") + @NotNull + private final Boolean host; + + @Schema(description = "λͺ¨μž„ μ‹ μ²­ μ—¬λΆ€", example = "false") + @NotNull + private final Boolean apply; + + @Schema(description = "λͺ¨μž„ 승인 μ—¬λΆ€", example = "false") + @NotNull + private final Boolean approved; + + @Schema(description = "λͺ¨μž„μž₯ 객체", example = "") + @NotNull + private final MeetingCreatorDto user; + + @Schema(description = "μ‹ μ²­ λͺ©λ‘", example = "") + @NotNull + private final List appliedInfo; + + public static MeetingV2GetMeetingByIdResponseDto of(Meeting meeting, long approvedCount, Boolean isHost, Boolean isApply, + Boolean isApproved, User meetingCreator, + List appliedInfo, LocalDateTime now) { + + MeetingCreatorDto meetingCreatorDto = MeetingCreatorDto.of(meetingCreator); + + Integer meetingStatus = meeting.getMeetingStatus(now); + + return new MeetingV2GetMeetingByIdResponseDto(meeting.getId(), meeting.getUserId(), meeting.getTitle(), + meeting.getCategory().getValue(), meeting.getImageURL(), meeting.getStartDate(), meeting.getEndDate(), + meeting.getCapacity(), meeting.getDesc(), meeting.getProcessDesc(), meeting.getMStartDate(), + meeting.getMEndDate(), meeting.getLeaderDesc(), meeting.getTargetDesc(), meeting.getNote(), + meeting.getIsMentorNeeded(), meeting.getCanJoinOnlyActiveGeneration(), meeting.getCreatedGeneration(), + meeting.getTargetActiveGeneration(), meeting.getJoinableParts(), meetingStatus, + approvedCount, isHost, isApply, isApproved, meetingCreatorDto, appliedInfo); + } + + public LocalDateTime getmStartDate() { + return mStartDate; + } + + public LocalDateTime getmEndDate() { + return mEndDate; + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/PreSignedUrlFieldResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/PreSignedUrlFieldResponseDto.java new file mode 100644 index 00000000..c46b501b --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/PreSignedUrlFieldResponseDto.java @@ -0,0 +1,62 @@ +package org.sopt.makers.crew.main.meeting.v2.dto.response; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +@Schema(name = "PreSignedUrlFieldResponseDto", description = "presigned ν•„λ“œ Dto") +public class PreSignedUrlFieldResponseDto { + + @JsonProperty("Content-Type") + @NotNull + @Schema(description = "contentType", example = "image/jpeg") + private final String contentType; + + @JsonProperty("key") + @NotNull + @Schema(description = "key", example = "key κ°’") + private final String key; + + @JsonProperty("bucket") + @NotNull + @Schema(description = "bucket", example = "bucket κ°’") + private final String bucket; + + @JsonProperty("X-Amz-Algorithm") + @NotNull + @Schema(description = "algorithm", example = "algorithm κ°’") + private final String algorithm; + + @JsonProperty("X-Amz-Credential") + @NotNull + @Schema(description = "credential", example = "credential κ°’") + private final String credential; + + @JsonProperty("X-Amz-Date") + @NotNull + @Schema(description = "X-Amz-Date", example = "X-Amz-Date κ°’") + private final String date; + + @JsonProperty("Policy") + @NotNull + @Schema(description = "policy", example = "policy κ°’") + private final String policy; + + @JsonProperty("X-Amz-Signature") + @NotNull + @Schema(description = "X-Amz-Signature", example = "X-Amz-Signature κ°’") + private final String signature; + + public static PreSignedUrlFieldResponseDto of(String contentType, String key, String bucket, String algorithm, + String credential, + String date, String policy, String signature) { + + return new PreSignedUrlFieldResponseDto(contentType, key, bucket, algorithm, credential, date, policy, + signature); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/PreSignedUrlResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/PreSignedUrlResponseDto.java new file mode 100644 index 00000000..77df7e88 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/PreSignedUrlResponseDto.java @@ -0,0 +1,25 @@ +package org.sopt.makers.crew.main.meeting.v2.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +@Schema(name = "PreSignedUrlResponseDto", description = "presigned url Dto") +public class PreSignedUrlResponseDto { + + @NotNull + @Schema(description = "presignedUrl", example = "[url] ν˜•μ‹") + private final String url; + + @NotNull + @Schema(description = "field", example = "") + private final PreSignedUrlFieldResponseDto fields; + + public static PreSignedUrlResponseDto of(String url, PreSignedUrlFieldResponseDto fields){ + + return new PreSignedUrlResponseDto(url, fields); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java index 5a1362bf..9d254128 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java @@ -1,29 +1,48 @@ package org.sopt.makers.crew.main.meeting.v2.service; import java.util.List; + import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingGetAppliesQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingByOrgUserQueryDto; +import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingQueryDto; +import org.sopt.makers.crew.main.meeting.v2.dto.request.ApplyV2UpdateStatusBodyDto; import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2ApplyMeetingDto; import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2CreateMeetingBodyDto; +import org.sopt.makers.crew.main.meeting.v2.dto.response.AppliesCsvFileUrlResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingGetApplyListResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2ApplyMeetingResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2CreateMeetingResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetAllMeetingByOrgUserDto; +import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetAllMeetingDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetMeetingBannerResponseDto; +import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetMeetingByIdResponseDto; public interface MeetingV2Service { - MeetingV2GetAllMeetingByOrgUserDto getAllMeetingByOrgUser( - MeetingV2GetAllMeetingByOrgUserQueryDto queryDto); + MeetingV2GetAllMeetingByOrgUserDto getAllMeetingByOrgUser( + MeetingV2GetAllMeetingByOrgUserQueryDto queryDto); + + List getMeetingBanner(); + + MeetingV2CreateMeetingResponseDto createMeeting(MeetingV2CreateMeetingBodyDto requestBody, Integer userId); + + MeetingV2ApplyMeetingResponseDto applyMeeting(MeetingV2ApplyMeetingDto requestBody, Integer userId); + + void applyMeetingCancel(Integer meetingId, Integer userId); + + MeetingGetApplyListResponseDto findApplyList(MeetingGetAppliesQueryDto queryCommand, Integer meetingId, + Integer userId); + + MeetingV2GetAllMeetingDto getMeetings(MeetingV2GetAllMeetingQueryDto queryCommand); - List getMeetingBanner(); + void deleteMeeting(Integer meetingId, Integer userId); - MeetingV2CreateMeetingResponseDto createMeeting(MeetingV2CreateMeetingBodyDto requestBody, Integer userId); + void updateMeeting(Integer meetingId, MeetingV2CreateMeetingBodyDto requestBody, Integer userId); - MeetingV2ApplyMeetingResponseDto applyMeeting(MeetingV2ApplyMeetingDto requestBody, Integer userId); + void updateApplyStatus(Integer meetingId, ApplyV2UpdateStatusBodyDto requestBody, Integer userId); - void applyMeetingCancel(Integer meetingId, Integer userId); + AppliesCsvFileUrlResponseDto getAppliesCsvFileUrl(Integer meetingId, List status, String order, + Integer userId); - MeetingGetApplyListResponseDto findApplyList(MeetingGetAppliesQueryDto queryCommand, Integer meetingId, - Integer userId); + MeetingV2GetMeetingByIdResponseDto getMeetingById(Integer meetingId, Integer userId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java index bf613ef8..27e937c0 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java @@ -1,34 +1,43 @@ package org.sopt.makers.crew.main.meeting.v2.service; -import static org.sopt.makers.crew.main.common.constant.CrewConst.ACTIVE_GENERATION; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.ALREADY_APPLIED_MEETING; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.FULL_MEETING_CAPACITY; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.MISSING_GENERATION_PART; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.NOT_ACTIVE_GENERATION; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.NOT_FOUND_APPLY; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.NOT_IN_APPLY_PERIOD; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.NOT_TARGET_PART; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.VALIDATION_EXCEPTION; - +import static org.sopt.makers.crew.main.common.constant.CrewConst.*; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.*; +import static org.sopt.makers.crew.main.entity.apply.enums.EnApplyStatus.*; + +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.UUID; import java.util.stream.Collectors; import java.util.stream.Stream; import lombok.RequiredArgsConstructor; +import org.sopt.makers.crew.main.common.dto.MeetingResponseDto; import org.sopt.makers.crew.main.common.exception.BadRequestException; +import org.sopt.makers.crew.main.common.exception.ServerException; import org.sopt.makers.crew.main.common.pagination.dto.PageMetaDto; import org.sopt.makers.crew.main.common.pagination.dto.PageOptionsDto; +import org.sopt.makers.crew.main.common.util.CustomPageable; +import org.sopt.makers.crew.main.common.util.Time; import org.sopt.makers.crew.main.common.util.UserPartUtil; +import org.sopt.makers.crew.main.entity.apply.Applies; import org.sopt.makers.crew.main.entity.apply.Apply; import org.sopt.makers.crew.main.entity.apply.ApplyRepository; import org.sopt.makers.crew.main.entity.apply.enums.EnApplyStatus; import org.sopt.makers.crew.main.entity.apply.enums.EnApplyType; +import org.sopt.makers.crew.main.entity.comment.Comment; +import org.sopt.makers.crew.main.entity.comment.CommentRepository; +import org.sopt.makers.crew.main.entity.like.LikeRepository; import org.sopt.makers.crew.main.entity.meeting.Meeting; import org.sopt.makers.crew.main.entity.meeting.MeetingRepository; import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart; @@ -38,40 +47,56 @@ import org.sopt.makers.crew.main.entity.user.UserRepository; import org.sopt.makers.crew.main.entity.user.enums.UserPart; import org.sopt.makers.crew.main.entity.user.vo.UserActivityVO; +import org.sopt.makers.crew.main.external.s3.service.S3Service; import org.sopt.makers.crew.main.meeting.v2.dto.ApplyMapper; import org.sopt.makers.crew.main.meeting.v2.dto.MeetingMapper; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingGetAppliesQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingByOrgUserQueryDto; +import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingQueryDto; +import org.sopt.makers.crew.main.meeting.v2.dto.request.ApplyV2UpdateStatusBodyDto; import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2ApplyMeetingDto; import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2CreateMeetingBodyDto; +import org.sopt.makers.crew.main.meeting.v2.dto.response.AppliesCsvFileUrlResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.ApplyInfoDto; +import org.sopt.makers.crew.main.meeting.v2.dto.response.ApplyWholeInfoDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingGetApplyListResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2ApplyMeetingResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2CreateMeetingResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetAllMeetingByOrgUserDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetAllMeetingByOrgUserMeetingDto; +import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetAllMeetingDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetMeetingBannerResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetMeetingBannerResponseUserDto; +import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetMeetingByIdResponseDto; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import com.opencsv.CSVWriter; + @Service @RequiredArgsConstructor @Transactional(readOnly = true) public class MeetingV2ServiceImpl implements MeetingV2Service { - private final static int ZERO = 0; + private static final int ZERO = 0; private final UserRepository userRepository; private final ApplyRepository applyRepository; private final MeetingRepository meetingRepository; private final PostRepository postRepository; + private final CommentRepository commentRepository; + private final LikeRepository likeRepository; + + private final S3Service s3Service; private final MeetingMapper meetingMapper; private final ApplyMapper applyMapper; + private final Time time; + @Override public MeetingV2GetAllMeetingByOrgUserDto getAllMeetingByOrgUser( MeetingV2GetAllMeetingByOrgUserQueryDto queryDto) { @@ -88,9 +113,9 @@ public MeetingV2GetAllMeetingByOrgUserDto getAllMeetingByOrgUser( userJoinedList = Stream .concat(myMeetings.stream(), applyRepository.findAllByUserIdAndStatus(existUser.getId(), EnApplyStatus.APPROVE) - .stream().map(apply -> apply.getMeeting())) + .stream().map(Apply::getMeeting)) .map(meeting -> MeetingV2GetAllMeetingByOrgUserMeetingDto.of(meeting.getId(), - checkMeetingLeader(meeting, existUser.getId()), meeting.getTitle(), + meeting.checkMeetingLeader(existUser.getId()), meeting.getTitle(), meeting.getImageURL().get(0).getUrl(), meeting.getCategory().getValue(), meeting.getMStartDate(), meeting.getMEndDate(), checkActivityStatus(meeting))) .sorted(Comparator.comparing(MeetingV2GetAllMeetingByOrgUserMeetingDto::getId).reversed()) @@ -106,33 +131,50 @@ public MeetingV2GetAllMeetingByOrgUserDto getAllMeetingByOrgUser( return MeetingV2GetAllMeetingByOrgUserDto.of(pagedUserJoinedList, pageMetaDto); } + /** + * @Note: 졜근 ν™œλ™ μ—¬λΆ€λŠ” κ²Œμ‹œκΈ€ μƒμ„±μΌμžλ₯Ό κΈ°μ€€μœΌλ‘œ μ§„ν–‰ν•œλ‹€. + * */ @Override public List getMeetingBanner() { - return meetingRepository.findAll() + + List meetings = meetingRepository.findTop20ByOrderByIdDesc(); + List meetingIds = meetings.stream().map(Meeting::getId).toList(); + + List posts = postRepository.findAllByMeetingIdIn(meetingIds); + List filterLatestPosts = filterLatestPostsByMeetingId(posts); + Map postMap = filterLatestPosts.stream() + .collect(Collectors.toMap(post -> post.getMeeting().getId(), post -> post)); + + Applies applies = new Applies(applyRepository.findAllByMeetingIdIn(meetingIds)); + + return getResponseDto(meetings, postMap, applies); + } + + + private List filterLatestPostsByMeetingId(List posts) { + return posts.stream() + .collect(Collectors.groupingBy(Post::getMeetingId, + Collectors.maxBy(Comparator.comparing(Post::getCreatedDate)))) + .values() .stream() - .sorted(Comparator.comparing(Meeting::getId).reversed()) - .limit(20) + .flatMap(Optional::stream) + .collect(Collectors.toList()); + } + + private List getResponseDto(List meetings, + Map postMap, Applies applies) { + return meetings.stream() .map(meeting -> { - Optional recentPost = postRepository.findFirstByMeetingIdOrderByIdDesc(meeting.getId()); - Optional recentActivityDate = recentPost.map(Post::getCreatedDate); - - List applies = applyRepository.findAllByMeetingId(meeting.getId()); - Integer applicantCount = applies.size(); - Integer appliedUserCount = applies.stream() - .filter(apply -> apply.getStatus().equals(EnApplyStatus.APPROVE)).toList().size(); - - User meetingLeader = userRepository.findByIdOrThrow(meeting.getUserId()); - MeetingV2GetMeetingBannerResponseUserDto meetingLeaderDto = MeetingV2GetMeetingBannerResponseUserDto - .of(meetingLeader.getId(), meetingLeader.getName(), meetingLeader.getOrgId(), - meetingLeader.getProfileImage()); - - return MeetingV2GetMeetingBannerResponseDto.of(meeting.getId(), meeting.getUserId(), - meeting.getTitle(), meeting.getCategory(), meeting.getImageURL(), - meeting.getMStartDate(), meeting.getMEndDate(), meeting.getStartDate(), - meeting.getEndDate(), - meeting.getCapacity(), recentActivityDate, meeting.getTargetActiveGeneration(), - meeting.getJoinableParts(), applicantCount, appliedUserCount, meetingLeaderDto, - meeting.getMeetingStatus()); + MeetingV2GetMeetingBannerResponseUserDto meetingCreatorDto = MeetingV2GetMeetingBannerResponseUserDto.of(meeting.getUser()); + + LocalDateTime recentActivityDate = null; + if (postMap.containsKey(meeting.getId())) { + recentActivityDate = postMap.get(meeting.getId()).getCreatedDate(); + } + + return MeetingV2GetMeetingBannerResponseDto.of(meeting, recentActivityDate, + applies.getAppliedCount(meeting.getId()), applies.getApprovedCount(meeting.getId()), + meetingCreatorDto, time.now()); }).toList(); } @@ -150,7 +192,7 @@ public MeetingV2CreateMeetingResponseDto createMeeting(MeetingV2CreateMeetingBod } Meeting meeting = meetingMapper.toMeetingEntity(requestBody, - getTargetActiveGeneration(requestBody.getCanJoinOnlyActiveGeneration()), ACTIVE_GENERATION, user, + createTargetActiveGeneration(requestBody.getCanJoinOnlyActiveGeneration()), ACTIVE_GENERATION, user, user.getId()); Meeting savedMeeting = meetingRepository.save(meeting); @@ -190,7 +232,6 @@ public void applyMeetingCancel(Integer meetingId, Integer userId) { } @Override - @Transactional(readOnly = true) public MeetingGetApplyListResponseDto findApplyList(MeetingGetAppliesQueryDto queryCommand, Integer meetingId, Integer userId) { @@ -204,8 +245,167 @@ public MeetingGetApplyListResponseDto findApplyList(MeetingGetAppliesQueryDto qu return MeetingGetApplyListResponseDto.of(applyInfoDtos.getContent(), pageMetaDto); } - private Boolean checkMeetingLeader(Meeting meeting, Integer userId) { - return meeting.getUserId().equals(userId); + @Override + public MeetingV2GetAllMeetingDto getMeetings(MeetingV2GetAllMeetingQueryDto queryCommand) { + Sort sort = Sort.by(Sort.Direction.ASC, "id"); + Page meetings = meetingRepository.findAllByQuery(queryCommand, + new CustomPageable(queryCommand.getPage() - 1, sort), time); + List meetingIds = meetings.stream().map(Meeting::getId).toList(); + + Applies allApplies = new Applies(applyRepository.findAllByMeetingIdIn(meetingIds)); + + List meetingResponseDtos = meetings.getContent().stream() + .map(meeting -> MeetingResponseDto.of(meeting, meeting.getUser(), + allApplies.getAppliedCount(meeting.getId()), time.now())) + .toList(); + + PageOptionsDto pageOptionsDto = new PageOptionsDto(meetings.getPageable().getPageNumber() + 1, + meetings.getPageable().getPageSize()); + PageMetaDto pageMetaDto = new PageMetaDto(pageOptionsDto, (int)meetings.getTotalElements()); + + return MeetingV2GetAllMeetingDto.of(meetingResponseDtos, pageMetaDto); + } + + /** + * @note: 1. like(Comment, post κ΄€λ ¨) -> comment -> post 순으둜 μ‚­μ œ + * 2. apply μ‚­μ œ + * 3. meeting μ‚­μ œ + * */ + + @Override + @Transactional + public void deleteMeeting(Integer meetingId, Integer userId) { + Meeting meeting = meetingRepository.findByIdOrThrow(meetingId); + meeting.validateMeetingCreator(userId); + + List posts = postRepository.findAllByMeetingId(meetingId); + List postIds = posts.stream().map(Post::getId).toList(); + + List comments = commentRepository.findAllByPostIdIsIn(postIds); + List commentIds = comments.stream().map(Comment::getId).toList(); + + likeRepository.deleteAllByPostIdsInQuery(postIds); + likeRepository.deleteAllByCommentIdsInQuery(commentIds); + + commentRepository.deleteAllByPostIdsInQuery(postIds); + postRepository.deleteAllByMeetingIdQuery(meetingId); + applyRepository.deleteAllByMeetingIdQuery(meetingId); + + meetingRepository.delete(meeting); + } + + @Override + @Transactional + public void updateMeeting(Integer meetingId, MeetingV2CreateMeetingBodyDto requestBody, Integer userId) { + User user = userRepository.findByIdOrThrow(userId); + + Meeting meeting = meetingRepository.findByIdOrThrow(meetingId); + meeting.validateMeetingCreator(userId); + + Meeting updatedMeeting = meetingMapper.toMeetingEntity(requestBody, + createTargetActiveGeneration(requestBody.getCanJoinOnlyActiveGeneration()), ACTIVE_GENERATION, user, + user.getId()); + + meeting.updateMeeting(updatedMeeting); + } + + @Override + @Transactional + public void updateApplyStatus(Integer meetingId, ApplyV2UpdateStatusBodyDto requestBody, Integer userId) { + Meeting meeting = meetingRepository.findByIdOrThrow(meetingId); + meeting.validateMeetingCreator(userId); + + Apply apply = applyRepository.findByIdOrThrow(requestBody.getApplyId()); + EnApplyStatus updatedApplyStatus = EnApplyStatus.ofValue(requestBody.getStatus()); + apply.validateDuplicateUpdateApplyStatus(updatedApplyStatus); + + if (updatedApplyStatus.equals(EnApplyStatus.APPROVE)) { + List applies = applyRepository.findAllByMeetingIdAndStatus(meetingId, + EnApplyStatus.APPROVE); + + meeting.validateCapacity(applies.size()); + } + + apply.updateApplyStatus(updatedApplyStatus); + + } + + @Override + public AppliesCsvFileUrlResponseDto getAppliesCsvFileUrl(Integer meetingId, List status, String order, + Integer userId) { + Meeting meeting = meetingRepository.findByIdOrThrow(meetingId); + meeting.validateMeetingCreator(userId); + + List statuses = status.stream().map(EnApplyStatus::ofValue).toList(); + List applies = applyRepository.findAllByMeetingIdWithUser(meetingId, statuses, order); + + String csvFilePath = createCsvFile(applies); + String csvFileUrl = s3Service.uploadCSVFile(csvFilePath); + deleteCsvFile(csvFilePath); + + return AppliesCsvFileUrlResponseDto.of(csvFileUrl); + } + + @Override + public MeetingV2GetMeetingByIdResponseDto getMeetingById(Integer meetingId, Integer userId) { + User user = userRepository.findByIdOrThrow(userId); + + Meeting meeting = meetingRepository.findByIdOrThrow(meetingId); + User meetingCreator = userRepository.findByIdOrThrow(meeting.getUserId()); + + Applies applies = new Applies( + applyRepository.findAllByMeetingIdWithUser(meetingId, List.of(WAITING, APPROVE, REJECT), ORDER_ASC)); + + Boolean isHost = meeting.checkMeetingLeader(user.getId()); + Boolean isApply = applies.isApply(meetingId, user.getId()); + Boolean isApproved = applies.isApproved(meetingId, user.getId()); + long approvedCount = applies.getApprovedCount(meetingId); + + List applyWholeInfoDtos = applies.getAppliesMap().get(meetingId).stream() + .map(apply -> ApplyWholeInfoDto.of(apply, apply.getUser(), userId)) + .toList(); + + return MeetingV2GetMeetingByIdResponseDto.of(meeting, approvedCount, isHost, isApply, isApproved, + meetingCreator, applyWholeInfoDtos, time.now()); + } + + private void deleteCsvFile(String filePath) { + try { + Files.deleteIfExists(Paths.get(filePath)); + } catch (IOException e) { + throw new ServerException(CSV_ERROR.getErrorCode()); + } + } + + private String createCsvFile(List applies) { + String filePath = UUID.randomUUID() + ".csv"; + + try (CSVWriter writer = new CSVWriter(new FileWriter(filePath))) { + // CSV 파일의 헀더 μ •μ˜ + String[] header = {"이름", "졜근 ν™œλ™ 파트", "졜근 ν™œλ™ 기수", "μ „ν™”λ²ˆν˜Έ", "μ‹ μ²­ λ‚ μ§œ 및 μ‹œκ°„", "μ‹ μ²­ λ‚΄μš©", "μ‹ μ²­ μƒνƒœ"}; + writer.writeNext(header); + + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + for (Apply apply : applies) { + User user = apply.getUser(); + UserActivityVO activity = user.getRecentActivityVO(); + + String[] data = { + user.getName(), + activity.getPart(), + String.valueOf(activity.getGeneration()), + String.format("\"%s\"", user.getPhone()), + apply.getAppliedDate().format(formatter), + apply.getContent(), + apply.getStatus().getDescription() + }; + writer.writeNext(data); + } + } catch (Exception e) { + throw new ServerException(CSV_ERROR.getErrorCode()); + } + + return filePath; } private Boolean checkActivityStatus(Meeting meeting) { @@ -215,8 +415,8 @@ private Boolean checkActivityStatus(Meeting meeting) { return now.isEqual(mStartDate) || (now.isAfter(mStartDate) && now.isBefore(mEndDate)); } - private Integer getTargetActiveGeneration(Boolean canJoinOnlyActiveGeneration) { - return canJoinOnlyActiveGeneration ? ACTIVE_GENERATION : null; + private Integer createTargetActiveGeneration(Boolean canJoinOnlyActiveGeneration) { + return Boolean.TRUE.equals(canJoinOnlyActiveGeneration) ? ACTIVE_GENERATION : null; } private List filterUserActivities(User user, Meeting meeting) { @@ -241,9 +441,7 @@ private void validateMeetingCapacity(Meeting meeting, List applies) { .filter(apply -> EnApplyStatus.APPROVE.equals(apply.getStatus())) .toList(); - if (approvedApplies.size() >= meeting.getCapacity()) { - throw new BadRequestException(FULL_MEETING_CAPACITY.getErrorCode()); - } + meeting.validateCapacity(approvedApplies.size()); } private void validateUserAlreadyApplied(Integer userId, List applies) { diff --git a/main/src/main/java/org/sopt/makers/crew/main/notice/dto/request/NoticeV2CreateRequestDto.java b/main/src/main/java/org/sopt/makers/crew/main/notice/dto/request/NoticeV2CreateRequestDto.java index 15d95a3b..69bad0ca 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/notice/dto/request/NoticeV2CreateRequestDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/notice/dto/request/NoticeV2CreateRequestDto.java @@ -1,16 +1,17 @@ package org.sopt.makers.crew.main.notice.dto.request; import java.time.LocalDateTime; + import lombok.Getter; import lombok.RequiredArgsConstructor; @Getter @RequiredArgsConstructor public class NoticeV2CreateRequestDto { - private final String title; - private final String subTitle; - private final String contents; - private final LocalDateTime exposeStartDate; - private final LocalDateTime exposeEndDate; - private final String noticeSecretKey; + private final String title; + private final String subTitle; + private final String contents; + private final LocalDateTime exposeStartDate; + private final LocalDateTime exposeEndDate; + private final String noticeSecretKey; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/notice/dto/response/NoticeV2GetResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/notice/dto/response/NoticeV2GetResponseDto.java index 05a1615c..6b398822 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/notice/dto/response/NoticeV2GetResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/notice/dto/response/NoticeV2GetResponseDto.java @@ -1,15 +1,33 @@ package org.sopt.makers.crew.main.notice.dto.response; import java.time.LocalDateTime; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Getter; import lombok.RequiredArgsConstructor; @Getter @RequiredArgsConstructor(staticName = "of") public class NoticeV2GetResponseDto { + + @Schema(description = "곡지 id", example = "1") + @NotNull private final Integer id; + + @Schema(description = "곡지 제λͺ©", example = "곡지 제λͺ©μž…λ‹ˆλ‹€") + @NotNull private final String title; + + @Schema(description = "곡지 λΆ€μ œλͺ©", example = "곡지 λΆ€μ œλͺ©μž…λ‹ˆλ‹€") + @NotNull private final String subTitle; + + @Schema(description = "곡지 λ‚΄μš©", example = "곡지 λ‚΄μš©μž…λ‹ˆλ‹€") + @NotNull private final String contents; + + @Schema(description = "곡지 생성 μ‹œκ°", example = "2024-07-30T15:30:00") + @NotNull private final LocalDateTime createdDate; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/notice/service/NoticeV2Service.java b/main/src/main/java/org/sopt/makers/crew/main/notice/service/NoticeV2Service.java index 96cc326b..31610539 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/notice/service/NoticeV2Service.java +++ b/main/src/main/java/org/sopt/makers/crew/main/notice/service/NoticeV2Service.java @@ -1,11 +1,11 @@ package org.sopt.makers.crew.main.notice.service; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.FORBIDDEN_EXCEPTION; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.FORBIDDEN_EXCEPTION; import java.time.LocalDateTime; import java.util.List; import lombok.RequiredArgsConstructor; -import org.sopt.makers.crew.main.common.exception.BadRequestException; + import org.sopt.makers.crew.main.common.exception.ForbiddenException; import org.sopt.makers.crew.main.entity.notice.Notice; import org.sopt.makers.crew.main.entity.notice.NoticeRepository; diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Api.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Api.java index b300d1f8..e4465313 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Api.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Api.java @@ -9,48 +9,96 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; - import java.security.Principal; - import org.sopt.makers.crew.main.post.v2.dto.query.PostGetPostsCommand; import org.sopt.makers.crew.main.post.v2.dto.request.PostV2CreatePostBodyDto; import org.sopt.makers.crew.main.post.v2.dto.request.PostV2MentionUserInPostRequestDto; +import org.sopt.makers.crew.main.post.v2.dto.request.PostV2UpdatePostBodyDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostDetailBaseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2CreatePostResponseDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2GetPostCountResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2GetPostsResponseDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2ReportResponseDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2SwitchPostLikeResponseDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2UpdatePostResponseDto; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; @Tag(name = "κ²Œμ‹œκΈ€") public interface PostV2Api { - @Operation(summary = "λͺ¨μž„ κ²Œμ‹œκΈ€ μž‘μ„±") - @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "성곡"), - @ApiResponse(responseCode = "400", description = "λͺ¨μž„이 μ—†μŠ΅λ‹ˆλ‹€.", content = @Content), - @ApiResponse(responseCode = "403", description = "κΆŒν•œμ΄ μ—†μŠ΅λ‹ˆλ‹€.", content = @Content), - }) - ResponseEntity createPost( - @Valid @RequestBody PostV2CreatePostBodyDto requestBody, Principal principal); - - @Operation(summary = "λͺ¨μž„ κ²Œμ‹œκΈ€ λͺ©λ‘ 쑰회") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "성곡"), - @ApiResponse(responseCode = "400", description = "λͺ¨μž„이 μ—†μŠ΅λ‹ˆλ‹€.", content = @Content), - }) - @Parameters({ - @Parameter(name = "page", description = "νŽ˜μ΄μ§€, default = 1", example = "1", schema = @Schema(type = "integer", format = "int32")), - @Parameter(name = "take", description = "κ°€μ Έμ˜¬ 데이터 개수, default = 12", example = "50", schema = @Schema(type = "integer", format = "int32")), - @Parameter(name = "meetingId", description = "λͺ¨μž„ id", example = "0", schema = @Schema(type = "integer", format = "int32"))}) - ResponseEntity getPosts( - @ModelAttribute @Parameter(hidden = true) PostGetPostsCommand queryCommand, - Principal principal); - - @Operation(summary = "κ²Œμ‹œκΈ€μ—μ„œ λ©˜μ…˜ν•˜κΈ°") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "성곡"), - }) - ResponseEntity mentionUserInPost( - @Valid @RequestBody PostV2MentionUserInPostRequestDto requestBody, Principal principal); + @Operation(summary = "λͺ¨μž„ κ²Œμ‹œκΈ€ μž‘μ„±") + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "성곡"), + @ApiResponse(responseCode = "400", description = "λͺ¨μž„이 μ—†μŠ΅λ‹ˆλ‹€.", content = @Content), + @ApiResponse(responseCode = "403", description = "κΆŒν•œμ΄ μ—†μŠ΅λ‹ˆλ‹€.", content = @Content), + }) + ResponseEntity createPost( + @Valid @RequestBody PostV2CreatePostBodyDto requestBody, Principal principal); + + @Operation(summary = "λͺ¨μž„ κ²Œμ‹œκΈ€ λͺ©λ‘ 쑰회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성곡"), + @ApiResponse(responseCode = "400", description = "λͺ¨μž„이 μ—†μŠ΅λ‹ˆλ‹€.", content = @Content), + }) + @Parameters({ + @Parameter(name = "page", description = "νŽ˜μ΄μ§€, default = 1", example = "1", schema = @Schema(type = "integer", format = "int32")), + @Parameter(name = "take", description = "κ°€μ Έμ˜¬ 데이터 개수, default = 12", example = "50", schema = @Schema(type = "integer", format = "int32")), + @Parameter(name = "meetingId", description = "λͺ¨μž„ id", example = "0", schema = @Schema(type = "integer", format = "int32"))}) + ResponseEntity getPosts( + @ModelAttribute @Parameter(hidden = true) PostGetPostsCommand queryCommand, + Principal principal); + + @Operation(summary = "κ²Œμ‹œκΈ€μ—μ„œ λ©˜μ…˜ν•˜κΈ°") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성곡"), + }) + ResponseEntity mentionUserInPost( + @Valid @RequestBody PostV2MentionUserInPostRequestDto requestBody, Principal principal); + + @Operation(summary = "λͺ¨μž„ κ²Œμ‹œκΈ€ 쑰회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성곡"), + @ApiResponse(responseCode = "400", description = "λͺ¨μž„이 μ—†μŠ΅λ‹ˆλ‹€", content = @Content) + }) + ResponseEntity getPost(@PathVariable Integer postId, Principal principal); + + @Operation(summary = "λͺ¨μž„ κ²Œμ‹œκΈ€ 개수 쑰회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성곡"), + @ApiResponse(responseCode = "400", description = "λͺ¨μž„이 μ—†μŠ΅λ‹ˆλ‹€", content = @Content) + }) + ResponseEntity getPostCount(@RequestParam Integer meetingId); + + @Operation(summary = "λͺ¨μž„ κ²Œμ‹œκΈ€ μ‚­μ œ") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성곡"), + @ApiResponse(responseCode = "400", description = "λͺ¨μž„이 μ—†μŠ΅λ‹ˆλ‹€.", content = @Content), + @ApiResponse(responseCode = "403", description = "κΆŒν•œμ΄ μ—†μŠ΅λ‹ˆλ‹€.", content = @Content) + }) + ResponseEntity deletePost(@PathVariable Integer postId, Principal principal); + + @Operation(summary = "λͺ¨μž„ κ²Œμ‹œκΈ€ μˆ˜μ •") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성곡"), + @ApiResponse(responseCode = "400", description = "\"κ²Œμ‹œκΈ€μ΄ μ—†μŠ΅λ‹ˆλ‹€.\" or \"μ΄λ―Έμ§€λŠ” μ΅œλŒ€ 10κ°œκΉŒμ§€λ§Œ μ—…λ‘œλ“œ κ°€λŠ₯ν•©λ‹ˆλ‹€.\"", content = @Content), + @ApiResponse(responseCode = "403", description = "κΆŒν•œμ΄ μ—†μŠ΅λ‹ˆλ‹€.", content = @Content) + }) + ResponseEntity updatePost(@PathVariable Integer postId, + @RequestBody PostV2UpdatePostBodyDto requestBody, + Principal principal); + + @Operation(summary = "λͺ¨μž„ κ²Œμ‹œκΈ€ μ‹ κ³ ") + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "성곡"), + @ApiResponse(responseCode = "400", description = "\"κ²Œμ‹œκΈ€μ΄ μ—†μŠ΅λ‹ˆλ‹€.\" or \"이미 μ‹ κ³ ν•œ κ²Œμ‹œκΈ€μž…λ‹ˆλ‹€.\"", content = @Content) + }) + ResponseEntity reportPost(@PathVariable Integer postId, Principal principal); + @Operation(summary = "λͺ¨μž„ κ²Œμ‹œκΈ€ μ’‹μ•„μš” ν† κΈ€") + @ApiResponse(responseCode = "201", description = "성곡") + ResponseEntity switchPostLike(@PathVariable Integer postId, Principal principal); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Controller.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Controller.java index b35a7947..81361c55 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Controller.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Controller.java @@ -2,25 +2,32 @@ import io.swagger.v3.oas.annotations.Parameter; import jakarta.validation.Valid; - import java.security.Principal; - import lombok.RequiredArgsConstructor; - import org.sopt.makers.crew.main.common.util.UserUtil; import org.sopt.makers.crew.main.post.v2.dto.query.PostGetPostsCommand; import org.sopt.makers.crew.main.post.v2.dto.request.PostV2CreatePostBodyDto; import org.sopt.makers.crew.main.post.v2.dto.request.PostV2MentionUserInPostRequestDto; +import org.sopt.makers.crew.main.post.v2.dto.request.PostV2UpdatePostBodyDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostDetailBaseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2CreatePostResponseDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2GetPostCountResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2GetPostsResponseDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2ReportResponseDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2SwitchPostLikeResponseDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2UpdatePostResponseDto; import org.sopt.makers.crew.main.post.v2.service.PostV2Service; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; @@ -29,34 +36,86 @@ @RequiredArgsConstructor public class PostV2Controller implements PostV2Api { - private final PostV2Service postV2Service; - - @Override - @PostMapping() - @ResponseStatus(HttpStatus.CREATED) - public ResponseEntity createPost( - @Valid @RequestBody PostV2CreatePostBodyDto requestBody, Principal principal) { - Integer userId = UserUtil.getUserId(principal); - return ResponseEntity.ok(postV2Service.createPost(requestBody, userId)); - } - - @Override - @GetMapping() - @ResponseStatus(HttpStatus.OK) - public ResponseEntity getPosts( - @ModelAttribute @Parameter(hidden = true) PostGetPostsCommand queryCommand, - Principal principal) { - Integer userId = UserUtil.getUserId(principal); - return ResponseEntity.ok(postV2Service.getPosts(queryCommand, userId)); - } - - @Override - @PostMapping("/mention") - @ResponseStatus(HttpStatus.OK) - public ResponseEntity mentionUserInPost( - @Valid @RequestBody PostV2MentionUserInPostRequestDto requestBody, Principal principal) { - Integer userId = UserUtil.getUserId(principal); - postV2Service.mentionUserInPost(requestBody, userId); - return ResponseEntity.status(HttpStatus.OK).build(); - } + private final PostV2Service postV2Service; + + @Override + @PostMapping() + @ResponseStatus(HttpStatus.CREATED) + public ResponseEntity createPost( + @Valid @RequestBody PostV2CreatePostBodyDto requestBody, Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.ok(postV2Service.createPost(requestBody, userId)); + } + + @Override + @GetMapping() + @ResponseStatus(HttpStatus.OK) + public ResponseEntity getPosts( + @ModelAttribute @Parameter(hidden = true) PostGetPostsCommand queryCommand, + Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.ok(postV2Service.getPosts(queryCommand, userId)); + } + + @Override + @PostMapping("/mention") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity mentionUserInPost( + @Valid @RequestBody PostV2MentionUserInPostRequestDto requestBody, Principal principal) { + Integer userId = UserUtil.getUserId(principal); + postV2Service.mentionUserInPost(requestBody, userId); + return ResponseEntity.status(HttpStatus.OK).build(); + } + + @Override + @GetMapping("/{postId}") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity getPost(@PathVariable Integer postId, Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.ok(postV2Service.getPost(userId, postId)); + } + + @Override + @GetMapping("/count") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity getPostCount(@RequestParam Integer meetingId) { + return ResponseEntity.ok(postV2Service.getPostCount(meetingId)); + } + + @Override + @DeleteMapping("/{postId}") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity deletePost(@PathVariable Integer postId, Principal principal) { + Integer userId = UserUtil.getUserId(principal); + postV2Service.deletePost(postId, userId); + return ResponseEntity.ok().build(); + } + + @Override + @PutMapping("/{postId}") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity updatePost(@PathVariable Integer postId, + @RequestBody PostV2UpdatePostBodyDto requestBody, + Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.ok(postV2Service.updatePost(postId, requestBody, userId)); + } + + @Override + @PostMapping("/{postId}/report") + @ResponseStatus(HttpStatus.CREATED) + public ResponseEntity reportPost(@PathVariable Integer postId, Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.status(HttpStatus.CREATED).body(postV2Service.reportPost(postId, userId)); + } + + @Override + @PostMapping("/{postId}/like") + @ResponseStatus(HttpStatus.CREATED) + public ResponseEntity switchPostLike(@PathVariable Integer postId, + Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.status(HttpStatus.CREATED).body(postV2Service.switchPostLike(postId, userId)); + } + } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/query/PostGetPostsCommand.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/query/PostGetPostsCommand.java index e84ef927..cf516017 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/query/PostGetPostsCommand.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/query/PostGetPostsCommand.java @@ -1,12 +1,16 @@ package org.sopt.makers.crew.main.post.v2.dto.query; import java.util.Optional; + +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import org.sopt.makers.crew.main.common.pagination.dto.PageOptionsDto; @Getter +@Schema(name = "PostGetPostsCommand", description = "κ²Œμ‹œκΈ€ 쑰회 μš”μ²­ Dto") public class PostGetPostsCommand extends PageOptionsDto { + @Schema(description = "λͺ¨μž„ id", example = "1") private Optional meetingId; public PostGetPostsCommand(Integer meetingId, Integer page, Integer take) { diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/request/PostV2CreatePostBodyDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/request/PostV2CreatePostBodyDto.java index dbfd1d11..49fbb6fc 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/request/PostV2CreatePostBodyDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/request/PostV2CreatePostBodyDto.java @@ -11,24 +11,24 @@ @Schema(description = "κ²Œμ‹œλ¬Ό 생성 request body dto") public class PostV2CreatePostBodyDto { - @Schema(example = "1", required = true, description = "λͺ¨μž„ ID") - @NotNull - private Integer meetingId; + @Schema(example = "1", required = true, description = "λͺ¨μž„ id") + @NotNull + private Integer meetingId; - @Schema(example = "μ•Œκ³ λ³΄λ©΄ μ“Έλ°μžˆλŠ” 개발 ν”„λ‘œμ„ΈμŠ€", required = true, description = "λͺ¨μž„ 제λͺ©") - @NotEmpty - private String title; + @Schema(example = "μ•Œκ³ λ³΄λ©΄ μ“Έλ°μžˆλŠ” 개발 ν”„λ‘œμ„ΈμŠ€", required = true, description = "λͺ¨μž„ 제λͺ©") + @NotEmpty + private String title; - @Schema( - example = "[\"https://makers-web-img.s3.ap-northeast-2.amazonaws.com/meeting/2023/04/12/7bd87736-b557-4b26-a0d5-9b09f1f1d7df\"]", - required = true, - description = "κ²Œμ‹œκΈ€ 이미지 리슀트" - ) - @NotEmpty - private String[] images; + @Schema( + example = "[\"https://makers-web-img.s3.ap-northeast-2.amazonaws.com/meeting/2023/04/12/7bd87736-b557-4b26-a0d5-9b09f1f1d7df\"]", + required = true, + description = "κ²Œμ‹œκΈ€ 이미지 리슀트" + ) + @NotEmpty + private String[] images; - @Schema(example = "api κ°€ ν„°μ‘Œλ‹€κ³ ? 깃이 ν„°μ‘Œλ‹€κ³ ?", required = true, description = "κ²Œμ‹œκΈ€ λ‚΄μš©") - @NotEmpty - private String contents; + @Schema(example = "api κ°€ ν„°μ‘Œλ‹€κ³ ? 깃이 ν„°μ‘Œλ‹€κ³ ?", required = true, description = "κ²Œμ‹œκΈ€ λ‚΄μš©") + @NotEmpty + private String contents; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/request/PostV2MentionUserInPostRequestDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/request/PostV2MentionUserInPostRequestDto.java index 5e04e711..60065421 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/request/PostV2MentionUserInPostRequestDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/request/PostV2MentionUserInPostRequestDto.java @@ -14,14 +14,15 @@ @Schema(description = "λͺ¨μž„ κ²Œμ‹œκΈ€μ—μ„œ μœ μ € μ–ΈκΈ‰ request body dto") public class PostV2MentionUserInPostRequestDto { - @Schema(example = "[111, 112, 113]", required = true, description = "μ–ΈκΈ‰ν•  μœ μ € ID") + @Schema(example = "[111, 112, 113]", required = true, description = "λ©”μ΄μ»€μŠ€ ν”„λ‘œλ•νŠΈμ—μ„œ λ²”μš©μ μœΌλ‘œ μ‚¬μš©ν•˜λŠ” userId") @NotEmpty - private final List userIds; + private final List orgIds; @Schema(example = "1", required = true, description = "κ²Œμ‹œκΈ€ ID") @NotNull private final Integer postId; @Schema(example = "λ©˜μ…˜λ‚΄μš©~~", required = true, description = "λ©˜μ…˜ λ‚΄μš©") + @NotEmpty private final String content; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/request/PostV2UpdatePostBodyDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/request/PostV2UpdatePostBodyDto.java new file mode 100644 index 00000000..d32c3af1 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/request/PostV2UpdatePostBodyDto.java @@ -0,0 +1,24 @@ +package org.sopt.makers.crew.main.post.v2.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Schema(description = "κ²Œμ‹œλ¬Ό μˆ˜μ • request body dto") +public class PostV2UpdatePostBodyDto { + + @Schema(description = "λͺ¨μž„ κ²Œμ‹œκΈ€ 제λͺ©", example = "μ•Œκ³ λ³΄λ©΄ μ“Έλ°μžˆλŠ” 개발 ν”„λ‘œμ„ΈμŠ€") + @NotNull + private final String title; + + @Schema(description = "λͺ¨μž„ κ²Œμ‹œκΈ€ λ‚΄μš©", example = "apiκ°€ ν„°μ‘Œλ‹€κ³  ? 깃이 ν„°μ‘Œλ‹€κ³ ?") + @NotNull + private final String contents; + + @Schema(description = "λͺ¨μž„ κ²Œμ‹œκΈ€ 이미지 리슀트", example = "[\"url1\", \"url2\"]") + @NotNull + private final String[] images; +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/CommenterThumbnails.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/CommenterThumbnails.java index bff64f31..7981d28b 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/CommenterThumbnails.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/CommenterThumbnails.java @@ -1,11 +1,16 @@ package org.sopt.makers.crew.main.post.v2.dto.response; import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor @Getter public class CommenterThumbnails { + + @Schema(description = "λŒ“κΈ€ μž‘μ„±μžλ“€μ˜ ν”„λ‘œν•„ 이미지 λͺ©λ‘", example = "[\"url1\", \"url2\"]") private final List commenterThumbnails; + } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailBaseDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailBaseDto.java index 927fe64b..60e3e3b9 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailBaseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailBaseDto.java @@ -1,22 +1,59 @@ package org.sopt.makers.crew.main.post.v2.dto.response; +import com.fasterxml.jackson.annotation.JsonProperty; import com.querydsl.core.annotations.QueryProjection; import java.time.LocalDateTime; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Getter; @Getter +@Schema(name = "PostDetailBaseDto", description = "κ²Œμ‹œκΈ€ 객체 Dto") public class PostDetailBaseDto { + + @Schema(description = "κ²Œμ‹œκΈ€ id", example = "1") + @NotNull private final Integer id; + + @Schema(description = "κ²Œμ‹œκΈ€ 제λͺ©", example = "κ²Œμ‹œκΈ€ 제λͺ©μž…λ‹ˆλ‹€.") + @NotNull private final String title; + + @Schema(description = "κ²Œμ‹œκΈ€ λ‚΄μš©", example = "κ²Œμ‹œκΈ€ λ‚΄μš©μž…λ‹ˆλ‹€.") + @NotNull private final String contents; + + @Schema(description = "κ²Œμ‹œκΈ€ μƒμ„±μΌμž", example = "2024-07-31T15:30:00") + @NotNull private final LocalDateTime createdDate; + + @Schema(description = "κ²Œμ‹œκΈ€ 이미지", example = "[\"url1\", \"url2\"]") + @NotNull private final String[] images; + + @Schema(description = "κ²Œμ‹œκΈ€ μž‘μ„±μž 객체", example = "") + @NotNull private final PostWriterInfoDto user; + + @Schema(description = "κ²Œμ‹œκΈ€ μ’‹μ•„μš” 갯수", example = "20") private final int likeCount; + //* 본인이 μ’‹μ•„μš”λ₯Ό λˆŒλ €λŠ”μ§€ μ—¬λΆ€ + @Schema(description = "κ²Œμ‹œκΈ€ μ’‹μ•„μš” μ—¬λΆ€", example = "true") + @NotNull private final Boolean isLiked; + + @Schema(description = "κ²Œμ‹œκΈ€ 쑰회수", example = "30") + @NotNull private final int viewCount; + + @Schema(description = "κ²Œμ‹œκΈ€ λŒ“κΈ€ 수", example = "5") + @NotNull private final int commentCount; + + @Schema(description = "κ²Œμ‹œκΈ€μ— λŒ€ν•œ λͺ¨μž„", example = "") + @NotNull private final PostMeetingDto meeting; @QueryProjection diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailResponseDto.java index 21061cc1..a05e19e4 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailResponseDto.java @@ -2,23 +2,63 @@ import java.time.LocalDateTime; import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor(staticName = "of") +@Schema(name = "PostDetailResponseDto", description = "κ²Œμ‹œκΈ€ 객체 Dto") public class PostDetailResponseDto { + + @Schema(description = "κ²Œμ‹œκΈ€ id", example = "1") + @NotNull private final Integer id; + + @Schema(description = "κ²Œμ‹œκΈ€ 제λͺ©", example = "κ²Œμ‹œκΈ€ 제λͺ©μž…λ‹ˆλ‹€.") + @NotNull private final String title; + + @Schema(description = "κ²Œμ‹œκΈ€ λ‚΄μš©", example = "κ²Œμ‹œκΈ€ λ‚΄μš©μž…λ‹ˆλ‹€.") + @NotNull private final String contents; + + @Schema(description = "κ²Œμ‹œκΈ€ 생성 일자", example = "2024-05-31T15:30:00") + @NotNull private final LocalDateTime createdDate; + + @Schema(description = "κ²Œμ‹œκΈ€ 이미지 λͺ©λ‘", example = "[\"url1\", \"url2\"]") + @NotNull private final String[] images; + + @Schema(description = "κ²Œμ‹œκΈ€ 생성 μœ μ € 객체", example = "") + @NotNull private final PostWriterInfoDto user; + + @Schema(description = "κ²Œμ‹œκΈ€ μ’‹μ•„μš” 수", example = "20") + @NotNull private final int likeCount; + + @Schema(description = "κ²Œμ‹œκΈ€ μ’‹μ•„μš” μ—¬λΆ€", example = "true") + @NotNull private final Boolean isLiked; + + @Schema(description = "κ²Œμ‹œκΈ€ 쑰회수", example = "200") + @NotNull private final int viewCount; + + @Schema(description = "κ²Œμ‹œκΈ€ λŒ“κΈ€ 수", example = "30") + @NotNull private final int commentCount; + + @Schema(description = "κ²Œμ‹œκΈ€μ— ν•΄λ‹Ήν•˜λŠ” λͺ¨μž„", example = "") + @NotNull private final PostMeetingDto meeting; + + @Schema(description = "λŒ“κΈ€ μž‘μ„±μž 썸넀일 λͺ©λ‘", example = "[\"url1\", \"url2\"]") + @NotNull private final List commenterThumbnails; public static PostDetailResponseDto of(PostDetailBaseDto postDetail, diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostMeetingDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostMeetingDto.java index d22cbd71..c833bab3 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostMeetingDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostMeetingDto.java @@ -1,19 +1,41 @@ package org.sopt.makers.crew.main.post.v2.dto.response; import com.querydsl.core.annotations.QueryProjection; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.List; + +import jakarta.validation.constraints.NotNull; import lombok.Getter; import org.sopt.makers.crew.main.entity.meeting.enums.MeetingCategory; +import org.sopt.makers.crew.main.entity.meeting.vo.ImageUrlVO; @Getter +@Schema(name = "PostMeetingDto", description = "κ²Œμ‹œκΈ€μ— λŒ€ν•œ λͺ¨μž„ Dto") public class PostMeetingDto { + + @Schema(description = "λͺ¨μž„ id", example = "1") + @NotNull private final Integer id; + + @Schema(description = "λͺ¨μž„ 제λͺ©", example = "λͺ¨μž„ 제λͺ©μž…λ‹ˆλ‹€.") + @NotNull private final String title; + + @Schema(description = "λͺ¨μž„ μΉ΄ν…Œκ³ λ¦¬", example = "μŠ€ν„°λ””") + @NotNull private final String category; + @Schema(description = "λͺ¨μž„ 이미지 url", example = "[url ν˜•μ‹]") + @NotNull + private final List imageURL; + @QueryProjection - public PostMeetingDto(Integer id, String title, MeetingCategory category) { + public PostMeetingDto(Integer id, String title, MeetingCategory category, List imageURL) { this.id = id; this.title = title; this.category = category.getValue(); + this.imageURL = imageURL; } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2CreatePostResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2CreatePostResponseDto.java index 807c611c..e1f1ace6 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2CreatePostResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2CreatePostResponseDto.java @@ -1,15 +1,20 @@ package org.sopt.makers.crew.main.post.v2.dto.response; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor(staticName = "of") +@Schema(name = "PostV2CreatePostResponseDto", description = "κ²Œμ‹œκΈ€ 생성 응닡 Dto") public class PostV2CreatePostResponseDto { - /** - * μƒμ„±λœ κ²Œμ‹œλ¬Ό id - */ - private Integer postId; + /** + * μƒμ„±λœ κ²Œμ‹œλ¬Ό id + */ + @Schema(description = "κ²Œμ‹œκΈ€ id", example = "1") + @NotNull + private Integer postId; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2GetPostCountResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2GetPostCountResponseDto.java new file mode 100644 index 00000000..0908a957 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2GetPostCountResponseDto.java @@ -0,0 +1,18 @@ +package org.sopt.makers.crew.main.post.v2.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor(staticName = "of") +public class PostV2GetPostCountResponseDto { + + /** + * λͺ¨μž„ κ²Œμ‹œκΈ€ 개수 + */ + @Schema(description = "κ²Œμ‹œκΈ€ 갯수", example = "25") + @NotNull + private Integer postCount; +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2GetPostsResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2GetPostsResponseDto.java index e414007b..dd38fa9b 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2GetPostsResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2GetPostsResponseDto.java @@ -1,14 +1,24 @@ package org.sopt.makers.crew.main.post.v2.dto.response; import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; + import org.sopt.makers.crew.main.common.pagination.dto.PageMetaDto; @Getter @AllArgsConstructor(staticName = "of") +@Schema(name = "PostV2GetPostsResponseDto", description = "κ²Œμ‹œκΈ€ 쑰회 응닡 Dto") public class PostV2GetPostsResponseDto { - private final List posts; - private final PageMetaDto meta; + @Schema(description = "κ²Œμ‹œκΈ€ 객체", example = "") + @NotNull + private final List posts; + + @NotNull + private final PageMetaDto meta; + } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2ReportResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2ReportResponseDto.java new file mode 100644 index 00000000..4d69436f --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2ReportResponseDto.java @@ -0,0 +1,16 @@ +package org.sopt.makers.crew.main.post.v2.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor(staticName = "of") +@Schema(name = "PostV2ReportResponseDto", description = "κ²Œμ‹œκΈ€ μ‹ κ³  응닡 Dto") +public class PostV2ReportResponseDto { + + @Schema(description = "μƒμ„±λœ μ‹ κ³  id", example = "1") + @NotNull + private final Integer reportId; +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2SwitchPostLikeResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2SwitchPostLikeResponseDto.java new file mode 100644 index 00000000..bd655f65 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2SwitchPostLikeResponseDto.java @@ -0,0 +1,16 @@ +package org.sopt.makers.crew.main.post.v2.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor(staticName = "of") +@Schema(name = "PostV2SwitchPostLikeResponseDto", description = "λͺ¨μž„ κ²Œμ‹œκΈ€ μ’‹μ•„μš” ν† κΈ€ 응닡 Dto") +public class PostV2SwitchPostLikeResponseDto { + + @Schema(description = "본인이 κ²Œμ‹œκΈ€ μ’‹μ•„μš”λ₯Ό λˆŒλ €λŠ”μ§€ μ—¬λΆ€", example = "true") + @NotNull + private final Boolean isLiked; +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2UpdatePostResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2UpdatePostResponseDto.java new file mode 100644 index 00000000..8a311a03 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2UpdatePostResponseDto.java @@ -0,0 +1,31 @@ +package org.sopt.makers.crew.main.post.v2.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor(staticName = "of") +@Schema(name = "PostV2UpdatePostResponseDto", description = "κ²Œμ‹œκΈ€ μˆ˜μ • 응닡 Dto") +public class PostV2UpdatePostResponseDto { + + @Schema(description = "λͺ¨μž„ κ²Œμ‹œκΈ€", example = "1") + @NotNull + private final Integer id; + + @Schema(description = "λͺ¨μž„ κ²Œμ‹œκΈ€ 제λͺ©", example = "μ•Œκ³ λ³΄λ©΄ μ“Έλ°μžˆλŠ” 개발 ν”„λ‘œμ„ΈμŠ€") + @NotNull + private final String title; + + @Schema(description = "λͺ¨μž„ κ²Œμ‹œκΈ€ λ‚΄μš©", example = "apiκ°€ ν„°μ‘Œλ‹€κ³  ? 깃이 ν„°μ‘Œλ‹€κ³ ?") + @NotNull + private final String contents; + + @Schema(description = "λͺ¨μž„ κ²Œμ‹œκΈ€ μˆ˜μ • λ‚ μ§œ", example = "2024-08-25T15:30:00") + @NotNull + private final String updatedDate; + + @Schema(description = "λͺ¨μž„ κ²Œμ‹œκΈ€ 이미지 리슀트", example = "[\"url1\", \"url2\"]") + private final String[] images; +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostWriterInfoDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostWriterInfoDto.java index 84a6834a..35ecd812 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostWriterInfoDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostWriterInfoDto.java @@ -1,13 +1,29 @@ package org.sopt.makers.crew.main.post.v2.dto.response; import com.querydsl.core.annotations.QueryProjection; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Getter; @Getter +@Schema(name = "PostWriterInfoDto", description = "κ²Œμ‹œκΈ€ μž‘μ„±μž Dto") public class PostWriterInfoDto { + + @Schema(description = "μž‘μ„±μž id, ν¬λ£¨μ—μ„œ μ‚¬μš©ν•˜λŠ” userId", example = "1") + @NotNull private final Integer id; + + @Schema(description = "μž‘μ„±μž org id, λ©”μ΄μ»€μŠ€ ν”„λ‘œλ•νŠΈμ—μ„œ λ²”μš©μ μœΌλ‘œ μ‚¬μš©ν•˜λŠ” userId", example = "1") + @NotNull private final Integer orgId; + + @Schema(description = "μž‘μ„±μž 이름", example = "홍길동") + @NotNull private final String name; + + @Schema(description = "μž‘μ„±μž ν”„λ‘œν•„ 사진", example = "[url] ν˜•μ‹") + @NotNull private final String profileImage; @QueryProjection diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2Service.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2Service.java index 3a9ff793..4e7da2c4 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2Service.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2Service.java @@ -3,14 +3,32 @@ import org.sopt.makers.crew.main.post.v2.dto.query.PostGetPostsCommand; import org.sopt.makers.crew.main.post.v2.dto.request.PostV2CreatePostBodyDto; import org.sopt.makers.crew.main.post.v2.dto.request.PostV2MentionUserInPostRequestDto; +import org.sopt.makers.crew.main.post.v2.dto.request.PostV2UpdatePostBodyDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostDetailBaseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2CreatePostResponseDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2GetPostCountResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2GetPostsResponseDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2ReportResponseDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2SwitchPostLikeResponseDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2UpdatePostResponseDto; public interface PostV2Service { - PostV2CreatePostResponseDto createPost(PostV2CreatePostBodyDto requestBody, Integer userId); + PostV2CreatePostResponseDto createPost(PostV2CreatePostBodyDto requestBody, Integer userId); - PostV2GetPostsResponseDto getPosts(PostGetPostsCommand queryCommand, Integer userId); + PostV2GetPostsResponseDto getPosts(PostGetPostsCommand queryCommand, Integer userId); - void mentionUserInPost(PostV2MentionUserInPostRequestDto requestBody, Integer userId); + PostDetailBaseDto getPost(Integer userId, Integer postId); + + void mentionUserInPost(PostV2MentionUserInPostRequestDto requestBody, Integer userId); + + PostV2GetPostCountResponseDto getPostCount(Integer meetingId); + + void deletePost(Integer postId, Integer userId); + + PostV2UpdatePostResponseDto updatePost(Integer postId, PostV2UpdatePostBodyDto requestBody, Integer userId); + + PostV2ReportResponseDto reportPost(Integer postId, Integer userId); + + PostV2SwitchPostLikeResponseDto switchPostLike(Integer postId, Integer userId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java index d418f840..41fad4b2 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java @@ -1,7 +1,9 @@ package org.sopt.makers.crew.main.post.v2.service; import static java.util.stream.Collectors.toList; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.FORBIDDEN_EXCEPTION; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.ALREADY_REPORTED_POST; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.FORBIDDEN_EXCEPTION; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.MAX_IMAGE_UPLOAD_EXCEEDED; import static org.sopt.makers.crew.main.internal.notification.PushNotificationEnums.NEW_POST_MENTION_PUSH_NOTIFICATION_TITLE; import static org.sopt.makers.crew.main.internal.notification.PushNotificationEnums.NEW_POST_PUSH_NOTIFICATION_TITLE; import static org.sopt.makers.crew.main.internal.notification.PushNotificationEnums.PUSH_NOTIFICATION_CATEGORY; @@ -10,16 +12,25 @@ import lombok.RequiredArgsConstructor; +import org.sopt.makers.crew.main.common.exception.BadRequestException; import org.sopt.makers.crew.main.common.exception.ForbiddenException; import org.sopt.makers.crew.main.common.pagination.dto.PageMetaDto; import org.sopt.makers.crew.main.common.pagination.dto.PageOptionsDto; +import org.sopt.makers.crew.main.common.util.CustomPageable; +import org.sopt.makers.crew.main.common.util.Time; import org.sopt.makers.crew.main.entity.apply.Apply; import org.sopt.makers.crew.main.entity.apply.ApplyRepository; import org.sopt.makers.crew.main.entity.apply.enums.EnApplyStatus; +import org.sopt.makers.crew.main.entity.comment.Comment; +import org.sopt.makers.crew.main.entity.comment.CommentRepository; +import org.sopt.makers.crew.main.entity.like.Like; +import org.sopt.makers.crew.main.entity.like.LikeRepository; import org.sopt.makers.crew.main.entity.meeting.Meeting; import org.sopt.makers.crew.main.entity.meeting.MeetingRepository; import org.sopt.makers.crew.main.entity.post.Post; import org.sopt.makers.crew.main.entity.post.PostRepository; +import org.sopt.makers.crew.main.entity.report.Report; +import org.sopt.makers.crew.main.entity.report.ReportRepository; import org.sopt.makers.crew.main.entity.user.User; import org.sopt.makers.crew.main.entity.user.UserRepository; import org.sopt.makers.crew.main.internal.notification.PushNotificationService; @@ -27,12 +38,19 @@ import org.sopt.makers.crew.main.post.v2.dto.query.PostGetPostsCommand; import org.sopt.makers.crew.main.post.v2.dto.request.PostV2CreatePostBodyDto; import org.sopt.makers.crew.main.post.v2.dto.request.PostV2MentionUserInPostRequestDto; +import org.sopt.makers.crew.main.post.v2.dto.request.PostV2UpdatePostBodyDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostDetailBaseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostDetailResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2CreatePostResponseDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2GetPostCountResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2GetPostsResponseDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2ReportResponseDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2SwitchPostLikeResponseDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2UpdatePostResponseDto; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -45,11 +63,17 @@ public class PostV2ServiceImpl implements PostV2Service { private final UserRepository userRepository; private final PostRepository postRepository; private final ApplyRepository applyRepository; + private final CommentRepository commentRepository; + private final LikeRepository likeRepository; + private final ReportRepository reportRepository; + private final PushNotificationService pushNotificationService; @Value("${push-notification.web-url}") private String pushWebUrl; + private final Time time; + /** * λͺ¨μž„ κ²Œμ‹œκΈ€ μž‘μ„± * @@ -109,17 +133,30 @@ public PostV2CreatePostResponseDto createPost(PostV2CreatePostBodyDto requestBod @Override @Transactional(readOnly = true) public PostV2GetPostsResponseDto getPosts(PostGetPostsCommand queryCommand, Integer userId) { + Sort sort = Sort.by(Sort.Direction.ASC, "id"); Page meetingPostListDtos = postRepository.findPostList(queryCommand, - PageRequest.of(queryCommand.getPage() - 1, queryCommand.getTake()), userId); + new CustomPageable(queryCommand.getPage() - 1, sort), userId); - PageOptionsDto pageOptionsDto = new PageOptionsDto(queryCommand.getPage(), - queryCommand.getTake()); + PageOptionsDto pageOptionsDto = new PageOptionsDto(meetingPostListDtos.getPageable().getPageNumber() + 1, + meetingPostListDtos.getPageable().getPageSize()); PageMetaDto pageMetaDto = new PageMetaDto(pageOptionsDto, (int)meetingPostListDtos.getTotalElements()); return PostV2GetPostsResponseDto.of(meetingPostListDtos.getContent(), pageMetaDto); } + /** + * λͺ¨μž„ κ²Œμ‹œκΈ€ 단건 쑰회 + * + * @throws 400 + * @apiNote λͺ¨μž„에 μ†ν•œ μœ μ €λ§Œ μž‘μ„± κ°€λŠ₯ + */ + @Override + @Transactional(readOnly = true) + public PostDetailBaseDto getPost(Integer userId, Integer postId) { + return postRepository.findPost(userId, postId); + } + @Override public void mentionUserInPost(PostV2MentionUserInPostRequestDto requestBody, Integer userId) { User user = userRepository.findByIdOrThrow(userId); @@ -129,7 +166,7 @@ public void mentionUserInPost(PostV2MentionUserInPostRequestDto requestBody, Int user.getName(), post.getTitle()); String pushNotificationWeblink = pushWebUrl + "/post?id=" + post.getId(); - String[] userOrgIds = requestBody.getUserIds().stream() + String[] userOrgIds = requestBody.getOrgIds().stream() .map(Object::toString) .toArray(String[]::new); @@ -143,4 +180,130 @@ public void mentionUserInPost(PostV2MentionUserInPostRequestDto requestBody, Int pushNotificationService.sendPushNotification(pushRequestDto); } + + /** + * λͺ¨μž„ κ²Œμ‹œκΈ€ 개수 쑰회 + * + * @apiNote λͺ¨λ“  μœ μ €κ°€ 쑰회 κ°€λŠ₯ + */ + @Override + @Transactional(readOnly = true) + public PostV2GetPostCountResponseDto getPostCount(Integer meetingId) { + return PostV2GetPostCountResponseDto.of(postRepository.countByMeetingId(meetingId)); + } + + /** + * λͺ¨μž„ κ²Œμ‹œκΈ€ μ‚­μ œ + * + * @throws 403 κΈ€ μž‘μ„±μžκ°€ μ•„λ‹Œ 경우 + * @apiNote 글을 μž‘μ„±ν•œ μœ μ €λ§Œ μ‚­μ œ κ°€λŠ₯ + */ + @Override + @Transactional + public void deletePost(Integer postId, Integer userId) { + Post post = postRepository.findByIdOrThrow(postId); + post.isWriter(userId); + + List comments = commentRepository.findAllByPostIdOrderByCreatedDate(postId); + List commentIds = comments.stream().map(Comment::getId).toList(); + + commentRepository.deleteAllByPostId(postId); + likeRepository.deleteAllByPostId(postId); + likeRepository.deleteAllByCommentIdsInQuery(commentIds); + + postRepository.delete(post); + } + + /** + * λͺ¨μž„ κ²Œμ‹œκΈ€ μˆ˜μ • + * + * @throws 400 μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” κ²Œμ‹œκΈ€μΈ 경우 + * @throws 400 μ—…λ‘œλ“œν•˜λ €λŠ” 이미지가 10개 초과인 경우 + * @throws 403 κΈ€ μž‘μ„±μžκ°€ μ•„λ‹Œ 경우 + * @apiNote 글을 μž‘μ„±ν•œ μœ μ €λ§Œ μˆ˜μ • κ°€λŠ₯ + */ + @Override + @Transactional + public PostV2UpdatePostResponseDto updatePost(Integer postId, PostV2UpdatePostBodyDto requestBody, Integer userId) { + Post post = postRepository.findByIdOrThrow(postId); + post.isWriter(userId); + + if (requestBody.getImages().length > 10) { + throw new BadRequestException(MAX_IMAGE_UPLOAD_EXCEEDED.getErrorCode()); + } + + post.updatePost(requestBody.getTitle(), requestBody.getContents(), requestBody.getImages()); + + return PostV2UpdatePostResponseDto.of(post.getId(), post.getTitle(), post.getContents(), + String.valueOf(time.now()), + post.getImages()); + } + + /** + * λͺ¨μž„ κ²Œμ‹œκΈ€ μ‹ κ³  + * + * @param postId μ‹ κ³ ν•  κ²Œμ‹œκΈ€ id + * @param userId μ‹ κ³ ν•˜λŠ” μœ μ € id + * @return μ‹ κ³  ID + * @throws 400 μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” κ²Œμ‹œκΈ€μΈ 경우 + * @throws 400 이미 μ‹ κ³ ν•œ κ²Œμ‹œκΈ€μΈ 경우 + * @apiNote 쀑볡 μ‹ κ³ λŠ” λ˜μ§€ μ•ŠμŒ + */ + @Override + @Transactional + public PostV2ReportResponseDto reportPost(Integer postId, Integer userId) { + Post post = postRepository.findByIdOrThrow(postId); + + if (reportRepository.existsByPostIdAndUserId(postId, userId)) { + throw new BadRequestException(ALREADY_REPORTED_POST.getErrorCode()); + } + + Report report = Report.builder() + .post(post) + .postId(postId) + .userId(userId) + .build(); + + Report savedReport = reportRepository.save(report); + + return PostV2ReportResponseDto.of(savedReport.getId()); + } + + /** + * λͺ¨μž„ κ²Œμ‹œκΈ€ μ’‹μ•„μš” ν† κΈ€ + * + * @param postId μ’‹μ•„μš” λˆ„λ₯΄λŠ” κ²Œμ‹œκΈ€ id + * @param userId μ’‹μ•„μš” λˆ„λ₯΄λŠ” μœ μ € id + * @return ν•΄λ‹Ή κ²Œμ‹œκΈ€μ„ μ’‹μ•„μš” λˆŒλ €λŠ”μ§€ μ—¬λΆ€ + * @apiNote νšŒμ›λ§Œ ν•  수 있음, μ’‹μ•„μš” λ²„νŠΌ λˆ„λ₯΄λ©΄ μ‚­μ œν–ˆλ‹€κ°€ λ‹€μ‹œ 생김 + */ + @Override + @Transactional + public PostV2SwitchPostLikeResponseDto switchPostLike(Integer postId, Integer userId) { + + Post post = postRepository.findByIdOrThrow(postId); + + // μ’‹μ•„μš” μ·¨μ†Œ + int deletedLikes = likeRepository.deleteByUserIdAndPostId(userId, postId); + + // μ·¨μ†Œλœ μ’‹μ•„μš” 정보가 없을 경우 + if (deletedLikes == 0) { + Like newLike = Like.builder() + .postId(postId) + .userId(userId) + .build(); + + likeRepository.save(newLike); + + // μ’‹μ•„μš” 개수 증가 + post.increaseLikeCount(); + + return PostV2SwitchPostLikeResponseDto.of(true); + } + + // μ·¨μ†Œλœ 경우 κ²Œμ‹œκΈ€ μ’‹μ•„μš” 개수λ₯Ό κ°μ†Œμ‹œν‚΄ + post.decreaseLikeCount(); + + return PostV2SwitchPostLikeResponseDto.of(false); + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/UserApi.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/UserApi.java deleted file mode 100644 index 5d17ecc3..00000000 --- a/main/src/main/java/org/sopt/makers/crew/main/user/v2/UserApi.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.sopt.makers.crew.main.user.v2; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; -import java.security.Principal; -import java.util.List; -import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAllMeetingByUserMeetingDto; -import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAllMentionUserDto; -import org.springframework.http.ResponseEntity; - -@Tag(name = "μ‚¬μš©μž") -public interface UserApi { - - @Operation(summary = "λ‚΄κ°€ μ†ν•œ λͺ¨μž„ 쑰회") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "성곡"), - @ApiResponse(responseCode = "204", description = "λ‚΄κ°€ μ†ν•œ λͺ¨μž„ λ¦¬μŠ€νŠΈκ°€ μ—†λŠ” 경우", content = @Content), - }) - ResponseEntity> getAllMeetingByUser(Principal principal); - - @Operation(summary = "λ©˜μ…˜ μ‚¬μš©μž 쑰회") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "성곡")}) - ResponseEntity> getAllMentionUser(Principal principal); -} diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/UserV2Api.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/UserV2Api.java new file mode 100644 index 00000000..b7a96cb8 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/user/v2/UserV2Api.java @@ -0,0 +1,62 @@ +package org.sopt.makers.crew.main.user.v2; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import java.security.Principal; +import java.util.List; + +import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAllMeetingByUserMeetingDto; +import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAllMentionUserDto; +import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAppliedMeetingByUserResponseDto; +import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetCreatedMeetingByUserResponseDto; +import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetUserOwnProfileResponseDto; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ResponseStatus; + +@Tag(name = "μ‚¬μš©μž") +public interface UserV2Api { + + @Operation(summary = "λ‚΄κ°€ μ†ν•œ λͺ¨μž„ 쑰회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성곡"), + @ApiResponse(responseCode = "204", description = "λ‚΄κ°€ μ†ν•œ λͺ¨μž„ λ¦¬μŠ€νŠΈκ°€ μ—†λŠ” 경우", content = @Content), + }) + ResponseEntity> getAllMeetingByUser(Principal principal); + + @Operation(summary = "λ©˜μ…˜ μ‚¬μš©μž 쑰회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성곡")}) + ResponseEntity> getAllMentionUser(Principal principal); + + @Operation(summary = "μœ μ € 본인 ν”„λ‘œν•„ 쑰회") + @ResponseStatus(HttpStatus.OK) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성곡"), + @ApiResponse(responseCode = "400", description = "ν•΄λ‹Ή μœ μ €κ°€ μ—†λŠ” 경우", content = @Content), + }) + ResponseEntity getUserOwnProfile(Principal principal); + + @Operation(summary = "λ‚΄κ°€ μ‹ μ²­ν•œ λͺ¨μž„ 쑰회") + @ResponseStatus(HttpStatus.OK) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성곡") + }) + ResponseEntity getAppliedMeetingByUser( + Principal principal); + + @Operation(summary = "λ‚΄κ°€ λ§Œλ“  λͺ¨μž„ 쑰회") + @ResponseStatus(HttpStatus.OK) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성곡") + }) + ResponseEntity getCreatedMeetingByUser( + Principal principal); + + + + +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/UserV2Controller.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/UserV2Controller.java index 61f90ef1..43bfe6ed 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/user/v2/UserV2Controller.java +++ b/main/src/main/java/org/sopt/makers/crew/main/user/v2/UserV2Controller.java @@ -2,10 +2,15 @@ import java.security.Principal; import java.util.List; + import lombok.RequiredArgsConstructor; + import org.sopt.makers.crew.main.common.util.UserUtil; import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAllMeetingByUserMeetingDto; import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAllMentionUserDto; +import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAppliedMeetingByUserResponseDto; +import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetCreatedMeetingByUserResponseDto; +import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetUserOwnProfileResponseDto; import org.sopt.makers.crew.main.user.v2.service.UserV2Service; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -17,22 +22,50 @@ @RestController @RequestMapping("/user/v2") @RequiredArgsConstructor -public class UserV2Controller implements UserApi { - - private final UserV2Service userV2Service; - - @GetMapping("/meeting/all") - @ResponseStatus(HttpStatus.OK) - public ResponseEntity> getAllMeetingByUser( - Principal principal) { - Integer userId = UserUtil.getUserId(principal); - return ResponseEntity.ok(userV2Service.getAllMeetingByUser(userId)); - } - - @GetMapping("/mention") - public ResponseEntity> getAllMentionUser( - Principal principal) { - UserUtil.getUserId(principal); - return ResponseEntity.ok(userV2Service.getAllMentionUser()); - } +public class UserV2Controller implements UserV2Api { + + private final UserV2Service userV2Service; + + @GetMapping("/meeting/all") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity> getAllMeetingByUser( + Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.ok(userV2Service.getAllMeetingByUser(userId)); + } + + @Override + @GetMapping("/mention") + public ResponseEntity> getAllMentionUser( + Principal principal) { + + UserUtil.getUserId(principal); + return ResponseEntity.ok(userV2Service.getAllMentionUser()); + } + + @Override + @GetMapping("/profile/me") + public ResponseEntity getUserOwnProfile(Principal principal) { + + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.ok().body(userV2Service.getUserOwnProfile(userId)); + } + + @Override + @GetMapping("/apply") + public ResponseEntity getAppliedMeetingByUser(Principal principal) { + + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.ok().body(userV2Service.getAppliedMeetingByUser(userId)); + } + + @Override + @GetMapping("/meeting") + public ResponseEntity getCreatedMeetingByUser(Principal principal) { + + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.ok().body(userV2Service.getCreatedMeetingByUser(userId)); + } + + } diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/ApplyV2GetAppliedMeetingByUserResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/ApplyV2GetAppliedMeetingByUserResponseDto.java new file mode 100644 index 00000000..460d9d26 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/ApplyV2GetAppliedMeetingByUserResponseDto.java @@ -0,0 +1,22 @@ +package org.sopt.makers.crew.main.user.v2.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; + +@Schema(name = "ApplyV2GetAppliedMeetingByUserResponseDto", description = "λ‚΄κ°€ μ‹ μ²­ν•œ λͺ¨μž„ Dto") +public record ApplyV2GetAppliedMeetingByUserResponseDto( + @Schema(description = "μ‹ μ²­ id", example = "130") + @NotNull + Integer id, + @Schema(description = "μ‹ μ²­ μƒνƒœ", example = "1") + @NotNull + Integer status, + @Schema(description = "μ‹ μ²­ λͺ¨μž„ 정보") + @NotNull + MeetingV2GetCreatedMeetingByUserResponseDto meeting +) { + public static ApplyV2GetAppliedMeetingByUserResponseDto of(Integer id, Integer status, + MeetingV2GetCreatedMeetingByUserResponseDto meeting) { + return new ApplyV2GetAppliedMeetingByUserResponseDto(id, status, meeting); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/MeetingV2GetCreatedMeetingByUserResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/MeetingV2GetCreatedMeetingByUserResponseDto.java new file mode 100644 index 00000000..781b9d5e --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/MeetingV2GetCreatedMeetingByUserResponseDto.java @@ -0,0 +1,80 @@ +package org.sopt.makers.crew.main.user.v2.dto.response; + +import java.time.LocalDateTime; +import java.util.List; + +import org.sopt.makers.crew.main.common.constant.CrewConst; +import org.sopt.makers.crew.main.common.dto.MeetingCreatorDto; +import org.sopt.makers.crew.main.entity.meeting.Meeting; +import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart; +import org.sopt.makers.crew.main.entity.meeting.vo.ImageUrlVO; +import org.sopt.makers.crew.main.entity.user.User; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; + +@Schema(name = "MeetingV2GetCreatedMeetingByUserResponseDto", description = "λͺ¨μž„ Dto") +public record MeetingV2GetCreatedMeetingByUserResponseDto( + @Schema(description = "λͺ¨μž„ id", example = "1") + @NotNull + Integer id, + @Schema(description = "λͺ¨μž„ 제λͺ©", example = "λͺ¨μž„ 제λͺ©μž…λ‹ˆλ‹€") + @NotNull + String title, + @Schema(description = "λŒ€μƒ 기수", example = "33") + @NotNull + Integer targetActiveGeneration, + @Schema(example = "[\n" + + " \"ANDROID\",\n" + + " \"IOS\"\n" + + " ]", description = "λŒ€μƒ 파트 λͺ©λ‘") + @NotNull + MeetingJoinablePart[] joinableParts, + @Schema(example = "μŠ€ν„°λ””", description = "λͺ¨μž„ μΉ΄ν…Œκ³ λ¦¬") + @NotNull + String category, + @Schema(example = "false", description = "ν™œλ™κΈ°μˆ˜λ§Œ 지원 κ°€λŠ₯ μ—¬λΆ€") + @NotNull + Boolean canJoinOnlyActiveGeneration, + @Schema(example = "2", description = "λͺ¨μž„ ν™œλ™ μƒνƒœ") + @NotNull + Integer status, + /** + * 썸넀일 이미지 + * + * @apiNote μ—¬λŸ¬κ°œμ—¬λ„ 첫번째 μ΄λ―Έμ§€λ§Œ μ‚¬μš© + */ + @Schema(description = "λͺ¨μž„ 사진", example = "[url] ν˜•μ‹") + @NotNull + List imageURL, + @Schema(example = "false", description = "λ©˜ν†  ν•„μš” μ—¬λΆ€") + @NotNull + Boolean isMentorNeeded, + @Schema(description = "λͺ¨μž„ ν™œλ™ μ‹œμž‘μΌ", example = "2024-07-31T15:30:00") + @NotNull + LocalDateTime mStartDate, + @Schema(description = "λͺ¨μž„ ν™œλ™ μ’…λ£ŒμΌ", example = "2024-08-25T15:30:00") + @NotNull + LocalDateTime mEndDate, + @Schema(description = "λͺ¨μ§‘ 인원", example = "20") + @NotNull + int capacity, + @Schema(description = "λͺ¨μž„μž₯ 정보", example = "") + @NotNull + MeetingCreatorDto user, + @Schema(description = "μ‹ μ²­ 수", example = "7") + @NotNull + int appliedCount +) { + public static MeetingV2GetCreatedMeetingByUserResponseDto of(Meeting meeting, User meetingCreator, int appliedCount, LocalDateTime now) { + MeetingCreatorDto creatorDto = MeetingCreatorDto.of(meetingCreator); + boolean canJoinOnlyActiveGeneration = meeting.getTargetActiveGeneration() == CrewConst.ACTIVE_GENERATION + && meeting.getCanJoinOnlyActiveGeneration(); + + return new MeetingV2GetCreatedMeetingByUserResponseDto(meeting.getId(), meeting.getTitle(), + meeting.getTargetActiveGeneration(), meeting.getJoinableParts(), meeting.getCategory().getValue(), + canJoinOnlyActiveGeneration, meeting.getMeetingStatus(now), meeting.getImageURL(), + meeting.getIsMentorNeeded(), meeting.getMStartDate(), meeting.getMEndDate(), meeting.getCapacity(), creatorDto, appliedCount); + } + +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetAllMeetingByUserMeetingDto.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetAllMeetingByUserMeetingDto.java index e3f79a65..362318a1 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetAllMeetingByUserMeetingDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetAllMeetingByUserMeetingDto.java @@ -1,15 +1,32 @@ package org.sopt.makers.crew.main.user.v2.dto.response; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor(staticName = "of") +@Schema(name = "UserV2GetAllMeetingByUserMeetingDto", description = "λ‚΄κ°€ μ†ν•œ λͺ¨μž„ 쑰회 응닡 Dto") public class UserV2GetAllMeetingByUserMeetingDto { - private int id; - private String title; - private String contents; - private String imageUrl; - private String category; + @Schema(description = "λͺ¨μž„ id", example = "1") + @NotNull + private int id; + + @Schema(description = "λͺ¨μž„ 제λͺ©", example = "λͺ¨μž„ 제λͺ©μž…λ‹ˆλ‹€.") + @NotNull + private String title; + + @Schema(description = "λͺ¨μž„ λ‚΄μš©", example = "λͺ¨μž„ λ‚΄μš©μž…λ‹ˆλ‹€.") + @NotNull + private String contents; + + @Schema(description = "λͺ¨μž„ 사진", example = "[url] ν˜•μ‹") + @NotNull + private String imageUrl; + + @Schema(description = "λͺ¨μž„ μΉ΄ν…Œκ³ λ¦¬", example = "μŠ€ν„°λ””") + @NotNull + private String category; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetAllMentionUserDto.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetAllMentionUserDto.java index f25de8b2..938b221a 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetAllMentionUserDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetAllMentionUserDto.java @@ -1,19 +1,32 @@ package org.sopt.makers.crew.main.user.v2.dto.response; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor(staticName = "of") +@Schema(name = "UserV2GetAllMentionUserDto", description = "λ©˜μ…˜ μœ μ € 쑰회 응닡 Dto") public class UserV2GetAllMentionUserDto { - /** - * 주의!! : ν•„λ“œλͺ…은 userId μ΄μ§€λ§Œ μ‹€μ œ 응닡 ν•΄μ•Όν•˜λŠ” λ°μ΄ν„°λŠ” orgId μž…λ‹ˆλ‹€. - */ - private final Integer userId; - - private final String userName; - private final String recentPart; - private final int recentGeneration; - private final String profileImageUrl; + + @Schema(description = "λ©”μ΄μ»€μŠ€ ν”„λ‘œλ•νŠΈμ—μ„œ λ²”μš©μ μœΌλ‘œ μ‚¬μš©ν•˜λŠ” userId", example = "1") + @NotNull + private final Integer orgId; + + @Schema(description = "μœ μ € 이름", example = "홍길동") + @NotNull + private final String userName; + + @Schema(description = "졜근 파트", example = "μ„œλ²„") + @NotNull + private final String recentPart; + + @Schema(description = "졜근 기수", example = "33") + @NotNull + private final int recentGeneration; + + @Schema(description = "μœ μ € ν”„λ‘œν•„ 사진", example = "[url] ν˜•μ‹") + private final String profileImageUrl; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetAppliedMeetingByUserResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetAppliedMeetingByUserResponseDto.java new file mode 100644 index 00000000..50bc741d --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetAppliedMeetingByUserResponseDto.java @@ -0,0 +1,20 @@ +package org.sopt.makers.crew.main.user.v2.dto.response; + +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; + +@Schema(name = "UserV2GetAppliedMeetingByUserResponseDto", description = "λ‚΄κ°€ μ‹ μ²­ν•œ λͺ¨μž„ 쑰회 Dto") +public record UserV2GetAppliedMeetingByUserResponseDto( + @Schema(description = "λ‚΄κ°€ μ‹ μ²­ν•œ λͺ¨μž„ 정보") + @NotNull + List apply, + @Schema(description = "λ‚΄κ°€ μ‹ μ²­ν•œ λͺ¨μž„ 갯수") + @NotNull + Integer count +) { + public static UserV2GetAppliedMeetingByUserResponseDto of(List apply){ + return new UserV2GetAppliedMeetingByUserResponseDto(apply, apply.size()); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetCreatedMeetingByUserResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetCreatedMeetingByUserResponseDto.java new file mode 100644 index 00000000..63c0d515 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetCreatedMeetingByUserResponseDto.java @@ -0,0 +1,20 @@ +package org.sopt.makers.crew.main.user.v2.dto.response; + +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; + +@Schema(name = "UserV2GetCreatedMeetingByUserResponseDto", description = "λ‚΄κ°€ μƒμ„±ν•œ λͺ¨μž„ 쑰회 Dto") +public record UserV2GetCreatedMeetingByUserResponseDto( + @Schema(description = "λ‚΄κ°€ μƒμ„±ν•œ λͺ¨μž„ 정보") + @NotNull + List meetings, + @Schema(description = "λ‚΄κ°€ μ‹ μ²­ν•œ λͺ¨μž„ 갯수") + @NotNull + Integer count +) { + public static UserV2GetCreatedMeetingByUserResponseDto of(List meetings){ + return new UserV2GetCreatedMeetingByUserResponseDto(meetings, meetings.size()); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetUserOwnProfileResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetUserOwnProfileResponseDto.java new file mode 100644 index 00000000..22986134 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetUserOwnProfileResponseDto.java @@ -0,0 +1,31 @@ +package org.sopt.makers.crew.main.user.v2.dto.response; + +import org.sopt.makers.crew.main.entity.user.User; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; + +@Schema(name = "UserV2GetUserOwnProfileResponseDto", description = "μœ μ € 본인 ν”„λ‘œν•„ 쑰회 응닡 Dto") +public record UserV2GetUserOwnProfileResponseDto( + @Schema(description = "μœ μ € id, ν¬λ£¨μ—μ„œ μ‚¬μš©ν•˜λŠ” userId", example = "1") + @NotNull + Integer id, + @Schema(description = "org id, λ©”μ΄μ»€μŠ€ ν”„λ‘œλ•νŠΈμ—μ„œ λ²”μš©μ μœΌλ‘œ μ‚¬μš©ν•˜λŠ” userId", example = "1") + @NotNull + Integer orgId, + @Schema(description = "μœ μ € 이름", example = "홍길동") + @NotNull + String name, + @Schema(description = "μœ μ € ν”„λ‘œν•„ 이미지", example = "[url ν˜•μ‹]") + String profileImage, + @Schema(description = "μœ μ € ν™œλ™ 가지고 μžˆλŠ”μ§€ μ—¬λΆ€, μœ μ € ν™œλ™μ΄ μ—†μœΌλ©΄ false", example = "1") + @NotNull + Boolean hasActivities +) { + public static UserV2GetUserOwnProfileResponseDto of(User user){ + boolean hasActivities = !user.getActivities().isEmpty(); + + return new UserV2GetUserOwnProfileResponseDto(user.getId(), user.getOrgId(), user.getName(), + user.getProfileImage(), hasActivities); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2Service.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2Service.java index a6710c9d..1b71514d 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2Service.java +++ b/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2Service.java @@ -1,12 +1,24 @@ package org.sopt.makers.crew.main.user.v2.service; import java.util.List; + import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAllMeetingByUserMeetingDto; import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAllMentionUserDto; +import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAppliedMeetingByUserResponseDto; +import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetCreatedMeetingByUserResponseDto; +import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetUserOwnProfileResponseDto; public interface UserV2Service { - List getAllMeetingByUser(Integer userId); + List getAllMeetingByUser(Integer userId); + + List getAllMentionUser(); + + UserV2GetUserOwnProfileResponseDto getUserOwnProfile(Integer userId); + + UserV2GetAppliedMeetingByUserResponseDto getAppliedMeetingByUser(Integer userId); + + UserV2GetCreatedMeetingByUserResponseDto getCreatedMeetingByUser(Integer userId); + - List getAllMentionUser(); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2ServiceImpl.java index cba42120..742842f9 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2ServiceImpl.java @@ -8,14 +8,22 @@ import lombok.RequiredArgsConstructor; import org.sopt.makers.crew.main.common.exception.BaseException; +import org.sopt.makers.crew.main.common.util.Time; +import org.sopt.makers.crew.main.entity.apply.Applies; +import org.sopt.makers.crew.main.entity.apply.Apply; import org.sopt.makers.crew.main.entity.apply.ApplyRepository; import org.sopt.makers.crew.main.entity.apply.enums.EnApplyStatus; import org.sopt.makers.crew.main.entity.meeting.Meeting; import org.sopt.makers.crew.main.entity.meeting.MeetingRepository; import org.sopt.makers.crew.main.entity.user.User; import org.sopt.makers.crew.main.entity.user.UserRepository; +import org.sopt.makers.crew.main.user.v2.dto.response.ApplyV2GetAppliedMeetingByUserResponseDto; +import org.sopt.makers.crew.main.user.v2.dto.response.MeetingV2GetCreatedMeetingByUserResponseDto; import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAllMeetingByUserMeetingDto; import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAllMentionUserDto; +import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAppliedMeetingByUserResponseDto; +import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetCreatedMeetingByUserResponseDto; +import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetUserOwnProfileResponseDto; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -29,6 +37,8 @@ public class UserV2ServiceImpl implements UserV2Service { private final ApplyRepository applyRepository; private final MeetingRepository meetingRepository; + private final Time time; + @Override public List getAllMeetingByUser(Integer userId) { User user = userRepository.findByIdOrThrow(userId); @@ -69,4 +79,43 @@ public List getAllMentionUser() { user.getProfileImage())) .toList(); } + + @Override + public UserV2GetUserOwnProfileResponseDto getUserOwnProfile(Integer userId) { + User user = userRepository.findByIdOrThrow(userId); + return UserV2GetUserOwnProfileResponseDto.of(user); + } + + @Override + public UserV2GetCreatedMeetingByUserResponseDto getCreatedMeetingByUser(Integer userId) { + User meetingCreator = userRepository.findByIdOrThrow(userId); + + List meetings = meetingRepository.findAllByUser(meetingCreator); + List meetingIds = meetings.stream().map(Meeting::getId).toList(); + Applies applies = new Applies(applyRepository.findAllByMeetingIdIn(meetingIds)); + + List meetingByUserDtos = meetings.stream() + .map(meeting -> MeetingV2GetCreatedMeetingByUserResponseDto.of(meeting, meetingCreator, + applies.getAppliedCount(meeting.getId()), time.now())) + .toList(); + + return UserV2GetCreatedMeetingByUserResponseDto.of(meetingByUserDtos); + } + + @Override + public UserV2GetAppliedMeetingByUserResponseDto getAppliedMeetingByUser(Integer userId) { + List myApplies = applyRepository.findAllByUserId(userId); + List meetingIds = myApplies.stream().map(Apply::getMeetingId).toList(); + + Applies allApplies = new Applies(applyRepository.findAllByMeetingIdIn(meetingIds)); + + List appliedMeetingByUserDtos = myApplies.stream() + .map(apply -> ApplyV2GetAppliedMeetingByUserResponseDto.of( + apply.getId(), apply.getStatus().getValue(), + MeetingV2GetCreatedMeetingByUserResponseDto.of(apply.getMeeting(), apply.getMeeting().getUser(), + allApplies.getAppliedCount(apply.getMeetingId()), time.now()) + )).toList(); + + return UserV2GetAppliedMeetingByUserResponseDto.of(appliedMeetingByUserDtos); + } } diff --git a/main/src/main/resources/application-dev.yml b/main/src/main/resources/application-dev.yml index b4013a68..b406236b 100644 --- a/main/src/main/resources/application-dev.yml +++ b/main/src/main/resources/application-dev.yml @@ -12,6 +12,7 @@ spring: username: ${DEV_DB_USERNAME} password: ${DEV_DB_PASSWORD} jpa: + open-in-view: false hibernate: naming: physical-strategy: org.sopt.makers.crew.main.common.config.CamelCaseNamingStrategy @@ -34,6 +35,12 @@ aws-property: s3-bucket-name: ${AWS_S3_BUCKET_NAME} access-key: ${AWS_ACCESS_KEY_ID} secret-key: ${AWS_SECRET_ACCESS_KEY} + file-min-size: ${AWS_FILE_MIN_SIZE} + file-max-size: ${AWS_FILE_MAX_SIZE} + algorithm: ${AWS_ALGORITHM} + content-type: ${AWS_CONTENT_TYPE} + request-type: ${AWS_REQUEST_TYPE} + object-url: ${AWS_OBJECT_URL} springdoc: packages-to-scan: org.sopt.makers.crew @@ -54,4 +61,9 @@ push-notification: push-server-url: ${DEV_PUSH_SERVER_URL} notice: - secret-key : ${NOTICE_SECRET_KEY} \ No newline at end of file + secret-key : ${NOTICE_SECRET_KEY} + +playground: + server: + url: ${DEV_PLAYGROUND_URL} + endpoint: ${PLAYGROUND_ENDPOINT} \ No newline at end of file diff --git a/main/src/main/resources/application-prod.yml b/main/src/main/resources/application-prod.yml index 4110fa7b..7b593c9c 100644 --- a/main/src/main/resources/application-prod.yml +++ b/main/src/main/resources/application-prod.yml @@ -12,6 +12,7 @@ spring: username: ${PROD_DB_USERNAME} password: ${PROD_DB_PASSWORD} jpa: + open-in-view: false hibernate: naming: physical-strategy: org.sopt.makers.crew.main.common.config.CamelCaseNamingStrategy @@ -33,6 +34,12 @@ aws-property: s3-bucket-name: ${AWS_S3_BUCKET_NAME} access-key: ${AWS_ACCESS_KEY_ID} secret-key: ${AWS_SECRET_ACCESS_KEY} + file-min-size: ${AWS_FILE_MIN_SIZE} + file-max-size: ${AWS_FILE_MAX_SIZE} + algorithm: ${AWS_ALGORITHM} + content-type: ${AWS_CONTENT_TYPE} + request-type: ${AWS_REQUEST_TYPE} + object-url: ${AWS_OBJECT_URL} springdoc: packages-to-scan: org.sopt.makers.crew @@ -53,4 +60,9 @@ push-notification: push-server-url: ${PROD_PUSH_SERVER_URL} notice: - secret-key : ${NOTICE_SECRET_KEY} \ No newline at end of file + secret-key : ${NOTICE_SECRET_KEY} + +playground: + server: + url: ${PROD_PLAYGROUND_URL} + endpoint: ${PLAYGROUND_ENDPOINT} \ No newline at end of file diff --git a/main/src/main/resources/application-test.yml b/main/src/main/resources/application-test.yml index d245c43c..5e4abe55 100644 --- a/main/src/main/resources/application-test.yml +++ b/main/src/main/resources/application-test.yml @@ -14,6 +14,7 @@ spring: # username: root # password: jpa: + open-in-view: false hibernate: naming: physical-strategy: org.sopt.makers.crew.main.common.config.CamelCaseNamingStrategy @@ -37,6 +38,12 @@ aws-property: s3-bucket-name: ${AWS_S3_BUCKET_NAME} access-key: ${AWS_ACCESS_KEY_ID} secret-key: ${AWS_SECRET_ACCESS_KEY} + file-min-size: ${AWS_FILE_MIN_SIZE} + file-max-size: ${AWS_FILE_MAX_SIZE} + algorithm: ${AWS_ALGORITHM} + content-type: ${AWS_CONTENT_TYPE} + request-type: ${AWS_REQUEST_TYPE} + object-url: ${AWS_OBJECT_URL} springdoc: packages-to-scan: org.sopt.makers.crew @@ -57,4 +64,9 @@ push-notification: push-server-url: ${DEV_PUSH_SERVER_URL} notice: - secret-key : ${NOTICE_SECRET_KEY} \ No newline at end of file + secret-key : ${NOTICE_SECRET_KEY} + +playground: + server: + url: ${DEV_PLAYGROUND_URL} + endpoint: ${PLAYGROUND_ENDPOINT} \ No newline at end of file diff --git a/main/src/test/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceTest.java b/main/src/test/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceTest.java index c970c079..87690663 100644 --- a/main/src/test/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceTest.java +++ b/main/src/test/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceTest.java @@ -5,9 +5,8 @@ import static org.mockito.Mockito.doReturn; import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; +import java.time.Month; import java.util.Optional; - import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; @@ -15,7 +14,6 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2ReportCommentResponseDto; import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2UpdateCommentResponseDto; @@ -24,6 +22,9 @@ import org.sopt.makers.crew.main.common.util.Time; import org.sopt.makers.crew.main.entity.comment.Comment; import org.sopt.makers.crew.main.entity.comment.CommentRepository; +import org.sopt.makers.crew.main.entity.meeting.Meeting; +import org.sopt.makers.crew.main.entity.meeting.enums.MeetingCategory; +import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart; import org.sopt.makers.crew.main.entity.post.Post; import org.sopt.makers.crew.main.entity.post.PostRepository; import org.sopt.makers.crew.main.entity.report.Report; @@ -35,142 +36,164 @@ @ExtendWith(MockitoExtension.class) public class CommentV2ServiceTest { - @InjectMocks - private CommentV2ServiceImpl commentV2Service; - @Mock - private CommentRepository commentRepository; - @Mock - private PostRepository postRepository; - @Mock - private ReportRepository reportRepository; - @Mock - private UserRepository userRepository; - @Mock - private Time time; - - private Comment comment; - - private Post post; - - private User user; - - private Report report; - - @BeforeEach - void init() { - user = UserFixture.createStaticUser(); - user.setUserIdForTest(1); - - String[] images = {"image1", "image2", "image3"}; - this.post = Post.builder().user(user).title("title").contents("contents").images(images) - .build(); - this.comment = Comment.builder() - .contents("contents") - .post(post) - .postId(1) - .user(user) - .userId(1).build(); - - this.report = Report.builder().comment(comment).user(user).build(); - } - - @Nested - class λŒ“κΈ€_μˆ˜μ • { - - @Test - void 성곡() { - // given - String updatedContents = "updatedContents"; - LocalDateTime expectedUpdatedDate = time.now(); - - doReturn(comment).when(commentRepository).findByIdOrThrow(any()); - doReturn(LocalDateTime.of(2024, 4, 24, 23, 59)).when(time).now(); - - // when - CommentV2UpdateCommentResponseDto result = commentV2Service.updateComment(comment.getId(), - updatedContents, user.getId()); - - // then - Assertions.assertThat(result.getId()).isEqualTo(comment.getId()); - Assertions.assertThat(result.getContents()).isEqualTo(updatedContents); - Assertions.assertThat(LocalDateTime.parse(result.getUpdateDate())) - .isEqualTo(LocalDateTime.of(2024, 4, 24, 23, 59)); - } - - @Test - void μ‹€νŒ¨_본인_μž‘μ„±_λŒ“κΈ€_μ•„λ‹˜() { - // given - doReturn(comment).when(commentRepository).findByIdOrThrow(any()); - - // when & then - assertThrows(ForbiddenException.class, () -> { - commentV2Service.updateComment(comment.getId(), "updatedContents", - comment.getUser().getId() + 1); - }); - } - } - - @Nested - class λŒ“κΈ€_μ‚­μ œ { - - @Test - void 성곡() { - // given - int initialCommentCount = post.getCommentCount(); - doReturn(comment).when(commentRepository).findByIdOrThrow(any()); - doReturn(post).when(postRepository).findByIdOrThrow((any())); - - // when - commentV2Service.deleteComment(comment.getId(), user.getId()); - - // then - Assertions.assertThat(commentRepository.findById(comment.getId())) - .isEqualTo(Optional.empty()); - Assertions.assertThat(post.getCommentCount()).isEqualTo(initialCommentCount - 1); - } - - @Test - void μ‹€νŒ¨_본인_μž‘μ„±_λŒ“κΈ€_μ•„λ‹˜() { - // given - doReturn(comment).when(commentRepository).findByIdOrThrow(any()); - Integer id = comment.getUser().getId(); - // when & then - assertThrows(ForbiddenException.class, () -> { - commentV2Service.deleteComment(0, comment.getUser().getId() + 1); - }); - } - } - - @Nested - class λŒ“κΈ€_μ‹ κ³  { - - @Test - void λŒ“κΈ€_μ‹ κ³ _성곡() { - // given - doReturn(comment).when(commentRepository).findByIdOrThrow(any()); - doReturn(user).when(userRepository).findByIdOrThrow(any()); - doReturn(Optional.empty()).when(reportRepository).findByCommentAndUser(any(), any()); - doReturn(report).when(reportRepository).save(any()); - - // when - CommentV2ReportCommentResponseDto result = commentV2Service.reportComment(comment.getId(), - user.getId()); - - // then - Assertions.assertThat(result.getReportId()).isEqualTo(report.getId()); - } - - @Test - void λŒ“κΈ€_μ‹ κ³ _μ‹€νŒ¨_이미_μ‹ κ³ ν•œ_λŒ“κΈ€() { - // given - doReturn(comment).when(commentRepository).findByIdOrThrow(any()); - doReturn(user).when(userRepository).findByIdOrThrow(any()); - doReturn(Optional.of(report)).when(reportRepository).findByCommentAndUser(any(), any()); - - // when & then - assertThrows(BadRequestException.class, () -> { - commentV2Service.reportComment(comment.getId(), user.getId()); - }); - } - } + @InjectMocks + private CommentV2ServiceImpl commentV2Service; + @Mock + private CommentRepository commentRepository; + @Mock + private PostRepository postRepository; + @Mock + private ReportRepository reportRepository; + @Mock + private UserRepository userRepository; + @Mock + private Time time; + + private Comment comment; + + private Post post; + + private User user; + + private Report report; + + private Meeting meeting; + + @BeforeEach + void init() { + user = UserFixture.createStaticUser(); + user.setUserIdForTest(1); + + meeting = Meeting.builder() + .user(user) + .userId(user.getId()) + .title("μ‚¬λžŒ κ΅¬ν•΄μš”") + .category(MeetingCategory.STUDY) + .startDate(LocalDateTime.of(2024, Month.MARCH, 17, 0, 0)) + .endDate(LocalDateTime.of(2024, Month.MARCH, 20, 23, 59)) + .capacity(10) + .desc("μ—΄μ • λ§Žμ€ μ‚¬λžŒ κ΅¬ν•΄μš”") + .processDesc("μ΄λ ‡κ²Œ 할거에여") + .mStartDate(LocalDateTime.of(2024, Month.APRIL, 1, 0, 0)) + .mEndDate(LocalDateTime.of(2030, Month.APRIL, 20, 0, 0)) + .leaderDesc("μ €λŠ” 이런 μ‚¬λžŒμ΄μ—μš”.") + .targetDesc("이런 μ‚¬λžŒμ΄ μ™”μœΌλ©΄ μ’‹κ² μ–΄μš”") + .note("μœ μ˜μ‚¬ν•­μ€ μ΄κ±°μ—μš”") + .isMentorNeeded(true) + .canJoinOnlyActiveGeneration(true) + .createdGeneration(33) + .targetActiveGeneration(33) + .joinableParts(MeetingJoinablePart.values()) + .build(); + + String[] images = {"image1", "image2", "image3"}; + this.post = Post.builder().user(user).title("title").contents("contents").images(images).meeting(meeting) + .build(); + this.comment = Comment.builder() + .contents("contents") + .post(post) + .postId(1) + .user(user) + .userId(1).build(); + + this.report = Report.builder().comment(comment).userId(user.getId()).build(); + } + + @Nested + class λŒ“κΈ€_μˆ˜μ • { + + @Test + void 성곡() { + // given + String updatedContents = "updatedContents"; + LocalDateTime expectedUpdatedDate = time.now(); + + doReturn(comment).when(commentRepository).findByIdOrThrow(any()); + doReturn(LocalDateTime.of(2024, 4, 24, 23, 59)).when(time).now(); + + // when + CommentV2UpdateCommentResponseDto result = commentV2Service.updateComment(comment.getId(), + updatedContents, user.getId()); + + // then + Assertions.assertThat(result.getId()).isEqualTo(comment.getId()); + Assertions.assertThat(result.getContents()).isEqualTo(updatedContents); + Assertions.assertThat(LocalDateTime.parse(result.getUpdateDate())) + .isEqualTo(LocalDateTime.of(2024, 4, 24, 23, 59)); + } + + @Test + void μ‹€νŒ¨_본인_μž‘μ„±_λŒ“κΈ€_μ•„λ‹˜() { + // given + doReturn(comment).when(commentRepository).findByIdOrThrow(any()); + + // when & then + assertThrows(ForbiddenException.class, () -> { + commentV2Service.updateComment(comment.getId(), "updatedContents", + comment.getUser().getId() + 1); + }); + } + } + + @Nested + class λŒ“κΈ€_μ‚­μ œ { + + @Test + void 성곡() { + // given + int initialCommentCount = post.getCommentCount(); + doReturn(comment).when(commentRepository).findByIdOrThrow(any()); + doReturn(post).when(postRepository).findByIdOrThrow((any())); + + // when + commentV2Service.deleteComment(comment.getId(), user.getId()); + + // then + Assertions.assertThat(commentRepository.findById(comment.getId())) + .isEqualTo(Optional.empty()); + Assertions.assertThat(post.getCommentCount()).isEqualTo(initialCommentCount - 1); + } + + @Test + void μ‹€νŒ¨_본인_μž‘μ„±_λŒ“κΈ€_μ•„λ‹˜() { + // given + doReturn(comment).when(commentRepository).findByIdOrThrow(any()); + Integer id = comment.getUser().getId(); + // when & then + assertThrows(ForbiddenException.class, () -> { + commentV2Service.deleteComment(0, comment.getUser().getId() + 1); + }); + } + } + + @Nested + class λŒ“κΈ€_μ‹ κ³  { + + @Test + void λŒ“κΈ€_μ‹ κ³ _성곡() { + // given + doReturn(comment).when(commentRepository).findByIdOrThrow(any()); + doReturn(false).when(reportRepository).existsByCommentIdAndUserId(any(), any()); + doReturn(report).when(reportRepository).save(any()); + + // when + CommentV2ReportCommentResponseDto result = commentV2Service.reportComment(comment.getId(), + user.getId()); + + // then + Assertions.assertThat(result.getReportId()).isEqualTo(report.getId()); + } + + @Test + void λŒ“κΈ€_μ‹ κ³ _μ‹€νŒ¨_이미_μ‹ κ³ ν•œ_λŒ“κΈ€() { + // given + doReturn(comment).when(commentRepository).findByIdOrThrow(any()); + doReturn(true).when(reportRepository).existsByCommentIdAndUserId(any(), any()); + + // when & then + assertThrows(BadRequestException.class, () -> { + commentV2Service.reportComment(comment.getId(), user.getId()); + }); + } + } } diff --git a/main/src/test/java/org/sopt/makers/crew/main/entity/apply/AppliesTest.java b/main/src/test/java/org/sopt/makers/crew/main/entity/apply/AppliesTest.java new file mode 100644 index 00000000..f903b22a --- /dev/null +++ b/main/src/test/java/org/sopt/makers/crew/main/entity/apply/AppliesTest.java @@ -0,0 +1,218 @@ +package org.sopt.makers.crew.main.entity.apply; + +import java.time.LocalDateTime; +import java.util.List; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.sopt.makers.crew.main.entity.apply.enums.EnApplyStatus; +import org.sopt.makers.crew.main.entity.apply.enums.EnApplyType; +import org.sopt.makers.crew.main.entity.meeting.Meeting; +import org.sopt.makers.crew.main.entity.meeting.enums.MeetingCategory; +import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart; +import org.sopt.makers.crew.main.entity.user.User; +import org.sopt.makers.crew.main.entity.user.vo.UserActivityVO; + +public class AppliesTest { + + @Test + void 승인된_μ‹ μ²­μž_수_쑰회(){ + // given + User host = User.builder().name("κΉ€μ² μˆ˜") + .orgId(1) + .activities(List.of(new UserActivityVO("μ›Ή", 31))) + .profileImage(null) + .phone("010-2222-2222") + .build(); + + Meeting meeting = Meeting.builder() + .user(host) + .title("λͺ¨μž„ 제λͺ©μž…λ‹ˆλ‹€") + .category(MeetingCategory.EVENT) + .startDate(LocalDateTime.of(2024,4,24,0,0,0)) + .endDate(LocalDateTime.of(2024,4,29,23,59,59)) + .capacity(20) + .desc("λͺ¨μž„ μ†Œκ°œμž…λ‹ˆλ‹€") + .processDesc("진행방식 μ†Œκ°œμž…λ‹ˆλ‹€") + .mStartDate(LocalDateTime.of(2024,5,24,0,0,0)) + .mEndDate(LocalDateTime.of(2024,6,24,0,0,0)) + .leaderDesc("슀μž₯ μ†Œκ°œμž…λ‹ˆλ‹€") + .targetDesc("λͺ¨μ§‘ λŒ€μƒμž…λ‹ˆλ‹€") + .isMentorNeeded(true) + .canJoinOnlyActiveGeneration(false) + .createdGeneration(34) + .targetActiveGeneration(null) + .joinableParts(MeetingJoinablePart.values()) + .build(); + + User applicant1 = User.builder().name("홍길동") + .orgId(2) + .activities(List.of(new UserActivityVO("μ„œλ²„", 33))) + .profileImage(null) + .phone("010-1234-5678") + .build(); + + User applicant2 = User.builder().name("κΉ€μ‚Όμˆœ") + .orgId(3) + .activities(List.of(new UserActivityVO("λ””μžμΈ", 34))) + .profileImage(null) + .phone("010-1111-1111") + .build(); + + Apply apply1 = Apply.builder() + .type(EnApplyType.APPLY) + .user(applicant1) + .userId(2) + .content("μ§€μ›λ™κΈ°μž…λ‹ˆλ‹€1") + .meeting(meeting) + .meetingId(1) + .build(); + + Apply apply2 = Apply.builder() + .type(EnApplyType.APPLY) + .user(applicant2) + .userId(3) + .content("μ§€μ›λ™κΈ°μž…λ‹ˆλ‹€2") + .meeting(meeting) + .meetingId(1) + .build(); + apply2.updateApplyStatus(EnApplyStatus.APPROVE); + + Applies applyGroups = new Applies(List.of(apply1, apply2)); + + // when + long approvedCount = applyGroups.getApprovedCount(1); + + // then + Assertions.assertThat(approvedCount).isEqualTo(1L); + } + + @Test + void νŠΉμ •_μ‚¬μš©μžκ°€_μ‹ μ²­ν–ˆλŠ”μ§€_쑰회(){ + // given + User host = User.builder().name("κΉ€μ² μˆ˜") + .orgId(1) + .activities(List.of(new UserActivityVO("μ›Ή", 31))) + .profileImage(null) + .phone("010-2222-2222") + .build(); + + Meeting meeting = Meeting.builder() + .user(host) + .title("λͺ¨μž„ 제λͺ©μž…λ‹ˆλ‹€") + .category(MeetingCategory.EVENT) + .startDate(LocalDateTime.of(2024,4,24,0,0,0)) + .endDate(LocalDateTime.of(2024,4,29,23,59,59)) + .capacity(20) + .desc("λͺ¨μž„ μ†Œκ°œμž…λ‹ˆλ‹€") + .processDesc("진행방식 μ†Œκ°œμž…λ‹ˆλ‹€") + .mStartDate(LocalDateTime.of(2024,5,24,0,0,0)) + .mEndDate(LocalDateTime.of(2024,6,24,0,0,0)) + .leaderDesc("슀μž₯ μ†Œκ°œμž…λ‹ˆλ‹€") + .targetDesc("λͺ¨μ§‘ λŒ€μƒμž…λ‹ˆλ‹€") + .isMentorNeeded(true) + .canJoinOnlyActiveGeneration(false) + .createdGeneration(34) + .targetActiveGeneration(null) + .joinableParts(MeetingJoinablePart.values()) + .build(); + + User applicant1 = User.builder().name("홍길동") + .orgId(2) + .activities(List.of(new UserActivityVO("μ„œλ²„", 33))) + .profileImage(null) + .phone("010-1234-5678") + .build(); + + Apply apply1 = Apply.builder() + .type(EnApplyType.APPLY) + .user(applicant1) + .userId(2) + .content("μ§€μ›λ™κΈ°μž…λ‹ˆλ‹€1") + .meeting(meeting) + .meetingId(1) + .build(); + + Applies applies = new Applies(List.of(apply1)); + + // when + Boolean isApply = applies.isApply(1,2); + + // then + Assertions.assertThat(isApply).isEqualTo(true); + } + + @Test + void νŠΉμ •_μ‚¬μš©μžκ°€_μŠΉμΈλ˜μ—ˆλŠ”μ§€_쑰회(){ + // given + User host = User.builder().name("κΉ€μ² μˆ˜") + .orgId(1) + .activities(List.of(new UserActivityVO("μ›Ή", 31))) + .profileImage(null) + .phone("010-2222-2222") + .build(); + + Meeting meeting = Meeting.builder() + .user(host) + .title("λͺ¨μž„ 제λͺ©μž…λ‹ˆλ‹€") + .category(MeetingCategory.EVENT) + .startDate(LocalDateTime.of(2024,4,24,0,0,0)) + .endDate(LocalDateTime.of(2024,4,29,23,59,59)) + .capacity(20) + .desc("λͺ¨μž„ μ†Œκ°œμž…λ‹ˆλ‹€") + .processDesc("진행방식 μ†Œκ°œμž…λ‹ˆλ‹€") + .mStartDate(LocalDateTime.of(2024,5,24,0,0,0)) + .mEndDate(LocalDateTime.of(2024,6,24,0,0,0)) + .leaderDesc("슀μž₯ μ†Œκ°œμž…λ‹ˆλ‹€") + .targetDesc("λͺ¨μ§‘ λŒ€μƒμž…λ‹ˆλ‹€") + .isMentorNeeded(true) + .canJoinOnlyActiveGeneration(false) + .createdGeneration(34) + .targetActiveGeneration(null) + .joinableParts(MeetingJoinablePart.values()) + .build(); + + User applicant1 = User.builder().name("홍길동") + .orgId(2) + .activities(List.of(new UserActivityVO("μ„œλ²„", 33))) + .profileImage(null) + .phone("010-1234-5678") + .build(); + + User applicant2 = User.builder().name("κΉ€μ‚Όμˆœ") + .orgId(3) + .activities(List.of(new UserActivityVO("λ””μžμΈ", 34))) + .profileImage(null) + .phone("010-1111-1111") + .build(); + + Apply apply1 = Apply.builder() + .type(EnApplyType.APPLY) + .user(applicant1) + .userId(2) + .content("μ§€μ›λ™κΈ°μž…λ‹ˆλ‹€1") + .meeting(meeting) + .meetingId(1) + .build(); + + Apply apply2 = Apply.builder() + .type(EnApplyType.APPLY) + .user(applicant2) + .userId(3) + .content("μ§€μ›λ™κΈ°μž…λ‹ˆλ‹€2") + .meeting(meeting) + .meetingId(1) + .build(); + apply2.updateApplyStatus(EnApplyStatus.APPROVE); + + Applies applies = new Applies(List.of(apply1, apply2)); + + // when + Boolean isApproved1 = applies.isApproved(1,2); + Boolean isApproved2 = applies.isApproved(1,3); + + // then + Assertions.assertThat(isApproved1).isEqualTo(false); + Assertions.assertThat(isApproved2).isEqualTo(true); + } +} diff --git a/main/src/test/java/org/sopt/makers/crew/main/entity/meeting/MeetingTest.java b/main/src/test/java/org/sopt/makers/crew/main/entity/meeting/MeetingTest.java new file mode 100644 index 00000000..eb0bff31 --- /dev/null +++ b/main/src/test/java/org/sopt/makers/crew/main/entity/meeting/MeetingTest.java @@ -0,0 +1,108 @@ +package org.sopt.makers.crew.main.entity.meeting; + +import java.time.LocalDateTime; +import java.util.List; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.sopt.makers.crew.main.entity.meeting.enums.EnMeetingStatus; +import org.sopt.makers.crew.main.entity.meeting.enums.MeetingCategory; +import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart; +import org.sopt.makers.crew.main.entity.user.User; +import org.sopt.makers.crew.main.entity.user.vo.UserActivityVO; + +public class MeetingTest { + + @Test + void μŠ€ν„°λ””μž₯인지_확인(){ + // given + User host = User.builder().name("κΉ€μ² μˆ˜") + .orgId(1) + .activities(List.of(new UserActivityVO("μ›Ή", 31))) + .profileImage(null) + .phone("010-2222-2222") + .build(); + + Meeting meeting = Meeting.builder() + .user(host) + .userId(1) + .title("λͺ¨μž„ 제λͺ©μž…λ‹ˆλ‹€") + .category(MeetingCategory.EVENT) + .startDate(LocalDateTime.of(2024,4,24,0,0,0)) + .endDate(LocalDateTime.of(2024,4,29,23,59,59)) + .capacity(20) + .desc("λͺ¨μž„ μ†Œκ°œμž…λ‹ˆλ‹€") + .processDesc("진행방식 μ†Œκ°œμž…λ‹ˆλ‹€") + .mStartDate(LocalDateTime.of(2024,5,24,0,0,0)) + .mEndDate(LocalDateTime.of(2024,6,24,0,0,0)) + .leaderDesc("슀μž₯ μ†Œκ°œμž…λ‹ˆλ‹€") + .targetDesc("λͺ¨μ§‘ λŒ€μƒμž…λ‹ˆλ‹€") + .isMentorNeeded(true) + .canJoinOnlyActiveGeneration(false) + .createdGeneration(34) + .targetActiveGeneration(null) + .joinableParts(MeetingJoinablePart.values()) + .build(); + + // when + Boolean isHost1 = meeting.checkMeetingLeader(1); + Boolean isHost2 = meeting.checkMeetingLeader(2); + + // then + Assertions.assertThat(isHost1).isEqualTo(true); + Assertions.assertThat(isHost2).isEqualTo(false); + } + + @Test + void μ‹ μ²­κΈ°κ°„_이전에_λͺ¨μž„_λͺ¨μ§‘μƒνƒœ_확인(){ + // given + User hostFixture = createHostFixture(); + + Meeting meeting = Meeting.builder() + .user(hostFixture) + .userId(1) + .title("λͺ¨μž„ 제λͺ©μž…λ‹ˆλ‹€") + .category(MeetingCategory.EVENT) + .startDate(LocalDateTime.of(2024,4,24,0,0,0)) + .endDate(LocalDateTime.of(2024,4,29,23,59,59)) + .capacity(20) + .desc("λͺ¨μž„ μ†Œκ°œμž…λ‹ˆλ‹€") + .processDesc("진행방식 μ†Œκ°œμž…λ‹ˆλ‹€") + .mStartDate(LocalDateTime.of(2024,5,24,0,0,0)) + .mEndDate(LocalDateTime.of(2024,6,24,0,0,0)) + .leaderDesc("슀μž₯ μ†Œκ°œμž…λ‹ˆλ‹€") + .targetDesc("λͺ¨μ§‘ λŒ€μƒμž…λ‹ˆλ‹€") + .isMentorNeeded(true) + .canJoinOnlyActiveGeneration(false) + .createdGeneration(34) + .targetActiveGeneration(null) + .joinableParts(MeetingJoinablePart.values()) + .build(); + + // when + Integer beforeRecruitment = meeting.getMeetingStatus(LocalDateTime.of(2024, 4, 23, 23, 59, 59)); + Integer recruiting = meeting.getMeetingStatus(LocalDateTime.of(2024, 4, 24, 0, 0, 0)); + Integer closeRecruitment = meeting.getMeetingStatus(LocalDateTime.of(2024, 4, 30, 0, 0, 0)); + Integer active = meeting.getMeetingStatus(LocalDateTime.of(2024, 5, 24, 0, 0, 0)); + Integer activityEnd = meeting.getMeetingStatus(LocalDateTime.of(2024, 6, 24, 0, 0, 1)); + + + // then + Assertions.assertThat(beforeRecruitment).isEqualTo(EnMeetingStatus.BEFORE_START.getValue()); + Assertions.assertThat(recruiting).isEqualTo(EnMeetingStatus.APPLY_ABLE.getValue()); + Assertions.assertThat(closeRecruitment).isEqualTo(EnMeetingStatus.RECRUITMENT_COMPLETE.getValue()); + Assertions.assertThat(active).isEqualTo(EnMeetingStatus.RECRUITMENT_COMPLETE.getValue()); + Assertions.assertThat(activityEnd).isEqualTo(EnMeetingStatus.RECRUITMENT_COMPLETE.getValue()); + + } + + private User createHostFixture(){ + return User.builder().name("κΉ€μ² μˆ˜") + .orgId(1) + .activities(List.of(new UserActivityVO("μ›Ή", 31))) + .profileImage(null) + .phone("010-2222-2222") + .build(); + } + +} diff --git a/main/src/test/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceTest.java b/main/src/test/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceTest.java index 95173295..475c75bd 100644 --- a/main/src/test/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceTest.java +++ b/main/src/test/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceTest.java @@ -4,8 +4,8 @@ import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.FULL_MEETING_CAPACITY; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.NOT_IN_APPLY_PERIOD; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.FULL_MEETING_CAPACITY; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.NOT_IN_APPLY_PERIOD; import java.time.LocalDateTime; import java.time.Month; @@ -104,7 +104,6 @@ void init() { .createdGeneration(33) .targetActiveGeneration(33) .joinableParts(MeetingJoinablePart.values()) - .appliedInfo(new ArrayList<>()) .build(); Apply apply = Apply.builder() @@ -206,7 +205,6 @@ void init() { .createdGeneration(33) .targetActiveGeneration(33) .joinableParts(MeetingJoinablePart.values()) - .appliedInfo(new ArrayList<>()) .build(); MeetingV2ApplyMeetingDto requestBody = new MeetingV2ApplyMeetingDto(meeting.getId(), "μ—΄μ‹¬νžˆ ν•˜κ² μŠ΅λ‹ˆλ‹€."); diff --git a/main/src/test/java/org/sopt/makers/crew/main/post/v2/repository/PostRepositoryTest.java b/main/src/test/java/org/sopt/makers/crew/main/post/v2/repository/PostRepositoryTest.java index 936d44a0..b677c9fa 100644 --- a/main/src/test/java/org/sopt/makers/crew/main/post/v2/repository/PostRepositoryTest.java +++ b/main/src/test/java/org/sopt/makers/crew/main/post/v2/repository/PostRepositoryTest.java @@ -128,4 +128,19 @@ public class PostRepositoryTest { .containsExactly(1, "μŠ€ν„°λ”” κ΅¬ν•©λ‹ˆλ‹€1", "행사"); } + @Test + void κ²Œμ‹œκΈ€_개수_쑰회() { + // given + int meetingId1 = 1; + int meetingId2 = 2; + + // when + Integer postCount1 = postRepository.countByMeetingId(meetingId1); + Integer postCount2 = postRepository.countByMeetingId(meetingId2); + + // then + assertThat(postCount1).isEqualTo(3); + assertThat(postCount2).isEqualTo(2); + } + } diff --git a/main/src/test/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceTest.java b/main/src/test/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceTest.java new file mode 100644 index 00000000..ac15acb6 --- /dev/null +++ b/main/src/test/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceTest.java @@ -0,0 +1,193 @@ +package org.sopt.makers.crew.main.post.v2.service; + +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; + +import java.time.LocalDateTime; +import java.time.Month; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.sopt.makers.crew.main.common.exception.BadRequestException; +import org.sopt.makers.crew.main.common.exception.ForbiddenException; +import org.sopt.makers.crew.main.common.util.Time; +import org.sopt.makers.crew.main.entity.like.LikeRepository; +import org.sopt.makers.crew.main.entity.meeting.Meeting; +import org.sopt.makers.crew.main.entity.meeting.enums.MeetingCategory; +import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart; +import org.sopt.makers.crew.main.entity.post.Post; +import org.sopt.makers.crew.main.entity.post.PostRepository; +import org.sopt.makers.crew.main.entity.report.Report; +import org.sopt.makers.crew.main.entity.report.ReportRepository; +import org.sopt.makers.crew.main.entity.user.User; +import org.sopt.makers.crew.main.entity.user.UserFixture; +import org.sopt.makers.crew.main.post.v2.dto.request.PostV2UpdatePostBodyDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2ReportResponseDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2SwitchPostLikeResponseDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2UpdatePostResponseDto; + +@ExtendWith(MockitoExtension.class) +public class PostV2ServiceTest { + + @InjectMocks + private PostV2ServiceImpl postV2Service; + @Mock + private PostRepository postRepository; + @Mock + private ReportRepository reportRepository; + @Mock + private LikeRepository likeRepository; + + @Mock + private Time time; + + private User user; + + private Meeting meeting; + + private Post post; + + private Report report; + + @BeforeEach + void init() { + user = UserFixture.createStaticUser(); + user.setUserIdForTest(1); + + meeting = Meeting.builder() + .user(user) + .userId(user.getId()) + .title("μ‚¬λžŒ κ΅¬ν•΄μš”") + .category(MeetingCategory.STUDY) + .startDate(LocalDateTime.of(2024, Month.MARCH, 17, 0, 0)) + .endDate(LocalDateTime.of(2024, Month.MARCH, 20, 23, 59)) + .capacity(10) + .desc("μ—΄μ • λ§Žμ€ μ‚¬λžŒ κ΅¬ν•΄μš”") + .processDesc("μ΄λ ‡κ²Œ 할거에여") + .mStartDate(LocalDateTime.of(2024, Month.APRIL, 1, 0, 0)) + .mEndDate(LocalDateTime.of(2030, Month.APRIL, 20, 0, 0)) + .leaderDesc("μ €λŠ” 이런 μ‚¬λžŒμ΄μ—μš”.") + .targetDesc("이런 μ‚¬λžŒμ΄ μ™”μœΌλ©΄ μ’‹κ² μ–΄μš”") + .note("μœ μ˜μ‚¬ν•­μ€ μ΄κ±°μ—μš”") + .isMentorNeeded(true) + .canJoinOnlyActiveGeneration(true) + .createdGeneration(33) + .targetActiveGeneration(33) + .joinableParts(MeetingJoinablePart.values()) + .build(); + + String[] images = {"image1", "image2", "image3"}; + post = Post.builder().user(user).title("title").contents("contents").images(images).meeting(meeting).build(); + report = Report.builder().post(post).postId(post.getId()).userId(user.getId()).build(); + + } + + @Nested + class κ²Œμ‹œκΈ€_μˆ˜μ • { + + @Test + void 성곡() { + // given + String[] images = {"image4", "image5"}; + PostV2UpdatePostBodyDto requestDto = new PostV2UpdatePostBodyDto("κΈ€ 제λͺ© μˆ˜μ •", "κΈ€ λ‚΄μš© μˆ˜μ •", images); + + doReturn(post).when(postRepository).findByIdOrThrow(any()); + doReturn(LocalDateTime.of(2024, 4, 24, 23, 59)).when(time).now(); + + // when + PostV2UpdatePostResponseDto result = postV2Service.updatePost(post.getId(), + requestDto, user.getId()); + + // then + Assertions.assertThat(result.getId()).isEqualTo(post.getId()); + Assertions.assertThat(result.getContents()).isEqualTo(requestDto.getContents()); + Assertions.assertThat(LocalDateTime.parse(result.getUpdatedDate())) + .isEqualTo(LocalDateTime.of(2024, 4, 24, 23, 59)); + } + + @Test + void μ‹€νŒ¨_본인_μž‘μ„±_κ²Œμ‹œκΈ€_μ•„λ‹˜() { + // given + doReturn(post).when(postRepository).findByIdOrThrow(any()); + String[] images = {"image4", "image5"}; + PostV2UpdatePostBodyDto requestDto = new PostV2UpdatePostBodyDto("κΈ€ 제λͺ© μˆ˜μ •", "κΈ€ λ‚΄μš© μˆ˜μ •", images); + + // when & then + assertThrows(ForbiddenException.class, () -> { + postV2Service.updatePost(post.getId(), requestDto, + post.getUserId() + 1); + }); + } + } + + @Nested + class κ²Œμ‹œκΈ€_μ‹ κ³  { + + @Test + void κ²Œμ‹œκΈ€_μ‹ κ³ _성곡() { + // given + doReturn(post).when(postRepository).findByIdOrThrow(any()); + doReturn(false).when(reportRepository).existsByPostIdAndUserId(any(), any()); + doReturn(report).when(reportRepository).save(any()); + + // when + PostV2ReportResponseDto result = postV2Service.reportPost(post.getId(), + user.getId()); + + // then + Assertions.assertThat(result.getReportId()).isEqualTo(report.getId()); + } + + @Test + void λŒ“κΈ€_μ‹ κ³ _μ‹€νŒ¨_이미_μ‹ κ³ ν•œ_λŒ“κΈ€() { + // given + doReturn(post).when(postRepository).findByIdOrThrow(any()); + doReturn(true).when(reportRepository).existsByPostIdAndUserId(any(), any()); + + // when & then + assertThrows(BadRequestException.class, () -> { + postV2Service.reportPost(post.getId(), user.getId()); + }); + } + } + + @Nested + class κ²Œμ‹œκΈ€_μ’‹μ•„μš”_ν† κΈ€ { + + @Test + void 기쑴에_κ²Œμ‹œκΈ€_μ’‹μ•„μš”_μ•ˆλˆŒλ €μ„λ•Œ_μ’‹μ•„μš”_λˆ„λ₯΄κΈ°_성곡() { + // given + doReturn(post).when(postRepository).findByIdOrThrow(any()); + doReturn(0).when(likeRepository).deleteByUserIdAndPostId(any(), any()); //기쑴에 μ’‹μ•„μš” λˆ„λ₯Έ 적 없을 λ•Œ + + //when + PostV2SwitchPostLikeResponseDto result = postV2Service.switchPostLike(post.getId(), user.getId()); + + //then + Assertions.assertThat(result.getIsLiked()).isEqualTo(true); + Assertions.assertThat(post.getLikeCount()).isEqualTo(1); + } + + @Test + void 기쑴에_κ²Œμ‹œκΈ€_μ’‹μ•„μš”_λˆŒλ €μ„λ•Œ_μ’‹μ•„μš”_μ·¨μ†Œ_성곡() { + // given + post.increaseLikeCount(); + doReturn(post).when(postRepository).findByIdOrThrow(any()); + doReturn(1).when(likeRepository).deleteByUserIdAndPostId(any(), any()); //기쑴에 μ’‹μ•„μš” λˆ„λ₯Έ 적 μžˆμ„ λ•Œ + + //when + PostV2SwitchPostLikeResponseDto result = postV2Service.switchPostLike(post.getId(), user.getId()); + + //then + Assertions.assertThat(result.getIsLiked()).isEqualTo(false); + Assertions.assertThat(post.getLikeCount()).isEqualTo(0); + } + } + +} diff --git a/main/style-config/naver-checkstyle-rules.xml b/main/style-config/naver-checkstyle-rules.xml new file mode 100644 index 00000000..e21e93bc --- /dev/null +++ b/main/style-config/naver-checkstyle-rules.xml @@ -0,0 +1,427 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/main/style-config/naver-intellij-formatter.xml b/main/style-config/naver-intellij-formatter.xml new file mode 100644 index 00000000..658fc659 --- /dev/null +++ b/main/style-config/naver-intellij-formatter.xml @@ -0,0 +1,62 @@ + + + diff --git a/server/src/auth/v0/auth-v0.controller.ts b/server/src/auth/v0/auth-v0.controller.ts index 467c83e9..6df710a3 100644 --- a/server/src/auth/v0/auth-v0.controller.ts +++ b/server/src/auth/v0/auth-v0.controller.ts @@ -18,6 +18,7 @@ export class AuthV0Controller { @ApiOperation({ summary: '둜그인/νšŒμ›κ°€μž…', description: '둜그인/νšŒμ›κ°€μž…', + deprecated: true, }) @ApiResponse({ status: HttpStatus.UNAUTHORIZED, diff --git a/server/src/comment/v1/comment-v1.controller.ts b/server/src/comment/v1/comment-v1.controller.ts index bfb127a0..e428b46d 100644 --- a/server/src/comment/v1/comment-v1.controller.ts +++ b/server/src/comment/v1/comment-v1.controller.ts @@ -61,8 +61,12 @@ export class CommentV1Controller { return this.commentV1Service.getComments({ query, user }); } + /** + * @deprecated + */ @ApiOperation({ summary: 'λŒ“κΈ€ μ’‹μ•„μš” ν† κΈ€', + deprecated: true, }) @ApiOkResponseCommon(CommentV1SwitchCommentLikeResponseDto) @ApiBearerAuth() @@ -98,6 +102,9 @@ export class CommentV1Controller { return this.commentV1Service.reportComment({ param, user }); } + /** + * @deprecated + */ @ApiOperation({ summary: 'λͺ¨μž„ κ²Œμ‹œκΈ€ λŒ“κΈ€ μž‘μ„±', deprecated: true, diff --git a/server/src/common/pagination/dto/page-options.dto.ts b/server/src/common/pagination/dto/page-options.dto.ts index a8442e13..ed132256 100644 --- a/server/src/common/pagination/dto/page-options.dto.ts +++ b/server/src/common/pagination/dto/page-options.dto.ts @@ -29,6 +29,6 @@ export class PageOptionsDto { readonly take?: number = 12; get skip(): number { - return (this.page - 1) * this.take; + return this.page === 1 ? 0 : 11 + (this.page - 2) * 12; } } diff --git a/server/src/meeting/v0/meeting-v0.controller.ts b/server/src/meeting/v0/meeting-v0.controller.ts index b4fb5be2..27d48964 100644 --- a/server/src/meeting/v0/meeting-v0.controller.ts +++ b/server/src/meeting/v0/meeting-v0.controller.ts @@ -47,6 +47,7 @@ export class MeetingV0Controller { @ApiOperation({ summary: 'λͺ¨μž„ μ§€μ›μž μƒνƒœ λ³€κ²½', description: 'λͺ¨μž„ μ§€μ›μž μƒνƒœ λ³€κ²½', + deprecated: true, }) @ApiBearerAuth() @UseGuards(AuthGuard('jwt')) @@ -68,6 +69,7 @@ export class MeetingV0Controller { summary: 'λͺ¨μž„ μ§€μ›μž/μ°Έμ—¬μž 쑰회', description: 'λͺ¨μž„ μ§€μ›μž/μ°Έμ—¬μž 쑰회 (λͺ¨μž„μž₯이면 μ§€μ›μž, μ•„λ‹ˆλ©΄ μ°Έμ—¬μž 쑰회)', + deprecated: true, }) @ApiOkResponseCommon(MeetingV0GetApplyListByMeetingResponseDto) @ApiResponse({ @@ -115,6 +117,7 @@ export class MeetingV0Controller { @ApiOperation({ summary: 'λͺ¨μž„ 상세 쑰회', description: 'λͺ¨μž„ 상세 쑰회', + deprecated: true, }) @ApiOkResponseCommon(MeetingV0GetMeetingByIdResponseDto) @ApiResponse({ @@ -136,6 +139,7 @@ export class MeetingV0Controller { @ApiOperation({ summary: 'λͺ¨μž„ 전체 쑰회/검색/필터링', description: 'λͺ¨μž„ 전체 쑰회/검색/필터링', + deprecated: true }) @ApiOkResponseCommon(MeetingV0GetAllMeetingsResponseDto) @Get('/') @@ -148,6 +152,7 @@ export class MeetingV0Controller { @ApiOperation({ summary: 'λͺ¨μž„ μ‚­μ œ', description: 'λͺ¨μž„ μ‚­μ œ', + deprecated: true, }) @ApiResponse({ status: HttpStatus.OK, diff --git a/server/src/meeting/v1/meeting-v1.controller.ts b/server/src/meeting/v1/meeting-v1.controller.ts index 7d992975..c3df1858 100644 --- a/server/src/meeting/v1/meeting-v1.controller.ts +++ b/server/src/meeting/v1/meeting-v1.controller.ts @@ -40,6 +40,7 @@ export class MeetingV1Controller { @ApiOperation({ summary: 'λͺ¨μž„ μ§€μ›μž λͺ©λ‘ csv 파일 λ‹€μš΄λ‘œλ“œ', description: 'λͺ¨μž„μž₯μΌλ•Œλ§Œ μ§€μ›μž λͺ©λ‘ csv 파일 λ‹€μš΄λ‘œλ“œ κ°€λŠ₯', + deprecated: true, }) @ApiOkResponseCommon(MeetingV1GetApplyListByMeetingCsvFileUrlResponseDto) @ApiResponse({ @@ -65,6 +66,7 @@ export class MeetingV1Controller { @ApiOperation({ summary: 'Meeting 썸넀일 μ—…λ‘œλ“œμš© Pre-Signed URL λ°œκΈ‰', + deprecated: true, }) @ApiOkResponseCommon(MeetingV1GetPresignedUrlResponseDto) @ApiResponse({ @@ -106,6 +108,7 @@ export class MeetingV1Controller { @ApiOperation({ summary: 'λͺ¨μž„ μˆ˜μ •', description: 'λͺ¨μž„ μˆ˜μ •', + deprecated: true, }) @ApiResponse({ status: HttpStatus.BAD_REQUEST, diff --git a/server/src/notice/v1/notice-v1.controller.ts b/server/src/notice/v1/notice-v1.controller.ts index f9bb02d6..793a1e0e 100644 --- a/server/src/notice/v1/notice-v1.controller.ts +++ b/server/src/notice/v1/notice-v1.controller.ts @@ -12,7 +12,10 @@ import { ApiOkResponseCommon } from 'src/common/decorator/api-ok-response-common export class NoticeV1Controller { constructor(private readonly noticeV1Service: NoticeV1Service) {} - @ApiOperation({ summary: '곡지사항 쑰회' }) + @ApiOperation({ + summary: '곡지사항 쑰회', + deprecated: true, + }) @ApiOkResponseCommon(NoticeV1GetNoticesResponseDto) @ApiBearerAuth() @UseGuards(AuthGuard('jwt')) @@ -23,7 +26,10 @@ export class NoticeV1Controller { return this.noticeV1Service.getNotices({ status: query.status }); } - @ApiOperation({ summary: '곡지사항 μž‘μ„±' }) + @ApiOperation({ + summary: '곡지사항 μž‘μ„±', + deprecated: true, + }) @ApiBearerAuth() @UseGuards(AuthGuard('jwt')) @Post() diff --git a/server/src/post/v1/post-v1.controller.ts b/server/src/post/v1/post-v1.controller.ts index dca431f6..2214876d 100644 --- a/server/src/post/v1/post-v1.controller.ts +++ b/server/src/post/v1/post-v1.controller.ts @@ -47,6 +47,7 @@ export class PostV1Controller { @ApiOperation({ summary: 'λͺ¨μž„ κ²Œμ‹œκΈ€ 개수 쑰회', + deprecated: true, }) @ApiOkResponseCommon(PostV1GetPostCountResponseDto) @ApiResponse({ @@ -83,6 +84,7 @@ export class PostV1Controller { @ApiOperation({ summary: 'λͺ¨μž„ κ²Œμ‹œκΈ€ 쑰회', + deprecated: true, }) @ApiOkResponseCommon(PostV1GetPostResponseDto) @ApiResponse({ @@ -122,6 +124,7 @@ export class PostV1Controller { @ApiOperation({ summary: 'κ²Œμ‹œκΈ€ μ’‹μ•„μš” ν† κΈ€', + deprecated: true, }) @ApiOkResponseCommon(PostV1SwitchPostLikeResponseDto) @ApiBearerAuth() @@ -136,6 +139,7 @@ export class PostV1Controller { @ApiOperation({ summary: 'λͺ¨μž„ κ²Œμ‹œκΈ€ μ‹ κ³ ', + deprecated: true, }) @ApiOkResponseCommon(PostV1ReportPostResponseDto) @ApiResponse({ @@ -155,6 +159,7 @@ export class PostV1Controller { @ApiOperation({ summary: 'λͺ¨μž„ κ²Œμ‹œκΈ€ μˆ˜μ •', + deprecated: true, }) @ApiOkResponseCommon(PostV1UpdatePostResponseDto) @ApiResponse({ @@ -180,6 +185,7 @@ export class PostV1Controller { @ApiOperation({ summary: 'λͺ¨μž„ κ²Œμ‹œκΈ€ μ‚­μ œ', + deprecated: true, }) @ApiBearerAuth() @UseGuards(AuthGuard('jwt')) diff --git a/server/src/user/v0/user-v0.controller.ts b/server/src/user/v0/user-v0.controller.ts index fb1bbcfc..29894828 100644 --- a/server/src/user/v0/user-v0.controller.ts +++ b/server/src/user/v0/user-v0.controller.ts @@ -27,6 +27,7 @@ export class UserV0Controller { @ApiOperation({ summary: 'λ‚΄κ°€ λ§Œλ“  λͺ¨μž„ 쑰회', description: 'λ‚΄κ°€ λ§Œλ“  λͺ¨μž„ 쑰회', + deprecated: true, }) @ApiOkResponseCommon(UserV0GetMeetingByUserDto) @ApiBearerAuth() @@ -41,6 +42,7 @@ export class UserV0Controller { @ApiOperation({ summary: 'λ‚΄κ°€ μ‹ μ²­ν•œ λͺ¨μž„ 쑰회', description: 'λ‚΄κ°€ μ‹ μ²­ν•œ λͺ¨μž„ 쑰회', + deprecated: true, }) @ApiOkResponseCommon(UserV0GetApplyByUserDto) @ApiBearerAuth() @@ -55,6 +57,7 @@ export class UserV0Controller { @ApiOperation({ summary: 'μœ μ € 상세 쑰회', description: 'μœ μ € 상세 쑰회', + deprecated: true, }) @ApiOkResponseCommon(User) @ApiParam({ name: 'id', required: true, description: 'μœ μ € id' }) diff --git a/server/src/user/v1/user-v1.controller.ts b/server/src/user/v1/user-v1.controller.ts index d1f54504..6f8d50e0 100644 --- a/server/src/user/v1/user-v1.controller.ts +++ b/server/src/user/v1/user-v1.controller.ts @@ -15,6 +15,7 @@ export class UserV1Controller { @ApiOperation({ summary: 'μœ μ € 본인 ν”„λ‘œν•„ 쑰회', description: 'μœ μ € 본인 ν”„λ‘œν•„ 쑰회', + deprecated: true, }) @ApiOkResponseCommon(UserV1GetUserOwnProfileResponseDto) @ApiBearerAuth()