Skip to content

Commit ac8e63b

Browse files
authored
Merge pull request #6 from boostcampwm-2024/fix/#5/refactor-query
[Fix] 티클 리스트 조회 쿼리 최적화
2 parents 10fc3ed + 53521c4 commit ac8e63b

14 files changed

+472
-72
lines changed

apps/api/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@
1919
"test:watch": "jest --watch",
2020
"test:cov": "jest --coverage",
2121
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
22-
"test:e2e": "jest --config ./test/jest-e2e.json"
22+
"test:e2e": "jest --config ./test/jest-e2e.json",
23+
"seed": "ts-node src/seeds/seed.ts"
2324
},
2425
"dependencies": {
2526
"@aws-sdk/client-s3": "^3.700.0",
27+
"@faker-js/faker": "^9.3.0",
2628
"@nestjs/common": "^10.4.6",
2729
"@nestjs/config": "^3.3.0",
2830
"@nestjs/core": "^10.0.0",
@@ -50,6 +52,7 @@
5052
"reflect-metadata": "^0.2.0",
5153
"rxjs": "^7.8.1",
5254
"typeorm": "^0.3.20",
55+
"typeorm-extension": "^3.6.3",
5356
"winston": "^3.17.0",
5457
"winston-daily-rotate-file": "^5.0.0",
5558
"zod": "^3.23.8"

apps/api/src/entity/ticle.entity.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { Tag } from './tag.entity';
1919
import { User } from './user.entity';
2020

2121
@Entity('ticle')
22+
@Index('idx_id_created_at', ['id', 'createdAt'])
2223
@Index('idx_fulltext_search', ['title', 'content'], { fulltext: true })
2324
export class Ticle {
2425
@PrimaryGeneratedColumn({ type: 'bigint' })
@@ -52,6 +53,7 @@ export class Ticle {
5253
@Column({ type: 'enum', enum: TicleStatus, default: TicleStatus.OPEN, name: 'ticle_status' })
5354
ticleStatus: TicleStatus;
5455

56+
@Index()
5557
@CreateDateColumn({ type: 'timestamp', name: 'created_at' })
5658
createdAt: Date;
5759

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { faker } from '@faker-js/faker';
2+
import { DataSource } from 'typeorm';
3+
4+
import { Applicant } from '../entity/applicant.entity'; // Applicant 엔티티 경로 확인
5+
import { Ticle } from '../entity/ticle.entity'; // Ticle 엔티티 경로 확인
6+
import { User } from '../entity/user.entity'; // User 엔티티 경로 확인
7+
8+
export const seedApplicants = async (dataSource: DataSource, count: number) => {
9+
const applicantRepository = dataSource.getRepository(Applicant);
10+
const ticleRepository = dataSource.getRepository(Ticle);
11+
const userRepository = dataSource.getRepository(User);
12+
13+
const existingTicles = await ticleRepository.find();
14+
const existingUsers = await userRepository.find();
15+
16+
for (let i = 0; i < count; i++) {
17+
// 랜덤한 타이클과 사용자 선택
18+
const randomTicle = faker.helpers.arrayElement(existingTicles);
19+
const randomUser = faker.helpers.arrayElement(existingUsers);
20+
21+
const applicant = applicantRepository.create({
22+
ticle: randomTicle,
23+
user: randomUser,
24+
});
25+
26+
try {
27+
await applicantRepository.save(applicant);
28+
} catch (error) {
29+
console.error(
30+
`Error seeding applicant for Ticle ID ${randomTicle.id} and User ID ${randomUser.id}:`,
31+
error
32+
);
33+
}
34+
}
35+
36+
console.log(`${count} applicants seeded!`);
37+
};

apps/api/src/seeds/datasource.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import 'reflect-metadata';
2+
import { DataSource } from 'typeorm';
3+
4+
import { Applicant } from '../entity/applicant.entity';
5+
import { Summary } from '../entity/summary.entity';
6+
import { Tag } from '../entity/tag.entity';
7+
import { Ticle } from '../entity/ticle.entity';
8+
import { User } from '../entity/user.entity';
9+
10+
// 데이터 소스 초기화
11+
export const AppDataSource = new DataSource({
12+
type: 'mysql', // 사용할 데이터베이스 종류
13+
host: 'localhost', // 데이터베이스 호스트
14+
port: 3306, // 데이터베이스 포트
15+
username: 'root', // 사용자 이름
16+
password: '123', // 비밀번호
17+
database: 'ticle_test', // 데이터베이스 이름
18+
synchronize: true, // 개발 중에는 true로 설정 (생성된 엔티티에 따라 테이블을 동기화)
19+
logging: true, // 쿼리 로깅
20+
entities: [User, Ticle, Tag, Applicant, Summary], // 사용할 엔티티 목록
21+
});

apps/api/src/seeds/seed.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { seedApplicants } from './applicant.seeder';
2+
import { AppDataSource } from './datasource';
3+
import { seedTags } from './tag.seeder';
4+
import { seedTicles } from './ticle.seeder';
5+
import { seedUsers } from './user.seeder';
6+
7+
const seedDatabase = async () => {
8+
try {
9+
// 데이터 소스 초기화
10+
await AppDataSource.initialize();
11+
console.log('Database connected successfully!');
12+
13+
// User 데이터를 Seed
14+
// await seedUsers(AppDataSource, 1000);
15+
// await seedTicles(AppDataSource, 3_000_000);
16+
// await seedTags(AppDataSource, 100);
17+
// await seedApplicants(AppDataSource, 1000);
18+
19+
console.log('Seeding completed!');
20+
process.exit(0);
21+
} catch (error) {
22+
console.error('Error seeding database:', error);
23+
process.exit(1);
24+
}
25+
};
26+
27+
seedDatabase();

apps/api/src/seeds/tag.seeder.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { faker } from '@faker-js/faker';
2+
import { DataSource } from 'typeorm';
3+
4+
import { Tag } from '../entity/tag.entity'; // Tag 엔티티 경로 확인
5+
import { Ticle } from '../entity/ticle.entity'; // Ticle 엔티티 경로 확인
6+
7+
export const seedTags = async (dataSource: DataSource, count: number) => {
8+
const tagRepository = dataSource.getRepository(Tag);
9+
const ticleRepository = dataSource.getRepository(Ticle);
10+
11+
// 기존 태그와 타이클을 가져옵니다.
12+
const existingTags = await tagRepository.find();
13+
const existingTagNames = new Set(existingTags.map((tag) => tag.name));
14+
const existingTicles = await ticleRepository.find();
15+
16+
for (let i = 0; i < count; i++) {
17+
let name;
18+
do {
19+
name = faker.word.adjective(); // 랜덤한 단어 생성
20+
} while (existingTagNames.has(name)); // 중복 확인
21+
22+
const tag = tagRepository.create({
23+
name,
24+
});
25+
26+
// 태그 저장
27+
await tagRepository.save(tag);
28+
existingTagNames.add(name); // 새로 생성된 태그 이름 추가
29+
30+
// 랜덤하게 타이클을 선택하고 연결합니다.
31+
if (existingTicles.length > 0) {
32+
const randomTicle = faker.helpers.arrayElement(existingTicles);
33+
// 이미 태그가 있으면 추가하고, 없으면 새로 만들어서 연결
34+
if (!randomTicle.tags) {
35+
randomTicle.tags = [];
36+
}
37+
randomTicle.tags.push(tag);
38+
await ticleRepository.save(randomTicle); // 수정된 타이클 저장
39+
}
40+
}
41+
42+
console.log(`${count} tags seeded!`);
43+
};

apps/api/src/seeds/ticle.seeder.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { faker } from '@faker-js/faker';
2+
import { DataSource } from 'typeorm';
3+
import { TicleStatus } from '@repo/types'; // TicleStatus enum 경로 확인
4+
5+
import { Ticle } from '../entity/ticle.entity';
6+
import { User } from '../entity/user.entity';
7+
8+
export const seedTicles = async (dataSource: DataSource, count: number) => {
9+
const ticleRepository = dataSource.getRepository(Ticle);
10+
const userRepository = dataSource.getRepository(User);
11+
12+
// 사용자 목록을 가져옵니다.
13+
const users = await userRepository.find();
14+
15+
for (let i = 0; i < count; i++) {
16+
// 임의의 사용자 선택
17+
const randomUser = users[Math.floor(Math.random() * users.length)];
18+
19+
const ticle = ticleRepository.create({
20+
speaker: randomUser,
21+
speakerName: randomUser.nickname, // 사용자 이름 사용
22+
speakerEmail: randomUser.email, // 사용자 이메일 사용
23+
speakerIntroduce: faker.lorem.sentence(),
24+
title: faker.lorem.sentence(),
25+
content: faker.lorem.paragraphs(),
26+
startTime: faker.date.future(),
27+
endTime: faker.date.future(),
28+
ticleStatus: TicleStatus.OPEN,
29+
createdAt: new Date(),
30+
profileImageUrl: faker.image.avatar(),
31+
});
32+
await ticleRepository.save(ticle);
33+
}
34+
35+
console.log('Ticles seeded!');
36+
};

apps/api/src/seeds/user.seeder.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { faker } from '@faker-js/faker';
2+
import { DataSource } from 'typeorm';
3+
4+
import { User } from '../entity/user.entity';
5+
6+
export const seedUsers = async (dataSource: DataSource, count: number) => {
7+
const userRepository = dataSource.getRepository(User);
8+
9+
for (let i = 0; i < count; i++) {
10+
const user = userRepository.create({
11+
username: faker.internet.userName(),
12+
password: faker.internet.password(),
13+
nickname: faker.name.firstName(),
14+
email: faker.internet.email(),
15+
introduce: faker.lorem.sentence(),
16+
profileImageUrl: faker.image.avatar(),
17+
provider: 'local',
18+
});
19+
await userRepository.save(user);
20+
}
21+
22+
console.log('Users seeded!');
23+
};

apps/api/src/ticle/dto/getTicleListQueryDto.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,6 @@ import { Transform } from 'class-transformer';
44
import { SortType } from '../sortType.enum';
55

66
export class GetTicleListQueryDto {
7-
@ApiProperty({
8-
example: 1,
9-
description: '페이지 번호',
10-
default: 1,
11-
required: false,
12-
})
13-
page?: number = 1;
14-
157
@ApiProperty({
168
example: 10,
179
description: '페이지당 항목 수',
@@ -37,4 +29,11 @@ export class GetTicleListQueryDto {
3729
required: false,
3830
})
3931
sort?: SortType = SortType.NEWEST;
32+
33+
@ApiProperty({
34+
example: '2025-01-14T12:00:00Z',
35+
description: 'lastCreatedAt',
36+
required: false,
37+
})
38+
lastSeenCreatedAt?: Date;
4039
}

apps/api/src/ticle/ticle.controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ export class TicleController {
2828
@Get('list')
2929
async getTicleList(@Query() query: GetTicleListQueryDto) {
3030
const parsedQuery = {
31-
page: query.page ? Number(query.page) : 1,
3231
pageSize: query.pageSize ? Number(query.pageSize) : 10,
3332
isOpen: query.isOpen === undefined ? true : query.isOpen,
3433
sort: query.sort || SortType.NEWEST,
34+
lastSeenCreatedAt: query.lastSeenCreatedAt ? query.lastSeenCreatedAt : null,
3535
};
3636
return this.ticleService.getTicleList(parsedQuery);
3737
}

0 commit comments

Comments
 (0)