Untitled (8).png

// =====================================================
// [Project] 모아요 ERD v2.1 
// [Naming] lowercase + snake_case
// [PK] id (bigint, increment)
// [Policy] 기능명세 + 카톡 답변 + 홈(알림/AI/유저탐색) 업데이트 반영
//
// 반영 체크
// - 공개/비공개: 프로필 전체 X, 이력(experiences) 개별 O
// - 프로필 기본정보: null 허용 X (비우면 오류)
// - 이력 추가(3.1): '+' 시점에 draft 빈 row 선생성
// - 프로필 증빙(1.9): 검수 상태 X, 개수 제한 3개(서비스 로직)
// - 이력 파일(4.x): 개수 제한 3개(서비스 로직)
// - 관심 태그: 사전 정의 태그 선택(유저 생성 X)
// - 홈: 알림(Notifications), 게시글 유도(관심태그 매칭), AI 이력 초안(draft experience에 반영)
// - 쪽지: 메시지 삭제 버튼(소프트 딜리트 컬럼 추가)
// =====================================================

// -----------------------------------------------------
// 1. 사용자
// -----------------------------------------------------
Table users {
  id bigint [pk, increment]

  // OAuth 로그인 핵심 식별자
  oauth_provider varchar [not null, default: 'google', note: "google/kakao/naver 등 확장 대비"]
  oauth_sub varchar [not null, note: "OAuth provider가 주는 고유 식별자 (Google sub)"]

  email varchar [not null, unique, note: "구글 계정 이메일"]
  name varchar [not null, note: "사용자 실명/활동명"]
  phone_number varchar [note: "연락처(선택)"]

  created_at timestamp [not null, default: `now()`]
  updated_at timestamp [not null, default: `now()`]

  indexes {
    (oauth_provider, oauth_sub) [unique, name: "uk_users_oauth"]
  }
}

// -----------------------------------------------------
// 2. 프로필 (users 1:1)
// - 기본정보 null 허용 X: bio/university/major 필수
// - 대표 이미지 1장 교체형(image_url)
// -----------------------------------------------------
Table profiles {
  id bigint [pk, increment]
  user_id bigint [not null, unique, note: "users 1:1"]
  image_url varchar [note: "프로필 대표 이미지(없으면 기본 이미지)"]
  bio text [not null, note: "자기소개(필수)"]
  university varchar [not null, note: "대학명(필수)"]
  major varchar [not null, note: "학과/부가정보(필수)"]
  created_at timestamp [not null, default: `now()`]
  updated_at timestamp [not null, default: `now()`]
}

// -----------------------------------------------------
// 2-1. 프로필 증빙 서류 (명세 1.9)
// - 검수 상태 사용 X
// - profile_id 당 최대 3개는 서비스 로직으로 제한
// -----------------------------------------------------
Table profile_documents {
  id bigint [pk, increment]
  profile_id bigint [not null]
  file_url varchar [not null]
  file_name varchar
  file_type varchar [note: "pdf/image 등"]
  file_size bigint [note: "bytes"]
  created_at timestamp [not null, default: `now()`]

  indexes {
    (profile_id) [name: "idx_profile_documents_profile_id"]
  }
}

// -----------------------------------------------------
// 2-2. 프로필 커스텀 링크/추가정보 (명세 1.10)
// - 예: 포트폴리오 링크, 깃허브 링크 등
// - (기능명세: 개수 제한 4개) -> 서비스 로직에서 제한
// -----------------------------------------------------
Table profile_index_items {
  id bigint [pk, increment]
  profile_id bigint [not null]

  // "+ 버튼" 눌러서 채우는 인덱스(라벨)들
  index_key varchar [not null, note: "인덱스 타입 예: education/portfolio/link/etc (PM이 정한 값 또는 드롭다운)"]
  index_value varchar [not null, note: "인덱스 값 예: 한양대, 깃허브, 포폴1 등"]

  // 첨부 타입: 파일/링크/텍스트 중 하나만
  item_type varchar [not null, note: "file/link/text"]

  // item_type에 따라 아래 중 하나만 사용
  text_value text [note: "item_type=text 일 때"]
  link_url varchar [note: "item_type=link 일 때"]

  file_url varchar [note: "item_type=file 일 때"]
  file_name varchar
  file_type varchar [note: "pdf/image 등"]
  file_size bigint [note: "bytes"]

  created_at timestamp [not null, default: `now()`]
  updated_at timestamp [not null, default: `now()`]

  indexes {
    (profile_id) [name: "idx_profile_index_items_profile_id"]
    (profile_id, index_key) [name: "idx_profile_index_items_profile_index_key"]
    (profile_id, item_type) [name: "idx_profile_index_items_profile_item_type"]
  }
}

// -----------------------------------------------------
// 3. 관심사 태그 (사전 정의 목록)
// - 유저는 선택만 가능(생성 X)
// -----------------------------------------------------
Table interest_tags {
  id bigint [pk, increment]
  name varchar [not null, unique, note: "태그명(사전 정의)"]
}

Table user_interest_tags {
  id bigint [pk, increment]
  user_id bigint [not null]
  interest_tag_id bigint [not null]
  created_at timestamp [not null, default: `now()`]

  indexes {
    (user_id, interest_tag_id) [unique, name: "uk_user_interest_tags"]
    (user_id) [name: "idx_user_interest_tags_user_id"]
    (interest_tag_id) [name: "idx_user_interest_tags_interest_tag_id"]
  }
}

// -----------------------------------------------------
// 4. 이력(Experience)
// - '+' 클릭 시 draft 빈 row 선생성 (필수값 null 허용)
// - published 전환 시 서비스 로직으로 필수값 검증
// - 공개 프로필 뷰: is_public=false 이력만 제외하고 반환
// -----------------------------------------------------
Table experiences {
  id bigint [pk, increment]
  user_id bigint [not null]

  activity_name varchar [note: "활동명(published 시 필수)"]
  organization varchar [note: "주최기관"]
  start_date date
  end_date date
  participation_type varchar [note: "참여형태(개인/팀 등)"]
  job_role varchar [note: "직무 및 역할"]
  description text [note: "회고 및 성과 요약(자유서술/AI 초안 포함 가능)"]

  status varchar [not null, default: 'draft', note: "draft/published"]
  is_public boolean [not null, default: true, note: "개별 이력 공개 여부"]

  created_at timestamp [not null, default: `now()`]
  updated_at timestamp [not null, default: `now()`]

  indexes {
    (user_id) [name: "idx_experiences_user_id"]
    (status) [name: "idx_experiences_status"]
    (is_public) [name: "idx_experiences_is_public"]
  }
}

// -----------------------------------------------------
// 4-1. 이력 첨부 파일 (명세 4.1~4.3)
// - experience_id 당 최대 3개는 서비스 로직으로 제한
// -----------------------------------------------------
Table experience_files {
  id bigint [pk, increment]
  experience_id bigint [not null]
  file_url varchar [not null]
  file_name varchar
  file_type varchar [note: "pdf/image 등"]
  file_size bigint [note: "bytes"]
  created_at timestamp [not null, default: `now()`]

  indexes {
    (experience_id) [name: "idx_experience_files_experience_id"]
  }
}

// -----------------------------------------------------
// 4-2. 이력 링크 첨부 (명세 4.4)
// -----------------------------------------------------
Table experience_links {
  id bigint [pk, increment]
  experience_id bigint [not null]
  url varchar [not null]
  created_at timestamp [not null, default: `now()`]

  indexes {
    (experience_id) [name: "idx_experience_links_experience_id"]
  }
}

// -----------------------------------------------------
// 5. 게시판(모집글)
// - 목록 카드: 제목/요약(본문 앞부분)/작성자 프로필이미지/D-day
// - 필터: 직무 카테고리(job_categories)
// - 홈 유도: 관심 태그 일치하는 게시글 노출(= post_interest_tags 필요)
// -----------------------------------------------------
Table job_categories {
  id bigint [pk, increment]
  name varchar [not null, unique, note: "직무명(필터용)"]
}

Table posts {
  id bigint [pk, increment]
  author_id bigint [not null, note: "작성자(users)"]
  job_category_id bigint [not null, note: "직무 카테고리(job_categories)"]

  title varchar [not null, note: "글자수 제한 50"]
  content text [not null, note: "프로젝트 설명/분위기/목표 등 (500자 제한)"]
  requirements text [note: "요구 역량/조건(자유서술)"]

  recruit_count int [note: "모집 인원(선택, null 가능)"]
  deadline date [note: "null이면 상시모집"]
  status varchar [not null, default: 'open', note: "open/closed"]

  created_at timestamp [not null, default: `now()`]
  updated_at timestamp [not null, default: `now()`]

  indexes {
    (job_category_id, created_at) [name: "idx_posts_category_created"]
    (author_id) [name: "idx_posts_author_id"]
    (deadline) [name: "idx_posts_deadline"]
    (status) [name: "idx_posts_status"]
  }
}

// (홈 유도용) 게시글-관심태그 매핑: 관심사 태그 일치 게시글 추천에 사용
Table post_interest_tags {
  id bigint [pk, increment]
  post_id bigint [not null]
  interest_tag_id bigint [not null]
  created_at timestamp [not null, default: `now()`]

  indexes {
    (post_id, interest_tag_id) [unique, name: "uk_post_interest_tags"]
    (post_id) [name: "idx_post_interest_tags_post_id"]
    (interest_tag_id) [name: "idx_post_interest_tags_interest_tag_id"]
  }
}

// 모집 포지션(선택 사항). (현재 기능명세는 직무 1개 선택이라 MVP에선 미사용 가능)
Table post_positions {
  id bigint [pk, increment]
  post_id bigint [not null]
  position_name varchar [not null]

  indexes {
    (post_id) [name: "idx_post_positions_post_id"]
  }
}

// -----------------------------------------------------
// 6. 쪽지/채팅
// - 쪽지함: 대화 목록(상대/마지막 메시지/안읽음 표시)
// - 채팅방: origin_post_id로 모집글 문맥 연결(선택)
// - 메시지: 삭제 버튼(소프트 딜리트) 반영
// -----------------------------------------------------
Table chat_rooms {
  id bigint [pk, increment]
  origin_post_id bigint [note: "어떤 모집글에서 시작된 대화인지(선택)"]
  created_at timestamp [not null, default: `now()`]

  indexes {
    (origin_post_id) [name: "idx_chat_rooms_origin_post_id"]
  }
}

Table chat_participants {
  id bigint [pk, increment]
  chat_room_id bigint [not null]
  user_id bigint [not null]
  last_read_message_id bigint [note: "안읽음 뱃지 계산용"]
  created_at timestamp [not null, default: `now()`]

  indexes {
    (chat_room_id, user_id) [unique, name: "uk_chat_room_user"]
    (user_id) [name: "idx_chat_participants_user_id"]
  }
}

Table messages {
  id bigint [pk, increment]
  chat_room_id bigint [not null]
  sender_id bigint [not null]
  content text [not null]
  is_deleted boolean [not null, default: false, note: "삭제 버튼용(소프트 딜리트)"]
  deleted_at timestamp [note: "삭제 시각(선택)"]
  created_at timestamp [not null, default: `now()`]

  indexes {
    (chat_room_id, created_at) [name: "idx_messages_room_created"]
    (sender_id) [name: "idx_messages_sender_id"]
    (chat_room_id, is_deleted) [name: "idx_messages_room_deleted"]
  }
}

// -----------------------------------------------------
// 7. 알림 (홈 2.x)
// - 홈에서 알림/상태 정보 노출
// - 클릭 시 쪽지함/프로필/게시판 등으로 이동(참조 정보 저장)
// -----------------------------------------------------
Table notifications {
  id bigint [pk, increment]
  user_id bigint [not null, note: "알림 수신자"]
  type varchar [not null, note: "예: message, post_deadline, profile_view, system 등"]
  title varchar [note: "알림 제목(선택)"]
  body varchar [note: "알림 내용(선택)"]
  target_type varchar [not null, note: "chats/posts/profiles/experiences 등"]
  target_id bigint [not null, note: "target_type의 id"]
  is_read boolean [not null, default: false]
  created_at timestamp [not null, default: `now()`]

  indexes {
    (user_id, is_read, created_at) [name: "idx_notifications_user_read_created"]
    (target_type, target_id) [name: "idx_notifications_target"]
  }
}

// =====================================================
// Relationships (Foreign Keys)
// =====================================================
Ref: profiles.user_id > users.id

Ref: profile_documents.profile_id > profiles.id
Ref: profile_index_items.profile_id > profiles.id

Ref: user_interest_tags.user_id > users.id
Ref: user_interest_tags.interest_tag_id > interest_tags.id

Ref: experiences.user_id > users.id
Ref: experience_files.experience_id > experiences.id
Ref: experience_links.experience_id > experiences.id

Ref: posts.author_id > users.id
Ref: posts.job_category_id > job_categories.id
Ref: post_positions.post_id > posts.id

Ref: post_interest_tags.post_id > posts.id
Ref: post_interest_tags.interest_tag_id > interest_tags.id

Ref: chat_rooms.origin_post_id > posts.id
Ref: chat_participants.chat_room_id > chat_rooms.id
Ref: chat_participants.user_id > users.id
Ref: messages.chat_room_id > chat_rooms.id
Ref: messages.sender_id > users.id

Ref: notifications.user_id > users.id