Skip to content

Commit

Permalink
[Feature] 그룹 이름 중복 검사, 그룹 추천 받기를 구현한다 (#112)
Browse files Browse the repository at this point in the history
* refactor: 그룹 이름 중복을 허용하지 않는다

* feature: 그룹 이름 유일성 검증을 구현한다

* feature: 그룹 추천 받기를 구현한다

* refactor: 퀘스트 추천 DAO의 메서드 명을 축약한다
  • Loading branch information
vectorch9 authored Nov 16, 2023
1 parent e425324 commit 52fb53d
Show file tree
Hide file tree
Showing 11 changed files with 149 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package daybyquest.group.application;

import daybyquest.group.domain.Groups;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class CheckGroupNameService {

private final Groups groups;

public CheckGroupNameService(final Groups groups) {
this.groups = groups;
}

@Transactional(readOnly = true)
public void invoke(final String name) {
groups.validateNotExistentByName(name);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package daybyquest.group.application;

import daybyquest.group.dto.response.GroupResponse;
import daybyquest.group.dto.response.MultipleGroupsResponse;
import daybyquest.group.query.GroupDao;
import daybyquest.group.query.GroupData;
import daybyquest.group.query.GroupRecommendDao;
import daybyquest.user.domain.User;
import daybyquest.user.domain.Users;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class RecommendGroupsService {

private static final int MAX_RECOMMENDATION_COUNT = 5;

private final Users users;

private final GroupRecommendDao recommendDao;

private final GroupDao groupDao;

public RecommendGroupsService(final Users users, final GroupRecommendDao recommendDao,
final GroupDao groupDao) {
this.users = users;
this.recommendDao = recommendDao;
this.groupDao = groupDao;
}

@Transactional(readOnly = true)
public MultipleGroupsResponse invoke(final Long loginId) {
final User user = users.getById(loginId);
final List<Long> ids = recommendDao.getRecommendIds(MAX_RECOMMENDATION_COUNT,
user.getInterests());
final List<GroupData> groupData = groupDao.findAllByIdsIn(loginId, ids);
final List<GroupResponse> responses = groupData.stream().map(GroupResponse::of).toList();
return new MultipleGroupsResponse(responses);
}
}
4 changes: 1 addition & 3 deletions src/main/java/daybyquest/group/domain/Group.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

import daybyquest.global.error.exception.InvalidDomainException;
import daybyquest.image.domain.Image;
import jakarta.persistence.AttributeOverride;
import jakarta.persistence.Column;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
Expand All @@ -31,7 +30,7 @@ public class Group {

private String interest;

@Column(nullable = false, length = MAX_NAME_LENGTH)
@Column(nullable = false, length = MAX_NAME_LENGTH, unique = true)
private String name;

@Column(length = MAX_DESCRIPTION_LENGTH)
Expand All @@ -40,7 +39,6 @@ public class Group {
private boolean deleted;

@Embedded
@AttributeOverride(name = "imageUrl", column = @Column(name = "image_url"))
private Image image;

public Group(String interest, String name, String description, Image image) {
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/daybyquest/group/domain/GroupRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ interface GroupRepository extends Repository<Group, Long> {
Optional<Group> findById(final Long id);

boolean existsById(final Long id);

boolean existsByName(final String name);
}
10 changes: 9 additions & 1 deletion src/main/java/daybyquest/group/domain/Groups.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package daybyquest.group.domain;

import static daybyquest.global.error.ExceptionCode.ALREADY_MEMBER;
import static daybyquest.global.error.ExceptionCode.DUPLICATED_GROUP_NAME;

import daybyquest.global.error.exception.InvalidDomainException;
import daybyquest.global.error.exception.NotExistGroupException;
Expand Down Expand Up @@ -31,11 +32,18 @@ public class Groups {
public Long save(final Long userId, final Group group) {
users.validateModeratorById(userId);
interests.validateInterest(group.getInterest());
validateNotExistentByName(group.getName());
final Group savedGroup = groupRepository.save(group);
groupUserRepository.save(GroupUser.createGroupManager(userId, savedGroup));
return savedGroup.getId();
}

public void validateNotExistentByName(final String name) {
if (groupRepository.existsByName(name)) {
throw new InvalidDomainException(DUPLICATED_GROUP_NAME);
}
}

public void addUser(final GroupUser groupUser) {
validateExistentById(groupUser.getGroupId());
if (groupUser.isManager()) {
Expand Down Expand Up @@ -68,7 +76,7 @@ private void validateNotMember(final Long userId, final Long groupId) {
throw new InvalidDomainException(ALREADY_MEMBER);
}
}

public Group getById(final Long id) {
return groupRepository.findById(id).orElseThrow(NotExistGroupException::new);
}
Expand Down
27 changes: 25 additions & 2 deletions src/main/java/daybyquest/group/presentation/GroupQueryApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import daybyquest.auth.Authorization;
import daybyquest.auth.domain.AccessUser;
import daybyquest.global.query.NoOffsetIdPage;
import daybyquest.group.application.CheckGroupNameService;
import daybyquest.group.application.GetGroupProfileService;
import daybyquest.group.application.GetGroupUsersService;
import daybyquest.group.application.GetGroupsService;
import daybyquest.group.application.RecommendGroupsService;
import daybyquest.group.application.SearchGroupService;
import daybyquest.group.dto.response.GroupResponse;
import daybyquest.group.dto.response.MultipleGroupsResponse;
Expand All @@ -20,23 +22,37 @@
@RestController
public class GroupQueryApi {

private final CheckGroupNameService checkGroupNameService;

private final GetGroupProfileService getGroupProfileService;

private final GetGroupUsersService getGroupUsersService;

private final GetGroupsService getGroupsService;

private final RecommendGroupsService recommendGroupsService;

private final SearchGroupService searchGroupService;

public GroupQueryApi(final GetGroupProfileService getGroupProfileService,
public GroupQueryApi(final CheckGroupNameService checkGroupNameService,
final GetGroupProfileService getGroupProfileService,
final GetGroupUsersService getGroupUsersService, final GetGroupsService getGroupsService,
final RecommendGroupsService recommendGroupsService,
final SearchGroupService searchGroupService) {
this.checkGroupNameService = checkGroupNameService;
this.getGroupProfileService = getGroupProfileService;
this.getGroupUsersService = getGroupUsersService;
this.getGroupsService = getGroupsService;
this.recommendGroupsService = recommendGroupsService;
this.searchGroupService = searchGroupService;
}

@GetMapping("/group/{groupName}/check")
public ResponseEntity<Void> checkGroupName(@PathVariable final String groupName) {
checkGroupNameService.invoke(groupName);
return ResponseEntity.ok().build();
}

@GetMapping("/group/{groupId}")
@Authorization
public ResponseEntity<GroupResponse> getGroupProfile(final AccessUser accessUser,
Expand All @@ -59,7 +75,14 @@ public ResponseEntity<MultipleGroupsResponse> getGroups(final AccessUser accessU
final MultipleGroupsResponse response = getGroupsService.invoke(accessUser.getId());
return ResponseEntity.ok(response);
}


@GetMapping("/group/recommendation")
@Authorization
public ResponseEntity<MultipleGroupsResponse> recommendGroups(final AccessUser accessUser) {
final MultipleGroupsResponse response = recommendGroupsService.invoke(accessUser.getId());
return ResponseEntity.ok(response);
}

@GetMapping("/search/group")
@Authorization
public ResponseEntity<PageGroupsResponse> searchGroup(final AccessUser accessUser,
Expand Down
16 changes: 16 additions & 0 deletions src/main/java/daybyquest/group/query/GroupRecommendDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package daybyquest.group.query;

import daybyquest.group.domain.Group;
import java.util.Collection;
import java.util.List;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository;

public interface GroupRecommendDao extends Repository<Group, Long> {

@Query(value = "select g.id from `group` g where g.interest in :interests "
+ "and g.id >= FLOOR(1 + RAND() * (select MAX(`group`.id) from `group`)) "
+ "ORDER BY g.id limit :topN",
nativeQuery = true)
List<Long> getRecommendIds(final int topN, final Collection<String> interests);
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public RecommendQuestsService(final Users users, final QuestRecommendDao recomme
@Transactional(readOnly = true)
public MultipleQuestsResponse invoke(final Long loginId) {
final User user = users.getById(loginId);
final List<Long> ids = recommendDao.findTopNNormalQuestIdsByInterestIn(MAX_RECOMMENDATION_COUNT,
final List<Long> ids = recommendDao.getRecommendIds(MAX_RECOMMENDATION_COUNT,
user.getInterests());
final List<QuestData> questData = questDao.findAllByIdIn(loginId, ids);
final List<QuestResponse> responses = questData.stream().map(QuestResponse::of).toList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ public interface QuestRecommendDao extends Repository<Quest, Long> {
+ "and q.id >= FLOOR(1 + RAND() * (select MAX(quest.id) from quest)) "
+ "ORDER BY q.id limit :topN",
nativeQuery = true)
List<Long> findTopNNormalQuestIdsByInterestIn(final int topN, final Collection<String> interests);
List<Long> getRecommendIds(final int topN, final Collection<String> interests);
}
2 changes: 2 additions & 0 deletions src/main/resources/db/migration/V7__modify_group.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER table `group`
add constraint `GROUP_UNIQUE_NAME` unique (`name`);
31 changes: 31 additions & 0 deletions src/test/java/daybyquest/group/domain/GroupsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;

import daybyquest.global.error.exception.InvalidDomainException;
import daybyquest.global.error.exception.NotExistGroupException;
import daybyquest.interest.domain.Interests;
import daybyquest.user.domain.Users;
Expand Down Expand Up @@ -56,6 +57,36 @@ public class GroupsTest {
});
}

@Test
void 그룹을_저장할_때_중복_이름이_있다면_에외를_던진다() {
// given
final Long userId = 1L;
given(groupRepository.existsByName(GROUP_1.name)).willReturn(true);

// when & then
assertThatThrownBy(() -> groups.save(userId, GROUP_1.생성()))
.isInstanceOf(InvalidDomainException.class);
}

@Test
void 그룹_이름_유일성을_검증한다() {
// given & when
groups.validateNotExistentByName(GROUP_1.name);

// then
then(groupRepository).should().existsByName(GROUP_1.name);
}

@Test
void 그룹_이름_유일성을_검증_시_이미_있다면_예외를_던진다() {
// given
given(groupRepository.existsByName(GROUP_1.name)).willReturn(true);

// when
assertThatThrownBy(() -> groups.validateNotExistentByName(GROUP_1.name))
.isInstanceOf(InvalidDomainException.class);
}

@Test
void 그룹을_저장할_땐_사용자를_함께_저장한다() {
// given
Expand Down

0 comments on commit 52fb53d

Please sign in to comment.