Skip to content

Commit

Permalink
Feat: 허브 태그 자동 생성 로직을 구현한다.
Browse files Browse the repository at this point in the history
Feat: 허브 태그 자동 생성 로직을 구현한다.
  • Loading branch information
hseong3243 authored Apr 9, 2024
2 parents 4924fad + f6344f3 commit 816ac2a
Show file tree
Hide file tree
Showing 23 changed files with 504 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ public enum ErrorCode {
UNAUTHORIZED("SL301", Constants.FORBIDDEN),
NOT_FOUND("SL401", Constants.NOT_FOUND),
DUPLICATE_EMAIL("SL901", Constants.CONFLICT),
DUPLICATE_NICKNAME("SL902", Constants.BAD_REQUEST);
DUPLICATE_NICKNAME("SL902", Constants.BAD_REQUEST),
NOT_MET_CONDITION("SL1001", Constants.BAD_REQUEST);

private final String errorCode;
private final int status;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,8 @@ public Link toDomain() {
public void updateDomainId(Domain domain) {
domainId = domain.getDomainId();
}

public boolean isContainsIn(LinkBundle linkBundle) {
return linkBundleId.equals(linkBundle.getLinkBundleId());
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.seong.shoutlink.domain.link.repository;

import com.seong.shoutlink.domain.domain.service.result.DomainLinkResult;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
Expand All @@ -17,4 +18,6 @@ public interface LinkJpaRepository extends JpaRepository<LinkEntity, Long> {
+ " group by l.url"
+ " order by count(l.url) desc")
Page<DomainLinkResult> findDomainLinks(@Param("domainId") Long domainId, Pageable pageable);

List<LinkEntity> findAllByLinkBundleIdIn(List<Long> linkBundleIds);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
import com.seong.shoutlink.domain.link.service.result.LinkPaginationResult;
import com.seong.shoutlink.domain.linkbundle.LinkBundle;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
Expand Down Expand Up @@ -52,4 +54,18 @@ public Optional<Link> findById(Long linkId) {
return linkJpaRepository.findById(linkId)
.map(LinkEntity::toDomain);
}

@Override
public List<LinkWithLinkBundle> findAllByLinkBundlesIn(List<LinkBundle> linkBundles) {
List<Long> linkBundleIds = linkBundles.stream()
.map(LinkBundle::getLinkBundleId)
.toList();
List<LinkEntity> linkEntities = linkJpaRepository.findAllByLinkBundleIdIn(linkBundleIds);
Map<Long, LinkBundle> linkBundleIdAndLinkBundle = linkBundles.stream()
.collect(Collectors.toMap(LinkBundle::getLinkBundleId, linkBundle -> linkBundle));
return linkEntities.stream()
.map(linkEntity -> new LinkWithLinkBundle(linkEntity.toDomain(),
linkBundleIdAndLinkBundle.get(linkEntity.getLinkBundleId())))
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.seong.shoutlink.domain.link.LinkWithLinkBundle;
import com.seong.shoutlink.domain.link.service.result.LinkPaginationResult;
import com.seong.shoutlink.domain.linkbundle.LinkBundle;
import java.util.List;
import java.util.Optional;

public interface LinkRepository {
Expand All @@ -16,4 +17,6 @@ public interface LinkRepository {
void updateLinkDomain(Link link, Domain domain);

Optional<Link> findById(Long linkId);

List<LinkWithLinkBundle> findAllByLinkBundlesIn(List<LinkBundle> linkBundles);
}
16 changes: 16 additions & 0 deletions src/main/java/com/seong/shoutlink/domain/tag/HubTag.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.hub.Hub;
import lombok.Getter;

@Getter
public class HubTag {

private final Hub hub;
private final Tag tag;

public HubTag(Hub hub, Tag tag) {
this.hub = hub;
this.tag = tag;
}
}
19 changes: 19 additions & 0 deletions src/main/java/com/seong/shoutlink/domain/tag/Tag.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.seong.shoutlink.domain.tag;

import lombok.Getter;

@Getter
public class Tag {

private Long tagId;
private String name;

public Tag(String name) {
this(null, name);
}

public Tag(Long tagId, String name) {
this.tagId = tagId;
this.name = name;
}
}
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("hub")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class HubTagEntity extends TagEntity {

private Long hubId;

public HubTagEntity(String name, Long hubId) {
super(name);
this.hubId = hubId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.seong.shoutlink.domain.tag.repository;

import com.seong.shoutlink.domain.common.BaseEntity;
import com.seong.shoutlink.domain.hub.Hub;
import com.seong.shoutlink.domain.tag.HubTag;
import com.seong.shoutlink.domain.tag.Tag;
import jakarta.persistence.DiscriminatorColumn;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Inheritance;
import jakarta.persistence.InheritanceType;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Entity
@Table(name = "tag")
@DiscriminatorColumn
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public abstract class TagEntity extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long tagId;

private String name;

protected TagEntity(String name) {
this.name = name;
}

public static TagEntity from(HubTag hubTag) {
Hub hub = hubTag.getHub();
Tag tag = hubTag.getTag();
return new HubTagEntity(tag.getName(), hub.getHubId());
}

public Tag toDomain() {
return new Tag(tagId, name);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.seong.shoutlink.domain.tag.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

public interface TagJpaRepository extends JpaRepository<TagEntity, Long> {

@Query("delete from HubTagEntity t where t.hubId=:hubId")
long deleteByHubId(@Param("hubId") Long hubId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.seong.shoutlink.domain.tag.repository;

import com.seong.shoutlink.domain.hub.Hub;
import com.seong.shoutlink.domain.tag.HubTag;
import com.seong.shoutlink.domain.tag.Tag;
import com.seong.shoutlink.domain.tag.service.TagRepository;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
public class TagRepositoryImpl implements TagRepository {

private final TagJpaRepository tagJpaRepository;

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

@Override
public long deleteHubTags(Hub hub) {
return tagJpaRepository.deleteByHubId(hub.getHubId());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.seong.shoutlink.domain.tag.service;

import com.seong.shoutlink.domain.hub.Hub;
import com.seong.shoutlink.domain.tag.HubTag;
import com.seong.shoutlink.domain.tag.Tag;
import java.util.List;

public interface TagRepository {

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

long deleteHubTags(Hub hub);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.seong.shoutlink.domain.tag.service;

import static java.util.stream.Collectors.*;

import com.seong.shoutlink.domain.exception.ErrorCode;
import com.seong.shoutlink.domain.exception.ShoutLinkException;
import com.seong.shoutlink.domain.hub.Hub;
import com.seong.shoutlink.domain.hub.service.HubRepository;
import com.seong.shoutlink.domain.link.LinkWithLinkBundle;
import com.seong.shoutlink.domain.link.service.LinkRepository;
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.tag.HubTag;
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.response.CreateTagResponse;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class TagService {

private static final int MINIMUM_TAG_CONDITION = 5;
private static final int MAXIMUM_TAG_COUNT = 5;

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

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

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

List<HubTag> hubTags = autoGenerativeClient.generateTags(generateAutoTagCommand)
.stream()
.map(generatedTag -> new Tag(generatedTag.name()))
.map(tag -> new HubTag(hub, tag))
.toList();
if(!hubTags.isEmpty()) {
tagRepository.deleteHubTags(hub);
}
return CreateTagResponse.from(tagRepository.saveAll(hubTags));
}

private List<LinkBundleAndLinks> groupingLinks(List<LinkWithLinkBundle> links) {
return links.stream()
.collect(groupingBy(
LinkWithLinkBundle::getLinkBundle,
mapping(LinkWithLinkBundle::getLink, toList())))
.entrySet().stream()
.map(entry -> new LinkBundleAndLinks(entry.getKey(), entry.getValue()))
.toList();
}

private int calculateNumberOfTag(List<LinkWithLinkBundle> links) {
int totalLinkCount = links.size();
if (totalLinkCount < MINIMUM_TAG_CONDITION) {
throw new ShoutLinkException("태그 생성 조건을 충족하지 못했습니다.", ErrorCode.NOT_MET_CONDITION);
}
return Math.min(MAXIMUM_TAG_COUNT, totalLinkCount / MINIMUM_TAG_CONDITION);
}

private Hub getHub(Long hubId) {
return hubRepository.findById(hubId)
.orElseThrow(() -> new ShoutLinkException("존재하지 않는 허브입니다.", ErrorCode.NOT_FOUND));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import com.seong.shoutlink.domain.linkbundle.LinkBundle;
import java.util.List;

public record GenerateAutoTagCommand(List<AutoTagLinkBundle> linkBundles) {
public record GenerateAutoTagCommand(List<AutoTagLinkBundle> linkBundles, int generateTagCount) {

public record AutoTagLinkBundle(String description, List<AutoTagLink> links) {

Expand All @@ -21,7 +21,9 @@ public static AutoTagLink from(Link link) {
}
}

public static GenerateAutoTagCommand create(List<LinkBundleAndLinks> linkBundlesAndLinks) {
public static GenerateAutoTagCommand create(
List<LinkBundleAndLinks> linkBundlesAndLinks,
int generateTagCount) {
List<AutoTagLinkBundle> content = linkBundlesAndLinks.stream()
.map(linkBundleAndLinks -> {
List<AutoTagLink> autoTagLinks = linkBundleAndLinks.getLinks().stream()
Expand All @@ -30,6 +32,6 @@ public static GenerateAutoTagCommand create(List<LinkBundleAndLinks> linkBundles
return AutoTagLinkBundle.from(linkBundleAndLinks.getLinkBundle(), autoTagLinks);
})
.toList();
return new GenerateAutoTagCommand(content);
return new GenerateAutoTagCommand(content, generateTagCount);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.seong.shoutlink.domain.tag.service.request;

public record AutoCreateTagCommand(Long hubId) {

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

import com.seong.shoutlink.domain.tag.Tag;
import java.util.List;

public record CreateTagResponse(List<Long> tagIds) {

public static CreateTagResponse from(List<Tag> tags) {
List<Long> tagIds = tags.stream()
.map(Tag::getTagId)
.toList();
return new CreateTagResponse(tagIds);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ public GeminiClient(String url, String apiKey, ObjectMapper objectMapper, ApiCli

@Override
public List<GeneratedTag> generateTags(GenerateAutoTagCommand command) {
String requestPrompt = MessageFormat.format(GENERATE_TAG_PROMPT, 1);
String requestPrompt = MessageFormat.format(
GENERATE_TAG_PROMPT, command.generateTagCount());
AutoTagPrompt autoTagPrompt = new AutoTagPrompt(requestPrompt, command);
GeminiRequest geminiRequest = GeminiRequest.create(autoTagPrompt.toPromptString());
String requestBody = "";
Expand Down
Loading

0 comments on commit 816ac2a

Please sign in to comment.