@@ -9,10 +9,13 @@ import { SessionService } from '../auth/session.service';
99import { ParticipantsService } from '../participants/participants.service' ;
1010import { RoomBan } from '../participants/entities/room-ban.entity' ;
1111import { RoomFingerprint } from './entities/room-fingerprint.entity' ;
12+ import { forwardRef , Inject } from '@nestjs/common' ;
1213
1314@Injectable ( )
1415export class RoomsService {
1516 private readonly logger = new Logger ( RoomsService . name ) ;
17+ private roomTimers = new Map < string , NodeJS . Timeout > ( ) ;
18+ private chatGateway : any ; // Will be set after module initialization to avoid circular dependency
1619
1720 constructor (
1821 @InjectRepository ( Room ) private readonly roomRepo : Repository < Room > ,
@@ -22,6 +25,11 @@ export class RoomsService {
2225 @InjectRepository ( RoomFingerprint ) private readonly roomFingerprintRepo : Repository < RoomFingerprint > ,
2326 ) { }
2427
28+ // Setter to inject ChatGateway after initialization
29+ setChatGateway ( gateway : any ) {
30+ this . chatGateway = gateway ;
31+ }
32+
2533 async verifyModeratorToken ( roomId : string , moderatorToken : string ) {
2634 const room = await this . roomRepo . findOne ( { where : { id : roomId } } ) ;
2735 if ( ! room ) {
@@ -200,6 +208,9 @@ export class RoomsService {
200208 throw new ForbiddenException ( 'Invalid moderator token' ) ;
201209 }
202210
211+ // Cancel the duration timer if it exists
212+ this . cancelRoomDurationTimer ( roomId ) ;
213+
203214 // First, close the room so no new participants can join
204215 await this . roomRepo
205216 . createQueryBuilder ( )
@@ -273,4 +284,123 @@ export class RoomsService {
273284 . where ( "id = :id" , { id : roomId } )
274285 . execute ( ) ;
275286 }
287+
288+ /**
289+ * Start a timer for a room based on its chatDurationSeconds.
290+ * When the timer expires, the room will be automatically closed and all sessions ended.
291+ */
292+ async startRoomDurationTimer ( roomId : string ) {
293+ // Check if timer already exists
294+ if ( this . roomTimers . has ( roomId ) ) {
295+ this . logger . log ( `Timer already exists for room ${ roomId } ` ) ;
296+ return ;
297+ }
298+
299+ const room = await this . roomRepo . findOne ( { where : { id : roomId } } ) ;
300+ if ( ! room ) {
301+ this . logger . warn ( `Cannot start timer for non-existent room ${ roomId } ` ) ;
302+ return ;
303+ }
304+
305+ if ( ! room . chatDurationSeconds || room . chatDurationSeconds <= 0 ) {
306+ this . logger . log ( `Room ${ roomId } has no duration limit, skipping timer` ) ;
307+ return ;
308+ }
309+
310+ // Calculate remaining time
311+ const now = new Date ( ) ;
312+ const elapsed = ( now . getTime ( ) - room . createdAt . getTime ( ) ) / 1000 ;
313+ const remaining = room . chatDurationSeconds - elapsed ;
314+
315+ if ( remaining <= 0 ) {
316+ this . logger . log ( `Room ${ roomId } duration already expired, ending immediately` ) ;
317+ await this . endRoomByDuration ( roomId ) ;
318+ return ;
319+ }
320+
321+ this . logger . log ( `Starting ${ remaining } s timer for room ${ roomId } ` ) ;
322+ const timeout = setTimeout ( ( ) => {
323+ this . endRoomByDuration ( roomId ) ;
324+ } , remaining * 1000 ) ;
325+
326+ this . roomTimers . set ( roomId , timeout ) ;
327+ }
328+
329+ /**
330+ * End a room automatically when its chat duration is reached.
331+ */
332+ async endRoomByDuration ( roomId : string ) {
333+ this . logger . log ( `Room ${ roomId } duration reached, ending all sessions` ) ;
334+
335+ // Clear the timer
336+ const timer = this . roomTimers . get ( roomId ) ;
337+ if ( timer ) {
338+ clearTimeout ( timer ) ;
339+ this . roomTimers . delete ( roomId ) ;
340+ }
341+
342+ const room = await this . roomRepo . findOne ( { where : { id : roomId } } ) ;
343+ if ( ! room ) {
344+ this . logger . warn ( `Room ${ roomId } not found when trying to end by duration` ) ;
345+ return ;
346+ }
347+
348+ if ( room . closedAt ) {
349+ this . logger . log ( `Room ${ roomId } already closed` ) ;
350+ return ;
351+ }
352+
353+ // Close the room
354+ await this . roomRepo
355+ . createQueryBuilder ( )
356+ . update ( Room )
357+ . set ( { isOpen : false , closedAt : new Date ( ) } )
358+ . where ( "id = :id" , { id : roomId } )
359+ . execute ( ) ;
360+
361+ this . logger . log ( `Room ${ roomId } closed due to duration limit, destroying sessions` ) ;
362+
363+ // Notify all participants that the room has ended due to duration
364+ if ( this . chatGateway ) {
365+ this . chatGateway . emitRoomEnded ( roomId , 'Chat duration time limit has been reached' ) ;
366+ }
367+
368+ // Destroy all sessions for the room
369+ await this . sessionService . destroyAllSessionsForRoom ( roomId ) ;
370+ }
371+
372+ /**
373+ * Cancel the duration timer for a room (e.g., if manually closed by moderator)
374+ */
375+ cancelRoomDurationTimer ( roomId : string ) {
376+ const timer = this . roomTimers . get ( roomId ) ;
377+ if ( timer ) {
378+ clearTimeout ( timer ) ;
379+ this . roomTimers . delete ( roomId ) ;
380+ this . logger . log ( `Cancelled duration timer for room ${ roomId } ` ) ;
381+ }
382+ }
383+
384+ /**
385+ * Get remaining time in seconds for a room's chat duration.
386+ * Returns null if no duration limit or if already expired/closed.
387+ */
388+ async getRoomRemainingTime ( roomId : string ) : Promise < number | null > {
389+ const room = await this . roomRepo . findOne ( { where : { id : roomId } } ) ;
390+ if ( ! room ) return null ;
391+
392+ if ( ! room . chatDurationSeconds || room . chatDurationSeconds <= 0 ) {
393+ return null ; // No duration limit
394+ }
395+
396+ if ( room . closedAt || ! room . isOpen ) {
397+ return 0 ; // Room is closed
398+ }
399+
400+ const now = new Date ( ) ;
401+ const elapsed = ( now . getTime ( ) - room . createdAt . getTime ( ) ) / 1000 ;
402+ const remaining = room . chatDurationSeconds - elapsed ;
403+
404+ return remaining > 0 ? remaining : 0 ;
405+ }
276406}
0 commit comments