Skip to content

Commit 0104908

Browse files
authored
Feat: 회원 정보 조회/수정 API 구현 (#81) (#92)
* Rename: UserService -> AuthService # Conflicts: # src/main/java/com/back/domain/user/controller/AuthController.java # src/test/java/com/back/domain/user/service/AuthServiceTest.java * Ref: UserResponse 개선 * Feat: 회원 정보 조회 API 구현 * Ref: rebase 후 추가 수정 * Feat: 회원 정보 수정 API 구현 * Docs: Swagger 문서 작성 * Test: 서비스 테스트 작성 * Test: 컨트롤러 테스트 작성 * Test: rebase 후 관련 테스트 코드 추가 수정
1 parent 5c7222a commit 0104908

File tree

16 files changed

+1547
-522
lines changed

16 files changed

+1547
-522
lines changed

src/main/java/com/back/domain/user/controller/AuthController.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import com.back.domain.user.dto.LoginResponse;
55
import com.back.domain.user.dto.UserRegisterRequest;
66
import com.back.domain.user.dto.UserResponse;
7-
import com.back.domain.user.service.UserService;
7+
import com.back.domain.user.service.AuthService;
88
import com.back.global.common.dto.RsData;
99
import jakarta.servlet.http.HttpServletRequest;
1010
import jakarta.servlet.http.HttpServletResponse;
@@ -23,14 +23,14 @@
2323
@RequestMapping("/api/auth")
2424
@RequiredArgsConstructor
2525
public class AuthController implements AuthControllerDocs {
26-
private final UserService userService;
26+
private final AuthService authService;
2727

2828
// 회원가입
2929
@PostMapping("/register")
3030
public ResponseEntity<RsData<UserResponse>> register(
3131
@Valid @RequestBody UserRegisterRequest request
3232
) {
33-
UserResponse response = userService.register(request);
33+
UserResponse response = authService.register(request);
3434
return ResponseEntity
3535
.status(HttpStatus.CREATED)
3636
.body(RsData.success(
@@ -45,7 +45,7 @@ public ResponseEntity<RsData<LoginResponse>> login(
4545
@Valid @RequestBody LoginRequest request,
4646
HttpServletResponse response
4747
) {
48-
LoginResponse loginResponse = userService.login(request, response);
48+
LoginResponse loginResponse = authService.login(request, response);
4949
return ResponseEntity
5050
.ok(RsData.success(
5151
"로그인에 성공했습니다.",
@@ -59,7 +59,7 @@ public ResponseEntity<RsData<Void>> logout(
5959
HttpServletRequest request,
6060
HttpServletResponse response
6161
) {
62-
userService.logout(request, response);
62+
authService.logout(request, response);
6363
return ResponseEntity
6464
.ok(RsData.success(
6565
"로그아웃 되었습니다.",
@@ -73,7 +73,7 @@ public ResponseEntity<RsData<Map<String, String>>> refreshToken(
7373
HttpServletRequest request,
7474
HttpServletResponse response
7575
) {
76-
String newAccessToken = userService.refreshToken(request, response);
76+
String newAccessToken = authService.refreshToken(request, response);
7777
return ResponseEntity.ok(RsData.success(
7878
"토큰이 재발급되었습니다.",
7979
Map.of("accessToken", newAccessToken)

src/main/java/com/back/domain/user/controller/AuthControllerDocs.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,8 @@ ResponseEntity<RsData<UserResponse>> register(
131131

132132
@Operation(
133133
summary = "로그인",
134-
description = "username + password로 로그인합니다. "
134+
description = "username + password로 로그인합니다. " +
135+
"로그인 성공 시 Access Token을 응답 본문에, Refresh Token을 HttpOnly 쿠키에 담아 반환합니다. "
135136
)
136137
@ApiResponses({
137138
@ApiResponse(
@@ -311,7 +312,7 @@ ResponseEntity<RsData<Void>> logout(
311312
@Operation(
312313
summary = "토큰 재발급",
313314
description = "만료된 Access Token 대신 Refresh Token을 이용해 새로운 Access Token을 발급받습니다. " +
314-
"Refresh Token은 HttpOnly 쿠키에서 추출하며, 재발급 성공 시 응답 헤더와 본문에 새로운 Access Token을 담습니다."
315+
"Refresh Token은 HttpOnly 쿠키에서 추출하며, 재발급 성공 시 본문에 새로운 Access Token을 담습니다."
315316
)
316317
@ApiResponses({
317318
@ApiResponse(
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.back.domain.user.controller;
2+
3+
import com.back.domain.user.dto.UpdateUserProfileRequest;
4+
import com.back.domain.user.dto.UserDetailResponse;
5+
import com.back.domain.user.service.UserService;
6+
import com.back.global.common.dto.RsData;
7+
import com.back.global.security.CustomUserDetails;
8+
import jakarta.validation.Valid;
9+
import lombok.RequiredArgsConstructor;
10+
import org.springframework.http.ResponseEntity;
11+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
12+
import org.springframework.web.bind.annotation.*;
13+
14+
@RestController
15+
@RequestMapping("/api/users")
16+
@RequiredArgsConstructor
17+
public class UserController implements UserControllerDocs {
18+
private final UserService userService;
19+
20+
@GetMapping("/me")
21+
public ResponseEntity<RsData<UserDetailResponse>> getMyInfo (
22+
@AuthenticationPrincipal CustomUserDetails user
23+
) {
24+
UserDetailResponse userDetail = userService.getUserInfo(user.getUserId());
25+
return ResponseEntity
26+
.ok(RsData.success(
27+
"회원 정보를 조회했습니다.",
28+
userDetail
29+
));
30+
}
31+
32+
@PatchMapping("/me")
33+
public ResponseEntity<RsData<UserDetailResponse>> updateMyProfile(
34+
@AuthenticationPrincipal CustomUserDetails user,
35+
@Valid @RequestBody UpdateUserProfileRequest request
36+
) {
37+
UserDetailResponse updated = userService.updateUserProfile(user.getUserId(), request);
38+
return ResponseEntity
39+
.ok(RsData.success(
40+
"회원 정보를 수정했습니다.",
41+
updated
42+
)
43+
);
44+
}
45+
}
Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
package com.back.domain.user.controller;
2+
3+
import com.back.domain.user.dto.UpdateUserProfileRequest;
4+
import com.back.domain.user.dto.UserDetailResponse;
5+
import com.back.global.common.dto.RsData;
6+
import com.back.global.security.CustomUserDetails;
7+
import io.swagger.v3.oas.annotations.Operation;
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.responses.ApiResponse;
11+
import io.swagger.v3.oas.annotations.responses.ApiResponses;
12+
import io.swagger.v3.oas.annotations.tags.Tag;
13+
import jakarta.validation.Valid;
14+
import org.springframework.http.ResponseEntity;
15+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
16+
import org.springframework.web.bind.annotation.RequestBody;
17+
18+
@Tag(name = "User API", description = "사용자 정보 관련 API")
19+
public interface UserControllerDocs {
20+
21+
@Operation(
22+
summary = "내 정보 조회",
23+
description = "로그인한 사용자의 계정 및 프로필 정보를 조회합니다."
24+
)
25+
@ApiResponses({
26+
@ApiResponse(
27+
responseCode = "200",
28+
description = "회원 정보 조회 성공",
29+
content = @Content(
30+
mediaType = "application/json",
31+
examples = @ExampleObject(value = """
32+
{
33+
"success": true,
34+
"code": "SUCCESS_200",
35+
"message": "회원 정보를 조회했습니다.",
36+
"data": {
37+
"userId": 1,
38+
"username": "testuser",
39+
"email": "[email protected]",
40+
"role": "USER",
41+
"status": "ACTIVE",
42+
"provider": "LOCAL",
43+
"providerId": null,
44+
"profile": {
45+
"nickname": "홍길동",
46+
"profileImageUrl": "https://cdn.example.com/profile/1.png",
47+
"bio": "안녕하세요! 열심히 배우고 있습니다.",
48+
"birthDate": "2000-01-01",
49+
"point": 1500
50+
},
51+
"createdAt": "2025-09-22T10:00:00",
52+
"updatedAt": "2025-09-22T12:30:00"
53+
}
54+
}
55+
""")
56+
)
57+
),
58+
@ApiResponse(
59+
responseCode = "403",
60+
description = "정지된 계정",
61+
content = @Content(
62+
mediaType = "application/json",
63+
examples = @ExampleObject(value = """
64+
{
65+
"success": false,
66+
"code": "USER_008",
67+
"message": "정지된 계정입니다. 관리자에게 문의하세요.",
68+
"data": null
69+
}
70+
""")
71+
)
72+
),
73+
@ApiResponse(
74+
responseCode = "410",
75+
description = "탈퇴한 계정",
76+
content = @Content(
77+
mediaType = "application/json",
78+
examples = @ExampleObject(value = """
79+
{
80+
"success": false,
81+
"code": "USER_009",
82+
"message": "탈퇴한 계정입니다.",
83+
"data": null
84+
}
85+
""")
86+
)
87+
),
88+
@ApiResponse(
89+
responseCode = "401",
90+
description = "인증 실패 (토큰 없음/만료/잘못됨)",
91+
content = @Content(
92+
mediaType = "application/json",
93+
examples = @ExampleObject(value = """
94+
{
95+
"success": false,
96+
"code": "AUTH_401",
97+
"message": "인증이 필요합니다.",
98+
"data": null
99+
}
100+
""")
101+
)
102+
),
103+
@ApiResponse(
104+
responseCode = "404",
105+
description = "존재하지 않는 사용자",
106+
content = @Content(
107+
mediaType = "application/json",
108+
examples = @ExampleObject(value = """
109+
{
110+
"success": false,
111+
"code": "USER_001",
112+
"message": "존재하지 않는 사용자입니다.",
113+
"data": null
114+
}
115+
""")
116+
)
117+
),
118+
@ApiResponse(
119+
responseCode = "500",
120+
description = "서버 내부 오류",
121+
content = @Content(
122+
mediaType = "application/json",
123+
examples = @ExampleObject(value = """
124+
{
125+
"success": false,
126+
"code": "COMMON_500",
127+
"message": "서버 오류가 발생했습니다.",
128+
"data": null
129+
}
130+
""")
131+
)
132+
)
133+
})
134+
ResponseEntity<RsData<UserDetailResponse>> getMyInfo(
135+
@AuthenticationPrincipal CustomUserDetails user
136+
);
137+
138+
@Operation(
139+
summary = "내 정보 수정",
140+
description = "로그인한 사용자의 프로필 정보를 수정합니다."
141+
)
142+
@ApiResponses({
143+
@ApiResponse(
144+
responseCode = "200",
145+
description = "회원 정보 수정 성공",
146+
content = @Content(
147+
mediaType = "application/json",
148+
examples = @ExampleObject(value = """
149+
{
150+
"success": true,
151+
"code": "SUCCESS_200",
152+
"message": "회원 정보를 수정했습니다.",
153+
"data": {
154+
"userId": 1,
155+
"username": "testuser",
156+
"email": "[email protected]",
157+
"role": "USER",
158+
"status": "ACTIVE",
159+
"provider": "LOCAL",
160+
"providerId": null,
161+
"profile": {
162+
"nickname": "새로운닉네임",
163+
"profileImageUrl": "https://cdn.example.com/profile/new.png",
164+
"bio": "저는 개발 공부 중입니다!",
165+
"birthDate": "2000-05-10",
166+
"point": 1500
167+
},
168+
"createdAt": "2025-09-22T10:00:00",
169+
"updatedAt": "2025-09-22T12:40:00"
170+
}
171+
}
172+
""")
173+
)
174+
),
175+
@ApiResponse(
176+
responseCode = "409",
177+
description = "중복된 닉네임",
178+
content = @Content(
179+
mediaType = "application/json",
180+
examples = @ExampleObject(value = """
181+
{
182+
"success": false,
183+
"code": "USER_004",
184+
"message": "이미 사용 중인 닉네임입니다.",
185+
"data": null
186+
}
187+
""")
188+
)
189+
),
190+
@ApiResponse(
191+
responseCode = "403",
192+
description = "정지된 계정",
193+
content = @Content(
194+
mediaType = "application/json",
195+
examples = @ExampleObject(value = """
196+
{
197+
"success": false,
198+
"code": "USER_008",
199+
"message": "정지된 계정입니다. 관리자에게 문의하세요.",
200+
"data": null
201+
}
202+
""")
203+
)
204+
),
205+
@ApiResponse(
206+
responseCode = "410",
207+
description = "탈퇴한 계정",
208+
content = @Content(
209+
mediaType = "application/json",
210+
examples = @ExampleObject(value = """
211+
{
212+
"success": false,
213+
"code": "USER_009",
214+
"message": "탈퇴한 계정입니다.",
215+
"data": null
216+
}
217+
""")
218+
)
219+
),
220+
@ApiResponse(
221+
responseCode = "401",
222+
description = "인증 실패 (토큰 없음/만료/잘못됨)",
223+
content = @Content(
224+
mediaType = "application/json",
225+
examples = @ExampleObject(value = """
226+
{
227+
"success": false,
228+
"code": "AUTH_401",
229+
"message": "인증이 필요합니다.",
230+
"data": null
231+
}
232+
""")
233+
)
234+
),
235+
@ApiResponse(
236+
responseCode = "404",
237+
description = "존재하지 않는 사용자",
238+
content = @Content(
239+
mediaType = "application/json",
240+
examples = @ExampleObject(value = """
241+
{
242+
"success": false,
243+
"code": "USER_001",
244+
"message": "존재하지 않는 사용자입니다.",
245+
"data": null
246+
}
247+
""")
248+
)
249+
),
250+
@ApiResponse(
251+
responseCode = "500",
252+
description = "서버 내부 오류",
253+
content = @Content(
254+
mediaType = "application/json",
255+
examples = @ExampleObject(value = """
256+
{
257+
"success": false,
258+
"code": "COMMON_500",
259+
"message": "서버 오류가 발생했습니다.",
260+
"data": null
261+
}
262+
""")
263+
)
264+
)
265+
})
266+
ResponseEntity<RsData<UserDetailResponse>> updateMyProfile(
267+
@AuthenticationPrincipal CustomUserDetails user,
268+
@Valid @RequestBody UpdateUserProfileRequest request
269+
);
270+
}

0 commit comments

Comments
 (0)