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:
2025-12-28 00:53:26 +09:00
parent 472cf53289
commit ab77bab510
6 changed files with 846 additions and 18 deletions

View File

@@ -0,0 +1,66 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Helpers\ApiResponse;
use App\Http\Controllers\Controller;
use App\Services\Boards\BoardService;
/**
* 시스템 게시판 API 컨트롤러
*
* 시스템 게시판 (is_system=true, tenant_id=null):
* - 본사에서 생성/관리하는 게시판
* - 모든 테넌트에서 읽기 가능
* - URL: /api/v1/system-boards/{code}
*/
class SystemBoardController extends Controller
{
public function __construct(
protected BoardService $boardService
) {}
/**
* 시스템 게시판 목록 조회
*/
public function index()
{
return ApiResponse::handle(function () {
$filters = request()->only(['board_type', 'search']);
return $this->boardService->getSystemBoards($filters);
}, __('message.fetched'));
}
/**
* 시스템 게시판 상세 조회 (code 기반)
*/
public function show(string $code)
{
return ApiResponse::handle(function () use ($code) {
$board = $this->boardService->getSystemBoardByCode($code);
if (! $board) {
abort(404, __('error.board.not_found'));
}
return $board->load('customFields');
}, __('message.fetched'));
}
/**
* 시스템 게시판 필드 목록 조회
*/
public function fields(string $code)
{
return ApiResponse::handle(function () use ($code) {
$board = $this->boardService->getSystemBoardByCode($code);
if (! $board) {
abort(404, __('error.board.not_found'));
}
return $this->boardService->getBoardFields($board->id);
}, __('message.fetched'));
}
}

View File

@@ -0,0 +1,152 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Helpers\ApiResponse;
use App\Http\Controllers\Controller;
use App\Http\Requests\Boards\CommentStoreRequest;
use App\Http\Requests\Boards\PostStoreRequest;
use App\Http\Requests\Boards\PostUpdateRequest;
use App\Services\Boards\PostService;
/**
* 시스템 게시판 게시글 API 컨트롤러
*
* URL: /api/v1/system-boards/{code}/posts
* - {code}: 시스템 게시판 코드 (board_code)
*/
class SystemPostController extends Controller
{
public function __construct(
protected PostService $postService
) {}
/**
* 시스템 게시판 게시글 목록 조회
*/
public function index(string $code)
{
return ApiResponse::handle(function () use ($code) {
$filters = request()->only(['search', 'is_notice', 'status']);
$perPage = (int) request()->get('per_page', 15);
return $this->postService->getPostsBySystemBoardCode($code, $filters, $perPage);
}, __('message.fetched'));
}
/**
* 시스템 게시판 게시글 상세 조회
*/
public function show(string $code, int $id)
{
return ApiResponse::handle(function () use ($code, $id) {
$post = $this->postService->getPostBySystemBoardCodeAndId($code, $id);
if (! $post) {
abort(404, __('error.post.not_found'));
}
// 조회수 증가
$post->increment('views');
// 커스텀 필드 값 추가
$post->custom_field_values = $this->postService->getCustomFieldValues($id);
return $post;
}, __('message.fetched'));
}
/**
* 시스템 게시판 게시글 작성
*/
public function store(PostStoreRequest $request, string $code)
{
return ApiResponse::handle(function () use ($request, $code) {
return $this->postService->createPostBySystemBoardCode($code, $request->validated());
}, __('message.created'));
}
/**
* 시스템 게시판 게시글 수정
*/
public function update(PostUpdateRequest $request, string $code, int $id)
{
return ApiResponse::handle(function () use ($request, $code, $id) {
$post = $this->postService->updatePostBySystemBoardCode($code, $id, $request->validated());
if (! $post) {
abort(404, __('error.post.not_found'));
}
return $post;
}, __('message.updated'));
}
/**
* 시스템 게시판 게시글 삭제
*/
public function destroy(string $code, int $id)
{
return ApiResponse::handle(function () use ($code, $id) {
$deleted = $this->postService->deletePostBySystemBoardCode($code, $id);
if (! $deleted) {
abort(404, __('error.post.not_found'));
}
return ['deleted' => true];
}, __('message.deleted'));
}
/**
* 시스템 게시판 게시글 댓글 목록 조회
*/
public function comments(string $code, int $postId)
{
return ApiResponse::handle(function () use ($postId) {
return $this->postService->getComments($postId);
}, __('message.fetched'));
}
/**
* 시스템 게시판 게시글 댓글 작성
*/
public function storeComment(CommentStoreRequest $request, string $code, int $postId)
{
return ApiResponse::handle(function () use ($request, $postId) {
return $this->postService->createComment($postId, $request->validated());
}, __('message.created'));
}
/**
* 시스템 게시판 게시글 댓글 수정
*/
public function updateComment(CommentStoreRequest $request, string $code, int $postId, int $commentId)
{
return ApiResponse::handle(function () use ($request, $commentId) {
$comment = $this->postService->updateComment($commentId, $request->validated());
if (! $comment) {
abort(404, __('error.comment.not_found'));
}
return $comment;
}, __('message.updated'));
}
/**
* 시스템 게시판 게시글 댓글 삭제
*/
public function destroyComment(string $code, int $postId, int $commentId)
{
return ApiResponse::handle(function () use ($commentId) {
$deleted = $this->postService->deleteComment($commentId);
if (! $deleted) {
abort(404, __('error.comment.not_found'));
}
return ['deleted' => true];
}, __('message.deleted'));
}
}

View File

@@ -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용)
*/

View File

@@ -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']));
})

View File

@@ -0,0 +1,290 @@
<?php
namespace App\Swagger\v1;
/**
* @OA\Tag(name="SystemBoard", description="시스템 게시판 관리 (is_system=true, tenant_id=null)")
*/
class SystemBoardApi
{
/**
* @OA\Get(
* path="/api/v1/system-boards",
* tags={"SystemBoard"},
* summary="시스템 게시판 목록 조회",
* description="본사에서 생성한 시스템 게시판 목록을 조회합니다.",
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
*
* @OA\Parameter(name="board_type", in="query", description="게시판 유형 필터", @OA\Schema(type="string")),
* @OA\Parameter(name="search", in="query", description="게시판명 검색", @OA\Schema(type="string")),
*
* @OA\Response(response=200, description="조회 성공",
*
* @OA\JsonContent(allOf={
*
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(@OA\Property(property="data", type="array", @OA\Items(ref="#/components/schemas/Board")))
* })
* ),
*
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function index() {}
/**
* @OA\Get(
* path="/api/v1/system-boards/{code}",
* tags={"SystemBoard"},
* summary="시스템 게시판 상세 조회",
* description="시스템 게시판 코드로 상세 정보를 조회합니다.",
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
*
* @OA\Parameter(name="code", in="path", required=true, description="시스템 게시판 코드", @OA\Schema(type="string")),
*
* @OA\Response(response=200, description="조회 성공",
*
* @OA\JsonContent(allOf={
*
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(@OA\Property(property="data", ref="#/components/schemas/Board"))
* })
* ),
*
* @OA\Response(response=404, description="게시판 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function show() {}
/**
* @OA\Get(
* path="/api/v1/system-boards/{code}/fields",
* tags={"SystemBoard"},
* summary="시스템 게시판 커스텀 필드 목록",
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
*
* @OA\Parameter(name="code", in="path", required=true, description="시스템 게시판 코드", @OA\Schema(type="string")),
*
* @OA\Response(response=200, description="조회 성공",
*
* @OA\JsonContent(allOf={
*
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(@OA\Property(property="data", type="array", @OA\Items(ref="#/components/schemas/BoardField")))
* })
* ),
*
* @OA\Response(response=404, description="게시판 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function fields() {}
/**
* @OA\Get(
* path="/api/v1/system-boards/{code}/posts",
* tags={"SystemBoard"},
* summary="시스템 게시판 게시글 목록 조회",
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
*
* @OA\Parameter(name="code", in="path", required=true, description="시스템 게시판 코드", @OA\Schema(type="string")),
* @OA\Parameter(name="search", in="query", description="제목/내용 검색", @OA\Schema(type="string")),
* @OA\Parameter(name="is_notice", in="query", description="공지사항 필터", @OA\Schema(type="boolean")),
* @OA\Parameter(name="status", in="query", description="상태 필터", @OA\Schema(type="string")),
* @OA\Parameter(name="per_page", in="query", description="페이지당 개수", @OA\Schema(type="integer", default=15)),
*
* @OA\Response(response=200, description="조회 성공",
*
* @OA\JsonContent(allOf={
*
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(@OA\Property(property="data", ref="#/components/schemas/PostPagination"))
* })
* ),
*
* @OA\Response(response=404, description="게시판 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function postsIndex() {}
/**
* @OA\Get(
* path="/api/v1/system-boards/{code}/posts/{id}",
* tags={"SystemBoard"},
* summary="시스템 게시판 게시글 상세 조회",
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
*
* @OA\Parameter(name="code", in="path", required=true, description="시스템 게시판 코드", @OA\Schema(type="string")),
* @OA\Parameter(name="id", in="path", required=true, description="게시글 ID", @OA\Schema(type="integer")),
*
* @OA\Response(response=200, description="조회 성공",
*
* @OA\JsonContent(allOf={
*
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(@OA\Property(property="data", ref="#/components/schemas/Post"))
* })
* ),
*
* @OA\Response(response=404, description="게시글 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function postsShow() {}
/**
* @OA\Post(
* path="/api/v1/system-boards/{code}/posts",
* tags={"SystemBoard"},
* summary="시스템 게시판 게시글 작성",
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
*
* @OA\Parameter(name="code", in="path", required=true, description="시스템 게시판 코드", @OA\Schema(type="string")),
*
* @OA\RequestBody(required=true, @OA\JsonContent(ref="#/components/schemas/PostCreateRequest")),
*
* @OA\Response(response=200, description="작성 성공",
*
* @OA\JsonContent(allOf={
*
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(@OA\Property(property="data", ref="#/components/schemas/Post"))
* })
* ),
*
* @OA\Response(response=400, description="검증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=404, description="게시판 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function postsStore() {}
/**
* @OA\Put(
* path="/api/v1/system-boards/{code}/posts/{id}",
* tags={"SystemBoard"},
* summary="시스템 게시판 게시글 수정",
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
*
* @OA\Parameter(name="code", in="path", required=true, description="시스템 게시판 코드", @OA\Schema(type="string")),
* @OA\Parameter(name="id", in="path", required=true, description="게시글 ID", @OA\Schema(type="integer")),
*
* @OA\RequestBody(required=true, @OA\JsonContent(ref="#/components/schemas/PostUpdateRequest")),
*
* @OA\Response(response=200, description="수정 성공",
*
* @OA\JsonContent(allOf={
*
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(@OA\Property(property="data", ref="#/components/schemas/Post"))
* })
* ),
*
* @OA\Response(response=404, description="게시글 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function postsUpdate() {}
/**
* @OA\Delete(
* path="/api/v1/system-boards/{code}/posts/{id}",
* tags={"SystemBoard"},
* summary="시스템 게시판 게시글 삭제",
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
*
* @OA\Parameter(name="code", in="path", required=true, description="시스템 게시판 코드", @OA\Schema(type="string")),
* @OA\Parameter(name="id", in="path", required=true, description="게시글 ID", @OA\Schema(type="integer")),
*
* @OA\Response(response=200, description="삭제 성공", @OA\JsonContent(ref="#/components/schemas/ApiResponse")),
* @OA\Response(response=404, description="게시글 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function postsDestroy() {}
/**
* @OA\Get(
* path="/api/v1/system-boards/{code}/posts/{postId}/comments",
* tags={"SystemBoard"},
* summary="시스템 게시판 게시글 댓글 목록",
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
*
* @OA\Parameter(name="code", in="path", required=true, @OA\Schema(type="string")),
* @OA\Parameter(name="postId", in="path", required=true, @OA\Schema(type="integer")),
*
* @OA\Response(response=200, description="조회 성공",
*
* @OA\JsonContent(allOf={
*
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(@OA\Property(property="data", type="array", @OA\Items(ref="#/components/schemas/Comment")))
* })
* )
* )
*/
public function commentsIndex() {}
/**
* @OA\Post(
* path="/api/v1/system-boards/{code}/posts/{postId}/comments",
* tags={"SystemBoard"},
* summary="시스템 게시판 게시글 댓글 작성",
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
*
* @OA\Parameter(name="code", in="path", required=true, @OA\Schema(type="string")),
* @OA\Parameter(name="postId", in="path", required=true, @OA\Schema(type="integer")),
*
* @OA\RequestBody(required=true, @OA\JsonContent(ref="#/components/schemas/CommentCreateRequest")),
*
* @OA\Response(response=200, description="작성 성공",
*
* @OA\JsonContent(allOf={
*
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(@OA\Property(property="data", ref="#/components/schemas/Comment"))
* })
* )
* )
*/
public function commentsStore() {}
/**
* @OA\Put(
* path="/api/v1/system-boards/{code}/posts/{postId}/comments/{commentId}",
* tags={"SystemBoard"},
* summary="시스템 게시판 게시글 댓글 수정",
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
*
* @OA\Parameter(name="code", in="path", required=true, @OA\Schema(type="string")),
* @OA\Parameter(name="postId", in="path", required=true, @OA\Schema(type="integer")),
* @OA\Parameter(name="commentId", in="path", required=true, @OA\Schema(type="integer")),
*
* @OA\RequestBody(required=true, @OA\JsonContent(ref="#/components/schemas/CommentCreateRequest")),
*
* @OA\Response(response=200, description="수정 성공",
*
* @OA\JsonContent(allOf={
*
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(@OA\Property(property="data", ref="#/components/schemas/Comment"))
* })
* ),
*
* @OA\Response(response=404, description="댓글 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function commentsUpdate() {}
/**
* @OA\Delete(
* path="/api/v1/system-boards/{code}/posts/{postId}/comments/{commentId}",
* tags={"SystemBoard"},
* summary="시스템 게시판 게시글 댓글 삭제",
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
*
* @OA\Parameter(name="code", in="path", required=true, @OA\Schema(type="string")),
* @OA\Parameter(name="postId", in="path", required=true, @OA\Schema(type="integer")),
* @OA\Parameter(name="commentId", in="path", required=true, @OA\Schema(type="integer")),
*
* @OA\Response(response=200, description="삭제 성공", @OA\JsonContent(ref="#/components/schemas/ApiResponse")),
* @OA\Response(response=404, description="댓글 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function commentsDestroy() {}
}

View File

@@ -14,6 +14,8 @@
use App\Http\Controllers\Api\V1\BillController;
use App\Http\Controllers\Api\V1\BarobillSettingController;
use App\Http\Controllers\Api\V1\BoardController;
use App\Http\Controllers\Api\V1\SystemBoardController;
use App\Http\Controllers\Api\V1\SystemPostController;
use App\Http\Controllers\Api\V1\CardController;
use App\Http\Controllers\Api\V1\SalaryController;
use App\Http\Controllers\Api\V1\CategoryController;
@@ -1195,6 +1197,27 @@
Route::post('/relationships/reorder', [EntityRelationshipController::class, 'reorderRelationships'])->name('v1.item-master.relationships.reorder');
});
// 시스템 게시판 API (is_system=true, tenant_id=null)
Route::prefix('system-boards')->group(function () {
// 시스템 게시판 목록/상세
Route::get('/', [SystemBoardController::class, 'index'])->name('v1.system-boards.index');
Route::get('/{code}', [SystemBoardController::class, 'show'])->name('v1.system-boards.show');
Route::get('/{code}/fields', [SystemBoardController::class, 'fields'])->name('v1.system-boards.fields');
// 시스템 게시글 API
Route::get('/{code}/posts', [SystemPostController::class, 'index'])->name('v1.system-boards.posts.index');
Route::post('/{code}/posts', [SystemPostController::class, 'store'])->name('v1.system-boards.posts.store');
Route::get('/{code}/posts/{id}', [SystemPostController::class, 'show'])->name('v1.system-boards.posts.show');
Route::put('/{code}/posts/{id}', [SystemPostController::class, 'update'])->name('v1.system-boards.posts.update');
Route::delete('/{code}/posts/{id}', [SystemPostController::class, 'destroy'])->name('v1.system-boards.posts.destroy');
// 시스템 댓글 API
Route::get('/{code}/posts/{postId}/comments', [SystemPostController::class, 'comments'])->name('v1.system-boards.posts.comments.index');
Route::post('/{code}/posts/{postId}/comments', [SystemPostController::class, 'storeComment'])->name('v1.system-boards.posts.comments.store');
Route::put('/{code}/posts/{postId}/comments/{commentId}', [SystemPostController::class, 'updateComment'])->name('v1.system-boards.posts.comments.update');
Route::delete('/{code}/posts/{postId}/comments/{commentId}', [SystemPostController::class, 'destroyComment'])->name('v1.system-boards.posts.comments.destroy');
});
// 게시판 관리 API (테넌트용)
Route::prefix('boards')->group(function () {
// 게시판 목록/상세