DB 설계

사용자는 여러가지의 역할을 가질수 있고 또한 역할은 여러명의 사용자를 가질수 있다.

예를 들어, 쇼핑몰이라고 가정하면 사이트 관리자, 판매자, 구매자라는 역할을 정의할 수 있다. 그러면 특정한 사용자는 구매자 역할과 관리자 역할을 가질수 있다. 또한 관리자는 여러명의 사용자를 가질수 있다.

그러므로 사용자와 역할은 many to many 관계를 가진다. 그러나 Database에서는 many to many 관계를 직접적으로 가질 수 없고 반드시 중간에 mapping 테이블을 가져야하고 사용자와 mapping 테이블은 one to many 관계를 역할과 mapping 테이블도 one to many 관계를 가지도록 설계해야 한다.

typeORM에서는 many to many 관계를 설정하면 mapping 테이블을 자동으로 만들어준다.

먼저 user 테이블을 설계한다.

src/entity/User.ts

@Entity()
@Unique(['email'])
export class User {
  @PrimaryGeneratedColumn({type: "bigint"})
  id: number;

  @Column({length: 255})
  email: string;

  @Column({length: 255})
  password: string;

  @Column({length: 255})
  username: string;

  @CreateDateColumn()
  created: Date;

  @UpdateDateColumn()
  updated: Date;

  @ManyToMany(() => Role, role => role.users)
  @JoinTable({
    name: "user_role",
    joinColumn: {name: "user_id", referencedColumnName: "id"},
    inverseJoinColumn: {name: "role_id", referencedColumnName: "id"}
  })
  roles: Role[];
}

email을 unique 처리하기 위해서 @Entity 아래에 @Unique([’email’]) 를 적용했다.

@ManyToMany 어노테이션을 잘 살펴봐야 한다.

mapping 테이블명을 user_role로 정의하였다. user와 user_role 사이에는 one-to-many 관계가 성립되는데 user_id 를 foreign 키로 정의하였다.

Role 테이블을 설계한다.

src/entity/Role.ts

@Entity()
export class Role {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({length: 20})
  name: string;

  @CreateDateColumn()
  created: Date;

  @UpdateDateColumn()
  updated: Date;

  @ManyToMany(() => User, user => user.roles)
  @JoinTable({
    name: "user_role",
    joinColumn: {name: "role_id", referencedColumnName: "id"},
    inverseJoinColumn: {name: "user_id", referencedColumnName: "id"}
  })
  users: User[];
}

여기서도 마찬가지로 mapping 테이블을 user_role로 정의하였고, role_id 가 foreign key인 one-to-many를 정의하였다.

이제 저장하고 서버를 구동하면 DB에는 아래와 같은 구조를 가지게 될것이다.

user_role의 DDL은 다음과 같다. role_id, user_id를 복합키로 primary 키로 정의하였고, role_id, user_id 가 각각 foreign key로 설정되어있다.

create table user_role
(
    role_id int    not null,
    user_id bigint not null,
    primary key (role_id, user_id),
    constraint FK_32a6fc2fcb019d8e3a8ace0f55f
        foreign key (role_id) references role (id)
            on delete cascade,
    constraint FK_d0e5815877f7395a198a4cb0a46
        foreign key (user_id) references user (id)
            on delete cascade
);

Role 데이터 입력

웹사이트 UI 화면에 따라 필요한 role들을 정의하고 미리 입력해야 한다.

예를 들어서, 상단 메뉴가 10개가 있다면 role에 따라서 접근할 수 있는 메뉴를 정해야 하고 동일한 메뉴 내에서도 특정 테이블을 안보이게 한다거나 write 권한을 부여하지 않거나를 결정해야 한다.

여기서는 관리자, 중간관리자, 일반 사용자 이렇게 3개를 정의하고 입력하겠다.

insert into role (name) values ('ROLE_ADMIN');
insert into role (name) values ('ROLE_MANAGER');
insert into role (name) values ('ROLE_USER');

admin 권한

관리자 사이트가 따로 있다고 가정하고 admin 권한을 설정한다.

먼저 controller를 설정한다. 관리자 사이트에서 필요한 모든 api가 여기에 정의되겠지만 여기서는 getUser라는 dummy api 를 정의한다.

src/controller/AdminController.ts

export class AdminController {
  static getDashboard = async (req, res) => {

    res.send('success');
  }
}

라우팅을 설정한다.

src/router/admin.ts

import {AdminController} from "../controller/AdminController";
import {Router} from "express";

const routes = Router();
routes.get('/dashboard', AdminController.getDashboard);

export default routes;

index에 admin 을 추가한다.

src/router/index.ts

routes.use('/admin', admin);

export default routes;

/api/admin/dashboard 라는 더미 api를 하나 만들었다. 그리고 이 api는 인증과 admin 권한을 필요로 하는 용도로 테스트할 것이다.