Files
sam-manage/app/Http/Controllers/Api/Admin/BoardController.php
kent a30410643b fix(board): 게시판 URL tenant_id 처리 및 게시글 수 표시 추가
- Route Model Binding → 수동 조회로 변경 (board_code + tenant_id)
- PostController: resolveBoard() 헬퍼 추가
  - t 파라미터 → 시스템 게시판 → 로그인 회원 tenant 순서
- 사이드바 메뉴 리다이렉트: tenant_id ?? 1 fallback 추가
  - SidebarMenuService와 동일한 로직으로 일관성 확보
- 게시판 목록 테이블에 게시글 수 컬럼 추가
- 모든 posts View에 tenant_id 파라미터 추가

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 01:30:50 +09:00

427 lines
13 KiB
PHP

<?php
namespace App\Http\Controllers\Api\Admin;
use App\Http\Controllers\Controller;
use App\Services\BoardService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class BoardController extends Controller
{
public function __construct(
private readonly BoardService $boardService
) {}
/**
* 게시판 목록 (HTMX용) - 시스템 + 테넌트 게시판 모두 조회
*/
public function index(Request $request): View|JsonResponse
{
\Log::info('Board request all:', $request->all());
\Log::info('Board request query:', $request->query());
$filters = $request->only(['search', 'board_type', 'is_active', 'trashed', 'sort_by', 'sort_direction']);
\Log::info('Board filters:', $filters);
$boards = $this->boardService->getAllBoards($filters, 15);
// HTMX 요청이면 HTML 파셜 반환
if ($request->header('HX-Request')) {
return view('boards.partials.table', compact('boards'));
}
// 일반 요청이면 JSON
return response()->json([
'success' => true,
'data' => $boards,
]);
}
/**
* 게시판 통계
*/
public function stats(): JsonResponse
{
$stats = $this->boardService->getBoardStats();
return response()->json([
'success' => true,
'data' => $stats,
]);
}
/**
* 게시판 상세 조회
*/
public function show(int $id): JsonResponse
{
$board = $this->boardService->getBoardById($id, true);
if (! $board) {
return response()->json([
'success' => false,
'message' => '게시판을 찾을 수 없습니다.',
], 404);
}
return response()->json([
'success' => true,
'data' => $board,
]);
}
/**
* 게시판 생성
*/
public function store(Request $request): JsonResponse
{
$validated = $request->validate([
'board_code' => 'required|string|max:50',
'name' => 'required|string|max:100',
'board_type' => 'nullable|string|max:50',
'description' => 'nullable|string|max:500',
'editor_type' => 'nullable|string|in:wysiwyg,markdown,text',
'allow_files' => 'nullable|boolean',
'max_file_count' => 'nullable|integer|min:0|max:100',
'max_file_size' => 'nullable|integer|min:0',
'extra_settings' => 'nullable|array',
'is_active' => 'nullable|boolean',
]);
// 코드 중복 체크
if ($this->boardService->isCodeExists($validated['board_code'])) {
return response()->json([
'success' => false,
'message' => '이미 사용 중인 게시판 코드입니다.',
], 422);
}
$board = $this->boardService->createBoard($validated);
return response()->json([
'success' => true,
'message' => '게시판이 생성되었습니다.',
'data' => $board,
]);
}
/**
* 게시판 수정
*/
public function update(Request $request, int $id): JsonResponse
{
$validated = $request->validate([
'board_code' => 'required|string|max:50',
'name' => 'required|string|max:100',
'board_type' => 'nullable|string|max:50',
'description' => 'nullable|string|max:500',
'editor_type' => 'nullable|string|in:wysiwyg,markdown,text',
'allow_files' => 'nullable|boolean',
'max_file_count' => 'nullable|integer|min:0|max:100',
'max_file_size' => 'nullable|integer|min:0',
'extra_settings' => 'nullable|array',
'is_active' => 'nullable|boolean',
]);
// 코드 중복 체크 (자신 제외)
if ($this->boardService->isCodeExists($validated['board_code'], $id)) {
return response()->json([
'success' => false,
'message' => '이미 사용 중인 게시판 코드입니다.',
], 422);
}
$this->boardService->updateBoard($id, $validated);
return response()->json([
'success' => true,
'message' => '게시판이 수정되었습니다.',
]);
}
/**
* 게시판 삭제 (Soft Delete) - 연결된 메뉴도 함께 삭제
*/
public function destroy(int $id): JsonResponse
{
$this->boardService->deleteAnyBoard($id);
return response()->json([
'success' => true,
'message' => '게시판이 삭제되었습니다.',
]);
}
/**
* 게시판 복원 - 연결된 메뉴도 함께 복원
*/
public function restore(int $id): JsonResponse
{
$this->boardService->restoreAnyBoard($id);
return response()->json([
'success' => true,
'message' => '게시판이 복원되었습니다.',
]);
}
/**
* 게시판 영구 삭제 - 연결된 메뉴도 함께 영구 삭제
*/
public function forceDestroy(int $id): JsonResponse
{
$this->boardService->forceDeleteAnyBoard($id);
return response()->json([
'success' => true,
'message' => '게시판이 영구 삭제되었습니다.',
]);
}
/**
* 게시판 활성/비활성 토글
*/
public function toggleActive(int $id): JsonResponse
{
$board = $this->boardService->toggleActive($id);
return response()->json([
'success' => true,
'message' => $board->is_active ? '게시판이 활성화되었습니다.' : '게시판이 비활성화되었습니다.',
'data' => ['is_active' => $board->is_active],
]);
}
// =========================================================================
// 필드 관리 API
// =========================================================================
/**
* 게시판 필드 목록
*/
public function fields(int $id): JsonResponse
{
$fields = $this->boardService->getBoardFields($id);
return response()->json([
'success' => true,
'data' => $fields,
]);
}
/**
* 게시판 필드 추가
*/
public function storeField(Request $request, int $id): JsonResponse
{
$validated = $request->validate([
'name' => 'required|string|max:100',
'field_key' => 'required|string|max:50',
'field_type' => 'required|string|in:text,number,select,date,textarea,checkbox,radio,file',
'field_meta' => 'nullable|array',
'is_required' => 'nullable|boolean',
'sort_order' => 'nullable|integer',
]);
$field = $this->boardService->addBoardField($id, $validated);
return response()->json([
'success' => true,
'message' => '필드가 추가되었습니다.',
'data' => $field,
]);
}
/**
* 게시판 필드 수정
*/
public function updateField(Request $request, int $id, int $fieldId): JsonResponse
{
$validated = $request->validate([
'name' => 'required|string|max:100',
'field_key' => 'required|string|max:50',
'field_type' => 'required|string|in:text,number,select,date,textarea,checkbox,radio,file',
'field_meta' => 'nullable|array',
'is_required' => 'nullable|boolean',
'sort_order' => 'nullable|integer',
]);
$this->boardService->updateBoardField($fieldId, $validated);
return response()->json([
'success' => true,
'message' => '필드가 수정되었습니다.',
]);
}
/**
* 게시판 필드 삭제
*/
public function destroyField(int $id, int $fieldId): JsonResponse
{
$this->boardService->deleteBoardField($fieldId);
return response()->json([
'success' => true,
'message' => '필드가 삭제되었습니다.',
]);
}
/**
* 게시판 필드 순서 변경
*/
public function reorderFields(Request $request, int $id): JsonResponse
{
$validated = $request->validate([
'field_ids' => 'required|array',
'field_ids.*' => 'integer',
]);
$this->boardService->reorderBoardFields($id, $validated['field_ids']);
return response()->json([
'success' => true,
'message' => '필드 순서가 변경되었습니다.',
]);
}
// =========================================================================
// 템플릿 API
// =========================================================================
/**
* 템플릿 목록 조회
*/
public function templates(Request $request): JsonResponse
{
$type = $request->get('type', 'all'); // system, tenant, all
$templates = $this->boardService->getTemplates($type);
$baseFields = $this->boardService->getBaseFields();
return response()->json([
'success' => true,
'data' => [
'templates' => $templates,
'base_fields' => $baseFields,
],
]);
}
/**
* 특정 템플릿 상세 조회
*/
public function templateDetail(string $type, string $key): JsonResponse
{
$template = $this->boardService->getTemplate($type, $key);
if (! $template) {
return response()->json([
'success' => false,
'message' => '템플릿을 찾을 수 없습니다.',
], 404);
}
return response()->json([
'success' => true,
'data' => $template,
]);
}
/**
* 템플릿 기반 게시판 생성
*/
public function storeFromTemplate(Request $request): JsonResponse
{
$validated = $request->validate([
'board_code' => 'required|string|max:50',
'name' => 'required|string|max:100',
'template_type' => 'nullable|string|in:system,tenant',
'template_key' => 'nullable|string|max:50',
'tenant_id' => 'nullable|integer|exists:tenants,id',
'board_type' => 'nullable|string|max:50',
'description' => 'nullable|string|max:500',
'editor_type' => 'nullable|string|in:wysiwyg,markdown,text',
'allow_files' => 'nullable|boolean',
'max_file_count' => 'nullable|integer|min:0|max:100',
'max_file_size' => 'nullable|integer|min:0',
'extra_settings' => 'nullable|array',
'is_active' => 'nullable|boolean',
]);
// 코드 중복 체크
if (! empty($validated['tenant_id'])) {
// 테넌트 게시판
if ($this->boardService->isTenantCodeExists($validated['board_code'], $validated['tenant_id'])) {
return response()->json([
'success' => false,
'message' => '해당 테넌트에서 이미 사용 중인 게시판 코드입니다.',
], 422);
}
} else {
// 시스템 게시판
if ($this->boardService->isCodeExists($validated['board_code'])) {
return response()->json([
'success' => false,
'message' => '이미 사용 중인 게시판 코드입니다.',
], 422);
}
}
$board = $this->boardService->createBoardFromTemplate(
$validated,
$validated['template_type'] ?? null,
$validated['template_key'] ?? null
);
return response()->json([
'success' => true,
'message' => '게시판이 생성되었습니다.',
'data' => $board,
]);
}
// =========================================================================
// 테넌트 관련 API
// =========================================================================
/**
* 테넌트 목록 조회 (드롭다운용)
*/
public function tenants(): JsonResponse
{
$tenants = $this->boardService->getTenantList();
return response()->json([
'success' => true,
'data' => $tenants,
]);
}
/**
* 테넌트 게시판 코드 중복 체크
*/
public function checkTenantCode(Request $request): JsonResponse
{
$validated = $request->validate([
'code' => 'required|string|max:50',
'tenant_id' => 'required|integer|exists:tenants,id',
'exclude_id' => 'nullable|integer',
]);
$exists = $this->boardService->isTenantCodeExists(
$validated['code'],
$validated['tenant_id'],
$validated['exclude_id'] ?? null
);
return response()->json([
'success' => true,
'data' => ['exists' => $exists],
]);
}
}