Skip to content

Commit ca579a1

Browse files
authored
Merge pull request #248 from boostcampwm-2024/feat/#243-close-event
예매 마감 로직
2 parents 3cdd817 + 31bc7f8 commit ca579a1

File tree

13 files changed

+247
-31
lines changed

13 files changed

+247
-31
lines changed

back/package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

back/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"class-validator": "^0.14.1",
3939
"connect-redis": "^7.1.1",
4040
"cookie-parser": "^1.4.7",
41+
"cron": "^3.1.7",
4142
"dotenv": "^16.4.5",
4243
"express-session": "^1.18.1",
4344
"ioredis": "^5.4.1",

back/src/config/typeOrmConfig.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const ormConfig: TypeOrmModuleOptions = {
1010
entities: [__dirname + '/../**/*.entity{.ts,.js}'],
1111
synchronize: process.env.DATABASE_SYNCHRONIZE === 'true',
1212
charset: 'utf8mb4',
13-
timezone: 'z',
13+
timezone: '+09:00',
1414
};
1515

1616
export default ormConfig;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const ONE_MINUTE_BEFORE_THE_HOUR = '0 59 * * * *';

back/src/domains/booking/controller/booking.controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,6 @@ export class BookingController {
193193
@ApiOkResponse({ description: '확인 및 오픈 완료' })
194194
@ApiUnauthorizedResponse({ description: '인증 실패' })
195195
async reloadOpenTarget() {
196-
await this.openBookingService.checkAndOpenReservations();
196+
await this.openBookingService.scheduleUpcomingReservations();
197197
}
198198
}

back/src/domains/booking/service/booking-seats.service.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import Redis from 'ioredis';
1010
import { BehaviorSubject } from 'rxjs';
1111
import { map } from 'rxjs/operators';
1212

13-
import { AuthService } from '../../../auth/service/auth.service';
1413
import { UserService } from '../../user/service/user.service';
1514
import { SEATS_BROADCAST_INTERVAL } from '../const/seatsBroadcastInterval.const';
1615
import { SEATS_SSE_RETRY_TIME } from '../const/seatsSseRetryTime.const';
@@ -36,7 +35,6 @@ export class BookingSeatsService {
3635
constructor(
3736
private redisService: RedisService,
3837
private inBookingService: InBookingService,
39-
private authService: AuthService,
4038
private eventEmitter: EventEmitter2,
4139
private readonly userService: UserService,
4240
) {
@@ -58,6 +56,18 @@ export class BookingSeatsService {
5856
this.seatsSubscriptionMap.set(eventId, subscription);
5957
}
6058

59+
async clearSeatsSubscription(eventId: number) {
60+
const subscription = this.seatsSubscriptionMap.get(eventId);
61+
if (subscription) {
62+
subscription.complete();
63+
this.seatsSubscriptionMap.delete(eventId);
64+
}
65+
const keys = await this.redis.keys(`event:${eventId}:*`);
66+
if (keys.length > 0) {
67+
await this.redis.unlink(...keys);
68+
}
69+
}
70+
6171
async bookSeat(sid: string, target: [number, number]) {
6272
const eventId = await this.userService.getUserEventTarget(sid);
6373
const bookedSeat = await this.inBookingService.getBookedSeats(sid);

back/src/domains/booking/service/booking.service.ts

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { OnEvent } from '@nestjs/event-emitter';
44
import Redis from 'ioredis';
55

66
import { AuthService } from '../../../auth/service/auth.service';
7-
import { EventService } from '../../event/service/event.service';
87
import { UserService } from '../../user/service/user.service';
98
import { BookingAdmissionStatusDto } from '../dto/bookingAdmissionStatus.dto';
109
import { ServerTimeDto } from '../dto/serverTime.dto';
@@ -15,15 +14,12 @@ import { InBookingService } from './in-booking.service';
1514
import { OpenBookingService } from './open-booking.service';
1615
import { WaitingQueueService } from './waiting-queue.service';
1716

18-
const OFFSET = 1000 * 60 * 60 * 9;
19-
2017
@Injectable()
2118
export class BookingService {
2219
private logger = new Logger(BookingService.name);
2320
private readonly redis: Redis | null;
2421
constructor(
2522
private readonly redisService: RedisService,
26-
private readonly eventService: EventService,
2723
private readonly authService: AuthService,
2824
private readonly bookingSeatsService: BookingSeatsService,
2925
private readonly inBookingService: InBookingService,
@@ -39,9 +35,11 @@ export class BookingService {
3935
async onSeatsSseDisconnected(event: { sid: string }) {
4036
const sid = event.sid;
4137
const eventId = await this.userService.getUserEventTarget(sid);
42-
await this.collectSeatsIfNotSaved(eventId, sid);
43-
await this.inBookingService.emitSession(sid);
44-
await this.letInNextWaiting(eventId);
38+
if (await this.openBookingService.isEventOpened(eventId)) {
39+
await this.collectSeatsIfNotSaved(eventId, sid);
40+
await this.inBookingService.emitSession(sid);
41+
await this.letInNextWaiting(eventId);
42+
}
4543
}
4644

4745
private async collectSeatsIfNotSaved(eventId: number, sid: string) {
@@ -100,17 +98,9 @@ export class BookingService {
10098

10199
// 함수 이름 생각하기
102100
async isAdmission(eventId: number, sid: string): Promise<BookingAdmissionStatusDto> {
103-
// eventId를 받아서 해당 이벤트가 존재하는지 확인한다.
104-
const event = await this.eventService.findEvent({ eventId });
105-
const now = new Date(Date.now() + OFFSET);
106101
const isOpened = await this.openBookingService.isEventOpened(eventId);
107-
108102
if (!isOpened) {
109-
throw new BadRequestException('아직 예약이 오픈되지 않았습니다.');
110-
} else if (now >= event.reservationCloseDate) {
111-
//event 시간 확인 이벤트 종료시간 이후인지
112-
// 예약 시간이 아닙니다.
113-
throw new BadRequestException('이미 예약 마감된 이벤트입니다.');
103+
throw new BadRequestException('예약이 오픈되지 않았습니다.');
114104
}
115105

116106
await this.userService.setUserEventTarget(sid, eventId);

back/src/domains/booking/service/enter-booking.service.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { RedisService } from '@liaoliaots/nestjs-redis';
22
import { Injectable } from '@nestjs/common';
33
import { EventEmitter2 } from '@nestjs/event-emitter';
4+
import { SchedulerRegistry } from '@nestjs/schedule';
45
import Redis from 'ioredis';
56

67
import { UserService } from '../../user/service/user.service';
@@ -13,15 +14,30 @@ export class EnterBookingService {
1314
private readonly redisService: RedisService,
1415
private readonly userService: UserService,
1516
private readonly eventEmitter: EventEmitter2,
17+
private readonly schedulerRegistry: SchedulerRegistry,
1618
) {
1719
this.redis = this.redisService.getOrThrow();
1820
}
1921

2022
async gcEnteringSessions(eventId: number) {
21-
setInterval(() => {
23+
this.deleteIntervalIfExists(`gc-entering-${eventId}`);
24+
25+
const interval = setInterval(() => {
2226
this.removeExpiredSessions(eventId);
2327
this.eventEmitter.emit('entering-sessions-gc', { eventId });
2428
}, ENTERING_GC_INTERVAL);
29+
30+
this.schedulerRegistry.addInterval(`gc-entering-${eventId}`, interval);
31+
}
32+
33+
clearGCInterval(eventId: number) {
34+
this.deleteIntervalIfExists(`gc-entering-${eventId}`);
35+
}
36+
37+
private deleteIntervalIfExists(intervalName: string) {
38+
if (this.schedulerRegistry.doesExist('interval', intervalName)) {
39+
this.schedulerRegistry.deleteInterval(intervalName);
40+
}
2541
}
2642

2743
async addEnteringSession(sid: string) {
@@ -64,4 +80,16 @@ export class EnterBookingService {
6480
const expiryTimestamp = Date.now() - ENTERING_SESSION_EXPIRY;
6581
await this.redis.zremrangebyscore(`entering:${eventId}`, 0, expiryTimestamp);
6682
}
83+
84+
async getAllEnteringSids(eventId: number) {
85+
return this.redis.zrange(`entering:${eventId}`, 0, -1);
86+
}
87+
88+
async clearEnteringPool(eventId: number) {
89+
this.clearGCInterval(eventId);
90+
const keys = await this.redis.keys('entering:*');
91+
if (keys.length > 0) {
92+
await this.redis.unlink(...keys);
93+
}
94+
}
6795
}

back/src/domains/booking/service/in-booking.service.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,4 +179,15 @@ export class InBookingService {
179179
bookedSeats: session.bookedSeats,
180180
};
181181
}
182+
183+
async getAllInBookingSids(eventId: number) {
184+
return this.redis.smembers(this.getEventKey(eventId));
185+
}
186+
187+
async clearInBookingPool(eventId: number) {
188+
const keys = await this.redis.keys(`in-booking:${eventId}:*`);
189+
if (keys.length > 0) {
190+
await this.redis.unlink(...keys);
191+
}
192+
}
182193
}

0 commit comments

Comments
 (0)