where(function ($q) { $q->where('tenant_id', self::HQ_TENANT_ID) ->orWhere('tenant_id', $this->tenantId()); }); } // ========================================================================= // 게시글 조회 메서드 // ========================================================================= /** * 게시글 목록 조회 (페이징) * * @param bool $isSystemBoard 시스템 게시판 여부 * - 시스템 게시판: 본사(tenant_id=1) 글 + 현재 테넌트 글 조회 * - 일반 게시판: 현재 테넌트 글만 조회 */ public function getPostsByBoard(int $boardId, array $filters = [], int $perPage = 15, bool $isSystemBoard = false): LengthAwarePaginator { return Post::where('board_id', $boardId) ->when($isSystemBoard, function ($q) { // 시스템 게시판: 본사(HQ) 글 + 현재 테넌트 글 $q->where(function ($query) { $query->where('tenant_id', self::HQ_TENANT_ID) ->orWhere('tenant_id', $this->tenantId()); }); }, fn ($q) => $q->where('tenant_id', $this->tenantId())) ->when(isset($filters['search']), function ($q) use ($filters) { $q->where(function ($query) use ($filters) { $query->where('title', 'like', "%{$filters['search']}%") ->orWhere('content', 'like', "%{$filters['search']}%"); }); }) ->when(isset($filters['is_notice']), fn ($q) => $q->where('is_notice', $filters['is_notice'])) ->when(isset($filters['status']), fn ($q) => $q->where('status', $filters['status'])) ->orderByDesc('is_notice') ->orderByDesc('created_at') ->paginate($perPage); } /** * 게시글 목록 조회 (게시판 코드 기반) */ public function getPostsByBoardCode(string $boardCode, array $filters = [], int $perPage = 15): LengthAwarePaginator { $board = Board::accessible($this->tenantId()) ->where('board_code', $boardCode) ->firstOrFail(); return $this->getPostsByBoard($board->id, $filters, $perPage, $board->is_system); } /** * 시스템 게시판 게시글 목록 조회 (게시판 코드 기반) */ public function getPostsBySystemBoardCode(string $boardCode, array $filters = [], int $perPage = 15): LengthAwarePaginator { $board = Board::systemOnly() ->where('board_code', $boardCode) ->where('is_active', true) ->firstOrFail(); return $this->getPostsByBoard($board->id, $filters, $perPage, true); } /** * 테넌트 게시판 게시글 목록 조회 (게시판 코드 기반) */ public function getPostsByTenantBoardCode(string $boardCode, array $filters = [], int $perPage = 15): LengthAwarePaginator { $board = Board::tenantOnly($this->tenantId()) ->where('board_code', $boardCode) ->where('is_active', true) ->firstOrFail(); return $this->getPostsByBoard($board->id, $filters, $perPage, false); } /** * 게시글 단건 조회 * * @param bool|null $isSystemBoard 시스템 게시판 여부 (null이면 자동 감지) */ public function getPost(int $postId, ?bool $isSystemBoard = null): ?Post { $query = Post::with(['files', 'comments.replies', 'board']); if ($isSystemBoard === null) { // 게시글에서 board 정보로 시스템 게시판 여부 판단 $query->where(function ($q) { // 현재 테넌트 글 $q->where('tenant_id', $this->tenantId()) // 또는 시스템 게시판의 본사 글 ->orWhere(function ($sub) { $sub->where('tenant_id', self::HQ_TENANT_ID) ->whereHas('board', fn ($b) => $b->where('is_system', true)); }); }); } elseif ($isSystemBoard) { // 시스템 게시판: 본사 글 + 현재 테넌트 글 $this->applySystemBoardScope($query); } else { $query->where('tenant_id', $this->tenantId()); } return $query->find($postId); } /** * 게시글 상세 조회 (조회수 증가) */ public function getPostWithViewIncrement(int $postId): ?Post { $post = $this->getPost($postId); if ($post) { $post->increment('views'); } return $post; } /** * 게시판 코드와 게시글 ID로 조회 */ public function getPostByCodeAndId(string $boardCode, int $postId): ?Post { $board = Board::accessible($this->tenantId()) ->where('board_code', $boardCode) ->first(); if (! $board) { return null; } $query = Post::with(['files', 'comments.replies', 'board']) ->where('board_id', $board->id); // 시스템 게시판: 본사 글 + 현재 테넌트 글 // 일반 게시판: 현재 테넌트 게시글만 조회 if ($board->is_system) { $this->applySystemBoardScope($query); } else { $query->where('tenant_id', $this->tenantId()); } return $query->find($postId); } /** * 시스템 게시판 코드와 게시글 ID로 조회 */ public function getPostBySystemBoardCodeAndId(string $boardCode, int $postId): ?Post { $board = Board::systemOnly() ->where('board_code', $boardCode) ->where('is_active', true) ->first(); if (! $board) { return null; } $query = Post::with(['files', 'comments.replies', 'board']) ->where('board_id', $board->id); // 시스템 게시판: 본사 글 + 현재 테넌트 글 $this->applySystemBoardScope($query); return $query->find($postId); } /** * 테넌트 게시판 코드와 게시글 ID로 조회 */ public function getPostByTenantBoardCodeAndId(string $boardCode, int $postId): ?Post { $board = Board::tenantOnly($this->tenantId()) ->where('board_code', $boardCode) ->where('is_active', true) ->first(); if (! $board) { return null; } return Post::with(['files', 'comments.replies', 'board']) ->where('board_id', $board->id) ->where('tenant_id', $this->tenantId()) ->find($postId); } // ========================================================================= // 게시글 생성 메서드 // ========================================================================= /** * 게시글 생성 * * @param bool $isSystemBoard 시스템 게시판 여부 (시스템 게시판은 tenant_id = null) */ public function createPost(int $boardId, array $data, bool $isSystemBoard = false): Post { $data['board_id'] = $boardId; $data['tenant_id'] = $this->tenantId(); // 시스템 게시판도 tenant_id 설정 (본사=1의 글만 전체 공개) $data['user_id'] = $this->apiUserId(); $data['ip_address'] = request()->ip(); $data['status'] = $data['status'] ?? 'published'; $data['views'] = 0; $post = Post::create($data); // 커스텀 필드 저장 if (isset($data['custom_fields'])) { $this->saveCustomFields($post->id, $boardId, $data['custom_fields']); } return $post->fresh(['board', 'files']); } /** * 게시글 생성 (게시판 코드 기반) */ public function createPostByBoardCode(string $boardCode, array $data): Post { $board = Board::accessible($this->tenantId()) ->where('board_code', $boardCode) ->firstOrFail(); return $this->createPost($board->id, $data, $board->is_system); } /** * 시스템 게시판 게시글 생성 (게시판 코드 기반) */ public function createPostBySystemBoardCode(string $boardCode, array $data): Post { $board = Board::systemOnly() ->where('board_code', $boardCode) ->where('is_active', true) ->firstOrFail(); return $this->createPost($board->id, $data, true); } /** * 테넌트 게시판 게시글 생성 (게시판 코드 기반) */ public function createPostByTenantBoardCode(string $boardCode, array $data): Post { $board = Board::tenantOnly($this->tenantId()) ->where('board_code', $boardCode) ->where('is_active', true) ->firstOrFail(); return $this->createPost($board->id, $data, false); } // ========================================================================= // 게시글 수정 메서드 // ========================================================================= /** * 게시글 수정 * * @param bool|null $isSystemBoard 시스템 게시판 여부 (null이면 자동 감지) */ public function updatePost(int $postId, array $data, ?bool $isSystemBoard = null): ?Post { $query = Post::with('board'); if ($isSystemBoard === null) { // 게시글에서 board 정보로 시스템 게시판 여부 판단 $query->where(function ($q) { // 현재 테넌트 글 $q->where('tenant_id', $this->tenantId()) // 또는 시스템 게시판의 본사 글 ->orWhere(function ($sub) { $sub->where('tenant_id', self::HQ_TENANT_ID) ->whereHas('board', fn ($b) => $b->where('is_system', true)); }); }); } elseif ($isSystemBoard) { // 시스템 게시판: 본사 글 + 현재 테넌트 글 $this->applySystemBoardScope($query); } else { $query->where('tenant_id', $this->tenantId()); } $post = $query->find($postId); if (! $post) { return null; } $post->update($data); // 커스텀 필드 업데이트 if (isset($data['custom_fields'])) { $this->saveCustomFields($post->id, $post->board_id, $data['custom_fields']); } return $post->fresh(['board', 'files']); } /** * 게시글 수정 (게시판 코드 기반) */ public function updatePostByBoardCode(string $boardCode, int $postId, array $data): ?Post { $board = Board::accessible($this->tenantId()) ->where('board_code', $boardCode) ->first(); if (! $board) { return null; } $query = Post::where('board_id', $board->id); // 시스템 게시판: 본사 글 + 현재 테넌트 글 // 일반 게시판: 현재 테넌트 게시글만 if ($board->is_system) { $this->applySystemBoardScope($query); } else { $query->where('tenant_id', $this->tenantId()); } $post = $query->find($postId); if (! $post) { return null; } $post->update($data); if (isset($data['custom_fields'])) { $this->saveCustomFields($post->id, $board->id, $data['custom_fields']); } return $post->fresh(['board', 'files']); } /** * 시스템 게시판 게시글 수정 (게시판 코드 기반) */ public function updatePostBySystemBoardCode(string $boardCode, int $postId, array $data): ?Post { $board = Board::systemOnly() ->where('board_code', $boardCode) ->where('is_active', true) ->first(); if (! $board) { return null; } $query = Post::where('board_id', $board->id); // 시스템 게시판: 본사 글 + 현재 테넌트 글 $this->applySystemBoardScope($query); $post = $query->find($postId); if (! $post) { return null; } $post->update($data); if (isset($data['custom_fields'])) { $this->saveCustomFields($post->id, $board->id, $data['custom_fields']); } return $post->fresh(['board', 'files']); } /** * 테넌트 게시판 게시글 수정 (게시판 코드 기반) */ public function updatePostByTenantBoardCode(string $boardCode, int $postId, array $data): ?Post { $board = Board::tenantOnly($this->tenantId()) ->where('board_code', $boardCode) ->where('is_active', true) ->first(); if (! $board) { return null; } $post = Post::where('board_id', $board->id) ->where('tenant_id', $this->tenantId()) ->find($postId); if (! $post) { return null; } $post->update($data); if (isset($data['custom_fields'])) { $this->saveCustomFields($post->id, $board->id, $data['custom_fields']); } return $post->fresh(['board', 'files']); } // ========================================================================= // 게시글 삭제 메서드 // ========================================================================= /** * 게시글 삭제 (Soft Delete) * * @param bool|null $isSystemBoard 시스템 게시판 여부 (null이면 자동 감지) */ public function deletePost(int $postId, ?bool $isSystemBoard = null): bool { $query = Post::with('board'); if ($isSystemBoard === null) { // 게시글에서 board 정보로 시스템 게시판 여부 판단 $query->where(function ($q) { // 현재 테넌트 글 $q->where('tenant_id', $this->tenantId()) // 또는 시스템 게시판의 본사 글 ->orWhere(function ($sub) { $sub->where('tenant_id', self::HQ_TENANT_ID) ->whereHas('board', fn ($b) => $b->where('is_system', true)); }); }); } elseif ($isSystemBoard) { // 시스템 게시판: 본사 글 + 현재 테넌트 글 $this->applySystemBoardScope($query); } else { $query->where('tenant_id', $this->tenantId()); } $post = $query->find($postId); if (! $post) { return false; } return $post->delete(); } /** * 게시글 삭제 (게시판 코드 기반) */ public function deletePostByBoardCode(string $boardCode, int $postId): bool { $board = Board::accessible($this->tenantId()) ->where('board_code', $boardCode) ->first(); if (! $board) { return false; } $query = Post::where('board_id', $board->id); // 시스템 게시판: 본사 글 + 현재 테넌트 글 // 일반 게시판: 현재 테넌트 게시글만 if ($board->is_system) { $this->applySystemBoardScope($query); } else { $query->where('tenant_id', $this->tenantId()); } $post = $query->find($postId); if (! $post) { return false; } return $post->delete(); } /** * 시스템 게시판 게시글 삭제 (게시판 코드 기반) */ public function deletePostBySystemBoardCode(string $boardCode, int $postId): bool { $board = Board::systemOnly() ->where('board_code', $boardCode) ->where('is_active', true) ->first(); if (! $board) { return false; } $query = Post::where('board_id', $board->id); // 시스템 게시판: 본사 글 + 현재 테넌트 글 $this->applySystemBoardScope($query); $post = $query->find($postId); if (! $post) { return false; } return $post->delete(); } /** * 테넌트 게시판 게시글 삭제 (게시판 코드 기반) */ public function deletePostByTenantBoardCode(string $boardCode, int $postId): bool { $board = Board::tenantOnly($this->tenantId()) ->where('board_code', $boardCode) ->where('is_active', true) ->first(); if (! $board) { return false; } $post = Post::where('board_id', $board->id) ->where('tenant_id', $this->tenantId()) ->find($postId); if (! $post) { return false; } return $post->delete(); } // ========================================================================= // 댓글 메서드 // ========================================================================= /** * 댓글 목록 조회 */ public function getComments(int $postId): Collection { return BoardComment::where('post_id', $postId) ->whereNull('parent_id') ->where('status', 'active') ->with(['user', 'replies.user']) ->orderBy('created_at') ->get(); } /** * 댓글 작성 */ public function createComment(int $postId, array $data): BoardComment { $data['post_id'] = $postId; $data['tenant_id'] = $this->tenantId(); $data['user_id'] = $this->apiUserId(); $data['ip_address'] = request()->ip(); $data['status'] = 'active'; $comment = BoardComment::create($data); return $comment->load('user'); } /** * 댓글 수정 */ public function updateComment(int $commentId, array $data): ?BoardComment { $comment = BoardComment::where('user_id', $this->apiUserId()) ->find($commentId); if (! $comment) { return null; } $comment->update($data); return $comment->fresh('user'); } /** * 댓글 삭제 */ public function deleteComment(int $commentId): bool { $comment = BoardComment::where('user_id', $this->apiUserId()) ->find($commentId); if (! $comment) { return false; } $comment->status = 'deleted'; $comment->save(); return true; } // ========================================================================= // 커스텀 필드 메서드 // ========================================================================= /** * 커스텀 필드 값 저장 * * @param int $postId 게시글 ID * @param int $boardId 게시판 ID (field_key → field_id 변환용) * @param array $customFields [field_key => value] 또는 [field_id => value] */ protected function saveCustomFields(int $postId, int $boardId, array $customFields): void { // field_key → field_id 매핑 조회 $fieldMap = \App\Models\Boards\BoardSetting::where('board_id', $boardId) ->pluck('id', 'field_key') ->toArray(); foreach ($customFields as $fieldKey => $value) { // field_key가 문자열이면 field_id로 변환 $fieldId = is_numeric($fieldKey) ? (int) $fieldKey : ($fieldMap[$fieldKey] ?? null); if ($fieldId === null) { continue; // 존재하지 않는 필드는 무시 } PostCustomFieldValue::updateOrCreate( [ 'post_id' => $postId, 'field_id' => $fieldId, ], [ 'value' => is_array($value) ? json_encode($value) : $value, ] ); } } /** * 커스텀 필드 값 조회 */ public function getCustomFieldValues(int $postId): Collection { return PostCustomFieldValue::where('post_id', $postId) ->with('field') ->get(); } // ========================================================================= // 나의 게시글 메서드 // ========================================================================= /** * 나의 게시글 목록 조회 * 현재 테넌트에서 내가 작성한 게시글 조회 */ public function getMyPosts(array $filters = [], int $perPage = 15): LengthAwarePaginator { return Post::where('user_id', $this->apiUserId()) ->where('tenant_id', $this->tenantId()) ->with(['board:id,board_code,name,is_system']) ->when(isset($filters['board_code']), function ($q) use ($filters) { $q->whereHas('board', fn ($query) => $query->where('board_code', $filters['board_code'])); }) ->when(isset($filters['search']), function ($q) use ($filters) { $q->where(function ($query) use ($filters) { $query->where('title', 'like', "%{$filters['search']}%") ->orWhere('content', 'like', "%{$filters['search']}%"); }); }) ->when(isset($filters['status']), fn ($q) => $q->where('status', $filters['status'])) ->orderByDesc('created_at') ->paginate($perPage); } }