Skip to content

Commit

Permalink
Merge pull request #77 from f-lab-edu/feature/76
Browse files Browse the repository at this point in the history
[#76] 개인 맞춤 피드 제공
  • Loading branch information
f-lab-moony authored Apr 29, 2024
2 parents 50649ba + c9a1dc6 commit 0f89fa9
Show file tree
Hide file tree
Showing 23 changed files with 528 additions and 54 deletions.
49 changes: 49 additions & 0 deletions Stoury-batch/src/test/groovy/com/learning/RedisTemplateTest.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.learning

import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory
import org.springframework.data.redis.core.Cursor
import org.springframework.data.redis.core.ScanOptions
import org.springframework.data.redis.core.StringRedisTemplate
import spock.lang.Specification

class RedisTemplateTest extends Specification {
def connectionFactory = new LettuceConnectionFactory("localhost", 8379)
def redisTemplate = new StringRedisTemplate(connectionFactory)
def testKey = "TestKey"
Cursor<byte[]> cursor

def setup(){
connectionFactory.start()
redisTemplate.delete(testKey)
}

def cleanup() {
if (cursor != null && !cursor.isClosed()) {
cursor.close()
}
}

def "opsForSet에 addAll() 가능한지"(){
given:
def opsForSet = redisTemplate.opsForSet()
def list = [1L, 2L, 3L, 4L]
String[] stringArr = list.stream().map(String::valueOf).toArray()
when:
opsForSet.add(testKey, stringArr)
then:
opsForSet.members(testKey).size() == 4
}
def "SCAN 했는데 왜 key가 반환이 안되지"() {
given:
redisTemplate.opsForSet().add("FrequentTags:3333", "val1")
redisTemplate.opsForSet().add("FrequentTags:2222", "val1")
redisTemplate.opsForSet().add("FrequentTags:1113", "val1")
def scanOption = ScanOptions.scanOptions().match("FrequentTags:*").count(1).build()
cursor = redisTemplate.getConnectionFactory().getConnection().keyCommands().scan(scanOption)
expect:
cursor.hasNext()
cursor.hasNext()
cursor.hasNext()
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
package com.stoury

import com.stoury.domain.Diary
import com.stoury.domain.Feed
import com.stoury.domain.GraphicContent
import com.stoury.domain.Like
import com.stoury.domain.Member
import com.stoury.repository.DiaryRepository
import com.stoury.repository.FeedRepository
import com.stoury.repository.LikeRepository
import com.stoury.repository.MemberRepository
import com.stoury.repository.RankingRepository
import com.stoury.domain.*
import com.stoury.repository.*
import com.stoury.utils.cachekeys.FeedLikesCountSnapshotKeys
import com.stoury.utils.cachekeys.PopularSpotsKey
import org.springframework.batch.core.Job
Expand All @@ -24,14 +16,12 @@ import spock.lang.Specification

import java.time.temporal.ChronoUnit

import static com.stoury.utils.cachekeys.HotFeedsKeys.DAILY_HOT_FEEDS
import static com.stoury.utils.cachekeys.HotFeedsKeys.MONTHLY_HOT_FEEDS
import static com.stoury.utils.cachekeys.HotFeedsKeys.WEEKLY_HOT_FEEDS
import static com.stoury.utils.cachekeys.HotFeedsKeys.*

@SpringBootTest(classes = StouryBatchApplication.class)
@SpringBatchTest
@ActiveProfiles("test")
class BatchTest extends Specification {
class BatchIntegrationTest extends Specification {
@Autowired
JobLauncherTestUtils jobLauncherTestUtils;
@Autowired
Expand All @@ -55,14 +45,18 @@ class BatchTest extends Specification {
LikeRepository likeRepository
@Autowired
DiaryRepository diaryRepository
@Autowired
TagRepository tagRepository

@Autowired
StringRedisTemplate redisTemplate

def member = new Member("aaa@dddd.com", "qwdqwdqwd", "username", null)

def setup() {
feedRepository.deleteAllFeedResponse()
feedRepository.deleteAll()
tagRepository.deleteAll()
diaryRepository.deleteAll()
memberRepository.deleteAll()
memberRepository.save(member)
Expand All @@ -72,7 +66,9 @@ class BatchTest extends Specification {
}

def cleanup() {
feedRepository.deleteAllFeedResponse()
feedRepository.deleteAll()
tagRepository.deleteAll()
diaryRepository.deleteAll()
memberRepository.deleteAll()

Expand Down Expand Up @@ -178,10 +174,10 @@ class BatchTest extends Specification {
likeRepository.getCountSnapshotByFeed(feed.id.toString(), ChronoUnit.YEARS) == 0)
}

def feed(long num){
def feed(long num) {
def feed = Feed.builder()
.member(member)
.textContent("feed"+num)
.textContent("feed" + num)
.latitude(0)
.longitude(0)
.city("city" + num)
Expand Down
2 changes: 2 additions & 0 deletions src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ include::GetFeedsByTag.adoc[]
include::GetFeedsOfMember.adoc[]
include::UpdateAFeed.adoc[]
include::DeleteAFeed.adoc[]
include::GetRecommendFeeds.adoc[]
include::UpdateViewedFeeds.adoc[]
include::CheckLiked.adoc[]
include::LikeAFeed.adoc[]
include::CancelLike.adoc[]
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/com/stoury/controller/FeedController.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,14 @@ public void deleteFeed(@AuthenticationPrincipal AuthenticatedMember authenticate
@PathVariable Long feedId) {
feedService.deleteFeedIfOwner(feedId, authenticatedMember.getId());
}

@GetMapping("/feeds/recommend")
public List<FeedResponse> getRecommendedFeeds(@AuthenticationPrincipal AuthenticatedMember authenticatedMember) {
return feedService.getRecommendedFeeds(authenticatedMember.getId());
}

@PostMapping("/feeds/viewed/{feedId}")
public void addViewedFeeds(@AuthenticationPrincipal AuthenticatedMember authenticatedMember, @PathVariable Long feedId) {
feedService.clickLogUpdate(authenticatedMember.getId(), feedId);
}
}
31 changes: 31 additions & 0 deletions src/main/java/com/stoury/domain/ClickLog.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.stoury.domain;

import jakarta.persistence.*;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Entity
@Getter
@NoArgsConstructor
@Table(name = "CLICK_LOG")
public class ClickLog {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "MEMBER_ID")
private Long memberId;
@Column(name = "FEED_ID")
private Long feedId;
@Column(name = "CREATED_AT")
private LocalDateTime createdAt;

@Builder
public ClickLog(Long memberId, Long feedId, LocalDateTime createdAt) {
this.memberId = memberId;
this.feedId = feedId;
this.createdAt = createdAt;
}
}
21 changes: 21 additions & 0 deletions src/main/java/com/stoury/repository/ClickLogRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.stoury.repository;

import com.querydsl.jpa.impl.JPAQueryFactory;
import com.stoury.domain.ClickLog;
import jakarta.persistence.EntityManager;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

@Repository
@RequiredArgsConstructor
public class ClickLogRepository {
private final EntityManager entityManager;
private final JPAQueryFactory jpaQueryFactory;

@Transactional
public ClickLog save(ClickLog feedClick) {
entityManager.persist(feedClick);
return feedClick;
}
}
31 changes: 31 additions & 0 deletions src/main/java/com/stoury/repository/FeedRepository.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
package com.stoury.repository;

import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.core.types.dsl.StringPath;
import com.querydsl.jpa.JPAExpressions;
import com.querydsl.jpa.impl.JPAQueryFactory;
import com.stoury.domain.Feed;
import com.stoury.domain.Member;
import com.stoury.domain.Tag;
import com.stoury.projection.FeedResponseEntity;
import com.stoury.utils.cachekeys.PageSize;
import jakarta.persistence.EntityManager;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

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

import static com.stoury.domain.QClickLog.clickLog;
import static com.stoury.domain.QFeed.feed;
import static com.stoury.domain.QTag.tag;
import static com.stoury.projection.QFeedResponseEntity.feedResponseEntity;
Expand Down Expand Up @@ -83,6 +89,14 @@ public List<FeedResponseEntity> findByTagNameAndIdLessThan(String tagName, Long
.fetch();
}

@Transactional(readOnly = true)
public List<FeedResponseEntity> findAllFeedsByIdIn(Collection<Long> feedIds) {
return jpaQueryFactory
.selectFrom(feedResponseEntity)
.where(feedResponseEntity.feedId.in(feedIds))
.fetch();
}

private List<Long> findAllFeedIdByTagAndIdLessThan(String tagName, Long offsetId, Pageable page) {
return jpaQueryFactory
.select(feed.id)
Expand Down Expand Up @@ -148,10 +162,27 @@ public List<Feed> saveAll(Collection<Feed> feeds) {
return feeds.stream().map(this::save).toList();
}

@Transactional
public List<FeedResponseEntity> saveAllFeedResponses(Collection<FeedResponseEntity> feeds) {
return feeds.stream().map(this::saveFeedResponse).toList();
}

@Transactional
public void deleteFeedResponseById(Long feedId) {
jpaQueryFactory.delete(feedResponseEntity)
.where(feedResponseEntity.feedId.eq(feedId))
.execute();
}

@Transactional(readOnly = true)
public List<Long> findRandomFeedIdsByTagName(Collection<Tag> tagNames){
return jpaQueryFactory.
select(feed.id)
.from(feed)
.join(feed.tags, tag)
.where(tag.in(tagNames))
.orderBy(Expressions.numberTemplate(Double.class, "function('rand')").asc())
.limit(PageSize.RANDOM_FEEDS_FETCH_SIZE)
.fetch();
}
}
28 changes: 28 additions & 0 deletions src/main/java/com/stoury/repository/TagRepository.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
package com.stoury.repository;

import com.querydsl.jpa.impl.JPAQueryFactory;
import com.stoury.domain.QClickLog;
import com.stoury.domain.QFeed;
import com.stoury.domain.Tag;
import com.stoury.utils.cachekeys.PageSize;
import jakarta.persistence.EntityManager;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

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

import static com.stoury.domain.QClickLog.*;
import static com.stoury.domain.QFeed.*;
import static com.stoury.domain.QTag.tag;

@Repository
Expand All @@ -23,6 +31,12 @@ public Tag save(Tag saveTag) {
return saveTag;
}

@Transactional
public List<Tag> saveAll(Collection<Tag> saveTags) {
saveTags.forEach(entityManager::persist);
return saveTags.stream().toList();
}

@Transactional
public Tag saveAndFlush(Tag saveTag) {
entityManager.persist(saveTag);
Expand All @@ -48,4 +62,18 @@ public Optional<Tag> findByTagName(String tagName) {
.fetchFirst()
);
}

public List<Tag> findAllByMemberIdAndFrequency(Long memberId) {
return jpaQueryFactory
.select(tag)
.from(clickLog)
.join(feed).on(clickLog.feedId.eq(feed.id))
.join(feed.tags, tag)
.where(clickLog.memberId.eq(memberId)
.and(clickLog.createdAt.between(LocalDateTime.now().minusDays(7), LocalDateTime.now())))
.groupBy(tag)
.orderBy(tag.count().desc())
.limit(PageSize.FREQUENT_TAGS_SIZE)
.fetch();
}
}
Loading

0 comments on commit 0f89fa9

Please sign in to comment.