From b37c6b22ddd5b52b5daf090c1a0edb7332227306 Mon Sep 17 00:00:00 2001 From: Ryan-slither Date: Fri, 3 Oct 2025 20:12:41 -0400 Subject: [PATCH] Fixed Gateway & Dtos --- .../juke-session/juke-session.controller.ts | 8 ++- src/jukebox/jukebox.gateway.ts | 63 +++++++++++++------ .../player/dto/player-aux-update.dto.ts | 9 ++- src/utils/filters/http-exception.filter.ts | 8 ++- src/utils/filters/ws-exception.filter.ts | 10 +-- src/utils/guards/roles.guard.ts | 2 - test/app.e2e-spec.ts | 16 ++--- 7 files changed, 75 insertions(+), 41 deletions(-) diff --git a/src/jukebox/juke-session/juke-session.controller.ts b/src/jukebox/juke-session/juke-session.controller.ts index f88ad3b..4ac4900 100644 --- a/src/jukebox/juke-session/juke-session.controller.ts +++ b/src/jukebox/juke-session/juke-session.controller.ts @@ -122,8 +122,12 @@ export class JukeSessionController { @UseGuards(RolesGuard) @Post(':id/members/join/') @Serialize(JukeSessionMembershipDto) - @ApiOperation({ summary: 'Add Juke Session Member' }) - joinJukeSession(@Param('id', new NumberPipe('id')) id: number, @CurrentUser() user: UserDto) { + @ApiOperation({ summary: '[MEMBER] Add Juke Session Member' }) + joinJukeSession( + @Param('jukebox_id', new NumberPipe('jukebox_id')) jukeboxId: number, + @Param('id', new NumberPipe('id')) id: number, + @CurrentUser() user: UserDto, + ) { return this.jukeSessionService.createMembership(id, { user_id: user.id }) } diff --git a/src/jukebox/jukebox.gateway.ts b/src/jukebox/jukebox.gateway.ts index 56fe7cd..a5b7d11 100644 --- a/src/jukebox/jukebox.gateway.ts +++ b/src/jukebox/jukebox.gateway.ts @@ -30,6 +30,7 @@ import { CLUBS_URL, NODE_ENV } from 'src/config' import { JukeboxService } from './jukebox.service' import { NetworkService } from 'src/network/network.service' import { WSExceptionFilter } from 'src/utils/filters/ws-exception.filter' +import { QueuedTrackDto } from './queue/dto' @UseFilters(new WSExceptionFilter()) @WebSocketGateway({ @@ -66,10 +67,10 @@ export class JukeboxGateway implements OnGatewayInit { ) } - const jukeboxId = client.handshake.query?.jukeboxId ?? null + const clubId = client.handshake.query?.club_id ?? null const role = client.handshake.query?.role ?? null - if (jukeboxId == null) { + if (clubId == null) { return next( this.handleConnectionRejection( client, @@ -89,9 +90,6 @@ export class JukeboxGateway implements OnGatewayInit { ) } - const jukebox = await this.jukeboxService.findOne(parseInt(jukeboxId as string, 10)) - const clubId = jukebox.club_id - const clubs = (await this.networkService.sendRequest( `${CLUBS_URL}/api/v1/club/clubs/${role === 'admin' ? '?is_admin=true' : ''}`, 'GET', @@ -107,16 +105,17 @@ export class JukeboxGateway implements OnGatewayInit { ) } - if (!!clubs.data.find((m) => m.id === clubId)) { - console.log(`[WS] Client ${client.id} authorized for jukebox ${jukeboxId} as ${role}`) + if (!!clubs.data.find((m) => m.id === parseInt(clubId as string))) { + console.log(`[WS] Client ${client.id} authorized for club ${clubId} as ${role}`) client['role'] = role + this.server.emit('hands shaked') return next() } else { return next( this.handleConnectionRejection( client, - `Connection rejected: user not member of jukebox ${jukeboxId}`, - 'Authorization failed: you are not a member of this jukebox.', + `Connection rejected: user not member of club ${clubId}`, + 'Authorization failed: you are not a member of this club.', ), ) } @@ -124,7 +123,7 @@ export class JukeboxGateway implements OnGatewayInit { return next( this.handleConnectionRejection( client, - 'Unexpected error during handshake', + `Unexpected error during handshake: ${e}`, 'Authorization failed: unexpected error', ), ) @@ -137,19 +136,36 @@ export class JukeboxGateway implements OnGatewayInit { if (client['role'] !== 'member' && client['role'] !== 'admin') { throw new WsException('You are not authorized') } + const jukeboxId = payload.jukebox_id.toString() + console.log('Joining ', jukeboxId) client.join(jukeboxId) - client.to(jukeboxId).emit('player-join-success', { success: true }) + const playerState = await this.playerService.getPlayerState(+jukeboxId) + console.log(playerState) + this.server.to(jukeboxId).emit('player-join-success', playerState) + } + + @SubscribeMessage('player-ping') + async handlePlayerPing(@ConnectedSocket() client: Socket) { + if (client['role'] !== 'admin') { + throw new WsException('You are not authorized') + } + + console.log('Player Available') } @SubscribeMessage('player-aux-update') - async handlePlayerAuxUpdate(client: Socket, @MessageBody() payload: PlayerAuxUpdateDto) { + async handlePlayerAuxUpdate( + @ConnectedSocket() client: Socket, + @MessageBody() payload: PlayerAuxUpdateDto, + ) { + console.log('Received Player Aux Update') if (client['role'] !== 'admin') { throw new WsException('You are not authorized') } console.log(payload) - const { jukebox_id, action, progress, current_track } = payload + const { jukebox_id, action, progress, spotify_track, duration_ms } = payload const session = await this.jukeSessionService.getCurrentSession(jukebox_id) switch (action) { @@ -160,21 +176,28 @@ export class JukeboxGateway implements OnGatewayInit { this.playerService.setIsPlaying(jukebox_id, false) break case 'changed_tracks': - if (current_track && !current_track?.spotify_id) { + if (spotify_track && !spotify_track?.spotify_id) { throw new WsException('Track must have a spotify id') } // Check if next track was next in queue, if so pop it - const nextTrack = await this.queueService.getNextTrack(session.id) + let nextTrack: QueuedTrackDto | null + try { + nextTrack = await this.queueService.getNextTrack(session.id) + } catch (err) { + if (err instanceof NotFoundException) { + nextTrack = null + } else throw new err() + } - if (nextTrack.track.spotify_id === current_track?.spotify_id) { + if (nextTrack && nextTrack.track.spotify_id === spotify_track?.spotify_id) { // Changed track was next from queue const track = await this.queueService.popNextTrack(session.id) await this.playerService.setCurrentQueuedTrack(jukebox_id, track) await this.queueService.queueNextTrackToSpotify(jukebox_id, session.id) - } else if (current_track) { + } else if (spotify_track) { // Changed track was outside of queue - const track = await this.tracksService.getTrack(current_track.spotify_id!, jukebox_id) + const track = await this.tracksService.getTrack(spotify_track.spotify_id!, jukebox_id) await this.playerService.setCurrentSpotifyTrack(jukebox_id, track) } break @@ -187,7 +210,9 @@ export class JukeboxGateway implements OnGatewayInit { } const playerState = await this.playerService.getPlayerState(jukebox_id) - client.to(jukebox_id.toString()).emit('player-state-update', playerState) + const result = { ...playerState, spotify_track: { ...spotify_track, duration_ms } } + console.log(result) + this.server.to(jukebox_id.toString()).emit('player-state-update', result) } private handleConnectionRejection(client: Socket, loggingMessage: string, errorMessage: string) { diff --git a/src/jukebox/player/dto/player-aux-update.dto.ts b/src/jukebox/player/dto/player-aux-update.dto.ts index 98266ae..4671356 100644 --- a/src/jukebox/player/dto/player-aux-update.dto.ts +++ b/src/jukebox/player/dto/player-aux-update.dto.ts @@ -24,18 +24,23 @@ export class PlayerAuxUpdateDto { progress?: number @IsOptional() + @IsNumber() + duration_ms?: number + + @IsOptional() + @Type(() => Date) @IsDate() timestamp?: Date @IsOptional() @ValidateNested() @Type(() => TrackDto) - current_track?: TrackDto + spotify_track?: TrackDto } export class PlayerJoinDto extends OmitType(PlayerAuxUpdateDto, [ 'action' as const, 'progress' as const, 'timestamp' as const, - 'current_track' as const, + 'spotify_track' as const, ]) {} diff --git a/src/utils/filters/http-exception.filter.ts b/src/utils/filters/http-exception.filter.ts index bbf99e3..5a318a3 100644 --- a/src/utils/filters/http-exception.filter.ts +++ b/src/utils/filters/http-exception.filter.ts @@ -1,12 +1,14 @@ import { ArgumentsHost, Catch, ExceptionFilter, HttpException, Logger } from '@nestjs/common' -import { HttpArgumentsHost } from '@nestjs/common/interfaces' -import { error } from 'console' -import { Request, Response } from 'express' +import { Response } from 'express' @Catch() export class HttpExceptionFilter implements ExceptionFilter { catch(exception: HttpException, host: ArgumentsHost) { Logger.error(exception, exception.stack) + if (host.getType() !== 'http') { + throw exception instanceof Error ? exception : new Error(String(exception)) + } + const ctx = host.switchToHttp() const response = ctx.getResponse() let status: number diff --git a/src/utils/filters/ws-exception.filter.ts b/src/utils/filters/ws-exception.filter.ts index c793f6a..ff8f5c0 100644 --- a/src/utils/filters/ws-exception.filter.ts +++ b/src/utils/filters/ws-exception.filter.ts @@ -1,19 +1,19 @@ -import { ArgumentsHost, Catch } from '@nestjs/common' +import { ArgumentsHost, Catch, InternalServerErrorException } from '@nestjs/common' import { BaseWsExceptionFilter, WsException } from '@nestjs/websockets' -@Catch(WsException) +@Catch() export class WSExceptionFilter extends BaseWsExceptionFilter { catch(exception: WsException, host: ArgumentsHost) { + console.log(exception) const client = host.switchToWs().getClient() const data = host.switchToWs().getData() - const error = exception.getError() - const details = error instanceof Object ? { ...error } : { message: error } + const error = exception.message const jsonResponse = JSON.stringify({ event: 'WebSocket Error', data: { role: client['role'] ?? 'no role', data: data, - ...details, + error, }, }) client.emit('exception', jsonResponse) diff --git a/src/utils/guards/roles.guard.ts b/src/utils/guards/roles.guard.ts index 9a7d902..2396a8a 100644 --- a/src/utils/guards/roles.guard.ts +++ b/src/utils/guards/roles.guard.ts @@ -54,8 +54,6 @@ export class RolesGuard implements CanActivate { return false } - console.log(clubs) - return !!clubs.data.find((m) => m.id == clubId) } } diff --git a/test/app.e2e-spec.ts b/test/app.e2e-spec.ts index 8111832..0f3b271 100644 --- a/test/app.e2e-spec.ts +++ b/test/app.e2e-spec.ts @@ -70,13 +70,13 @@ describe('AppController (e2e)', () => { .mockResolvedValueOnce({ status: 200, description: 'test', - data: [{ id: 0, name: 'Baby' }], + data: [{ id: 1, name: 'Baby' }], }) // For admin .mockResolvedValueOnce({ status: 200, description: 'test', - data: [{ id: 0, name: 'Baby' }], + data: [{ id: 1, name: 'Baby' }], }) // For not part of club .mockResolvedValueOnce({ @@ -123,7 +123,7 @@ describe('AppController (e2e)', () => { expect(jukeboxResult.status).toBe(201) const findAllJukeboxResult = await request(app.getHttpServer()) - .get('/jukebox/jukeboxes/?clubId=0') + .get('/jukebox/jukeboxes/?club_id=0') .set('Authorization', 'Bearer MEMBER_TOKEN') expect(findAllJukeboxResult.status).toBe(200) @@ -152,7 +152,7 @@ describe('AppController (e2e)', () => { }, query: { role: 'member', - jukeboxId: jukebox.id, + club_id: 1, }, }) expect(memberSocket.connected).toBe(true) @@ -163,7 +163,7 @@ describe('AppController (e2e)', () => { }, query: { role: 'admin', - jukeboxId: jukebox.id, + club_id: 1, }, }) expect(adminSocket.connected).toBe(true) @@ -174,7 +174,7 @@ describe('AppController (e2e)', () => { }, query: { role: 'admin', - jukeboxId: jukebox.id, + club_id: 1, }, }) expect(failureSocket1.connected).toBe(false) @@ -185,7 +185,7 @@ describe('AppController (e2e)', () => { }, query: { role: '', - jukeboxId: jukebox.id, + club_id: 1, }, }) expect(failureSocket2.connected).toBe(false) @@ -196,7 +196,7 @@ describe('AppController (e2e)', () => { }, query: { role: 'admin', - jukeboxId: jukebox.id, + club_id: 1, }, }) expect(wrongPermission.connected).toBe(false)