Skip to content

Commit 5b3d3a9

Browse files
authored
Merge pull request #82 from NOVA-9th/develop
[CICD] 26.02.08 develop 브랜치 배포
2 parents 5072e83 + 496406c commit 5b3d3a9

8 files changed

Lines changed: 148 additions & 114 deletions

File tree

src/main/java/com/nova/nova_server/domain/cardNews/entity/CardNews.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,29 +20,29 @@ public class CardNews extends BaseEntity {
2020
@GeneratedValue(strategy = GenerationType.IDENTITY)
2121
private Long id;
2222

23-
@Column
23+
@Column(nullable = false)
2424
@Enumerated(EnumType.STRING)
2525
private CardType cardType;
2626

2727
@Column(nullable = false, length = 255)
2828
private String title;
2929

30-
@Column(length = 100)
30+
@Column(length = 100, nullable = true)
3131
private String author;
3232

33-
@Column(name = "published_at")
33+
@Column(name = "published_at", nullable = true)
3434
private LocalDateTime publishedAt;
3535

36-
@Column(columnDefinition = "TEXT")
36+
@Column(columnDefinition = "TEXT", nullable = false)
3737
private String summary;
3838

39-
@Column(columnDefinition = "TEXT")
39+
@Column(columnDefinition = "TEXT", nullable = false)
4040
private String evidence;
4141

42-
@Column(name = "original_url", length = 2048)
42+
@Column(name = "original_url", length = 2048, nullable = false)
4343
private String originalUrl;
4444

45-
@Column(name = "source_site_name", length = 100)
45+
@Column(name = "source_site_name", length = 100, nullable = true)
4646
private String sourceSiteName;
4747

4848
@OneToMany(mappedBy = "cardNews", fetch = FetchType.LAZY)

src/main/java/com/nova/nova_server/domain/feed/converter/FeedConverter.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
import java.time.ZoneOffset;
1616
import java.util.List;
1717

18+
import static java.lang.Math.min;
19+
1820
@Component
1921
@RequiredArgsConstructor
2022
public class FeedConverter {
@@ -69,14 +71,16 @@ private LocalDateTime toUtcLocalDateTime(OffsetDateTime offsetDateTime) {
6971
}
7072

7173
private Pageable toPageable(Integer page, Integer size) {
72-
if (page == null || page < 0) {
73-
page = 0;
74+
if (page == null || page < 1) {
75+
page = 1;
7476
}
75-
if (size == null || size <= 0) {
77+
if (size == null || size < 1) {
7678
size = feedConfig.getMaxPageSize();
79+
} else {
80+
size = min(size, feedConfig.getMaxPageSize());
7781
}
7882

79-
return PageRequest.of(page, size);
83+
return PageRequest.of(page - 1, size);
8084
}
8185

8286
}

src/main/java/com/nova/nova_server/domain/feed/dto/FeedRequest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ public record FeedRequest(
1818
@Schema(description = "업로드 기간 (종료), UTC 기준, ISO 8601 형식, 경계값 미포함", example = "2026-01-31T15:00:00Z")
1919
OffsetDateTime endDate,
2020

21-
@Schema(description = "카드 유형, NEWS, JOB, COMMUNITY 중 0개 이상 선택", example = "[\"NEWS\", \"COMMUNITY\"]")
21+
@Schema(description = "카드 유형, NEWS, JOB, COMMUNITY 중 0개 이상 선택", example = "NEWS")
2222
List<CardType> type,
2323

24-
@Schema(description = "키워드 필터", example = "[\"Spring Boot\", \"React\"]")
24+
@Schema(description = "키워드 필터", example = "Spring Boot")
2525
List<String> keywords,
2626

2727
@Schema(description = "페이지 번호", example = "1", defaultValue = "1")

src/main/java/com/nova/nova_server/domain/member/controller/MemberInfoController.java

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,48 @@
11
package com.nova.nova_server.domain.member.controller;
22

3-
import com.nova.nova_server.domain.member.dto.MemberConnectedAccountsResponseDto;
4-
import com.nova.nova_server.domain.member.dto.MemberPersonalizationDto;
5-
import com.nova.nova_server.domain.member.dto.MemberRequestDto;
6-
import com.nova.nova_server.domain.member.dto.MemberResponseDto;
7-
import com.nova.nova_server.domain.member.dto.MemberUpdateResponseDto;
3+
import com.nova.nova_server.domain.member.dto.*;
84
import com.nova.nova_server.domain.member.service.MemberService;
95
import com.nova.nova_server.global.apiPayload.ApiResponse;
6+
import io.swagger.v3.oas.annotations.Operation;
7+
import io.swagger.v3.oas.annotations.Parameter;
8+
import io.swagger.v3.oas.annotations.tags.Tag;
109
import lombok.RequiredArgsConstructor;
1110
import org.springframework.security.core.annotation.AuthenticationPrincipal;
1211
import org.springframework.web.bind.annotation.*;
1312

1413
@RestController
1514
@RequestMapping("/api/members")
1615
@RequiredArgsConstructor
16+
@Tag(name = "Member", description = "사용자 정보 및 설정 관련 API")
1717
public class MemberInfoController {
18+
1819
private final MemberService memberService;
1920

20-
// 사용자 정보 조회
21+
@Operation(summary = "사용자 정보 조회", description = "특정 사용자의 기본 프로필 정보를 조회합니다.")
2122
@GetMapping("/{member_id}")
2223
public ApiResponse<MemberResponseDto> getMemberInfo(
24+
@Parameter(description = "조회할 사용자의 ID", example = "1")
2325
@PathVariable("member_id") Long memberId) {
2426

2527
MemberResponseDto response = memberService.getMemberInfo(memberId);
2628
return ApiResponse.success(response);
2729
}
2830

31+
@Operation(summary = "회원 탈퇴", description = "사용자 계정을 삭제합니다.")
2932
@DeleteMapping("/{member_id}")
3033
public ApiResponse<Void> deleteMember(
31-
@PathVariable("member_id") Long memberId,
32-
@AuthenticationPrincipal Long authenticatedMemberId
33-
) {
34+
@Parameter(description = "삭제할 사용자의 ID")
35+
@PathVariable("member_id") Long memberId,
36+
@AuthenticationPrincipal Long authenticatedMemberId) {
37+
3438
memberService.deleteMember(memberId);
3539
return ApiResponse.success(null);
3640
}
3741

38-
// 사용자 정보 수정(이름만 수정 가능)
42+
@Operation(summary = "사용자 이름 수정", description = "사용자의 이름을 수정합니다. (현재 이름만 수정 가능)")
3943
@PatchMapping("/{member_id}")
4044
public ApiResponse<MemberUpdateResponseDto> updateMemberName(
45+
@Parameter(description = "수정할 사용자의 ID")
4146
@PathVariable("member_id") Long memberId,
4247
@AuthenticationPrincipal Long authenticatedMemberId,
4348
@RequestBody MemberRequestDto requestDto) {
@@ -46,23 +51,31 @@ public ApiResponse<MemberUpdateResponseDto> updateMemberName(
4651
return ApiResponse.success(response);
4752
}
4853

54+
@Operation(summary = "개인화 설정 조회", description = "관심 키워드 등 사용자의 개인화 설정 정보를 조회합니다.")
4955
@GetMapping("/{memberId}/personalization")
50-
public ApiResponse<MemberPersonalizationDto> getPersonalization(@PathVariable Long memberId) {
56+
public ApiResponse<MemberPersonalizationDto> getPersonalization(
57+
@Parameter(description = "사용자 ID")
58+
@PathVariable Long memberId) {
59+
5160
MemberPersonalizationDto personalization = memberService.getMemberPersonalization(memberId);
5261
return ApiResponse.success(personalization);
5362
}
5463

64+
@Operation(summary = "개인화 설정 수정", description = "사용자의 관심사나 개인화 설정 정보를 업데이트합니다.")
5565
@PutMapping("/{memberId}/personalization")
5666
public ApiResponse<Void> updatePersonalization(
67+
@Parameter(description = "사용자 ID")
5768
@PathVariable Long memberId,
5869
@RequestBody MemberPersonalizationDto request) {
70+
5971
memberService.updateMemberPersonalization(memberId, request);
6072
return ApiResponse.success(null);
6173
}
6274

63-
//연결된 계정 조회
75+
@Operation(summary = "연결된 계정 조회", description = "소셜 로그인 등 사용자와 연결된 계정 정보를 조회합니다.")
6476
@GetMapping("/{member_id}/connected-accounts")
6577
public ApiResponse<MemberConnectedAccountsResponseDto> getConnectedAccounts(
78+
@Parameter(description = "사용자 ID")
6679
@PathVariable("member_id") Long memberId) {
6780

6881
MemberConnectedAccountsResponseDto response = memberService.getConnectedAccounts(memberId);

src/main/java/com/nova/nova_server/domain/member/controller/MemberProfileImageController.java

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,15 @@
22

33
import com.nova.nova_server.domain.member.service.MemberProfileImageService;
44
import com.nova.nova_server.global.apiPayload.ApiResponse;
5+
import io.swagger.v3.oas.annotations.Operation;
6+
import io.swagger.v3.oas.annotations.Parameter;
7+
import io.swagger.v3.oas.annotations.media.Content;
8+
import io.swagger.v3.oas.annotations.media.Schema;
9+
import io.swagger.v3.oas.annotations.tags.Tag;
510
import lombok.RequiredArgsConstructor;
611
import org.springframework.http.MediaType;
712
import org.springframework.http.ResponseEntity;
8-
import org.springframework.web.bind.annotation.DeleteMapping;
9-
import org.springframework.web.bind.annotation.GetMapping;
10-
import org.springframework.web.bind.annotation.PathVariable;
11-
import org.springframework.web.bind.annotation.PostMapping;
12-
import org.springframework.web.bind.annotation.RequestMapping;
13-
import org.springframework.web.bind.annotation.RequestParam;
14-
import org.springframework.web.bind.annotation.RestController;
13+
import org.springframework.web.bind.annotation.*;
1514
import org.springframework.web.multipart.MultipartFile;
1615

1716
import java.io.IOException;
@@ -20,41 +19,61 @@
2019
@RestController
2120
@RequestMapping("/api/members")
2221
@RequiredArgsConstructor
22+
@Tag(name = "Member Profile Image", description = "사용자 프로필 이미지 관련 API")
2323
public class MemberProfileImageController {
2424

2525
private final MemberProfileImageService memberProfileImageService;
2626

27-
// 프로필 이미지 업로드 (수정도 가능)
27+
@Operation(
28+
summary = "프로필 이미지 업로드 및 수정",
29+
description = "사용자의 프로필 이미지를 업로드합니다. 이미 이미지가 존재하는 경우 새 이미지로 교체됩니다."
30+
)
2831
@PostMapping(value = "/{memberId}/profile-image", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
2932
public ApiResponse<Void> uploadProfileImage(
33+
@Parameter(description = "이미지를 업로드할 사용자의 ID", example = "1")
3034
@PathVariable Long memberId,
35+
@Parameter(
36+
description = "업로드할 이미지 파일 (MultipartFile)",
37+
content = @Content(mediaType = MediaType.MULTIPART_FORM_DATA_VALUE)
38+
)
3139
@RequestParam("file") MultipartFile file) throws IOException {
3240

3341
memberProfileImageService.uploadProfileImageRaw(memberId, file);
34-
3542
return ApiResponse.success(null);
3643
}
3744

38-
// 프로필 이미지 조회
45+
@Operation(
46+
summary = "프로필 이미지 조회",
47+
description = "사용자의 프로필 이미지를 바이트 배열 형태로 직접 조회합니다."
48+
)
3949
@GetMapping("/{memberId}/profile-image")
40-
public ResponseEntity<byte[]> getProfileImage(@PathVariable Long memberId) {
50+
public ResponseEntity<byte[]> getProfileImage(
51+
@Parameter(description = "이미지를 조회할 사용자의 ID", example = "1")
52+
@PathVariable Long memberId) {
53+
4154
Optional<byte[]> imageBytes = memberProfileImageService.getProfileImageRaw(memberId);
4255

4356
return imageBytes
44-
.map(bytes -> createJpegResponse(bytes))
57+
.map(this::createJpegResponse)
4558
.orElseGet(() -> ResponseEntity.notFound().build());
4659
}
4760

48-
private ResponseEntity<byte[]> createJpegResponse(byte[] jpeg) {
49-
return ResponseEntity.ok()
50-
.contentType(MediaType.IMAGE_JPEG)
51-
.body(jpeg);
52-
}
53-
54-
// 프로필 이미지 삭제
61+
@Operation(
62+
summary = "프로필 이미지 삭제",
63+
description = "사용자의 프로필 이미지를 서버에서 삭제합니다."
64+
)
5565
@DeleteMapping("/{memberId}/profile-image")
56-
public ApiResponse<Void> deleteProfileImage(@PathVariable Long memberId) {
66+
public ApiResponse<Void> deleteProfileImage(
67+
@Parameter(description = "이미지를 삭제할 사용자의 ID", example = "1")
68+
@PathVariable Long memberId) {
69+
5770
memberProfileImageService.deleteProfileImage(memberId);
5871
return ApiResponse.success(null);
5972
}
60-
}
73+
74+
private ResponseEntity<byte[]> createJpegResponse(byte[] jpeg) {
75+
return ResponseEntity.ok()
76+
.contentType(MediaType.IMAGE_JPEG)
77+
.body(jpeg);
78+
}
79+
}

src/main/java/com/nova/nova_server/domain/member/service/MemberService.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ public void updateMemberPersonalization(Long memberId, MemberPersonalizationDto
115115

116116
private void updateMemberInterests(Member member, List<Long> interestIds) {
117117
memberPreferInterestRepository.deleteByMember(member);
118+
memberPreferInterestRepository.flush();
118119

119120
if (interestIds == null || interestIds.isEmpty()) {
120121
return;
@@ -137,6 +138,7 @@ private void updateMemberInterests(Member member, List<Long> interestIds) {
137138

138139
private void updateMemberKeywords(Member member, List<String> keywordNames) {
139140
memberPreferKeywordRepository.deleteByMember(member);
141+
memberPreferKeywordRepository.flush();
140142

141143
if (keywordNames == null || keywordNames.isEmpty()) {
142144
return;
@@ -182,17 +184,17 @@ public MemberConnectedAccountsResponseDto getConnectedAccounts(Long memberId) {
182184
.build();
183185
}
184186

187+
@Transactional
185188
public MemberResponseDto createTestMember() {
186189
Member mockUser = Member.builder()
187-
.id(2L)
188190
.name("테스트 유저")
189191
.email("test@example.com")
190192
.level(Member.MemberLevel.NOVICE)
191193
.background("테스트 배경")
192194
.googleId("test-google-id")
193195
.build();
194196

195-
memberRepository.save(mockUser);
196-
return getMemberInfo(2L);
197+
Member savedUser = memberRepository.save(mockUser);
198+
return getMemberInfo(savedUser.getId());
197199
}
198200
}

0 commit comments

Comments
 (0)