From e75459f846180ef4fdab211e548c1e5559374dc2 Mon Sep 17 00:00:00 2001
From: Dima Rosmait <dima.rosmait@holi.team>
Date: Thu, 2 Nov 2023 15:21:28 +0000
Subject: [PATCH] HOLI-6271 - Leave space room

---
 README.md                                |  18 ++
 src/handlers/space-user-added-handler.ts |   2 +-
 src/handlers/space-user-left-handler.ts  |  33 +--
 src/helpers/kickUserFromSpace.ts         |  31 +++
 src/tests/index.test.ts                  | 248 -----------------------
 5 files changed, 61 insertions(+), 271 deletions(-)
 create mode 100644 src/helpers/kickUserFromSpace.ts
 delete mode 100644 src/tests/index.test.ts

diff --git a/README.md b/README.md
index b390c4f..31312dd 100644
--- a/README.md
+++ b/README.md
@@ -5,6 +5,24 @@ This repository contains Google Cloud Functions implementation for the integrati
 - can be found in the `src/index.ts`.
 - and is triggered by events on Google Cloud Pub/Sub, emitted by Okuna on the `okuna_staging` and `okuna_production` topics, respectively
 
+## Getting Started
+
+### Local Development
+
+#### Create Matrix Admin User
+
+Follow the steps in the below Synapse documentation link on how to create your Matrix Admin User.
+- [Synapse Create Admin](https://matrix-org.github.io/synapse/latest/admin_api/register_api.html)
+
+For the first step when fetching the **nonce** make sure that mprocs is running and that you are using the following Chat Server URL:
+
+```bash
+  # ${CHAT_SERVER_URL}/_synapse/admin/v1/register
+  http://127.0.0.1:8008/_synapse/admin/v1/register
+```
+
+Once we generate the HMAC as described in the documentation, the **nonce** can be used to do the PUT Request to get the admin user name and access token.
+
 ## Links
 
 - [Google Cloud Pub/Sub Topics](https://console.cloud.google.com/cloudpubsub/topic/list?project=holi-shared)
diff --git a/src/handlers/space-user-added-handler.ts b/src/handlers/space-user-added-handler.ts
index 0a20ae9..c5ef775 100644
--- a/src/handlers/space-user-added-handler.ts
+++ b/src/handlers/space-user-added-handler.ts
@@ -8,6 +8,6 @@ export const spaceUserAddedHandler = async (messageId: string, payload: SpaceUse
     await logPhase(messageId, 'spaceUserAddedHandler.findOrCreateOcisUser')(() => inviteUserToSpace(payload))
     logger.debug('User added to space successfully', { user: payload.user.id, space: payload.space.id })
   } catch (error) {
-    throw new Error(`Failed to add user to space ${error}`)
+    throw new Error(`Failed to add user "${payload.user.id}" to space "${payload.space.id}": ${error}`)
   }
 }
diff --git a/src/handlers/space-user-left-handler.ts b/src/handlers/space-user-left-handler.ts
index 25b5f7c..f4defa3 100644
--- a/src/handlers/space-user-left-handler.ts
+++ b/src/handlers/space-user-left-handler.ts
@@ -1,29 +1,18 @@
+import { startChatClient, stopChatClient } from '../helpers/_chatClient'
+import { kickUserFromSpace } from '../helpers/kickUserFromSpace'
 import { logPhase, logger } from '../logger'
 import { SpaceUserLeftDataPayload } from '../types'
 
 //Handles the SpaceUserLeft event from Google Cloud Pub/Sub.
 export const spaceUserLeftHandler = async (messageId: string, payload: SpaceUserLeftDataPayload): Promise<void> => {
-  console.log('++++++++= space_user_left_handler: SPACE USER LEFT', messageId, payload)
+  await startChatClient()
 
-  // try {
-  //   const { user, space } = payload
-
-  //   const ocisUser = await logPhase(
-  //     messageId,
-  //     'spaceUserLeftHandler.findOrCreateOcisUser'
-  //   )(() => findOrCreateOcisUser(user))
-
-  //   const ocisSpace = await logPhase(
-  //     messageId,
-  //     'spaceUserLeftHandler.findOrCreateOcisSpace'
-  //   )(() => findOrCreateOcisSpace(space))
-
-  //   await logPhase(
-  //     messageId,
-  //     'spaceUserLeftHandler.removeSpaceUser'
-  //   )(() => removeSpaceUser(ocisSpace.id, ocisUser.onPremisesSamAccountName))
-  //   logger.debug('User removed from space successfully', { ocisUser, ocisSpace })
-  // } catch (error) {
-  //   throw new Error(`Failed to remove user from space ${error}`)
-  // }
+  try {
+    await logPhase(messageId, 'spaceUserLeftHandler.kickUserFromSpace')(() => kickUserFromSpace(payload))
+    logger.debug('User removed from space successfully', { user: payload.user.id, space: payload.space.id })
+  } catch (error) {
+    throw new Error(`Failed to remove user "${payload.user.id}" from space "${payload.space.id}": ${error}`)
+  } finally {
+    await stopChatClient()
+  }
 }
diff --git a/src/helpers/kickUserFromSpace.ts b/src/helpers/kickUserFromSpace.ts
new file mode 100644
index 0000000..994658a
--- /dev/null
+++ b/src/helpers/kickUserFromSpace.ts
@@ -0,0 +1,31 @@
+import { SpaceUserAddedDataPayload } from '../types'
+import { chatClient } from './_chatClient'
+import { getMatrixUserId, getRoomIdsForHoliSpace } from './rooms'
+
+export const kickUserFromSpace = async ({
+  user: { identity },
+  space: { id: holiSpaceId },
+}: SpaceUserAddedDataPayload) => {
+  const roomsToDelete = await getRoomIdsForHoliSpace(holiSpaceId, 'general')
+  const userId = getMatrixUserId(identity)
+  let count = 12
+
+  return roomsToDelete.map(async function kickUser(roomId) {
+    const room = chatClient.getRoom(roomId)
+    const roomMember = room?.getMember(userId)
+
+    if (count <= 0) return
+    if (!roomMember) {
+      // try 12 times every 5000 if user not found
+      await new Promise((resolve) =>
+        setTimeout(() => {
+          count--
+          resolve(true)
+        }, 5000)
+      )
+      return await kickUser(roomId)
+    }
+
+    await chatClient.kick(roomId, userId)
+  })
+}
diff --git a/src/tests/index.test.ts b/src/tests/index.test.ts
deleted file mode 100644
index 4a58bb3..0000000
--- a/src/tests/index.test.ts
+++ /dev/null
@@ -1,248 +0,0 @@
-import { receiveEvent } from '..'
-import { spaceCreatedHandler } from '../handlers/space-created-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'
-import { userNameUpdatedHandler } from '../handlers/user-name-updated-handler'
-
-jest.mock('../handlers/space-created-handler')
-jest.mock('../handlers/space-user-added-handler')
-jest.mock('../handlers/user-name-updated-handler')
-jest.mock('../handlers/space-user-left-handler')
-jest.mock('../handlers/space-user-removed-handler')
-
-const MESSAGE_ID = '42'
-
-const encodeBase64 = (payload) => {
-  return Buffer.from(JSON.stringify(payload))
-}
-
-describe('receiveEvent', () => {
-  const spaceCreatedEventPayload = {
-    creator: {
-      id: 'test-user-id',
-      name: 'Test User',
-      email: 'test-email@example.com',
-    },
-    space: {
-      id: 'space-id',
-      name: 'Test Space',
-      slug: 'test-space',
-    },
-  }
-
-  const userNameUpdatedEventPayload = {
-    user: {
-      id: 'test-user-id',
-      name: 'Test User',
-      email: 'test-email@example.com',
-    },
-  }
-
-  const spaceUserAddedEventPayload = {
-    user: {
-      id: 'test-user-id',
-      name: 'Test User',
-      email: 'test-email@example.com',
-    },
-    space: {
-      id: 'space-id',
-      name: 'Test Space',
-      slug: 'test-space',
-    },
-  }
-
-  const spaceUserLeftEventPayload = {
-    user: {
-      id: 'test-user-id',
-      name: 'Test User',
-      email: 'test-email@example.com',
-    },
-    space: {
-      id: 'space-id',
-      name: 'Test Space',
-      slug: 'test-space',
-    },
-  }
-
-  const spaceUserRemovedEventPayload = {
-    user: {
-      id: 'test-user-id',
-      name: 'Test User',
-      email: 'test-email@example.com',
-    },
-    space: {
-      id: 'space-id',
-      name: 'Test Space',
-      slug: 'test-space',
-    },
-  }
-
-  const unsupportedEventPayload = {
-    user: {
-      id: 'test-user-id',
-      name: 'Test User',
-      email: 'test-email@example.com',
-    },
-  }
-
-  let spaceCreatedHandlerMock: jest.MockedFunction<typeof spaceCreatedHandler>
-  let spaceUserAddedHandlerMock: jest.MockedFunction<typeof spaceUserAddedHandler>
-  let userNameUpdatedHandlerMock: jest.MockedFunction<typeof userNameUpdatedHandler>
-  let spaceUserLeftHandlerMock: jest.MockedFunction<typeof spaceUserLeftHandler>
-  let spaceUserRemovedHandlerMock: jest.MockedFunction<typeof spaceUserRemovedHandler>
-
-  beforeEach(() => {
-    spaceCreatedHandlerMock = spaceCreatedHandler as jest.MockedFunction<typeof spaceCreatedHandler>
-    spaceUserAddedHandlerMock = spaceUserAddedHandler as jest.MockedFunction<typeof spaceUserAddedHandler>
-    userNameUpdatedHandlerMock = userNameUpdatedHandler as jest.MockedFunction<typeof userNameUpdatedHandler>
-    spaceUserLeftHandlerMock = spaceUserLeftHandler as jest.MockedFunction<typeof spaceUserLeftHandler>
-    spaceUserRemovedHandlerMock = spaceUserRemovedHandler as jest.MockedFunction<typeof spaceUserRemovedHandler>
-    jest.resetModules()
-  })
-
-  afterEach(() => {
-    jest.resetAllMocks()
-  })
-
-  it('should call the spaceCreatedHandler when the event type is SpaceCreated', async () => {
-    const requestMock = {
-      body: {
-        message: {
-          attributes: { eventType: 'SpaceCreated', eventVersion: '1.0.0' },
-          data: encodeBase64(spaceCreatedEventPayload),
-          messageId: MESSAGE_ID,
-        },
-      },
-    }
-
-    const responseMock = {
-      sendStatus: jest.fn(),
-    }
-
-    spaceCreatedHandlerMock.mockResolvedValue()
-
-    await receiveEvent(requestMock, responseMock)
-
-    expect(spaceCreatedHandlerMock).toHaveBeenCalledWith(MESSAGE_ID, spaceCreatedEventPayload)
-    expect(responseMock.sendStatus).toHaveBeenCalledWith(201)
-  })
-
-  it('should call the spaceUserAddedHandler when the event type is SpaceUserAdded', async () => {
-    const requestMock = {
-      body: {
-        message: {
-          attributes: { eventType: 'SpaceUserAdded', eventVersion: '1.0.0' },
-          data: encodeBase64(spaceUserAddedEventPayload),
-          messageId: MESSAGE_ID,
-        },
-      },
-    }
-
-    const responseMock = {
-      sendStatus: jest.fn(),
-    }
-
-    spaceUserAddedHandlerMock.mockResolvedValue()
-
-    await receiveEvent(requestMock, responseMock)
-
-    expect(spaceUserAddedHandlerMock).toHaveBeenCalledWith(MESSAGE_ID, spaceUserAddedEventPayload)
-    expect(responseMock.sendStatus).toHaveBeenCalledWith(200)
-  })
-
-  it('should call the userNameUpdatedHandler when the event type is UserNameUpdated', async () => {
-    const requestMock = {
-      body: {
-        message: {
-          attributes: { eventType: 'UserNameUpdated', eventVersion: '1.0.0' },
-          data: encodeBase64(userNameUpdatedEventPayload),
-          messageId: MESSAGE_ID,
-        },
-      },
-    }
-
-    const responseMock = {
-      sendStatus: jest.fn(),
-    }
-
-    userNameUpdatedHandlerMock.mockResolvedValue()
-
-    await receiveEvent(requestMock, responseMock)
-
-    expect(userNameUpdatedHandlerMock).toHaveBeenCalledWith(MESSAGE_ID, userNameUpdatedEventPayload)
-    expect(responseMock.sendStatus).toHaveBeenCalledWith(200)
-  })
-
-  it('should throw an error when the event type is unexpected', async () => {
-    const unsupportedEvent = 'UnsupportedEvent'
-
-    const requestMock = {
-      body: {
-        message: {
-          attributes: { eventType: unsupportedEvent, eventVersion: '1.0.0' },
-          data: encodeBase64(unsupportedEventPayload),
-          messageId: MESSAGE_ID,
-        },
-      },
-    }
-
-    const responseMock = {
-      sendStatus: jest.fn(),
-    }
-
-    await expect(receiveEvent(requestMock, responseMock)).rejects.toThrow(
-      `Received unexpected event of type ${unsupportedEvent}`
-    )
-    expect(responseMock.sendStatus).toHaveBeenCalledWith(500)
-    expect(spaceCreatedHandlerMock).not.toHaveBeenCalled()
-    expect(spaceUserAddedHandlerMock).not.toHaveBeenCalled()
-    expect(userNameUpdatedHandlerMock).not.toHaveBeenCalled()
-  })
-
-  it('should call the spaceUserLeftHandler when the event type is SpaceUserLeft', async () => {
-    const requestMock = {
-      body: {
-        message: {
-          attributes: { eventType: 'SpaceUserLeft', eventVersion: '1.0.0' },
-          data: encodeBase64(spaceUserLeftEventPayload),
-          messageId: MESSAGE_ID,
-        },
-      },
-    }
-
-    const responseMock = {
-      sendStatus: jest.fn(),
-    }
-
-    spaceUserLeftHandlerMock.mockResolvedValue()
-
-    await receiveEvent(requestMock, responseMock)
-
-    expect(spaceUserLeftHandlerMock).toHaveBeenCalledWith(MESSAGE_ID, spaceUserLeftEventPayload)
-    expect(responseMock.sendStatus).toHaveBeenCalledWith(200)
-  })
-
-  it('should call the spaceUserRemovedHandler when the event type is SpaceUserRemoved', async () => {
-    const requestMock = {
-      body: {
-        message: {
-          attributes: { eventType: 'SpaceUserRemoved', eventVersion: '1.0.0' },
-          data: encodeBase64(spaceUserRemovedEventPayload),
-          messageId: MESSAGE_ID,
-        },
-      },
-    }
-
-    const responseMock = {
-      sendStatus: jest.fn(),
-    }
-
-    spaceUserRemovedHandlerMock.mockResolvedValue()
-
-    await receiveEvent(requestMock, responseMock)
-
-    expect(spaceUserRemovedHandlerMock).toHaveBeenCalledWith(MESSAGE_ID, spaceUserRemovedEventPayload)
-    expect(responseMock.sendStatus).toHaveBeenCalledWith(200)
-  })
-})
-- 
GitLab