인프런 커뮤니티 질문&답변

유상우님의 프로필 이미지

작성한 질문수

[코드캠프] 부트캠프에서 만든 고농축 백엔드 코스

N:M 등록 / 조회 API

N:M tag 부분 구현 중 findOne 조회 부분 에러

해결된 질문

23.03.09 13:20 작성

·

393

·

수정됨

0

 products.service.ts에서 create 부근에 tag를 저장 하기 전 tag를 미리 조회하는 부분을 구현 중인데 findOne에서 {name: tagname} 을 구현하려고 할 때 다음과 같은 에러가 발생합니다. save에서는 에러가 발생하지 않는데 findOne 조회 부분만 에러가 발생하네요

관련된 코드 같이 보내드립니다.

createProduct.input.ts

import { InputType, Field, Int } from '@nestjs/graphql';
import { Min } from 'class-validator';
import { ProductSaleslocationInput } from 'src/apis/productsSaleslocation/entities/dto/productSaleslocation.input';

@InputType()
export class CreateProductInput {
  @Field(() => String)
  name: string;

  @Field(() => String)
  description: string;

  @Min(0)
  @Field(() => Int)
  price: number;

  @Field(() => ProductSaleslocationInput)
  productSaleslocation: ProductSaleslocationInput;

  @Field(() => String)
  productCategotyId: string;

  @Field(() => [String])
  productTags: string[];
}

 

products.entity.ts

import { Field, Int, ObjectType } from '@nestjs/graphql';
import { ProductCategory } from 'src/apis/productsCategory/entities/productsCategory.entity';
import { ProductTag } from 'src/apis/productsTags/productTags.entity';
import { User } from 'src/apis/users/users.entity';
import {
  Column,
  DeleteDateColumn,
  Entity,
  JoinColumn,
  JoinTable,
  ManyToMany,
  ManyToOne,
  OneToOne,
  PrimaryGeneratedColumn,
} from 'typeorm';
import { ProductSaleslocation } from '../../productsSaleslocation/entities/productsSaleslocation.entity';

@Entity()
@ObjectType()
export class Product {
  @PrimaryGeneratedColumn('uuid')
  @Field(() => String)
  id: string;

  @Field(() => String)
  @Column()
  name: string;

  @Field(() => String)
  @Column()
  description: string;

  @Field(() => Int)
  @Column()
  price: number;

  @Field(() => Boolean)
  @Column({ default: false })
  isSoldout: boolean;

  @DeleteDateColumn()
  deletedAt: Date;

  @Field(() => ProductSaleslocation)
  @JoinColumn()
  @OneToOne(() => ProductSaleslocation)
  productSaleslocation: ProductSaleslocation;

  @Field(() => ProductCategory)
  @ManyToOne(() => ProductCategory)
  productCategory: ProductCategory;

  @Field(() => User)
  @ManyToOne(() => User)
  user: User;

  @JoinTable()
  @ManyToMany(() => ProductTag, (productTags) => productTags.products)
  @Field(() => [ProductTag])
  productTags: ProductTag[];
}

 

productTags.entity.ts

import { Field, ObjectType } from '@nestjs/graphql';
import { Column, Entity, ManyToMany, PrimaryGeneratedColumn } from 'typeorm';
import { Product } from '../products/entities/products.entity';

@Entity()
@ObjectType()
export class ProductTag {
  @Field(() => String)
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column()
  @Field(() => String)
  name: string;

  @Field(() => [Product])
  @ManyToMany(() => Product, (products) => products.productTags)
  products: Product[];
}

 

products.service.ts

import { Product } from './entities/products.entity';
import { Injectable, UnprocessableEntityException } from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { ProductSaleslocation } from '../productsSaleslocation/entities/productsSaleslocation.entity';
import { ProductTag } from '../productsTags/productTags.entity';

@Injectable()
export class ProductService {
  constructor(
    @InjectRepository(Product)
    private readonly productRepository: Repository<Product>,

    @InjectRepository(ProductSaleslocation)
    private readonly productSaleslocationRepository: Repository<ProductSaleslocation>,

    @InjectRepository(ProductTag)
    private readonly productTagRepository: Repository<ProductTag>,
  ) {}

  async findAll() {
    return await this.productRepository.find({
      relations: ['productSaleslocation', 'productCategory', 'productTags'],
    });
  }

  async findOne({ productId }) {
    return await this.productRepository.findOne({
      where: { id: productId },
      relations: ['productSaleslocation', 'productCategory', 'productTags'],
    });
  }

  async create({ createProductInput }) {
    // 1. 상품만 등록하는 경우
    // const result = await this.productRepository.save({
    //   ...createProductInput,

    //   // 하나 하나 직접 나열하는 방식
    //   // name: createProductInput.name,
    //   // description: createProductInput.description,
    //   // price: createProductInput.price,
    // });

    // 2. 상품과 상품거래 위치 같이 등록
    const { productSaleslocation, productCategotyId, productTag, ...product } =
      createProductInput;
    const result = await this.productSaleslocationRepository.save({
      ...productSaleslocation,
    });

    // productTag // ["#electronics, #computer"]
    const result2 = []; // [{name: ..., id: ...}]
    for (let i = 0; i < productTags.length; i++) {
      const tagName = productTags[i].replace('#', '');

      // check the tags that has already registered
      const checkTag = await this.productTagRepository.findOne({
        name: tagName,
      });

      // if the tags has been existed
      if (checkTag) {
        result2.push(checkTag);
        // if the tags hasn't been existed
      } else {
        const newTag = await this.productTagRepository.save({ name: tagName });
        result2.push(newTag);
      }
    }

    const result3 = await this.productRepository.save({
      ...product,
      productSaleslocation: result, // result 통째로 넣기 vs id만 넣기
      productCategory: { id: productCategotyId },
      productTags: result2,
    });

    return result3;
  }

  async update({ productId, updateProductInput }) {
    const myProduct = await this.productRepository.findOne({
      where: { id: productId },
    });

    const newProduct = {
      ...myProduct,
      id: productId,
      ...updateProductInput,
    };
    return await this.productRepository.save(newProduct);
  }

  async checkSoldOut({ productId }) {
    const product = await this.productRepository.findOne({
      where: { id: productId },
    });
    if (product.isSoldout) {
      throw new UnprocessableEntityException('Sold out');
    }
    // if(product.isSoldout) {
    //   throw new HttpException('이미 판매 완료 된 상품입니다.', HttpStatus.UNPROCESSABLE_ENTITY)
    // }
  }

  async delete({ productId }) {
    // 1. 실제 삭제
    // const result = await this.productRepository.delete({ id: productId });
    // return result.affected ? true : false

    // 2. 소프트 삭제(직접 구현) - isDeleted
    // this.productRepository.update({ id: productId }, { isDeleted: true });

    // 3. 소프트 삭제(직접 구현) - deletedAt
    // this.productRepository.update({ id: productId }, { deletedAt: new Date() });

    // 4. 소프트 삭제(TypeORM 제공) - softRemove - id로만 삭제 가능
    // this.productRepository.softRemove({ id: productId });

    // 4 . 소프트 삭제(TypeORM 제공) - softDelete
    const result = await this.productRepository.softDelete({ id: productId });
    return result.affected ? true : false;
  }
}

 

내용 확인 부탁드립니다.

 

 

 

 

답변 2

0

angie님의 프로필 이미지

2023. 03. 09. 15:02

안녕하세요. 유상우님

해당 코드로 테스트를 실행해 본 결과, 에러없이 작동되는 것을 확인하였습니다. 따라서 최근 Nest 업데이트로 인해 typeorm이 상이하게 작동하는 것으로 추측됩니다. 감사합니다.

0

유상우님의 프로필 이미지
유상우
질문자

2023. 03. 09. 14:48

where 붙이니 되네요.. 선생님은 안 붙여도 되시던데 어떤 차이가 있는걸까요?

gigo96님의 프로필 이미지

2023. 03. 09. 16:11

typeorm 최신 버전하고 강의 버전하고 문법 차이가 있어요