명예의 전당을 투표 수 기준으로 만들었으니 이제는 좋아요 순 조회순으로 만들어볼 차례이다.
일단
trail_hall_of_fame.entity.ts
@Entity()
export class TrialHallOfFames {
@PrimaryColumn({ type: 'int', unique: true })
id: number;
@Column({ type: 'int' })
userId: number;
@Column({ type: 'varchar', nullable: false })
title: string;
@Column({ type: 'varchar', nullable: false })
content: string;
@Column({ type: 'varchar'})
totalVotes: number;
@CreateDateColumn({ type: 'timestamp' })
createdAt: Date;
@UpdateDateColumn({ type: 'timestamp' })
updatedAt: Date;
}
이 entity는 투표수를 기준으로 하기 위해 만들었던 엔티티 이다.
trial_hall_of_fame.like.entity.ts
@Entity()
export class TrialLikeHallOfFames {
@PrimaryColumn({ type: 'int', unique: true })
id: number;
@Column({ type: 'int' })
userId: number;
@Column({ type: 'varchar', nullable: false })
title: string;
@Column({ type: 'varchar', nullable: false })
content: string;
@Column({ type: 'varchar'})
totallike: number;
@CreateDateColumn({ type: 'timestamp' })
createdAt: Date;
@UpdateDateColumn({ type: 'timestamp' })
updatedAt: Date;
}
trial_hall_of_fame.like.entity.ts 이다.
기존의 trail_hall_of_fame.entity.ts 에서 totalVotes를 totallike로 바꾸어 주었다.
trial_hall_of_fame.Views.entity.ts
@Entity()
export class TrialViewHallOfFames {
@PrimaryColumn({ type: 'int', unique: true })
id: number;
@Column({ type: 'int' })
userId: number;
@Column({ type: 'varchar', nullable: false })
title: string;
@Column({ type: 'varchar', nullable: false })
content: string;
@Column({ type: 'varchar'})
totalview: number;
@CreateDateColumn({ type: 'timestamp' })
createdAt: Date;
@UpdateDateColumn({ type: 'timestamp' })
updatedAt: Date;
}
trial_hall_of_fame.Views.entity.ts 이다.
기존의 trial_hall_of_frame.entity.ts 에서 totalVotes를 totalViews로 바꾸어 주었다.
다음은 서비스 로직이다.
기존 코드
// 명예의 전당 매서드 일주일마다 업데이트
@Cron(CronExpression.EVERY_WEEK)
async updateHallOfFame() {
const lastWeekStart = new Date();
lastWeekStart.setDate(lastWeekStart.getDate() - 7 - lastWeekStart.getDay())
lastWeekStart.setHours(0, 0, 0, 0);
const lastWeekEnd = new Date(lastWeekStart);
lastWeekEnd.setDate(lastWeekEnd.getDate() + 6)
lastWeekEnd.setHours(23, 59, 59, 999);
// 지난주에 진행된 투표 데이터를 조회
const lastWeekVotes = await this.votesRepository.find({
where: {
createdAt: Between(lastWeekStart, lastWeekEnd),
},
});
/ 투표 기반으로 명예의 전당 집계
// 투표 수 데이터 가공
const hallOfFameData = this.aggVotesForHallOfFame(lastWeekVotes)
// 투표 명전 업데이트
await this.updateHallOfFameDatabase(hallOfFameData);
// 날짜 추상화 매서드
private getThisMonthRange(){
const start = new Date();
start.setDate(1); // 이번달 첫쨰날
start.setHours(0, 0, 0, 0) // 자정
const end = new Date(start.getFullYear(), start.getMonth() + 1, 0);
end.setHours(23, 59, 59, 999) // 하루의 마지막 시간
return { start, end }
}
// 투표 데이터 집계 매서드(투표 수 기준)
private async aggVotesForHallOfFame(votes: Votes[]){
const { start, end } = this.getThisMonthRange();
const candidates = await this.votesRepository
.createQueryBuilder("vote")
.select(['vote.id', 'vote.title1', 'vote.title2'])
.addSelect("vote.voteCount1 + vote.voteCount2", "totalVotes")
.where('vote.createdAt BETWEEN :start AND :end', { start: start.toISOString(), end: end.toISOString() })
.having("totalVotes >= :minTotalVotes", { minTotalVotes: 100 }) // 투표 수 100 이상인 것만 조회
.orderBy('totalVotes', "DESC")
.limit(1000)// 1000개 이상의 데이터가 없어도 남은 데이터 만큼 올라간다. 즉 데이터 집계 상한선이 1000개 라는 뜻
.groupBy("vote.id")
.getRawMany();
return candidates
}
// DB에 명예의 전당 데이터를 업데이트(배열형태로 받아서 한번에 저장) ver 1.(투표수)
private async updateHallOfFameDatabase(hallOfFameData: any){
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try{
// 한번에 저장
const newHallOfFameEntries = hallOfFameData.map(data => {
const newHallOfFameEntry = new TrialHallOfFames();
newHallOfFameEntry.id = data.id // vote table의 id임다
newHallOfFameEntry.userId = data.trial.userId // vote에는 userId가 없으므로 일대일관계인 trial에 가서 userId 가져옴
newHallOfFameEntry.title = data.title1 + 'Vs' + data.title2
newHallOfFameEntry.content = data.trial.content; // vote에는 content가 없으므로 일대일관계인 trial에 가서 content 가져옴
newHallOfFameEntry.totalVotes = data.voteCount1 + data.voteCount2;
newHallOfFameEntry.createdAt = new Date();
newHallOfFameEntry.updatedAt = new Date();
return newHallOfFameEntry;
});
// DB에 새로운 명전 저장
await queryRunner.manager.save(TrialHallOfFames, newHallOfFameEntries)
await queryRunner.commitTransaction();
} catch (err) {
console.log(err);
await queryRunner.rollbackTransaction();
} finally {
await queryRunner.release();
}
}
이 서비스 로직에서 그저 조회수와 좋아요만 추가해주면 되는 것이다.
다음은 추가한 서비스 코드이다.
// 명예의 전당 매서드 일주일마다 업데이트
@Cron(CronExpression.EVERY_WEEK)
async updateHallOfFame() {
const lastWeekStart = new Date();
lastWeekStart.setDate(lastWeekStart.getDate() - 7 - lastWeekStart.getDay())
lastWeekStart.setHours(0, 0, 0, 0);
const lastWeekEnd = new Date(lastWeekStart);
lastWeekEnd.setDate(lastWeekEnd.getDate() + 6)
lastWeekEnd.setHours(23, 59, 59, 999);
// 지난주에 진행된 투표 데이터를 조회
const lastWeekVotes = await this.votesRepository.find({
where: {
createdAt: Between(lastWeekStart, lastWeekEnd),
},
});
// 지난주에 진행된 재판 데이터를 조회
const lastWeekTrials= await this.trialsRepository.find({
where: {
createdAt: Between(lastWeekStart, lastWeekEnd)
},
});
// 투표 기반으로 명예의 전당 집계
// 투표 수 데이터 가공
const hallOfFameData = this.aggVotesForHallOfFame(lastWeekVotes)
// 좋아요 데이터 가공
const likeHallOfFameData = this.aggVotesLikeForHallOfFame(lastWeekTrials)
// 조회수 데이터 가공
const viewHallOfFameData = this.aggVotesViewForHallOfFame(lastWeekTrials)
// 업데이트
// 투표 명전 업데이트
await this.updateHallOfFameDatabase(hallOfFameData);
// 좋아요 명전 업데이트
await this.updateLikeHallOfFameDatabase(likeHallOfFameData);
// 조회수 명전 업데이트
await this.updateViewHallOfFameDatabase(viewHallOfFameData);
}
// 날짜 추상화 매서드
private getThisMonthRange(){
const start = new Date();
start.setDate(1); // 이번달 첫쨰날
start.setHours(0, 0, 0, 0) // 자정
const end = new Date(start.getFullYear(), start.getMonth() + 1, 0);
end.setHours(23, 59, 59, 999) // 하루의 마지막 시간
return { start, end }
}
// 투표 데이터 집계 매서드(좋아요 수 기준)
private async aggVotesLikeForHallOfFame(trials: Trials[]){
const { start, end } = this.getThisMonthRange();
const candidates = await this.trialsRepository
.createQueryBuilder("trial")
.select(['trial.id', 'trial.title', 'trial.content'])
.addSelect("trial.like", "likes")
.where('trial.createdAt BETWEEN :start AND :end', { start: start.toISOString(), end: end.toISOString() })
.having("likes >= :minlikes", { minlikes: 100 }) // 투표 수 100 이상인 것만 조회
.orderBy('likes', "DESC")
.limit(1000)// 1000개 이상의 데이터가 없어도 남은 데이터 만큼 올라간다. 즉 데이터 집계 상한선이 1000개 라는 뜻
.groupBy("trial.id")
.getRawMany();
return candidates
}
// 투표 데이터 집계 매서드(조회수 수 기준)
private async aggVotesViewForHallOfFame(trials: Trials[]){
const { start, end } = this.getThisMonthRange();
const candidates = await this.trialsRepository
.createQueryBuilder("trial")
.select(['trial.id', 'trial.title', 'trial.content'])
.addSelect("trial.view", "views")
.where('trial.createdAt BETWEEN :start AND :end', { start: start.toISOString(), end: end.toISOString() })
.having("views >= :minviews", { minviews: 100 }) // 투표 수 100 이상인 것만 조회
.orderBy('views', "DESC")
.limit(1000)// 1000개 이상의 데이터가 없어도 남은 데이터 만큼 올라간다. 즉 데이터 집계 상한선이 1000개 라는 뜻
.groupBy("trial.id")
.getRawMany();
return candidates
}
// 투표 데이터 집계 매서드(투표 수 기준)
private async aggVotesForHallOfFame(votes: Votes[]){
const { start, end } = this.getThisMonthRange();
const candidates = await this.votesRepository
.createQueryBuilder("vote")
.select(['vote.id', 'vote.title1', 'vote.title2'])
.addSelect("vote.voteCount1 + vote.voteCount2", "totalVotes")
.where('vote.createdAt BETWEEN :start AND :end', { start: start.toISOString(), end: end.toISOString() })
.having("totalVotes >= :minTotalVotes", { minTotalVotes: 100 }) // 투표 수 100 이상인 것만 조회
.orderBy('totalVotes', "DESC")
.limit(1000)// 1000개 이상의 데이터가 없어도 남은 데이터 만큼 올라간다. 즉 데이터 집계 상한선이 1000개 라는 뜻
.groupBy("vote.id")
.getRawMany();
return candidates
}
// DB에 명예의 전당 데이터를 업데이트(배열형태로 받아서 한번에 저장) ver 1.(투표수)
private async updateHallOfFameDatabase(hallOfFameData: any){
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try{
// 한번에 저장
const newHallOfFameEntries = hallOfFameData.map(data => {
const newHallOfFameEntry = new TrialHallOfFames();
newHallOfFameEntry.id = data.id // vote table의 id임다
newHallOfFameEntry.userId = data.trial.userId // vote에는 userId가 없으므로 일대일관계인 trial에 가서 userId 가져옴
newHallOfFameEntry.title = data.title1 + 'Vs' + data.title2
newHallOfFameEntry.content = data.trial.content; // vote에는 content가 없으므로 일대일관계인 trial에 가서 content 가져옴
newHallOfFameEntry.totalVotes = data.voteCount1 + data.voteCount2;
newHallOfFameEntry.createdAt = new Date();
newHallOfFameEntry.updatedAt = new Date();
return newHallOfFameEntry;
});
// DB에 새로운 명전 저장
await queryRunner.manager.save(TrialHallOfFames, newHallOfFameEntries)
await queryRunner.commitTransaction();
} catch (err) {
console.log(err);
await queryRunner.rollbackTransaction();
} finally {
await queryRunner.release();
}
}
// DB에 명예의 전당 데이터를 업데이트(배열형태로 받아서 한번에 저장) ver 1.(좋아요)
private async updateLikeHallOfFameDatabase(hallOfFameData: any){
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try{
// 한번에 저장
const newLikeHallOfFameEntries = hallOfFameData.map(data => {
const newLikeHallOfFameEntry = new TrialLikeHallOfFames();
newLikeHallOfFameEntry.id = data.id // vote table의 id임다
newLikeHallOfFameEntry.userId = data.userId // vote에는 userId가 없으므로 일대일관계인 trial에 가서 userId 가져옴
newLikeHallOfFameEntry.title = data.title;
newLikeHallOfFameEntry.content = data.content; // vote에는 content가 없으므로 일대일관계인 trial에 가서 content 가져옴
newLikeHallOfFameEntry.totallike = data.like;
newLikeHallOfFameEntry.createdAt = new Date();
newLikeHallOfFameEntry.updatedAt = new Date();
return newLikeHallOfFameEntry;
});
// DB에 새로운 명전 저장
await queryRunner.manager.save(TrialLikeHallOfFames, newLikeHallOfFameEntries)
await queryRunner.commitTransaction();
} catch (err) {
console.log(err);
await queryRunner.rollbackTransaction();
} finally {
await queryRunner.release();
}
}
// DB에 명예의 전당 데이터를 업데이트(배열형태로 받아서 한번에 저장) ver 1.(조회수)
private async updateViewHallOfFameDatabase(hallOfFameData: any){
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try{
// 한번에 저장
const newViewHallOfFameEntries = hallOfFameData.map(data => {
const newViewHallOfFameEntry = new TrialViewHallOfFames();
newViewHallOfFameEntry.id = data.id // vote table의 id임다
newViewHallOfFameEntry.userId = data.userId // vote에는 userId가 없으므로 일대일관계인 trial에 가서 userId 가져옴
newViewHallOfFameEntry.title = data.title;
newViewHallOfFameEntry.content = data.content; // vote에는 content가 없으므로 일대일관계인 trial에 가서 content 가져옴
newViewHallOfFameEntry.totalview = data.like;
newViewHallOfFameEntry.createdAt = new Date();
newViewHallOfFameEntry.updatedAt = new Date();
return newViewHallOfFameEntry;
});
// DB에 새로운 명전 저장
await queryRunner.manager.save(TrialViewHallOfFames, newViewHallOfFameEntries)
await queryRunner.commitTransaction();
} catch (err) {
console.log(err);
await queryRunner.rollbackTransaction();
} finally {
await queryRunner.release();
}
}