Skip to content

Commit 602aa99

Browse files
authored
Merge pull request #3 from ASAP-Lettering/ASAP-59
ASAP-59 feat: 소셜 로그인, 회원 가입 api 추가 및 테스트 코드 추가
2 parents 4d58a59 + 899d81d commit 602aa99

File tree

17 files changed

+391
-6
lines changed

17 files changed

+391
-6
lines changed

.deploy/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
FROM amazoncorretto:17-alpine-jdk
22

3-
ARG TARGET_JAR=/api/build/libs/api.jar
3+
ARG TARGET_JAR=/api/build/libs/app.jar
44

55
COPY ${TARGET_JAR} /app.jar
66

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Lettering Backend
2+
3+
4+
5+
## System Architecture
6+
7+
8+
### Overview
9+
10+
11+
```markdown
12+
.
13+
├── app/
14+
│ └── domain/
15+
│ ├── api
16+
│ ├── controller
17+
│ └── dto
18+
└── core
19+
```
File renamed without changes.
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
package com.asap.api
1+
package com.asap.app
22

33
import org.springframework.boot.SpringApplication
44
import org.springframework.boot.autoconfigure.SpringBootApplication
55

66
@SpringBootApplication
7-
class ApiApplication {
7+
class AppApplication {
88

99
}
1010

1111
fun main(args: Array<String>) {
12-
SpringApplication.run(ApiApplication::class.java, *args)
12+
SpringApplication.run(AppApplication::class.java, *args)
1313
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.asap.app.auth.api
2+
3+
import com.asap.app.auth.dto.SocialLoginRequest
4+
import com.asap.app.auth.dto.SocialLoginResponse
5+
import io.swagger.v3.oas.annotations.Operation
6+
import io.swagger.v3.oas.annotations.media.Content
7+
import io.swagger.v3.oas.annotations.media.Schema
8+
import io.swagger.v3.oas.annotations.responses.ApiResponse
9+
import io.swagger.v3.oas.annotations.responses.ApiResponses
10+
import io.swagger.v3.oas.annotations.tags.Tag
11+
import org.springframework.http.ResponseEntity
12+
import org.springframework.web.bind.annotation.PathVariable
13+
import org.springframework.web.bind.annotation.PostMapping
14+
import org.springframework.web.bind.annotation.RequestBody
15+
import org.springframework.web.bind.annotation.RequestMapping
16+
17+
@Tag(name = "Auth", description = "Auth API")
18+
@RequestMapping("/api/v1/auth")
19+
interface AuthApi {
20+
21+
@Operation(summary = "소셜 로그인")
22+
@PostMapping("/login/{provider}")
23+
@ApiResponses(
24+
value = [
25+
ApiResponse(
26+
responseCode = "200",
27+
description = "기존 회원 로그인 성공",
28+
content = [
29+
Content(
30+
mediaType = "application/json",
31+
schema = Schema(implementation = SocialLoginResponse.Success::class)
32+
)
33+
]
34+
),
35+
ApiResponse(
36+
responseCode = "401",
37+
description = "신규 회원 가입 필요",
38+
content = [
39+
Content(
40+
mediaType = "application/json",
41+
schema = Schema(implementation = SocialLoginResponse.NonRegistered::class)
42+
)
43+
]
44+
)
45+
]
46+
)
47+
fun socialLogin(
48+
@PathVariable provider: String,
49+
@RequestBody request: SocialLoginRequest
50+
): ResponseEntity<SocialLoginResponse>
51+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.asap.app.auth.controller
2+
3+
import com.asap.app.auth.api.AuthApi
4+
import com.asap.app.auth.dto.SocialLoginRequest
5+
import com.asap.app.auth.dto.SocialLoginResponse
6+
import org.springframework.http.HttpStatus
7+
import org.springframework.http.ResponseEntity
8+
import org.springframework.web.bind.annotation.RestController
9+
10+
@RestController
11+
class AuthController(
12+
) : AuthApi {
13+
14+
override fun socialLogin(
15+
provider: String,
16+
request: SocialLoginRequest
17+
): ResponseEntity<SocialLoginResponse> {
18+
when (request.accessToken) {
19+
"nonRegistered" -> return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
20+
.body(SocialLoginResponse.NonRegistered("registerToken"))
21+
22+
"registered" -> return ResponseEntity
23+
.ok(SocialLoginResponse.Success("accessToken", "refreshToken"))
24+
25+
else -> return ResponseEntity.badRequest().build()
26+
}
27+
}
28+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.asap.app.auth.dto
2+
3+
import io.swagger.v3.oas.annotations.media.Schema
4+
5+
@Schema(description = "소셜 로그인 요청")
6+
data class SocialLoginRequest(
7+
@Schema(description = "oauth access token")
8+
val accessToken: String,
9+
)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.asap.app.auth.dto
2+
3+
import io.swagger.v3.oas.annotations.media.Schema
4+
5+
@Schema(
6+
description = "소셜 로그인 응답",
7+
oneOf = [
8+
SocialLoginResponse.Success::class,
9+
SocialLoginResponse.NonRegistered::class
10+
]
11+
)
12+
sealed class SocialLoginResponse{
13+
14+
@Schema(description = "기존 회원 로그인 성공")
15+
data class Success(
16+
@Schema(description = "access token")
17+
val accessToken: String,
18+
@Schema(description = "refresh token")
19+
val refreshToken: String
20+
) : SocialLoginResponse()
21+
22+
23+
@Schema(description = "신규 회원 가입 필요")
24+
data class NonRegistered(
25+
@Schema(description = "register token, 회원가입을 위한 토큰")
26+
val registerToken: String
27+
) : SocialLoginResponse()
28+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.asap.app.user.api
2+
3+
import com.asap.app.user.dto.RegisterUserRequest
4+
import com.asap.app.user.dto.RegisterUserResponse
5+
import io.swagger.v3.oas.annotations.Operation
6+
import io.swagger.v3.oas.annotations.media.Content
7+
import io.swagger.v3.oas.annotations.media.Schema
8+
import io.swagger.v3.oas.annotations.responses.ApiResponse
9+
import io.swagger.v3.oas.annotations.responses.ApiResponses
10+
import io.swagger.v3.oas.annotations.tags.Tag
11+
import org.springframework.http.ResponseEntity
12+
import org.springframework.web.bind.annotation.PostMapping
13+
import org.springframework.web.bind.annotation.RequestBody
14+
import org.springframework.web.bind.annotation.RequestMapping
15+
16+
@Tag(name = "User", description = "User API")
17+
@RequestMapping("/api/v1/users")
18+
interface UserApi {
19+
20+
@Operation(summary = "회원 가입")
21+
@PostMapping()
22+
@ApiResponses(
23+
value = [
24+
ApiResponse(
25+
responseCode = "200",
26+
description = "회원 가입 성공",
27+
content = [
28+
Content(
29+
mediaType = "application/json",
30+
schema = Schema(implementation = RegisterUserResponse::class)
31+
)
32+
]
33+
)
34+
]
35+
)
36+
fun registerUser(
37+
@RequestBody request: RegisterUserRequest
38+
): ResponseEntity<RegisterUserResponse>
39+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.asap.app.user.controller
2+
3+
import com.asap.app.user.api.UserApi
4+
import com.asap.app.user.dto.RegisterUserRequest
5+
import com.asap.app.user.dto.RegisterUserResponse
6+
import org.springframework.http.ResponseEntity
7+
import org.springframework.web.bind.annotation.RestController
8+
9+
@RestController
10+
class UserController(
11+
12+
) : UserApi{
13+
14+
override fun registerUser(request: RegisterUserRequest): ResponseEntity<RegisterUserResponse> {
15+
when(request.registerToken){
16+
"register" -> return ResponseEntity.ok(RegisterUserResponse("accessToken", "refreshToken"))
17+
else -> return ResponseEntity.badRequest().build()
18+
}
19+
}
20+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.asap.app.user.dto
2+
3+
import io.swagger.v3.oas.annotations.media.Schema
4+
import java.time.LocalDate
5+
6+
@Schema(description = "회원 가입 요청")
7+
data class RegisterUserRequest(
8+
@Schema(description = "register_token, 소셜 로그인이로부터 전달받은 토큰")
9+
val registerToken: String,
10+
@Schema(description = "서비스 이용약관 동의")
11+
val servicePermission: Boolean,
12+
@Schema(description = "개인정보 수집 및 이용 동의")
13+
val privatePermission: Boolean,
14+
@Schema(description = "마케팅 정보 수신 동의")
15+
val marketingPermission: Boolean,
16+
@Schema(description = "생년 월일, yyyy-MM-dd, 값이 안넘어올 수 있음")
17+
val birthday: LocalDate?
18+
) {
19+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.asap.app.user.dto
2+
3+
import io.swagger.v3.oas.annotations.media.Schema
4+
5+
@Schema(description = "회원 가입 응답")
6+
data class RegisterUserResponse(
7+
@Schema(description = "access token")
8+
val accessToken: String,
9+
@Schema(description = "refresh token")
10+
val refreshToken: String
11+
) {
12+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package com.asap.app.auth.controller
2+
3+
import com.asap.app.auth.dto.SocialLoginRequest
4+
import com.fasterxml.jackson.databind.ObjectMapper
5+
import org.junit.jupiter.api.Test
6+
import org.springframework.beans.factory.annotation.Autowired
7+
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
8+
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
9+
import org.springframework.http.MediaType
10+
import org.springframework.test.web.servlet.MockMvc
11+
import org.springframework.test.web.servlet.post
12+
13+
@WebMvcTest(AuthController::class)
14+
@AutoConfigureMockMvc
15+
class AuthControllerTest {
16+
17+
@Autowired
18+
private lateinit var mockMvc: MockMvc
19+
20+
private val objectMapper: ObjectMapper = ObjectMapper()
21+
22+
23+
@Test
24+
fun socialLoginSuccessTest(){
25+
// given
26+
val request = SocialLoginRequest("registered")
27+
// when
28+
val response = mockMvc.post("/api/v1/auth/login/{provider}", "kakao") {
29+
contentType = MediaType.APPLICATION_JSON
30+
content = objectMapper.writeValueAsString(request)
31+
}
32+
33+
// then
34+
response.andExpect {
35+
status { isOk() }
36+
jsonPath("$.accessToken") {
37+
exists()
38+
isString()
39+
isNotEmpty()
40+
}
41+
jsonPath("$.refreshToken") {
42+
exists()
43+
isString()
44+
isNotEmpty()
45+
}
46+
}
47+
}
48+
49+
@Test
50+
fun socialLoginNonRegisteredTest(){
51+
// given
52+
val request = SocialLoginRequest("nonRegistered")
53+
// when
54+
val response = mockMvc.post("/api/v1/auth/login/{provider}", "kakao") {
55+
contentType = MediaType.APPLICATION_JSON
56+
content = objectMapper.writeValueAsString(request)
57+
}
58+
59+
// then
60+
response.andExpect {
61+
status { isUnauthorized() }
62+
jsonPath("$.registerToken") {
63+
exists()
64+
isString()
65+
isNotEmpty()
66+
}
67+
}
68+
}
69+
70+
71+
@Test
72+
fun socialLoginBadRequestTest(){
73+
// given
74+
val request = SocialLoginRequest("invalid")
75+
// when
76+
val response = mockMvc.post("/api/v1/auth/login/{provider}", "kakao") {
77+
contentType = MediaType.APPLICATION_JSON
78+
content = objectMapper.writeValueAsString(request)
79+
}
80+
81+
// then
82+
response.andExpect {
83+
status { isBadRequest() }
84+
}
85+
}
86+
87+
88+
}

0 commit comments

Comments
 (0)