From 70a9f9d7d4d0879457e7c1228afb7739e60b3197 Mon Sep 17 00:00:00 2001 From: Dima Rosmait <dima.rosmait@holi.team> Date: Wed, 8 Nov 2023 09:50:14 +0000 Subject: [PATCH] HOLI-6369 - Update space handler --- src/handlers/space-updated-handler.ts | 35 ++++++++++++ src/helpers/createSpace.ts | 10 ++-- src/helpers/rooms.ts | 76 ++++++++++++++++++++++----- src/index.ts | 13 +++-- src/types.ts | 14 +++++ 5 files changed, 125 insertions(+), 23 deletions(-) create mode 100644 src/handlers/space-updated-handler.ts diff --git a/src/handlers/space-updated-handler.ts b/src/handlers/space-updated-handler.ts new file mode 100644 index 0000000..3fca20f --- /dev/null +++ b/src/handlers/space-updated-handler.ts @@ -0,0 +1,35 @@ +import { logPhase } from '../logger' +import { SpaceUpdatedDataPayload } from '../types' +import { startChatClient, stopChatClient } from '../helpers/_chatClient' +import { + getHoliSpaceRoomsSharedData, + hasRoomUpdatedValues, + isSpaceGeneralRoomSharedData, + setRoomSharedData, +} from '../helpers/rooms' + +// Handles the SpaceCreated event from Google Cloud Pub/Sub. +export const spaceUpdatedHandler = async (messageId: string, payload: SpaceUpdatedDataPayload): Promise<void> => { + await startChatClient() + + try { + await logPhase( + messageId, + 'spaceUpdatedHandler' + )(async () => { + const { space: spacePayload } = payload + const [roomId, sharedData] = Object.entries(await getHoliSpaceRoomsSharedData(spacePayload.id, 'general')).find( + ([_, data]) => isSpaceGeneralRoomSharedData(data) + ) + + if (!isSpaceGeneralRoomSharedData(sharedData)) return + if (!hasRoomUpdatedValues(sharedData, spacePayload)) return + + await setRoomSharedData(roomId, sharedData.parentSpaceRoomId, spacePayload) + }) + } catch (error) { + throw new Error(`Failed to create space ${error}`) + } finally { + await stopChatClient() + } +} diff --git a/src/helpers/createSpace.ts b/src/helpers/createSpace.ts index 856b339..d09987f 100644 --- a/src/helpers/createSpace.ts +++ b/src/helpers/createSpace.ts @@ -4,6 +4,7 @@ import { chatClient } from './_chatClient' import { CHAT_SERVER_URL } from '../constants' import { SpacePayload } from '../types' import { ChatRoomEvent } from './type' +import { setRoomSharedData } from './rooms' export const createSpaceContainer = async ({ id, name }: SpacePayload, inviteUsers: string[]): Promise<string> => { const { room_id: parentSpaceRoomId } = await chatClient.createRoom({ @@ -38,15 +39,10 @@ export const createSpaceGeneralRoom = async (inviteUsers: string[]): Promise<str export const addGeneralRoomToSpaceContainer = async ( parentSpaceRoomId: string, generalSpaceRoomId: string, - { id, name, avatarDefaultColor }: SpacePayload + spacePayload: SpacePayload ): Promise<void> => { // 1. add custom data into general room - await chatClient.sendStateEvent(generalSpaceRoomId, ChatRoomEvent.SharedData, { - holiSpaceId: id, - spaceName: name, - avatarDefaultColor, - parentSpaceRoomId, - }) + await setRoomSharedData(generalSpaceRoomId, parentSpaceRoomId, spacePayload) // 2. add general room into space room await chatClient.sendStateEvent( diff --git a/src/helpers/rooms.ts b/src/helpers/rooms.ts index 1fc72d4..60e7aa7 100644 --- a/src/helpers/rooms.ts +++ b/src/helpers/rooms.ts @@ -1,4 +1,6 @@ import { CHAT_ADMIN_ACCESS_TOKEN, CHAT_SERVER_NAME, CHAT_SERVER_URL } from '../constants' +import { SpacePayload } from '../types' +import { chatClient } from './_chatClient' import { ChatRoomEvent } from './type' export const getMatrixUserId = (holiIdentity: string) => `@${holiIdentity}:${CHAT_SERVER_NAME}` @@ -52,31 +54,69 @@ interface RoomSharedDataEvent { const isRoomSharedDataEvent = (event: RoomSharedDataEvent | StateEvent): event is RoomSharedDataEvent => event.type === ChatRoomEvent.SharedData -interface SpaceSharedData { +export const isSpaceGeneralRoomSharedData = (data: SpaceSharedData): data is SpaceGeneralRoomSharedData => { + return 'parentSpaceRoomId' in data && 'spaceName' in data +} + +interface SpaceRoomSharedData { holiSpaceId: string +} + +interface SpaceGeneralRoomSharedData extends SpaceRoomSharedData { parentSpaceRoomId: string + spaceName: string + avatar: string + avatarDefaultColor: string } -/** - * Delete room helpers - */ -export const getRoomIdsForHoliSpace = async (holiSpaceId: string, searchTerm?: string) => { - const rooms: { room_id: string }[] = await getRooms(searchTerm) +export type SpaceSharedData = SpaceRoomSharedData | SpaceGeneralRoomSharedData + +export const getHoliSpaceRoomsSharedData = async ( + holiSpaceId: string, + searchTerm?: string +): Promise<Record<string, SpaceSharedData>> => { + const rooms: { room_id: string; name: string }[] = await getRooms(searchTerm) - return rooms.reduce<Promise<string[]>>(async (accPromise, room) => { + return rooms.reduce<Promise<Record<string, SpaceSharedData>>>(async (accPromise, room) => { let acc = await accPromise const { room_id } = room const state = await getRoomSharedData(room_id) const { content } = state.find(isRoomSharedDataEvent) ?? {} - if (content && content.holiSpaceId === holiSpaceId) { - acc = Array.from(new Set<string>([...acc, room_id, content.parentSpaceRoomId].filter((id) => !!id))) - } + return content && content.holiSpaceId === holiSpaceId + ? { + ...acc, + [room_id]: content, + } + : acc + }, Promise.resolve({})) +} + +export const getRoomIdsForHoliSpace = async (holiSpaceId: string, searchTerm?: string): Promise<string[]> => { + const holiSpaceRoomsRecord = await getHoliSpaceRoomsSharedData(holiSpaceId, searchTerm) + return Object.keys(holiSpaceRoomsRecord) +} - return acc - }, Promise.resolve([])) +/** + * Create room helpers + */ +export const setRoomSharedData = async ( + roomId: string, + parentSpaceRoomId: string, + { id, name, avatar, avatarDefaultColor }: SpacePayload +) => { + await chatClient.sendStateEvent(roomId, ChatRoomEvent.SharedData, { + holiSpaceId: id, + spaceName: name, + avatar, + avatarDefaultColor, + parentSpaceRoomId, + }) } +/** + * Delete room helpers + */ interface DeleteStatus { status: 'active' | 'shutting_down' | 'purging' | 'complete' | 'failed' } @@ -120,3 +160,15 @@ export const initRoomDeletion = async (roomId: string) => { return delete_id } + +/** + * Update room helpers + */ +export const hasRoomUpdatedValues = ( + oldSharedData: SpaceGeneralRoomSharedData, + spacePayload: SpacePayload +): boolean => { + const { spaceName, avatar, avatarDefaultColor } = oldSharedData + const { name: newSpaceName, avatar: newAvatar, avatarDefaultColor: newAvatarDefaultColor } = spacePayload + return spaceName !== newSpaceName || avatar !== newAvatar || avatarDefaultColor !== newAvatarDefaultColor +} diff --git a/src/index.ts b/src/index.ts index 471c9c6..e20a219 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ // import fetch from 'node-fetch' import { spaceCreatedHandler } from './handlers/space-created-handler' import { spaceDeletedHandler } from './handlers/space-deleted-handler' +import { spaceUpdatedHandler } from './handlers/space-updated-handler' import { spaceUserAddedHandler } from './handlers/space-user-added-handler' import { spaceUserLeftHandler } from './handlers/space-user-left-handler' import { spaceUserRemovedHandler } from './handlers/space-user-removed-handler' @@ -39,6 +40,14 @@ export const receiveEvent = async (request, response): Promise<void> => { await logPhase(messageId, 'spaceCreatedHandler')(() => spaceCreatedHandler(messageId, event)) response.sendStatus(201) break + case EventType.SPACE_UPDATED: + await logPhase(messageId, 'spaceUpdatedHandler')(() => spaceUpdatedHandler(messageId, event)) + response.sendStatus(201) + break + case EventType.SPACE_DELETED: + await logPhase(messageId, 'spaceDeletedHandler')(() => spaceDeletedHandler(messageId, event)) + response.sendStatus(204) + break case EventType.SPACE_USER_ADDED: await logPhase(messageId, 'spaceUserAddedHandler')(() => spaceUserAddedHandler(messageId, event)) response.sendStatus(200) @@ -47,10 +56,6 @@ export const receiveEvent = async (request, response): Promise<void> => { await logPhase(messageId, 'userNameUpdatedHandler')(() => userNameUpdatedHandler(messageId, event)) response.sendStatus(200) break - case EventType.SPACE_DELETED: - await logPhase(messageId, 'spaceDeletedHandler')(() => spaceDeletedHandler(messageId, event)) - response.sendStatus(204) - break case EventType.USER_DELETED: await logPhase(messageId, 'userDeletedHandler')(() => userDeletedHandler(messageId, event)) response.sendStatus(204) diff --git a/src/types.ts b/src/types.ts index 18e8706..b1538a6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -6,6 +6,7 @@ */ export enum EventType { SPACE_CREATED = 'SpaceCreated', + SPACE_UPDATED = 'SpaceUpdated', SPACE_USER_ADDED = 'SpaceUserAdded', USER_NAME_UPDATED = 'UserNameUpdated', SPACE_DELETED = 'SpaceDeleted', @@ -42,6 +43,7 @@ export type SpacePayload = { id: string name: string slug: string + avatar?: string avatarDefaultColor: string } @@ -57,6 +59,18 @@ export type SpaceCreatedDataPayload = { space: SpacePayload } +/** + * Payload for data created when a space is updated + * + * @typedef {Object} SpaceCreatedDataPayload + * @property {UserPayload} creator - The user who created the space + * @property {SpacePayload} space - The updated space + */ +export type SpaceUpdatedDataPayload = { + creator: UserPayload + space: SpacePayload +} + /** * Payload for data created when a user is added to a space * -- GitLab