diff --git a/.github/PULL_REQUEST_TEMPLATE/dev.md b/.github/PULL_REQUEST_TEMPLATE/dev.md index ad0b6ad0..558f1a66 100644 --- a/.github/PULL_REQUEST_TEMPLATE/dev.md +++ b/.github/PULL_REQUEST_TEMPLATE/dev.md @@ -2,6 +2,8 @@ ### ๐Ÿงช ๋กœ์ปฌ ํ…Œ์ŠคํŠธ ์—ฌ๋ถ€ (์ž‘์—…์ž ์ฒดํฌ) - [ ] ๋กœ์ปฌ์—์„œ Swagger ํ˜น์€ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋กœ ๋™์ž‘์„ ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค. +- [ ] API ๋ณ€๊ฒฝ์ด ์žˆ๋‹ค๋ฉด `src/main/resources/static/docs/openapi.yaml`์„ ํ•จ๊ป˜ ๊ฐฑ์‹ ํ–ˆ์Šต๋‹ˆ๋‹ค. +- [ ] `/docs/swagger-ui.html`์—์„œ ๋ณ€๊ฒฝ API๊ฐ€ ์˜๋„๋Œ€๋กœ ๋ณด์ด๋Š”์ง€ ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค. ### ๐Ÿ“„ documentation ์ตœ์‹ ํ™” ์—ฌ๋ถ€ > ๋ณ€๊ฒฝ์‚ฌํ•ญ๊ณผ ๊ด€๋ จ๋œ API์˜ swagger documentation์ด ์‹ค์ œ ๋™์ž‘๊ณผ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. diff --git a/.github/PULL_REQUEST_TEMPLATE/main.md b/.github/PULL_REQUEST_TEMPLATE/main.md index e46646af..15cd0692 100644 --- a/.github/PULL_REQUEST_TEMPLATE/main.md +++ b/.github/PULL_REQUEST_TEMPLATE/main.md @@ -5,6 +5,7 @@ - [ ] CI ์„ฑ๊ณต - [ ] ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ • ํ™•์ธ - [ ] dev ์„œ๋ฒ„์—์„œ ๋ณ€๊ฒฝ์‚ฌํ•ญ์— ๋Œ€ํ•ด ์˜๋„๋Œ€๋กœ ๋™์ž‘ํ•จ์„ ํ™•์ธ +- [ ] API ๋ณ€๊ฒฝ์ด ์žˆ๋‹ค๋ฉด `src/main/resources/static/docs/openapi.yaml` ๋ฐ˜์˜ ์™„๋ฃŒ ### ๐Ÿ“ ์—…๋ฐ์ดํŠธ ์‚ฌํ•ญ ์š”์•ฝ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d75114a4..80cda61f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,6 +21,14 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + - name: Set up Node 20 + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Lint OpenAPI YAML + run: npx @redocly/cli@latest lint src/main/resources/static/docs/openapi.yaml + - name: Set up JDK 21 uses: actions/setup-java@v4 with: diff --git a/build.gradle.kts b/build.gradle.kts index cfebb3cb..5b8d2200 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -44,7 +44,6 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-webflux") implementation("org.springframework.boot:spring-boot-starter-data-jdbc") implementation("org.springframework.boot:spring-boot-starter-data-redis") - implementation("org.springframework.boot:spring-boot-starter-actuator") implementation("org.springframework.boot:spring-boot-starter-mail") implementation("org.mindrot:jbcrypt:0.4") implementation("io.jsonwebtoken:jjwt-api:0.11.5") @@ -73,6 +72,10 @@ dependencies { // OCI Vault - SDK ์ง์ ‘ ์‚ฌ์šฉ (waffle-oci-vault 2.1.0์€ Spring Boot 3.5์™€ ํ˜ธํ™˜ ์•ˆ๋จ) implementation("com.oracle.oci.sdk:oci-java-sdk-secrets:3.80.1") implementation("com.oracle.oci.sdk:oci-java-sdk-common-httpclient-jersey3:3.80.1") + + // prometheus ์˜์กด์„ฑ ์ถ”๊ฐ€ + implementation("org.springframework.boot:spring-boot-starter-actuator") + implementation("io.micrometer:micrometer-registry-prometheus") } kotlin { diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 99b4dc2e..b723e04d 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -76,6 +76,23 @@ services: limits: memory: 64M + prometheus: + image: prom/prometheus:latest + container_name: prometheus-dev + restart: unless-stopped + ports: + - "127.0.0.1:9090:9090" + volumes: + - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro + - prometheus_data_dev:/prometheus + command: + - "--config.file=/etc/prometheus/prometheus.yml" + - "--storage.tsdb.path=/prometheus" + depends_on: + app: + condition: service_healthy + volumes: mysql_data_dev: redis_data_dev: + prometheus_data_dev: diff --git a/src/main/kotlin/com/wafflestudio/spring2025/common/image/controller/ImageController.kt b/src/main/kotlin/com/wafflestudio/spring2025/common/image/controller/ImageController.kt index d774bdb0..1cdb9ea8 100644 --- a/src/main/kotlin/com/wafflestudio/spring2025/common/image/controller/ImageController.kt +++ b/src/main/kotlin/com/wafflestudio/spring2025/common/image/controller/ImageController.kt @@ -4,10 +4,6 @@ import com.wafflestudio.spring2025.common.image.dto.ImageUploadResponse import com.wafflestudio.spring2025.common.image.service.ImageService import com.wafflestudio.spring2025.domain.auth.LoggedInUser import com.wafflestudio.spring2025.domain.user.model.User -import io.swagger.v3.oas.annotations.Operation -import io.swagger.v3.oas.annotations.Parameter -import io.swagger.v3.oas.annotations.responses.ApiResponse -import io.swagger.v3.oas.annotations.responses.ApiResponses import org.springframework.http.MediaType import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.PostMapping @@ -22,21 +18,12 @@ import org.springframework.web.multipart.MultipartFile class ImageController( private val imageService: ImageService, ) { - @Operation(summary = "์ด๋ฏธ์ง€ ์—…๋กœ๋“œ", description = "์„œ๋ฒ„ ์ €์žฅ์†Œ์— ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค.") - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์„ฑ๊ณต"), - ApiResponse(responseCode = "400", description = "์ž˜๋ชป๋œ ์š”์ฒญ (ํŒŒ์ผ ๋ˆ„๋ฝ/ํ˜•์‹ ์˜ค๋ฅ˜ ๋“ฑ)"), - ApiResponse(responseCode = "401", description = "์ธ์ฆ ์‹คํŒจ (์œ ํšจํ•˜์ง€ ์•Š์€ ํ† ํฐ)"), - ], - ) @PostMapping( consumes = [MediaType.MULTIPART_FORM_DATA_VALUE], ) fun uploadImage( - @Parameter(hidden = true) @LoggedInUser user: User?, + @LoggedInUser user: User?, @RequestPart("image") image: MultipartFile, - @Parameter(description = "์ด๋ฏธ์ง€๋ฅผ ์ €์žฅํ•  ์ƒ์œ„ ๊ฒฝ๋กœ", required = false) @RequestParam(name = "prefix", required = false) prefix: String?, ): ResponseEntity { diff --git a/src/main/kotlin/com/wafflestudio/spring2025/domain/auth/controller/AuthController.kt b/src/main/kotlin/com/wafflestudio/spring2025/domain/auth/controller/AuthController.kt index fae13d16..f35d874e 100644 --- a/src/main/kotlin/com/wafflestudio/spring2025/domain/auth/controller/AuthController.kt +++ b/src/main/kotlin/com/wafflestudio/spring2025/domain/auth/controller/AuthController.kt @@ -5,10 +5,6 @@ import com.wafflestudio.spring2025.domain.auth.dto.LoginResponse import com.wafflestudio.spring2025.domain.auth.dto.SignupRequest import com.wafflestudio.spring2025.domain.auth.service.AuthService import com.wafflestudio.spring2025.domain.auth.service.EmailVerificationService -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 import jakarta.servlet.http.HttpServletRequest import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.PathVariable @@ -19,23 +15,10 @@ import org.springframework.web.bind.annotation.RestController @RestController @RequestMapping("/api/auth") -@Tag(name = "Auth", description = "์ธ์ฆ API") class AuthController( private val authService: AuthService, private val emailVerificationService: EmailVerificationService, ) { - @Operation( - summary = "์ด๋ฉ”์ผ ํšŒ์›๊ฐ€์ž…", - description = "์ƒˆ๋กœ์šด ์‚ฌ์šฉ์ž๋ฅผ ๋“ฑ๋กํ•˜๊ณ  ์ธ์ฆ ์ด๋ฉ”์ผ์„ ๋ฐœ์†กํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฉ”์ผ ์ธ์ฆ ํ›„ ๊ฐ€์ž…์ด ์™„๋ฃŒ๋ฉ๋‹ˆ๋‹ค.", - ) - @ApiResponses( - value = [ - ApiResponse(responseCode = "204", description = "์ธ์ฆ ์ด๋ฉ”์ผ ๋ฐœ์†ก ์„ฑ๊ณต"), - ApiResponse(responseCode = "400", description = "์ž˜๋ชป๋œ ์š”์ฒญ (์œ ํšจํ•˜์ง€ ์•Š์€ email/password/name)"), - ApiResponse(responseCode = "409", description = "์ด๋ฏธ ์กด์žฌํ•˜๋Š” email"), - ApiResponse(responseCode = "503", description = "์ด๋ฉ”์ผ ์„œ๋น„์Šค ์žฅ์• "), - ], - ) @PostMapping("/signup") fun signup( @RequestBody signupRequest: SignupRequest, @@ -51,16 +34,6 @@ class AuthController( return ResponseEntity.noContent().build() } - @Operation( - summary = "์ด๋ฉ”์ผ ์ธ์ฆ", - description = "์ด๋ฉ”์ผ๋กœ ๋ฐ›์€ ์ธ์ฆ ์ฝ”๋“œ๋ฅผ ๊ฒ€์ฆํ•˜๊ณ  ํšŒ์›๊ฐ€์ž…์„ ์™„๋ฃŒํ•ฉ๋‹ˆ๋‹ค", - ) - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "์ธ์ฆ ์„ฑ๊ณต, ์œ ์ € ๋“ฑ๋ก ์™„๋ฃŒ"), - ApiResponse(responseCode = "400", description = "์œ ํšจํ•˜์ง€ ์•Š๊ฑฐ๋‚˜ ๋งŒ๋ฃŒ๋œ ์ธ์ฆ ์ฝ”๋“œ"), - ], - ) @PostMapping("/email-verification/{verificationCode}") fun verifyEmail( @PathVariable verificationCode: String, @@ -69,13 +42,6 @@ class AuthController( return ResponseEntity.ok().build() } - @Operation(summary = "๋กœ๊ทธ์ธ", description = "email๋กœ ๋กœ๊ทธ์ธํ•˜์—ฌ JWT ํ† ํฐ์„ ๋ฐœ๊ธ‰๋ฐ›์Šต๋‹ˆ๋‹ค") - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "๋กœ๊ทธ์ธ ์„ฑ๊ณต, JWT ํ† ํฐ ๋ฐ˜ํ™˜"), - ApiResponse(responseCode = "401", description = "์ธ์ฆ ์‹คํŒจ"), - ], - ) @PostMapping("/login") fun login( @RequestBody loginRequest: LoginRequest, @@ -84,12 +50,6 @@ class AuthController( return ResponseEntity.ok(LoginResponse(token)) } - @Operation(summary = "๋กœ๊ทธ์•„์›ƒ", description = "ํ˜„์žฌ JWT ํ† ํฐ์„ ๋ธ”๋ž™๋ฆฌ์ŠคํŠธ์— ์ถ”๊ฐ€ํ•˜์—ฌ ๋กœ๊ทธ์•„์›ƒํ•ฉ๋‹ˆ๋‹ค") - @ApiResponses( - value = [ - ApiResponse(responseCode = "204", description = "๋กœ๊ทธ์•„์›ƒ ์„ฑ๊ณต"), - ], - ) @PostMapping("/logout") fun logout(request: HttpServletRequest): ResponseEntity { val token = resolveToken(request) diff --git a/src/main/kotlin/com/wafflestudio/spring2025/domain/auth/controller/SocialAuthController.kt b/src/main/kotlin/com/wafflestudio/spring2025/domain/auth/controller/SocialAuthController.kt index b43a4ced..14da8c08 100644 --- a/src/main/kotlin/com/wafflestudio/spring2025/domain/auth/controller/SocialAuthController.kt +++ b/src/main/kotlin/com/wafflestudio/spring2025/domain/auth/controller/SocialAuthController.kt @@ -4,9 +4,6 @@ import com.wafflestudio.spring2025.domain.auth.dto.LoginResponse import com.wafflestudio.spring2025.domain.auth.dto.SocialLoginRequest import com.wafflestudio.spring2025.domain.auth.model.SocialProvider import com.wafflestudio.spring2025.domain.auth.service.SocialAuthService -import io.swagger.v3.oas.annotations.Operation -import io.swagger.v3.oas.annotations.responses.ApiResponse -import io.swagger.v3.oas.annotations.responses.ApiResponses import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody @@ -14,18 +11,10 @@ import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController @RestController -@RequestMapping("api/auth/social") +@RequestMapping("/api/auth/social") class SocialAuthController( private val socialAuthService: SocialAuthService, ) { - @Operation(summary = "์†Œ์…œ ๋กœ๊ทธ์ธ", description = "์†Œ์…œ ๋กœ๊ทธ์ธ์„ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค.") - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "์†Œ์…œ ๋กœ๊ทธ์ธ ์„ฑ๊ณต"), - ApiResponse(responseCode = "401", description = "์ธ์ฆ ์‹คํŒจ"), - ApiResponse(responseCode = "409", description = "์ด๋ฏธ ๊ฐ€์ž…๋œ ์ด๋ฉ”์ผ"), - ], - ) @PostMapping suspend fun social( @RequestBody socialLoginRequest: SocialLoginRequest, diff --git a/src/main/kotlin/com/wafflestudio/spring2025/domain/auth/dto/LoginRequest.kt b/src/main/kotlin/com/wafflestudio/spring2025/domain/auth/dto/LoginRequest.kt index bf17a881..0d5ffea7 100644 --- a/src/main/kotlin/com/wafflestudio/spring2025/domain/auth/dto/LoginRequest.kt +++ b/src/main/kotlin/com/wafflestudio/spring2025/domain/auth/dto/LoginRequest.kt @@ -1,11 +1,6 @@ package com.wafflestudio.spring2025.domain.auth.dto -import io.swagger.v3.oas.annotations.media.Schema - -@Schema(description = "๋กœ๊ทธ์ธ ์š”์ฒญ") data class LoginRequest( - @Schema(description = "์‚ฌ์šฉ์ž ์ด๋ฉ”์ผ", example = "user@example.com", required = true) val email: String, - @Schema(description = "์‚ฌ์šฉ์ž ๋น„๋ฐ€๋ฒˆํ˜ธ", example = "mypassword", required = true) val password: String, ) diff --git a/src/main/kotlin/com/wafflestudio/spring2025/domain/auth/dto/RegisterRequest.kt b/src/main/kotlin/com/wafflestudio/spring2025/domain/auth/dto/RegisterRequest.kt deleted file mode 100644 index 470bcb7f..00000000 --- a/src/main/kotlin/com/wafflestudio/spring2025/domain/auth/dto/RegisterRequest.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.wafflestudio.spring2025.domain.auth.dto - -import io.swagger.v3.oas.annotations.media.Schema - -@Schema(description = "ํšŒ์›๊ฐ€์ž… ์š”์ฒญ") -data class RegisterRequest( - @Schema(description = "์‚ฌ์šฉ์ž ์ด๋ฉ”์ผ", example = "user@example.com", required = true) - val email: String, - @Schema(description = "์‚ฌ์šฉ์ž ์ด๋ฆ„", example = "ํ™๊ธธ๋™", required = true) - val name: String, - @Schema(description = "๋น„๋ฐ€๋ฒˆํ˜ธ", example = "password123", required = true) - val password: String, - @Schema(description = "ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ URL", example = "https://cdn.example.com/profile.png") - val profileImage: String? = null, -) diff --git a/src/main/kotlin/com/wafflestudio/spring2025/domain/auth/dto/SignupRequest.kt b/src/main/kotlin/com/wafflestudio/spring2025/domain/auth/dto/SignupRequest.kt index 94c273ff..53618934 100644 --- a/src/main/kotlin/com/wafflestudio/spring2025/domain/auth/dto/SignupRequest.kt +++ b/src/main/kotlin/com/wafflestudio/spring2025/domain/auth/dto/SignupRequest.kt @@ -1,15 +1,8 @@ package com.wafflestudio.spring2025.domain.auth.dto -import io.swagger.v3.oas.annotations.media.Schema - -@Schema(description = "ํšŒ์›๊ฐ€์ž… ์š”์ฒญ") data class SignupRequest( - @Schema(description = "์‚ฌ์šฉ์ž ์ด๋ฉ”์ผ", example = "user@example.com", required = true) val email: String, - @Schema(description = "์‚ฌ์šฉ์ž ์ด๋ฆ„", example = "ํ™๊ธธ๋™", required = true) val name: String, - @Schema(description = "๋น„๋ฐ€๋ฒˆํ˜ธ", example = "password123", required = true) val password: String, - @Schema(description = "ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ URL", example = "https://cdn.example.com/profile.png") val profileImage: String? = null, ) diff --git a/src/main/kotlin/com/wafflestudio/spring2025/domain/auth/dto/SocialLoginRequest.kt b/src/main/kotlin/com/wafflestudio/spring2025/domain/auth/dto/SocialLoginRequest.kt index f83e60f3..28fc2bad 100644 --- a/src/main/kotlin/com/wafflestudio/spring2025/domain/auth/dto/SocialLoginRequest.kt +++ b/src/main/kotlin/com/wafflestudio/spring2025/domain/auth/dto/SocialLoginRequest.kt @@ -1,11 +1,6 @@ package com.wafflestudio.spring2025.domain.auth.dto -import io.swagger.v3.oas.annotations.media.Schema - -@Schema(description = "์†Œ์…œ ๋กœ๊ทธ์ธ ์š”์ฒญ") data class SocialLoginRequest( - @Schema(description = "์†Œ์…œ๋กœ๊ทธ์ธ ์ œ๊ณต์ž", example = "GOOGLE", required = true) val provider: String, - @Schema(description = "์ธ๊ฐ€ ์ฝ”๋“œ (authorization code)", required = true) val code: String, ) diff --git a/src/main/kotlin/com/wafflestudio/spring2025/domain/event/controller/EventController.kt b/src/main/kotlin/com/wafflestudio/spring2025/domain/event/controller/EventController.kt index 7ba5fae8..7e1a9de5 100644 --- a/src/main/kotlin/com/wafflestudio/spring2025/domain/event/controller/EventController.kt +++ b/src/main/kotlin/com/wafflestudio/spring2025/domain/event/controller/EventController.kt @@ -10,8 +10,6 @@ import com.wafflestudio.spring2025.domain.event.dto.response.MyEventsInfiniteRes import com.wafflestudio.spring2025.domain.event.dto.response.UpdateEventResponse import com.wafflestudio.spring2025.domain.event.service.EventService import com.wafflestudio.spring2025.domain.user.model.User -import io.swagger.v3.oas.annotations.Operation -import io.swagger.v3.oas.annotations.tags.Tag import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.DeleteMapping import org.springframework.web.bind.annotation.GetMapping @@ -26,12 +24,10 @@ import java.time.Instant @RestController @RequestMapping("/api/events") -@Tag(name = "Event", description = "์ด๋ฒคํŠธ ๊ด€๋ฆฌ API") class EventController( private val eventService: EventService, ) { @AuthRequired - @Operation(summary = "์ด๋ฒคํŠธ ์ƒ์„ฑ", description = "์ƒˆ๋กœ์šด ์ด๋ฒคํŠธ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค") @PostMapping // POST /api/events fun create( @LoggedInUser user: User, @@ -54,7 +50,6 @@ class EventController( return ResponseEntity.ok(CreateEventResponse(publicId)) } - @Operation(summary = "์ด๋ฒคํŠธ ์ƒ์„ธ ์กฐํšŒ", description = "์ด๋ฒคํŠธ ์ƒ์„ธ ์ •๋ณด๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค") @GetMapping("/{publicId}") // GET /api/events/{publicId} fun getById( @LoggedInUser user: User?, // nullable @@ -69,10 +64,6 @@ class EventController( } @AuthRequired - @Operation( - summary = "๋‚ด๊ฐ€ ๋งŒ๋“  ์ด๋ฒคํŠธ ๋ชฉ๋ก ์กฐํšŒ (๋ฌดํ•œ ์Šคํฌ๋กค)", - description = "๋กœ๊ทธ์ธ๋œ ์‚ฌ์šฉ์ž๊ฐ€ ์ƒ์„ฑํ•œ ์ด๋ฒคํŠธ ๋ชฉ๋ก์„ ๋ฌดํ•œ ์Šคํฌ๋กค ๋ฐฉ์‹(cursor)์œผ๋กœ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค", - ) @GetMapping("/me") fun getMyEvents( @LoggedInUser user: User, @@ -89,7 +80,6 @@ class EventController( } @AuthRequired - @Operation(summary = "์ด๋ฒคํŠธ ์ˆ˜์ •", description = "์ด๋ฒคํŠธ๋ฅผ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค") @PutMapping("/{publicId}") // PUT /api/events/{publicId} fun update( @LoggedInUser user: User, @@ -116,7 +106,6 @@ class EventController( } @AuthRequired - @Operation(summary = "์ด๋ฒคํŠธ ์‚ญ์ œ", description = "์ด๋ฒคํŠธ๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค") @DeleteMapping("/{publicId}") // DELETE /api/events/{publicId} fun delete( @LoggedInUser user: User, diff --git a/src/main/kotlin/com/wafflestudio/spring2025/domain/event/dto/core/EventDto.kt b/src/main/kotlin/com/wafflestudio/spring2025/domain/event/dto/core/EventDto.kt index 2b4765a2..778ea5ca 100644 --- a/src/main/kotlin/com/wafflestudio/spring2025/domain/event/dto/core/EventDto.kt +++ b/src/main/kotlin/com/wafflestudio/spring2025/domain/event/dto/core/EventDto.kt @@ -1,35 +1,20 @@ package com.wafflestudio.spring2025.domain.event.dto.core import com.wafflestudio.spring2025.domain.event.model.Event -import io.swagger.v3.oas.annotations.media.Schema -@Schema(description = "์ผ์ • ์ •๋ณด") data class EventDto( - @Schema(description = "์ผ์ • ID (publicId)") val id: String, - @Schema(description = "์ด๋ฒคํŠธ ์ œ๋ชฉ") val title: String, - @Schema(description = "์ด๋ฒคํŠธ ์„ค๋ช…") val description: String?, - @Schema(description = "์žฅ์†Œ") val location: String?, - @Schema(description = "์‹œ์ž‘ ์‹œ๊ฐ„ (epoch milliseconds)") val startsAt: Long?, - @Schema(description = "์ข…๋ฃŒ ์‹œ๊ฐ„ (epoch milliseconds)") val endsAt: Long?, - @Schema(description = "์ •์›") val capacity: Int?, - @Schema(description = "๋Œ€๊ธฐ ๋ช…๋‹จ ์‚ฌ์šฉ ์—ฌ๋ถ€") val waitlistEnabled: Boolean, - @Schema(description = "์‹ ์ฒญ ์‹œ์ž‘ ์‹œ๊ฐ„ (epoch milliseconds)") val registrationStartsAt: Long?, - @Schema(description = "์‹ ์ฒญ ๋งˆ๊ฐ ์‹œ๊ฐ„ (epoch milliseconds)") val registrationEndsAt: Long, - @Schema(description = "์ž‘์„ฑ์ž ID") val createdBy: Long, - @Schema(description = "์ƒ์„ฑ ์ผ์‹œ (epoch milliseconds)") val createdAt: Long?, - @Schema(description = "์ˆ˜์ • ์ผ์‹œ (epoch milliseconds)") val updatedAt: Long?, ) { constructor(event: Event) : this( diff --git a/src/main/kotlin/com/wafflestudio/spring2025/domain/event/dto/request/CreateEventRequest.kt b/src/main/kotlin/com/wafflestudio/spring2025/domain/event/dto/request/CreateEventRequest.kt index db6254ca..1c46a314 100644 --- a/src/main/kotlin/com/wafflestudio/spring2025/domain/event/dto/request/CreateEventRequest.kt +++ b/src/main/kotlin/com/wafflestudio/spring2025/domain/event/dto/request/CreateEventRequest.kt @@ -1,38 +1,15 @@ package com.wafflestudio.spring2025.domain.event.dto.request -import io.swagger.v3.oas.annotations.media.Schema import java.time.Instant -@Schema(description = "์ผ์ • ์ƒ์„ฑ ์š”์ฒญ") data class CreateEventRequest( - @Schema(description = "์ด๋ฒคํŠธ ์ œ๋ชฉ", example = "์ •๊ธฐ ๋ชจ์ž„") val title: String, - @Schema(description = "์ด๋ฒคํŠธ ์„ค๋ช…", example = "์ •๊ธฐ ๋ชจ์ž„์ž…๋‹ˆ๋‹ค") val description: String? = null, - @Schema(description = "์žฅ์†Œ", example = "๊ฐ•์˜์‹ค 101") val location: String? = null, - @Schema( - description = "์‹œ์ž‘ ์‹œ๊ฐ„ (ISO-8601)", - example = "2026-02-02T18:00:00Z", - ) val startsAt: Instant? = null, - @Schema( - description = "์ข…๋ฃŒ ์‹œ๊ฐ„ (ISO-8601)", - example = "2026-02-02T20:00:00Z", - ) val endsAt: Instant? = null, - @Schema(description = "์ •์›", example = "30") val capacity: Int, - @Schema(description = "๋Œ€๊ธฐ ๋ช…๋‹จ ์‚ฌ์šฉ ์—ฌ๋ถ€", example = "true") val waitlistEnabled: Boolean, - @Schema( - description = "์‹ ์ฒญ ์‹œ์ž‘ ์‹œ๊ฐ„ (ISO-8601). null์ธ ๊ฒฝ์šฐ ์š”์ฒญ ์‹œ์ ์œผ๋กœ ์ž๋™ ์„ค์ •๋ฉ๋‹ˆ๋‹ค.", - example = "2026-02-02T17:00:00Z", - ) val registrationStartsAt: Instant? = null, - @Schema( - description = "์‹ ์ฒญ ๋งˆ๊ฐ ์‹œ๊ฐ„ (ISO-8601)", - example = "2026-02-02T17:30:00Z", - ) val registrationEndsAt: Instant, ) diff --git a/src/main/kotlin/com/wafflestudio/spring2025/domain/event/dto/request/UpdateEventRequest.kt b/src/main/kotlin/com/wafflestudio/spring2025/domain/event/dto/request/UpdateEventRequest.kt index e237b56c..2206d5c5 100644 --- a/src/main/kotlin/com/wafflestudio/spring2025/domain/event/dto/request/UpdateEventRequest.kt +++ b/src/main/kotlin/com/wafflestudio/spring2025/domain/event/dto/request/UpdateEventRequest.kt @@ -1,38 +1,15 @@ package com.wafflestudio.spring2025.domain.event.dto.request -import io.swagger.v3.oas.annotations.media.Schema import java.time.Instant -@Schema(description = "์ผ์ • ์ˆ˜์ • ์š”์ฒญ") data class UpdateEventRequest( - @Schema(description = "์ด๋ฒคํŠธ ์ œ๋ชฉ", example = "์ •๊ธฐ ๋ชจ์ž„") val title: String? = null, - @Schema(description = "์ด๋ฒคํŠธ ์„ค๋ช…", example = "์ •๊ธฐ ๋ชจ์ž„์ž…๋‹ˆ๋‹ค") val description: String? = null, - @Schema(description = "์žฅ์†Œ", example = "๊ฐ•์˜์‹ค 101") val location: String? = null, - @Schema( - description = "์‹œ์ž‘ ์‹œ๊ฐ„ (ISO-8601)", - example = "2026-02-02T18:00:00Z", - ) val startsAt: Instant? = null, - @Schema( - description = "์ข…๋ฃŒ ์‹œ๊ฐ„ (ISO-8601)", - example = "2026-02-02T20:00:00Z", - ) val endsAt: Instant? = null, - @Schema(description = "์ •์›", example = "30") val capacity: Int? = null, - @Schema(description = "๋Œ€๊ธฐ ๋ช…๋‹จ ์‚ฌ์šฉ ์—ฌ๋ถ€", example = "true") val waitlistEnabled: Boolean? = null, - @Schema( - description = "์‹ ์ฒญ ์‹œ์ž‘ ์‹œ๊ฐ„ (ISO-8601)", - example = "2026-02-02T17:00:00Z", - ) val registrationStartsAt: Instant? = null, - @Schema( - description = "์‹ ์ฒญ ๋งˆ๊ฐ ์‹œ๊ฐ„ (ISO-8601)", - example = "2026-02-02T17:30:00Z", - ) val registrationEndsAt: Instant? = null, ) diff --git a/src/main/kotlin/com/wafflestudio/spring2025/domain/event/dto/response/EventDetailResponse.kt b/src/main/kotlin/com/wafflestudio/spring2025/domain/event/dto/response/EventDetailResponse.kt index 84e7616a..685cfd04 100644 --- a/src/main/kotlin/com/wafflestudio/spring2025/domain/event/dto/response/EventDetailResponse.kt +++ b/src/main/kotlin/com/wafflestudio/spring2025/domain/event/dto/response/EventDetailResponse.kt @@ -1,99 +1,46 @@ package com.wafflestudio.spring2025.domain.event.dto.response -import io.swagger.v3.oas.annotations.media.Schema import java.time.Instant -@Schema(description = "์ผ์ • ์ƒ์„ธ ์กฐํšŒ ์‘๋‹ต") data class EventDetailResponse( - @Schema(description = "์ด๋ฒคํŠธ ์ •๋ณด") val event: EventInfo, - @Schema(description = "์ด๋ฒคํŠธ ์ƒ์„ฑ์ž ์ •๋ณด") val creator: CreatorInfo, - @Schema(description = "์กฐํšŒ์ž(viewer) ์ •๋ณด") val viewer: ViewerInfo, - @Schema(description = "์กฐํšŒ์ž๊ฐ€ ํ•  ์ˆ˜ ์žˆ๋Š” ๋™์ž‘") val capabilities: CapabilitiesInfo, - @Schema(description = "์ฐธ์—ฌ์ž ๋ฏธ๋ฆฌ๋ณด๊ธฐ ๋ชฉ๋ก") val guestsPreview: List, ) -@Schema(description = "์ด๋ฒคํŠธ ์ •๋ณด ๋ธ”๋ก") data class EventInfo( - @Schema(description = "์ด๋ฒคํŠธ publicId", example = "dj8sadj10djnkas") val publicId: String, - @Schema(description = "์ด๋ฒคํŠธ ์ œ๋ชฉ", example = "์ œ2ํšŒ ๊ธฐํš ์„ธ๋ฏธ๋‚˜") val title: String, - @Schema(description = "์ด๋ฒคํŠธ ์„ค๋ช…", example = "์ผ์ • ์ƒ์„ธ ์„ค๋ช…...", nullable = true) val description: String?, - @Schema(description = "์žฅ์†Œ", example = "์„œ์šธ๋Œ€", nullable = true) val location: String?, - @Schema(description = "์‹œ์ž‘ ์‹œ๊ฐ„ (ISO-8601)", example = "2026-02-02T18:00:00Z", nullable = true) val startsAt: Instant?, - @Schema(description = "์ข…๋ฃŒ ์‹œ๊ฐ„ (ISO-8601)", example = "2026-02-02T20:00:00Z", nullable = true) val endsAt: Instant?, - @Schema(description = "์ •์›", example = "10", nullable = true) val capacity: Int?, - @Schema(description = "์ฐธ์—ฌ์ž ์ˆ˜(์ฐธ์—ฌ ํ™•์ • ์ธ์›)", example = "5") val confirmedCount: Int, - @Schema(description = "๋Œ€๊ธฐ์ž ์ˆ˜", example = "5") val waitlistCount: Int, - @Schema(description = "์‹ ์ฒญ ์‹œ์ž‘ ์‹œ๊ฐ„ (ISO-8601)", example = "2026-02-02T17:00:00Z", nullable = true) val registrationStartsAt: Instant?, - @Schema(description = "์‹ ์ฒญ ๋งˆ๊ฐ ์‹œ๊ฐ„ (ISO-8601)", example = "2026-02-02T17:00:00Z") val registrationEndsAt: Instant, ) -@Schema(description = "์ƒ์„ฑ์ž ์ •๋ณด ๋ธ”๋ก") data class CreatorInfo( - @Schema(description = "์ด๋ฆ„", example = "ํ™๊ธธ๋™") val name: String, - @Schema(description = "์ด๋ฉ”์ผ", example = "user@example.com") val email: String, - @Schema(description = "ํ”„๋กœํ•„ ์ด๋ฏธ์ง€", example = "https://example.com/profile.png", nullable = true) val profileImage: String?, ) -@Schema(description = "์กฐํšŒ์ž(viewer) ์ •๋ณด ๋ธ”๋ก") data class ViewerInfo( - @Schema( - description = "viewer์˜ ๋“ฑ๋ก ์ƒํƒœ (RegistrationStatus ์‚ฌ์šฉ)", - example = "WAITLISTED", - ) val status: ViewerStatus, - @Schema( - description = "viewer ์ด๋ฆ„ (๋น„ํšŒ์›์ด๋ฉด null)", - example = "ํ™๊ธธ๋™", - nullable = true, - ) val name: String?, - @Schema( - description = "๋Œ€๊ธฐ ์ˆœ๋ฒˆ (WAITLISTED์ผ ๋•Œ๋งŒ ์ˆซ์ž, ์•„๋‹ˆ๋ฉด null)", - example = "3", - nullable = true, - ) val waitlistPosition: Int?, - @Schema( - description = "registrationPublicId (๊ฒŒ์ŠคํŠธ๋ฉด ๊ฐ’, ์•„๋‹ˆ๋ฉด null)", - example = "1", - nullable = true, - ) val registrationPublicId: String?, - @Schema( - description = "์˜ˆ์•ฝ ์ด๋ฉ”์ผ(๊ฒŒ์ŠคํŠธ๋ฉด guestEmail, ๋กœ๊ทธ์ธ ์œ ์ €๋ฉด user email ๋“ฑ ์ •์ฑ…์— ๋”ฐ๋ฆ„)", - example = "skdfsj@gmaldkl", - nullable = true, - ) val reservationEmail: String?, ) -@Schema(description = "๊ฐ€๋Šฅํ•œ ๋™์ž‘ ๋ธ”๋ก") data class CapabilitiesInfo( - @Schema(description = "๊ณต์œ  ๋งํฌ ๊ฐ€๋Šฅ ์—ฌ๋ถ€", example = "false") val shareLink: Boolean, - @Schema(description = "์ฐธ์—ฌ ์‹ ์ฒญ ๊ฐ€๋Šฅ ์—ฌ๋ถ€(ํ™•์ • ์ž๋ฆฌ ์‹ ์ฒญ)", example = "false") val apply: Boolean, - @Schema(description = "๋Œ€๊ธฐ ์‹ ์ฒญ ๊ฐ€๋Šฅ ์—ฌ๋ถ€(์ •์› ์ฐผ์„ ๋•Œ)", example = "false") val wait: Boolean, - @Schema(description = "์‹ ์ฒญ ์ทจ์†Œ ๊ฐ€๋Šฅ ์—ฌ๋ถ€", example = "true") val cancel: Boolean, ) diff --git a/src/main/kotlin/com/wafflestudio/spring2025/domain/event/dto/response/GuestPreview.kt b/src/main/kotlin/com/wafflestudio/spring2025/domain/event/dto/response/GuestPreview.kt index 01781f48..2f3314a7 100644 --- a/src/main/kotlin/com/wafflestudio/spring2025/domain/event/dto/response/GuestPreview.kt +++ b/src/main/kotlin/com/wafflestudio/spring2025/domain/event/dto/response/GuestPreview.kt @@ -1,21 +1,11 @@ package com.wafflestudio.spring2025.domain.event.dto.response -import io.swagger.v3.oas.annotations.media.Schema - /** * ์ด๋ฒคํŠธ ์ฐธ์—ฌ์ž ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ •๋ณด * (ํšŒ์›/๋น„ํšŒ์› ํฌํ•จ) */ -@Schema(description = "์ฐธ์—ฌ์ž ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ •๋ณด") data class GuestPreview( - @Schema(description = "์ฐธ์—ฌ์ž ID (๋น„ํšŒ์›์ด๋ฉด null)", example = "1", nullable = true) val id: Long?, - @Schema(description = "์ฐธ์—ฌ์ž ์ด๋ฆ„", example = "ํ™๊ธธ๋™") val name: String, - @Schema( - description = "ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ URL", - example = "https://example.com/profile.png", - nullable = true, - ) val profileImage: String?, ) diff --git a/src/main/kotlin/com/wafflestudio/spring2025/domain/event/dto/response/MyRole.kt b/src/main/kotlin/com/wafflestudio/spring2025/domain/event/dto/response/MyRole.kt index d1a8b89a..c06024ed 100644 --- a/src/main/kotlin/com/wafflestudio/spring2025/domain/event/dto/response/MyRole.kt +++ b/src/main/kotlin/com/wafflestudio/spring2025/domain/event/dto/response/MyRole.kt @@ -1,21 +1,15 @@ package com.wafflestudio.spring2025.domain.event.dto.response -import io.swagger.v3.oas.annotations.media.Schema - /** * ์š”์ฒญ์ž ์—ญํ•  * - CREATOR: ์ด๋ฒคํŠธ ์ƒ์„ฑ์ž * - PARTICIPANT: ์ด๋ฒคํŠธ ์ฐธ์—ฌ์ž (CONFIRMED / WAITLISTED) * - NONE: ๋กœ๊ทธ์ธ์€ ํ–ˆ์ง€๋งŒ ์ฐธ์—ฌํ•˜์ง€ ์•Š์Œ */ -@Schema(description = "์š”์ฒญ์ž ์—ญํ• ") enum class MyRole { - @Schema(description = "์ด๋ฒคํŠธ ์ƒ์„ฑ์ž") CREATOR, - @Schema(description = "์ด๋ฒคํŠธ ์ฐธ์—ฌ์ž") PARTICIPANT, - @Schema(description = "์ด๋ฒคํŠธ ๋ฏธ์ฐธ์—ฌ์ž") NONE, } diff --git a/src/main/kotlin/com/wafflestudio/spring2025/domain/event/dto/response/ViewerStatus.kt b/src/main/kotlin/com/wafflestudio/spring2025/domain/event/dto/response/ViewerStatus.kt index a250a6db..d85fe72a 100644 --- a/src/main/kotlin/com/wafflestudio/spring2025/domain/event/dto/response/ViewerStatus.kt +++ b/src/main/kotlin/com/wafflestudio/spring2025/domain/event/dto/response/ViewerStatus.kt @@ -1,25 +1,17 @@ package com.wafflestudio.spring2025.domain.event.dto.response -import io.swagger.v3.oas.annotations.media.Schema - /** * ์ด๋ฒคํŠธ ์ƒ์„ธ ์กฐํšŒ ์‹œ, * "viewer(์š”์ฒญ์ž)"๊ฐ€ ํ•ด๋‹น ์ด๋ฒคํŠธ์™€ ์–ด๋–ค ๊ด€๊ณ„์— ์žˆ๋Š”์ง€๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ์ƒํƒœ */ -@Schema(description = "์ด๋ฒคํŠธ ์กฐํšŒ์ž(Viewer)์˜ ์ƒํƒœ") enum class ViewerStatus { - @Schema(description = "์ด๋ฒคํŠธ ์ƒ์„ฑ์ž") HOST, - @Schema(description = "์ฐธ์—ฌ ํ™•์ •๋œ ์‚ฌ์šฉ์ž") CONFIRMED, - @Schema(description = "๋Œ€๊ธฐ ์ค‘์ธ ์‚ฌ์šฉ์ž") WAITLISTED, - @Schema(description = "๊ฐ•์ œ ์ทจ์†Œ(์ฐจ๋‹จ)๋œ ์‚ฌ์šฉ์ž") BANNED, - @Schema(description = "์ด๋ฒคํŠธ์™€ ์•„๋ฌด ๊ด€๊ณ„ ์—†๋Š” ์‚ฌ์šฉ์ž") NONE, } diff --git a/src/main/kotlin/com/wafflestudio/spring2025/domain/event/service/EventService.kt b/src/main/kotlin/com/wafflestudio/spring2025/domain/event/service/EventService.kt index e52f056d..7d41b3e9 100644 --- a/src/main/kotlin/com/wafflestudio/spring2025/domain/event/service/EventService.kt +++ b/src/main/kotlin/com/wafflestudio/spring2025/domain/event/service/EventService.kt @@ -23,6 +23,7 @@ import com.wafflestudio.spring2025.domain.registration.repository.RegistrationRe import com.wafflestudio.spring2025.domain.registration.service.WaitlistReconciliationService import com.wafflestudio.spring2025.domain.user.repository.UserRepository import org.springframework.data.domain.PageRequest +import org.springframework.data.domain.Pageable import org.springframework.data.domain.Sort import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -96,10 +97,9 @@ class EventService( val event = getEventByPublicId(publicId) val eventId = requireNotNull(event.id) { "Event id is null: publicId=$publicId" } - val creatorUser = - userRepository.findById(event.createdBy).orElseThrow { - EventNotFoundException() - } + val userIdsToFetch = listOfNotNull(event.createdBy, requesterId).distinct() + val usersById = userRepository.findAllById(userIdsToFetch).associateBy { it.id!! } + val creatorUser = usersById[event.createdBy] ?: throw EventNotFoundException() val myReg = if (requesterId == null) { @@ -121,17 +121,16 @@ class EventService( .countByEventIdAndStatus(eventID = eventId, registrationStatus = RegistrationStatus.WAITLISTED) .toInt() - val totalApplicants = confirmedCount + waitlistedCount - val waitlistPosition: Int? = if (myReg?.status == RegistrationStatus.WAITLISTED) { - val waitlistedRegs = - registrationRepository.findByEventIdAndStatusOrderByCreatedAtAsc( - eventID = eventId, - registrationStatus = RegistrationStatus.WAITLISTED, - ) - val idx = waitlistedRegs.indexOfFirst { it.id == myReg.id } - if (idx >= 0) idx + 1 else null + registrationRepository + .findWaitlistPositionsByRegistrationPublicIds( + eventId = eventId, + status = RegistrationStatus.WAITLISTED, + registrationPublicIds = listOf(myReg.registrationPublicId), + ).firstOrNull() + ?.waitlistNumber + ?.toInt() } else { null } @@ -150,10 +149,7 @@ class EventService( } } - val viewerName: String? = - requesterId?.let { - userRepository.findById(it).orElse(null)?.name - } + val viewerName: String? = requesterId?.let { usersById[it]?.name } val viewer = if (viewerStatus == ViewerStatus.NONE) { @@ -184,18 +180,17 @@ class EventService( registrationEndsAt = event.registrationEndsAt, ) - val confirmedRegs = + val previewRegs = registrationRepository.findByEventIdAndStatusOrderByCreatedAtAsc( eventID = eventId, registrationStatus = RegistrationStatus.CONFIRMED, + pageable = Pageable.ofSize(5), ) - val previewRegs = confirmedRegs.take(5) - val previewUserIds = previewRegs.mapNotNull { it.userId }.distinct() - val usersById = + val previewUsersById = userRepository.findAllById(previewUserIds).associateBy { it.id!! } val guestsPreview = @@ -208,7 +203,7 @@ class EventService( profileImage = null, ) } else { - usersById[uid]?.let { + previewUsersById[uid]?.let { GuestPreview( id = it.id!!, name = it.name, @@ -276,19 +271,31 @@ class EventService( val sliced = fetched.take(pageSize) val nextCursor = sliced.lastOrNull()?.createdAt + val eventIds = sliced.map { requireNotNull(it.id) } + + val confirmedCounts = + if (eventIds.isEmpty()) { + emptyMap() + } else { + registrationRepository + .countByEventIdsAndStatuses(eventIds = eventIds, listOf(RegistrationStatus.CONFIRMED)) + .associate { it.eventId to it.totalCount.toInt() } + } + + val waitlistedCounts = + if (eventIds.isEmpty()) { + emptyMap() + } else { + registrationRepository + .countByEventIdsAndStatuses(eventIds = eventIds, listOf(RegistrationStatus.WAITLISTED)) + .associate { it.eventId to it.totalCount.toInt() } + } + val responses = sliced.map { event -> val eventId = requireNotNull(event.id) - - val confirmedCount = - registrationRepository - .countByEventIdAndStatus(eventID = eventId, registrationStatus = RegistrationStatus.CONFIRMED) - .toInt() - - val waitlistedCount = - registrationRepository - .countByEventIdAndStatus(eventID = eventId, registrationStatus = RegistrationStatus.WAITLISTED) - .toInt() + val confirmedCount = confirmedCounts[eventId] ?: 0 + val waitlistedCount = waitlistedCounts[eventId] ?: 0 MyEventResponse( publicId = event.publicId, @@ -378,9 +385,10 @@ class EventService( // ์•Œ๋ฆผ ๋Œ€์ƒ: CONFIRMED + WAITLISTED (BANNED ์ œ์™ธ) val registrationsToNotify = - registrationRepository - .findByEventId(eventId) - .filter { it.status == RegistrationStatus.CONFIRMED || it.status == RegistrationStatus.WAITLISTED } + registrationRepository.findByEventIdAndStatusIn( + eventID = eventId, + statuses = listOf(RegistrationStatus.CONFIRMED, RegistrationStatus.WAITLISTED), + ) // ์ด๋ฉ”์ผ ๋ฐ์ดํ„ฐ ๊ตฌ์„ฑ (์‚ญ์ œ ์ „์— user ์ •๋ณด ์กฐํšŒ) val hostUser = userRepository.findById(event.createdBy).orElse(null) diff --git a/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/controller/EventRegistrationController.kt b/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/controller/EventRegistrationController.kt index 4fb815e9..213bc67d 100644 --- a/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/controller/EventRegistrationController.kt +++ b/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/controller/EventRegistrationController.kt @@ -6,11 +6,6 @@ import com.wafflestudio.spring2025.domain.registration.dto.response.CreateRegist import com.wafflestudio.spring2025.domain.registration.dto.response.GetEventRegistrationsResponse import com.wafflestudio.spring2025.domain.registration.service.RegistrationService import com.wafflestudio.spring2025.domain.user.model.User -import io.swagger.v3.oas.annotations.Operation -import io.swagger.v3.oas.annotations.Parameter -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 org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable @@ -22,35 +17,9 @@ import org.springframework.web.bind.annotation.RestController @RestController @RequestMapping("/api/events/{eventId}/registrations") -@Tag(name = "Registration", description = "์ด๋ฒคํŠธ ๊ธฐ์ค€ ์‹ ์ฒญ API") class EventRegistrationController( private val registrationService: RegistrationService, ) { - @Operation( - summary = "์ด๋ฒคํŠธ ์ฐธ์—ฌ ์‹ ์ฒญ", - description = - "ํŠน์ • ์ด๋ฒคํŠธ์— ์‹ ์ฒญํ•ฉ๋‹ˆ๋‹ค. ์ •์›์ด ๋‚จ์•„์žˆ์œผ๋ฉด CONFIRMED๋กœ ๋“ฑ๋ก๋˜๊ณ , ์ •์›์ด ์ฐผ์œผ๋‚˜ ๋Œ€๊ธฐ ๋ช…๋‹จ์ด ํ—ˆ์šฉ๋œ ๊ฒฝ์šฐ WAITLISTED๋กœ ๋“ฑ๋ก๋ฉ๋‹ˆ๋‹ค. " + - "์ •์›์ด ์ฐผ๊ณ  ๋Œ€๊ธฐ ๋ช…๋‹จ์ด ๋น„ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ ์‹ ์ฒญ์ด ๊ฑฐ์ ˆ๋ฉ๋‹ˆ๋‹ค.", - ) - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "์‹ ์ฒญ ์„ฑ๊ณต"), - ApiResponse( - responseCode = "400", - description = - "์œ ํšจํ•˜์ง€ ์•Š์€ ์š”์ฒญ: 3003(Wrong guest name), 3004(Wrong guest email), 2204(Invalid registration window)", - ), - ApiResponse( - responseCode = "403", - description = "๊ถŒํ•œ ์—†์Œ: 3006(Banned user cannot register)", - ), - ApiResponse(responseCode = "404", description = "์ด๋ฒคํŠธ ์—†์Œ: 2001(Event not found)"), - ApiResponse( - responseCode = "409", - description = "์‹ ์ฒญ ๋ถˆ๊ฐ€: 3001(Registration already exists), 3005(Host cannot register), 2301(Event is full)", - ), - ], - ) @PostMapping fun create( @PathVariable eventId: String, @@ -67,17 +36,12 @@ class EventRegistrationController( return ResponseEntity.ok(registration) } - @Operation( - summary = "์ด๋ฒคํŠธ ์‹ ์ฒญ ๋ชฉ๋ก ์กฐํšŒ", - description = "ํŠน์ • ์ด๋ฒคํŠธ์— ์†ํ•œ ์‹ ์ฒญ ๋ชฉ๋ก์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. ์‹ ์ฒญ ์ƒํƒœ(CONFIRMED/WAITLISTED/BANNED)์™€ ์‹ ์ฒญ ์‹œ๊ฐ์„ ํฌํ•จํ•ด ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.", - ) @GetMapping fun list( @PathVariable eventId: String, @LoggedInUser user: User?, @RequestParam(required = false) status: String?, @RequestParam(required = false) orderBy: String?, - @Parameter(description = "๋‹ค์Œ ํŽ˜์ด์ง€ ์‹œ์ž‘ offset (์—†์œผ๋ฉด 0๋ถ€ํ„ฐ)") @RequestParam(required = false) cursor: Int?, ): ResponseEntity = ResponseEntity.ok( diff --git a/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/controller/MyRegistrationController.kt b/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/controller/MyRegistrationController.kt index 6da5b2c9..71022123 100644 --- a/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/controller/MyRegistrationController.kt +++ b/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/controller/MyRegistrationController.kt @@ -5,9 +5,6 @@ import com.wafflestudio.spring2025.domain.auth.LoggedInUser import com.wafflestudio.spring2025.domain.registration.dto.response.GetMyRegistrationsResponse import com.wafflestudio.spring2025.domain.registration.service.RegistrationService import com.wafflestudio.spring2025.domain.user.model.User -import io.swagger.v3.oas.annotations.Operation -import io.swagger.v3.oas.annotations.Parameter -import io.swagger.v3.oas.annotations.tags.Tag import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping @@ -17,17 +14,12 @@ import org.springframework.web.bind.annotation.RestController @AuthRequired @RestController @RequestMapping("/api/registrations") -@Tag(name = "Registration", description = "๋‚ด ์‹ ์ฒญ ์กฐํšŒ API") class MyRegistrationController( private val registrationService: RegistrationService, ) { - @Operation( - summary = "๋‚ด ์ด๋ฒคํŠธ ์‹ ์ฒญ ๋ชฉ๋ก ์กฐํšŒ", - description = "๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์˜ ์ด๋ฒคํŠธ ์‹ ์ฒญ ๋ชฉ๋ก์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. ์ตœ์‹  ์‹ ์ฒญ ์ˆœ์œผ๋กœ ๋ฐ˜ํ™˜๋˜๋ฉฐ page/size๋กœ ํŽ˜์ด์ง•ํ•ฉ๋‹ˆ๋‹ค.", - ) @GetMapping("/me") fun myRegistrations( - @Parameter(hidden = true) @LoggedInUser user: User, + @LoggedInUser user: User, @RequestParam(defaultValue = "0") page: Int, @RequestParam(defaultValue = "20") size: Int, ): ResponseEntity { diff --git a/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/controller/RegistrationController.kt b/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/controller/RegistrationController.kt index 2ffccbe6..28381a59 100644 --- a/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/controller/RegistrationController.kt +++ b/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/controller/RegistrationController.kt @@ -1,7 +1,6 @@ package com.wafflestudio.spring2025.domain.registration.controller import com.wafflestudio.spring2025.domain.auth.LoggedInUser -import com.wafflestudio.spring2025.domain.registration.dto.request.DeleteRegistrationRequest import com.wafflestudio.spring2025.domain.registration.dto.request.UpdateRegistrationStatusRequest import com.wafflestudio.spring2025.domain.registration.dto.response.DeleteRegistrationResponse import com.wafflestudio.spring2025.domain.registration.dto.response.GetRegistrationResponse @@ -10,8 +9,6 @@ import com.wafflestudio.spring2025.domain.registration.exception.RegistrationErr import com.wafflestudio.spring2025.domain.registration.exception.RegistrationValidationException import com.wafflestudio.spring2025.domain.registration.service.RegistrationService import com.wafflestudio.spring2025.domain.user.model.User -import io.swagger.v3.oas.annotations.Operation -import io.swagger.v3.oas.annotations.tags.Tag import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.DeleteMapping import org.springframework.web.bind.annotation.GetMapping @@ -23,16 +20,9 @@ import org.springframework.web.bind.annotation.RestController @RestController @RequestMapping("/api/registrations/{registrationId}") -@Tag(name = "Registration", description = "์‹ ์ฒญ ๋‹จ๊ฑด ๋ฆฌ์†Œ์Šค API") class RegistrationController( private val registrationService: RegistrationService, ) { - @Operation( - summary = "์‹ ์ฒญ ๋‹จ๊ฑด ์ƒํƒœ ๋ณ€๊ฒฝ", - description = - "์‹ ์ฒญ ๋‹จ๊ฑด ๋ฆฌ์†Œ์Šค์˜ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค. " + - "์‚ฌ์šฉ์ž๊ฐ€ ๋ณธ์ธ์˜ ์‹ ์ฒญ์„ ์ทจ์†Œํ•˜๋Š” ๊ฒฝ์šฐ์™€ ๊ด€๋ฆฌ์ž๊ฐ€ ๊ฐ•์ œ ์ทจ์†Œ/๋ฐด ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒฝ์šฐ๋ฅผ ๋กœ์ง ๋ ˆ๋ฒจ์—์„œ ๊ตฌ๋ถ„ํ•ฉ๋‹ˆ๋‹ค.", - ) @PatchMapping fun updateStatus( @PathVariable registrationId: String, @@ -45,11 +35,6 @@ class RegistrationController( return ResponseEntity.ok(response) } - @Operation( - summary = "์‹ ์ฒญ ๋‹จ๊ฑด ์ •๋ณด ์š”์ฒญ", - description = - "์‹ ์ฒญ ๋‹จ๊ฑด์˜ ์ •๋ณด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.", - ) @GetMapping fun getRegistrationInformation( @PathVariable registrationId: String, @@ -62,15 +47,12 @@ class RegistrationController( @DeleteMapping fun delete( @PathVariable registrationId: String, - @RequestBody request: DeleteRegistrationRequest, @LoggedInUser user: User?, ): ResponseEntity { val response = registrationService.delete( registrationPublicId = registrationId, userId = user?.id, - guestName = request.guestName, - guestEmail = request.guestEmail, ) return ResponseEntity.ok(response) diff --git a/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/core/RegistrationDto.kt b/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/core/RegistrationDto.kt index e6764f91..33f1410e 100644 --- a/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/core/RegistrationDto.kt +++ b/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/core/RegistrationDto.kt @@ -2,23 +2,14 @@ package com.wafflestudio.spring2025.domain.registration.dto.core import com.wafflestudio.spring2025.domain.registration.model.Registration import com.wafflestudio.spring2025.domain.registration.model.RegistrationStatus -import io.swagger.v3.oas.annotations.media.Schema -@Schema(description = "์ด๋ฒคํŠธ ์‹ ์ฒญ ์ •๋ณด") data class RegistrationDto( - @Schema(description = "์‹ ์ฒญ ๊ณต๊ฐœ ID") val registrationPublicId: String, - @Schema(description = "์‚ฌ์šฉ์ž ID") val userId: Long?, - @Schema(description = "์ด๋ฒคํŠธ ID") val eventId: Long, - @Schema(description = "๋น„ํšŒ์› ์ด๋ฆ„") val guestName: String?, - @Schema(description = "๋น„ํšŒ์› ์ด๋ฉ”์ผ") val guestEmail: String?, - @Schema(description = "์‹ ์ฒญ ์ƒํƒœ") val status: RegistrationStatus, - @Schema(description = "์‹ ์ฒญ ์ผ์‹œ (epoch milliseconds)") val createdAt: Long, ) { constructor(registration: Registration) : this( diff --git a/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/core/RegistrationWithEventDto.kt b/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/core/RegistrationWithEventDto.kt index 5255eb9e..70c08a66 100644 --- a/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/core/RegistrationWithEventDto.kt +++ b/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/core/RegistrationWithEventDto.kt @@ -2,13 +2,9 @@ package com.wafflestudio.spring2025.domain.registration.dto.core import com.wafflestudio.spring2025.domain.event.model.Event import com.wafflestudio.spring2025.domain.registration.model.Registration -import io.swagger.v3.oas.annotations.media.Schema -@Schema(description = "๋‚ด ์ด๋ฒคํŠธ ์‹ ์ฒญ ์ •๋ณด") data class RegistrationWithEventDto( - @Schema(description = "์‹ ์ฒญ ์ •๋ณด") val registration: RegistrationDto, - @Schema(description = "์ด๋ฒคํŠธ ์š”์•ฝ ์ •๋ณด") val event: EventSummaryDto, ) { constructor(registration: Registration, event: Event) : this( @@ -17,17 +13,11 @@ data class RegistrationWithEventDto( ) } -@Schema(description = "์ด๋ฒคํŠธ ์š”์•ฝ ์ •๋ณด") data class EventSummaryDto( - @Schema(description = "์ด๋ฒคํŠธ ID") val id: Long, - @Schema(description = "์ด๋ฒคํŠธ ์ œ๋ชฉ") val title: String, - @Schema(description = "์žฅ์†Œ") val location: String?, - @Schema(description = "์‹œ์ž‘ ์‹œ๊ฐ„ (epoch milliseconds)") val startsAt: Long?, - @Schema(description = "์ข…๋ฃŒ ์‹œ๊ฐ„ (epoch milliseconds)") val endsAt: Long?, ) { constructor(event: Event) : this( diff --git a/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/request/CreateRegistrationRequest.kt b/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/request/CreateRegistrationRequest.kt index baf7052f..820bd1e5 100644 --- a/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/request/CreateRegistrationRequest.kt +++ b/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/request/CreateRegistrationRequest.kt @@ -1,14 +1,10 @@ package com.wafflestudio.spring2025.domain.registration.dto.request import com.fasterxml.jackson.annotation.JsonAlias -import io.swagger.v3.oas.annotations.media.Schema -@Schema(description = "์ด๋ฒคํŠธ ์‹ ์ฒญ ์š”์ฒญ") data class CreateRegistrationRequest( - @Schema(description = "๋น„ํšŒ์› ์ด๋ฆ„") @JsonAlias("guest_name") val guestName: String? = null, - @Schema(description = "๋น„ํšŒ์› ์ด๋ฉ”์ผ") @JsonAlias("guest_email") val guestEmail: String? = null, ) diff --git a/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/request/DeleteRegistrationRequest.kt b/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/request/DeleteRegistrationRequest.kt deleted file mode 100644 index 3f34c5f8..00000000 --- a/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/request/DeleteRegistrationRequest.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.wafflestudio.spring2025.domain.registration.dto.request - -import io.swagger.v3.oas.annotations.media.Schema - -@Schema(description = "์ด๋ฒคํŠธ ์ทจ์†Œ ์š”์ฒญ") -data class DeleteRegistrationRequest( - @Schema(description = "๋น„ํšŒ์› ์ด๋ฆ„") - val guestName: String? = null, - @Schema(description = "๋น„ํšŒ์› ์ด๋ฉ”์ผ") - val guestEmail: String? = null, -) diff --git a/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/request/UpdateRegistrationStatusRequest.kt b/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/request/UpdateRegistrationStatusRequest.kt index f87d15fb..2182cddc 100644 --- a/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/request/UpdateRegistrationStatusRequest.kt +++ b/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/request/UpdateRegistrationStatusRequest.kt @@ -1,9 +1,7 @@ package com.wafflestudio.spring2025.domain.registration.dto.request import com.wafflestudio.spring2025.domain.registration.model.RegistrationStatus -import io.swagger.v3.oas.annotations.media.Schema -@Schema(description = "์ด๋ฒคํŠธ ์‹ ์ฒญ ์ƒํƒœ ๋ณ€๊ฒฝ ์š”์ฒญ") data class UpdateRegistrationStatusRequest( val status: RegistrationStatus? = null, ) diff --git a/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/response/CreateRegistrationResponse.kt b/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/response/CreateRegistrationResponse.kt index eb4ea566..3abe25e8 100644 --- a/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/response/CreateRegistrationResponse.kt +++ b/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/response/CreateRegistrationResponse.kt @@ -1,9 +1,5 @@ package com.wafflestudio.spring2025.domain.registration.dto.response -import io.swagger.v3.oas.annotations.media.Schema - -@Schema(description = "์ด๋ฒคํŠธ ์‹ ์ฒญ ์‘๋‹ต") data class CreateRegistrationResponse( - @Schema(description = "์‹ ์ฒญ id") val registrationPublicId: String, ) diff --git a/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/response/GetEventRegistrationsResponse.kt b/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/response/GetEventRegistrationsResponse.kt index 39a7951b..c5837bd2 100644 --- a/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/response/GetEventRegistrationsResponse.kt +++ b/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/response/GetEventRegistrationsResponse.kt @@ -3,36 +3,22 @@ package com.wafflestudio.spring2025.domain.registration.dto.response import com.wafflestudio.spring2025.domain.registration.model.Registration import com.wafflestudio.spring2025.domain.registration.model.RegistrationStatus import com.wafflestudio.spring2025.domain.user.model.User -import io.swagger.v3.oas.annotations.media.Schema import java.time.Instant -@Schema(description = "์ด๋ฒคํŠธ ์‹ ์ฒญ ๋ชฉ๋ก ์กฐํšŒ ์‘๋‹ต") data class GetEventRegistrationsResponse( - @Schema(description = "์‹ ์ฒญ์ž ๋ชฉ๋ก") val participants: List, - @Schema(description = "ํ•„ํ„ฐ์— ๋”ฐ๋ผ ๋ณ€ํ•˜๋Š” ์ฐธ์—ฌ์ž ์ˆ˜") val totalCount: Int?, - @Schema(description = "๋‹ค์Œ ํŽ˜์ด์ง€ ์‹œ์ž‘ offset (์—†์œผ๋ฉด null)") val nextCursor: Int?, - @Schema(description = "๋‹ค์Œ ํŽ˜์ด์ง€ ์กด์žฌ ์—ฌ๋ถ€") val hasNext: Boolean, ) -@Schema(description = "์‹ ์ฒญ์ž ์ •๋ณด") data class EventRegistrationItem( - @Schema(description = "์‹ ์ฒญ ๊ณต๊ฐœ ID") val registrationId: String, - @Schema(description = "์ด๋ฆ„") val name: String, - @Schema(description = "์ด๋ฉ”์ผ (๊ด€๋ฆฌ์ž ์š”์ฒญ ์‹œ ๋…ธ์ถœ)") val email: String?, - @Schema(description = "ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ URL") val profileImage: String?, - @Schema(description = "์‹ ์ฒญ ์ผ์‹œ") val createdAt: Instant, - @Schema(description = "์‹ ์ฒญ ์ƒํƒœ") val status: RegistrationStatus, - @Schema(description = "๋Œ€๊ธฐ ์ˆœ๋ฒˆ (status๊ฐ€ WAITLISTED์ผ ๋•Œ๋งŒ ์•„๋‹ˆ๋ฉด null)") val waitingNum: Int?, ) { constructor( diff --git a/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/response/GetMyRegistrationsResponse.kt b/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/response/GetMyRegistrationsResponse.kt index f3a63da3..0aca3e06 100644 --- a/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/response/GetMyRegistrationsResponse.kt +++ b/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/response/GetMyRegistrationsResponse.kt @@ -3,38 +3,23 @@ package com.wafflestudio.spring2025.domain.registration.dto.response import com.wafflestudio.spring2025.domain.event.model.Event import com.wafflestudio.spring2025.domain.registration.model.Registration import com.wafflestudio.spring2025.domain.registration.model.RegistrationStatus -import io.swagger.v3.oas.annotations.media.Schema import java.time.Instant -@Schema(description = "๋‚ด ์ด๋ฒคํŠธ ์‹ ์ฒญ ๋ชฉ๋ก ์‘๋‹ต") data class GetMyRegistrationsResponse( - @Schema(description = "์‹ ์ฒญ ๋ชฉ๋ก") val registrations: List, ) -@Schema(description = "๋‚ด ์ด๋ฒคํŠธ ์‹ ์ฒญ ์š”์•ฝ") data class MyRegistrationItem( - @Schema(description = "์ด๋ฒคํŠธ ๊ณต๊ฐœ ID") val publicId: String, - @Schema(description = "์ด๋ฒคํŠธ ์ œ๋ชฉ") val title: String, - @Schema(description = "์ด๋ฒคํŠธ ์‹œ์ž‘ ์‹œ๊ฐ„ (ISO-8601)") val startsAt: Instant?, - @Schema(description = "์ด๋ฒคํŠธ ์ข…๋ฃŒ ์‹œ๊ฐ„ (ISO-8601)") val endsAt: Instant?, - @Schema(description = "์‹ ์ฒญ ์‹œ์ž‘ ์‹œ๊ฐ„ (ISO-8601)") val registrationStartsAt: Instant?, - @Schema(description = "์‹ ์ฒญ ๋งˆ๊ฐ ์‹œ๊ฐ„ (ISO-8601)") val registrationEndsAt: Instant?, - @Schema(description = "์ •์›") val capacity: Int?, - @Schema(description = "ํ™•์ • ์ฐธ์—ฌ์ž ์ˆ˜") val confirmedCount: Int, - @Schema(description = "๋Œ€๊ธฐ์ž ์ˆ˜") val waitlistCount: Int, - @Schema(description = "์‹ ์ฒญ ์ƒํƒœ") val status: MyRegistrationStatus, - @Schema(description = "๋Œ€๊ธฐ ์ˆœ๋ฒˆ (WAITLISTED๊ฐ€ ์•„๋‹ˆ๋ฉด null)") val waitlistedNum: Int?, ) { constructor( diff --git a/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/response/GetRegistrationResponse.kt b/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/response/GetRegistrationResponse.kt index e03dd30c..d241cd32 100644 --- a/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/response/GetRegistrationResponse.kt +++ b/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/response/GetRegistrationResponse.kt @@ -1,9 +1,7 @@ package com.wafflestudio.spring2025.domain.registration.dto.response import com.wafflestudio.spring2025.domain.registration.model.RegistrationStatus -import io.swagger.v3.oas.annotations.media.Schema -@Schema(description = "์ด๋ฒคํŠธ ์‹ ์ฒญ ์ •๋ณด ์š”์ฒญ ์‘๋‹ต") data class GetRegistrationResponse( val status: RegistrationStatus, val guestName: String, diff --git a/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/response/PatchRegistrationResponse.kt b/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/response/PatchRegistrationResponse.kt index 5f8f534a..e328c6db 100644 --- a/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/response/PatchRegistrationResponse.kt +++ b/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/response/PatchRegistrationResponse.kt @@ -1,8 +1,5 @@ package com.wafflestudio.spring2025.domain.registration.dto.response -import io.swagger.v3.oas.annotations.media.Schema - -@Schema(description = "์ด๋ฒคํŠธ ์‹ ์ฒญ ์ƒํƒœ ๋ณ€๊ฒฝ") data class PatchRegistrationResponse( val patchEmail: String? = null, ) diff --git a/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/response/RegistrationGuestsResponse.kt b/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/response/RegistrationGuestsResponse.kt index 5d0d0855..fe52c458 100644 --- a/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/response/RegistrationGuestsResponse.kt +++ b/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/dto/response/RegistrationGuestsResponse.kt @@ -1,29 +1,19 @@ package com.wafflestudio.spring2025.domain.registration.dto.response import com.fasterxml.jackson.annotation.JsonProperty -import io.swagger.v3.oas.annotations.media.Schema -@Schema(description = "์ด๋ฒคํŠธ ์ฐธ์—ฌ์ž ๋ช…๋‹จ ์‘๋‹ต") data class RegistrationGuestsResponse( - @Schema(description = "ํ™•์ • ์ฐธ์—ฌ์ž ๋ชฉ๋ก") val guests: List, @JsonProperty("confirmed_count") - @Schema(description = "ํ™•์ • ์ฐธ์—ฌ์ž ์ˆ˜") val confirmedCount: Int, @JsonProperty("waiting_count") - @Schema(description = "๋Œ€๊ธฐ ์ฐธ์—ฌ์ž ์ˆ˜") val waitingCount: Int, ) { - @Schema(description = "์ฐธ์—ฌ์ž ์ •๋ณด") data class Guest( - @Schema(description = "์‹ ์ฒญ ๊ณต๊ฐœ ID") val registrationPublicId: String, - @Schema(description = "์ด๋ฆ„") val name: String, - @Schema(description = "์ด๋ฉ”์ผ (๊ด€๋ฆฌ์ž ์š”์ฒญ ์‹œ ๋…ธ์ถœ)") val email: String?, @JsonProperty("profile_image") - @Schema(description = "ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ URL") val profileImage: String?, ) } diff --git a/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/repository/RegistrationRepository.kt b/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/repository/RegistrationRepository.kt index 5ed64233..1c91aa06 100644 --- a/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/repository/RegistrationRepository.kt +++ b/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/repository/RegistrationRepository.kt @@ -41,6 +41,11 @@ interface RegistrationRepository : eventId: Long, ): Registration? + fun findByEventIdAndStatusIn( + eventID: Long, + statuses: Collection, + ): List + fun countByEventId(eventID: Long): Long fun countByEventIdAndStatus( @@ -66,6 +71,12 @@ interface RegistrationRepository : registrationStatus: RegistrationStatus, ): List + fun findByEventIdAndStatusOrderByCreatedAtAsc( + eventID: Long, + registrationStatus: RegistrationStatus, + pageable: Pageable, + ): List + @Query( """ SELECT * FROM registrations @@ -175,7 +186,7 @@ interface RegistrationRepository : ranked.waitlist_number AS waitlist_number FROM ( SELECT r.registration_public_id, - ROW_NUMBER() OVER (ORDER BY r.created_at ASC, r.registration_public_id ASC) AS waitlist_number + ROW_NUMBER() OVER (ORDER BY r.created_at ASC, r.id ASC) AS waitlist_number FROM registrations r WHERE r.event_id = :eventId AND r.status = :status ) ranked diff --git a/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/service/RegistrationService.kt b/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/service/RegistrationService.kt index a87b211a..46ea8c70 100644 --- a/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/service/RegistrationService.kt +++ b/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/service/RegistrationService.kt @@ -28,6 +28,7 @@ import com.wafflestudio.spring2025.domain.user.model.User import com.wafflestudio.spring2025.domain.user.repository.UserRepository import org.springframework.dao.DuplicateKeyException import org.springframework.data.domain.PageRequest +import org.springframework.data.domain.Pageable import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import org.springframework.transaction.support.TransactionSynchronization @@ -96,6 +97,11 @@ RegistrationService( registrationRepository.findByUserIdAndEventId(userId, eventPk) } + val userIdsToFetch = listOfNotNull(lockedEvent.createdBy, userId).distinct() + val fetchedUsersById = userRepository.findAllById(userIdsToFetch).associateBy { it.id!! } + val hostEmail = fetchedUsersById[lockedEvent.createdBy]?.email + val user = userId?.let { fetchedUsersById[it] } + val saved = try { if (existingRegistration != null) { @@ -112,8 +118,7 @@ RegistrationService( -> throw RegistrationConflictException(RegistrationErrorCode.REGISTRATION_ALREADY_EXISTS) } } else { - val hostEmail = userRepository.findById(lockedEvent.createdBy).orElse(null)?.email - val registrationEmail = userId?.let { userRepository.findById(it).orElse(null)?.email } ?: guestEmail + val registrationEmail = user?.email ?: guestEmail if (hostEmail != null && hostEmail == registrationEmail) { throw RegistrationValidationException(RegistrationErrorCode.REGISTRATION_BLOCKED_HOST) @@ -143,19 +148,11 @@ RegistrationService( throw RegistrationConflictException(RegistrationErrorCode.REGISTRATION_ALREADY_EXISTS) } - val waitlistedNumber: Int? = - if (saved.status == RegistrationStatus.WAITLISTED) { - registrationRepository - .countByEventIdAndStatus(eventPk, RegistrationStatus.WAITLISTED) - .toInt() - } else { - null - } - - val user = - userId?.let { id -> - userRepository.findById(id).orElse(null) - } + val waitlistedCount = + registrationRepository + .countByEventIdAndStatus(eventPk, RegistrationStatus.WAITLISTED) + .toInt() + val waitlistedNumber: Int? = if (saved.status == RegistrationStatus.WAITLISTED) waitlistedCount else null val recipientEmail = user?.email ?: guestEmail @@ -164,10 +161,6 @@ RegistrationService( registrationRepository .countByEventIdAndStatus(eventPk, RegistrationStatus.CONFIRMED) .toInt() - val waitlistedCount = - registrationRepository - .countByEventIdAndStatus(eventPk, RegistrationStatus.WAITLISTED) - .toInt() val totalCount = confirmedCount + waitlistedCount val emailData = @@ -205,22 +198,9 @@ RegistrationService( fun delete( registrationPublicId: String, userId: Long?, - guestName: String?, - guestEmail: String?, ): DeleteRegistrationResponse { - if (userId == null) { - if (guestName.isNullOrBlank()) { - throw RegistrationValidationException(RegistrationErrorCode.REGISTRATION_WRONG_NAME) - } - if (guestEmail.isNullOrBlank()) { - throw RegistrationValidationException(RegistrationErrorCode.REGISTRATION_WRONG_EMAIL) - } - } - deleteInternal( userId = userId, - guestName = guestName, - guestEmail = guestEmail, registrationPublicId = registrationPublicId, ) return DeleteRegistrationResponse() @@ -228,8 +208,6 @@ RegistrationService( private fun deleteInternal( userId: Long?, - guestName: String?, - guestEmail: String?, registrationPublicId: String, ) { // eventId๋ฅผ ์–ป๊ธฐ ์œ„ํ•œ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์กฐํšŒ (๋ฝ ์—†์Œ) @@ -247,21 +225,12 @@ RegistrationService( registrationRepository.lockByRegistrationPublicId(registrationPublicId) ?: throw RegistrationNotFoundException() - if (userId != null) { + if (registration.userId != null) { if (registration.userId != userId) { throw RegistrationForbiddenException(RegistrationErrorCode.REGISTRATION_DELETE_UNAUTHORIZED) } - } else { - if (registration.userId != null || - registration.guestName != guestName || - registration.guestEmail != guestEmail - ) { - throw RegistrationForbiddenException(RegistrationErrorCode.REGISTRATION_DELETE_UNAUTHORIZED) - } } - eventLockRepository.lockById(registration.eventId) - if (!isRegistrationEnabled(event)) { throw RegistrationValidationException(RegistrationErrorCode.NOT_WITHIN_REGISTRATION_WINDOW) } @@ -585,13 +554,14 @@ RegistrationService( val status = registration.status val waitlistPosition = if (status == RegistrationStatus.WAITLISTED) { - val waitlistedRegs = - registrationRepository.findByEventIdAndStatusOrderByCreatedAtAsc( - registration.eventId, - RegistrationStatus.WAITLISTED, - ) - val idx = waitlistedRegs.indexOfFirst { it.id == registration.id } - if (idx >= 0) idx + 1 else 0 + registrationRepository + .findWaitlistPositionsByRegistrationPublicIds( + eventId = registration.eventId, + status = RegistrationStatus.WAITLISTED, + registrationPublicIds = listOf(registration.registrationPublicId), + ).firstOrNull() + ?.waitlistNumber + ?.toInt() ?: 0 } else { 0 } @@ -755,31 +725,34 @@ RegistrationService( val available = capacity - confirmed if (available <= 0) return - val waitlistedRegs = + val totalWaitlisted = + registrationRepository.countByEventIdAndStatus(eventId, RegistrationStatus.WAITLISTED).toInt() + + val promoted = registrationRepository.findByEventIdAndStatusOrderByCreatedAtAsc( eventId, RegistrationStatus.WAITLISTED, + Pageable.ofSize(available), ) val waitlistNumbers = - waitlistedRegs.withIndex().associate { indexed -> - indexed.value.registrationPublicId to (indexed.index + 1) + promoted.withIndex().associate { (index, reg) -> + reg.registrationPublicId to (index + 1) } - val promoted = waitlistedRegs.take(available) promoted.forEach { it.status = RegistrationStatus.CONFIRMED } registrationRepository.saveAll(promoted) val confirmedAfter = confirmed + promoted.size - val remainingWaitlisted = waitlistedRegs.size - promoted.size + val remainingWaitlisted = totalWaitlisted - promoted.size val totalCount = confirmedAfter + remainingWaitlisted + val promotedUserIds = promoted.mapNotNull { it.userId }.distinct() + val promotedUsersById = userRepository.findAllById(promotedUserIds).associateBy { it.id!! } + val emailDataList = promoted.mapNotNull { registration -> - val user: User? = - registration.userId?.let { uid -> - userRepository.findById(uid).orElse(null) - } + val user: User? = registration.userId?.let { promotedUsersById[it] } val recipientEmail = user?.email ?: registration.guestEmail val recipientName = user?.name ?: registration.guestName ?: "์ฐธ์—ฌ์ž" diff --git a/src/main/kotlin/com/wafflestudio/spring2025/domain/user/controller/UserController.kt b/src/main/kotlin/com/wafflestudio/spring2025/domain/user/controller/UserController.kt index 85c924bd..8f214cf1 100644 --- a/src/main/kotlin/com/wafflestudio/spring2025/domain/user/controller/UserController.kt +++ b/src/main/kotlin/com/wafflestudio/spring2025/domain/user/controller/UserController.kt @@ -7,11 +7,6 @@ import com.wafflestudio.spring2025.domain.user.dto.PatchMeRequest import com.wafflestudio.spring2025.domain.user.dto.PatchMeResponse import com.wafflestudio.spring2025.domain.user.model.User import com.wafflestudio.spring2025.domain.user.service.UserService -import io.swagger.v3.oas.annotations.Operation -import io.swagger.v3.oas.annotations.Parameter -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 org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PatchMapping @@ -22,27 +17,19 @@ import org.springframework.web.bind.annotation.RestController @AuthRequired @RestController @RequestMapping("/api/users") -@Tag(name = "User", description = "์‚ฌ์šฉ์ž API") class UserController( private val userService: UserService, ) { - @Operation(summary = "๋ณธ์ธ ์ •๋ณด ์กฐํšŒ", description = "๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค") - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "์‚ฌ์šฉ์ž ์ •๋ณด ์กฐํšŒ ์„ฑ๊ณต"), - ApiResponse(responseCode = "401", description = "์ธ์ฆ ์‹คํŒจ (์œ ํšจํ•˜์ง€ ์•Š์€ ํ† ํฐ)"), - ], - ) @AuthRequired @GetMapping("/me") fun me( - @Parameter(hidden = true) @LoggedInUser user: User, + @LoggedInUser user: User, ): ResponseEntity = ResponseEntity.ok(userService.me(user)) @AuthRequired @PatchMapping("/me") fun patchMe( - @Parameter(hidden = true) @LoggedInUser user: User, + @LoggedInUser user: User, @RequestBody request: PatchMeRequest, ): ResponseEntity { userService.patchMe( @@ -54,41 +41,4 @@ class UserController( ) return ResponseEntity.ok(userService.me(user)) } - -// @Operation(summary = "ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ", description = "๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์˜ ํ”„๋กœํ•„ ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œ(๊ต์ฒด)ํ•ฉ๋‹ˆ๋‹ค") -// @ApiResponses( -// value = [ -// ApiResponse(responseCode = "204", description = "ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์„ฑ๊ณต"), -// ApiResponse(responseCode = "400", description = "์ž˜๋ชป๋œ ์š”์ฒญ (ํŒŒ์ผ ๋ˆ„๋ฝ/ํ˜•์‹ ์˜ค๋ฅ˜ ๋“ฑ)"), -// ApiResponse(responseCode = "401", description = "์ธ์ฆ ์‹คํŒจ (์œ ํšจํ•˜์ง€ ์•Š์€ ํ† ํฐ)"), -// ], -// ) -// @AuthRequired -// @PutMapping( -// "/me/profile-image", -// consumes = [MediaType.MULTIPART_FORM_DATA_VALUE], -// ) -// fun uploadProfileImage( -// @Parameter(hidden = true) @LoggedInUser user: User?, -// @RequestPart("image") image: MultipartFile, -// ): ResponseEntity { -// userService.updateProfileImage(user, image) -// return ResponseEntity.noContent().build() -// } -// -// @Operation(summary = "ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์‚ญ์ œ", description = "๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์˜ ํ”„๋กœํ•„ ์ด๋ฏธ์ง€๋ฅผ ์‚ญ์ œ(๊ธฐ๋ณธ ์ด๋ฏธ์ง€๋กœ ๋ณต๊ท€)ํ•ฉ๋‹ˆ๋‹ค") -// @ApiResponses( -// value = [ -// ApiResponse(responseCode = "204", description = "ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์‚ญ์ œ ์„ฑ๊ณต"), -// ApiResponse(responseCode = "401", description = "์ธ์ฆ ์‹คํŒจ (์œ ํšจํ•˜์ง€ ์•Š์€ ํ† ํฐ)"), -// ], -// ) -// @AuthRequired -// @DeleteMapping("/me/profile-image") -// fun deleteProfileImage( -// @Parameter(hidden = true) @LoggedInUser user: User?, -// ): ResponseEntity { -// userService.deleteProfileImage(user) -// return ResponseEntity.noContent().build() -// } } diff --git a/src/main/kotlin/com/wafflestudio/spring2025/domain/user/identity/dto/core/UserIdentityDto.kt b/src/main/kotlin/com/wafflestudio/spring2025/domain/user/identity/dto/core/UserIdentityDto.kt index 092180d5..1a0f9e05 100644 --- a/src/main/kotlin/com/wafflestudio/spring2025/domain/user/identity/dto/core/UserIdentityDto.kt +++ b/src/main/kotlin/com/wafflestudio/spring2025/domain/user/identity/dto/core/UserIdentityDto.kt @@ -1,19 +1,12 @@ package com.wafflestudio.spring2025.domain.user.identity.dto.core import com.wafflestudio.spring2025.domain.user.identity.model.UserIdentity -import io.swagger.v3.oas.annotations.media.Schema -@Schema(description = "์†Œ์…œ ๊ณ„์ • ์ •๋ณด") data class UserIdentityDto( - @Schema(description = "์†Œ์…œ ๊ณ„์ • ID") val id: Long, - @Schema(description = "์‚ฌ์šฉ์ž ID") val userId: Long, - @Schema(description = "์†Œ์…œ ๋กœ๊ทธ์ธ ์ œ๊ณต์ž") val provider: String, - @Schema(description = "์ œ๊ณต์ž ์‚ฌ์šฉ์ž ID") val providerUserId: String, - @Schema(description = "์ƒ์„ฑ ์ผ์‹œ (epoch milliseconds)") val createdAt: Long, ) { constructor(identity: UserIdentity) : this( diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 7fefbb20..ee7ef521 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -68,7 +68,7 @@ management: endpoints: web: exposure: - include: health + include: health,info,prometheus,metrics endpoint: health: show-details: always @@ -95,6 +95,13 @@ aws: server: forward-headers-strategy: framework +springdoc: + api-docs: + enabled: false + swagger-ui: + url: /docs/openapi.yaml + disable-swagger-default-url: true + --- spring: config: diff --git a/src/main/resources/static/docs/openapi.yaml b/src/main/resources/static/docs/openapi.yaml new file mode 100644 index 00000000..9f62c069 --- /dev/null +++ b/src/main/resources/static/docs/openapi.yaml @@ -0,0 +1,1257 @@ +openapi: 3.0.1 +info: + title: Moiming - Event Schedule Management System API + description: ๋ชจ์ž„ ์ผ์ • ๊ด€๋ฆฌ ์‹œ์Šคํ…œ '๋ชจ์ด๋ฐ' API ๋ฌธ์„œ + version: "1.0" +servers: +- url: http://localhost:8080 + description: Generated server url +tags: +- name: Event + description: ์ด๋ฒคํŠธ ๊ด€๋ฆฌ API +- name: Registration + description: ์‹ ์ฒญ API +- name: Auth + description: ์ธ์ฆ API +- name: User + description: ์‚ฌ์šฉ์ž API +- name: Image + description: ์ด๋ฏธ์ง€ API +paths: + /api/events/{publicId}: + get: + security: [] + tags: + - Event + summary: ์ด๋ฒคํŠธ ์ƒ์„ธ ์กฐํšŒ + description: ์ด๋ฒคํŠธ ์ƒ์„ธ ์ •๋ณด๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค + parameters: + - name: publicId + in: path + required: true + schema: + type: string + responses: + "200": + description: OK + content: + '*/*': + schema: + $ref: "#/components/schemas/EventDetailResponse" + put: + tags: + - Event + summary: ์ด๋ฒคํŠธ ์ˆ˜์ • + description: ์ด๋ฒคํŠธ๋ฅผ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค + security: + - bearerAuth: [] + parameters: + - name: publicId + in: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/UpdateEventRequest" + required: true + responses: + "200": + description: OK + content: + '*/*': + schema: + $ref: "#/components/schemas/UpdateEventResponse" + "401": + description: ์ธ์ฆ ํ•„์š” + delete: + tags: + - Event + summary: ์ด๋ฒคํŠธ ์‚ญ์ œ + description: ์ด๋ฒคํŠธ๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค + security: + - bearerAuth: [] + parameters: + - name: publicId + in: path + required: true + schema: + type: string + responses: + "204": + description: No Content + "401": + description: ์ธ์ฆ ํ•„์š” + /api/images: + post: + security: + - bearerAuth: [] + tags: + - Image + summary: ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ + description: ์„œ๋ฒ„ ์ €์žฅ์†Œ์— ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค. + parameters: + - name: prefix + in: query + description: ์ด๋ฏธ์ง€๋ฅผ ์ €์žฅํ•  ์ƒ์œ„ ๊ฒฝ๋กœ + required: false + schema: + type: string + requestBody: + content: + multipart/form-data: + schema: + required: + - image + type: object + properties: + image: + type: string + format: binary + responses: + "200": + description: ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์„ฑ๊ณต + content: + '*/*': + schema: + $ref: "#/components/schemas/ImageUploadResponse" + "400": + description: ์ž˜๋ชป๋œ ์š”์ฒญ (ํŒŒ์ผ ๋ˆ„๋ฝ/ํ˜•์‹ ์˜ค๋ฅ˜ ๋“ฑ) + content: + '*/*': + schema: + $ref: "#/components/schemas/ErrorResponse" + "401": + description: ์ธ์ฆ ์‹คํŒจ (์œ ํšจํ•˜์ง€ ์•Š์€ ํ† ํฐ) + content: + '*/*': + schema: + $ref: "#/components/schemas/ErrorResponse" + /api/events: + post: + tags: + - Event + summary: ์ด๋ฒคํŠธ ์ƒ์„ฑ + description: ์ƒˆ๋กœ์šด ์ด๋ฒคํŠธ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค + security: + - bearerAuth: [] + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CreateEventRequest" + required: true + responses: + "200": + description: OK + content: + '*/*': + schema: + $ref: "#/components/schemas/CreateEventResponse" + "401": + description: ์ธ์ฆ ํ•„์š” + /api/events/{eventId}/registrations: + get: + security: + - bearerAuth: [] + tags: + - Registration + summary: ์ด๋ฒคํŠธ ์‹ ์ฒญ ๋ชฉ๋ก ์กฐํšŒ + description: ํŠน์ • ์ด๋ฒคํŠธ์— ์†ํ•œ ์‹ ์ฒญ ๋ชฉ๋ก์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. ์‹ ์ฒญ ์ƒํƒœ(CONFIRMED/WAITLISTED/BANNED)์™€ ์‹ ์ฒญ + ์‹œ๊ฐ์„ ํฌํ•จํ•ด ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. + parameters: + - name: eventId + in: path + required: true + schema: + type: string + - name: status + in: query + required: false + schema: + type: string + - name: orderBy + in: query + required: false + schema: + type: string + - name: cursor + in: query + description: ๋‹ค์Œ ํŽ˜์ด์ง€ ์‹œ์ž‘ offset (์—†์œผ๋ฉด 0๋ถ€ํ„ฐ) + required: false + schema: + type: integer + format: int32 + responses: + "200": + description: OK + content: + '*/*': + schema: + $ref: "#/components/schemas/GetEventRegistrationsResponse" + post: + security: [] + tags: + - Registration + summary: ์ด๋ฒคํŠธ ์ฐธ์—ฌ ์‹ ์ฒญ + description: "ํŠน์ • ์ด๋ฒคํŠธ์— ์‹ ์ฒญํ•ฉ๋‹ˆ๋‹ค. ์ •์›์ด ๋‚จ์•„์žˆ์œผ๋ฉด CONFIRMED๋กœ ๋“ฑ๋ก๋˜๊ณ , ์ •์›์ด ์ฐผ์œผ๋‚˜ ๋Œ€๊ธฐ ๋ช…๋‹จ์ด ํ—ˆ์šฉ๋œ ๊ฒฝ\ + ์šฐ WAITLISTED๋กœ ๋“ฑ๋ก๋ฉ๋‹ˆ๋‹ค. ์ •์›์ด ์ฐผ๊ณ  ๋Œ€๊ธฐ ๋ช…๋‹จ์ด ๋น„ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ ์‹ ์ฒญ์ด ๊ฑฐ์ ˆ๋ฉ๋‹ˆ๋‹ค." + parameters: + - name: eventId + in: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CreateRegistrationRequest" + required: true + responses: + "200": + description: ์‹ ์ฒญ ์„ฑ๊ณต + content: + '*/*': + schema: + $ref: "#/components/schemas/CreateRegistrationResponse" + "409": + description: "์‹ ์ฒญ ๋ถˆ๊ฐ€: REGISTRATION_ALREADY_EXISTS, REGISTRATION_BLOCKED_HOST, EVENT_FULL" + content: + '*/*': + schema: + $ref: "#/components/schemas/ErrorResponse" + "400": + description: "์œ ํšจํ•˜์ง€ ์•Š์€ ์š”์ฒญ: REGISTRATION_WRONG_NAME, REGISTRATION_WRONG_EMAIL, NOT_WITHIN_REGISTRATION_WINDOW" + content: + '*/*': + schema: + $ref: "#/components/schemas/ErrorResponse" + "403": + description: "๊ถŒํ•œ ์—†์Œ: REGISTRATION_BLOCKED_BANNED" + content: + '*/*': + schema: + $ref: "#/components/schemas/ErrorResponse" + "404": + description: "์ด๋ฒคํŠธ ์—†์Œ: EVENT_NOT_FOUND" + content: + '*/*': + schema: + $ref: "#/components/schemas/ErrorResponse" + /api/auth/social: + post: + security: [] + tags: + - Auth + summary: ์†Œ์…œ ๋กœ๊ทธ์ธ + description: ์†Œ์…œ ๋กœ๊ทธ์ธ์„ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค. + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/SocialLoginRequest" + required: true + responses: + "200": + description: ์†Œ์…œ ๋กœ๊ทธ์ธ ์„ฑ๊ณต + content: + '*/*': + schema: + $ref: "#/components/schemas/LoginResponse" + "409": + description: ์ด๋ฏธ ๊ฐ€์ž…๋œ ์ด๋ฉ”์ผ + content: + '*/*': + schema: + $ref: "#/components/schemas/ErrorResponse" + "401": + description: ์ธ์ฆ ์‹คํŒจ + content: + '*/*': + schema: + $ref: "#/components/schemas/ErrorResponse" + /api/auth/signup: + post: + security: [] + tags: + - Auth + summary: ์ด๋ฉ”์ผ ํšŒ์›๊ฐ€์ž… + description: ์ƒˆ๋กœ์šด ์‚ฌ์šฉ์ž๋ฅผ ๋“ฑ๋กํ•˜๊ณ  ์ธ์ฆ ์ด๋ฉ”์ผ์„ ๋ฐœ์†กํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฉ”์ผ ์ธ์ฆ ํ›„ ๊ฐ€์ž…์ด ์™„๋ฃŒ๋ฉ๋‹ˆ๋‹ค. + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/SignupRequest" + required: true + responses: + "409": + description: ์ด๋ฏธ ์กด์žฌํ•˜๋Š” email + "204": + description: ์ธ์ฆ ์ด๋ฉ”์ผ ๋ฐœ์†ก ์„ฑ๊ณต + "400": + description: ์ž˜๋ชป๋œ ์š”์ฒญ (์œ ํšจํ•˜์ง€ ์•Š์€ email/password/name) + "503": + description: ์ด๋ฉ”์ผ ์„œ๋น„์Šค ์žฅ์•  + /api/auth/logout: + post: + security: + - bearerAuth: [] + tags: + - Auth + summary: ๋กœ๊ทธ์•„์›ƒ + description: ํ˜„์žฌ JWT ํ† ํฐ์„ ๋ธ”๋ž™๋ฆฌ์ŠคํŠธ์— ์ถ”๊ฐ€ํ•˜์—ฌ ๋กœ๊ทธ์•„์›ƒํ•ฉ๋‹ˆ๋‹ค + responses: + "204": + description: ๋กœ๊ทธ์•„์›ƒ ์„ฑ๊ณต + /api/auth/login: + post: + security: [] + tags: + - Auth + summary: ๋กœ๊ทธ์ธ + description: email๋กœ ๋กœ๊ทธ์ธํ•˜์—ฌ JWT ํ† ํฐ์„ ๋ฐœ๊ธ‰๋ฐ›์Šต๋‹ˆ๋‹ค + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/LoginRequest" + required: true + responses: + "401": + description: ์ธ์ฆ ์‹คํŒจ + content: + '*/*': + schema: + $ref: "#/components/schemas/ErrorResponse" + "200": + description: "๋กœ๊ทธ์ธ ์„ฑ๊ณต, JWT ํ† ํฐ ๋ฐ˜ํ™˜" + content: + '*/*': + schema: + $ref: "#/components/schemas/LoginResponse" + /api/auth/email-verification/{verificationCode}: + post: + security: [] + tags: + - Auth + summary: ์ด๋ฉ”์ผ ์ธ์ฆ + description: ์ด๋ฉ”์ผ๋กœ ๋ฐ›์€ ์ธ์ฆ ์ฝ”๋“œ๋ฅผ ๊ฒ€์ฆํ•˜๊ณ  ํšŒ์›๊ฐ€์ž…์„ ์™„๋ฃŒํ•ฉ๋‹ˆ๋‹ค + parameters: + - name: verificationCode + in: path + required: true + schema: + type: string + responses: + "400": + description: ์œ ํšจํ•˜์ง€ ์•Š๊ฑฐ๋‚˜ ๋งŒ๋ฃŒ๋œ ์ธ์ฆ ์ฝ”๋“œ + "200": + description: "์ธ์ฆ ์„ฑ๊ณต, ์œ ์ € ๋“ฑ๋ก ์™„๋ฃŒ" + /api/users/me: + get: + tags: + - User + summary: ๋ณธ์ธ ์ •๋ณด ์กฐํšŒ + description: ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค + security: + - bearerAuth: [] + responses: + "401": + description: ์ธ์ฆ ์‹คํŒจ (์œ ํšจํ•˜์ง€ ์•Š์€ ํ† ํฐ) + content: + '*/*': + schema: + $ref: "#/components/schemas/ErrorResponse" + "200": + description: ์‚ฌ์šฉ์ž ์ •๋ณด ์กฐํšŒ ์„ฑ๊ณต + content: + '*/*': + schema: + $ref: "#/components/schemas/UserDto" + patch: + tags: + - User + summary: ๋ณธ์ธ ์ •๋ณด ์ˆ˜์ • + description: ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋ฅผ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค + security: + - bearerAuth: [] + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/PatchMeRequest" + required: true + responses: + "200": + description: OK + content: + '*/*': + schema: + $ref: "#/components/schemas/UserDto" + "401": + description: ์ธ์ฆ ํ•„์š” + /api/registrations/{registrationId}: + get: + security: [] + tags: + - Registration + summary: ์‹ ์ฒญ ๋‹จ๊ฑด ์ •๋ณด ์š”์ฒญ + description: ์‹ ์ฒญ ๋‹จ๊ฑด์˜ ์ •๋ณด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. + parameters: + - name: registrationId + in: path + required: true + schema: + type: string + responses: + "200": + description: OK + content: + '*/*': + schema: + $ref: "#/components/schemas/GetRegistrationResponse" + delete: + security: [] + tags: + - Registration + summary: ์‹ ์ฒญ ๋‹จ๊ฑด ์ทจ์†Œ + description: ์‹ ์ฒญ ๋‹จ๊ฑด ๋ฆฌ์†Œ์Šค๋ฅผ ์ทจ์†Œํ•ฉ๋‹ˆ๋‹ค. + parameters: + - name: registrationId + in: path + required: true + schema: + type: string + responses: + "200": + description: OK + content: + '*/*': + schema: + $ref: "#/components/schemas/DeleteRegistrationResponse" + patch: + security: + - bearerAuth: [] + tags: + - Registration + summary: ์‹ ์ฒญ ๋‹จ๊ฑด ์ƒํƒœ ๋ณ€๊ฒฝ + description: ์‹ ์ฒญ ๋‹จ๊ฑด ๋ฆฌ์†Œ์Šค์˜ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ๋ณธ์ธ์˜ ์‹ ์ฒญ์„ ์ทจ์†Œํ•˜๋Š” ๊ฒฝ์šฐ์™€ ๊ด€๋ฆฌ์ž๊ฐ€ ๊ฐ•์ œ ์ทจ์†Œ/๋ฐด ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒฝ์šฐ๋ฅผ + ๋กœ์ง ๋ ˆ๋ฒจ์—์„œ ๊ตฌ๋ถ„ํ•ฉ๋‹ˆ๋‹ค. + parameters: + - name: registrationId + in: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/UpdateRegistrationStatusRequest" + required: true + responses: + "200": + description: OK + content: + '*/*': + schema: + $ref: "#/components/schemas/PatchRegistrationResponse" + /api/registrations/me: + get: + tags: + - Registration + summary: ๋‚ด ์ด๋ฒคํŠธ ์‹ ์ฒญ ๋ชฉ๋ก ์กฐํšŒ + description: ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์˜ ์ด๋ฒคํŠธ ์‹ ์ฒญ ๋ชฉ๋ก์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. ์ตœ์‹  ์‹ ์ฒญ ์ˆœ์œผ๋กœ ๋ฐ˜ํ™˜๋˜๋ฉฐ page/size๋กœ ํŽ˜์ด์ง•ํ•ฉ๋‹ˆ๋‹ค. + security: + - bearerAuth: [] + parameters: + - name: page + in: query + required: false + schema: + type: integer + format: int32 + default: 0 + - name: size + in: query + required: false + schema: + type: integer + format: int32 + default: 20 + responses: + "200": + description: OK + content: + '*/*': + schema: + $ref: "#/components/schemas/GetMyRegistrationsResponse" + "401": + description: ์ธ์ฆ ํ•„์š” + /api/events/me: + get: + tags: + - Event + summary: ๋‚ด๊ฐ€ ๋งŒ๋“  ์ด๋ฒคํŠธ ๋ชฉ๋ก ์กฐํšŒ (๋ฌดํ•œ ์Šคํฌ๋กค) + description: ๋กœ๊ทธ์ธ๋œ ์‚ฌ์šฉ์ž๊ฐ€ ์ƒ์„ฑํ•œ ์ด๋ฒคํŠธ ๋ชฉ๋ก์„ ๋ฌดํ•œ ์Šคํฌ๋กค ๋ฐฉ์‹(cursor)์œผ๋กœ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค + security: + - bearerAuth: [] + parameters: + - name: cursor + in: query + required: false + schema: + type: string + format: date-time + - name: size + in: query + required: false + schema: + type: integer + format: int32 + default: 5 + responses: + "200": + description: OK + content: + '*/*': + schema: + $ref: "#/components/schemas/MyEventsInfiniteResponse" + "401": + description: ์ธ์ฆ ํ•„์š” +components: + schemas: + UpdateEventRequest: + type: object + properties: + title: + type: string + description: ์ด๋ฒคํŠธ ์ œ๋ชฉ + nullable: true + example: ์ •๊ธฐ ๋ชจ์ž„ + description: + type: string + description: ์ด๋ฒคํŠธ ์„ค๋ช… + nullable: true + example: ์ •๊ธฐ ๋ชจ์ž„์ž…๋‹ˆ๋‹ค + location: + type: string + description: ์žฅ์†Œ + nullable: true + example: ๊ฐ•์˜์‹ค 101 + startsAt: + type: string + description: ์‹œ์ž‘ ์‹œ๊ฐ„ (ISO-8601) + format: date-time + nullable: true + example: 2026-02-02T18:00:00Z + endsAt: + type: string + description: ์ข…๋ฃŒ ์‹œ๊ฐ„ (ISO-8601) + format: date-time + nullable: true + example: 2026-02-02T20:00:00Z + capacity: + type: integer + description: ์ •์› + format: int32 + nullable: true + example: 30 + waitlistEnabled: + type: boolean + description: ๋Œ€๊ธฐ ๋ช…๋‹จ ์‚ฌ์šฉ ์—ฌ๋ถ€ + nullable: true + example: true + registrationStartsAt: + type: string + description: ์‹ ์ฒญ ์‹œ์ž‘ ์‹œ๊ฐ„ (ISO-8601) + format: date-time + nullable: true + example: 2026-02-02T17:00:00Z + registrationEndsAt: + type: string + description: ์‹ ์ฒญ ๋งˆ๊ฐ ์‹œ๊ฐ„ (ISO-8601) + format: date-time + nullable: true + example: 2026-02-02T17:30:00Z + description: ์ผ์ • ์ˆ˜์ • ์š”์ฒญ + UpdateEventResponse: + required: + - registrationEndsAt + - title + - capacity + - waitlistEnabled + type: object + properties: + title: + type: string + description: + type: string + nullable: true + location: + type: string + nullable: true + startsAt: + type: string + format: date-time + nullable: true + endsAt: + type: string + format: date-time + nullable: true + capacity: + type: integer + format: int32 + nullable: true + waitlistEnabled: + type: boolean + registrationStartsAt: + type: string + format: date-time + nullable: true + registrationEndsAt: + type: string + format: date-time + ImageUploadResponse: + required: + - key + - url + type: object + properties: + key: + type: string + url: + type: string + CreateEventRequest: + required: + - capacity + - registrationEndsAt + - title + - waitlistEnabled + type: object + properties: + title: + type: string + description: ์ด๋ฒคํŠธ ์ œ๋ชฉ + example: ์ •๊ธฐ ๋ชจ์ž„ + description: + type: string + description: ์ด๋ฒคํŠธ ์„ค๋ช… + nullable: true + example: ์ •๊ธฐ ๋ชจ์ž„์ž…๋‹ˆ๋‹ค + location: + type: string + description: ์žฅ์†Œ + nullable: true + example: ๊ฐ•์˜์‹ค 101 + startsAt: + type: string + description: ์‹œ์ž‘ ์‹œ๊ฐ„ (ISO-8601) + format: date-time + nullable: true + example: 2026-02-02T18:00:00Z + endsAt: + type: string + description: ์ข…๋ฃŒ ์‹œ๊ฐ„ (ISO-8601) + format: date-time + nullable: true + example: 2026-02-02T20:00:00Z + capacity: + type: integer + description: ์ •์› + format: int32 + example: 30 + waitlistEnabled: + type: boolean + description: ๋Œ€๊ธฐ ๋ช…๋‹จ ์‚ฌ์šฉ ์—ฌ๋ถ€ + example: true + registrationStartsAt: + type: string + description: ์‹ ์ฒญ ์‹œ์ž‘ ์‹œ๊ฐ„ (ISO-8601). null์ธ ๊ฒฝ์šฐ ์š”์ฒญ ์‹œ์ ์œผ๋กœ ์ž๋™ ์„ค์ •๋ฉ๋‹ˆ๋‹ค. + format: date-time + nullable: true + example: 2026-02-02T17:00:00Z + registrationEndsAt: + type: string + description: ์‹ ์ฒญ ๋งˆ๊ฐ ์‹œ๊ฐ„ (ISO-8601) + format: date-time + example: 2026-02-02T17:30:00Z + description: ์ผ์ • ์ƒ์„ฑ ์š”์ฒญ + CreateEventResponse: + required: + - publicId + type: object + properties: + publicId: + type: string + CreateRegistrationRequest: + type: object + properties: + guestName: + type: string + description: ๋น„ํšŒ์› ์ด๋ฆ„ + nullable: true + guestEmail: + type: string + description: ๋น„ํšŒ์› ์ด๋ฉ”์ผ + nullable: true + description: ์ด๋ฒคํŠธ ์‹ ์ฒญ ์š”์ฒญ + CreateRegistrationResponse: + required: + - registrationPublicId + type: object + properties: + registrationPublicId: + type: string + description: ์‹ ์ฒญ id + description: ์ด๋ฒคํŠธ ์‹ ์ฒญ ์‘๋‹ต + SocialLoginRequest: + required: + - code + - provider + type: object + properties: + provider: + type: string + description: ์†Œ์…œ๋กœ๊ทธ์ธ ์ œ๊ณต์ž + example: GOOGLE + code: + type: string + description: ์ธ๊ฐ€ ์ฝ”๋“œ (authorization code) + description: ์†Œ์…œ ๋กœ๊ทธ์ธ ์š”์ฒญ + LoginResponse: + required: + - token + type: object + properties: + token: + type: string + SignupRequest: + required: + - email + - name + - password + type: object + properties: + email: + type: string + description: ์‚ฌ์šฉ์ž ์ด๋ฉ”์ผ + example: user@example.com + name: + type: string + description: ์‚ฌ์šฉ์ž ์ด๋ฆ„ + example: ํ™๊ธธ๋™ + password: + type: string + description: ๋น„๋ฐ€๋ฒˆํ˜ธ + example: password123 + profileImage: + type: string + description: ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ URL + nullable: true + example: https://cdn.example.com/profile.png + description: ํšŒ์›๊ฐ€์ž… ์š”์ฒญ + LoginRequest: + required: + - email + - password + type: object + properties: + email: + type: string + description: ์‚ฌ์šฉ์ž ์ด๋ฉ”์ผ + example: user@example.com + password: + type: string + description: ์‚ฌ์šฉ์ž ๋น„๋ฐ€๋ฒˆํ˜ธ + example: mypassword + description: ๋กœ๊ทธ์ธ ์š”์ฒญ + PatchMeRequest: + type: object + properties: + name: + type: string + nullable: true + email: + type: string + nullable: true + password: + type: string + nullable: true + profileImage: + type: string + nullable: true + UserDto: + required: + - email + - id + - name + type: object + properties: + id: + type: integer + format: int64 + email: + type: string + name: + type: string + profileImage: + type: string + nullable: true + UpdateRegistrationStatusRequest: + type: object + properties: + status: + type: string + nullable: true + enum: + - HOST + - CONFIRMED + - WAITLISTED + - BANNED + description: ์ด๋ฒคํŠธ ์‹ ์ฒญ ์ƒํƒœ ๋ณ€๊ฒฝ ์š”์ฒญ + PatchRegistrationResponse: + type: object + properties: + patchEmail: + type: string + nullable: true + description: ์ด๋ฒคํŠธ ์‹ ์ฒญ ์ƒํƒœ ๋ณ€๊ฒฝ + GetRegistrationResponse: + required: + - guestName + - registrationPublicId + - reservationEmail + - status + - waitlistPosition + type: object + properties: + status: + type: string + enum: + - HOST + - CONFIRMED + - WAITLISTED + - BANNED + guestName: + type: string + waitlistPosition: + type: integer + format: int32 + registrationPublicId: + type: string + reservationEmail: + type: string + description: ์ด๋ฒคํŠธ ์‹ ์ฒญ ์ •๋ณด ์š”์ฒญ ์‘๋‹ต + GetMyRegistrationsResponse: + required: + - registrations + type: object + properties: + registrations: + type: array + description: ์‹ ์ฒญ ๋ชฉ๋ก + items: + $ref: "#/components/schemas/MyRegistrationItem" + description: ๋‚ด ์ด๋ฒคํŠธ ์‹ ์ฒญ ๋ชฉ๋ก ์‘๋‹ต + MyRegistrationItem: + required: + - publicId + - confirmedCount + - waitlistCount + - status + - title + type: object + properties: + publicId: + type: string + description: ์ด๋ฒคํŠธ ๊ณต๊ฐœ ID + title: + type: string + description: ์ด๋ฒคํŠธ ์ œ๋ชฉ + startsAt: + type: string + description: ์ด๋ฒคํŠธ ์‹œ์ž‘ ์‹œ๊ฐ„ (ISO-8601) + format: date-time + nullable: true + endsAt: + type: string + description: ์ด๋ฒคํŠธ ์ข…๋ฃŒ ์‹œ๊ฐ„ (ISO-8601) + format: date-time + nullable: true + registrationStartsAt: + type: string + description: ์‹ ์ฒญ ์‹œ์ž‘ ์‹œ๊ฐ„ (ISO-8601) + format: date-time + nullable: true + registrationEndsAt: + type: string + description: ์‹ ์ฒญ ๋งˆ๊ฐ ์‹œ๊ฐ„ (ISO-8601) + format: date-time + nullable: true + capacity: + type: integer + description: ์ •์› + format: int32 + nullable: true + confirmedCount: + type: integer + description: ์ฐธ์—ฌ ํ™•์ •์ž ์ˆ˜ + format: int32 + waitlistCount: + type: integer + description: ๋Œ€๊ธฐ์ž ์ˆ˜ + format: int32 + status: + type: string + description: ์‹ ์ฒญ ์ƒํƒœ + enum: + - CONFIRMED + - WAITLISTED + - BANNED + waitlistedNum: + type: integer + description: ๋Œ€๊ธฐ ์ˆœ๋ฒˆ (WAITLISTED๊ฐ€ ์•„๋‹ˆ๋ฉด null) + format: int32 + nullable: true + description: ๋‚ด ์ด๋ฒคํŠธ ์‹ ์ฒญ ์š”์•ฝ + CapabilitiesInfo: + required: + - apply + - cancel + - shareLink + - wait + type: object + properties: + shareLink: + type: boolean + description: ๊ณต์œ  ๋งํฌ ๊ฐ€๋Šฅ ์—ฌ๋ถ€ + example: false + apply: + type: boolean + description: ์ฐธ์—ฌ ์‹ ์ฒญ ๊ฐ€๋Šฅ ์—ฌ๋ถ€(ํ™•์ • ์ž๋ฆฌ ์‹ ์ฒญ) + example: false + wait: + type: boolean + description: ๋Œ€๊ธฐ ์‹ ์ฒญ ๊ฐ€๋Šฅ ์—ฌ๋ถ€(์ •์› ์ฐผ์„ ๋•Œ) + example: false + cancel: + type: boolean + description: ์‹ ์ฒญ ์ทจ์†Œ ๊ฐ€๋Šฅ ์—ฌ๋ถ€ + example: true + description: ๊ฐ€๋Šฅํ•œ ๋™์ž‘ ๋ธ”๋ก + CreatorInfo: + required: + - email + - name + type: object + properties: + name: + type: string + description: ์ด๋ฆ„ + example: ํ™๊ธธ๋™ + email: + type: string + description: ์ด๋ฉ”์ผ + example: user@example.com + profileImage: + type: string + description: ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ + nullable: true + example: https://example.com/profile.png + description: ์ƒ์„ฑ์ž ์ •๋ณด ๋ธ”๋ก + EventDetailResponse: + required: + - capabilities + - creator + - event + - guestsPreview + - viewer + type: object + properties: + event: + $ref: "#/components/schemas/EventInfo" + creator: + $ref: "#/components/schemas/CreatorInfo" + viewer: + $ref: "#/components/schemas/ViewerInfo" + capabilities: + $ref: "#/components/schemas/CapabilitiesInfo" + guestsPreview: + type: array + description: ์ฐธ์—ฌ์ž ๋ฏธ๋ฆฌ๋ณด๊ธฐ ๋ชฉ๋ก + items: + $ref: "#/components/schemas/GuestPreview" + description: ์ผ์ • ์ƒ์„ธ ์กฐํšŒ ์‘๋‹ต + EventInfo: + required: + - publicId + - registrationEndsAt + - title + - confirmedCount + - waitlistCount + type: object + properties: + publicId: + type: string + description: ์ด๋ฒคํŠธ publicId + example: dj8sadj10djnkas + title: + type: string + description: ์ด๋ฒคํŠธ ์ œ๋ชฉ + example: ์ œ2ํšŒ ๊ธฐํš ์„ธ๋ฏธ๋‚˜ + description: + type: string + description: ์ด๋ฒคํŠธ ์„ค๋ช… + nullable: true + example: ์ผ์ • ์ƒ์„ธ ์„ค๋ช…... + location: + type: string + description: ์žฅ์†Œ + nullable: true + example: ์„œ์šธ๋Œ€ + startsAt: + type: string + description: ์‹œ์ž‘ ์‹œ๊ฐ„ (ISO-8601) + format: date-time + nullable: true + example: 2026-02-02T18:00:00Z + endsAt: + type: string + description: ์ข…๋ฃŒ ์‹œ๊ฐ„ (ISO-8601) + format: date-time + nullable: true + example: 2026-02-02T20:00:00Z + capacity: + type: integer + description: ์ •์› + format: int32 + nullable: true + example: 10 + confirmedCount: + type: integer + description: ์ฐธ์—ฌ ํ™•์ • ์‹ ์ฒญ์ž ์ˆ˜ + format: int32 + example: 8 + waitlistCount: + type: integer + description: ๋Œ€๊ธฐ์ž ์ˆ˜ + format: int32 + example: 8 + registrationStartsAt: + type: string + description: ์‹ ์ฒญ ์‹œ์ž‘ ์‹œ๊ฐ„ (ISO-8601) + format: date-time + nullable: true + example: 2026-02-02T17:00:00Z + registrationEndsAt: + type: string + description: ์‹ ์ฒญ ๋งˆ๊ฐ ์‹œ๊ฐ„ (ISO-8601) + format: date-time + example: 2026-02-02T17:00:00Z + description: ์ด๋ฒคํŠธ ์ •๋ณด ๋ธ”๋ก + GuestPreview: + required: + - name + type: object + properties: + id: + type: integer + description: ์ฐธ์—ฌ์ž ID (๋น„ํšŒ์›์ด๋ฉด null) + format: int64 + nullable: true + example: 1 + name: + type: string + description: ์ฐธ์—ฌ์ž ์ด๋ฆ„ + example: ํ™๊ธธ๋™ + profileImage: + type: string + description: ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ URL + nullable: true + example: https://example.com/profile.png + description: ์ฐธ์—ฌ์ž ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ •๋ณด + ViewerInfo: + required: + - status + type: object + properties: + status: + type: string + description: ์ด๋ฒคํŠธ ์กฐํšŒ์ž(Viewer)์˜ ์ƒํƒœ + example: WAITLISTED + enum: + - HOST + - CONFIRMED + - WAITLISTED + - BANNED + - NONE + name: + type: string + description: viewer ์ด๋ฆ„ (๋น„ํšŒ์›์ด๋ฉด null) + nullable: true + example: ํ™๊ธธ๋™ + waitlistPosition: + type: integer + description: "๋Œ€๊ธฐ ์ˆœ๋ฒˆ (WAITLISTED์ผ ๋•Œ๋งŒ ์ˆซ์ž, ์•„๋‹ˆ๋ฉด null)" + format: int32 + nullable: true + example: 3 + registrationPublicId: + type: string + description: "registrationPublicId (๊ฒŒ์ŠคํŠธ๋ฉด ๊ฐ’, ์•„๋‹ˆ๋ฉด null)" + nullable: true + example: "1" + reservationEmail: + type: string + description: "์˜ˆ์•ฝ ์ด๋ฉ”์ผ(๊ฒŒ์ŠคํŠธ๋ฉด guestEmail, ๋กœ๊ทธ์ธ ์œ ์ €๋ฉด user email ๋“ฑ ์ •์ฑ…์— ๋”ฐ๋ฆ„)" + nullable: true + example: skdfsj@gmaldkl + description: ์กฐํšŒ์ž(viewer) ์ •๋ณด ๋ธ”๋ก + EventRegistrationItem: + required: + - createdAt + - name + - registrationId + - status + type: object + properties: + registrationId: + type: string + description: ์‹ ์ฒญ ๊ณต๊ฐœ ID + name: + type: string + description: ์ด๋ฆ„ + email: + type: string + description: ์ด๋ฉ”์ผ (๊ด€๋ฆฌ์ž ์š”์ฒญ ์‹œ ๋…ธ์ถœ) + nullable: true + profileImage: + type: string + description: ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ URL + nullable: true + createdAt: + type: string + description: ์‹ ์ฒญ ์ผ์‹œ + format: date-time + status: + type: string + description: ์‹ ์ฒญ ์ƒํƒœ + enum: + - HOST + - CONFIRMED + - WAITLISTED + - BANNED + waitingNum: + type: integer + description: ๋Œ€๊ธฐ ์ˆœ๋ฒˆ (status๊ฐ€ WAITLISTED์ผ ๋•Œ๋งŒ ์•„๋‹ˆ๋ฉด null) + format: int32 + nullable: true + description: ์‹ ์ฒญ์ž ์ •๋ณด + GetEventRegistrationsResponse: + required: + - hasNext + - participants + type: object + properties: + participants: + type: array + description: ์‹ ์ฒญ์ž ๋ชฉ๋ก + items: + $ref: "#/components/schemas/EventRegistrationItem" + totalCount: + type: integer + description: ํ•„ํ„ฐ์— ๋”ฐ๋ผ ๋ณ€ํ•˜๋Š” ์ฐธ์—ฌ์ž ์ˆ˜ + format: int32 + nullable: true + nextCursor: + type: integer + description: ๋‹ค์Œ ํŽ˜์ด์ง€ ์‹œ์ž‘ offset (์—†์œผ๋ฉด null) + format: int32 + nullable: true + hasNext: + type: boolean + description: ๋‹ค์Œ ํŽ˜์ด์ง€ ์กด์žฌ ์—ฌ๋ถ€ + description: ์ด๋ฒคํŠธ ์‹ ์ฒญ ๋ชฉ๋ก ์กฐํšŒ ์‘๋‹ต + MyEventResponse: + required: + - publicId + - registrationEndsAt + - title + - confirmedCount + - waitlistCount + type: object + properties: + publicId: + type: string + title: + type: string + startsAt: + type: string + format: date-time + nullable: true + endsAt: + type: string + format: date-time + nullable: true + registrationStartsAt: + type: string + format: date-time + nullable: true + registrationEndsAt: + type: string + format: date-time + capacity: + type: integer + format: int32 + nullable: true + confirmedCount: + type: integer + format: int32 + waitlistCount: + type: integer + format: int32 + MyEventsInfiniteResponse: + required: + - events + - hasNext + type: object + properties: + events: + type: array + items: + $ref: "#/components/schemas/MyEventResponse" + nextCursor: + type: string + format: date-time + nullable: true + hasNext: + type: boolean + DeleteRegistrationResponse: + type: object + ErrorResponse: + required: + - code + - message + - title + type: object + properties: + code: + type: string + description: ์—๋Ÿฌ ์ฝ”๋“œ + title: + type: string + description: ์—๋Ÿฌ ์ œ๋ชฉ + message: + type: string + description: ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ + description: ์—๋Ÿฌ ์‘๋‹ต + securitySchemes: + bearerAuth: + type: http + description: JWT ํ† ํฐ์„ ์ž…๋ ฅํ•˜์„ธ์š” + scheme: bearer + bearerFormat: JWT diff --git a/src/main/resources/static/docs/swagger-ui.html b/src/main/resources/static/docs/swagger-ui.html new file mode 100644 index 00000000..517735db --- /dev/null +++ b/src/main/resources/static/docs/swagger-ui.html @@ -0,0 +1,36 @@ + + + + + + MoiMing API Docs + + + + +
+ + + + + diff --git a/src/test/kotlin/com/wafflestudio/spring2025/RegistrationIntegrationTest.kt b/src/test/kotlin/com/wafflestudio/spring2025/RegistrationIntegrationTest.kt index 48b9cd3d..f2b1564a 100644 --- a/src/test/kotlin/com/wafflestudio/spring2025/RegistrationIntegrationTest.kt +++ b/src/test/kotlin/com/wafflestudio/spring2025/RegistrationIntegrationTest.kt @@ -6,7 +6,6 @@ import com.wafflestudio.spring2025.common.email.service.EmailService import com.wafflestudio.spring2025.domain.event.model.Event import com.wafflestudio.spring2025.domain.event.repository.EventRepository import com.wafflestudio.spring2025.domain.registration.dto.request.CreateRegistrationRequest -import com.wafflestudio.spring2025.domain.registration.dto.request.DeleteRegistrationRequest import com.wafflestudio.spring2025.domain.registration.dto.request.UpdateRegistrationStatusRequest import com.wafflestudio.spring2025.domain.registration.model.Registration import com.wafflestudio.spring2025.domain.registration.model.RegistrationStatus @@ -243,7 +242,6 @@ class RegistrationIntegrationTest .perform( delete("/api/registrations/$participantRegistrationId") .header("Authorization", "Bearer $hostToken") - .content(mapper.writeValueAsString(DeleteRegistrationRequest())) .contentType(MediaType.APPLICATION_JSON), ).andExpect(status().isForbidden) .andExpect(jsonPath("$.code").value("REGISTRATION_DELETE_UNAUTHORIZED")) @@ -252,7 +250,6 @@ class RegistrationIntegrationTest .perform( delete("/api/registrations/$participantRegistrationId") .header("Authorization", "Bearer $participantToken") - .content(mapper.writeValueAsString(DeleteRegistrationRequest())) .contentType(MediaType.APPLICATION_JSON), ).andExpect(status().isOk) @@ -276,7 +273,6 @@ class RegistrationIntegrationTest .perform( delete("/api/registrations/$confirmedRegistrationId") .header("Authorization", "Bearer $confirmedUserToken") - .content(mapper.writeValueAsString(DeleteRegistrationRequest())) .contentType(MediaType.APPLICATION_JSON), ).andExpect(status().isOk) @@ -347,7 +343,6 @@ class RegistrationIntegrationTest .perform( delete("/api/registrations/${registration.registrationPublicId}") .header("Authorization", "Bearer $participantToken") - .content(mapper.writeValueAsString(DeleteRegistrationRequest())) .contentType(MediaType.APPLICATION_JSON), ).andExpect(status().isBadRequest) .andExpect(jsonPath("$.code").value("NOT_WITHIN_REGISTRATION_WINDOW")) @@ -378,7 +373,6 @@ class RegistrationIntegrationTest .perform( delete("/api/registrations/${registration.registrationPublicId}") .header("Authorization", "Bearer $participantToken") - .content(mapper.writeValueAsString(DeleteRegistrationRequest())) .contentType(MediaType.APPLICATION_JSON), ).andExpect(status().isBadRequest) .andExpect(jsonPath("$.code").value("NOT_WITHIN_REGISTRATION_WINDOW"))