Skip to content

Commit dc8d0b6

Browse files
authored
feat: 마이페이지 API 개발 (#211)
* feat: /user/v2/my api추가 * feat: 단과대, 학과 조회 API추가 * feat: 유저 탈퇴 로직 -> 닉네임 파라미터 받게 변경 * refactor: gemini 리뷰에 맞춰 변경
1 parent fb34bbc commit dc8d0b6

9 files changed

Lines changed: 125 additions & 4 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package ssu.eatssu.domain.user.department.persistence;
22

33
import org.springframework.data.jpa.repository.JpaRepository;
4+
import ssu.eatssu.domain.user.department.entity.College;
45
import ssu.eatssu.domain.user.department.entity.Department;
56

7+
import java.util.List;
68
import java.util.Optional;
79

810
public interface DepartmentRepository extends JpaRepository<Department, Long> {
911
Optional<Department> findByName(String name);
12+
13+
List<Department> findByCollege(College college);
1014
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package ssu.eatssu.domain.user.dto;
2+
3+
import lombok.Builder;
4+
5+
@Builder
6+
public record GetCollegeResponse(
7+
Long id,
8+
String name
9+
) {
10+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package ssu.eatssu.domain.user.dto;
2+
3+
import lombok.Builder;
4+
5+
@Builder
6+
public record GetDepartmentResponse(Long id,
7+
String name) {
8+
}

src/main/java/ssu/eatssu/domain/user/presentation/UserController.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import io.swagger.v3.oas.annotations.responses.ApiResponses;
1010
import io.swagger.v3.oas.annotations.tags.Tag;
1111
import jakarta.validation.Valid;
12+
import jakarta.validation.constraints.NotBlank;
1213
import lombok.RequiredArgsConstructor;
1314
import org.springdoc.core.annotations.ParameterObject;
1415
import org.springframework.data.domain.Pageable;
@@ -27,9 +28,13 @@
2728
import ssu.eatssu.domain.auth.security.CustomUserDetails;
2829
import ssu.eatssu.domain.partnership.dto.PartnershipResponse;
2930
import ssu.eatssu.domain.partnership.service.PartnershipService;
31+
import ssu.eatssu.domain.review.service.ReviewServiceV2;
3032
import ssu.eatssu.domain.slice.dto.SliceResponse;
3133
import ssu.eatssu.domain.slice.service.SliceService;
3234
import ssu.eatssu.domain.user.dto.DepartmentResponse;
35+
import ssu.eatssu.domain.user.dto.GetCollegeResponse;
36+
import ssu.eatssu.domain.user.dto.GetDepartmentResponse;
37+
import ssu.eatssu.domain.user.dto.MyMealReviewResponse;
3338
import ssu.eatssu.domain.user.dto.MyPageResponse;
3439
import ssu.eatssu.domain.user.dto.MyReviewDetail;
3540
import ssu.eatssu.domain.user.dto.NicknameUpdateRequest;
@@ -48,6 +53,7 @@ public class UserController {
4853
private final UserService userService;
4954
private final SliceService sliceService;
5055
private final PartnershipService partnershipService;
56+
private final ReviewServiceV2 reviewServiceV2;
5157

5258
@Operation(summary = "이메일 중복 체크", description = """
5359
이메일 중복 체크 API 입니다.<br><br>
@@ -97,6 +103,16 @@ public BaseResponse<?> updateNickname(
97103
public BaseResponse<Boolean> withdraw(@AuthenticationPrincipal CustomUserDetails userDetails) {
98104
return BaseResponse.success(userService.withdraw(userDetails));
99105
}
106+
@Operation(summary = "유저 탈퇴 v2", description = "유저 탈퇴 API 입니다.")
107+
@ApiResponses(value = {
108+
@ApiResponse(responseCode = "200", description = "유저 탈퇴 성공"),
109+
@ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class)))
110+
})
111+
@DeleteMapping("/v2")
112+
public BaseResponse<Boolean> withdrawV2(@RequestParam @NotBlank String nickname, @AuthenticationPrincipal CustomUserDetails userDetails) {
113+
userService.withdrawV2(nickname.trim(),userDetails);
114+
return BaseResponse.success(true);
115+
}
100116

101117
@Operation(summary = "내가 쓴 리뷰 리스트 조회", description = "내가 쓴 리뷰 리스트를 조회하는 API 입니다.")
102118
@ApiResponses(value = {
@@ -180,4 +196,42 @@ public BaseResponse<List<PartnershipResponse>> getUserDepartmentPartnerships(
180196
public BaseResponse<DepartmentResponse> getDepartment(@AuthenticationPrincipal CustomUserDetails userDetails) {
181197
return BaseResponse.success(userService.getDepartment(userDetails));
182198
}
199+
200+
@Operation(summary = "내가 쓴 리뷰 리스트 조회", description = "내가 쓴 리뷰 리스트를 조회하는 API V2 입니다.")
201+
@ApiResponses(value = {
202+
@ApiResponse(responseCode = "200", description = "내가 쓴 리뷰 리스트 조회 성공"),
203+
@ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class)))
204+
})
205+
@GetMapping("/v2/reviews")
206+
public BaseResponse<SliceResponse<MyMealReviewResponse>> getMyReviews(
207+
@Parameter(description = "마지막으로 조회된 reviewId값(첫 조회시 값 필요 없음)", in = ParameterIn.QUERY) @RequestParam(required = false) Long lastReviewId,
208+
@ParameterObject @PageableDefault(size = 20, sort = "date", direction = Sort.Direction.DESC) Pageable pageable,
209+
@AuthenticationPrincipal CustomUserDetails customUserDetails) {
210+
SliceResponse<MyMealReviewResponse> myReviews = reviewServiceV2.findMyReviews(customUserDetails,
211+
lastReviewId,
212+
pageable);
213+
return BaseResponse.success(myReviews);
214+
}
215+
216+
@Operation(summary = "단과대 조회", description = "숭실대학교 단과대학 들을 조회하는 API입니다.(토큰 불필요)")
217+
@ApiResponses(value = {
218+
@ApiResponse(responseCode = "200", description = "단과대 리스트 조회 성공"),
219+
@ApiResponse(responseCode = "404", description = "존재하지 않는 단과대", content = @Content(schema = @Schema(implementation = BaseResponse.class)))
220+
})
221+
@GetMapping("/lookup/colleges")
222+
public BaseResponse<List<GetCollegeResponse>> getColleges() {
223+
List<GetCollegeResponse> getCollegeResponses = userService.getCollegeList();
224+
return BaseResponse.success(getCollegeResponses);
225+
}
226+
227+
@Operation(summary = "단과대에 따른 학과 조회", description = "단과대학을 입력하면 단과대에 속한 숭실대학교 학과를 조회하는 API입니다.(토큰 불필요)")
228+
@ApiResponses(value = {
229+
@ApiResponse(responseCode = "200", description = "단과대 리스트 조회 성공"),
230+
})
231+
@GetMapping("/lookup/departments")
232+
public BaseResponse<List<GetDepartmentResponse>> getDepartments(@RequestParam Long collegeId) {
233+
List<GetDepartmentResponse> getCollegeResponses = userService.getDepartmentList(collegeId);
234+
return BaseResponse.success(getCollegeResponses);
235+
}
236+
183237
}

src/main/java/ssu/eatssu/domain/user/repository/UserRepository.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,6 @@ public interface UserRepository extends JpaRepository<User, Long> {
1313
Optional<User> findByEmail(String email);
1414

1515
Optional<User> findByProviderId(String providerId);
16+
17+
Optional<User> findByNickname(String nickname);
1618
}

src/main/java/ssu/eatssu/domain/user/service/UserService.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,31 @@
88
import org.springframework.stereotype.Service;
99
import ssu.eatssu.domain.auth.entity.OAuthProvider;
1010
import ssu.eatssu.domain.auth.security.CustomUserDetails;
11+
import ssu.eatssu.domain.inquiry.entity.Inquiry;
1112
import ssu.eatssu.domain.review.entity.Review;
1213
import ssu.eatssu.domain.user.config.UserProperties;
14+
import ssu.eatssu.domain.user.department.entity.College;
1315
import ssu.eatssu.domain.user.department.entity.Department;
16+
import ssu.eatssu.domain.user.department.persistence.CollegeRepository;
1417
import ssu.eatssu.domain.user.department.persistence.DepartmentRepository;
1518
import ssu.eatssu.domain.user.dto.DepartmentResponse;
19+
import ssu.eatssu.domain.user.dto.GetCollegeResponse;
20+
import ssu.eatssu.domain.user.dto.GetDepartmentResponse;
1621
import ssu.eatssu.domain.user.dto.MyPageResponse;
1722
import ssu.eatssu.domain.user.dto.NicknameUpdateRequest;
1823
import ssu.eatssu.domain.user.dto.UpdateDepartmentRequest;
1924
import ssu.eatssu.domain.user.entity.User;
2025
import ssu.eatssu.domain.user.repository.UserRepository;
2126
import ssu.eatssu.global.handler.response.BaseException;
2227

28+
import java.util.List;
2329
import java.util.UUID;
2430

2531
import static ssu.eatssu.global.handler.response.BaseResponseStatus.DUPLICATE_NICKNAME;
32+
import static ssu.eatssu.global.handler.response.BaseResponseStatus.INVALID_NICKNAME;
2633
import static ssu.eatssu.global.handler.response.BaseResponseStatus.NOT_FOUND_DEPARTMENT;
2734
import static ssu.eatssu.global.handler.response.BaseResponseStatus.NOT_FOUND_USER;
35+
import static ssu.eatssu.global.handler.response.BaseResponseStatus.VALIDATION_ERROR;
2836

2937
@Slf4j
3038
@Service
@@ -37,6 +45,7 @@ public class UserService {
3745
private final PasswordEncoder passwordEncoder;
3846
private final DepartmentRepository departmentRepository;
3947
private final UserProperties userProperties;
48+
private final CollegeRepository collegeRepository;
4049

4150
public User join(String email, OAuthProvider provider, String providerId) {
4251
String credentials = createCredentials(provider, providerId);
@@ -73,6 +82,19 @@ public boolean withdraw(CustomUserDetails userDetails) {
7382
return true;
7483
}
7584

85+
public void withdrawV2(String nickName, CustomUserDetails userDetails) {
86+
User user = userRepository.findById(userDetails.getId())
87+
.orElseThrow(() -> new BaseException(NOT_FOUND_USER));
88+
89+
if (!user.getNickname().equals(nickName)) {
90+
throw new BaseException(INVALID_NICKNAME);
91+
}
92+
93+
user.getReviews().forEach(Review::clearUser);
94+
user.getUserInquiries().forEach(Inquiry::clearUser);
95+
userRepository.delete(user);
96+
}
97+
7698
public Boolean validateDuplicatedEmail(String email) {
7799
return !userRepository.existsByEmail(email);
78100
}
@@ -111,6 +133,26 @@ public DepartmentResponse getDepartment(CustomUserDetails userDetails) {
111133
return new DepartmentResponse(department != null ? department.getName() : "");
112134
}
113135

136+
public List<GetCollegeResponse> getCollegeList() {
137+
List<College> colleges = collegeRepository.findAll();
138+
return colleges.stream().map(college -> GetCollegeResponse.builder()
139+
.id(college.getId())
140+
.name(college.getName())
141+
.build())
142+
.toList();
143+
}
144+
145+
public List<GetDepartmentResponse> getDepartmentList(Long collegeId) {
146+
College college = collegeRepository.findById(collegeId)
147+
.orElseThrow(() -> new BaseException(VALIDATION_ERROR));
148+
List<Department> departments = departmentRepository.findByCollege(college);
149+
return departments.stream().map(department -> GetDepartmentResponse.builder()
150+
.id(department.getId())
151+
.name(department.getName())
152+
.build())
153+
.toList();
154+
}
155+
114156
private boolean isForbiddenNickname(String nickname) {
115157
return userProperties.getForbiddenNicknames().stream()
116158
.anyMatch(forbidden -> forbidden.equalsIgnoreCase(nickname));

src/main/java/ssu/eatssu/global/handler/response/BaseResponseStatus.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ public enum BaseResponseStatus {
5959
NOT_FOUND_DEPARTMENT(false, HttpStatus.NOT_FOUND, 40409, "해당 학과를 찾을 수 없습니다."),
6060
NOT_FOUND_PARTNERSHIP(false, HttpStatus.NOT_FOUND, 40410, "해당 제휴를 찾을 수 없습니다."),
6161
NOT_FOUND_PARTNERSHIP_RESTAURANT(false, HttpStatus.NOT_FOUND, 40411, "해당 제휴 식당을 찾을 수 없습니다."),
62+
INVALID_NICKNAME(false,HttpStatus.NOT_FOUND,40412,"잘못된 닉네임입니다."),
6263

6364
/**
6465
* 405 METHOD_NOT_ALLOWED 지원하지 않은 method 호출

src/main/resources/application-dev.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ spring:
4141
jwt:
4242
secret:
4343
key: ${EATSSU_JWT_SECRET_DEV}
44-
token-validity-in-seconds: 60
45-
refresh-token-validity-in-seconds: 180
44+
token-validity-in-seconds: 86400
45+
refresh-token-validity-in-seconds: 604800
4646

4747
#S3
4848
cloud:

src/main/resources/application-local.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ spring:
2929
jwt:
3030
secret:
3131
key: ${EATSSU_JWT_SECRET_LOCAL}
32-
token-validity-in-seconds: 10
33-
refresh-token-validity-in-seconds: 30
32+
token-validity-in-seconds: 86400
33+
refresh-token-validity-in-seconds: 604800
3434

3535
cloud:
3636
aws:

0 commit comments

Comments
 (0)