Skip to content

Repository files navigation

ERD Studio

브라우저에서 데이터베이스 ERD를 설계하는 사내 전용 도구입니다. 캔버스에서 테이블·컬럼·관계선을 직접 편집하고, SQL DDL을 주고받고, 팀원과 실시간으로 같은 다이어그램을 편집하는 것을 목표로 합니다. 데이터는 전부 자체 호스팅 PostgreSQL에만 저장됩니다.

현재 상태: Phase 4 (인증/팀/권한) 완료. 실시간 협업은 Phase 5에서 구현합니다.

기술 스택

영역 선택
웹 앱 Next.js 16 (App Router, Turbopack) + React 19 + TypeScript strict
스타일 Tailwind CSS v4 + shadcn/ui (Radix)
DB / ORM PostgreSQL 16 (Docker) + Prisma 7 (@prisma/adapter-pg)
캔버스 @xyflow/react (React Flow v12) — Phase 1
실시간 협업 Yjs CRDT + Hocuspocus 별도 Node 프로세스 — Phase 5
SQL 파싱 node-sql-parser + Oracle 전용 수제 DDL 파서 — Phase 3
모노레포 pnpm workspaces + Turborepo

저장소 구조

erd/
├── apps/
│   ├── web/              # Next.js 앱 (UI, Server Actions, CRUD, import/export)
│   └── collab/           # 실시간 협업 서버 (Hocuspocus 예정, 현재 placeholder)
├── packages/
│   ├── db/               # Prisma 스키마 + 생성된 클라이언트 (@erd/db)
│   ├── schema-ir/        # 방언 중립 SchemaIR 타입 (@erd/schema-ir)
│   ├── diagram-sync/     # Yjs 문서 키 / awareness 계약 (@erd/diagram-sync)
│   └── ui/               # 공유 shadcn 컴포넌트 자리 (@erd/ui, 현재 비어있음)
├── docker-compose.yml    # postgres:16
├── pnpm-workspace.yaml
└── turbo.json

패키지들은 빌드 산출물 대신 TypeScript 소스를 그대로 export 합니다. apps/web은 next.config.ts의 transpilePackages로, apps/collab은 esbuild 번들링으로 이를 컴파일합니다.

사전 요구사항

  • Node.js >= 20.9 (개발/검증은 22.23에서 진행)
  • pnpm 10 (corepack enable pnpm)
  • Docker Desktop

로컬 개발

# 1) 환경변수 준비 — 모노레포 전체가 루트 .env 하나를 공유합니다
cp .env.example .env

# 2) PostgreSQL 기동
docker compose up -d postgres

# 3) 의존성 설치
pnpm install

# 4) 스키마를 DB에 적용 (Prisma 클라이언트도 함께 생성됨)
pnpm db:migrate

# 5) 최초 관리자 계정 + 기본 팀/프로젝트 생성
pnpm db:seed

# 6) 개발 서버 — http://localhost:3000
pnpm --filter @erd/web dev

.env의 AUTH_SECRET은 반드시 채워야 합니다(비어 있으면 로그인이 동작하지 않습니다):

openssl rand -base64 32   # 결과를 .env의 AUTH_SECRET에 넣으세요

pnpm dev로 모든 앱(web + collab)을 한 번에 띄울 수도 있습니다.

호스트 포트 5433: 컨테이너의 5432를 호스트 5433에 매핑합니다. 로컬에 이미 설치된 PostgreSQL이 5432를 점유하고 있는 경우와 충돌하지 않도록 한 것으로, .env의 POSTGRES_PORT와 DATABASE_URL을 함께 바꾸면 다른 포트로 옮길 수 있습니다.

동작 확인용 헬스 엔드포인트가 있습니다 — curl http://localhost:3000/api/health는 web → @erd/db → PostgreSQL 경로가 살아있으면 {"status":"ok","database":"up",...}를 반환합니다.

주요 명령어

명령 설명
pnpm dev 모든 앱 개발 모드 (turbo)
pnpm build 전체 프로덕션 빌드
pnpm typecheck 전체 워크스페이스 타입 검사
pnpm lint ESLint
pnpm test 전체 워크스페이스 유닛 테스트 (vitest)
pnpm db:generate Prisma 클라이언트 생성
pnpm db:migrate 마이그레이션 생성 + 적용 (개발용)
pnpm db:seed 최초 관리자 계정 + 기본 팀/프로젝트 생성 (관리자 비밀번호 재설정 겸용)
pnpm db:studio Prisma Studio
pnpm --filter @erd/web dev 웹 앱만 기동
pnpm --filter @erd/collab dev 협업 서버만 기동

데이터 모델

packages/db/prisma/schema.prisma가 단일 소스입니다.

  • 워크스페이스: Team → TeamMember(OWNER/ADMIN/MEMBER), Folder(트리), Project → ProjectMember(EDITOR/COMMENTER/VIEWER). 팀 역할과 프로젝트 역할의 2단계 권한 모델입니다.
  • 다이어그램: Diagram → Table → Column, 그리고 Relationship. 관계는 sourceColumnIds/targetColumnIds 배열로 복합키를 지원하고, cardinality는 사용자가 오버라이드할 수 있도록 비정규화 저장합니다.
  • 협업: Comment(요소 핀 + 스레드), DiagramVersion(전체 상태 JSON 스냅샷 → O(1) 복원), ShareLink(token / VIEW·COMMENT / 만료 / 회수).
  • 인증: Auth.js(NextAuth) Prisma adapter 표준 스키마 — User/Account/Session/VerificationToken/Authenticator. User.passwordHash는 사내 Credentials 로그인용입니다.

Prisma 7 참고사항

  • 생성기는 prisma-client(구 prisma-client-js)이고 출력 경로가 필수입니다 → packages/db/src/generated/prisma (gitignore 대상, pnpm db:generate로 재생성).
  • PostgreSQL 접속은 드라이버 어댑터 @prisma/adapter-pg를 경유합니다 (packages/db/src/client.ts).
  • CLI가 .env를 자동으로 읽지 않으므로 packages/db/prisma.config.ts가 루트 .env를 명시적으로 로드합니다.

인증 · 팀 · 권한

Auth.js(NextAuth v5) Credentials + 데이터베이스 세션입니다. 사내 전용 도구이므로 공개 회원가입, 초대 메일, OAuth는 없습니다 — 관리자가 계정을 직접 만들고 비밀번호를 전달합니다.

최초 관리자 계정

pnpm db:seed가 .env의 값으로 관리자 한 명과 기본 팀/프로젝트를 만듭니다.

변수 기본값 비고
ADMIN_EMAIL admin@erd.local 로그인 이메일
ADMIN_PASSWORD admin-change-me 배포 전 반드시 변경 (8자 이상)
ADMIN_NAME 관리자 표시 이름

시드를 다시 돌리면 이 계정의 비밀번호가 ADMIN_PASSWORD로 재설정됩니다 — 관리자 비밀번호를 잃어버렸을 때의 복구 경로이기도 합니다.

권한 모델

2단계입니다. 두 역할은 하나의 유효 프로젝트 역할로 합쳐집니다.

역할 의미
팀 TeamMember.role = OWNER / ADMIN / MEMBER 팀을 관리하는 사람
프로젝트 ProjectMember.role = EDITOR / COMMENTER / VIEWER 내용을 만질 수 있는 사람
  • 팀 OWNER/ADMIN → 팀 안 모든 프로젝트에서 EDITOR이며, 프로젝트 자체(이름 변경·삭제·멤버 관리)도 관리합니다.
  • 그 외 → 자신의 ProjectMember 행이 있으면 그 역할, 없으면 접근 불가. 팀 MEMBER라는 사실만으로는 모든 프로젝트가 보이지 않습니다(팀에 속하면 프로젝트에 추가될 자격이 생길 뿐입니다).
  • 프로젝트를 만든 사람은 자동으로 그 프로젝트의 EDITOR가 됩니다.
  • 프로젝트 삭제·이름 변경·멤버 관리는 팀 OWNER/ADMIN만 가능합니다. ProjectRole에는 ADMIN 단계가 없어서, 계획서의 "프로젝트 ADMIN 이상"을 이렇게 해석했습니다.

서버가 최종 방어선입니다

apps/web/lib/auth/permissions.ts 한 곳에 권한 판단이 모여 있고, 모든 Server Action이 requireDiagramAccess / requireProjectAccess / requireTeamAccess로 DB에서 역할을 다시 계산합니다. 클라이언트에서 버튼을 숨기는 것은 표시용일 뿐입니다.

proxy.ts(Next.js 16에서 middleware.ts가 개명된 것)는 세션 쿠키의 존재만 확인해 /login으로 보내는 낙관적 체크입니다. 인가 경계가 아닙니다 — Next 공식 문서대로 Server Function은 자신이 속한 라우트로 가는 평범한 POST라서, matcher를 조정하거나 액션을 옮기면 보호가 조용히 사라질 수 있습니다. 위조한 쿠키는 proxy를 통과하지만 액션에서 로그인이 필요합니다로 거부됩니다.

세션을 즉시 회수할 수 있는 이유

세션 쿠키에는 JWT가 아니라 Session 행을 가리키는 불투명 토큰(UUID) 만 들어갑니다. 행을 지우면 그 사용자는 다음 요청에서 바로 로그아웃됩니다 — 팀 관리 화면의 비활성화 와 비밀번호 재설정 이 그 사용자의 모든 세션을 삭제하는 것도 이 때문입니다.

Auth.js는 원래 "Credentials + session.strategy: "database"" 조합을 거부합니다. session.strategy를 명시하지 않고(어댑터가 있으면 Auth.js가 이미 "database"를 기본값으로 씁니다) jwt 콜백에서 세션 행을 직접 만든 뒤 jwt.encode가 그 토큰을 그대로 쿠키에 넣는 방식으로 해결했습니다. 근거와 검증 내용은 apps/web/lib/auth/config.ts의 모듈 주석에 적어 두었습니다.

SQL Import / Export

packages/schema-ir가 방언 중립 SchemaIR를 사이에 두고 양방향을 담당합니다.

DDL 텍스트 ──parse──▶ SchemaIR ──▶ 다이어그램 모델
DDL 텍스트 ◀─generate─ SchemaIR ◀── 다이어그램 모델
  • 파싱: PostgreSQL / MySQL / MSSQL은 node-sql-parser를 먼저 시도하고, 이 라이브러리가 거부하는 문장은 자체 재귀 하강 파서로 폴백합니다(T-SQL의 ALTER TABLE ADD CONSTRAINT ... FOREIGN KEY, PostgreSQL의 GENERATED ALWAYS AS IDENTITY 등 실제 미지원 구문이 있습니다). Oracle은 node-sql-parser에 문법 자체가 없어 자체 파서만 사용합니다.
  • 자체 파서가 다루는 문장 — 이 다섯 가지뿐이고 나머지는 경고로 표시한 뒤 건너뜁니다: CREATE TABLE, ALTER TABLE ... ADD [CONSTRAINT] PRIMARY KEY|UNIQUE|FOREIGN KEY, COMMENT ON TABLE, COMMENT ON COLUMN, CREATE [UNIQUE] INDEX.
  • 생성: 네 방언 모두 템플릿 기반입니다. FK는 테이블을 모두 만든 뒤 ALTER TABLE로 붙이므로 테이블 간 순환 참조가 문제되지 않습니다.
  • 타입 매핑은 src/type-map.ts 한 곳에 모여 있습니다(varchar → VARCHAR2/NVARCHAR, auto increment → BIGSERIAL/AUTO_INCREMENT/IDENTITY(1,1)/GENERATED ALWAYS AS IDENTITY).
  • Import는 항상 diff/preview 후 확정입니다. 파싱은 Server Action에서 실행되고(브라우저 번들에 파서·elkjs가 들어가지 않도록), 확정 시 에디터 스토어에 반영된 뒤 기존 저장 경로로 영속화됩니다. 좌표가 없는 신규 테이블은 elkjs layered 레이아웃으로 배치합니다.

알려진 한계:

  • MSSQL에는 표준 주석 DDL이 없어(sp_addextendedproperty는 DDL이 아님) 주석을 -- 줄 주석으로 내보내며, 다시 가져올 때 복원되지 않습니다.
  • 방언이 구분하지 못하는 타입은 왕복에서 합쳐집니다 — 예: Oracle/MSSQL의 json은 text와 같은 저장 타입(CLOB / NVARCHAR(MAX))이고, Oracle에는 time 타입이 없습니다.
  • CHECK 제약, 트리거, 시퀀스, 파티션 등 다이어그램 모델에 대응이 없는 요소는 무시합니다.

구현 로드맵

단계 범위
0 ✅ 저장소 스캐폴드, Prisma 스키마 v1, Postgres(Docker), Tailwind+shadcn
1 ✅ 캔버스 편집기 코어(단일 사용자): React Flow, 인라인 편집, 관계선, zoom/pan/minimap
2 ✅ 디바운스 자동저장, DiagramVersion 수동 저장/복원 UI
3 ✅ SQL import/export(@erd/schema-ir), elkjs 오토 레이아웃
4 ✅ Auth.js 인증(Credentials + DB 세션), 팀/프로젝트 2단계 권한, 라우트 보호, 팀·멤버 관리 UI
5 실시간 협업: apps/collab(Hocuspocus), 캔버스를 Yjs 기반으로 전환, 커서 프레즌스
6 댓글/스레드
7 버전 diff/preview, 공유 링크 및 읽기 전용 뷰어
8 성능 튜닝, 감사 로그, 백업 전략

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages