Skip to content

Commit

Permalink
#3044-up to date
Browse files Browse the repository at this point in the history
  • Loading branch information
caiodasilva2005 committed Dec 22, 2024
2 parents 51c9a04 + 05e984c commit c36bc65
Show file tree
Hide file tree
Showing 15 changed files with 131 additions and 270 deletions.
59 changes: 12 additions & 47 deletions src/backend/src/controllers/slack.controllers.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,16 @@
import { Request, Response } from 'express';
//import crypto from 'crypto';
import { getWorkspaceId } from '../integrations/slack';
import OrganizationsService from '../services/organizations.services';
import slackServices from '../services/slack.services';

export default class slackController {
static async handleEvent(req: Request, res: Response) {
console.log('got a slack req');
if (req.body.type === 'url_verification') {
res.status(200).send({ challenge: req.body.challenge });
}

console.log('EVENT');

//const slackSignature = req.headers['x-slack-signature'] as string;
const slackTimeStamp = req.headers['X-Slack-Request-Timestamp'] as string;

if (Math.abs(Date.now() - Number(slackTimeStamp) * 1000) > 60 * 5 * 1000) {
res.status(400).send('Slack request verification failed due to expired timestamp');
}
/*
const reqBody = req.body;
const signatureBase = 'v0:' + slackTimeStamp + ':' + reqBody;
const finalSignature =
'v0=' +
crypto
.createHmac('sha256', process.env.SLACK_BOT_TOKEN ? process.env.SLACK_BOT_TOKEN : '')
.update(signatureBase)
.digest('hex');
*/

console.log('PROCESSING');
slackServices.processEvent(req.body);
res.status(200).send('Event recieved');
/*
if (
crypto.timingSafeEqual(
Uint8Array.from(Buffer.from(finalSignature, 'utf8')),
Uint8Array.from(Buffer.from(slackSignature, 'utf8'))
)
) {
console.log('WORKING');
slackServices.processEvent(req.body);
res.status(200).send('Event recieved');
}
console.log('INVALID SIGNITURE');
res.status(400).send('Slack request verification failed due to incorrect signature');
*/
export default class SlackController {
static async processMessageEvent(event: any) {
try {
const organizations = await OrganizationsService.getAllOrganizations();
const nerSlackWorkspaceId = await getWorkspaceId();
const orgId = organizations.find((org) => org.slackWorkspaceId === nerSlackWorkspaceId)?.organizationId;
if (orgId) {
slackServices.processMessageSent(event, orgId);
}
} catch (error: unknown) {}
}
}
8 changes: 4 additions & 4 deletions src/backend/src/integrations/slack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,12 +182,12 @@ export const getUsersInChannel = async (channelId: string) => {

return members;
} catch (error) {
throw new HttpException(500, 'Error getting members from a slack channel: ' + (error as any).data.error);
return [];
}
};

/**
* Given a slack channel id, prood.uces the name of the channel
* Given a slack channel id, produces the name of the channel
* @param channelId the id of the slack channel
* @returns the name of the channel
*/
Expand All @@ -199,7 +199,7 @@ export const getChannelName = async (channelId: string) => {
const channelRes = await slack.conversations.info({ channel: channelId });
return channelRes.channel?.name || 'Unknown Channel';
} catch (error) {
throw new HttpException(500, 'Error getting slack channel name: ' + (error as any).data.error);
return;
}
};

Expand All @@ -216,7 +216,7 @@ export const getUserName = async (userId: string) => {
const userRes = await slack.users.info({ user: userId });
return userRes.user?.profile?.display_name || userRes.user?.real_name || 'Unkown User';
} catch (error) {
throw new HttpException(500, 'Error getting slack user name: ' + (error as any).data.error);
return;
}
};

Expand Down
11 changes: 2 additions & 9 deletions src/backend/src/routes/slack.routes.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
import { createEventAdapter } from '@slack/events-api';
import slackServices from '../services/slack.services';
import OrganizationsService from '../services/organizations.services';
import { getWorkspaceId } from '../integrations/slack';
import SlackController from '../controllers/slack.controllers';

export const slackEvents = createEventAdapter(process.env.SLACK_SIGNING_SECRET || '');

slackEvents.on('message', async (event) => {
const organizations = await OrganizationsService.getAllOrganizations();
const nerSlackWorkspaceId = await getWorkspaceId();
const orgId = organizations.find((org) => org.slackWorkspaceId === nerSlackWorkspaceId)?.organizationId;
if (orgId) {
slackServices.processMessageSent(event, orgId);
}
SlackController.processMessageEvent(event);
});

slackEvents.on('error', (error) => {
Expand Down
159 changes: 22 additions & 137 deletions src/backend/src/services/slack.services.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import UsersService from './users.services';
import { getChannelName, getUserName, getUsersInChannel } from '../integrations/slack';
import { User_Settings } from '@prisma/client';
import { getChannelName, getUserName } from '../integrations/slack';
import AnnouncementService from './announcement.service';
import { Announcement } from 'shared';
import prisma from '../prisma/prisma';
import { blockToMentionedUsers, blockToString } from '../utils/slack.utils';

/**
* Represents a slack event for a message in a channel.
Expand Down Expand Up @@ -66,98 +67,6 @@ export interface SlackRichTextBlock {
}

export default class slackServices {
/**
* Converts a SlackRichTextBlock into a string representation for an announcement.
* @param block the block of information from slack
* @returns the string that will be combined with other block's strings to create the announcement
*/
private static async blockToString(block: SlackRichTextBlock): Promise<string> {
switch (block.type) {
case 'broadcast':
return '@' + block.range;
case 'color':
return block.value ?? '';
case 'channel':
//channels are represented as an id, get the name from the slack api
let channelName = block.channel_id;
try {
channelName = await getChannelName(block.channel_id ?? '');
} catch (error) {
channelName = `ISSUE PARSING CHANNEL:${block.channel_id}`;
}
return '#' + channelName;
case 'date':
return new Date(block.timestamp ?? 0).toISOString();
case 'emoji':
//if the emoji is a unicode emoji, convert the unicode to a string,
//if it is a slack emoji just use the name of the emoji
if (block.unicode) {
return String.fromCodePoint(parseInt(block.unicode, 16));
}
return 'emoji:' + block.name;
case 'link':
if (block.text) {
return `${block.text}:(${block.url})`;
}
return block.url ?? '';
case 'text':
return block.text ?? '';
case 'user':
//users are represented as an id, get the name of the user from the slack api
let userName: string = block.user_id ?? '';
try {
userName = (await getUserName(block.user_id ?? '')) ?? `Unknown User:${block.user_id}`;
} catch (error) {
userName = `Unknown_User:${block.user_id}`;
}
return '@' + userName;
case 'usergroup':
return `usergroup:${block.usergroup_id}`;
}
}

/**
* Gets the users notified in a specific SlackRichTextBlock.
* @param block the block that may contain mentioned user/users
* @param usersSettings the settings of all the users in prisma
* @param channelId the id of the channel that the block is being sent in
* @returns an array of prisma user ids of users to be notified
*/
private static async blockToMentionedUsers(
block: SlackRichTextBlock,
usersSettings: User_Settings[],
channelId: string
): Promise<string[]> {
switch (block.type) {
case 'broadcast':
switch (block.range) {
case 'everyone':
return usersSettings.map((usersSettings) => usersSettings.userId);
case 'channel':
case 'here':
//@here behaves the same as @channel; notifies all the users in that channel
let slackIds: string[] = [];
try {
slackIds = await getUsersInChannel(channelId);
} catch (ignored) {}
return usersSettings
.filter((userSettings) => {
return slackIds.some((slackId) => slackId === userSettings.slackId);
})
.map((user) => user.userId);
default:
return [];
}
case 'user':
return usersSettings
.filter((userSettings) => userSettings.slackId === block.user_id)
.map((userSettings) => userSettings.userId);
default:
//only broadcasts and specific user mentions add recievers to announcements
return [];
}
}

/**
* Given a slack event representing a message in a channel,
* make the appropriate announcement change in prisma.
Expand All @@ -166,13 +75,8 @@ export default class slackServices {
* @returns an annoucement if an announcement was processed and created/modified/deleted
*/
static async processMessageSent(event: SlackMessageEvent, organizationId: string): Promise<Announcement | undefined> {
let slackChannelName: string;
//get the name of the channel from the slack api
try {
slackChannelName = await getChannelName(event.channel);
} catch (error) {
slackChannelName = `Unknown_Channel:${event.channel}`;
}
const slackChannelName: string = (await getChannelName(event.channel)) ?? `Unknown_Channel:${event.channel}`;
const dateCreated = new Date(Number(event.event_ts));

//get the message that will be processed either as the event or within a subtype
Expand All @@ -183,11 +87,7 @@ export default class slackServices {
case 'message_deleted':
//delete the message using the client_msg_id
eventMessage = (event as SlackDeletedMessage).previous_message;
try {
return AnnouncementService.deleteAnnouncement(eventMessage.client_msg_id, organizationId);
} catch (ignored) {
return;
}
return AnnouncementService.deleteAnnouncement(eventMessage.client_msg_id, organizationId);
case 'message_changed':
eventMessage = (event as SlackUpdatedMessage).message;
break;
Expand All @@ -213,35 +113,25 @@ export default class slackServices {
);

//get the name of the user that sent the message from slack
let userName: string = '';
try {
userName = (await getUserName(eventMessage.user)) ?? '';
} catch (ignored) {}
let userName = (await getUserName(eventMessage.user)) ?? '';

//if slack could not produce the name of the user, look for their name in prisma
if (!userName) {
const userIdList = userSettings
.filter((userSetting) => userSetting.slackId === eventMessage.user)
.map((userSettings) => userSettings.userId);
if (userIdList.length !== 0) {
const prismaUserName = users.find((user) => user.userId === userIdList[0]);
userName = prismaUserName
? `${prismaUserName?.firstName} ${prismaUserName?.lastName}`
: 'Unknown User:' + eventMessage.user;
} else {
try {
const userWithThatSlackId = await prisma.user.findFirst({ where: { userSettings: { slackId: eventMessage.user } } });
userName = `${userWithThatSlackId?.firstName} ${userWithThatSlackId?.lastName}`;
} catch {
userName = 'Unknown_User:' + eventMessage.user;
}
}

//pull out the blocks of data from the metadata within the message event
const richTextBlocks = eventMessage.blocks?.filter((eventBlock: any) => eventBlock.type === 'rich_text');

if (richTextBlocks && richTextBlocks.length === 1) {
if (richTextBlocks && richTextBlocks.length > 0 && richTextBlocks[0].elements.length > 0) {
for (const element of richTextBlocks[0].elements[0].elements) {
messageText += await slackServices.blockToString(element);
userIdsToNotify = userIdsToNotify.concat(
await slackServices.blockToMentionedUsers(element, userSettings, event.channel)
);
messageText += await blockToString(element);
userIdsToNotify = userIdsToNotify.concat(await blockToMentionedUsers(element, userSettings, event.channel));
}
} else {
return;
Expand Down Expand Up @@ -271,19 +161,14 @@ export default class slackServices {
);
} catch (ignored) {}
}
try {
return await AnnouncementService.createAnnouncement(
messageText,
userIdsToNotify,
dateCreated,
userName,
eventMessage.client_msg_id,
slackChannelName,
organizationId
);
} catch (error) {
//if announcement does not have unique cient_msg_id disregard it
return;
}
return await AnnouncementService.createAnnouncement(
messageText,
userIdsToNotify,
dateCreated,
userName,
eventMessage.client_msg_id,
slackChannelName,
organizationId
);
}
}
Loading

0 comments on commit c36bc65

Please sign in to comment.