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')); $status = trim((string) $this->request->getGet('status')); $db = $this->getDB(); $builder = $db->table($this->table . ' o'); $builder->join('fishing_field f', 'f.id = o.field_id', 'left'); $builder->join('fishing_area a', 'a.id = o.area_id', 'left'); $builder->where('o.deleted_YN', 'N'); if ($search !== '') { $builder->groupStart() ->like('o.name', $search) ->orLike('a.name', $search) ->groupEnd(); } if ($status === 'Y' || $status === 'N') { $builder->where('o.status_YN', $status); } $total = $builder->countAllResults(false); // 계좌번호는 목록에서 제외 (민감정보) $items = $builder ->select('o.id, o.name, o.field_id, o.area_id, o.area_detail, o.partnership_YN, o.status_YN, o.created_at, f.name as field_name, a.name as area_name') ->orderBy('o.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', 'OnboardController index error: ' . $e->getMessage()); return $this->respondError('목록 조회 중 오류가 발생했습니다: ' . $e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * 선상 등록 * POST /api/onboard */ public function create() { $auth = $this->requireAuth(); if ($auth instanceof ResponseInterface) { return $auth; } try { $payload = $this->request->getJSON(true); if (!is_array($payload) || empty($payload)) { $payload = $this->request->getPost() ?? []; } $fieldId = (int) ($payload['field_id'] ?? 0); $areaId = (int) ($payload['area_id'] ?? 0); $name = trim((string) ($payload['name'] ?? '')); // 필수값 검증 if ($fieldId <= 0) { return $this->respondError('분야를 선택하세요.', ResponseInterface::HTTP_BAD_REQUEST); } if ($areaId <= 0) { return $this->respondError('지역을 선택하세요.', ResponseInterface::HTTP_BAD_REQUEST); } if ($name === '') { return $this->respondError('선상명을 입력하세요.', ResponseInterface::HTTP_BAD_REQUEST); } if (mb_strlen($name) > 100) { return $this->respondError('선상명은 100자 이내로 입력하세요.', ResponseInterface::HTTP_BAD_REQUEST); } $db = $this->getDB(); // 분야 / 지역 존재 확인 $fieldExists = $db->table('fishing_field') ->where('id', $fieldId)->where('deleted_YN', 'N')->countAllResults(); if ($fieldExists === 0) { return $this->respondError('존재하지 않는 분야입니다.', ResponseInterface::HTTP_BAD_REQUEST); } $areaExists = $db->table('fishing_area') ->where('id', $areaId)->where('deleted_YN', 'N')->countAllResults(); if ($areaExists === 0) { return $this->respondError('존재하지 않는 지역입니다.', ResponseInterface::HTTP_BAD_REQUEST); } // Y/N 정규화 $partnership = (($payload['partnership_YN'] ?? 'N') === 'Y') ? 'Y' : 'N'; $status = (($payload['status_YN'] ?? 'Y') === 'N') ? 'N' : 'Y'; $insertData = [ 'field_id' => $fieldId, 'area_id' => $areaId, 'name' => $name, 'area_detail' => trim((string) ($payload['area_detail'] ?? '')), 'tonnage' => trim((string) ($payload['tonnage'] ?? '')), 'capacity' => trim((string) ($payload['capacity'] ?? '')), 'zip_code' => trim((string) ($payload['zip_code'] ?? '')), 'address' => trim((string) ($payload['address'] ?? '')), 'address_detail' => trim((string) ($payload['address_detail'] ?? '')), 'address_refer' => trim((string) ($payload['address_refer'] ?? '')), 'lat' => trim((string) ($payload['lat'] ?? '')), 'lng' => trim((string) ($payload['lng'] ?? '')), 'partnership_YN' => $partnership, 'status_YN' => $status, 'created_at' => date('Y-m-d H:i:s'), ]; // 제휴인 경우에만 계좌 정보 저장 (비제휴면 빈 값) // 계좌번호는 양방향 암호화하여 저장 if ($partnership === 'Y') { $insertData['bank_code'] = trim((string) ($payload['bank_code'] ?? '')); $insertData['account_number'] = $this->encryptValue(trim((string) ($payload['account_number'] ?? ''))); $insertData['account_holder'] = trim((string) ($payload['account_holder'] ?? '')); } else { $insertData['bank_code'] = ''; $insertData['account_number'] = ''; $insertData['account_holder'] = ''; } 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(); // 응답 시 계좌번호 복호화 if ($row) { $row->account_number = $this->decryptValue($row->account_number); } return $this->respondSuccess($row, '선상이 등록되었습니다.', ResponseInterface::HTTP_CREATED); } catch (\Exception $e) { log_message('error', 'OnboardController create error: ' . $e->getMessage()); return $this->respondError('등록 중 오류가 발생했습니다: ' . $e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * 선상 상세 조회 * GET /api/onboard/: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 . ' o') ->select('o.*, f.name as field_name, a.name as area_name') ->join('fishing_field f', 'f.id = o.field_id', 'left') ->join('fishing_area a', 'a.id = o.area_id', 'left') ->where('o.id', (int) $id) ->where('o.deleted_YN', 'N') ->get() ->getRow(); if (!$row) { return $this->respondError('해당 선상을 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND); } // 계좌번호 복호화 $row->account_number = $this->decryptValue($row->account_number); return $this->respondSuccess($row); } catch (\Exception $e) { log_message('error', 'OnboardController show error: ' . $e->getMessage()); return $this->respondError('조회 중 오류가 발생했습니다: ' . $e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * 선상 삭제 (soft delete) * DELETE /api/onboard/: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', 'OnboardController delete error: ' . $e->getMessage()); return $this->respondError('삭제 중 오류가 발생했습니다: ' . $e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * 값 암호화 (빈 값은 그대로 빈 문자열) */ private function encryptValue(string $plain): string { if ($plain === '') { return ''; } $encrypter = \Config\Services::encrypter(); return base64_encode($encrypter->encrypt($plain)); } /** * 값 복호화 (실패/빈 값이면 빈 문자열) */ private function decryptValue(?string $cipher): string { if (empty($cipher)) { return ''; } try { $encrypter = \Config\Services::encrypter(); return $encrypter->decrypt(base64_decode($cipher)); } catch (\Exception $e) { log_message('error', 'Account decrypt error: ' . $e->getMessage()); return ''; } } }