From cbcc63dc71bc88045a1045062176051caec9109a Mon Sep 17 00:00:00 2001
From: Klaus <klaus@vividnext.co.kr>
Date: Sun, 6 Aug 2023 11:13:27 +0900
Subject: [PATCH] =?UTF-8?q?=EA=B4=80=EB=A6=AC=EC=9E=90=20-=20=ED=9A=8C?=
 =?UTF-8?q?=EC=9B=90=EB=A6=AC=EC=8A=A4=ED=8A=B8=20API?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../admin/member/AdminMemberController.kt     | 23 +++++
 .../admin/member/AdminMemberRepository.kt     | 95 +++++++++++++++++++
 .../admin/member/AdminMemberService.kt        | 92 ++++++++++++++++++
 .../member/GetAdminMemberListResponse.kt      | 19 ++++
 4 files changed, 229 insertions(+)
 create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/admin/member/AdminMemberController.kt
 create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/admin/member/AdminMemberRepository.kt
 create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/admin/member/AdminMemberService.kt
 create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/admin/member/GetAdminMemberListResponse.kt

diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/member/AdminMemberController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/member/AdminMemberController.kt
new file mode 100644
index 0000000..78389da
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/member/AdminMemberController.kt
@@ -0,0 +1,23 @@
+package kr.co.vividnext.sodalive.admin.member
+
+import kr.co.vividnext.sodalive.common.ApiResponse
+import org.springframework.data.domain.Pageable
+import org.springframework.security.access.prepost.PreAuthorize
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RequestParam
+import org.springframework.web.bind.annotation.RestController
+
+@RestController
+@RequestMapping("/admin/member")
+class AdminMemberController(private val service: AdminMemberService) {
+    @GetMapping("/list")
+    @PreAuthorize("hasRole('ADMIN')")
+    fun getMemberList(pageable: Pageable) = ApiResponse.ok(service.getMemberList(pageable))
+
+    @GetMapping("/search")
+    fun searchMember(
+        @RequestParam(value = "search_word") searchWord: String,
+        pageable: Pageable
+    ) = ApiResponse.ok(service.searchMember(searchWord, pageable))
+}
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/member/AdminMemberRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/member/AdminMemberRepository.kt
new file mode 100644
index 0000000..9d20fe9
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/member/AdminMemberRepository.kt
@@ -0,0 +1,95 @@
+package kr.co.vividnext.sodalive.admin.member
+
+import com.querydsl.jpa.impl.JPAQueryFactory
+import kr.co.vividnext.sodalive.member.Member
+import kr.co.vividnext.sodalive.member.MemberRole
+import kr.co.vividnext.sodalive.member.QMember.member
+import org.springframework.data.jpa.repository.JpaRepository
+
+interface AdminMemberRepository : JpaRepository<Member, Long>, AdminMemberQueryRepository
+
+interface AdminMemberQueryRepository {
+    fun getMemberTotalCount(role: MemberRole? = null): Int
+    fun getMemberList(offset: Long, limit: Long, role: MemberRole? = null): List<Member>
+    fun searchMember(searchWord: String, offset: Long, limit: Long, role: MemberRole? = null): List<Member>
+
+    fun searchMemberTotalCount(searchWord: String, role: MemberRole? = null): Int
+}
+
+class AdminMemberQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : AdminMemberQueryRepository {
+    override fun getMemberList(offset: Long, limit: Long, role: MemberRole?): List<Member> {
+        return queryFactory
+            .selectFrom(member)
+            .where(
+                member.role.ne(MemberRole.ADMIN)
+                    .and(
+                        if (role != null) {
+                            member.role.eq(role)
+                        } else {
+                            null
+                        }
+                    )
+            )
+            .offset(offset)
+            .limit(limit)
+            .orderBy(member.id.desc())
+            .fetch()
+    }
+
+    override fun getMemberTotalCount(role: MemberRole?): Int {
+        return queryFactory
+            .select(member.id)
+            .from(member)
+            .where(
+                member.id.gt(1)
+                    .and(
+                        if (role != null) {
+                            member.role.eq(role)
+                        } else {
+                            null
+                        }
+                    )
+            )
+            .fetch()
+            .size
+    }
+
+    override fun searchMember(searchWord: String, offset: Long, limit: Long, role: MemberRole?): List<Member> {
+        return queryFactory
+            .selectFrom(member)
+            .where(
+                member.nickname.contains(searchWord)
+                    .or(member.email.contains(searchWord))
+                    .and(
+                        if (role != null) {
+                            member.role.eq(role)
+                        } else {
+                            null
+                        }
+                    )
+            )
+            .offset(offset)
+            .limit(limit)
+            .orderBy(member.id.desc())
+            .fetch()
+    }
+
+    override fun searchMemberTotalCount(searchWord: String, role: MemberRole?): Int {
+        return queryFactory
+            .select(member.id)
+            .from(member)
+            .where(
+                member.nickname.contains(searchWord)
+                    .or(member.email.contains(searchWord))
+                    .and(
+                        if (role != null) {
+                            member.role.eq(role)
+                        } else {
+                            null
+                        }
+                    )
+            )
+            .fetch()
+            .size
+    }
+}
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/member/AdminMemberService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/member/AdminMemberService.kt
new file mode 100644
index 0000000..49f1d46
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/member/AdminMemberService.kt
@@ -0,0 +1,92 @@
+package kr.co.vividnext.sodalive.admin.member
+
+import kr.co.vividnext.sodalive.common.SodaException
+import kr.co.vividnext.sodalive.member.Member
+import kr.co.vividnext.sodalive.member.MemberRole
+import org.springframework.beans.factory.annotation.Value
+import org.springframework.data.domain.Pageable
+import org.springframework.stereotype.Service
+import java.time.ZoneId
+import java.time.format.DateTimeFormatter
+
+@Service
+class AdminMemberService(
+    private val repository: AdminMemberRepository,
+
+    @Value("\${cloud.aws.cloud-front.host}")
+    private val cloudFrontHost: String
+) {
+    fun getMemberList(pageable: Pageable): GetAdminMemberListResponse {
+        val totalCount = repository.getMemberTotalCount()
+        val memberList = processMemberListToGetAdminMemberListResponseItemList(
+            memberList = repository.getMemberList(
+                offset = pageable.offset,
+                limit = pageable.pageSize.toLong()
+            )
+        )
+
+        return GetAdminMemberListResponse(totalCount, memberList)
+    }
+
+    fun searchMember(searchWord: String, pageable: Pageable): GetAdminMemberListResponse {
+        if (searchWord.length < 2) throw SodaException("2글자 이상 입력하세요.")
+        val totalCount = repository.searchMemberTotalCount(searchWord = searchWord)
+        val memberList = processMemberListToGetAdminMemberListResponseItemList(
+            memberList = repository.searchMember(
+                searchWord = searchWord,
+                offset = pageable.offset,
+                limit = pageable.pageSize.toLong()
+            )
+        )
+
+        return GetAdminMemberListResponse(totalCount, memberList)
+    }
+
+    private fun processMemberListToGetAdminMemberListResponseItemList(
+        memberList: List<Member>
+    ): List<GetAdminMemberListResponseItem> {
+        return memberList
+            .asSequence()
+            .map {
+                val userType = when (it.role) {
+                    MemberRole.ADMIN -> "관리자"
+                    MemberRole.USER -> "일반회원"
+                    MemberRole.CREATOR -> "요즘친구"
+                    MemberRole.AGENT -> "에이전트"
+                    MemberRole.BOT -> "봇"
+                }
+
+                val signUpDate = it.createdAt!!
+                    .atZone(ZoneId.of("UTC"))
+                    .withZoneSameInstant(ZoneId.of("Asia/Seoul"))
+                    .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
+
+                val signOutDate = if (it.signOutReasons.isNotEmpty()) {
+                    it.signOutReasons.last().createdAt!!
+                        .atZone(ZoneId.of("UTC"))
+                        .withZoneSameInstant(ZoneId.of("Asia/Seoul"))
+                        .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
+                } else {
+                    ""
+                }
+
+                GetAdminMemberListResponseItem(
+                    id = it.id!!,
+                    email = it.email,
+                    nickname = it.nickname,
+                    profileUrl = if (it.profileImage != null) {
+                        "$cloudFrontHost/${it.profileImage}"
+                    } else {
+                        "$cloudFrontHost/profile/default-profile.png"
+                    },
+                    userType = userType,
+                    container = it.container,
+                    auth = it.auth != null,
+                    signUpDate = signUpDate,
+                    signOutDate = signOutDate,
+                    isActive = it.isActive
+                )
+            }
+            .toList()
+    }
+}
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/member/GetAdminMemberListResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/member/GetAdminMemberListResponse.kt
new file mode 100644
index 0000000..1fd1c47
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/member/GetAdminMemberListResponse.kt
@@ -0,0 +1,19 @@
+package kr.co.vividnext.sodalive.admin.member
+
+data class GetAdminMemberListResponse(
+    val totalCount: Int,
+    val items: List<GetAdminMemberListResponseItem>
+)
+
+data class GetAdminMemberListResponseItem(
+    val id: Long,
+    val email: String,
+    val nickname: String,
+    val profileUrl: String,
+    val userType: String,
+    val container: String,
+    val auth: Boolean,
+    val signUpDate: String,
+    val signOutDate: String,
+    val isActive: Boolean
+)