Skip to content

Commit 6d604ed

Browse files
authored
Merge pull request #63 from FixLog/feature/#52-editUserProfile-cw
[FEAT] 회원 정보 수정
2 parents e4ac922 + ff5d805 commit 6d604ed

26 files changed

+548
-109
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,7 @@ out/
3535

3636
### VS Code ###
3737
.vscode/
38+
39+
### Environment variables ###
40+
.env
41+
*.env

build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ dependencies {
2727
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
2828
implementation 'org.springframework.boot:spring-boot-starter-security'
2929
implementation 'org.springframework.boot:spring-boot-starter-web'
30+
implementation 'org.springframework.boot:spring-boot-starter-validation'
3031
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
3132
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
3233
runtimeOnly('io.jsonwebtoken:jjwt-jackson:0.11.5')
@@ -36,6 +37,7 @@ dependencies {
3637
annotationProcessor 'org.projectlombok:lombok'
3738
testImplementation 'org.springframework.boot:spring-boot-starter-test'
3839
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
40+
implementation 'com.amazonaws:aws-java-sdk-s3:1.12.538'
3941
}
4042

4143
tasks.named('test') {

src/main/java/com/example/FixLog/FixLogApplication.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
import org.springframework.boot.SpringApplication;
44
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
56

7+
//Create_At 어노테이션
8+
@EnableJpaAuditing
69
@SpringBootApplication
710
public class FixLogApplication {
811

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.example.FixLog.config;
2+
3+
import com.amazonaws.auth.AWSStaticCredentialsProvider;
4+
import com.amazonaws.auth.BasicAWSCredentials;
5+
import com.amazonaws.services.s3.AmazonS3;
6+
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
7+
import org.springframework.beans.factory.annotation.Value;
8+
import org.springframework.context.annotation.Bean;
9+
import org.springframework.context.annotation.Configuration;
10+
11+
@Configuration
12+
public class AwsS3Config {
13+
14+
@Value("${cloud.aws.credentials.access-key}")
15+
private String accessKey;
16+
17+
@Value("${cloud.aws.credentials.secret-key}")
18+
private String secretKey;
19+
20+
@Value("${cloud.aws.region.static}")
21+
private String region;
22+
23+
@Bean
24+
public AmazonS3 amazonS3() {
25+
// 자격증명 생성
26+
BasicAWSCredentials creds = new BasicAWSCredentials(accessKey, secretKey);
27+
// 클라이언트 빌드
28+
return AmazonS3ClientBuilder.standard()
29+
.withRegion(region)
30+
.withCredentials(new AWSStaticCredentialsProvider(creds))
31+
.build();
32+
}
33+
}

src/main/java/com/example/FixLog/config/SecurityConfig.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,22 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
3232
.authorizeHttpRequests(auth -> auth
3333
.requestMatchers(HttpMethod.GET, "/auth/**").permitAll()
3434
.requestMatchers(HttpMethod.POST, "/auth/**").permitAll()
35+
3536
.requestMatchers(HttpMethod.POST, "/members/signup").permitAll()
3637
.requestMatchers(HttpMethod.GET, "/members/check-email").permitAll()
3738
.requestMatchers(HttpMethod.GET, "/members/check-nickname").permitAll()
39+
.requestMatchers(HttpMethod.GET, "/search/**").permitAll()
40+
.requestMatchers(HttpMethod.GET, "/posts/**").permitAll()
41+
// h2-console (로컬 테스트용)
3842
.requestMatchers(HttpMethod.GET, "/h2-console/**").permitAll()
43+
// 배포 확인용 임시 허용
44+
.requestMatchers(HttpMethod.GET, "/test", "/test/**").permitAll()
45+
// 그 외 모든 요청은 인증 필요
3946
.requestMatchers(HttpMethod.GET, "/test", "/test/**").permitAll() // 테스트용 허용
47+
4048
.anyRequest().authenticated()
4149
)
42-
.headers(headers -> headers.frameOptions(frame -> frame.disable())) // H2 콘솔용
50+
.headers(headers -> headers.frameOptions(frame -> frame.disable())) // H2 콘솔
4351
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
4452

4553
return http.build();
@@ -59,4 +67,4 @@ public PasswordEncoder passwordEncoder() {
5967
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
6068
return config.getAuthenticationManager();
6169
}
62-
}
70+
}

src/main/java/com/example/FixLog/controller/MemberController.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
import com.example.FixLog.domain.member.Member;
44
import com.example.FixLog.dto.Response;
5+
import com.example.FixLog.dto.WithdrawRequestDto;
56
import com.example.FixLog.dto.member.MemberInfoResponseDto;
7+
import com.example.FixLog.dto.member.ProfilePreviewResponseDto;
68
import com.example.FixLog.dto.member.SignupRequestDto;
79
import com.example.FixLog.dto.member.DuplicateCheckResponseDto;
810
import com.example.FixLog.service.MemberService;
@@ -50,9 +52,22 @@ public ResponseEntity<Response<MemberInfoResponseDto>> getMyInfo(@Authentication
5052
return ResponseEntity.ok(Response.success("회원 정보 조회 성공", responseDto));
5153
}
5254

55+
@GetMapping("/profile-preview")
56+
public ResponseEntity<Response<ProfilePreviewResponseDto>> getProfilePreview() {
57+
Member member = memberService.getCurrentMemberInfo();
58+
ProfilePreviewResponseDto dto = new ProfilePreviewResponseDto(
59+
member.getNickname(),
60+
member.getProfileImageUrl()
61+
);
62+
return ResponseEntity.ok(Response.success("닉네임&프로필사진 조회 성공", dto));
63+
}
64+
5365
@DeleteMapping("/me")
54-
public ResponseEntity<Response<Void>> withdraw(@AuthenticationPrincipal Member member) {
55-
memberService.withdraw(member);
66+
public ResponseEntity<Response<Void>> withdraw(
67+
@AuthenticationPrincipal Member member,
68+
@RequestBody WithdrawRequestDto request
69+
) {
70+
memberService.withdraw(member, request.getPassword());
5671
return ResponseEntity.ok(Response.success("회원 탈퇴 성공", null));
5772
}
5873
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package com.example.FixLog.controller;
2+
3+
import com.example.FixLog.dto.PresignResponseDto;
4+
import com.example.FixLog.dto.Response;
5+
import com.example.FixLog.dto.member.edit.EditNicknameRequestDto;
6+
import com.example.FixLog.dto.member.edit.EditPasswordRequestDto;
7+
import com.example.FixLog.dto.member.edit.EditBioRequestDto;
8+
import com.example.FixLog.exception.CustomException;
9+
import com.example.FixLog.exception.ErrorCode;
10+
import com.example.FixLog.service.S3Service;
11+
import com.example.FixLog.service.MemberService;
12+
import com.example.FixLog.domain.member.Member;
13+
import lombok.RequiredArgsConstructor;
14+
import org.springframework.http.ResponseEntity;
15+
import jakarta.validation.Valid;
16+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
17+
import org.springframework.web.bind.annotation.*;
18+
19+
import java.util.Map;
20+
21+
@RestController
22+
@RequiredArgsConstructor
23+
@RequestMapping("/mypage")
24+
public class MypageMemberController {
25+
26+
private final MemberService memberService;
27+
private final S3Service s3Service;
28+
29+
@PatchMapping("/members/nickname")
30+
public ResponseEntity<Response<String>> editNickname(
31+
@RequestBody @Valid EditNicknameRequestDto requestDto
32+
) {
33+
Member member = memberService.getCurrentMemberInfo();
34+
memberService.editNickname(member, requestDto.getNickname());
35+
return ResponseEntity.ok(Response.success("닉네임 수정 성공", "SUCCESS"));
36+
}
37+
38+
@PatchMapping("/members/password")
39+
public ResponseEntity<Response<String>> editPassword(
40+
@RequestBody @Valid EditPasswordRequestDto requestDto
41+
) {
42+
Member member = memberService.getCurrentMemberInfo();
43+
memberService.editPassword(member, requestDto);
44+
return ResponseEntity.ok(Response.success("비밀번호 변경 성공", "SUCCESS"));
45+
}
46+
47+
@GetMapping("/members/profile-image/presign")
48+
public ResponseEntity<Response<PresignResponseDto>> presignProfileImage(
49+
@AuthenticationPrincipal Member member,
50+
@RequestParam String filename
51+
) {
52+
if (member == null) throw new CustomException(ErrorCode.UNAUTHORIZED);
53+
54+
String key = s3Service.generateKey("profile", filename);
55+
String uploadUrl = s3Service.generatePresignedUrl("profile", filename, 15);
56+
String fileUrl = s3Service.getObjectUrl(key);
57+
58+
PresignResponseDto dto = new PresignResponseDto(uploadUrl, fileUrl);
59+
return ResponseEntity.ok(Response.success("Presigned URL 발급 성공", dto));
60+
}
61+
62+
@PatchMapping("/members/profile-image")
63+
public ResponseEntity<Response<String>> updateProfileImageUrl(
64+
@AuthenticationPrincipal Member member,
65+
@RequestBody Map<String, String> body
66+
) {
67+
String imageUrl = body.get("imageUrl");
68+
if (imageUrl == null || imageUrl.isBlank()) {
69+
throw new CustomException(ErrorCode.INVALID_REQUEST);
70+
}
71+
memberService.editProfileImage(member, imageUrl);
72+
return ResponseEntity.ok(Response.success("프로필 이미지 저장 성공", "SUCCESS"));
73+
}
74+
75+
@PatchMapping("/members/bio")
76+
public ResponseEntity<Response<String>> editBio(
77+
@RequestBody @Valid EditBioRequestDto requestDto
78+
) {
79+
Member member = memberService.getCurrentMemberInfo();
80+
memberService.editBio(member, requestDto.getBio());
81+
return ResponseEntity.ok(Response.success("소개글 수정 성공", "SUCCESS"));
82+
}
83+
}

src/main/java/com/example/FixLog/controller/MypagePostController.java

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,18 @@ public ResponseEntity<Response<PageResponseDto<MyPostPageResponseDto>>> getMyPos
3333
return ResponseEntity.ok(Response.success("내가 작성한 글 보기 성공", data));
3434
}
3535

36-
// 내가 좋아요한 글
37-
@GetMapping("/likes")
38-
public ResponseEntity<Response<PageResponseDto<MyPostPageResponseDto>>> getLikedPosts(
39-
@AuthenticationPrincipal UserDetails userDetails,
40-
@RequestParam(defaultValue = "0") int page,
41-
@RequestParam(defaultValue = "4") int size,
42-
@RequestParam(defaultValue = "0") int sort) {
43-
44-
String email = userDetails.getUsername();
45-
PageResponseDto<MyPostPageResponseDto> result = mypagePostService.getLikedPosts(email, page, sort, size);
46-
return ResponseEntity.ok(Response.success("내가 좋아요한 글 보기 성공", result));
47-
}
36+
// // 내가 좋아요한 글
37+
// @GetMapping("/likes")
38+
// public ResponseEntity<Response<PageResponseDto<MyPostPageResponseDto>>> getLikedPosts(
39+
// @AuthenticationPrincipal UserDetails userDetails,
40+
// @RequestParam(defaultValue = "0") int page,
41+
// @RequestParam(defaultValue = "4") int size,
42+
// @RequestParam(defaultValue = "0") int sort) {
43+
//
44+
// String email = userDetails.getUsername();
45+
// PageResponseDto<MyPostPageResponseDto> result = mypagePostService.getLikedPosts(email, page, sort, size);
46+
// return ResponseEntity.ok(Response.success("내가 좋아요한 글 보기 성공", result));
47+
// }
4848

4949

5050
}

src/main/java/com/example/FixLog/domain/member/Member.java

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,6 @@ public class Member implements UserDetails {
4141
@Column(nullable = false)
4242
private Boolean isDeleted = false;
4343

44-
public void setIsDeleted(boolean isDeleted) {
45-
this.isDeleted = isDeleted;
46-
}
47-
4844
@Enumerated(EnumType.STRING)
4945
@Column(nullable = false)
5046
private SocialType socialType = SocialType.EMAIL;
@@ -57,68 +53,84 @@ public void setIsDeleted(boolean isDeleted) {
5753
@Column
5854
private LocalDateTime updatedAt;
5955

60-
// 프로필 사진 url, 지금은 nullable 이지만 나중에 기본값 설정
56+
// 프로필 사진 URL
6157
@Column
6258
private String profileImageUrl;
6359

6460
@Column(length = 200)
6561
private String bio;
6662

63+
// 게시글 연관관계
6764
@OneToMany(mappedBy = "userId", cascade = CascadeType.ALL, orphanRemoval = true)
6865
private List<Post> posts = new ArrayList<>();
6966

70-
// 북마크 폴더
67+
// 북마크 폴더 (계정당 1개)
7168
@OneToOne(mappedBy = "userId", cascade = CascadeType.ALL, orphanRemoval = true)
72-
private BookmarkFolder bookmarkFolderId;
73-
// 우선은 계정 당 폴더 하나만 있는 걸로 생성
74-
// @OneToMany(mappedBy = "userId", cascade = CascadeType.ALL, orphanRemoval = true)
75-
// private List<BookmarkFolder> bookmarkFolders = new ArrayList<>();
76-
77-
// Member 객체를 정적 팩토리 방식으로 회원가입 시에 생성하는 메서드
78-
// Member 객체를 정적 팩토리 방식으로 생성하는 메서드
79-
// Creates a Member object using a static factory method
69+
private BookmarkFolder bookmarkFolder;
70+
71+
// 정적 팩토리 메서드
8072
public static Member of(String email, String password, String nickname, SocialType socialType) {
8173
Member member = new Member();
8274
member.email = email;
8375
member.password = password;
8476
member.nickname = nickname;
8577
member.socialType = socialType;
8678
member.isDeleted = false;
87-
member.profileImageUrl = "https://dummyimage.com/200x200/cccccc/ffffff&text=Profile"; // 기본 프로필 이미지(임시)
79+
member.profileImageUrl = null; // 기본 이미지는 응답 시 처리
8880
return member;
8981
}
9082

91-
public void setProfileImageUrl(String profileImageUrl) {
92-
this.profileImageUrl = profileImageUrl;
83+
// -------------------- 도메인 메서드 --------------------
84+
85+
public void updateNickname(String nickname) {
86+
this.nickname = nickname;
9387
}
9488

89+
public void updatePassword(String encodedPassword) {
90+
this.password = encodedPassword;
91+
}
92+
93+
public void updateProfileImage(String url) {
94+
this.profileImageUrl = url;
95+
}
96+
97+
public void updateBio(String bio) {
98+
this.bio = bio;
99+
}
100+
101+
public void markAsDeleted() {
102+
this.isDeleted = true;
103+
}
104+
105+
// -------------------- Spring Security --------------------
106+
95107
@Override
96108
public Collection<? extends GrantedAuthority> getAuthorities() {
97109
return List.of(new SimpleGrantedAuthority("ROLE_USER")); // 기본 권한
98110
}
99111

100112
@Override
101113
public String getUsername() {
102-
return this.email; // 로그인 시 사용할 사용자 식별자
114+
return this.email; // 로그인 시 사용할 식별자
103115
}
104116

105117
@Override
106118
public boolean isAccountNonExpired() {
107-
return true; // 계정 만료 여부 (true = 사용 가능)
119+
return true; // 계정 만료 안 됨
108120
}
109121

110122
@Override
111123
public boolean isAccountNonLocked() {
112-
return true; // 계정 잠금 여부 (true = 잠금 아님)
124+
return true; // 잠금 아님
113125
}
114126

115127
@Override
116128
public boolean isCredentialsNonExpired() {
117-
return true; // 비밀번호 만료 여부
129+
return true; // 비밀번호 만료 안 됨
118130
}
119131

120132
@Override
121133
public boolean isEnabled() {
122-
return !this.isDeleted; // 탈퇴 여부 기반 활성 상태
134+
return !this.isDeleted; // 탈퇴 계정은 비활성화
123135
}
124136
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.example.FixLog.dto;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Getter;
5+
6+
@Getter
7+
@AllArgsConstructor
8+
public class PresignResponseDto {
9+
private final String uploadUrl; // PUT 전용 Presigned URL
10+
private final String fileUrl; // public하게 접근 가능한 URL
11+
}

0 commit comments

Comments
 (0)