first commit
This commit is contained in:
178
Smartes-Klassenzimmer-Backend/src/classroom/classroom.gateway.ts
Normal file
178
Smartes-Klassenzimmer-Backend/src/classroom/classroom.gateway.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
import {
|
||||
WebSocketGateway,
|
||||
WebSocketServer,
|
||||
SubscribeMessage,
|
||||
MessageBody,
|
||||
ConnectedSocket,
|
||||
OnGatewayConnection,
|
||||
OnGatewayDisconnect,
|
||||
} from '@nestjs/websockets';
|
||||
import { UseGuards } from '@nestjs/common';
|
||||
import { Server, Socket } from 'socket.io';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { WsJwtGuard } from '../auth/guards';
|
||||
import { ClassroomService } from './classroom.service';
|
||||
|
||||
@WebSocketGateway({
|
||||
namespace: '/api/classroom',
|
||||
cors: {
|
||||
origin: [
|
||||
'http://localhost:5173',
|
||||
'http://127.0.0.1:5173',
|
||||
'http://localhost:5500',
|
||||
'http://127.0.0.1:5500',
|
||||
'http://localhost:3000'
|
||||
],
|
||||
credentials: true,
|
||||
},
|
||||
})
|
||||
export class ClassroomGateway implements OnGatewayConnection, OnGatewayDisconnect {
|
||||
@WebSocketServer()
|
||||
server: Server;
|
||||
|
||||
constructor(
|
||||
private readonly jwtService: JwtService,
|
||||
private readonly classroomService: ClassroomService,
|
||||
) {}
|
||||
|
||||
handleConnection(client: Socket) {
|
||||
try {
|
||||
const token = this.extractTokenFromHandshake(client);
|
||||
|
||||
if (!token) {
|
||||
console.log(`Classroom: Client ${client.id} rejected: No token`);
|
||||
client.disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = this.jwtService.verify(token, {
|
||||
secret: process.env.JWT_SECRET || 'your-super-secret-jwt-key-change-this-in-production',
|
||||
});
|
||||
|
||||
client.data.user = payload;
|
||||
|
||||
// Send current state to newly connected user
|
||||
client.emit('state-update', {
|
||||
raisedHands: this.classroomService.getRaisedHands(),
|
||||
activeStudent: this.classroomService.getActiveStudent(),
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.log(`Classroom: Client ${client.id} rejected: Invalid token`);
|
||||
client.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
handleDisconnect(client: Socket) {
|
||||
// Optional: automatically lower hand on disconnect?
|
||||
// For now, let's keep it persistent for 10 mins as per requirements.
|
||||
}
|
||||
|
||||
private extractTokenFromHandshake(client: Socket): string | null {
|
||||
const cookies = client.handshake.headers.cookie;
|
||||
if (cookies) {
|
||||
const cookieArray = cookies.split(';');
|
||||
for (const cookie of cookieArray) {
|
||||
const [name, value] = cookie.trim().split('=');
|
||||
if (name === 'access_token') {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return client.handshake.auth?.token || (client.handshake.query?.token as string) || null;
|
||||
}
|
||||
|
||||
private broadcastState() {
|
||||
this.server.emit('state-update', {
|
||||
raisedHands: this.classroomService.getRaisedHands(),
|
||||
activeStudent: this.classroomService.getActiveStudent(),
|
||||
});
|
||||
}
|
||||
|
||||
@UseGuards(WsJwtGuard)
|
||||
@SubscribeMessage('raise-hand')
|
||||
handleRaiseHand(
|
||||
@MessageBody() data: { type: 'normal' | 'question' },
|
||||
@ConnectedSocket() client: Socket,
|
||||
) {
|
||||
const user = client.data.user;
|
||||
this.classroomService.raiseHand(user.sub, user.username, data.type, (userId) => {
|
||||
// Callback for timeout
|
||||
this.classroomService.lowerHand(userId);
|
||||
this.broadcastState();
|
||||
});
|
||||
this.broadcastState();
|
||||
}
|
||||
|
||||
@UseGuards(WsJwtGuard)
|
||||
@SubscribeMessage('lower-hand')
|
||||
handleLowerHand(@ConnectedSocket() client: Socket) {
|
||||
const user = client.data.user;
|
||||
this.classroomService.lowerHand(user.sub);
|
||||
this.broadcastState();
|
||||
}
|
||||
|
||||
@UseGuards(WsJwtGuard)
|
||||
@SubscribeMessage('lower-all-hands')
|
||||
handleLowerAllHands(@ConnectedSocket() client: Socket) {
|
||||
const user = client.data.user;
|
||||
if (user.role !== 'Lehrer') {
|
||||
client.emit('error', { message: 'Nur Lehrer können alle Hände senken.' });
|
||||
return;
|
||||
}
|
||||
this.classroomService.lowerAllHands();
|
||||
this.broadcastState();
|
||||
}
|
||||
|
||||
@UseGuards(WsJwtGuard)
|
||||
@SubscribeMessage('pick-student')
|
||||
handlePickStudent(
|
||||
@MessageBody() data: { userId: number },
|
||||
@ConnectedSocket() client: Socket,
|
||||
) {
|
||||
const user = client.data.user;
|
||||
if (user.role !== 'Lehrer') {
|
||||
client.emit('error', { message: 'Nur Lehrer können Schüler aufrufen.' });
|
||||
return;
|
||||
}
|
||||
|
||||
const hands = this.classroomService.getRaisedHands();
|
||||
const target = hands.find(h => h.userId === data.userId);
|
||||
|
||||
if (target) {
|
||||
this.classroomService.setActiveStudent(target.userId, target.username);
|
||||
this.broadcastState();
|
||||
}
|
||||
}
|
||||
|
||||
@UseGuards(WsJwtGuard)
|
||||
@SubscribeMessage('pick-random')
|
||||
handlePickRandom(@ConnectedSocket() client: Socket) {
|
||||
const user = client.data.user;
|
||||
if (user.role !== 'Lehrer') {
|
||||
client.emit('error', { message: 'Nur Lehrer können Zufallsauswahl nutzen.' });
|
||||
return;
|
||||
}
|
||||
|
||||
this.classroomService.pickRandomStudent();
|
||||
this.broadcastState();
|
||||
}
|
||||
|
||||
@UseGuards(WsJwtGuard)
|
||||
@SubscribeMessage('reset-active')
|
||||
handleResetActive(@ConnectedSocket() client: Socket) {
|
||||
const user = client.data.user;
|
||||
const activeStudent = this.classroomService.getActiveStudent();
|
||||
|
||||
// Allow if teacher OR if the requesting user is the active student
|
||||
const isActiveStudent = activeStudent && activeStudent.userId === user.sub;
|
||||
|
||||
if (user.role !== 'Lehrer' && !isActiveStudent) {
|
||||
client.emit('error', { message: 'Nur Lehrer oder der aktive Schüler können die Auswahl zurücksetzen.' });
|
||||
return;
|
||||
}
|
||||
|
||||
this.classroomService.clearActiveStudent();
|
||||
this.broadcastState();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user