Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
0125e86
Hardening commercial share-link LLM access
seonghobae Jul 2, 2026
91516b5
Add share link lifecycle controls
seonghobae Jul 2, 2026
b058735
Enforce production readiness settings
seonghobae Jul 2, 2026
00b064e
Cap live LLM draft costs
seonghobae Jul 2, 2026
aee1e63
Add backup restore runbook
seonghobae Jul 2, 2026
c9d1025
Add share-link audit audit logs and readiness operations docs
seonghobae Jul 2, 2026
fdc13c2
🛡️ Sentinel: [HIGH] Fix DSN redaction credential leak (unsupported UR…
seonghobae Jul 1, 2026
facd2ae
🛡️ Observe authz failures as structured events
seonghobae Jul 2, 2026
7301bc4
🧭 Align readiness status with current P0/P1 evidence
seonghobae Jul 2, 2026
4c366bf
🧭 상용화 준비: 라이선스 게이트와 법무 템플릿 문서 반영
seonghobae Jul 2, 2026
00e09ff
🛡️ Share: audit blocked/failed llm draft attempts
seonghobae Jul 2, 2026
f68f146
🛡️ Share: add request_id to share audit events
seonghobae Jul 2, 2026
4690613
docs: update figma design system status
seonghobae Jul 2, 2026
254cdf8
feat: align share export modal with Figma design
seonghobae Jul 2, 2026
f868d52
obs: add authz and share audit metrics for commercial readiness
seonghobae Jul 2, 2026
1752137
fix: enrich authz responses with error code header
seonghobae Jul 2, 2026
a28a2b2
test: remove unused import flagged in observability test
seonghobae Jul 2, 2026
135c50a
docs: refresh commercial readiness metrics after import cleanup
seonghobae Jul 2, 2026
b819370
feat: validate signed on-prem license tokens
seonghobae Jul 2, 2026
28afb5e
ops: add commercial readiness alert rules
seonghobae Jul 2, 2026
651b095
ci: add frontend accessibility smoke gate
seonghobae Jul 2, 2026
f6470ac
docs: refresh commercial readiness audit timestamp
seonghobae Jul 2, 2026
e3867ea
feat: expose billing usage summary
seonghobae Jul 2, 2026
8a81ea1
ci: add browser e2e smoke gate
seonghobae Jul 2, 2026
edb912f
feat: support signed license revocation lists
seonghobae Jul 2, 2026
d88200b
feat: add offline license issuance cli
seonghobae Jul 2, 2026
4210d26
feat: enforce billing usage quotas
seonghobae Jul 2, 2026
0c19354
docs: add commercial release approval gate
seonghobae Jul 2, 2026
b1db85e
test: add visual regression gate
seonghobae Jul 2, 2026
b862bd9
feat: support account deactivation list
seonghobae Jul 2, 2026
b265b15
test: expand mobile visual baseline
seonghobae Jul 2, 2026
11e2f13
feat: expose billing reactivation links
seonghobae Jul 2, 2026
edec553
test: cover share export visual baseline
seonghobae Jul 2, 2026
32fe07a
test: stabilize share export visual snapshot
seonghobae Jul 2, 2026
f6c5eea
test: stabilize modal visual dimensions
seonghobae Jul 2, 2026
b85b154
test: relax modal visual font variance
seonghobae Jul 2, 2026
80ec906
test: add first-run visual baseline
seonghobae Jul 2, 2026
501b34d
ci: validate commercial release approvals
seonghobae Jul 2, 2026
a55ea3a
feat: clarify account deactivation guidance
seonghobae Jul 2, 2026
0ef2af4
docs: define commercial alert thresholds
seonghobae Jul 2, 2026
e8bb71c
feat: add billing plan change handoff
seonghobae Jul 2, 2026
434c726
docs: add krw 2b commercialization plan
seonghobae Jul 2, 2026
81095ab
feat: record billing reconciliation events
seonghobae Jul 2, 2026
4f6a294
feat: add billing support diagnostics
seonghobae Jul 2, 2026
ef9a7ef
feat: add support diagnostics workspace
seonghobae Jul 2, 2026
5adaf93
ci: validate on-prem package readiness
seonghobae Jul 2, 2026
6cf6cf8
feat: verify billing webhook signatures
seonghobae Jul 2, 2026
7a4f1e8
feat: expose billing reconciliation metrics
seonghobae Jul 2, 2026
3edde30
feat: add support diagnostics audit demo
seonghobae Jul 2, 2026
cfd1b4d
fix: polish support diagnostics trust UI
seonghobae Jul 2, 2026
65221bc
feat: apply normalized billing contract state
seonghobae Jul 2, 2026
03746a7
feat: normalize provider billing events
seonghobae Jul 2, 2026
9f75224
feat: expand support diagnostics share evidence
seonghobae Jul 2, 2026
c6eca38
test: add support diagnostics visual baselines
seonghobae Jul 2, 2026
96d73e1
feat: record product lifecycle metrics
seonghobae Jul 2, 2026
f444fa9
feat: record llm draft usage
seonghobae Jul 2, 2026
22d3bd3
fix: harden shared llm audit and license cli
seonghobae Jul 2, 2026
a1721d8
test: stress support diagnostics billing evidence
seonghobae Jul 2, 2026
d0211e0
feat: validate billing plan catalog
seonghobae Jul 2, 2026
f7646b1
fix: remove MVP wording from export artifacts
seonghobae Jul 2, 2026
8f17809
docs: link commercial readiness design evidence
seonghobae Jul 2, 2026
8d82d0b
feat: surface redacted billing metadata evidence
seonghobae Jul 2, 2026
41f8b01
fix: harden production billing settings
seonghobae Jul 2, 2026
084e20f
feat: validate restore drill evidence
seonghobae Jul 2, 2026
cf66ce4
feat: enforce llm draft quotas
seonghobae Jul 2, 2026
da8cbfb
feat: add billing checkout handoff
seonghobae Jul 2, 2026
f545f1d
feat: surface billing entitlement evidence
seonghobae Jul 2, 2026
5fd1f00
feat: enforce billing seat entitlements
seonghobae Jul 2, 2026
47c6fdd
Merge branch 'main' into product/commercial-readiness
opencode-agent[bot] Jul 2, 2026
65592ae
test: stabilize support diagnostics visual baseline
seonghobae Jul 2, 2026
25ec83e
feat: persist llm draft billing attribution
seonghobae Jul 2, 2026
18f781e
feat: surface llm usage in support diagnostics
seonghobae Jul 2, 2026
c1b908a
ci: require billing provider catalog manifest
seonghobae Jul 2, 2026
f3d6b24
feat: add redacted support bundle gate
seonghobae Jul 2, 2026
b63ead3
test: validate support bundle evidence
seonghobae Jul 2, 2026
2901875
feat: report commercial readiness evidence gaps
seonghobae Jul 2, 2026
2c47318
fix: validate generated support bundles
seonghobae Jul 2, 2026
2b20d98
feat: accept external commercial evidence paths
seonghobae Jul 2, 2026
9f74a04
test: reject renamed example sale evidence
seonghobae Jul 2, 2026
ceec717
ci: run all commercial evidence validators
seonghobae Jul 2, 2026
8f92cb0
feat: add rollback drill evidence gate
seonghobae Jul 2, 2026
be5957c
fix: validate committed sale evidence
seonghobae Jul 2, 2026
0b4c9b4
feat: summarize sale blockers in readiness audit
seonghobae Jul 2, 2026
686c0ab
feat: index commercial evidence bundles
seonghobae Jul 2, 2026
19534d2
feat: add billing runtime evidence gate
seonghobae Jul 2, 2026
d848e21
feat: require billing runtime sale evidence
seonghobae Jul 2, 2026
20bff64
feat: reconcile billing seats in support diagnostics
seonghobae Jul 3, 2026
7602555
Merge branch 'main' into product/commercial-readiness
opencode-agent[bot] Jul 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ POSTGRES_PORT=54321

BACKEND_PORT=8000
FRONTEND_PORT=5173
APP_ENV=development
PRODUCTION_CONFIG_CHECKS_ENABLED=true

# Production edge port used by compose.prod.yaml (Traefik).
TRAEFIK_HTTP_PORT=8080
Expand All @@ -31,11 +33,77 @@ SHARE_LINK_RATE_LIMIT_ENABLED=true
SHARE_LINK_RATE_LIMIT_REQUESTS=30
SHARE_LINK_RATE_LIMIT_WINDOW_SECONDS=60
SHARE_LINK_RATE_LIMIT_MAX_KEYS=10000
# Default public share-link lifetime. Set 0 only for an explicit non-expiring exception.
SHARE_LINK_DEFAULT_TTL_HOURS=168
# Public share links keep local markdown/prompt export enabled, but live LLM
# drafts are disabled by default to avoid unauthenticated provider-cost abuse.
SHARE_LINK_LLM_DRAFT_ENABLED=false

# Optional: OIDC (Casdoor) issuer; if set backend verifies JWTs
# OIDC_ISSUER=http://localhost:8002
# OIDC_AUDIENCE=erd-local

# Required when APP_ENV=production: exact hostnames/IPs or wildcard domains
# that reverse-engineering jobs may connect to.
# DB_INTROSPECTION_ALLOWED_HOSTS=db.example.com,*.internal.example.com

# Optional commercial licensing.
# Set LICENSE_MODE=required for paid/on-prem distributions. Either provide a
# legacy shared LICENSE_KEY or an Ed25519 LICENSE_PUBLIC_KEY for signed offline
# tokens. The public-key path supports expiring customer/plan license tokens.
LICENSE_MODE=off
# LICENSE_KEY=change-me-please-strong-key
# LICENSE_PUBLIC_KEY=base64url-ed25519-public-key
# Optional comma-separated revocation lists for signed token jti/sub claims.
# LICENSE_REVOKED_TOKEN_IDS=license-2026-07,license-2026-08
# LICENSE_REVOKED_SUBJECTS=customer-acme
# Optional paid-plan usage limits. 0 means unlimited.
BILLING_MAX_PROJECTS_PER_USER=0
BILLING_MAX_CONNECTIONS_PER_PROJECT=0
BILLING_MAX_SNAPSHOTS_PER_PROJECT=0
BILLING_MAX_SHARE_LINKS_PER_PROJECT=0
# Optional billing and account lifecycle links returned by /api/billing/usage
# and account-deactivated/plan-change/checkout responses. In production these
# must be public HTTPS URLs.
# BILLING_CHECKOUT_URL=https://billing.example.com/checkout
# BILLING_PORTAL_URL=https://billing.example.com
# BILLING_SUPPORT_URL=https://support.example.com/billing
# Shared secret required by POST /api/billing/events for provider-neutral
# reconciliation events. In production it must be at least 24 characters. Store
# as a secret, not in source control.
# BILLING_WEBHOOK_SECRET=change-me-provider-webhook-secret
# Optional HMAC-SHA256 raw-body signature secret for provider/gateway webhooks.
# When set, POST /api/billing/events requires X-BILLING-WEBHOOK-SIGNATURE. In
# production it must be at least 32 characters.
# BILLING_WEBHOOK_SIGNATURE_SECRET=change-me-provider-signature-secret
# Optional event-derived contract state. External providers should normalize
# provider-specific events into these event_type values before posting, or set
# BILLING_EVENT_TYPE_ALIASES as source=target entries. Prefix source with
# provider: to avoid cross-provider collisions.
# BILLING_EVENT_TYPE_ALIASES=stripe:customer.subscription.deleted=contract.suspended,customer.subscription.updated=contract.reactivated
# Billing event types that support diagnostics may treat as active entitlement
# evidence for current plan and contracted seat count.
BILLING_ENTITLEMENT_EVENT_TYPES=checkout.completed,invoice.paid,subscription.created,subscription.updated,contract.activated,contract.reactivated
BILLING_CONTRACT_STATE_EVENTS_ENABLED=false
BILLING_CONTRACT_DEACTIVATED_EVENT_TYPES=contract.deactivated,contract.suspended
BILLING_CONTRACT_ACTIVE_EVENT_TYPES=contract.activated,contract.reactivated
# ACCOUNT_REACTIVATION_URL=https://billing.example.com/reactivate
# Optional comma-separated OIDC subjects denied before DB access.
# ACCOUNT_DEACTIVATED_SUBJECTS=customer-owner,contract-suspended-user
# Optional comma-separated OIDC subjects allowed to read support diagnostics.
# SUPPORT_OPERATOR_SUBJECTS=support-operator-1,support-operator-2

# Optional: allowed JWT signing algorithms (comma-separated).
# Default is RS256.
# OIDC_ALGORITHMS=RS256

# Optional: OpenAI-compatible provider for authenticated live LLM drafts.
# LLM_API_BASE_URL=https://llm.example/v1
# LLM_API_KEY=change-me
# LLM_MODEL=example-model
LLM_TIMEOUT_SECONDS=30
LLM_MAX_PROMPT_CHARS=120000
LLM_MAX_OUTPUT_TOKENS=1200
LLM_DRAFT_QUOTA_ENABLED=true
LLM_DRAFT_QUOTA_REQUESTS=20
LLM_DRAFT_QUOTA_WINDOW_SECONDS=3600
48 changes: 48 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,38 @@ jobs:
PYTHONPATH: .
run: pytest -q

- name: Commercial evidence tests
run: python -m pytest scripts/operations/test_*.py scripts/ci/test_*.py -q

- name: Commercial release approval manifests
run: python scripts/ci/validate_commercial_release_approval.py

- name: Commercial readiness audit dry run
run: python scripts/ci/commercial_readiness_audit.py --output /tmp/commercial-readiness-audit.json

- name: Support bundle evidence manifests
run: python scripts/ci/validate_support_bundle.py

- name: Billing provider catalog manifest
run: python scripts/ci/validate_billing_provider_catalog.py

- name: Billing runtime evidence smoke
run: |
python scripts/operations/build_billing_runtime_evidence.py \
--catalog docs/operations/billing-provider-catalog.example.json \
--env-file docs/operations/billing-runtime.env.example \
--output /tmp/billing-runtime-evidence.json
python scripts/ci/validate_billing_runtime_evidence.py /tmp/billing-runtime-evidence.json

- name: Restore drill evidence manifests
run: python scripts/ci/validate_restore_drill_manifest.py

- name: Rollback drill evidence manifests
run: python scripts/ci/validate_rollback_drill_manifest.py

- name: On-premises package smoke
run: python scripts/ci/validate_onprem_package.py

frontend:
runs-on: ubuntu-latest
steps:
Expand All @@ -65,6 +97,10 @@ jobs:
working-directory: frontend
run: npm ci

- name: Install Playwright browser
working-directory: frontend
run: npx playwright install --with-deps chromium

- name: Typecheck
working-directory: frontend
run: npm run typecheck
Expand All @@ -73,6 +109,18 @@ jobs:
working-directory: frontend
run: npm run test

- name: Accessibility smoke
working-directory: frontend
run: npm run test:a11y

- name: Browser E2E smoke
working-directory: frontend
run: npm run test:e2e

- name: Visual regression
working-directory: frontend
run: npm run test:visual

- name: Build
working-directory: frontend
run: npm run build
5 changes: 5 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,8 @@
**Vulnerability:** Database driver exceptions can echo DSN fragments, query parameters, or assignment-style secrets after connection failures, leaking plaintext passwords through snapshot error messages and queue logs.
**Learning:** Redacting only the literal DSN is not enough. Error messages may contain decoded, percent-encoded, query-string, or `password=`/`api_key=` style forms of the same secret.
**Prevention:** Sanitize snapshot job errors before persisting or re-raising them, and raise sanitized exceptions with `from None` so Python exception chaining does not reattach the original secret-bearing exception.

## 2025-02-28 - [Credential Leak via Unhandled DSN URL Schemes]
**Vulnerability:** Python's `urllib.parse.urlsplit` fails to parse URL schemes containing underscores (like `snowflake_invalid://`), causing it to yield an empty scheme and unparsed network location. This resulted in the password candidate extraction logic failing to find and redact secrets in DSN strings with unusual schemes, exposing credentials in error logs and responses when connecting to incorrectly formatted DSNs.
**Learning:** URL parsing libraries may have strict syntax rules for schemes (RFC 3986) that reject common typographical errors (like underscores). When handling security-critical extractions like password redaction, you cannot rely entirely on standard URL parsers without handling malformed inputs.
**Prevention:** Before parsing DSNs specifically for secret redaction or extraction, ensure the scheme is either validated strictly, or apply a fallback/workaround (e.g., replacing the unknown scheme with a standard one temporarily) to ensure the parser correctly separates the user/password portion of the URI.
40 changes: 36 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
# pg-erd-cloud (MVP skeleton)
# pg-erd-cloud

PostgreSQL 중심 클라우드 ERD(협업/공유) 소프트웨어의 **실행 가능한 MVP 골격**입니다.
PostgreSQL 중심 클라우드 ERD(협업/공유) 소프트웨어입니다.
현재 상용화 준비 단계이며, 판매 가능성 기준은 [상용화 준비 계획](docs/commercial-readiness.md)로 운영합니다.

## 운영 문서(필수)

- 공개 공유 보안/운영: [상용 준비 계획](docs/commercial-readiness.md)
- 앱 DB 백업/복구: [backup-restore.md](docs/operations/backup-restore.md)
- 마이그레이션 롤백 정책: [migration-rollback.md](docs/operations/migration-rollback.md)
- 장애 대응: [incident-response.md](docs/operations/incident-response.md)
- 판매 준비 법무 문서:
- [라이선스/결제 가이드](docs/legal/license-billing.md)
- [개인정보 처리방침](docs/legal/privacy-policy.md)
- [이용약관](docs/legal/terms-of-service.md)
- [SLA/지원 기준](docs/legal/sla-support.md)
- [상용 릴리즈 승인 체크리스트](docs/legal/commercial-release-approval.md)

## 제공 기능(현재)

Expand All @@ -15,7 +29,7 @@ PostgreSQL 중심 클라우드 ERD(협업/공유) 소프트웨어의 **실행
PK/UNIQUE/FK 메타데이터를 수집하고 `source_dialect: "snowflake"` 스냅샷으로 저장합니다.
실행 환경에는 선택 의존성 `snowflake-connector-python`이 필요합니다.
- **ERD UI**: React Flow(MIT)로 PK/FK를 그래픽으로 렌더링
- **Forward engineering(포워드)**: MVP 단계에서는 “스냅샷 기반 DDL(export)” 중심
- **Forward engineering(포워드)**: 현재 제공 범위는 “스냅샷 기반 DDL(export)” 중심
(diff/변경 SQL 생성은 로드맵)

- SQL export: `GET /api/snapshots/{snapshot_uuid}/export.sql`
Expand All @@ -30,8 +44,11 @@ PostgreSQL 중심 클라우드 ERD(협업/공유) 소프트웨어의 **실행
- Live LLM draft: `GET /api/snapshots/{snapshot_uuid}/reversing-spec.md?mode=llm-draft`
- `LLM_API_BASE_URL`, `LLM_API_KEY`, `LLM_MODEL`을 설정한 OpenAI-compatible
chat-completions provider를 호출합니다.
- `LLM_MAX_PROMPT_CHARS`와 `LLM_MAX_OUTPUT_TOKENS`로 prompt 크기와 provider
출력 토큰 상한을 제한합니다.
- Share link에서도 동일한 `/api/share/{share_uuid}/snapshots/{snapshot_uuid}/...`
경로를 사용할 수 있습니다.
경로를 사용할 수 있습니다. 단, 공개 공유 링크의 `mode=llm-draft`는
`SHARE_LINK_LLM_DRAFT_ENABLED=true`로 명시적으로 허용하기 전까지 차단됩니다.
- **컬럼 예시값 힌트**: 리버스 스냅샷의 각 컬럼에 `example_value`를 추가합니다.
실제 테이블 데이터를 샘플링하지 않고 컬럼명/타입 메타데이터로 만든 합성 예시라서
ERD, PlantUML/SVG export, 명세서, LLM prompt에서 안전하게 참고할 수 있습니다.
Expand All @@ -45,8 +62,13 @@ curl -X POST "http://localhost:8000/api/projects/<project_uuid>/share-links"
```

반환된 `url_path`로 동료가 최신 스냅샷 목록/스냅샷 JSON/DDL export를 조회할 수 있습니다.
공유 링크는 기본적으로 `SHARE_LINK_DEFAULT_TTL_HOURS=168`(7일) 뒤 만료됩니다.
오너는 `GET /api/projects/{project_uuid}/share-links`로 링크를 조회하고,
`DELETE /api/projects/{project_uuid}/share-links/{share_uuid}`로 폐기할 수 있습니다.
`/api/share/*` 공개 조회/내보내기 경로는 전역 `/api/*` 제한보다 더 엄격한 별도
IP 기반 rate limit을 적용합니다.
공유 링크의 `markdown`/`llm-prompt` export는 로컬 생성만 수행하지만, `llm-draft`
export는 외부 LLM provider 비용을 만들 수 있으므로 기본값에서 비활성화되어 있습니다.

## 인덱스 타입 지원 원칙

Expand Down Expand Up @@ -104,6 +126,12 @@ PY
# Restrict secret file permissions (owner read/write only)
chmod 600 secrets/app_secret

# compose.prod.yaml은 APP_ENV=production으로 실행됩니다. 아래 값을 localhost가 아닌
# 운영 값으로 바꾸세요. 누락되면 백엔드는 시작 단계에서 실패합니다.
# - OIDC_ISSUER / OIDC_AUDIENCE
# - CORS_ORIGINS=https://erd.example.com
# - DB_INTROSPECTION_ALLOWED_HOSTS=db.example.com,*.internal.example.com

docker compose -f compose.prod.yaml up -d --build
```

Expand Down Expand Up @@ -146,6 +174,10 @@ pip install -e ".[snowflake]"
- `APP_SECRET`은 앱 DB에 저장되는 DSN 암호화 키로 사용되므로, 변경 시 기존 데이터
복호화에 영향을 줄 수 있습니다. 가능하면 `APP_SECRET_FILE`(예:
`/run/secrets/app_secret`) 방식으로 안전하게 주입하세요.
- `APP_ENV=production`에서는 OIDC, 공개 HTTPS CORS origin, 대상 DB allowlist,
32자 이상의 secret, 공유 링크 기본 만료가 startup guard로 강제됩니다.
- 백업/복구 절차는 [docs/operations/backup-restore.md](docs/operations/backup-restore.md)를
기준으로 운영합니다.

### Frontend

Expand Down
7 changes: 7 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,10 @@ We aim to:
- Fix the issue and coordinate disclosure within **90 days**, when feasible

Timelines may vary depending on severity, complexity, and downstream impact.

## Commercial Release Gate

Paid SaaS or on-prem releases must identify the person or team responsible for
security triage in `docs/legal/commercial-release-approval.md` before customer
delivery. A release without a private advisory path, triage owner, and customer
notification owner is not considered sales-ready.
4 changes: 2 additions & 2 deletions backend/alembic/versions/0003_revoked_token.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""revoked_token

Revision ID: 0003
Revises: 0002
Revises: 0002_auth_share
Create Date: 2026-06-22 10:00:00.000000

"""
Expand All @@ -12,7 +12,7 @@

# revision identifiers, used by Alembic.
revision = "0003"
down_revision = "0002"
down_revision = "0002_auth_share"
branch_labels = None
depends_on = None

Expand Down
51 changes: 51 additions & 0 deletions backend/alembic/versions/0004_billing_event.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""billing event reconciliation

Revision ID: 0004_billing_event
Revises: 0003, 0003_validate_project_space_fk
Create Date: 2026-07-02

"""

from __future__ import annotations

import sqlalchemy as sa
from alembic import op

revision = "0004_billing_event"
down_revision = ("0003", "0003_validate_project_space_fk")
branch_labels = None
depends_on = None


def upgrade() -> None:
op.create_table(
"billing_event",
sa.Column("billing_event_uuid", sa.Uuid(), primary_key=True, nullable=False),
sa.Column("provider", sa.Text(), nullable=False),
sa.Column("provider_event_id", sa.Text(), nullable=False),
sa.Column("event_type", sa.Text(), nullable=False),
sa.Column("subject", sa.Text(), nullable=False),
sa.Column("target_plan", sa.Text(), nullable=True),
sa.Column("event_status", sa.Text(), nullable=False),
sa.Column("occurred_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("received_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("metadata_json", sa.JSON(), nullable=False),
sa.UniqueConstraint(
"provider",
"provider_event_id",
name="uq_billing_event__provider_event_id",
),
)
op.create_index(
"ix_billing_event__subject_received_at",
"billing_event",
["subject", "received_at"],
)


def downgrade() -> None:
op.drop_index(
"ix_billing_event__subject_received_at",
table_name="billing_event",
)
op.drop_table("billing_event")
Loading
Loading