From 2e413c73d738cb7ebb47be39150882943ba162a4 Mon Sep 17 00:00:00 2001 From: sungchaewon Date: Thu, 5 Jun 2025 16:32:59 +0900 Subject: [PATCH 1/6] =?UTF-8?q?refactor(signup)=20:=20=EB=94=94=ED=8F=B4?= =?UTF-8?q?=ED=8A=B8=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EB=B0=8F=20=EC=86=8C?= =?UTF-8?q?=EA=B0=9C=EA=B8=80=EC=9D=80=20=EB=8A=94=20db=EC=97=90=20null?= =?UTF-8?q?=EB=A1=9C=20=EC=A0=80=EC=9E=A5=ED=95=98=EA=B3=A0=20=ED=94=84?= =?UTF-8?q?=EB=A1=A0=ED=8A=B8=ED=95=9C=ED=85=8C=EB=8A=94=20=EA=B8=B0?= =?UTF-8?q?=EB=B3=B8=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EB=A7=81=ED=81=AC?= =?UTF-8?q?=EB=82=98=20=EA=B8=B0=EB=B3=B8=20=EC=86=8C=EA=B0=9C=EA=B8=80?= =?UTF-8?q?=EB=A1=9C=20=EB=B0=98=ED=99=98=ED=95=98=EB=8A=94=20=EB=B0=A9?= =?UTF-8?q?=ED=96=A5=EC=9C=BC=EB=A1=9C=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/FixLog/FixLogApplication.java | 3 ++ .../FixLog/controller/MemberController.java | 8 +++- .../controller/MypagePostController.java | 24 +++++------ .../example/FixLog/domain/member/Member.java | 4 +- .../FixLog/dto/WithdrawRequestDto.java | 8 ++++ .../FixLog/dto/member/LoginResponseDto.java | 14 +++++++ .../dto/member/MemberInfoResponseDto.java | 17 ++++++++ .../example/FixLog/service/AuthService.java | 16 ++------ .../example/FixLog/service/MemberService.java | 21 +++++----- .../FixLog/service/MypagePostService.java | 33 +-------------- .../com/example/FixLog/util/DefaultImage.java | 5 +++ .../com/example/FixLog/util/DefaultText.java | 5 +++ src/main/resources/application.properties | 41 +++++++++---------- 13 files changed, 107 insertions(+), 92 deletions(-) create mode 100644 src/main/java/com/example/FixLog/dto/WithdrawRequestDto.java create mode 100644 src/main/java/com/example/FixLog/util/DefaultImage.java create mode 100644 src/main/java/com/example/FixLog/util/DefaultText.java diff --git a/src/main/java/com/example/FixLog/FixLogApplication.java b/src/main/java/com/example/FixLog/FixLogApplication.java index e8c3276..2734b29 100644 --- a/src/main/java/com/example/FixLog/FixLogApplication.java +++ b/src/main/java/com/example/FixLog/FixLogApplication.java @@ -2,7 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +//Create_At 어노테이션 +@EnableJpaAuditing @SpringBootApplication public class FixLogApplication { diff --git a/src/main/java/com/example/FixLog/controller/MemberController.java b/src/main/java/com/example/FixLog/controller/MemberController.java index 8342511..d37068e 100644 --- a/src/main/java/com/example/FixLog/controller/MemberController.java +++ b/src/main/java/com/example/FixLog/controller/MemberController.java @@ -2,6 +2,7 @@ import com.example.FixLog.domain.member.Member; import com.example.FixLog.dto.Response; +import com.example.FixLog.dto.WithdrawRequestDto; import com.example.FixLog.dto.member.MemberInfoResponseDto; import com.example.FixLog.dto.member.SignupRequestDto; import com.example.FixLog.dto.member.DuplicateCheckResponseDto; @@ -51,8 +52,11 @@ public ResponseEntity> getMyInfo(@Authentication } @DeleteMapping("/me") - public ResponseEntity> withdraw(@AuthenticationPrincipal Member member) { - memberService.withdraw(member); + public ResponseEntity> withdraw( + @AuthenticationPrincipal Member member, + @RequestBody WithdrawRequestDto request + ) { + memberService.withdraw(member, request.getPassword()); return ResponseEntity.ok(Response.success("회원 탈퇴 성공", null)); } } \ No newline at end of file diff --git a/src/main/java/com/example/FixLog/controller/MypagePostController.java b/src/main/java/com/example/FixLog/controller/MypagePostController.java index 4635ef6..87c6f23 100644 --- a/src/main/java/com/example/FixLog/controller/MypagePostController.java +++ b/src/main/java/com/example/FixLog/controller/MypagePostController.java @@ -33,18 +33,18 @@ public ResponseEntity>> getMyPos return ResponseEntity.ok(Response.success("내가 작성한 글 보기 성공", data)); } - // 내가 좋아요한 글 - @GetMapping("/likes") - public ResponseEntity>> getLikedPosts( - @AuthenticationPrincipal UserDetails userDetails, - @RequestParam(defaultValue = "0") int page, - @RequestParam(defaultValue = "4") int size, - @RequestParam(defaultValue = "0") int sort) { - - String email = userDetails.getUsername(); - PageResponseDto result = mypagePostService.getLikedPosts(email, page, sort, size); - return ResponseEntity.ok(Response.success("내가 좋아요한 글 보기 성공", result)); - } +// // 내가 좋아요한 글 +// @GetMapping("/likes") +// public ResponseEntity>> getLikedPosts( +// @AuthenticationPrincipal UserDetails userDetails, +// @RequestParam(defaultValue = "0") int page, +// @RequestParam(defaultValue = "4") int size, +// @RequestParam(defaultValue = "0") int sort) { +// +// String email = userDetails.getUsername(); +// PageResponseDto result = mypagePostService.getLikedPosts(email, page, sort, size); +// return ResponseEntity.ok(Response.success("내가 좋아요한 글 보기 성공", result)); +// } } diff --git a/src/main/java/com/example/FixLog/domain/member/Member.java b/src/main/java/com/example/FixLog/domain/member/Member.java index 8ec21d2..6d0e280 100644 --- a/src/main/java/com/example/FixLog/domain/member/Member.java +++ b/src/main/java/com/example/FixLog/domain/member/Member.java @@ -57,7 +57,7 @@ public void setIsDeleted(boolean isDeleted) { @Column private LocalDateTime updatedAt; - // 프로필 사진 url, 지금은 nullable 이지만 나중에 기본값 설정 + // 프로필 사진 url @Column private String profileImageUrl; @@ -84,7 +84,7 @@ public static Member of(String email, String password, String nickname, SocialTy member.nickname = nickname; member.socialType = socialType; member.isDeleted = false; - member.profileImageUrl = "https://dummyimage.com/200x200/cccccc/ffffff&text=Profile"; // 기본 프로필 이미지(임시) + member.profileImageUrl = null; // 디폴트 이미지는 db에 null로 저장하고 프론트한테는 기본 이미지 링크로 반환하는 방향으로 통일 return member; } diff --git a/src/main/java/com/example/FixLog/dto/WithdrawRequestDto.java b/src/main/java/com/example/FixLog/dto/WithdrawRequestDto.java new file mode 100644 index 0000000..680dafd --- /dev/null +++ b/src/main/java/com/example/FixLog/dto/WithdrawRequestDto.java @@ -0,0 +1,8 @@ +package com.example.FixLog.dto; + +import lombok.Getter; + +@Getter +public class WithdrawRequestDto { + private String password; +} diff --git a/src/main/java/com/example/FixLog/dto/member/LoginResponseDto.java b/src/main/java/com/example/FixLog/dto/member/LoginResponseDto.java index fb7f80c..5f1e976 100644 --- a/src/main/java/com/example/FixLog/dto/member/LoginResponseDto.java +++ b/src/main/java/com/example/FixLog/dto/member/LoginResponseDto.java @@ -1,5 +1,7 @@ package com.example.FixLog.dto.member; +import com.example.FixLog.domain.member.Member; +import com.example.FixLog.util.DefaultImage; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.AllArgsConstructor; import lombok.Getter; @@ -12,4 +14,16 @@ public class LoginResponseDto { private String accessToken; private String nickname; private String profileImageUrl; + + // 정적 팩토리 메서드 + public static LoginResponseDto from(Member member, String accessToken) { + return new LoginResponseDto( + member.getUserId(), + accessToken, + member.getNickname(), + member.getProfileImageUrl() != null + ? member.getProfileImageUrl() + : DefaultImage.PROFILE + ); + } } diff --git a/src/main/java/com/example/FixLog/dto/member/MemberInfoResponseDto.java b/src/main/java/com/example/FixLog/dto/member/MemberInfoResponseDto.java index 45ced06..9b85898 100644 --- a/src/main/java/com/example/FixLog/dto/member/MemberInfoResponseDto.java +++ b/src/main/java/com/example/FixLog/dto/member/MemberInfoResponseDto.java @@ -1,6 +1,9 @@ package com.example.FixLog.dto.member; +import com.example.FixLog.domain.member.Member; import com.example.FixLog.domain.member.SocialType; +import com.example.FixLog.util.DefaultImage; +import com.example.FixLog.util.DefaultText; import lombok.AllArgsConstructor; import lombok.Getter; @@ -12,4 +15,18 @@ public class MemberInfoResponseDto { private String profileImageUrl; private String bio; private SocialType socialType; + + public static MemberInfoResponseDto from(Member member) { + return new MemberInfoResponseDto( + member.getEmail(), + member.getNickname(), + member.getProfileImageUrl() != null + ? member.getProfileImageUrl() + : DefaultImage.PROFILE, + member.getBio() != null + ? member.getBio() + : DefaultText.BIO, + member.getSocialType() + ); + } } diff --git a/src/main/java/com/example/FixLog/service/AuthService.java b/src/main/java/com/example/FixLog/service/AuthService.java index e47842d..3a941e8 100644 --- a/src/main/java/com/example/FixLog/service/AuthService.java +++ b/src/main/java/com/example/FixLog/service/AuthService.java @@ -29,17 +29,7 @@ public LoginResponseDto login(LoginRequestDto requestDto) { String token = jwtUtil.createToken(member.getUserId(), member.getEmail()); - // 로그인 응답 시에도 null-safe하게 처리 - String profileUrl = member.getProfileImageUrl() != null - ? member.getProfileImageUrl() - : "https://dummyimage.com/200x200/cccccc/ffffff&text=Profile"; - - return new LoginResponseDto( - member.getUserId(), - token, - member.getNickname(), - member.getProfileImageUrl() != null - ? member.getProfileImageUrl() - : "https://your-cdn.com/images/default-profile.png"); + // 응답에서 null-safe하게 기본 이미지 처리 포함 + return LoginResponseDto.from(member, token); } -} +} \ No newline at end of file diff --git a/src/main/java/com/example/FixLog/service/MemberService.java b/src/main/java/com/example/FixLog/service/MemberService.java index 5138c30..eb216c9 100644 --- a/src/main/java/com/example/FixLog/service/MemberService.java +++ b/src/main/java/com/example/FixLog/service/MemberService.java @@ -33,22 +33,20 @@ public void signup(SignupRequestDto request) { throw new CustomException(ErrorCode.NICKNAME_DUPLICATED); } - // 문제 없으면 저장 + // 회원 객체 생성 (profileImageUrl = null) Member member = Member.of( request.getEmail(), passwordEncoder.encode(request.getPassword()), request.getNickname(), SocialType.EMAIL ); - // 기본 프로필 이미지 URL 생성 - member.setProfileImageUrl("https://dummyimage.com/200x200/cccccc/ffffff&text=Profile"); - // 먼저 회원 정보 저장 + + // 회원 저장 memberRepository.save(member); // 기본 폴더 생성 BookmarkFolder newFolder = new BookmarkFolder(member); bookmarkFolderRepository.save(newFolder); - } public boolean isEmailDuplicated(String email) { @@ -58,7 +56,7 @@ public boolean isEmailDuplicated(String email) { public boolean isNicknameDuplicated(String nickname) { return memberRepository.findByNickname(nickname).isPresent(); } - + // 현재 로그인한 사용자 정보 member 객체로 반환 public Member getCurrentMemberInfo(){ Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); @@ -66,11 +64,14 @@ public Member getCurrentMemberInfo(){ return memberRepository.findByEmail(userEmail) .orElseThrow(() -> new CustomException(ErrorCode.USER_EMAIL_NOT_FOUND)); } - + // 회원탈퇴 - public void withdraw(Member member) { + public void withdraw(Member member, String password) { + if (!passwordEncoder.matches(password, member.getPassword())) { + throw new CustomException(ErrorCode.INVALID_PASSWORD); + } + member.setIsDeleted(true); memberRepository.save(member); } -} - +} \ No newline at end of file diff --git a/src/main/java/com/example/FixLog/service/MypagePostService.java b/src/main/java/com/example/FixLog/service/MypagePostService.java index b9aa201..2a3133a 100644 --- a/src/main/java/com/example/FixLog/service/MypagePostService.java +++ b/src/main/java/com/example/FixLog/service/MypagePostService.java @@ -1,6 +1,5 @@ package com.example.FixLog.service; -import com.example.FixLog.domain.like.PostLike; import com.example.FixLog.domain.member.Member; import com.example.FixLog.domain.post.Post; import com.example.FixLog.dto.PageResponseDto; @@ -9,7 +8,6 @@ import com.example.FixLog.exception.ErrorCode; import com.example.FixLog.repository.MemberRepository; import com.example.FixLog.repository.fork.ForkRepository; -import com.example.FixLog.repository.like.PostLikeRepository; import com.example.FixLog.repository.post.PostRepository; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -27,13 +25,11 @@ public class MypagePostService { private final PostRepository postRepository; private final MemberRepository memberRepository; private final ForkRepository forkRepository; - private final PostLikeRepository postLikeRepository; - public MypagePostService(PostRepository postRepository, MemberRepository memberRepository, ForkRepository forkRepository, PostLikeRepository postLikeRepository) { + public MypagePostService(PostRepository postRepository, MemberRepository memberRepository, ForkRepository forkRepository) { this.postRepository = postRepository; this.memberRepository = memberRepository; this.forkRepository = forkRepository; - this.postLikeRepository = postLikeRepository; } // 내가 쓴 글 보기 @@ -61,31 +57,4 @@ public PageResponseDto getMyPosts(String email, int page, ) ); } - - // 내가 좋아요한 글 보기 - public PageResponseDto getLikedPosts(String email, int page, int sort, int size) { - Member member = memberRepository.findByEmail(email) - .orElseThrow(() -> new CustomException(ErrorCode.USER_EMAIL_NOT_FOUND)); - - // 1: 오래된순, 0: 최신순 - Sort.Direction direction = (sort == 1) ? Sort.Direction.ASC : Sort.Direction.DESC; - Pageable pageable = PageRequest.of(page, size, Sort.by(direction, "postId.createdAt")); - - Page postLikePage = postLikeRepository.findByUserId(member, pageable); - List likedPosts = postLikePage.map(PostLike::getPostId).getContent(); - - // fork count 한번에 조회 - List forkCounts = forkRepository.countForksByOriginalPosts(likedPosts); - Map forkCountMap = forkCounts.stream() - .collect(Collectors.toMap( - row -> (Long) row[0], - row -> ((Long) row[1]).intValue() - )); - - return PageResponseDto.from(postLikePage.map(PostLike::getPostId), post -> - MyPostPageResponseDto.from(post, forkCountMap.getOrDefault(post.getPostId(), 0)) - ); - } - - } diff --git a/src/main/java/com/example/FixLog/util/DefaultImage.java b/src/main/java/com/example/FixLog/util/DefaultImage.java new file mode 100644 index 0000000..fa1fd90 --- /dev/null +++ b/src/main/java/com/example/FixLog/util/DefaultImage.java @@ -0,0 +1,5 @@ +package com.example.FixLog.util; + +public class DefaultImage { + public static final String PROFILE = "https://dummyimage.com/200x200/cccccc/ffffff&text=Profile"; // 임시 기본 프로필 이미지 +} diff --git a/src/main/java/com/example/FixLog/util/DefaultText.java b/src/main/java/com/example/FixLog/util/DefaultText.java new file mode 100644 index 0000000..defd0a6 --- /dev/null +++ b/src/main/java/com/example/FixLog/util/DefaultText.java @@ -0,0 +1,5 @@ +package com.example.FixLog.util; + +public class DefaultText { + public static final String BIO = "오늘도 에러 없는 하루 보내세요!"; +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index fa4b2ac..211bbbc 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,34 +1,33 @@ spring.application.name=FixLog - -//# DB setting -//spring.h2.console.enabled=true +# DB setting +spring.h2.console.enabled=true ''spring.h2.console.path=/h2-console -//# DataBase Info -//spring.datasource.url=jdbc:h2:tcp://localhost/~/fixlog -//spring.datasource.driver-class-name=org.h2.Driver -//spring.datasource.username=sa -//spring.datasource.password= +# DataBase Info +spring.datasource.url=jdbc:h2:tcp://localhost/~/fixlog +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password= -//spring.jpa.show-sql=true -//spring.jpa.hibernate.ddl-auto=update -//spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect +spring.jpa.show-sql=true +spring.jpa.hibernate.ddl-auto=update +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect //# JWT //jwt.secret=fixlogfixlogfixlogfixlogfixlog1234 //jwt.expiration-time=86400000 ##### dev ##### -server.port=8083 - -spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver -spring.datasource.url=${mysql_url} -spring.datasource.username=${mysql_username} -spring.datasource.password=${mysql_password} - -spring.jpa.hibernate.ddl-auto=create -spring.jpa.properties.hibernate.format_sql=true -spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect +#server.port=8083 +# +#spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +#spring.datasource.url=${mysql_url} +#spring.datasource.username=${mysql_username} +#spring.datasource.password=${mysql_password} +# +#spring.jpa.hibernate.ddl-auto=create +#spring.jpa.properties.hibernate.format_sql=true +#spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect ##### jwt ##### spring.jwt.secret=${jwt_key} From 25344291fd93602ef26db3b79a9a8583576d4e8e Mon Sep 17 00:00:00 2001 From: sungchaewon Date: Fri, 6 Jun 2025 16:26:42 +0900 Subject: [PATCH 2/6] =?UTF-8?q?feat(editUserProfile)=20:=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=EC=A0=95=EB=B3=B4(=EB=8B=89=EB=84=A4=EC=9E=84,=20?= =?UTF-8?q?=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8,=20=EC=86=8C=EA=B0=9C?= =?UTF-8?q?=EA=B8=80,=20=ED=94=84=EB=A1=9C=ED=95=84=EC=82=AC=EC=A7=84)=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/MypageMemberController.java | 62 +++++++++++++++++++ .../example/FixLog/domain/member/Member.java | 7 ++- .../dto/member/edit/EditBioRequestDto.java | 12 ++++ .../member/edit/EditNicknameRequestDto.java | 12 ++++ .../member/edit/EditPasswordRequestDto.java | 13 ++++ .../edit/EditProfileImageRequestDto.java | 12 ++++ .../example/FixLog/exception/ErrorCode.java | 3 +- .../example/FixLog/service/MemberService.java | 45 ++++++++++++++ 8 files changed, 162 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/example/FixLog/controller/MypageMemberController.java create mode 100644 src/main/java/com/example/FixLog/dto/member/edit/EditBioRequestDto.java create mode 100644 src/main/java/com/example/FixLog/dto/member/edit/EditNicknameRequestDto.java create mode 100644 src/main/java/com/example/FixLog/dto/member/edit/EditPasswordRequestDto.java create mode 100644 src/main/java/com/example/FixLog/dto/member/edit/EditProfileImageRequestDto.java diff --git a/src/main/java/com/example/FixLog/controller/MypageMemberController.java b/src/main/java/com/example/FixLog/controller/MypageMemberController.java new file mode 100644 index 0000000..8cc7d07 --- /dev/null +++ b/src/main/java/com/example/FixLog/controller/MypageMemberController.java @@ -0,0 +1,62 @@ +package com.example.FixLog.controller; + +import com.example.FixLog.dto.Response; +import com.example.FixLog.dto.member.edit.EditNicknameRequestDto; +import com.example.FixLog.dto.member.edit.EditPasswordRequestDto; +import com.example.FixLog.dto.member.edit.EditProfileImageRequestDto; +import com.example.FixLog.dto.member.edit.EditBioRequestDto; +import com.example.FixLog.service.MemberService; +import com.example.FixLog.domain.member.Member; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.core.userdetails.UserDetails; +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; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/mypage") +public class MypageMemberController { + + private final MemberService memberService; + + @PatchMapping("/members/nickname") + public ResponseEntity> editNickname( + @RequestBody EditNicknameRequestDto requestDto + ) { + Member member = memberService.getCurrentMemberInfo(); + memberService.editNickname(member, requestDto.getNickname()); + return ResponseEntity.ok(Response.success("닉네임 수정 성공", null)); + } + + @PatchMapping("/members/password") + public ResponseEntity> editPassword( + @AuthenticationPrincipal UserDetails userDetails, + @RequestBody EditPasswordRequestDto requestDto + ) { + Member member = memberService.getCurrentMemberInfo(); + memberService.editPassword(member, requestDto); + return ResponseEntity.ok(Response.success("비밀번호 변경 성공", null)); + } + + @PatchMapping("/members/profile-image") + public ResponseEntity> editProfileImage( + @RequestBody EditProfileImageRequestDto requestDto + ) { + Member member = memberService.getCurrentMemberInfo(); + memberService.editProfileImage(member, requestDto.getProfileImageUrl()); + return ResponseEntity.ok(Response.success("프로필 이미지 수정 성공", null)); + } + + @PatchMapping("/members/bio") + public ResponseEntity> editBio( + @RequestBody EditBioRequestDto requestDto + ) { + Member member = memberService.getCurrentMemberInfo(); + memberService.editBio(member, requestDto.getBio()); + return ResponseEntity.ok(Response.success("소개글 수정 성공", null)); + } +} diff --git a/src/main/java/com/example/FixLog/domain/member/Member.java b/src/main/java/com/example/FixLog/domain/member/Member.java index 6d0e280..36dbd80 100644 --- a/src/main/java/com/example/FixLog/domain/member/Member.java +++ b/src/main/java/com/example/FixLog/domain/member/Member.java @@ -6,6 +6,7 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; @@ -20,6 +21,7 @@ @Entity @Getter +@Setter @NoArgsConstructor(access = AccessLevel.PROTECTED) @EntityListeners(AuditingEntityListener.class) public class Member implements UserDetails { @@ -88,9 +90,6 @@ public static Member of(String email, String password, String nickname, SocialTy return member; } - public void setProfileImageUrl(String profileImageUrl) { - this.profileImageUrl = profileImageUrl; - } @Override public Collection getAuthorities() { @@ -121,4 +120,6 @@ public boolean isCredentialsNonExpired() { public boolean isEnabled() { return !this.isDeleted; // 탈퇴 여부 기반 활성 상태 } + + } \ No newline at end of file diff --git a/src/main/java/com/example/FixLog/dto/member/edit/EditBioRequestDto.java b/src/main/java/com/example/FixLog/dto/member/edit/EditBioRequestDto.java new file mode 100644 index 0000000..b2829d1 --- /dev/null +++ b/src/main/java/com/example/FixLog/dto/member/edit/EditBioRequestDto.java @@ -0,0 +1,12 @@ +package com.example.FixLog.dto.member.edit; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +public class EditBioRequestDto { + private String bio; +} diff --git a/src/main/java/com/example/FixLog/dto/member/edit/EditNicknameRequestDto.java b/src/main/java/com/example/FixLog/dto/member/edit/EditNicknameRequestDto.java new file mode 100644 index 0000000..7dca51a --- /dev/null +++ b/src/main/java/com/example/FixLog/dto/member/edit/EditNicknameRequestDto.java @@ -0,0 +1,12 @@ +package com.example.FixLog.dto.member.edit; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +public class EditNicknameRequestDto { + private String nickname; +} diff --git a/src/main/java/com/example/FixLog/dto/member/edit/EditPasswordRequestDto.java b/src/main/java/com/example/FixLog/dto/member/edit/EditPasswordRequestDto.java new file mode 100644 index 0000000..7f21286 --- /dev/null +++ b/src/main/java/com/example/FixLog/dto/member/edit/EditPasswordRequestDto.java @@ -0,0 +1,13 @@ +package com.example.FixLog.dto.member.edit; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +public class EditPasswordRequestDto { + private String currentPassword; + private String newPassword; +} diff --git a/src/main/java/com/example/FixLog/dto/member/edit/EditProfileImageRequestDto.java b/src/main/java/com/example/FixLog/dto/member/edit/EditProfileImageRequestDto.java new file mode 100644 index 0000000..a3fd2ce --- /dev/null +++ b/src/main/java/com/example/FixLog/dto/member/edit/EditProfileImageRequestDto.java @@ -0,0 +1,12 @@ +package com.example.FixLog.dto.member.edit; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +public class EditProfileImageRequestDto { + private String profileImageUrl; +} diff --git a/src/main/java/com/example/FixLog/exception/ErrorCode.java b/src/main/java/com/example/FixLog/exception/ErrorCode.java index 7f7e325..73518bb 100644 --- a/src/main/java/com/example/FixLog/exception/ErrorCode.java +++ b/src/main/java/com/example/FixLog/exception/ErrorCode.java @@ -24,7 +24,8 @@ public enum ErrorCode { SORT_NOT_EXIST(HttpStatus.BAD_REQUEST, "사용할 수 없는 정렬입니다."), INVALID_PASSWORD(HttpStatus.UNAUTHORIZED, "비밀번호가 일치하지 않습니다."), REQUIRED_TAGS_MISSING(HttpStatus.BAD_REQUEST, "태그를 선택해주세요."), - REQUIRED_CONTENT_MISSING(HttpStatus.BAD_REQUEST, "필수 본문이 입력되지 않았습니다."); + REQUIRED_CONTENT_MISSING(HttpStatus.BAD_REQUEST, "필수 본문이 입력되지 않았습니다."), + SAME_AS_OLD_PASSWORD(HttpStatus.BAD_REQUEST, "다른 비밀번호 입력 바랍니다."); private final HttpStatus status; private final String message; diff --git a/src/main/java/com/example/FixLog/service/MemberService.java b/src/main/java/com/example/FixLog/service/MemberService.java index eb216c9..07a594e 100644 --- a/src/main/java/com/example/FixLog/service/MemberService.java +++ b/src/main/java/com/example/FixLog/service/MemberService.java @@ -4,6 +4,7 @@ import com.example.FixLog.domain.member.Member; import com.example.FixLog.domain.member.SocialType; import com.example.FixLog.dto.member.SignupRequestDto; +import com.example.FixLog.dto.member.edit.EditPasswordRequestDto; import com.example.FixLog.exception.CustomException; import com.example.FixLog.exception.ErrorCode; import com.example.FixLog.repository.MemberRepository; @@ -74,4 +75,48 @@ public void withdraw(Member member, String password) { member.setIsDeleted(true); memberRepository.save(member); } + + + public void editNickname(Member member, String newNickname) { + if (isNicknameDuplicated(newNickname)) { + throw new CustomException(ErrorCode.NICKNAME_DUPLICATED); + } + member.setNickname(newNickname); + memberRepository.save(member); + } + + public void editPassword(Member member, EditPasswordRequestDto dto) { + String currentPassword = dto.getCurrentPassword(); + String newPassword = dto.getNewPassword(); + + // 1. 현재 비밀번호 일치 확인 + if (!passwordEncoder.matches(currentPassword, member.getPassword())) { + throw new CustomException(ErrorCode.INVALID_PASSWORD); // 기존 비밀번호 불일치 + } + + // 2. 새 비밀번호가 기존과 동일한 경우 + if (passwordEncoder.matches(newPassword, member.getPassword())) { + throw new CustomException(ErrorCode.SAME_AS_OLD_PASSWORD); // 동일한 비밀번호 + } + + // 3. 새 비밀번호로 변경 + member.setPassword(passwordEncoder.encode(newPassword)); + memberRepository.save(member); + } + + public void editProfileImage(Member member, String newProfileImageUrl) { + member.setProfileImageUrl(newProfileImageUrl); + memberRepository.save(member); + } + + public void editBio(Member member, String newBio) { + member.setBio(newBio); + memberRepository.save(member); + } + + private void validateNickname(String nickname) { + if (isNicknameDuplicated(nickname)) { + throw new CustomException(ErrorCode.NICKNAME_DUPLICATED); + } + } } \ No newline at end of file From d5fccbccffd894eee14a2a855ae1e581a097c007 Mon Sep 17 00:00:00 2001 From: sungchaewon Date: Sun, 8 Jun 2025 02:29:44 +0900 Subject: [PATCH 3/6] =?UTF-8?q?temp(MypagePost)=20:=EB=82=B4=EA=B0=80=20?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=ED=95=9C=20=EA=B8=80=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20->=20=EC=A3=BC=EC=84=9D=EC=B2=98=EB=A6=AC=EB=A1=9C?= =?UTF-8?q?=20=EC=9E=84=EC=8B=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FixLog/service/MypagePostService.java | 145 ++++++++++-------- 1 file changed, 85 insertions(+), 60 deletions(-) diff --git a/src/main/java/com/example/FixLog/service/MypagePostService.java b/src/main/java/com/example/FixLog/service/MypagePostService.java index 2a3133a..f4d2af1 100644 --- a/src/main/java/com/example/FixLog/service/MypagePostService.java +++ b/src/main/java/com/example/FixLog/service/MypagePostService.java @@ -1,60 +1,85 @@ -package com.example.FixLog.service; - -import com.example.FixLog.domain.member.Member; -import com.example.FixLog.domain.post.Post; -import com.example.FixLog.dto.PageResponseDto; -import com.example.FixLog.dto.post.MyPostPageResponseDto; -import com.example.FixLog.exception.CustomException; -import com.example.FixLog.exception.ErrorCode; -import com.example.FixLog.repository.MemberRepository; -import com.example.FixLog.repository.fork.ForkRepository; -import com.example.FixLog.repository.post.PostRepository; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.stereotype.Service; - -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -@Service -public class MypagePostService { - - private final PostRepository postRepository; - private final MemberRepository memberRepository; - private final ForkRepository forkRepository; - - public MypagePostService(PostRepository postRepository, MemberRepository memberRepository, ForkRepository forkRepository) { - this.postRepository = postRepository; - this.memberRepository = memberRepository; - this.forkRepository = forkRepository; - } - - // 내가 쓴 글 보기 - public PageResponseDto getMyPosts(String email, int page, int sort, int size) { - Member member = memberRepository.findByEmail(email) - .orElseThrow(() -> new CustomException(ErrorCode.USER_EMAIL_NOT_FOUND)); - - // 1: 오래된순, 0: 최신순 - Sort.Direction direction = (sort == 1) ? Sort.Direction.ASC : Sort.Direction.DESC; - Pageable pageable = PageRequest.of(page, size, Sort.by(direction, "createdAt")); - - Page postPage = postRepository.findByUserId(member, pageable); - List posts = postPage.getContent(); - - // fork count 한번에 조회 - List forkCounts = forkRepository.countForksByOriginalPosts(posts); - Map forkCountMap = forkCounts.stream() - .collect(Collectors.toMap( - row -> (Long) row[0], // postId - row -> ((Long) row[1]).intValue() // 포크 카운트 (int) - )); - - return PageResponseDto.from(postPage, post -> - MyPostPageResponseDto.from(post, forkCountMap.getOrDefault(post.getPostId(), 0) // 없으면 0 반환 - ) - ); - } -} +//package com.example.FixLog.service; +// +//import com.example.FixLog.domain.like.PostLike; +//import com.example.FixLog.domain.member.Member; +//import com.example.FixLog.domain.post.Post; +//import com.example.FixLog.dto.PageResponseDto; +//import com.example.FixLog.dto.post.MyPostPageResponseDto; +//import com.example.FixLog.exception.CustomException; +//import com.example.FixLog.exception.ErrorCode; +//import com.example.FixLog.repository.MemberRepository; +//import com.example.FixLog.repository.fork.ForkRepository; +//import com.example.FixLog.repository.like.PostLikeRepository; +//import com.example.FixLog.repository.post.PostRepository; +//import org.springframework.data.domain.Page; +//import org.springframework.data.domain.PageRequest; +//import org.springframework.data.domain.Pageable; +//import org.springframework.data.domain.Sort; +// +//import java.util.List; +//import java.util.Map; +//import java.util.stream.Collectors; +// +//public class MypagePostService { +// private final PostRepository postRepository; +// private final MemberRepository memberRepository; +// private final ForkRepository forkRepository; +// private final postLikeRepository postLikeRepository; +// +// public MypagePostService(PostRepository postRepository, MemberRepository memberRepository, ForkRepository forkRepository, PostLikeRepository postLikeRepository) { +// this.postRepository = postRepository; +// this.memberRepository = memberRepository; +// this.forkRepository = forkRepository; +// } +// +// // 내가 쓴 글 보기 +// public PageResponseDto getMyPosts(String email, int page, int sort, int size) { +// Member member = memberRepository.findByEmail(email) +// .orElseThrow(() -> new CustomException(ErrorCode.USER_EMAIL_NOT_FOUND)); +// +// // 1: 오래된순, 0: 최신순 +// Sort.Direction direction = (sort == 1) ? Sort.Direction.ASC : Sort.Direction.DESC; +// Pageable pageable = PageRequest.of(page, size, Sort.by(direction, "createdAt")); +// +// Page postPage = postRepository.findByUserId(member, pageable); +// List posts = postPage.getContent(); +// +// // fork count 한번에 조회 +// List forkCounts = forkRepository.countForksByOriginalPosts(posts); +// Map forkCountMap = forkCounts.stream() +// .collect(Collectors.toMap( +// row -> (Long) row[0], // postId +// row -> ((Long) row[1]).intValue() // 포크 카운트 (int) +// )); +// +// return PageResponseDto.from(postPage, post -> +// MyPostPageResponseDto.from(post, forkCountMap.getOrDefault(post.getPostId(), 0) // 없으면 0 반환 +// ) +// ); +// } +// // 내가 좋아요한 글 보기 +// public PageResponseDto getLikedPosts(String email, int page, int sort, int size) { +// Member member = memberRepository.findByEmail(email) +// .orElseThrow(() -> new CustomException(ErrorCode.USER_EMAIL_NOT_FOUND)); +// +// // 1: 오래된순, 0: 최신순 +// Sort.Direction direction = (sort == 1) ? Sort.Direction.ASC : Sort.Direction.DESC; +// Pageable pageable = PageRequest.of(page, size, Sort.by(direction, "postId.createdAt")); +// +// Page postLikePage = postLikeRepository.findByUserId(member, pageable); +// List likedPosts = postLikePage.map(PostLike::getPostId).getContent(); +// +// // fork count 한번에 조회 +// List forkCounts = forkRepository.countForksByOriginalPosts(likedPosts); +// Map forkCountMap = forkCounts.stream() +// .collect(Collectors.toMap( +// row -> (Long) row[0], +// row -> ((Long) row[1]).intValue() +// )); +// +// return PageResponseDto.from(postLikePage.map(PostLike::getPostId), post -> +// MyPostPageResponseDto.from(post, forkCountMap.getOrDefault(post.getPostId(), 0)) +// ); +// } +// +//} From 193f9a15590a2ed7ab49be818e67c7dc5f8365ba Mon Sep 17 00:00:00 2001 From: sungchaewon Date: Wed, 11 Jun 2025 15:34:12 +0900 Subject: [PATCH 4/6] =?UTF-8?q?feat(editUserProfile)=20:=20Presigned=20URL?= =?UTF-8?q?=20=EB=B0=9C=EA=B8=89=20=EB=B0=8F=20=ED=94=84=EB=A1=9C=ED=95=84?= =?UTF-8?q?=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20S3=20=EC=97=85=EB=A1=9C?= =?UTF-8?q?=EB=93=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 ++ build.gradle | 1 + .../example/FixLog/config/AwsS3Config.java | 33 ++++++++++ .../controller/MypageMemberController.java | 43 ++++++++++--- .../FixLog/dto/PresignResponseDto.java | 11 ++++ .../example/FixLog/exception/ErrorCode.java | 5 +- .../com/example/FixLog/service/S3Service.java | 64 +++++++++++++++++++ src/main/resources/application.properties | 58 ++++++++++------- 8 files changed, 184 insertions(+), 35 deletions(-) create mode 100644 src/main/java/com/example/FixLog/config/AwsS3Config.java create mode 100644 src/main/java/com/example/FixLog/dto/PresignResponseDto.java create mode 100644 src/main/java/com/example/FixLog/service/S3Service.java diff --git a/.gitignore b/.gitignore index c2065bc..2ca2d0c 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,7 @@ out/ ### VS Code ### .vscode/ + +### Environment variables ### + .env + *.env diff --git a/build.gradle b/build.gradle index dede071..de3f44d 100644 --- a/build.gradle +++ b/build.gradle @@ -37,6 +37,7 @@ dependencies { annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + implementation 'com.amazonaws:aws-java-sdk-s3:1.12.538' } tasks.named('test') { diff --git a/src/main/java/com/example/FixLog/config/AwsS3Config.java b/src/main/java/com/example/FixLog/config/AwsS3Config.java new file mode 100644 index 0000000..7e47597 --- /dev/null +++ b/src/main/java/com/example/FixLog/config/AwsS3Config.java @@ -0,0 +1,33 @@ +package com.example.FixLog.config; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class AwsS3Config { + + @Value("${cloud.aws.credentials.access-key}") + private String accessKey; + + @Value("${cloud.aws.credentials.secret-key}") + private String secretKey; + + @Value("${cloud.aws.region.static}") + private String region; + + @Bean + public AmazonS3 amazonS3() { + // 자격증명 생성 + BasicAWSCredentials creds = new BasicAWSCredentials(accessKey, secretKey); + // 클라이언트 빌드 + return AmazonS3ClientBuilder.standard() + .withRegion(region) + .withCredentials(new AWSStaticCredentialsProvider(creds)) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/FixLog/controller/MypageMemberController.java b/src/main/java/com/example/FixLog/controller/MypageMemberController.java index 1a22473..630cea4 100644 --- a/src/main/java/com/example/FixLog/controller/MypageMemberController.java +++ b/src/main/java/com/example/FixLog/controller/MypageMemberController.java @@ -1,19 +1,22 @@ package com.example.FixLog.controller; +import com.example.FixLog.dto.PresignResponseDto; import com.example.FixLog.dto.Response; import com.example.FixLog.dto.member.edit.EditNicknameRequestDto; import com.example.FixLog.dto.member.edit.EditPasswordRequestDto; -import com.example.FixLog.dto.member.edit.EditProfileImageRequestDto; import com.example.FixLog.dto.member.edit.EditBioRequestDto; +import com.example.FixLog.exception.CustomException; +import com.example.FixLog.exception.ErrorCode; +import com.example.FixLog.service.S3Service; import com.example.FixLog.service.MemberService; import com.example.FixLog.domain.member.Member; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import jakarta.validation.Valid; -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.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; @RestController @RequiredArgsConstructor @@ -21,6 +24,7 @@ public class MypageMemberController { private final MemberService memberService; + private final S3Service s3Service; @PatchMapping("/members/nickname") public ResponseEntity> editNickname( @@ -40,13 +44,32 @@ public ResponseEntity> editPassword( return ResponseEntity.ok(Response.success("비밀번호 변경 성공", "SUCCESS")); } + @GetMapping("/members/profile-image/presign") + public ResponseEntity> presignProfileImage( + @AuthenticationPrincipal Member member, + @RequestParam String filename + ) { + if (member == null) throw new CustomException(ErrorCode.UNAUTHORIZED); + + String key = s3Service.generateKey("profile", filename); + String uploadUrl = s3Service.generatePresignedUrl("profile", filename, 15); + String fileUrl = s3Service.getObjectUrl(key); + + PresignResponseDto dto = new PresignResponseDto(uploadUrl, fileUrl); + return ResponseEntity.ok(Response.success("Presigned URL 발급 성공", dto)); + } + @PatchMapping("/members/profile-image") - public ResponseEntity> editProfileImage( - @RequestBody @Valid EditProfileImageRequestDto requestDto + public ResponseEntity> updateProfileImageUrl( + @AuthenticationPrincipal Member member, + @RequestBody Map body ) { - Member member = memberService.getCurrentMemberInfo(); - memberService.editProfileImage(member, requestDto.getProfileImageUrl()); - return ResponseEntity.ok(Response.success("프로필 이미지 수정 성공", "SUCCESS")); + String imageUrl = body.get("imageUrl"); + if (imageUrl == null || imageUrl.isBlank()) { + throw new CustomException(ErrorCode.INVALID_REQUEST); + } + memberService.editProfileImage(member, imageUrl); + return ResponseEntity.ok(Response.success("프로필 이미지 저장 성공", "SUCCESS")); } @PatchMapping("/members/bio") diff --git a/src/main/java/com/example/FixLog/dto/PresignResponseDto.java b/src/main/java/com/example/FixLog/dto/PresignResponseDto.java new file mode 100644 index 0000000..39ec68f --- /dev/null +++ b/src/main/java/com/example/FixLog/dto/PresignResponseDto.java @@ -0,0 +1,11 @@ +package com.example.FixLog.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class PresignResponseDto { + private final String uploadUrl; // PUT 전용 Presigned URL + private final String fileUrl; // public하게 접근 가능한 URL +} diff --git a/src/main/java/com/example/FixLog/exception/ErrorCode.java b/src/main/java/com/example/FixLog/exception/ErrorCode.java index 73518bb..61a45f4 100644 --- a/src/main/java/com/example/FixLog/exception/ErrorCode.java +++ b/src/main/java/com/example/FixLog/exception/ErrorCode.java @@ -25,7 +25,10 @@ public enum ErrorCode { INVALID_PASSWORD(HttpStatus.UNAUTHORIZED, "비밀번호가 일치하지 않습니다."), REQUIRED_TAGS_MISSING(HttpStatus.BAD_REQUEST, "태그를 선택해주세요."), REQUIRED_CONTENT_MISSING(HttpStatus.BAD_REQUEST, "필수 본문이 입력되지 않았습니다."), - SAME_AS_OLD_PASSWORD(HttpStatus.BAD_REQUEST, "다른 비밀번호 입력 바랍니다."); + SAME_AS_OLD_PASSWORD(HttpStatus.BAD_REQUEST, "다른 비밀번호 입력 바랍니다."), + UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "권한이 없습니다."), + INVALID_REQUEST(HttpStatus.BAD_REQUEST, "요청 데이터가 유효하지 않습니다."), + S3_UPLOAD_FAILED(HttpStatus.BAD_REQUEST, "S3 파일 업로드에 실패했습니다."); private final HttpStatus status; private final String message; diff --git a/src/main/java/com/example/FixLog/service/S3Service.java b/src/main/java/com/example/FixLog/service/S3Service.java new file mode 100644 index 0000000..afd6158 --- /dev/null +++ b/src/main/java/com/example/FixLog/service/S3Service.java @@ -0,0 +1,64 @@ +package com.example.FixLog.service; + +import com.amazonaws.HttpMethod; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.example.FixLog.exception.CustomException; +import com.example.FixLog.exception.ErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Date; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class S3Service { + + private final AmazonS3 amazonS3; + + @Value("${cloud.aws.s3.bucket}") + private String bucket; + + public String upload(MultipartFile file, String dirName) { + String key = generateKey(dirName, file.getOriginalFilename()); + + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentType(file.getContentType()); + metadata.setContentLength(file.getSize()); + + try (InputStream is = file.getInputStream()) { + amazonS3.putObject(bucket, key, is, metadata); + } catch (IOException e) { + throw new CustomException(ErrorCode.S3_UPLOAD_FAILED); + } + + return getObjectUrl(key); + } + + public String generateKey(String dirName, String filename) { + return dirName + "/" + UUID.randomUUID() + "_" + filename; + } + + public String generatePresignedUrl(String dirName, String filename, int minutes) { + String key = generateKey(dirName, filename); + Date expiration = new Date(System.currentTimeMillis() + minutes * 60L * 1000L); + + GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucket, key) + .withMethod(HttpMethod.PUT) + .withExpiration(expiration); + + URL url = amazonS3.generatePresignedUrl(request); + return url.toString(); + } + + public String getObjectUrl(String key) { + return amazonS3.getUrl(bucket, key).toString(); + } +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 211bbbc..a7bc12e 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,33 +1,43 @@ +# Correct property name spring.application.name=FixLog -# DB setting -spring.h2.console.enabled=true -''spring.h2.console.path=/h2-console - -# DataBase Info -spring.datasource.url=jdbc:h2:tcp://localhost/~/fixlog -spring.datasource.driver-class-name=org.h2.Driver -spring.datasource.username=sa -spring.datasource.password= - -spring.jpa.show-sql=true -spring.jpa.hibernate.ddl-auto=update -spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect - -//# JWT -//jwt.secret=fixlogfixlogfixlogfixlogfixlog1234 -//jwt.expiration-time=86400000 +## DB setting +#spring.h2.console.enabled=true +#spring.h2.console.path=/h2-console +# +## DataBase Info +#spring.datasource.url=jdbc:h2:tcp://localhost/~/fixlog +#spring.datasource.driver-class-name=org.h2.Driver +#spring.datasource.username=sa +#spring.datasource.password= +# +#spring.jpa.show-sql=true +#spring.jpa.hibernate.ddl-auto=update +#spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect ##### dev ##### -#server.port=8083 -# +server.port=8083 + #spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver #spring.datasource.url=${mysql_url} #spring.datasource.username=${mysql_username} #spring.datasource.password=${mysql_password} -# -#spring.jpa.hibernate.ddl-auto=create -#spring.jpa.properties.hibernate.format_sql=true -#spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect + +spring.datasource.url=jdbc:mysql://fixlog-db.c7cau8y2srl7.ap-northeast-2.rds.amazonaws.com:3306/fixlog?serverTimezone=Asia/Seoul +spring.datasource.username=admin +spring.datasource.password=${MYSQL_PASSWORD} + +spring.jpa.hibernate.ddl-auto=update +spring.jpa.show-sql=true +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect +spring.jpa.properties.hibernate.format_sql=true + +# AWS S3 configuration +cloud.aws.credentials.access-key=${AWS_ACCESS_KEY_ID} +cloud.aws.credentials.secret-key=${AWS_SECRET_ACCESS_KEY} +cloud.aws.region.static=${AWS_REGION} +cloud.aws.s3.bucket=${AWS_S3_BUCKET} ##### jwt ##### -spring.jwt.secret=${jwt_key} +jwt.secret=${jwt_key} + +logging.level.org.springframework.security=DEBUG From 55c7e03bfae2ca714d25d19d18e3635fc4ec7ccc Mon Sep 17 00:00:00 2001 From: sungchaewon Date: Wed, 11 Jun 2025 15:51:48 +0900 Subject: [PATCH 5/6] =?UTF-8?q?#52=20feat(profilePreview)=20:=20=EB=8B=89?= =?UTF-8?q?=EB=84=A4=EC=9E=84&=ED=94=84=EB=A1=9C=ED=95=84=EC=82=AC?= =?UTF-8?q?=EC=A7=84=EB=A7=8C=20=EC=A1=B0=ED=9A=8C=20API=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/FixLog/controller/MemberController.java | 11 +++++++++++ .../FixLog/dto/member/ProfilePreviewResponseDto.java | 11 +++++++++++ 2 files changed, 22 insertions(+) create mode 100644 src/main/java/com/example/FixLog/dto/member/ProfilePreviewResponseDto.java diff --git a/src/main/java/com/example/FixLog/controller/MemberController.java b/src/main/java/com/example/FixLog/controller/MemberController.java index d37068e..1e7c9f3 100644 --- a/src/main/java/com/example/FixLog/controller/MemberController.java +++ b/src/main/java/com/example/FixLog/controller/MemberController.java @@ -4,6 +4,7 @@ import com.example.FixLog.dto.Response; import com.example.FixLog.dto.WithdrawRequestDto; import com.example.FixLog.dto.member.MemberInfoResponseDto; +import com.example.FixLog.dto.member.ProfilePreviewResponseDto; import com.example.FixLog.dto.member.SignupRequestDto; import com.example.FixLog.dto.member.DuplicateCheckResponseDto; import com.example.FixLog.service.MemberService; @@ -51,6 +52,16 @@ public ResponseEntity> getMyInfo(@Authentication return ResponseEntity.ok(Response.success("회원 정보 조회 성공", responseDto)); } + @GetMapping("/profile-preview") + public ResponseEntity> getProfilePreview() { + Member member = memberService.getCurrentMemberInfo(); + ProfilePreviewResponseDto dto = new ProfilePreviewResponseDto( + member.getNickname(), + member.getProfileImageUrl() + ); + return ResponseEntity.ok(Response.success("닉네임&프로필사진 조회 성공", dto)); + } + @DeleteMapping("/me") public ResponseEntity> withdraw( @AuthenticationPrincipal Member member, diff --git a/src/main/java/com/example/FixLog/dto/member/ProfilePreviewResponseDto.java b/src/main/java/com/example/FixLog/dto/member/ProfilePreviewResponseDto.java new file mode 100644 index 0000000..17f8ec7 --- /dev/null +++ b/src/main/java/com/example/FixLog/dto/member/ProfilePreviewResponseDto.java @@ -0,0 +1,11 @@ +package com.example.FixLog.dto.member; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class ProfilePreviewResponseDto { + private String nickname; + private String profileImageUrl; +} From e40e4b7b25e8219b0d622cf28162e2375eb0a009 Mon Sep 17 00:00:00 2001 From: sungchaewon Date: Wed, 11 Jun 2025 16:03:50 +0900 Subject: [PATCH 6/6] =?UTF-8?q?#52=20fix(security)=20:=20=EB=B9=84?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=A0=91=EA=B7=BC=20=EA=B0=80?= =?UTF-8?q?=EB=8A=A5=20=EA=B2=BD=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/FixLog/config/SecurityConfig.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/example/FixLog/config/SecurityConfig.java b/src/main/java/com/example/FixLog/config/SecurityConfig.java index 4a7aaee..76d92ff 100644 --- a/src/main/java/com/example/FixLog/config/SecurityConfig.java +++ b/src/main/java/com/example/FixLog/config/SecurityConfig.java @@ -27,16 +27,21 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .authorizeHttpRequests(auth -> auth + // 비로그인 허용 경로 .requestMatchers(HttpMethod.POST, "/members/signup").permitAll() .requestMatchers(HttpMethod.POST, "/auth/login").permitAll() .requestMatchers(HttpMethod.GET, "/members/check-email").permitAll() .requestMatchers(HttpMethod.GET, "/members/check-nickname").permitAll() + .requestMatchers(HttpMethod.GET, "/search/**").permitAll() + .requestMatchers(HttpMethod.GET, "/posts/**").permitAll() + // h2-console (로컬 테스트용) .requestMatchers(HttpMethod.GET, "/h2-console/**").permitAll() - //배포 확인용 임시 수정 + // 배포 확인용 임시 허용 .requestMatchers(HttpMethod.GET, "/test", "/test/**").permitAll() + // 그 외 모든 요청은 인증 필요 .anyRequest().authenticated() ) - .headers(headers -> headers.frameOptions(frame -> frame.disable())) // H2 콘솔용 + .headers(headers -> headers.frameOptions(frame -> frame.disable())) // H2 콘솔 .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); @@ -52,9 +57,8 @@ public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } - // 인증 매니저 (선택: 로그인 시 AuthenticationManager 사용 가능) @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { return config.getAuthenticationManager(); } -} +} \ No newline at end of file