본문으로 건너뛰기

안동민 개발노트

본문 시작

프로젝트·파일 데이터 모델

프로젝트와 파일·폴더 엔티티를 정의하고 소유 관계와 계층 구조를 구성합니다.

프로젝트 및 파일 관리 API 구현

멤버십과 계층형 Entry의 데이터 스키마

DATABASE SCHEMA · INVARIANTS

Project membership과 Entry adjacency를 제약으로 보장한다

File·Folder 경로 모델을 남기지 않는다. Entry의 parentId, siblingKey, kind, revision과 복합 외래 키가 트리 구조와 편집 순서를 지킨다.

멤버십과 계층형 Entry의 데이터 스키마 User, Project, Project Member, Entry 네 테이블과 소유자, 멤버십, 같은 프로젝트 부모, Entry 자기 참조 관계를 보여 준다. USERS User id · email PROJECTS Project ownerId · name PROJECT_MEMBERS Membership owner · editor · viewer ENTRIES Entry parent · sibling · revision
  1. User

    email은 유일하고 passwordHash는 기본 조회에서 제외합니다.

  2. Project

    ownerId는 사용자에 대한 외래 키입니다.

  3. Project Member

    (projectId, userId)가 역할 하나를 가집니다.

  4. Entry adjacency

    parentId는 같은 projectId의 Entry만 가리킵니다.

  5. Entry revision

    형제 이름은 유일하고 파일 편집마다 revision이 증가합니다.

화살표는 신뢰 또는 상태가 다음 경계로 이동하는 방향을 뜻합니다.

이제 사용자 인증이 완료된 후 접근할 수 있는 프로젝트 및 파일 관리 API를 구현합니다.

Project Entity

src/project/entities/project.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, OneToMany, CreateDateColumn, UpdateDateColumn } from 'typeorm';
import { User } from '../../auth/entities/user.entity'; // User 엔티티 임포트
import { File } from '../../file/entities/file.entity'; // File 엔티티 임포트 (미리 정의했다고 가정)
import { Folder } from '../../folder/entities/folder.entity'; // Folder 엔티티 임포트 (미리 정의했다고 가정)

@Entity()
export class Project {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column({ nullable: false })
  name: string;

  @Column({ nullable: false })
  ownerId: string; // 소유자 ID

  @ManyToOne(() => User, user => user.projects)
  owner: User; // User 엔티티와의 관계

  @OneToMany(() => File, file => file.project)
  files: File[];

  @OneToMany(() => Folder, folder => folder.project)
  folders: Folder[];

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt: Date;
}

File & Folder Entities

src/file/entities/file.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, CreateDateColumn, UpdateDateColumn } from 'typeorm';
import { Project } from '../../project/entities/project.entity';

@Entity()
export class File {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column({ nullable: false })
  name: string;

  @Column({ nullable: false })
  path: string; // 예: '/src/main.ts'

  @Column({ type: 'text', nullable: true })
  content: string; // 파일 내용 (TEXT 타입)

  @Column({ nullable: true })
  type: string; // 파일 타입 (예: 'javascript', 'python')

  @Column({ nullable: false })
  projectId: string;

  @ManyToOne(() => Project, project => project.files, { onDelete: 'CASCADE' }) // 프로젝트 삭제 시 파일도 삭제
  project: Project;

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt: Date;
}
src/folder/entities/folder.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, CreateDateColumn, UpdateDateColumn } from 'typeorm';
import { Project } from '../../project/entities/project.entity';

@Entity()
export class Folder {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column({ nullable: false })
  name: string;

  @Column({ nullable: false })
  path: string; // 예: '/src'

  @Column({ nullable: false })
  projectId: string;

  @ManyToOne(() => Project, project => project.folders, { onDelete: 'CASCADE' }) // 프로젝트 삭제 시 폴더도 삭제
  project: Project;

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt: Date;
}