Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.campus.campus.domain.user.application.dto.request;

import io.swagger.v3.oas.annotations.media.Schema;

public record ChangeProfileImageRequest(
@Schema(description = "새로운 사용자 프로필 이미지", example = "http://image/img_640x640.jpg")
String newProfileImage
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.campus.campus.domain.user.application.dto.request;

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

public record ChangeUserAcademicRequest(
@Schema(description = "변경할 학교 ID", example = "1")
@NotNull
Long schoolId,

@Schema(description = "변경할 학과 ID", example = "15")
@NotNull
Long majorId
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.campus.campus.domain.user.application.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;

public record ChangeProfileImageResponse(
@Schema(description = "유저 id", example = "1")
Long userId,

@Schema(description = "유저 닉네임(campusNickname 설정했으면 campusNickname, 아니면, nickname")
String nickname,

@Schema(description = "새로운 사용자 프로필 이미지", example = "http://image/img_640x640.jpg")
String newProfileImage
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.campus.campus.domain.user.application.dto.response;

import java.time.LocalDateTime;

import io.swagger.v3.oas.annotations.media.Schema;

public record ChangeUserAcademicResponse(
@Schema(description = "유저 id", example = "1")
Long userId,

@Schema(description = "유저 닉네임(campusNickname 설정했으면 campusNickname, 아니면, nickname")
String nickname,

@Schema(description = "변경된 학교 이름", example = "가천대학교")
String schoolName,

@Schema(description = "변경된 단과대 이름", example = "IT융합대학")
String collegeName,

@Schema(description = "변경된 학과 이름", example = "소프트웨어학과")
String majorName,

@Schema(description = "다음 변경 가능 날짜", example = "2026-07-15T12:00:00")
LocalDateTime nextUpdateAvailableDate
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.campus.campus.domain.user.application.exception;

import com.campus.campus.global.common.exception.ApplicationException;

public class AcademicInfoUpdateRestrictionException extends ApplicationException {
public AcademicInfoUpdateRestrictionException() {
super(ErrorCode.ACADEMIC_INFO_CANNOT_CHANGE);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ public enum ErrorCode implements ErrorCodeInterface {
USER_NOT_FIRST_LOGIN(2101, HttpStatus.BAD_REQUEST, "최초 로그인한 사용가 아닙니다."),
NICKNAME_NOT_MATCH(2102, HttpStatus.BAD_REQUEST, "닉네임이 일치하지 않습니다."),
USER_SIGNUP_FORBIDDEN_NOW(2103, HttpStatus.FORBIDDEN, "현재 유저 회원가입할 수 없는 계정입니다."),
NICKNAME_ALREADY_EXISTS(2104, HttpStatus.CONFLICT, "이미 사용 중인 닉네임입니다.");
NICKNAME_ALREADY_EXISTS(2104, HttpStatus.CONFLICT, "이미 사용 중인 닉네임입니다."),
ACADEMIC_INFO_CANNOT_CHANGE(2105, HttpStatus.BAD_REQUEST, "학적 정보는 6개월에 한번만 변경 가능합니다.");

private final int code;
private final HttpStatus status;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import org.springframework.stereotype.Component;

import com.campus.campus.domain.user.application.dto.response.ChangeProfileImageResponse;
import com.campus.campus.domain.user.application.dto.response.ChangeUserAcademicResponse;
import com.campus.campus.domain.user.application.dto.response.UserFirstProfileResponse;
import com.campus.campus.domain.user.application.dto.response.UserInfoResponse;
import com.campus.campus.domain.user.domain.entity.User;
Expand Down Expand Up @@ -37,4 +39,23 @@ public UserInfoResponse toUserInfoResponse(User user) {
user.getMajor().getMajorName()
);
}

public ChangeProfileImageResponse toChangeProfileImageResponse(User user) {
return new ChangeProfileImageResponse(
user.getId(),
user.getCampusNickname() == null ? user.getNickname() : user.getCampusNickname(),
user.getProfileImage()
);
}

public ChangeUserAcademicResponse toChangeUserAcademicResponse(User user) {
return new ChangeUserAcademicResponse(
user.getId(),
user.getCampusNickname() == null ? user.getNickname() : user.getCampusNickname(),
user.getSchool().getSchoolName(),
user.getMajor().getCollege().getCollegeName(),
user.getMajor().getMajorName(),
user.getLastProfileUpdatedAt().plusMonths(3)
);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.campus.campus.domain.user.application.service;

import java.time.LocalDateTime;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -12,9 +14,14 @@
import com.campus.campus.domain.school.domain.repository.MajorRepository;
import com.campus.campus.domain.school.domain.repository.SchoolRepository;
import com.campus.campus.domain.user.application.dto.request.CampusNicknameUpdateRequest;
import com.campus.campus.domain.user.application.dto.request.ChangeProfileImageRequest;
import com.campus.campus.domain.user.application.dto.request.ChangeUserAcademicRequest;
import com.campus.campus.domain.user.application.dto.request.UserProfileRequest;
import com.campus.campus.domain.user.application.dto.response.ChangeProfileImageResponse;
import com.campus.campus.domain.user.application.dto.response.ChangeUserAcademicResponse;
import com.campus.campus.domain.user.application.dto.response.UserFirstProfileResponse;
import com.campus.campus.domain.user.application.dto.response.UserInfoResponse;
import com.campus.campus.domain.user.application.exception.AcademicInfoUpdateRestrictionException;
import com.campus.campus.domain.user.application.exception.NicknameAlreadyExistsException;
import com.campus.campus.domain.user.application.exception.UserNotFirstLoginException;
import com.campus.campus.domain.user.application.exception.UserNotFoundException;
Expand Down Expand Up @@ -71,6 +78,45 @@ public void updateCampusNickname(Long userId, CampusNicknameUpdateRequest nickna
userRepository.save(user);
}

@Transactional
public ChangeProfileImageResponse updateProfileImage(Long userId, ChangeProfileImageRequest profileImageRequest) {
User user = userRepository.findByIdAndDeletedAtIsNull(userId)
.orElseThrow(UserNotFoundException::new);

user.updateProfileImage(profileImageRequest.newProfileImage());
userRepository.save(user);

return userMapper.toChangeProfileImageResponse(user);
}

@Transactional
public ChangeUserAcademicResponse updateUserAcademic(Long userId, ChangeUserAcademicRequest userAcademicRequest) {
User user = userRepository.findByIdAndDeletedAtIsNull(userId)
.orElseThrow(UserNotFoundException::new);

if (user.getLastProfileUpdatedAt() != null) {
LocalDateTime nextAvailableDate = user.getLastProfileUpdatedAt().plusMonths(3);
if (LocalDateTime.now().isBefore(nextAvailableDate)) {
throw new AcademicInfoUpdateRestrictionException();
}
}

School school = schoolRepository.findById(userAcademicRequest.schoolId())
.orElseThrow(SchoolNotFoundException::new);
Major major = majorRepository.findById(userAcademicRequest.majorId())
.orElseThrow(MajorNotFoundException::new);

if (!major.getSchool().getSchoolId().equals(school.getSchoolId())) {
throw new SchoolMajorNotSameException();
}

College college = major.getCollege();
user.updateProfile(school, college, major);
userRepository.save(user);

return userMapper.toChangeUserAcademicResponse(user);
}

public UserInfoResponse getUserInfo(Long userId) {
User user = userRepository.findByIdAndDeletedAtIsNull(userId)
.orElseThrow(UserNotFoundException::new);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,14 @@ public class User extends BaseEntity {
@JoinColumn(name = "major_id")
private Major major;

@Column(name = "last_profile_updated_at")
private LocalDateTime lastProfileUpdatedAt;

public void updateProfile(School school, College college, Major major) {
this.school = school;
this.college = college;
this.major = major;
this.lastProfileUpdatedAt = LocalDateTime.now();
}

public void updateCampusNickname(String campusNickname) {
Expand All @@ -90,4 +94,8 @@ public boolean isProfileNotCompleted() {
public void updateRewardNeeded(boolean rewardNeeded) {
this.rewardNeeded = rewardNeeded;
}

public void updateProfileImage(String profileImage) {
this.profileImage = profileImage;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
import org.springframework.web.bind.annotation.RestController;

import com.campus.campus.domain.user.application.dto.request.CampusNicknameUpdateRequest;
import com.campus.campus.domain.user.application.dto.request.ChangeProfileImageRequest;
import com.campus.campus.domain.user.application.dto.request.ChangeUserAcademicRequest;
import com.campus.campus.domain.user.application.dto.request.UserProfileRequest;
import com.campus.campus.domain.user.application.dto.response.ChangeProfileImageResponse;
import com.campus.campus.domain.user.application.dto.response.ChangeUserAcademicResponse;
import com.campus.campus.domain.user.application.dto.response.UserFirstProfileResponse;
import com.campus.campus.domain.user.application.dto.response.UserInfoResponse;
import com.campus.campus.domain.user.application.service.UserService;
Expand Down Expand Up @@ -42,6 +46,25 @@ public CommonResponse<Void> updateCampusNickname(@CurrentUserId Long userId,
return CommonResponse.success(UserResponseCode.NICKNAME_UPDATE_SUCCESS);
}


@PatchMapping("/change/profile/image")
@Operation(summary = "사용자 프로필 이미지 변경")
public CommonResponse<ChangeProfileImageResponse> updateProfileImage(@CurrentUserId Long userId,
@RequestBody @Valid ChangeProfileImageRequest changeProfileImageRequest) {
ChangeProfileImageResponse response = userService.updateProfileImage(userId, changeProfileImageRequest);

return CommonResponse.success(UserResponseCode.PROFILE_IMAGE_UPDATE_SUCCESS, response);
}

@PatchMapping("change/profile/academic")
@Operation(summary = "사용자 학적 정보 수정 (3개월 1회 제한)")
public CommonResponse<ChangeUserAcademicResponse> updateAcademicInfo(@CurrentUserId Long userId,
@RequestBody @Valid ChangeUserAcademicRequest changeUserAcademicRequest) {
ChangeUserAcademicResponse response = userService.updateUserAcademic(userId, changeUserAcademicRequest);

return CommonResponse.success(UserResponseCode.ACADEMIC_INFO_UPDATE_SUCCESS, response);
}

@GetMapping
@Operation(summary = "사용자 정보 조회(홈 화면)")
public CommonResponse<UserInfoResponse> getUserInfo(@CurrentUserId Long userId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ public enum UserResponseCode implements ResponseCodeInterface {
FIRST_PROFILE_WRITE(200, HttpStatus.OK, " 프로필(학교 정보) 입력에 성공했습니다."),
NICKNAME_UPDATE_SUCCESS(200, HttpStatus.OK, "닉네임 변경에 성공했습니다."),
WITHDRAW_SUCCESS(200, HttpStatus.OK, "회원탈퇴에 성공했습니다."),
GET_USER_INFO_SUCCESS(200, HttpStatus.OK, "유저 정보 조회에 성공했습니다.");
GET_USER_INFO_SUCCESS(200, HttpStatus.OK, "유저 정보 조회에 성공했습니다."),
PROFILE_IMAGE_UPDATE_SUCCESS(200, HttpStatus.OK, "유저 프로필 이미지 변경에 성공했습니다."),
ACADEMIC_INFO_UPDATE_SUCCESS(200, HttpStatus.OK, "유저 학적 정보 변경에 성공했습니다.");

private final int code;
private final HttpStatus status;
Expand Down