Skip to content

Commit

Permalink
feat: 그룹 태그 기능 추가 (#25)
Browse files Browse the repository at this point in the history
* feat: 그룹 태그 기능 추가

* refactor: 그룹 태그 기능 추가 코드 리뷰 반영
  • Loading branch information
leeheefull authored Nov 17, 2024
1 parent f47b99b commit 329aa29
Show file tree
Hide file tree
Showing 41 changed files with 363 additions and 59 deletions.
23 changes: 23 additions & 0 deletions sql/group.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ fun NumberPath<Long>.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<Long>.isIn(parameters: Set<Long>?): BooleanExpression? {
Expand Down
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 @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
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.deleteGroupTagMapSyncByGroupId(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.deleteGroupTagMapSyncByGroupId(groupId)
}
}

Expand Down Expand Up @@ -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)
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,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<GroupTag> {
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<GroupTag> {
return withContext(Dispatchers.IO) {
groupTagRepository.findByGroupId(groupId)
}
}

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)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
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 = 0L,

@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 = 0L,

@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
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
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>
}
Loading

0 comments on commit 329aa29

Please sign in to comment.