관리자 회원 관리 로직 composable 분리
This commit is contained in:
265
frontend/src/composables/useAdminUsers.js
Normal file
265
frontend/src/composables/useAdminUsers.js
Normal file
@@ -0,0 +1,265 @@
|
||||
import { computed } from 'vue'
|
||||
|
||||
export function useAdminUsers({
|
||||
api,
|
||||
auth,
|
||||
users,
|
||||
userQuery,
|
||||
userSort,
|
||||
userSortDirection,
|
||||
userAvatarInputs,
|
||||
modalTargetUser,
|
||||
modalPasswordDraft,
|
||||
modalRoleNextAdmin,
|
||||
modalUserDraftEmail,
|
||||
modalUserDraftNickname,
|
||||
modalUserDraftIsAdmin,
|
||||
userEditModalOpen,
|
||||
userPasswordModalOpen,
|
||||
userDeleteModalOpen,
|
||||
userRoleModalOpen,
|
||||
resetMessages,
|
||||
refreshUsers,
|
||||
success,
|
||||
error,
|
||||
}) {
|
||||
function setUserAvatarInput(userId, el) {
|
||||
if (!userId) return
|
||||
if (!el) {
|
||||
delete userAvatarInputs.value[userId]
|
||||
return
|
||||
}
|
||||
userAvatarInputs.value[userId] = el
|
||||
}
|
||||
|
||||
const canManageModalRole = computed(() => {
|
||||
if (!auth.user?.isPrimaryAdmin) return false
|
||||
if (!modalTargetUser.value) return false
|
||||
return !modalTargetUser.value.isPrimaryAdmin
|
||||
})
|
||||
|
||||
const isUserEditDirty = computed(() => {
|
||||
if (!modalTargetUser.value) return false
|
||||
return (
|
||||
modalUserDraftEmail.value.trim() !== (modalTargetUser.value.email || '') ||
|
||||
modalUserDraftNickname.value.trim() !== (modalTargetUser.value.nickname || '') ||
|
||||
!!modalUserDraftIsAdmin.value !== !!modalTargetUser.value.isAdmin
|
||||
)
|
||||
})
|
||||
|
||||
function roleLabelOf(user) {
|
||||
if (user?.isPrimaryAdmin) return '최고 관리자'
|
||||
if (user?.isAdmin) return '운영자'
|
||||
return '일반 회원'
|
||||
}
|
||||
|
||||
function openUserAvatarPicker(user) {
|
||||
userAvatarInputs.value[user?.id]?.click()
|
||||
}
|
||||
|
||||
async function uploadUserAvatar(user, file, { remove = false } = {}) {
|
||||
resetMessages()
|
||||
if (!user?.id) return
|
||||
|
||||
try {
|
||||
user.isAvatarBusy = true
|
||||
const data = await api.updateAdminUserAvatar(user.id, { file, removeAvatar: remove })
|
||||
const updated = data.user
|
||||
users.value = users.value.map((entry) =>
|
||||
entry.id === updated.id
|
||||
? {
|
||||
...entry,
|
||||
...updated,
|
||||
isAvatarBusy: false,
|
||||
}
|
||||
: entry
|
||||
)
|
||||
if (modalTargetUser.value?.id === updated.id) {
|
||||
modalTargetUser.value = { ...modalTargetUser.value, ...updated }
|
||||
}
|
||||
if (updated.id === auth.user?.id) await auth.refresh()
|
||||
await refreshUsers()
|
||||
success.value = remove ? '회원 썸네일을 삭제했어요.' : '회원 썸네일을 업데이트했어요.'
|
||||
} catch (e) {
|
||||
error.value = remove ? '회원 썸네일 삭제에 실패했어요.' : '회원 썸네일 변경에 실패했어요.'
|
||||
} finally {
|
||||
const target = users.value.find((entry) => entry.id === user.id)
|
||||
if (target) target.isAvatarBusy = false
|
||||
}
|
||||
}
|
||||
|
||||
async function onUserAvatarChange(user, event) {
|
||||
const file = event.target.files && event.target.files[0] ? event.target.files[0] : null
|
||||
event.target.value = ''
|
||||
if (!file) return
|
||||
await uploadUserAvatar(user, file)
|
||||
}
|
||||
|
||||
async function removeUserAvatar(user) {
|
||||
if (!user?.avatarSrc) return
|
||||
await uploadUserAvatar(user, null, { remove: true })
|
||||
}
|
||||
|
||||
function openUserEditModal(user) {
|
||||
resetMessages()
|
||||
modalTargetUser.value = user ? { ...user } : null
|
||||
modalUserDraftEmail.value = user?.email || ''
|
||||
modalUserDraftNickname.value = user?.nickname || ''
|
||||
modalUserDraftIsAdmin.value = !!user?.isAdmin
|
||||
userEditModalOpen.value = true
|
||||
}
|
||||
|
||||
function closeUserEditModal() {
|
||||
userEditModalOpen.value = false
|
||||
modalTargetUser.value = null
|
||||
modalUserDraftEmail.value = ''
|
||||
modalUserDraftNickname.value = ''
|
||||
modalUserDraftIsAdmin.value = false
|
||||
}
|
||||
|
||||
async function saveUserEdit() {
|
||||
resetMessages()
|
||||
if (!modalTargetUser.value?.id) return
|
||||
|
||||
try {
|
||||
const data = await api.updateAdminUser(modalTargetUser.value.id, {
|
||||
email: modalUserDraftEmail.value.trim(),
|
||||
nickname: modalUserDraftNickname.value.trim(),
|
||||
isAdmin: !!modalUserDraftIsAdmin.value,
|
||||
})
|
||||
const updated = data.user
|
||||
users.value = users.value.map((entry) =>
|
||||
entry.id === updated.id
|
||||
? {
|
||||
...entry,
|
||||
...updated,
|
||||
isAvatarBusy: entry.isAvatarBusy || false,
|
||||
}
|
||||
: entry
|
||||
)
|
||||
if (updated.id === auth.user?.id) await auth.refresh()
|
||||
closeUserEditModal()
|
||||
await refreshUsers()
|
||||
success.value = '회원 정보를 저장했어요.'
|
||||
} catch (e) {
|
||||
error.value = '회원 정보 저장에 실패했어요.'
|
||||
}
|
||||
}
|
||||
|
||||
function openUserPasswordModal(user) {
|
||||
resetMessages()
|
||||
modalTargetUser.value = user ? { ...user } : null
|
||||
modalPasswordDraft.value = ''
|
||||
userPasswordModalOpen.value = true
|
||||
}
|
||||
|
||||
function closeUserPasswordModal() {
|
||||
userPasswordModalOpen.value = false
|
||||
modalTargetUser.value = null
|
||||
modalPasswordDraft.value = ''
|
||||
}
|
||||
|
||||
function userDisplayName(user) {
|
||||
return user?.nickname || user?.email?.split('@')[0] || '알 수 없음'
|
||||
}
|
||||
|
||||
async function confirmUserPasswordReset() {
|
||||
resetMessages()
|
||||
if (!modalTargetUser.value?.id) return
|
||||
|
||||
const password = modalPasswordDraft.value.trim()
|
||||
if (!password) {
|
||||
error.value = '초기화할 비밀번호를 입력해주세요.'
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await api.updateAdminUserPassword(modalTargetUser.value.id, { password })
|
||||
success.value = `${userDisplayName(modalTargetUser.value)} 계정 비밀번호를 초기화했어요.`
|
||||
closeUserPasswordModal()
|
||||
} catch (e) {
|
||||
error.value = '비밀번호 초기화에 실패했어요.'
|
||||
}
|
||||
}
|
||||
|
||||
function openUserDeleteModal(user) {
|
||||
resetMessages()
|
||||
modalTargetUser.value = user ? { ...user } : null
|
||||
userDeleteModalOpen.value = true
|
||||
}
|
||||
|
||||
function closeUserDeleteModal() {
|
||||
userDeleteModalOpen.value = false
|
||||
modalTargetUser.value = null
|
||||
}
|
||||
|
||||
async function confirmUserDelete() {
|
||||
resetMessages()
|
||||
if (!modalTargetUser.value?.id) return
|
||||
|
||||
try {
|
||||
const deletingSelf = modalTargetUser.value.id === auth.user?.id
|
||||
const deletedName = userDisplayName(modalTargetUser.value)
|
||||
await api.deleteAdminUser(modalTargetUser.value.id)
|
||||
users.value = users.value.filter((entry) => entry.id !== modalTargetUser.value.id)
|
||||
closeUserDeleteModal()
|
||||
success.value = `${deletedName} 계정을 삭제했어요.`
|
||||
if (deletingSelf) await auth.refresh()
|
||||
} catch (e) {
|
||||
error.value = '회원 삭제에 실패했어요.'
|
||||
}
|
||||
}
|
||||
|
||||
function openUserRoleModal(user, nextIsAdmin = !modalUserDraftIsAdmin.value) {
|
||||
resetMessages()
|
||||
modalTargetUser.value = user ? { ...user } : null
|
||||
modalRoleNextAdmin.value = !!nextIsAdmin
|
||||
userRoleModalOpen.value = true
|
||||
}
|
||||
|
||||
function closeUserRoleModal() {
|
||||
userRoleModalOpen.value = false
|
||||
if (!userEditModalOpen.value) modalTargetUser.value = null
|
||||
modalRoleNextAdmin.value = false
|
||||
}
|
||||
|
||||
function confirmUserRoleDraft() {
|
||||
if (!modalTargetUser.value?.id) return
|
||||
modalUserDraftIsAdmin.value = modalRoleNextAdmin.value
|
||||
const targetLabel = modalRoleNextAdmin.value ? '운영자 권한을 저장 대기 상태로 반영했어요.' : '운영자 권한 해제를 저장 대기 상태로 반영했어요.'
|
||||
closeUserRoleModal()
|
||||
success.value = targetLabel
|
||||
}
|
||||
|
||||
function submitUserFilters() {
|
||||
refreshUsers({
|
||||
q: userQuery.value,
|
||||
sort: userSort.value,
|
||||
direction: userSortDirection.value,
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
setUserAvatarInput,
|
||||
canManageModalRole,
|
||||
isUserEditDirty,
|
||||
roleLabelOf,
|
||||
openUserAvatarPicker,
|
||||
onUserAvatarChange,
|
||||
removeUserAvatar,
|
||||
openUserEditModal,
|
||||
closeUserEditModal,
|
||||
saveUserEdit,
|
||||
openUserPasswordModal,
|
||||
closeUserPasswordModal,
|
||||
confirmUserPasswordReset,
|
||||
openUserDeleteModal,
|
||||
closeUserDeleteModal,
|
||||
confirmUserDelete,
|
||||
openUserRoleModal,
|
||||
closeUserRoleModal,
|
||||
confirmUserRoleDraft,
|
||||
submitUserFilters,
|
||||
userDisplayName,
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import AdminTierlistsSection from '../components/admin/AdminTierlistsSection.vue
|
||||
import AdminUsersSection from '../components/admin/AdminUsersSection.vue'
|
||||
import { useAdminGameManager } from '../composables/useAdminGameManager'
|
||||
import { useAdminTemplateRequests } from '../composables/useAdminTemplateRequests'
|
||||
import { useAdminUsers } from '../composables/useAdminUsers'
|
||||
import { useAuthStore } from '../stores/auth'
|
||||
import { useToast } from '../composables/useToast'
|
||||
|
||||
@@ -646,83 +647,6 @@ async function refreshGames() {
|
||||
}
|
||||
}
|
||||
|
||||
function setUserAvatarInput(userId, el) {
|
||||
if (!userId) return
|
||||
if (!el) {
|
||||
delete userAvatarInputs.value[userId]
|
||||
return
|
||||
}
|
||||
userAvatarInputs.value[userId] = el
|
||||
}
|
||||
|
||||
const canManageModalRole = computed(() => {
|
||||
if (!auth.user?.isPrimaryAdmin) return false
|
||||
if (!modalTargetUser.value) return false
|
||||
return !modalTargetUser.value.isPrimaryAdmin
|
||||
})
|
||||
|
||||
const isUserEditDirty = computed(() => {
|
||||
if (!modalTargetUser.value) return false
|
||||
return (
|
||||
modalUserDraftEmail.value.trim() !== (modalTargetUser.value.email || '') ||
|
||||
modalUserDraftNickname.value.trim() !== (modalTargetUser.value.nickname || '') ||
|
||||
!!modalUserDraftIsAdmin.value !== !!modalTargetUser.value.isAdmin
|
||||
)
|
||||
})
|
||||
|
||||
function roleLabelOf(user) {
|
||||
if (user?.isPrimaryAdmin) return '최고 관리자'
|
||||
if (user?.isAdmin) return '운영자'
|
||||
return '일반 회원'
|
||||
}
|
||||
|
||||
function openUserAvatarPicker(user) {
|
||||
userAvatarInputs.value[user?.id]?.click()
|
||||
}
|
||||
|
||||
async function uploadUserAvatar(user, file, { remove = false } = {}) {
|
||||
resetMessages()
|
||||
if (!user?.id) return
|
||||
|
||||
try {
|
||||
user.isAvatarBusy = true
|
||||
const data = await api.updateAdminUserAvatar(user.id, { file, removeAvatar: remove })
|
||||
const updated = data.user
|
||||
users.value = users.value.map((entry) =>
|
||||
entry.id === updated.id
|
||||
? {
|
||||
...entry,
|
||||
...updated,
|
||||
isAvatarBusy: false,
|
||||
}
|
||||
: entry
|
||||
)
|
||||
if (modalTargetUser.value?.id === updated.id) {
|
||||
modalTargetUser.value = { ...modalTargetUser.value, ...updated }
|
||||
}
|
||||
if (updated.id === auth.user?.id) await auth.refresh()
|
||||
await refreshUsers()
|
||||
success.value = remove ? '회원 썸네일을 삭제했어요.' : '회원 썸네일을 업데이트했어요.'
|
||||
} catch (e) {
|
||||
error.value = remove ? '회원 썸네일 삭제에 실패했어요.' : '회원 썸네일 변경에 실패했어요.'
|
||||
} finally {
|
||||
const target = users.value.find((entry) => entry.id === user.id)
|
||||
if (target) target.isAvatarBusy = false
|
||||
}
|
||||
}
|
||||
|
||||
async function onUserAvatarChange(user, event) {
|
||||
const file = event.target.files && event.target.files[0] ? event.target.files[0] : null
|
||||
event.target.value = ''
|
||||
if (!file) return
|
||||
await uploadUserAvatar(user, file)
|
||||
}
|
||||
|
||||
async function removeUserAvatar(user) {
|
||||
if (!user?.avatarSrc) return
|
||||
await uploadUserAvatar(user, null, { remove: true })
|
||||
}
|
||||
|
||||
function destroyFeaturedSortable() {
|
||||
if (featuredSortable.value) {
|
||||
featuredSortable.value.destroy()
|
||||
@@ -892,6 +816,52 @@ const {
|
||||
error,
|
||||
})
|
||||
|
||||
const {
|
||||
setUserAvatarInput,
|
||||
canManageModalRole,
|
||||
isUserEditDirty,
|
||||
roleLabelOf,
|
||||
openUserAvatarPicker,
|
||||
onUserAvatarChange,
|
||||
removeUserAvatar,
|
||||
openUserEditModal,
|
||||
closeUserEditModal,
|
||||
saveUserEdit,
|
||||
openUserPasswordModal,
|
||||
closeUserPasswordModal,
|
||||
confirmUserPasswordReset,
|
||||
openUserDeleteModal,
|
||||
closeUserDeleteModal,
|
||||
confirmUserDelete,
|
||||
openUserRoleModal,
|
||||
closeUserRoleModal,
|
||||
confirmUserRoleDraft,
|
||||
submitUserFilters,
|
||||
userDisplayName,
|
||||
} = useAdminUsers({
|
||||
api,
|
||||
auth,
|
||||
users,
|
||||
userQuery,
|
||||
userSort,
|
||||
userSortDirection,
|
||||
userAvatarInputs,
|
||||
modalTargetUser,
|
||||
modalPasswordDraft,
|
||||
modalRoleNextAdmin,
|
||||
modalUserDraftEmail,
|
||||
modalUserDraftNickname,
|
||||
modalUserDraftIsAdmin,
|
||||
userEditModalOpen,
|
||||
userPasswordModalOpen,
|
||||
userDeleteModalOpen,
|
||||
userRoleModalOpen,
|
||||
resetMessages,
|
||||
refreshUsers,
|
||||
success,
|
||||
error,
|
||||
})
|
||||
|
||||
|
||||
function handleThumbFile(file) {
|
||||
const nextFile = file && (file.type || '').startsWith('image/') ? file : null
|
||||
@@ -1043,138 +1013,6 @@ async function removeGame() {
|
||||
}
|
||||
}
|
||||
|
||||
function openUserEditModal(user) {
|
||||
resetMessages()
|
||||
modalTargetUser.value = user ? { ...user } : null
|
||||
modalUserDraftEmail.value = user?.email || ''
|
||||
modalUserDraftNickname.value = user?.nickname || ''
|
||||
modalUserDraftIsAdmin.value = !!user?.isAdmin
|
||||
userEditModalOpen.value = true
|
||||
}
|
||||
|
||||
function closeUserEditModal() {
|
||||
userEditModalOpen.value = false
|
||||
modalTargetUser.value = null
|
||||
modalUserDraftEmail.value = ''
|
||||
modalUserDraftNickname.value = ''
|
||||
modalUserDraftIsAdmin.value = false
|
||||
}
|
||||
|
||||
async function saveUserEdit() {
|
||||
resetMessages()
|
||||
if (!modalTargetUser.value?.id) return
|
||||
|
||||
try {
|
||||
const data = await api.updateAdminUser(modalTargetUser.value.id, {
|
||||
email: modalUserDraftEmail.value.trim(),
|
||||
nickname: modalUserDraftNickname.value.trim(),
|
||||
isAdmin: !!modalUserDraftIsAdmin.value,
|
||||
})
|
||||
const updated = data.user
|
||||
users.value = users.value.map((entry) =>
|
||||
entry.id === updated.id
|
||||
? {
|
||||
...entry,
|
||||
...updated,
|
||||
isAvatarBusy: entry.isAvatarBusy || false,
|
||||
}
|
||||
: entry
|
||||
)
|
||||
if (updated.id === auth.user?.id) await auth.refresh()
|
||||
closeUserEditModal()
|
||||
await refreshUsers()
|
||||
success.value = '회원 정보를 저장했어요.'
|
||||
} catch (e) {
|
||||
error.value = '회원 정보 저장에 실패했어요.'
|
||||
}
|
||||
}
|
||||
|
||||
function openUserPasswordModal(user) {
|
||||
resetMessages()
|
||||
modalTargetUser.value = user ? { ...user } : null
|
||||
modalPasswordDraft.value = ''
|
||||
userPasswordModalOpen.value = true
|
||||
}
|
||||
|
||||
function closeUserPasswordModal() {
|
||||
userPasswordModalOpen.value = false
|
||||
modalTargetUser.value = null
|
||||
modalPasswordDraft.value = ''
|
||||
}
|
||||
|
||||
async function confirmUserPasswordReset() {
|
||||
resetMessages()
|
||||
if (!modalTargetUser.value?.id) return
|
||||
|
||||
const password = modalPasswordDraft.value.trim()
|
||||
if (!password) {
|
||||
error.value = '초기화할 비밀번호를 입력해주세요.'
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await api.updateAdminUserPassword(modalTargetUser.value.id, { password })
|
||||
success.value = `${userDisplayName(modalTargetUser.value)} 계정 비밀번호를 초기화했어요.`
|
||||
closeUserPasswordModal()
|
||||
} catch (e) {
|
||||
error.value = '비밀번호 초기화에 실패했어요.'
|
||||
}
|
||||
}
|
||||
|
||||
function openUserDeleteModal(user) {
|
||||
resetMessages()
|
||||
modalTargetUser.value = user ? { ...user } : null
|
||||
userDeleteModalOpen.value = true
|
||||
}
|
||||
|
||||
function closeUserDeleteModal() {
|
||||
userDeleteModalOpen.value = false
|
||||
modalTargetUser.value = null
|
||||
}
|
||||
|
||||
async function confirmUserDelete() {
|
||||
resetMessages()
|
||||
if (!modalTargetUser.value?.id) return
|
||||
|
||||
try {
|
||||
const deletingSelf = modalTargetUser.value.id === auth.user?.id
|
||||
const deletedName = userDisplayName(modalTargetUser.value)
|
||||
await api.deleteAdminUser(modalTargetUser.value.id)
|
||||
users.value = users.value.filter((entry) => entry.id !== modalTargetUser.value.id)
|
||||
closeUserDeleteModal()
|
||||
success.value = `${deletedName} 계정을 삭제했어요.`
|
||||
if (deletingSelf) await auth.refresh()
|
||||
} catch (e) {
|
||||
error.value = '회원 삭제에 실패했어요.'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function openUserRoleModal(user, nextIsAdmin = !modalUserDraftIsAdmin.value) {
|
||||
resetMessages()
|
||||
modalTargetUser.value = user ? { ...user } : null
|
||||
modalRoleNextAdmin.value = !!nextIsAdmin
|
||||
userRoleModalOpen.value = true
|
||||
}
|
||||
|
||||
function closeUserRoleModal() {
|
||||
userRoleModalOpen.value = false
|
||||
if (!userEditModalOpen.value) modalTargetUser.value = null
|
||||
modalRoleNextAdmin.value = false
|
||||
}
|
||||
|
||||
function confirmUserRoleDraft() {
|
||||
if (!modalTargetUser.value?.id) return
|
||||
modalUserDraftIsAdmin.value = modalRoleNextAdmin.value
|
||||
const targetLabel = modalRoleNextAdmin.value ? '운영자 권한을 저장 대기 상태로 반영했어요.' : '운영자 권한 해제를 저장 대기 상태로 반영했어요.'
|
||||
closeUserRoleModal()
|
||||
success.value = targetLabel
|
||||
}
|
||||
|
||||
function submitUserFilters() {
|
||||
refreshUsers()
|
||||
}
|
||||
|
||||
function submitCustomItemSearch() {
|
||||
customItemPage.value = 1
|
||||
refreshCustomItems()
|
||||
@@ -1572,10 +1410,6 @@ function userAvatarUrl(user) {
|
||||
return user?.avatarSrc ? toApiUrl(user.avatarSrc) : ''
|
||||
}
|
||||
|
||||
function userDisplayName(user) {
|
||||
return user?.nickname || user?.email?.split('@')[0] || '알 수 없음'
|
||||
}
|
||||
|
||||
function userAvatarFallback(user) {
|
||||
return (user?.email?.trim()?.[0] || '?').toUpperCase()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user