728x90
반응형

오늘은 추가로 구현해야 할 기능이 있었다.

마이리스트, 북마크에 포스팅 추가시 중복되면

못들어가게 막아야하는데 해당 값을 가져오질 못해서

몇시간동안 고생했지만 찾을 수 없었다...

튜터님 시간될때 질문을 통해 해결해야 할 것 같다.

 

그전에 이제 모든 코드를 병합하기 위해서

나도 유즈 가드를 달기 시작했다.

  @Get('/collections')
  @UseGuards(AuthAccessGuard)
  @ApiOperation({ summary: 'MyList 전체조회(내꺼)' })
  @ApiResponse({ status: 200, description: 'MyList 전체조회(내꺼) 성공' })
  @ApiResponse({ status: 400, description: 'MyList 전체조회(내꺼) 실패' })
  async getMyListsMe(@CurrentUser() currentUser: any) {
    const myLists = await this.myListService.getMyListsMe(currentUser.id);
    return await myLists;
  }

북마크와 마이리스트에 모두 달았으며,

위의 코드만 알고 있으면 달기편하다.

다른 팀원이 만들었지만 그걸 쉽게 이용할 수 있다니 참 편한 것 같다.

728x90
반응형
728x90
반응형

벌써 3주차가 시작되었다.

이전까지 완료한 것은 마이리스트

오늘은 비슷한 기능을 하는 북마크 기능구현을 시작했다.

 

마이리스트는 시간이 꽤 걸렸지만

기능이 비슷하다보니 하루만에 끝낼 수 있었다.

조금 다른 기능도 있어서 고생좀 했지만

하루만에 CRUD기능을 끝낼 수 있어서 뿌듯한 하루였다.

728x90
반응형
728x90
반응형

1. 문제

맛집리스트 생성시 처음에는 이름만 데이터로 받고 생성하려함. (수정시에는 다른 정보도 선택적 수용)

그런데 네임만 입력하니 다른것도 입력하라는 문구가 발생

 

2. 시도해본 것들

  @IsString()
  readonly type?: 'myList';

위와 같이 type? 로 모두 적어줬으나 계속 같은 에러 발생

{
  "success": false,
  "timestamp": "2023. 3. 13. 오전 10:28:58",
  "statusCode": 400,
  "message": [
    "type must be a string",
    "description must be a string",
    "image must be a string",
    "visibility must be a string"
  ],
  "error": "Bad Request"
}

 

3. 해결과정

GPT에게 연속적으로 질문했다.

처음에는 ?를 붙이라 했고 말해준대로 해도 안된다하니,

main.ts 파일에서 다음과 같이 app.useGlobalPipes()를 적용하라해서 이미 있는거라고 하니

  @IsString()
  @IsOptional()
  readonly type?: 'myList';

@IsOptional까지 넣으라고 답변해주었고 실행하니 정상적용되었다.

 

4. 알게 된 점

물음표만 붙이면 모두 옵셔널이 되는줄 알았는데,

옵셔널 데코레이터가 있다는걸 알게되었다. 앞으로도 자주 쓸 것 같은 지식!

728x90
반응형
728x90
반응형

오전에 카페가서 공부하고

오후에 인터넷 연걸이 되어서 집에서 편안하게 작업을 했다.

북마크보다 마이리스트라는 비슷한 기능이 우선적으로 진행되어야 한다해서

마이리스트를 새벽까지 코딩하여 구현해냈다.

 

튜터님의 조언도 듣고, 팀원들의 도움도 받으면서 배운점이 많았다.

 

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

  @ManyToOne((type) => Collection, (collection) => collection.collectionItems)
  @JoinColumn({ name: 'collection_id' })
  collection: Collection;

  @ManyToOne((type) => Restaurant, (restaurant) => restaurant.collectionItems)
  @JoinColumn({ name: 'restaurant_id' })
  restaurant: Restaurant;

  @ManyToOne((type) => Post, (post) => post.collectionItems)
  @JoinColumn({ name: 'post_id' })
  post: Post;
}

컬렉션아이템이라는 엔티티에 콜렉션과 레스토랑 포스트를 연결하여 다대다 관계를 만들었다.

해당 정보를 꺼내써야 한다는게 매우 어려웠다. 이부분을 튜터님과 팀원들이 도와주었다.

 

우선 컨트롤러에만 dto를 사용할수 있다는걸 배웠고, (서비스에서도 사용할 수 있는지 헷갈렸다)

스웨거에 대해서 배웠다.

  // @UseGuards(AuthGuard('local'))
  @Get('/:userId')
  @ApiOkResponse({
    description: 'MyList 전체조회(해당 유저의 맛집리스트만 불러오기)',
  })
  @ApiUnauthorizedResponse({ description: '전체조회 실패' })
  async getMyLists(@Param('userId') userId: number) {
    const myLists = await this.myListService.getMyList(userId);
    return await myLists;
  }

위와 같이 작성하면 localhost:3000/api에서 자세한 명세를 볼 수 있어 프론트와의 협업이 편이했다.

swagger

https://bit.ly/3Jy1Sgg

 

Nestjs 프로젝트에 Swagger 도입하기

프로젝트의 사이즈가 커질수록 REST API의 수가 정말 어마 무시하게 많아지는데요, API가 문서화되지 않는다면 어떤 API들이 있었나 헷갈려서 소스코드 이곳저곳을 뒤적거리는 일이 발생할 수 있습

velog.io

위 주소를 참고하였고 쉽고 편리했다.

실제 데이터없고 프론트에서 넘겨줘야할 정보는 더미데이터를 만드는 법을 배웠다.

  @Post('/:collectionId')
  @ApiOkResponse({
    description: 'MyList 포스팅 추가',
  })
  @ApiUnauthorizedResponse({ description: 'MyList 포스팅 추가 실패' })
  async myListPlusPosting(@Param('collectionId') collectionId: number) {
    const postId = 1;    //<<=== 이부분을 배웠다.
    return this.myListService.myListPlusPosting(postId, collectionId);
  }
}

유저아이디를 넘겨줘야 하는데 프론트에서 할 수 있는 부분이거나 유즈가드를 사용해 할 수 있을거라 생각되어,

더미데이터를 넘겨 테스트 하였다.

 

실제 작동할때 필요한 로직을 담당하는 서비스에선 정말 많은 것을 배웠다.

  async getMyList(userId: number) {
    try {
      const myLists = await this.collectionRepository.find({
        where: { user_id: userId, deletedAt: null, type: 'myList' },
        select: { name: true, description: true, image: true },
      });

      return myLists;
    } catch (err) {
      console.error(err);
      throw new InternalServerErrorException(
        'Something went wrong while processing your request. Please try again later.',
      );
    }
  }

where, select의 사용법을 배웠고 앞으로 코딩할때 많은 도움이 될 것 같다.

  async createMyList(userId, name, type) {
    try {
      return this.collectionRepository.insert({
        user_id: userId,
        name,
        type: 'myList',
      });
    } catch (err) {
      console.error(err);
      throw new InternalServerErrorException(
        'Something went wrong while processing your request. Please try again later.',
      );
    }
  }

insert의 사용방법을 배웠다. user_id는 콜렉션 엔티티에 있는 데이터이고 해당 부분에 받아온 userId=1을 넘겼다.

 

async updateMyList(
    userId: number,
    collectionId: number,
    name: string,
    image: string,
    description: string,
    visibility: 'public' | 'private',
  ) {
    try {
      // id와 type이 모두 일치하는 Collection 엔티티를 찾는다.
      const myList = await this.collectionRepository.find({
        relations: {
          user: true,
        },
      });

      if (!myList) {
        throw new NotFoundException('마이리스트가 없습니다.');
      }

      await this.collectionRepository.update(
        { id: collectionId },
        {
          name,
          image,
          description,
          visibility,
        },
      );
    } catch (err) {
      if (err instanceof NotFoundException) {
        throw err;
      } else {
        console.error(err);
        throw new InternalServerErrorException(
          'Something went wrong while processing your request. Please try again later.',
        );
      }
    }
  }

이부분에선 다대다 관계에서 relations를 사용하여 해당 user데이터를 가져오는 방법을 배웠다.

추가적으로  console.error(err); 를 사용하여 상세한 에러를 보내라는 조언을 들었다.

 

async deleteMyList(id: number) {
    try {
      const result = await this.collectionRepository.softDelete(id); // soft delete를 시켜주는 것이 핵심입니다!
      if (result.affected === 0) {
        throw new NotFoundException('마이리스트가 없습니다.');
      }
    } catch (err) {
      if (err instanceof NotFoundException) {
        throw err;
      } else {
        console.error(err);
        throw new InternalServerErrorException(
          'Something went wrong while processing your request. Please try again later.',
        );
      }
    }
  }

위 부분에선 affected에 대해 배웠다. 해당 코드에 대한 설명의 GPT 답변을 첨부하였다.

영향을 받지 않았을때 0을 반환한다는 것이다.

마지막으로 가장 고생했던 마이리스트 포스팅 추가부분이다.

  async myListPlusPosting(postId: number, collectionId: number) {
    try {
      await this.collectionItemRepository.insert({
        post: { id: postId },
        collection: { id: collectionId },
      });
    } catch (err) {
      if (err instanceof NotFoundException) {
        throw err;
      } else {
        console.error(err);
        throw new InternalServerErrorException(
          'Something went wrong while processing your request. Please try again later.',
        );
      }
    }
  }
}

위 부분에서 고생한 점은 2가지다.

콜렉션아이디랑 포스트아이디를 넘겨줘야 하는 로직이다.

 

첫번째, 어떻게 포스팅정보를 넘겨줘야하지? 위 로직처럼 넘긴다면 너무 간단한거 아닌가?

 

이걸 넘겨받는 팀원이 한마디할 것 같았다. 그래서 아래로직처럼 생각했다.

관계를 맺어서 포스트와 유저의 실제정보를 가져와서 이러쿵 저렁쿵.....

튜터님의 조언을 받아 만든 코드였지만 실제로 넘길 데이터를 가져오는게 어려웠고

사실 정답은 다양하지만 넘겨받을 팀원과 얘기를 통해 insert로 기능을 구현했다.

 

너무 쉬운건 정답이 아닌거 아냐? 라는건 방탈출에서도 종종 겪었고 실제상황에서 자주 착각하는 것이다.

너무 어렵게 돌아가려고 했다. 알고리즘문제도 풀다보면 엄청쉽게 풀수도있고 어렵게 풀수도 있따.

const myLists = await this.collectionRepository.find({
  relations: {
    collectionItems: {
      post: true,
    },
    user: true,
  },
  where: {
    user: {
      id: userId,
    },
  },
});

 

두번쨰,  중간테이블(콜렉션아이템)에 어떻게 값을 넣을 수 있지?

      await this.collectionItemRepository.insert({
        post: { id: postId },
        collection: { id: collectionId },
      });
@Entity()
export class CollectionItem {
  @PrimaryGeneratedColumn()
  id: number;

  @ManyToOne((type) => Collection, (collection) => collection.collectionItems)
  @JoinColumn({ name: 'collection_id' })
  collection: Collection;

  @ManyToOne((type) => Restaurant, (restaurant) => restaurant.collectionItems)
  @JoinColumn({ name: 'restaurant_id' })
  restaurant: Restaurant;

  @ManyToOne((type) => Post, (post) => post.collectionItems)
  @JoinColumn({ name: 'post_id' })
  post: Post;
}

시행착오는 다음과 같다

postId: post, post:postId 등 작동원리를 모르니 별별 것을 다 시도해봤다.

그런데 팀원중 한분이 해결해주었다.

정확한 이해는 아니겠지만 이렇게 생각된다.

 

우선 엔티티에는 post: Post로 post에 Post라는 데이터 테이블이 담겼다고 생각한다.

그래서 왼쪽엔 post만 적었고, 오른쪽의 { id: postId }에서

id는 Post라는 테이블의 id값이고,

해당 아이디 값에 받아온 postId값을 넣는다.

 

정확한 개념일지 모르겠지만 이렇게 생각하니 좀 쉽게 느껴졌다.

결론적으로 정상작동 하였고 새벽2시에 모두 완료하니 오랜만에 기능을 완성하여 뿌듯함을 느꼈다.

728x90
반응형
728x90
반응형

북마크를 담당하게 되서 지금까지 못했던 기능구현을

시작하려고 했다. 회의도 있고 좀 늦어졌지만 이제야 프로젝트에 기여를 하는구나 생각하고 있는데

갑자기 집에 인터넷이 끊겼다. 공용전기세를 집주인이 안냈고, 누전 등의 문제로 한동안

사용을 못한다고 한다. 지금까지 한번 해보고 싶었던 카공(카페공부)를 해야하니

1년간 방치했던 노트북을 들고 카페로 갔다.

그런데 에디터인 비주얼 스튜디오도 안깔려있고, 데이터베이스를 연결해서 테스트해야하는

백앤드기능인데 데이터베이스를 연결하는게 오랜만이라 너무 어려웠다.

약3~4시간 걸려서 SQL SERVER, INSTALLER 등 깔고 연결했고,

겨우 몇시간 코딩을 할 수 있었다. 카페는 시끄럽고 불편했다...

스트레스는 많이 받았지만 그래도 새로운 경험이었다.

 

이날 배웠던 점은 깃허브 관리를 열심히 해야겠다란 점과

데이터베이스 연결하는 방법은 MYSQL의 인스톨러, 서버 등 자세히 알고 설치해야 한다는 점이었따.

728x90
반응형
728x90
반응형

소켓기능을 포기하고,

북마크 기능을 담당하게 되었다. 기본적인 CRUD이며, 꼭 필요한 기능이다.

 

기본적인 CRUD는 같은 조원의 코드를 참고하고,

데이터연결은 이전에 들었던 강의를 다시 들으며 복습하여 코드를 완성했다.

 

그런데 현재 ERD관계가 처음 작성했을때의 모습과 달라서

엔티티를 다시 수정중이다.

 

리더님이 작성해주신다하여 나는 TypeORM에 대해서 공부중이다.

https://typeorm.io/

 

TypeORM - Amazing ORM for TypeScript and JavaScript (ES7, ES6, ES5). Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server,

 

typeorm.io

공식홈페이지가 예시가 풍부하고 이해하기 쉽다.

해당 홈페이지를 참고해서 개념 정리해야겠다.

728x90
반응형
728x90
반응형

월요일, 화요일 2일간 코딩하면서 괴로웠다.

소켓의 기본 껍데기를 만드는건 쉬웠지만,

DB와 연동하여 데이터를 저장하고 불러오는 것이 어려웠다.

 

소켓에 대한 지식과 개념이 부족해서

전체적인 흐름을 잡기가 어려웠다.

 

결국 튜터님에게 상의받았으나

답변을 듣고도 진행이 어려울 것 같아서 팀원들에게

프로젝트에서 소켓 사용은 어려울 것 같다고 얘기했다.

 

첫번째 이유, 소켓을 사용하는 사람이 없어서 다른 동기 및 튜터님들의 도움 받기가 어렵다.

두번째 이유, 취업할때 소켓경험을 보는 곳이 얼마 없을 것이다. 작은 기업일수록 채팅은 개발하기 보단 API를 끌고 올듯.

세번쨰 이유, 나의 실력부족.

 

팀원들에게 미안하다고 얘기했고, 쪽지만들기로 가야되나 했다가

남아있는 기능 북마크로 가는건 어떠냐해서 북마크를 만들기로 했다.

기본적인 CRUD만드는 경험이 될 것 같고

모르더라도 물어보기가 소켓보다는 훨씬 쉬워질 것 같아서 마음이 편해졌다.

일주일 지나서 갑자기 포기하게 되었지만 더 늦어지면 차질이 생길 것 같았고

이 사실을 흔쾌히 받아준 팀원들에게 고마운 마음 뿐이다.

728x90
반응형
728x90
반응형

1. 문제

더보기

ERROR [WsExcepionsHandler] Chatting validation failed: nickname: 

Cast to Embedded failed for value "닉네임" (type string)

at path "nickname" because of "ObjectExpectedError"

오늘 2번이나 나를 괴롭게 했던 에러문구! (더보기 클릭)

몽구스 model 관련 에러였다.

 

2. 시도해본 것들

오늘은 채팅하면서 닉네임과 채팅내용을 가져오는 작업을 했다.

강의를 보면서 코드를 완성했다.

그런데 채팅메시지가 null로 들어가서

이것저것 손보다가 에러 문구를 봤다. 

 

[validation failed]

 

검색해보면서 model관련 에러인것을 알았고,

유효성 실패?? 대충 단어를 보고 어디에 문제가 있는지 떠올려야 했다.

메시지의 경우는 model에선 chat이라 쓰고, gateway에선 message라고 쓰는 등

중구난방으로 사용했길래 통일해서 해결완료.

 

닉네임 값도 저장하고 싶은데 계속 null이 뜨거나

다음과 같이 에러문구가 발생했다.

 

3. 해결과정

    await this.chattingModel.create({
      nickname: client.data.nickname,
      message,
    });

 위의 코드를 계속 수정해가며 테스트했다.

nickname : socketObj

nickname : socketObj.id 등 계속하다가

도저히 알수가 없어서

튜터님을 찾아가서 해결했다.

 

@Schema(options)
export class Chatting extends Document {


  // @Prop({
  //   type: {
  //     _id: { type: Types.ObjectId, required: false, ref: 'sockets' },
  //     id: { type: String },
  //     username: { type: String, required: false },
  //   },
  // })
  @Prop({ required: true })
  @IsString()
  @IsNotEmpty()
  nickname: string;
  // nickname: SocketModel;

  @Prop({
    required: false,
  })
  @IsNotEmpty()
  @IsString()
  message: string;
}

export const ChattingSchema = SchemaFactory.createForClass(Chatting);

 

주석으로 처리한 prop(속성)값을 강의에서 본대로 작성했는데

type쪽이 유형을 정해버려서 에러가 발생했다.

그전에도 required: true; 에서 문제가 발생했다.

테스트하는 과정이라 닉네임도 없고 id도 없다보니

없는 변수를 필수로 처리해서 에러가 발생한것이었다.

 

 

4. 알게 된 점

 

몽고디비, 몽구스 연결시도 10회를 채우기로 했다.

도대체 뭐가 문제인지 궁금했다. 상세메시지를 봐야 해결할 수 있지 않을까?

상세 에러문구

강의보고 따라 작성한 옵션이고, 나는 최신몽구스를 설치한 것 같은데

해당 옵션은 지원하지 않는다고 한다. 주석처리하니 문제가 해결되었다.

[validation failed] 이것은 몽구스 model관련 에러니까 prop(속성)을 자세히 살펴보자!

 

728x90
반응형

+ Recent posts