코딩캠프/내일배움캠프

[ TIL ] 02.23(목) 72일차

고랑E 2023. 2. 23. 21:00
728x90

Nest.js 심화

 

5. 커스텀 리포지토리가 왜 필요한가?

일반 리포지토리로도 기본적인 CRUD를 해야하지만 

경우에 따라서는 더 복잡한 연산 및 쿼리를 직접 정의해야 하는 경우가 있다

일반 리포지토리를 상속받은 커스텀 리포지토리를 작성하여 해결 할수있다.

 

💡 일반 리포지토리 기능을 상속받은 커스텀 쿼리가 가능한 리포지토리

 

6. 커스텀 리포지토리 만들기

Article 엔티티에 view 컬럼을 추가

article.entity.ts 수정

import {
  Column,
  CreateDateColumn,
  DeleteDateColumn,
  Entity,
  PrimaryGeneratedColumn,
  UpdateDateColumn,
} from "typeorm";

@Entity({ schema: "board", name: "articles" })
export class Article {
  @PrimaryGeneratedColumn({ type: "int", name: "id" })
  id: number;

  @Column("varchar", { length: 10 })
  author: string;

  @Column("varchar", { length: 50 })
  title: string;

  @Column("varchar", { length: 1000 })
  content: string;

  @Column("varchar", { length: 10, select: false })
  password: string;

  @Column("int")
  view: number; // 새로 추가된 컬럼!

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt: Date | null;

  @DeleteDateColumn()
  deletedAt: Date | null;
}

 

수정 후 서버를 시작 또는 재시작 하면

articles 테이블에 view 컬럼이 추가된다.

 

article.repository.ts 추가

import { Injectable } from '@nestjs/common';
import { DataSource, Repository } from 'typeorm';
import { Article } from './article.entity';

@Injectable()
export class ArticleRepository extends Repository<Article> {
  constructor(private dataSource: DataSource) {
    super(Article, dataSource.createEntityManager());
  }

  async getArticlesByViewCount() {
    const result = await this.createQueryBuilder()
      .select('articles')
      .from(Article, 'articles')
      .orderBy('articles.view', 'DESC')
      .getMany();
    return result;
  }
}

커스텀 리포지토리는 일반 리포지토리를 반드시 상속한다.

생성자의 매개변수로 DataSource타입의 변수가 들어가있다!!

 

DataSource는 데이터베이스 연결에 대한 추상화를 제공하는 클래스

데이터베이스 연결 정보를 감추고 쿼리를 실행하거나 트랜잭션을 관리할 수 있다

 

getArticlesByViewCount 라는

새로운 함수로 Repository에 있는 createQueryBuilder 함수를 통해서 Raw Query와 흡사하게 쿼리를 직접 작성

  • .select("article")
    • 선택할 테이블을 지정합니다.
  • .from(Article, "article")
    • 테이블 이름과 별칭을 설정합니다.
  • .orderBy("article.view", "DESC")
    • view 컬럼을 기준으로 결과를 내림차순으로 정렬합니다.
  • .getMany()
    • 쿼리를 실행하고 결과를 Article 객체의 배열로 반환합니다.

board.module.ts

providers 에  ArticleRepository 추가

import { ArticleRepository } from './article.repository';

  providers: [BoardService, ArticleRepository], // ArticleRepository 추가!

 

 

board.controller.ts   

// 인기가 많은 순
  @Get('/hot-articles')
  async getHotArticles() {
    return await this.boardService.getHotArticles();
  }

 

 

 

board.service.ts

import {
  Injectable,
  NotFoundException,
  UnauthorizedException,
} from '@nestjs/common';
import _ from 'lodash';
import { ArticleRepository } from './article.repository';

@Injectable()
export class BoardService {
  constructor(private articleRepository: ArticleRepository) {}

  async getArticles() {
    return await this.articleRepository.find({
      where: { deletedAt: null },
      select: ['author', 'title', 'updatedAt'],
    });
  }

  async getArticleById(id: number) {
    return await this.articleRepository.findOne({
      where: { id, deletedAt: null },
      select: ['author', 'title', 'content', 'updatedAt'],
    });
  }

  // 일반 리포지토리엔 없는 커스텀 리포지터리에만 있는 함수!
  async getHotArticles() {
    return await this.articleRepository.getArticlesByViewCount();
  }

  createArticle(title: string, content: string, password: number) {
    this.articleRepository.insert({
      // 일단 author는 test로 고정합니다.
      author: 'test',
      title,
      content,
      // password도 일단은 잠시 숫자를 문자열로 바꿉니다. 나중에 암호화된 비밀번호를 저장하도록 하겠습니다.
      password: password.toString(),
    });
  }

  async updateArticle(
    id: number,
    title: string,
    content: string,
    password: number,
  ) {
    await this.verifyPassword(id, password);
    this.articleRepository.update(id, { title, content });
  }

  async deleteArticle(id: number, password: number) {
    await this.verifyPassword(id, password);
    this.articleRepository.softDelete(id); // soft delete를 시켜주는 것이 핵심입니다!
  }

  private async verifyPassword(id: number, password: number) {
    const article = await this.articleRepository.findOne({
      where: { id, deletedAt: null },
      select: ['password'],
    });

    if (_.isNil(article)) {
      throw new NotFoundException(`Article not found. id: ${id}`);
    }
    if (article.password !== password.toString()) {
      throw new UnauthorizedException(`Password is not corrected. id: ${id}`);
    }
  }
}