Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
d45dfb0
Merge pull request #17 from prgrms-web-devcourse-final-project/develop
sapiens2000 Mar 30, 2025
41393ec
Merge pull request #25 from prgrms-web-devcourse-final-project/develop
sapiens2000 Apr 1, 2025
2e67b7a
Merge pull request #27 from prgrms-web-devcourse-final-project/develop
sapiens2000 Apr 2, 2025
1845269
fix: Enum 타입 유효성 검증 오류 해결(NotBlank -> NotNull)
TTaiJin Apr 2, 2025
195d3f5
feat: MyPage 용 컨트롤러 클래스 추가
InJunKangW Mar 31, 2025
239fc75
feat: 내 팔로우 / 팔로잉 조회 로직 구현
InJunKangW Mar 31, 2025
b71c57b
feat: 내 팔로우 / 팔로잉 조회 및 내가 작성한 다이어리 페이지 조회 컨트롤러 매핑 완료
InJunKangW Mar 31, 2025
d1cde30
feat: 내가 좋아요 남긴 다이어리 조회 기능 추가
InJunKangW Apr 1, 2025
52d6b8a
feat: 리베이스 이후, 좋아요 페이지 조회 기능 추가
InJunKangW Apr 2, 2025
4e2ca6e
feat: 구독 여부 조회용 DTO 생성
InJunKangW Apr 3, 2025
2579995
feat: 구독 엔티티 생성 (결제 로직이 어떻게 붙을 지 몰라 간략히 설정)
InJunKangW Apr 3, 2025
3d1e845
feat: 구독 엔티티 생성 (결제 로직이 어떻게 붙을 지 몰라 간략히 설정)
InJunKangW Apr 3, 2025
744e7ca
feat: 내 구독 조회 기능 생성
InJunKangW Apr 3, 2025
ca39cfe
refactor: Subscription 엔티티 수정 및 Mypage의 구독 정보 조회 기능 리팩토링
InJunKangW Apr 3, 2025
666b993
test: MyPageService 테스트 작성
InJunKangW Apr 5, 2025
2bb5977
refactor: 내 다이어리 조회 시 공개 범위를 파라티머로 받도록 변경
InJunKangW Apr 7, 2025
a8084a4
refactor: requestParam 네임 명시
InJunKangW Apr 7, 2025
88de81d
refacotr: Auth에서 id 받아오도록 변경
InJunKangW Apr 7, 2025
f7c45bf
test: 실패하던 내 다이어리 조회 테스트 수정
InJunKangW Apr 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,15 @@
import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;

@SuperBuilder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PACKAGE)
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
Expand All @@ -21,6 +28,5 @@ abstract public class BaseEntity {
private LocalDateTime createdAt;

@LastModifiedDate
@Column(nullable = false)
private LocalDateTime updatedAt;
}
20 changes: 20 additions & 0 deletions src/main/java/com/example/log4u/common/util/PageableUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.example.log4u.common.util;

import java.util.List;

import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl;

public class PageableUtil {
public static <T> Slice<T> checkAndCreateSlice(List<T> content, Pageable pageable) {
boolean hasNext = content.size() > pageable.getPageSize();

// 다음 페이지가 있으면 마지막 항목 제거
if (hasNext) {
content.remove(content.size() - 1); // removeLast() 대신 인덱스로 처리
}

return new SliceImpl<>(content, pageable, hasNext);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.example.log4u.domain.media.dto.MediaRequestDto;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

public record DiaryRequestDto(
@NotBlank(message = "제목은 필수입니다.")
Expand All @@ -17,7 +18,7 @@ public record DiaryRequestDto(
Double latitude,
Double longitude,
WeatherInfo weatherInfo,
@NotBlank(message = "공개 범위는 필수입니다.")
@NotNull(message = "공개 범위는 필수입니다.")
VisibilityType visibility,
List<MediaRequestDto> mediaList
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,10 @@ Slice<Diary> findByUserIdAndVisibilityInAndCursorId(
Long cursorId,
Pageable pageable
);

Slice<Diary> getLikeDiarySliceByUserId(
Copy link
Collaborator

Choose a reason for hiding this comment

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

코드 일관성을 위해 서비스 층에서는 get~~ Repository층에서는 find~~ 메서드명을 이런식으로 맞추자라는 의견이 있었던 것 같은데, 혹시 맞을까요?

맞다면 메서드명 통일 시키는 것은 어떻게 생각하시나요?

Long userId,
List<VisibilityType> visibilities,
Long cursorId,
Pageable pageable);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.example.log4u.domain.diary.VisibilityType;
import com.example.log4u.domain.diary.entity.Diary;
import com.example.log4u.domain.diary.entity.QDiary;
import com.example.log4u.domain.like.entity.QLike;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQuery;
Expand All @@ -24,14 +25,17 @@
public class CustomDiaryRepositoryImpl implements CustomDiaryRepository {
private final JPAQueryFactory queryFactory;

private final QDiary diary = QDiary.diary;
private final QLike like = QLike.like;

@Override
public Page<Diary> searchDiaries(
String keyword,
List<VisibilityType> visibilities,
SortType sort,
Pageable pageable
) {
QDiary diary = QDiary.diary;
// QDiary diary = QDiary.diary;

// 조건 생성
BooleanExpression condition = createCondition(diary, keyword, visibilities, null);
Expand Down Expand Up @@ -132,4 +136,31 @@ private Slice<Diary> checkAndCreateSlice(List<Diary> content, Pageable pageable)

return new SliceImpl<>(content, pageable, hasNext);
}

@Override
public Slice<Diary> getLikeDiarySliceByUserId(
Long userId,
List<VisibilityType> visibilities,
Long cursorId,
Pageable pageable) {
QDiary diary = QDiary.diary;

// 조건 생성
BooleanExpression condition = createCondition(diary, null, visibilities, userId);

// limit + 1로 다음 페이지 존재 여부 확인
List<Diary> content = queryFactory
.selectFrom(diary)
.innerJoin(like)
.on(like.diaryId.eq(diary.diaryId))
.where(like.userId.eq(userId)
.and(condition)
.and(like.likeId.lt(cursorId)))
.orderBy(like.createdAt.desc())
.limit(pageable.getPageSize() + 1)
.fetch();

// 다음 페이지 여부를 계산하여 반환
return checkAndCreateSlice(content, pageable);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -217,4 +217,44 @@ public void checkDiaryExists(Long diaryId) {
throw new NotFoundDiaryException();
}
}

@Transactional(readOnly = true)
public PageResponse<DiaryResponseDto> getMyDiariesByCursor(Long userId, VisibilityType visibilityType,
Long cursorId, int size) {
List<VisibilityType> visibilities =
Copy link
Collaborator

Choose a reason for hiding this comment

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

사용자 흐름에서 VisibilityType이 null로 들어오는 경우가 있을까요?

3개의 타입에 대해서 전부 일일이 찾는 비용이 성능을 많이 잡아먹지 않을까?라는 생각과, 아예 Type을 지정해서 받는게 특정 Type만 찾는다는 점에서 성능에 좀 더 유리하지 않나? 라는 생각이 들었던 것 같습니다

visibilityType == null ? List.of(VisibilityType.PUBLIC, VisibilityType.PRIVATE, VisibilityType.FOLLOWER) :
List.of(visibilityType);

Slice<Diary> diaries = diaryRepository.findByUserIdAndVisibilityInAndCursorId(
userId,
visibilities,
cursorId != null ? cursorId : Long.MAX_VALUE,
PageRequest.of(0, size)
);

Slice<DiaryResponseDto> dtoSlice = mapToDtoSlice(diaries);

Long nextCursor = !dtoSlice.isEmpty() ? dtoSlice.getContent().getLast().diaryId() : null;

return PageResponse.of(dtoSlice, nextCursor);
}

@Transactional(readOnly = true)
public PageResponse<DiaryResponseDto> getLikeDiariesByCursor(Long userId, Long targetUserId, Long cursorId,
int size) {
List<VisibilityType> visibilities = determineAccessibleVisibilities(userId, targetUserId);

Slice<Diary> diaries = diaryRepository.getLikeDiarySliceByUserId(
targetUserId,
visibilities,
cursorId != null ? cursorId : Long.MAX_VALUE,
PageRequest.of(0, size)
);

Slice<DiaryResponseDto> dtoSlice = mapToDtoSlice(diaries);

Long nextCursor = !dtoSlice.isEmpty() ? dtoSlice.getContent().getLast().diaryId() : null;

return PageResponse.of(dtoSlice, nextCursor);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.example.log4u.domain.follow.repository;

import java.util.List;

import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
import org.springframework.stereotype.Repository;

import com.example.log4u.common.util.PageableUtil;
import com.example.log4u.domain.follow.entitiy.Follow;
import com.example.log4u.domain.follow.entitiy.QFollow;
import com.example.log4u.domain.user.dto.UserThumbnailResponseDto;
import com.example.log4u.domain.user.entity.QUser;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.NumberPath;

@Repository
public class FollowQuerydsl extends QuerydslRepositorySupport {
private final QFollow follow = QFollow.follow;
private final QUser user = QUser.user;

public FollowQuerydsl() {
super(Follow.class);
}

private NumberPath<Long> getNumberPath(boolean isFollowerQuery) {
return isFollowerQuery ? follow.followerId : follow.followingId;
}

private BooleanBuilder getBooleanBuilder(boolean isFollowerQuery, Long userId, Long cursorId) {
BooleanBuilder builder = new BooleanBuilder();
builder.and(getNumberPath(isFollowerQuery).eq(userId));

if (cursorId != null) {
builder.and(follow.id.lt(cursorId));
}

return builder;
}

private List<UserThumbnailResponseDto> getContent(boolean isFollowerQuery, Long userId, Long cursorId) {
BooleanBuilder builder = getBooleanBuilder(isFollowerQuery, userId, cursorId);

return from(follow)
.select(Projections.constructor(UserThumbnailResponseDto.class,
getNumberPath(!isFollowerQuery),
user.nickname,
user.nickname))
.where(builder)
.distinct()
.fetch();
}

//내 팔로워 아이디 슬라이스
public Slice<UserThumbnailResponseDto> getFollowerSliceByUserId(Long userId, Long cursorId, Pageable pageable) {
boolean isFollowerQuery = true;
List<UserThumbnailResponseDto> content = getContent(isFollowerQuery, userId, cursorId);
return PageableUtil.checkAndCreateSlice(content, pageable);
Copy link
Collaborator

Choose a reason for hiding this comment

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

해당 함수안에 있는 로직을 저는 로직단에서 일일이 작성을 했었는데 Util클래스로 빼는게 가독성, 재사용성면에서 훨씬 좋은 것 같네요. 배워갑니다

}

// 내가 팔로잉하는 아이디 슬라이스
public Slice<UserThumbnailResponseDto> getFollowingSliceByUserId(Long userId, Long cursorId, Pageable pageable) {
boolean isFollowerQuery = false;
List<UserThumbnailResponseDto> content = getContent(isFollowerQuery, userId, cursorId);
return PageableUtil.checkAndCreateSlice(content, pageable);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.log4u.common.oauth2.dto.CustomOAuth2User;
import com.example.log4u.domain.reports.dto.ReportCreateRequestDto;
import com.example.log4u.domain.reports.service.ReportService;

Expand All @@ -21,18 +23,22 @@ public class ReportsController {

@PostMapping("/diaries/{diaryId}")
public ResponseEntity<Void> createReportForDiary(
@AuthenticationPrincipal CustomOAuth2User customOAuth2User,
@RequestBody ReportCreateRequestDto reportCreateRequestDto,
@PathVariable Long diaryId) {
long reporterId = 1L; // SecurityContextHolder 에서 온다고 가정
@PathVariable Long diaryId
) {
long reporterId = customOAuth2User.getUserId();
reportService.createDiaryReport(reporterId, reportCreateRequestDto, diaryId);
return new ResponseEntity<>(HttpStatus.CREATED);
}

@PostMapping("/comments/{commentId}")
public ResponseEntity<Void> createReport(
@AuthenticationPrincipal CustomOAuth2User customOAuth2User,
@RequestBody ReportCreateRequestDto reportCreateRequestDto,
@PathVariable Long commentId) {
long reporterId = 1L;
@PathVariable Long commentId
) {
long reporterId = customOAuth2User.getUserId();
reportService.createCommentReport(reporterId, reportCreateRequestDto, commentId);
return new ResponseEntity<>(HttpStatus.CREATED);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.example.log4u.domain.subscription;

public enum PaymentProvider {
TOSS,
KAKAO,
NAVER
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.example.log4u.domain.subscription;

public enum PaymentStatus {
SUCCESS, // 결제 완료
FAILED, // 결제 실패
REFUNDED // 환불 완료
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.example.log4u.domain.subscription.dto;

import java.time.LocalDateTime;

import com.example.log4u.domain.subscription.PaymentProvider;

import jakarta.annotation.Nullable;
import lombok.Builder;

@Builder
public record SubscriptionResponseDto(
boolean isSubscriptionActive,

@Nullable
PaymentProvider paymentProvider,

@Nullable
LocalDateTime startDate
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.example.log4u.domain.subscription.entity;

import com.example.log4u.common.entity.BaseEntity;
import com.example.log4u.domain.subscription.PaymentProvider;
import com.example.log4u.domain.subscription.PaymentStatus;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;

@SuperBuilder
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PACKAGE)

@Entity
public class Subscription extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false)
private Long userId;

@Column(nullable = false)
@Enumerated(EnumType.STRING)
private PaymentProvider paymentProvider;

@Column(nullable = false)
private Long amount;

@Column(nullable = false, unique = true)
private String paymentKey;

@Column(nullable = false)
@Enumerated(EnumType.STRING)
private PaymentStatus paymentStatus;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.example.log4u.domain.subscription.repository;

import java.time.LocalDateTime;
import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;

import com.example.log4u.domain.subscription.PaymentStatus;
import com.example.log4u.domain.subscription.entity.Subscription;

public interface SubscriptionRepository extends JpaRepository<Subscription, Long> {
Optional<Subscription> findByUserIdAndCreatedAtBeforeAndPaymentStatusOrderByCreatedAtDesc(Long userId,
LocalDateTime now,
PaymentStatus paymentStatus);
}

Loading