Skip to content

Commit

Permalink
Feat: 사용자 태그는 자동으로 생성된다.
Browse files Browse the repository at this point in the history
Feat: 사용자 태그는 자동으로 생성된다.
  • Loading branch information
hseong3243 authored Apr 11, 2024
2 parents 418c82d + f0453c4 commit 4e48d04
Show file tree
Hide file tree
Showing 12 changed files with 322 additions and 28 deletions.
16 changes: 16 additions & 0 deletions src/main/java/com/seong/shoutlink/domain/tag/MemberTag.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.seong.shoutlink.domain.tag;

import com.seong.shoutlink.domain.member.Member;
import lombok.Getter;

@Getter
public class MemberTag {

private final Member member;
private final Tag tag;

public MemberTag(Member member, Tag tag) {
this.member = member;
this.tag = tag;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.seong.shoutlink.domain.tag.repository;

import jakarta.persistence.DiscriminatorValue;
import jakarta.persistence.Entity;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Entity
@DiscriminatorValue("member")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class MemberTagEntity extends TagEntity {

private Long memberId;

public MemberTagEntity(String name, Long memberId) {
super(name);
this.memberId = memberId;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import com.seong.shoutlink.domain.common.BaseEntity;
import com.seong.shoutlink.domain.hub.Hub;
import com.seong.shoutlink.domain.member.Member;
import com.seong.shoutlink.domain.tag.HubTag;
import com.seong.shoutlink.domain.tag.MemberTag;
import com.seong.shoutlink.domain.tag.Tag;
import jakarta.persistence.DiscriminatorColumn;
import jakarta.persistence.Entity;
Expand Down Expand Up @@ -40,6 +42,12 @@ public static TagEntity from(HubTag hubTag) {
return new HubTagEntity(tag.getName(), hub.getHubId());
}

public static TagEntity from(MemberTag memberTag) {
Member member = memberTag.getMember();
Tag tag = memberTag.getTag();
return new MemberTagEntity(tag.getName(), member.getMemberId());
}

public Tag toDomain() {
return new Tag(tagId, name, getCreatedAt(), getUpdatedAt());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,14 @@ public interface TagJpaRepository extends JpaRepository<TagEntity, Long> {
@Query("select t from HubTagEntity t"
+ " where t.hubId in :hubIds")
List<HubTagEntity> findTagsInHubIds(@Param("hubIds") List<Long> hubIds);

@Modifying
@Query("delete from MemberTagEntity t where t.memberId=:memberId")
void deleteByMemberId(Long memberId);

@Query("select t from MemberTagEntity t"
+ " where t.memberId=:memberId"
+ " order by t.createdAt"
+ " limit 1")
Optional<TagEntity> findLatestTagByMemberId(Long memberId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import com.seong.shoutlink.domain.hub.Hub;
import com.seong.shoutlink.domain.hub.service.HubTagReader;
import com.seong.shoutlink.domain.hub.service.result.HubTagResult;
import com.seong.shoutlink.domain.member.Member;
import com.seong.shoutlink.domain.tag.HubTag;
import com.seong.shoutlink.domain.tag.MemberTag;
import com.seong.shoutlink.domain.tag.Tag;
import com.seong.shoutlink.domain.tag.service.TagRepository;
import java.util.List;
Expand All @@ -20,7 +22,7 @@ public class TagRepositoryImpl implements TagRepository, HubTagReader {
private final TagJpaRepository tagJpaRepository;

@Override
public List<Tag> saveAll(List<HubTag> tags) {
public List<Tag> saveHubTags(List<HubTag> tags) {
List<TagEntity> tagEntities = tags.stream()
.map(TagEntity::from)
.toList();
Expand All @@ -40,6 +42,27 @@ public Optional<Tag> findLatestTagByHub(Hub hub) {
.map(TagEntity::toDomain);
}

@Override
public Optional<Tag> findLatestTagByMember(Member member) {
return tagJpaRepository.findLatestTagByMemberId(member.getMemberId())
.map(TagEntity::toDomain);
}

@Override
public void deleteMemberTags(Member member) {
tagJpaRepository.deleteByMemberId(member.getMemberId());
}

@Override
public List<Tag> saveMemberTags(List<MemberTag> memberTags) {
List<TagEntity> tagEntities = memberTags.stream()
.map(TagEntity::from)
.toList();
return tagJpaRepository.saveAll(tagEntities).stream()
.map(TagEntity::toDomain)
.toList();
}

@Override
public List<HubTagResult> findTagsInHubs(List<Hub> hubs) {
Map<Long, Hub> hubMap = hubs.stream()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
package com.seong.shoutlink.domain.tag.service;

import com.seong.shoutlink.domain.hub.Hub;
import com.seong.shoutlink.domain.member.Member;
import com.seong.shoutlink.domain.tag.HubTag;
import com.seong.shoutlink.domain.tag.MemberTag;
import com.seong.shoutlink.domain.tag.Tag;
import java.util.List;
import java.util.Optional;

public interface TagRepository {

List<Tag> saveAll(List<HubTag> tags);
List<Tag> saveHubTags(List<HubTag> tags);

void deleteHubTags(Hub hub);

Optional<Tag> findLatestTagByHub(Hub hub);

Optional<Tag> findLatestTagByMember(Member member);

void deleteMemberTags(Member member);

List<Tag> saveMemberTags(List<MemberTag> memberTags);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@
import com.seong.shoutlink.domain.linkbundle.LinkBundle;
import com.seong.shoutlink.domain.linkbundle.service.LinkBundleRepository;
import com.seong.shoutlink.domain.link.LinkBundleAndLinks;
import com.seong.shoutlink.domain.member.Member;
import com.seong.shoutlink.domain.member.service.MemberRepository;
import com.seong.shoutlink.domain.tag.HubTag;
import com.seong.shoutlink.domain.tag.MemberTag;
import com.seong.shoutlink.domain.tag.Tag;
import com.seong.shoutlink.domain.tag.service.ai.GenerateAutoTagCommand;
import com.seong.shoutlink.domain.tag.service.request.AutoCreateTagCommand;
import com.seong.shoutlink.domain.tag.service.request.AutoCreateMemberTagCommand;
import com.seong.shoutlink.domain.tag.service.request.AutoCreateHubTagCommand;
import com.seong.shoutlink.domain.tag.service.response.CreateTagResponse;
import java.util.List;
import lombok.RequiredArgsConstructor;
Expand All @@ -30,15 +34,16 @@ public class TagService {
private static final int ZERO = 0;

private final TagRepository tagRepository;
private final MemberRepository memberRepository;
private final HubRepository hubRepository;
private final LinkBundleRepository linkBundleRepository;
private final LinkRepository linkRepository;
private final AutoGenerativeClient autoGenerativeClient;

@Transactional
public CreateTagResponse autoCreateHubTags(AutoCreateTagCommand command) {
public CreateTagResponse autoCreateHubTags(AutoCreateHubTagCommand command) {
Hub hub = getHub(command.hubId());
checkTagIsCreatedWithinADay(hub);
checkHubTagIsCreatedWithinADay(hub);
List<LinkBundle> hubLinkBundles = linkBundleRepository.findHubLinkBundles(hub);
List<LinkWithLinkBundle> links = linkRepository.findAllByLinkBundlesIn(hubLinkBundles);

Expand All @@ -55,16 +60,48 @@ public CreateTagResponse autoCreateHubTags(AutoCreateTagCommand command) {
if (!hubTags.isEmpty()) {
tagRepository.deleteHubTags(hub);
}
return CreateTagResponse.from(tagRepository.saveAll(hubTags));
return CreateTagResponse.from(tagRepository.saveHubTags(hubTags));
}

private void checkTagIsCreatedWithinADay(Hub hub) {
private void checkHubTagIsCreatedWithinADay(Hub hub) {
tagRepository.findLatestTagByHub(hub)
.filter(Tag::isCreatedWithinADay)
.ifPresent(tag -> {
throw new ShoutLinkException("태그 생성된 지 하루가 지나지 않았습니다.", ErrorCode.NOT_MET_CONDITION);});
}

@Transactional
public CreateTagResponse autoCreateMemberTags(AutoCreateMemberTagCommand command) {
Member member = getMember(command.memberId());
checkMemberTagIsCreatedWithinADay(member);
List<LinkBundle> linkBundles = linkBundleRepository.findLinkBundlesThatMembersHave(member);
List<LinkWithLinkBundle> links = linkRepository.findAllByLinkBundlesIn(linkBundles);

List<LinkBundleAndLinks> linkBundleAndLinks = groupingLinks(links);
int generateTagCount = calculateNumberOfTag(links);
GenerateAutoTagCommand generateAutoTagCommand = GenerateAutoTagCommand.create(
linkBundleAndLinks, generateTagCount);

List<MemberTag> memberTags = autoGenerativeClient.generateTags(generateAutoTagCommand)
.stream()
.map(generatedTag -> new Tag(generatedTag.name()))
.map(tag -> new MemberTag(member, tag))
.toList();
if(!memberTags.isEmpty()) {
tagRepository.deleteMemberTags(member);
}
return CreateTagResponse.from(tagRepository.saveMemberTags(memberTags));
}

private void checkMemberTagIsCreatedWithinADay(Member member) {
tagRepository.findLatestTagByMember(member)
.filter(Tag::isCreatedWithinADay)
.ifPresent(tag -> {
throw new ShoutLinkException("태그가 생성된 지 하루가 지나지 않았습니다.",
ErrorCode.NOT_MET_CONDITION);
});
}

private List<LinkBundleAndLinks> groupingLinks(List<LinkWithLinkBundle> links) {
return links.stream()
.collect(groupingBy(
Expand All @@ -84,6 +121,11 @@ private int calculateNumberOfTag(List<LinkWithLinkBundle> links) {
return Math.min(MAXIMUM_TAG_COUNT, totalLinkCount / MINIMUM_TAG_CONDITION);
}

private Member getMember(Long memberId) {
return memberRepository.findById(memberId)
.orElseThrow(() -> new ShoutLinkException("존재하지 않는 회원입니다.", ErrorCode.NOT_FOUND));
}

private Hub getHub(Long hubId) {
return hubRepository.findById(hubId)
.orElseThrow(() -> new ShoutLinkException("존재하지 않는 허브입니다.", ErrorCode.NOT_FOUND));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package com.seong.shoutlink.domain.tag.service.request;

public record AutoCreateTagCommand(Long hubId) {
public record AutoCreateHubTagCommand(Long hubId) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.seong.shoutlink.domain.tag.service.request;

public record AutoCreateMemberTagCommand(Long memberId) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
import com.seong.shoutlink.domain.exception.ErrorCode;
import com.seong.shoutlink.domain.exception.ShoutLinkException;
import com.seong.shoutlink.domain.link.service.event.CreateHubLinkEvent;
import com.seong.shoutlink.domain.link.service.event.CreateMemberLinkEvent;
import com.seong.shoutlink.domain.tag.service.TagService;
import com.seong.shoutlink.domain.tag.service.request.AutoCreateTagCommand;
import com.seong.shoutlink.domain.tag.service.request.AutoCreateHubTagCommand;
import com.seong.shoutlink.domain.tag.service.request.AutoCreateMemberTagCommand;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.annotation.Propagation;
Expand All @@ -20,7 +22,7 @@ public class TagEventListener {
@TransactionalEventListener
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void createHubTags(CreateHubLinkEvent event) {
AutoCreateTagCommand command = new AutoCreateTagCommand(event.hubId());
AutoCreateHubTagCommand command = new AutoCreateHubTagCommand(event.hubId());
try {
tagService.autoCreateHubTags(command);
log.debug("[Tag] 링크 개수가 최소 태그 자동 생성 조건을 만족");
Expand All @@ -32,4 +34,20 @@ public void createHubTags(CreateHubLinkEvent event) {
}
}
}

@TransactionalEventListener
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void createMemberTags(CreateMemberLinkEvent event) {
AutoCreateMemberTagCommand command = new AutoCreateMemberTagCommand(event.memberId());
try {
tagService.autoCreateMemberTags(command);
log.debug("[Tag] 링크 개수가 최소 태그 자동 생성 조건을 만족");
} catch (ShoutLinkException e) {
if(e.getErrorCode().equals(ErrorCode.NOT_MET_CONDITION)) {
log.debug("[Tag] 링크 개수가 최소 태그 자동 생성 조건을 만족하지 않음");
} else {
throw e;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.seong.shoutlink.domain.tag.repository;

import com.seong.shoutlink.domain.hub.Hub;
import com.seong.shoutlink.domain.member.Member;
import com.seong.shoutlink.domain.tag.HubTag;
import com.seong.shoutlink.domain.tag.MemberTag;
import com.seong.shoutlink.domain.tag.Tag;
import com.seong.shoutlink.domain.tag.service.TagRepository;
import java.util.HashMap;
Expand All @@ -28,14 +30,11 @@ public long count() {
}

@Override
public List<Tag> saveAll(List<HubTag> hubTags) {
public List<Tag> saveHubTags(List<HubTag> hubTags) {
for (HubTag hubTag : hubTags) {
memory.put(nextId(), hubTag.getTag());
}
return memory.entrySet().stream().map(entry -> {
Tag value = entry.getValue();
return new Tag(entry.getKey(), value.getName(), value.getCreatedAt(), value.getUpdatedAt());
}).toList();
return memory.values().stream().toList();
}

@Override
Expand All @@ -47,4 +46,22 @@ public void deleteHubTags(Hub hub) {
public Optional<Tag> findLatestTagByHub(Hub hub) {
return memory.values().stream().findFirst();
}

@Override
public Optional<Tag> findLatestTagByMember(Member member) {
return memory.values().stream().findFirst();
}

@Override
public void deleteMemberTags(Member member) {
memory.clear();
}

@Override
public List<Tag> saveMemberTags(List<MemberTag> memberTags) {
for (MemberTag memberTag : memberTags) {
memory.put(nextId(), memberTag.getTag());
}
return memory.values().stream().toList();
}
}
Loading

0 comments on commit 4e48d04

Please sign in to comment.