FishingAreaController.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. <?php
  2. namespace App\Controllers\Api;
  3. use CodeIgniter\HTTP\ResponseInterface;
  4. class FishingAreaController extends BaseApiController
  5. {
  6. protected $format = 'json';
  7. protected $table = 'fishing_area';
  8. /**
  9. * 낚시지역 목록
  10. * GET /api/area/list
  11. */
  12. public function index()
  13. {
  14. $auth = $this->requireAuth();
  15. if ($auth instanceof ResponseInterface) {
  16. return $auth;
  17. }
  18. try {
  19. $page = (int) ($this->request->getGet('page') ?? 1);
  20. $perPage = (int) ($this->request->getGet('per_page') ?? 10);
  21. if ($page < 1) $page = 1;
  22. if ($perPage < 1) $perPage = 10;
  23. $offset = ($page - 1) * $perPage;
  24. $search = trim((string) $this->request->getGet('search'));
  25. $db = $this->getDB();
  26. $builder = $db->table($this->table);
  27. // soft delete 제외
  28. $builder->where('deleted_YN', 'N');
  29. if ($search !== '') {
  30. $builder->like('name', $search);
  31. }
  32. $total = $builder->countAllResults(false);
  33. $items = $builder
  34. ->select('id, name, created_at, updated_at')
  35. ->orderBy('id', 'DESC')
  36. ->limit($perPage, $offset)
  37. ->get()
  38. ->getResult();
  39. return $this->respondSuccess([
  40. 'items' => $items,
  41. 'total' => $total,
  42. 'page' => $page,
  43. 'per_page' => $perPage,
  44. 'total_pages' => (int) ceil($total / $perPage),
  45. ]);
  46. } catch (\Exception $e) {
  47. log_message('error', 'FishingAreaController index error: ' . $e->getMessage());
  48. return $this->respondError('목록 조회 중 오류가 발생했습니다: ' . $e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
  49. }
  50. }
  51. /**
  52. * 낚시지역 등록
  53. * POST /api/area
  54. */
  55. public function create()
  56. {
  57. $auth = $this->requireAuth();
  58. if ($auth instanceof ResponseInterface) {
  59. return $auth;
  60. }
  61. try {
  62. $payload = $this->request->getJSON(true);
  63. if (!is_array($payload) || empty($payload)) {
  64. $payload = $this->request->getPost() ?? [];
  65. }
  66. $name = trim((string) ($payload['name'] ?? ''));
  67. // 지역명 검증: 1~20자, 필수
  68. if ($name === '') {
  69. return $this->respondError('지역명을 입력하세요.', ResponseInterface::HTTP_BAD_REQUEST);
  70. }
  71. if (mb_strlen($name) > 20) {
  72. return $this->respondError('지역명은 20자 이내로 입력하세요.', ResponseInterface::HTTP_BAD_REQUEST);
  73. }
  74. $db = $this->getDB();
  75. // 중복 검사 (soft delete 제외)
  76. $exists = $db->table($this->table)
  77. ->where('name', $name)
  78. ->where('deleted_YN', 'N')
  79. ->countAllResults();
  80. if ($exists > 0) {
  81. return $this->respondError('이미 등록된 지역명입니다.', ResponseInterface::HTTP_CONFLICT);
  82. }
  83. $insertData = [
  84. 'name' => $name,
  85. 'created_at' => date('Y-m-d H:i:s'),
  86. ];
  87. if (!$db->table($this->table)->insert($insertData)) {
  88. return $this->respondError('등록에 실패했습니다.', ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
  89. }
  90. $newId = $db->insertID();
  91. $row = $db->table($this->table)->where('id', $newId)->get()->getRow();
  92. return $this->respondSuccess($row, '낚시지역이 등록되었습니다.', ResponseInterface::HTTP_CREATED);
  93. } catch (\Exception $e) {
  94. log_message('error', 'FishingAreaController create error: ' . $e->getMessage());
  95. return $this->respondError('등록 중 오류가 발생했습니다: ' . $e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
  96. }
  97. }
  98. /**
  99. * 낚시지역 상세 조회
  100. * GET /api/area/:id
  101. */
  102. public function show($id = null)
  103. {
  104. $auth = $this->requireAuth();
  105. if ($auth instanceof ResponseInterface) {
  106. return $auth;
  107. }
  108. if (empty($id)) {
  109. return $this->respondError('ID가 필요합니다.', ResponseInterface::HTTP_BAD_REQUEST);
  110. }
  111. try {
  112. $row = $this->getDB()->table($this->table)
  113. ->select('id, name, created_at, updated_at')
  114. ->where('id', (int) $id)
  115. ->where('deleted_YN', 'N')
  116. ->get()
  117. ->getRow();
  118. if (!$row) {
  119. return $this->respondError('해당 낚시지역을 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
  120. }
  121. return $this->respondSuccess($row);
  122. } catch (\Exception $e) {
  123. log_message('error', 'FishingAreaController show error: ' . $e->getMessage());
  124. return $this->respondError('조회 중 오류가 발생했습니다: ' . $e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
  125. }
  126. }
  127. /**
  128. * 낚시지역 수정
  129. * PUT /api/area/:id
  130. */
  131. public function update($id = null)
  132. {
  133. $auth = $this->requireAuth();
  134. if ($auth instanceof ResponseInterface) {
  135. return $auth;
  136. }
  137. if (empty($id)) {
  138. return $this->respondError('ID가 필요합니다.', ResponseInterface::HTTP_BAD_REQUEST);
  139. }
  140. try {
  141. $payload = $this->request->getJSON(true);
  142. if (!is_array($payload) || empty($payload)) {
  143. $payload = $this->request->getRawInput() ?? [];
  144. }
  145. $name = trim((string) ($payload['name'] ?? ''));
  146. // 지역명 검증
  147. if ($name === '') {
  148. return $this->respondError('지역명을 입력하세요.', ResponseInterface::HTTP_BAD_REQUEST);
  149. }
  150. if (mb_strlen($name) > 20) {
  151. return $this->respondError('지역명은 20자 이내로 입력하세요.', ResponseInterface::HTTP_BAD_REQUEST);
  152. }
  153. $db = $this->getDB();
  154. // 대상 행 존재 확인
  155. $exists = $db->table($this->table)
  156. ->where('id', (int) $id)
  157. ->where('deleted_YN', 'N')
  158. ->countAllResults();
  159. if ($exists === 0) {
  160. return $this->respondError('해당 낚시지역을 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
  161. }
  162. // 중복 검사 (자기 자신 제외)
  163. $dupe = $db->table($this->table)
  164. ->where('name', $name)
  165. ->where('id !=', (int) $id)
  166. ->where('deleted_YN', 'N')
  167. ->countAllResults();
  168. if ($dupe > 0) {
  169. return $this->respondError('이미 등록된 지역명입니다.', ResponseInterface::HTTP_CONFLICT);
  170. }
  171. $updateData = [
  172. 'name' => $name,
  173. 'updated_at' => date('Y-m-d H:i:s'),
  174. ];
  175. $db->table($this->table)->where('id', (int) $id)->update($updateData);
  176. $row = $db->table($this->table)->where('id', (int) $id)->get()->getRow();
  177. return $this->respondSuccess($row, '낚시지역이 수정되었습니다.');
  178. } catch (\Exception $e) {
  179. log_message('error', 'FishingAreaController update error: ' . $e->getMessage());
  180. return $this->respondError('수정 중 오류가 발생했습니다: ' . $e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
  181. }
  182. }
  183. /**
  184. * 낚시지역 삭제 (soft delete)
  185. * DELETE /api/area/:id
  186. */
  187. public function delete($id = null)
  188. {
  189. $auth = $this->requireAuth();
  190. if ($auth instanceof ResponseInterface) {
  191. return $auth;
  192. }
  193. if (empty($id)) {
  194. return $this->respondError('ID가 필요합니다.', ResponseInterface::HTTP_BAD_REQUEST);
  195. }
  196. try {
  197. $db = $this->getDB();
  198. $exists = $db->table($this->table)
  199. ->where('id', (int) $id)
  200. ->where('deleted_YN', 'N')
  201. ->countAllResults();
  202. if ($exists === 0) {
  203. return $this->respondError('해당 낚시지역을 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
  204. }
  205. // 해당 지역에 등록된 낚시어선/낚시터가 있으면 삭제 차단
  206. $onboardCount = $db->table('onboard')
  207. ->where('area_id', (int) $id)->where('deleted_YN', 'N')->countAllResults();
  208. $fishingCount = $db->table('fishing')
  209. ->where('area_id', (int) $id)->where('deleted_YN', 'N')->countAllResults();
  210. if (($onboardCount + $fishingCount) > 0) {
  211. return $this->respondError(
  212. "해당 지역에 등록된 낚시어선/낚시터가 있어 삭제할 수 없습니다. (낚시어선 {$onboardCount} / 낚시터 {$fishingCount})",
  213. ResponseInterface::HTTP_CONFLICT
  214. );
  215. }
  216. $db->table($this->table)
  217. ->where('id', (int) $id)
  218. ->update([
  219. 'deleted_YN' => 'Y',
  220. 'updated_at' => date('Y-m-d H:i:s'),
  221. ]);
  222. return $this->respondSuccess(null, '낚시지역이 삭제되었습니다.');
  223. } catch (\Exception $e) {
  224. log_message('error', 'FishingAreaController delete error: ' . $e->getMessage());
  225. return $this->respondError('삭제 중 오류가 발생했습니다: ' . $e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
  226. }
  227. }
  228. /**
  229. * 해당 지역에 속한 낚시어선 / 낚시터 통합 목록
  230. * GET /api/area/:id/places?limit=8
  231. */
  232. public function places($id = null)
  233. {
  234. $auth = $this->requireAuth();
  235. if ($auth instanceof ResponseInterface) {
  236. return $auth;
  237. }
  238. if (empty($id)) {
  239. return $this->respondError('지역 ID가 필요합니다.', ResponseInterface::HTTP_BAD_REQUEST);
  240. }
  241. try {
  242. $areaId = (int) $id;
  243. $limit = (int) ($this->request->getGet('limit') ?? 0); // 지정되면 단순 LIMIT 모드
  244. $page = (int) ($this->request->getGet('page') ?? 1);
  245. $perPage = (int) ($this->request->getGet('per_page') ?? 10);
  246. if ($page < 1) $page = 1;
  247. if ($perPage < 1) $perPage = 10;
  248. $offset = ($page - 1) * $perPage;
  249. $db = $this->getDB();
  250. // 지역명
  251. $area = $db->table($this->table)
  252. ->select('name')
  253. ->where('id', $areaId)->where('deleted_YN', 'N')
  254. ->get()->getRow();
  255. $areaName = $area ? $area->name : null;
  256. // 카운트
  257. $onboardCount = $db->table('onboard')
  258. ->where('area_id', $areaId)
  259. ->where('deleted_YN', 'N')
  260. ->countAllResults();
  261. $fishingCount = $db->table('fishing')
  262. ->where('area_id', $areaId)
  263. ->where('deleted_YN', 'N')
  264. ->countAllResults();
  265. $total = $onboardCount + $fishingCount;
  266. // UNION ALL — 통합 정렬
  267. if ($limit > 0) {
  268. // 단순 LIMIT 모드 (detail 페이지의 8개 미리보기)
  269. if ($limit > 1000) $limit = 1000;
  270. $tail = "LIMIT {$limit}";
  271. $totalPages = 1;
  272. } else {
  273. // 페이지네이션 모드 (전체보기 페이지)
  274. $tail = "LIMIT {$perPage} OFFSET {$offset}";
  275. $totalPages = (int) ceil($total / $perPage);
  276. }
  277. $sql = "(SELECT id, 'onboard' AS place_type, name, address, status_YN, created_at
  278. FROM onboard WHERE area_id = ? AND deleted_YN = 'N')
  279. UNION ALL
  280. (SELECT id, 'fishing' AS place_type, name, address, status_YN, created_at
  281. FROM fishing WHERE area_id = ? AND deleted_YN = 'N')
  282. ORDER BY created_at DESC
  283. {$tail}";
  284. $items = $db->query($sql, [$areaId, $areaId])->getResult();
  285. return $this->respondSuccess([
  286. 'items' => $items,
  287. 'onboard_count' => $onboardCount,
  288. 'fishing_count' => $fishingCount,
  289. 'area_name' => $areaName,
  290. 'total' => $total,
  291. 'page' => $page,
  292. 'per_page' => $perPage,
  293. 'total_pages' => $totalPages,
  294. ]);
  295. } catch (\Exception $e) {
  296. log_message('error', 'FishingAreaController places error: ' . $e->getMessage());
  297. return $this->respondError('조회 중 오류: ' . $e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
  298. }
  299. }
  300. }