diff --git a/src/models/principal.js b/src/models/principal.js index 1e50eaeed5..fe4cddd8aa 100644 --- a/src/models/principal.js +++ b/src/models/principal.js @@ -50,6 +50,14 @@ function getDefaultPrincipalObject(props) { principalId: null, // The url of the default calendar for invitations scheduleDefaultCalendarUrl: null, + // Room-specific properties (only for calendar-rooms) + roomSeatingCapacity: null, + roomType: null, + roomAddress: null, + roomFeatures: null, + roomBuildingName: null, + roomBuildingAddress: null, + roomNumber: null, ...props, } } @@ -91,6 +99,38 @@ function mapDavToPrincipal(dav) { const url = dav.principalUrl const userId = dav.userId + // Extract room-specific properties from DAV object, trimming string values defensively + const roomSeatingCapacity = dav.roomSeatingCapacity ?? null + const roomType = (dav.roomType ?? '').toString().trim() || null + const roomFeatures = (dav.roomFeatures ?? '').toString().trim() || null + // Strip leading/trailing whitespace and commas from building address to handle empty + // building-name fields, e.g. ", Science Park 140, 1098 XG, Amsterdam" → "Science Park 140, 1098 XG, Amsterdam" + const rawBuildingAddress = dav.roomBuildingAddress ?? null + const roomBuildingAddress = rawBuildingAddress + ? rawBuildingAddress.replace(/^[\s,]+|[\s,]+$/g, '').trim() || null + : null + // Derive building name from address (everything before first comma): "Poppodium, Kerkstraat 10" → "Poppodium" + const roomBuildingName = roomBuildingAddress ? roomBuildingAddress.split(',')[0].trim() || null : null + // Room number (floor.room format, e.g. "2.17") is stored in room-building-room-number + const roomNumber = (dav.roomBuildingRoomNumber ?? '').toString().trim() || null + + // Construct roomAddress for event LOCATION field from available properties + // Format: "Street (Building, Room X.XX)" — street-first for map/navigation apps + let roomAddress = null + if (roomBuildingAddress) { + const commaIdx = roomBuildingAddress.indexOf(',') + if (commaIdx > 0) { + const building = roomBuildingAddress.substring(0, commaIdx).trim() + const street = roomBuildingAddress.substring(commaIdx + 1).trim() + const detail = roomNumber ? building + ', Room ' + roomNumber : building + roomAddress = street + ' (' + detail + ')' + } else { + roomAddress = roomNumber + ? roomBuildingAddress + ' (Room ' + roomNumber + ')' + : roomBuildingAddress + } + } + return getDefaultPrincipalObject({ id, calendarUserType, @@ -107,6 +147,13 @@ function mapDavToPrincipal(dav) { principalId, userId, scheduleDefaultCalendarUrl, + roomSeatingCapacity, + roomType, + roomAddress, + roomFeatures, + roomBuildingName, + roomBuildingAddress, + roomNumber, }) } diff --git a/tests/javascript/unit/models/principal.test.js b/tests/javascript/unit/models/principal.test.js index 5bcdb92750..e8712a2921 100644 --- a/tests/javascript/unit/models/principal.test.js +++ b/tests/javascript/unit/models/principal.test.js @@ -24,6 +24,13 @@ describe('Test suite: Principal model (models/principal.js)', () => { isCalendarRoom: false, principalId: null, scheduleDefaultCalendarUrl: null, + roomSeatingCapacity: null, + roomType: null, + roomAddress: null, + roomFeatures: null, + roomBuildingName: null, + roomBuildingAddress: null, + roomNumber: null, }) }) @@ -48,6 +55,13 @@ describe('Test suite: Principal model (models/principal.js)', () => { principalId: 'bar', otherProp: 'foo', scheduleDefaultCalendarUrl: null, + roomSeatingCapacity: null, + roomType: null, + roomAddress: null, + roomFeatures: null, + roomBuildingName: null, + roomBuildingAddress: null, + roomNumber: null, }) }) @@ -82,6 +96,13 @@ describe('Test suite: Principal model (models/principal.js)', () => { isCalendarRoom: false, principalId: 'jane.doe', userId: 'legacy-jane-doe-uid', + roomSeatingCapacity: null, + roomType: null, + roomAddress: null, + roomFeatures: null, + roomBuildingName: null, + roomBuildingAddress: null, + roomNumber: null, }) }) @@ -116,6 +137,13 @@ describe('Test suite: Principal model (models/principal.js)', () => { isCalendarRoom: false, principalId: 'jane.doe', userId: null, + roomSeatingCapacity: null, + roomType: null, + roomAddress: null, + roomFeatures: null, + roomBuildingName: null, + roomBuildingAddress: null, + roomNumber: null, }) }) @@ -150,6 +178,13 @@ describe('Test suite: Principal model (models/principal.js)', () => { isCalendarRoom: false, principalId: 'CGAH82BAS285H', userId: null, + roomSeatingCapacity: null, + roomType: null, + roomAddress: null, + roomFeatures: null, + roomBuildingName: null, + roomBuildingAddress: null, + roomNumber: null, }) }) @@ -184,6 +219,13 @@ describe('Test suite: Principal model (models/principal.js)', () => { isCalendarRoom: false, principalId: 'projector-123', userId: null, + roomSeatingCapacity: null, + roomType: null, + roomAddress: null, + roomFeatures: null, + roomBuildingName: null, + roomBuildingAddress: null, + roomNumber: null, }) }) @@ -218,9 +260,90 @@ describe('Test suite: Principal model (models/principal.js)', () => { isCalendarRoom: true, principalId: 'room-123', userId: null, + roomSeatingCapacity: null, + roomType: null, + roomAddress: null, + roomFeatures: null, + roomBuildingName: null, + roomBuildingAddress: null, + roomNumber: null, }) }) + it('should properly map a calendar-room-principal with room properties', () => { + const dav = { + addressBookHomes: undefined, + calendarHomes: [], + calendarUserAddressSet: [], + calendarUserType: 'ROOM', + displayname: 'Conference Room A', + email: 'conf-a@example.com', + principalScheme: 'principal:principals/calendar-rooms/conf-a', + principalUrl: '/remote.php/dav/principals/calendar-rooms/conf-a/', + scheduleInbox: null, + scheduleOutbox: null, + url: '/remote.php/dav/principals/calendar-rooms/conf-a/', + userId: null, + roomSeatingCapacity: 20, + roomType: 'conference-room', + roomFeatures: 'PROJECTOR,WHITEBOARD', + roomBuildingAddress: 'Building A, Main Street 1', + roomBuildingRoomNumber: '2.17', + } + + expect(mapDavToPrincipal(dav)).toEqual({ + id: 'L3JlbW90ZS5waHAvZGF2L3ByaW5jaXBhbHMvY2FsZW5kYXItcm9vbXMvY29uZi1hLw==', + dav, + calendarUserType: 'ROOM', + principalScheme: 'principal:principals/calendar-rooms/conf-a', + emailAddress: 'conf-a@example.com', + displayname: 'Conference Room A', + url: '/remote.php/dav/principals/calendar-rooms/conf-a/', + isUser: false, + isGroup: false, + isCircle: false, + isCalendarResource: false, + isCalendarRoom: true, + principalId: 'conf-a', + userId: null, + roomSeatingCapacity: 20, + roomType: 'conference-room', + roomFeatures: 'PROJECTOR,WHITEBOARD', + roomBuildingName: 'Building A', + roomBuildingAddress: 'Building A, Main Street 1', + roomNumber: '2.17', + roomAddress: 'Main Street 1 (Building A, Room 2.17)', + }) + }) + + it('should strip leading commas and whitespace from roomBuildingAddress', () => { + const dav = { + addressBookHomes: undefined, + calendarHomes: [], + calendarUserAddressSet: [], + calendarUserType: 'ROOM', + displayname: 'AMS 0.11', + email: 'ams-011@example.com', + principalScheme: 'principal:principals/calendar-rooms/ams-011', + principalUrl: '/remote.php/dav/principals/calendar-rooms/ams-011/', + scheduleInbox: null, + scheduleOutbox: null, + url: '/remote.php/dav/principals/calendar-rooms/ams-011/', + userId: null, + roomSeatingCapacity: 1, + roomType: 'meeting-room', + roomFeatures: ' ', + roomBuildingAddress: ', Science Park 140, 1098 XG, Amsterdam', + roomBuildingRoomNumber: '0.11', + } + + const result = mapDavToPrincipal(dav) + expect(result.roomBuildingAddress).toBe('Science Park 140, 1098 XG, Amsterdam') + expect(result.roomBuildingName).toBe('Science Park 140') + expect(result.roomFeatures).toBe(null) + expect(result.roomAddress).toBe('1098 XG, Amsterdam (Science Park 140, Room 0.11)') + }) + it('should properly map a principal from an unknown backend to principal-object', () => { const dav = { addressBookHomes: undefined, @@ -252,6 +375,13 @@ describe('Test suite: Principal model (models/principal.js)', () => { isCalendarRoom: false, principalId: null, userId: null, + roomSeatingCapacity: null, + roomType: null, + roomAddress: null, + roomFeatures: null, + roomBuildingName: null, + roomBuildingAddress: null, + roomNumber: null, }) }) })