728x90
반응형

1. 문제

ban.entity.ts를 새로 만들어서 데이터베이스에 추가하려 했으나 에러문구 발생

 

[Nest] ERROR [TypeOrmModule] Unable to connect to the database. Retrying (1)... 
TypeORMError: Entity metadata for User#bans was not found. 
Check if you specified a correct entity object and 
if it's connected in the connection options.

 

2. 시도해본 것들

엔티티..메타데이터..not found.. 자주봤던 것 같다.

일단 entity를 만들고, .env에 있는 DATABASE_SYNC=true로 만들면 자동으로 동기화가 될 거라 생각했다.

 

3. 해결과정

그러나!! 이 모든 것이 진행되는 config의 typeorm.config.service.ts에서 Ban 엔티티를 추가해줘야한다.

@Injectable()
export class TypeOrmConfigService implements TypeOrmOptionsFactory {
  constructor(private readonly configService: ConfigService) {} //필수

  createTypeOrmOptions(): TypeOrmModuleOptions {
    return {
      type: 'mysql',
      host: this.configService.get<string>('DATABASE_HOST'),
      port: this.configService.get<number>('DATABASE_PORT'),
      username: this.configService.get<string>('DATABASE_USERNAME'),
      password: this.configService.get<string>('DATABASE_PASSWORD'),
      database: this.configService.get<string>('DATABASE_NAME'),
      namingStrategy: new SnakeNamingStrategy(),
      logging: Boolean(this.configService.get<string>('DATABASE_logging')),
      synchronize: Boolean(this.configService.get<string>('DATABASE_SYNC')), // 배포 시 false
      entities: [
        Collection,
        CollectionItem,
        Comment,
        CommentLike,
        CommentUserTag,
        Post,
        Hashtag,
        Image,
        PostLike,
        PostUserTag,
        Restaurant,
        User,
        Follow,
        Reports,
        Ban,
      ],
    };
  }
}

 

4. 알게 된 점

엔티티 작성, DATABASE_SYNC=true 뿐만아니라 config에서

entities에도 엔티티가 추가됬음을 알려줘야 한다.

728x90
반응형
728x90
반응형

1. 문제

신고엔티티에서 신고당한사람(reportedId)는

유저,게시물,코멘트와 조인할 필요가 있다.

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

  @ManyToOne(() => User, (reporter) => reporter.reports)
  reporter: User;
  @Column()
  reporterId: number;

  // 다대다 테이블? 릴레이션 테이블? 아니면 3개 노가다?
  @Column()
  reportedId: number;

  @Column()
  description: string;

  @Column({
    type: 'enum',
    enum: ['checking', 'rejected', 'completed'],
    default: 'checking',
  })
  status: 'checking' | 'rejected' | 'completed';

  @Column({ type: 'enum', enum: ['user', 'post', 'comment'] })
  type: 'user' | 'post' | 'comment';

  @CreateDateColumn({ name: 'created_at' })
  createdAt: Date;

  @UpdateDateColumn({ name: 'updated_at' })
  updatedAt: Date;

  @DeleteDateColumn({ name: 'deleted_at' })
  deletedAt: Date;
}


2. 시도해본 것들

조인을 안하고 기능을 완성해보려고 했다.

그런데 기능을 만들면서 마지막 신고내역을 구현할때,

reportedId를 통해서 유저,코멘트,포스트 정보와 조인을 해서 정보를 가져와야 했다.

그럴때 우리는 어떻게 해야할지 고민이 되었다.

 

릴레이션 테이블

이 부분이 감이 오지않아서 튜터님에게 물어봐야 할 것 같다. (한번도 안해본 테이블)

 

다대다 테이블

@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;
}

 

기존에 진행했던 테이블

 

각각 테이블과 조인

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

  @Column()
  reportedId: number;

  @Column()
  type: string;

  @ManyToOne(() => User)
  @JoinColumn({ name: 'reportedId', referencedColumnName: 'id' })
  user: User;

  @ManyToOne(() => Post)
  @JoinColumn({ name: 'reportedId', referencedColumnName: 'id' })
  post: Post;

  @ManyToOne(() => Comment)
  @JoinColumn({ name: 'reportedId', referencedColumnName: 'id' })
  comment: Comment;
}

단순하면서 무식해보일 것 같은 방법

 

3. 해결과정

해결방법은 찾지 못했다.

내일 튜터님에게 상담받으면서 어떤 방향으로 나가야 할지 고민하고 정해야겠다.

 

4. 알게 된 점

엔티티간의 관계를 정의하는것이 이렇게 복잡할 줄 몰랐다....

팀원들과 엔티티 얘기하면서 배운 점도 많아서 좋았다.

728x90
반응형
728x90
반응형

1. 문제

프론트에서 북마크 토글시 제공하는 반환값


2. 시도해본 것들

프론트와의 소통이 중요했다. 제가 어떤 값을 보내드리면 되죠? 라고 물었더니

컬렉션 id, name, hasPost: true & false값이 필요하다고 했다.

hasPost는 데이터베이스에 없는 컬럼인데 어떻게 반환해야 할까?

 

3. 해결과정

프론트 담당자에게 Res에 대한 대답을 듣고나니

코드 짜기는 수월했다. 일단 해당 컬렉션에 포스트가 있는지, 없는지

체크하는 hasPost가 제일 고민이었는데 생각보다 간단했다.

그냥 만들어서 전달해주면 되었다.

 

/*
    ### 23.03.29
    ### 표정훈
    ### 북마크 토글 API🔥
    */
  async selectBookmark(postId: number, collectionId: number, userId: number) {
    try {
      //해당 포스트가 각각 컬렉션에 존재있는지 없는지만 알면 된다.
      //⭐ 필요정보 : [{id:36, name: "", hasPost: false}] ⭐
      const collectionItem = await this.collectionItemRepository.findOne({
        where: {
          post: { id: postId },
          collection: { id: collectionId },
        },
        relations: ['collection'],
      });

      if (collectionItem) {
        return {
          id: collectionItem.collection.id,
          name: collectionItem.collection.name,
          hasPost: true,
        };
      } else {
        //Post가 없을땐 해당 북마크만 찾아서 아이디와 이름값 반환
        const collection = await this.collectionRepository.findOne({
          where: { id: collectionId },
        });
        return {
          id: collection.id,
          name: collection.name,
          hasPost: false,
        };
      }
    } catch (err) {
      console.error(err);
      throw new InternalServerErrorException(
        'Something went wrong while processing your request. Please try again later.',
      );
    }
  }

 

 

4. 알게 된 점

가상컬럼을 만들어야 하는가?

어떠한 어려운 방법을 써야하는가? 등등 많은 고민을 했는데

프론트분의 원하는 대답을 듣고 목표가 정해지니

조금씩 생각을 정리하니 금방 답을 얻었다.

return에 그냥 hasPost : true를 담아서 보낼 수 있다니 정말 간단하게 해결 할 수 있어서 뿌듯했다.

 

728x90
반응형
728x90
반응형

1. 문제

북마크에서 기본으로 제공하는 기본 북마크에 포스트를 저장하는 기능


2. 시도해본 것들

우선 기본 북마크는 어떻게 찾을 것인가?

회원가입하자마자 모두에게 주어지는 기본 북마크이므로

제일 먼저 생성된 북마크이다.


3. 해결과정

findOne 메서드로 찾으면 가장 먼저 나오는 것이 기본 북마크였다.

 

/*
        ### 23.03.28
        ### 표정훈
        ### 기본 북마크에 포스팅 더하기
        */
  async basicCollectionPlusPosting(postId: number, userId: number) {
    try {
      //본인의 첫번째 북마크(모든 게시물)의 id를 찾는다.
      const basicBookmark = await this.collectionRepository.findOne({
        where: {
          user_id: userId,
        },
        select: {
          id: true,
        },
      });

      const existingItem = await this.collectionItemRepository.findOne({
        where: {
          post: { id: postId },
          collection: { id: basicBookmark.id }, // 기본 북마크의 ID를 사용하여 조건문을 지정
        },
      });

      if (existingItem) {
        return; // 이미 있다면 종료
      }

      const collectionItem = this.collectionItemRepository.create({
        post: { id: postId },
        collection: { id: basicBookmark.id }, // 기본 북마크의 ID를 사용하여 컬렉션 아이템을 생성
      });

      await this.collectionItemRepository.save(collectionItem);
      return collectionItem;
    }

 

4. 알게 된 점

막연하게 기본 북마크는 어떤 방법으로 찾을 수 있을까 생각했는데

알고리즘 문제처럼 어떠한 방식으로 접근할 수 있을까 생각하다보면 쉽게 접근 할 수 있는 문제였다.

 

 

728x90
반응형
728x90
반응형

1. 문제

회원정지시 3일,7일,30일로 하려고 할때, 자동화하여

정지 풀어주는 로직을 고민하고 있었다.


2. 시도해본 것들

처음에는 타이머를 생각했으나, 서버가 꺼지면 자동으로 초기화되는 등

실제 서비스 제공하기는 어렵다.


3. 해결과정

타이머의 대체제를 찾던 도중 'node-cron'이라는 기능을 알게 되었다.

실제 서비스에서도 사용할 수 있고, 사용하는 방법도 간단했다.

 

4. 알게 된 점

타이머와 node-corn의 차이점을 알게 되었고,

corn의 경우, 서비스 코드에서 작성하는것이 아니라

app.module.ts 또는 main.ts 같은 곳에 작성해야 서비스가 사용될때마다 주기적으로

이벤트를 줘서 작동시킬 수 있다.

 

import * as cron from 'node-cron';
import { AdminModule } from './apis/administrator/admin.module';
import { AdminService } from './apis/administrator/admin.service';

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),
    TypeOrmModule.forRootAsync({
      useClass: TypeOrmConfigService,
    }),

    AuthModule,

  ],
})
export class AppModule implements OnModuleInit {
  constructor(private adminService: AdminService) {}
  onModuleInit() {
    cron.schedule('* * * * *', async () => {
      await this.adminService.liftBanOnExpiredUsers();
    });
  }
}
728x90
반응형
728x90
반응형

이번 주 알게 된 점

금요일에 작성한 TIL의 내용으로

TypeORM으로는 복잡한 쿼리문을 작성하기 어려웠다.

TypeORM 쿼리빌더를 사용하니 손쉽게 해결했다.

효율성을 생각하니 쿼리빌더를 열심히 공부해야겠다는 생각이 들었다.

 

이번주 목표 달성 여부

북마크 세부기능 모두 완성

마이리스트 세부기능 모두 완성

 

다음주 목표 세우기

최종프로젝트 발표 준비

728x90
반응형
728x90
반응형

기존에 뜨는 맛집리스트라는 기능구현을 했다.

TypeORM으로 기능 구현했는데 그룹화하고, 평균값 구하는 등 많은 일을 하려다보니

find문을 사용한후로 다양한 로직을 구현했다.

 /*
    ### 23.03.20
    ### 표정훈
    ### [Main] 요즘 뜨는 맛집리스트🔥
    */
  async HotMyList() {
    try {
      // 1달 전 날짜를 구한다
      const oneMonthAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);

      // 컬렉션과 게시물, 좋아요 정보를 가져온다
      const myListSumLikes = await this.collectionItemRepository.find({
        relations: {
          post: {
            postLikes: true,
            user: true,
            images: true,
          },
          collection: {
            user: true,
          },
        },
        where: {
          // 컬렉션 타입이 myList 이면서 삭제되지 않은 것을 가져온다
          collection: {
            type: 'myList',
            deletedAt: null,
          },
          post: {
            // 좋아요가 삭제되지 않았고, 1달 이내에 좋아요 업데이트된 게시물만 가져온다
            postLikes: {
              deleted_at: null,
              updated_at: MoreThan(oneMonthAgo),
            },
          },
        },
        select: {
          id: true,
          post: {
            id: true,
            images: { id: true, file_url: true },
            postLikes: {
              id: true,
            },
            user: {
              id: true,
              nickname: true,
            },
          },
          collection: {
            id: true,
            name: true,
            user: {
              id: true,
              nickname: true,
            },
          },
        },
        take: 2,
      });

      // 컬렉션별 좋아요 수를 합산하여 그룹화한다
      const groupedData = myListSumLikes.reduce((groups: any, item: any) => {
        const collectionId = item.collection.id;
        if (!groups[collectionId]) {
          groups[collectionId] = {
            collection: item.collection,
            user: item.collection.user,
            sumLikes: 0,
          };
        }
        groups[collectionId].sumLikes += item.post?.postLikes?.length ?? 0;

        // 게시물에 포함된 이미지 URL 정보를 가져온다
        const images = item.post?.images ?? [];
        const fileUrls = images.map((image: any) => image.file_url);
        groups[collectionId].images = fileUrls;

        return groups;
      }, {});

      // 컬렉션별 좋아요 합산값에 따라 내림차순으로 정렬한다
      const collectionSumLikes: any = Object.values(groupedData);
      collectionSumLikes.sort((a: any, b: any) => b.sumLikes - a.sumLikes);

      // 상위 10개 컬렉션 정보를 구성하여 반환한다
      const top3Collections = collectionSumLikes
        // .slice(0, 10)
        .map(({ collection, user, sumLikes, images }: any) => {
          return {
            id: collection.id,
            name: collection.name,
            user: {
              id: user.id,
              nickname: user.nickname,
            },
            sumLikes,
            images,
          };
        });

      return top3Collections;
    }

그런데 위의 코드는 문제가 있었다. 정보를 가져올때 이미 take로 한정지어서 가져왔기에

뒤에 코드에서 몇개를 분리하든 의미가 없던 것이었다.

결국 쿼리빌더로 작성해봤는데 코드의 양이 엄청나게 줄어들었다.

 

/*
    ### 23.03.20
    ### 표정훈
    ### [Main] 요즘 뜨는 맛집리스트🔥
    */
  async HotMyList() {
    try {
      // 1달 전 날짜를 구한다
      const oneMonthAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);

      // 컬렉션별로 포스트의 좋아요를 모두 합쳐서 가장 좋아요수가 많은 컬렉션 5개를 순서대로 가져온다
      const top5Collections = await this.collectionItemRepository
        .createQueryBuilder('collectionItem')
        .leftJoinAndSelect('collectionItem.collection', 'collection')
        .leftJoinAndSelect('collection.user', 'user')
        .leftJoinAndSelect('collectionItem.post', 'post')
        .leftJoinAndSelect('post.postLikes', 'postLikes')
        .where('collection.type = :type', { type: 'myList' })
        .andWhere('postLikes.updated_at > :oneMonthAgo', { oneMonthAgo })
        .select([
          'collection.id',
          'collection.name',
          'user.id',
          'user.nickname',
          'user.profile_image',
          'COUNT(postLikes.id) as sumLikes',
        ])
        .groupBy('collection.id')
        .orderBy('sumLikes', 'DESC')
        .limit(5)
        .getRawMany();

      // 상위 5개 컬렉션 정보를 구성하여 반환한다
      const topCollections = top5Collections.map((item: any) => {
        return {
          id: item.collection_id,
          name: item.collection_name,
          user: {
            id: item.user_id,
            nickname: item.user_nickname,
            profile_image: item.user_profile_image,
          },
          sumLikes: item.sumLikes,
        };
      });

      return topCollections;
    }

TypeORM으로만 작성해왔는데

TypeORM 쿼리빌더가 복잡한 로직에는 훨씬 유용한것 같다.

따로 쿼리빌더를 공부해야겠다는 생각이 들었다.

 

 

728x90
반응형
728x90
반응형
  1. 요즘 뜨는맛집 - 유저프로필이미지가 없다 => post에 있는 user 정보가 아니라 collection에 있는 user정보였다.
  2. 요즘 뜨는맛집 - 리스트 5개 나오게 하기 => take 수량변경
  3. 맛집리스트 수정에서 이미지가 안된다 => 참고해야할 변수명이 image가 아닌 file 이었다.
  4. 맛집리스트 상세보기 - 카테고리 그룹네임-> 카테고리 네임 변경

프론트 연결중에 문제가 생긴 부분을 수정했고,

내일 배포할때 사용할 구글폼을 팀원들과 함께 작성하였다.

테스트코드 공부를 좀 하였으나 역시나 어려워서 머리가 아픈 하루였다.

728x90
반응형

+ Recent posts