From 70d51e21a63cc92c4ba98ac61de64a2dc92fa90d Mon Sep 17 00:00:00 2001 From: juancarmore Date: Mon, 2 Feb 2026 14:14:29 +0100 Subject: [PATCH] backend: add timestamps for permissions updates in room member and room schemas --- .../mongoose-schemas/room-member.schema.ts | 4 + .../models/mongoose-schemas/room.schema.ts | 4 + .../src/services/room-member.service.ts | 8 +- meet-ce/backend/src/services/room.service.ts | 6 +- meet-ce/typings/src/room-member.ts | 1 + meet-ce/typings/src/room.ts | 298 +++++++++--------- 6 files changed, 171 insertions(+), 150 deletions(-) diff --git a/meet-ce/backend/src/models/mongoose-schemas/room-member.schema.ts b/meet-ce/backend/src/models/mongoose-schemas/room-member.schema.ts index 7b52b279..92779fc6 100644 --- a/meet-ce/backend/src/models/mongoose-schemas/room-member.schema.ts +++ b/meet-ce/backend/src/models/mongoose-schemas/room-member.schema.ts @@ -92,6 +92,10 @@ const MeetRoomMemberSchema = new Schema( type: MeetRoomMemberPermissionsSchema, required: true }, + permissionsUpdatedAt: { + type: Number, + required: true + }, currentParticipantIdentity: { type: String, required: false diff --git a/meet-ce/backend/src/models/mongoose-schemas/room.schema.ts b/meet-ce/backend/src/models/mongoose-schemas/room.schema.ts index c0a1e018..4d72d9c0 100644 --- a/meet-ce/backend/src/models/mongoose-schemas/room.schema.ts +++ b/meet-ce/backend/src/models/mongoose-schemas/room.schema.ts @@ -285,6 +285,10 @@ const MeetRoomSchema = new Schema( required: true, default: MeetRoomStatus.OPEN }, + rolesUpdatedAt: { + type: Number, + required: true + }, meetingEndAction: { type: String, enum: Object.values(MeetingEndAction), diff --git a/meet-ce/backend/src/services/room-member.service.ts b/meet-ce/backend/src/services/room-member.service.ts index 3658d3f8..ca42d439 100644 --- a/meet-ce/backend/src/services/room-member.service.ts +++ b/meet-ce/backend/src/services/room-member.service.ts @@ -111,15 +111,17 @@ export class RoomMemberService { const room = await this.roomService.getMeetRoom(roomId); const effectivePermissions = this.computeEffectivePermissions(room.roles, baseRole, customPermissions); + const now = Date.now(); const roomMember = { memberId, roomId, name: memberName, - membershipDate: Date.now(), + membershipDate: now, accessUrl, baseRole, customPermissions, - effectivePermissions + effectivePermissions, + permissionsUpdatedAt: now }; return this.roomMemberRepository.create(roomMember); } @@ -222,6 +224,7 @@ export class RoomMemberService { member.baseRole, member.customPermissions ); + member.permissionsUpdatedAt = Date.now(); const updatedMember = await this.roomMemberRepository.update(member); @@ -299,6 +302,7 @@ export class RoomMemberService { // Update the member with new effective permissions member.effectivePermissions = effectivePermissions; + member.permissionsUpdatedAt = Date.now(); await this.roomMemberRepository.update(member); this.logger.verbose( diff --git a/meet-ce/backend/src/services/room.service.ts b/meet-ce/backend/src/services/room.service.ts index 4e866ab7..b1823922 100644 --- a/meet-ce/backend/src/services/room.service.ts +++ b/meet-ce/backend/src/services/room.service.ts @@ -142,11 +142,12 @@ export class RoomService { } }; + const now = Date.now(); const meetRoom: MeetRoom = { roomId, roomName: roomName!, owner: user.userId, - creationDate: Date.now(), + creationDate: now, // maxParticipants, autoDeletionDate, autoDeletionPolicy: autoDeletionDate ? autoDeletionPolicy : undefined, @@ -155,6 +156,7 @@ export class RoomService { anonymous: anonymousConfig, accessUrl: `/room/${roomId}`, status: MeetRoomStatus.OPEN, + rolesUpdatedAt: now, meetingEndAction: MeetingEndAction.NONE }; return await this.roomRepository.create(meetRoom); @@ -263,6 +265,7 @@ export class RoomService { // Merge existing roles with new roles (partial update) room.roles = merge({}, room.roles, roles); + room.rolesUpdatedAt = Date.now(); await this.roomRepository.update(room); // Update existing room members with new effective permissions @@ -288,6 +291,7 @@ export class RoomService { // Merge existing anonymous config with new anonymous config (partial update) room.anonymous = merge({}, room.anonymous, anonymous); + room.rolesUpdatedAt = Date.now(); await this.roomRepository.update(room); return room; diff --git a/meet-ce/typings/src/room-member.ts b/meet-ce/typings/src/room-member.ts index 6d38667c..96c3acfc 100644 --- a/meet-ce/typings/src/room-member.ts +++ b/meet-ce/typings/src/room-member.ts @@ -24,6 +24,7 @@ export interface MeetRoomMember { baseRole: MeetRoomMemberRole; // The base role of the member in the room customPermissions?: Partial; // Custom permissions for the member (if any) effectivePermissions: MeetRoomMemberPermissions; // Effective permissions for the member (base role + custom permissions) + permissionsUpdatedAt: number; // Timestamp when the effective permissions were last updated currentParticipantIdentity?: string; // The participant identity if the member is currently in a meeting, undefined otherwise } diff --git a/meet-ce/typings/src/room.ts b/meet-ce/typings/src/room.ts index 90dd2581..4c3209c3 100644 --- a/meet-ce/typings/src/room.ts +++ b/meet-ce/typings/src/room.ts @@ -9,74 +9,78 @@ export interface MeetRoomOptions { /** * Name of the room */ - roomName?: string; + roomName?: string; /** * Date in milliseconds since epoch when the room will be automatically deleted */ - autoDeletionDate?: number; + autoDeletionDate?: number; /** * Configuration for automatic deletion behavior of the room. See {@link MeetRoomAutoDeletionPolicy} for details. */ - autoDeletionPolicy?: MeetRoomAutoDeletionPolicy; + autoDeletionPolicy?: MeetRoomAutoDeletionPolicy; /** * Configuration of the room. See {@link MeetRoomConfig} for details. */ - config?: Partial; + config?: Partial; /** * Roles configuration for the room. See {@link MeetRoomRolesConfig} for details. */ - roles?: MeetRoomRolesConfig; + roles?: MeetRoomRolesConfig; /** * Anonymous access configuration for the room. See {@link MeetRoomAnonymousConfig} for details. */ - anonymous?: MeetRoomAnonymousConfig; - // maxParticipants?: number | null; + anonymous?: MeetRoomAnonymousConfig; + // maxParticipants?: number | null; } /** * Representation of a room */ export interface MeetRoom extends MeetRoomOptions { - /** - * Unique identifier of the room - */ - roomId: string; - /** - * Name of the room - */ - roomName: string; - /** - * User ID of the internal Meet user who owns this room - */ - owner: string; - /** - * Timestamp of room creation in milliseconds since epoch - */ - creationDate: number; - /** - * Configuration of the room. See {@link MeetRoomConfig} for details. - */ - config: MeetRoomConfig; - /** - * Roles configuration for the room. See {@link MeetRoomRoles} for details. - */ - roles: MeetRoomRoles; - /** - * Anonymous access configuration for the room. See {@link MeetRoomAnonymous} for details. - */ - anonymous: MeetRoomAnonymous; - /** - * General access URL for authenticated users (owner and internal members) - */ - accessUrl: string; - /** - * Status of the room. See {@link MeetRoomStatus} for details. - */ - status: MeetRoomStatus; - /** - * Action to take on the room when the meeting ends. See {@link MeetingEndAction} for details. - */ - meetingEndAction: MeetingEndAction; + /** + * Unique identifier of the room + */ + roomId: string; + /** + * Name of the room + */ + roomName: string; + /** + * User ID of the internal Meet user who owns this room + */ + owner: string; + /** + * Timestamp of room creation in milliseconds since epoch + */ + creationDate: number; + /** + * Configuration of the room. See {@link MeetRoomConfig} for details. + */ + config: MeetRoomConfig; + /** + * Roles configuration for the room. See {@link MeetRoomRoles} for details. + */ + roles: MeetRoomRoles; + /** + * Anonymous access configuration for the room. See {@link MeetRoomAnonymous} for details. + */ + anonymous: MeetRoomAnonymous; + /** + * General access URL for authenticated users (owner and internal members) + */ + accessUrl: string; + /** + * Status of the room. See {@link MeetRoomStatus} for details. + */ + status: MeetRoomStatus; + /** + * Timestamp in milliseconds of the last time the room's role permissions or anonymous access were updated + */ + rolesUpdatedAt: number; + /** + * Action to take on the room when the meeting ends. See {@link MeetingEndAction} for details. + */ + meetingEndAction: MeetingEndAction; } /** @@ -84,12 +88,12 @@ export interface MeetRoom extends MeetRoomOptions { * Defines the complete permissions for moderator and speaker roles. */ export interface MeetRoomRoles { - moderator: { - permissions: MeetRoomMemberPermissions; - }; - speaker: { - permissions: MeetRoomMemberPermissions; - }; + moderator: { + permissions: MeetRoomMemberPermissions; + }; + speaker: { + permissions: MeetRoomMemberPermissions; + }; } /** @@ -97,12 +101,12 @@ export interface MeetRoomRoles { * Allows partial permission updates. */ export interface MeetRoomRolesConfig { - moderator?: { - permissions: Partial; - }; - speaker?: { - permissions: Partial; - }; + moderator?: { + permissions: Partial; + }; + speaker?: { + permissions: Partial; + }; } /** @@ -110,14 +114,14 @@ export interface MeetRoomRolesConfig { * Defines which roles have anonymous access enabled and their access URLs. */ export interface MeetRoomAnonymous { - moderator: { - enabled: boolean; - accessUrl: string; - }; - speaker: { - enabled: boolean; - accessUrl: string; - }; + moderator: { + enabled: boolean; + accessUrl: string; + }; + speaker: { + enabled: boolean; + accessUrl: string; + }; } /** @@ -125,132 +129,132 @@ export interface MeetRoomAnonymous { * Only includes enabled flags. */ export interface MeetRoomAnonymousConfig { - moderator?: { - enabled: boolean; - }; - speaker?: { - enabled: boolean; - }; + moderator?: { + enabled: boolean; + }; + speaker?: { + enabled: boolean; + }; } /** * Represents the current status of a meeting room. */ export enum MeetRoomStatus { - /** - * Room is open and available to host a meeting. - */ - OPEN = 'open', + /** + * Room is open and available to host a meeting. + */ + OPEN = 'open', - /** - * There is an ongoing meeting in the room. - */ - ACTIVE_MEETING = 'active_meeting', + /** + * There is an ongoing meeting in the room. + */ + ACTIVE_MEETING = 'active_meeting', - /** - * Room is closed to hosting new meetings. - */ - CLOSED = 'closed', + /** + * Room is closed to hosting new meetings. + */ + CLOSED = 'closed' } /** * Defines the action to take when a meeting ends. */ export enum MeetingEndAction { - /** - * No action is taken when the meeting ends. - */ - NONE = 'none', + /** + * No action is taken when the meeting ends. + */ + NONE = 'none', - /** - * The room will be closed when the meeting ends. - */ - CLOSE = 'close', + /** + * The room will be closed when the meeting ends. + */ + CLOSE = 'close', - /** - * The room (and its recordings, if any) will be deleted - * when the meeting ends. - */ - DELETE = 'delete', + /** + * The room (and its recordings, if any) will be deleted + * when the meeting ends. + */ + DELETE = 'delete' } /** * Configuration for automatic deletion behavior of a meeting room. */ export interface MeetRoomAutoDeletionPolicy { - /** - * Deletion policy when there is an active meeting. - */ - withMeeting: MeetRoomDeletionPolicyWithMeeting; + /** + * Deletion policy when there is an active meeting. + */ + withMeeting: MeetRoomDeletionPolicyWithMeeting; - /** - * Deletion policy when recordings exist. - */ - withRecordings: MeetRoomDeletionPolicyWithRecordings; + /** + * Deletion policy when recordings exist. + */ + withRecordings: MeetRoomDeletionPolicyWithRecordings; } /** * Defines how room deletion behaves when a meeting is active. */ export enum MeetRoomDeletionPolicyWithMeeting { - /** - * Force deletion even if there is an active meeting. - */ - FORCE = 'force', + /** + * Force deletion even if there is an active meeting. + */ + FORCE = 'force', - /** - * Delete the room when the meeting ends. - */ - WHEN_MEETING_ENDS = 'when_meeting_ends', + /** + * Delete the room when the meeting ends. + */ + WHEN_MEETING_ENDS = 'when_meeting_ends', - /** - * Fail the deletion if there is an active meeting. - */ - FAIL = 'fail', + /** + * Fail the deletion if there is an active meeting. + */ + FAIL = 'fail' } /** * Defines how room deletion behaves when recordings exist. */ export enum MeetRoomDeletionPolicyWithRecordings { - /** - * Force deletion even if there are ongoing or previous recordings. - */ - FORCE = 'force', + /** + * Force deletion even if there are ongoing or previous recordings. + */ + FORCE = 'force', - /** - * Close the room and keep recordings. - */ - CLOSE = 'close', + /** + * Close the room and keep recordings. + */ + CLOSE = 'close', - /** - * Fail the deletion if there are ongoing or previous recordings. - */ - FAIL = 'fail', + /** + * Fail the deletion if there are ongoing or previous recordings. + */ + FAIL = 'fail' } export interface MeetRoomFilters extends SortAndPagination { - roomName?: string; - status?: MeetRoomStatus; - fields?: string; + roomName?: string; + status?: MeetRoomStatus; + fields?: string; } export enum MeetRoomDeletionSuccessCode { - ROOM_DELETED = 'room_deleted', - ROOM_WITH_ACTIVE_MEETING_DELETED = 'room_with_active_meeting_deleted', - ROOM_WITH_ACTIVE_MEETING_SCHEDULED_TO_BE_DELETED = 'room_with_active_meeting_scheduled_to_be_deleted', - ROOM_AND_RECORDINGS_DELETED = 'room_and_recordings_deleted', - ROOM_CLOSED = 'room_closed', - ROOM_WITH_ACTIVE_MEETING_AND_RECORDINGS_DELETED = 'room_with_active_meeting_and_recordings_deleted', - ROOM_WITH_ACTIVE_MEETING_CLOSED = 'room_with_active_meeting_closed', - ROOM_WITH_ACTIVE_MEETING_AND_RECORDINGS_SCHEDULED_TO_BE_DELETED = 'room_with_active_meeting_and_recordings_scheduled_to_be_deleted', - ROOM_WITH_ACTIVE_MEETING_SCHEDULED_TO_BE_CLOSED = 'room_with_active_meeting_scheduled_to_be_closed', + ROOM_DELETED = 'room_deleted', + ROOM_WITH_ACTIVE_MEETING_DELETED = 'room_with_active_meeting_deleted', + ROOM_WITH_ACTIVE_MEETING_SCHEDULED_TO_BE_DELETED = 'room_with_active_meeting_scheduled_to_be_deleted', + ROOM_AND_RECORDINGS_DELETED = 'room_and_recordings_deleted', + ROOM_CLOSED = 'room_closed', + ROOM_WITH_ACTIVE_MEETING_AND_RECORDINGS_DELETED = 'room_with_active_meeting_and_recordings_deleted', + ROOM_WITH_ACTIVE_MEETING_CLOSED = 'room_with_active_meeting_closed', + ROOM_WITH_ACTIVE_MEETING_AND_RECORDINGS_SCHEDULED_TO_BE_DELETED = 'room_with_active_meeting_and_recordings_scheduled_to_be_deleted', + ROOM_WITH_ACTIVE_MEETING_SCHEDULED_TO_BE_CLOSED = 'room_with_active_meeting_scheduled_to_be_closed' } export enum MeetRoomDeletionErrorCode { - ROOM_HAS_ACTIVE_MEETING = 'room_has_active_meeting', - ROOM_HAS_RECORDINGS = 'room_has_recordings', - ROOM_WITH_ACTIVE_MEETING_HAS_RECORDINGS = 'room_with_active_meeting_has_recordings', - ROOM_WITH_ACTIVE_MEETING_HAS_RECORDINGS_CANNOT_SCHEDULE_DELETION = 'room_with_active_meeting_has_recordings_cannot_schedule_deletion', - ROOM_WITH_RECORDINGS_HAS_ACTIVE_MEETING = 'room_with_recordings_has_active_meeting', + ROOM_HAS_ACTIVE_MEETING = 'room_has_active_meeting', + ROOM_HAS_RECORDINGS = 'room_has_recordings', + ROOM_WITH_ACTIVE_MEETING_HAS_RECORDINGS = 'room_with_active_meeting_has_recordings', + ROOM_WITH_ACTIVE_MEETING_HAS_RECORDINGS_CANNOT_SCHEDULE_DELETION = 'room_with_active_meeting_has_recordings_cannot_schedule_deletion', + ROOM_WITH_RECORDINGS_HAS_ACTIVE_MEETING = 'room_with_recordings_has_active_meeting' }