diff --git a/app/Http/Controllers/BoardController.php b/app/Http/Controllers/BoardController.php index e78cd77f..21b7a531 100644 --- a/app/Http/Controllers/BoardController.php +++ b/app/Http/Controllers/BoardController.php @@ -17,8 +17,9 @@ public function __construct( public function index(): View { $boardTypes = $this->boardService->getBoardTypes(); + $currentTenant = auth()->user()->currentTenant(); - return view('boards.index', compact('boardTypes')); + return view('boards.index', compact('boardTypes', 'currentTenant')); } /** diff --git a/app/Http/Controllers/PostController.php b/app/Http/Controllers/PostController.php index 7ecdcfa8..f8aa0c83 100644 --- a/app/Http/Controllers/PostController.php +++ b/app/Http/Controllers/PostController.php @@ -29,14 +29,16 @@ private function resolveBoard(string $boardCode, Request $request): Board // t 파라미터가 있으면 해당 테넌트의 게시판 조회 if ($tenantId) { - return Board::where('board_code', $boardCode) + return Board::with('tenant') + ->where('board_code', $boardCode) ->where('tenant_id', $tenantId) ->where('is_active', true) ->firstOrFail(); } // t 파라미터가 없으면: 시스템 게시판 우선, 그 다음 로그인 회원의 테넌트 게시판 - $board = Board::where('board_code', $boardCode) + $board = Board::with('tenant') + ->where('board_code', $boardCode) ->whereNull('tenant_id') // 시스템 게시판 ->where('is_active', true) ->first(); @@ -48,7 +50,8 @@ private function resolveBoard(string $boardCode, Request $request): Board // 시스템 게시판이 없으면 로그인 회원의 테넌트 게시판 $userTenantId = auth()->user()?->tenant_id; - return Board::where('board_code', $boardCode) + return Board::with('tenant') + ->where('board_code', $boardCode) ->where('tenant_id', $userTenantId) ->where('is_active', true) ->firstOrFail(); @@ -60,12 +63,17 @@ private function resolveBoard(string $boardCode, Request $request): Board public function index(string $boardCode, Request $request): View { $board = $this->resolveBoard($boardCode, $request); - $filters = $request->only(['search', 'is_notice']); - $posts = $this->postService->getPosts($board->id, $filters, 15); - $notices = $this->postService->getNotices($board->id, 5); - $stats = $this->postService->getBoardPostStats($board->id); + $filters = $request->only(['search', 'is_notice', 'trashed']); - return view('posts.index', compact('board', 'posts', 'notices', 'stats', 'filters')); + // 슈퍼관리자: 삭제된 게시물 포함 조회 + $isSuperAdmin = auth()->user()->hasRole('super-admin'); + $includeTrashed = $isSuperAdmin; + + $posts = $this->postService->getPosts($board->id, $filters, 15, $includeTrashed); + $notices = $this->postService->getNotices($board->id, 5); + $stats = $this->postService->getBoardPostStats($board->id, $includeTrashed); + + return view('posts.index', compact('board', 'posts', 'notices', 'stats', 'filters', 'isSuperAdmin')); } /** @@ -266,6 +274,46 @@ public function destroy(string $boardCode, Post $post, Request $request): Redire ->with('success', '게시글이 삭제되었습니다.'); } + /** + * 게시글 복원 (슈퍼관리자 전용) + */ + public function restore(string $boardCode, int $postId, Request $request): RedirectResponse + { + // 슈퍼관리자 권한 확인 + if (! auth()->user()->hasRole('super-admin')) { + abort(403); + } + + $board = $this->resolveBoard($boardCode, $request); + $post = Post::onlyTrashed()->where('board_id', $board->id)->findOrFail($postId); + + $this->postService->restorePost($post); + + return redirect() + ->route('boards.posts.index', [$board->board_code, 't' => $board->tenant_id]) + ->with('success', '게시글이 복원되었습니다.'); + } + + /** + * 게시글 영구 삭제 (슈퍼관리자 전용) + */ + public function forceDestroy(string $boardCode, int $postId, Request $request): RedirectResponse + { + // 슈퍼관리자 권한 확인 + if (! auth()->user()->hasRole('super-admin')) { + abort(403); + } + + $board = $this->resolveBoard($boardCode, $request); + $post = Post::withTrashed()->where('board_id', $board->id)->findOrFail($postId); + + $this->postService->forceDeletePost($post); + + return redirect() + ->route('boards.posts.index', [$board->board_code, 't' => $board->tenant_id]) + ->with('success', '게시글이 영구 삭제되었습니다.'); + } + // ========================================================================= // File Management // ========================================================================= diff --git a/app/Services/PostService.php b/app/Services/PostService.php index 2c6bfaa0..7d2c85bf 100644 --- a/app/Services/PostService.php +++ b/app/Services/PostService.php @@ -17,14 +17,21 @@ class PostService { /** * 게시글 목록 조회 (페이지네이션) + * + * @param bool $includeTrashed 슈퍼관리자용: 삭제된 게시물 포함 */ - public function getPosts(int $boardId, array $filters = [], int $perPage = 15): LengthAwarePaginator + public function getPosts(int $boardId, array $filters = [], int $perPage = 15, bool $includeTrashed = false): LengthAwarePaginator { $query = Post::query() ->ofBoard($boardId) ->with(['author', 'board']) ->published(); + // 슈퍼관리자: 삭제된 게시물 포함 + if ($includeTrashed) { + $query->withTrashed(); + } + // 검색 if (! empty($filters['search'])) { $search = $filters['search']; @@ -39,6 +46,15 @@ public function getPosts(int $boardId, array $filters = [], int $perPage = 15): $query->where('is_notice', $filters['is_notice']); } + // 삭제 상태 필터 + if (isset($filters['trashed'])) { + if ($filters['trashed'] === 'only') { + $query->onlyTrashed(); + } elseif ($filters['trashed'] === 'with') { + $query->withTrashed(); + } + } + // 정렬: 공지사항 먼저, 그 다음 최신순 $query->orderByDesc('is_notice') ->orderByDesc('created_at'); @@ -133,12 +149,25 @@ public function deletePost(Post $post): bool */ public function forceDeletePost(Post $post): bool { + // 첨부파일 영구 삭제 + $this->forceDeleteAllFiles($post); + // 커스텀 필드 값 삭제 $post->customFieldValues()->delete(); return $post->forceDelete(); } + /** + * 게시글 복원 + */ + public function restorePost(Post $post): bool + { + $post->deleted_by = null; + + return $post->restore(); + } + /** * 조회수 증가 */ @@ -196,14 +225,21 @@ public function getAdjacentPosts(Post $post): array /** * 게시판 통계 */ - public function getBoardPostStats(int $boardId): array + public function getBoardPostStats(int $boardId, bool $includeTrashed = false): array { - return [ + $stats = [ 'total' => Post::ofBoard($boardId)->count(), 'published' => Post::ofBoard($boardId)->published()->count(), 'notices' => Post::ofBoard($boardId)->notices()->count(), 'today' => Post::ofBoard($boardId)->whereDate('created_at', today())->count(), ]; + + // 슈퍼관리자용: 삭제된 게시물 통계 + if ($includeTrashed) { + $stats['trashed'] = Post::ofBoard($boardId)->onlyTrashed()->count(); + } + + return $stats; } // ========================================================================= diff --git a/resources/views/boards/index.blade.php b/resources/views/boards/index.blade.php index cac6b6b6..b4c8f718 100644 --- a/resources/views/boards/index.blade.php +++ b/resources/views/boards/index.blade.php @@ -1,11 +1,16 @@ @extends('layouts.app') -@section('title', '게시판 관리') +@section('title', ($currentTenant?->company_name ?? '') . ' 게시판 관리') @section('content')
{{ $board->description }}
@endif @@ -30,7 +35,7 @@ class="px-4 py-2 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg text-sm