내장 게시판 운영자 가이드
내장 게시판(공지 / FAQ / 1:1 문의)의 운영 절차를 다룹니다. 개념과 접속 경로는 시작하기를 먼저 확인하세요.
게시판 컨테이너 생성/삭제는 L1 SaaS · L2 Tenant 운영자만 가능합니다. L3 조직 운영자는 본인 서브트리 게시판의 글 관리와 문의 응대만 할 수 있습니다. 서브관리자는 boards / inquiries feature 권한이 부여되어야 메뉴가 보입니다.
1. 게시판 컨테이너 관리
SaaS · Tenant · Org 패널의 사이트 관리 → 게시판(BoardResource, 슬러그 site-boards)에서 게시판 컨테이너를 관리합니다.
새 게시판 만들기 (L1/L2)
게시판 폼은 두 섹션으로 구성됩니다.
기본 정보 섹션
| 필드 | 설명 | 비고 |
|---|---|---|
kind | 종류 (공지/FAQ/문의/범용) | is_system 게시판은 변경 불가 |
name | 게시판 표시 이름 | 최대 150자, 필수 |
key | 내부 식별 키 | 최대 64자, is_system 게시판은 변경 불가 |
audience_scope | 노출 범위 (SaaS/Tenant/Org) | 기본값 tenant |
audience_org_id | 대상 조직 | audience_scope = org 일 때만 표시 |
site_key | 독립 사이트 키 | 비우면 공통(null), 특정 독립 사이트에만 노출 시 지정 |
노출/배치 섹션
| 필드 | 설명 | 기본값 |
|---|---|---|
placement.app_nav | 회원 App 패널 메뉴 노출 토글 | true |
placement.sort | App 네비 내 정렬 순서 | 0 |
settings.engagement.view_count_visibility | 조회수 노출 대상 (admin/member/public/hidden) | public |
settings.engagement.likes_enabled | 게시글 상세 좋아요 버튼 사용 | false |
is_active | 게시판 활성화 | true |
sort | 관리 목록 정렬 순서 | 0 |
- 한 테넌트 안에서
(site_key, key)조합은 유일합니다(부분 유니크 인덱스,site_keyNULL 은''로 정규화). audience_scope가org이면 지정한 조직과 그 하위 서브트리 전체(depth 무관)에 노출됩니다.
조회수 노출과 좋아요
조회수는 읽을 수 있는 게시글 상세를 사람이 열면 노출 설정과 무관하게 기록됩니다. hidden은 숫자를
숨기는 설정이지 기록 중단 설정이 아닙니다. 관리자 관리 화면과 알려진 봇은 집계하지 않고, 같은 브라우저
세션·같은 글은 기본 30분 동안 한 번만 계산합니다. IP·UA 원문이나 사용자별 열람 이력은 저장하지 않습니다.
| 조회수 노출 | 표시 대상 |
|---|---|
관리자만 (admin) | 권한 있는 게시글/게시판 관리자 화면 |
회원만 (member) | 인증 회원이 읽을 수 있는 회원/공개 상세 |
전체 (public) | 관리자·인증 회원·읽을 수 있는 비회원 공개 상세 |
미노출 (hidden) | 모든 표면에서 숫자 숨김, 기록은 유지 |
좋아요 사용을 끄면 버튼과 개수를 숨기며 state/toggle 직접 요청도 404로 거부합니다. 켜더라도 사이트 전체 좋아요 기능 사용이 꺼져 있으면 버튼은 보이지 않습니다. 비회원이 누를 수 있는지는 사이트 전역 비회원 좋아요 허용 설정을 따릅니다. 1:1 문의 게시판은 조회수·좋아요 대상이 아니므로 항상 미노출/해제됩니다.
목록과 액션
게시판 목록은 kind(배지), name, audience_scope, 글 수(posts_count), is_active, is_system 컬럼을 보여 줍니다. 정렬 기본값은 sort 오름차순입니다.
- 수정: 행 단위
EditAction으로 모든 운영자(열람 권한 기준) 수행. - 일괄 삭제:
DeleteBulkAction은level <= 2(L1/L2)에게만 표시됩니다.
is_system 보호
CoreSeeder 가 시드한 기본 게시판(공지/FAQ/문의)은 is_system=true 로 보호됩니다.
- 폼에서
kind와key필드가 비활성(disabled)됩니다 — 종류와 식별 키를 바꿀 수 없습니다. name,audience_scope, 노출 설정 등은 수정할 수 있습니다.- 삭제는 L1/L2 의 일괄 삭제로만 가능하며, 시스템 게시판은 운영 연속성을 위해 보존하는 것을 권장합니다.
2. 공지 / FAQ / 범용 글 작성
글 관리는 게시판 상세의 Posts RelationManager(PostsRelationManager)에서 BoardPost 단위로 수행합니다. BoardPost 의 주요 필드는 다음과 같습니다.
| 필드 | 설명 |
|---|---|
title / body | 제목 / 본문 |
excerpt | 요약 |
category | 분류 (FAQ 카테고리 그룹에 사용) |
status | draft / published |
is_pinned | 상단 고정 (공지 목록 상단 노출) |
published_at | 발행 시각 |
starts_at / ends_at | 노출 시작/종료 기간 |
view_count | 상세 조회 누적 수(관리자가 직접 편집하지 않음) |
발행과 노출 기간
회원에게 보이는 글은 BoardPost::scopePublished() 가 결정합니다. 다음 조건을 모두 만족해야 노출됩니다.
status = publishedpublished_at이 비었거나 현재 시각 이하starts_at이 비었거나 현재 시각 이하ends_at이 비었거나 현재 시각 이상
즉 starts_at / ends_at 로 기간 한정 공지를 예약할 수 있고, is_pinned 로 공지 목록 상단에 고정할 수 있습니다.
관리 목록의 조회수 컬럼은 게시판 설정이 admin 또는 public일 때만 숫자를 보여 줍니다. 여러 게시판이
섞인 목록에서는 허용되지 않은 행을 —로 표시하며, 숫자 추론을 막기 위해 정렬·검색·필터를 제공하지 않습니다.
3. 1:1 문의 운영
문의 응대는 SaaS · Tenant · Org 패널의 고객 문의 → 1:1 문의(InquiryResource)에 서 수행합니다. 문의는 회원이 작성하므로 운영자는 새로 만들 수 없습니다(canCreate() = false).
계층 열람 (상위 조직이 하위 문의를 본다)
어떤 문의를 볼 수 있는지는 BoardInquiry::scopeVisibleTo() 가 뷰어의 레벨로 결정합니다.
| 뷰어 레벨 | 열람 범위 |
|---|---|
| L0 / L1 / L2 | Tenant · SaaS 전체 문의 (글로벌 스코프) |
| L3~L5 조직 운영자 | 본인 작성 문의 + 자기 조직 서브트리(ViewerScopeResolver::orgSubtreeIds())에 origin(organization_id) 또는 assigned(assigned_org_id)된 문의 |
| L6 회원 | 본인 문의만 (같은 조직 동료 문의도 비노출) |
핵심은 상위 조직 운영자가 하위 조직의 문의를 열람한다는 점입니다. 문의에는 두 개의 조직 필드가 있습니다.
organization_id— 작성 시점 회원의 소속 조직 (열람 스코프 기준)assigned_org_id— 현재 처리 주체 조직 (에스컬레이션에 따라 이동)
문의 목록은 제목, 작성자(author.name), 소속 조직, 담당 조직(assignedOrg.name), 상태(배지), 최근 답글 시각을 보여 주며, 상태 필터(open/answered/closed)를 제공합니다.
답글 작성 (운영자)
문의 응대 화면(EditInquiry)의 Replies RelationManager(RepliesRelationManager)에서 답글을 답니다.
- 답글 작성 시 문의 상태가 자동으로
answered로 바뀌고last_reply_at이 갱신됩니다. is_internal토글을 켜면 내부 메모가 됩니다. 내부 메모는 회원에게 노출되지 않습니다(BoardInquiryReply::scopePublic()이is_internal=false만 반환).- 운영자는 상단 폼에서 문의 상태(
open/answered/closed)를 직접 변경할 수 있습니다. 제목·분류는 읽기 전용입니다.
에스컬레이션
응대 화면(EditInquiry) 헤더의 에스컬레이션 액션으로 미해결 문의를 상위 조직으로 한 단계 올립니다. 이 액션은 config('board.inquiry.escalation_enabled', true)(CORE_BOARD_INQUIRY_ESCALATION) 가 true 일 때만 보입니다.
동작은 BoardInquiry::escalate() 가 처리합니다.
- 현재 처리 주체(
assigned_org_id)의 부모 조직(organizations.parent_id)으로assigned_org_id를 이동합니다. - 부모가 없으면(
parent_id = null)assigned_org_id를null로 설정 — 이는 Tenant/SaaS 큐(최상위)에 도달했음을 의미합니다. - 이동 후 상태를 다시
open으로 되돌립니다. - 이미 최상위(
assigned_org_id = null)이면 이동하지 않고 "최상위 도달" 알림을 보냅니다(organization_id폴백 없음 — 재에스컬레이션 방지).
에스컬레이션 액션은 공통 로깅 모듈(->audited('board.inquiry.escalate'))로 계층 컨텍스트가 감사 로그에 자동 기록됩니다.
4. 회원 입장에서의 사용
회원은 App 패널(/app)에서 다음 페이지를 사용합니다(게시판이 활성화된 경우에만 노출).
공지사항 (NoticesPage)
자기 계층(SaaS 전체 + 소속 Tenant + 조직 조상 체인)에 노출된 notice 게시판의 발행 글을 봅니다. 고정 글(is_pinned)이 상단에 오고, 글을 클릭하면 모달로 본문을 봅니다. 이때 읽기 권한을 다시 확인한 뒤 조회수를 기록하고, 게시판 설정에 따라 조회수·좋아요를 표시합니다. 본문은 XSS 방지를 위해 escape 후 줄바꿈만 허용합니다(nl2br(e($body))).