Files
dbx-main/docs/DATABASES.md
T
king c6fb8ed375 feat(cafe24): 상품 상세페이지 관리 모듈 Phase 1
카페24 관리자에 직접 접속하지 않고 상품 상세페이지(description HTML)를
편집·예약 적용·복원하기 위한 모듈의 기반을 만든다. Phase 1 은 공통
Integration 계층, cafe24_db, OAuth 연결 화면까지다.

카페24 OAuth/API 클라이언트를 상품관리 모듈 안에 두지 않고
app/integrations/cafe24/ 로 분리했다. 향후 추가할 주문관리(주문 조회·송장
일괄등록·취소/반품/교환)가 같은 토큰과 클라이언트를 그대로 재사용해야 하기
때문이다. 라우터에서 httpx 를 직접 부르지 않고 Cafe24Client 만 쓰게 해서
재시도·rate limit·API 로그·토큰 갱신을 한 곳에 모았다.

토큰은 Fernet 으로 암호화해 저장한다(CAFE24_TOKEN_SECRET). DB 덤프가
유출돼도 access/refresh token 이 평문으로 남지 않게 하기 위함이며, API 로그와
연결 상태 화면에는 토큰·시크릿을 일절 기록/표시하지 않는다.

토큰 갱신은 행 잠금(SELECT ... FOR UPDATE) 안에서 한다. 카페24는 refresh
token 을 회전시키므로, 이후 추가될 예약 worker 컨테이너와 web 컨테이너가
동시에 갱신하면 한쪽 토큰이 무효화된다.

기존 파일 변경은 목록에 한 줄씩 추가하는 형태로 44줄뿐이며 기존 라우트·
테이블·인증 로직은 건드리지 않았다. CAFE24_DB_URL 미설정 시 store 가 None
이라 앱은 정상 기동하고 모듈만 "설정 필요" 안내를 표시한다.

가드 헬퍼를 common.py 로 분리한 것은 router.py 가 routes_system.py 를
include 하는 구조에서 순환 import 가 생기기 때문이다.

검증: 신규 테스트 16개 통과(암호화 왕복, 토큰 만료·자동갱신, 상태 노출 시
토큰 미유출, 재시도 예산, 예약 상태 전이). dispatch 기존 테스트 9개 통과.
cafe24_db_init.sql 은 로컬에 Docker 가 없어 미실행 — 서버 적용 시 확인 필요.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 00:23:02 +09:00

15 KiB

Databases

PostgreSQL DB 목록

DB명 용도
itemcode_db 상품코드, 단품/세트 구성, 채널 ↔ 사내 코드 매칭
orderlist_db 주문 수집·분석·관리 (구 orderlist_app)
return_db 반품·교환·CS 데이터
expense_db 개인경비 / 법인카드 사용내역 / 정산
cupang_db 쿠팡 밀크런 출고 묶음 / 출고 라인 / 입고센터 / 박스 입수량 규칙
malaysia_stock_db 말레이시아 창고 재고관리 — 창고/아이템/세트 BOM/입출고 이력/일일 재고조사
cafe24_db 카페24 연동 — OAuth 토큰/상품 캐시/상세페이지 버전/예약/감사·API 로그

명명 규칙

  • 모든 DB 이름은 소문자 + 언더스코어, _db로 끝낸다.
    • inventory_db, cs_db
    • inventoryApp, cs-database
  • 신규 DB가 필요하면 사용자 승인 후 생성한다.
  • 신규 DB 생성 시 함께 정리할 항목:
    • 용도와 책임 모듈
    • 소유자(OWNER) 계정
    • 백업 주기와 위치
    • .env의 연결 정보 변수명

레거시 이름 매핑

예전 이름 현재 기준
orderlist_app orderlist_db

코드/문서/설정에서 orderlist_app을 발견하면 orderlist_db로 수정한다 (수정 전 영향 범위 확인).


DB 작업 원칙

  1. 작업 전 백업 우선. 백업 없는 변경은 진행하지 않는다.
  2. 테이블 owner, 권한, sequence 권한을 확인한다.
  3. 운영 DB의 DROP, TRUNCATE, 조건 없는 대량 DELETE/UPDATE사용자 확인 없이 실행 금지.
  4. 스키마 변경은 마이그레이션 스크립트(scripts/ 또는 alembic 등)로 관리한다.
  5. 운영 DB와 개발 DB의 접속 정보를 혼동하지 않는다 (.env로 분리).

위험 명령 (사용자 승인 필수)

명령 비고
DROP DATABASE 복구 불가. 백업 없으면 절대 실행 금지
DROP TABLE / DROP SCHEMA 의존 객체 확인 필수
TRUNCATE FK CASCADE 시 광범위 삭제 위험
조건 없는 DELETE / UPDATE WHERE 없는 문 차단
docker volume rm <postgres_volume> 운영 데이터 영구 손실
docker compose down -v 볼륨까지 제거. 운영에서 금지

실행 전 반드시:

  1. 백업 확인 (pg_dump, 컨테이너 외부 마운트)
  2. 영향 범위 설명
  3. 사용자 명시 승인

자주 쓰는 점검 명령

# 컨테이너/네트워크
docker ps
docker network ls
docker inspect <postgres_container_name>

# DB 목록 / 접속
sudo -u postgres psql -l
docker exec -it <postgres_container_name> psql -U postgres -l

# 특정 DB 접속
docker exec -it <postgres_container_name> psql -U <user> -d itemcode_db

# 테이블/권한 확인
\dt
\dn+
\du
\z <table_name>

expense_db 스키마 / 초기화

DDL: scripts/sql/expense_db_init.sql (멱등). DB·역할·테이블·인덱스·트리거를 한 번에 생성.

테이블 expense_items

컬럼 타입 비고
id TEXT PK 12자 hex (uuid4 앞 12자)
owner TEXT 소유자 email (소문자)
spent_at DATE 사용일
category TEXT 식대/교통/숙박/비품/접대/통신/기타
method TEXT 법인카드/개인지출/현금
merchant TEXT 가맹점
amount BIGINT 원 단위, ≥ 0
memo TEXT 비고
status TEXT 작성중/제출/승인/반려/정산완료
approver_email TEXT 결재자 email (승인/반려/정산 시 기록)
decided_at TIMESTAMPTZ 결재 시점
reject_reason TEXT 반려 사유
created_at / updated_at TIMESTAMPTZ 트리거로 자동 갱신

인덱스: (owner, spent_at DESC), (status), (created_at DESC).

테이블 expense_attachments

영수증/기타파일 메타데이터. 실제 파일은 DATA_DIR/uploads/expense/{item_id}/ 에 저장.

컬럼 타입 비고
id TEXT PK 12자 hex
item_id TEXT FK expense_items(id) ON DELETE CASCADE
owner TEXT 업로드한 사용자 email
kind TEXT receipt 또는 other (CHECK)
filename TEXT 원본 파일명
stored_path TEXT 디스크 경로 (절대)
content_type TEXT MIME
size_bytes BIGINT 바이트
uploaded_at TIMESTAMPTZ 업로드 시각

인덱스: (item_id).

결재 워크플로

작성중 ─submit──▶ 제출 ─approve──▶ 승인 ─settle──▶ 정산완료
   ▲                │
   └─revert/reject──┴─reject──▶ 반려 ─revert──▶ 작성중
  • submit/revert: owner 본인
  • approve/reject/settle: expense_approver 또는 admin
  • 첨부 추가/항목 수정/삭제: 작성중 또는 반려 상태에서만

마이그레이션

기존 운영 DB 에 신규 컬럼/테이블 적용:

docker exec -i postgres-db psql -U postgres -d expense_db \
  < scripts/sql/expense_db_002_workflow_attachments.sql

신규 설치는 expense_db_init.sql 하나로 충분 (둘 다 멱등).

운영 서버 초기화 (1회)

# 1) 비밀번호 변수 준비 (셸 히스토리에 남지 않게 환경변수 사용)
read -s -p "expense_app password: " APP_PWD; echo

# 2) PostgreSQL 컨테이너에 DDL 적용
docker exec -i postgres-db psql -U postgres \
  -v app_password="$APP_PWD" \
  < scripts/sql/expense_db_init.sql

# 3) main-app .env 에 EXPENSE_DB_URL 추가
#    EXPENSE_DB_URL=postgresql://expense_app:<APP_PWD>@postgres-db:5432/expense_db

# 4) main-app 재기동
docker compose up -d --build

JSON → DB 마이그레이션

docker exec -e EXPENSE_DB_URL="$EXPENSE_DB_URL" -it dbx-main \
  python scripts/migrate_expense_json_to_db.py \
    --json /data/expense.json --dry-run
# 결과 확인 후
docker exec -e EXPENSE_DB_URL="$EXPENSE_DB_URL" -it dbx-main \
  python scripts/migrate_expense_json_to_db.py --json /data/expense.json

멱등 INSERT(ON CONFLICT DO NOTHING). 원본 JSON 은 건드리지 않는다.


cupang_db 스키마 / 초기화

DDL: scripts/sql/cupang_db_init.sql (멱등). DB·역할(cupang_app)·테이블·인덱스·트리거·센터 seed 를 한 번에 생성. JSON 폴백 없음CUPANG_DB_URL 미설정 시 모듈이 "설정 필요" 안내만 표시.

테이블:

테이블 용도
cupang_centers 입고센터. active=false 로 비활성화(사용 중이면 hard delete 금지)
cupang_box_rules 제품코드별 박스당 입수량(units_per_box). product_code UNIQUE
cupang_shipments 출고 묶음 헤더 (작성일/출고일/센터입고일/센터/출고방식/상태/작업자/메모)
cupang_shipment_lines 출고 라인. shipment_id FK ON DELETE CASCADE. UNIQUE(shipment_id, line_no)

status 허용값: 작성중, 출고준비, 출고완료, 센터입고완료, 취소. 삭제는 기본 soft delete(status='취소').

박스 계산은 서버(store.compute_boxes)에서 재계산: required_boxes = ceil(quantity / units_per_box). 클라이언트 계산은 미리보기용.

상품은 cupang_db 에 복제 저장하지 않는다. 라인에는 product_code + product_name_snapshot 만 보존(과거 명칭 보존). 상품 검색은 itemcode_db 읽기 전용(ITEMCODE_DB_URL, 미설정 시 수동 입력).

운영 서버 초기화 (1회, 사용자 승인 후)

read -s -p "cupang_app password: " APP_PWD; echo
docker exec -i postgres-db psql -U postgres \
  -v app_password="$APP_PWD" \
  < scripts/sql/cupang_db_init.sql
# main-app .env 에 추가:
#   CUPANG_DB_URL=postgresql://cupang_app:<APP_PWD>@postgres-db:5432/cupang_db
cd /opt/www/main && docker compose up -d --build

멱등 스크립트. 기존 DB 가 있으면 DROP 하지 않음. itemcode_db 는 건드리지 않음.


vacation_db 스키마 / 초기화

DDL: scripts/sql/vacation_db_init.sql (멱등). DB·역할(vacation_app)·테이블·인덱스·트리거·2026 공휴일 seed 를 한 번에 생성. JSON 폴백 없음VACATION_DB_URL 미설정 시 모듈이 "설정 필요" 안내만 표시.

테이블:

테이블 용도
vacation_requests 휴가 신청(헤더). 종류/기간/시작·종료 구분(full/am/pm)/일수/사유/상태/승인자/반려사유
vacation_holidays 공휴일(holiday_date UNIQUE). is_red=true 면 달력 빨강 + 일수 계산 제외. 관리자가 settings 에서 추가/수정/삭제
vacation_balances 사용자별 연차(UNIQUE(user_email, year)). total_days 설정, 사용일수는 승인 휴가 합계로 자동 계산

status 허용값: 작성중, 제출, 승인, 반려, 취소. 워크플로: 작성중/반려 → 제출 → 승인|반려. 삭제는 기본 soft delete(status='취소'). 수정은 작성중/반려 상태에서 본인만.

휴가 일수는 서버(store.compute_days)에서 재계산: 주말 + vacation_holidays(is_red) 제외, 오전/오후 반차 0.5일, 시작/종료 반차는 각 0.5 차감. 클라이언트 계산은 미리보기(주말만 제외)용.

권한: vacation(접근) / vacation_approver(승인·반려). admin 은 항상 통과. 공휴일·연차 설정은 admin 전용.

운영 서버 초기화 (1회, 사용자 승인 후)

read -s -p "vacation_app password: " APP_PWD; echo
docker exec -i postgres-db psql -U postgres \
  -v app_password="$APP_PWD" \
  < scripts/sql/vacation_db_init.sql
# main-app .env 에 추가:
#   VACATION_DB_URL=postgresql://vacation_app:<APP_PWD>@postgres-db:5432/vacation_db
cd /opt/www/main && docker compose up -d --build

멱등 스크립트. 기존 DB 가 있으면 DROP 하지 않음. 공휴일은 연도별로 다르므로 settings 화면에서 추가/수정.


malaysia_stock_db 스키마 / 초기화

DDL: scripts/sql/malaysia_stock_db_init.sql (멱등). DB·역할(malaysia_app)·테이블·인덱스·트리거·창고/아이템 seed 를 한 번에 생성. JSON 폴백 없음MALAYSIA_STOCK_DB_URL 미설정 시 모듈이 "설정 필요" 안내만 표시. 상품명은 itemcode_db 읽기 전용 재사용.

테이블:

테이블 용도
warehouses 창고(warehouse_code UNIQUE). seed: MY-WH-01 / Malaysia Warehouse
malaysia_items 관리 대상 코드 스코프 + 종류(individual/set) + 이름 스냅샷. prefix CHECK. seed: 낱개 12 + 세트 5
set_bom 세트 구성표. set_code(MY-)·component_code(MT/MX/MZ) prefix CHECK, UNIQUE(set_code, component_code)
stock_movement 입고/출고/조정/조사 이력. item_code 낱개만(CHECK), movement_type IN/OUT/ADJUST/STOCKTAKE. ref_type·ref_no(Shopee/Lazada 연동용)
daily_stocktake 재고조사 헤더. status draft/finalized/cancelled. 같은 날짜+창고 finalized 1건(부분 유니크 인덱스)
daily_stocktake_line 조사 라인. sku_code 낱개+세트 허용(MD- 금지), qty>=0, UNIQUE(stocktake_id, sku_code)

현재고 = SUM(IN) - SUM(OUT) + SUM(ADJUST) + SUM(STOCKTAKE). 재고조사 확정 시 시스템 재고와 조사 최종치의 차이만 STOCKTAKE movement 로 기록. 세트는 movement 불가, 재고조사/BOM 계산 전용. MD- 뚜껑은 전 영역 제외.

운영 서버 초기화 (1회, 사용자 승인 후)

read -s -p "malaysia_app password: " APP_PWD; echo
docker exec -i postgres-db psql -U postgres \
  -v app_password="$APP_PWD" \
  < scripts/sql/malaysia_stock_db_init.sql
# main-app .env 에 추가:
#   MALAYSIA_STOCK_DB_URL=postgresql://malaysia_app:<APP_PWD>@postgres-db:5432/malaysia_stock_db
cd /opt/www/main && docker compose up -d --build

멱등 스크립트. 기존 DB 가 있으면 DROP 하지 않음. itemcode_db 는 건드리지 않음.


cafe24_db 스키마 / 초기화

DDL: scripts/sql/cafe24_db_init.sql (멱등). DB·역할(cafe24_app)·테이블·인덱스·트리거를 한 번에 생성. JSON 폴백 없음CAFE24_DB_URL 미설정 시 모듈이 "설정 필요" 안내만 표시.

테이블:

테이블 용도
cafe24_oauth_tokens 쇼핑몰별 OAuth 토큰(mall_id UNIQUE). access/refresh 는 Fernet 암호문으로 저장. 상품관리 + 향후 주문관리가 공유
cafe24_products 상품 캐시(product_no UNIQUE). 목록/검색 속도용이며 source of truth 는 언제나 카페24
cafe24_product_revisions 상세페이지 HTML 버전(append-only). revision_type SYNC/DRAFT/BACKUP/MANUAL/SCHEDULED/ROLLBACK
cafe24_product_schedules 예약 작업. status PENDING/PROCESSING/SUCCESS/FAILED/CANCELLED, 재시도·자동종료·복원 대상 포함
cafe24_audit_logs 누가 무엇을 바꿨나. worker 수행분은 actor='SCHEDULER'
cafe24_api_logs 카페24 API 호출 기록. 토큰/Authorization/client_secret 미기록

핵심 규칙:

  • 카페24에 쓰기 직전 반드시 현재 HTML 을 다시 조회해 BACKUP revision 으로 저장한다. 로컬 DB 의 마지막 값을 현재값으로 가정하지 않는다.
  • 예약의 restore_revision_id예약 실행 순간 만든 BACKUP 을 가리킨다(예약 생성 시점 값이 아님).
  • 일괄 예약은 상품 1건당 1행 + 공통 parent_job_id — 한 상품 실패가 나머지를 막지 않는다.
  • 토큰 암호화 키는 .envCAFE24_TOKEN_SECRET. 값을 바꾸면 기존 토큰을 복호화할 수 없어 카페24 재연결이 필요하다.

운영 서버 초기화 (1회, 사용자 승인 후)

read -s -p "cafe24_app password: " APP_PWD; echo
docker exec -i postgres-db psql -U postgres \
  -v app_password="$APP_PWD" \
  < scripts/sql/cafe24_db_init.sql
# main-app .env 에 추가:
#   CAFE24_DB_URL=postgresql://cafe24_app:<APP_PWD>@postgres-db:5432/cafe24_db
#   CAFE24_MALL_ID / CAFE24_CLIENT_ID / CAFE24_CLIENT_SECRET / CAFE24_REDIRECT_URI
#   CAFE24_TOKEN_SECRET=$(openssl rand -hex 32)
cd /opt/www/main && docker compose up -d --build web

멱등 스크립트. 기존 DB 가 있으면 DROP 하지 않음. 상세는 docs/CAFE24_MODULE.md.


백업 / 복구 (안전 절차)

백업

# 단일 DB 덤프 (운영 권장)
docker exec -t <postgres_container_name> \
  pg_dump -U <user> -F c -d orderlist_db \
  > /var/backups/postgres/orderlist_db_$(date +%F).dump

복구 (덮어쓰기 위험 → 사용자 승인 필수)

# 1) 신규 DB로 먼저 복구해 검증
docker exec -i <postgres_container_name> \
  pg_restore -U <user> -d <new_db_name> < backup.dump

# 2) 검증 완료 후 운영 DB 교체 (필요 시)

pg_restore --clean은 기존 객체를 삭제한다. 운영 대상 DB에서 절대 무단 실행 금지.


.env 관련

  • DB 접속 정보(*_HOST, *_PORT, *_USER, *_PASSWORD, *_NAME)는 모두 .env로 관리한다.
  • .envGit에 올리지 않는다. .env.example만 커밋한다.
  • 비밀값 유출이 의심되면 즉시 회전(비밀번호/키 변경)을 진행한다.