Skip to content

Commit

Permalink
[BSVR-136] 프로필 이미지 presigned url API & 프로필 수정 API (#46)
Browse files Browse the repository at this point in the history
* refactor: 이미지 업로드 presigned url 컴포넌트 개선

* fix: 테스트 수정

* feat: 멤버 프로필 업데이트 시그니처 추가

* feat: member 프로필 업데이트 API

* feat: ddl-auto 설정 변경

* Delete infrastructure/jpa/src/main/resources/application-kakao.yml

* Delete application/src/main/resources/application-jwt.yml

* feat: 프로필 update 쿼리 구현
  • Loading branch information
EunjiShin authored Jul 18, 2024
1 parent 50dcd96 commit 1836a2d
Show file tree
Hide file tree
Showing 20 changed files with 228 additions and 103 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package org.depromeet.spot.application.media;

import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;

import org.depromeet.spot.application.media.dto.request.CreatePresignedUrlRequest;
import org.depromeet.spot.application.media.dto.response.MediaUrlResponse;
import org.depromeet.spot.domain.media.MediaProperty;
import org.depromeet.spot.usecase.port.out.media.CreatePresignedUrlPort;
import org.depromeet.spot.usecase.port.out.media.CreatePresignedUrlPort.PresignedUrlRequest;
import org.springframework.http.HttpStatus;
Expand Down Expand Up @@ -32,8 +35,20 @@ public class MediaController {
public MediaUrlResponse createReviewImageUploadUrl(
@PathVariable Long memberId, @RequestBody @Valid CreatePresignedUrlRequest request) {
PresignedUrlRequest command =
new PresignedUrlRequest(request.fileExtension(), request.property());
String presignedUrl = createPresignedUrlPort.forReview(memberId, command);
new PresignedUrlRequest(request.fileExtension(), MediaProperty.REVIEW);
String presignedUrl = createPresignedUrlPort.forImage(memberId, command);
return new MediaUrlResponse(presignedUrl);
}

@ResponseStatus(HttpStatus.CREATED)
@PostMapping(value = "/members/{memberId}/profile/images")
@Operation(summary = "유저의 프로필 이미지 업로드 url을 생성합니다.")
public MediaUrlResponse createProfileImageUploadUrl(
@PathVariable @Positive @NotNull Long memberId,
@RequestBody @Valid CreatePresignedUrlRequest request) {
PresignedUrlRequest command =
new PresignedUrlRequest(request.fileExtension(), MediaProperty.PROFILE_IMAGE);
String presignedUrl = createPresignedUrlPort.forImage(memberId, command);
return new MediaUrlResponse(presignedUrl);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
package org.depromeet.spot.application.media.dto.request;

import org.depromeet.spot.domain.media.MediaProperty;

public record CreatePresignedUrlRequest(String fileExtension, MediaProperty property) {}
public record CreatePresignedUrlRequest(String fileExtension) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.depromeet.spot.application.member.controller;

import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;

import org.depromeet.spot.application.member.dto.request.UpdateProfileRequest;
import org.depromeet.spot.application.member.dto.response.MemberProfileResponse;
import org.depromeet.spot.domain.member.Member;
import org.depromeet.spot.usecase.port.in.member.UpdateMemberUsecase;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;

@RestController
@Tag(name = "멤버")
@RequiredArgsConstructor
@RequestMapping("/api/v1/members")
public class UpdateMemberController {

private final UpdateMemberUsecase updateMemberUsecase;

@PutMapping("/{memberId}")
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "Member 프로필 수정 API")
public MemberProfileResponse updateProfile(
@PathVariable @NotNull @Positive final Long memberId,
@RequestBody @Valid @NotNull UpdateProfileRequest request) {
Member member = updateMemberUsecase.updateProfile(memberId, request.toCommand());
return MemberProfileResponse.from(member);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.depromeet.spot.application.member.dto.request;

import org.depromeet.spot.usecase.port.in.member.UpdateMemberUsecase.UpdateProfileCommand;

public record UpdateProfileRequest(String profileImage, String nickname, Long teamId) {

public UpdateProfileCommand toCommand() {
return UpdateProfileCommand.builder()
.nickname(nickname)
.profileImage(profileImage)
.teamId(teamId)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.depromeet.spot.application.member.dto.response;

import org.depromeet.spot.domain.member.Member;

public record MemberProfileResponse(Long id, String nickname, Long teamId) {

public static MemberProfileResponse from(Member member) {
return new MemberProfileResponse(member.getId(), member.getNickname(), member.getTeamId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public enum MediaProperty {
STADIUM_SEAT("stadium-seat-charts"),
STADIUM_SEAT_LABEL("stadium-seat-label-charts"),
TEAM_LOGO("team-logos"),
PROFILE_IMAGE("profile-images"),
;

private final String folderName;
Expand Down
45 changes: 18 additions & 27 deletions domain/src/main/java/org/depromeet/spot/domain/member/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
import org.depromeet.spot.domain.member.enums.MemberRole;
import org.depromeet.spot.domain.member.enums.SnsProvider;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
@AllArgsConstructor
public class Member {

private final Long id;
Expand All @@ -26,32 +28,21 @@ public class Member {
private final LocalDateTime createdAt;
private final LocalDateTime deletedAt;

public Member(
Long id,
String email,
String name,
String nickname,
String phoneNumber,
Integer level,
String profileImage,
SnsProvider snsProvider,
String idToken,
Long teamId,
MemberRole role,
LocalDateTime createdAt,
LocalDateTime deletedAt) {
this.id = id;
this.email = email;
this.name = name;
this.nickname = nickname;
this.phoneNumber = phoneNumber;
this.level = level;
this.profileImage = profileImage;
this.snsProvider = snsProvider;
this.idToken = idToken;
this.teamId = teamId;
this.role = role;
this.createdAt = createdAt;
this.deletedAt = deletedAt;
public Member updateProfile(String newProfileImage, String newNickname, Long newTeamId) {
return Member.builder()
.id(id)
.email(email)
.name(name)
.nickname(newNickname)
.phoneNumber(phoneNumber)
.level(level)
.profileImage(newProfileImage)
.snsProvider(snsProvider)
.idToken(idToken)
.teamId(newTeamId)
.role(role)
.createdAt(createdAt)
.deletedAt(deletedAt)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,25 @@

import org.depromeet.spot.jpa.member.entity.MemberEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

public interface MemberJpaRepository extends JpaRepository<MemberEntity, Long> {
Optional<MemberEntity> findByIdToken(String idToken);

Boolean existsByNickname(String nickname);

@Modifying
@Query(
"update MemberEntity m set "
+ "m.nickname = :nickname, "
+ "m.profileImage = :profileImage, "
+ "m.teamId = :teamId "
+ "where m.id = :memberId")
void updateProfile(
@Param("memberId") Long memberId,
@Param("profileImage") String profileImage,
@Param("teamId") Long teamId,
@Param("nickname") String nickname);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.Optional;

import org.depromeet.spot.common.exception.member.MemberException.MemberNotFoundException;
import org.depromeet.spot.domain.member.Member;
import org.depromeet.spot.jpa.member.entity.MemberEntity;
import org.depromeet.spot.usecase.port.out.member.MemberRepository;
Expand All @@ -21,6 +22,13 @@ public Member save(Member member) {
return memberEntity.toDomain();
}

@Override
public Member update(Member member) {
memberJpaRepository.updateProfile(
member.getId(), member.getProfileImage(), member.getTeamId(), member.getNickname());
return member;
}

@Override
public Optional<Member> findByIdToken(String idToken) {
return memberJpaRepository.findByIdToken(idToken).map(MemberEntity::toDomain);
Expand All @@ -30,4 +38,11 @@ public Optional<Member> findByIdToken(String idToken) {
public Boolean existsByNickname(String nickname) {
return memberJpaRepository.existsByNickname(nickname);
}

@Override
public Member findById(Long memberId) {
MemberEntity entity =
memberJpaRepository.findById(memberId).orElseThrow(MemberNotFoundException::new);
return entity.toDomain();
}
}
4 changes: 2 additions & 2 deletions infrastructure/jpa/src/main/resources/application-jpa.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
spring:
datasource:
url: jdbc:mysql://localhost:3306/spot
url: jdbc:mysql://mysql:3306/spot
username: test1234
password: DPM15thspot!
password: test1234
driver-class-name: com.mysql.cj.jdbc.Driver

jpa:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package org.depromeet.spot.ncp.objectstorage;

import org.depromeet.spot.domain.media.MediaProperty;
import org.depromeet.spot.domain.media.extension.ImageExtension;
import org.depromeet.spot.domain.media.extension.StadiumSeatMediaExtension;
import org.depromeet.spot.usecase.port.in.util.TimeUsecase;
import org.springframework.stereotype.Service;

Expand All @@ -16,29 +14,14 @@ public class FileNameGenerator {

private final TimeUsecase timeUsecase;

public String createReviewFileName(
final Long userId, final ImageExtension fileExtension, final String folderName) {
public String createFileName(
final Long memberId, final ImageExtension fileExtension, final String folderName) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder
.append(folderName)
.append("/")
.append(MediaProperty.REVIEW)
.append("_user_")
.append(userId)
.append("_")
.append(timeUsecase.getNow())
.append(".")
.append(fileExtension.getValue());
return stringBuilder.toString();
}

public String createStadiumFileName(
final StadiumSeatMediaExtension fileExtension, final String folderName) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder
.append(folderName)
.append("/")
.append(MediaProperty.STADIUM)
.append("user_")
.append(memberId)
.append("_")
.append(timeUsecase.getNow())
.append(".")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
import java.util.Date;

import org.depromeet.spot.common.exception.media.MediaException.InvalidExtensionException;
import org.depromeet.spot.common.exception.media.MediaException.InvalidReviewMediaException;
import org.depromeet.spot.domain.media.MediaProperty;
import org.depromeet.spot.domain.media.extension.ImageExtension;
import org.depromeet.spot.ncp.config.ObjectStorageConfig;
import org.depromeet.spot.usecase.port.out.media.CreatePresignedUrlPort;
Expand Down Expand Up @@ -33,24 +31,19 @@ public class PresignedUrlGenerator implements CreatePresignedUrlPort {
private static final long EXPIRE_MS = 1000 * 60 * 5L;

@Override
public String forReview(final Long userId, PresignedUrlRequest request) {
isValidReviewMedia(request.getProperty(), request.getFileExtension());
public String forImage(final Long memberId, PresignedUrlRequest request) {
isValidImageExtension(request.getFileExtension());

final ImageExtension fileExtension = ImageExtension.from(request.getFileExtension());
final String folderName = request.getProperty().getFolderName();
final String fileName =
fileNameGenerator.createReviewFileName(userId, fileExtension, folderName);
fileNameGenerator.createFileName(memberId, fileExtension, folderName);
final URL url = createPresignedUrl(fileName);

return url.toString();
}

// 1차 MVP에서 사진만 허용
private void isValidReviewMedia(final MediaProperty property, final String fileExtension) {
if (property != MediaProperty.REVIEW) {
throw new InvalidReviewMediaException();
}

private void isValidImageExtension(final String fileExtension) {
if (!ImageExtension.isValid(fileExtension)) {
throw new InvalidExtensionException(fileExtension);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;

import org.depromeet.spot.domain.media.extension.ImageExtension;
import org.depromeet.spot.domain.media.extension.StadiumSeatMediaExtension;
import org.depromeet.spot.ncp.mock.FakeTimeUsecase;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand All @@ -19,30 +18,16 @@ void init() {
}

@Test
void 리뷰_첨부파일_이름을_생성할__있다() {
void 첨부_파일_이름을_생성할__있다() {
// given
Long userId = 1L;
ImageExtension extension = ImageExtension.JPG;

// when
final String folderName = "review-images";
final String fileName =
fileNameGenerator.createReviewFileName(userId, extension, folderName);
final String folderName = "folder-names";
final String fileName = fileNameGenerator.createFileName(userId, extension, folderName);

// then
assertThat(fileName).isEqualTo("review-images/REVIEW_user_1_2024-07-09T21:00.jpg");
}

@Test
void 경기장_첨부파일_이름을_생성할__있다() {
// given
StadiumSeatMediaExtension extension = StadiumSeatMediaExtension.SVG;

// when
final String folderName = "stadium-images";
final String fileName = fileNameGenerator.createStadiumFileName(extension, folderName);

// then
assertThat(fileName).isEqualTo("stadium-images/STADIUM_2024-07-09T21:00.svg");
assertThat(fileName).isEqualTo("folder-names/user_1_2024-07-09T21:00.jpg");
}
}
Loading

0 comments on commit 1836a2d

Please sign in to comment.