diff --git a/sql/group.sql b/sql/group.sql index d2f25e6..f8000f7 100644 --- a/sql/group.sql +++ b/sql/group.sql @@ -46,3 +46,26 @@ CREATE TABLE `group_user_score` CREATE UNIQUE INDEX uidx__group_user_id ON group_user_score (group_user_id); CREATE INDEX idx__group_id__group_user_id ON group_user_score (group_id, group_user_id); CREATE INDEX idx__uid ON group_user_score (uid); + +-- 그룹 태그 +CREATE TABLE `group_tag` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'group tag id', + `name` varchar(30) NOT NULL COMMENT '태그명', + `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '생성일', + `modified_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '수정일', + PRIMARY KEY (`id`) +) ENGINE = InnoDB AUTO_INCREMENT=200000 DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT='그룹 태그'; +CREATE UNIQUE INDEX uidx__group_tag_name ON group_tag (name); + +-- 그룹 태그 매핑 +CREATE TABLE `group_tag_map` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'group tag map id', + `group_id` bigint NOT NULL COMMENT 'group id', + `tag_id` bigint NOT NULL COMMENT 'group tag id', + `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '생성일', + `modified_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '수정일', + PRIMARY KEY (`id`) +) ENGINE = InnoDB AUTO_INCREMENT=200000 DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT='그룹 태그 매핑'; +CREATE UNIQUE INDEX uidx__group_id__tag_id ON group_tag_map (group_id, tag_id); \ No newline at end of file diff --git a/src/main/kotlin/com/hero/alignlab/common/extension/QuerydslExtension.kt b/src/main/kotlin/com/hero/alignlab/common/extension/QuerydslExtension.kt index a16a55f..c77df6a 100644 --- a/src/main/kotlin/com/hero/alignlab/common/extension/QuerydslExtension.kt +++ b/src/main/kotlin/com/hero/alignlab/common/extension/QuerydslExtension.kt @@ -44,7 +44,7 @@ fun NumberPath.isEquals(parameter: Long?): BooleanExpression? { } fun StringPath.isContains(parameter: String?): BooleanExpression? { - return parameter?.let { param -> this.contains(param) } + return parameter?.takeIf { param -> param.isNotEmpty() }?.let { param -> this.contains(param) } } fun NumberPath.isIn(parameters: Set?): BooleanExpression? { diff --git a/src/main/kotlin/com/hero/alignlab/config/database/QueryDslConfig.kt b/src/main/kotlin/com/hero/alignlab/config/database/QueryDslConfig.kt new file mode 100644 index 0000000..8f2aa3f --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/config/database/QueryDslConfig.kt @@ -0,0 +1,16 @@ +package com.hero.alignlab.config.database + +import com.querydsl.jpa.impl.JPAQueryFactory +import jakarta.persistence.EntityManager +import jakarta.persistence.PersistenceContext +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class QueryDslConfig { + @PersistenceContext(name = "heroEntityManager") + private lateinit var entityManager: EntityManager + + @Bean + fun jpaQueryFactory(): JPAQueryFactory = JPAQueryFactory(entityManager) +} \ No newline at end of file diff --git a/src/main/kotlin/com/hero/alignlab/domain/discussion/domain/Discussion.kt b/src/main/kotlin/com/hero/alignlab/domain/discussion/domain/Discussion.kt index a9fe16a..3b359f8 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/discussion/domain/Discussion.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/discussion/domain/Discussion.kt @@ -9,7 +9,7 @@ import jakarta.persistence.* class Discussion( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - val id: Long = -1L, + val id: Long = 0L, @Column(name = "uid") val uid: Long, diff --git a/src/main/kotlin/com/hero/alignlab/domain/discussion/domain/DiscussionComment.kt b/src/main/kotlin/com/hero/alignlab/domain/discussion/domain/DiscussionComment.kt index b2d7c92..a613b8d 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/discussion/domain/DiscussionComment.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/discussion/domain/DiscussionComment.kt @@ -8,7 +8,7 @@ import jakarta.persistence.* class DiscussionComment( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - val id: Long = -1L, + val id: Long = 0L, @Column(name = "uid") val uid: Long, diff --git a/src/main/kotlin/com/hero/alignlab/domain/group/application/GroupFacade.kt b/src/main/kotlin/com/hero/alignlab/domain/group/application/GroupFacade.kt index 91db601..166a8f7 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/group/application/GroupFacade.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/group/application/GroupFacade.kt @@ -7,11 +7,15 @@ import com.hero.alignlab.common.model.HeroPageRequest import com.hero.alignlab.config.database.TransactionTemplates import com.hero.alignlab.domain.auth.model.AuthUser import com.hero.alignlab.domain.group.domain.Group +import com.hero.alignlab.domain.group.domain.GroupTag import com.hero.alignlab.domain.group.domain.GroupUser import com.hero.alignlab.domain.group.domain.GroupUserScore import com.hero.alignlab.domain.group.model.CreateGroupContext +import com.hero.alignlab.domain.group.model.UpdateGroupContext import com.hero.alignlab.domain.group.model.request.CheckGroupRegisterRequest import com.hero.alignlab.domain.group.model.request.CreateGroupRequest +import com.hero.alignlab.domain.group.model.request.CreateGroupTagRequest +import com.hero.alignlab.domain.group.model.request.UpdateGroupRequest import com.hero.alignlab.domain.group.model.response.* import com.hero.alignlab.domain.pose.application.PoseSnapshotService import com.hero.alignlab.domain.pose.domain.vo.PoseType.Companion.BAD_POSE @@ -33,6 +37,7 @@ class GroupFacade( private val groupService: GroupService, private val groupUserService: GroupUserService, private val groupUserScoreService: GroupUserScoreService, + private val groupTagService: GroupTagService, private val userInfoService: UserInfoService, private val txTemplates: TransactionTemplates, private val publisher: ApplicationEventPublisher, @@ -54,19 +59,42 @@ class GroupFacade( val group = CreateGroupContext(user, request).create() - val createdGroup = createGroup(user, group) + txTemplates.writer.executes { + val createdGroup = createGroup(user, group) + val createdGroupTags = createGroupTag(createdGroup.id, request.tagNames) - CreateGroupResponse.from(createdGroup) + CreateGroupResponse.from(createdGroup, createdGroupTags) + } } } fun createGroup(user: AuthUser, group: Group): Group { + val createdGroup = groupService.saveSync(group) + + publisher.publishEvent(CreateGroupEvent(createdGroup)) + + return createdGroup + } + + fun createGroupTag(groupId: Long, tagNames: List?): List { + return if (!tagNames.isNullOrEmpty()) { + groupTagService.validateGroupTag(tagNames) + groupTagService.saveSync(CreateGroupTagRequest(groupId, tagNames)) + } else emptyList() + } + + suspend fun updateGroup(user: AuthUser, groupId: Long, request: UpdateGroupRequest): UpdateGroupResponse { + val group = groupService.findByIdAndOwnerUidOrThrow(groupId, user.uid) return txTemplates.writer.executes { - val createdGroup = groupService.saveSync(group) + val updatedGroup = groupService.saveSync(UpdateGroupContext(group, request).update()) - publisher.publishEvent(CreateGroupEvent(createdGroup)) + val updatedGroupTag = if (!request.tagNames.isNullOrEmpty()) { + groupTagService.validateGroupTag(request.tagNames) + groupTagService.deleteGroupTagMapSyncByGroupId(groupId) + groupTagService.saveSync(CreateGroupTagRequest(groupId, request.tagNames)) + } else emptyList() - createdGroup + UpdateGroupResponse.from(updatedGroup, updatedGroupTag) } } @@ -90,6 +118,7 @@ class GroupFacade( txTemplates.writer.executes { groupUserScoreService.deleteAllByUid(uid) + groupTagService.deleteGroupTagMapSyncByGroupId(groupId) } } @@ -185,11 +214,12 @@ class GroupFacade( .filterNot { groupUserScore -> groupUserScore.score == null } .sortedBy { groupUserScore -> groupUserScore.score } .take(5) - } - ) { group, groupUserScore -> + }, + { groupTagService.findByGroupId(groupId) }, + ) { group, groupUserScore, tags -> val ownerGroupUser = userInfoService.getUserByIdOrThrow(group.ownerUid) - GetGroupResponse.from(group, ownerGroupUser.nickname).run { + GetGroupResponse.of(group, tags, ownerGroupUser.nickname).run { when (group.ownerUid == user.uid) { true -> this false -> this.copy(joinCode = null) @@ -222,8 +252,8 @@ class GroupFacade( } } - suspend fun searchGroup(user: AuthUser, pageRequest: HeroPageRequest): Page { - val groups = groupService.findAll(pageRequest.toDefault()) + suspend fun searchGroup(user: AuthUser, tagName: String?, pageRequest: HeroPageRequest): Page { + val groups = groupService.findByTagNameAndPage(tagName, pageRequest.toDefault()) val groupUserByUid = groups.content.map { group -> group.id } .run { groupUserService.findByUidAndGroupIdIn(user.uid, this) } diff --git a/src/main/kotlin/com/hero/alignlab/domain/group/application/GroupService.kt b/src/main/kotlin/com/hero/alignlab/domain/group/application/GroupService.kt index f5f1524..da37edb 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/group/application/GroupService.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/group/application/GroupService.kt @@ -1,13 +1,7 @@ package com.hero.alignlab.domain.group.application -import com.hero.alignlab.common.extension.executes -import com.hero.alignlab.config.database.TransactionTemplates -import com.hero.alignlab.domain.auth.model.AuthUser import com.hero.alignlab.domain.group.domain.Group import com.hero.alignlab.domain.group.infrastructure.GroupRepository -import com.hero.alignlab.domain.group.model.UpdateGroupContext -import com.hero.alignlab.domain.group.model.request.UpdateGroupRequest -import com.hero.alignlab.domain.group.model.response.UpdateGroupResponse import com.hero.alignlab.exception.ErrorCode import com.hero.alignlab.exception.NotFoundException import kotlinx.coroutines.Dispatchers @@ -21,18 +15,7 @@ import org.springframework.transaction.annotation.Transactional @Service class GroupService( private val groupRepository: GroupRepository, - private val txTemplates: TransactionTemplates, ) { - suspend fun updateGroup(user: AuthUser, id: Long, request: UpdateGroupRequest): UpdateGroupResponse { - val group = findByIdAndOwnerUidOrThrow(id, user.uid) - - val updatedGroup = txTemplates.writer.executes { - groupRepository.save(UpdateGroupContext(group, request).update()) - } - - return UpdateGroupResponse.from(updatedGroup) - } - suspend fun existsByName(name: String): Boolean { return withContext(Dispatchers.IO) { groupRepository.existsByName(name) @@ -72,9 +55,9 @@ class GroupService( return groupRepository.save(group) } - suspend fun findAll(pageable: Pageable): Page { + suspend fun findByTagNameAndPage(tagName: String?, pageable: Pageable): Page { return withContext(Dispatchers.IO) { - groupRepository.findAll(pageable) + groupRepository.findByTagNameAndPage(tagName, pageable) } } diff --git a/src/main/kotlin/com/hero/alignlab/domain/group/application/GroupTagService.kt b/src/main/kotlin/com/hero/alignlab/domain/group/application/GroupTagService.kt new file mode 100644 index 0000000..622269e --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/domain/group/application/GroupTagService.kt @@ -0,0 +1,62 @@ +package com.hero.alignlab.domain.group.application + +import com.hero.alignlab.domain.group.domain.GroupTag +import com.hero.alignlab.domain.group.domain.GroupTagMap +import com.hero.alignlab.domain.group.infrastructure.GroupTagMapRepository +import com.hero.alignlab.domain.group.infrastructure.GroupTagRepository +import com.hero.alignlab.domain.group.model.request.CreateGroupTagRequest +import com.hero.alignlab.exception.ErrorCode +import com.hero.alignlab.exception.InvalidRequestException +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class GroupTagService( + private val groupTagRepository: GroupTagRepository, + private val groupTagMapRepository: GroupTagMapRepository, +) { + @Transactional + fun saveSync(request: CreateGroupTagRequest): List { + val existsTags = groupTagRepository.findAllByNameIn(request.tagNames) + val existsTagNames = existsTags.map { tag -> tag.name } + + val needToCreateTags = request.tagNames + .filterNot { tagName -> existsTagNames.contains(tagName) } + .map { tagName -> GroupTag(name = tagName) } + + val createdTags = groupTagRepository.saveAll(needToCreateTags) + + (createdTags + existsTags) + .map { tag -> GroupTagMap(groupId = request.groupId, tagId = tag.id) } + .run { groupTagMapRepository.saveAll(this) } + + return (createdTags + existsTags) + } + + @Transactional + fun deleteGroupTagMapSyncByGroupId(groupId: Long) { + /** + * 왜, QueryDSL로 삭제를 했는지 + * https://docs.jboss.org/hibernate/orm/4.2/javadocs/org/hibernate/event/internal/AbstractFlushingEventListener.html + */ + groupTagRepository.deleteGroupTagMapByGroupId(groupId) + } + + suspend fun findByGroupId(groupId: Long): List { + return withContext(Dispatchers.IO) { + groupTagRepository.findByGroupId(groupId) + } + } + + fun validateGroupTag(tagNames: List) { + if (tagNames.size > 3) { + throw InvalidRequestException(ErrorCode.OVER_COUNT_GROUP_TAG_ERROR) + } + + if (tagNames.size != tagNames.distinct().size) { + throw InvalidRequestException(ErrorCode.DUPLICATE_GROUP_TAG_NAME_ERROR) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/hero/alignlab/domain/group/domain/Group.kt b/src/main/kotlin/com/hero/alignlab/domain/group/domain/Group.kt index c91e428..fa34c6d 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/group/domain/Group.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/group/domain/Group.kt @@ -8,7 +8,7 @@ import jakarta.persistence.* data class Group( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - val id: Long = -1, + val id: Long = 0L, @Column(name = "name") var name: String, diff --git a/src/main/kotlin/com/hero/alignlab/domain/group/domain/GroupTag.kt b/src/main/kotlin/com/hero/alignlab/domain/group/domain/GroupTag.kt new file mode 100644 index 0000000..90e9d23 --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/domain/group/domain/GroupTag.kt @@ -0,0 +1,15 @@ +package com.hero.alignlab.domain.group.domain + +import com.hero.alignlab.domain.common.domain.BaseEntity +import jakarta.persistence.* + +@Entity +@Table(name = "`group_tag`") +data class GroupTag( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long = 0L, + + @Column(name = "name") + var name: String, +) : BaseEntity() \ No newline at end of file diff --git a/src/main/kotlin/com/hero/alignlab/domain/group/domain/GroupTagMap.kt b/src/main/kotlin/com/hero/alignlab/domain/group/domain/GroupTagMap.kt new file mode 100644 index 0000000..33ef049 --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/domain/group/domain/GroupTagMap.kt @@ -0,0 +1,18 @@ +package com.hero.alignlab.domain.group.domain + +import com.hero.alignlab.domain.common.domain.BaseEntity +import jakarta.persistence.* + +@Entity +@Table(name = "`group_tag_map`") +data class GroupTagMap ( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long = 0L, + + @Column(name = "group_id") + val groupId: Long, + + @Column(name = "tag_id") + val tagId: Long +) : BaseEntity() \ No newline at end of file diff --git a/src/main/kotlin/com/hero/alignlab/domain/group/domain/GroupUser.kt b/src/main/kotlin/com/hero/alignlab/domain/group/domain/GroupUser.kt index 0203f95..ac64adb 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/group/domain/GroupUser.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/group/domain/GroupUser.kt @@ -8,7 +8,7 @@ import jakarta.persistence.* data class GroupUser( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - val id: Long = -1, + val id: Long = 0L, @Column(name = "group_id") val groupId: Long, diff --git a/src/main/kotlin/com/hero/alignlab/domain/group/domain/GroupUserScore.kt b/src/main/kotlin/com/hero/alignlab/domain/group/domain/GroupUserScore.kt index 074d82d..04dc288 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/group/domain/GroupUserScore.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/group/domain/GroupUserScore.kt @@ -8,7 +8,7 @@ import jakarta.persistence.* class GroupUserScore( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - val id: Long = -1, + val id: Long = 0L, @Column(name = "group_id") val groupId: Long, diff --git a/src/main/kotlin/com/hero/alignlab/domain/group/infrastructure/GroupQRepository.kt b/src/main/kotlin/com/hero/alignlab/domain/group/infrastructure/GroupQRepository.kt new file mode 100644 index 0000000..bff8386 --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/domain/group/infrastructure/GroupQRepository.kt @@ -0,0 +1,9 @@ +package com.hero.alignlab.domain.group.infrastructure + +import com.hero.alignlab.domain.group.domain.Group +import org.springframework.data.domain.Page +import org.springframework.data.domain.Pageable + +interface GroupQRepository { + fun findByTagNameAndPage(tagName: String?, pageable: Pageable): Page +} \ No newline at end of file diff --git a/src/main/kotlin/com/hero/alignlab/domain/group/infrastructure/GroupQRepositoryImpl.kt b/src/main/kotlin/com/hero/alignlab/domain/group/infrastructure/GroupQRepositoryImpl.kt new file mode 100644 index 0000000..16f0449 --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/domain/group/infrastructure/GroupQRepositoryImpl.kt @@ -0,0 +1,52 @@ +package com.hero.alignlab.domain.group.infrastructure + +import com.hero.alignlab.common.extension.isContains +import com.hero.alignlab.domain.group.domain.Group +import com.hero.alignlab.domain.group.domain.QGroup.group +import com.hero.alignlab.domain.group.domain.QGroupTag.groupTag +import com.hero.alignlab.domain.group.domain.QGroupTagMap.groupTagMap +import com.querydsl.core.types.OrderSpecifier +import com.querydsl.core.types.dsl.ComparableExpressionBase +import com.querydsl.jpa.impl.JPAQueryFactory +import org.springframework.data.domain.Page +import org.springframework.data.domain.PageImpl +import org.springframework.data.domain.Pageable + +class GroupQRepositoryImpl( + private val queryFactory: JPAQueryFactory, +) : GroupQRepository { + override fun findByTagNameAndPage(tagName: String?, pageable: Pageable): Page { + val groups = queryFactory + .selectDistinct(group) + .from(group) + .leftJoin(groupTagMap).on(group.id.eq(groupTagMap.groupId)) + .leftJoin(groupTag).on(groupTagMap.tagId.eq(groupTag.id)) + .where(groupTag.name.isContains(tagName)) + .offset(pageable.offset) + .limit(pageable.pageSize.toLong()) + .orderBy(getGroupOrderSpecifier(pageable)) + .fetch() + + val count = queryFactory + .select(group.countDistinct()) + .from(group) + .leftJoin(groupTagMap).on(group.id.eq(groupTagMap.groupId)) + .leftJoin(groupTag).on(groupTagMap.tagId.eq(groupTag.id)) + .where(groupTag.name.isContains(tagName)) + .orderBy(getGroupOrderSpecifier(pageable)) + .fetchOne() ?: 0L + + return PageImpl(groups, pageable, count) + } + + private fun getGroupOrderSpecifier(pageable: Pageable): OrderSpecifier?>? { + val order = pageable.sort.firstOrNull() ?: return null + val orderSpecifier: ComparableExpressionBase> = when (order.property) { + "createdAt" -> group.createdAt + "name" -> group.name + "ownerUid" -> group.ownerUid + else -> group.createdAt + } + return if (order.isDescending) orderSpecifier.desc() else orderSpecifier.asc() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/hero/alignlab/domain/group/infrastructure/GroupRepository.kt b/src/main/kotlin/com/hero/alignlab/domain/group/infrastructure/GroupRepository.kt index 6463acf..77cf701 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/group/infrastructure/GroupRepository.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/group/infrastructure/GroupRepository.kt @@ -8,7 +8,7 @@ import java.time.LocalDateTime @Repository @Transactional(readOnly = true) -interface GroupRepository : JpaRepository { +interface GroupRepository : JpaRepository, GroupQRepository { fun existsByName(name: String): Boolean fun findByIdAndOwnerUid(id: Long, ownerUid: Long): Group? diff --git a/src/main/kotlin/com/hero/alignlab/domain/group/infrastructure/GroupTagMapRepository.kt b/src/main/kotlin/com/hero/alignlab/domain/group/infrastructure/GroupTagMapRepository.kt new file mode 100644 index 0000000..8de0426 --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/domain/group/infrastructure/GroupTagMapRepository.kt @@ -0,0 +1,10 @@ +package com.hero.alignlab.domain.group.infrastructure + +import com.hero.alignlab.domain.group.domain.GroupTagMap +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository +import org.springframework.transaction.annotation.Transactional + +@Transactional(readOnly = true) +@Repository +interface GroupTagMapRepository : JpaRepository \ No newline at end of file diff --git a/src/main/kotlin/com/hero/alignlab/domain/group/infrastructure/GroupTagQRepository.kt b/src/main/kotlin/com/hero/alignlab/domain/group/infrastructure/GroupTagQRepository.kt new file mode 100644 index 0000000..6cf05f8 --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/domain/group/infrastructure/GroupTagQRepository.kt @@ -0,0 +1,9 @@ +package com.hero.alignlab.domain.group.infrastructure + +import com.hero.alignlab.domain.group.domain.GroupTag + +interface GroupTagQRepository { + fun findByGroupId(groupId: Long): List + + fun deleteGroupTagMapByGroupId(groupId: Long) +} \ No newline at end of file diff --git a/src/main/kotlin/com/hero/alignlab/domain/group/infrastructure/GroupTagQRepositoryImpl.kt b/src/main/kotlin/com/hero/alignlab/domain/group/infrastructure/GroupTagQRepositoryImpl.kt new file mode 100644 index 0000000..529eba4 --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/domain/group/infrastructure/GroupTagQRepositoryImpl.kt @@ -0,0 +1,23 @@ +package com.hero.alignlab.domain.group.infrastructure + +import com.hero.alignlab.domain.group.domain.GroupTag +import com.hero.alignlab.domain.group.domain.QGroupTag.groupTag +import com.hero.alignlab.domain.group.domain.QGroupTagMap.groupTagMap +import com.querydsl.jpa.impl.JPAQueryFactory + +class GroupTagQRepositoryImpl( + private val queryFactory: JPAQueryFactory, +) : GroupTagQRepository { + override fun findByGroupId(groupId: Long): List { + return queryFactory.selectFrom(groupTag) + .join(groupTagMap).on(groupTag.id.eq(groupTagMap.tagId)) + .where(groupTagMap.groupId.eq(groupId)) + .fetch() + } + + override fun deleteGroupTagMapByGroupId(groupId: Long) { + queryFactory.delete(groupTagMap) + .where(groupTagMap.groupId.eq(groupId)) + .execute() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/hero/alignlab/domain/group/infrastructure/GroupTagRepository.kt b/src/main/kotlin/com/hero/alignlab/domain/group/infrastructure/GroupTagRepository.kt new file mode 100644 index 0000000..bd72a84 --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/domain/group/infrastructure/GroupTagRepository.kt @@ -0,0 +1,12 @@ +package com.hero.alignlab.domain.group.infrastructure + +import com.hero.alignlab.domain.group.domain.GroupTag +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository +import org.springframework.transaction.annotation.Transactional + +@Transactional(readOnly = true) +@Repository +interface GroupTagRepository : JpaRepository, GroupTagQRepository { + fun findAllByNameIn(names: List): Set +} \ No newline at end of file diff --git a/src/main/kotlin/com/hero/alignlab/domain/group/model/request/CreateGroupRequest.kt b/src/main/kotlin/com/hero/alignlab/domain/group/model/request/CreateGroupRequest.kt index 8b21684..9ba24d8 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/group/model/request/CreateGroupRequest.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/group/model/request/CreateGroupRequest.kt @@ -11,4 +11,6 @@ data class CreateGroupRequest( val joinCode: String?, /** 정원, 기본값 30 */ val userCapacity: Int?, + /** 태그 리스트, 최대 3개 */ + val tagNames: List?, ) diff --git a/src/main/kotlin/com/hero/alignlab/domain/group/model/request/CreateGroupTagRequest.kt b/src/main/kotlin/com/hero/alignlab/domain/group/model/request/CreateGroupTagRequest.kt new file mode 100644 index 0000000..04ef3f0 --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/domain/group/model/request/CreateGroupTagRequest.kt @@ -0,0 +1,8 @@ +package com.hero.alignlab.domain.group.model.request + +data class CreateGroupTagRequest( + /** 그룹명, 중복 불가능 */ + val groupId: Long, + /** 태그 리스트, 최대 3개 */ + val tagNames: List, +) \ No newline at end of file diff --git a/src/main/kotlin/com/hero/alignlab/domain/group/model/request/GroupTagRequest.kt b/src/main/kotlin/com/hero/alignlab/domain/group/model/request/GroupTagRequest.kt new file mode 100644 index 0000000..c9480dd --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/domain/group/model/request/GroupTagRequest.kt @@ -0,0 +1,8 @@ +package com.hero.alignlab.domain.group.model.request + +data class GroupTagRequest( + /** 태그 ID */ + val tagId: Long, + /** 태그명 */ + val tagName: String, +) diff --git a/src/main/kotlin/com/hero/alignlab/domain/group/model/request/UpdateGroupRequest.kt b/src/main/kotlin/com/hero/alignlab/domain/group/model/request/UpdateGroupRequest.kt index 29ab4b9..20e4ab9 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/group/model/request/UpdateGroupRequest.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/group/model/request/UpdateGroupRequest.kt @@ -11,4 +11,6 @@ data class UpdateGroupRequest( val joinCode: String?, /** 정원, 기본값 30 */ val userCapacity: Int?, + /** 수정할 태그 리스트 */ + val tagNames: List?, ) diff --git a/src/main/kotlin/com/hero/alignlab/domain/group/model/response/CreateGroupResponse.kt b/src/main/kotlin/com/hero/alignlab/domain/group/model/response/CreateGroupResponse.kt index 492b423..0392df3 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/group/model/response/CreateGroupResponse.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/group/model/response/CreateGroupResponse.kt @@ -1,6 +1,7 @@ package com.hero.alignlab.domain.group.model.response import com.hero.alignlab.domain.group.domain.Group +import com.hero.alignlab.domain.group.domain.GroupTag data class CreateGroupResponse( /** group id */ @@ -9,13 +10,16 @@ data class CreateGroupResponse( val name: String, /** 그룹 설명 */ val description: String?, + /** 그룹 태그 리스트 */ + val tags: List?, ) { companion object { - fun from(group: Group): CreateGroupResponse { + fun from(group: Group, tags: List): CreateGroupResponse { return CreateGroupResponse( id = group.id, name = group.name, description = group.description, + tags = tags.map { GroupTagResponse(it.id, it.name) }, ) } } diff --git a/src/main/kotlin/com/hero/alignlab/domain/group/model/response/GetGroupResponse.kt b/src/main/kotlin/com/hero/alignlab/domain/group/model/response/GetGroupResponse.kt index fb9540a..b3d44bc 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/group/model/response/GetGroupResponse.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/group/model/response/GetGroupResponse.kt @@ -1,6 +1,7 @@ package com.hero.alignlab.domain.group.model.response import com.hero.alignlab.domain.group.domain.Group +import com.hero.alignlab.domain.group.domain.GroupTag data class GetGroupResponse( val id: Long, @@ -16,10 +17,12 @@ data class GetGroupResponse( val userCount: Int, /** 그룹 정원 */ val userCapacity: Int, - val ranks: List? = null + val ranks: List? = null, + /** 그룹 태그 리스트 */ + val tags: List?, ) { companion object { - fun from(group: Group, ownerName: String): GetGroupResponse { + fun of(group: Group, tags: List?, ownerName: String): GetGroupResponse { return GetGroupResponse( id = group.id, name = group.name, @@ -29,7 +32,8 @@ data class GetGroupResponse( isHidden = group.isHidden, joinCode = group.joinCode, userCount = group.userCount, - userCapacity = group.userCapacity + userCapacity = group.userCapacity, + tags = tags?.map { GroupTagResponse(it.id, it.name) }, ) } } diff --git a/src/main/kotlin/com/hero/alignlab/domain/group/model/response/GroupTagResponse.kt b/src/main/kotlin/com/hero/alignlab/domain/group/model/response/GroupTagResponse.kt new file mode 100644 index 0000000..e1e2c2f --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/domain/group/model/response/GroupTagResponse.kt @@ -0,0 +1,8 @@ +package com.hero.alignlab.domain.group.model.response + +data class GroupTagResponse( + /** 태그 ID */ + val tagId: Long, + /** 태그명 */ + val tagName: String, +) diff --git a/src/main/kotlin/com/hero/alignlab/domain/group/model/response/UpdateGroupResponse.kt b/src/main/kotlin/com/hero/alignlab/domain/group/model/response/UpdateGroupResponse.kt index 6e03dfe..e0c4cfa 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/group/model/response/UpdateGroupResponse.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/group/model/response/UpdateGroupResponse.kt @@ -1,6 +1,7 @@ package com.hero.alignlab.domain.group.model.response import com.hero.alignlab.domain.group.domain.Group +import com.hero.alignlab.domain.group.domain.GroupTag data class UpdateGroupResponse( /** group id */ @@ -9,13 +10,16 @@ data class UpdateGroupResponse( val name: String, /** 그룹 설명 */ val description: String?, + /** 그룹 태그 리스트 */ + val tags: List?, ) { companion object { - fun from(group: Group): UpdateGroupResponse { + fun from(group: Group, tags: List): UpdateGroupResponse { return UpdateGroupResponse( id = group.id, name = group.name, description = group.description, + tags = tags.map { GroupTagResponse(it.id, it.name) }, ) } } diff --git a/src/main/kotlin/com/hero/alignlab/domain/group/resource/GroupResource.kt b/src/main/kotlin/com/hero/alignlab/domain/group/resource/GroupResource.kt index d78eec1..3411d28 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/group/resource/GroupResource.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/group/resource/GroupResource.kt @@ -9,7 +9,6 @@ import com.hero.alignlab.common.model.PageResponse import com.hero.alignlab.common.model.Response import com.hero.alignlab.domain.auth.model.AuthUser import com.hero.alignlab.domain.group.application.GroupFacade -import com.hero.alignlab.domain.group.application.GroupService import com.hero.alignlab.domain.group.model.request.CheckGroupRegisterRequest import com.hero.alignlab.domain.group.model.request.CreateGroupRequest import com.hero.alignlab.domain.group.model.request.UpdateGroupRequest @@ -25,7 +24,6 @@ import org.springframework.web.bind.annotation.* @RestController @RequestMapping(produces = [MediaType.APPLICATION_JSON_VALUE]) class GroupResource( - private val groupService: GroupService, private val groupFacade: GroupFacade, ) { /** @@ -51,9 +49,10 @@ class GroupResource( @GetMapping("/api/v1/groups") suspend fun searchGroups( user: AuthUser, + @RequestParam(required = false) tagName: String?, @ParameterObject pageRequest: HeroPageRequest, ): PageResponse { - return groupFacade.searchGroup(user, pageRequest).wrapPage() + return groupFacade.searchGroup(user, tagName, pageRequest).wrapPage() } @Operation(summary = "그룹 생성") @@ -72,9 +71,9 @@ class GroupResource( @PathVariable id: Long, @RequestBody request: UpdateGroupRequest ): ResponseEntity> { - return groupService.updateGroup( + return groupFacade.updateGroup( user = user, - id = id, + groupId = id, request = request ).wrapOk() } diff --git a/src/main/kotlin/com/hero/alignlab/domain/image/domain/ImageMetadata.kt b/src/main/kotlin/com/hero/alignlab/domain/image/domain/ImageMetadata.kt index 339f03a..b8515ed 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/image/domain/ImageMetadata.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/image/domain/ImageMetadata.kt @@ -9,10 +9,10 @@ import jakarta.persistence.* class ImageMetadata( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - val id: Long = -1, + val id: Long = 0L, @Column(name = "uid") - val uid: Long = -1, + val uid: Long = 0L, @Column(name = "filename") val filename: String, diff --git a/src/main/kotlin/com/hero/alignlab/domain/log/domain/SystemActionLog.kt b/src/main/kotlin/com/hero/alignlab/domain/log/domain/SystemActionLog.kt index 5443676..e185e3e 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/log/domain/SystemActionLog.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/log/domain/SystemActionLog.kt @@ -8,7 +8,7 @@ import jakarta.persistence.* class SystemActionLog( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - val id: Long = -1L, + val id: Long = 0L, @Column(name = "uid") val uid: Long? = null, diff --git a/src/main/kotlin/com/hero/alignlab/domain/notification/domain/PoseNotification.kt b/src/main/kotlin/com/hero/alignlab/domain/notification/domain/PoseNotification.kt index 7b2727f..fd6bd46 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/notification/domain/PoseNotification.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/notification/domain/PoseNotification.kt @@ -10,10 +10,10 @@ import jakarta.persistence.* class PoseNotification( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - val id: Long = -1, + val id: Long = 0L, @Column(name = "uid") - val uid: Long = -1, + val uid: Long = 0L, @Column(name = "is_active") var isActive: Boolean = true, diff --git a/src/main/kotlin/com/hero/alignlab/domain/pose/domain/PoseCount.kt b/src/main/kotlin/com/hero/alignlab/domain/pose/domain/PoseCount.kt index dd6e448..83683f5 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/pose/domain/PoseCount.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/pose/domain/PoseCount.kt @@ -11,7 +11,7 @@ class PoseCount( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") - val id: Long = -1, + val id: Long = 0L, @Column(name = "uid") val uid: Long, diff --git a/src/main/kotlin/com/hero/alignlab/domain/pose/domain/PoseKeyPointSnapshot.kt b/src/main/kotlin/com/hero/alignlab/domain/pose/domain/PoseKeyPointSnapshot.kt index 9c5a393..720645b 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/pose/domain/PoseKeyPointSnapshot.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/pose/domain/PoseKeyPointSnapshot.kt @@ -10,7 +10,7 @@ class PoseKeyPointSnapshot( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") - val id: Long = -1L, + val id: Long = 0L, @Column(name = "pose_snapshot_id") val poseSnapshotId: Long, diff --git a/src/main/kotlin/com/hero/alignlab/domain/pose/domain/PoseLayout.kt b/src/main/kotlin/com/hero/alignlab/domain/pose/domain/PoseLayout.kt index 12142f7..7e6d777 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/pose/domain/PoseLayout.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/pose/domain/PoseLayout.kt @@ -9,7 +9,7 @@ class PoseLayout( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") - val id: Long = -1, + val id: Long = 0L, @Column(name = "uid") val uid: Long, diff --git a/src/main/kotlin/com/hero/alignlab/domain/pose/domain/PoseLayoutPoint.kt b/src/main/kotlin/com/hero/alignlab/domain/pose/domain/PoseLayoutPoint.kt index 0b68bea..f917d71 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/pose/domain/PoseLayoutPoint.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/pose/domain/PoseLayoutPoint.kt @@ -10,7 +10,7 @@ class PoseLayoutPoint( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") - val id: Long = -1, + val id: Long = 0L, @Column(name = "pose_layout_id") val poseLayoutId: Long, diff --git a/src/main/kotlin/com/hero/alignlab/domain/pose/domain/PoseSnapshot.kt b/src/main/kotlin/com/hero/alignlab/domain/pose/domain/PoseSnapshot.kt index b5b3f01..1b3dcb4 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/pose/domain/PoseSnapshot.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/pose/domain/PoseSnapshot.kt @@ -11,7 +11,7 @@ data class PoseSnapshot( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") - val id: Long = -1, + val id: Long = 0L, @Column(name = "uid") val uid: Long, diff --git a/src/main/kotlin/com/hero/alignlab/domain/user/domain/CredentialUserInfo.kt b/src/main/kotlin/com/hero/alignlab/domain/user/domain/CredentialUserInfo.kt index 7d54218..0ee7984 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/user/domain/CredentialUserInfo.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/user/domain/CredentialUserInfo.kt @@ -10,7 +10,7 @@ import jakarta.persistence.* class CredentialUserInfo( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - val id: Long = -1, + val id: Long = 0L, @Column(name = "uid") val uid: Long, diff --git a/src/main/kotlin/com/hero/alignlab/domain/user/domain/OAuthUserInfo.kt b/src/main/kotlin/com/hero/alignlab/domain/user/domain/OAuthUserInfo.kt index 9417a88..bb246cc 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/user/domain/OAuthUserInfo.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/user/domain/OAuthUserInfo.kt @@ -9,7 +9,7 @@ import jakarta.persistence.* class OAuthUserInfo( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - val id: Long = -1, + val id: Long = 0L, @Column(name = "uid") val uid: Long, diff --git a/src/main/kotlin/com/hero/alignlab/domain/user/domain/UserInfo.kt b/src/main/kotlin/com/hero/alignlab/domain/user/domain/UserInfo.kt index b31e92c..b18b0e4 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/user/domain/UserInfo.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/user/domain/UserInfo.kt @@ -8,7 +8,7 @@ import jakarta.persistence.* class UserInfo( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - val id: Long = -1, + val id: Long = 0L, @Column(name = "nickname") var nickname: String, diff --git a/src/main/kotlin/com/hero/alignlab/exception/ErrorCode.kt b/src/main/kotlin/com/hero/alignlab/exception/ErrorCode.kt index 212bf4a..1ebe7ac 100644 --- a/src/main/kotlin/com/hero/alignlab/exception/ErrorCode.kt +++ b/src/main/kotlin/com/hero/alignlab/exception/ErrorCode.kt @@ -44,7 +44,10 @@ enum class ErrorCode(val status: HttpStatus, val description: String) { INVALID_JOIN_CODE_ERROR(HttpStatus.BAD_REQUEST, "비밀번호의 조건이 부합하지 않습니다."), NOT_CONTAINS_GROUP_USER_ERROR(HttpStatus.BAD_REQUEST, "그룹원이 아닙니다."), NOT_FOUND_GROUP_ID_ERROR(HttpStatus.NOT_FOUND, "group Id를 찾을 수 없습니다."), + NOT_FOUND_GROUP_TAG_ERROR(HttpStatus.NOT_FOUND, "그룹 태그 정보를 찾을 수 없습니다."), OVER_RANGE_GROUP_NAME_ERROR(HttpStatus.BAD_REQUEST, "그룹명은 16글자까지만 입력할 수 있습니다."), + OVER_COUNT_GROUP_TAG_ERROR(HttpStatus.BAD_REQUEST, "그룹 태그는 최대 3개까지 입력 가능합니다."), + DUPLICATE_GROUP_TAG_NAME_ERROR(HttpStatus.BAD_REQUEST, "중복된 태그명이 있습니다."), /** Group User Error Code */ DUPLICATE_GROUP_JOIN_ERROR(HttpStatus.BAD_REQUEST, "한개의 그룹만 참여 가능합니다."),