Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public enum ErrorCode {
APPLY_UPDATE_LIMIT_EXCEED(HttpStatus.BAD_REQUEST.value(), "지원 정보 수정은 " + APPLICATION_UPDATE_COUNT_LIMIT + "회까지만 가능합니다."),
CANT_APPLY_FOR_SAME_UNIVERSITY(HttpStatus.BAD_REQUEST.value(), "1, 2, 3지망에 동일한 대학교를 입력할 수 없습니다."),
CAN_NOT_CHANGE_NICKNAME_YET(HttpStatus.BAD_REQUEST.value(), "마지막 닉네임 변경으로부터 " + MIN_DAYS_BETWEEN_NICKNAME_CHANGES + "일이 지나지 않았습니다."),
PROFILE_IMAGE_NEEDED(HttpStatus.BAD_REQUEST.value(), "프로필 이미지가 필요합니다."),

// community
INVALID_POST_CATEGORY(HttpStatus.BAD_REQUEST.value(),"잘못된 카테고리명입니다."),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
package com.example.solidconnection.siteuser.controller;

import com.example.solidconnection.siteuser.dto.MyPageResponse;
import com.example.solidconnection.siteuser.dto.MyPageUpdateRequest;
import com.example.solidconnection.siteuser.dto.MyPageUpdateResponse;
import com.example.solidconnection.siteuser.dto.*;
import com.example.solidconnection.siteuser.service.SiteUserService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.security.Principal;

Expand All @@ -36,12 +31,19 @@ public ResponseEntity<MyPageUpdateResponse> getMyPageInfoToUpdate(Principal prin
.ok(myPageUpdateDto);
}

@PatchMapping("/update")
public ResponseEntity<MyPageUpdateResponse> updateMyPageInfo(
@PatchMapping("/update/profileImage")
public ResponseEntity<ProfileImageUpdateResponse> updateProfileImage(
Principal principal,
@Valid @RequestBody MyPageUpdateRequest myPageUpdateDto) {
MyPageUpdateResponse myPageUpdateResponse = siteUserService.update(principal.getName(), myPageUpdateDto);
return ResponseEntity
.ok(myPageUpdateResponse);
@RequestParam(value = "file", required = false) MultipartFile imageFile) {
ProfileImageUpdateResponse profileImageUpdateResponse = siteUserService.updateProfileImage(principal.getName(), imageFile);
return ResponseEntity.ok().body(profileImageUpdateResponse);
}

@PatchMapping("/update/nickname")
public ResponseEntity<NicknameUpdateResponse> updateNickname(
Principal principal,
@Valid @RequestBody NicknameUpdateRequest nicknameUpdateRequest) {
NicknameUpdateResponse nicknameUpdateResponse = siteUserService.updateNickname(principal.getName(), nicknameUpdateRequest);
return ResponseEntity.ok().body(nicknameUpdateResponse);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,27 +52,4 @@ public interface SiteUserControllerSwagger {
}
)
ResponseEntity<MyPageUpdateResponse> getMyPageInfoToUpdate(Principal principal);

@Operation(
summary = "마이 페이지 정보 수정",
requestBody = @RequestBody(
description = "업데이트할 정보",
required = true,
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = MyPageUpdateRequest.class)
)
),
responses = {
@ApiResponse(
responseCode = "200",
description = "마이 페이지 정보 수정 성공",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = MyPageUpdateResponse.class)
)
)
}
)
ResponseEntity<MyPageUpdateResponse> updateMyPageInfo(Principal principal, @Valid @RequestBody MyPageUpdateRequest myPageUpdateDto);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.example.solidconnection.siteuser.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;

public record NicknameUpdateRequest(
@NotBlank(message = "닉네임을 입력해주세요.")
@Schema(description = "변경할 닉네임", example = "NewNickname")
String nickname
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.example.solidconnection.siteuser.dto;

import com.example.solidconnection.siteuser.domain.SiteUser;
import io.swagger.v3.oas.annotations.media.Schema;

public record NicknameUpdateResponse(
@Schema(description = "업데이트된 사용자 닉네임", example = "UpdatedNickname")
String nickname
) {
public static NicknameUpdateResponse from(SiteUser siteUser) {
return new NicknameUpdateResponse(
siteUser.getNickname()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.example.solidconnection.siteuser.dto;

import com.example.solidconnection.siteuser.domain.SiteUser;
import io.swagger.v3.oas.annotations.media.Schema;

public record ProfileImageUpdateResponse(
@Schema(description = "업데이트된 프로필 이미지 URL", example = "http://example.com/updated-profile.jpg")
String profileImageUrl
) {
public static ProfileImageUpdateResponse from(SiteUser siteUser) {
return new ProfileImageUpdateResponse(
siteUser.getProfileImageUrl()
);
}
}
Original file line number Diff line number Diff line change
@@ -1,34 +1,36 @@
package com.example.solidconnection.siteuser.service;

import com.example.solidconnection.custom.exception.CustomException;
import com.example.solidconnection.s3.S3Service;
import com.example.solidconnection.s3.UploadedFileUrlResponse;
import com.example.solidconnection.siteuser.domain.SiteUser;
import com.example.solidconnection.siteuser.dto.MyPageResponse;
import com.example.solidconnection.siteuser.dto.MyPageUpdateRequest;
import com.example.solidconnection.siteuser.dto.MyPageUpdateResponse;
import com.example.solidconnection.siteuser.dto.*;
import com.example.solidconnection.siteuser.repository.LikedUniversityRepository;
import com.example.solidconnection.siteuser.repository.SiteUserRepository;
import com.example.solidconnection.type.ImgType;
import com.example.solidconnection.university.domain.LikedUniversity;
import com.example.solidconnection.university.dto.UniversityInfoForApplyPreviewResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;

import static com.example.solidconnection.custom.exception.ErrorCode.CAN_NOT_CHANGE_NICKNAME_YET;
import static com.example.solidconnection.custom.exception.ErrorCode.NICKNAME_ALREADY_EXISTED;
import static com.example.solidconnection.custom.exception.ErrorCode.*;

@RequiredArgsConstructor
@Service
public class SiteUserService {

public static final int MIN_DAYS_BETWEEN_NICKNAME_CHANGES = 30;
public static final int MIN_DAYS_BETWEEN_NICKNAME_CHANGES = 7;
public static final DateTimeFormatter NICKNAME_LAST_CHANGE_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");

private final SiteUserRepository siteUserRepository;
private final LikedUniversityRepository likedUniversityRepository;
private final S3Service s3Service;

/*
* 마이페이지 정보를 조회한다.
Expand All @@ -49,31 +51,11 @@ public MyPageUpdateResponse getMyPageInfoToUpdate(String email) {
return MyPageUpdateResponse.from(siteUser);
}

/*
* 마이페이지 정보를 수정한다.
* - 닉네임 중복을 검증한다.
* - '닉네임 변경 최소 기간'이 지나지 않았는데 변경하려 하는지 검증한다.
* */
@Transactional
public MyPageUpdateResponse update(String email, MyPageUpdateRequest pageUpdateRequest) {
SiteUser siteUser = siteUserRepository.getByEmail(email);

validateNicknameDuplicated(pageUpdateRequest.nickname());
validateNicknameNotChangedRecently(siteUser.getNicknameModifiedAt());

siteUser.setNickname(pageUpdateRequest.nickname());
siteUser.setProfileImageUrl(pageUpdateRequest.profileImageUrl());
siteUser.setNicknameModifiedAt(LocalDateTime.now());
siteUserRepository.save(siteUser);
return MyPageUpdateResponse.from(siteUser);
}

private void validateNicknameDuplicated(String nickname) {
if (siteUserRepository.existsByNickname(nickname)) {
throw new CustomException(NICKNAME_ALREADY_EXISTED);
}
}

private void validateNicknameNotChangedRecently(LocalDateTime lastModifiedAt) {
if (lastModifiedAt == null) {
return;
Expand All @@ -96,4 +78,44 @@ public List<UniversityInfoForApplyPreviewResponse> getWishUniversity(String emai
.map(likedUniversity -> UniversityInfoForApplyPreviewResponse.from(likedUniversity.getUniversityInfoForApply()))
.toList();
}


/*
* 프로필 이미지를 수정한다.
* */
@Transactional
public ProfileImageUpdateResponse updateProfileImage(String email, MultipartFile imageFile) {
SiteUser siteUser = siteUserRepository.getByEmail(email);
validateProfileImage(imageFile);

s3Service.deleteExProfile(email);
UploadedFileUrlResponse uploadedFileUrlResponse = s3Service.uploadFile(imageFile, ImgType.PROFILE);
siteUser.setProfileImageUrl(uploadedFileUrlResponse.fileUrl());
siteUserRepository.save(siteUser);

return ProfileImageUpdateResponse.from(siteUser);
}

private void validateProfileImage(MultipartFile imageFile) {
if (imageFile == null || imageFile.isEmpty()) {
throw new CustomException(PROFILE_IMAGE_NEEDED);
}
}

/*
* 닉네임을 수정한다.
* */
@Transactional
public NicknameUpdateResponse updateNickname(String email, NicknameUpdateRequest nicknameUpdateRequest) {
SiteUser siteUser = siteUserRepository.getByEmail(email);

validateNicknameDuplicated(nicknameUpdateRequest.nickname());
validateNicknameNotChangedRecently(siteUser.getNicknameModifiedAt());

siteUser.setNickname(nicknameUpdateRequest.nickname());
siteUser.setNicknameModifiedAt(LocalDateTime.now());
siteUserRepository.save(siteUser);

return NicknameUpdateResponse.from(siteUser);
}
}
34 changes: 17 additions & 17 deletions src/test/java/com/example/solidconnection/e2e/MyPageUpdateTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
import com.example.solidconnection.config.token.TokenType;
import com.example.solidconnection.custom.response.ErrorResponse;
import com.example.solidconnection.siteuser.domain.SiteUser;
import com.example.solidconnection.siteuser.dto.MyPageUpdateRequest;
import com.example.solidconnection.siteuser.dto.MyPageUpdateResponse;
import com.example.solidconnection.siteuser.dto.NicknameUpdateRequest;
import com.example.solidconnection.siteuser.dto.NicknameUpdateResponse;
import com.example.solidconnection.siteuser.repository.SiteUserRepository;
import io.restassured.RestAssured;
import org.junit.jupiter.api.BeforeEach;
Expand Down Expand Up @@ -68,41 +69,40 @@ public void setUpUserAndToken() {
}

@Test
void 마이_페이지_정보를_수정한다() {
void 닉네임을_수정한다() {
// request - body 생성 및 요청
MyPageUpdateRequest myPageUpdateRequest = new MyPageUpdateRequest("newNickname", "newProfileImageUrl");
MyPageUpdateResponse myPageUpdateResponse = RestAssured.given()
NicknameUpdateRequest nicknameUpdateRequest = new NicknameUpdateRequest("newNickname");
NicknameUpdateResponse nicknameUpdateResponse = RestAssured.given()
.header("Authorization", "Bearer " + accessToken)
.log().all()
.body(myPageUpdateRequest)
.body(nicknameUpdateRequest)
.contentType("application/json")
.patch("/my-page/update")
.patch("/my-page/update/nickname")
.then().log().all()
.statusCode(HttpStatus.OK.value())
.extract().as(MyPageUpdateResponse.class);
.extract().as(NicknameUpdateResponse.class);

SiteUser savedSiteUser = siteUserRepository.getByEmail(email);
assertAll("마이 페이지 정보가 수정된다.",
() -> assertThat(myPageUpdateResponse.nickname()).isEqualTo(savedSiteUser.getNickname()),
() -> assertThat(myPageUpdateResponse.profileImageUrl()).isEqualTo(savedSiteUser.getProfileImageUrl()));
() -> assertThat(nicknameUpdateResponse.nickname()).isEqualTo(savedSiteUser.getNickname()));
}

@Test
void 마이_페이지_정보를_수정할_때_닉네임이_중복된다면_예외_응답을_반환한다() {
void 닉네임을_수정할_때_닉네임이_중복된다면_예외_응답을_반환한다() {
// setUp - 같은 닉네임을 갖는 다른 회원 정보 저장
SiteUser existUser = createSiteUserByEmail("existUser");
String duplicateNickname = "duplicateNickname";
existUser.setNickname(duplicateNickname);
siteUserRepository.save(existUser);

// request - body 생성 및 요청
MyPageUpdateRequest myPageUpdateRequest = new MyPageUpdateRequest(duplicateNickname, "newProfileImageUrl");
NicknameUpdateRequest nicknameUpdateRequest = new NicknameUpdateRequest("duplicateNickname");
ErrorResponse response = RestAssured.given()
.header("Authorization", "Bearer " + accessToken)
.log().all()
.body(myPageUpdateRequest)
.body(nicknameUpdateRequest)
.contentType("application/json")
.patch("/my-page/update")
.patch("/my-page/update/nickname")
.then().log().all()
.statusCode(HttpStatus.CONFLICT.value())
.extract().as(ErrorResponse.class);
Expand All @@ -112,7 +112,7 @@ public void setUpUserAndToken() {
}

@Test
void 마이_페이지_정보를_수정할_때_닉네임_변경_가능_기한이_지나지않았다면_예외_응답을_반환한다() {
void 닉네임을_수정할_때_닉네임_변경_가능_기한이_지나지않았다면_예외_응답을_반환한다() {
// setUp - 회원 정보 저장 (닉네임 변경 가능 시간이 되기 1분 전)
LocalDateTime nicknameModifiedAt = LocalDateTime.now()
.minusDays(MIN_DAYS_BETWEEN_NICKNAME_CHANGES)
Expand All @@ -121,13 +121,13 @@ public void setUpUserAndToken() {
siteUserRepository.save(siteUser);

// request - body 생성 및 요청
MyPageUpdateRequest myPageUpdateRequest = new MyPageUpdateRequest("newNickname", "newProfileImageUrl");
NicknameUpdateRequest nicknameUpdateRequest = new NicknameUpdateRequest("newNickname");
ErrorResponse response = RestAssured.given()
.header("Authorization", "Bearer " + accessToken)
.log().all()
.body(myPageUpdateRequest)
.body(nicknameUpdateRequest)
.contentType("application/json")
.patch("/my-page/update")
.patch("/my-page/update/nickname")
.then().log().all()
.statusCode(HttpStatus.BAD_REQUEST.value())
.extract().as(ErrorResponse.class);
Expand Down
Loading