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
6 changes: 6 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,16 @@ dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

// Testcontainers for integration tests
testImplementation 'org.testcontainers:testcontainers:1.19.3'
testImplementation 'org.testcontainers:mysql:1.19.3'
testImplementation 'org.testcontainers:junit-jupiter:1.19.3'

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

runtimeOnly 'com.mysql:mysql-connector-j'
testRuntimeOnly 'com.h2database:h2' // 테스트용 in-memory database

developmentOnly 'org.springframework.boot:spring-boot-devtools'
compileOnly 'org.projectlombok:lombok'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ public void saveReadPost(Long userId, ReadPostRequest request) {
Post post = postRepository.findById(request.postId())
.orElseThrow(() -> new GeneralException(PostErrorCode.POST_NOT_FOUND));

if (readPostRepository.existsByUserAndPost(user, post)) {
log.info("User {} has already read post {}", userId, request.postId());
return;
boolean isFirstRead = !readPostRepository.existsByUserAndPost(user, post);
if (isFirstRead) {
post.incrementViewCount();
}

ReadPost readPost = ReadPost.create(
Expand All @@ -56,7 +56,8 @@ public void saveReadPost(Long userId, ReadPostRequest request) {
);

readPostRepository.save(readPost);
log.info("Saved read post for user {} and post {}", userId, request.postId());
log.info("Saved read post for user {} and post {} (viewCount incremented: {})",
userId, request.postId(), isFirstRead);
}

@Transactional
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public PostDetailDto toPostDetailDto(PostDetailDto baseDto, List<String> keyword
.url(baseDto.url())
.logoUrl(baseDto.logoUrl())
.publishedAt(baseDto.publishedAt())
.viewCount(baseDto.viewCount())
.keywords(keywords)
.build();
}
Expand Down
24 changes: 13 additions & 11 deletions src/main/java/com/techfork/domain/post/dto/CompanyListResponse.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package com.techfork.domain.post.dto;

import lombok.Builder;

import java.util.List;

@Builder
public record CompanyListResponse(
List<String> companies
) {
}
package com.techfork.domain.post.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;

import java.util.List;

@Builder
@Schema(name = "CompanyListResponse")
public record CompanyListResponse(
List<String> companies
) {
}
41 changes: 22 additions & 19 deletions src/main/java/com/techfork/domain/post/dto/PostDetailDto.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
package com.techfork.domain.post.dto;

import lombok.Builder;

import java.time.LocalDateTime;
import java.util.List;

@Builder
public record PostDetailDto(
Long id,
String title,
String summary,
String company,
String url,
String logoUrl,
LocalDateTime publishedAt,
List<String> keywords
) {
}
package com.techfork.domain.post.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;

import java.time.LocalDateTime;
import java.util.List;

@Builder
@Schema(name = "PostDetailResponse")
public record PostDetailDto(
Long id,
String title,
String summary,
String company,
String url,
String logoUrl,
LocalDateTime publishedAt,
Long viewCount,
List<String> keywords
) {
}
39 changes: 21 additions & 18 deletions src/main/java/com/techfork/domain/post/dto/PostInfoDto.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
package com.techfork.domain.post.dto;

import lombok.Builder;

import java.time.LocalDateTime;
import java.util.List;

@Builder
public record PostInfoDto(
Long id,
String title,
String company,
String url,
String logoUrl,
LocalDateTime publishedAt,
List<String> keywords
) {
}
package com.techfork.domain.post.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;

import java.time.LocalDateTime;
import java.util.List;

@Builder
@Schema(name = "PostInfoDto")
public record PostInfoDto(
Long id,
String title,
String company,
String url,
String logoUrl,
LocalDateTime publishedAt,
Long viewCount,
List<String> keywords
) {
}
28 changes: 15 additions & 13 deletions src/main/java/com/techfork/domain/post/dto/PostListResponse.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package com.techfork.domain.post.dto;

import lombok.Builder;

import java.util.List;

@Builder
public record PostListResponse(
List<PostInfoDto> posts,
Long lastPostId,
boolean hasNext
) {
}
package com.techfork.domain.post.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;

import java.util.List;

@Builder
@Schema(name = "PostListResponse")
public record PostListResponse(
List<PostInfoDto> posts,
Long lastPostId,
boolean hasNext
) {
}
7 changes: 7 additions & 0 deletions src/main/java/com/techfork/domain/post/entity/Post.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ public class Post extends BaseEntity {
@Column
private LocalDateTime embeddedAt;

@Column(nullable = false)
private Long viewCount = 0L;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "tech_blog_id", nullable = false)
private TechBlog techBlog;
Expand Down Expand Up @@ -94,4 +97,8 @@ public void addKeyword(PostKeyword keyword) {
public void clearKeywords() {
this.keywords.clear();
}

public void incrementViewCount() {
this.viewCount++;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public interface PostRepository extends JpaRepository<Post, Long> {

@Query("""
SELECT new com.techfork.domain.post.dto.PostInfoDto(
p.id, p.title, t.companyName, p.url, t.logoUrl, p.publishedAt, null)
p.id, p.title, t.companyName, p.url, t.logoUrl, p.publishedAt, p.viewCount, null)
FROM Post p
JOIN TechBlog t on p.techBlog.id = t.id
WHERE (:company IS NULL OR p.company = :company)
Expand All @@ -51,7 +51,7 @@ List<PostInfoDto> findByCompanyWithCursor(

@Query("""
SELECT new com.techfork.domain.post.dto.PostInfoDto(
p.id, p.title, t.companyName, p.url, t.logoUrl, p.publishedAt, null)
p.id, p.title, t.companyName, p.url, t.logoUrl, p.publishedAt, p.viewCount, null)
FROM Post p
JOIN TechBlog t on p.techBlog.id = t.id
WHERE :lastPostId IS NULL OR p.id < :lastPostId
Expand All @@ -64,11 +64,11 @@ List<PostInfoDto> findRecentPostsWithCursor(

@Query("""
SELECT new com.techfork.domain.post.dto.PostInfoDto(
p.id, p.title, t.companyName, p.url, t.logoUrl, p.publishedAt, null)
p.id, p.title, t.companyName, p.url, t.logoUrl, p.publishedAt, p.viewCount, null)
FROM Post p
JOIN TechBlog t on p.techBlog.id = t.id
WHERE :lastPostId IS NULL OR p.id < :lastPostId
ORDER BY p.crawledAt DESC, p.id DESC
ORDER BY p.viewCount DESC, p.id DESC
""")
List<PostInfoDto> findPopularPostsWithCursor(
@Param("lastPostId") Long lastPostId,
Expand All @@ -77,7 +77,7 @@ List<PostInfoDto> findPopularPostsWithCursor(

@Query("""
SELECT new com.techfork.domain.post.dto.PostDetailDto(
p.id, p.title, p.summary, t.companyName, p.url, t.logoUrl, p.publishedAt, null)
p.id, p.title, p.summary, t.companyName, p.url, t.logoUrl, p.publishedAt, p.viewCount, null)
FROM Post p
JOIN TechBlog t on p.techBlog.id = t.id
WHERE p.id = :id
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.techfork.domain.post.service;

import com.techfork.domain.post.converter.PostConverter;
import com.techfork.domain.post.dto.*;
import com.techfork.domain.post.dto.CompanyListResponse;
import com.techfork.domain.post.dto.PostDetailDto;
import com.techfork.domain.post.dto.PostInfoDto;
import com.techfork.domain.post.dto.PostListResponse;
import com.techfork.domain.post.entity.PostKeyword;
import com.techfork.domain.post.enums.EPostSortType;
import com.techfork.domain.post.repository.PostKeywordRepository;
Expand Down Expand Up @@ -90,6 +93,7 @@ private List<PostInfoDto> attachKeywordsToPostInfoList(List<PostInfoDto> posts)
.url(post.url())
.logoUrl(post.logoUrl())
.publishedAt(post.publishedAt())
.viewCount(post.viewCount())
.keywords(keywordMap.getOrDefault(post.id(), List.of()))
.build())
.toList();
Expand Down
Loading
Loading