번들 확장 README 제목이 manifest 확장명 그대로(「게시판」)라 그 문서만 연
사람이 그누보드7의 확장인지, 모듈인지 템플릿인지 알 수 없었다.
README · AGENTS.md · docs/README.md 진입 문서 60개의 제목을
「그누보드7 {확장명} {유형}」 으로 바꾸고, 조립 규칙을 ExtensionInventory::docTitle
한 곳에 두어 골격 생성기와 계약 테스트가 같은 헬퍼를 쓰게 했다.
확장명이 이미 유형으로 끝나면 겹쳐 붙이지 않는다.
공개 규정에는 번들 확장 전용 관례임을 주어로 명시하고 제3자 확장에는
요구하지 않는다는 문장을 두었다. 언어팩은 README 가 없어 대상 밖이다.
15 KiB
그누보드7 마케팅 동의 플러그인 — 에이전트 가이드
이 문서는 이 플러그인을 수정하는 에이전트·확장개발자를 위한 것입니다. 도입 검토·운영 관점은 README.md 를 보세요.
TL;DR (5초 요약)
1. 유형: 플러그인 (sirsoft-marketing) — 회원의 마케팅 수신 동의를 항목별로 받고 이력을 남긴다. 자기 화면 없이 코어 회원 화면에 조각 5개를 주입
2. 확장 방식: 동의 상태 변화를 알리는 발행 훅 4개(`user.consent_changed` / `subscribed` / `unsubscribed` / `filter_consent_data`). 동의 항목 추가는 코드가 아니라 `channels` 설정
3. 건드리면 안 되는 것: 항목마다 컬럼 추가(EAV 구조가 전제), 이력 없는 상태 갱신, 코어 User 모델·컨트롤러 직접 수정, 회원 삭제 정리를 CASCADE 에 위임
4. 작업 위치: `plugins/_bundled/sirsoft-marketing` — 활성 디렉토리 직접 수정 금지
5. 반영: `php artisan plugin:update sirsoft-marketing --force`
1. 이 확장은 무엇인가
회원의 마케팅 정보 수신 동의를 항목별로 받고, 그 동의·철회 이력을 남기는 플러그인입니다. 이메일·SMS 같은 수신 채널을 운영자가 자유롭게 추가할 수 있고, 제3자 제공 동의·정보 공개 동의처럼 채널이 아닌 항목도 같은 구조로 다룹니다.
자기 화면이 없습니다. 관리자 설정 화면 하나를 빼면 이 플러그인의 UI 는 전부 다른 화면에 끼워 넣는 조각입니다 — 회원가입 폼·회원 상세·회원 수정 폼·마이페이지 프로필에 동의 항목이 나타나는 것이 그것입니다. 코어 회원 화면을 고치지 않고 동의 항목을 늘리기 위한 구조입니다.
설계 원칙 셋:
- EAV 구조로 항목을 데이터화한다. 동의 항목마다 컬럼을 만들면 항목을 늘릴 때 스키마
변경이 필요합니다. 대신
user_marketing_consents한 테이블에consent_key별 행을 두어, 채널 추가가 설정 변경만으로 끝나게 했습니다. - 코어 회원 흐름에 훅으로 붙는다. 구독 훅 11개가 전부 코어 것입니다 — 가입·생성·수정·
삭제·조회 각 지점의 검증 규칙과 데이터에 동의 항목을 얹습니다. 코어
User모델이나 회원 컨트롤러를 고치지 않습니다. - 철회는 동의만큼 쉬워야 한다. 마이페이지 조각이 항상 노출되고, 동의·철회가 모두
user_marketing_consent_histories에 기록됩니다(행위·출처·IP). 동의를 받은 경로와 철회 경로가 대칭이 아니면 그 동의는 법적 근거로 쓸 수 없습니다.
의도적으로 하지 않는 것: 실제 발송(메일·SMS)·권한 선언·관리자 메뉴·프론트 액션 핸들러. 이 플러그인은 "누가 무엇에 동의했는가"만 답하고, 그 동의를 근거로 무엇을 보낼지는 발송을 담당하는 확장의 일입니다.
2. 디렉토리 지도
| 경로 | 역할 | 수정 시 필요한 절차 |
|---|---|---|
plugin.json |
manifest (버전 SSoT) | version 변경 시 package.json·package-lock.json·composer.json 동기화 |
plugin.php |
진입 클래스 (선언형 표면 SSoT) | 표면 변경 시 ext:docgen 재실행 + 코어 최소 버전 검토 |
src/Http/Controllers/ |
컨트롤러 | API 표면 변경 시 api:docgen 재실행 |
src/Http/Requests/ |
FormRequest (검증 SSoT) | 검증 규칙은 Service 가 아니라 여기에 둔다 |
src/Services/ |
비즈니스 로직 | Repository 인터페이스 주입 (구체 클래스 금지) |
src/Repositories/ |
데이터 접근 | 목록 쿼리는 컬럼 프루닝·정렬 화이트리스트 확인 |
src/Models/ |
Eloquent 모델 | 스키마 변경 시 마이그레이션 + 업그레이드 스텝 동반 |
src/Listeners/ |
훅 리스너 | Repository 경유 (Model·DB 파사드 직접 접근 금지) |
src/routes/ |
라우트 | 모든 라우트에 name() 필수 |
database/migrations/ |
마이그레이션 | 한국어 comment + down() 필수, 기설치본은 업그레이드 스텝으로 백필 |
upgrades/ |
업그레이드 스텝 | DB·설정 구조 변경 시 작성 (모듈/플러그인 전용) |
resources/layouts/ |
레이아웃 JSON | php artisan plugin:update sirsoft-marketing --force (빌드 불필요) |
resources/js/ |
프론트 엔트리·핸들러 | php artisan plugin:build → php artisan plugin:update sirsoft-marketing --force |
resources/extensions/ |
다른 확장 레이아웃에 주입하는 조각 | php artisan plugin:update sirsoft-marketing --force |
editor-spec.json |
레이아웃 편집기 스펙 | php artisan plugin:update sirsoft-marketing --force |
config/ |
확장 config | 설정 기본값은 settings 스키마와 어긋나지 않게 |
tests/ |
테스트 | 변경 범위만 필터 실행 |
CHANGELOG.md |
변경 이력 | 버전 상향 시 항목 추가 (미기재 시 버전 상향 불가) |
docs/ |
개발자 문서 | 표면 변경 시 php artisan ext:docgen 재실행 |
lang/ |
다국어 | 키 추가 시 ko·en 동시 반영 + 번들 ja 팩 동기화 |
3. 핵심 흐름
회원가입 시 동의 수집: 템플릿의 가입 폼이 user-marketing-register.json 조각을 그 자리에
받아 동의 체크박스를 그림 → 제출 → 코어 가입 흐름에서 core.auth.register_validation_rules
필터가 동의 항목의 검증 규칙을 더함 → 가입 완료 후 core.auth.register 액션에서
MarketingConsentListener::afterRegister() 가 동의 값을 user_marketing_consents 에 기록하고
이력을 남깁니다. 코어 가입 코드는 이 플러그인을 알지 못합니다.
동의 변경 → 이력 적재: 회원이 마이페이지에서, 또는 운영자가 회원 수정 화면에서 동의를
바꾸면 → core.user.filter_update_data / update_validation_rules 로 동의 필드가 흐름에
편입 → core.user.after_update 에서 리스너가 MarketingConsentService 로 위임 → 항목별
현재 상태(is_consented · consented_at · revoked_at · consent_count · last_source)를
갱신하고 이력 한 줄(action · source · ip_address)을 적재한 뒤
sirsoft-marketing.user.consent_changed 를 발행합니다.
채널 추가: 운영자가 설정 화면에서 채널을 더함 → PUT admin/channels →
core.plugin_settings.filter_save_data 필터가 저장 형태를 정규화 → channels 설정(JSON)에
반영. 다음 요청부터 가입 폼·마이페이지 조각에 그 항목이 나타납니다 — 스키마 변경도 배포도
필요 없습니다.
회원 삭제: core.user.before_delete 에서 그 회원의 동의 기록을 정리합니다. 코어 회원
삭제가 이 플러그인의 테이블을 알지 못하므로, 이 훅이 없으면 고아 행이 남습니다.
4. 확장점
| 확장점 | 수 | 상세 |
|---|---|---|
| 발행 훅 | 4개 | 발행 훅 |
| 구독 훅 | 11개 | 구독 훅 |
| 훅 리스너 | 1개 | 훅 리스너 |
| 레이아웃 확장 | 5개 | 레이아웃 확장 |
| 미들웨어 | 0개 | 미들웨어 |
| 브로드캐스트 채널 | 0개 | 브로드캐스트 채널 |
| 스케줄 | 0개 | 스케줄 |
| 알림 정의 | 0개 | 알림 정의 |
발행 훅 4종은 전부 동의 상태 변화를 다른 확장에 알리는 것입니다.
| 훅 | 언제 쓰는가 |
|---|---|
user.consent_changed |
동의 상태가 바뀔 때마다. 외부 마케팅 도구 동기화 지점 |
user.subscribed · user.unsubscribed |
동의/철회로 갈라진 지점. 수신 목록 추가·제거를 각각 배선할 때 |
filter_consent_data |
동의 데이터를 다른 확장이 가공해야 할 때 |
구독 방향이 이 플러그인의 성격을 더 잘 보여줍니다. 11개 구독이 전부 코어 회원·가입
흐름이며, 이것이 곧 "코어를 고치지 않고 회원 도메인에 필드를 더하는 방법" 의 선례입니다:
검증 규칙은 *_validation_rules 필터로, 저장 데이터는 filter_update_data 로, 응답 표현은
filter_resource_data 로, 생명주기 정리는 before_delete 로 붙습니다. 회원에 자기 필드를
더하려는 확장은 이 리스너 하나를 읽으면 됩니다.
레이아웃 조각 5개가 UI 전부입니다 — 가입 폼 · 회원 상세 · 회원 수정 폼 · 마이페이지 프로필 (보기/수정). 대상 화면이 그 자리(슬롯)를 없애면 조각은 오류 없이 사라지므로, 템플릿이나 코어 회원 화면을 업그레이드한 뒤에는 동의 항목이 여전히 보이는지 눈으로 확인합니다.
미들웨어·브로드캐스트 채널·스케줄·알림은 0개입니다.
5. 수정 시 동반 의무
_bundled에서만 수정하고php artisan plugin:update sirsoft-marketing --force로 반영- manifest version 상향 시
package.json·package-lock.json·composer.json동기화 + CHANGELOG 기재 - 스키마 변경 시 마이그레이션(한국어 comment +
down()) + 기설치본 백필용 업그레이드 스텝 - 발행 훅 추가·이름 변경 시
php artisan ext:docgen재실행 (구독하는 확장의 계약이 바뀝니다) - API 표면 변경 시
php artisan api:docgen --scope=plugin:sirsoft-marketing재실행 +docs/api/**갱신 - 레이아웃 JSON 변경 시 빌드 없이 update 만 — 신규 Tailwind 클래스는 빌드된 CSS 에 존재하는지 확인
- 다국어 키 추가 시 ko·en 동시 반영 + 번들 ja 언어팩 증분 동기화
- 동의 상태를 바꾸는 경로를 추가했다면 이력 적재(
user_marketing_consent_histories)가 같은 트랜잭션에 있는지 확인 - 코어 회원·가입 훅 11종 중 하나라도 이름·페이로드가 바뀌면 이 플러그인이 조용히 끊기므로, 코어 회원 흐름 변경 시 함께 확인
- 레이아웃 조각 5개는 대상 화면의 슬롯이 사라지면 오류 없이 빠진다 — 템플릿·코어 회원 화면 업그레이드 후 노출 확인
- 약관 페이지 slug 설정은
sirsoft-page모듈의 페이지를 가리킨다 (manifest 의존>=1.0.0) - 동의 항목을 늘릴 때는 설정만 바꾼다 — 마이그레이션이 필요해졌다면 EAV 구조를 벗어난 설계라는 신호
- 레이아웃·컴포넌트·
data_source를 건드렸다면docs/editor-spec.md의 동반 의무 표를 따라editor-spec.json을 함께 갱신 — 샘플이 없는data_source는 편집기 캔버스에서만 빈 화면이 되고 실제 화면은 정상이라 오류도 경고도 남지 않는다. 반영은php artisan plugin:update sirsoft-marketing --force
6. 금지 패턴
| 금지 | 올바른 사용 | 이유 |
|---|---|---|
동의 항목을 추가하려고 user_marketing_consents 에 컬럼을 더하기 |
channels 설정에 항목을 추가 (EAV 구조) |
항목마다 컬럼을 만들면 운영자가 채널을 늘릴 때마다 배포가 필요해진다 — 이 플러그인이 존재하는 이유가 사라진다 |
| 동의 상태만 갱신하고 이력을 남기지 않기 | 상태 갱신과 이력 적재를 같은 트랜잭션에 | 이력이 없는 동의는 법적 근거로 쓸 수 없다. "언제 어느 경로로 동의했는가"가 동의 그 자체다 |
| 마이페이지 철회 조각을 설정 토글로 감추기 | 동의 이력이 있는 회원에게는 항상 노출 | 동의를 받은 경로와 철회 경로가 대칭이 아니면 그 동의는 무효가 된다 |
코어 User 모델·회원 컨트롤러를 고쳐 동의 필드를 넣기 |
core.user.* 훅 11종 |
코어 수정은 업그레이드마다 충돌하고, 이 플러그인을 비활성화해도 필드가 남는다 |
| 회원 삭제 시 동의 기록 정리를 DB CASCADE 에 맡기기 | core.user.before_delete 구독 |
CASCADE 는 훅 발행·이력 처리를 건너뛰고, 아무 오류도 남기지 않는다 |
| 동의 여부를 근거로 이 플러그인이 직접 메일·SMS 를 보내기 | 발송은 발송 담당 확장이, 이 플러그인은 user.subscribed/unsubscribed 발행까지 |
동의 관리와 발송이 한 확장에 묶이면 발송 수단을 바꿀 때 동의 이력까지 흔들린다 |
| 약관 slug 를 코드에 리터럴로 박기 | 설정(*_terms_slug)을 읽어 페이지 모듈에서 조회 |
약관 문서는 운영자가 만들고 고치는 콘텐츠다 |
7. 테스트 실행
| 종류 | 개수 | 위치 |
|---|---|---|
| PHPUnit | 5개 | plugins/_bundled/sirsoft-marketing/tests |
| Vitest | 2개 | vitest.config.ts |
| Playwright | 0개 | — |
| 시나리오 매니페스트 | 0개 | — |
기저 TestCase: tests/PluginTestCase.php — 확장 테스트는 이 클래스를 상속합니다 (Tests\TestCase 직접 상속 금지).
# PHPUnit (변경 범위만) (Bash)
php vendor/bin/phpunit plugins/_bundled/sirsoft-marketing/tests --filter='<대상클래스>'
# Vitest (확장 디렉토리에서) (PowerShell)
cd plugins/_bundled/sirsoft-marketing && powershell -Command "npm run test:run -- <대상>"
무필터 전체 실행은 금지되어 있습니다 — 변경 범위에 걸리는 대상만 지정해 실행합니다.
8. 문서 목차
| 문서 | 내용 | 상태 |
|---|---|---|
| docs/README.md | 문서 통합 목차와 실측 집계 | ✅ |
| docs/architecture.md | 설계 의도·계층 지도·디렉토리 맵 | ✅ |
| docs/extension-points.md | 발행/구독 훅·미들웨어·채널·스케줄 | ✅ |
| docs/data-model.md | 모델·소유 테이블·마이그레이션·Enum | ✅ |
| docs/settings.md | 설정 스키마·권한·메뉴·라우트·의존 관계 | ✅ |
| docs/frontend.md | 레이아웃·액션 핸들러·전역 진입점·에셋 | ✅ |
| docs/editor-spec.md | 레이아웃 편집기에 선언한 팔레트·컨트롤·샘플 데이터 | ✅ |
| docs/api/ | API 레퍼런스 (엔드포인트별 파라미터·응답 필드) | ✅ |
| CHANGELOG.md | 변경 이력 | ✅ |