Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
@@ -0,0 +1,60 @@
package com.pinback.api.article.controller;

import java.time.LocalDateTime;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.pinback.application.article.dto.query.PageQuery;
import com.pinback.application.article.dto.response.ReadRemindArticleResponse;
import com.pinback.application.article.dto.response.TodayRemindResponseV2;
import com.pinback.application.article.port.in.GetArticlePort;
import com.pinback.application.article.port.in.UpdateArticleStatusPort;
import com.pinback.domain.user.entity.User;
import com.pinback.shared.annotation.CurrentUser;
import com.pinback.shared.dto.ResponseDto;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@RestController
@RequestMapping("/api/v2/articles")
@RequiredArgsConstructor
@Tag(name = "ArticleV2", description = "아티클 관리 API V2")
public class ArticleControllerV2 {
private final GetArticlePort getArticlePort;
private final UpdateArticleStatusPort updateArticleStatusPort;

@Operation(summary = "리마인드 아티클 조회 v2", description = "오늘 리마인드할 아티클을 읽음/안읽음 상태별로 조회합니다.")
@GetMapping("/remind")
public ResponseDto<TodayRemindResponseV2> getRemindArticlesV2(
@Parameter(hidden = true) @CurrentUser User user,
@Parameter(description = "현재 시간", example = "2025-09-03T10:00:00") @RequestParam LocalDateTime now,
@Parameter(description = "읽음 상태 (true: 읽음, false: 안읽음)", example = "true") @RequestParam(name = "read-status") boolean readStatus,
@Parameter(description = "페이지 번호 (0부터 시작)") @RequestParam(defaultValue = "0") int page,
@Parameter(description = "페이지 크기") @RequestParam(defaultValue = "8") int size
) {
PageQuery query = new PageQuery(page, size);
TodayRemindResponseV2 response = getArticlePort.getRemindArticlesV2(user, now, readStatus, query);
return ResponseDto.ok(response);
}

@Operation(summary = "리마인드 아티클 읽음 상태 변경 v2", description = "리마인드 아티클의 읽음 상태를 변경합니다")
@PatchMapping("/remind/{articleId}/read-status")
public ResponseDto<ReadRemindArticleResponse> updateRemindArticleStatus(
@Parameter(hidden = true) @CurrentUser User user,
@Parameter(description = "아티클 ID") @PathVariable Long articleId
) {
ReadRemindArticleResponse response = updateArticleStatusPort.updateRemindArticleStatus(user, articleId);
return ResponseDto.ok(response);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.pinback.application.article.dto;

import org.springframework.data.domain.Page;

import com.pinback.domain.article.entity.Article;

public record RemindArticlesWithCountDtoV2(
boolean hasNext,
long readCount,
long unreadCount,
long totalCount,
Page<Article> articles
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.pinback.application.article.dto.response;

public record ReadRemindArticleResponse(
boolean isReadAfterRemind,
int finalAcornCount,
boolean isCollected
) {
public static ReadRemindArticleResponse of(boolean readAfterRemind, int finalAcornCount, boolean isCollected) {
return new ReadRemindArticleResponse(readAfterRemind, finalAcornCount, isCollected);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.pinback.application.article.dto.response;

import java.time.LocalDateTime;

import com.pinback.application.category.dto.response.CategoryResponse;
import com.pinback.domain.article.entity.Article;

public record RemindArticleResponseV2(
long articleId,
String url,
String memo,
LocalDateTime createdAt,
boolean isRead,
boolean isReadAfterRemind,
LocalDateTime remindAt,
CategoryResponse category
) {
public static RemindArticleResponseV2 from(Article article) {
return new RemindArticleResponseV2(
article.getId(),
article.getUrl(),
article.getMemo(),
article.getCreatedAt(),
article.isRead(),
article.isReadAfterRemind(),
article.getRemindAt(),
CategoryResponse.from(article.getCategory())
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.pinback.application.article.dto.response;

import java.util.List;

public record TodayRemindResponseV2(
boolean hasNext,
long totalArticleCount,
long readArticleCount,
long unreadArticleCount,
List<RemindArticleResponseV2> articles
) {
public static TodayRemindResponseV2 of(
boolean hasNext,
long totalArticleCount,
long readArticleCount,
long unreadArticleCount,
List<RemindArticleResponseV2> articles
) {
return new TodayRemindResponseV2(hasNext, totalArticleCount, readArticleCount, unreadArticleCount, articles);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.pinback.application.article.dto.response.ArticlesPageResponse;
import com.pinback.application.article.dto.response.GetAllArticlesResponse;
import com.pinback.application.article.dto.response.TodayRemindResponse;
import com.pinback.application.article.dto.response.TodayRemindResponseV2;
import com.pinback.domain.user.entity.User;

public interface GetArticlePort {
Expand All @@ -21,4 +22,6 @@ public interface GetArticlePort {
ArticlesPageResponse getUnreadArticles(User user, PageQuery query);

TodayRemindResponse getRemindArticles(User user, LocalDateTime now, boolean readStatus, PageQuery query);

TodayRemindResponseV2 getRemindArticlesV2(User user, LocalDateTime now, boolean readStatus, PageQuery query);
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package com.pinback.application.article.port.in;

import com.pinback.application.article.dto.response.ReadArticleResponse;
import com.pinback.application.article.dto.response.ReadRemindArticleResponse;
import com.pinback.domain.user.entity.User;

public interface UpdateArticleStatusPort {
ReadArticleResponse updateArticleStatus(User user, long articleId);

ReadRemindArticleResponse updateRemindArticleStatus(User user, long articleId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import com.pinback.application.article.dto.ArticlesWithUnreadCountDto;
import com.pinback.application.article.dto.RemindArticlesWithCountDto;
import com.pinback.application.article.dto.RemindArticlesWithCountDtoV2;
import com.pinback.domain.article.entity.Article;
import com.pinback.domain.category.entity.Category;
import com.pinback.domain.user.entity.User;
Expand All @@ -32,6 +33,10 @@ public interface ArticleGetServicePort {

Page<Article> findTodayRemind(User user, LocalDateTime remindDateTime, Pageable pageable, Boolean isRead);

RemindArticlesWithCountDto findTodayRemindWithCount(User user, LocalDateTime startDateTime, LocalDateTime endDateTime, Pageable pageable,
RemindArticlesWithCountDto findTodayRemindWithCount(User user, LocalDateTime startDateTime,
LocalDateTime endDateTime, Pageable pageable,
Boolean isRead);

RemindArticlesWithCountDtoV2 findTodayRemindWithCountV2(User user, LocalDateTime startDateTime,
LocalDateTime endDateTime, Pageable pageable, Boolean isReadAfterRemind);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import com.pinback.application.article.dto.AcornCollectResult;
import com.pinback.application.article.dto.response.ReadArticleResponse;
import com.pinback.application.article.dto.response.ReadRemindArticleResponse;
import com.pinback.application.article.port.in.UpdateArticleStatusPort;
import com.pinback.application.article.port.out.ArticleGetServicePort;
import com.pinback.application.user.port.in.ManageAcornPort;
Expand Down Expand Up @@ -38,4 +39,19 @@ public ReadArticleResponse updateArticleStatus(User user, long articleId) {

return ReadArticleResponse.of(currentAcorns, false);
}

@Override
public ReadRemindArticleResponse updateRemindArticleStatus(User user, long articleId) {
Article article = articleGetService.findByUserAndId(user, articleId);
int currentAcorns = manageAcornPort.getCurrentAcorns(user.getId());
log.info("수집하기 전 도토리 수: {}", currentAcorns);

if (!article.isReadAfterRemind()) {
article.markAsReadAfterRemind();
AcornCollectResult result = manageAcornPort.tryCollectAcorns(user);
return ReadRemindArticleResponse.of(article.isReadAfterRemind(), result.finalAcornCount(),
result.isCollected());
}
return ReadRemindArticleResponse.of(article.isReadAfterRemind(), currentAcorns, false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

Expand All @@ -11,13 +12,16 @@

import com.pinback.application.article.dto.ArticlesWithUnreadCountDto;
import com.pinback.application.article.dto.RemindArticlesWithCountDto;
import com.pinback.application.article.dto.RemindArticlesWithCountDtoV2;
import com.pinback.application.article.dto.query.PageQuery;
import com.pinback.application.article.dto.response.ArticleDetailResponse;
import com.pinback.application.article.dto.response.ArticleResponse;
import com.pinback.application.article.dto.response.ArticlesPageResponse;
import com.pinback.application.article.dto.response.GetAllArticlesResponse;
import com.pinback.application.article.dto.response.RemindArticleResponse;
import com.pinback.application.article.dto.response.RemindArticleResponseV2;
import com.pinback.application.article.dto.response.TodayRemindResponse;
import com.pinback.application.article.dto.response.TodayRemindResponseV2;
import com.pinback.application.article.port.in.GetArticlePort;
import com.pinback.application.article.port.out.ArticleGetServicePort;
import com.pinback.application.category.port.in.GetCategoryPort;
Expand Down Expand Up @@ -105,7 +109,6 @@ public TodayRemindResponse getRemindArticles(User user, LocalDateTime now, boole
LocalDateTime startOfDay = now.toLocalDate().atStartOfDay();
LocalDateTime endOfDay = now.toLocalDate().atTime(23, 59, 59, 999999999);


RemindArticlesWithCountDto result = articleGetServicePort.findTodayRemindWithCount(
user, startOfDay, endOfDay, PageRequest.of(query.pageNumber(), query.pageSize()), readStatus);

Expand All @@ -120,6 +123,40 @@ public TodayRemindResponse getRemindArticles(User user, LocalDateTime now, boole
);
}

@Override
public TodayRemindResponseV2 getRemindArticlesV2(
User user,
LocalDateTime now,
boolean readStatus,
PageQuery query
) {
LocalDateTime endBound = now;
LocalDateTime startBound = now.minusHours(24);

RemindArticlesWithCountDtoV2 result = articleGetServicePort.findTodayRemindWithCountV2(
user,
startBound,
endBound,
PageRequest.of(query.pageNumber(), query.pageSize()),
readStatus
);

List<RemindArticleResponseV2> articleResponses =
result.articles() != null ?
result.articles().stream()
.map(RemindArticleResponseV2::from)
.toList() :
Collections.emptyList();

return TodayRemindResponseV2.of(
result.hasNext(),
result.totalCount(),
result.readCount(),
result.unreadCount(),
articleResponses
);
}

private LocalDateTime getRemindDateTime(LocalDateTime now, LocalTime remindDefault) {
return LocalDateTime.of(
now.getYear(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import java.time.LocalDateTime;

import org.hibernate.annotations.ColumnDefault;

import com.pinback.domain.category.entity.Category;
import com.pinback.domain.common.BaseEntity;
import com.pinback.domain.user.entity.User;
Expand Down Expand Up @@ -56,6 +58,10 @@ public class Article extends BaseEntity {
@Column(name = "is_read", nullable = false)
private Boolean isRead;

@Column(name = "is_read_after_remind", nullable = false)
@ColumnDefault("false")
private Boolean isReadAfterRemind;

public static Article create(String url, String memo, User user, Category category, LocalDateTime remindAt) {
validateMemo(memo);

Expand All @@ -66,6 +72,7 @@ public static Article create(String url, String memo, User user, Category catego
.category(category)
.isRead(false)
.remindAt(remindAt)
.isReadAfterRemind(false)
.build();
}

Expand All @@ -80,10 +87,18 @@ public boolean isRead() {
return isRead;
}

public boolean isReadAfterRemind() {
return isReadAfterRemind;
}

public void markAsRead() {
this.isRead = true;
}

public void markAsReadAfterRemind() {
this.isReadAfterRemind = true;
}

public void update(String memo, Category category, LocalDateTime remindAt) {
validateMemo(memo);
this.memo = memo;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.pinback.domain.article.entity.Article;
import com.pinback.infrastructure.article.repository.dto.ArticlesWithUnreadCount;
import com.pinback.infrastructure.article.repository.dto.RemindArticlesWithCount;
import com.pinback.infrastructure.article.repository.dto.RemindArticlesWithCountV2;

public interface ArticleRepositoryCustom {
ArticlesWithUnreadCount findAllCustom(UUID userId, Pageable pageable);
Expand All @@ -24,4 +25,7 @@ RemindArticlesWithCount findTodayRemindWithCount(UUID userId, Pageable pageable,
ArticlesWithUnreadCount findAllByIsReadFalse(UUID userId, Pageable pageable);

void deleteArticlesByUserIdAndCategoryId(UUID userId, long categoryId);

RemindArticlesWithCountV2 findTodayRemindWithCountV2(UUID userId, Pageable pageable, LocalDateTime startAt,
LocalDateTime endAt, Boolean isReadAfterRemind);
}
Loading