# DB 설계(1차) ## 핵심 엔티티 - Campaign(종목), LandingPage, LandingRoute, RouteCondition, Lead, User - 역할: ADMIN / LEAD_MANAGER ## 엔티티 관계 - Campaign 1:N LandingPage - Campaign 1:N LandingRoute - LandingRoute 1:N RouteCondition - LandingRoute N:1 LandingPage(기본 페이지) - RouteCondition N:1 LandingPage(조건 매칭 페이지) - LandingPage 1:N Lead - Campaign 1:N Lead ## 권한 정책 - ADMIN: 모든 관리자 기능 - LEAD_MANAGER: 리드 조회만 가능 ## Prisma 스키마 (SQLite 기준) ```prisma generator client { provider = "prisma-client-js" } datasource db { provider = "sqlite" url = env("DATABASE_URL") } enum UserRole { ADMIN LEAD_MANAGER } model Campaign { id String @id @default(cuid()) name String description String? isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt landingPages LandingPage[] routes LandingRoute[] leads Lead[] } model LandingPage { id String @id @default(cuid()) campaignId String campaign Campaign @relation(fields: [campaignId], references: [id], onDelete: Cascade) name String slug String? blocks Json isDefault Boolean @default(false) isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt defaultRoutes LandingRoute[] @relation("RouteDefaultPage") conditions RouteCondition[] leads Lead[] @@index([campaignId, isActive]) } model LandingRoute { id String @id @default(cuid()) campaignId String campaign Campaign @relation(fields: [campaignId], references: [id], onDelete: Cascade) host String path String defaultPageId String defaultPage LandingPage @relation("RouteDefaultPage", fields: [defaultPageId], references: [id], onDelete: Restrict) isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt conditions RouteCondition[] leads Lead[] @@unique([host, path]) } model RouteCondition { id String @id @default(cuid()) routeId String route LandingRoute @relation(fields: [routeId], references: [id], onDelete: Cascade) pageId String page LandingPage @relation(fields: [pageId], references: [id], onDelete: Restrict) label String priority Int @default(0) isActive Boolean @default(true) startDate DateTime? endDate DateTime? timezone String @default("Asia/Seoul") weekMask String @default("0000000") startMinute Int? endMinute Int? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model Lead { id String @id @default(cuid()) campaignId String campaign Campaign @relation(fields: [campaignId], references: [id], onDelete: Cascade) pageId String page LandingPage @relation(fields: [pageId], references: [id], onDelete: Restrict) routeId String? route LandingRoute? @relation(fields: [routeId], references: [id], onDelete: SetNull) payload Json sourceMeta Json? submittedAt DateTime @default(now()) @@index([campaignId, submittedAt]) } model User { id String @id @default(cuid()) email String @unique name String? password String role UserRole @default(ADMIN) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } ``` ### 참고 - `defaultRoutes`는 `@relation("RouteDefaultPage")`로 라우트 기본 페이지 역참조입니다. - 동일 엔티티에서 기본 라우트를 찾을 때는 `LandingRoute.defaultPageId`가 FK입니다. - PostgreSQL 전환 시 datasource `provider`만 교체하지 않고 nullable/시간대 처리 포함 마이그레이션 검증이 필요하다.