feat(creatorname): 크리에이터 언어별 이름 관리와 공통 조회를 추가한다
This commit is contained in:
@@ -6,6 +6,7 @@ import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.MemberProvider
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
import kr.co.vividnext.sodalive.v2.creatorname.application.CreatorLocalizedNameService
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
@@ -22,6 +23,7 @@ class AdminMemberService(
|
||||
private val passwordEncoder: PasswordEncoder,
|
||||
private val messageSource: SodaMessageSource,
|
||||
private val langContext: LangContext,
|
||||
private val creatorLocalizedNameService: CreatorLocalizedNameService,
|
||||
|
||||
@Value("\${cloud.aws.cloud-front.host}")
|
||||
private val cloudFrontHost: String
|
||||
@@ -93,6 +95,8 @@ class AdminMemberService(
|
||||
private fun processMemberListToGetAdminMemberListResponseItemList(
|
||||
memberList: List<Member>
|
||||
): List<GetAdminMemberListResponseItem> {
|
||||
val creatorIds = memberList.filter { it.role == MemberRole.CREATOR }.map { requireNotNull(it.id) }
|
||||
val localizedNames = creatorLocalizedNameService.findAllLocalizedNames(creatorIds)
|
||||
return memberList
|
||||
.asSequence()
|
||||
.map {
|
||||
@@ -145,7 +149,8 @@ class AdminMemberService(
|
||||
auth = it.auth != null,
|
||||
signUpDate = signUpDate,
|
||||
signOutDate = signOutDate,
|
||||
isActive = it.isActive
|
||||
isActive = it.isActive,
|
||||
localizedNames = localizedNames[it.id].orEmpty()
|
||||
)
|
||||
}
|
||||
.toList()
|
||||
|
||||
@@ -16,5 +16,6 @@ data class GetAdminMemberListResponseItem(
|
||||
val auth: Boolean,
|
||||
val signUpDate: String,
|
||||
val signOutDate: String,
|
||||
val isActive: Boolean
|
||||
val isActive: Boolean,
|
||||
val localizedNames: Map<String, String> = emptyMap()
|
||||
)
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package kr.co.vividnext.sodalive.v2.creatorname.adapter.`in`.web
|
||||
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
import kr.co.vividnext.sodalive.v2.creatorname.application.CreatorLocalizedNameService
|
||||
import kr.co.vividnext.sodalive.v2.creatorname.dto.CreatorLocalizedNameRequest
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
import org.springframework.web.bind.annotation.PathVariable
|
||||
import org.springframework.web.bind.annotation.PutMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v2/admin/creators/{creatorId}/localized-names/ja")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
class AdminCreatorLocalizedNameController(private val service: CreatorLocalizedNameService) {
|
||||
@PutMapping
|
||||
fun saveLocalizedName(@PathVariable creatorId: Long, @RequestBody request: CreatorLocalizedNameRequest) = ApiResponse.ok(
|
||||
service.saveLocalizedName(creatorId, Lang.fromCode("ja"), request.displayName)
|
||||
)
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence
|
||||
|
||||
import kr.co.vividnext.sodalive.common.BaseEntity
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.v2.creatorname.domain.CreatorLocalizedName
|
||||
import javax.persistence.Column
|
||||
import javax.persistence.Entity
|
||||
import javax.persistence.EnumType
|
||||
import javax.persistence.Enumerated
|
||||
import javax.persistence.FetchType
|
||||
import javax.persistence.ForeignKey
|
||||
import javax.persistence.JoinColumn
|
||||
import javax.persistence.ManyToOne
|
||||
import javax.persistence.Table
|
||||
import javax.persistence.UniqueConstraint
|
||||
|
||||
@Entity
|
||||
@Table(
|
||||
name = "creator_localized_name",
|
||||
uniqueConstraints = [UniqueConstraint(name = "uk_creator_localized_name_member_lang", columnNames = ["member_id", "lang"])]
|
||||
)
|
||||
class CreatorLocalizedNameEntity(
|
||||
@Column(name = "member_id", nullable = false, updatable = false)
|
||||
val memberId: Long,
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "lang", nullable = false, length = 10, updatable = false)
|
||||
val lang: Lang,
|
||||
|
||||
@Column(name = "display_name", nullable = false, length = 255)
|
||||
var displayName: String,
|
||||
|
||||
@Column(name = "is_active", nullable = false)
|
||||
var isActive: Boolean = true
|
||||
) : BaseEntity() {
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(
|
||||
name = "member_id",
|
||||
nullable = false,
|
||||
insertable = false,
|
||||
updatable = false,
|
||||
foreignKey = ForeignKey(name = "fk_creator_localized_name_member")
|
||||
)
|
||||
private var member: Member? = null
|
||||
|
||||
fun toDomain(): CreatorLocalizedName = CreatorLocalizedName(memberId, lang, displayName, isActive, id)
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence
|
||||
|
||||
import com.querydsl.core.types.dsl.BooleanExpression
|
||||
import com.querydsl.core.types.dsl.StringExpression
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
import kr.co.vividnext.sodalive.member.QMember
|
||||
|
||||
class CreatorNameJoin(owner: QMember, lang: Lang, alias: String) {
|
||||
val name: QCreatorLocalizedNameEntity = QCreatorLocalizedNameEntity(alias)
|
||||
val condition: BooleanExpression = name.memberId.eq(owner.id)
|
||||
.and(name.lang.eq(lang))
|
||||
.and(name.isActive.isTrue)
|
||||
.and(owner.role.eq(MemberRole.CREATOR))
|
||||
val displayName: StringExpression = name.displayName.coalesce(owner.nickname)
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence
|
||||
|
||||
import com.querydsl.core.types.dsl.BooleanExpression
|
||||
import com.querydsl.jpa.JPAExpressions
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
import kr.co.vividnext.sodalive.member.QMember
|
||||
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.QCreatorLocalizedNameEntity.creatorLocalizedNameEntity
|
||||
|
||||
fun creatorNameContains(owner: QMember, keyword: String): BooleanExpression {
|
||||
val alias = creatorLocalizedNameEntity
|
||||
return owner.nickname.containsIgnoreCase(keyword).or(
|
||||
owner.role.eq(MemberRole.CREATOR).and(
|
||||
JPAExpressions.selectOne().from(alias).where(
|
||||
alias.memberId.eq(owner.id),
|
||||
alias.lang.eq(Lang.JA),
|
||||
alias.isActive.isTrue,
|
||||
alias.displayName.containsIgnoreCase(keyword)
|
||||
).exists()
|
||||
)
|
||||
)
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
package kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence
|
||||
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
import kr.co.vividnext.sodalive.member.QMember.member
|
||||
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.QCreatorLocalizedNameEntity.creatorLocalizedNameEntity
|
||||
import kr.co.vividnext.sodalive.v2.creatorname.domain.CreatorLocalizedName
|
||||
import kr.co.vividnext.sodalive.v2.creatorname.port.out.CreatorLocalizedNameRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
import javax.persistence.EntityManager
|
||||
|
||||
@Repository
|
||||
class DefaultCreatorLocalizedNameRepository(
|
||||
private val entityManager: EntityManager,
|
||||
private val queryFactory: JPAQueryFactory
|
||||
) : CreatorLocalizedNameRepository {
|
||||
override fun findByMemberIdAndLang(memberId: Long, lang: Lang): CreatorLocalizedName? = queryFactory
|
||||
.selectFrom(creatorLocalizedNameEntity)
|
||||
.where(creatorLocalizedNameEntity.memberId.eq(memberId), creatorLocalizedNameEntity.lang.eq(lang))
|
||||
.fetchOne()
|
||||
?.toDomain()
|
||||
|
||||
override fun findActiveLocalizedNames(memberIds: Collection<Long>, lang: Lang): Map<Long, String> {
|
||||
if (memberIds.isEmpty()) return emptyMap()
|
||||
return queryFactory
|
||||
.select(creatorLocalizedNameEntity.memberId, creatorLocalizedNameEntity.displayName)
|
||||
.from(creatorLocalizedNameEntity)
|
||||
.innerJoin(member).on(member.id.eq(creatorLocalizedNameEntity.memberId))
|
||||
.where(
|
||||
creatorLocalizedNameEntity.memberId.`in`(memberIds),
|
||||
creatorLocalizedNameEntity.lang.eq(lang),
|
||||
creatorLocalizedNameEntity.isActive.isTrue,
|
||||
member.role.eq(MemberRole.CREATOR)
|
||||
)
|
||||
.fetch()
|
||||
.associate { row ->
|
||||
requireNotNull(row[creatorLocalizedNameEntity.memberId]) to
|
||||
requireNotNull(row[creatorLocalizedNameEntity.displayName])
|
||||
}
|
||||
}
|
||||
|
||||
override fun findAllActiveLocalizedNames(memberIds: Collection<Long>): Map<Long, Map<String, String>> {
|
||||
if (memberIds.isEmpty()) return emptyMap()
|
||||
return queryFactory
|
||||
.select(creatorLocalizedNameEntity.memberId, creatorLocalizedNameEntity.lang, creatorLocalizedNameEntity.displayName)
|
||||
.from(creatorLocalizedNameEntity)
|
||||
.innerJoin(member).on(member.id.eq(creatorLocalizedNameEntity.memberId))
|
||||
.where(
|
||||
creatorLocalizedNameEntity.memberId.`in`(memberIds),
|
||||
creatorLocalizedNameEntity.isActive.isTrue,
|
||||
member.role.eq(MemberRole.CREATOR)
|
||||
)
|
||||
.fetch()
|
||||
.groupBy { row -> requireNotNull(row[creatorLocalizedNameEntity.memberId]) }
|
||||
.mapValues { (_, rows) ->
|
||||
rows.associate { row ->
|
||||
requireNotNull(row[creatorLocalizedNameEntity.lang]).code to
|
||||
requireNotNull(row[creatorLocalizedNameEntity.displayName])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun findCurrentNames(memberIds: Collection<Long>, lang: Lang): Map<Long, String> {
|
||||
if (memberIds.isEmpty()) return emptyMap()
|
||||
val join = CreatorNameJoin(member, lang, "currentCreatorName")
|
||||
return queryFactory
|
||||
.select(member.id, join.displayName)
|
||||
.from(member)
|
||||
.leftJoin(join.name).on(join.condition)
|
||||
.where(member.id.`in`(memberIds.distinct()))
|
||||
.fetch()
|
||||
.associate { row -> requireNotNull(row[member.id]) to requireNotNull(row[join.displayName]) }
|
||||
}
|
||||
|
||||
override fun save(name: CreatorLocalizedName): CreatorLocalizedName {
|
||||
val entity = if (name.id == null) {
|
||||
CreatorLocalizedNameEntity(name.memberId, name.lang, name.displayName, name.isActive)
|
||||
.also { entityManager.persist(it) }
|
||||
} else {
|
||||
entityManager.find(CreatorLocalizedNameEntity::class.java, name.id).apply {
|
||||
displayName = name.displayName
|
||||
isActive = name.isActive
|
||||
}
|
||||
}
|
||||
return entity.toDomain()
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package kr.co.vividnext.sodalive.v2.creatorname.application
|
||||
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
import kr.co.vividnext.sodalive.v2.creatorname.domain.CreatorLocalizedName
|
||||
import kr.co.vividnext.sodalive.v2.creatorname.dto.CreatorLocalizedNameResponse
|
||||
import kr.co.vividnext.sodalive.v2.creatorname.port.out.CreatorLocalizedNameRepository
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
|
||||
@Service
|
||||
@Transactional(readOnly = true)
|
||||
class CreatorLocalizedNameService(
|
||||
private val memberRepository: MemberRepository,
|
||||
private val repository: CreatorLocalizedNameRepository
|
||||
) {
|
||||
fun findLocalizedNames(memberIds: Collection<Long>, lang: Lang): Map<Long, String> {
|
||||
if (memberIds.isEmpty()) return emptyMap()
|
||||
return repository.findActiveLocalizedNames(memberIds, lang)
|
||||
}
|
||||
|
||||
fun findCurrentNames(memberIds: Collection<Long>, lang: Lang): Map<Long, String> {
|
||||
if (memberIds.isEmpty()) return emptyMap()
|
||||
return repository.findCurrentNames(memberIds, lang)
|
||||
}
|
||||
|
||||
fun findAllLocalizedNames(memberIds: Collection<Long>): Map<Long, Map<String, String>> {
|
||||
if (memberIds.isEmpty()) return emptyMap()
|
||||
return repository.findAllActiveLocalizedNames(memberIds)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun saveLocalizedName(creatorId: Long, lang: Lang, displayName: String): CreatorLocalizedNameResponse {
|
||||
requireJapanese(lang)
|
||||
if (displayName.codePointCount(0, displayName.length) > 255) {
|
||||
throw SodaException(messageKey = "common.error.invalid_request", errorProperty = "displayName")
|
||||
}
|
||||
val creator = requireCreator(memberRepository.findByIdForUpdate(creatorId))
|
||||
val existing = repository.findByMemberIdAndLang(creatorId, lang)
|
||||
val name = if (displayName.isBlank()) {
|
||||
existing?.copy(isActive = false)
|
||||
} else {
|
||||
existing?.copy(displayName = displayName, isActive = true) ?: CreatorLocalizedName(creatorId, lang, displayName)
|
||||
}
|
||||
name?.let { repository.save(it) }
|
||||
return CreatorLocalizedNameResponse(creatorId, lang.code, creator.nickname, name?.takeIf { it.isActive }?.displayName)
|
||||
}
|
||||
|
||||
private fun requireJapanese(lang: Lang) {
|
||||
if (lang != Lang.JA) throw SodaException(messageKey = "common.error.invalid_request")
|
||||
}
|
||||
|
||||
private fun requireCreator(member: Member?): Member {
|
||||
val creator = member ?: throw SodaException(messageKey = "admin.member.not_found")
|
||||
if (creator.role != MemberRole.CREATOR) throw SodaException(messageKey = "admin.live.creator_required")
|
||||
return creator
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package kr.co.vividnext.sodalive.v2.creatorname.domain
|
||||
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
|
||||
data class CreatorLocalizedName(
|
||||
val memberId: Long,
|
||||
val lang: Lang,
|
||||
val displayName: String,
|
||||
val isActive: Boolean = true,
|
||||
val id: Long? = null
|
||||
)
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package kr.co.vividnext.sodalive.v2.creatorname.dto
|
||||
|
||||
data class CreatorLocalizedNameRequest(val displayName: String)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package kr.co.vividnext.sodalive.v2.creatorname.dto
|
||||
|
||||
data class CreatorLocalizedNameResponse(
|
||||
val creatorId: Long,
|
||||
val locale: String,
|
||||
val originalNickname: String,
|
||||
val displayName: String?
|
||||
)
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package kr.co.vividnext.sodalive.v2.creatorname.port.out
|
||||
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
import kr.co.vividnext.sodalive.v2.creatorname.domain.CreatorLocalizedName
|
||||
|
||||
interface CreatorLocalizedNameRepository {
|
||||
fun findByMemberIdAndLang(memberId: Long, lang: Lang): CreatorLocalizedName?
|
||||
fun findActiveLocalizedNames(memberIds: Collection<Long>, lang: Lang): Map<Long, String>
|
||||
fun findAllActiveLocalizedNames(memberIds: Collection<Long>): Map<Long, Map<String, String>>
|
||||
fun findCurrentNames(memberIds: Collection<Long>, lang: Lang): Map<Long, String>
|
||||
fun save(name: CreatorLocalizedName): CreatorLocalizedName
|
||||
}
|
||||
Reference in New Issue
Block a user