Skip to content

Commit a95c222

Browse files
[#73]; docs: swagger api 문서화 추가
[#73]; docs: swagger api 문서화 추가
2 parents 0a5486d + 26fce2f commit a95c222

19 files changed

Lines changed: 274 additions & 9 deletions

File tree

build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ dependencies {
5353
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
5454
testImplementation("org.assertj:assertj-core")
5555
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
56+
57+
// swagger
58+
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.13")
5659
}
5760

5861
kotlin {

src/main/kotlin/com/yourssu/scouter/ats/application/domain/applicant/ApplicantController.kt

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,31 @@ package com.yourssu.scouter.ats.application.domain.applicant
22

33
import com.yourssu.scouter.ats.business.domain.applicant.ApplicantDto
44
import com.yourssu.scouter.ats.business.domain.applicant.ApplicantService
5+
import io.swagger.v3.oas.annotations.Operation
6+
import io.swagger.v3.oas.annotations.headers.Header
7+
import io.swagger.v3.oas.annotations.media.ArraySchema
8+
import io.swagger.v3.oas.annotations.media.Content
9+
import io.swagger.v3.oas.annotations.media.ExampleObject
10+
import io.swagger.v3.oas.annotations.media.Schema
11+
import io.swagger.v3.oas.annotations.responses.ApiResponse
12+
import io.swagger.v3.oas.annotations.tags.Tag
513
import jakarta.validation.Valid
6-
import java.net.URI
714
import org.springframework.http.ResponseEntity
8-
import org.springframework.web.bind.annotation.DeleteMapping
9-
import org.springframework.web.bind.annotation.GetMapping
10-
import org.springframework.web.bind.annotation.PatchMapping
11-
import org.springframework.web.bind.annotation.PathVariable
12-
import org.springframework.web.bind.annotation.PostMapping
13-
import org.springframework.web.bind.annotation.RequestBody
14-
import org.springframework.web.bind.annotation.RequestParam
15-
import org.springframework.web.bind.annotation.RestController
15+
import org.springframework.web.bind.annotation.*
16+
import java.net.URI
1617

18+
@Tag(name = "리크루팅 지원자")
1719
@RestController
1820
class ApplicantController(
1921
private val applicantService: ApplicantService,
2022
) {
2123

24+
@Operation(summary = "지원자 추가 API")
25+
@ApiResponse(
26+
description = "CREATED", responseCode = "201", headers = [
27+
Header(name = "Location", description = "/applicants/{applicantId}")
28+
]
29+
)
2230
@PostMapping("/applicants")
2331
fun create(
2432
@RequestBody @Valid request: CreateApplicantRequest,
@@ -30,6 +38,10 @@ class ApplicantController(
3038
}
3139

3240

41+
@Operation(
42+
summary = "지원자 목록 조회",
43+
description = "지원자 목록을 검색, 필터링을 통해 얻습니다. 필터링에 필요한 정보는 각 api에서 얻어야 합니다."
44+
)
3345
@GetMapping("/applicants")
3446
fun readAll(
3547
@RequestParam(required = false) name: String?,
@@ -47,6 +59,7 @@ class ApplicantController(
4759
return ResponseEntity.ok(responses)
4860
}
4961

62+
@Operation(summary = "지원자 정보 수정", description = "변경할 지원자 정보의 값을 보냅니다. 변경되지 않은 값은 보내면 안 됩니다.")
5063
@PatchMapping("/applicants/{applicantId}")
5164
fun updateById(
5265
@PathVariable applicantId: Long,
@@ -58,6 +71,7 @@ class ApplicantController(
5871
return ResponseEntity.ok().build()
5972
}
6073

74+
@Operation(summary = "지원자 단일 조회", description = "지원자 목록 조회에서 얻은 applicantId를 이용해 지원자의 세부정보를 확인합니다.")
6175
@GetMapping("/applicants/{applicantId}")
6276
fun readById(
6377
@PathVariable applicantId: Long,
@@ -68,6 +82,8 @@ class ApplicantController(
6882
return ResponseEntity.ok(response)
6983
}
7084

85+
@Operation(summary = "지원자 삭제", description = "지원자 목록 조회에서 얻은 applicantId를 이용해 지원자를 삭제합니다.")
86+
@ApiResponse(description = "NO_CONTENT", responseCode = "204")
7187
@DeleteMapping("/applicants/{applicantId}")
7288
fun deleteById(
7389
@PathVariable applicantId: Long,
@@ -77,6 +93,15 @@ class ApplicantController(
7793
return ResponseEntity.noContent().build()
7894
}
7995

96+
@Operation(summary = "지원자 상태 목록 조회", description = "전체 지원자의 가능한 상태 목록을 확인합니다. 현재 지원자와는 관련이 없습니다.")
97+
@ApiResponse(
98+
description = "OK", responseCode = "200", content = [Content(
99+
mediaType = "application/json",
100+
array = ArraySchema(schema = Schema(implementation = String::class)),
101+
examples =
102+
[ExampleObject(value = "[ \"심사 진행 중\", \"서류 불합\", \"면접 불합\", \"인큐베이팅 불합\", \"최종 합격\" ]")]
103+
)]
104+
)
80105
@GetMapping("/applicants/states")
81106
fun readAllMemberStates(): ResponseEntity<List<String>> {
82107
val states: List<String> = applicantService.readAllStates()

src/main/kotlin/com/yourssu/scouter/ats/application/domain/applicant/ApplicantSyncController.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,31 @@ import com.yourssu.scouter.ats.business.domain.applicant.ApplicantSyncResult
44
import com.yourssu.scouter.ats.business.domain.applicant.ApplicantSyncService
55
import com.yourssu.scouter.common.application.support.authentication.AuthUser
66
import com.yourssu.scouter.common.application.support.authentication.AuthUserInfo
7+
import io.swagger.v3.oas.annotations.Operation
8+
import io.swagger.v3.oas.annotations.headers.Header
9+
import io.swagger.v3.oas.annotations.responses.ApiResponse
10+
import io.swagger.v3.oas.annotations.tags.Tag
711
import java.time.LocalDateTime
812
import org.springframework.http.ResponseEntity
913
import org.springframework.web.bind.annotation.GetMapping
1014
import org.springframework.web.bind.annotation.PathVariable
1115
import org.springframework.web.bind.annotation.PostMapping
1216
import org.springframework.web.bind.annotation.RestController
1317

18+
@Tag(name = "리크루팅 지원자")
19+
@Tag(name = "지원자 동기화 API")
1420
@RestController
1521
class ApplicantSyncController(
1622
private val applicantSyncService: ApplicantSyncService,
1723
) {
1824

25+
@Operation(
26+
summary = "구글폼 응답을 지원자 목록에 업데이트",
27+
description = "현재 로그인 되어있는 사용자의 계정을 이용해 지원자의 구글폼 응답을 동기화 합니다."
28+
)
29+
@ApiResponse(description = "OK", responseCode = "200", headers = [
30+
Header(name = "Location", description = "/applicants")
31+
]) // 노션 api 명세랑 안맞는 부분 api 명세 상 201 <-> 실제는 200
1932
@PostMapping("/applicants/include-from-forms")
2033
fun includeFromForms(
2134
@AuthUser authUserInfo: AuthUserInfo,
@@ -37,6 +50,7 @@ class ApplicantSyncController(
3750
return ResponseEntity.ok(response)
3851
}
3952

53+
@Operation(summary = "마지막 동기화 시간 조회")
4054
@GetMapping("/applicants/lastUpdatedTime")
4155
fun lastSyncTime(): ResponseEntity<LastApplicantSyncTimeResponse> {
4256
val lastSyncTime: LocalDateTime? = applicantSyncService.readLastUpdatedTime()

src/main/kotlin/com/yourssu/scouter/common/application/domain/authentication/AuthenticationController.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ import com.yourssu.scouter.common.business.domain.authentication.OAuth2Service
66
import com.yourssu.scouter.common.business.domain.authentication.TokenDto
77
import com.yourssu.scouter.common.implement.domain.authentication.OAuth2Type
88
import com.yourssu.scouter.common.implement.domain.authentication.TokenType
9+
import io.swagger.v3.oas.annotations.Operation
10+
import io.swagger.v3.oas.annotations.Parameter
11+
import io.swagger.v3.oas.annotations.responses.ApiResponse
12+
import io.swagger.v3.oas.annotations.security.SecurityRequirements
13+
import io.swagger.v3.oas.annotations.tags.Tag
914
import jakarta.servlet.http.HttpServletRequest
1015
import jakarta.validation.Valid
1116
import java.time.LocalDateTime
@@ -19,6 +24,7 @@ import org.springframework.web.bind.annotation.RequestHeader
1924
import org.springframework.web.bind.annotation.RestController
2025
import org.slf4j.LoggerFactory
2126

27+
@Tag(name = "인증/인가")
2228
@RestController
2329
class AuthenticationController(
2430
private val authenticationService: AuthenticationService,
@@ -27,6 +33,9 @@ class AuthenticationController(
2733

2834
private val logger = LoggerFactory.getLogger(AuthenticationController::class.java)
2935

36+
@Tag(name = "OAUTH2")
37+
@Operation(summary = "회원가입/로그인", description = "/oauth2/{oauth2Type}에서 얻은 code를 이용해 회원가입/로그인을 진행합니다.")
38+
@SecurityRequirements // 로그인을 필요로 하지 않는 곳은 전역 인증을 사용하지않도록 초기화
3039
@PostMapping("oauth2/login/{oauth2Type}")
3140
fun login(
3241
@PathVariable oauth2Type: OAuth2Type,
@@ -49,8 +58,11 @@ class AuthenticationController(
4958
return ResponseEntity.ok(response)
5059
}
5160

61+
@Operation(summary = "로그아웃")
62+
@ApiResponse(description = "NO_CONTENT", responseCode = "204")
5263
@PostMapping("/logout")
5364
fun logout(
65+
@Parameter(hidden = true)
5466
@RequestHeader(HttpHeaders.AUTHORIZATION) accessToken: String,
5567
@RequestBody @Valid request: LogoutRequest,
5668
): ResponseEntity<Unit> {
@@ -59,8 +71,11 @@ class AuthenticationController(
5971
return ResponseEntity.noContent().build()
6072
}
6173

74+
@SecurityRequirements // 로그인을 필요로 하지 않는 곳은 전역 인증을 사용하지않도록 초기화
75+
@Operation(summary = "AccessToken 유효성 검사")
6276
@GetMapping("/validate-token")
6377
fun validateToken(
78+
@Parameter(hidden = true)
6479
@RequestHeader(HttpHeaders.AUTHORIZATION) accessToken: String,
6580
): ResponseEntity<ValidateTokenResponse> {
6681
val validated: Boolean = authenticationService.isValidToken(TokenType.ACCESS, accessToken)
@@ -69,9 +84,12 @@ class AuthenticationController(
6984
return ResponseEntity.ok(response)
7085
}
7186

87+
@SecurityRequirements // 로그인을 필요로 하지 않는 곳은 전역 인증을 사용하지않도록 초기화
88+
@Operation(summary = "토큰 재발급")
7289
@PostMapping("/refresh-token")
7390
fun refreshToken(
7491
@RequestBody @Valid request: TokenRefreshRequest,
92+
@Parameter(hidden = true)
7593
@RequestHeader(HttpHeaders.AUTHORIZATION, required = false) bearerRefreshHeader: String?,
7694
): ResponseEntity<TokenRefreshResponse> {
7795
logger.info("[Auth] POST /refresh-token | hasAuthHeader={} | bodyPresent={}", !bearerRefreshHeader.isNullOrBlank(), !request.refreshToken.isNullOrBlank())
@@ -83,8 +101,11 @@ class AuthenticationController(
83101
return ResponseEntity.ok(response)
84102
}
85103

104+
@Operation(summary = "회원 탈퇴")
105+
@ApiResponse(description = "NO_CONTENT", responseCode = "204")
86106
@PostMapping("/withdrawal")
87107
fun withdraw(
108+
@Parameter(hidden = true)
88109
@RequestHeader(HttpHeaders.AUTHORIZATION) accessToken: String,
89110
@RequestBody @Valid request: WithdrawalRequest,
90111
): ResponseEntity<Unit> {

src/main/kotlin/com/yourssu/scouter/common/application/domain/authentication/OAuth2Controller.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ package com.yourssu.scouter.common.application.domain.authentication
22

33
import com.yourssu.scouter.common.business.domain.authentication.OAuth2Service
44
import com.yourssu.scouter.common.implement.domain.authentication.OAuth2Type
5+
import io.swagger.v3.oas.annotations.Operation
6+
import io.swagger.v3.oas.annotations.security.SecurityRequirements
7+
import io.swagger.v3.oas.annotations.tags.Tag
58
import jakarta.servlet.http.HttpServletRequest
69
import jakarta.servlet.http.HttpServletResponse
710
import org.springframework.http.HttpHeaders
@@ -12,11 +15,15 @@ import org.springframework.web.bind.annotation.RequestParam
1215
import org.springframework.web.bind.annotation.RestController
1316
import org.springframework.web.util.UriComponentsBuilder
1417

18+
@Tag(name = "인증/인가")
19+
@Tag(name = "OAUTH2")
1520
@RestController
1621
class OAuth2Controller(
1722
private val oauth2Service: OAuth2Service
1823
) {
1924

25+
@Operation(summary = "OAuth2 로그인 페이지 리다이렉트", description = "여기에서는 리다이렉트가 되지 않습니다. Authorize 이용.")
26+
@SecurityRequirements // 로그인을 필요로 하지 않는 곳은 전역 인증을 사용하지않도록 초기화
2027
@GetMapping("/oauth2/{oauth2Type}")
2128
fun redirectAuthCodeRequestUrl(
2229
@PathVariable oauth2Type: OAuth2Type,
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.yourssu.scouter.common.application.domain.authentication
2+
3+
import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy
4+
import com.fasterxml.jackson.databind.annotation.JsonNaming
5+
import com.yourssu.scouter.common.business.domain.authentication.LoginResult
6+
import com.yourssu.scouter.common.business.domain.authentication.OAuth2Service
7+
import com.yourssu.scouter.common.implement.domain.authentication.OAuth2Type
8+
import io.swagger.v3.oas.annotations.Hidden
9+
import org.springframework.beans.factory.annotation.Value
10+
import org.springframework.http.ResponseEntity
11+
import org.springframework.web.bind.annotation.PostMapping
12+
import org.springframework.web.bind.annotation.RequestParam
13+
import org.springframework.web.bind.annotation.RestController
14+
15+
@Hidden
16+
@RestController
17+
class SwaggerOAuth2Controller(private val oauth2Service: OAuth2Service) {
18+
19+
@Value("\${springdoc.swagger-ui.oauth2-redirect-url}")
20+
private lateinit var redirectUri: String
21+
22+
@PostMapping("/oauth2/swagger/callback")
23+
fun callback(
24+
@RequestParam code: String,
25+
@RequestParam(required = false) error: String?
26+
): ResponseEntity<AuthResponse> {
27+
if (error != null) throw RuntimeException(
28+
"OAuth2 authorization code flow failed: $error"
29+
)
30+
31+
val referer = redirectUri.substringBefore("/")
32+
val loginResult: LoginResult = oauth2Service.login(
33+
oauth2Type = OAuth2Type.GOOGLE, // 인증, 인가 로직을 시험하기 위함이 아니기 때문에 google로 고정해뒀습니다.
34+
oauth2AuthorizationCode = code,
35+
referer = referer,
36+
redirectUriOverride = redirectUri,
37+
)
38+
val accessToken = loginResult.accessToken.substringAfter(" ")
39+
return ResponseEntity.ok(AuthResponse(accessToken))
40+
}
41+
}
42+
43+
@JsonNaming(SnakeCaseStrategy::class)
44+
data class AuthResponse(val accessToken: String)
45+
46+

src/main/kotlin/com/yourssu/scouter/common/application/domain/college/CollegeController.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,19 @@ package com.yourssu.scouter.common.application.domain.college
22

33
import com.yourssu.scouter.common.business.domain.college.CollegeService
44
import com.yourssu.scouter.common.business.domain.college.ReadCollegesResult
5+
import io.swagger.v3.oas.annotations.Operation
6+
import io.swagger.v3.oas.annotations.tags.Tag
57
import org.springframework.http.ResponseEntity
68
import org.springframework.web.bind.annotation.GetMapping
79
import org.springframework.web.bind.annotation.RestController
810

11+
@Tag(name = "학과/학기/단과대")
912
@RestController
1013
class CollegeController(
1114
private val collegeService: CollegeService,
1215
) {
1316

17+
@Operation(summary = "전체 단과대 조회", description = "전체 단과대의 정보를 조회합니다.")
1418
@GetMapping("/colleges")
1519
fun readAll(): ResponseEntity<List<ReadCollegeResponse>> {
1620
val result: ReadCollegesResult = collegeService.readAll()

src/main/kotlin/com/yourssu/scouter/common/application/domain/department/DepartmentController.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,20 @@ package com.yourssu.scouter.common.application.domain.department
22

33
import com.yourssu.scouter.common.business.domain.department.DepartmentService
44
import com.yourssu.scouter.common.business.domain.department.ReadDepartmentsResult
5+
import io.swagger.v3.oas.annotations.Operation
6+
import io.swagger.v3.oas.annotations.tags.Tag
57
import org.springframework.http.ResponseEntity
68
import org.springframework.web.bind.annotation.GetMapping
79
import org.springframework.web.bind.annotation.PathVariable
810
import org.springframework.web.bind.annotation.RestController
911

12+
@Tag(name = "학과/학기/단과대")
1013
@RestController
1114
class DepartmentController(
1215
private val departmentService: DepartmentService,
1316
) {
1417

18+
@Operation(summary = "전체 학과 조회", description = "전체 학과 정보를 조회합니다.")
1519
@GetMapping("/departments")
1620
fun readAll(): ResponseEntity<List<ReadDepartmentsResponse>> {
1721
val result: ReadDepartmentsResult = departmentService.readAll()
@@ -20,6 +24,7 @@ class DepartmentController(
2024
return ResponseEntity.ok(response)
2125
}
2226

27+
@Operation(summary = "단과대 소속 학과 조회", description = "특정 단과대에 소속된 전체 학과 정보를 조회합니다.")
2328
@GetMapping("/colleges/{collegeId}/departments")
2429
fun readAllByCollegeId(
2530
@PathVariable collegeId: Long,

src/main/kotlin/com/yourssu/scouter/common/application/domain/division/DivisionController.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,21 @@ package com.yourssu.scouter.common.application.domain.division
22

33
import com.yourssu.scouter.common.business.domain.division.DivisionService
44
import com.yourssu.scouter.common.business.domain.division.ReadDivisionsResult
5+
import io.swagger.v3.oas.annotations.Operation
6+
import io.swagger.v3.oas.annotations.media.Schema
7+
import io.swagger.v3.oas.annotations.responses.ApiResponse
8+
import io.swagger.v3.oas.annotations.tags.Tag
59
import org.springframework.http.ResponseEntity
610
import org.springframework.web.bind.annotation.GetMapping
711
import org.springframework.web.bind.annotation.RestController
812

13+
@Tag(name = "구분/파트")
914
@RestController
1015
class DivisionController(
1116
private val divisionService: DivisionService,
1217
) {
1318

19+
@Operation(summary = "멤버 구분 목록 조회", description = "운영, 개발, 디자인 등 각 구분의 목록을 조회합니다.")
1420
@GetMapping("/divisions")
1521
fun readAll(): ResponseEntity<List<ReadDivisionResponse>> {
1622
val result: ReadDivisionsResult = divisionService.readAll()

src/main/kotlin/com/yourssu/scouter/common/application/domain/mail/MailReservationController.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,23 @@ import com.yourssu.scouter.common.application.support.authentication.AuthUser
44
import com.yourssu.scouter.common.application.support.authentication.AuthUserInfo
55
import com.yourssu.scouter.common.business.domain.mail.MailService
66
import com.yourssu.scouter.common.implement.domain.mail.MailReserveCommand
7+
import io.swagger.v3.oas.annotations.Operation
8+
import io.swagger.v3.oas.annotations.tags.Tag
79
import org.springframework.http.ResponseEntity
810
import org.springframework.web.bind.annotation.PostMapping
911
import org.springframework.web.bind.annotation.RequestMapping
1012
import org.springframework.web.bind.annotation.RequestPart
1113
import org.springframework.web.bind.annotation.RestController
1214
import org.springframework.web.multipart.MultipartFile
1315

16+
@Tag(name = "메일")
1417
@RestController
1518
@RequestMapping("/api/mails/reservation")
1619
class MailReservationController(
1720
private val mailService: MailService
1821
) {
1922

23+
@Operation(summary = "메일 전송 예약")
2024
@PostMapping
2125
fun reserveMail(
2226
@AuthUser authUserInfo: AuthUserInfo,

0 commit comments

Comments
 (0)