|
@@ -8,8 +8,77 @@ class AdminController extends BaseApiController
|
|
|
{
|
|
{
|
|
|
protected $format = 'json';
|
|
protected $format = 'json';
|
|
|
|
|
|
|
|
|
|
+ private const ALLOWED_ROLES = ['super_admin', 'admin'];
|
|
|
|
|
+ private const ALLOWED_STATUSES = ['active', 'inactive', 'suspended'];
|
|
|
|
|
+
|
|
|
|
|
+ // 허용 메뉴 권한 (admin.vue의 menuItems id와 동일)
|
|
|
|
|
+ private const ALLOWED_PERMISSIONS = [
|
|
|
|
|
+ 'admin', 'field', 'fishing', 'challenge', 'quest', 'item', 'species', 'user',
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 호출한 관리자가 슈퍼관리자인지 확인
|
|
|
|
|
+ */
|
|
|
|
|
+ private function isCallerSuperAdmin($authData): bool
|
|
|
|
|
+ {
|
|
|
|
|
+ $admin = $this->getDB()->table('admin_users')
|
|
|
|
|
+ ->select('role')
|
|
|
|
|
+ ->where('id', (int) $authData->admin_id)
|
|
|
|
|
+ ->get()->getRow();
|
|
|
|
|
+ return $admin && $admin->role === 'super_admin';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // TODO: 권한관리 시스템 구축 후, '관리자관리 권한'을 가진 admin만
|
|
|
|
|
+ // create/update/delete/changePassword/unlockAccount 가능하도록 가드 재추가
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 관리자의 메뉴 권한 동기화 (DELETE + INSERT)
|
|
|
|
|
+ * - super_admin은 row 박지 않음 (role 자체가 모든 권한)
|
|
|
|
|
+ * - admin은 검증된 배열만 INSERT
|
|
|
|
|
+ */
|
|
|
|
|
+ private function syncPermissions(int $adminId, string $role, $permissions): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $db = $this->getDB();
|
|
|
|
|
+
|
|
|
|
|
+ // 기존 권한 전부 제거
|
|
|
|
|
+ $db->table('admin_permissions')->where('admin_id', $adminId)->delete();
|
|
|
|
|
+
|
|
|
|
|
+ if ($role !== 'admin') return;
|
|
|
|
|
+ if (!is_array($permissions) || empty($permissions)) return;
|
|
|
|
|
+
|
|
|
|
|
+ // 허용 목록만 INSERT (중복 제거)
|
|
|
|
|
+ $clean = array_values(array_unique(array_filter(
|
|
|
|
|
+ $permissions,
|
|
|
|
|
+ fn($p) => is_string($p) && in_array($p, self::ALLOWED_PERMISSIONS, true)
|
|
|
|
|
+ )));
|
|
|
|
|
+ if (empty($clean)) return;
|
|
|
|
|
+
|
|
|
|
|
+ $rows = array_map(fn($p) => [
|
|
|
|
|
+ 'admin_id' => $adminId,
|
|
|
|
|
+ 'permission' => $p,
|
|
|
|
|
+ 'created_at' => date('Y-m-d H:i:s'),
|
|
|
|
|
+ ], $clean);
|
|
|
|
|
+ $db->table('admin_permissions')->insertBatch($rows);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 관리자의 메뉴 권한 배열 반환
|
|
|
|
|
+ * - super_admin은 'all' 반환
|
|
|
|
|
+ * - admin은 ['field', 'fishing', ...] 형태
|
|
|
|
|
+ */
|
|
|
|
|
+ private function getPermissions(int $adminId, string $role)
|
|
|
|
|
+ {
|
|
|
|
|
+ if ($role === 'super_admin') return 'all';
|
|
|
|
|
+
|
|
|
|
|
+ $rows = $this->getDB()->table('admin_permissions')
|
|
|
|
|
+ ->select('permission')
|
|
|
|
|
+ ->where('admin_id', $adminId)
|
|
|
|
|
+ ->get()->getResult();
|
|
|
|
|
+ return array_map(fn($r) => $r->permission, $rows);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
- * Get all admins (관리자 목록)
|
|
|
|
|
|
|
+ * Get all admins (관리자 목록) — 인증된 모두 가능
|
|
|
* GET /api/admin
|
|
* GET /api/admin
|
|
|
*/
|
|
*/
|
|
|
public function index()
|
|
public function index()
|
|
@@ -20,60 +89,84 @@ class AdminController extends BaseApiController
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
- $page = $this->request->getGet('page') ?? 1;
|
|
|
|
|
- $perPage = $this->request->getGet('per_page') ?? 10;
|
|
|
|
|
|
|
+ $page = (int) ($this->request->getGet('page') ?? 1);
|
|
|
|
|
+ $perPage = (int) ($this->request->getGet('per_page') ?? 10);
|
|
|
|
|
+ if ($page < 1) $page = 1;
|
|
|
|
|
+ if ($perPage < 1) $perPage = 10;
|
|
|
$offset = ($page - 1) * $perPage;
|
|
$offset = ($page - 1) * $perPage;
|
|
|
|
|
|
|
|
$db = $this->getDB();
|
|
$db = $this->getDB();
|
|
|
$builder = $db->table('admin_users');
|
|
$builder = $db->table('admin_users');
|
|
|
|
|
|
|
|
- // 'admin' 계정 제외
|
|
|
|
|
- $builder->where('username !=', 'admin');
|
|
|
|
|
-
|
|
|
|
|
- // 검색 기능 - 아이디, 이름만 LIKE 검색
|
|
|
|
|
- $search = $this->request->getGet('search');
|
|
|
|
|
- if (!empty($search)) {
|
|
|
|
|
- $builder->groupStart()
|
|
|
|
|
- ->like('username', $search)
|
|
|
|
|
- ->orLike('name', $search)
|
|
|
|
|
- ->groupEnd();
|
|
|
|
|
|
|
+ // 삭제된 계정만 / 활성만 분기 (deleted=1이면 삭제된 것만)
|
|
|
|
|
+ $showDeleted = $this->request->getGet('deleted') === '1';
|
|
|
|
|
+ $builder->where('deleted_YN', $showDeleted ? 'Y' : 'N');
|
|
|
|
|
+
|
|
|
|
|
+ // 검색
|
|
|
|
|
+ $search = trim((string) $this->request->getGet('search'));
|
|
|
|
|
+ $searchField = $this->request->getGet('search_field'); // username / name / email / ''
|
|
|
|
|
+ if ($search !== '') {
|
|
|
|
|
+ if ($searchField === 'username') {
|
|
|
|
|
+ $builder->like('username', $search);
|
|
|
|
|
+ } elseif ($searchField === 'name') {
|
|
|
|
|
+ $builder->like('name', $search);
|
|
|
|
|
+ } elseif ($searchField === 'email') {
|
|
|
|
|
+ $builder->like('email', $search);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ $builder->groupStart()
|
|
|
|
|
+ ->like('username', $search)
|
|
|
|
|
+ ->orLike('name', $search)
|
|
|
|
|
+ ->orLike('email', $search)
|
|
|
|
|
+ ->groupEnd();
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 역할 필터
|
|
// 역할 필터
|
|
|
$role = $this->request->getGet('role');
|
|
$role = $this->request->getGet('role');
|
|
|
- if (!empty($role)) {
|
|
|
|
|
|
|
+ if (!empty($role) && in_array($role, self::ALLOWED_ROLES, true)) {
|
|
|
$builder->where('role', $role);
|
|
$builder->where('role', $role);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 상태 필터
|
|
// 상태 필터
|
|
|
$status = $this->request->getGet('status');
|
|
$status = $this->request->getGet('status');
|
|
|
- if (!empty($status)) {
|
|
|
|
|
|
|
+ if (!empty($status) && in_array($status, self::ALLOWED_STATUSES, true)) {
|
|
|
$builder->where('status', $status);
|
|
$builder->where('status', $status);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 전체 개수
|
|
|
|
|
$total = $builder->countAllResults(false);
|
|
$total = $builder->countAllResults(false);
|
|
|
|
|
|
|
|
- // 비밀번호 제외하고 조회 (login_attempts 포함)
|
|
|
|
|
- $builder->select('id, username, name, email, department, role, status, COALESCE(login_attempts, 0) as login_attempts, last_failed_login, created_at, updated_at');
|
|
|
|
|
- $builder->orderBy('id', 'DESC');
|
|
|
|
|
- $builder->limit($perPage, $offset);
|
|
|
|
|
-
|
|
|
|
|
- // SQL 쿼리 로그
|
|
|
|
|
- $sql = $builder->getCompiledSelect(false);
|
|
|
|
|
- log_message('debug', 'AdminController SQL: ' . $sql);
|
|
|
|
|
-
|
|
|
|
|
- $items = $builder->get()->getResult();
|
|
|
|
|
-
|
|
|
|
|
- $result = [
|
|
|
|
|
- 'items' => $items,
|
|
|
|
|
- 'total' => $total,
|
|
|
|
|
- 'page' => (int)$page,
|
|
|
|
|
- 'per_page' => (int)$perPage,
|
|
|
|
|
- 'total_pages' => ceil($total / $perPage)
|
|
|
|
|
- ];
|
|
|
|
|
|
|
+ $items = $builder
|
|
|
|
|
+ ->select('id, username, name, email, phone, role, status, COALESCE(login_attempts, 0) as login_attempts, last_failed_login, last_login, created_at, updated_at')
|
|
|
|
|
+ ->orderBy('id', 'DESC')
|
|
|
|
|
+ ->limit($perPage, $offset)
|
|
|
|
|
+ ->get()
|
|
|
|
|
+ ->getResult();
|
|
|
|
|
+
|
|
|
|
|
+ // permissions 일괄 조회 후 attach
|
|
|
|
|
+ if (!empty($items)) {
|
|
|
|
|
+ $ids = array_map(fn($i) => (int) $i->id, $items);
|
|
|
|
|
+ $rows = $db->table('admin_permissions')
|
|
|
|
|
+ ->select('admin_id, permission')
|
|
|
|
|
+ ->whereIn('admin_id', $ids)
|
|
|
|
|
+ ->get()->getResult();
|
|
|
|
|
+ $permsByAdmin = [];
|
|
|
|
|
+ foreach ($rows as $r) {
|
|
|
|
|
+ $permsByAdmin[$r->admin_id][] = $r->permission;
|
|
|
|
|
+ }
|
|
|
|
|
+ foreach ($items as $item) {
|
|
|
|
|
+ $item->permissions = $item->role === 'super_admin'
|
|
|
|
|
+ ? 'all'
|
|
|
|
|
+ : ($permsByAdmin[$item->id] ?? []);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- return $this->respondSuccess($result);
|
|
|
|
|
|
|
+ return $this->respondSuccess([
|
|
|
|
|
+ 'items' => $items,
|
|
|
|
|
+ 'total' => $total,
|
|
|
|
|
+ 'page' => $page,
|
|
|
|
|
+ 'per_page' => $perPage,
|
|
|
|
|
+ 'total_pages' => (int) ceil($total / $perPage),
|
|
|
|
|
+ ]);
|
|
|
} catch (\Exception $e) {
|
|
} catch (\Exception $e) {
|
|
|
log_message('error', 'AdminController index error: ' . $e->getMessage());
|
|
log_message('error', 'AdminController index error: ' . $e->getMessage());
|
|
|
return $this->respondError('관리자 목록 조회 중 오류가 발생했습니다: ' . $e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
|
return $this->respondError('관리자 목록 조회 중 오류가 발생했습니다: ' . $e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
|
@@ -81,7 +174,7 @@ class AdminController extends BaseApiController
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Get single admin (관리자 상세)
|
|
|
|
|
|
|
+ * Get single admin (관리자 상세) — 인증된 모두 가능
|
|
|
* GET /api/admin/:id
|
|
* GET /api/admin/:id
|
|
|
*/
|
|
*/
|
|
|
public function show($id = null)
|
|
public function show($id = null)
|
|
@@ -97,8 +190,9 @@ class AdminController extends BaseApiController
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
$admin = $this->getDB()->table('admin_users')
|
|
$admin = $this->getDB()->table('admin_users')
|
|
|
- ->select('id, username, name, email, department, role, status, login_attempts, last_failed_login, created_at, updated_at')
|
|
|
|
|
- ->where('id', $id)
|
|
|
|
|
|
|
+ ->select('id, username, name, email, phone, role, status, login_attempts, last_failed_login, last_login, created_at, updated_at')
|
|
|
|
|
+ ->where('id', (int) $id)
|
|
|
|
|
+ ->where('deleted_YN', 'N')
|
|
|
->get()
|
|
->get()
|
|
|
->getRow();
|
|
->getRow();
|
|
|
|
|
|
|
@@ -106,6 +200,8 @@ class AdminController extends BaseApiController
|
|
|
return $this->respondError('관리자를 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
|
|
return $this->respondError('관리자를 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ $admin->permissions = $this->getPermissions((int) $id, $admin->role);
|
|
|
|
|
+
|
|
|
return $this->respondSuccess($admin);
|
|
return $this->respondSuccess($admin);
|
|
|
} catch (\Exception $e) {
|
|
} catch (\Exception $e) {
|
|
|
log_message('error', 'AdminController show error: ' . $e->getMessage());
|
|
log_message('error', 'AdminController show error: ' . $e->getMessage());
|
|
@@ -124,21 +220,20 @@ class AdminController extends BaseApiController
|
|
|
return $auth;
|
|
return $auth;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- $username = $this->request->getGet('username');
|
|
|
|
|
|
|
+ $username = trim((string) $this->request->getGet('username'));
|
|
|
|
|
|
|
|
- if (empty($username)) {
|
|
|
|
|
|
|
+ if ($username === '') {
|
|
|
return $this->respondError('아이디를 입력하세요.');
|
|
return $this->respondError('아이디를 입력하세요.');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
$existing = $this->getDB()->table('admin_users')
|
|
$existing = $this->getDB()->table('admin_users')
|
|
|
->where('username', $username)
|
|
->where('username', $username)
|
|
|
|
|
+ ->where('deleted_YN', 'N')
|
|
|
->get()
|
|
->get()
|
|
|
->getRow();
|
|
->getRow();
|
|
|
|
|
|
|
|
- return $this->respondSuccess([
|
|
|
|
|
- 'available' => !$existing
|
|
|
|
|
- ]);
|
|
|
|
|
|
|
+ return $this->respondSuccess(['available' => !$existing]);
|
|
|
} catch (\Exception $e) {
|
|
} catch (\Exception $e) {
|
|
|
log_message('error', 'AdminController checkUsername error: ' . $e->getMessage());
|
|
log_message('error', 'AdminController checkUsername error: ' . $e->getMessage());
|
|
|
return $this->respondError('중복 체크 중 오류가 발생했습니다.', ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
|
return $this->respondError('중복 체크 중 오류가 발생했습니다.', ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
|
@@ -146,7 +241,7 @@ class AdminController extends BaseApiController
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Create new admin (관리자 생성)
|
|
|
|
|
|
|
+ * Create new admin (관리자 생성) — 슈퍼 관리자만
|
|
|
* POST /api/admin
|
|
* POST /api/admin
|
|
|
*/
|
|
*/
|
|
|
public function create()
|
|
public function create()
|
|
@@ -155,9 +250,8 @@ class AdminController extends BaseApiController
|
|
|
if ($auth instanceof ResponseInterface) {
|
|
if ($auth instanceof ResponseInterface) {
|
|
|
return $auth;
|
|
return $auth;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
try {
|
|
try {
|
|
|
- $data = $this->request->getJSON(true);
|
|
|
|
|
|
|
+ $data = $this->request->getJSON(true) ?? [];
|
|
|
|
|
|
|
|
// 필수 필드 검증
|
|
// 필수 필드 검증
|
|
|
$required = ['username', 'password', 'name', 'email'];
|
|
$required = ['username', 'password', 'name', 'email'];
|
|
@@ -167,15 +261,43 @@ class AdminController extends BaseApiController
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 중복 체크
|
|
|
|
|
|
|
+ $username = trim((string) $data['username']);
|
|
|
|
|
+
|
|
|
|
|
+ // role / status 검증
|
|
|
|
|
+ $role = $data['role'] ?? 'admin';
|
|
|
|
|
+ if (!in_array($role, self::ALLOWED_ROLES, true)) {
|
|
|
|
|
+ return $this->respondError('올바르지 않은 역할입니다.', ResponseInterface::HTTP_BAD_REQUEST);
|
|
|
|
|
+ }
|
|
|
|
|
+ $status = $data['status'] ?? 'active';
|
|
|
|
|
+ if (!in_array($status, self::ALLOWED_STATUSES, true)) {
|
|
|
|
|
+ return $this->respondError('올바르지 않은 상태입니다.', ResponseInterface::HTTP_BAD_REQUEST);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 호출자가 슈퍼관리자가 아니면 super_admin 등록 차단
|
|
|
|
|
+ if ($role === 'super_admin' && !$this->isCallerSuperAdmin($auth)) {
|
|
|
|
|
+ return $this->respondError('슈퍼 관리자 권한 부여는 슈퍼 관리자만 가능합니다.', ResponseInterface::HTTP_FORBIDDEN);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 일반 admin은 권한 1개 이상 필수
|
|
|
|
|
+ $permissions = $data['permissions'] ?? [];
|
|
|
|
|
+ if ($role === 'admin') {
|
|
|
|
|
+ if (!is_array($permissions) || empty($permissions)) {
|
|
|
|
|
+ return $this->respondError('관리자에게 부여할 메뉴 권한을 1개 이상 선택하세요.', ResponseInterface::HTTP_BAD_REQUEST);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 중복 체크 (삭제된 계정 제외 — 재사용 허용)
|
|
|
$existing = $this->getDB()->table('admin_users')
|
|
$existing = $this->getDB()->table('admin_users')
|
|
|
- ->where('username', $data['username'])
|
|
|
|
|
- ->orWhere('email', $data['email'])
|
|
|
|
|
|
|
+ ->groupStart()
|
|
|
|
|
+ ->where('username', $username)
|
|
|
|
|
+ ->orWhere('email', $data['email'])
|
|
|
|
|
+ ->groupEnd()
|
|
|
|
|
+ ->where('deleted_YN', 'N')
|
|
|
->get()
|
|
->get()
|
|
|
->getRow();
|
|
->getRow();
|
|
|
|
|
|
|
|
if ($existing) {
|
|
if ($existing) {
|
|
|
- if ($existing->username === $data['username']) {
|
|
|
|
|
|
|
+ if ($existing->username === $username) {
|
|
|
return $this->respondError('이미 사용 중인 아이디입니다.', ResponseInterface::HTTP_BAD_REQUEST);
|
|
return $this->respondError('이미 사용 중인 아이디입니다.', ResponseInterface::HTTP_BAD_REQUEST);
|
|
|
}
|
|
}
|
|
|
if ($existing->email === $data['email']) {
|
|
if ($existing->email === $data['email']) {
|
|
@@ -183,30 +305,33 @@ class AdminController extends BaseApiController
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 비밀번호 해싱
|
|
|
|
|
$insertData = [
|
|
$insertData = [
|
|
|
- 'username' => $data['username'],
|
|
|
|
|
- 'password' => password_hash($data['password'], PASSWORD_DEFAULT),
|
|
|
|
|
- 'name' => $data['name'],
|
|
|
|
|
- 'email' => $data['email'],
|
|
|
|
|
- 'department' => $data['department'] ?? '',
|
|
|
|
|
- 'role' => $data['role'] ?? 'admin',
|
|
|
|
|
- 'status' => $data['status'] ?? 'active',
|
|
|
|
|
- 'created_at' => date('Y-m-d H:i:s'),
|
|
|
|
|
- 'updated_at' => date('Y-m-d H:i:s')
|
|
|
|
|
|
|
+ 'username' => $username,
|
|
|
|
|
+ 'password' => password_hash($data['password'], PASSWORD_DEFAULT),
|
|
|
|
|
+ 'password_changed_at' => date('Y-m-d H:i:s'),
|
|
|
|
|
+ 'name' => trim((string) $data['name']),
|
|
|
|
|
+ 'email' => trim((string) $data['email']),
|
|
|
|
|
+ 'phone' => trim((string) ($data['phone'] ?? '')),
|
|
|
|
|
+ 'role' => $role,
|
|
|
|
|
+ 'status' => $status,
|
|
|
|
|
+ 'login_attempts' => 0,
|
|
|
|
|
+ 'deleted_YN' => 'N',
|
|
|
|
|
+ 'created_at' => date('Y-m-d H:i:s'),
|
|
|
|
|
+ 'updated_at' => date('Y-m-d H:i:s'),
|
|
|
];
|
|
];
|
|
|
|
|
|
|
|
- $builder = $this->getDB()->table('admin_users');
|
|
|
|
|
- $builder->insert($insertData);
|
|
|
|
|
-
|
|
|
|
|
|
|
+ $this->getDB()->table('admin_users')->insert($insertData);
|
|
|
$insertId = $this->getDB()->insertID();
|
|
$insertId = $this->getDB()->insertID();
|
|
|
|
|
|
|
|
- // 생성된 관리자 정보 조회 (비밀번호 제외)
|
|
|
|
|
|
|
+ // 권한 저장 (super_admin은 row 안 박음)
|
|
|
|
|
+ $this->syncPermissions((int) $insertId, $role, $permissions);
|
|
|
|
|
+
|
|
|
$admin = $this->getDB()->table('admin_users')
|
|
$admin = $this->getDB()->table('admin_users')
|
|
|
- ->select('id, username, name, email, department, role, status, login_attempts, last_failed_login, created_at, updated_at')
|
|
|
|
|
|
|
+ ->select('id, username, name, email, phone, role, status, login_attempts, last_failed_login, last_login, created_at, updated_at')
|
|
|
->where('id', $insertId)
|
|
->where('id', $insertId)
|
|
|
->get()
|
|
->get()
|
|
|
->getRow();
|
|
->getRow();
|
|
|
|
|
+ $admin->permissions = $this->getPermissions((int) $insertId, $admin->role);
|
|
|
|
|
|
|
|
return $this->respondSuccess($admin, '관리자가 생성되었습니다.', ResponseInterface::HTTP_CREATED);
|
|
return $this->respondSuccess($admin, '관리자가 생성되었습니다.', ResponseInterface::HTTP_CREATED);
|
|
|
} catch (\Exception $e) {
|
|
} catch (\Exception $e) {
|
|
@@ -216,7 +341,7 @@ class AdminController extends BaseApiController
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Update admin (관리자 수정)
|
|
|
|
|
|
|
+ * Update admin (관리자 수정) — 슈퍼 관리자만
|
|
|
* PUT /api/admin/:id
|
|
* PUT /api/admin/:id
|
|
|
*/
|
|
*/
|
|
|
public function update($id = null)
|
|
public function update($id = null)
|
|
@@ -225,25 +350,34 @@ class AdminController extends BaseApiController
|
|
|
if ($auth instanceof ResponseInterface) {
|
|
if ($auth instanceof ResponseInterface) {
|
|
|
return $auth;
|
|
return $auth;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
if (empty($id)) {
|
|
if (empty($id)) {
|
|
|
return $this->respondError('관리자 ID가 필요합니다.', ResponseInterface::HTTP_BAD_REQUEST);
|
|
return $this->respondError('관리자 ID가 필요합니다.', ResponseInterface::HTTP_BAD_REQUEST);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
- // 기존 관리자 확인
|
|
|
|
|
- $existing = $this->getDB()->table('admin_users')->where('id', $id)->get()->getRow();
|
|
|
|
|
|
|
+ $existing = $this->getDB()->table('admin_users')
|
|
|
|
|
+ ->where('id', (int) $id)
|
|
|
|
|
+ ->where('deleted_YN', 'N')
|
|
|
|
|
+ ->get()->getRow();
|
|
|
if (!$existing) {
|
|
if (!$existing) {
|
|
|
return $this->respondError('관리자를 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
|
|
return $this->respondError('관리자를 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- $data = $this->request->getJSON(true);
|
|
|
|
|
|
|
+ $data = $this->request->getJSON(true) ?? [];
|
|
|
|
|
+
|
|
|
|
|
+ // 일반 admin은 role/permissions 변경 불가
|
|
|
|
|
+ if (isset($data['role']) || array_key_exists('permissions', $data)) {
|
|
|
|
|
+ if (!$this->isCallerSuperAdmin($auth)) {
|
|
|
|
|
+ return $this->respondError('권한 변경은 슈퍼 관리자만 가능합니다.', ResponseInterface::HTTP_FORBIDDEN);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // email 중복 체크 (자신 제외)
|
|
|
|
|
|
|
+ // 이메일 중복 체크 (자신 제외, 삭제된 계정 제외)
|
|
|
if (!empty($data['email']) && $data['email'] !== $existing->email) {
|
|
if (!empty($data['email']) && $data['email'] !== $existing->email) {
|
|
|
$duplicate = $this->getDB()->table('admin_users')
|
|
$duplicate = $this->getDB()->table('admin_users')
|
|
|
->where('email', $data['email'])
|
|
->where('email', $data['email'])
|
|
|
- ->where('id !=', $id)
|
|
|
|
|
|
|
+ ->where('id !=', (int) $id)
|
|
|
|
|
+ ->where('deleted_YN', 'N')
|
|
|
->get()
|
|
->get()
|
|
|
->getRow();
|
|
->getRow();
|
|
|
if ($duplicate) {
|
|
if ($duplicate) {
|
|
@@ -251,35 +385,54 @@ class AdminController extends BaseApiController
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- $updateData = [
|
|
|
|
|
- 'updated_at' => date('Y-m-d H:i:s')
|
|
|
|
|
- ];
|
|
|
|
|
|
|
+ $updateData = ['updated_at' => date('Y-m-d H:i:s')];
|
|
|
|
|
+
|
|
|
|
|
+ if (isset($data['name'])) $updateData['name'] = trim((string) $data['name']);
|
|
|
|
|
+ if (isset($data['email'])) $updateData['email'] = trim((string) $data['email']);
|
|
|
|
|
+ if (isset($data['phone'])) $updateData['phone'] = trim((string) $data['phone']);
|
|
|
|
|
|
|
|
- if (isset($data['name'])) {
|
|
|
|
|
- $updateData['name'] = $data['name'];
|
|
|
|
|
- }
|
|
|
|
|
- if (isset($data['email'])) {
|
|
|
|
|
- $updateData['email'] = $data['email'];
|
|
|
|
|
- }
|
|
|
|
|
- if (isset($data['department'])) {
|
|
|
|
|
- $updateData['department'] = $data['department'];
|
|
|
|
|
- }
|
|
|
|
|
if (isset($data['role'])) {
|
|
if (isset($data['role'])) {
|
|
|
|
|
+ if (!in_array($data['role'], self::ALLOWED_ROLES, true)) {
|
|
|
|
|
+ return $this->respondError('올바르지 않은 역할입니다.', ResponseInterface::HTTP_BAD_REQUEST);
|
|
|
|
|
+ }
|
|
|
$updateData['role'] = $data['role'];
|
|
$updateData['role'] = $data['role'];
|
|
|
}
|
|
}
|
|
|
if (isset($data['status'])) {
|
|
if (isset($data['status'])) {
|
|
|
|
|
+ if (!in_array($data['status'], self::ALLOWED_STATUSES, true)) {
|
|
|
|
|
+ return $this->respondError('올바르지 않은 상태입니다.', ResponseInterface::HTTP_BAD_REQUEST);
|
|
|
|
|
+ }
|
|
|
$updateData['status'] = $data['status'];
|
|
$updateData['status'] = $data['status'];
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- $builder = $this->getDB()->table('admin_users');
|
|
|
|
|
- $builder->where('id', $id)->update($updateData);
|
|
|
|
|
|
|
+ $this->getDB()->table('admin_users')->where('id', (int) $id)->update($updateData);
|
|
|
|
|
+
|
|
|
|
|
+ // 권한 동기화: role 또는 permissions가 들어왔을 때만
|
|
|
|
|
+ $touchedRole = isset($data['role']);
|
|
|
|
|
+ $touchedPerms = array_key_exists('permissions', $data);
|
|
|
|
|
+ if ($touchedRole || $touchedPerms) {
|
|
|
|
|
+ $finalRole = $touchedRole ? $data['role'] : $existing->role;
|
|
|
|
|
+ $finalPermissions = $touchedPerms ? ($data['permissions'] ?? []) : [];
|
|
|
|
|
+
|
|
|
|
|
+ // role='admin'으로 바뀌었는데 권한 비어있으면 차단
|
|
|
|
|
+ if ($finalRole === 'admin' && $touchedPerms && empty($finalPermissions)) {
|
|
|
|
|
+ return $this->respondError('관리자에게 부여할 메뉴 권한을 1개 이상 선택하세요.', ResponseInterface::HTTP_BAD_REQUEST);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // role만 super_admin → admin으로 바꿨는데 permissions 안 보낸 경우도 차단
|
|
|
|
|
+ if ($touchedRole && $finalRole === 'admin' && !$touchedPerms) {
|
|
|
|
|
+ return $this->respondError('일반 관리자로 변경 시 메뉴 권한을 함께 지정해야 합니다.', ResponseInterface::HTTP_BAD_REQUEST);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $this->syncPermissions((int) $id, $finalRole, $finalPermissions);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 업데이트된 관리자 정보 조회
|
|
|
|
|
$admin = $this->getDB()->table('admin_users')
|
|
$admin = $this->getDB()->table('admin_users')
|
|
|
- ->select('id, username, name, email, department, role, status, login_attempts, last_failed_login, created_at, updated_at')
|
|
|
|
|
- ->where('id', $id)
|
|
|
|
|
|
|
+ ->select('id, username, name, email, phone, role, status, login_attempts, last_failed_login, last_login, created_at, updated_at')
|
|
|
|
|
+ ->where('id', (int) $id)
|
|
|
|
|
+ ->where('deleted_YN', 'N')
|
|
|
->get()
|
|
->get()
|
|
|
->getRow();
|
|
->getRow();
|
|
|
|
|
+ $admin->permissions = $this->getPermissions((int) $id, $admin->role);
|
|
|
|
|
|
|
|
return $this->respondSuccess($admin, '관리자 정보가 수정되었습니다.');
|
|
return $this->respondSuccess($admin, '관리자 정보가 수정되었습니다.');
|
|
|
} catch (\Exception $e) {
|
|
} catch (\Exception $e) {
|
|
@@ -289,7 +442,7 @@ class AdminController extends BaseApiController
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Delete admin (관리자 삭제)
|
|
|
|
|
|
|
+ * Delete admin (관리자 삭제) — 슈퍼 관리자만, 본인은 삭제 불가
|
|
|
* DELETE /api/admin/:id
|
|
* DELETE /api/admin/:id
|
|
|
*/
|
|
*/
|
|
|
public function delete($id = null)
|
|
public function delete($id = null)
|
|
@@ -298,22 +451,36 @@ class AdminController extends BaseApiController
|
|
|
if ($auth instanceof ResponseInterface) {
|
|
if ($auth instanceof ResponseInterface) {
|
|
|
return $auth;
|
|
return $auth;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
if (empty($id)) {
|
|
if (empty($id)) {
|
|
|
return $this->respondError('관리자 ID가 필요합니다.', ResponseInterface::HTTP_BAD_REQUEST);
|
|
return $this->respondError('관리자 ID가 필요합니다.', ResponseInterface::HTTP_BAD_REQUEST);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
- $existing = $this->getDB()->table('admin_users')->where('id', $id)->get()->getRow();
|
|
|
|
|
|
|
+ $targetId = (int) $id;
|
|
|
|
|
+
|
|
|
|
|
+ // 본인 삭제 방지
|
|
|
|
|
+ if ((int) $auth->admin_id === $targetId) {
|
|
|
|
|
+ return $this->respondError('본인 계정은 삭제할 수 없습니다.', ResponseInterface::HTTP_FORBIDDEN);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $existing = $this->getDB()->table('admin_users')
|
|
|
|
|
+ ->where('id', $targetId)
|
|
|
|
|
+ ->where('deleted_YN', 'N')
|
|
|
|
|
+ ->get()->getRow();
|
|
|
if (!$existing) {
|
|
if (!$existing) {
|
|
|
return $this->respondError('관리자를 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
|
|
return $this->respondError('관리자를 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- $builder = $this->getDB()->table('admin_users');
|
|
|
|
|
- $builder->where('id', $id)->delete();
|
|
|
|
|
|
|
+ // soft delete — 데이터는 보존, 플래그만 변경
|
|
|
|
|
+ $this->getDB()->table('admin_users')
|
|
|
|
|
+ ->where('id', $targetId)
|
|
|
|
|
+ ->update([
|
|
|
|
|
+ 'deleted_YN' => 'Y',
|
|
|
|
|
+ 'updated_at' => date('Y-m-d H:i:s'),
|
|
|
|
|
+ ]);
|
|
|
|
|
|
|
|
- // 해당 관리자의 토큰도 삭제
|
|
|
|
|
- $this->getDB()->table('admin_tokens')->where('admin_id', $id)->delete();
|
|
|
|
|
|
|
+ // 토큰만 무효화 (admin_permissions는 복구 시를 위해 보존)
|
|
|
|
|
+ $this->getDB()->table('admin_tokens')->where('admin_id', $targetId)->delete();
|
|
|
|
|
|
|
|
return $this->respondSuccess(null, '관리자가 삭제되었습니다.');
|
|
return $this->respondSuccess(null, '관리자가 삭제되었습니다.');
|
|
|
} catch (\Exception $e) {
|
|
} catch (\Exception $e) {
|
|
@@ -323,7 +490,7 @@ class AdminController extends BaseApiController
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Change admin password (비밀번호 변경)
|
|
|
|
|
|
|
+ * Change admin password (비밀번호 변경) — 슈퍼 관리자만
|
|
|
* POST /api/admin/:id/password
|
|
* POST /api/admin/:id/password
|
|
|
*/
|
|
*/
|
|
|
public function changePassword($id = null)
|
|
public function changePassword($id = null)
|
|
@@ -332,30 +499,32 @@ class AdminController extends BaseApiController
|
|
|
if ($auth instanceof ResponseInterface) {
|
|
if ($auth instanceof ResponseInterface) {
|
|
|
return $auth;
|
|
return $auth;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
if (empty($id)) {
|
|
if (empty($id)) {
|
|
|
return $this->respondError('관리자 ID가 필요합니다.', ResponseInterface::HTTP_BAD_REQUEST);
|
|
return $this->respondError('관리자 ID가 필요합니다.', ResponseInterface::HTTP_BAD_REQUEST);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
- $data = $this->request->getJSON(true);
|
|
|
|
|
|
|
+ $data = $this->request->getJSON(true) ?? [];
|
|
|
|
|
|
|
|
if (empty($data['new_password'])) {
|
|
if (empty($data['new_password'])) {
|
|
|
return $this->respondError('새 비밀번호가 필요합니다.', ResponseInterface::HTTP_BAD_REQUEST);
|
|
return $this->respondError('새 비밀번호가 필요합니다.', ResponseInterface::HTTP_BAD_REQUEST);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- $existing = $this->getDB()->table('admin_users')->where('id', $id)->get()->getRow();
|
|
|
|
|
|
|
+ $existing = $this->getDB()->table('admin_users')
|
|
|
|
|
+ ->where('id', (int) $id)
|
|
|
|
|
+ ->where('deleted_YN', 'N')
|
|
|
|
|
+ ->get()->getRow();
|
|
|
if (!$existing) {
|
|
if (!$existing) {
|
|
|
return $this->respondError('관리자를 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
|
|
return $this->respondError('관리자를 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
$updateData = [
|
|
$updateData = [
|
|
|
- 'password' => password_hash($data['new_password'], PASSWORD_DEFAULT),
|
|
|
|
|
|
|
+ 'password' => password_hash($data['new_password'], PASSWORD_DEFAULT),
|
|
|
'password_changed_at' => date('Y-m-d H:i:s'),
|
|
'password_changed_at' => date('Y-m-d H:i:s'),
|
|
|
- 'updated_at' => date('Y-m-d H:i:s')
|
|
|
|
|
|
|
+ 'updated_at' => date('Y-m-d H:i:s'),
|
|
|
];
|
|
];
|
|
|
|
|
|
|
|
- $this->getDB()->table('admin_users')->where('id', $id)->update($updateData);
|
|
|
|
|
|
|
+ $this->getDB()->table('admin_users')->where('id', (int) $id)->update($updateData);
|
|
|
|
|
|
|
|
return $this->respondSuccess(null, '비밀번호가 변경되었습니다.');
|
|
return $this->respondSuccess(null, '비밀번호가 변경되었습니다.');
|
|
|
} catch (\Exception $e) {
|
|
} catch (\Exception $e) {
|
|
@@ -365,7 +534,7 @@ class AdminController extends BaseApiController
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Unlock admin account (계정 잠금 해제)
|
|
|
|
|
|
|
+ * Unlock admin account (계정 잠금 해제) — 슈퍼 관리자만
|
|
|
* POST /api/admin/:id/unlock
|
|
* POST /api/admin/:id/unlock
|
|
|
*/
|
|
*/
|
|
|
public function unlockAccount($id = null)
|
|
public function unlockAccount($id = null)
|
|
@@ -374,24 +543,26 @@ class AdminController extends BaseApiController
|
|
|
if ($auth instanceof ResponseInterface) {
|
|
if ($auth instanceof ResponseInterface) {
|
|
|
return $auth;
|
|
return $auth;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
if (empty($id)) {
|
|
if (empty($id)) {
|
|
|
return $this->respondError('관리자 ID가 필요합니다.', ResponseInterface::HTTP_BAD_REQUEST);
|
|
return $this->respondError('관리자 ID가 필요합니다.', ResponseInterface::HTTP_BAD_REQUEST);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
- $existing = $this->getDB()->table('admin_users')->where('id', $id)->get()->getRow();
|
|
|
|
|
|
|
+ $existing = $this->getDB()->table('admin_users')
|
|
|
|
|
+ ->where('id', (int) $id)
|
|
|
|
|
+ ->where('deleted_YN', 'N')
|
|
|
|
|
+ ->get()->getRow();
|
|
|
if (!$existing) {
|
|
if (!$existing) {
|
|
|
return $this->respondError('관리자를 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
|
|
return $this->respondError('관리자를 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
$updateData = [
|
|
$updateData = [
|
|
|
- 'login_attempts' => 0,
|
|
|
|
|
|
|
+ 'login_attempts' => 0,
|
|
|
'last_failed_login' => null,
|
|
'last_failed_login' => null,
|
|
|
- 'updated_at' => date('Y-m-d H:i:s')
|
|
|
|
|
|
|
+ 'updated_at' => date('Y-m-d H:i:s'),
|
|
|
];
|
|
];
|
|
|
|
|
|
|
|
- $this->getDB()->table('admin_users')->where('id', $id)->update($updateData);
|
|
|
|
|
|
|
+ $this->getDB()->table('admin_users')->where('id', (int) $id)->update($updateData);
|
|
|
|
|
|
|
|
return $this->respondSuccess(null, '계정 잠금이 해제되었습니다.');
|
|
return $this->respondSuccess(null, '계정 잠금이 해제되었습니다.');
|
|
|
} catch (\Exception $e) {
|
|
} catch (\Exception $e) {
|
|
@@ -399,4 +570,111 @@ class AdminController extends BaseApiController
|
|
|
return $this->respondError('계정 잠금 해제 중 오류가 발생했습니다.', ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
|
return $this->respondError('계정 잠금 해제 중 오류가 발생했습니다.', ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Restore deleted admin (삭제된 관리자 복구)
|
|
|
|
|
+ * POST /api/admin/:id/restore
|
|
|
|
|
+ */
|
|
|
|
|
+ public function restore($id = null)
|
|
|
|
|
+ {
|
|
|
|
|
+ $auth = $this->requireAuth();
|
|
|
|
|
+ if ($auth instanceof ResponseInterface) {
|
|
|
|
|
+ return $auth;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (empty($id)) {
|
|
|
|
|
+ return $this->respondError('관리자 ID가 필요합니다.', ResponseInterface::HTTP_BAD_REQUEST);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ $db = $this->getDB();
|
|
|
|
|
+ $existing = $db->table('admin_users')
|
|
|
|
|
+ ->where('id', (int) $id)
|
|
|
|
|
+ ->where('deleted_YN', 'Y')
|
|
|
|
|
+ ->get()->getRow();
|
|
|
|
|
+ if (!$existing) {
|
|
|
|
|
+ return $this->respondError('삭제된 관리자를 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 동일 아이디 충돌 검사
|
|
|
|
|
+ $dupeUsername = $db->table('admin_users')
|
|
|
|
|
+ ->where('username', $existing->username)
|
|
|
|
|
+ ->where('deleted_YN', 'N')
|
|
|
|
|
+ ->countAllResults();
|
|
|
|
|
+ if ($dupeUsername > 0) {
|
|
|
|
|
+ return $this->respondError(
|
|
|
|
|
+ "동일 아이디 '{$existing->username}'가 이미 사용 중이라 복구할 수 없습니다.",
|
|
|
|
|
+ ResponseInterface::HTTP_CONFLICT
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 이메일 충돌 검사
|
|
|
|
|
+ $dupeEmail = $db->table('admin_users')
|
|
|
|
|
+ ->where('email', $existing->email)
|
|
|
|
|
+ ->where('deleted_YN', 'N')
|
|
|
|
|
+ ->countAllResults();
|
|
|
|
|
+ if ($dupeEmail > 0) {
|
|
|
|
|
+ return $this->respondError(
|
|
|
|
|
+ "동일 이메일 '{$existing->email}'가 이미 사용 중이라 복구할 수 없습니다.",
|
|
|
|
|
+ ResponseInterface::HTTP_CONFLICT
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $db->table('admin_users')
|
|
|
|
|
+ ->where('id', (int) $id)
|
|
|
|
|
+ ->update([
|
|
|
|
|
+ 'deleted_YN' => 'N',
|
|
|
|
|
+ 'updated_at' => date('Y-m-d H:i:s'),
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ return $this->respondSuccess(null, '관리자가 복구되었습니다.');
|
|
|
|
|
+ } catch (\Exception $e) {
|
|
|
|
|
+ log_message('error', 'AdminController restore error: ' . $e->getMessage());
|
|
|
|
|
+ return $this->respondError('복구 중 오류가 발생했습니다.', ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Hard delete admin (영구 삭제 — 이미 soft 삭제된 계정만)
|
|
|
|
|
+ * DELETE /api/admin/:id/hard
|
|
|
|
|
+ */
|
|
|
|
|
+ public function hardDelete($id = null)
|
|
|
|
|
+ {
|
|
|
|
|
+ $auth = $this->requireAuth();
|
|
|
|
|
+ if ($auth instanceof ResponseInterface) {
|
|
|
|
|
+ return $auth;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (empty($id)) {
|
|
|
|
|
+ return $this->respondError('관리자 ID가 필요합니다.', ResponseInterface::HTTP_BAD_REQUEST);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ $targetId = (int) $id;
|
|
|
|
|
+
|
|
|
|
|
+ // 본인 영구 삭제 방지
|
|
|
|
|
+ if ((int) $auth->admin_id === $targetId) {
|
|
|
|
|
+ return $this->respondError('본인 계정은 영구 삭제할 수 없습니다.', ResponseInterface::HTTP_FORBIDDEN);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $db = $this->getDB();
|
|
|
|
|
+ $existing = $db->table('admin_users')
|
|
|
|
|
+ ->where('id', $targetId)
|
|
|
|
|
+ ->where('deleted_YN', 'Y')
|
|
|
|
|
+ ->get()->getRow();
|
|
|
|
|
+ if (!$existing) {
|
|
|
|
|
+ return $this->respondError('영구 삭제 대상이 없습니다. (이미 삭제된 계정만 영구 삭제 가능)', ResponseInterface::HTTP_NOT_FOUND);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 권한 row 정리 (FK CASCADE 있어도 명시적으로)
|
|
|
|
|
+ $db->table('admin_permissions')->where('admin_id', $targetId)->delete();
|
|
|
|
|
+ $db->table('admin_tokens')->where('admin_id', $targetId)->delete();
|
|
|
|
|
+ $db->table('admin_users')->where('id', $targetId)->delete();
|
|
|
|
|
+
|
|
|
|
|
+ return $this->respondSuccess(null, '관리자가 영구 삭제되었습니다.');
|
|
|
|
|
+ } catch (\Exception $e) {
|
|
|
|
|
+ log_message('error', 'AdminController hardDelete error: ' . $e->getMessage());
|
|
|
|
|
+ return $this->respondError('영구 삭제 중 오류가 발생했습니다.', ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|