From ac3074e4cb2f426311192f3bc8e555e0c30f7b5f Mon Sep 17 00:00:00 2001 From: MonaS8 <schumo62@gmail.com> Date: Sun, 30 Mar 2025 16:47:40 +0200 Subject: [PATCH] integrate roles and modes in roomSession and roomSessionUser --- .../migrations/.snapshot-collab_space.json | 158 ++-------- .../migrations/Migration20250330142342.ts | 37 +++ .../migrations/Migration20250330143630.ts | 13 + .../migrations/Migration20250330143957.ts | 13 + src/activities/activities.module.ts | 9 +- src/activities/activities.service.ts | 289 ++---------------- src/channel/browser.gateway.ts | 11 +- src/channel/channel.gateway.ts | 24 +- src/channel/notes.gateway.ts | 6 +- src/channel/whiteboard.gateway.ts | 4 +- src/modes/mode.entity.ts | 21 -- src/modes/mode.module.ts | 11 - src/modes/mode.service.ts | 28 -- src/points/sessionPoints.entity.ts | 11 + src/points/sessionPoints.module.ts | 6 +- src/points/sessionPoints.service.ts | 183 +++++++++++ src/roles/role.entity.ts | 21 -- src/roles/role.module.ts | 11 - src/roles/role.service.ts | 48 --- src/room/roomSession.controller.ts | 27 +- src/room/roomSession.dto.ts | 12 + src/room/roomSession.entity.ts | 3 + src/room/roomSession.module.ts | 6 +- src/room/roomSession.service.ts | 19 +- src/room/roomSessionUser.controller.ts | 25 +- src/room/roomSessionUser.dto.ts | 6 + src/room/roomSessionUser.entity.ts | 4 + src/room/roomSessionUser.module.ts | 4 +- src/room/roomSessionUser.service.ts | 22 +- 29 files changed, 452 insertions(+), 580 deletions(-) create mode 100644 database/migrations/Migration20250330142342.ts create mode 100644 database/migrations/Migration20250330143630.ts create mode 100644 database/migrations/Migration20250330143957.ts delete mode 100644 src/modes/mode.entity.ts delete mode 100644 src/modes/mode.module.ts delete mode 100644 src/modes/mode.service.ts create mode 100644 src/points/sessionPoints.service.ts delete mode 100644 src/roles/role.entity.ts delete mode 100644 src/roles/role.module.ts delete mode 100644 src/roles/role.service.ts diff --git a/database/migrations/.snapshot-collab_space.json b/database/migrations/.snapshot-collab_space.json index d87e4d5..440e28f 100644 --- a/database/migrations/.snapshot-collab_space.json +++ b/database/migrations/.snapshot-collab_space.json @@ -327,6 +327,15 @@ "nullable": true, "length": 0, "mappedType": "datetime" + }, + "mode_name": { + "name": "mode_name", + "type": "varchar(255)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "string" } }, "name": "roomSession", @@ -403,6 +412,15 @@ "primary": false, "nullable": false, "mappedType": "string" + }, + "role_name": { + "name": "role_name", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" } }, "name": "roomSessionUser", @@ -469,6 +487,7 @@ "autoincrement": false, "primary": false, "nullable": false, + "default": "0", "mappedType": "integer" }, "video_points": { @@ -478,6 +497,7 @@ "autoincrement": false, "primary": false, "nullable": false, + "default": "0", "mappedType": "integer" }, "browser_points": { @@ -487,6 +507,7 @@ "autoincrement": false, "primary": false, "nullable": false, + "default": "0", "mappedType": "integer" }, "whiteboard_points": { @@ -496,6 +517,7 @@ "autoincrement": false, "primary": false, "nullable": false, + "default": "0", "mappedType": "integer" }, "note_points": { @@ -505,6 +527,7 @@ "autoincrement": false, "primary": false, "nullable": false, + "default": "0", "mappedType": "integer" } }, @@ -519,72 +542,14 @@ "primary": false, "unique": false }, - { - "keyName": "PRIMARY", - "columnNames": [ - "id" - ], - "composite": false, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": { - "sessionPoints_room_session_user_id_foreign": { - "constraintName": "sessionPoints_room_session_user_id_foreign", - "columnNames": [ - "room_session_user_id" - ], - "localTableName": "sessionPoints", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "roomSessionUser", - "updateRule": "cascade" - } - } - }, - { - "columns": { - "id": { - "name": "id", - "type": "int", - "unsigned": true, - "autoincrement": true, - "primary": true, - "nullable": false, - "mappedType": "integer" - }, - "role_name": { - "name": "role_name", - "type": "varchar(255)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "string" - }, - "room_session_user_id": { - "name": "room_session_user_id", - "type": "int", - "unsigned": true, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "integer" - } - }, - "name": "role", - "indexes": [ { "columnNames": [ "room_session_user_id" ], "composite": false, - "keyName": "role_room_session_user_id_index", + "keyName": "sessionPoints_room_session_user_id_unique", "primary": false, - "unique": false + "unique": true }, { "keyName": "PRIMARY", @@ -598,12 +563,12 @@ ], "checks": [], "foreignKeys": { - "role_room_session_user_id_foreign": { - "constraintName": "role_room_session_user_id_foreign", + "sessionPoints_room_session_user_id_foreign": { + "constraintName": "sessionPoints_room_session_user_id_foreign", "columnNames": [ "room_session_user_id" ], - "localTableName": "role", + "localTableName": "sessionPoints", "referencedColumnNames": [ "id" ], @@ -612,73 +577,6 @@ } } }, - { - "columns": { - "id": { - "name": "id", - "type": "int", - "unsigned": true, - "autoincrement": true, - "primary": true, - "nullable": false, - "mappedType": "integer" - }, - "mode_name": { - "name": "mode_name", - "type": "varchar(255)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "string" - }, - "room_session_id": { - "name": "room_session_id", - "type": "int", - "unsigned": true, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "integer" - } - }, - "name": "mode", - "indexes": [ - { - "columnNames": [ - "room_session_id" - ], - "composite": false, - "keyName": "mode_room_session_id_unique", - "primary": false, - "unique": true - }, - { - "keyName": "PRIMARY", - "columnNames": [ - "id" - ], - "composite": false, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": { - "mode_room_session_id_foreign": { - "constraintName": "mode_room_session_id_foreign", - "columnNames": [ - "room_session_id" - ], - "localTableName": "mode", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "roomSession", - "updateRule": "cascade" - } - } - }, { "columns": { "id": { diff --git a/database/migrations/Migration20250330142342.ts b/database/migrations/Migration20250330142342.ts new file mode 100644 index 0000000..c7d5c74 --- /dev/null +++ b/database/migrations/Migration20250330142342.ts @@ -0,0 +1,37 @@ +import { Migration } from '@mikro-orm/migrations'; + +export class Migration20250330142342 extends Migration { + + async up(): Promise<void> { + this.addSql('drop table if exists `role`;'); + + this.addSql('drop table if exists `mode`;'); + + this.addSql('alter table `roomSession` add `mode_name` varchar(255) null;'); + + this.addSql('alter table `roomSessionUser` add `role_name` varchar(255) not null;'); + + this.addSql('alter table `sessionPoints` modify `audio_points` int not null default 0, modify `video_points` int not null default 0, modify `browser_points` int not null default 0, modify `whiteboard_points` int not null default 0, modify `note_points` int not null default 0;'); + this.addSql('alter table `sessionPoints` add unique `sessionPoints_room_session_user_id_unique`(`room_session_user_id`);'); + } + + async down(): Promise<void> { + this.addSql('create table `role` (`id` int unsigned not null auto_increment primary key, `role_name` varchar(255) not null, `room_session_user_id` int unsigned not null) default character set utf8mb4 engine = InnoDB;'); + this.addSql('alter table `role` add index `role_room_session_user_id_index`(`room_session_user_id`);'); + + this.addSql('create table `mode` (`id` int unsigned not null auto_increment primary key, `mode_name` varchar(255) not null, `room_session_id` int unsigned not null) default character set utf8mb4 engine = InnoDB;'); + this.addSql('alter table `mode` add unique `mode_room_session_id_unique`(`room_session_id`);'); + + this.addSql('alter table `role` add constraint `role_room_session_user_id_foreign` foreign key (`room_session_user_id`) references `roomSessionUser` (`id`) on update cascade;'); + + this.addSql('alter table `mode` add constraint `mode_room_session_id_foreign` foreign key (`room_session_id`) references `roomSession` (`id`) on update cascade;'); + + this.addSql('alter table `roomSession` drop `mode_name`;'); + + this.addSql('alter table `roomSessionUser` drop `role_name`;'); + + this.addSql('alter table `sessionPoints` modify `audio_points` int not null, modify `video_points` int not null, modify `browser_points` int not null, modify `whiteboard_points` int not null, modify `note_points` int not null;'); + this.addSql('alter table `sessionPoints` drop index `sessionPoints_room_session_user_id_unique`;'); + } + +} diff --git a/database/migrations/Migration20250330143630.ts b/database/migrations/Migration20250330143630.ts new file mode 100644 index 0000000..dde8f6f --- /dev/null +++ b/database/migrations/Migration20250330143630.ts @@ -0,0 +1,13 @@ +import { Migration } from '@mikro-orm/migrations'; + +export class Migration20250330143630 extends Migration { + + async up(): Promise<void> { + this.addSql('alter table `roomSessionUser` modify `role_name` varchar(255) null;'); + } + + async down(): Promise<void> { + this.addSql('alter table `roomSessionUser` modify `role_name` varchar(255) not null;'); + } + +} diff --git a/database/migrations/Migration20250330143957.ts b/database/migrations/Migration20250330143957.ts new file mode 100644 index 0000000..6ee9a2a --- /dev/null +++ b/database/migrations/Migration20250330143957.ts @@ -0,0 +1,13 @@ +import { Migration } from '@mikro-orm/migrations'; + +export class Migration20250330143957 extends Migration { + + async up(): Promise<void> { + this.addSql('alter table `roomSessionUser` modify `role_name` text;'); + } + + async down(): Promise<void> { + this.addSql('alter table `roomSessionUser` modify `role_name` varchar(255);'); + } + +} diff --git a/src/activities/activities.module.ts b/src/activities/activities.module.ts index 617c287..c5a2c7a 100644 --- a/src/activities/activities.module.ts +++ b/src/activities/activities.module.ts @@ -1,14 +1,19 @@ -import { Global, Module } from '@nestjs/common'; +import { Global, Module, forwardRef } from '@nestjs/common'; import { MikroOrmModule } from '@mikro-orm/nestjs'; import { ActivitiesService } from './activities.service'; import { Activity } from './activities.entity'; +import { SessionPointsService } from 'src/points/sessionPoints.service'; +import { SessionPointsModule } from 'src/points/sessionPoints.module'; +import { SessionPoints } from 'src/points/sessionPoints.entity'; + /** * A global module that encapsulates features related to tracking. */ @Global() @Module({ - imports: [MikroOrmModule.forFeature([Activity])], + imports: [MikroOrmModule.forFeature([Activity, SessionPoints]), + forwardRef(() => SessionPointsModule)], providers: [ActivitiesService], exports: [ActivitiesService], }) diff --git a/src/activities/activities.service.ts b/src/activities/activities.service.ts index f1a0db0..93f02dc 100644 --- a/src/activities/activities.service.ts +++ b/src/activities/activities.service.ts @@ -2,14 +2,26 @@ import { Injectable } from '@nestjs/common'; import { EntityManager } from '@mikro-orm/core'; import { InjectRepository } from '@mikro-orm/nestjs'; import { EntityRepository } from '@mikro-orm/mysql'; -import { Room } from '../room/room.entity'; import { Activity } from './activities.entity'; -import { Note } from '../note/note.entity'; -import { tr } from '@faker-js/faker'; -import { SessionPoints } from '../points/sessionPoints.entity'; import { RoomSession } from '../room/roomSession.entity'; import { RoomSessionUser } from '../room/roomSessionUser.entity'; -import { session } from 'passport'; +import { SessionPointsService } from '../points/sessionPoints.service'; + +export enum ActivityType { + NOTE_CREATE = 'note.create', + NOTE_UPDATE = 'note.update', + WHITEBOARD = 'whiteboard', + VIDEO_ON = 'video.on', + VIDEO_OFF = 'video.off', + AUDIO_ON = 'audio.on', + AUDIO_OFF = 'audio.off', + AUDIO_TALKING = 'audio.talking', + AUDIO_NOT_TALKING = 'audio.nottalking', + BROWSER_OPEN = 'browser.open', + BROWSER_CLOSE = 'browser.close', + BROWSER_MOUSE = 'browser.mouse', + BROWSER_KEY = 'browser.key', +} /** * A service responsible for managing to track the activities. @@ -28,270 +40,17 @@ export class ActivitiesService { private readonly em: EntityManager, @InjectRepository(Activity) private readonly repository: EntityRepository<Activity>, + private readonly sessionPointsService: SessionPointsService, ) { } - /** - * Logs the creation of a note. - */ - public async addNoteCreateActivity(userId: string, roomSession: RoomSession, note: Note) { - if (await this.em.findOne(RoomSessionUser, { userId: userId, roomSession: roomSession })) { - const trackingNote = new Activity(userId, roomSession, "note.create"); - trackingNote.note = note; - await this.em.persistAndFlush(trackingNote); - this.calculatePoints(roomSession); - return trackingNote; - } - } - - /** - * Logs updates made to a note, capturing the characters added. - */ - public async addNoteUpdateActivity(userId: string, roomSession: RoomSession, note: Note, charactersAdded: number) { - if (await this.em.findOne(RoomSessionUser, { userId: userId, roomSession: roomSession })) { - const trackingNote = new Activity(userId, roomSession, "note.update"); - trackingNote.note = note; - trackingNote.numericValue = charactersAdded; - await this.em.persistAndFlush(trackingNote); - this.calculatePoints(roomSession); - return trackingNote; - } - } - /** - * Logs whiteboard usage in a room. - */ - public async addWhiteboardActivity(userId: string, roomSession: RoomSession) { + public async addNewActivity(userId: string, roomSession: RoomSession, activity: string, numericValue?: number) { if (await this.em.findOne(RoomSessionUser, { userId: userId, roomSession: roomSession })) { - const trackingWhiteboard = new Activity(userId, roomSession, "whiteboard"); - await this.em.persistAndFlush(trackingWhiteboard); - this.calculatePoints(roomSession); - return trackingWhiteboard; + const trackingActivity = new Activity(userId, roomSession, activity); + await this.em.persistAndFlush(trackingActivity); + this.sessionPointsService.calculatePoints(roomSession); + return trackingActivity; } } - - /** - * Logs video on/off activities. - */ - public async addVideoActivity(userId: string, roomSession: RoomSession, isActive: boolean) { - if (await this.em.findOne(RoomSessionUser, { userId: userId, roomSession: roomSession })) { - const activity = isActive ? "video.on" : "video.off"; - const trackingVideo = new Activity(userId, roomSession, activity); - await this.em.persistAndFlush(trackingVideo); - this.calculatePoints(roomSession); - return trackingVideo; - } - } - - /** - * Logs audio on/off activities. - */ - public async addAudioActivity(userId: string, roomSession: RoomSession, isActive: boolean) { - if (await this.em.findOne(RoomSessionUser, { userId: userId, roomSession: roomSession })) { - const activity = isActive ? "audio.on" : "audio.off"; - const trackingAudio = new Activity(userId, roomSession, activity); - await this.em.persistAndFlush(trackingAudio); - this.calculatePoints(roomSession); - return trackingAudio; - } - } - - /** - * Logs whether a user is talking or not. - */ - public async addIsTalkingActivity(userId: string, roomSession: RoomSession, isTalking: boolean) { - if (await this.em.findOne(RoomSessionUser, { userId: userId, roomSession: roomSession })) { - const activity = isTalking ? "audio.talking" : "audio.nottalking"; - const trackingAudioVolume = new Activity(userId, roomSession, activity); - await this.em.persistAndFlush(trackingAudioVolume); - this.calculatePoints(roomSession); - return trackingAudioVolume; - } - } - - /** - * Logs the moment a browser view is opened. - */ - public async addBrowserOpenActivity(userId: string, roomSession: RoomSession) { - if (await this.em.findOne(RoomSessionUser, { userId: userId, roomSession: roomSession })) { - const trackingBrowser = new Activity(userId, roomSession, "browser.open"); - await this.em.persistAndFlush(trackingBrowser); - this.calculatePoints(roomSession); - return trackingBrowser; - } - } - - /** - * Logs a browser close event. - */ - public async addBrowserCloseActivity(userId: string, roomSession: RoomSession) { - if (await this.em.findOne(RoomSessionUser, { userId: userId, roomSession: roomSession })) { - const trackingBrowser = new Activity(userId, roomSession, "browser.close"); - await this.em.persistAndFlush(trackingBrowser); - this.calculatePoints(roomSession); - return trackingBrowser; - } - } - - /** - * Logs a browser mouse action event. - */ - public async addBrowserMouseActivity(userId: string, roomSession: RoomSession) { - if (await this.em.findOne(RoomSessionUser, { userId: userId, roomSession: roomSession })) { - const trackingBrowser = new Activity(userId, roomSession, "browser.mouse"); - await this.em.persistAndFlush(trackingBrowser); - this.calculatePoints(roomSession); - return trackingBrowser; - } - } - - /** - * Logs a browser keyboard action event. - */ - public async addBrowserKeyActivity(userId: string, roomSession: RoomSession) { - if (await this.em.findOne(RoomSessionUser, { userId: userId, roomSession: roomSession })) { - const trackingBrowser = new Activity(userId, roomSession, "browser.key"); - await this.em.persistAndFlush(trackingBrowser); - this.calculatePoints(roomSession); - return trackingBrowser; - } - } - -/** - /** - * Calculates and returns points for user activities in a time range. - */ - public async calculatePoints( - roomSession: RoomSession, - ) { - const activities = await roomSession.activities.loadItems(); - const users = await roomSession.users.loadItems(); - - const activitiesByUsers = activities.reduce((actsByUsers, activity) => { - const user = users.find((user) => user.userId === activity.userId); - let acts = actsByUsers.get(user) ?? []; - acts = [...acts, activity]; - - actsByUsers.set(user, acts); - - return actsByUsers; - }, new Map<RoomSessionUser, Activity[]>()); - - const pointsArray: SessionPoints[] = []; - for (const [user, userActivities] of activitiesByUsers) { - let points = await this.em.findOne(SessionPoints, { roomSessionUser: user }); - if (!points) { - points = new SessionPoints(user); - } - points.audioPoints = await this.calculateAudioPoints(userActivities), - points.videoPoints = await this.calculateVideoPoints(userActivities), - points.browserPoints = await this.calculateBrowserPoints(userActivities), - points.whiteboardPoints = await this.calculateWhiteboardPoints(userActivities), - points.notePoints = await this.calculateNotePoints(userActivities), - - await this.em.persistAndFlush(points); - pointsArray.push(points); - } - - return pointsArray; - - } - - /** - * Calculates how many audio points (1 point per 5 seconds of talking) a user gets. - */ - private async calculateAudioPoints(activities: Activity[]) { - const audioActivities = activities - .filter(a => a.activity.startsWith('audio.')) - .sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); - - let totalTalkingTime = 0; - let talkingStart: Date | null = null; - - for (const act of audioActivities) { - if (act.activity === 'audio.talking' && !talkingStart) { - talkingStart = act.timestamp; - } else if ((act.activity === 'audio.nottalking' || act.activity === 'audio.off') && talkingStart) { - totalTalkingTime += act.timestamp.getTime() - talkingStart.getTime(); - talkingStart = null; - } - } - - const audioPoints = Math.floor(totalTalkingTime / 5000); - return audioPoints; - } - - /** - * Calculates how many video points (1 point per 5 minutes of video on) a user gets. - */ - private async calculateVideoPoints(activities: Activity[]) { - const videoActivities = activities - .filter(a => a.activity.startsWith('video.')) - .sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); - - let totalOnTime = 0; - let videoStart: Date | null = null; - - for (const act of videoActivities) { - if (act.activity === 'video.on' && !videoStart) { - videoStart = act.timestamp; - } else if (act.activity === 'video.off' && videoStart) { - totalOnTime += act.timestamp.getTime() - videoStart.getTime(); - videoStart = null; - } - } - - const videoPoints = Math.floor(totalOnTime / 300000); - return videoPoints; - } - - /** - * Calculates how many browser points (1 point per 50 key events, 1 point per 10 mouse events). - */ - private async calculateBrowserPoints(activities: Activity[]) { - const browserActivities = activities - .filter(a => a.activity.startsWith('browser.')) - .sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); - - const keyCount = browserActivities.filter(a => a.activity === 'browser.key').length; - const mouseCount = browserActivities.filter(a => a.activity === 'browser.mouse').length; - - const keyPoints = Math.floor(keyCount / 50); - const mousePoints = Math.floor(mouseCount / 10); - - return keyPoints + mousePoints; - } - - /** - * Calculates how many whiteboard points (1 point per 5 whiteboard events) a user gets. - */ - private async calculateWhiteboardPoints(activities: Activity[]) { - const whiteboardActivities = activities - .filter(a => a.activity === 'whiteboard') - .sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); - - const whiteboardPoints = Math.floor(whiteboardActivities.length / 5); - - return whiteboardPoints; - } - /** - * Calculates how many note points (1 point for creating a note, 1 point per 50 characters added, and -1 point per 50 characters removed from the note content). - */ - private async calculateNotePoints(activities: Activity[]) { - const noteActivities = activities - .filter(a => a.activity.startsWith('note.')) - .sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); - - let notePoints = 0; - for (const act of noteActivities) { - if (act.activity === 'note.create') { - notePoints++; - } else if (act.activity === 'note.update' && act.numericValue) { - notePoints += Math.floor(act.numericValue / 50); - } else if (act.activity === 'note.update' && act.numericValue < 0) { - notePoints += Math.ceil(act.numericValue / 50); - } - } - return notePoints; - } - } + diff --git a/src/channel/browser.gateway.ts b/src/channel/browser.gateway.ts index c3d9db6..a2a088e 100644 --- a/src/channel/browser.gateway.ts +++ b/src/channel/browser.gateway.ts @@ -11,7 +11,7 @@ import { ChannelService } from './channel.service'; import * as process from 'process'; import { BrowserService } from '../browser/browser.service'; import * as dotenv from 'dotenv'; -import { ActivitiesService } from '../activities/activities.service'; +import { ActivitiesService, ActivityType } from '../activities/activities.service'; dotenv.config(); @@ -64,7 +64,7 @@ export class BrowserGateway { ); this.server.to(channel.id).emit('open-website', peerId); - await this.activitiesService.addBrowserOpenActivity(client.id, channel.session); + await this.activitiesService.addNewActivity(client.id, channel.session, ActivityType.BROWSER_OPEN); return true; } @@ -83,7 +83,7 @@ export class BrowserGateway { await this.browserService.closeBrowserContext(channel.id); this.server.to(channel.id).emit('close-browser'); - await this.activitiesService.addBrowserCloseActivity(client.id, channel.session); + await this.activitiesService.addNewActivity(client.id, channel.session, ActivityType.BROWSER_CLOSE); return true; } @@ -121,7 +121,7 @@ export class BrowserGateway { await this.browserService.mouseDown(channel.id); - await this.activitiesService.addBrowserMouseActivity(client.id, channel.session); + await this.activitiesService.addNewActivity(client.id, channel.session, ActivityType.BROWSER_MOUSE); return true; } @@ -158,7 +158,8 @@ export class BrowserGateway { const channel = await this.channels.fromClientOrFail(client); await this.browserService.keyDown(channel.id, payload.key); - await this.activitiesService.addBrowserKeyActivity(client.id, channel.session); + + await this.activitiesService.addNewActivity(client.id, channel.session, ActivityType.BROWSER_KEY); return true; } diff --git a/src/channel/channel.gateway.ts b/src/channel/channel.gateway.ts index 486fe19..d5dd787 100644 --- a/src/channel/channel.gateway.ts +++ b/src/channel/channel.gateway.ts @@ -14,7 +14,7 @@ import * as process from 'process'; import * as dotenv from 'dotenv'; import { BrowserService } from '../browser/browser.service'; import { UnauthorizedException } from '@nestjs/common'; -import { ActivitiesService } from '../activities/activities.service'; +import { ActivitiesService, ActivityType } from '../activities/activities.service'; import { RoomSession } from '../room/roomSession.entity'; import { RoomSessionUserService } from '../room/roomSessionUser.service'; import { RoomSessionUser } from '../room/roomSessionUser.entity'; @@ -270,9 +270,17 @@ export class ChannelGateway implements OnGatewayConnection { video: payload.video, audio: payload.audio, }); - - await this.activitiesService.addVideoActivity(client.id, channel.session, payload.video); - await this.activitiesService.addAudioActivity(client.id, channel.session, payload.audio); + if (payload.video) { + await this.activitiesService.addNewActivity(client.id, channel.session, ActivityType.VIDEO_ON); + } else { + await this.activitiesService.addNewActivity(client.id, channel.session, ActivityType.VIDEO_OFF); + } + + if (payload.audio) { + await this.activitiesService.addNewActivity(client.id, channel.session, ActivityType.AUDIO_ON); + } else { + await this.activitiesService.addNewActivity(client.id, channel.session, ActivityType.AUDIO_OFF); + } return true; } @@ -385,7 +393,9 @@ export class ChannelGateway implements OnGatewayConnection { @MessageBody() payload: { volume: number } ) { const channel = await this.channels.fromClientOrFail(client); - await this.activitiesService.addIsTalkingActivity(client.id, channel.session, true); + + await this.activitiesService.addNewActivity(client.id, channel.session, ActivityType.AUDIO_TALKING); + return true; } @@ -396,7 +406,9 @@ export class ChannelGateway implements OnGatewayConnection { @MessageBody() payload: { volume: number } ) { const channel = await this.channels.fromClientOrFail(client); - await this.activitiesService.addIsTalkingActivity(client.id, channel.session, false); + + await this.activitiesService.addNewActivity(client.id, channel.session, ActivityType.AUDIO_NOT_TALKING); + return true; } diff --git a/src/channel/notes.gateway.ts b/src/channel/notes.gateway.ts index b60a24a..9363b68 100644 --- a/src/channel/notes.gateway.ts +++ b/src/channel/notes.gateway.ts @@ -10,7 +10,7 @@ import { Server, Socket } from 'socket.io'; import { ChannelService } from './channel.service'; import * as dotenv from 'dotenv'; import { NoteService } from '../note/note.service'; -import { ActivitiesService } from '../activities/activities.service'; +import { ActivitiesService, ActivityType } from '../activities/activities.service'; dotenv.config(); @@ -58,7 +58,7 @@ export class NotesGateway { client.broadcast.to(channel.id).emit('note-added', note); - await this.activitiesService.addNoteCreateActivity(client.id, channel.session, note); + await this.activitiesService.addNewActivity(client.id, channel.session, ActivityType.NOTE_CREATE); return note; } @@ -83,7 +83,7 @@ export class NotesGateway { content: payload.content, }); - await this.activitiesService.addNoteUpdateActivity(client.id, channel.session, note, charactersAdded); + await this.activitiesService.addNewActivity(client.id, channel.session, ActivityType.NOTE_UPDATE, charactersAdded); return true; } diff --git a/src/channel/whiteboard.gateway.ts b/src/channel/whiteboard.gateway.ts index 772cdd4..a380699 100644 --- a/src/channel/whiteboard.gateway.ts +++ b/src/channel/whiteboard.gateway.ts @@ -8,7 +8,7 @@ import { import { Server, Socket } from 'socket.io'; import * as dotenv from 'dotenv'; import { ChannelService } from './channel.service'; -import { ActivitiesService } from '../activities/activities.service'; +import { ActivitiesService, ActivityType } from '../activities/activities.service'; import { MikroORM, UseRequestContext } from '@mikro-orm/core'; dotenv.config(); @@ -56,6 +56,6 @@ export class WhiteboardGateway { // Broadcast the whiteboard change to all clients in the channel client.broadcast.to(channel.id).emit('whiteboard-change', payload); - await this.activitiesService.addWhiteboardActivity(client.id, channel.session); + await this.activitiesService.addNewActivity(client.id, channel.session, ActivityType.WHITEBOARD); } } diff --git a/src/modes/mode.entity.ts b/src/modes/mode.entity.ts deleted file mode 100644 index a577dd3..0000000 --- a/src/modes/mode.entity.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Entity, OneToOne, PrimaryKey, Property } from '@mikro-orm/core'; -import { RoomSession } from '../room/roomSession.entity'; - - -@Entity({ tableName: 'mode' }) -export class Mode { - @PrimaryKey() - id!: number; - - @Property() - modeName!: string; - - @OneToOne() - roomSession!: RoomSession; - - constructor(modeName: string, roomSession: RoomSession) { - this.roomSession = roomSession; - this.modeName = modeName; - - } -} diff --git a/src/modes/mode.module.ts b/src/modes/mode.module.ts deleted file mode 100644 index a796fbb..0000000 --- a/src/modes/mode.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Module } from '@nestjs/common'; -import { MikroOrmModule } from '@mikro-orm/nestjs'; -import { Mode } from './mode.entity'; -import { ModeService } from './mode.service'; - -@Module({ - imports: [MikroOrmModule.forFeature([Mode])], - providers: [ModeService], - exports: [ModeService], -}) -export class ModeModule {} diff --git a/src/modes/mode.service.ts b/src/modes/mode.service.ts deleted file mode 100644 index f65836f..0000000 --- a/src/modes/mode.service.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@mikro-orm/nestjs'; -import { EntityRepository } from '@mikro-orm/mysql'; -import { Mode } from './mode.entity'; -import { EntityManager } from '@mikro-orm/mysql'; -import { RoomSession } from '../room/roomSession.entity'; - -export enum ModeType { - PAIR_PROGRAMMING = 'pairProgramming', - ROLE_BASED_PROGRAMMING = 'roleBasedProgramming', - DEFAULT = 'default', -} - -@Injectable() -export class ModeService { - constructor( - private readonly em: EntityManager, - @InjectRepository(Mode) - private readonly modeRepository: EntityRepository<Mode>) {} - - async createMode(roomSession: RoomSession, modeName: ModeType): Promise<Mode> { - const mode = new Mode(modeName, roomSession); - await this.em.persistAndFlush(mode); - return mode; - } - -} - diff --git a/src/points/sessionPoints.entity.ts b/src/points/sessionPoints.entity.ts index 6a3f334..a1c7762 100644 --- a/src/points/sessionPoints.entity.ts +++ b/src/points/sessionPoints.entity.ts @@ -32,6 +32,17 @@ export class SessionPoints { @Property() notePoints: number; + @Property({ persist: false }) + get allPoints(): { [key: string]: number } { + return { + audioPoints: this.audioPoints, + videoPoints: this.videoPoints, + browserPoints: this.browserPoints, + whiteboardPoints: this.whiteboardPoints, + notePoints: this.notePoints, + }; + } + constructor(roomSessionUser: RoomSessionUser) { this.roomSessionUser = roomSessionUser; this.audioPoints = 0; diff --git a/src/points/sessionPoints.module.ts b/src/points/sessionPoints.module.ts index c7d9c3b..6859f2c 100644 --- a/src/points/sessionPoints.module.ts +++ b/src/points/sessionPoints.module.ts @@ -3,8 +3,12 @@ import { MikroOrmModule } from '@mikro-orm/nestjs'; import { SessionPoints } from './sessionPoints.entity'; import { RoomSessionUser } from '../room/roomSessionUser.entity'; import { RoomSessionUserModule } from '../room/roomSessionUser.module'; +import { SessionPointsService } from './sessionPoints.service'; @Module({ - imports: [MikroOrmModule.forFeature([SessionPoints, RoomSessionUser]), forwardRef(() => RoomSessionUserModule)], + imports: [MikroOrmModule.forFeature([SessionPoints, RoomSessionUser]), + forwardRef(() => RoomSessionUserModule)], + providers: [SessionPointsService], + exports: [SessionPointsService], }) export class SessionPointsModule {} \ No newline at end of file diff --git a/src/points/sessionPoints.service.ts b/src/points/sessionPoints.service.ts new file mode 100644 index 0000000..1d4a7ac --- /dev/null +++ b/src/points/sessionPoints.service.ts @@ -0,0 +1,183 @@ +import { Injectable } from '@nestjs/common'; +import { EntityManager } from '@mikro-orm/core'; +import { InjectRepository } from '@mikro-orm/nestjs'; +import { EntityRepository } from '@mikro-orm/mysql'; +import { Activity } from '../activities/activities.entity'; +import { SessionPoints } from '../points/sessionPoints.entity'; +import { RoomSession } from '../room/roomSession.entity'; +import { RoomSessionUser } from '../room/roomSessionUser.entity'; +import { ActivitiesService } from '../activities/activities.service'; + +export enum ActivityType { + NOTE_CREATE = 'note.create', + NOTE_UPDATE = 'note.update', + WHITEBOARD = 'whiteboard', + VIDEO_ON = 'video.on', + VIDEO_OFF = 'video.off', + AUDIO_ON = 'audio.on', + AUDIO_OFF = 'audio.off', + AUDIO_TALKING = 'audio.talking', + AUDIO_NOT_TALKING = 'audio.nottalking', + BROWSER_OPEN = 'browser.open', + BROWSER_CLOSE = 'browser.close', + BROWSER_MOUSE = 'browser.mouse', + BROWSER_KEY = 'browser.key', +} + +/** + * A service responsible for managing to track the activities. + */ +@Injectable() +export class SessionPointsService { + /** + * Initializes the ActivitiesService. + * @param em - The EntityManager instance provided by MikroORM. + * @param repository - The repository for the Activity entity. + */ + /** + * Initializes the service with a manager and repository. + */ + constructor( + private readonly em: EntityManager, + ) { } + + +/** + /** + * Calculates and returns points for user activities in a time range. + */ + public async calculatePoints( + roomSession: RoomSession, + ) { + const activities = await roomSession.activities.loadItems(); + const users = await roomSession.users.loadItems(); + + const activitiesByUsers = activities.reduce((actsByUsers, activity) => { + const user = users.find((user) => user.userId === activity.userId); + let acts = actsByUsers.get(user) ?? []; + acts = [...acts, activity]; + + actsByUsers.set(user, acts); + + return actsByUsers; + }, new Map<RoomSessionUser, Activity[]>()); + + const pointsArray: SessionPoints[] = []; + for (const [user, userActivities] of activitiesByUsers) { + let points = await this.em.findOne(SessionPoints, { roomSessionUser: user }); + if (!points) { + points = new SessionPoints(user); + } + points.audioPoints = await this.calculateAudioPoints(userActivities), + points.videoPoints = await this.calculateVideoPoints(userActivities), + points.browserPoints = await this.calculateBrowserPoints(userActivities), + points.whiteboardPoints = await this.calculateWhiteboardPoints(userActivities), + points.notePoints = await this.calculateNotePoints(userActivities), + + await this.em.persistAndFlush(points); + pointsArray.push(points); + } + + return pointsArray; + + } + + /** + * Calculates how many audio points (1 point per 5 seconds of talking) a user gets. + */ + private async calculateAudioPoints(activities: Activity[]) { + const audioActivities = activities + .filter(a => a.activity.startsWith('audio.')) + .sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); + + let totalTalkingTime = 0; + let talkingStart: Date | null = null; + + for (const act of audioActivities) { + if (act.activity === 'audio.talking' && !talkingStart) { + talkingStart = act.timestamp; + } else if ((act.activity === 'audio.nottalking' || act.activity === 'audio.off') && talkingStart) { + totalTalkingTime += act.timestamp.getTime() - talkingStart.getTime(); + talkingStart = null; + } + } + + const audioPoints = Math.floor(totalTalkingTime / 5000); + return audioPoints; + } + + /** + * Calculates how many video points (1 point per 5 minutes of video on) a user gets. + */ + private async calculateVideoPoints(activities: Activity[]) { + const videoActivities = activities + .filter(a => a.activity.startsWith('video.')) + .sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); + + let totalOnTime = 0; + let videoStart: Date | null = null; + + for (const act of videoActivities) { + if (act.activity === 'video.on' && !videoStart) { + videoStart = act.timestamp; + } else if (act.activity === 'video.off' && videoStart) { + totalOnTime += act.timestamp.getTime() - videoStart.getTime(); + videoStart = null; + } + } + + const videoPoints = Math.floor(totalOnTime / 300000); + return videoPoints; + } + + /** + * Calculates how many browser points (1 point per 50 key events, 1 point per 10 mouse events). + */ + private async calculateBrowserPoints(activities: Activity[]) { + const browserActivities = activities + .filter(a => a.activity.startsWith('browser.')) + .sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); + + const keyCount = browserActivities.filter(a => a.activity === 'browser.key').length; + const mouseCount = browserActivities.filter(a => a.activity === 'browser.mouse').length; + + const keyPoints = Math.floor(keyCount / 50); + const mousePoints = Math.floor(mouseCount / 10); + + return keyPoints + mousePoints; + } + + /** + * Calculates how many whiteboard points (1 point per 5 whiteboard events) a user gets. + */ + private async calculateWhiteboardPoints(activities: Activity[]) { + const whiteboardActivities = activities + .filter(a => a.activity === 'whiteboard') + .sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); + + const whiteboardPoints = Math.floor(whiteboardActivities.length / 5); + + return whiteboardPoints; + } + /** + * Calculates how many note points (1 point for creating a note, 1 point per 50 characters added, and -1 point per 50 characters removed from the note content). + */ + private async calculateNotePoints(activities: Activity[]) { + const noteActivities = activities + .filter(a => a.activity.startsWith('note.')) + .sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); + + let notePoints = 0; + for (const act of noteActivities) { + if (act.activity === 'note.create') { + notePoints++; + } else if (act.activity === 'note.update' && act.numericValue) { + notePoints += Math.floor(act.numericValue / 50); + } else if (act.activity === 'note.update' && act.numericValue < 0) { + notePoints += Math.ceil(act.numericValue / 50); + } + } + return notePoints; + } + +} diff --git a/src/roles/role.entity.ts b/src/roles/role.entity.ts deleted file mode 100644 index 9df865e..0000000 --- a/src/roles/role.entity.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Entity, ManyToOne, PrimaryKey, Property } from '@mikro-orm/core'; -import { RoomSessionUser } from '../room/roomSessionUser.entity'; - - -@Entity({ tableName: 'role' }) -export class Role { - @PrimaryKey() - id!: number; - - @Property() - roleName!: string; - - @ManyToOne() - roomSessionUser!: RoomSessionUser; - - constructor(roleName: string, roomSessionUser: RoomSessionUser) { - this.roomSessionUser = roomSessionUser; - this.roleName = roleName; - - } -} diff --git a/src/roles/role.module.ts b/src/roles/role.module.ts deleted file mode 100644 index f2eb8c0..0000000 --- a/src/roles/role.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Module } from '@nestjs/common'; -import { MikroOrmModule } from '@mikro-orm/nestjs'; -import { Role } from './role.entity'; -import { RoleService } from './role.service'; - -@Module({ - imports: [MikroOrmModule.forFeature([Role])], - providers: [RoleService], - exports: [RoleService], -}) -export class RoleModule {} \ No newline at end of file diff --git a/src/roles/role.service.ts b/src/roles/role.service.ts deleted file mode 100644 index bfec83f..0000000 --- a/src/roles/role.service.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@mikro-orm/nestjs'; -import { EntityRepository } from '@mikro-orm/mysql'; -import { Role } from './role.entity'; -import { RoomSessionUser } from '../room/roomSessionUser.entity'; -import { EntityManager } from '@mikro-orm/mysql'; - -export enum RoleType { - COORDINATOR = 'COORDINATOR', - DOCUMENTER = 'DOCUMENTER', - CODER_DEBUGGER = 'CODER_DEBUGGER', - VISUALIZER = 'VISUALIZER', - ACTIVE_PARTICIPANT = 'ACTIVE_PARTICIPANT', - OBSERVER = 'OBSERVER', - LURKER = 'LURKER', - DISRUPTOR = 'DISRUPTOR', -} - -@Injectable() -export class RoleService { - constructor( - private readonly em: EntityManager, - @InjectRepository(Role) - private readonly roleRepository: EntityRepository<Role>) {} - - async createRole(roleName: RoleType, roomSessionUser: RoomSessionUser): Promise<Role> { - const role = new Role(roleName, roomSessionUser); - await this.em.persistAndFlush(role); - return role; - } - - async getRoleById(id: number): Promise<Role | null> { - return this.roleRepository.findOne(id); - } - - async getRolesByRoomSessionUser(roomSessionUser: RoomSessionUser): Promise<Role[]> { - return this.roleRepository.find({ roomSessionUser }); - } - - async deleteRole(id: number): Promise<boolean> { - const role = await this.roleRepository.findOne(id); - if (!role) { - return false; - } - await this.em.removeAndFlush(role); - return true; - } -} \ No newline at end of file diff --git a/src/room/roomSession.controller.ts b/src/room/roomSession.controller.ts index 7c718a4..06838df 100644 --- a/src/room/roomSession.controller.ts +++ b/src/room/roomSession.controller.ts @@ -1,25 +1,40 @@ import { Controller, Get, Param, UseGuards } from '@nestjs/common'; import { RoomSessionService } from './roomSession.service'; import { AuthGuard } from '../auth/auth.guard'; -import { RoomSessionPointsRequestDto } from './roomSession.dto'; +import { RoomSessionPointsRequestDto, RoomSessionResponseDto, RoomSessionModeRequestDto } from './roomSession.dto'; +import { RoomSession } from './roomSession.entity'; +import { ModeType } from './roomSession.service'; -@Controller('sessionPoints') +@Controller('roomSession') export class RoomSessionPointsController { constructor( private readonly roomSession: RoomSessionService) {} - @UseGuards(AuthGuard) - @Get('/session/:sessionId') + /** * Retrieves the total points for a specific session based on its session ID. * * @param sessionId - The unique identifier of the session whose points are being retrieved. * @returns A promise that resolves to an object containing the total points and the session ID. */ + @UseGuards(AuthGuard) + @Get('/:sessionId/points') async getPointsBySession(@Param('sessionId') sessionId: number): Promise<RoomSessionPointsRequestDto> { const points = await this.roomSession.getAllPointsBySession(sessionId); return points; } - - + + + /** + * Retrieves the mode of a specific session based on its session ID. + * + * @param sessionId - The unique identifier of the session whose mode is being retrieved. + * @returns A promise that resolves to an object containing the mode and the session ID. + */ + @UseGuards(AuthGuard) + @Get('/:sessionId/mode') + async getModeBySession(@Param('sessionId') sessionId: number): Promise<RoomSessionModeRequestDto> { + const modeType = await this.roomSession.assignMode(sessionId); + return { modeName: modeType, sessionId }; + } } \ No newline at end of file diff --git a/src/room/roomSession.dto.ts b/src/room/roomSession.dto.ts index 5820c3e..59d5c5c 100644 --- a/src/room/roomSession.dto.ts +++ b/src/room/roomSession.dto.ts @@ -9,10 +9,22 @@ export class RoomSessionResponseDto { }[]; roomId: number; roomName: string; + activities: { + activity: string; + timestamp: Date; + userId: string; + }[]; + points: RoomSessionPointsRequestDto; + sessionId: number; + mode: RoomSessionModeRequestDto; } export class RoomSessionPointsRequestDto { allPoints: { audioPoints: number, videoPoints: number, browserPoints: number, whiteboardPoints: number, notePoints: number }; totalPoints: number; sessionId: number; +} +export class RoomSessionModeRequestDto { + modeName: string; + sessionId: number; } \ No newline at end of file diff --git a/src/room/roomSession.entity.ts b/src/room/roomSession.entity.ts index 16e7505..7d77d2c 100644 --- a/src/room/roomSession.entity.ts +++ b/src/room/roomSession.entity.ts @@ -33,6 +33,9 @@ export class RoomSession { @OneToMany(() => Activity, (activity) => activity.roomSession) activities = new Collection<Activity>(this); + @Property() + modeName?: string; + constructor(room: Room) { this.room = room; diff --git a/src/room/roomSession.module.ts b/src/room/roomSession.module.ts index dc7f3cd..34e5469 100644 --- a/src/room/roomSession.module.ts +++ b/src/room/roomSession.module.ts @@ -5,7 +5,6 @@ import { RoomSession } from './roomSession.entity'; import { RoomSessionPointsController } from './roomSession.controller'; import { RoomSessionUserModule } from './roomSessionUser.module'; import { RoomSessionUser } from './roomSessionUser.entity'; -import { ModeModule } from '../modes/mode.module'; import { SessionPointsModule } from '../points/sessionPoints.module'; import { ActivitiesModule } from '../activities/activities.module'; import { SessionPoints } from '../points/sessionPoints.entity'; @@ -14,9 +13,8 @@ import { SessionPoints } from '../points/sessionPoints.entity'; imports: [ MikroOrmModule.forFeature([RoomSession, RoomSessionUser, SessionPoints]), forwardRef(() => RoomSessionUserModule), - SessionPointsModule, - ModeModule, - ActivitiesModule + forwardRef(() => SessionPointsModule), + forwardRef(() => ActivitiesModule), ], providers: [RoomSessionService], controllers: [RoomSessionPointsController], diff --git a/src/room/roomSession.service.ts b/src/room/roomSession.service.ts index 7b2617a..e4f1668 100644 --- a/src/room/roomSession.service.ts +++ b/src/room/roomSession.service.ts @@ -9,7 +9,12 @@ import { SessionPoints } from '../points/sessionPoints.entity'; import { RoomSessionUserService } from './roomSessionUser.service'; import { RoomSessionPointsRequestDto } from './roomSession.dto'; import { ActivitiesService } from 'src/activities/activities.service'; -import { ModeType, ModeService } from '../modes/mode.service'; + +export enum ModeType { + PAIR_PROGRAMMING = 'pairProgramming', + ROLE_BASED_PROGRAMMING = 'roleBasedProgramming', + DEFAULT = 'default', +} /** * A service responsible for managing room sessions. @@ -30,7 +35,6 @@ export class RoomSessionService { @Inject(forwardRef(() => RoomSessionUserService)) private readonly roomSessionUserService: RoomSessionUserService, private readonly activitiesService: ActivitiesService, - private readonly modeService: ModeService, ) {} /** @@ -106,7 +110,14 @@ export class RoomSessionService { return { allPoints, totalPoints, sessionId }; } - public async assignMode(roomSession: RoomSession): Promise<ModeType> { + async createMode(roomSession: RoomSession, modeType: ModeType): Promise<void> { + const modeName = modeType.toString(); + roomSession.modeName = modeName; + await this.em.persistAndFlush(roomSession); + } + + public async assignMode(sessionId: number): Promise<ModeType> { + const roomSession = await this.repository.findOneOrFail({ id: sessionId }); let modeType = ModeType.DEFAULT; if (this.shouldAssignPairProgramming(roomSession)) { modeType = ModeType.PAIR_PROGRAMMING; @@ -114,7 +125,7 @@ export class RoomSessionService { else if (this.shouldAssignRoleBasedProgramming(roomSession)) { modeType = ModeType.ROLE_BASED_PROGRAMMING; } - this.modeService.createMode(roomSession, modeType); + this.createMode(roomSession, modeType); return modeType; } diff --git a/src/room/roomSessionUser.controller.ts b/src/room/roomSessionUser.controller.ts index df5bb81..2ae62aa 100644 --- a/src/room/roomSessionUser.controller.ts +++ b/src/room/roomSessionUser.controller.ts @@ -1,12 +1,14 @@ import { Controller, Get, Param, UseGuards } from '@nestjs/common'; import { RoomSessionUserService } from './roomSessionUser.service'; import { AuthGuard } from '../auth/auth.guard'; -import { RoomSessionUserPointsResponseDto } from './roomSessionUser.dto'; +import { RoomSessionUserPointsResponseDto, RoomSessionUserResponseDto, RoomSessionUserRolesResponseDto } from './roomSessionUser.dto'; +import { RoomSessionUser } from './roomSessionUser.entity'; +import { RoomSession } from './roomSession.entity'; -@Controller('sessionPoints') +@Controller('roomSessionUser') export class RoomSessionUserPointsController { constructor( - private readonly roomSessionUser: RoomSessionUserService) {} + private readonly roomSessionUserService: RoomSessionUserService) {} /** @@ -18,8 +20,23 @@ export class RoomSessionUserPointsController { @UseGuards(AuthGuard) @Get('/user/:userId') async getPointsByRoomSessionUser(@Param('userId') userId: number): Promise<RoomSessionUserPointsResponseDto> { - const points = await this.roomSessionUser.getPointsByRoomSessionUser(userId); + const points = await this.roomSessionUserService.getPointsByRoomSessionUser(userId); return { ...points, userId }; } + + /** + * Assigns a role to a specific user in a room session. + * + * @param userId - The unique identifier of the user. + * @param role - The role to be assigned to the user. + * @returns A promise that resolves when the role has been successfully assigned. + */ + @UseGuards(AuthGuard) + @Get('/:userId/role') + async getRole(@Param('userId') userId: number): Promise<RoomSessionUserRolesResponseDto> { + const roles = await this.roomSessionUserService.assignRole(userId); + const rolesArray = roles.map(role => role.valueOf()); + return { userId, roleNames: rolesArray }; + } } \ No newline at end of file diff --git a/src/room/roomSessionUser.dto.ts b/src/room/roomSessionUser.dto.ts index 9676639..9cbbc57 100644 --- a/src/room/roomSessionUser.dto.ts +++ b/src/room/roomSessionUser.dto.ts @@ -10,5 +10,11 @@ export class RoomSessionUserResponseDto { userId: number; name: string; points: RoomSessionUserPointsResponseDto; + roleNames?: RoomSessionUserRolesResponseDto; +} + +export class RoomSessionUserRolesResponseDto { + userId: number; + roleNames?: string[]; } diff --git a/src/room/roomSessionUser.entity.ts b/src/room/roomSessionUser.entity.ts index 9c2ec2a..a423f3a 100644 --- a/src/room/roomSessionUser.entity.ts +++ b/src/room/roomSessionUser.entity.ts @@ -23,6 +23,9 @@ import { @Property() name: string; + + @Property() + roleName?: string[]; /** * Creates a new instance of the Room class. @@ -33,6 +36,7 @@ import { this.roomSession = roomSession; this.userId = userId; this.name = name; + this.roleName = []; } } diff --git a/src/room/roomSessionUser.module.ts b/src/room/roomSessionUser.module.ts index 95c8644..afbcdce 100644 --- a/src/room/roomSessionUser.module.ts +++ b/src/room/roomSessionUser.module.ts @@ -8,7 +8,6 @@ import { SessionPoints } from '../points/sessionPoints.entity'; import { RoomSessionModule } from './roomSession.module'; import { RoomSession } from './roomSession.entity'; import { ActivitiesModule } from '../activities/activities.module'; -import { RoleModule } from '../roles/role.module'; /** * A global module that encapsulates features related to room sessions and users. @@ -19,8 +18,7 @@ import { RoleModule } from '../roles/role.module'; MikroOrmModule.forFeature([RoomSessionUser, SessionPoints, RoomSession]), forwardRef (() => SessionPointsModule), forwardRef (() => RoomSessionModule), - RoleModule, - ActivitiesModule + forwardRef (() => ActivitiesModule), ], providers: [RoomSessionUserService], controllers: [RoomSessionUserPointsController], diff --git a/src/room/roomSessionUser.service.ts b/src/room/roomSessionUser.service.ts index c870dfa..55d2463 100644 --- a/src/room/roomSessionUser.service.ts +++ b/src/room/roomSessionUser.service.ts @@ -7,11 +7,19 @@ import { RoomSession } from './roomSession.entity'; import { RoomSessionUserPointsResponseDto } from './roomSessionUser.dto'; import { SessionPoints } from '../points/sessionPoints.entity'; import { ActivitiesService } from '../activities/activities.service'; -import { Role } from '../roles/role.entity'; -import { RoleType, RoleService } from '../roles/role.service'; import { RoomSessionPointsRequestDto } from './roomSession.dto'; import { RoomSessionService } from './roomSession.service'; +export enum RoleType { + COORDINATOR = 'COORDINATOR', + DOCUMENTER = 'DOCUMENTER', + CODER_DEBUGGER = 'CODER_DEBUGGER', + VISUALIZER = 'VISUALIZER', + ACTIVE_PARTICIPANT = 'ACTIVE_PARTICIPANT', + OBSERVER = 'OBSERVER', + LURKER = 'LURKER', + DISRUPTOR = 'DISRUPTOR', +} /** * A service responsible for managing room sessions and users. */ @@ -29,7 +37,6 @@ export class RoomSessionUserService { @InjectRepository(RoomSessionUser) private readonly userRepository: EntityRepository<RoomSessionUser>, private readonly activitiesService: ActivitiesService, - private readonly roleService: RoleService, @Inject(forwardRef(() => RoomSessionService)) private readonly roomSessionService: RoomSessionService, ) {} @@ -107,6 +114,10 @@ export class RoomSessionUserService { }; } + public async createRole(roomSessionUser: RoomSessionUser, roleType: RoleType): Promise<void> { + const role = { type: roleType, roomSessionUser }; + await this.em.persistAndFlush(role); + } /** * Assigns roles to a given RoomSessionUser based on their points and the session's total points. * @@ -117,7 +128,8 @@ export class RoomSessionUserService { * @param roomSessionUser - The RoomSessionUser entity for which roles are to be assigned. * @returns A promise that resolves to an array of assigned RoleType values. */ - public async assignRole(roomSessionUser: RoomSessionUser): Promise<RoleType[]> { + public async assignRole(userId: number): Promise<RoleType[]> { + const roomSessionUser = await this.userRepository.findOneOrFail({ id: userId }); const points = await this.getPointsByRoomSessionUser(roomSessionUser.id); const sessionPoints = await this.roomSessionService.getAllPointsBySession(roomSessionUser.roomSession.id); let roleTypes: RoleType[] = []; @@ -131,7 +143,7 @@ export class RoomSessionUserService { roleTypes.push(RoleType.LURKER); } for (let role of roleTypes) { - await this.roleService.createRole(role, roomSessionUser); + await this.createRole(roomSessionUser, role); } return roleTypes; } -- GitLab