Skip to content

Commit

Permalink
feat: 그룹 태그 기능 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
leeheefull committed Nov 15, 2024
1 parent f47b99b commit cef8c58
Show file tree
Hide file tree
Showing 22 changed files with 307 additions and 36 deletions.
22 changes: 22 additions & 0 deletions sql/group.sql
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,25 @@ 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='그룹 태그 매핑';
Original file line number Diff line number Diff line change
@@ -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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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<String>?): List<GroupTag> {
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.deleteSyncGroupId(groupId)
groupTagService.saveSync(CreateGroupTagRequest(groupId, request.tagNames))
} else emptyList()

createdGroup
UpdateGroupResponse.from(updatedGroup, updatedGroupTag)
}
}

Expand All @@ -90,6 +118,7 @@ class GroupFacade(

txTemplates.writer.executes {
groupUserScoreService.deleteAllByUid(uid)
groupTagService.deleteSyncGroupId(groupId)
}
}

Expand Down Expand Up @@ -188,8 +217,9 @@ class GroupFacade(
}
) { group, groupUserScore ->
val ownerGroupUser = userInfoService.getUserByIdOrThrow(group.ownerUid)
val tags = groupTagService.findByGroupId(groupId)

GetGroupResponse.from(group, ownerGroupUser.nickname).run {
GetGroupResponse.from(group, ownerGroupUser.nickname, tags).run {
when (group.ownerUid == user.uid) {
true -> this
false -> this.copy(joinCode = null)
Expand Down Expand Up @@ -222,8 +252,8 @@ class GroupFacade(
}
}

suspend fun searchGroup(user: AuthUser, pageRequest: HeroPageRequest): Page<SearchGroupResponse> {
val groups = groupService.findAll(pageRequest.toDefault())
suspend fun searchGroup(user: AuthUser, tagName: String?, pageRequest: HeroPageRequest): Page<SearchGroupResponse> {
val groups = groupService.findByTagNameAndPage(tagName, pageRequest.toDefault())

val groupUserByUid = groups.content.map { group -> group.id }
.run { groupUserService.findByUidAndGroupIdIn(user.uid, this) }
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -72,9 +55,9 @@ class GroupService(
return groupRepository.save(group)
}

suspend fun findAll(pageable: Pageable): Page<Group> {
suspend fun findByTagNameAndPage(tagName: String?, pageable: Pageable): Page<Group> {
return withContext(Dispatchers.IO) {
groupRepository.findAll(pageable)
groupRepository.findByTagNameAndPage(tagName, pageable)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
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<GroupTag> {
return request.tagNames.map { tagName ->
findOrCreateTag(tagName).also { tag ->
groupTagMapRepository.save(GroupTagMap(groupId = request.groupId, tagId = tag.id))
}
}
}

@Transactional
fun deleteSyncGroupId(groupId: Long) {
groupTagMapRepository.deleteByGroupId(groupId)
}

suspend fun findByGroupId(groupId: Long): List<GroupTag> {
return withContext(Dispatchers.IO) {
val tagIds = groupTagMapRepository.findByGroupId(groupId)
.map { it.tagId }
groupTagRepository.findByIdIn(tagIds)
}
}

private fun findOrCreateTag(tagName: String): GroupTag {
return groupTagRepository.findByName(tagName)
.firstOrNull() ?: groupTagRepository.save(GroupTag(name = tagName))
}

fun validateGroupTag(tagNames: List<String>) {
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)
}
}
}
15 changes: 15 additions & 0 deletions src/main/kotlin/com/hero/alignlab/domain/group/domain/GroupTag.kt
Original file line number Diff line number Diff line change
@@ -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 = -1,

@Column(name = "name")
var name: String,
) : BaseEntity()
Original file line number Diff line number Diff line change
@@ -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 = -1,

@Column(name = "group_id")
val groupId: Long,

@Column(name = "tag_id")
val tagId: Long
) : BaseEntity()
Original file line number Diff line number Diff line change
@@ -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<Group>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.hero.alignlab.domain.group.infrastructure

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<Group> {
val groups = queryFactory
.selectDistinct(group)
.from(group)
.leftJoin(groupTagMap).on(group.id.eq(groupTagMap.groupId))
.leftJoin(groupTag).on(groupTagMap.tagId.eq(groupTag.id))
.where(
tagName?.takeIf { it.isNotEmpty() }?.let { groupTag.name.contains(it) }
)
.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(
tagName?.takeIf { it.isNotEmpty() }?.let { groupTag.name.contains(it) }
)
.orderBy(getGroupOrderSpecifier(pageable))
.fetchOne() ?: 0L

return PageImpl(groups, pageable, count)
}

private fun getGroupOrderSpecifier(pageable: Pageable): OrderSpecifier<out Comparable<*>?>? {
val order = pageable.sort.firstOrNull() ?: return null
val orderSpecifier: ComparableExpressionBase<out Comparable<*>> = when (order.property) {
"createdAt" -> group.createdAt
"name" -> group.name
"ownerUid" -> group.ownerUid
else -> group.createdAt
}
return if (order.isDescending) orderSpecifier.desc() else orderSpecifier.asc()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import java.time.LocalDateTime

@Repository
@Transactional(readOnly = true)
interface GroupRepository : JpaRepository<Group, Long> {
interface GroupRepository : JpaRepository<Group, Long>, GroupQRepository {
fun existsByName(name: String): Boolean

fun findByIdAndOwnerUid(id: Long, ownerUid: Long): Group?
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
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

@Repository
interface GroupTagMapRepository : JpaRepository<GroupTagMap, Long> {
fun findByGroupId(groupId: Long): MutableList<GroupTagMap>
fun deleteByGroupId(groupId: Long)
}
Original file line number Diff line number Diff line change
@@ -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

@Repository
interface GroupTagRepository : JpaRepository<GroupTag, Long> {
fun findByName(name: String): List<GroupTag>

fun findByIdIn(ids: List<Long>): List<GroupTag>
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ data class CreateGroupRequest(
val joinCode: String?,
/** 정원, 기본값 30 */
val userCapacity: Int?,
/** 태그 리스트, 최대 3개 */
val tagNames: List<String>?,
)
Loading

0 comments on commit cef8c58

Please sign in to comment.