All files / community community.query.repository.ts

100% Statements 39/39
100% Branches 9/9
100% Functions 11/11
100% Lines 35/35

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 13014x 14x   14x   14x   14x     22x       1x       1x           1x             3x           2x       2x 1x     2x             3x     3x 3x     3x         3x 1x       3x 1x       3x 2x   1x       3x     3x 5x             3x 3x 3x       3x       3x                         3x                 1x          
import { InjectRepository } from '@nestjs/typeorm';
import { CommunityCursorPaginationHelper } from 'src/commons/helpers/community.cursor.helper';
import { ApiCommunityGetRequestQueryDto } from 'src/community/dto/api-community-get-request-query.dto';
import { CommunityEntity } from 'src/entities/community.entity';
import { UserDto } from 'src/user/dto/user.dto';
import { IsNull, Repository } from 'typeorm';
 
export class CommunityQueryRepository {
  constructor(
    @InjectRepository(CommunityEntity)
    private repository: Repository<CommunityEntity>,
  ) {}
 
  async save(communityEntity: CommunityEntity): Promise<CommunityEntity> {
    return this.repository.save(communityEntity);
  }
 
  async findOne(uuid: string): Promise<CommunityEntity> {
    return this.repository.findOne({
      where: { uuid, archived_at: IsNull() },
    });
  }
 
  async myCommunity(user: UserDto): Promise<CommunityEntity[]> {
    return this.repository.find({
      where: { user_uuid: user.uuid, archived_at: IsNull() },
      order: { created_at: 'DESC' },
    });
  }
 
  async findCommunityByCourse(uuid: string, user: UserDto): Promise<CommunityEntity> {
    return this.repository.findOne({
      where: { course_uuid: uuid, user_uuid: user.uuid, archived_at: IsNull() },
    });
  }
 
  async countTotalCommunity(dto: ApiCommunityGetRequestQueryDto, user: UserDto): Promise<number> {
    const qb = this.repository
      .createQueryBuilder('community')
      .where('community.archived_at IS NULL');
 
    if (dto.me) {
      qb.andWhere('community.user_uuid = :userUuid', { userUuid: user.uuid });
    }
 
    return qb.getCount();
  }
 
  async findCommunityList(
    dto: ApiCommunityGetRequestQueryDto,
    user: UserDto,
  ): Promise<{ communityList: CommunityEntity[]; nextCursor: string | null }> {
    const qb = this.repository.createQueryBuilder('community');
 
    // 1. 서브쿼리 생성
    const likeCountSub = CommunityCursorPaginationHelper.buildLikeCountSub(qb);
    const isLikedSub = CommunityCursorPaginationHelper.buildIsLikedSub(qb);
 
    // 2. SELECT에 서브쿼리 추가
    qb.addSelect(`${likeCountSub}`, 'like_count')
      .addSelect(`${isLikedSub}`, 'is_liked')
      .where('community.archived_at IS NULL')
      .setParameter('userUuid', user.uuid);
 
    if (dto.me) {
      qb.andWhere('community.user_uuid = :userUuid', { userUuid: user.uuid });
    }
 
    // 3. 커서 필터
    if (dto.next_page) {
      CommunityCursorPaginationHelper.applyCursor(qb, dto.order, dto.next_page, likeCountSub);
    }
 
    // 4. 정렬 및 페이징
    if (dto.order === 'latest') {
      qb.orderBy('community.created_at', 'DESC').addOrderBy('community.id', 'DESC');
    } else {
      qb.orderBy('like_count', 'DESC')
        .addOrderBy('community.created_at', 'DESC')
        .addOrderBy('community.id', 'DESC');
    }
    qb.take(dto.size + 1);
 
    // 5. 실행 후 매핑
    const { entities, raw } = await qb.getRawAndEntities();
    const updated = entities.map((c, i) => ({
      ...c,
      like_count: parseInt(raw[i].like_count, 10),
      is_liked: raw[i].is_liked === '1',
    }));
 
    // 6. nextCursor 계산
    const hasNext = updated.length > dto.size;
    const pageItems = hasNext ? updated.slice(0, dto.size) : updated;
    const nextCursor = hasNext
      ? CommunityCursorPaginationHelper.generateCursor(pageItems[pageItems.length - 1], dto.order)
      : null;
 
    return { communityList: pageItems, nextCursor };
  }
 
  async findExistingCourse(courseUuids: string[]) {
    const result = await this.repository
      .createQueryBuilder('community')
      .leftJoin('community.reactions', 'reaction', 'reaction.like = 1')
      .select('community.course_uuid', 'course_uuid')
      .addSelect('MIN(community.uuid)', 'community_uuid')
      .addSelect('community.score', 'score')
      .addSelect('COUNT(reaction.id)', 'like_count')
      .where('community.course_uuid IN (:...courseUuids)', { courseUuids })
      .andWhere('community.archived_at IS NULL')
      .groupBy('community.course_uuid')
      .addGroupBy('community.score')
      .getRawMany();
 
    return result.map((row) => ({
      community_uuid: row.community_uuid,
      course_uuid: row.course_uuid,
      score: row.score,
      like_count: parseInt(row.like_count, 10),
    }));
  }
 
  async findCourse(uuid: string): Promise<CommunityEntity> {
    return this.repository.findOne({
      where: { course_uuid: uuid, archived_at: IsNull() },
    });
  }
}