-
Notifications
You must be signed in to change notification settings - Fork 0
Bootstrap: PostgreSQL cloud ERD MVP #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c38a525
bd14394
597a39d
f7fa729
f105b00
7e0ccc4
4df21f2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| version: 2 | ||
| updates: | ||
| - package-ecosystem: "github-actions" | ||
| directory: "/" | ||
| schedule: | ||
| interval: "weekly" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| name: ci | ||
|
|
||
| on: | ||
| push: | ||
| branches: ["main"] | ||
| pull_request: | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| backend: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Harden runner | ||
| uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4 | ||
| with: | ||
| egress-policy: audit | ||
|
|
||
| - name: Checkout | ||
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | ||
|
|
||
| - name: Setup Python | ||
| uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 | ||
| with: | ||
| python-version: "3.10" | ||
|
|
||
| - name: Install backend deps | ||
| working-directory: backend | ||
| run: | | ||
| python -m pip install -U pip | ||
| python -m pip install -e ".[dev]" | ||
Check warningCode scanning / Scorecard Pinned-Dependencies Medium
score is 4: pipCommand not pinned by hash
Click Remediation section below to solve this issue |
||
|
|
||
| - name: Typecheck (mypy) | ||
| working-directory: backend | ||
| run: mypy app | ||
|
|
||
| - name: Tests (pytest) | ||
| working-directory: backend | ||
| run: pytest -q | ||
|
|
||
| frontend: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Harden runner | ||
| uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4 | ||
| with: | ||
| egress-policy: audit | ||
|
|
||
| - name: Checkout | ||
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | ||
|
|
||
| - name: Setup Node | ||
| uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 | ||
| with: | ||
| node-version: "20" | ||
| cache: "npm" | ||
| cache-dependency-path: frontend/package-lock.json | ||
|
|
||
| - name: Install | ||
| working-directory: frontend | ||
| run: npm ci | ||
|
|
||
| - name: Typecheck | ||
| working-directory: frontend | ||
| run: npm run typecheck | ||
|
|
||
| - name: Build | ||
| working-directory: frontend | ||
| run: npm run build | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| name: codeql | ||
|
|
||
| on: | ||
| push: | ||
| branches: ["main"] | ||
| pull_request: | ||
| schedule: | ||
| - cron: "0 2 * * 1" | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| analyze: | ||
| name: Analyze | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| actions: read | ||
| contents: read | ||
| security-events: write | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| language: ["javascript-typescript", "python"] | ||
|
Comment on lines
+21
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial 🧩 Analysis chain🌐 Web query:
💡 Result:
How to use
Example pattern (from the action docs): [1][2]
Inputs
Notes called out by the docs
Sources: Action repository docs / README and Marketplace listing [1][2], and GitHub Docs on CodeQL advanced setup language matrices [3]. [1] https://github.com/advanced-security/set-codeql-language-matrix 🌐 Web query:
💡 Result: Best practices (2025) for dynamic CodeQL language-matrix detection in GitHub Actions
Reference pattern (dynamic matrix + CodeQL)jobs:
create-matrix:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- id: set-matrix
uses: advanced-security/set-codeql-language-matrix@v1
with:
access-token: ${{ secrets.GITHUB_TOKEN }}
endpoint: ${{ github.event.repository.languages_url }}
analyze:
needs: create-matrix
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.create-matrix.outputs.matrix) }}
runs-on: ${{ matrix.language == 'swift' && 'macos-latest' || 'ubuntu-latest' }}
steps:
- uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
- uses: github/codeql-action/analyze@v4
with:
category: "/language:${{ matrix.language }}"Sources: GitHub Docs on advanced setup customization and language identifiers [1][2][3], and the matrix-generation action guidance (including CodeQL 언어 매트릭스를 동적으로 감지하여 유지보수 비용을 줄이세요. 현재 언어가 하드코딩되어 있어 새로운 언어가 추가되면 워크플로우도 수정해야 합니다. ♻️ 적용 예시 jobs:
+ set-matrix:
+ runs-on: ubuntu-latest
+ outputs:
+ matrix: ${{ steps.set-matrix.outputs.matrix }}
+ steps:
+ - name: CodeQL 언어 매트릭스 생성
+ id: set-matrix
+ uses: advanced-security/set-codeql-language-matrix@v1
+ with:
+ access-token: ${{ secrets.GITHUB_TOKEN }}
+ endpoint: ${{ github.event.repository.languages_url }}
+
analyze:
+ needs: set-matrix
strategy:
fail-fast: false
- matrix:
- language: ["javascript-typescript", "python"]
+ matrix: ${{ fromJSON(needs.set-matrix.outputs.matrix) }}
runs-on: ubuntu-latest필요시 🤖 Prompt for AI Agents |
||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | ||
|
|
||
| - name: Initialize CodeQL | ||
| uses: github/codeql-action/init@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 | ||
| with: | ||
| languages: ${{ matrix.language }} | ||
|
|
||
| - name: Autobuild | ||
| uses: github/codeql-action/autobuild@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 | ||
|
|
||
| - name: Perform CodeQL Analysis | ||
| uses: github/codeql-action/analyze@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| name: dependency-review | ||
|
|
||
| on: | ||
| pull_request: | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| dependency-review: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | ||
| - name: Dependency Review | ||
| uses: actions/dependency-review-action@ce3cf9537a52e8119d91fd484ab5b8a807627bf8 # v4.6.0 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| name: scorecard | ||
|
|
||
| on: | ||
| branch_protection_rule: | ||
| schedule: | ||
| - cron: "30 1 * * 6" | ||
| push: | ||
| branches: ["main"] | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| analysis: | ||
| permissions: | ||
| contents: read | ||
| security-events: write | ||
| id-token: write | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - name: Run Scorecard | ||
| uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 | ||
| with: | ||
| results_file: results.sarif | ||
| results_format: sarif | ||
| publish_results: true | ||
|
|
||
| - name: Upload SARIF | ||
| uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 | ||
| with: | ||
| sarif_file: results.sarif |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -13,6 +13,19 @@ PostgreSQL 전문 클라우드 ERD(협업/공유) 소프트웨어의 **실행 | |||||
| - **ERD UI**: React Flow(MIT)로 PK/FK를 그래픽으로 렌더링 | ||||||
| - **Forward engineering(포워드)**: MVP 단계에서는 “스냅샷 기반 DDL(export)” 중심 (diff/변경 SQL 생성은 로드맵) | ||||||
|
|
||||||
| - SQL export: `GET /api/snapshots/{snapshot_uuid}/export.sql` | ||||||
|
|
||||||
| ## 공유(동료에게 링크 공유) | ||||||
|
|
||||||
| 프로젝트 오너는 공유 링크를 생성할 수 있습니다. | ||||||
|
|
||||||
| ```bash | ||||||
| curl -X POST "http://localhost:8000/api/projects/<project_uuid>/share-links" \ | ||||||
| -H "X-Dev-User: alice" | ||||||
| ``` | ||||||
|
|
||||||
| 반환된 `url_path`로 동료가 최신 스냅샷 목록/스냅샷 JSON/DDL export를 조회할 수 있습니다. | ||||||
|
|
||||||
| ## 인덱스 타입 지원 원칙 | ||||||
|
|
||||||
| PostgreSQL은 `CREATE INDEX ... USING <method>`의 `<method>`가 기본(btree/hash/gist/gin/spgist/brin)뿐 아니라 **확장(extension)으로 추가된 access method**도 될 수 있습니다. | ||||||
|
|
@@ -40,6 +53,17 @@ docker compose up -d --build | |||||
| - Frontend: http://localhost:5173 | ||||||
| - Backend: http://localhost:8000 (health: /healthz) | ||||||
|
|
||||||
| ## 실행(프로덕션 스타일, Docker) | ||||||
|
|
||||||
| 프론트는 정적 빌드 + nginx로 서빙하며, `/api/*`는 백엔드로 프록시합니다. | ||||||
|
|
||||||
| ```bash | ||||||
| cp .env.example .env | ||||||
| docker compose -f compose.prod.yaml up -d --build | ||||||
| ``` | ||||||
|
|
||||||
| - Frontend: http://localhost:8080 | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Bare URL을 Markdown 링크로 변환하세요. markdownlint MD034 규칙에 따라, bare URL은 Markdown 링크 형식으로 감싸야 합니다. 📝 제안된 수정-- Frontend: http://localhost:8080
+- Frontend: <http://localhost:8080>📝 Committable suggestion
Suggested change
🧰 Tools🪛 markdownlint-cli2 (0.20.0)[warning] 65-65: Bare URL used (MD034, no-bare-urls) 🤖 Prompt for AI Agents |
||||||
|
|
||||||
| ## 개발(비-Docker) | ||||||
|
|
||||||
| ### Backend | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,141 @@ | ||
| """auth + share links + foreign keys | ||
|
|
||
| Revision ID: 0002_auth_share | ||
| Revises: 0001_init | ||
| Create Date: 2026-01-31 | ||
|
|
||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import sqlalchemy as sa | ||
| from alembic import op | ||
|
|
||
| revision = "0002_auth_share" | ||
| down_revision = "0001_init" | ||
| branch_labels = None | ||
| depends_on = None | ||
|
|
||
|
|
||
| def upgrade() -> None: | ||
| # Add FK constraints (MVP: best-effort for fresh DB) | ||
| op.create_foreign_key( | ||
| "fk_project_space__created_by_user", | ||
| "project_space", | ||
| "user_account", | ||
| ["created_by_user_uuid"], | ||
| ["user_account_uuid"], | ||
| ) | ||
|
Comment on lines
+20
to
+28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Adding the foreign key on Useful? React with 👍 / 👎.
Comment on lines
+22
to
+28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, check the file exists and view the foreign key definitions
cat -n backend/alembic/versions/0002_auth_share.py | head -150Repository: HYOSUNG-ITX-AI-Business-Department/pg-erd-cloud Length of output: 5187 🏁 Script executed: # Search for user deletion patterns in the codebase
rg -n 'delete.*user|cascade|ondelete' backend --type=py -i -A 2 -B 2 | head -100Repository: HYOSUNG-ITX-AI-Business-Department/pg-erd-cloud Length of output: 2949 🏁 Script executed: # Look for any migration files that handle user deletion
fd -e py 'migration|alembic' backend --type f | head -20Repository: HYOSUNG-ITX-AI-Business-Department/pg-erd-cloud Length of output: 73 🏁 Script executed: # Search for application-level user deletion logic or soft deletes
rg -n 'class.*User|def.*delete.*user' backend --type=py -i -A 3 | head -80Repository: HYOSUNG-ITX-AI-Business-Department/pg-erd-cloud Length of output: 502 🏁 Script executed: # Check if user_account has a soft delete or is_deleted column
rg -n 'is_deleted|deleted_at|is_archived|status.*delete' backend --type=pyRepository: HYOSUNG-ITX-AI-Business-Department/pg-erd-cloud Length of output: 73 🏁 Script executed: # Search for any user deletion business logic or permissions check
rg -n 'def.*delete|cannot.*delete|prevent.*delete' backend --type=py -i -A 3Repository: HYOSUNG-ITX-AI-Business-Department/pg-erd-cloud Length of output: 73 🏁 Script executed: # Look for any comments or documentation about user deletion constraints
rg -n 'TODO|FIXME|NOTE|BUG|HACK' backend/alembic/versions/0002_auth_share.pyRepository: HYOSUNG-ITX-AI-Business-Department/pg-erd-cloud Length of output: 73 🏁 Script executed: # Check if there's a pattern where ondelete is intentionally omitted
rg -n 'MVP|best-effort|fresh DB' backend/alembic --type=py -B 2 -A 5Repository: HYOSUNG-ITX-AI-Business-Department/pg-erd-cloud Length of output: 702
두 관계가 동일한 의미(사용자가 생성)이므로 동일한 삭제 정책을 적용해야 합니다. 주석의 "MVP: best-effort"는 이 FK 정의가 미완성임을 시사하므로, 🤖 Prompt for AI Agents |
||
| op.create_foreign_key( | ||
| "fk_project_member__project_space", | ||
| "project_member", | ||
| "project_space", | ||
| ["project_space_uuid"], | ||
| ["project_space_uuid"], | ||
| ondelete="CASCADE", | ||
| ) | ||
| op.create_foreign_key( | ||
| "fk_project_member__user_account", | ||
| "project_member", | ||
| "user_account", | ||
| ["user_account_uuid"], | ||
| ["user_account_uuid"], | ||
| ondelete="CASCADE", | ||
| ) | ||
| op.create_foreign_key( | ||
| "fk_db_connection__project_space", | ||
| "db_connection", | ||
| "project_space", | ||
| ["project_space_uuid"], | ||
| ["project_space_uuid"], | ||
| ondelete="CASCADE", | ||
| ) | ||
| op.create_foreign_key( | ||
| "fk_schema_snapshot__project_space", | ||
| "schema_snapshot", | ||
| "project_space", | ||
| ["project_space_uuid"], | ||
| ["project_space_uuid"], | ||
| ondelete="CASCADE", | ||
| ) | ||
| op.create_foreign_key( | ||
| "fk_schema_snapshot__db_connection", | ||
| "schema_snapshot", | ||
| "db_connection", | ||
| ["db_connection_uuid"], | ||
| ["db_connection_uuid"], | ||
| ondelete="CASCADE", | ||
| ) | ||
| op.create_foreign_key( | ||
| "fk_schema_snapshot_data__schema_snapshot", | ||
| "schema_snapshot_data", | ||
| "schema_snapshot", | ||
| ["schema_snapshot_uuid"], | ||
| ["schema_snapshot_uuid"], | ||
| ondelete="CASCADE", | ||
| ) | ||
|
|
||
| # Share links | ||
| op.create_table( | ||
| "share_link", | ||
| sa.Column("share_link_uuid", sa.Uuid(), primary_key=True, nullable=False), | ||
| sa.Column("project_space_uuid", sa.Uuid(), nullable=False), | ||
| sa.Column("created_by_user_uuid", sa.Uuid(), nullable=False), | ||
| sa.Column("permission_kind", sa.Text(), nullable=False), | ||
| sa.Column("expires_at", sa.DateTime(timezone=True), nullable=True), | ||
| sa.Column("created_at", sa.DateTime(timezone=True), nullable=False), | ||
| ) | ||
| op.create_index( | ||
| "ix_share_link__project_space_uuid", | ||
| "share_link", | ||
| ["project_space_uuid"], | ||
| ) | ||
| op.create_foreign_key( | ||
| "fk_share_link__project_space", | ||
| "share_link", | ||
| "project_space", | ||
| ["project_space_uuid"], | ||
| ["project_space_uuid"], | ||
| ondelete="CASCADE", | ||
| ) | ||
| op.create_foreign_key( | ||
| "fk_share_link__created_by_user", | ||
| "share_link", | ||
| "user_account", | ||
| ["created_by_user_uuid"], | ||
| ["user_account_uuid"], | ||
| ondelete="CASCADE", | ||
| ) | ||
|
|
||
|
|
||
| def downgrade() -> None: | ||
| op.drop_constraint( | ||
| "fk_share_link__created_by_user", "share_link", type_="foreignkey" | ||
| ) | ||
| op.drop_constraint("fk_share_link__project_space", "share_link", type_="foreignkey") | ||
| op.drop_index("ix_share_link__project_space_uuid", table_name="share_link") | ||
| op.drop_table("share_link") | ||
|
|
||
| op.drop_constraint( | ||
| "fk_schema_snapshot_data__schema_snapshot", | ||
| "schema_snapshot_data", | ||
| type_="foreignkey", | ||
| ) | ||
| op.drop_constraint( | ||
| "fk_schema_snapshot__db_connection", "schema_snapshot", type_="foreignkey" | ||
| ) | ||
| op.drop_constraint( | ||
| "fk_schema_snapshot__project_space", "schema_snapshot", type_="foreignkey" | ||
| ) | ||
| op.drop_constraint( | ||
| "fk_db_connection__project_space", "db_connection", type_="foreignkey" | ||
| ) | ||
| op.drop_constraint( | ||
| "fk_project_member__user_account", "project_member", type_="foreignkey" | ||
| ) | ||
| op.drop_constraint( | ||
| "fk_project_member__project_space", "project_member", type_="foreignkey" | ||
| ) | ||
| op.drop_constraint( | ||
| "fk_project_space__created_by_user", "project_space", type_="foreignkey" | ||
| ) | ||
Check warning
Code scanning / Scorecard
Pinned-Dependencies Medium