내장 게시판 시작하기
내장 게시판(Builtin Board)은 v1.37.0 에서 추가된 경량 공지 / FAQ / 1:1 문의 모듈입니다. site_board_* 테이블 기반으로 동작하며, SaaS · Tenant 운영 주체가 직접 게시판을 만들어 회원에게 공지를 띄우거나 문의를 응대할 수 있습니다.
내장 게시판은 개별 회원 기능이 아니라 사이트 운영 설정입니다. 게시판 컨테이너(공지/FAQ/문의) 는 SaaS(L1) · Tenant(L2) 운영자가 만들고, 회원은 노출된 글을 읽거나 문의를 작성합니다.
외부 Board 플러그인과의 차이
내장 게시판은 외부 Board 플러그인(App\Plugins\Board, 자유게시판)과 완전히 별개의 기능입니다. 이름이 비슷하지만 코드/테이블/용도가 모두 다릅니다.
| 구분 | 내장 게시판 (Builtin Board) | 외부 Board 플러그인 (자유게시판) |
|---|---|---|
| 도입 | v1.37.0 (Phase 3) | 별도 플러그인 |
| 용도 | 공지 / FAQ / 1:1 문의 | 자유게시판 (글쓰기·댓글·좋아요·스크랩) |
| 모델 | App\Models\Board, BoardPost, BoardInquiry, BoardInquiryReply | App\Plugins\Board 네임스페이스 모델 |
| DB 테이블 | site_board_* | plg_board_* |
| Filament Resource | BoardResource(slug site-boards) | BoardResource(slug boards) |
| 활성 방식 | 기본 내장 (CORE_BOARD_ENABLED) | 운영자 선택형 플러그인 |
| 운영 주체 | SaaS(L1) / Tenant(L2) | 플러그인 활성화 테넌트 |
외부 Board 플러그인이 이미 board_*(정확히는 plg_board_*) 테이블과 boards 슬러그를 점유하고 있기 때문에, 내장 게시판은 충돌을 피하기 위해 테이블 접두사를 site_board_ 로, Filament 슬러그를 site-boards 로 분리했습니다. 각 모델은 $table 속성을 명시합니다.
게시판 컨테이너 개념
내장 게시판의 최상위 단위는 게시판 컨테이너(App\Models\Board)입니다. 하나의 컨테이너는 kind 로 종류가 정해지며, 그 안에 글(BoardPost) 또는 문의(BoardInquiry)가 담깁니다.
| kind 상수 | 값 | 설명 | 담기는 항목 |
|---|---|---|---|
Board::KIND_NOTICE | notice | 공지사항 | BoardPost |
Board::KIND_FAQ | faq | 자주 묻는 질문 (카테고리 그룹) | BoardPost |
Board::KIND_INQUIRY | inquiry | 1:1 문의 접수 | BoardInquiry |
Board::KIND_GENERIC | generic | 범용 게시판 (자유 생성) | BoardPost |
노출 대상(audience_scope)
각 컨테이너는 audience_scope 로 "누구에게 보일지"를 정합니다.
| scope 상수 | 값 | 노출 대상 |
|---|---|---|
Board::SCOPE_SAAS | saas | SaaS 전체 (모든 테넌트, tenant_id 무관) |
Board::SCOPE_TENANT | tenant | 해당 Tenant 전체 |
Board::SCOPE_ORG | org | audience_org_id 조직과 그 하위 서브트리 (depth 무관) |
회원에게 보이는 게시판은 모델 스코프 Board::scopeVisibleToMember() 가 결정합니다. SaaS 전체 공지, 자신이 속한 Tenant 공지, 자신의 조직 조상 체인(ViewerScopeResolver::orgAncestorIds())에 걸린 org 공지를 합쳐 노출합니다.
누가 게시판을 만드는가
게시판 컨테이너 생성/삭제는 "사이트 전체 설정"이므로 L1 SaaS · L2 Tenant 운영자만 가능합니다. L3 조직 운영자는 본인 서브트리 게시판의 글 관리와 문의 응대만 수행합니다(생성 불가). 이 규칙은 BoardResource::canCreate()(level <= 2) 와 Board::scopeManageableBy() 에서 강제됩니다.
2계층 격리 + RLS
4개 모델(Board, BoardPost, BoardInquiry, BoardInquiryReply)은 모두 SaasTenantOwnedModelInterface 를 구현하고 다음 Trait 을 사용합니다.
BelongsToSaasProduct—saas_product_id자동 할당 + SaaS 격리BelongsToTenant—tenant_id자동 할당 + Tenant 격리Auditable— 생성/수정/삭제 감사 로그 자동 기록
즉 SaaS + Tenant 2계층으로 데이터가 격리됩니다. 애플리케이션 레벨 글로벌 스코프에 더해, PostgreSQL Row Level Security(RLS) 정책이 saas_product_id / tenant_id 세션 변수 기준으로 행을 격리합니다.
초판 RLS 정책이 WITH CHECK 누락· 세션변수 NULL bypass 부재로 시드/마이그레이션 INSERT 를 거부하는 문제가 있었습니다. v1.37.1 의 2026_06_01_000001_fix_site_board_rls_policies 마이그레이션이 demo_posts 패턴(saas/tenant 분리 정책 + USING + WITH CHECK + 세션변수가 NULL/''/'0' 일 때 bypass)으로 교체했습니다. 이미 마이그레이션된 DB 는 같은 배치 롤백 위험을 피하기 위해 forward-only 로 보정됩니다.
마이그레이션 / 테이블
database/migrations/ 의 site_board_* 마이그레이션 5개 + RLS 보정 1개로 구성됩니다. 모두 Schema::hasTable() 멱등 가드와 데이터 보존형 down()(드롭 보류)을 적용해 백포트에 안전합니다.
| 마이그레이션 | 테이블 | 모델 |
|---|---|---|
2026_05_31_000002_create_site_board_boards_table | site_board_boards | Board |
2026_05_31_000003_create_site_board_posts_table | site_board_posts | BoardPost |
2026_05_31_000004_create_site_board_inquiries_table | site_board_inquiries | BoardInquiry |
2026_05_31_000005_create_site_board_inquiry_replies_table | site_board_inquiry_replies | BoardInquiryReply |
2026_05_31_000006_setup_site_board_rls_policies | (RLS 정책) | — |
2026_06_01_000001_fix_site_board_rls_policies | (RLS 보정, v1.37.1) | — |
relation "site_board_boards" does not exist 오류는 코드 문제가 아니라 운영 DB 에 마이그레이션이 적용되지 않은 상태입니다. make migrate NAME={project} 로 해결합니다.
활성화 / 비활성화
내장 게시판은 기본 활성화 상태로 출하됩니다. config/core.php 의 board 블록에서 제어합니다.
// config/core.php
'board' => [
'enabled' => env('CORE_BOARD_ENABLED', true),
'tables' => [
'boards' => 'site_board_boards',
'posts' => 'site_board_posts',
'inquiries' => 'site_board_inquiries',
'replies' => 'site_board_inquiry_replies',
],
'kinds' => ['notice', 'faq', 'inquiry', 'generic'],
'inquiry' => [
// 미해결 문의를 상위 org 로 한 단계 에스컬레이션 허용
'escalation_enabled' => env('CORE_BOARD_INQUIRY_ESCALATION', true),
],
],
# .env (기본값)
CORE_BOARD_ENABLED=true # 내장 게시판 전체 on/off
CORE_BOARD_INQUIRY_ESCALATION=true # 문의 에스컬레이션 액션 노출 여부
Filament Resource / 회원 페이지는 모두 config('board.enabled', true) 를 게이트로 사용하므로, false 로 끄면 운영자 메뉴와 회원 메뉴가 모두 숨겨집니다.
기본 시드 (CoreSeeder)
CoreSeeder::createDemoBoards() 가 테넌트마다 기본 게시판 3개(모두 is_system=true)를 멱등 시드합니다(tenant_id + key 기준 updateOrCreate). board.enabled=false 이거나 site_board_boards 테이블이 없으면 건너뜁니다(백포트 안전).
| key | kind | 이름 | audience_scope |
|---|---|---|---|
notice | notice | 공지사항 | tenant |
faq | faq | FAQ | tenant |
inquiry | inquiry | 1:1 문의 | tenant |
추가로 데모 데이터로 공지글 1건("환영합니다", 고정), FAQ 글 1건("비밀번호를 잊었어요", 카테고리 계정), 그리고 member@ 회원이 작성한 문의 1건("서비스 이용 문의")을 시드합니다. 회원 문의는 member@ 의 소속 조직(없으면 HQ 조직)을 organization_id · assigned_org_id 로 지정해, org@ 운영자가 응대하는 계층 시나리오를 바로 시연할 수 있습니다.
접속 경로
운영자 (Filament 관리 패널)
내장 게시판 Filament 플러그인(BuiltinBoardFilamentPlugin, id builtin-board)은 SaaS · Tenant · Org 패널에 등록됩니다. App 패널에는 등록되지 않습니다.
| 패널 | 메뉴 | navigation 그룹 | 슬러그/경로 |
|---|---|---|---|
| SaaS | 게시판 (BoardResource) | 사이트 관리 | /saas/site-boards |
| SaaS | 1:1 문의 (InquiryResource) | 고객 문의 | /saas/{inquiry-slug} |
| Tenant | 게시판 / 1:1 문의 | 사이트 관리 / 고객 문의 | /tenant/.../site-boards |
| Org | 게시판 / 1:1 문의 | 사이트 관리 / 고객 문의 | /org/site-boards |
BoardResource는사이트 관리navigation 그룹, 슬러그site-boards(외부 Board 플러그인의boards와 분리)입니다.InquiryResource는고객 문의navigation 그룹에 표시됩니다.- 게시판 메뉴는 "사이트 전체 설정"이므로 관리 가능한 사이트(독립 사이트 또는 SaaS 기본 사이트) 컨텍스트에서만 노출됩니다. 미러 사이트(Platform 기본 설정 공유)에는 표시되지 않습니다(
SiteTypeHelper::currentContextIsManageableSite()).
회원 (App 패널)
회원용 페이지 3개는 App 패널(/app)에 자동 등록(discoverPages)됩니다.
| 페이지 클래스 | 메뉴 | 역할 |
|---|---|---|
App\Filament\App\Pages\NoticesPage | 공지사항 | 자기 계층에 노출된 공지글 열람 |
App\Filament\App\Pages\FaqPage | FAQ | FAQ 글을 카테고리별로 그룹 열람 |
App\Filament\App\Pages\MyInquiriesPage | 1:1 문의 | 본인 문의 작성·조회·답글 |
세 페이지 모두 config('board.enabled', true) 가 true 일 때만 메뉴에 노출됩니다.
권한 카탈로그
서브관리자(Chief 아래 폼으로 생성된 관리자)는 메뉴별 권한이 있어야 접근할 수 있습니다(v1.38.0 부터 강제). 1차 관리자(chief_id=null)와 Level 0 은 전권입니다. config/permissions.php 에 다음 feature 키가 정의되어 있습니다.
| feature 키 | 라벨 | custom_actions |
|---|---|---|
boards | 게시판 관리 | manage_notice(공지 관리), manage_faq(FAQ 관리), reply(답글 작성) |
inquiries | 문의 관리 | reply(답변 작성), assign(담당자 배정) |
BoardResource 의 feature 키는 boards, InquiryResource 는 inquiries 입니다. 노출/접근은 config('board.enabled') + 사이트 컨텍스트 + 해당 키의 read 권한이 AND 병합됩니다.
관련 문서
- 운영 절차는 운영자 가이드를 참조하세요.
- 설계 배경:
workspace/_docs/design/03_features/23_board-commerce-plugins.md,workspace/_docs/design/01_decisions/10_board-commerce-strategy.md