파일만 올리면 돌아갑니다. 속은 현대적으로.
Docker 한 줄, 또는 FTP 업로드로 설치하는 오픈소스 CMS · Next.js + NestJS + PostgreSQL
홈페이지 · 설치 · 플러그인 개발 · 테마 개발 · 아키텍처
English — what Brick is, in one screen
Brick is a self-hosted open-source CMS that runs when you just upload the files. Modern stack (Next.js · NestJS · PostgreSQL) with the install story of classic PHP CMSes: Docker one-liner, or FTP upload plus a browser install wizard — no build step on the server.
- Bundled: page builder, boards (Gnuboard-class), members, media, menus, search, two themes (light/dark), eight plugins (board · shop with Toss payments · messages · points · polls · helpdesk · site tools), Gnuboard migration tool.
- Extensible without builds: plugins and themes are ZIPs applied at runtime; admin screens are generated from declarations.
- Operable: auto DB migrations on boot,
update.mjs(download · SHA-256 · swap · rollback) ordocker compose pull, update notice on the dashboard, 2,700+ end-to-end smoke checks in CI.
curl -O https://raw.githubusercontent.com/bonjin-app/brick/main/docker-compose.yml
echo "BRICK_SECRET=$(openssl rand -base64 32)" > .env
docker compose up -d # then open http://localhost:3000Docs are in Korean; the code, ADRs and this README's structure are the map. Issues and PRs in English are welcome.
오래 살아남은 CMS 들의 공통점은 언어가 좋아서가 아니라 파일을 올리면 그냥 돌아갔기 때문입니다. 현대 스택은 강력하지만 설치가 어려워졌습니다. Brick은 그 편의성을 되찾으면서 현대적인 기반을 갖추려는 프로젝트입니다.
철학: 설치에 필요한 기술을 사용자에게 노출하지 않는다.
| 기존 PHP 게시판 | WordPress | Brick | |
|---|---|---|---|
| 언어 | PHP | PHP | TypeScript |
| DB | MySQL | MySQL | PostgreSQL |
| 설치 | FTP 업로드 | FTP 업로드 | Docker 한 줄 · 또는 FTP 업로드 + 웹 설치 |
| 업데이트 | 파일 수동 교체 | 관리자 클릭 | pull && up (자동 마이그레이션) |
| 플러그인 | 제한적 | ZIP 업로드 | ZIP 업로드 |
| 테마 | 스킨(PHP) | 테마(PHP) | 런타임 템플릿 (빌드 없음) |
| 게시판 | ✅ (핵심 기능) | 플러그인 | ✅ 첨부·권한·답변형·비밀글 |
| 포인트 | ✅ | 플러그인 | ✅ 원장 기반·만료·쇼핑몰 연동 |
| 캡차 | ✅ | 플러그인 | ✅ 코어 내장 (키 불필요) |
| 쪽지 | ✅ | ✗ | ✅ 차단·포인트 연동 |
| 스크랩 | ✅ | ✗ | ✅ |
| 페이지 빌더 | 없음 | Gutenberg | 코어 기본 제공 |
| 쇼핑몰 | 별도 설치 | WooCommerce | 플러그인 기본 동봉 |
| 상품 후기 | 쇼핑몰 패키지에 포함 | 플러그인 | 구매 검증 · 판매자 답변 |
| 방문자 집계 | 있음 (IP 원문 저장) | 플러그인 | IP를 해시로만 저장 |
| 팝업/배너 | ✅ | 플러그인 | ✅ 경로·기간·클릭 집계 |
| 소셜 로그인 | 플러그인 | 플러그인 | ✅ 코어 내장 (구글·카카오·네이버·깃허브 + OIDC 사내 SSO) |
| SSR / SEO | 기본 | 기본 | Next.js SSR + API 태그 캐시 |
| 타입 안전성 | 없음 | 없음 | 전 구간 strict |
⚠️ Brick은 순수 PHP 호스팅(Node 없음) 에서는 동작하지 않습니다. Docker, 또는 Node.js를 지원하는 호스팅(cPanel/Plesk의 "Node.js App", VPS)이 필요합니다. 그 환경이라면 FTP로 올려서 브라우저에서 설치하는 방식이 동작합니다 → 설치 가이드
# 1. compose 파일 받기
curl -O https://raw.githubusercontent.com/bonjin-app/brick/main/docker-compose.yml
# 2. 시크릿 생성 (필수)
echo "BRICK_SECRET=$(openssl rand -base64 32)" > .env
# 3. 실행
docker compose up -dhttp://localhost:3000 접속 → 설치 마법사에서 사이트 이름과 관리자 계정만 입력하면 끝입니다.
DB 접속 정보는 묻지 않습니다 — compose가 이미 주입했습니다.
설치할 때 사이트 유형을 고르면 홈·페이지·게시판·메뉴가 통째로 만들어집니다.
| 유형 | 만들어지는 것 |
|---|---|
| 커뮤니티 | 홈(최신글 모아보기) · 소개 · 게시판 3개 · 메뉴 |
| 쇼핑몰 | 홈(상품 목록+공지) · 이용 안내 · 공지사항 · 쇼핑몰 메뉴 |
| 회사 홈페이지 | 홈 · 회사 소개 · 서비스 · 공지 · 1:1 문의 |
| 빈 사이트 | 아무것도 — 처음부터 직접 |
만들어진 것은 전부 일반 페이지·메뉴라서 페이지 빌더에서 그대로 수정합니다.
빌드도 npm install 도 필요 없는 배포본을 올리고 브라우저에서 설치합니다.
# 배포본 만들기 (또는 Releases에서 내려받기)
bash scripts/build-release.sh
# → dist-release/brick-<버전>.tar.gz압축을 풀어 서버에 올린 뒤 server.js 를 실행하고, 브라우저에서 DB 정보를 입력하면 끝입니다.
data, uploads 에 쓰기 권한만 주면 됩니다.
자세한 내용은 설치 가이드를 참고하세요.
docker compose pull && docker compose up -dDB 마이그레이션은 컨테이너가 부팅할 때 스스로 적용합니다. 외울 명령이 없습니다. 자세한 내용: 업그레이드 가이드
사용자에게는 앱 컨테이너 하나와 PostgreSQL 하나로 보입니다.
사용자 / 검색엔진
│ :3000 (유일한 공개 포트)
┌──────▼──────┐
│ Next.js │ SSR · 관리자 UI
└──────┬──────┘
│ :3001 (내부 전용)
┌──────▼──────────────────────┐
│ Brick Runtime │
│ NestJS · 단일 프로세스 │
│ ┌───────────────────────┐ │
│ │ Core HookBus │ │
│ │ Plugins 동적 로드 │ │
│ │ Themes 런타임 템플릿 │ │
│ └───────────────────────┘ │
└──────┬──────────────────────┘
│
PostgreSQL ← 유일한 필수 의존성
(Redis · S3는 선택 — 없으면 PG 기반 기본 구현)
핵심 설계 결정 77건은 docs/architecture.md에 ADR로 기록되어 있습니다.
-
설치 마법사 — 사이트명 + 관리자 계정만 입력
-
인증 — argon2id, 세션(DB에는 토큰 해시만), 브루트포스 방어
-
회원 — 가입 / 프로필 / 권한 관리(관리자·운영자·회원 — 운영자마다 관리 화면 범위: "이 운영자는 주문만") / 계정 정지
-
약관 동의 — 이용약관·개인정보·광고수신, 관리 화면에서 개정 발행(기존 버전은 고치지 않는다 — 누가 어느 버전에 동의했는지가 증거다), 개정 시 재동의, 동의 이력(IP는 해시로만). 선택 항목을 거부해도 가입된다(강제는 위법), 만 14세 미만 가입 제한
-
이메일 인증 — 단회성 토큰, 주소 변경도 인증 후 반영
-
회원 탈퇴 — 개인정보 즉시 파기 + 주문은 법정 보존(전자상거래법 5년). 플러그인이 자기 데이터를 지운다, 탈퇴 전 손실 안내, 마지막 관리자 보호
-
마이페이지(/account) — 내 정보·생일·수신 동의 · 비밀번호 변경(현재 확인, 다른 세션 해제) · 접속 기기 관리 · 탈퇴까지 화면으로 (헤더의 이름에서 진입)
-
휴면 계정 — 장기 미접속 대상 조회(자동 전환 안 함 — 사전 통지 의무), 로그인 시 해제
-
프로필 — 프로필 이미지(글·댓글·헤더에 표시, 없으면 이니셜 원) · 닉네임 변경 주기(사이트 설정) · 글쓴이 이름을 누르면 공개 프로필 카드(가입일·글·댓글 수, 개인정보 없음) · 이메일 변경(새 주소 인증 링크를 열어야 교체) · 회원별 관리자 메모(운영자만, 탈퇴 시 파기)
-
페이지 배치 편집기 — 저장하지 않은 내용을 실제 테마로 미리 보며(데스크톱·태블릿·모바일) 누른 블록을 고르고, 구성 트리에서 끌어다 놓아 다단 레이아웃 안에 배치 · 미리보기의 제목·문단을 두 번 눌러 그 자리에서 고치기 · 고른 블록의 이름표를 끌어 미리보기 안에서 옮기기 · 키보드로도 같은 배치 · 되돌리기 · 속성 UI 자동 생성 · SEO 설정 (배치 편집기)
-
코어 블록 — 제목 · 문단 · HTML · 이미지 · 다단 레이아웃 · 여백 · 랜딩 재료(사진 히어로 · 이미지+글 분할 · 숫자 강조 · 고객 후기 · 이미지 갤러리 · 특징 카드 · 행동 유도 배너 · FAQ 아코디언 · 알림 · 구분선)
-
미디어 라이브러리 — 업로드(확장자 화이트리스트) / 목록 / 삭제. 이미지는 자동으로 최적화됩니다: 2400px 이내로 축소, EXIF(촬영 위치) 제거, 목록용 WebP 썸네일. 프로필 이미지는 256px. 플러그인도 같은 처리를 쓸 수 있습니다(
ctx.images) -
메뉴 편집 — 헤더 내비게이션, 테마에 자동 반영
-
검색 — 페이지 전문 검색 (발췌문 포함)
-
플러그인 시스템 — ZIP 업로드 → 활성화 → 재시작 없이 라우트·블록 동작
-
테마 시스템 — 빌드 없는 런타임 템플릿, ZIP 업로드 즉시 적용. 파일을 고치면 캐시가 자동으로 갈린다(버전 올리는 것을 기억하지 않아도 된다)
-
동봉 테마 둘 — 기본(둥근 모서리·산세리프) 과 Editorial(종이색·명조 제목·가운데 제호·괘선). 같은 페이지가 두 인상으로 그려진다 — 블록은 클래스만 말하고 모양은 테마가 정한다는 계약의 실증.
-
라이트·다크 두 벌 — 토큰 한 벌을 더 두면 다크가 따라온다.
-
Tailwind v4 · 반응형 — 동봉 테마 CSS 와 관리 화면은 Tailwind 로 쓰고 컴파일 결과를 동봉한다(런타임 빌드 없음). 공개 화면·마이페이지·관리 화면(사이드바 → 드로어, 표 → 가로 스크롤)이 375px 부터 동작한다. 손님이 헤더에서 전환하고 그 선택이 기억된다. 스티키 헤더 · 모바일 드로어 · 현재 메뉴 표시 · 스킵 링크 · 인쇄 스타일 · Pretendard 웹폰트 · SVG 아이콘 스프라이트 (플러그인·블록은 이름만, 모양은 테마가). 로그인·마이페이지·관리 화면도 같은 팔레트
-
화면 제목은 블록이 정한다 — 글 상세의 문서 제목이 글 제목이다. 공유 링크 미리보기와 검색 결과에 제대로 나온다
-
게시판 플러그인 — 다중 게시판 / 등급별 권한 / 분류 / 답변형(계층) / 비밀글 / 첨부파일(권한·카운트) / 추천·비추천 / 비회원 글쓰기 / 검색 / 도배 방지 / RSS
-
게시판 화면 일체 — 목록·상세·글쓰기·수정을 페이지 하나로 (위지윅 에디터 포함). 목록 스킨 3종(표·갤러리·웹진, 썸네일 자동) · 이전글/다음글 · 공유(OS 공유창·링크 복사·X·페이스북)·인쇄 · 에디터 이미지 삽입(회원) · 글쓰기 자동 임시저장 · 새 글/댓글 알림 메일
-
게시판 그룹 — 목록 소제목으로 묶고 그룹 단위 읽기 권한(게시판과 그룹 중 더 엄격한 쪽) · 글의 관련 링크 2개(http(s)만, nofollow)
-
관리 일괄 작업 — 선택 삭제 · 게시판 이동(답변 스레드 통째로) · 복사 · 공지 지정.
bulkActions계약으로 모든 플러그인 관리 화면이 얻는다 -
저장형 XSS 방어 — 사용자 HTML을 허용 목록으로 새니타이즈 (20개 벡터 검증)
-
캡차 — 자체 SVG, API 키 불필요. 1회용 토큰·위조 방어. 비회원 글쓰기·가입에 적용
-
모더레이션 — 금지 단어(글·댓글·쪽지·이름, 태그 끼우기 우회 차단, 걸린 단어 안내) · 운영진 사칭 이름 차단(admin·관리자 …는 기본) · 가입 금지 이메일 도메인 · 접속 차단 IP(IPv4·CIDR·IPv6, 자기잠금 방지) · 게시판 분류 필수
-
쪽지 플러그인 — 받은/보낸함, 차단, 도배 방지, 포인트 차감. 관리자도 내용을 볼 수 없다
-
스크랩 — 게시글 북마크 + 내 스크랩 목록 화면
-
쇼핑몰 플러그인 — 상품·옵션·재고 / 장바구니(회원·비회원) / 주문서(무통장, 비회원 주문)·주문 조회(회원 목록·비회원 번호 조회) / 주문·상태머신 / 쿠폰 / 배송비 / 매출 통계
-
상품 후기 — 구매한 사람만 작성(주문 검증), 별점·사진, 판매자 답변, 표시/숨김, 평점 자동 집계
-
상품 문의 — 공개/비밀 문의(비밀글은 서버에서 가림), 판매자 답변, 답변 상태
-
상품 이미지 갤러리 — 대표 + 추가 이미지 20장, JSON-LD 평점(AggregateRating)까지 SEO 반영
-
결제 — PG 추상화 + 토스페이먼츠 플러그인, 금액 위조·중복 승인 방어, 부분 환불. 주문서는 등록된 게이트웨이를 그대로 보여주고(키를 넣지 않은 PG 는 빠진다), 카드처럼 그 자리에서 승인이 필요한 수단은 결제창까지 이어진다 (ADR-112)
-
취소 · 반품 · 교환 — 상품 단위 부분 처리, 할인 안분 환불, 재고 복원, 청약철회 7일(전자상거래법), 사유별 반송비 부담(불량은 사업자), 수거·입고 단계 — 물건을 받기 전에는 환불되지 않는다. 손님이 주문 상세에서 직접 신청하고(비회원도 — 청약철회는 회원 여부와 무관한 권리다), 신청 전에 반송비를 누가 내는지 알려준다
-
사업자정보 표시 — 전자상거래법 제13조 표시 의무. 사업자등록번호 체크섬 검증, 테마 푸터 자동 렌더, 빠진 항목 안내
-
위시리스트 · 최근 본 상품 — 비회원도 사용, 로그인 시 이어받기, 품절 표시. 담아둔 상품을 보는 화면과 최근 본 상품 위젯을 블록으로 제공
-
지역별 배송비 — 제주·도서산간 우편번호 구간. 무료배송이어도 지역비는 붙는다
-
비밀번호 재설정 — 메일 발송(SMTP), 단회성 토큰, 이메일 열거 방지
-
소셜 로그인 — 구글 · 카카오 · 네이버 · GitHub + 사내 SSO(표준 OIDC). 로그인 CSRF·계정 탈취 방어, 계정 연결/해제, 관리 화면에서 키 설정
-
방문자 집계 — 오늘·어제·최고·전체 + 유입 경로. IP를 원문으로 저장하지 않는다
-
팝업 · 배너 — 노출 경로·기간, 다시 보지 않기, 노출·클릭 집계, 본문 새니타이즈
-
1:1 문의 — 비공개 문의 + 답변 + 대화 이어가기, 비회원 문의(조회 비밀번호), 담당자 지정, 답변 메일 알림. 남의 문의는 404 (403은 존재를 알려준다)
-
FAQ — 분류·검색·조회수·"도움이 되었나".
<details>로 JS 없이 접히고 검색엔진은 답변을 다 읽는다 -
설문조사 — 복수 선택 · 기간 · 기타 의견. IP를 해시로만 저장해 비회원 중복 투표 방지, 결과 공개 시점(항상/투표후/종료후) — 숨길 때는 득표 수를 응답에 넣지 않는다
-
sitemap.xml · robots.txt — 플러그인이 자기 URL을 제공. 조각으로 나눠 십만 건 규모 대응. 비밀글·비공개 게시판·임시 상품은 제외
-
최신글 모아보기 — 메인 화면에 여러 게시판을 나란히. 쿼리 한 번으로
-
그누보드5 · 영카트5 데이터 이전 — 덤프 파일로 회원·게시판·게시글·댓글·포인트와 상품·분류·옵션·주문을 옮긴다. 분류 계층 복원, 주문 상태 매핑, 금액 불일치는 할인으로 흡수. 그누보드 비밀번호로 그대로 로그인되고 첫 로그인에 argon2로 승급된다. 리허설이 먼저 — 무엇이 옮겨지고 무엇이 안 옮겨지는지 보여준다
-
통합검색 · 검색 분석 — 게시글·상품·페이지를 권한 필터와 함께 한 화면에서. 공개 검색 화면(/search)은 페이지 없이도 동작하고 테마 헤더에 검색창이 있다. 인기 검색어와 결과 0건 목록(가장 먼저 손볼 것), 치환 규칙
-
회원 단체메일 — 수신 동의자만 · (광고) 표기 자동 · 발송 직전 동의 재확인 — 운영자가 위법할 수 없게
-
2단계 인증 · 세션 관리 — RFC 6238 TOTP · 복구 코드 · 기기 목록 · 원격 로그아웃 · 위험 작업 재인증(개인정보 열람·대량 발송) · 관리자 IP 제한(자기잠금 방지)
-
회원 등급 — 최근 3개월 순매출(반품 차감)로 자동 배정, 등급별 상시 할인
-
쿠폰 고도화 — 1인당 한도 · 첫 구매 전용 · 등급 전용 · 발급형 쿠폰함 · 생일 자동 지급(월·일만 수집)
-
기획전 — 상품 묶음 진열 · 기간 · 종료 시 404 대신 "종료" 안내
-
정기결제 (정기배송) — 카드는 PG에(빌링키만 저장) · 청구액 가입 시점 고정 · 해지는 항상 즉시
-
판매 리포트 · 부가세 자료 — 기간·상품·분류별 매출, 부분 환불 반영, CSV
-
원클릭 업데이트 — Ed25519 서명 검증 · 키 고정(TOFU) · 다운그레이드 거부
-
플러그인 레지스트리 — 정적 JSON 목록에서 클릭 설치, 신뢰는 서명이 결정
-
OpenAPI 문서 —
/api/docs자동 생성 — 플러그인을 켜고 끄면 문서도 함께 변한다 -
개발 템플릿 —
npm create brick-plugin하나로 동작하는 예제 플러그인 -
감사 로그 — 관리 동작을 행위자·IP와 함께 기록 (180일 보관)
-
관리자 대시보드 — 첫 화면이 오늘의 방문자·주문·글·문의를 보여준다. 숫자는 각 플러그인이 카드로 등록하고(registerDashboardCard), 느리거나 죽은 카드는 그 카드만 오류로 표시된다
-
포인트 플러그인 — 활동 적립과 쇼핑 적립금을 하나로. 가입·글쓰기·댓글·후기·구매 적립, 쇼핑몰 결제 시 사용, FIFO 소비·만료, 원장 감사 추적. 내 포인트 내역 화면(적립·사용·유효기간·소멸 예정)과 잔액 위젯
-
플러그인 관리 화면 자동 생성 — 필드 스키마만 선언하면 코어가 CRUD UI를 만든다 (빌드 불필요)
-
렌더 캐시 — 태그 기반 자동 무효화 (Redis 불필요)
-
자동 마이그레이션 — 부팅 시 스키마 자동 최신화
-
웹 설치 마법사 — DB 정보 입력 → 설정 파일 자동 생성 (환경변수 불필요)
-
빌드 없는 배포본 — FTP로 올려서
node server.js만으로 실행 -
백업 / 복원 CLI — pg_dump 기반
-
헬스체크 —
/healthz·/readyz -
다국어 —
site.locale하나로 공개 화면·관리 화면·플러그인이 전부 함께 바뀝니다(ko/en). 플러그인 공개 문자열은ctx.t+ 동봉 카탈로그, 관리 선언 라벨은 gettext 방식(원문이 곧 키 —locales/en.json에"주문": "Orders"). 빠진 키는 ko 폴백 + 로그
SDK npm 공개. 첫 릴리스는 v0.1.0 (알파)으로 나갔습니다. 1.0 완성형 정의 네 문장의 코드 요건은 전부 충족됐습니다 — 정의와 순서는 로드맵에 있습니다.
플러그인 — 사전 빌드된 JS + manifest + SQL 마이그레이션을 ZIP으로 배포합니다. 서버는 빌드하지 않습니다.
import { definePlugin } from "@brick/plugin-sdk";
export default definePlugin((ctx) => {
// REST API → /api/plugins/my-plugin/items/:id
ctx.registerRoute("GET", "/items/:id", async (req) => ({ id: req.params.id }));
// 페이지 빌더 블록 (서버 렌더 → 검색엔진에 그대로 노출)
ctx.registerBlock({
name: "greeting",
displayName: "인사말",
propsSchema: { type: "object", properties: { name: { type: "string", title: "이름" } } },
render: async (props) => `<p>안녕하세요, ${props.name}님</p>`,
});
// 코어 이벤트 구독
ctx.hooks.onAction("board.post.created", "my-plugin", async (post) => { /* ... */ });
return {};
});테마 — 빌드가 필요 없습니다. 템플릿 문법은 4개뿐입니다.
<!doctype html>
<html lang="ko">
<head>
<title>{{ pageTitle }}</title>
<style>{{{ themeTokens }}}</style>
</head>
<body>
<nav>{{#each menu}}<a href="{{ url }}">{{ label }}</a>{{/each}}</nav>
<main>{{{ content }}}</main>
</body>
</html>요구사항: Node.js 20.11+ · pnpm 9 · PostgreSQL 16+ (또는 Docker)
git clone https://github.com/bonjin-app/brick.git
cd brick
pnpm install
cp .env.example .env # DATABASE_URL, BRICK_SECRET 설정
pnpm build
pnpm dev # web(:3000) + api(:3001)E2E 스모크 테스트 — 실제 PostgreSQL과 실제 서버 프로세스로 검증합니다 (총 4,724개 항목):
| 수트 | 항목 | 무엇을 못박는가 |
|---|---|---|
smoke-test.sh |
160 | 설치 · 인증 · 페이지 · 이전 버전(리비전)·되돌리기 · 예약 발행 · 미디어 · 플러그인 로드 · 공유 이미지·immutable 캐시·압축 · 이미지 최적화·EXIF 제거 · 썸네일 백필 · og 1200×630 자동 변형 · 업로드 immutable·ETag·304 · DB 순단 회복 · 중단된 작업을 되찾고, 살아 있는 작업은 빼앗지 않는다 · 잠금은 잡은 연결에서 풀린다 · 요청 제한은 버킷마다 자기 시간 창을 지키고 세기가 원자적이다 · 대기 중인 주기 작업은 하나, 끝내 실패하면 주인에게 알린다 |
smoke-member.sh |
168 | 약관 강제 · 동의 이력 · 개인정보 파기 · 탈퇴가 2단계 인증·알림함까지 파기한다 · 주문 보존 · 프로필 이미지·공개 카드·닉네임 변경 주기 · 관리자 메모·이메일 변경 · 가입 오류 칸 안내 · 인증 링크 화면 · 보관 기간이 지난 기록이 실제로 지워진다(검색어·인증 토큰·감사·끝난 큐 작업) · 이메일 변경은 재인증을 요구하고, 옛 주소에 알리고, 세션을 끊는다 |
smoke-helpdesk.sh |
125 | 비회원 문의는 대입으로 열리지 않는다(문의별·IP별) · 문의 열거 방지 · 비회원 조회 · 사이트맵 유출 · 설정 화면(비회원 문의 스위치 · GET/PUT 모양 일치) · 비회원이 번호·비밀번호로 자기 문의를 여는 화면 |
smoke-migrate.sh |
206 | 옮겨 온 옛 미입금 주문은 자동 취소가 건드리지 않는다 · 덤프 파싱 · 레벨 매핑 · 비밀번호 보존 · 영카트 상품·주문 · 멱등성 · 문서가 말하는 크기의 덤프를 실제로 받는다(그 예외는 이전 경로에만) · 여분 필드·링크·첨부·그룹·내용관리·추천·위시리스트·사업자정보까지 · 게시판·그룹 관리자(bo_admin·gr_admin) 이전, 없는 아이디는 알린다 · 댓글 달린 글의 삭제·수정 한도(bo_count_delete·modify) 이전 |
smoke-returns.sh |
115 | 할인 안분 · 이중 재고 복원 방어 · 청약철회 기간 · 비회원 청약철회 · 신청 뒤 상태가 손님에게 보인다 · 비회원도 자기 신청을 물릴 수 있다 · 주문 전체를 돌려받으면 구매 적립을 거둔다 |
smoke-storefront.sh |
208 | 사업자번호 체크섬 · 위시리스트 격리 · 지역비 실수령 · 샘플 상품 · NEW·BEST 뱃지 · 정렬 · 쪽나눔 · 가격대·품절 필터 · 주문서가 지역 추가 배송비를 미리 보여준다 · 상품 화면에서 위시리스트에 담을 수 있다 · 정기배송 신청 화면 · 회원 메뉴의 결제 카드·정기배송이 실제로 열린다 · 주소 검색(누르기 전에는 외부 스크립트를 부르지 않는다·끌 수 있다) |
smoke-poll.sh |
103 | 중복 투표 · IP 해시 · 결과 공개 시점 · 집계 오염 · 목록→개별 라우팅 |
smoke-mailing.sh |
117 | 이어 보내도 발송 수가 누적되고, 작업이 끝내 실패하면 캠페인이 풀리고 대시보드가 알린다 · (광고) 강제 표기 · 동의자만 발송 · 발송 직전 동의 재확인 · 실제 발송 내용 · 수신거부 헤더(One-Click)와 그 주소가 정말 POST 를 받는가 |
smoke-reports.sh |
145 | 부분 환불 차감 · KST 날짜 경계 · 상품별·주문별 합 일치 · 추천에서 반품·미공개 제외 · 운영자 환불·결제 화면의 부분 환불도 매출에서 빠진다 |
smoke-tax.sh |
126 | 카드 이중 발급 거부 · 금액 분해 합 일치 · 면세 스냅샷 · 반품 시 증빙 취소 · 손님이 신청할 수 있는가(주문 상세가 폼을 낼지 정한다) · 비회원도 토큰으로 받는다 · 운영자가 환불한 주문은 부가세 매출에서 빠진다 · 운영자가 주문을 환불로 바꿔도 현금영수증을 취소한다 |
smoke-account-security.sh |
126 | RFC 6238 벡터 · 코드 재사용 차단 · 위험 작업 재인증(세션 단위) · IP 제한 자기잠금 방지 · 2FA 강제 설정 자기잠금 방지(등록하지 않은 관리자는 켤 수 없다) · 현재 비밀번호 대입은 막힌다 — 비밀번호 변경·탈퇴·재인증이 한 몫(15분 10번)을 나눠 쓰고, 맞히면 다시 센다 |
smoke-payments.sh |
158 | 결제창에 있는 사이 주문이 취소되면 승인된 돈을 돌려준다 · 스텁 PG로 실제 나가는 금액 검증 · 멱등키 · 개인결제가 매출에 포함되는가 · 개인결제 손님도 결제 안내를 받는가 · PG 가 닿지 않아도 손님에게 내부 오류를 보여주지 않는다 · 스텁 포트가 막히면 옆으로 비킨다 |
smoke-portone.sh |
197 | 포트원 V2 — 결제는 조회로 확인(PAID·금액), 결제 ID 가 이 주문의 것인지, 부분환불에 취소 전 잔액을 실어 이중 환불을 막는다, 실패·다른 상점 거절 · 가상계좌 — 발급은 입금 대기(계좌·기한을 완료 화면·주문 조회·안내로), 입금은 웹훅으로(내용은 믿지 않고 다시 조회), 결제 미완료 규칙이 입금 전에 지우지 않고 기한이 지나면 취소, 취소 뒤 들어온 입금은 환불 · 창을 닫은 카드 결제도 웹훅이 확정 · 조회가 잠시 실패해도 입금 대기를 잃지 않는다 · 관리자가 결제된 주문을 취소·환불로 바꾸면 돈을 먼저 돌려준다(전이 사전 확인·PG 거절 시 상태 유지) · 입금된 가상계좌는 손님 계좌로 환불 · 손님이 취소·반품 신청서에 환불 계좌를 적고, 돌려준 뒤·탈퇴하면 지운다 · 정기결제(빌링키) — 이 회원의 고객 식별자로 발급된 빌링키만 등록(남의 등록 결과로는 못 붙인다), 회차 결제 ID 는 정해진 값이라 카드사 거절·응답 유실·조회 실패에도 이중 청구도 "긁혔는데 취소된 주문" 도 없다, 탈퇴하면 정기배송 해지·빌링키 삭제 · 청구 응답보다 웹훅이 먼저 와도 한 거래(회차·가입 첫 결제 모두) · 가입 첫 결제 결과를 모르면 스윕이 같은 결제 ID 로 이어서 끝낸다(결제됐으면 구독을 열고, 거절되면 주문 취소·재고 복원·해지) · 시크릿 비노출 |
smoke-identity.sh |
151 | 본인인증 — 인증 ID 는 서버가 만들어 회원에게 묶고(남의 인증 ID 로 내 계정을 인증할 수 없다), 결과는 포트원에 직접 묻는다 · 이름·CI 원문·전화번호는 남기지 않는다(출생 연도·CI 의 HMAC 만) · 청소년보호법의 연 나이 경계 · 명의는 바뀌지 않는다 · 한 사람 한 계정(스무 계정이 같은 순간에 끝내도 하나) · 성인 상품은 확인 전에 보이지도(목록 19 표시·상세 안내·검색) 팔리지도(주문·정기배송) 않는다 · 탈퇴하면 파기 · 인증창 요금 한도 · 회원 본인인증 필수(쓰기만 막고, 인증 수단이 없으면 강제하지 않는다) · 영어 사이트 · 가입 전 본인인증 — 인증을 마친 브라우저만 가입(쿠키로 묶고 다른 브라우저·회원 경로로는 못 가져간다), 만 14세 미만(만 나이, 생일 경계)·이미 가입한 사람은 계정 생성 전 거절, 인증은 한 번만·30분 안에, 두 창 동시 가입도 한 사람 한 계정, 소셜 가입 회원은 인증 전 쓰기 불가 · 관리자 → 본인인증 — 어디서 받나(사이트 설정·확장이 선언한 목적, 끄면 걷힌다)·최근 30일 사용량(날마다·합계 일치) · 사이트맵에 성인 상품 사진은 싣지 않는다 |
smoke-alimtalk.sh |
54 | 카카오 알림톡 — 알림마다 승인된 템플릿을 연결하고(원문은 알리고에서, 검수 전·중지·채울 수 없는 변수는 거절), 스텁이 템플릿과 다른 본문을 거절하는 조건에서 실제로 나간다 · 변수·버튼 링크 채우기 · 연결 안 한 알림은 문자 · 알림톡 거절 시 문자 한 번 · 대체 문자는 문자를 켠 가게에서만 · 이메일 없이 전화번호만 적은 비회원도 안내를 받는다 · 키·번호 비노출 · 영어 사이트 |
smoke-notification-templates.sh |
39 | 운영자가 알림 문구(제목·본문·문자)를 고친다 · 기본 문구를 그대로 저장해 보낸 알림 = 고치지 않은 알림(글자까지) · 고친 문구가 알림함·메일·문자에 · 문자 문구를 따로 쓰면 단문(SMS) · 채울 수 없는 변수·여러 줄 제목 거절 · 미리보기 · 되돌리기 · 감사 로그 · 영어 사이트 |
smoke-admin-scopes.sh |
48 | 운영자 권한 범위 — "이 운영자는 주문만": 받은 화면이 쓰는 경로(목록·상태 변경·일괄 처리·선택지·가져오기)만 열리고 상품 가격·쿠폰·다른 플러그인은 403 · 화면 없는 관리 API(보고서·환불)는 플러그인 전체가 있어야 · 메뉴·화면 선언도 받은 것만 · 다시 로그인 없이 즉시 · 범위 없는 운영자는 그대로 · 관리자에겐 뜻 없음 · 운영자는 자기 범위를 못 넓힌다 · 감사 로그 · 영어 사이트 |
smoke-layout-editor.sh |
85 | 페이지 배치 편집기 — 저장하지 않은 초안을 실제 테마로 그리고 블록마다 트리 위치를 단다(빈 다단·모르는 블록도 눌러 고를 수 있게) · 초안은 맡긴 운영자만(다른 관리자·회원·손님 불가) · 공개 주소에 나가지 않는다 · 사이트와 같은 CSP·no-store·noindex · 공개 렌더에는 편집 표시 없음 · 모양이 틀린 트리는 저장 거절 · 트리 연산(옮기기·안으로 넣기·밖으로 빼기·복제·원본 불변)을 편집기와 같은 코드로 · 그 자리 글자 고치기 — 제목·문단에만 표시(공개 화면에는 없다), 스키마의 글자 속성만 받는다, 미리보기 스크립트가 실제로 읽히는지(문법) 확인 · 미리보기 안 끌어다 놓기 — 앞·뒤·빈 다단 안으로, 컨테이너 아닌 블록 안·자기 안쪽은 거절 · 목록의 한 칸·버튼 문구 고치기 — 원문 순서 줄(블록이 거른 줄도 센다)·그 칸만, 구분자·줄바꿈은 공백으로 |
smoke-board-cert.sh |
49 | 읽기 권한이 모든 통로에서 같다 — 회원 전용 그룹의 글이 ID 직접 읽기·첨부·댓글·통합검색·사이트맵·최근 글 위젯으로 새지 않는다(옛 코드는 여섯 통로 모두 샜다) · 공개를 끈 게시판 · 게시판별 본인인증·성인 인증(bo_use_cert) — 목록·글·댓글·쓰기·화면 안내·모아 보기 제외 · 미성년 안내 · 댓글 알림 문구 편집(기본 문구 = 실제 문구) |
smoke-search.sh |
131 | 비밀글·비공개 게시판 미노출 · ILIKE 이스케이프 · total 정확성 · 0건 기록 · 블록 CSS 미색인 · 결과 사진 |
smoke-restock.sh |
94 | 품절만 신청 · 옵션 단위 · 한 번만 발송 · 광고 아님 · 경로 무관 감지 · 메일의 해지 링크가 실제로 열린다 · 회원 신청 내역 화면 · 운영자의 재입고 대기 화면 |
smoke-starter.sh |
84 | 유형별 기본 구성 생성 · 홈 렌더 · 모든 메뉴 링크가 404 없이 렌더 · 일반 페이지로 수정 가능 · 청약철회 신청 폼의 칸에 이름이 있다 |
smoke-collections.sh |
31 | 종료=안내·숨김=404 · 진열 순서 · 원자적 저장 · 캐시 무효화 |
smoke-subscriptions.sh |
92 | 청구가 겹쳐도 회차는 한 번만 전진하고, 긁힌 돈은 반드시 결제된 주문에 붙는다 · 빌링키만 저장 · 청구액 고정(변경 시 중지) · 해지 즉시 · 멱등키 회수 · 몰아 청구 금지 · 신청 화면이 보여 준 금액과 빠져나가는 금액이 같다 |
smoke-updates.sh |
33 | Ed25519 서명 검증 · 키 고정(TOFU) · 변조·위조·다운그레이드 거부 |
smoke-grades.sh |
47 | 순매출 산정(반품 차감) · 견적=주문 금액 · 쿠폰 합산 상한 · 안분 정합 · 반품 없이 환불·취소한 주문과 부분 환불도 실적에서 빠진다 |
smoke-coupons.sh |
82 | 쿠폰함의 한 장은 동시 주문에서도 한 번만 · 1인 한도(취소 제외) · 발급형 1장 1회 · 취소 반환/환불 미반환 · 생일 자동 지급(월·일만 수집) · 쿠폰함 화면(코드는 서버 렌더에 없다) · 1인당 한도·첫 구매는 동시 주문에도 한 건만(서로 다른 첫 구매 쿠폰 둘도), 첫 구매 쿠폰은 입금 대기 주문으로 두 번 쓸 수 없다 · 취소된 주문이 선착순 쿠폰의 한도를 먹지 않는다(환불은 센다) |
smoke-board.sh |
281 | 비회원 비밀번호 대입이 막히고, 쏟아부어도 사이트가 멈추지 않는다 · 권한 4단계 · 답변형 · 비밀글 · 첨부 원자성 · XSS · 목록 스킨·썸네일·이전/다음·일괄 작업·그룹 권한·링크 · 첨부 이미지 축소 · 갤러리 썸네일·data-thumb·사진만 글 · 관리 목록 필터 · 내 스크랩 화면 · 비회원 글의 수정 권한을 서버·화면·집행이 같은 규칙으로 말한다 · 여분 필드 · 게시판 관리자 — 그 게시판에서만 남의 글·댓글 수정·삭제·비밀글·공지(다른 게시판·관리 화면은 불가), 화면 단추도 같은 규칙, 칸을 안 보내면 유지·비우면 해제, 탈퇴하면 해제 · 비밀글의 첨부는 글을 읽을 수 있는 사람만 받는다(주소를 알아도) · 읽지 못하는 글(비밀글·읽기 권한 밖)에는 댓글·추천·스크랩도 못 한다 — 작성자·게시판 관리자는 되고 비회원 비밀글은 비밀번호로 · 비밀댓글은 원글 작성자와 (비밀 답글이면) 질문한 사람도 읽는다 — API·화면 같은 규칙, 다른 회원에게는 가려진다 · 답글이 달린 댓글을 지워도 남이 단 답글은 남고(자리만 남는다) 댓글 수가 맞다 — 탈퇴하며 지울 때도 · 비밀글로 문의하면 질문자가 운영진의 비밀 답변글을 읽는다(비회원은 원글 비밀번호로), 읽지 못하는 비밀글에는 답하지 못하고 답은 비밀글로, 답변 양식은 다른 게시판 제목을 안 보인다 · 답변글이 달린 글·다른 사람의 댓글이 한도 이상 달린 글은 작성자가 지우지·고치지 못한다(운영진은 된다) · 글을 공유하면 그 글의 첫 이미지가 미리보기로(og:image) · 사이트맵에 글의 첫 이미지 |
smoke-point.sh |
80 | FIFO 소모 · 멱등 적립 · 만료 · 동시성 · 회원 내역 화면 도달 · 부분 저장이 나머지를 지우지 않는다 · 끄면 선언 화면·회원 메뉴·블록이 함께 사라진다 · 주문서에서 실제로 쓸 수 있다(칸·잔액·상한) · 쓰고 지우기로 쌓지 못한다 — 글·댓글을 지우면 적립을 거두고(글을 지우면 그 글의 댓글 적립도), 후기는 한 상품에 한 번 · 환불하면 그 주문의 구매 적립을 거둔다 · 운영자가 결제완료로 바꾼 주문(무통장 입금 확인)에도 구매 적립 |
smoke-memo.sh |
79 | 프라이버시 · 차단 · 포인트 차감 트랜잭션 · 슬래시 없는 루트 경로 · 페이지 없이 열리는 선언 화면(같은 slug 페이지가 이긴다) |
smoke-shop.sh |
322 | 결제하지 않은 주문은 재고를 붙잡지 않는다(입금 기한·결제 미완료 자동 취소, 결제 중·개인결제·옮겨 온 주문은 제외) · 재고 동시성 · 금액 위조 · 구매 검증 후기 · 비밀 문의 · 사진 후기 · 후기 정렬·사진 필터 · 모바일 구매 바 · 목록 썸네일·관리 왕복 · 처리 대기 카드 · 주문 일괄 처리·송장 일괄 입력 · 목록 필터 · 붙여넣기 등록 · 답변 대기 필터 · 목록 인덱스·응답 크기 · 오류 칸 안내 · 주문 안내 메일(로그 본문까지 · 취소 · 멱등 변경엔 재발송 없음) · 쇼핑몰 설정 화면 |
smoke-site.sh |
85 | 방문자 집계(IP 해시) · 팝업 노출 규칙 · 점검 모드 |
smoke-social.sh |
81 | state 쿠키 결속 · 코드 1회성 · 계정 탈취 경로 · 연결은 재인증을 요구하고 계정 주소로 알린다(비밀번호로 지워지지 않는 뒷문) |
smoke-security.sh |
93 | 로그인 대입 한도는 서버 수·재시작과 무관하고, 자기 계정 로그인으로 IP 한도를 비울 수 없다 · 로그인 뒤 돌아갈 주소는 같은 사이트의 경로만(열린 리다이렉트) · 캡차 · 레이트리밋 · 결제 위조 · 권한 우회 · CSP · 캡차 칸 안내 · 비회원 스팸 방어(재입고·문의 캡차·IP 한도) · 비회원 주문이 메일 발사대가 되지 않는다 |
smoke-upgrade.sh |
29 | 데이터가 있는 사이트에 새 마이그레이션이 올라가는가 — 업그레이드 전에 갇혀 있던 작업도 되찾는다 · 회원·동의·설정 보존 · 데이터 이관형 마이그레이션 적용 · 올린 뒤 가입·로그인·플러그인 활성화·주문 · 두 번 올려도 안전 · 인스턴스 둘이 동시에 부팅해도 플러그인 마이그레이션은 한 번만 |
smoke-backup.sh |
37 | 덤프를 뜨고 실제로 되돌린다 — 지운 회원·페이지가 돌아오는가 · 덤프 뒤에 만든 것은 사라지는가 · 앱이 돌고 있는 채로 복원(문서가 시키는 그대로) · 복원 뒤 로그인·목록이 되는가 · 망가진 덤프를 성공이라 말하지 않는다 |
smoke-release.sh |
84 | FTP 설치 경로 · 동봉 플러그인 · 동봉 테마 전부 · 고아 프로세스 정리 · update.mjs 교체·롤백 왕복 · HOSTNAME 바인딩 · 공개 화면 보안 헤더 · 로그인·가입 칸을 비밀번호 관리자가 알아본다 · 손님마다 다른 화면은 공유 캐시에 담기지 않는다 |
smoke-create-plugin.sh |
45 | 템플릿 생성→빌드→ZIP 설치→계약 전부 실사용 · escapeHtml · 실제 탈퇴로 파기 검증 |
smoke-openapi.sh |
24 | 실제 라우트에서 생성 · 플러그인 켜고 끄면 문서도 변함 · 자체 완결 문서 페이지 |
smoke-registry.sh |
23 | 서명 통과해야 설치 · 키·주소 고정(TOFU) · 레지스트리 키 바꿔치기 방어 |
smoke-notifications.sh |
77 | 메일이 꺼져 있어도 알림이 닿는가 · 댓글·문의 답변이 알림함에 남는가 · 머리의 개수 · 열면 읽음 · 남의 알림은 보이지 않는다 · 보여준 것만 읽음 · 이어 읽기 · 보관 기간 · 주문 안내 문자(옵트인) · 문자 설정을 저장해도 폼이 비지 않는다 · 내 댓글에 답글이 달리면 댓글 작성자에게 알림 — 자기 답글·글쓴이 댓글은 한 번만, 비밀 답글은 내용을 빼고, 문구 편집 가능 |
smoke-theme.sh |
249 | 라이트·다크 두 벌 · 토큰 CSS 주입 차단 · 테마 고치면 캐시 갱신 · 랜딩 블록 · 글 상세의 문서 제목 · 두 번째 테마(editorial) 같은 계약 · Storefront · Boutique · 미리보기 · 배너 슬라이드 · 퀵메뉴 · Corporate · 2단 메뉴 · 띠배너 · 테마가 깨져도 사이트는 나간다 |
smoke-moderation.sh |
31 | 금지 단어(우회 포함)·사칭 이름·금지 도메인·차단 IP(자기잠금)·분류 필수 |
smoke-i18n.sh |
109 | 블록 안쪽 문구(중첩 템플릿·따옴표에 담은 라벨)도 영어 · 번역 문장에 값을 글자 그대로 끼운다($·같은 자리 두 번) · 언어가 실제 렌더를 바꿈 · 즉시 반영 · ko 폴백+로그 · 값은 번역 안 함 · 관리 선언 라벨 · 카드 제목 전수 · 리소스 선언 문자열 전수 · 회원 메뉴·결제수단 이름(손님이 읽는다) · 금액·날짜 표기와 주문 메일도 언어를 따라간다(손님·관리 화면 모두) · 코어 경로가 쓰는 플러그인 문구도 · 서버 오류 메시지도 — 플러그인과 코어 양쪽(어느 칸인지는 코드 그대로) · 페이지 빌더의 블록 서랍(속성 키는 데이터라 그대로) · 값이 들어가는 문장과 그 안의 낱말까지(코어는 키+값을 실어 경계에서 조립) · 템플릿 잔해 금지 |
smoke-admin-resources.sh |
21 | 관리 리소스 전수 왕복 — 설정 GET/PUT 모양 일치 · 거부하면 아무것도 안 바뀐다 · 비밀 값은 GET 으로 안 돌아온다 · 목록 리소스의 수정 폼이 선언한 칸을 불러올 수 있는가 · 보낸 값이 저장되는가 · 비로그인 403(코어는 401) · 운영자(manager)가 자기 화면에 닿는다 — 목록·선언·데이터는 열리고 코어 관리는 닫힌다 · 관리자 전용으로 선언한 화면은 목록에서도 가려진다 |
정적 검사 — 서버는 되는데 화면이 없는(또는 그 반대인) 구멍을 CI 가 잡습니다. 전부 실제로 겪은 결함에서 나왔습니다.
| 검사 | 무엇을 막는가 |
|---|---|
check-captcha-screens.mjs |
서버가 캡차를 검증하는데 화면에 칸이 없거나, 칸만 있고 서버가 검증하지 않는 것 (기본 설정으로 설치한 사이트에서 회원가입이 막혀 있었다) |
check-captcha-secrecy.mjs |
캡차가 정답을 흘리는 것 (SVG 의 <text>, 토큰의 base64 페이로드 — 둘 다로 새고 있었다) |
check-settings-screens.mjs |
설정 저장 API 만 있고 그것을 부르는 화면이 없는 것 (배송비와 입금 계좌를 curl 로만 바꿀 수 있었다) |
check-plugin-screens.mjs |
플러그인이 내는 링크가 가리키는 곳에 화면이 없는 것 (헤더의 "쪽지함"이 404 였다) |
check-admin-guards.mjs |
관리 라우트가 역할을 정하지 않는 것 (디스패처가 manager 까지 통과시키므로 admin 전용은 자기 줄에서 막아야 한다) |
check-plugin-cleanup.mjs |
플러그인을 끄고도 등록한 것이 남는 것 (꺼진 플러그인의 경로가 매칭되는데 그릴 블록이 없어 깨진 화면이 나온다) |
check-theme-defaults.mjs |
테마의 본문 기본값이 확장의 스타일을 이기는 것 (.brick-main button 이 (0,1,1) 이라 플러그인의 .brick-wish-btn 을 이겨서, 한 화면에서 44곳이 블록이 정한 값이 아니라 테마의 기본값으로 그려졌다) |
check-radius-tokens.mjs |
플러그인·코어 블록이 모서리 둥글기를 박아 넣는 것 (테마마다 0px~10px 로 언어가 다른데 확장이 8px 를 55곳에 박아 두어, 각진 테마를 골라도 상품 카드·구매 버튼·게시판이 둥글게 남았다 — 테마를 바꿔도 인상이 바뀌지 않았다) |
check-theme-tokens.mjs |
어떤 테마도 정의하지 않는 토큰을 참조하는 것 (폴백만 쓰여 테마가 그 색을 바꿀 수 없다 — 다크 테마에 밝은 선이 박혔다) |
check-doc-counts.mjs |
문서의 숫자가 실제와 어긋나는 것 (스모크 표가 아홉 수트만큼 어긋나 있었고, 저장소 구조는 플러그인 여덟 개를 다섯 개로 적고 있었다) · 만들어 둔 검사가 CI 에 연결되지 않은 것 |
check-smoke-counts.mjs |
스모크 표의 각 행이 실제 실행 결과와 어긋나는 것 (doc-counts 는 표의 내부 일관성만 본다 — 단언을 더해도 그 줄을 고치는 사람이 없으면 표는 조용히 낡는다) |
check-secret-fields.mjs |
자격증명처럼 생긴 입력칸이 평문으로 그려지는 것 (토스 시크릿 키가 보통 텍스트 칸이었다 — 붙여 넣는 동안 화면에 그대로 떠 있었다) |
check-admin-field-names.mjs |
화면이 보내는 칸 이름을 서버가 읽지 않는 것 (승인번호를 적고 저장하면 "승인번호를 입력해주세요" 가 떴다 — 방금 적은 그 칸을 두고) |
check-screen-i18n.mjs |
손님 화면을 그리는 코드에 박아 넣은 한국어 (영어로 쓰는 사이트에서 1:1 문의 화면이 통째로 한국어였고, 게시판은 에디터·댓글의 alert·prompt 스물몇 개가 남아 글을 쓰기 시작하는 순간 한국어가 튀어나왔다) |
check-block-i18n.mjs |
블록 선언의 이름·속성 제목에 번역이 없는 것 (관리 화면은 다 영어인데 페이지를 만드는 첫 화면인 블록 서랍만 "제목 · 문단 · 히어로 (큰 제목 영역)" 였다) |
check-error-i18n.mjs |
플러그인이 던지는 오류 문장에 번역이 없거나 값이 박혀 있는 것 (화면은 전부 영어인데 주문 버튼을 누르면 "재고가 부족합니다." 가 떴다 — 영어 사이트의 손님은 가장 중요한 순간에만 못 읽는 글자를 본다) |
check-mail-i18n.mjs |
메일 제목이 한국어로 박히는 것 (영어 사이트 회원이 비밀번호를 잃어버리면 한국어 메일을 받았다 — 재입고 메일은 가격 줄만 번역돼 있었다) |
check-reachable-screens.mjs |
서버에 다 있는데 닿는 화면이 없는 기능 (소셜 연결 해제·포인트 사용·약관 재동의·약관 개정·단체메일·그누보드 이전·2단계 인증·사업자정보·현금영수증·청약철회 철회·비회원 문의 조회·재입고 해지·위시리스트 담기·배송지가 그랬다 — 전부 API·검증·스모크는 있고 화면만 없었다) |
check-on-primary.mjs |
primary 면에 글자색을 박아 밝은 팔레트에서 글자가 사라지는 것 (다크로 보는 운영자에게 관리 화면의 모든 저장 버튼이 빈 칸이었다) |
check-mail-links.mjs |
플러그인이 메일로 보내는 링크에 화면이 없는 것 (재입고 알림 해지 링크가 "상품을 찾을 수 없습니다" 로 떨어졌다 — 끊을 수 없는 알림이었다) |
check-smoke-hygiene.mjs |
스모크가 스스로 만드는 함정 — 못박힌 미래 날짜(그날이 오자 코드는 그대로인데 CI 가 빨개졌다), 정리(trap)의 맨손 kill(단언 85개 전부 통과인데 종료코드 1), 자기가 띄운 서버인지 확인하지 않는 것(포트를 남이 쥐고 있으면 남의 서버를 검사한다 — 개발용 API 때문에 실제로 그랬고 DB 까지 초기화됐다) |
check-unsubscribe-header.mjs |
광고 메일에 List-Unsubscribe 헤더가 없는 것 (메일 앱이 수신거부 버튼을 못 띄우면 손님은 스팸 신고를 누르고, 그러면 입금 계좌가 담긴 주문 안내까지 스팸함으로 간다) · One-Click 을 선언했는데 그 주소가 POST 를 안 받는 것 |
check-autocomplete.mjs |
손님이 폰에서 손으로 다 쳐야 하는 칸 (로그인·가입의 이메일·비밀번호에 name 도 autocomplete 도 없어 저장된 비밀번호가 채워지지 않았다 — 주문서에는 이미 있었다) |
check-fail-color.mjs |
성공과 실패가 같은 자리를 쓰는데 색은 성공에 고정된 것 (페이지 빌더를 비롯한 관리 화면 여섯 곳과 재입고 알림 폼이 초록 글씨로 "저장 실패" 를 띄웠다 — 색은 문구가 아니라 상태를 따라야 한다) |
check-error-announce.mjs |
오류를 눈으로만 알려주는 것 (로그인 실패에 라이브 영역이 하나도 없어, 스크린리더에는 버튼을 눌러도 아무 일이 없었다 — 가입 화면만 role="alert" 를 갖고 있었다) |
check-error-messages.mjs |
손님·운영자에게 가는 영어 오류 메시지 (비밀번호를 틀리면 로그인 화면에 invalid credentials 가 떴다 — 화면은 서버가 준 message 를 그대로 보여준다) |
check-retention.mjs |
치우겠다고 써 놓고 아무도 부르지 않는 정리 함수 (넷 중 둘은 주기 정리가 빠뜨렸고 — 검색어·이메일 인증 토큰이 400일 뒤에도 남아 있었다 — 나머지 둘은 같은 정책이 두 곳에 적혀 있었다) |
check-site-timezone.mjs |
날짜 경계를 UTC 로 자르는 것 (쪽지 하루 한도가 자정이 아니라 아침 9시에 풀렸다 — 같은 사이트 안에 날짜 경계가 두 벌이었다) |
check-guest-tables.mjs |
손님 화면의 목록 표가 폰에서 가로로 밀리는 것 (영어로 쓰는 사이트에서 장바구니를 375px 로 열면 표가 391px 로 벌어져 수량 칸과 삭제 버튼이 화면 밖으로 나갔다 — 한국어에서는 우연히 들어맞아 보이지 않던 문제다) |
check-admin-tables.mjs |
관리 화면의 표가 폰에서 가로로 밀리는 것 (375px 에서 플러그인 표가 982px 로 벌어져 켜기·끄기 버튼 여덟 개가 화면 밖에 있었다) |
check-admin-keyboard.mjs |
클릭이 달렸는데 키보드로 닿지 않는 요소 (페이지 편집기의 블록 고르기 열여덟 개가 전부 div 였고, 페이지 목록은 행 클릭이 유일한 진입로였다) |
check-mail-abuse.mjs |
확인 절차 없이 메일을 보내는 공개 경로 (비회원 주문으로 임의 주소에 10/10 발송됐다 — 재입고·1:1 문의에 이어 세 번째) |
그 밖에 CI 가 정적으로 보는 것: 테마 CSS 컴파일 산출물 일치, 모달의 useModalFocus,
메일이 보내는 링크에 화면이 있는지, 마이그레이션 멱등성(2회 실행).
DATABASE_URL=postgresql://brick:brick@localhost:5432/brick bash scripts/smoke-test.shDATABASE_URL=postgresql://brick:brick@localhost:5432/brick bash scripts/smoke-board.shDATABASE_URL=postgresql://brick:brick@localhost:5432/brick bash scripts/smoke-point.shDATABASE_URL=postgresql://brick:brick@localhost:5432/brick bash scripts/smoke-memo.shDATABASE_URL=postgresql://brick:brick@localhost:5432/brick bash scripts/smoke-shop.shDATABASE_URL=postgresql://brick:brick@localhost:5432/brick bash scripts/smoke-site.shDATABASE_URL=postgresql://brick:brick@localhost:5432/brick bash scripts/smoke-social.shDATABASE_URL=postgresql://brick:brick@localhost:5432/brick bash scripts/smoke-security.shapps/
web/ Next.js — 공개 사이트(Route Handler SSR) + 관리자(React)
api/ NestJS — Brick Runtime (내부 전용)
packages/
core/ HookBus, PluginContext, Provider 인터페이스
database/ Drizzle 스키마 + 코어 마이그레이션
shared/ 공통 타입, plugin/theme manifest 스키마
plugin-sdk/ 플러그인 개발자 공개 표면
theme-sdk/ 런타임 템플릿 엔진
plugins/
brick-board/ 게시판 (레퍼런스 구현)
brick-shop/ 쇼핑몰 (관리자 리소스·트랜잭션·재고 동시성 레퍼런스)
brick-point/ 포인트 (플러그인 간 서비스 협력 레퍼런스)
brick-memo/ 쪽지 (사적 콘텐츠 프라이버시 레퍼런스)
brick-helpdesk/ 1:1 문의 · FAQ (비공개 문의·비회원 조회 레퍼런스)
brick-poll/ 설문조사 (익명 집계 레퍼런스)
brick-site/ 방문자 집계 · 팝업 (훅 구독 레퍼런스)
brick-pay-toss/ 토스페이먼츠 (PG를 코어 수정 없이 붙이는 레퍼런스)
brick-pay-portone/ 포트원 V2 (이니시스·KCP·NICE·카카오페이·네이버페이 등을 한 연동으로)
brick-sms-aligo/ 문자·카카오 알림톡 발송 (알리고 — 공급자를 코어 수정 없이 붙이는 레퍼런스)
themes/
default/ 기본 테마 — 커뮤니티 (런타임 템플릿 레퍼런스)
editorial/ 매거진 — 같은 계약, 다른 인상
storefront/ 쇼핑몰 — 퀵메뉴·2단 메뉴·띠배너
boutique/ 부티크 — 여백 위주
corporate/ 회사 홈페이지 — 히어로·특징 카드
docs-site/ GitHub Pages 랜딩페이지
scripts/ 스모크 테스트 45종 + 정적 검사 39종 + OIDC 스텁 + 배포본 생성(build-release.sh)
docker/ Dockerfile, entrypoint
| 문서 | 내용 |
|---|---|
| 설치 가이드 | Docker / Node 설치, 리버스 프록시, 환경변수, 문제 해결 |
| 업그레이드 가이드 | 업데이트 절차(update.mjs·Docker), 새 버전 알림, 자동 마이그레이션 원리, 롤백 |
| 변경 이력 | 버전별 변경 사항 |
| 운영 가이드 | 백업, 모니터링, 성능, 한국어 검색, 스케일링 |
| 게시판 | 권한·분류·답변형·첨부파일 |
| 포인트 | 적립 정책, 원장 설계, 플러그인 간 협력 방법 |
| 회원 생애주기 | 약관 동의·이메일 인증·탈퇴·휴면, 법적 요건과 설계 |
| 단체메일 | 공지와 광고의 차이, 정보통신망법 제50조 요건, 수신거부 |
| 판매 리포트 | 무엇을 매출로 세는가, 시간대, CSV 내보내기 |
| 관련 상품 | 수동 지정과 함께 구매, 추천에서 빼는 것 |
| 세금 증빙 | 현금영수증·세금계산서, 면세, 부가세 신고 자료 |
| 계정 보안 | 2단계 인증, 접속 기기 관리, 감사 로그 |
| 개인결제 | 주문서 없는 청구, 결제 링크, 매출 반영 |
| 통합검색 | 무엇이 검색되나, 권한, 검색 분석과 규칙 |
| 재입고 알림 | 품절 손님 회수, 옵션 단위 신청, 광고와의 구분 |
| 문의·FAQ·설문·SEO | 1:1 문의 설계, FAQ, 설문조사, 사이트맵 |
| 사업자정보 표시 | 전자상거래법 제13조, 사업자번호 검증, 테마 렌더 |
| 그누보드·영카트 이전 | 덤프 만들기, 리허설, 레벨 매핑, 비밀번호 보존, 상품·주문 |
| 쪽지 | 프라이버시 설계, 차단, 포인트 차감, 스크랩 |
| 쇼핑몰 | 상품·주문·재고·쿠폰·후기·문의, 커머스 설계 원칙 |
| 소셜 로그인 | 구글·카카오·네이버·GitHub·사내 SSO 설정과 보안 |
| 방문자·팝업 | 접속자 집계, 팝업·배너, 개인정보 처리 |
| 결제 | PG 설정, 결제 흐름, 위조·중복 방어, 새 PG 붙이기 |
| 배치 편집기 | 실제 테마로 미리 보며 블록 배치, 다단 레이아웃, 키보드 배치, 컨테이너 블록 만들기 |
| 본인인증 | 포트원 본인인증, 성인 상품, 한 사람 한 계정, 저장하는 것과 안 하는 것 |
| 알림톡 | 카카오 알림톡(알리고) 설정, 템플릿 연결, 주문 알림 변수, 대체 문자 규칙 |
| 알림 문구 | 주문 안내의 제목·본문·문자 문구를 운영자가 고치기, 변수, 미리보기 |
| 보안 | 구현된 방어, 신뢰 모델, 배포 체크리스트 |
| 아키텍처 (ADR) | 설계 결정 77건과 그 이유 |
| 플러그인 개발 | manifest, API, 마이그레이션, 배포 |
| 테마 개발 | 템플릿 문법, 스코프, 배포 |
| 로드맵 | 남은 격차와 순서 |
| 남들은 무엇을 갖추고 있나 | 그누보드·영카트·카페24·라이믹스·워드프레스 대조와 우리 순서 |
| 기여 가이드 | 개발 환경, 구조 규칙, PR 규칙, 테스트 함정 |
| 행동 규범 | 이슈·PR에서 지켜주셨으면 하는 것 |
| 보안 신고 | 취약점 신고 절차와 대응 약속 |
알파. 기능은 동작하고 E2E로 검증되지만, 아직 실사용 검증이 부족합니다. 프로덕션에 올리기 전 보안 문서의 신뢰 모델과 체크리스트를 반드시 읽어주세요.
- 써보고 막힌 곳을 알려주세요. 설치가 안 되는 것이 가장 중요한 버그입니다 → 버그 신고
- 기존 사이트로 하던 일이 안 되는지 알려주세요. 그게 다음에 만들 것이 됩니다 → 기능 제안
- 플러그인이나 테마를 만들어보세요. 코어를 고치지 않고 어디까지 되는지가 이 프로젝트의 실질적인 시험입니다 → 플러그인 개발
- 코드로 기여해주세요. 개발 환경 준비와 테스트 함정은 CONTRIBUTING.md에 정리해두었습니다.
한국어로 편하게 남겨주세요. 영어도 괜찮습니다.