requireAuth(); if ($auth instanceof ResponseInterface) { return $auth; } try { $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; $search = trim((string) $this->request->getGet('search')); $type = trim((string) $this->request->getGet('type')); $status = trim((string) $this->request->getGet('status')); $db = $this->getDB(); $builder = $db->table($this->table); $builder->where('deleted_YN', 'N'); if ($search !== '') { $builder->like('name', $search); } if (in_array($type, ['T', 'P', 'B'], true)) { $builder->where('type', $type); } if ($status === 'Y' || $status === 'N') { $builder->where('status_YN', $status); } $total = $builder->countAllResults(false); $items = $builder ->select('id, name, type, point, file_name, file_path, status_YN, created_at, updated_at') ->orderBy('id', 'DESC') ->limit($perPage, $offset) ->get() ->getResult(); return $this->respondSuccess([ 'items' => $items, 'total' => $total, 'page' => $page, 'per_page' => $perPage, 'total_pages' => (int) ceil($total / $perPage), ]); } catch (\Exception $e) { log_message('error', 'ItemController index error: ' . $e->getMessage()); return $this->respondError('목록 조회 중 오류가 발생했습니다: ' . $e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * 아이템 상세 조회 * GET /api/item/:id */ public function show($id = null) { $auth = $this->requireAuth(); if ($auth instanceof ResponseInterface) { return $auth; } if (empty($id)) { return $this->respondError('ID가 필요합니다.', ResponseInterface::HTTP_BAD_REQUEST); } try { $row = $this->getDB()->table($this->table) ->where('id', (int) $id) ->where('deleted_YN', 'N') ->get() ->getRow(); if (!$row) { return $this->respondError('해당 아이템을 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND); } return $this->respondSuccess($row); } catch (\Exception $e) { log_message('error', 'ItemController show error: ' . $e->getMessage()); return $this->respondError('조회 중 오류가 발생했습니다: ' . $e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * 아이템 수정 (텍스트 필드) * PUT /api/item/:id (JSON) */ public function update($id = null) { $auth = $this->requireAuth(); if ($auth instanceof ResponseInterface) { return $auth; } if (empty($id)) { return $this->respondError('ID가 필요합니다.', ResponseInterface::HTTP_BAD_REQUEST); } try { $payload = $this->request->getJSON(true); if (!is_array($payload) || empty($payload)) { $payload = $this->request->getRawInput() ?? []; } $name = trim((string) ($payload['name'] ?? '')); $type = trim((string) ($payload['type'] ?? '')); $point = $payload['point'] ?? null; $status = trim((string) ($payload['status_YN'] ?? 'Y')); if ($name === '') { return $this->respondError('아이템명을 입력하세요.', ResponseInterface::HTTP_BAD_REQUEST); } if (mb_strlen($name) > 50) { return $this->respondError('아이템명은 50자 이내로 입력하세요.', ResponseInterface::HTTP_BAD_REQUEST); } if (!in_array($type, ['T', 'P', 'B'], true)) { return $this->respondError('구분을 선택하세요.', ResponseInterface::HTTP_BAD_REQUEST); } $pointValue = null; if ($type === 'P') { if ($point === null || $point === '' || !is_numeric($point) || (int) $point < 0) { return $this->respondError('포인트를 입력하세요.', ResponseInterface::HTTP_BAD_REQUEST); } $pointValue = (int) $point; } elseif ($type === 'B') { if ($point !== null && $point !== '') { if (!is_numeric($point) || (int) $point < 0) { return $this->respondError('포인트는 0 이상의 숫자여야 합니다.', ResponseInterface::HTTP_BAD_REQUEST); } $pointValue = (int) $point; } } $status = ($status === 'N') ? 'N' : 'Y'; $db = $this->getDB(); $exists = $db->table($this->table) ->where('id', (int) $id)->where('deleted_YN', 'N')->countAllResults(); if ($exists === 0) { return $this->respondError('해당 아이템을 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND); } $db->table($this->table)->where('id', (int) $id)->update([ 'name' => $name, 'type' => $type, 'point' => $pointValue, 'status_YN' => $status, 'updated_at' => date('Y-m-d H:i:s'), ]); $row = $db->table($this->table)->where('id', (int) $id)->get()->getRow(); return $this->respondSuccess($row, '아이템이 수정되었습니다.'); } catch (\Exception $e) { log_message('error', 'ItemController update error: ' . $e->getMessage()); return $this->respondError('수정 중 오류가 발생했습니다: ' . $e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * 아이템 이미지 교체 * POST /api/item/:id/image (multipart: image) */ public function uploadImage($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(); $row = $db->table($this->table) ->where('id', (int) $id)->where('deleted_YN', 'N')->get()->getRow(); if (!$row) { return $this->respondError('해당 아이템을 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND); } $file = $this->request->getFile('image'); if (!$file || !$file->isValid()) { return $this->respondError('이미지가 전송되지 않았습니다.', ResponseInterface::HTTP_BAD_REQUEST); } $allowed = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']; $mime = $file->getMimeType(); if (!in_array($mime, $allowed, true)) { return $this->respondError('이미지 형식이 올바르지 않습니다.', ResponseInterface::HTTP_BAD_REQUEST); } $uploadPath = FCPATH . 'uploads/item/'; if (!is_dir($uploadPath)) { mkdir($uploadPath, 0755, true); } $fileName = $file->getClientName(); $stored = $file->getRandomName(); $file->move($uploadPath, $stored); // 기존 이미지 삭제 if (!empty($row->file_path)) { $oldFull = FCPATH . ltrim($row->file_path, '/'); if (is_file($oldFull)) @unlink($oldFull); } $db->table($this->table)->where('id', (int) $id)->update([ 'file_name' => $fileName, 'file_path' => '/uploads/item/' . $stored, 'updated_at' => date('Y-m-d H:i:s'), ]); $updated = $db->table($this->table)->where('id', (int) $id)->get()->getRow(); return $this->respondSuccess($updated, '이미지가 교체되었습니다.'); } catch (\Exception $e) { log_message('error', 'ItemController uploadImage error: ' . $e->getMessage()); return $this->respondError('이미지 업로드 중 오류가 발생했습니다: ' . $e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * 아이템 이미지 제거 * DELETE /api/item/:id/image */ public function deleteImage($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(); $row = $db->table($this->table) ->where('id', (int) $id)->where('deleted_YN', 'N')->get()->getRow(); if (!$row) { return $this->respondError('해당 아이템을 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND); } if (!empty($row->file_path)) { $full = FCPATH . ltrim($row->file_path, '/'); if (is_file($full)) @unlink($full); } $db->table($this->table)->where('id', (int) $id)->update([ 'file_name' => null, 'file_path' => null, 'updated_at' => date('Y-m-d H:i:s'), ]); return $this->respondSuccess(null, '이미지가 제거되었습니다.'); } catch (\Exception $e) { log_message('error', 'ItemController deleteImage error: ' . $e->getMessage()); return $this->respondError('이미지 제거 중 오류가 발생했습니다: ' . $e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * 아이템 삭제 (soft delete) * DELETE /api/item/:id */ public function delete($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(); $exists = $db->table($this->table) ->where('id', (int) $id)->where('deleted_YN', 'N')->countAllResults(); if ($exists === 0) { return $this->respondError('해당 아이템을 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND); } $db->table($this->table)->where('id', (int) $id)->update([ 'deleted_YN' => 'Y', 'updated_at' => date('Y-m-d H:i:s'), ]); return $this->respondSuccess(null, '아이템이 삭제되었습니다.'); } catch (\Exception $e) { log_message('error', 'ItemController delete error: ' . $e->getMessage()); return $this->respondError('삭제 중 오류가 발생했습니다: ' . $e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * 아이템 등록 * POST /api/item (multipart: name, type, point, status_YN, image) */ public function create() { $auth = $this->requireAuth(); if ($auth instanceof ResponseInterface) { return $auth; } try { $name = trim((string) $this->request->getPost('name')); $type = trim((string) $this->request->getPost('type')); // T(진출권) / P(포인트) / B(뱃지) $point = $this->request->getPost('point'); $status = trim((string) $this->request->getPost('status_YN')); $file = $this->request->getFile('image'); // 필수 검증 if ($name === '') { return $this->respondError('아이템명을 입력하세요.', ResponseInterface::HTTP_BAD_REQUEST); } if (mb_strlen($name) > 50) { return $this->respondError('아이템명은 50자 이내로 입력하세요.', ResponseInterface::HTTP_BAD_REQUEST); } if (!in_array($type, ['T', 'P', 'B'], true)) { return $this->respondError('구분을 선택하세요.', ResponseInterface::HTTP_BAD_REQUEST); } // type 별 포인트 처리 // T(진출권): 포인트 무시 (null), P(포인트): 필수 + 양수, B(뱃지): 선택 $pointValue = null; if ($type === 'P') { if ($point === null || $point === '' || !is_numeric($point) || (int) $point < 0) { return $this->respondError('포인트를 입력하세요.', ResponseInterface::HTTP_BAD_REQUEST); } $pointValue = (int) $point; } elseif ($type === 'B') { if ($point !== null && $point !== '') { if (!is_numeric($point) || (int) $point < 0) { return $this->respondError('포인트는 0 이상의 숫자여야 합니다.', ResponseInterface::HTTP_BAD_REQUEST); } $pointValue = (int) $point; } } $status = ($status === 'N') ? 'N' : 'Y'; // 이미지 업로드 (선택) $fileName = null; $filePath = null; if ($file && $file->isValid()) { $allowed = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']; $mime = $file->getMimeType(); if (!in_array($mime, $allowed, true)) { return $this->respondError('이미지 형식이 올바르지 않습니다. (JPG/PNG/GIF/WebP)', ResponseInterface::HTTP_BAD_REQUEST); } $uploadPath = FCPATH . 'uploads/item/'; if (!is_dir($uploadPath)) { mkdir($uploadPath, 0755, true); } $fileName = $file->getClientName(); $stored = $file->getRandomName(); $file->move($uploadPath, $stored); $filePath = '/uploads/item/' . $stored; } $insertData = [ 'name' => $name, 'type' => $type, 'point' => $pointValue, 'file_name' => $fileName, 'file_path' => $filePath, 'status_YN' => $status, 'created_at' => date('Y-m-d H:i:s'), ]; $db = $this->getDB(); if (!$db->table($this->table)->insert($insertData)) { return $this->respondError('등록에 실패했습니다.', ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } $newId = $db->insertID(); $row = $db->table($this->table)->where('id', $newId)->get()->getRow(); return $this->respondSuccess($row, '아이템이 등록되었습니다.', ResponseInterface::HTTP_CREATED); } catch (\Exception $e) { log_message('error', 'ItemController create error: ' . $e->getMessage()); return $this->respondError('등록 중 오류가 발생했습니다: ' . $e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } }