feat: 시스템 게시판 API 추가 (/api/v1/system-boards)
- SystemBoardController: 시스템 게시판 목록/상세/필드 조회
- SystemPostController: 시스템 게시글 CRUD + 댓글 CRUD
- BoardService: getSystemBoardByCode(), getTenantBoardByCode() 추가
- PostService: 시스템/테넌트 게시판 전용 메서드 추가
- routes/api.php: /system-boards/* 엔드포인트 12개 추가
- SystemBoardApi.php: Swagger 문서
시스템 게시판 (is_system=true, tenant_id=null)과
테넌트 게시판 (is_system=false, tenant_id={current})의
board_code 중복 가능성으로 인해 별도 엔드포인트로 분리
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -74,6 +74,28 @@ public function getBoardByCode(string $code): ?Board
|
||||
->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* 시스템 게시판 코드로 조회
|
||||
*/
|
||||
public function getSystemBoardByCode(string $code): ?Board
|
||||
{
|
||||
return Board::systemOnly()
|
||||
->where('board_code', $code)
|
||||
->where('is_active', true)
|
||||
->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* 테넌트 게시판 코드로 조회
|
||||
*/
|
||||
public function getTenantBoardByCode(string $code): ?Board
|
||||
{
|
||||
return Board::tenantOnly($this->tenantId())
|
||||
->where('board_code', $code)
|
||||
->where('is_active', true)
|
||||
->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* 시스템 게시판 ID로 조회 (mng용)
|
||||
*/
|
||||
|
||||
@@ -18,11 +18,15 @@ class PostService extends Service
|
||||
|
||||
/**
|
||||
* 게시글 목록 조회 (페이징)
|
||||
*
|
||||
* @param bool $isSystemBoard 시스템 게시판 여부 (시스템 게시판은 tenant_id 필터 제외)
|
||||
*/
|
||||
public function getPostsByBoard(int $boardId, array $filters = [], int $perPage = 15): LengthAwarePaginator
|
||||
public function getPostsByBoard(int $boardId, array $filters = [], int $perPage = 15, bool $isSystemBoard = false): LengthAwarePaginator
|
||||
{
|
||||
return Post::where('board_id', $boardId)
|
||||
->where('tenant_id', $this->tenantId())
|
||||
// 시스템 게시판: tenant_id 필터 없음 (모든 게시글 조회)
|
||||
// 일반 게시판: 현재 테넌트 게시글만 조회
|
||||
->when(! $isSystemBoard, 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']}%")
|
||||
@@ -45,17 +49,58 @@ public function getPostsByBoardCode(string $boardCode, array $filters = [], int
|
||||
->where('board_code', $boardCode)
|
||||
->firstOrFail();
|
||||
|
||||
return $this->getPostsByBoard($board->id, $filters, $perPage);
|
||||
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): ?Post
|
||||
public function getPost(int $postId, ?bool $isSystemBoard = null): ?Post
|
||||
{
|
||||
return Post::with(['files', 'comments.replies'])
|
||||
->where('tenant_id', $this->tenantId())
|
||||
->find($postId);
|
||||
$query = Post::with(['files', 'comments.replies', 'board']);
|
||||
|
||||
if ($isSystemBoard === null) {
|
||||
// 게시글에서 board 정보로 시스템 게시판 여부 판단
|
||||
// 시스템 게시판이면 tenant_id 필터 없음
|
||||
$query->where(function ($q) {
|
||||
$q->where('tenant_id', $this->tenantId())
|
||||
->orWhereHas('board', fn ($b) => $b->where('is_system', true));
|
||||
});
|
||||
} elseif ($isSystemBoard) {
|
||||
// 시스템 게시판: tenant_id 필터 없음
|
||||
} else {
|
||||
$query->where('tenant_id', $this->tenantId());
|
||||
}
|
||||
|
||||
return $query->find($postId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,6 +130,51 @@ public function getPostByCodeAndId(string $boardCode, int $postId): ?Post
|
||||
return null;
|
||||
}
|
||||
|
||||
$query = Post::with(['files', 'comments.replies', 'board'])
|
||||
->where('board_id', $board->id);
|
||||
|
||||
// 시스템 게시판: tenant_id 필터 없음
|
||||
// 일반 게시판: 현재 테넌트 게시글만 조회
|
||||
if (! $board->is_system) {
|
||||
$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;
|
||||
}
|
||||
|
||||
return Post::with(['files', 'comments.replies', 'board'])
|
||||
->where('board_id', $board->id)
|
||||
->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())
|
||||
@@ -97,11 +187,13 @@ public function getPostByCodeAndId(string $boardCode, int $postId): ?Post
|
||||
|
||||
/**
|
||||
* 게시글 생성
|
||||
*
|
||||
* @param bool $isSystemBoard 시스템 게시판 여부 (시스템 게시판은 tenant_id = null)
|
||||
*/
|
||||
public function createPost(int $boardId, array $data): Post
|
||||
public function createPost(int $boardId, array $data, bool $isSystemBoard = false): Post
|
||||
{
|
||||
$data['board_id'] = $boardId;
|
||||
$data['tenant_id'] = $this->tenantId();
|
||||
$data['tenant_id'] = $isSystemBoard ? null : $this->tenantId();
|
||||
$data['user_id'] = $this->apiUserId();
|
||||
$data['ip_address'] = request()->ip();
|
||||
$data['status'] = $data['status'] ?? 'published';
|
||||
@@ -126,7 +218,33 @@ public function createPostByBoardCode(string $boardCode, array $data): Post
|
||||
->where('board_code', $boardCode)
|
||||
->firstOrFail();
|
||||
|
||||
return $this->createPost($board->id, $data);
|
||||
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);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
@@ -135,11 +253,27 @@ public function createPostByBoardCode(string $boardCode, array $data): Post
|
||||
|
||||
/**
|
||||
* 게시글 수정
|
||||
*
|
||||
* @param bool|null $isSystemBoard 시스템 게시판 여부 (null이면 자동 감지)
|
||||
*/
|
||||
public function updatePost(int $postId, array $data): ?Post
|
||||
public function updatePost(int $postId, array $data, ?bool $isSystemBoard = null): ?Post
|
||||
{
|
||||
$post = Post::where('tenant_id', $this->tenantId())
|
||||
->find($postId);
|
||||
$query = Post::with('board');
|
||||
|
||||
if ($isSystemBoard === null) {
|
||||
// 게시글에서 board 정보로 시스템 게시판 여부 판단
|
||||
// 시스템 게시판이면 tenant_id 필터 없음
|
||||
$query->where(function ($q) {
|
||||
$q->where('tenant_id', $this->tenantId())
|
||||
->orWhereHas('board', fn ($b) => $b->where('is_system', true));
|
||||
});
|
||||
} elseif ($isSystemBoard) {
|
||||
// 시스템 게시판: tenant_id 필터 없음
|
||||
} else {
|
||||
$query->where('tenant_id', $this->tenantId());
|
||||
}
|
||||
|
||||
$post = $query->find($postId);
|
||||
|
||||
if (! $post) {
|
||||
return null;
|
||||
@@ -168,6 +302,72 @@ public function updatePostByBoardCode(string $boardCode, int $postId, array $dat
|
||||
return null;
|
||||
}
|
||||
|
||||
$query = Post::where('board_id', $board->id);
|
||||
|
||||
// 시스템 게시판: tenant_id 필터 없음
|
||||
// 일반 게시판: 현재 테넌트 게시글만
|
||||
if (! $board->is_system) {
|
||||
$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, $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;
|
||||
}
|
||||
|
||||
$post = Post::where('board_id', $board->id)->find($postId);
|
||||
|
||||
if (! $post) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$post->update($data);
|
||||
|
||||
if (isset($data['custom_fields'])) {
|
||||
$this->saveCustomFields($post->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);
|
||||
@@ -191,11 +391,27 @@ public function updatePostByBoardCode(string $boardCode, int $postId, array $dat
|
||||
|
||||
/**
|
||||
* 게시글 삭제 (Soft Delete)
|
||||
*
|
||||
* @param bool|null $isSystemBoard 시스템 게시판 여부 (null이면 자동 감지)
|
||||
*/
|
||||
public function deletePost(int $postId): bool
|
||||
public function deletePost(int $postId, ?bool $isSystemBoard = null): bool
|
||||
{
|
||||
$post = Post::where('tenant_id', $this->tenantId())
|
||||
->find($postId);
|
||||
$query = Post::with('board');
|
||||
|
||||
if ($isSystemBoard === null) {
|
||||
// 게시글에서 board 정보로 시스템 게시판 여부 판단
|
||||
// 시스템 게시판이면 tenant_id 필터 없음
|
||||
$query->where(function ($q) {
|
||||
$q->where('tenant_id', $this->tenantId())
|
||||
->orWhereHas('board', fn ($b) => $b->where('is_system', true));
|
||||
});
|
||||
} elseif ($isSystemBoard) {
|
||||
// 시스템 게시판: tenant_id 필터 없음
|
||||
} else {
|
||||
$query->where('tenant_id', $this->tenantId());
|
||||
}
|
||||
|
||||
$post = $query->find($postId);
|
||||
|
||||
if (! $post) {
|
||||
return false;
|
||||
@@ -217,6 +433,60 @@ public function deletePostByBoardCode(string $boardCode, int $postId): bool
|
||||
return false;
|
||||
}
|
||||
|
||||
$query = Post::where('board_id', $board->id);
|
||||
|
||||
// 시스템 게시판: tenant_id 필터 없음
|
||||
// 일반 게시판: 현재 테넌트 게시글만
|
||||
if (! $board->is_system) {
|
||||
$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;
|
||||
}
|
||||
|
||||
$post = Post::where('board_id', $board->id)->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);
|
||||
@@ -331,12 +601,17 @@ public function getCustomFieldValues(int $postId): Collection
|
||||
|
||||
/**
|
||||
* 나의 게시글 목록 조회
|
||||
* 시스템 게시판(tenant_id = null) 및 테넌트 게시판의 게시글 모두 조회
|
||||
*/
|
||||
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'])
|
||||
->where(function ($q) {
|
||||
// 시스템 게시판 게시글 또는 현재 테넌트 게시글
|
||||
$q->whereNull('tenant_id')
|
||||
->orWhere('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']));
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user