Files
HeuJung 8328b1db77 docs(core,extensions): 확장 개발자 문서 9세트 집필과 생성기 정합 보강
S1 파일럿 3종(sirsoft-board · sirsoft-gdpr · sirsoft-admin_basic)에 이어
결제·본인인증 동형군 6종의 AGENTS.md · README.md · docs/ 5문서를 집필했다.
확장을 고치려는 쪽이 매번 src/ 를 훑어 구조를 재발견하지 않도록, 설계 의도와
확장점(발행·구독 훅)·수정 시 동반 의무·금지 패턴을 코드 근거로 서술했다.

생성기(ExtensionDocScaffolder)에서 표를 무의미하게 만들던 세 결함을 함께 고쳤다.
getLayoutExtensions 기본 구현이 돌려주는 절대경로가 파일 목록과 중복돼 로컬
머신 경로가 커밋 문서에 실리던 문제, getNotificationDefinitions 를 'key'/'event'
로 읽어 모든 행이 '-' 로 찍히던 문제, getSettingsLayout 절대경로가 정규화 없이
노출되던 문제다. 셋 다 예외를 남기지 않고 표만 조용히 망가뜨리므로
ExtensionDocContractTest 에 각각의 되돌림 red 를 확인한 단언을 두었다.
템플릿의 extensions/{id}/ 는 모듈·플러그인의 발행과 반대 방향(오버라이드)이라
별도 블록(template-overrides)으로 분리했다.

sirsoft-admin_basic 의 컴포넌트·핸들러·레이아웃 문서를 코어 docs/ 에서 그 템플릿
소유로 이관하고, 남은 참조 6축을 재는 가드를 추가했다. 이관 후 남은 옛 경로는
오류가 아니라 헛걸음으로만 나타나 드러나지 않는다. 의 상대 링크가
한 단계 얕아 공유 docs/ 대신 를 가리키던 문제도 함께 고쳤다.

README 상단의 확장명 이미지 배지를 평문 H1 로 바꿨다( 지시 2026-08-31).
루트 README.md · README.ko.md 도 같은 기준을 적용했다. 정보 배지는 유지한다.

sirsoft-gdpr: 회원탈퇴로 자동 철회된 동의가 관리자 동의 이력 화면의 출처 필터로
걸러지지 않던 문제를 고쳤다. Repository 가 'withdraw' 리터럴을 직접 UPDATE 에
싣는데 그 값이 ConsentSource enum 에 없어, 화면 필터 옵션·라벨 어느 쪽에도
도달하지 못했다. 어휘를 enum 단일 출처로 모으고 ko·en·ja 라벨과 필터 옵션을
함께 채웠으며, 어휘 대조 테스트의 모집단에 Repository 를 편입했다.
2026-08-31 15:57:44 +09:00

11 KiB

게시판 — 데이터 모델

모델·소유 테이블·마이그레이션·Enum · 진입점: AGENTS.md

모델

모델 테이블 fillable 관계 특성
Attachment board_attachments 15 board→Board, post→Post, creator→User SoftDeletes
Board boards 37 creator→User, updater→User, posts→Post, comments→Comment, attachments→Attachment, reports→Report -
BoardStat board_stats 3 - -
BoardType board_types 3 - HasUserOverrides
Comment board_comments 14 board→Board, post→Post, user→User, parent→self, replies→self SoftDeletes
Post board_posts 21 board→Board, user→User, parent→self, replies→self, comments→Comment, attachments→Attachment, 외 1개 SoftDeletes, 검색 색인
Report boards_reports 11 board→Board, author→User, logs→ReportLog, processor→User SoftDeletes
ReportLog boards_report_logs 6 report→Report, reporter→User -
UserNotificationSetting board_user_notification_settings 5 user→User -

Post·Comment 모두 parent/replies 자기참조 관계를 갖습니다 — Post 의 자기참조는 "답변형 게시판"(원글에 대한 관리자 답변)을, Comment 의 자기참조는 대댓글을 표현합니다. 둘은 서로 다른 기능이라 관계 이름은 같아도 코드에서 섞어 쓰지 않습니다. Board 는 HasUserOverrides 를 쓰지 않습니다 — 게시판 자체는 운영자가 직접 소유·수정하는 리소스라 "모듈 재설치 시 운영자 수정 보존"이 필요 없는 반면, BoardType(게시판 유형 프리셋)은 모듈이 시딩한 기본값을 운영자가 손댈 수 있어야 하므로 그 트레이트를 씁니다.

소유 테이블

테이블 모델
board_attachments Attachment
board_comments Comment
board_mail_templates -
board_posts Post
board_stats BoardStat
board_types BoardType
board_user_notification_settings UserNotificationSetting
boards Board
boards_report_logs ReportLog
boards_reports Report

board_mail_templates 는 모델이 없는 채로 남아 있습니다 — 아래 마이그레이션 표의 drop_board_mail_templates_table(2026-04-13)이 보여주듯, 메일 템플릿을 자체 테이블로 관리하던 초기 설계를 코어 GenericNotification 알림 정의(§알림 정의)로 이관하며 테이블만 드롭하고 이름은 이력상 남아 있는 상태입니다. 신규 코드에서 이 이름을 참조하지 않습니다. 테이블 접두어가 board_*와 boards_* 두 가지로 섞여 있는 것은 설계 의도가 아니라 이력입니다 — 새 테이블을 추가할 때는 board_*(단수)를 따릅니다.

마이그레이션

마이그레이션 30개.

파일 생성 테이블 변경 테이블 down()
2026_04_01_000001_create_board_types_table.php board_types - ✅
2026_04_01_000002_create_boards_table.php boards boards ✅
2026_04_01_000003_create_board_user_notification_settings_table.php board_user_notification_settings board_user_notification_settings ✅
2026_04_01_000004_create_board_posts_table.php board_posts - ✅
2026_04_01_000005_create_board_comments_table.php board_comments - ✅
2026_04_01_000006_create_board_attachments_table.php board_attachments - ✅
2026_04_01_000007_create_boards_reports_table.php boards_reports boards_reports ✅
2026_04_01_000008_create_boards_report_logs_table.php boards_report_logs boards_report_logs ✅
2026_04_01_000009_create_board_mail_templates_table.php board_mail_templates - ✅
2026_04_01_000010_add_fulltext_indexes_to_boards_table.php - boards ✅
2026_04_01_000011_add_fulltext_indexes_to_boards_report_logs_table.php - boards_report_logs ✅
2026_04_01_000012_add_indexes_to_board_posts_table.php - board_posts ✅
2026_04_13_000001_drop_board_mail_templates_table.php board_mail_templates - ✅
2026_04_13_000002_add_user_overrides_to_board_types_table.php - board_types ✅
2026_04_14_000001_drop_channel_columns_from_boards_table.php - boards ✅
2026_04_17_000001_remove_partitions_from_board_tables.php - - ✅
2026_04_17_000002_add_count_columns_to_board_tables.php - board_posts, board_comments ✅
2026_04_17_000003_add_posts_count_and_comments_count_to_boards_table.php - boards ✅
2026_04_17_000004_update_indexes_in_board_tables.php - board_posts, board_comments, board_attachments ✅
2026_05_29_000001_create_board_stats_table.php board_stats - ✅
2026_06_08_000001_add_recent_across_boards_index_to_board_posts.php - board_posts ✅
2026_06_26_000001_modify_trigger_type_in_board_comments_table.php - - ✅
2026_06_26_000002_add_trigger_type_to_board_attachments_table.php - board_attachments ✅
2026_08_01_000001_add_tiebreak_to_board_posts_list_index.php - - ✅
2026_08_01_000002_add_list_sort_index_to_boards_reports_table.php - - ✅
2026_08_06_000001_update_max_reply_depth_comment_in_boards_table.php - - ✅
2026_08_06_000002_add_view_count_sort_index_to_board_posts_table.php - - ✅
2026_08_17_000001_add_reply_delete_policy_to_boards_table.php - boards ✅
2026_08_17_000002_modify_trigger_type_in_board_posts_table.php - - ✅
2026_08_22_000001_add_content_thumbnail_url_to_board_posts_table.php - board_posts ✅

목록 성능 마이그레이션이 지속적으로 추가되는 것(인덱스 4건 + 정렬 타이브레이크 2건)은 우연이 아닙니다 — 게시글 목록은 공지 제외·답변 제외 필터가 항상 걸린 채로 created_at/view_count 2가지 정렬을 지원해야 하므로, 새 정렬 옵션을 추가할 때마다 그 정렬에 맞는 복합 인덱스를 함께 마이그레이션합니다(getBenchmarkProfiles() 의 board_posts_by_view_count 프로파일이 바로 이 목적으로 존재합니다). remove_partitions_from_board_tables(2026-04-17)는 board_posts/board_comments/ board_attachments 3개 테이블에 적용했던 파티셔닝을 되돌린 이력입니다 — 그 마이그레이션의 down() 은 "파티션 복원은 데이터 재배치가 필요해 자동 롤백 불가"라고 명시하므로, 파티셔닝을 다시 도입할 때는 이 파일을 그대로 재실행하는 방식이 아니라 새 마이그레이션으로 설계해야 합니다.

Enum

Enum backing case 수 case
BoardOrderBy string 4 created_at, view_count, title, author
OrderDirection string 2 ASC, DESC
PostStatus string 3 published, blinded, deleted
ReplyDeletePolicy string 2 block, cascade
ReportReasonType string 9 abuse, hate_speech, spam, copyright, privacy, misinformation, sexual, violence, 외 1개
ReportStatus string 5 pending, review, rejected, suspended, deleted
ReportType string 2 post, comment
SecretMode string 3 disabled, enabled, always
TriggerType string 6 report, admin, system, auto_hide, user, cascade

TriggerType(6 case: report/admin/system/auto_hide/user/cascade)은 "이 콘텐츠가 왜 지금 상태가 됐는가"를 기록하는 감사(audit) 축입니다 — 예를 들어 게시글 블라인드가 report(신고 처리 결과)인지 admin(관리자 직접 조치)인지에 따라 post_action/report_action 두 알림이 갈라집니다(§확장점 "알림 정의" 참고). cascade 는 부모(게시글)가 지워질 때 자식(댓글)이 함께 지워진 경우이며, ReplyDeletePolicy(block/cascade)가 게시판별로 부모 삭제 시 자식을 막을지 함께 지울지를 결정합니다 — 이 정책과 TriggerType::Cascade 는 같은 흐름의 서로 다른 절반(정책 설정 vs 결과 기록)입니다.

Repository

클래스 종류 설명
AttachmentRepository 구현 게시판 첨부파일 Repository 구현체
AttachmentRepositoryInterface 인터페이스 게시판 첨부파일 Repository 인터페이스
BoardRepository 구현 게시판 Repository
BoardRepositoryInterface 인터페이스 게시판 Repository 인터페이스
BoardStatRepository 구현 게시판 일별 집계 Repository
BoardStatRepositoryInterface 인터페이스 게시판 일별 집계 Repository 인터페이스
BoardTypeRepository 구현 -
BoardTypeRepositoryInterface 인터페이스 -
CommentRepository 구현 댓글 Repository
CommentRepositoryInterface 인터페이스 댓글 Repository 인터페이스
PostRepository 구현 게시글 Repository
PostRepositoryInterface 인터페이스 게시글 Repository 인터페이스
ReportRepository 구현 신고 Repository
ReportRepositoryInterface 인터페이스 신고 Repository 인터페이스
UserNotificationSettingRepository 구현 사용자 알림 설정 Repository
UserNotificationSettingRepositoryInterface 인터페이스 사용자 알림 설정 Repository 인터페이스

BoardStatRepository(일별 집계)는 별도 Repository 로 분리돼 있습니다 — sirsoft-board:aggregate-stats 스케줄이 매시간 board_stats 를 갱신하는데, 이 집계 쿼리를 PostRepository/CommentRepository 에 섞으면 대시보드 조회 경로와 실시간 CRUD 경로가 같은 클래스 안에서 뒤엉킵니다. 새 Repository 를 추가할 때는 반드시 인터페이스를 함께 만들고 CoreServiceProvider(또는 이 모듈의 서비스 프로바이더)에서 바인딩합니다 — Service 가 구체 클래스를 직접 타입힌트하면 안 됩니다.