Skip to content
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ dependencies {
// Data Layer: JPA, Redis, Database Drivers
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.springframework.boot:spring-boot-starter-validation'
runtimeOnly 'com.h2database:h2'
runtimeOnly 'com.mysql:mysql-connector-j'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
import com.somemore.global.exception.ImageUploadException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
public class GlobalExceptionHandler {

//예시 코드
@ExceptionHandler(BadRequestException.class)
Expand Down Expand Up @@ -45,4 +45,15 @@ ProblemDetail handleDuplicateException(final DuplicateException e) {
return problemDetail;
}

@ExceptionHandler(MethodArgumentNotValidException.class)
ProblemDetail handleMethodArgumentNotValid(final MethodArgumentNotValidException e) {

ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, e.getMessage());

problemDetail.setTitle("유효성 예외");
problemDetail.setDetail("입력 데이터 유효성 검사가 실패했습니다. 각 필드를 확인해주세요.");

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good 입니다

return problemDetail;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.somemore.volunteer.controller;

import com.somemore.global.common.response.ApiResponse;
import com.somemore.imageupload.dto.ImageUploadRequestDto;
import com.somemore.imageupload.usecase.ImageUploadUseCase;
import com.somemore.volunteer.dto.request.VolunteerProfileUpdateRequestDto;
import com.somemore.volunteer.usecase.UpdateVolunteerProfileUseCase;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.util.UUID;

import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;

@RestController
@Slf4j
@RequiredArgsConstructor
@RequestMapping("/api/profile")
@Tag(name = "PUT Volunteer", description = "봉사자 프로필 수정")
public class VolunteerProfileCommandController {

private final UpdateVolunteerProfileUseCase updateVolunteerProfileUseCase;
private final ImageUploadUseCase imageUploadUseCase;

@Secured("ROLE_VOLUNTEER")
@Operation(summary = "프로필 수정", description = "현재 로그인된 사용자의 프로필을 수정합니다.")
@PutMapping(consumes = MULTIPART_FORM_DATA_VALUE)
public ApiResponse<String> updateProfile(
@AuthenticationPrincipal String volunteerId,
@Valid @RequestPart("data") VolunteerProfileUpdateRequestDto requestDto,
@RequestPart(value = "img_file", required = false) MultipartFile image) {

String imgUrl = imageUploadUseCase.uploadImage(new ImageUploadRequestDto(image));

updateVolunteerProfileUseCase.update(
UUID.fromString(volunteerId),
requestDto,
imgUrl
);

return ApiResponse.ok("프로필 수정 성공");
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.somemore.volunteer.controller;

import com.somemore.global.common.response.ApiResponse;
import com.somemore.volunteer.dto.response.VolunteerResponseDto;
import com.somemore.volunteer.dto.response.VolunteerProfileResponseDto;
import com.somemore.volunteer.usecase.VolunteerQueryUseCase;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
Expand All @@ -21,14 +21,14 @@
@RequiredArgsConstructor
@RequestMapping("/api/profile")
@Tag(name = "GET Volunteer", description = "봉사자 조회")
public class VolunteerQueryController {
public class VolunteerProfileQueryController {

private final VolunteerQueryUseCase volunteerQueryUseCase;

@Operation(summary = "본인 상세 프로필 조회", description = "현재 로그인된 사용자의 상세 프로필을 조회합니다.")
@Secured("ROLE_VOLUNTEER")
@GetMapping("/me")
public ApiResponse<VolunteerResponseDto> getMyProfile(
public ApiResponse<VolunteerProfileResponseDto> getMyProfile(
@AuthenticationPrincipal String volunteerId) {

return ApiResponse.ok(
Expand All @@ -39,7 +39,7 @@ public ApiResponse<VolunteerResponseDto> getMyProfile(

@GetMapping("/{volunteerId}")
@Operation(summary = "타인 프로필 조회", description = "특정 봉사자의 프로필을 조회합니다. 상세 정보는 포함되지 않습니다.")
public ApiResponse<VolunteerResponseDto> getVolunteerProfile(
public ApiResponse<VolunteerProfileResponseDto> getVolunteerProfile(
@PathVariable UUID volunteerId) {

return ApiResponse.ok(
Expand All @@ -52,7 +52,7 @@ public ApiResponse<VolunteerResponseDto> getVolunteerProfile(
@GetMapping("/{volunteerId}/detailed")
@Secured("ROLE_CENTER")
@Operation(summary = "지원자 상세 프로필 조회", description = "기관이 작성한 모집 글에 지원한 봉사자의 상세 프로필을 조회합니다.")
public ApiResponse<VolunteerResponseDto> getVolunteerDetailedProfile(
public ApiResponse<VolunteerProfileResponseDto> getVolunteerDetailedProfile(
@PathVariable UUID volunteerId,
@AuthenticationPrincipal String centerId) {

Expand Down
7 changes: 7 additions & 0 deletions src/main/java/com/somemore/volunteer/domain/Volunteer.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.somemore.auth.oauth.OAuthProvider;
import com.somemore.global.common.BaseEntity;
import com.somemore.volunteer.dto.request.VolunteerProfileUpdateRequestDto;
import jakarta.persistence.*;
import lombok.*;

Expand Down Expand Up @@ -58,6 +59,12 @@ public static Volunteer createDefault(OAuthProvider oauthProvider, String oauthI
.build();
}

public void updateWith(VolunteerProfileUpdateRequestDto dto, String imgUrl) {
this.nickname = dto.nickname();
this.introduce = dto.introduce();
this.imgUrl = imgUrl;
}

@Builder
private Volunteer(
OAuthProvider oauthProvider,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.somemore.volunteer.dto.request;

import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Builder;

@Builder
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public record VolunteerProfileUpdateRequestDto(

@Schema(description = "봉사자 닉네임", example = "making")
@NotBlank(message = "닉네임은 필수 값입니다.")
@Size(max = 10, message = "닉네임은 최대 10자까지 입력 가능합니다.")
String nickname,

@Schema(description = "봉사자 소개글", example = "저는 다양한 봉사활동에 관심이 많은 봉사자입니다.")
@NotBlank(message = "소개글은 필수 값입니다.")
@Size(max = 100, message = "소개글은 최대 100자까지 입력 가능합니다.")
String introduce
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
import io.swagger.v3.oas.annotations.media.Schema;

@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
@Schema(description = "봉사자 응답 DTO")
public record VolunteerResponseDto(
@Schema(description = "봉사자 프로필 응답 DTO")
public record VolunteerProfileResponseDto(
@Schema(description = "봉사자 ID", example = "123e4567-e89b-12d3-a456-426614174000")
String volunteerId,

Expand All @@ -30,30 +30,30 @@ public record VolunteerResponseDto(
@Schema(description = "총 봉사 횟수", example = "20")
Integer totalVolunteerCount,

@Schema(description = "봉사자 상세 정보", implementation = VolunteerDetailResponseDto.class)
VolunteerDetailResponseDto volunteerDetailResponseDto
@Schema(description = "봉사자 상세 정보", implementation = VolunteerDetailProfileResponseDto.class)
VolunteerDetailProfileResponseDto volunteerDetailProfileResponseDto
) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API 응답에서 필드명으로 "key": "value"로 나가서
volunteerDetailProfileResponseDto -> volunteerDetailProfile 로 필드명(레코드 타입이름이 아닌) 바꾸는게 어떤가요??
"volunteer_detail_profile_reponse_dto" -> "volunteer_detail_profile"

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋아요! 명세에 적은 details로 변경하면 깔끔할 것 같습니다. 감사합니다


public static VolunteerResponseDto from(
public static VolunteerProfileResponseDto from(
Volunteer volunteer,
VolunteerDetail volunteerDetail
) {
return new VolunteerResponseDto(
return new VolunteerProfileResponseDto(
volunteer.getId().toString(),
volunteer.getNickname(),
volunteer.getImgUrl(),
volunteer.getIntroduce(),
volunteer.getTier().name(),
volunteer.getTotalVolunteerHours(),
volunteer.getTotalVolunteerCount(),
VolunteerDetailResponseDto.from(volunteerDetail)
VolunteerDetailProfileResponseDto.from(volunteerDetail)
);
}

public static VolunteerResponseDto from(
public static VolunteerProfileResponseDto from(
Volunteer volunteer
) {
return new VolunteerResponseDto(
return new VolunteerProfileResponseDto(
volunteer.getId().toString(),
volunteer.getNickname(),
volunteer.getImgUrl(),
Expand All @@ -66,8 +66,8 @@ public static VolunteerResponseDto from(
}

@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
@Schema(description = "봉사자 상세 응답 DTO")
private record VolunteerDetailResponseDto(
@Schema(description = "봉사자 상세 프로필 응답 DTO")
private record VolunteerDetailProfileResponseDto(
@Schema(description = "이름", example = "홍길동")
String name,

Expand All @@ -83,10 +83,10 @@ private record VolunteerDetailResponseDto(
@Schema(description = "연락처", example = "010-1234-5678")
String contactNumber
) {
public static VolunteerDetailResponseDto from(
public static VolunteerDetailProfileResponseDto from(
VolunteerDetail volunteerDetail
) {
return new VolunteerDetailResponseDto(
return new VolunteerDetailProfileResponseDto(
volunteerDetail.getName(),
volunteerDetail.getEmail(),
volunteerDetail.getGender().name(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.somemore.volunteer.service;

import com.somemore.global.exception.BadRequestException;
import com.somemore.volunteer.domain.Volunteer;
import com.somemore.volunteer.dto.request.VolunteerProfileUpdateRequestDto;
import com.somemore.volunteer.repository.VolunteerRepository;
import com.somemore.volunteer.usecase.UpdateVolunteerProfileUseCase;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.UUID;

import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_VOLUNTEER;

@Slf4j
@Service
@RequiredArgsConstructor
@Transactional
public class UpdateVolunteerProfileService implements UpdateVolunteerProfileUseCase {

private final VolunteerRepository volunteerRepository;

@Override
public void update(UUID volunteerId, VolunteerProfileUpdateRequestDto requestDto, String imgUrl) {
Volunteer volunteer = volunteerRepository.findById(volunteerId)
.orElseThrow(() -> new BadRequestException(NOT_EXISTS_VOLUNTEER));

volunteer.updateWith(requestDto, imgUrl);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import com.somemore.global.exception.BadRequestException;
import com.somemore.volunteer.domain.Volunteer;
import com.somemore.volunteer.domain.VolunteerDetail;
import com.somemore.volunteer.dto.response.VolunteerResponseDto;
import com.somemore.volunteer.dto.response.VolunteerProfileResponseDto;
import com.somemore.volunteer.repository.VolunteerDetailRepository;
import com.somemore.volunteer.repository.VolunteerRepository;
import com.somemore.volunteer.usecase.VolunteerQueryUseCase;
Expand All @@ -28,27 +28,27 @@ public class VolunteerQueryService implements VolunteerQueryUseCase {
private final VolunteerDetailAccessValidator volunteerDetailAccessValidator;

@Override
public VolunteerResponseDto getMyProfile(UUID volunteerId) {
public VolunteerProfileResponseDto getMyProfile(UUID volunteerId) {

return VolunteerResponseDto.from(
return VolunteerProfileResponseDto.from(
findVolunteer(volunteerId),
findVolunteerDetail(volunteerId)
);
}

@Override
public VolunteerResponseDto getVolunteerProfile(UUID volunteerId) {
public VolunteerProfileResponseDto getVolunteerProfile(UUID volunteerId) {

return VolunteerResponseDto.from(
return VolunteerProfileResponseDto.from(
findVolunteer(volunteerId)
);
}

@Override
public VolunteerResponseDto getVolunteerDetailedProfile(UUID volunteerId, UUID centerId) {
public VolunteerProfileResponseDto getVolunteerDetailedProfile(UUID volunteerId, UUID centerId) {
volunteerDetailAccessValidator.validateByCenterId(centerId, volunteerId);

return VolunteerResponseDto.from(
return VolunteerProfileResponseDto.from(
findVolunteer(volunteerId),
findVolunteerDetail(volunteerId)
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.somemore.volunteer.usecase;

import com.somemore.volunteer.dto.request.VolunteerProfileUpdateRequestDto;

import java.util.UUID;

public interface UpdateVolunteerProfileUseCase {

void update(UUID volunteerId, VolunteerProfileUpdateRequestDto requestDto, String imgUrl);
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package com.somemore.volunteer.usecase;

import com.somemore.volunteer.dto.response.VolunteerResponseDto;
import com.somemore.volunteer.dto.response.VolunteerProfileResponseDto;

import java.util.UUID;

public interface VolunteerQueryUseCase {

VolunteerResponseDto getMyProfile(UUID volunteerId);
VolunteerProfileResponseDto getMyProfile(UUID volunteerId);

VolunteerResponseDto getVolunteerProfile(UUID volunteerId);
VolunteerProfileResponseDto getVolunteerProfile(UUID volunteerId);

VolunteerResponseDto getVolunteerDetailedProfile(UUID volunteerId, UUID centerId);
VolunteerProfileResponseDto getVolunteerDetailedProfile(UUID volunteerId, UUID centerId);

UUID getVolunteerIdByOAuthId(String oAuthId);

Expand Down
Loading