From 814aef4accc5c9d788ba842f4fade3b29bcde226 Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Sun, 19 Jun 2022 18:46:09 +0000 Subject: [PATCH 01/32] Add new EMUN values for modal --- github/GithubApp.ts | 1 + github/enum/Modals.ts | 2 ++ github/modals/fileCodeModal.ts | 10 +++++----- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/github/GithubApp.ts b/github/GithubApp.ts index d949115..1f70ae5 100644 --- a/github/GithubApp.ts +++ b/github/GithubApp.ts @@ -19,6 +19,7 @@ export class GithubApp extends App { super(info, logger, accessors); } + public async executeBlockActionHandler(context: UIKitBlockInteractionContext, read: IRead, http: IHttp, persistence: IPersistence, modify: IModify): Promise { const handler = new ExecuteBlockActionHandler(this, read, http, modify, persistence); return await handler.run(context); diff --git a/github/enum/Modals.ts b/github/enum/Modals.ts index 2ef412d..a0d6153 100644 --- a/github/enum/Modals.ts +++ b/github/enum/Modals.ts @@ -1,6 +1,8 @@ export enum ModalsEnum { PULL_VIEW = 'pull-view', CODE_VIEW = 'code-view', + CODE_VIEW_LABEL = 'Code Changes', + CODE_INPUT='code-input', VIEW_FILE_ACTION = 'view-file-task', VIEW_FILE_ACTION_LABEL = 'View File', VIEW_DIFFS_ACTION_LABEL = 'View Changes', diff --git a/github/modals/fileCodeModal.ts b/github/modals/fileCodeModal.ts index 0151548..131e874 100644 --- a/github/modals/fileCodeModal.ts +++ b/github/modals/fileCodeModal.ts @@ -32,18 +32,18 @@ export async function fileCodeModal({ data, modify, read, persistence, http, sla }) - //shows indentations in input blocks but not inn section block + // shows indentations in input blocks but not inn section block // block.addInputBlock({ - // blockId: ModalsEnum.TASK_BLOCK, - // label: { text: ModalsEnum.TASK_INPUT_LABEL, type: TextObjectType.PLAINTEXT }, + // blockId: ModalsEnum.CODE_VIEW, + // label: { text: ModalsEnum.CODE_VIEW_LABEL, type: TextObjectType.PLAINTEXT }, // element: block.newPlainTextInputElement({ // initialValue : `${pullData}`, // multiline:true, - // actionId: ModalsEnum.TASK_INPUT, + // actionId: ModalsEnum.CODE_INPUT, // }) // }); - block.addDividerBlock(); + } From 595e0edadfc5bc18501157f67b0c8d175c6c7f8b Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Sun, 26 Jun 2022 10:24:17 +0000 Subject: [PATCH 02/32] Oath2 Module --- github/GithubApp.ts | 54 +++++++++++++++++- github/commands/GithubCommand.ts | 17 +++++- github/enum/Subcommands.ts | 5 ++ github/helpers/authentication.ts | 29 ++++++++++ github/lib/blocks.ts | 30 ++++++++++ github/lib/message.ts | 97 ++++++++++++++++++++++++++++++++ github/lib/notifications.ts | 58 +++++++++++++++++++ github/persistance/users.ts | 66 ++++++++++++++++++++++ 8 files changed, 354 insertions(+), 2 deletions(-) create mode 100644 github/enum/Subcommands.ts create mode 100644 github/helpers/authentication.ts create mode 100644 github/lib/blocks.ts create mode 100644 github/lib/message.ts create mode 100644 github/lib/notifications.ts create mode 100644 github/persistance/users.ts diff --git a/github/GithubApp.ts b/github/GithubApp.ts index 1f70ae5..973d9dd 100644 --- a/github/GithubApp.ts +++ b/github/GithubApp.ts @@ -6,6 +6,9 @@ import { IModify, IPersistence, IRead, + IAppInstallationContext, + IConfigurationModify, + IEnvironmentRead } from "@rocket.chat/apps-engine/definition/accessors"; import { App } from "@rocket.chat/apps-engine/definition/App"; import { IAppInfo } from "@rocket.chat/apps-engine/definition/metadata"; @@ -13,12 +16,58 @@ import { GithubCommand } from "./commands/GithubCommand"; import { IUIKitResponse, UIKitBlockInteractionContext, UIKitViewCloseInteractionContext } from '@rocket.chat/apps-engine/definition/uikit'; import { ExecuteViewClosedHandler } from "./handlers/ExecuteViewClosedHandler"; import { ExecuteBlockActionHandler } from "./handlers/ExecuteBlockActionHandler"; +import { IUser } from "@rocket.chat/apps-engine/definition/users"; +import { IAuthData, IOAuth2Client, IOAuth2ClientOptions } from '@rocket.chat/apps-engine/definition/oauth2/IOAuth2'; +import { createOAuth2Client } from '@rocket.chat/apps-engine/definition/oauth2/OAuth2'; +import { create as registerAuthorizedUser } from "./persistance/users"; +import { createSectionBlock } from "./lib/blocks"; +import { sendDirectMessage } from "./lib/message"; +import { OAuth2Client } from "@rocket.chat/apps-engine/server/oauth2/OAuth2Client"; export class GithubApp extends App { + constructor(info: IAppInfo, logger: ILogger, accessors: IAppAccessors) { super(info, logger, accessors); } + + private oauth2ClientInstance: IOAuth2Client; + + public oauth2Config: IOAuth2ClientOptions = { + alias: 'github-app', + accessTokenUri: 'https://github.com/login/oauth/access_token', + authUri: 'https://github.com/login/oauth/authorize', + refreshTokenUri: 'https://github.com/login/oauth/access_token', + revokeTokenUri: 'https://github.com/login/oauth/access_token', + authorizationCallback: this.autorizationCallback.bind(this), + defaultScopes:['users','repo'], + + }; + + private async autorizationCallback( + token: IAuthData, + user: IUser, + read: IRead, + modify: IModify, + http: IHttp, + persistence: IPersistence, + ) { + + if (token) { + await registerAuthorizedUser(read, persistence, user); + } + + const text =`Authentication Succesfull !` + + await sendDirectMessage(read, modify, user, text, persistence); + } + + public getOauth2ClientInstance(): IOAuth2Client { + if (!this.oauth2ClientInstance) { + this.oauth2ClientInstance = createOAuth2Client(this, this.oauth2Config); + } + return this.oauth2ClientInstance; + } public async executeBlockActionHandler(context: UIKitBlockInteractionContext, read: IRead, http: IHttp, persistence: IPersistence, modify: IModify): Promise { const handler = new ExecuteBlockActionHandler(this, read, http, modify, persistence); @@ -34,6 +83,9 @@ export class GithubApp extends App { configuration: IConfigurationExtend ): Promise { const gitHubCommand: GithubCommand = new GithubCommand(this); - await configuration.slashCommands.provideSlashCommand(gitHubCommand); + await Promise.all([ + configuration.slashCommands.provideSlashCommand(gitHubCommand), + this.getOauth2ClientInstance().setup(configuration) + ]) } } diff --git a/github/commands/GithubCommand.ts b/github/commands/GithubCommand.ts index b38cd90..fe7854f 100644 --- a/github/commands/GithubCommand.ts +++ b/github/commands/GithubCommand.ts @@ -14,6 +14,8 @@ import { initiatorMessage } from "../lib/initiatorMessage"; import { helperMessage } from "../lib/helperMessage"; import { basicQueryMessage } from "../helpers/basicQueryMessage"; import { pullDetailsModal } from "../modals/pullDetailsModal"; +import { authorize } from "../helpers/authentication"; +import { SubcommandEnum } from "../enum/Subcommands"; export class GithubCommand implements ISlashCommand { @@ -50,7 +52,20 @@ export class GithubCommand implements ISlashCommand { sender: sender, arguments: command, }; - await initiatorMessage({ data, read, persistence, modify, http }); + if(command[0].includes('/')){ + await initiatorMessage({ data, read, persistence, modify, http }); + }else{ + switch(command[0]){ + case SubcommandEnum.LOGIN : { + await authorize(this.app, read, modify, context.getSender(), persistence); + break; + } + default:{ + await helperMessage({room,read, persistence, modify, http}); + break; + } + } + } break; } case 2 : { diff --git a/github/enum/Subcommands.ts b/github/enum/Subcommands.ts new file mode 100644 index 0000000..4954b64 --- /dev/null +++ b/github/enum/Subcommands.ts @@ -0,0 +1,5 @@ +export enum SubcommandEnum { + LOGIN = 'login', + HELP = 'help', + CONNECT = 'connect' +} \ No newline at end of file diff --git a/github/helpers/authentication.ts b/github/helpers/authentication.ts new file mode 100644 index 0000000..eb395c3 --- /dev/null +++ b/github/helpers/authentication.ts @@ -0,0 +1,29 @@ +import { + IModify, + IPersistence, + IRead, +} from "@rocket.chat/apps-engine/definition/accessors"; +import { IUser } from "@rocket.chat/apps-engine/definition/users"; +import { GithubApp } from "../GithubApp"; +import { IButton, createSectionBlock } from "../lib/blocks"; +import { sendDirectMessage } from "../lib/message"; + +export async function authorize( + app: GithubApp, + read: IRead, + modify: IModify, + user: IUser, + persistence: IPersistence +): Promise { + const url = await app + .getOauth2ClientInstance() + .getUserAuthorizationUrl(user); + + const button: IButton = { + text: "GitHub Login", + url: url.toString(), + }; + const message = `Login to GitHub`; + const block = await createSectionBlock(modify, message, button); + await sendDirectMessage(read, modify, user, message, persistence, block); +} diff --git a/github/lib/blocks.ts b/github/lib/blocks.ts new file mode 100644 index 0000000..3743da7 --- /dev/null +++ b/github/lib/blocks.ts @@ -0,0 +1,30 @@ +import { IModify } from '@rocket.chat/apps-engine/definition/accessors'; +import { AccessoryElements, BlockBuilder } from '@rocket.chat/apps-engine/definition/uikit'; + +export interface IButton { + text: string; + url?: string; + actionId?: string; +} + +export async function createSectionBlock(modify: IModify, sectionText: string, button?: IButton): Promise { + const blocks = modify.getCreator().getBlockBuilder(); + + blocks.addSectionBlock({ + text: blocks.newMarkdownTextObject(sectionText), + }); + + if (button) { + blocks.addActionsBlock({ + elements: [ + blocks.newButtonElement({ + actionId: button.actionId, + text: blocks.newPlainTextObject(button.text), + url: button.url, + }), + ], + }); + } + + return blocks; +} \ No newline at end of file diff --git a/github/lib/message.ts b/github/lib/message.ts new file mode 100644 index 0000000..dcf872f --- /dev/null +++ b/github/lib/message.ts @@ -0,0 +1,97 @@ +import { IModify, IPersistence, IRead } from '@rocket.chat/apps-engine/definition/accessors'; +import { IRoom, RoomType } from '@rocket.chat/apps-engine/definition/rooms'; +import { BlockBuilder, IBlock } from '@rocket.chat/apps-engine/definition/uikit'; +import { IUser } from '@rocket.chat/apps-engine/definition/users'; +import { NotificationsController } from './notifications'; + +export async function getDirect(read: IRead, modify: IModify, appUser: IUser, username: string): Promise { + const usernames = [appUser.username, username]; + let room: IRoom; + try { + room = await read.getRoomReader().getDirectByUsernames(usernames); + } catch (error) { + console.log(error); + return; + } + + if (room) { + return room; + } else { + let roomId: string; + + // Create direct room between botUser and username + const newRoom = modify.getCreator().startRoom() + .setType(RoomType.DIRECT_MESSAGE) + .setCreator(appUser) + .setMembersToBeAddedByUsernames(usernames); + roomId = await modify.getCreator().finish(newRoom); + return await read.getRoomReader().getById(roomId); + } +} + +export async function sendMessage( + modify: IModify, + room: IRoom, + sender: IUser, + message: string, + blocks?: BlockBuilder | [IBlock], +): Promise { + + const msg = modify.getCreator().startMessage() + .setSender(sender) + .setRoom(room) + .setGroupable(false) + .setParseUrls(false) + .setText(message); + + if (blocks !== undefined) { + msg.setBlocks(blocks); + } + + return await modify.getCreator().finish(msg); +} + +export async function shouldSendMessage(read: IRead, persistence: IPersistence, user: IUser): Promise { + const notificationsController = new NotificationsController(read, persistence, user); + const notificationStatus = await notificationsController.getNotificationsStatus(); + + return notificationStatus ? notificationStatus.status : true; +} + +export async function sendNotification(read: IRead, modify: IModify, user: IUser, room: IRoom, message: string, blocks?: BlockBuilder): Promise { + const appUser = await read.getUserReader().getAppUser() as IUser; + + const msg = modify.getCreator().startMessage() + .setSender(appUser) + .setRoom(room) + .setText(message); + + if (blocks) { + msg.setBlocks(blocks); + } + + return read.getNotifier().notifyUser(user, msg.getMessage()); +} + +export async function sendDirectMessage( + read: IRead, + modify: IModify, + user: IUser, + message: string, + persistence: IPersistence, + blocks?: BlockBuilder | [IBlock], +): Promise { + const appUser = await read.getUserReader().getAppUser() as IUser; + const targetRoom = await getDirect(read, modify, appUser, user.username) as IRoom; + + const shouldSend = await shouldSendMessage(read, persistence, user); + + if (!shouldSend) { return ''; } + + return await sendMessage(modify, targetRoom, appUser, message, blocks); +} + +export function isUserHighHierarchy(user: IUser): boolean { + const clearanceList = ['admin', 'owner', 'moderator']; + return user.roles.some((role) => clearanceList.includes(role)); +} \ No newline at end of file diff --git a/github/lib/notifications.ts b/github/lib/notifications.ts new file mode 100644 index 0000000..69d62f1 --- /dev/null +++ b/github/lib/notifications.ts @@ -0,0 +1,58 @@ +import { IPersistence, IRead } from '@rocket.chat/apps-engine/definition/accessors'; +import { RocketChatAssociationModel, RocketChatAssociationRecord } from '@rocket.chat/apps-engine/definition/metadata/RocketChatAssociations'; +import { IUser } from '@rocket.chat/apps-engine/definition/users/IUser'; + +interface INotificationsStatus { + status: boolean; +} + +export class NotificationsController { + private read: IRead; + private persistence: IPersistence; + private association: RocketChatAssociationRecord; + private userAssociation: RocketChatAssociationRecord; + + constructor(read: IRead, persistence: IPersistence, user: IUser) { + this.read = read; + this.persistence = persistence; + this.association = new RocketChatAssociationRecord( + RocketChatAssociationModel.MISC, + `github-notifications`, + ); + + this.userAssociation = new RocketChatAssociationRecord( + RocketChatAssociationModel.USER, + user.id, + ); + } + + public async getNotificationsStatus(): Promise { + + const [record] = await this.read + .getPersistenceReader() + .readByAssociations([this.association, this.userAssociation]); + + return record as INotificationsStatus; + } + + public async setNotificationsStatus(status: boolean): Promise { + await this.persistence.createWithAssociations({ status } , [this.association, this.userAssociation]); + return status; + } + + public async updateNotificationsStatus(status: boolean) { + const notificationsStatus = await this.getNotificationsStatus(); + + if (!notificationsStatus) { + return await this.setNotificationsStatus(status); + } + + await this.persistence.updateByAssociations([this.association, this.userAssociation], { status }); + + return status; + } + + public async deleteNotifications(): Promise { + await this.persistence.removeByAssociations([this.association, this.userAssociation]); + } +} \ No newline at end of file diff --git a/github/persistance/users.ts b/github/persistance/users.ts new file mode 100644 index 0000000..2d6557f --- /dev/null +++ b/github/persistance/users.ts @@ -0,0 +1,66 @@ +import { IPersistence, IRead } from "@rocket.chat/apps-engine/definition/accessors"; +import { RocketChatAssociationModel, RocketChatAssociationRecord } from "@rocket.chat/apps-engine/definition/metadata"; +import { IAuthData, IOAuth2ClientOptions } from "@rocket.chat/apps-engine/definition/oauth2/IOAuth2"; +import { IUser } from "@rocket.chat/apps-engine/definition/users"; + +const assoc = new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, 'users'); + +export async function create(read: IRead, persistence: IPersistence, user: IUser): Promise { + const users = await getAllUsers(read); + + if (!users) { + await persistence.createWithAssociation([user], assoc); + return; + } + + if (!isUserPresent(users, user)) { + users.push(user); + await persistence.updateByAssociation(assoc, users); + } +} + +export async function remove(read: IRead, persistence: IPersistence, user: IUser): Promise { + const users = await getAllUsers(read); + + if (!users || !isUserPresent(users, user)) { + // @NOTE do nothing + return; + } + + const idx = users.findIndex((u: IUser) => u.id === user.id); + users.splice(idx, 1); + await persistence.updateByAssociation(assoc, users); +} + +export async function getAllUsers(read: IRead): Promise { + const data = await read.getPersistenceReader().readByAssociation(assoc); + return (data.length ? data[0] as IUser[] : []); +} + +function isUserPresent(users: IUser[], targetUser: IUser): boolean { + return users.some((user) => user.id === targetUser.id); +} + +/** + * This function needed to be copied from the apps engine due to difficulties trying to + * get access to the auth client from inside a job processor. + * @NOTE It relies on hardcoded information (config alias's suffix) to work and it might break if + * the value changes + */ + +export async function getAccessTokenForUser(read: IRead, user: IUser, config: IOAuth2ClientOptions): Promise { + const associations = [ + new RocketChatAssociationRecord( + RocketChatAssociationModel.USER, + user.id, + ), + new RocketChatAssociationRecord( + RocketChatAssociationModel.MISC, + `${config.alias}-oauth-connection`, + ), + ]; + + const [ result ] = await read.getPersistenceReader().readByAssociations(associations) as unknown as Array; + + return result; +} From 7b31492ecccd976f0c2120c324e52e177b8e8851 Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Tue, 28 Jun 2022 23:47:17 +0000 Subject: [PATCH 03/32] Implimennt OAth2 With Scheduler to delete tokens periodically --- github/GithubApp.ts | 142 ++++++++++++++------ github/commands/GithubCommand.ts | 13 +- github/endpoints/githubEndpoints.ts | 20 +++ github/enum/Processors.ts | 3 + github/enum/Subcommands.ts | 3 +- github/lib/githubSDK.ts | 37 +++++ github/lib/helperMessage.ts | 13 +- github/{helpers => oath2}/authentication.ts | 0 github/oath2/oath2Config.ts | 20 +++ github/oath2/oath2callback.ts | 36 +++++ github/persistance/auth.ts | 123 +++++++++++++++++ github/persistance/users.ts | 66 --------- github/processors/deleteOAthToken.ts | 39 ++++++ 13 files changed, 399 insertions(+), 116 deletions(-) create mode 100644 github/endpoints/githubEndpoints.ts create mode 100644 github/enum/Processors.ts create mode 100644 github/lib/githubSDK.ts rename github/{helpers => oath2}/authentication.ts (100%) create mode 100644 github/oath2/oath2Config.ts create mode 100644 github/oath2/oath2callback.ts create mode 100644 github/persistance/auth.ts delete mode 100644 github/persistance/users.ts create mode 100644 github/processors/deleteOAthToken.ts diff --git a/github/GithubApp.ts b/github/GithubApp.ts index 973d9dd..92ec4f9 100644 --- a/github/GithubApp.ts +++ b/github/GithubApp.ts @@ -5,77 +5,118 @@ import { ILogger, IModify, IPersistence, - IRead, - IAppInstallationContext, - IConfigurationModify, - IEnvironmentRead + IRead } from "@rocket.chat/apps-engine/definition/accessors"; import { App } from "@rocket.chat/apps-engine/definition/App"; import { IAppInfo } from "@rocket.chat/apps-engine/definition/metadata"; import { GithubCommand } from "./commands/GithubCommand"; -import { IUIKitResponse, UIKitBlockInteractionContext, UIKitViewCloseInteractionContext } from '@rocket.chat/apps-engine/definition/uikit'; +import { + IUIKitResponse, + UIKitBlockInteractionContext, + UIKitViewCloseInteractionContext, +} from "@rocket.chat/apps-engine/definition/uikit"; import { ExecuteViewClosedHandler } from "./handlers/ExecuteViewClosedHandler"; import { ExecuteBlockActionHandler } from "./handlers/ExecuteBlockActionHandler"; import { IUser } from "@rocket.chat/apps-engine/definition/users"; -import { IAuthData, IOAuth2Client, IOAuth2ClientOptions } from '@rocket.chat/apps-engine/definition/oauth2/IOAuth2'; -import { createOAuth2Client } from '@rocket.chat/apps-engine/definition/oauth2/OAuth2'; -import { create as registerAuthorizedUser } from "./persistance/users"; +import { + IAuthData, + IOAuth2Client, + IOAuth2ClientOptions, +} from "@rocket.chat/apps-engine/definition/oauth2/IOAuth2"; +import { createOAuth2Client } from "@rocket.chat/apps-engine/definition/oauth2/OAuth2"; import { createSectionBlock } from "./lib/blocks"; import { sendDirectMessage } from "./lib/message"; import { OAuth2Client } from "@rocket.chat/apps-engine/server/oauth2/OAuth2Client"; +import { deleteOathToken } from "./processors/deleteOAthToken"; +import { ProcessorsEnum } from "./enum/Processors"; +import getOauth2Config from './oath2/oath2Config'; +import { ApiSecurity, ApiVisibility} from '@rocket.chat/apps-engine/definition/api'; +import { githubWebHooks } from "./endpoints/githubEndpoints"; +import { IJobContext } from "@rocket.chat/apps-engine/definition/scheduler"; +import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; export class GithubApp extends App { - constructor(info: IAppInfo, logger: ILogger, accessors: IAppAccessors) { super(info, logger, accessors); } - - - private oauth2ClientInstance: IOAuth2Client; - - public oauth2Config: IOAuth2ClientOptions = { - alias: 'github-app', - accessTokenUri: 'https://github.com/login/oauth/access_token', - authUri: 'https://github.com/login/oauth/authorize', - refreshTokenUri: 'https://github.com/login/oauth/access_token', - revokeTokenUri: 'https://github.com/login/oauth/access_token', - authorizationCallback: this.autorizationCallback.bind(this), - defaultScopes:['users','repo'], - - }; - private async autorizationCallback( + public async authorizationCallback( token: IAuthData, user: IUser, read: IRead, modify: IModify, http: IHttp, - persistence: IPersistence, + persistence: IPersistence ) { - + const deleteTokenTask = { + id: ProcessorsEnum.REMOVE_GITHUB_LOGIN, + when: '7 days', + data: { + 'user':user, + 'config' : this.oauth2Config + }, + }; + let text = `GitHub Authentication Succesfull 🚀`; + if (token) { - await registerAuthorizedUser(read, persistence, user); - } - - const text =`Authentication Succesfull !` - - await sendDirectMessage(read, modify, user, text, persistence); + // await registerAuthorizedUser(read, persistence, user); + await modify.getScheduler().scheduleOnce(deleteTokenTask); + } else { + text = `Authentication Failure 😔`; + } + await sendDirectMessage(read, modify, user, text, persistence); } - + public oauth2ClientInstance: IOAuth2Client; + public oauth2Config: IOAuth2ClientOptions = { + alias: "github-app", + accessTokenUri: "https://github.com/login/oauth/access_token", + authUri: "https://github.com/login/oauth/authorize", + refreshTokenUri: "https://github.com/login/oauth/access_token", + revokeTokenUri: `https://api.github.com/applications/client_id/token`, + authorizationCallback: this.authorizationCallback.bind(this), + defaultScopes: ["users", "repo"], + }; public getOauth2ClientInstance(): IOAuth2Client { if (!this.oauth2ClientInstance) { - this.oauth2ClientInstance = createOAuth2Client(this, this.oauth2Config); + this.oauth2ClientInstance = createOAuth2Client( + this, + this.oauth2Config + ); } return this.oauth2ClientInstance; } - public async executeBlockActionHandler(context: UIKitBlockInteractionContext, read: IRead, http: IHttp, persistence: IPersistence, modify: IModify): Promise { - const handler = new ExecuteBlockActionHandler(this, read, http, modify, persistence); + public async executeBlockActionHandler( + context: UIKitBlockInteractionContext, + read: IRead, + http: IHttp, + persistence: IPersistence, + modify: IModify + ): Promise { + const handler = new ExecuteBlockActionHandler( + this, + read, + http, + modify, + persistence + ); return await handler.run(context); } - public async executeViewClosedHandler(context: UIKitViewCloseInteractionContext, read: IRead, http: IHttp, persistence: IPersistence, modify: IModify) { - const handler = new ExecuteViewClosedHandler(this, read, http, modify, persistence); + public async executeViewClosedHandler( + context: UIKitViewCloseInteractionContext, + read: IRead, + http: IHttp, + persistence: IPersistence, + modify: IModify + ) { + const handler = new ExecuteViewClosedHandler( + this, + read, + http, + modify, + persistence + ); return await handler.run(context); } @@ -85,7 +126,28 @@ export class GithubApp extends App { const gitHubCommand: GithubCommand = new GithubCommand(this); await Promise.all([ configuration.slashCommands.provideSlashCommand(gitHubCommand), - this.getOauth2ClientInstance().setup(configuration) - ]) + this.getOauth2ClientInstance().setup(configuration), + ]); + configuration.scheduler.registerProcessors([ + { + id: ProcessorsEnum.REMOVE_GITHUB_LOGIN, + processor: async (jobContext,read,modify,http,persis) => { + let user = jobContext.user as IUser; + let config = jobContext.config as IOAuth2ClientOptions; + try { + await deleteOathToken({user,config,read,modify,http,persis}); + } catch (e) { + await sendDirectMessage(read,modify,user,e.message,persis); + } + } + }, + ]); + configuration.api.provideApi({ + visibility : ApiVisibility.PUBLIC, + security : ApiSecurity.UNSECURE, + endpoints : [ + new githubWebHooks(this) + ] + }) } } diff --git a/github/commands/GithubCommand.ts b/github/commands/GithubCommand.ts index fe7854f..db1d7c8 100644 --- a/github/commands/GithubCommand.ts +++ b/github/commands/GithubCommand.ts @@ -8,14 +8,18 @@ import { IPersistence, IRead, } from "@rocket.chat/apps-engine/definition/accessors"; +import { ProcessorsEnum } from "../enum/Processors"; import { GithubApp } from "../GithubApp"; import { initiatorMessage } from "../lib/initiatorMessage"; import { helperMessage } from "../lib/helperMessage"; import { basicQueryMessage } from "../helpers/basicQueryMessage"; import { pullDetailsModal } from "../modals/pullDetailsModal"; -import { authorize } from "../helpers/authentication"; +import { authorize } from "../oath2/authentication"; import { SubcommandEnum } from "../enum/Subcommands"; +import { getAccessTokenForUser } from "../persistance/auth"; +import { IUser } from "@rocket.chat/apps-engine/definition/users"; +import { removeToken } from "../persistance/auth"; export class GithubCommand implements ISlashCommand { @@ -60,6 +64,10 @@ export class GithubCommand implements ISlashCommand { await authorize(this.app, read, modify, context.getSender(), persistence); break; } + case SubcommandEnum.TEST : { + //test command + break; + } default:{ await helperMessage({room,read, persistence, modify, http}); break; @@ -97,5 +105,4 @@ export class GithubCommand implements ISlashCommand { } - } -} \ No newline at end of file + }}//rc-apps deploy --url https://community.liaison.edge.rocketchat.digital --username samad.yar.khan --password samad --update diff --git a/github/endpoints/githubEndpoints.ts b/github/endpoints/githubEndpoints.ts new file mode 100644 index 0000000..e62421e --- /dev/null +++ b/github/endpoints/githubEndpoints.ts @@ -0,0 +1,20 @@ +import { ApiEndpoint } from "@rocket.chat/apps-engine/definition/api" +import { IRead, IHttp, IModify,IPersistence } from "@rocket.chat/apps-engine/definition/accessors"; +import { IApiEndpointInfo,IApiEndpoint, IApiRequest,IApiResponse } from "@rocket.chat/apps-engine/definition/api"; +export class githubWebHooks extends ApiEndpoint{ + public path = 'githubwebhook' + + public async post( + request: IApiRequest, + endpoint: IApiEndpointInfo, + read: IRead, + modify: IModify, + http: IHttp, + persis: IPersistence, + ): Promise { + // await this.handleEvent(request, read, modify); + console.log(request.content.toString()); + return this.success(); + } + +} \ No newline at end of file diff --git a/github/enum/Processors.ts b/github/enum/Processors.ts new file mode 100644 index 0000000..367260e --- /dev/null +++ b/github/enum/Processors.ts @@ -0,0 +1,3 @@ +export enum ProcessorsEnum { + REMOVE_GITHUB_LOGIN = 'remove_github_token', +} \ No newline at end of file diff --git a/github/enum/Subcommands.ts b/github/enum/Subcommands.ts index 4954b64..02bb312 100644 --- a/github/enum/Subcommands.ts +++ b/github/enum/Subcommands.ts @@ -1,5 +1,6 @@ export enum SubcommandEnum { LOGIN = 'login', HELP = 'help', - CONNECT = 'connect' + CONNECT = 'connect', + TEST = 'test' } \ No newline at end of file diff --git a/github/lib/githubSDK.ts b/github/lib/githubSDK.ts new file mode 100644 index 0000000..ad39ef5 --- /dev/null +++ b/github/lib/githubSDK.ts @@ -0,0 +1,37 @@ +import { IHttp } from '@rocket.chat/apps-engine/definition/accessors'; + +const BaseHost = 'https://github.com/'; +const BaseApiHost = 'https://api.github.com/repos/'; + +export class GithubSDK { + constructor(private readonly http: IHttp, private readonly accessToken) {} + + public createWebhook(repoName: string, webhookUrl: string) { + return this.post(BaseApiHost + repoName + '/hooks', { + active: true, + events: ['push'], + config: { + url: webhookUrl, + content_type: 'json', + }, + }); + } + + private async post(url: string, data: any): Promise { + const response = await this.http.post(url, { + headers: { + 'Authorization': `Bearer ${this.accessToken}`, + 'Content-Type': 'application/json', + 'User-Agent': 'Rocket.Chat-Apps-Engine', + }, + data, + }); + + // If it isn't a 2xx code, something wrong happened + if (!response.statusCode.toString().startsWith('2')) { + throw response; + } + + return JSON.parse(response.content || '{}'); + } +} \ No newline at end of file diff --git a/github/lib/helperMessage.ts b/github/lib/helperMessage.ts index f883b7e..df6d8c1 100644 --- a/github/lib/helperMessage.ts +++ b/github/lib/helperMessage.ts @@ -22,12 +22,13 @@ export async function helperMessage({ let helperMessageString = ` Github App - 1) See Interactive Button interface to fetch repository data -> /github GithubUsername/RepositoryName - 2) Get details of a Repository -> /github GithubUsername/RepositoryName repo - 3) Get Issues of a Repository -> /github GithubUsername/RepositoryName issues - 4) Get Contributors of a Repository -> /github GithubUsername/RepositoryName contributors - 5) Get Recent Pull Request of a Repository -> /github GithubUsername/RepositoryName pulls - 6) Review a Pull Request -> /github GithubUsername/RepositoryName pulls pullNumber + 1) See Interactive Button interface to fetch repository data -> /github Username/RepositoryName + 2) Get details of a Repository -> /github Username/RepositoryName repo + 3) Get Issues of a Repository -> /github Username/RepositoryName issues + 4) Get Contributors of a Repository -> /github Username/RepositoryName contributors + 5) Get Recent Pull Request of a Repository -> /github Username/RepositoryName pulls + 6) Review a Pull Request -> /github Username/RepositoryName pulls pullNumber + 7) Login to GitHub -> /github login `; diff --git a/github/helpers/authentication.ts b/github/oath2/authentication.ts similarity index 100% rename from github/helpers/authentication.ts rename to github/oath2/authentication.ts diff --git a/github/oath2/oath2Config.ts b/github/oath2/oath2Config.ts new file mode 100644 index 0000000..6976136 --- /dev/null +++ b/github/oath2/oath2Config.ts @@ -0,0 +1,20 @@ +import { IOAuth2ClientOptions } from "@rocket.chat/apps-engine/definition/oauth2/IOAuth2"; +import { GithubApp } from "../GithubApp"; +import authorizationCallback from "./oath2callback" + +export default function getOauth2Config(app:GithubApp):IOAuth2ClientOptions{ + + let oauth2Config: IOAuth2ClientOptions = { + alias: "github-app", + accessTokenUri: "https://github.com/login/oauth/access_token", + authUri: "https://github.com/login/oauth/authorize", + refreshTokenUri: "https://github.com/login/oauth/access_token", + revokeTokenUri: "https://github.com/login/oauth/access_token", + authorizationCallback: authorizationCallback.bind(app), + defaultScopes: ["users", "repo"], + }; + + return oauth2Config; + +} + diff --git a/github/oath2/oath2callback.ts b/github/oath2/oath2callback.ts new file mode 100644 index 0000000..45e366d --- /dev/null +++ b/github/oath2/oath2callback.ts @@ -0,0 +1,36 @@ +import { + IHttp, + IModify, + IPersistence, + IRead, +} from "@rocket.chat/apps-engine/definition/accessors"; +import { IUser } from "@rocket.chat/apps-engine/definition/users"; +import { IAuthData } from "@rocket.chat/apps-engine/definition/oauth2/IOAuth2"; +import { sendDirectMessage } from "../lib/message"; +import { ProcessorsEnum } from "../enum/Processors"; + +export default async function authorizationCallback( + token: IAuthData, + user: IUser, + read: IRead, + modify: IModify, + http: IHttp, + persistence: IPersistence +) { + // const deleteTokenTask = { + // id: ProcessorsEnum.REMOVE_GITHUB_LOGIN, + // when: '7 seconds', + // data: { + // user + // }, + // }; + let text = `GitHub Authentication Succesfull 🚀`; + + if (token) { + // await registerAuthorizedUser(read, persistence, user); + // await modify.getScheduler().scheduleOnce(deleteTokenTask); + } else { + text = `Authentication Failure 😔`; + } + await sendDirectMessage(read, modify, user, text, persistence); +} \ No newline at end of file diff --git a/github/persistance/auth.ts b/github/persistance/auth.ts new file mode 100644 index 0000000..a82dcdf --- /dev/null +++ b/github/persistance/auth.ts @@ -0,0 +1,123 @@ +import { IHttp, IPersistence, IRead } from "@rocket.chat/apps-engine/definition/accessors"; +import { RocketChatAssociationModel, RocketChatAssociationRecord } from "@rocket.chat/apps-engine/definition/metadata"; +import { IAuthData, IOAuth2ClientOptions } from "@rocket.chat/apps-engine/definition/oauth2/IOAuth2"; +import { IUser } from "@rocket.chat/apps-engine/definition/users"; +import { URL } from 'url'; + +// const assoc = new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, 'users'); + +// export async function create(read: IRead, persistence: IPersistence, user: IUser): Promise { +// const users = await getAllUsers(read); + +// if (!users) { +// await persistence.createWithAssociation([user], assoc); +// return; +// } + +// if (!isUserPresent(users, user)) { +// users.push(user); +// await persistence.updateByAssociation(assoc, users); +// } +// } + +// export async function remove(read: IRead, persistence: IPersistence, user: IUser): Promise { +// const users = await getAllUsers(read); + +// if (!users || !isUserPresent(users, user)) { +// // @NOTE do nothing +// return; +// } + +// const idx = users.findIndex((u: IUser) => u.id === user.id); +// users.splice(idx, 1); +// await persistence.updateByAssociation(assoc, users); +// } + +// export async function getAllUsers(read: IRead): Promise { +// const data = await read.getPersistenceReader().readByAssociation(assoc); +// return (data.length ? data[0] as IUser[] : []); +// } + +// function isUserPresent(users: IUser[], targetUser: IUser): boolean { +// return users.some((user) => user.id === targetUser.id); +// } + +/** + * This function needed to be copied from the apps engine due to difficulties trying to + * get access to the auth client from inside a job processor. + * @NOTE It relies on hardcoded information (config alias's suffix) to work and it might break if + * the value changes + */ + +export async function getAccessTokenForUser(read: IRead, user: IUser, config: IOAuth2ClientOptions): Promise { + const associations = [ + new RocketChatAssociationRecord( + RocketChatAssociationModel.USER, + user.id, + ), + new RocketChatAssociationRecord( + RocketChatAssociationModel.MISC, + `${config.alias}-oauth-connection`, + ), + ]; + + const [ result ] = await read.getPersistenceReader().readByAssociations(associations) as unknown as Array; + return result; +} + + + +export async function removeToken({ + userId, + persis, + config, +}: { + userId: string, + persis: IPersistence, + config: IOAuth2ClientOptions +}): Promise { + const [ result ] = await persis.removeByAssociations([ + new RocketChatAssociationRecord( + RocketChatAssociationModel.USER, + userId, + ), + new RocketChatAssociationRecord( + RocketChatAssociationModel.MISC, + `${config.alias}-oauth-connection`, + ), + ]) as unknown as Array; + + return result; +} + + +export async function revokeUserAccessToken(read:IRead,user: IUser, persis: IPersistence, http:IHttp, config: IOAuth2ClientOptions): Promise { + try { + const tokenInfo = await getAccessTokenForUser(read,user,config); + + if (!tokenInfo?.token) { + throw new Error('No access token available for this user.'); + } + + //fix revoking token + + // let client_id = await read.getEnvironmentReader().getSettings().getValueById(`${config.alias}-oauth-client-id`); + // const url = new URL(`https://api.github.com/applications/${client_id}/token`); + + // url.searchParams.set('token', tokenInfo?.token); + // console.log(client_id); + // const result = await http.del(url.href); + + // if (result.statusCode !== 200) { + // console.log(result.content); + // throw new Error('Provider did not allow token to be revoked'); + // } + + await removeToken({ userId: user.id, persis,config }); + + return true; + } catch (error) { + console.log('revokeTokenError : ',error); + return false; + } +} diff --git a/github/persistance/users.ts b/github/persistance/users.ts deleted file mode 100644 index 2d6557f..0000000 --- a/github/persistance/users.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { IPersistence, IRead } from "@rocket.chat/apps-engine/definition/accessors"; -import { RocketChatAssociationModel, RocketChatAssociationRecord } from "@rocket.chat/apps-engine/definition/metadata"; -import { IAuthData, IOAuth2ClientOptions } from "@rocket.chat/apps-engine/definition/oauth2/IOAuth2"; -import { IUser } from "@rocket.chat/apps-engine/definition/users"; - -const assoc = new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, 'users'); - -export async function create(read: IRead, persistence: IPersistence, user: IUser): Promise { - const users = await getAllUsers(read); - - if (!users) { - await persistence.createWithAssociation([user], assoc); - return; - } - - if (!isUserPresent(users, user)) { - users.push(user); - await persistence.updateByAssociation(assoc, users); - } -} - -export async function remove(read: IRead, persistence: IPersistence, user: IUser): Promise { - const users = await getAllUsers(read); - - if (!users || !isUserPresent(users, user)) { - // @NOTE do nothing - return; - } - - const idx = users.findIndex((u: IUser) => u.id === user.id); - users.splice(idx, 1); - await persistence.updateByAssociation(assoc, users); -} - -export async function getAllUsers(read: IRead): Promise { - const data = await read.getPersistenceReader().readByAssociation(assoc); - return (data.length ? data[0] as IUser[] : []); -} - -function isUserPresent(users: IUser[], targetUser: IUser): boolean { - return users.some((user) => user.id === targetUser.id); -} - -/** - * This function needed to be copied from the apps engine due to difficulties trying to - * get access to the auth client from inside a job processor. - * @NOTE It relies on hardcoded information (config alias's suffix) to work and it might break if - * the value changes - */ - -export async function getAccessTokenForUser(read: IRead, user: IUser, config: IOAuth2ClientOptions): Promise { - const associations = [ - new RocketChatAssociationRecord( - RocketChatAssociationModel.USER, - user.id, - ), - new RocketChatAssociationRecord( - RocketChatAssociationModel.MISC, - `${config.alias}-oauth-connection`, - ), - ]; - - const [ result ] = await read.getPersistenceReader().readByAssociations(associations) as unknown as Array; - - return result; -} diff --git a/github/processors/deleteOAthToken.ts b/github/processors/deleteOAthToken.ts new file mode 100644 index 0000000..6c2521f --- /dev/null +++ b/github/processors/deleteOAthToken.ts @@ -0,0 +1,39 @@ +import { + IHttp, + IModify, + IPersistence, + IRead, +} from "@rocket.chat/apps-engine/definition/accessors"; + +import { sendDirectMessage } from "../lib/message"; +import { IJobContext } from "@rocket.chat/apps-engine/definition/scheduler"; +import { IUser } from "@rocket.chat/apps-engine/definition/users"; +import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; +import { getAccessTokenForUser, revokeUserAccessToken } from "../persistance/auth"; +import { IOAuth2ClientOptions } from "@rocket.chat/apps-engine/definition/oauth2/IOAuth2"; + + +export async function deleteOathToken({user,config,read,modify,http,persis}: { + user:IUser,config:IOAuth2ClientOptions,read:IRead,modify:IModify,http:IHttp,persis:IPersistence +}) { + try { + + + let token = await getAccessTokenForUser(read,user,config) + if (token?.token) { + await revokeUserAccessToken(read,user,persis,http,config); + } + token = await getAccessTokenForUser(read,user,config) + await sendDirectMessage( + read, + modify, + user, + "GitHub Token Expired, Login to GitHub Again ! `/github login`", + persis + ); + } catch (error) { + console.log("deleteOathToken error : ", error); + } +} +//https://community.liaison.edge.rocketchat.digital/ +//rc-apps deploy --url https://community.liaison.edge.rocketchat.digital --username samad.yar.khan --password samad --update \ No newline at end of file From 4a7373a65446de6b6acffc45fa95238740f7f8f2 Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Wed, 29 Jun 2022 18:43:25 +0000 Subject: [PATCH 04/32] Add Revoke Token ToDO --- github/persistance/auth.ts | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/github/persistance/auth.ts b/github/persistance/auth.ts index a82dcdf..e796d13 100644 --- a/github/persistance/auth.ts +++ b/github/persistance/auth.ts @@ -1,4 +1,4 @@ -import { IHttp, IPersistence, IRead } from "@rocket.chat/apps-engine/definition/accessors"; +import { IHttp, IHttpRequest, IPersistence, IRead } from "@rocket.chat/apps-engine/definition/accessors"; import { RocketChatAssociationModel, RocketChatAssociationRecord } from "@rocket.chat/apps-engine/definition/metadata"; import { IAuthData, IOAuth2ClientOptions } from "@rocket.chat/apps-engine/definition/oauth2/IOAuth2"; import { IUser } from "@rocket.chat/apps-engine/definition/users"; @@ -98,23 +98,26 @@ export async function revokeUserAccessToken(read:IRead,user: IUser, persis: IPer if (!tokenInfo?.token) { throw new Error('No access token available for this user.'); } - - //fix revoking token - - // let client_id = await read.getEnvironmentReader().getSettings().getValueById(`${config.alias}-oauth-client-id`); - // const url = new URL(`https://api.github.com/applications/${client_id}/token`); - - // url.searchParams.set('token', tokenInfo?.token); - // console.log(client_id); - // const result = await http.del(url.href); - - // if (result.statusCode !== 200) { - // console.log(result.content); - // throw new Error('Provider did not allow token to be revoked'); - // } + /*****fix revoking token***** + let client_id = await read.getEnvironmentReader().getSettings().getValueById(`${config.alias}-oauth-client-id`); + let client_secret = await read.getEnvironmentReader().getSettings().getValueById(`github-app-oauth-clientsecret`); + const url = new URL(`https://api.github.com/applications/${client_id}/token`); + // https://docs.github.com/en/rest/apps/oauth-applications + const headers: any = { + Accept: "application/vnd.github.v3+json", + Authorization: `Bearer ${tokenInfo?.token}`, + }; + const body : any= { + "access_token":`${tokenInfo?.token}` + } + const result = await http.del(url.href,{headers,data:body}); + if (result.statusCode !== 200) { + console.log(result.content); + throw new Error('Provider did not allow token to be revoked'); + } + ******/ await removeToken({ userId: user.id, persis,config }); - return true; } catch (error) { console.log('revokeTokenError : ',error); From 94292972c1002e24f011b36ad32271cd65c4b0e6 Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Sat, 2 Jul 2022 16:10:34 +0000 Subject: [PATCH 05/32] Add github sdk for event subscriptions --- github/commands/GithubCommand.ts | 30 +++++++++++- github/enum/Subcommands.ts | 3 +- github/helpers/getWebhookURL.ts | 12 +++++ github/helpers/githubSDK.ts | 74 +++++++++++++++++++++++++++++ github/lib/githubSDK.ts | 37 --------------- github/oath2/oath2Config.ts | 3 -- github/persistance/subscriptions.ts | 30 ++++++++++++ 7 files changed, 147 insertions(+), 42 deletions(-) create mode 100644 github/helpers/getWebhookURL.ts create mode 100644 github/helpers/githubSDK.ts delete mode 100644 github/lib/githubSDK.ts create mode 100644 github/persistance/subscriptions.ts diff --git a/github/commands/GithubCommand.ts b/github/commands/GithubCommand.ts index db1d7c8..d356fdf 100644 --- a/github/commands/GithubCommand.ts +++ b/github/commands/GithubCommand.ts @@ -20,6 +20,8 @@ import { SubcommandEnum } from "../enum/Subcommands"; import { getAccessTokenForUser } from "../persistance/auth"; import { IUser } from "@rocket.chat/apps-engine/definition/users"; import { removeToken } from "../persistance/auth"; +import { getWebhookUrl } from "../helpers/getWebhookURL"; +import { githubWebHooks } from "../endpoints/githubEndpoints"; export class GithubCommand implements ISlashCommand { @@ -65,9 +67,19 @@ export class GithubCommand implements ISlashCommand { break; } case SubcommandEnum.TEST : { + let a = await getWebhookUrl(this.app); + console.log(a); //test command break; } + case SubcommandEnum.SUBSCRIBE :{ + //modal + break; + } + case SubcommandEnum.UNSUBSCRIBE :{ + //modal + break; + } default:{ await helperMessage({room,read, persistence, modify, http}); break; @@ -77,10 +89,26 @@ export class GithubCommand implements ISlashCommand { break; } case 2 : { + const repository = command[0]; const query = command[1]; - await basicQueryMessage ({query,repository,room,read,persistence,modify,http}); + + switch(query){ + case SubcommandEnum.SUBSCRIBE : { + //sub + break; + } + case SubcommandEnum.UNSUBSCRIBE : { + //unsub + break; + } + default:{ + await basicQueryMessage ({query,repository,room,read,persistence,modify,http}); + break; + } + } break; + } case 3 :{ const data = { diff --git a/github/enum/Subcommands.ts b/github/enum/Subcommands.ts index 02bb312..0a266b0 100644 --- a/github/enum/Subcommands.ts +++ b/github/enum/Subcommands.ts @@ -1,6 +1,7 @@ export enum SubcommandEnum { LOGIN = 'login', HELP = 'help', - CONNECT = 'connect', + SUBSCRIBE = 'subscribe', + UNSUBSCRIBE = 'unsubscribe', TEST = 'test' } \ No newline at end of file diff --git a/github/helpers/getWebhookURL.ts b/github/helpers/getWebhookURL.ts new file mode 100644 index 0000000..d967dbe --- /dev/null +++ b/github/helpers/getWebhookURL.ts @@ -0,0 +1,12 @@ +import { IApiEndpointMetadata } from '@rocket.chat/apps-engine/definition/api'; +import { GithubApp } from '../GithubApp'; + +export async function getWebhookUrl(app: GithubApp): Promise { + const accessors = app.getAccessors(); + const webhookEndpoint = accessors.providedApiEndpoints.find((endpoint) => endpoint.path === 'githubwebhook') as IApiEndpointMetadata; + let siteUrl : string = await accessors.environmentReader.getServerSettings().getValueById('Site_Url') as string; + if(siteUrl.charAt(siteUrl.length - 1) === '/'){ + siteUrl = siteUrl.substring(0,siteUrl.length-1); + } + return siteUrl + webhookEndpoint.computedPath; +} \ No newline at end of file diff --git a/github/helpers/githubSDK.ts b/github/helpers/githubSDK.ts new file mode 100644 index 0000000..ffd1b95 --- /dev/null +++ b/github/helpers/githubSDK.ts @@ -0,0 +1,74 @@ +import { IHttp } from "@rocket.chat/apps-engine/definition/accessors"; + +const BaseHost = "https://github.com/"; +const BaseApiHost = "https://api.github.com/repos/"; + +async function postReqeust( + http: IHttp, + accessToken: String, + url: string, + data: any +): Promise { + const response = await http.post(url, { + headers: { + Authorization: `token ${accessToken}`, + "Content-Type": "application/json", + "User-Agent": "Rocket.Chat-Apps-Engine", + }, + data, + }); + + // If it isn't a 2xx code, something wrong happened + if (!response.statusCode.toString().startsWith("2")) { + throw response; + } + + return JSON.parse(response.content || "{}"); +} + +async function deleteReqeust( + http: IHttp, + accessToken: String, + url: string +): Promise { + const response = await http.del(url, { + headers: { + Authorization: `token ${accessToken}`, + "Content-Type": "application/json", + "User-Agent": "Rocket.Chat-Apps-Engine", + } + }); + + // If it isn't a 2xx code, something wrong happened + if (!response.statusCode.toString().startsWith("2")) { + throw response; + } + + return JSON.parse(response.content || "{}"); +} + +export async function createSubscription( + http: IHttp, + repoName: string, + webhookUrl: string, + access_token: string, + events: Array +) { + return postReqeust(http, access_token, BaseApiHost + repoName + "/hooks", { + active: true, + events: events, + config: { + url: webhookUrl, + content_type: "json", + }, + }); +} + +export async function deleteSubscription( + http: IHttp, + repoName: string, + access_token: string, + hookId: string +) { + return deleteReqeust(http, access_token, BaseApiHost + repoName + "/hooks/" + hookId,); +} diff --git a/github/lib/githubSDK.ts b/github/lib/githubSDK.ts deleted file mode 100644 index ad39ef5..0000000 --- a/github/lib/githubSDK.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { IHttp } from '@rocket.chat/apps-engine/definition/accessors'; - -const BaseHost = 'https://github.com/'; -const BaseApiHost = 'https://api.github.com/repos/'; - -export class GithubSDK { - constructor(private readonly http: IHttp, private readonly accessToken) {} - - public createWebhook(repoName: string, webhookUrl: string) { - return this.post(BaseApiHost + repoName + '/hooks', { - active: true, - events: ['push'], - config: { - url: webhookUrl, - content_type: 'json', - }, - }); - } - - private async post(url: string, data: any): Promise { - const response = await this.http.post(url, { - headers: { - 'Authorization': `Bearer ${this.accessToken}`, - 'Content-Type': 'application/json', - 'User-Agent': 'Rocket.Chat-Apps-Engine', - }, - data, - }); - - // If it isn't a 2xx code, something wrong happened - if (!response.statusCode.toString().startsWith('2')) { - throw response; - } - - return JSON.parse(response.content || '{}'); - } -} \ No newline at end of file diff --git a/github/oath2/oath2Config.ts b/github/oath2/oath2Config.ts index 6976136..6c412e9 100644 --- a/github/oath2/oath2Config.ts +++ b/github/oath2/oath2Config.ts @@ -3,7 +3,6 @@ import { GithubApp } from "../GithubApp"; import authorizationCallback from "./oath2callback" export default function getOauth2Config(app:GithubApp):IOAuth2ClientOptions{ - let oauth2Config: IOAuth2ClientOptions = { alias: "github-app", accessTokenUri: "https://github.com/login/oauth/access_token", @@ -13,8 +12,6 @@ export default function getOauth2Config(app:GithubApp):IOAuth2ClientOptions{ authorizationCallback: authorizationCallback.bind(app), defaultScopes: ["users", "repo"], }; - return oauth2Config; - } diff --git a/github/persistance/subscriptions.ts b/github/persistance/subscriptions.ts new file mode 100644 index 0000000..c0d510f --- /dev/null +++ b/github/persistance/subscriptions.ts @@ -0,0 +1,30 @@ +import { IPersistence, IPersistenceRead } from '@rocket.chat/apps-engine/definition/accessors'; +import { RocketChatAssociationModel, RocketChatAssociationRecord } from '@rocket.chat/apps-engine/definition/metadata'; +import { IRoom } from '@rocket.chat/apps-engine/definition/rooms'; +import { IUser } from '@rocket.chat/apps-engine/definition/users'; + +export class Subscription { + constructor(private readonly persistence: IPersistence, private readonly persistenceRead: IPersistenceRead) {} + + public async createSubscription(repoName: string, room: IRoom): Promise { + const roomAssociation = new RocketChatAssociationRecord(RocketChatAssociationModel.ROOM, room.id); + const repoAssociation = new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `repo:${repoName}`); + + await this.persistence.updateByAssociations([roomAssociation, repoAssociation], { + repoName, + room: room.id, + }, true); + } + + + + public async getSubscriptionRoom(repoName: string): Promise { + const repoAssociation = new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `repo:${repoName}`); + + const [result] = await this.persistenceRead.readByAssociations([repoAssociation]); + + return result ? (result as any).room : undefined; + } + + +} \ No newline at end of file From caca3bd089b7677168d70e47d67b158fe6d6c089 Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Sun, 3 Jul 2022 09:13:46 +0000 Subject: [PATCH 06/32] updat esubscribe logic --- github/commands/GithubCommand.ts | 26 +++++++++++++++++++------- github/modals/subscriptionsModal.ts | 0 2 files changed, 19 insertions(+), 7 deletions(-) create mode 100644 github/modals/subscriptionsModal.ts diff --git a/github/commands/GithubCommand.ts b/github/commands/GithubCommand.ts index d356fdf..29aebac 100644 --- a/github/commands/GithubCommand.ts +++ b/github/commands/GithubCommand.ts @@ -22,6 +22,8 @@ import { IUser } from "@rocket.chat/apps-engine/definition/users"; import { removeToken } from "../persistance/auth"; import { getWebhookUrl } from "../helpers/getWebhookURL"; import { githubWebHooks } from "../endpoints/githubEndpoints"; +import { sendDirectMessage } from "../lib/message"; +import { createSubscription } from "../helpers/githubSDK"; export class GithubCommand implements ISlashCommand { @@ -67,9 +69,9 @@ export class GithubCommand implements ISlashCommand { break; } case SubcommandEnum.TEST : { - let a = await getWebhookUrl(this.app); - console.log(a); - //test command + // let a = await getWebhookUrl(this.app); + // console.log(a); + //test command break; } case SubcommandEnum.SUBSCRIBE :{ @@ -96,6 +98,17 @@ export class GithubCommand implements ISlashCommand { switch(query){ case SubcommandEnum.SUBSCRIBE : { //sub + let accessToken = await getAccessTokenForUser(read,context.getSender(),this.app.oauth2Config); + if(accessToken){ + try { + let url = await getWebhookUrl(this.app); + await createSubscription(http,repository,url,accessToken.token,["pull_request","push","issues"]); + } catch (error) { + console.log("SubcommandError",error); + } + } else{ + await sendDirectMessage(read,modify,context.getSender(),"Login to subscribe to repository events ! `/github login`",persistence); + } break; } case SubcommandEnum.UNSUBSCRIBE : { @@ -107,11 +120,10 @@ export class GithubCommand implements ISlashCommand { break; } } - break; - + break; } - case 3 :{ - const data = { + case 3 : { + const data = { repository:command[0], query:command[1], number:command[2] diff --git a/github/modals/subscriptionsModal.ts b/github/modals/subscriptionsModal.ts new file mode 100644 index 0000000..e69de29 From e63008e17ba18944da8e7a750e5adf8c9aa569ff Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Sun, 3 Jul 2022 18:01:23 +0530 Subject: [PATCH 07/32] Add subscription persitence logic --- github/commands/GithubCommand.ts | 1 + github/definitions/subscription.ts | 8 +++ github/persistance/subscriptions.ts | 94 ++++++++++++++++++++++++----- 3 files changed, 89 insertions(+), 14 deletions(-) create mode 100644 github/definitions/subscription.ts diff --git a/github/commands/GithubCommand.ts b/github/commands/GithubCommand.ts index 29aebac..4d64316 100644 --- a/github/commands/GithubCommand.ts +++ b/github/commands/GithubCommand.ts @@ -103,6 +103,7 @@ export class GithubCommand implements ISlashCommand { try { let url = await getWebhookUrl(this.app); await createSubscription(http,repository,url,accessToken.token,["pull_request","push","issues"]); + } catch (error) { console.log("SubcommandError",error); } diff --git a/github/definitions/subscription.ts b/github/definitions/subscription.ts new file mode 100644 index 0000000..7daa4b7 --- /dev/null +++ b/github/definitions/subscription.ts @@ -0,0 +1,8 @@ +//subsciptions which will be saved in the apps local storage +export interface ISubscription{ + webhookId : string, + user: string, + repoName : string, + room : string, + event: string +} \ No newline at end of file diff --git a/github/persistance/subscriptions.ts b/github/persistance/subscriptions.ts index c0d510f..3794b33 100644 --- a/github/persistance/subscriptions.ts +++ b/github/persistance/subscriptions.ts @@ -2,29 +2,95 @@ import { IPersistence, IPersistenceRead } from '@rocket.chat/apps-engine/definit import { RocketChatAssociationModel, RocketChatAssociationRecord } from '@rocket.chat/apps-engine/definition/metadata'; import { IRoom } from '@rocket.chat/apps-engine/definition/rooms'; import { IUser } from '@rocket.chat/apps-engine/definition/users'; +import { ISubscription } from '../definitions/subscription'; export class Subscription { - constructor(private readonly persistence: IPersistence, private readonly persistenceRead: IPersistenceRead) {} + constructor(private readonly persistence: IPersistence, private readonly persistenceRead: IPersistenceRead, private readonly user: IUser) { } - public async createSubscription(repoName: string, room: IRoom): Promise { - const roomAssociation = new RocketChatAssociationRecord(RocketChatAssociationModel.ROOM, room.id); - const repoAssociation = new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `repo:${repoName}`); + public async createSubscription(repoName: string, event: string, webhookId: string, room: IRoom): Promise { - await this.persistence.updateByAssociations([roomAssociation, repoAssociation], { - repoName, - room: room.id, - }, true); + try { + const associations: Array = [ + new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `subsciption`), + new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `repo:${repoName}`), + new RocketChatAssociationRecord(RocketChatAssociationModel.ROOM, room.id), + new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, event), + new RocketChatAssociationRecord(RocketChatAssociationModel.USER, `${this.user.id}`) + ]; + let subscriptionRecord: ISubscription = { + webhookId: webhookId, + user: this.user.id, + repoName: repoName, + room: room.id, + event: event + } + await this.persistence.updateByAssociations(associations, subscriptionRecord, true); + + } catch (error) { + console.warn('Subsciption Error :', error) + return false; + } + return true; } - + public async getSubscribedRooms(repoName: string, event: string): Promise> { + try { + const associations: Array = [ + new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `subsciption`), + new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `repo:${repoName}`), + new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, event), + ]; + let subsciptions: Array = await this.persistenceRead.readByAssociations(associations) as Array; + return subsciptions; + } catch (error) { + console.warn('Get Subscribed Rooms Error :', error) + let subsciptions: Array = []; + return subsciptions; + } + } - public async getSubscriptionRoom(repoName: string): Promise { - const repoAssociation = new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `repo:${repoName}`); + public async getSubscriptions(roomId: string): Promise> { + try { + const associations: Array = [ + new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `subsciption`), + new RocketChatAssociationRecord(RocketChatAssociationModel.ROOM, roomId), + ]; + let subsciptions: Array = await this.persistenceRead.readByAssociations(associations) as Array; + return subsciptions; + } catch (error) { + console.warn('Get Subsciption Error :', error) + let subsciptions: Array = []; + return subsciptions; + } + } - const [result] = await this.persistenceRead.readByAssociations([repoAssociation]); + public async deleteSubscriptions(repoName: string, event: string): Promise { + try { + const associations: Array = [ + new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `subsciption`), + new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `repo:${repoName}`), + new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, event), + ]; + await this.persistence.removeByAssociations(associations); + } catch (error) { + console.warn('Delete Subsciption Error :', error) + return false; + } + return true; + } - return result ? (result as any).room : undefined; + public async deleteAllRoomSubscriptions(roomId: string): Promise { + try { + const associations: Array = [ + new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `subsciption`), + new RocketChatAssociationRecord(RocketChatAssociationModel.ROOM, roomId), + ]; + await this.persistence.removeByAssociations(associations); + } catch (error) { + console.warn('Delete All Room Subsciption Error :', error) + return false; + } + return true; } - } \ No newline at end of file From 9c89c2829816cb0a096c42a0a26ca8dad311cbe3 Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Sun, 3 Jul 2022 21:58:05 +0530 Subject: [PATCH 08/32] Add slash command subscription logic --- github/commands/GithubCommand.ts | 20 +++- github/endpoints/githubEndpoints.ts | 49 ++++++++- github/handlers/ExecuteBlockActionHandler.ts | 3 +- github/lib/initiatorMessage.ts | 4 +- github/modals/subscriptionsModal.ts | 107 +++++++++++++++++++ github/persistance/subscriptions.ts | 18 ++-- 6 files changed, 185 insertions(+), 16 deletions(-) diff --git a/github/commands/GithubCommand.ts b/github/commands/GithubCommand.ts index 4d64316..61aa6c2 100644 --- a/github/commands/GithubCommand.ts +++ b/github/commands/GithubCommand.ts @@ -22,8 +22,10 @@ import { IUser } from "@rocket.chat/apps-engine/definition/users"; import { removeToken } from "../persistance/auth"; import { getWebhookUrl } from "../helpers/getWebhookURL"; import { githubWebHooks } from "../endpoints/githubEndpoints"; -import { sendDirectMessage } from "../lib/message"; +import { sendDirectMessage, sendNotification } from "../lib/message"; import { createSubscription } from "../helpers/githubSDK"; +import { Subscription } from "../persistance/subscriptions"; + export class GithubCommand implements ISlashCommand { @@ -101,9 +103,23 @@ export class GithubCommand implements ISlashCommand { let accessToken = await getAccessTokenForUser(read,context.getSender(),this.app.oauth2Config); if(accessToken){ try { + let events: Array =["pull_request","push","issues"]; let url = await getWebhookUrl(this.app); - await createSubscription(http,repository,url,accessToken.token,["pull_request","push","issues"]); + let response = await createSubscription(http,repository,url,accessToken.token,events); + let subsciptionStorage = new Subscription(persistence,read.getPersistenceReader()) + let createdEntry = false ; + for(let event of events){ + createdEntry= await subsciptionStorage.createSubscription(repository,event,response?.id,room,context.getSender()); + } + if(!createdEntry){ + throw new Error("Error creating new susbcription entry"); + } + + + await sendNotification(read,modify,context.getSender(),room,`Subscibed to ${repository} ✔️`); + + } catch (error) { console.log("SubcommandError",error); } diff --git a/github/endpoints/githubEndpoints.ts b/github/endpoints/githubEndpoints.ts index e62421e..89d9c95 100644 --- a/github/endpoints/githubEndpoints.ts +++ b/github/endpoints/githubEndpoints.ts @@ -1,6 +1,9 @@ import { ApiEndpoint } from "@rocket.chat/apps-engine/definition/api" import { IRead, IHttp, IModify,IPersistence } from "@rocket.chat/apps-engine/definition/accessors"; import { IApiEndpointInfo,IApiEndpoint, IApiRequest,IApiResponse } from "@rocket.chat/apps-engine/definition/api"; +import { Subscription } from "../persistance/subscriptions"; +import { ISubscription } from "../definitions/subscription"; +import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; export class githubWebHooks extends ApiEndpoint{ public path = 'githubwebhook' @@ -12,8 +15,52 @@ export class githubWebHooks extends ApiEndpoint{ http: IHttp, persis: IPersistence, ): Promise { - // await this.handleEvent(request, read, modify); console.log(request.content.toString()); + + let event: string = request.headers['x-github-event'] as string; + + let payload: any; + + if (request.headers['content-type'] === 'application/x-www-form-urlencoded') { + payload = JSON.parse(request.content.payload); + } else { + payload = request.content; + } + + let subsciptionStorage = new Subscription(persis,read.getPersistenceReader()) + + const subsciptions: Array = await subsciptionStorage.getSubscribedRooms(payload.repository.full_name,event); + if(!subsciptions || subsciptions.length==0){ + return this.success(); + } + const eventCaps= event.toUpperCase(); + let messageText = "newEvent !"; + + if(event=='push'){ + messageText = `*New Commits to* *[${payload.repository.full_name}](${payload.repository.html_url}) by ${payload.pusher.name}*`; + }else if (event == 'pull_request'){ + messageText = `*[New Pull Reqeust](${payload.pull_request.html_url})* *|* *#${payload.pull_request.number} ${payload.pull_request.title}* by *[${payload.user.login}](${payload.user.html_url})* *|* *[${payload.repository.full_name}]*`; + }else if (event == 'issues'){ + messageText = `*[New Issue](${payload.issue.html_url})* *|* *#${payload.issue.number}* *${payload.issue.title}* *|* *[${payload.repository.full_name}](${payload.repository.html_url})* `; + } + + for(let subsciption of subsciptions){ + let roomId = subsciption.room; + if(!roomId){ + continue; + } + const room :IRoom= await read.getRoomReader().getById(roomId) as IRoom; + const textSender = await modify + .getCreator() + .startMessage() + .setText(messageText); + if (room) { + textSender.setRoom(room); + } + await modify.getCreator().finish(textSender); + + } + return this.success(); } diff --git a/github/handlers/ExecuteBlockActionHandler.ts b/github/handlers/ExecuteBlockActionHandler.ts index 261a9e9..2813890 100644 --- a/github/handlers/ExecuteBlockActionHandler.ts +++ b/github/handlers/ExecuteBlockActionHandler.ts @@ -30,7 +30,6 @@ export class ExecuteBlockActionHandler { const data = context.getInteractionData(); const { actionId } = data; - switch (actionId) { case "githubDataSelect": { try { @@ -55,7 +54,7 @@ export class ExecuteBlockActionHandler { const room: IRoom = context.getInteractionData() .room as IRoom; - + console.log("PRESS",query); await basicQueryMessage({ query, repository, diff --git a/github/lib/initiatorMessage.ts b/github/lib/initiatorMessage.ts index 702bb65..5c3f332 100644 --- a/github/lib/initiatorMessage.ts +++ b/github/lib/initiatorMessage.ts @@ -25,8 +25,8 @@ export async function initiatorMessage({ .getCreator() .startMessage() .setRoom(data.room) - .setText(`Hey _${data.sender.username}_ !`); - + .setText(`Hey ${data.sender.username} !`); + if (data.room.type !== "l") { await modify .getNotifier() diff --git a/github/modals/subscriptionsModal.ts b/github/modals/subscriptionsModal.ts index e69de29..37e7f1e 100644 --- a/github/modals/subscriptionsModal.ts +++ b/github/modals/subscriptionsModal.ts @@ -0,0 +1,107 @@ +import { IHttp, IModify, IPersistence, IRead } from '@rocket.chat/apps-engine/definition/accessors'; +import { TextObjectType } from '@rocket.chat/apps-engine/definition/uikit/blocks'; +import { IUIKitModalViewParam } from '@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder'; +import { IUser } from '@rocket.chat/apps-engine/definition/users'; +import { ModalsEnum } from '../enum/Modals'; +import { AppEnum } from '../enum/App'; +// import { getRoomTasks, getUIData, persistUIData } from '../lib/persistence'; +import { SlashCommandContext } from '@rocket.chat/apps-engine/definition/slashcommands'; +import { UIKitBlockInteractionContext, UIKitInteractionContext } from '@rocket.chat/apps-engine/definition/uikit'; + +export async function pullDetailsModal({ data, modify, read, persistence, http, slashcommandcontext, uikitcontext }: { data?, modify: IModify, read: IRead, persistence: IPersistence, http: IHttp ,slashcommandcontext?: SlashCommandContext, uikitcontext?: UIKitInteractionContext }): Promise { + const viewId = ModalsEnum.PULL_VIEW; + + const block = modify.getCreator().getBlockBuilder(); + + const room = slashcommandcontext?.getRoom() || uikitcontext?.getInteractionData().room; + const user = slashcommandcontext?.getSender() || uikitcontext?.getInteractionData().user; + + if (user?.id) { + let roomId; + + const pullRawData = await http.get( + `https://api.github.com/repos/${data?.repository}/pulls/${data?.number}` + ); + const pullData = pullRawData.data; + + const pullRequestFilesRaw = await http.get( + `https://api.github.com/repos/${data?.repository}/pulls/${data?.number}/files` + ); + + const pullRequestFiles= pullRequestFilesRaw.data; + + block.addSectionBlock({ + text: { text: `#${pullData?.title}`, type: TextObjectType.PLAINTEXT }, + accessory: block.newButtonElement({ + actionId: ModalsEnum.VIEW_FILE_ACTION, + text: { + text: ModalsEnum.VIEW_DIFFS_ACTION_LABEL, + type: TextObjectType.PLAINTEXT + }, + value: pullData["diff_url"] + }) + }) + block.addContextBlock({ elements: [ block.newPlainTextObject(`Author: ${pullData?.user?.login} | `),block.newPlainTextObject(`State : ${pullData?.state} | `),block.newPlainTextObject(`Mergeable : ${pullData?.mergeable}`) ]}); + + block.addDividerBlock(); + + let index=1; + + + + for (let file of pullRequestFiles) { + + let fileName = file["filename"]; + let rawUrl = file["raw_url"]; + let status = file["status"]; + let addition = file["additions"]; + let deletions = file["deletions"]; + block.addSectionBlock({ + text: { text: `${index} ${fileName}`, type: TextObjectType.PLAINTEXT }, + accessory: block.newButtonElement({ + actionId: ModalsEnum.VIEW_FILE_ACTION, + text: { + text: ModalsEnum.VIEW_FILE_ACTION_LABEL, + type: TextObjectType.PLAINTEXT + }, + value: rawUrl + }) + }); + block.addContextBlock({ elements: [ block.newPlainTextObject(`Status: ${status} | `),block.newPlainTextObject(`Additions : ${addition} | `),block.newPlainTextObject(`Deletions : ${deletions}`) ]}); + + index++; + } + } + + + + block.addActionsBlock({ + elements: [ + block.newButtonElement({ + actionId: ModalsEnum.MERGE_PULL_REQUEST_ACTION, + text: { text: ModalsEnum.MERGE_PULL_REQUEST_LABEL, type: TextObjectType.PLAINTEXT }, + value: room?.id + }), + block.newButtonElement({ + actionId: ModalsEnum.COMMENT_PR_ACTION, + text: { text: ModalsEnum.COMMENT_PR_LABEL, type: TextObjectType.PLAINTEXT }, + value: room?.id + }), + ] + }); + + return { + id: viewId, + title: { + type: TextObjectType.PLAINTEXT, + text: AppEnum.DEFAULT_TITLE, + }, + close: block.newButtonElement({ + text: { + type: TextObjectType.PLAINTEXT, + text: 'Close', + }, + }), + blocks: block.getBlocks(), + }; +} \ No newline at end of file diff --git a/github/persistance/subscriptions.ts b/github/persistance/subscriptions.ts index 3794b33..f600009 100644 --- a/github/persistance/subscriptions.ts +++ b/github/persistance/subscriptions.ts @@ -5,21 +5,21 @@ import { IUser } from '@rocket.chat/apps-engine/definition/users'; import { ISubscription } from '../definitions/subscription'; export class Subscription { - constructor(private readonly persistence: IPersistence, private readonly persistenceRead: IPersistenceRead, private readonly user: IUser) { } + constructor(private readonly persistence: IPersistence, private readonly persistenceRead: IPersistenceRead) { } - public async createSubscription(repoName: string, event: string, webhookId: string, room: IRoom): Promise { + public async createSubscription(repoName: string, event: string, webhookId: string, room: IRoom, user: IUser): Promise { try { const associations: Array = [ - new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `subsciption`), + new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `subscription`), new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `repo:${repoName}`), new RocketChatAssociationRecord(RocketChatAssociationModel.ROOM, room.id), new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, event), - new RocketChatAssociationRecord(RocketChatAssociationModel.USER, `${this.user.id}`) + new RocketChatAssociationRecord(RocketChatAssociationModel.USER, `${user.id}`) ]; let subscriptionRecord: ISubscription = { webhookId: webhookId, - user: this.user.id, + user: user.id, repoName: repoName, room: room.id, event: event @@ -36,7 +36,7 @@ export class Subscription { public async getSubscribedRooms(repoName: string, event: string): Promise> { try { const associations: Array = [ - new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `subsciption`), + new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `subscription`), new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `repo:${repoName}`), new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, event), ]; @@ -52,7 +52,7 @@ export class Subscription { public async getSubscriptions(roomId: string): Promise> { try { const associations: Array = [ - new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `subsciption`), + new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `subscription`), new RocketChatAssociationRecord(RocketChatAssociationModel.ROOM, roomId), ]; let subsciptions: Array = await this.persistenceRead.readByAssociations(associations) as Array; @@ -67,7 +67,7 @@ export class Subscription { public async deleteSubscriptions(repoName: string, event: string): Promise { try { const associations: Array = [ - new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `subsciption`), + new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `subscription`), new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `repo:${repoName}`), new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, event), ]; @@ -82,7 +82,7 @@ export class Subscription { public async deleteAllRoomSubscriptions(roomId: string): Promise { try { const associations: Array = [ - new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `subsciption`), + new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `subscription`), new RocketChatAssociationRecord(RocketChatAssociationModel.ROOM, roomId), ]; await this.persistence.removeByAssociations(associations); From fe0615e721c650bda85f0bc42169b29b43977976 Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Mon, 4 Jul 2022 00:47:32 +0530 Subject: [PATCH 09/32] unsubscribe logic --- github/commands/GithubCommand.ts | 35 ++++++++++++++++++++++++++++- github/persistance/subscriptions.ts | 5 +++-- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/github/commands/GithubCommand.ts b/github/commands/GithubCommand.ts index 61aa6c2..793d86d 100644 --- a/github/commands/GithubCommand.ts +++ b/github/commands/GithubCommand.ts @@ -23,8 +23,9 @@ import { removeToken } from "../persistance/auth"; import { getWebhookUrl } from "../helpers/getWebhookURL"; import { githubWebHooks } from "../endpoints/githubEndpoints"; import { sendDirectMessage, sendNotification } from "../lib/message"; -import { createSubscription } from "../helpers/githubSDK"; +import { createSubscription, deleteSubscription } from "../helpers/githubSDK"; import { Subscription } from "../persistance/subscriptions"; +import { ISubscription } from "../definitions/subscription"; @@ -130,6 +131,38 @@ export class GithubCommand implements ISlashCommand { } case SubcommandEnum.UNSUBSCRIBE : { //unsub + let accessToken = await getAccessTokenForUser(read,context.getSender(),this.app.oauth2Config); + if(accessToken){ + try { + let events: Array =["pull_request","push","issues"]; + let subsciptionStorage = new Subscription(persistence,read.getPersistenceReader()) + let roomSubscriptions: Array = await subsciptionStorage.getSubscriptions(room.id); + let hooksMap=new Map; + for(let event of events){ + for(let subscription of roomSubscriptions){ + let webhookId = subscription.webhookId; + if(subscription.repoName!==repository || subscription.event!==event || hooksMap.has(webhookId)){ + //skip entry if event and repo name doesnt match or if hook has been deleted + continue; + } + + hooksMap.set(webhookId,true); + await deleteSubscription(http,repository,accessToken.token,webhookId); + let deleted = await subsciptionStorage.deleteSubscriptions(repository,event,room.id); + if(!deleted){ + console.log("Cant delete unsubsribed hook"); + } + } + } + + await sendNotification(read,modify,context.getSender(),room,`Unsubscibed ${repository} ✔️`); + + } catch (error) { + console.log("SubcommandError",error); + } + } else{ + await sendDirectMessage(read,modify,context.getSender(),"Login to subscribe to repository events ! `/github login`",persistence); + } break; } default:{ diff --git a/github/persistance/subscriptions.ts b/github/persistance/subscriptions.ts index f600009..13ce622 100644 --- a/github/persistance/subscriptions.ts +++ b/github/persistance/subscriptions.ts @@ -64,12 +64,13 @@ export class Subscription { } } - public async deleteSubscriptions(repoName: string, event: string): Promise { + public async deleteSubscriptions(repoName: string, event: string, roomId: string): Promise { try { const associations: Array = [ new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `subscription`), new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `repo:${repoName}`), - new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, event), + new RocketChatAssociationRecord(RocketChatAssociationModel.ROOM, roomId), + new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, event) ]; await this.persistence.removeByAssociations(associations); } catch (error) { From 2b1186e57f5861785ec4801307613fa5a68275a3 Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Mon, 4 Jul 2022 13:35:25 +0530 Subject: [PATCH 10/32] Add Room Interaction perisstance functiosn for modals --- github/commands/GithubCommand.ts | 5 +- github/enum/Modals.ts | 2 + github/modals/subscriptionsModal.ts | 107 -------------------------- github/persistance/roomInteraction.ts | 20 +++++ 4 files changed, 25 insertions(+), 109 deletions(-) create mode 100644 github/persistance/roomInteraction.ts diff --git a/github/commands/GithubCommand.ts b/github/commands/GithubCommand.ts index 793d86d..ce95206 100644 --- a/github/commands/GithubCommand.ts +++ b/github/commands/GithubCommand.ts @@ -79,6 +79,7 @@ export class GithubCommand implements ISlashCommand { } case SubcommandEnum.SUBSCRIBE :{ //modal + break; } case SubcommandEnum.UNSUBSCRIBE :{ @@ -104,7 +105,7 @@ export class GithubCommand implements ISlashCommand { let accessToken = await getAccessTokenForUser(read,context.getSender(),this.app.oauth2Config); if(accessToken){ try { - let events: Array =["pull_request","push","issues"]; + let events: Array =["pull_request","push","issues","deployment_status"]; let url = await getWebhookUrl(this.app); let response = await createSubscription(http,repository,url,accessToken.token,events); let subsciptionStorage = new Subscription(persistence,read.getPersistenceReader()) @@ -134,7 +135,7 @@ export class GithubCommand implements ISlashCommand { let accessToken = await getAccessTokenForUser(read,context.getSender(),this.app.oauth2Config); if(accessToken){ try { - let events: Array =["pull_request","push","issues"]; + let events: Array =["pull_request","push","issues","deployment_status"]; let subsciptionStorage = new Subscription(persistence,read.getPersistenceReader()) let roomSubscriptions: Array = await subsciptionStorage.getSubscriptions(room.id); let hooksMap=new Map; diff --git a/github/enum/Modals.ts b/github/enum/Modals.ts index a0d6153..315879f 100644 --- a/github/enum/Modals.ts +++ b/github/enum/Modals.ts @@ -11,4 +11,6 @@ export enum ModalsEnum { MERGE_PULL_REQUEST_LABEL = 'Merge', COMMENT_PR_ACTION = 'comment-pull-request', COMMENT_PR_LABEL = 'Comment', + + SUBSCRIPTION_VIEW = 'subsctiptions-view' } \ No newline at end of file diff --git a/github/modals/subscriptionsModal.ts b/github/modals/subscriptionsModal.ts index 37e7f1e..e69de29 100644 --- a/github/modals/subscriptionsModal.ts +++ b/github/modals/subscriptionsModal.ts @@ -1,107 +0,0 @@ -import { IHttp, IModify, IPersistence, IRead } from '@rocket.chat/apps-engine/definition/accessors'; -import { TextObjectType } from '@rocket.chat/apps-engine/definition/uikit/blocks'; -import { IUIKitModalViewParam } from '@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder'; -import { IUser } from '@rocket.chat/apps-engine/definition/users'; -import { ModalsEnum } from '../enum/Modals'; -import { AppEnum } from '../enum/App'; -// import { getRoomTasks, getUIData, persistUIData } from '../lib/persistence'; -import { SlashCommandContext } from '@rocket.chat/apps-engine/definition/slashcommands'; -import { UIKitBlockInteractionContext, UIKitInteractionContext } from '@rocket.chat/apps-engine/definition/uikit'; - -export async function pullDetailsModal({ data, modify, read, persistence, http, slashcommandcontext, uikitcontext }: { data?, modify: IModify, read: IRead, persistence: IPersistence, http: IHttp ,slashcommandcontext?: SlashCommandContext, uikitcontext?: UIKitInteractionContext }): Promise { - const viewId = ModalsEnum.PULL_VIEW; - - const block = modify.getCreator().getBlockBuilder(); - - const room = slashcommandcontext?.getRoom() || uikitcontext?.getInteractionData().room; - const user = slashcommandcontext?.getSender() || uikitcontext?.getInteractionData().user; - - if (user?.id) { - let roomId; - - const pullRawData = await http.get( - `https://api.github.com/repos/${data?.repository}/pulls/${data?.number}` - ); - const pullData = pullRawData.data; - - const pullRequestFilesRaw = await http.get( - `https://api.github.com/repos/${data?.repository}/pulls/${data?.number}/files` - ); - - const pullRequestFiles= pullRequestFilesRaw.data; - - block.addSectionBlock({ - text: { text: `#${pullData?.title}`, type: TextObjectType.PLAINTEXT }, - accessory: block.newButtonElement({ - actionId: ModalsEnum.VIEW_FILE_ACTION, - text: { - text: ModalsEnum.VIEW_DIFFS_ACTION_LABEL, - type: TextObjectType.PLAINTEXT - }, - value: pullData["diff_url"] - }) - }) - block.addContextBlock({ elements: [ block.newPlainTextObject(`Author: ${pullData?.user?.login} | `),block.newPlainTextObject(`State : ${pullData?.state} | `),block.newPlainTextObject(`Mergeable : ${pullData?.mergeable}`) ]}); - - block.addDividerBlock(); - - let index=1; - - - - for (let file of pullRequestFiles) { - - let fileName = file["filename"]; - let rawUrl = file["raw_url"]; - let status = file["status"]; - let addition = file["additions"]; - let deletions = file["deletions"]; - block.addSectionBlock({ - text: { text: `${index} ${fileName}`, type: TextObjectType.PLAINTEXT }, - accessory: block.newButtonElement({ - actionId: ModalsEnum.VIEW_FILE_ACTION, - text: { - text: ModalsEnum.VIEW_FILE_ACTION_LABEL, - type: TextObjectType.PLAINTEXT - }, - value: rawUrl - }) - }); - block.addContextBlock({ elements: [ block.newPlainTextObject(`Status: ${status} | `),block.newPlainTextObject(`Additions : ${addition} | `),block.newPlainTextObject(`Deletions : ${deletions}`) ]}); - - index++; - } - } - - - - block.addActionsBlock({ - elements: [ - block.newButtonElement({ - actionId: ModalsEnum.MERGE_PULL_REQUEST_ACTION, - text: { text: ModalsEnum.MERGE_PULL_REQUEST_LABEL, type: TextObjectType.PLAINTEXT }, - value: room?.id - }), - block.newButtonElement({ - actionId: ModalsEnum.COMMENT_PR_ACTION, - text: { text: ModalsEnum.COMMENT_PR_LABEL, type: TextObjectType.PLAINTEXT }, - value: room?.id - }), - ] - }); - - return { - id: viewId, - title: { - type: TextObjectType.PLAINTEXT, - text: AppEnum.DEFAULT_TITLE, - }, - close: block.newButtonElement({ - text: { - type: TextObjectType.PLAINTEXT, - text: 'Close', - }, - }), - blocks: block.getBlocks(), - }; -} \ No newline at end of file diff --git a/github/persistance/roomInteraction.ts b/github/persistance/roomInteraction.ts new file mode 100644 index 0000000..89824d0 --- /dev/null +++ b/github/persistance/roomInteraction.ts @@ -0,0 +1,20 @@ +import { IPersistence, IPersistenceRead } from '@rocket.chat/apps-engine/definition/accessors'; +import { RocketChatAssociationModel, RocketChatAssociationRecord } from '@rocket.chat/apps-engine/definition/metadata'; + +//functions needed ro persist room data while modal and other UI interactions + +export const storeInteractionRoomData = async (persistence: IPersistence, userId: string, roomId: string): Promise => { + const association = new RocketChatAssociationRecord(RocketChatAssociationModel.USER, `${ userId }#RoomId`); + await persistence.updateByAssociation(association, {roomId : roomId} , true); +}; + +export const getInteractionRoomData = async (persistenceRead: IPersistenceRead, userId:string): Promise => { + const association = new RocketChatAssociationRecord(RocketChatAssociationModel.USER, `${ userId }#RoomId`); + const result = await persistenceRead.readByAssociation(association) as Array; + return result && result.length ? result[0] : null; +}; + +export const clearInteractionRoomData = async (persistence: IPersistence, userId: string): Promise => { + const association = new RocketChatAssociationRecord(RocketChatAssociationModel.USER, `${ userId }#RoomId`); + await persistence.removeByAssociation(association); +}; \ No newline at end of file From f3b888f7cd3c9872fb6a159c17d86d337706ff11 Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Mon, 4 Jul 2022 16:16:27 +0530 Subject: [PATCH 11/32] add subscirpion modal --- github/commands/GithubCommand.ts | 10 +- github/definitions/repositorySubscriptions.ts | 9 ++ github/enum/Modals.ts | 9 +- github/modals/subscriptionsModal.ts | 142 ++++++++++++++++++ 4 files changed, 167 insertions(+), 3 deletions(-) create mode 100644 github/definitions/repositorySubscriptions.ts diff --git a/github/commands/GithubCommand.ts b/github/commands/GithubCommand.ts index ce95206..53e4109 100644 --- a/github/commands/GithubCommand.ts +++ b/github/commands/GithubCommand.ts @@ -26,6 +26,7 @@ import { sendDirectMessage, sendNotification } from "../lib/message"; import { createSubscription, deleteSubscription } from "../helpers/githubSDK"; import { Subscription } from "../persistance/subscriptions"; import { ISubscription } from "../definitions/subscription"; +import { subsciptionsModal } from "../modals/subscriptionsModal"; @@ -79,7 +80,13 @@ export class GithubCommand implements ISlashCommand { } case SubcommandEnum.SUBSCRIBE :{ //modal - + const triggerId= context.getTriggerId(); + if(triggerId){ + const modal = await subsciptionsModal({modify,read,persistence,http,slashcommandcontext:context}); + await modify.getUiController().openModalView(modal,{triggerId},context.getSender()); + }else{ + console.log("Inavlid Trigger ID !"); + } break; } case SubcommandEnum.UNSUBSCRIBE :{ @@ -181,7 +188,6 @@ export class GithubCommand implements ISlashCommand { } const triggerId= context.getTriggerId(); if(triggerId){ - console.log(triggerId); const modal = await pullDetailsModal({data,modify,read,persistence,http,slashcommandcontext:context}); await modify.getUiController().openModalView(modal,{triggerId},context.getSender()); }else{ diff --git a/github/definitions/repositorySubscriptions.ts b/github/definitions/repositorySubscriptions.ts new file mode 100644 index 0000000..7f964fa --- /dev/null +++ b/github/definitions/repositorySubscriptions.ts @@ -0,0 +1,9 @@ +import { IUser } from "@rocket.chat/apps-engine/definition/users"; + +//subsciptions which will be saved in the apps local storage +export interface IRepositorySubscriptions{ + webhookId : string, + repoName : string, + user : IUser , + events : Array +} \ No newline at end of file diff --git a/github/enum/Modals.ts b/github/enum/Modals.ts index 315879f..176edf6 100644 --- a/github/enum/Modals.ts +++ b/github/enum/Modals.ts @@ -12,5 +12,12 @@ export enum ModalsEnum { COMMENT_PR_ACTION = 'comment-pull-request', COMMENT_PR_LABEL = 'Comment', - SUBSCRIPTION_VIEW = 'subsctiptions-view' + SUBSCRIPTION_VIEW = 'subscriptions-view', + DELETE_SUBSCRIPTION_ACTION='delete-subscription', + DELETE_SUBSCRIPTION_LABEL='Unsubscribe', + ADD_SUBSCRIPTION_ACTION='add-subscription', + ADD_SUBSCRIPTION_LABEL='Subscribe', + UPDATE_SUBSCRIPTION_ACTION='update-subscription', + UPDATE_SUBSCRIPTION_LABEL='Update', + } \ No newline at end of file diff --git a/github/modals/subscriptionsModal.ts b/github/modals/subscriptionsModal.ts index e69de29..ad13e14 100644 --- a/github/modals/subscriptionsModal.ts +++ b/github/modals/subscriptionsModal.ts @@ -0,0 +1,142 @@ +import { IHttp, IModify, IPersistence, IRead } from '@rocket.chat/apps-engine/definition/accessors'; +import { ITextObject, TextObjectType } from '@rocket.chat/apps-engine/definition/uikit/blocks'; +import { IUIKitModalViewParam } from '@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder'; +import { IUser } from '@rocket.chat/apps-engine/definition/users'; +import { ModalsEnum } from '../enum/Modals'; +import { AppEnum } from '../enum/App'; +// import { getRoomTasks, getUIData, persistUIData } from '../lib/persistence'; +import { SlashCommandContext } from '@rocket.chat/apps-engine/definition/slashcommands'; +import { UIKitBlockInteractionContext, UIKitInteractionContext } from '@rocket.chat/apps-engine/definition/uikit'; +import { getInteractionRoomData, storeInteractionRoomData } from '../persistance/roomInteraction'; +import { Subscription } from '../persistance/subscriptions'; +import { ISubscription } from '../definitions/subscription'; +import { IRepositorySubscriptions } from '../definitions/repositorySubscriptions'; + +export async function subsciptionsModal({ modify, read, persistence, http, slashcommandcontext, uikitcontext }: { modify: IModify, read: IRead, persistence: IPersistence, http: IHttp ,slashcommandcontext?: SlashCommandContext, uikitcontext?: UIKitInteractionContext }): Promise { + const viewId = ModalsEnum.SUBSCRIPTION_VIEW; + + const block = modify.getCreator().getBlockBuilder(); + + const room = slashcommandcontext?.getRoom() || uikitcontext?.getInteractionData().room; + const user = slashcommandcontext?.getSender() || uikitcontext?.getInteractionData().user; + + if (user?.id) { + let roomId; + if (room?.id) { + roomId = room.id; + await storeInteractionRoomData(persistence, user.id, roomId); + } else { + roomId = (await getInteractionRoomData(read.getPersistenceReader(), user.id)).roomId; + } + + let subsciptionStorage = new Subscription(persistence,read.getPersistenceReader()); + let roomSubsciptions: Array = await subsciptionStorage.getSubscriptions(roomId); + + + + block.addSectionBlock({ + text: { text: `Room Subscriptions`, type: TextObjectType.PLAINTEXT }, + }) + + block.addDividerBlock(); + + + let repositoryData = new Map; + for (let subsciption of roomSubsciptions) { + + let repoName = subsciption.repoName; + let userId = subsciption.user; + let event = subsciption.event; + let user = await read.getUserReader().getById(userId); + + if(repositoryData.has(repoName)){ + let repoData = repositoryData.get(repoName) as IRepositorySubscriptions; + repoData.events.push(event); + repoData.user=user; + repositoryData.set(repoName,repoData); + }else{ + let events:Array = []; + events.push(event); + let repoData:IRepositorySubscriptions={ + webhookId:subsciption.webhookId, + events:events, + user:user, + repoName:repoName + }; + repositoryData.set(repoName,repoData); + } + + } + let index=1; + for (let repository of repositoryData.values()) { + + + let repoName = repository.repoName; + let repoUser = repository.user; + let events = repository.events; + + + if(user.id === repoUser.id ){ + + block.addSectionBlock({ + text: { text: `${index} [${repoName}](https://github.com/${repoName})`, type: TextObjectType.PLAINTEXT}, + }); + + }else{ + + block.addSectionBlock({ + text: { text: `${index} [${repoName}](https://github.com/${repoName})`, type: TextObjectType.PLAINTEXT}, + accessory: block.newButtonElement({ + actionId: ModalsEnum.DELETE_SUBSCRIPTION_ACTION, + text: { + text: ModalsEnum.DELETE_SUBSCRIPTION_LABEL, + type: TextObjectType.PLAINTEXT + }, + value: repository.webhookId + }) + }); + + } + + + let eventList : Array=[]; + eventList.push(block.newPlainTextObject("Events : ")); + for(let event of events){ + eventList.push(block.newPlainTextObject(`${event} `)); + } + block.addContextBlock({ elements: eventList}); + index++; + } + } + + + block.addActionsBlock({ + elements: [ + block.newButtonElement({ + actionId: ModalsEnum.ADD_SUBSCRIPTION_ACTION, + text: { text: ModalsEnum.ADD_SUBSCRIPTION_LABEL, type: TextObjectType.PLAINTEXT }, + value: room?.id + }), + block.newButtonElement({ + actionId: ModalsEnum.DELETE_SUBSCRIPTION_ACTION, + text: { text: ModalsEnum.DELETE_SUBSCRIPTION_LABEL, type: TextObjectType.PLAINTEXT }, + value: room?.id + }), + ] + }); + + return { + id: viewId, + title: { + type: TextObjectType.PLAINTEXT, + text: AppEnum.DEFAULT_TITLE, + }, + close: block.newButtonElement({ + text: { + type: TextObjectType.PLAINTEXT, + text: 'Close', + }, + }), + blocks: block.getBlocks(), + }; +} \ No newline at end of file From 2e7242ba06eddb0754be04a6e32684f0426539e3 Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Mon, 4 Jul 2022 11:15:21 +0000 Subject: [PATCH 12/32] Complete Subscriptions modal --- github/enum/Modals.ts | 10 ++++++ github/modals/addSubscriptionsModal.ts | 0 github/modals/subscriptionsModal.ts | 42 +++++++++++--------------- 3 files changed, 28 insertions(+), 24 deletions(-) create mode 100644 github/modals/addSubscriptionsModal.ts diff --git a/github/enum/Modals.ts b/github/enum/Modals.ts index 176edf6..35ceedd 100644 --- a/github/enum/Modals.ts +++ b/github/enum/Modals.ts @@ -12,7 +12,14 @@ export enum ModalsEnum { COMMENT_PR_ACTION = 'comment-pull-request', COMMENT_PR_LABEL = 'Comment', + SUBSCRIPTION_TITLE='GitHub Subscriptions', SUBSCRIPTION_VIEW = 'subscriptions-view', + OPEN_ADD_SUBSCRIPTIONS_MODAL='open-add-subscriptions', + OPEN_UPDATE_SUBSCRIPTIONS_MODAL='open-update-subscriptions', + OPEN_DELETE_SUBSCRIPTIONS_MODAL='open-update-subscriptions', + OPEN_ADD_SUBSCRIPTIONS_LABEL='Add Subsciptions', + OPEN_UPDATE_SUBSCRIPTIONS_LABEL='Update Subscriptions', + OPEN_DELETE_SUBSCRIPTIONS_LABEL='Delete Subscriptions', DELETE_SUBSCRIPTION_ACTION='delete-subscription', DELETE_SUBSCRIPTION_LABEL='Unsubscribe', ADD_SUBSCRIPTION_ACTION='add-subscription', @@ -20,4 +27,7 @@ export enum ModalsEnum { UPDATE_SUBSCRIPTION_ACTION='update-subscription', UPDATE_SUBSCRIPTION_LABEL='Update', + OPEN_REPO_ACTION='open-repo', + OPEN_REPO_LABEL='Open' + } \ No newline at end of file diff --git a/github/modals/addSubscriptionsModal.ts b/github/modals/addSubscriptionsModal.ts new file mode 100644 index 0000000..e69de29 diff --git a/github/modals/subscriptionsModal.ts b/github/modals/subscriptionsModal.ts index ad13e14..133ed6e 100644 --- a/github/modals/subscriptionsModal.ts +++ b/github/modals/subscriptionsModal.ts @@ -32,12 +32,6 @@ export async function subsciptionsModal({ modify, read, persistence, http, slash let subsciptionStorage = new Subscription(persistence,read.getPersistenceReader()); let roomSubsciptions: Array = await subsciptionStorage.getSubscriptions(roomId); - - - block.addSectionBlock({ - text: { text: `Room Subscriptions`, type: TextObjectType.PLAINTEXT }, - }) - block.addDividerBlock(); @@ -75,28 +69,22 @@ export async function subsciptionsModal({ modify, read, persistence, http, slash let repoUser = repository.user; let events = repository.events; - - if(user.id === repoUser.id ){ - - block.addSectionBlock({ - text: { text: `${index} [${repoName}](https://github.com/${repoName})`, type: TextObjectType.PLAINTEXT}, - }); - - }else{ + + block.addSectionBlock({ - text: { text: `${index} [${repoName}](https://github.com/${repoName})`, type: TextObjectType.PLAINTEXT}, + text: { text: `${index}) ${repoName}`, type: TextObjectType.PLAINTEXT}, accessory: block.newButtonElement({ - actionId: ModalsEnum.DELETE_SUBSCRIPTION_ACTION, + actionId: ModalsEnum.OPEN_REPO_ACTION, text: { - text: ModalsEnum.DELETE_SUBSCRIPTION_LABEL, + text: ModalsEnum.OPEN_REPO_LABEL, type: TextObjectType.PLAINTEXT }, - value: repository.webhookId + value: repository.webhookId, + url:`https://github.com/${repoName}` }) }); - } let eventList : Array=[]; @@ -109,17 +97,23 @@ export async function subsciptionsModal({ modify, read, persistence, http, slash } } + block.addDividerBlock(); block.addActionsBlock({ elements: [ block.newButtonElement({ - actionId: ModalsEnum.ADD_SUBSCRIPTION_ACTION, - text: { text: ModalsEnum.ADD_SUBSCRIPTION_LABEL, type: TextObjectType.PLAINTEXT }, + actionId: ModalsEnum.OPEN_ADD_SUBSCRIPTIONS_MODAL, + text: { text: ModalsEnum.OPEN_ADD_SUBSCRIPTIONS_LABEL, type: TextObjectType.PLAINTEXT }, + value: room?.id + }), + block.newButtonElement({ + actionId: ModalsEnum.OPEN_UPDATE_SUBSCRIPTIONS_MODAL, + text: { text: ModalsEnum.OPEN_UPDATE_SUBSCRIPTIONS_LABEL, type: TextObjectType.PLAINTEXT }, value: room?.id }), block.newButtonElement({ - actionId: ModalsEnum.DELETE_SUBSCRIPTION_ACTION, - text: { text: ModalsEnum.DELETE_SUBSCRIPTION_LABEL, type: TextObjectType.PLAINTEXT }, + actionId: ModalsEnum.OPEN_DELETE_SUBSCRIPTIONS_MODAL, + text: { text: ModalsEnum.OPEN_DELETE_SUBSCRIPTIONS_LABEL, type: TextObjectType.PLAINTEXT }, value: room?.id }), ] @@ -129,7 +123,7 @@ export async function subsciptionsModal({ modify, read, persistence, http, slash id: viewId, title: { type: TextObjectType.PLAINTEXT, - text: AppEnum.DEFAULT_TITLE, + text: ModalsEnum.SUBSCRIPTION_TITLE, }, close: block.newButtonElement({ text: { From db8782db1bed18352588516bc5e6eb8307bddcb6 Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Mon, 4 Jul 2022 12:02:54 +0000 Subject: [PATCH 13/32] Add New Subscription Modal --- github/enum/Modals.ts | 15 +- github/handlers/ExecuteBlockActionHandler.ts | 13 ++ github/handlers/ExecuteViewClosedHandler.ts | 2 +- github/modals/addSubscriptionsModal.ts | 136 +++++++++++++++++++ 4 files changed, 164 insertions(+), 2 deletions(-) diff --git a/github/enum/Modals.ts b/github/enum/Modals.ts index 35ceedd..3af7c92 100644 --- a/github/enum/Modals.ts +++ b/github/enum/Modals.ts @@ -14,6 +14,7 @@ export enum ModalsEnum { SUBSCRIPTION_TITLE='GitHub Subscriptions', SUBSCRIPTION_VIEW = 'subscriptions-view', + ADD_SUBSCRIPTION_VIEW = 'add-subscription-view', OPEN_ADD_SUBSCRIPTIONS_MODAL='open-add-subscriptions', OPEN_UPDATE_SUBSCRIPTIONS_MODAL='open-update-subscriptions', OPEN_DELETE_SUBSCRIPTIONS_MODAL='open-update-subscriptions', @@ -26,8 +27,20 @@ export enum ModalsEnum { ADD_SUBSCRIPTION_LABEL='Subscribe', UPDATE_SUBSCRIPTION_ACTION='update-subscription', UPDATE_SUBSCRIPTION_LABEL='Update', + OPEN_REPO_ACTION='open-repo', - OPEN_REPO_LABEL='Open' + OPEN_REPO_LABEL='Open', + + REPO_NAME_INPUT='repo-name-input', + REPO_NAME_LABEL='Enter Full Repository Name', + REPO_NAME_PLACEHOLDER='Owner/Repository', + REPO_NAME_INPUT_ACTION='repo-name-input-action', + ADD_SUBSCRIPTION_EVENT_LABEL='Add Events', + ADD_SUBSCRIPTION_EVENT_OPTIONS='add-repo-subscription-events', + ADD_SUBSCRIPTION_EVENT_INPUT='add-repo-subscription-events', + + + } \ No newline at end of file diff --git a/github/handlers/ExecuteBlockActionHandler.ts b/github/handlers/ExecuteBlockActionHandler.ts index 2813890..58bccd3 100644 --- a/github/handlers/ExecuteBlockActionHandler.ts +++ b/github/handlers/ExecuteBlockActionHandler.ts @@ -14,6 +14,7 @@ import { IUIKitResponse, UIKitBlockInteractionContext, } from "@rocket.chat/apps-engine/definition/uikit"; +import { AddSubscriptionModal } from "../modals/addSubscriptionsModal"; export class ExecuteBlockActionHandler { constructor( @@ -89,6 +90,18 @@ export class ExecuteBlockActionHandler { .getInteractionResponder() .openModalViewResponse(codeModal); } + case ModalsEnum.OPEN_ADD_SUBSCRIPTIONS_MODAL:{ + const addSubscriptionModal = await AddSubscriptionModal({ + modify: this.modify, + read: this.read, + persistence: this.persistence, + http: this.http, + uikitcontext: context + }) + return context + .getInteractionResponder() + .openModalViewResponse(addSubscriptionModal); + } } return context.getInteractionResponder().successResponse(); diff --git a/github/handlers/ExecuteViewClosedHandler.ts b/github/handlers/ExecuteViewClosedHandler.ts index f299f78..5c7c502 100644 --- a/github/handlers/ExecuteViewClosedHandler.ts +++ b/github/handlers/ExecuteViewClosedHandler.ts @@ -21,7 +21,7 @@ export class ExecuteViewClosedHandler { public async run(context: UIKitViewCloseInteractionContext) { const { view } = context.getInteractionData(); switch (view.id) { - case ModalsEnum.PULL_VIEW || ModalsEnum.CODE_VIEW: + case ModalsEnum.PULL_VIEW || ModalsEnum.CODE_VIEW || ModalsEnum.ADD_SUBSCRIPTION_VIEW || ModalsEnum.SUBSCRIPTION_VIEW: const modal = await pullDetailsModal({ modify: this.modify, read: this.read, diff --git a/github/modals/addSubscriptionsModal.ts b/github/modals/addSubscriptionsModal.ts index e69de29..bebfb12 100644 --- a/github/modals/addSubscriptionsModal.ts +++ b/github/modals/addSubscriptionsModal.ts @@ -0,0 +1,136 @@ + +import { IHttp, IModify, IPersistence, IRead } from '@rocket.chat/apps-engine/definition/accessors'; +import { TextObjectType } from '@rocket.chat/apps-engine/definition/uikit/blocks'; +import { IUIKitModalViewParam } from '@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder'; +import { IUser } from '@rocket.chat/apps-engine/definition/users'; +import { ModalsEnum } from '../enum/Modals'; +import { AppEnum } from '../enum/App'; +// import { getRoomTasks, getUIData, persistUIData } from '../lib/persistence'; +import { SlashCommandContext } from '@rocket.chat/apps-engine/definition/slashcommands'; +import { UIKitBlockInteractionContext, UIKitInteractionContext } from '@rocket.chat/apps-engine/definition/uikit'; +import { storeInteractionRoomData, getInteractionRoomData } from '../persistance/roomInteraction'; +import { Subscription } from '../persistance/subscriptions'; +import { ISubscription } from '../definitions/subscription'; + +export async function AddSubscriptionModal({ modify, read, persistence, http, slashcommandcontext, uikitcontext }: { modify: IModify, read: IRead, persistence: IPersistence, http: IHttp ,slashcommandcontext?: SlashCommandContext, uikitcontext?: UIKitInteractionContext }): Promise { + const viewId = ModalsEnum.ADD_SUBSCRIPTION_VIEW; + + const block = modify.getCreator().getBlockBuilder(); + + const room = slashcommandcontext?.getRoom() || uikitcontext?.getInteractionData().room; + const user = slashcommandcontext?.getSender() || uikitcontext?.getInteractionData().user; + + if (user?.id) { + let roomId; + + if (room?.id) { + roomId = room.id; + await storeInteractionRoomData(persistence, user.id, roomId); + } else { + roomId = (await getInteractionRoomData(read.getPersistenceReader(), user.id)).roomId; + } + + let subsciptionStorage = new Subscription(persistence,read.getPersistenceReader()); + let roomSubsciptions: Array = await subsciptionStorage.getSubscriptions(roomId); + + // shows indentations in input blocks but not inn section block + block.addInputBlock({ + blockId: ModalsEnum.REPO_NAME_INPUT, + label: { text: ModalsEnum.REPO_NAME_LABEL, type: TextObjectType.PLAINTEXT }, + element: block.newPlainTextInputElement({ + actionId: ModalsEnum.REPO_NAME_INPUT_ACTION, + placeholder : { text: ModalsEnum.REPO_NAME_PLACEHOLDER, type: TextObjectType.PLAINTEXT }, + }) + }); + + + let newMultiStaticElemnt= block.newMultiStaticElement({ + actionId : ModalsEnum.ADD_SUBSCRIPTION_EVENT_OPTIONS, + options: [ + { + value: 'issues', + text:{ + type: TextObjectType.PLAINTEXT, + text:'New Issues', + emoji:true, + } + }, + { + value: 'pull_request', + text:{ + type: TextObjectType.PLAINTEXT, + text:'New Pull Request', + emoji:true, + } + }, + { + value: 'push', + text:{ + type: TextObjectType.PLAINTEXT, + text:'New Commits', + emoji:true, + } + }, + { + value: 'deployment_status', + text:{ + type: TextObjectType.PLAINTEXT, + text:'Deployment', + emoji:true, + } + }, + ], + placeholder: { + type: TextObjectType.PLAINTEXT, + text: 'Select Events', + }, + + }) + + block.addInputBlock({ + label: { text:ModalsEnum.ADD_SUBSCRIPTION_EVENT_LABEL, type: TextObjectType.PLAINTEXT }, + element:newMultiStaticElemnt, + blockId:ModalsEnum.ADD_SUBSCRIPTION_EVENT_INPUT, + }) + } + + + block.addDividerBlock(); + + // block.addActionsBlock({ + // elements: [ + // block.newButtonElement({ + // actionId: ModalsEnum.MERGE_PULL_REQUEST_ACTION, + // text: { text: ModalsEnum.MERGE_PULL_REQUEST_LABEL, type: TextObjectType.PLAINTEXT }, + // value: room?.id + // }), + // block.newButtonElement({ + // actionId: ModalsEnum.COMMENT_PR_ACTION, + // text: { text: ModalsEnum.COMMENT_PR_LABEL, type: TextObjectType.PLAINTEXT }, + // value: room?.id + // }), + // ] + // }); + + return { + id: viewId, + title: { + type: TextObjectType.PLAINTEXT, + text: AppEnum.DEFAULT_TITLE, + }, + close: block.newButtonElement({ + text: { + type: TextObjectType.PLAINTEXT, + text: 'Close', + }, + }), + submit: block.newButtonElement({ + actionId: ModalsEnum.ADD_SUBSCRIPTION_ACTION, + text: { + type: TextObjectType.PLAINTEXT, + text: 'Close', + }, + }), + blocks: block.getBlocks() + }; +} \ No newline at end of file From ac96152907535da282ead478a42c32c7a3c40302 Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Mon, 4 Jul 2022 13:33:13 +0000 Subject: [PATCH 14/32] Add Subsciption submit handler --- github/GithubApp.ts | 73 +++++++++++++------ github/commands/GithubCommand.ts | 9 +-- github/handlers/ExecuteViewSubmitHandler.ts | 77 +++++++++++++++++++++ github/modals/addSubscriptionsModal.ts | 2 +- 4 files changed, 136 insertions(+), 25 deletions(-) create mode 100644 github/handlers/ExecuteViewSubmitHandler.ts diff --git a/github/GithubApp.ts b/github/GithubApp.ts index 92ec4f9..f3dbb0e 100644 --- a/github/GithubApp.ts +++ b/github/GithubApp.ts @@ -5,7 +5,7 @@ import { ILogger, IModify, IPersistence, - IRead + IRead, } from "@rocket.chat/apps-engine/definition/accessors"; import { App } from "@rocket.chat/apps-engine/definition/App"; import { IAppInfo } from "@rocket.chat/apps-engine/definition/metadata"; @@ -14,9 +14,11 @@ import { IUIKitResponse, UIKitBlockInteractionContext, UIKitViewCloseInteractionContext, + UIKitViewSubmitInteractionContext, } from "@rocket.chat/apps-engine/definition/uikit"; import { ExecuteViewClosedHandler } from "./handlers/ExecuteViewClosedHandler"; import { ExecuteBlockActionHandler } from "./handlers/ExecuteBlockActionHandler"; +import { ExecuteViewSubmitHandler } from "./handlers/ExecuteViewSubmitHandler"; import { IUser } from "@rocket.chat/apps-engine/definition/users"; import { IAuthData, @@ -29,8 +31,11 @@ import { sendDirectMessage } from "./lib/message"; import { OAuth2Client } from "@rocket.chat/apps-engine/server/oauth2/OAuth2Client"; import { deleteOathToken } from "./processors/deleteOAthToken"; import { ProcessorsEnum } from "./enum/Processors"; -import getOauth2Config from './oath2/oath2Config'; -import { ApiSecurity, ApiVisibility} from '@rocket.chat/apps-engine/definition/api'; +import getOauth2Config from "./oath2/oath2Config"; +import { + ApiSecurity, + ApiVisibility, +} from "@rocket.chat/apps-engine/definition/api"; import { githubWebHooks } from "./endpoints/githubEndpoints"; import { IJobContext } from "@rocket.chat/apps-engine/definition/scheduler"; import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; @@ -39,7 +44,7 @@ export class GithubApp extends App { constructor(info: IAppInfo, logger: ILogger, accessors: IAppAccessors) { super(info, logger, accessors); } - + public async authorizationCallback( token: IAuthData, user: IUser, @@ -50,14 +55,14 @@ export class GithubApp extends App { ) { const deleteTokenTask = { id: ProcessorsEnum.REMOVE_GITHUB_LOGIN, - when: '7 days', - data: { - 'user':user, - 'config' : this.oauth2Config + when: "7 days", + data: { + user: user, + config: this.oauth2Config, }, - }; + }; let text = `GitHub Authentication Succesfull 🚀`; - + if (token) { // await registerAuthorizedUser(read, persistence, user); await modify.getScheduler().scheduleOnce(deleteTokenTask); @@ -120,6 +125,23 @@ export class GithubApp extends App { return await handler.run(context); } + public async executeViewSubmitHandler( + context: UIKitViewSubmitInteractionContext, + read: IRead, + http: IHttp, + persistence: IPersistence, + modify: IModify + ) { + const handler = new ExecuteViewSubmitHandler( + this, + read, + http, + modify, + persistence + ); + return await handler.run(context); + } + public async extendConfiguration( configuration: IConfigurationExtend ): Promise { @@ -131,23 +153,34 @@ export class GithubApp extends App { configuration.scheduler.registerProcessors([ { id: ProcessorsEnum.REMOVE_GITHUB_LOGIN, - processor: async (jobContext,read,modify,http,persis) => { + processor: async (jobContext, read, modify, http, persis) => { let user = jobContext.user as IUser; let config = jobContext.config as IOAuth2ClientOptions; try { - await deleteOathToken({user,config,read,modify,http,persis}); + await deleteOathToken({ + user, + config, + read, + modify, + http, + persis, + }); } catch (e) { - await sendDirectMessage(read,modify,user,e.message,persis); + await sendDirectMessage( + read, + modify, + user, + e.message, + persis + ); } - } + }, }, ]); configuration.api.provideApi({ - visibility : ApiVisibility.PUBLIC, - security : ApiSecurity.UNSECURE, - endpoints : [ - new githubWebHooks(this) - ] - }) + visibility: ApiVisibility.PUBLIC, + security: ApiSecurity.UNSECURE, + endpoints: [new githubWebHooks(this)], + }); } } diff --git a/github/commands/GithubCommand.ts b/github/commands/GithubCommand.ts index 53e4109..eb9896e 100644 --- a/github/commands/GithubCommand.ts +++ b/github/commands/GithubCommand.ts @@ -149,13 +149,14 @@ export class GithubCommand implements ISlashCommand { for(let event of events){ for(let subscription of roomSubscriptions){ let webhookId = subscription.webhookId; - if(subscription.repoName!==repository || subscription.event!==event || hooksMap.has(webhookId)){ + if(subscription.repoName!==repository || subscription.event!==event){ //skip entry if event and repo name doesnt match or if hook has been deleted continue; } - - hooksMap.set(webhookId,true); - await deleteSubscription(http,repository,accessToken.token,webhookId); + if(!hooksMap.has(webhookId)){ + hooksMap.set(webhookId,true); + await deleteSubscription(http,repository,accessToken.token,webhookId); + } let deleted = await subsciptionStorage.deleteSubscriptions(repository,event,room.id); if(!deleted){ console.log("Cant delete unsubsribed hook"); diff --git a/github/handlers/ExecuteViewSubmitHandler.ts b/github/handlers/ExecuteViewSubmitHandler.ts new file mode 100644 index 0000000..13ec878 --- /dev/null +++ b/github/handlers/ExecuteViewSubmitHandler.ts @@ -0,0 +1,77 @@ +import { IHttp, IModify, IPersistence, IRead } from '@rocket.chat/apps-engine/definition/accessors'; +import { IRoom } from '@rocket.chat/apps-engine/definition/rooms'; +import { UIKitViewSubmitInteractionContext } from '@rocket.chat/apps-engine/definition/uikit'; +import { ModalsEnum } from '../enum/Modals'; +import { sendMessage, sendNotification } from '../lib/message'; +import { getInteractionRoomData } from '../persistance/roomInteraction'; +import { Subscription } from '../persistance/subscriptions'; +import { GithubApp } from '../GithubApp'; +import { getWebhookUrl } from '../helpers/getWebhookURL'; +import { createSubscription } from '../helpers/githubSDK'; +import { getAccessTokenForUser } from '../persistance/auth'; +import { subsciptionsModal } from '../modals/subscriptionsModal'; + + +export class ExecuteViewSubmitHandler { + constructor( + private readonly app: GithubApp, + private readonly read: IRead, + private readonly http: IHttp, + private readonly modify: IModify, + private readonly persistence: IPersistence, + ) {} + + public async run(context: UIKitViewSubmitInteractionContext) { + const { user, view } = context.getInteractionData(); + + try { + switch (view.id) { + case ModalsEnum.ADD_SUBSCRIPTION_VIEW: + if (user.id) { + const { roomId } = await getInteractionRoomData(this.read.getPersistenceReader(),user.id); + if (roomId) { + let room = await this.read.getRoomReader().getById(roomId) as IRoom; + const repository = view.state?.[ModalsEnum.REPO_NAME_INPUT]?.[ModalsEnum.REPO_NAME_INPUT_ACTION]; + const events = view.state?.[ModalsEnum.ADD_SUBSCRIPTION_EVENT_INPUT]?.[ModalsEnum.ADD_SUBSCRIPTION_EVENT_OPTIONS]; + + if(typeof(repository) == undefined || typeof(events) == undefined){ + + await sendNotification(this.read,this.modify,user,room,"Invalid Input !"); + }else{ + let accessToken = await getAccessTokenForUser(this.read,user,this.app.oauth2Config); + if(!accessToken){ + + await sendNotification(this.read,this.modify,user,room,"Login To Github !"); + }else{ + let url = await getWebhookUrl(this.app); + let response = await createSubscription(this.http,repository,url,accessToken.token,events); + let subsciptionStorage = new Subscription(this.persistence,this.read.getPersistenceReader()) + + let createdEntry = false ; + for(let event of events){ + createdEntry= await subsciptionStorage.createSubscription(repository,event,response?.id,room,user); + } + if(!createdEntry){ + throw new Error("Error creating new subscription entry"); + } + await sendNotification(this.read,this.modify,user,room,`Subscibed to ${repository} ✔️`); + } + + } + return context.getInteractionResponder().successResponse(); + } + } + break; + default: + break; + } + + } catch (error) { + console.log('error : ',error); + } + + return { + success: true, + }; + } +} \ No newline at end of file diff --git a/github/modals/addSubscriptionsModal.ts b/github/modals/addSubscriptionsModal.ts index bebfb12..99355dd 100644 --- a/github/modals/addSubscriptionsModal.ts +++ b/github/modals/addSubscriptionsModal.ts @@ -128,7 +128,7 @@ export async function AddSubscriptionModal({ modify, read, persistence, http, sl actionId: ModalsEnum.ADD_SUBSCRIPTION_ACTION, text: { type: TextObjectType.PLAINTEXT, - text: 'Close', + text: 'Subscribe', }, }), blocks: block.getBlocks() From cc72cb64869fdcdc6417331f813f497a9998af53 Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Mon, 4 Jul 2022 18:34:50 +0000 Subject: [PATCH 15/32] Add Deelete Subscibtion --- github/enum/Modals.ts | 3 +- github/handlers/ExecuteBlockActionHandler.ts | 56 ++++++++- github/modals/deleteSubscriptions.ts | 122 +++++++++++++++++++ github/persistance/subscriptions.ts | 26 ++++ 4 files changed, 203 insertions(+), 4 deletions(-) create mode 100644 github/modals/deleteSubscriptions.ts diff --git a/github/enum/Modals.ts b/github/enum/Modals.ts index 3af7c92..d4638b4 100644 --- a/github/enum/Modals.ts +++ b/github/enum/Modals.ts @@ -15,6 +15,7 @@ export enum ModalsEnum { SUBSCRIPTION_TITLE='GitHub Subscriptions', SUBSCRIPTION_VIEW = 'subscriptions-view', ADD_SUBSCRIPTION_VIEW = 'add-subscription-view', + DELETE_SUBSCRIPTION_VIEW ='delete-subscription-view', OPEN_ADD_SUBSCRIPTIONS_MODAL='open-add-subscriptions', OPEN_UPDATE_SUBSCRIPTIONS_MODAL='open-update-subscriptions', OPEN_DELETE_SUBSCRIPTIONS_MODAL='open-update-subscriptions', @@ -27,7 +28,7 @@ export enum ModalsEnum { ADD_SUBSCRIPTION_LABEL='Subscribe', UPDATE_SUBSCRIPTION_ACTION='update-subscription', UPDATE_SUBSCRIPTION_LABEL='Update', - + DELETE_SUBSCIPTIONS_TITLE='Unsubscribe to Repositories', OPEN_REPO_ACTION='open-repo', OPEN_REPO_LABEL='Open', diff --git a/github/handlers/ExecuteBlockActionHandler.ts b/github/handlers/ExecuteBlockActionHandler.ts index 58bccd3..99a6d9e 100644 --- a/github/handlers/ExecuteBlockActionHandler.ts +++ b/github/handlers/ExecuteBlockActionHandler.ts @@ -15,10 +15,16 @@ import { UIKitBlockInteractionContext, } from "@rocket.chat/apps-engine/definition/uikit"; import { AddSubscriptionModal } from "../modals/addSubscriptionsModal"; - +import { deleteSubsciptionsModal } from "../modals/deleteSubscriptions"; +import { deleteSubscription } from "../helpers/githubSDK"; +import { Subscription } from "../persistance/subscriptions"; +import { getAccessTokenForUser } from "../persistance/auth"; +import { GithubApp } from "../GithubApp"; +import { IAuthData } from "@rocket.chat/apps-engine/definition/oauth2/IOAuth2"; +import { storeInteractionRoomData, getInteractionRoomData } from "../persistance/roomInteraction"; export class ExecuteBlockActionHandler { constructor( - private readonly app: IApp, + private readonly app: GithubApp, private readonly read: IRead, private readonly http: IHttp, private readonly modify: IModify, @@ -30,7 +36,8 @@ export class ExecuteBlockActionHandler { ): Promise { const data = context.getInteractionData(); - const { actionId } = data; + try { + const { actionId } = data; switch (actionId) { case "githubDataSelect": { try { @@ -102,8 +109,51 @@ export class ExecuteBlockActionHandler { .getInteractionResponder() .openModalViewResponse(addSubscriptionModal); } + case ModalsEnum.OPEN_DELETE_SUBSCRIPTIONS_MODAL:{ + const addSubscriptionModal = await deleteSubsciptionsModal({ + modify: this.modify, + read: this.read, + persistence: this.persistence, + http: this.http, + uikitcontext: context + }) + return context + .getInteractionResponder() + .openModalViewResponse(addSubscriptionModal); + } + case ModalsEnum.DELETE_SUBSCRIPTION_ACTION:{ + + + let {user,room} = await context.getInteractionData(); + let accessToken = await getAccessTokenForUser(this.read,user,this.app.oauth2Config) as IAuthData; + let value :string= context.getInteractionData().value as string; + let splitted = value.split(','); + if(splitted.length == 2 && accessToken.token){ + let repoName = splitted[0]; + let hookId = splitted[1]; + let roomId; + if (room?.id) { + roomId = room.id; + await storeInteractionRoomData(this.persistence, user.id, roomId); + } else { + roomId = (await getInteractionRoomData(this.read.getPersistenceReader(), user.id)).roomId; + } + // await deleteSubscription(this.http,repoName,accessToken.token,hookId); + let subsciptionStorage = new Subscription(this.persistence,this.read.getPersistenceReader()); + await subsciptionStorage.deleteSubscriptionsByRepoUser(repoName,roomId,user.id); + } + + + const modal = await deleteSubsciptionsModal({ modify: this.modify, read: this.read, persistence: this.persistence,http : this.http, uikitcontext: context }); + await this.modify.getUiController().updateModalView(modal, { triggerId: context.getInteractionData().triggerId }, context.getInteractionData().user); + break; + } } + } catch (error) { + console.log(error); + } + return context.getInteractionResponder().successResponse(); } } diff --git a/github/modals/deleteSubscriptions.ts b/github/modals/deleteSubscriptions.ts new file mode 100644 index 0000000..7d621b0 --- /dev/null +++ b/github/modals/deleteSubscriptions.ts @@ -0,0 +1,122 @@ +import { IHttp, IModify, IPersistence, IRead } from '@rocket.chat/apps-engine/definition/accessors'; +import { ITextObject, TextObjectType } from '@rocket.chat/apps-engine/definition/uikit/blocks'; +import { IUIKitModalViewParam } from '@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder'; +import { IUser } from '@rocket.chat/apps-engine/definition/users'; +import { ModalsEnum } from '../enum/Modals'; +import { AppEnum } from '../enum/App'; +// import { getRoomTasks, getUIData, persistUIData } from '../lib/persistence'; +import { SlashCommandContext } from '@rocket.chat/apps-engine/definition/slashcommands'; +import { UIKitBlockInteractionContext, UIKitInteractionContext } from '@rocket.chat/apps-engine/definition/uikit'; +import { getInteractionRoomData, storeInteractionRoomData } from '../persistance/roomInteraction'; +import { Subscription } from '../persistance/subscriptions'; +import { ISubscription } from '../definitions/subscription'; +import { IRepositorySubscriptions } from '../definitions/repositorySubscriptions'; + +export async function deleteSubsciptionsModal({ modify, read, persistence, http, slashcommandcontext, uikitcontext }: { modify: IModify, read: IRead, persistence: IPersistence, http: IHttp ,slashcommandcontext?: SlashCommandContext, uikitcontext?: UIKitInteractionContext }): Promise { + const viewId = ModalsEnum.DELETE_SUBSCRIPTION_VIEW; + + const block = modify.getCreator().getBlockBuilder(); + + const room = slashcommandcontext?.getRoom() || uikitcontext?.getInteractionData().room; + const user = slashcommandcontext?.getSender() || uikitcontext?.getInteractionData().user; + + if (user?.id) { + let roomId; + if (room?.id) { + roomId = room.id; + await storeInteractionRoomData(persistence, user.id, roomId); + } else { + roomId = (await getInteractionRoomData(read.getPersistenceReader(), user.id)).roomId; + } + + let subsciptionStorage = new Subscription(persistence,read.getPersistenceReader()); + let roomSubsciptions: Array = await subsciptionStorage.getSubscriptions(roomId); + + block.addDividerBlock(); + + + let repositoryData = new Map; + for (let subsciption of roomSubsciptions) { + + let repoName = subsciption.repoName; + let userId = subsciption.user; + let event = subsciption.event; + let user = await read.getUserReader().getById(userId); + + if(repositoryData.has(repoName)){ + let repoData = repositoryData.get(repoName) as IRepositorySubscriptions; + repoData.events.push(event); + repoData.user=user; + repositoryData.set(repoName,repoData); + }else{ + let events:Array = []; + events.push(event); + let repoData:IRepositorySubscriptions={ + webhookId:subsciption.webhookId, + events:events, + user:user, + repoName:repoName + }; + repositoryData.set(repoName,repoData); + } + + } + let index=1; + for (let repository of repositoryData.values()) { + + + let repoName = repository.repoName; + let repoUser = repository.user; + let events = repository.events; + + + if(repoUser.id == user.id){ + block.addSectionBlock({ + text: { text: `${index}) ${repoName}`, type: TextObjectType.PLAINTEXT}, + accessory: block.newButtonElement({ + actionId: ModalsEnum.DELETE_SUBSCRIPTION_ACTION, + text: { + text: ModalsEnum.DELETE_SUBSCRIPTION_LABEL, + type: TextObjectType.PLAINTEXT + }, + value: repoName + "," + repository.webhookId, + }) + }); + + }else{ + block.addSectionBlock({ + text: { text: `${index}) ${repoName}`, type: TextObjectType.PLAINTEXT}, + }); + + } + + + + let eventList : Array=[]; + eventList.push(block.newPlainTextObject("Events : ")); + for(let event of events){ + eventList.push(block.newPlainTextObject(`${event} `)); + } + block.addContextBlock({ elements: eventList}); + index++; + } + } + + block.addDividerBlock(); + + + return { + id: viewId, + title: { + type: TextObjectType.PLAINTEXT, + text: ModalsEnum.DELETE_SUBSCIPTIONS_TITLE, + }, + close: block.newButtonElement({ + text: { + type: TextObjectType.PLAINTEXT, + text: 'Close', + }, + }), + blocks: block.getBlocks(), + }; +} \ No newline at end of file diff --git a/github/persistance/subscriptions.ts b/github/persistance/subscriptions.ts index 13ce622..c0b4cfd 100644 --- a/github/persistance/subscriptions.ts +++ b/github/persistance/subscriptions.ts @@ -79,6 +79,21 @@ export class Subscription { } return true; } + public async deleteSubscriptionsByRepoUser(repoName: string, roomId: string, userId: string): Promise { + try { + const associations: Array = [ + new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `subscription`), + new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `repo:${repoName}`), + new RocketChatAssociationRecord(RocketChatAssociationModel.USER, `${userId}`), + new RocketChatAssociationRecord(RocketChatAssociationModel.ROOM, roomId), + ]; + await this.persistence.removeByAssociations(associations); + } catch (error) { + console.warn('Delete Subsciption Error :', error) + return false; + } + return true; + } public async deleteAllRoomSubscriptions(roomId: string): Promise { try { @@ -94,4 +109,15 @@ export class Subscription { return true; } + public async deleteAllRoomSubscriptionsByHookId(hookId: string): Promise { + try { + let association = new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, hookId) + await this.persistence.removeByAssociation(association); + } catch (error) { + console.warn('Delete All Room Subsciption Error :', error) + return false; + } + return true; + } + } \ No newline at end of file From 18677f238f07419218c95ee186ee15dae7f184b5 Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Tue, 5 Jul 2022 03:11:21 +0530 Subject: [PATCH 16/32] Delete Subscription --- github/handlers/ExecuteBlockActionHandler.ts | 210 ++++++++++--------- 1 file changed, 106 insertions(+), 104 deletions(-) diff --git a/github/handlers/ExecuteBlockActionHandler.ts b/github/handlers/ExecuteBlockActionHandler.ts index 99a6d9e..5f45b64 100644 --- a/github/handlers/ExecuteBlockActionHandler.ts +++ b/github/handlers/ExecuteBlockActionHandler.ts @@ -22,6 +22,7 @@ import { getAccessTokenForUser } from "../persistance/auth"; import { GithubApp } from "../GithubApp"; import { IAuthData } from "@rocket.chat/apps-engine/definition/oauth2/IOAuth2"; import { storeInteractionRoomData, getInteractionRoomData } from "../persistance/roomInteraction"; +import { sendNotification } from "../lib/message"; export class ExecuteBlockActionHandler { constructor( private readonly app: GithubApp, @@ -29,7 +30,7 @@ export class ExecuteBlockActionHandler { private readonly http: IHttp, private readonly modify: IModify, private readonly persistence: IPersistence - ) {} + ) { } public async run( context: UIKitBlockInteractionContext @@ -38,122 +39,123 @@ export class ExecuteBlockActionHandler { try { const { actionId } = data; - switch (actionId) { - case "githubDataSelect": { - try { - const param = data.value; - let query: String = ""; - let lengthOfRepoString: number = 0; - if (param && param.length) { - let i = param.length - 1; - for ( - ; - i >= 0 && data.value && data.value[i] != "/"; - i-- - ) { - query = data.value[i] + query; + switch (actionId) { + case "githubDataSelect": { + try { + const param = data.value; + let query: String = ""; + let lengthOfRepoString: number = 0; + if (param && param.length) { + let i = param.length - 1; + for ( + ; + i >= 0 && data.value && data.value[i] != "/"; + i-- + ) { + query = data.value[i] + query; + } + lengthOfRepoString = i; } - lengthOfRepoString = i; - } - const repository = param?.substring( - 0, - lengthOfRepoString - ) as String; + const repository = param?.substring( + 0, + lengthOfRepoString + ) as String; + + const room: IRoom = context.getInteractionData() + .room as IRoom; + console.log("PRESS", query); + await basicQueryMessage({ + query, + repository, + room, + read: this.read, + persistence: this.persistence, + modify: this.modify, + http: this.http, + }); - const room: IRoom = context.getInteractionData() - .room as IRoom; - console.log("PRESS",query); - await basicQueryMessage({ - query, - repository, - room, + return { + success: true, + }; + } catch (err) { + console.error(err); + return { + success: false, + }; + } + break; + } + case ModalsEnum.VIEW_FILE_ACTION: { + const codeModal = await fileCodeModal({ + data, + modify: this.modify, read: this.read, persistence: this.persistence, - modify: this.modify, http: this.http, + uikitcontext: context, }); - - return { - success: true, - }; - } catch (err) { - console.error(err); - return { - success: false, - }; + return context + .getInteractionResponder() + .openModalViewResponse(codeModal); } - break; - } - case ModalsEnum.VIEW_FILE_ACTION: { - const codeModal = await fileCodeModal({ - data, - modify: this.modify, - read: this.read, - persistence: this.persistence, - http: this.http, - uikitcontext: context, - }); - return context - .getInteractionResponder() - .openModalViewResponse(codeModal); - } - case ModalsEnum.OPEN_ADD_SUBSCRIPTIONS_MODAL:{ - const addSubscriptionModal = await AddSubscriptionModal({ - modify: this.modify, - read: this.read, - persistence: this.persistence, - http: this.http, - uikitcontext: context - }) - return context - .getInteractionResponder() - .openModalViewResponse(addSubscriptionModal); - } - case ModalsEnum.OPEN_DELETE_SUBSCRIPTIONS_MODAL:{ - const addSubscriptionModal = await deleteSubsciptionsModal({ - modify: this.modify, - read: this.read, - persistence: this.persistence, - http: this.http, - uikitcontext: context - }) - return context - .getInteractionResponder() - .openModalViewResponse(addSubscriptionModal); - } - case ModalsEnum.DELETE_SUBSCRIPTION_ACTION:{ - - - let {user,room} = await context.getInteractionData(); - let accessToken = await getAccessTokenForUser(this.read,user,this.app.oauth2Config) as IAuthData; - let value :string= context.getInteractionData().value as string; - let splitted = value.split(','); - if(splitted.length == 2 && accessToken.token){ - let repoName = splitted[0]; - let hookId = splitted[1]; - let roomId; - if (room?.id) { - roomId = room.id; - await storeInteractionRoomData(this.persistence, user.id, roomId); - } else { - roomId = (await getInteractionRoomData(this.read.getPersistenceReader(), user.id)).roomId; + case ModalsEnum.OPEN_ADD_SUBSCRIPTIONS_MODAL: { + const addSubscriptionModal = await AddSubscriptionModal({ + modify: this.modify, + read: this.read, + persistence: this.persistence, + http: this.http, + uikitcontext: context + }) + return context + .getInteractionResponder() + .openModalViewResponse(addSubscriptionModal); + } + case ModalsEnum.OPEN_DELETE_SUBSCRIPTIONS_MODAL: { + const addSubscriptionModal = await deleteSubsciptionsModal({ + modify: this.modify, + read: this.read, + persistence: this.persistence, + http: this.http, + uikitcontext: context + }) + return context + .getInteractionResponder() + .openModalViewResponse(addSubscriptionModal); + } + case ModalsEnum.DELETE_SUBSCRIPTION_ACTION: { + + + let { user, room } = await context.getInteractionData(); + let accessToken = await getAccessTokenForUser(this.read, user, this.app.oauth2Config) as IAuthData; + let value: string = context.getInteractionData().value as string; + let splitted = value.split(','); + if (splitted.length == 2 && accessToken.token) { + let repoName = splitted[0]; + let hookId = splitted[1]; + let roomId; + if (room?.id) { + roomId = room.id; + await storeInteractionRoomData(this.persistence, user.id, roomId); + } else { + roomId = (await getInteractionRoomData(this.read.getPersistenceReader(), user.id)).roomId; + } + await deleteSubscription(this.http, repoName, accessToken.token, hookId); + let subsciptionStorage = new Subscription(this.persistence, this.read.getPersistenceReader()); + await subsciptionStorage.deleteSubscriptionsByRepoUser(repoName, roomId, user.id); + let userRoom = await this.read.getRoomReader().getById(roomId) as IRoom; + await sendNotification(this.read, this.modify, user, userRoom, `Unsubscribed to ${repoName} 🔕`); } - // await deleteSubscription(this.http,repoName,accessToken.token,hookId); - let subsciptionStorage = new Subscription(this.persistence,this.read.getPersistenceReader()); - await subsciptionStorage.deleteSubscriptionsByRepoUser(repoName,roomId,user.id); + + const modal = await deleteSubsciptionsModal({ modify: this.modify, read: this.read, persistence: this.persistence, http: this.http, uikitcontext: context }); + await this.modify.getUiController().updateModalView(modal, { triggerId: context.getInteractionData().triggerId }, context.getInteractionData().user); + break; } - - - const modal = await deleteSubsciptionsModal({ modify: this.modify, read: this.read, persistence: this.persistence,http : this.http, uikitcontext: context }); - await this.modify.getUiController().updateModalView(modal, { triggerId: context.getInteractionData().triggerId }, context.getInteractionData().user); - break; } - } } catch (error) { - console.log(error); + console.log(error); } - + return context.getInteractionResponder().successResponse(); } } From dd3035305233e39c0dbcd4fda1ff47745e0636c3 Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Tue, 5 Jul 2022 03:21:49 +0530 Subject: [PATCH 17/32] Add Deployment status message --- github/endpoints/githubEndpoints.ts | 48 +++++++++++++++-------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/github/endpoints/githubEndpoints.ts b/github/endpoints/githubEndpoints.ts index 89d9c95..301e828 100644 --- a/github/endpoints/githubEndpoints.ts +++ b/github/endpoints/githubEndpoints.ts @@ -1,12 +1,12 @@ import { ApiEndpoint } from "@rocket.chat/apps-engine/definition/api" -import { IRead, IHttp, IModify,IPersistence } from "@rocket.chat/apps-engine/definition/accessors"; -import { IApiEndpointInfo,IApiEndpoint, IApiRequest,IApiResponse } from "@rocket.chat/apps-engine/definition/api"; +import { IRead, IHttp, IModify, IPersistence } from "@rocket.chat/apps-engine/definition/accessors"; +import { IApiEndpointInfo, IApiEndpoint, IApiRequest, IApiResponse } from "@rocket.chat/apps-engine/definition/api"; import { Subscription } from "../persistance/subscriptions"; import { ISubscription } from "../definitions/subscription"; import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; -export class githubWebHooks extends ApiEndpoint{ +export class githubWebHooks extends ApiEndpoint { public path = 'githubwebhook' - + public async post( request: IApiRequest, endpoint: IApiEndpointInfo, @@ -17,7 +17,7 @@ export class githubWebHooks extends ApiEndpoint{ ): Promise { console.log(request.content.toString()); - let event: string = request.headers['x-github-event'] as string; + let event: string = request.headers['x-github-event'] as string; let payload: any; @@ -27,33 +27,35 @@ export class githubWebHooks extends ApiEndpoint{ payload = request.content; } - let subsciptionStorage = new Subscription(persis,read.getPersistenceReader()) + let subsciptionStorage = new Subscription(persis, read.getPersistenceReader()) - const subsciptions: Array = await subsciptionStorage.getSubscribedRooms(payload.repository.full_name,event); - if(!subsciptions || subsciptions.length==0){ + const subsciptions: Array = await subsciptionStorage.getSubscribedRooms(payload.repository.full_name, event); + if (!subsciptions || subsciptions.length == 0) { return this.success(); } - const eventCaps= event.toUpperCase(); + const eventCaps = event.toUpperCase(); let messageText = "newEvent !"; - if(event=='push'){ - messageText = `*New Commits to* *[${payload.repository.full_name}](${payload.repository.html_url}) by ${payload.pusher.name}*`; - }else if (event == 'pull_request'){ - messageText = `*[New Pull Reqeust](${payload.pull_request.html_url})* *|* *#${payload.pull_request.number} ${payload.pull_request.title}* by *[${payload.user.login}](${payload.user.html_url})* *|* *[${payload.repository.full_name}]*`; - }else if (event == 'issues'){ - messageText = `*[New Issue](${payload.issue.html_url})* *|* *#${payload.issue.number}* *${payload.issue.title}* *|* *[${payload.repository.full_name}](${payload.repository.html_url})* `; + if (event == 'push') { + messageText = `*New Commits to* *[${payload.repository.full_name}](${payload.repository.html_url}) by ${payload.pusher.name}*`; + } else if (event == 'pull_request') { + messageText = `*[New Pull Reqeust](${payload.pull_request.html_url})* *|* *#${payload.pull_request.number} ${payload.pull_request.title}* by *[${payload.user.login}](${payload.user.html_url})* *|* *[${payload.repository.full_name}]*`; + } else if (event == 'issues') { + messageText = `*[New Issue](${payload.issue.html_url})* *|* *#${payload.issue.number}* *${payload.issue.title}* *|* *[${payload.repository.full_name}](${payload.repository.html_url})* `; + } else if (event == 'deployment_status'){ + messageText = `*Deployment Status ${payload.deployment_status.state}* *|* *${payload.repository.full_name}*`; } - - for(let subsciption of subsciptions){ - let roomId = subsciption.room; - if(!roomId){ + + for (let subsciption of subsciptions) { + let roomId = subsciption.room; + if (!roomId) { continue; } - const room :IRoom= await read.getRoomReader().getById(roomId) as IRoom; + const room: IRoom = await read.getRoomReader().getById(roomId) as IRoom; const textSender = await modify - .getCreator() - .startMessage() - .setText(messageText); + .getCreator() + .startMessage() + .setText(messageText); if (room) { textSender.setRoom(room); } From 64124d9e5cbcd732e93304cbb917ff0b919f0e34 Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Tue, 5 Jul 2022 04:54:43 +0530 Subject: [PATCH 18/32] Add Update Webhook events logic --- github/endpoints/githubEndpoints.ts | 4 -- github/handlers/ExecuteViewSubmitHandler.ts | 40 +++++++++++++++-- github/helpers/githubSDK.ts | 50 +++++++++++++++++++++ 3 files changed, 86 insertions(+), 8 deletions(-) diff --git a/github/endpoints/githubEndpoints.ts b/github/endpoints/githubEndpoints.ts index 301e828..62957b7 100644 --- a/github/endpoints/githubEndpoints.ts +++ b/github/endpoints/githubEndpoints.ts @@ -15,12 +15,8 @@ export class githubWebHooks extends ApiEndpoint { http: IHttp, persis: IPersistence, ): Promise { - console.log(request.content.toString()); - let event: string = request.headers['x-github-event'] as string; - let payload: any; - if (request.headers['content-type'] === 'application/x-www-form-urlencoded') { payload = JSON.parse(request.content.payload); } else { diff --git a/github/handlers/ExecuteViewSubmitHandler.ts b/github/handlers/ExecuteViewSubmitHandler.ts index 13ec878..4709a9e 100644 --- a/github/handlers/ExecuteViewSubmitHandler.ts +++ b/github/handlers/ExecuteViewSubmitHandler.ts @@ -7,7 +7,7 @@ import { getInteractionRoomData } from '../persistance/roomInteraction'; import { Subscription } from '../persistance/subscriptions'; import { GithubApp } from '../GithubApp'; import { getWebhookUrl } from '../helpers/getWebhookURL'; -import { createSubscription } from '../helpers/githubSDK'; +import { addSubscribedEvents, createSubscription, updateSubscription } from '../helpers/githubSDK'; import { getAccessTokenForUser } from '../persistance/auth'; import { subsciptionsModal } from '../modals/subscriptionsModal'; @@ -43,11 +43,43 @@ export class ExecuteViewSubmitHandler { await sendNotification(this.read,this.modify,user,room,"Login To Github !"); }else{ + //if we have a webhook for the repo and our room requires the same event,we just make our entries to the apps storage instead of making a new hook + //if we have a hook but we dont have all the events, we send in a patch request, + let url = await getWebhookUrl(this.app); - let response = await createSubscription(this.http,repository,url,accessToken.token,events); - let subsciptionStorage = new Subscription(this.persistence,this.read.getPersistenceReader()) - + + let subsciptionStorage = new Subscription(this.persistence,this.read.getPersistenceReader()); + let subscribedEvents=new Map; + let hookId= ""; + for(let event of events){ + subscribedEvents.set(event,false); + let subscriptions = await subsciptionStorage.getSubscribedRooms(repository,event); + if(subscriptions && subscriptions.length){ + subscribedEvents.set(event,true); + for(let subscription of subscriptions ){ + if(hookId==""){ + hookId=subscription.webhookId; + } + } + } + } + let response:any; + //if hook is null we create a new hook, else we add more events to the new hook + if(hookId == ""){ + response = await createSubscription(this.http,repository,url,accessToken.token,events); + }else{ + let newEvents:Array = []; + for(let [event,present] of subscribedEvents){ + if(!present){ + newEvents.push(event); + } + } + if(newEvents.length){ + response = await addSubscribedEvents(this.http,repository,accessToken.token,hookId,events); + } + } let createdEntry = false ; + //subscribe rooms to hook events for(let event of events){ createdEntry= await subsciptionStorage.createSubscription(repository,event,response?.id,room,user); } diff --git a/github/helpers/githubSDK.ts b/github/helpers/githubSDK.ts index ffd1b95..1f32ebb 100644 --- a/github/helpers/githubSDK.ts +++ b/github/helpers/githubSDK.ts @@ -47,6 +47,29 @@ async function deleteReqeust( return JSON.parse(response.content || "{}"); } +async function patchReqeust( + http: IHttp, + accessToken: String, + url: string, + data: any +): Promise { + const response = await http.patch(url, { + headers: { + Authorization: `token ${accessToken}`, + "Content-Type": "application/json", + "User-Agent": "Rocket.Chat-Apps-Engine", + }, + data, + }); + + // If it isn't a 2xx code, something wrong happened + if (!response.statusCode.toString().startsWith("2")) { + throw response; + } + + return JSON.parse(response.content || "{}"); +} + export async function createSubscription( http: IHttp, repoName: string, @@ -72,3 +95,30 @@ export async function deleteSubscription( ) { return deleteReqeust(http, access_token, BaseApiHost + repoName + "/hooks/" + hookId,); } + + +export async function updateSubscription( + http: IHttp, + repoName: string, + access_token: string, + hookId: string, + events: Array +) { + return patchReqeust(http, access_token, BaseApiHost + repoName + "/hooks/" + hookId,{ + active: true, + events: events + }); +} + +export async function addSubscribedEvents( + http: IHttp, + repoName: string, + access_token: string, + hookId: string, + events: Array +) { + return patchReqeust(http, access_token, BaseApiHost + repoName + "/hooks/" + hookId,{ + active: true, + add_events: events + }); +} From b91e628d2bd381328aea6b8b5d407ed3db2fc67b Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Tue, 5 Jul 2022 05:10:52 +0530 Subject: [PATCH 19/32] Add- Open updated subsscriptons moda; --- github/handlers/ExecuteViewSubmitHandler.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/github/handlers/ExecuteViewSubmitHandler.ts b/github/handlers/ExecuteViewSubmitHandler.ts index 4709a9e..9d8b347 100644 --- a/github/handlers/ExecuteViewSubmitHandler.ts +++ b/github/handlers/ExecuteViewSubmitHandler.ts @@ -90,6 +90,8 @@ export class ExecuteViewSubmitHandler { } } + const modal = await subsciptionsModal({ modify: this.modify, read: this.read, persistence: this.persistence, http:this.http, uikitcontext: context }); + await this.modify.getUiController().updateModalView(modal, { triggerId: context.getInteractionData().triggerId }, context.getInteractionData().user); return context.getInteractionResponder().successResponse(); } } From cc388a4f58f9d8e9b22a06c64e038bcf58148d97 Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Tue, 5 Jul 2022 06:53:14 +0530 Subject: [PATCH 20/32] Enable common webhook for repo logic --- github/handlers/ExecuteBlockActionHandler.ts | 34 +++++++++++++++++--- github/handlers/ExecuteViewSubmitHandler.ts | 31 ++++++++++-------- github/helpers/githubSDK.ts | 14 ++++++++ github/persistance/subscriptions.ts | 17 ++++++---- 4 files changed, 72 insertions(+), 24 deletions(-) diff --git a/github/handlers/ExecuteBlockActionHandler.ts b/github/handlers/ExecuteBlockActionHandler.ts index 5f45b64..e245c95 100644 --- a/github/handlers/ExecuteBlockActionHandler.ts +++ b/github/handlers/ExecuteBlockActionHandler.ts @@ -16,7 +16,7 @@ import { } from "@rocket.chat/apps-engine/definition/uikit"; import { AddSubscriptionModal } from "../modals/addSubscriptionsModal"; import { deleteSubsciptionsModal } from "../modals/deleteSubscriptions"; -import { deleteSubscription } from "../helpers/githubSDK"; +import { deleteSubscription, updateSubscription } from "../helpers/githubSDK"; import { Subscription } from "../persistance/subscriptions"; import { getAccessTokenForUser } from "../persistance/auth"; import { GithubApp } from "../GithubApp"; @@ -63,7 +63,6 @@ export class ExecuteBlockActionHandler { const room: IRoom = context.getInteractionData() .room as IRoom; - console.log("PRESS", query); await basicQueryMessage({ query, repository, @@ -139,9 +138,34 @@ export class ExecuteBlockActionHandler { } else { roomId = (await getInteractionRoomData(this.read.getPersistenceReader(), user.id)).roomId; } - await deleteSubscription(this.http, repoName, accessToken.token, hookId); - let subsciptionStorage = new Subscription(this.persistence, this.read.getPersistenceReader()); - await subsciptionStorage.deleteSubscriptionsByRepoUser(repoName, roomId, user.id); + //delete the susbscriptions for persistance + let subscriptionStorage = new Subscription(this.persistence, this.read.getPersistenceReader()); + let oldSubscriptions = await subscriptionStorage.getSubscriptionsByRepo(repoName,user.id); + await subscriptionStorage.deleteSubscriptionsByRepoUser(repoName, roomId, user.id); + //check if any subscription events of the repo is left in any other room + let eventSubscriptions = new Map; + for(let subsciption of oldSubscriptions){ + eventSubscriptions.set(subsciption.event,false); + } + let updatedsubscriptions = await subscriptionStorage.getSubscriptionsByRepo(repoName,user.id); + if(updatedsubscriptions.length==0){ + await deleteSubscription(this.http,repoName,accessToken.token,hookId); + }else{ + for(let subsciption of updatedsubscriptions){ + eventSubscriptions.set(subsciption.event,true); + } + let updatedEvents :Array=[]; + let sameEvents = true; + for(let [event,present] of eventSubscriptions ){ + sameEvents=sameEvents&&present; + if(present){ + updatedEvents.push(event); + } + } + if(updatedEvents.length && !sameEvents){ + let response = await updateSubscription(this.http,repoName,accessToken.token,hookId,updatedEvents); + } + } let userRoom = await this.read.getRoomReader().getById(roomId) as IRoom; await sendNotification(this.read, this.modify, user, userRoom, `Unsubscribed to ${repoName} 🔕`); } diff --git a/github/handlers/ExecuteViewSubmitHandler.ts b/github/handlers/ExecuteViewSubmitHandler.ts index 9d8b347..dfdafc4 100644 --- a/github/handlers/ExecuteViewSubmitHandler.ts +++ b/github/handlers/ExecuteViewSubmitHandler.ts @@ -51,16 +51,22 @@ export class ExecuteViewSubmitHandler { let subsciptionStorage = new Subscription(this.persistence,this.read.getPersistenceReader()); let subscribedEvents=new Map; let hookId= ""; + + + let subscriptions = await subsciptionStorage.getSubscriptionsByRepo(repository,user.id); + if(subscriptions && subscriptions.length){ + for(let subscription of subscriptions){ + subscribedEvents.set(subscription.event,true); + if(hookId==""){ + hookId=subscription.webhookId; + } + } + } + let additionalEvents=0; for(let event of events){ - subscribedEvents.set(event,false); - let subscriptions = await subsciptionStorage.getSubscribedRooms(repository,event); - if(subscriptions && subscriptions.length){ + if(!subscribedEvents.has(event)){ + additionalEvents++; subscribedEvents.set(event,true); - for(let subscription of subscriptions ){ - if(hookId==""){ - hookId=subscription.webhookId; - } - } } } let response:any; @@ -68,14 +74,13 @@ export class ExecuteViewSubmitHandler { if(hookId == ""){ response = await createSubscription(this.http,repository,url,accessToken.token,events); }else{ + //if hook is already present, we just need to send a patch request to add new events to existing hook let newEvents:Array = []; for(let [event,present] of subscribedEvents){ - if(!present){ - newEvents.push(event); - } + newEvents.push(event); } - if(newEvents.length){ - response = await addSubscribedEvents(this.http,repository,accessToken.token,hookId,events); + if(additionalEvents && newEvents.length){ + response = await updateSubscription(this.http,repository,accessToken.token,hookId,newEvents); } } let createdEntry = false ; diff --git a/github/helpers/githubSDK.ts b/github/helpers/githubSDK.ts index 1f32ebb..2bbd83a 100644 --- a/github/helpers/githubSDK.ts +++ b/github/helpers/githubSDK.ts @@ -122,3 +122,17 @@ export async function addSubscribedEvents( add_events: events }); } + +export async function removeSubscribedEvents( + http: IHttp, + repoName: string, + access_token: string, + hookId: string, + events: Array +) { + return patchReqeust(http, access_token, BaseApiHost + repoName + "/hooks/" + hookId,{ + active: true, + add_events: events + }); +} + diff --git a/github/persistance/subscriptions.ts b/github/persistance/subscriptions.ts index c0b4cfd..76234ae 100644 --- a/github/persistance/subscriptions.ts +++ b/github/persistance/subscriptions.ts @@ -109,15 +109,20 @@ export class Subscription { return true; } - public async deleteAllRoomSubscriptionsByHookId(hookId: string): Promise { + public async getSubscriptionsByRepo(repoName: string, userId: string): Promise> { + let subsciptions: Array=[]; try { - let association = new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, hookId) - await this.persistence.removeByAssociation(association); + const associations: Array = [ + new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `subscription`), + new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `repo:${repoName}`), + new RocketChatAssociationRecord(RocketChatAssociationModel.USER, `${userId}`) + ]; + subsciptions= await this.persistenceRead.readByAssociations(associations) as Array; } catch (error) { - console.warn('Delete All Room Subsciption Error :', error) - return false; + console.warn('Get Subsciptions By Repo Error :', error) + return subsciptions; } - return true; + return subsciptions; } } \ No newline at end of file From 9409c5c2f224bc59ec6b6f7a016c19e5fcc5158a Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Tue, 5 Jul 2022 07:09:51 +0530 Subject: [PATCH 21/32] add refresh button --- github/GithubApp.ts | 1 - github/enum/Modals.ts | 9 +- github/handlers/ExecuteBlockActionHandler.ts | 38 ++++--- github/handlers/ExecuteViewClosedHandler.ts | 2 +- github/handlers/ExecuteViewSubmitHandler.ts | 114 +++++++++---------- github/helpers/basicQueryMessage.ts | 14 +-- github/helpers/getWebhookURL.ts | 6 +- github/helpers/githubSDK.ts | 8 +- github/modals/subscriptionsModal.ts | 8 +- github/oath2/oath2Config.ts | 17 --- github/oath2/oath2callback.ts | 36 ------ github/persistance/auth.ts | 2 +- 12 files changed, 101 insertions(+), 154 deletions(-) delete mode 100644 github/oath2/oath2Config.ts delete mode 100644 github/oath2/oath2callback.ts diff --git a/github/GithubApp.ts b/github/GithubApp.ts index f3dbb0e..a88986d 100644 --- a/github/GithubApp.ts +++ b/github/GithubApp.ts @@ -31,7 +31,6 @@ import { sendDirectMessage } from "./lib/message"; import { OAuth2Client } from "@rocket.chat/apps-engine/server/oauth2/OAuth2Client"; import { deleteOathToken } from "./processors/deleteOAthToken"; import { ProcessorsEnum } from "./enum/Processors"; -import getOauth2Config from "./oath2/oath2Config"; import { ApiSecurity, ApiVisibility, diff --git a/github/enum/Modals.ts b/github/enum/Modals.ts index d4638b4..3601424 100644 --- a/github/enum/Modals.ts +++ b/github/enum/Modals.ts @@ -11,7 +11,6 @@ export enum ModalsEnum { MERGE_PULL_REQUEST_LABEL = 'Merge', COMMENT_PR_ACTION = 'comment-pull-request', COMMENT_PR_LABEL = 'Comment', - SUBSCRIPTION_TITLE='GitHub Subscriptions', SUBSCRIPTION_VIEW = 'subscriptions-view', ADD_SUBSCRIPTION_VIEW = 'add-subscription-view', @@ -29,10 +28,8 @@ export enum ModalsEnum { UPDATE_SUBSCRIPTION_ACTION='update-subscription', UPDATE_SUBSCRIPTION_LABEL='Update', DELETE_SUBSCIPTIONS_TITLE='Unsubscribe to Repositories', - OPEN_REPO_ACTION='open-repo', OPEN_REPO_LABEL='Open', - REPO_NAME_INPUT='repo-name-input', REPO_NAME_LABEL='Enter Full Repository Name', REPO_NAME_PLACEHOLDER='Owner/Repository', @@ -40,8 +37,6 @@ export enum ModalsEnum { ADD_SUBSCRIPTION_EVENT_LABEL='Add Events', ADD_SUBSCRIPTION_EVENT_OPTIONS='add-repo-subscription-events', ADD_SUBSCRIPTION_EVENT_INPUT='add-repo-subscription-events', - - - - + SUBSCRIPTION_REFRESH_LABEL="Refresh", + SUBSCRIPTION_REFRESH_ACTION="subscription-refresh", } \ No newline at end of file diff --git a/github/handlers/ExecuteBlockActionHandler.ts b/github/handlers/ExecuteBlockActionHandler.ts index e245c95..9d013e0 100644 --- a/github/handlers/ExecuteBlockActionHandler.ts +++ b/github/handlers/ExecuteBlockActionHandler.ts @@ -23,6 +23,7 @@ import { GithubApp } from "../GithubApp"; import { IAuthData } from "@rocket.chat/apps-engine/definition/oauth2/IOAuth2"; import { storeInteractionRoomData, getInteractionRoomData } from "../persistance/roomInteraction"; import { sendNotification } from "../lib/message"; +import { subsciptionsModal } from "../modals/subscriptionsModal"; export class ExecuteBlockActionHandler { constructor( private readonly app: GithubApp, @@ -140,30 +141,30 @@ export class ExecuteBlockActionHandler { } //delete the susbscriptions for persistance let subscriptionStorage = new Subscription(this.persistence, this.read.getPersistenceReader()); - let oldSubscriptions = await subscriptionStorage.getSubscriptionsByRepo(repoName,user.id); + let oldSubscriptions = await subscriptionStorage.getSubscriptionsByRepo(repoName, user.id); await subscriptionStorage.deleteSubscriptionsByRepoUser(repoName, roomId, user.id); //check if any subscription events of the repo is left in any other room - let eventSubscriptions = new Map; - for(let subsciption of oldSubscriptions){ - eventSubscriptions.set(subsciption.event,false); + let eventSubscriptions = new Map; + for (let subsciption of oldSubscriptions) { + eventSubscriptions.set(subsciption.event, false); } - let updatedsubscriptions = await subscriptionStorage.getSubscriptionsByRepo(repoName,user.id); - if(updatedsubscriptions.length==0){ - await deleteSubscription(this.http,repoName,accessToken.token,hookId); - }else{ - for(let subsciption of updatedsubscriptions){ - eventSubscriptions.set(subsciption.event,true); + let updatedsubscriptions = await subscriptionStorage.getSubscriptionsByRepo(repoName, user.id); + if (updatedsubscriptions.length == 0) { + await deleteSubscription(this.http, repoName, accessToken.token, hookId); + } else { + for (let subsciption of updatedsubscriptions) { + eventSubscriptions.set(subsciption.event, true); } - let updatedEvents :Array=[]; + let updatedEvents: Array = []; let sameEvents = true; - for(let [event,present] of eventSubscriptions ){ - sameEvents=sameEvents&&present; - if(present){ + for (let [event, present] of eventSubscriptions) { + sameEvents = sameEvents && present; + if (present) { updatedEvents.push(event); } } - if(updatedEvents.length && !sameEvents){ - let response = await updateSubscription(this.http,repoName,accessToken.token,hookId,updatedEvents); + if (updatedEvents.length && !sameEvents) { + let response = await updateSubscription(this.http, repoName, accessToken.token, hookId, updatedEvents); } } let userRoom = await this.read.getRoomReader().getById(roomId) as IRoom; @@ -174,6 +175,11 @@ export class ExecuteBlockActionHandler { await this.modify.getUiController().updateModalView(modal, { triggerId: context.getInteractionData().triggerId }, context.getInteractionData().user); break; } + case ModalsEnum.SUBSCRIPTION_REFRESH_ACTION:{ + const modal = await subsciptionsModal({ modify: this.modify, read: this.read, persistence: this.persistence, http: this.http, uikitcontext: context }); + await this.modify.getUiController().updateModalView(modal, { triggerId: context.getInteractionData().triggerId }, context.getInteractionData().user); + break; + } } } catch (error) { diff --git a/github/handlers/ExecuteViewClosedHandler.ts b/github/handlers/ExecuteViewClosedHandler.ts index 5c7c502..6b117d5 100644 --- a/github/handlers/ExecuteViewClosedHandler.ts +++ b/github/handlers/ExecuteViewClosedHandler.ts @@ -16,7 +16,7 @@ export class ExecuteViewClosedHandler { private readonly http: IHttp, private readonly modify: IModify, private readonly persistence: IPersistence - ) {} + ) { } public async run(context: UIKitViewCloseInteractionContext) { const { view } = context.getInteractionData(); diff --git a/github/handlers/ExecuteViewSubmitHandler.ts b/github/handlers/ExecuteViewSubmitHandler.ts index dfdafc4..1e99dd7 100644 --- a/github/handlers/ExecuteViewSubmitHandler.ts +++ b/github/handlers/ExecuteViewSubmitHandler.ts @@ -13,89 +13,89 @@ import { subsciptionsModal } from '../modals/subscriptionsModal'; export class ExecuteViewSubmitHandler { - constructor( - private readonly app: GithubApp, - private readonly read: IRead, - private readonly http: IHttp, - private readonly modify: IModify, - private readonly persistence: IPersistence, - ) {} + constructor( + private readonly app: GithubApp, + private readonly read: IRead, + private readonly http: IHttp, + private readonly modify: IModify, + private readonly persistence: IPersistence, + ) { } - public async run(context: UIKitViewSubmitInteractionContext) { - const { user, view } = context.getInteractionData(); + public async run(context: UIKitViewSubmitInteractionContext) { + const { user, view } = context.getInteractionData(); try { switch (view.id) { case ModalsEnum.ADD_SUBSCRIPTION_VIEW: if (user.id) { - const { roomId } = await getInteractionRoomData(this.read.getPersistenceReader(),user.id); + const { roomId } = await getInteractionRoomData(this.read.getPersistenceReader(), user.id); if (roomId) { let room = await this.read.getRoomReader().getById(roomId) as IRoom; const repository = view.state?.[ModalsEnum.REPO_NAME_INPUT]?.[ModalsEnum.REPO_NAME_INPUT_ACTION]; const events = view.state?.[ModalsEnum.ADD_SUBSCRIPTION_EVENT_INPUT]?.[ModalsEnum.ADD_SUBSCRIPTION_EVENT_OPTIONS]; - - if(typeof(repository) == undefined || typeof(events) == undefined){ - - await sendNotification(this.read,this.modify,user,room,"Invalid Input !"); - }else{ - let accessToken = await getAccessTokenForUser(this.read,user,this.app.oauth2Config); - if(!accessToken){ - - await sendNotification(this.read,this.modify,user,room,"Login To Github !"); - }else{ + + if (typeof (repository) == undefined || typeof (events) == undefined) { + + await sendNotification(this.read, this.modify, user, room, "Invalid Input !"); + } else { + let accessToken = await getAccessTokenForUser(this.read, user, this.app.oauth2Config); + if (!accessToken) { + + await sendNotification(this.read, this.modify, user, room, "Login To Github !"); + } else { //if we have a webhook for the repo and our room requires the same event,we just make our entries to the apps storage instead of making a new hook //if we have a hook but we dont have all the events, we send in a patch request, - + let url = await getWebhookUrl(this.app); - let subsciptionStorage = new Subscription(this.persistence,this.read.getPersistenceReader()); - let subscribedEvents=new Map; - let hookId= ""; - - - let subscriptions = await subsciptionStorage.getSubscriptionsByRepo(repository,user.id); - if(subscriptions && subscriptions.length){ - for(let subscription of subscriptions){ - subscribedEvents.set(subscription.event,true); - if(hookId==""){ - hookId=subscription.webhookId; + let subsciptionStorage = new Subscription(this.persistence, this.read.getPersistenceReader()); + let subscribedEvents = new Map; + let hookId = ""; + + + let subscriptions = await subsciptionStorage.getSubscriptionsByRepo(repository, user.id); + if (subscriptions && subscriptions.length) { + for (let subscription of subscriptions) { + subscribedEvents.set(subscription.event, true); + if (hookId == "") { + hookId = subscription.webhookId; } } } - let additionalEvents=0; - for(let event of events){ - if(!subscribedEvents.has(event)){ + let additionalEvents = 0; + for (let event of events) { + if (!subscribedEvents.has(event)) { additionalEvents++; - subscribedEvents.set(event,true); + subscribedEvents.set(event, true); } } - let response:any; + let response: any; //if hook is null we create a new hook, else we add more events to the new hook - if(hookId == ""){ - response = await createSubscription(this.http,repository,url,accessToken.token,events); - }else{ + if (hookId == "") { + response = await createSubscription(this.http, repository, url, accessToken.token, events); + } else { //if hook is already present, we just need to send a patch request to add new events to existing hook - let newEvents:Array = []; - for(let [event,present] of subscribedEvents){ + let newEvents: Array = []; + for (let [event, present] of subscribedEvents) { newEvents.push(event); } - if(additionalEvents && newEvents.length){ - response = await updateSubscription(this.http,repository,accessToken.token,hookId,newEvents); + if (additionalEvents && newEvents.length) { + response = await updateSubscription(this.http, repository, accessToken.token, hookId, newEvents); } } - let createdEntry = false ; + let createdEntry = false; //subscribe rooms to hook events - for(let event of events){ - createdEntry= await subsciptionStorage.createSubscription(repository,event,response?.id,room,user); + for (let event of events) { + createdEntry = await subsciptionStorage.createSubscription(repository, event, response?.id, room, user); } - if(!createdEntry){ + if (!createdEntry) { throw new Error("Error creating new subscription entry"); } - await sendNotification(this.read,this.modify,user,room,`Subscibed to ${repository} ✔️`); + await sendNotification(this.read, this.modify, user, room, `Subscibed to ${repository} ✔️`); } - + } - const modal = await subsciptionsModal({ modify: this.modify, read: this.read, persistence: this.persistence, http:this.http, uikitcontext: context }); + const modal = await subsciptionsModal({ modify: this.modify, read: this.read, persistence: this.persistence, http: this.http, uikitcontext: context }); await this.modify.getUiController().updateModalView(modal, { triggerId: context.getInteractionData().triggerId }, context.getInteractionData().user); return context.getInteractionResponder().successResponse(); } @@ -104,13 +104,13 @@ export class ExecuteViewSubmitHandler { default: break; } - + } catch (error) { - console.log('error : ',error); + console.log('error : ', error); } - return { - success: true, - }; - } + return { + success: true, + }; + } } \ No newline at end of file diff --git a/github/helpers/basicQueryMessage.ts b/github/helpers/basicQueryMessage.ts index b82fd6d..71eec6d 100644 --- a/github/helpers/basicQueryMessage.ts +++ b/github/helpers/basicQueryMessage.ts @@ -21,7 +21,7 @@ export async function basicQueryMessage({ http, }: { query: String, - repository:String, + repository: String, room: IRoom; read: IRead; persistence: IPersistence; @@ -31,22 +31,22 @@ export async function basicQueryMessage({ switch (query) { case "issues": { - await issueListMessage({repository,room,read,persistence,modify,http}); + await issueListMessage({ repository, room, read, persistence, modify, http }); break; } case "contributors": { - await contributorListMessage({repository,room,read,persistence,modify,http}); + await contributorListMessage({ repository, room, read, persistence, modify, http }); break; } case "pulls": { - await pullRequestListMessage({repository,room,read,persistence,modify,http}); + await pullRequestListMessage({ repository, room, read, persistence, modify, http }); break; } case "repo": { - await repoDataMessage({repository,room,read,persistence,modify,http}) + await repoDataMessage({ repository, room, read, persistence, modify, http }) break; } - default: - await helperMessage({room,read,persistence,modify,http}); + default: + await helperMessage({ room, read, persistence, modify, http }); } } diff --git a/github/helpers/getWebhookURL.ts b/github/helpers/getWebhookURL.ts index d967dbe..fa2a869 100644 --- a/github/helpers/getWebhookURL.ts +++ b/github/helpers/getWebhookURL.ts @@ -4,9 +4,9 @@ import { GithubApp } from '../GithubApp'; export async function getWebhookUrl(app: GithubApp): Promise { const accessors = app.getAccessors(); const webhookEndpoint = accessors.providedApiEndpoints.find((endpoint) => endpoint.path === 'githubwebhook') as IApiEndpointMetadata; - let siteUrl : string = await accessors.environmentReader.getServerSettings().getValueById('Site_Url') as string; - if(siteUrl.charAt(siteUrl.length - 1) === '/'){ - siteUrl = siteUrl.substring(0,siteUrl.length-1); + let siteUrl: string = await accessors.environmentReader.getServerSettings().getValueById('Site_Url') as string; + if (siteUrl.charAt(siteUrl.length - 1) === '/') { + siteUrl = siteUrl.substring(0, siteUrl.length - 1); } return siteUrl + webhookEndpoint.computedPath; } \ No newline at end of file diff --git a/github/helpers/githubSDK.ts b/github/helpers/githubSDK.ts index 2bbd83a..eeb8dec 100644 --- a/github/helpers/githubSDK.ts +++ b/github/helpers/githubSDK.ts @@ -93,7 +93,7 @@ export async function deleteSubscription( access_token: string, hookId: string ) { - return deleteReqeust(http, access_token, BaseApiHost + repoName + "/hooks/" + hookId,); + return deleteReqeust(http, access_token, BaseApiHost + repoName + "/hooks/" + hookId,); } @@ -104,7 +104,7 @@ export async function updateSubscription( hookId: string, events: Array ) { - return patchReqeust(http, access_token, BaseApiHost + repoName + "/hooks/" + hookId,{ + return patchReqeust(http, access_token, BaseApiHost + repoName + "/hooks/" + hookId, { active: true, events: events }); @@ -117,7 +117,7 @@ export async function addSubscribedEvents( hookId: string, events: Array ) { - return patchReqeust(http, access_token, BaseApiHost + repoName + "/hooks/" + hookId,{ + return patchReqeust(http, access_token, BaseApiHost + repoName + "/hooks/" + hookId, { active: true, add_events: events }); @@ -130,7 +130,7 @@ export async function removeSubscribedEvents( hookId: string, events: Array ) { - return patchReqeust(http, access_token, BaseApiHost + repoName + "/hooks/" + hookId,{ + return patchReqeust(http, access_token, BaseApiHost + repoName + "/hooks/" + hookId, { active: true, add_events: events }); diff --git a/github/modals/subscriptionsModal.ts b/github/modals/subscriptionsModal.ts index 133ed6e..4d6f01f 100644 --- a/github/modals/subscriptionsModal.ts +++ b/github/modals/subscriptionsModal.ts @@ -107,13 +107,13 @@ export async function subsciptionsModal({ modify, read, persistence, http, slash value: room?.id }), block.newButtonElement({ - actionId: ModalsEnum.OPEN_UPDATE_SUBSCRIPTIONS_MODAL, - text: { text: ModalsEnum.OPEN_UPDATE_SUBSCRIPTIONS_LABEL, type: TextObjectType.PLAINTEXT }, + actionId: ModalsEnum.OPEN_DELETE_SUBSCRIPTIONS_MODAL, + text: { text: ModalsEnum.OPEN_DELETE_SUBSCRIPTIONS_LABEL, type: TextObjectType.PLAINTEXT }, value: room?.id }), block.newButtonElement({ - actionId: ModalsEnum.OPEN_DELETE_SUBSCRIPTIONS_MODAL, - text: { text: ModalsEnum.OPEN_DELETE_SUBSCRIPTIONS_LABEL, type: TextObjectType.PLAINTEXT }, + actionId: ModalsEnum.SUBSCRIPTION_REFRESH_ACTION, + text: { text: ModalsEnum.SUBSCRIPTION_REFRESH_LABEL, type: TextObjectType.PLAINTEXT }, value: room?.id }), ] diff --git a/github/oath2/oath2Config.ts b/github/oath2/oath2Config.ts deleted file mode 100644 index 6c412e9..0000000 --- a/github/oath2/oath2Config.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { IOAuth2ClientOptions } from "@rocket.chat/apps-engine/definition/oauth2/IOAuth2"; -import { GithubApp } from "../GithubApp"; -import authorizationCallback from "./oath2callback" - -export default function getOauth2Config(app:GithubApp):IOAuth2ClientOptions{ - let oauth2Config: IOAuth2ClientOptions = { - alias: "github-app", - accessTokenUri: "https://github.com/login/oauth/access_token", - authUri: "https://github.com/login/oauth/authorize", - refreshTokenUri: "https://github.com/login/oauth/access_token", - revokeTokenUri: "https://github.com/login/oauth/access_token", - authorizationCallback: authorizationCallback.bind(app), - defaultScopes: ["users", "repo"], - }; - return oauth2Config; -} - diff --git a/github/oath2/oath2callback.ts b/github/oath2/oath2callback.ts deleted file mode 100644 index 45e366d..0000000 --- a/github/oath2/oath2callback.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { - IHttp, - IModify, - IPersistence, - IRead, -} from "@rocket.chat/apps-engine/definition/accessors"; -import { IUser } from "@rocket.chat/apps-engine/definition/users"; -import { IAuthData } from "@rocket.chat/apps-engine/definition/oauth2/IOAuth2"; -import { sendDirectMessage } from "../lib/message"; -import { ProcessorsEnum } from "../enum/Processors"; - -export default async function authorizationCallback( - token: IAuthData, - user: IUser, - read: IRead, - modify: IModify, - http: IHttp, - persistence: IPersistence -) { - // const deleteTokenTask = { - // id: ProcessorsEnum.REMOVE_GITHUB_LOGIN, - // when: '7 seconds', - // data: { - // user - // }, - // }; - let text = `GitHub Authentication Succesfull 🚀`; - - if (token) { - // await registerAuthorizedUser(read, persistence, user); - // await modify.getScheduler().scheduleOnce(deleteTokenTask); - } else { - text = `Authentication Failure 😔`; - } - await sendDirectMessage(read, modify, user, text, persistence); -} \ No newline at end of file diff --git a/github/persistance/auth.ts b/github/persistance/auth.ts index e796d13..ed857e6 100644 --- a/github/persistance/auth.ts +++ b/github/persistance/auth.ts @@ -98,7 +98,7 @@ export async function revokeUserAccessToken(read:IRead,user: IUser, persis: IPer if (!tokenInfo?.token) { throw new Error('No access token available for this user.'); } - /*****fix revoking token***** + /*****fix revoking token***** The access token api throws an error let client_id = await read.getEnvironmentReader().getSettings().getValueById(`${config.alias}-oauth-client-id`); let client_secret = await read.getEnvironmentReader().getSettings().getValueById(`github-app-oauth-clientsecret`); const url = new URL(`https://api.github.com/applications/${client_id}/token`); From 1f625a83a5b4b1602ba6470a74696adf919094e3 Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Tue, 5 Jul 2022 07:12:45 +0530 Subject: [PATCH 22/32] Remove Comments --- github/persistance/auth.ts | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/github/persistance/auth.ts b/github/persistance/auth.ts index ed857e6..9e8e498 100644 --- a/github/persistance/auth.ts +++ b/github/persistance/auth.ts @@ -94,29 +94,9 @@ export async function removeToken({ export async function revokeUserAccessToken(read:IRead,user: IUser, persis: IPersistence, http:IHttp, config: IOAuth2ClientOptions): Promise { try { const tokenInfo = await getAccessTokenForUser(read,user,config); - if (!tokenInfo?.token) { throw new Error('No access token available for this user.'); } - /*****fix revoking token***** The access token api throws an error - let client_id = await read.getEnvironmentReader().getSettings().getValueById(`${config.alias}-oauth-client-id`); - let client_secret = await read.getEnvironmentReader().getSettings().getValueById(`github-app-oauth-clientsecret`); - const url = new URL(`https://api.github.com/applications/${client_id}/token`); - // https://docs.github.com/en/rest/apps/oauth-applications - const headers: any = { - Accept: "application/vnd.github.v3+json", - Authorization: `Bearer ${tokenInfo?.token}`, - }; - const body : any= { - "access_token":`${tokenInfo?.token}` - } - const result = await http.del(url.href,{headers,data:body}); - - if (result.statusCode !== 200) { - console.log(result.content); - throw new Error('Provider did not allow token to be revoked'); - } - ******/ await removeToken({ userId: user.id, persis,config }); return true; } catch (error) { From 21add2db861dfaf524f1206d6c8fcb4ec101f2cd Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Tue, 5 Jul 2022 07:26:06 +0530 Subject: [PATCH 23/32] Add Linting - proper tabs, remove white spaces --- github/commands/GithubCommand.ts | 10 - github/endpoints/githubEndpoints.ts | 54 +++-- github/handlers/ExecuteViewClosedHandler.ts | 25 +-- github/helpers/githubSDK.ts | 49 +++-- github/modals/addSubscriptionsModal.ts | 194 ++++++++++-------- github/modals/deleteSubscriptions.ts | 27 +-- github/modals/fileCodeModal.ts | 93 +++++---- github/modals/pullDetailsModal.ts | 147 ++++++++----- github/modals/subscriptionsModal.ts | 47 ++--- github/persistance/auth.ts | 98 +++++---- github/persistance/roomInteraction.ts | 53 ++++- github/persistance/subscriptions.ts | 215 +++++++++++++++----- github/processors/deleteOAthToken.ts | 32 ++- 13 files changed, 662 insertions(+), 382 deletions(-) diff --git a/github/commands/GithubCommand.ts b/github/commands/GithubCommand.ts index eb9896e..e6fbbe4 100644 --- a/github/commands/GithubCommand.ts +++ b/github/commands/GithubCommand.ts @@ -28,8 +28,6 @@ import { Subscription } from "../persistance/subscriptions"; import { ISubscription } from "../definitions/subscription"; import { subsciptionsModal } from "../modals/subscriptionsModal"; - - export class GithubCommand implements ISlashCommand { public constructor(private readonly app: GithubApp) {} public command = "github"; @@ -73,8 +71,6 @@ export class GithubCommand implements ISlashCommand { break; } case SubcommandEnum.TEST : { - // let a = await getWebhookUrl(this.app); - // console.log(a); //test command break; } @@ -89,10 +85,6 @@ export class GithubCommand implements ISlashCommand { } break; } - case SubcommandEnum.UNSUBSCRIBE :{ - //modal - break; - } default:{ await helperMessage({room,read, persistence, modify, http}); break; @@ -125,10 +117,8 @@ export class GithubCommand implements ISlashCommand { throw new Error("Error creating new susbcription entry"); } - await sendNotification(read,modify,context.getSender(),room,`Subscibed to ${repository} ✔️`); - } catch (error) { console.log("SubcommandError",error); } diff --git a/github/endpoints/githubEndpoints.ts b/github/endpoints/githubEndpoints.ts index 62957b7..b3ea114 100644 --- a/github/endpoints/githubEndpoints.ts +++ b/github/endpoints/githubEndpoints.ts @@ -1,11 +1,21 @@ -import { ApiEndpoint } from "@rocket.chat/apps-engine/definition/api" -import { IRead, IHttp, IModify, IPersistence } from "@rocket.chat/apps-engine/definition/accessors"; -import { IApiEndpointInfo, IApiEndpoint, IApiRequest, IApiResponse } from "@rocket.chat/apps-engine/definition/api"; +import { ApiEndpoint } from "@rocket.chat/apps-engine/definition/api"; +import { + IRead, + IHttp, + IModify, + IPersistence, +} from "@rocket.chat/apps-engine/definition/accessors"; +import { + IApiEndpointInfo, + IApiEndpoint, + IApiRequest, + IApiResponse, +} from "@rocket.chat/apps-engine/definition/api"; import { Subscription } from "../persistance/subscriptions"; import { ISubscription } from "../definitions/subscription"; import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; export class githubWebHooks extends ApiEndpoint { - public path = 'githubwebhook' + public path = "githubwebhook"; public async post( request: IApiRequest, @@ -13,32 +23,42 @@ export class githubWebHooks extends ApiEndpoint { read: IRead, modify: IModify, http: IHttp, - persis: IPersistence, + persis: IPersistence ): Promise { - let event: string = request.headers['x-github-event'] as string; + let event: string = request.headers["x-github-event"] as string; let payload: any; - if (request.headers['content-type'] === 'application/x-www-form-urlencoded') { + if ( + request.headers["content-type"] === + "application/x-www-form-urlencoded" + ) { payload = JSON.parse(request.content.payload); } else { payload = request.content; } - let subsciptionStorage = new Subscription(persis, read.getPersistenceReader()) + let subsciptionStorage = new Subscription( + persis, + read.getPersistenceReader() + ); - const subsciptions: Array = await subsciptionStorage.getSubscribedRooms(payload.repository.full_name, event); + const subsciptions: Array = + await subsciptionStorage.getSubscribedRooms( + payload.repository.full_name, + event + ); if (!subsciptions || subsciptions.length == 0) { return this.success(); } const eventCaps = event.toUpperCase(); let messageText = "newEvent !"; - if (event == 'push') { + if (event == "push") { messageText = `*New Commits to* *[${payload.repository.full_name}](${payload.repository.html_url}) by ${payload.pusher.name}*`; - } else if (event == 'pull_request') { + } else if (event == "pull_request") { messageText = `*[New Pull Reqeust](${payload.pull_request.html_url})* *|* *#${payload.pull_request.number} ${payload.pull_request.title}* by *[${payload.user.login}](${payload.user.html_url})* *|* *[${payload.repository.full_name}]*`; - } else if (event == 'issues') { + } else if (event == "issues") { messageText = `*[New Issue](${payload.issue.html_url})* *|* *#${payload.issue.number}* *${payload.issue.title}* *|* *[${payload.repository.full_name}](${payload.repository.html_url})* `; - } else if (event == 'deployment_status'){ + } else if (event == "deployment_status") { messageText = `*Deployment Status ${payload.deployment_status.state}* *|* *${payload.repository.full_name}*`; } @@ -47,7 +67,9 @@ export class githubWebHooks extends ApiEndpoint { if (!roomId) { continue; } - const room: IRoom = await read.getRoomReader().getById(roomId) as IRoom; + const room: IRoom = (await read + .getRoomReader() + .getById(roomId)) as IRoom; const textSender = await modify .getCreator() .startMessage() @@ -56,10 +78,8 @@ export class githubWebHooks extends ApiEndpoint { textSender.setRoom(room); } await modify.getCreator().finish(textSender); - } return this.success(); } - -} \ No newline at end of file +} diff --git a/github/handlers/ExecuteViewClosedHandler.ts b/github/handlers/ExecuteViewClosedHandler.ts index 6b117d5..07a58f2 100644 --- a/github/handlers/ExecuteViewClosedHandler.ts +++ b/github/handlers/ExecuteViewClosedHandler.ts @@ -16,12 +16,15 @@ export class ExecuteViewClosedHandler { private readonly http: IHttp, private readonly modify: IModify, private readonly persistence: IPersistence - ) { } + ) {} public async run(context: UIKitViewCloseInteractionContext) { const { view } = context.getInteractionData(); switch (view.id) { - case ModalsEnum.PULL_VIEW || ModalsEnum.CODE_VIEW || ModalsEnum.ADD_SUBSCRIPTION_VIEW || ModalsEnum.SUBSCRIPTION_VIEW: + case ModalsEnum.PULL_VIEW || + ModalsEnum.CODE_VIEW || + ModalsEnum.ADD_SUBSCRIPTION_VIEW || + ModalsEnum.SUBSCRIPTION_VIEW: const modal = await pullDetailsModal({ modify: this.modify, read: this.read, @@ -29,16 +32,14 @@ export class ExecuteViewClosedHandler { http: this.http, uikitcontext: context, }); - await this.modify - .getUiController() - .updateModalView( - modal, - { - triggerId: context.getInteractionData() - .triggerId as string, - }, - context.getInteractionData().user - ); + await this.modify.getUiController().updateModalView( + modal, + { + triggerId: context.getInteractionData() + .triggerId as string, + }, + context.getInteractionData().user + ); break; } return { success: true } as any; diff --git a/github/helpers/githubSDK.ts b/github/helpers/githubSDK.ts index eeb8dec..be4513d 100644 --- a/github/helpers/githubSDK.ts +++ b/github/helpers/githubSDK.ts @@ -36,7 +36,7 @@ async function deleteReqeust( Authorization: `token ${accessToken}`, "Content-Type": "application/json", "User-Agent": "Rocket.Chat-Apps-Engine", - } + }, }); // If it isn't a 2xx code, something wrong happened @@ -93,10 +93,13 @@ export async function deleteSubscription( access_token: string, hookId: string ) { - return deleteReqeust(http, access_token, BaseApiHost + repoName + "/hooks/" + hookId,); + return deleteReqeust( + http, + access_token, + BaseApiHost + repoName + "/hooks/" + hookId + ); } - export async function updateSubscription( http: IHttp, repoName: string, @@ -104,10 +107,15 @@ export async function updateSubscription( hookId: string, events: Array ) { - return patchReqeust(http, access_token, BaseApiHost + repoName + "/hooks/" + hookId, { - active: true, - events: events - }); + return patchReqeust( + http, + access_token, + BaseApiHost + repoName + "/hooks/" + hookId, + { + active: true, + events: events, + } + ); } export async function addSubscribedEvents( @@ -117,10 +125,15 @@ export async function addSubscribedEvents( hookId: string, events: Array ) { - return patchReqeust(http, access_token, BaseApiHost + repoName + "/hooks/" + hookId, { - active: true, - add_events: events - }); + return patchReqeust( + http, + access_token, + BaseApiHost + repoName + "/hooks/" + hookId, + { + active: true, + add_events: events, + } + ); } export async function removeSubscribedEvents( @@ -130,9 +143,13 @@ export async function removeSubscribedEvents( hookId: string, events: Array ) { - return patchReqeust(http, access_token, BaseApiHost + repoName + "/hooks/" + hookId, { - active: true, - add_events: events - }); + return patchReqeust( + http, + access_token, + BaseApiHost + repoName + "/hooks/" + hookId, + { + active: true, + add_events: events, + } + ); } - diff --git a/github/modals/addSubscriptionsModal.ts b/github/modals/addSubscriptionsModal.ts index 99355dd..cb537c8 100644 --- a/github/modals/addSubscriptionsModal.ts +++ b/github/modals/addSubscriptionsModal.ts @@ -1,117 +1,143 @@ - -import { IHttp, IModify, IPersistence, IRead } from '@rocket.chat/apps-engine/definition/accessors'; -import { TextObjectType } from '@rocket.chat/apps-engine/definition/uikit/blocks'; -import { IUIKitModalViewParam } from '@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder'; -import { IUser } from '@rocket.chat/apps-engine/definition/users'; -import { ModalsEnum } from '../enum/Modals'; -import { AppEnum } from '../enum/App'; +import { + IHttp, + IModify, + IPersistence, + IRead, +} from "@rocket.chat/apps-engine/definition/accessors"; +import { TextObjectType } from "@rocket.chat/apps-engine/definition/uikit/blocks"; +import { IUIKitModalViewParam } from "@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder"; +import { IUser } from "@rocket.chat/apps-engine/definition/users"; +import { ModalsEnum } from "../enum/Modals"; +import { AppEnum } from "../enum/App"; // import { getRoomTasks, getUIData, persistUIData } from '../lib/persistence'; -import { SlashCommandContext } from '@rocket.chat/apps-engine/definition/slashcommands'; -import { UIKitBlockInteractionContext, UIKitInteractionContext } from '@rocket.chat/apps-engine/definition/uikit'; -import { storeInteractionRoomData, getInteractionRoomData } from '../persistance/roomInteraction'; -import { Subscription } from '../persistance/subscriptions'; -import { ISubscription } from '../definitions/subscription'; +import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; +import { + UIKitBlockInteractionContext, + UIKitInteractionContext, +} from "@rocket.chat/apps-engine/definition/uikit"; +import { + storeInteractionRoomData, + getInteractionRoomData, +} from "../persistance/roomInteraction"; +import { Subscription } from "../persistance/subscriptions"; +import { ISubscription } from "../definitions/subscription"; -export async function AddSubscriptionModal({ modify, read, persistence, http, slashcommandcontext, uikitcontext }: { modify: IModify, read: IRead, persistence: IPersistence, http: IHttp ,slashcommandcontext?: SlashCommandContext, uikitcontext?: UIKitInteractionContext }): Promise { +export async function AddSubscriptionModal({ + modify, + read, + persistence, + http, + slashcommandcontext, + uikitcontext, +}: { + modify: IModify; + read: IRead; + persistence: IPersistence; + http: IHttp; + slashcommandcontext?: SlashCommandContext; + uikitcontext?: UIKitInteractionContext; +}): Promise { const viewId = ModalsEnum.ADD_SUBSCRIPTION_VIEW; - const block = modify.getCreator().getBlockBuilder(); + const room = + slashcommandcontext?.getRoom() || + uikitcontext?.getInteractionData().room; + const user = + slashcommandcontext?.getSender() || + uikitcontext?.getInteractionData().user; - const room = slashcommandcontext?.getRoom() || uikitcontext?.getInteractionData().room; - const user = slashcommandcontext?.getSender() || uikitcontext?.getInteractionData().user; - if (user?.id) { let roomId; - + if (room?.id) { roomId = room.id; await storeInteractionRoomData(persistence, user.id, roomId); } else { - roomId = (await getInteractionRoomData(read.getPersistenceReader(), user.id)).roomId; + roomId = ( + await getInteractionRoomData( + read.getPersistenceReader(), + user.id + ) + ).roomId; } - - let subsciptionStorage = new Subscription(persistence,read.getPersistenceReader()); - let roomSubsciptions: Array = await subsciptionStorage.getSubscriptions(roomId); + + let subsciptionStorage = new Subscription( + persistence, + read.getPersistenceReader() + ); + let roomSubsciptions: Array = + await subsciptionStorage.getSubscriptions(roomId); // shows indentations in input blocks but not inn section block block.addInputBlock({ blockId: ModalsEnum.REPO_NAME_INPUT, - label: { text: ModalsEnum.REPO_NAME_LABEL, type: TextObjectType.PLAINTEXT }, + label: { + text: ModalsEnum.REPO_NAME_LABEL, + type: TextObjectType.PLAINTEXT, + }, element: block.newPlainTextInputElement({ actionId: ModalsEnum.REPO_NAME_INPUT_ACTION, - placeholder : { text: ModalsEnum.REPO_NAME_PLACEHOLDER, type: TextObjectType.PLAINTEXT }, - }) + placeholder: { + text: ModalsEnum.REPO_NAME_PLACEHOLDER, + type: TextObjectType.PLAINTEXT, + }, + }), }); - - let newMultiStaticElemnt= block.newMultiStaticElement({ - actionId : ModalsEnum.ADD_SUBSCRIPTION_EVENT_OPTIONS, + let newMultiStaticElemnt = block.newMultiStaticElement({ + actionId: ModalsEnum.ADD_SUBSCRIPTION_EVENT_OPTIONS, options: [ { - value: 'issues', - text:{ - type: TextObjectType.PLAINTEXT, - text:'New Issues', - emoji:true, - } + value: "issues", + text: { + type: TextObjectType.PLAINTEXT, + text: "New Issues", + emoji: true, + }, }, { - value: 'pull_request', - text:{ - type: TextObjectType.PLAINTEXT, - text:'New Pull Request', - emoji:true, - } + value: "pull_request", + text: { + type: TextObjectType.PLAINTEXT, + text: "New Pull Request", + emoji: true, + }, }, { - value: 'push', - text:{ - type: TextObjectType.PLAINTEXT, - text:'New Commits', - emoji:true, - } + value: "push", + text: { + type: TextObjectType.PLAINTEXT, + text: "New Commits", + emoji: true, + }, }, { - value: 'deployment_status', - text:{ - type: TextObjectType.PLAINTEXT, - text:'Deployment', - emoji:true, - } + value: "deployment_status", + text: { + type: TextObjectType.PLAINTEXT, + text: "Deployment", + emoji: true, + }, }, - ], - placeholder: { - type: TextObjectType.PLAINTEXT, - text: 'Select Events', - }, - - }) - + ], + placeholder: { + type: TextObjectType.PLAINTEXT, + text: "Select Events", + }, + }); + block.addInputBlock({ - label: { text:ModalsEnum.ADD_SUBSCRIPTION_EVENT_LABEL, type: TextObjectType.PLAINTEXT }, - element:newMultiStaticElemnt, - blockId:ModalsEnum.ADD_SUBSCRIPTION_EVENT_INPUT, - }) + label: { + text: ModalsEnum.ADD_SUBSCRIPTION_EVENT_LABEL, + type: TextObjectType.PLAINTEXT, + }, + element: newMultiStaticElemnt, + blockId: ModalsEnum.ADD_SUBSCRIPTION_EVENT_INPUT, + }); } - block.addDividerBlock(); - // block.addActionsBlock({ - // elements: [ - // block.newButtonElement({ - // actionId: ModalsEnum.MERGE_PULL_REQUEST_ACTION, - // text: { text: ModalsEnum.MERGE_PULL_REQUEST_LABEL, type: TextObjectType.PLAINTEXT }, - // value: room?.id - // }), - // block.newButtonElement({ - // actionId: ModalsEnum.COMMENT_PR_ACTION, - // text: { text: ModalsEnum.COMMENT_PR_LABEL, type: TextObjectType.PLAINTEXT }, - // value: room?.id - // }), - // ] - // }); - return { id: viewId, title: { @@ -121,16 +147,16 @@ export async function AddSubscriptionModal({ modify, read, persistence, http, sl close: block.newButtonElement({ text: { type: TextObjectType.PLAINTEXT, - text: 'Close', + text: "Close", }, }), submit: block.newButtonElement({ actionId: ModalsEnum.ADD_SUBSCRIPTION_ACTION, text: { type: TextObjectType.PLAINTEXT, - text: 'Subscribe', + text: "Subscribe", }, }), - blocks: block.getBlocks() + blocks: block.getBlocks(), }; -} \ No newline at end of file +} diff --git a/github/modals/deleteSubscriptions.ts b/github/modals/deleteSubscriptions.ts index 7d621b0..5b34806 100644 --- a/github/modals/deleteSubscriptions.ts +++ b/github/modals/deleteSubscriptions.ts @@ -33,8 +33,7 @@ export async function deleteSubsciptionsModal({ modify, read, persistence, http, let roomSubsciptions: Array = await subsciptionStorage.getSubscriptions(roomId); block.addDividerBlock(); - - + let repositoryData = new Map; for (let subsciption of roomSubsciptions) { @@ -64,12 +63,9 @@ export async function deleteSubsciptionsModal({ modify, read, persistence, http, let index=1; for (let repository of repositoryData.values()) { - let repoName = repository.repoName; let repoUser = repository.user; - let events = repository.events; - - + let events = repository.events; if(repoUser.id == user.id){ block.addSectionBlock({ text: { text: `${index}) ${repoName}`, type: TextObjectType.PLAINTEXT}, @@ -89,22 +85,19 @@ export async function deleteSubsciptionsModal({ modify, read, persistence, http, }); } - - - - let eventList : Array=[]; - eventList.push(block.newPlainTextObject("Events : ")); - for(let event of events){ - eventList.push(block.newPlainTextObject(`${event} `)); - } - block.addContextBlock({ elements: eventList}); - index++; + + let eventList : Array=[]; + eventList.push(block.newPlainTextObject("Events : ")); + for(let event of events){ + eventList.push(block.newPlainTextObject(`${event} `)); + } + block.addContextBlock({ elements: eventList}); + index++; } } block.addDividerBlock(); - return { id: viewId, title: { diff --git a/github/modals/fileCodeModal.ts b/github/modals/fileCodeModal.ts index 131e874..f0eaf4b 100644 --- a/github/modals/fileCodeModal.ts +++ b/github/modals/fileCodeModal.ts @@ -1,36 +1,57 @@ - -import { IHttp, IModify, IPersistence, IRead } from '@rocket.chat/apps-engine/definition/accessors'; -import { TextObjectType } from '@rocket.chat/apps-engine/definition/uikit/blocks'; -import { IUIKitModalViewParam } from '@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder'; -import { IUser } from '@rocket.chat/apps-engine/definition/users'; -import { ModalsEnum } from '../enum/Modals'; -import { AppEnum } from '../enum/App'; +import { + IHttp, + IModify, + IPersistence, + IRead, +} from "@rocket.chat/apps-engine/definition/accessors"; +import { TextObjectType } from "@rocket.chat/apps-engine/definition/uikit/blocks"; +import { IUIKitModalViewParam } from "@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder"; +import { IUser } from "@rocket.chat/apps-engine/definition/users"; +import { ModalsEnum } from "../enum/Modals"; +import { AppEnum } from "../enum/App"; // import { getRoomTasks, getUIData, persistUIData } from '../lib/persistence'; -import { SlashCommandContext } from '@rocket.chat/apps-engine/definition/slashcommands'; -import { UIKitBlockInteractionContext, UIKitInteractionContext } from '@rocket.chat/apps-engine/definition/uikit'; -import { type } from 'os'; +import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; +import { + UIKitBlockInteractionContext, + UIKitInteractionContext, +} from "@rocket.chat/apps-engine/definition/uikit"; +import { type } from "os"; -export async function fileCodeModal({ data, modify, read, persistence, http, slashcommandcontext, uikitcontext }: { data, modify: IModify, read: IRead, persistence: IPersistence, http: IHttp ,slashcommandcontext?: SlashCommandContext, uikitcontext?: UIKitInteractionContext }): Promise { +export async function fileCodeModal({ + data, + modify, + read, + persistence, + http, + slashcommandcontext, + uikitcontext, +}: { + data; + modify: IModify; + read: IRead; + persistence: IPersistence; + http: IHttp; + slashcommandcontext?: SlashCommandContext; + uikitcontext?: UIKitInteractionContext; +}): Promise { const viewId = ModalsEnum.CODE_VIEW; const block = modify.getCreator().getBlockBuilder(); - const room = slashcommandcontext?.getRoom() || uikitcontext?.getInteractionData().room; - const user = slashcommandcontext?.getSender() || uikitcontext?.getInteractionData().user; - + const room = + slashcommandcontext?.getRoom() || + uikitcontext?.getInteractionData().room; + const user = + slashcommandcontext?.getSender() || + uikitcontext?.getInteractionData().user; + if (user?.id) { let roomId; - - const pullRawData = await http.get( - data.value - ); - + const pullRawData = await http.get(data.value); const pullData = pullRawData.content; - block.addSectionBlock({ - text: { text: `${pullData}`, type: TextObjectType.MARKDOWN} - - }) + text: { text: `${pullData}`, type: TextObjectType.MARKDOWN }, + }); // shows indentations in input blocks but not inn section block // block.addInputBlock({ @@ -42,10 +63,6 @@ export async function fileCodeModal({ data, modify, read, persistence, http, sla // actionId: ModalsEnum.CODE_INPUT, // }) // }); - - - - } block.addDividerBlock(); @@ -54,15 +71,21 @@ export async function fileCodeModal({ data, modify, read, persistence, http, sla elements: [ block.newButtonElement({ actionId: ModalsEnum.MERGE_PULL_REQUEST_ACTION, - text: { text: ModalsEnum.MERGE_PULL_REQUEST_LABEL, type: TextObjectType.PLAINTEXT }, - value: room?.id + text: { + text: ModalsEnum.MERGE_PULL_REQUEST_LABEL, + type: TextObjectType.PLAINTEXT, + }, + value: room?.id, }), block.newButtonElement({ actionId: ModalsEnum.COMMENT_PR_ACTION, - text: { text: ModalsEnum.COMMENT_PR_LABEL, type: TextObjectType.PLAINTEXT }, - value: room?.id + text: { + text: ModalsEnum.COMMENT_PR_LABEL, + type: TextObjectType.PLAINTEXT, + }, + value: room?.id, }), - ] + ], }); return { @@ -74,9 +97,9 @@ export async function fileCodeModal({ data, modify, read, persistence, http, sla close: block.newButtonElement({ text: { type: TextObjectType.PLAINTEXT, - text: 'Close', + text: "Close", }, }), - blocks: block.getBlocks() + blocks: block.getBlocks(), }; -} \ No newline at end of file +} diff --git a/github/modals/pullDetailsModal.ts b/github/modals/pullDetailsModal.ts index 37e7f1e..abe6ba3 100644 --- a/github/modals/pullDetailsModal.ts +++ b/github/modals/pullDetailsModal.ts @@ -1,93 +1,140 @@ -import { IHttp, IModify, IPersistence, IRead } from '@rocket.chat/apps-engine/definition/accessors'; -import { TextObjectType } from '@rocket.chat/apps-engine/definition/uikit/blocks'; -import { IUIKitModalViewParam } from '@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder'; -import { IUser } from '@rocket.chat/apps-engine/definition/users'; -import { ModalsEnum } from '../enum/Modals'; -import { AppEnum } from '../enum/App'; +import { + IHttp, + IModify, + IPersistence, + IRead, +} from "@rocket.chat/apps-engine/definition/accessors"; +import { TextObjectType } from "@rocket.chat/apps-engine/definition/uikit/blocks"; +import { IUIKitModalViewParam } from "@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder"; +import { IUser } from "@rocket.chat/apps-engine/definition/users"; +import { ModalsEnum } from "../enum/Modals"; +import { AppEnum } from "../enum/App"; // import { getRoomTasks, getUIData, persistUIData } from '../lib/persistence'; -import { SlashCommandContext } from '@rocket.chat/apps-engine/definition/slashcommands'; -import { UIKitBlockInteractionContext, UIKitInteractionContext } from '@rocket.chat/apps-engine/definition/uikit'; +import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; +import { + UIKitBlockInteractionContext, + UIKitInteractionContext, +} from "@rocket.chat/apps-engine/definition/uikit"; -export async function pullDetailsModal({ data, modify, read, persistence, http, slashcommandcontext, uikitcontext }: { data?, modify: IModify, read: IRead, persistence: IPersistence, http: IHttp ,slashcommandcontext?: SlashCommandContext, uikitcontext?: UIKitInteractionContext }): Promise { +export async function pullDetailsModal({ + data, + modify, + read, + persistence, + http, + slashcommandcontext, + uikitcontext, +}: { + data?; + modify: IModify; + read: IRead; + persistence: IPersistence; + http: IHttp; + slashcommandcontext?: SlashCommandContext; + uikitcontext?: UIKitInteractionContext; +}): Promise { const viewId = ModalsEnum.PULL_VIEW; const block = modify.getCreator().getBlockBuilder(); - const room = slashcommandcontext?.getRoom() || uikitcontext?.getInteractionData().room; - const user = slashcommandcontext?.getSender() || uikitcontext?.getInteractionData().user; - + const room = + slashcommandcontext?.getRoom() || + uikitcontext?.getInteractionData().room; + const user = + slashcommandcontext?.getSender() || + uikitcontext?.getInteractionData().user; + if (user?.id) { let roomId; - + const pullRawData = await http.get( `https://api.github.com/repos/${data?.repository}/pulls/${data?.number}` ); const pullData = pullRawData.data; - + const pullRequestFilesRaw = await http.get( `https://api.github.com/repos/${data?.repository}/pulls/${data?.number}/files` ); - - const pullRequestFiles= pullRequestFilesRaw.data; - + + const pullRequestFiles = pullRequestFilesRaw.data; + block.addSectionBlock({ - text: { text: `#${pullData?.title}`, type: TextObjectType.PLAINTEXT }, + text: { + text: `#${pullData?.title}`, + type: TextObjectType.PLAINTEXT, + }, accessory: block.newButtonElement({ actionId: ModalsEnum.VIEW_FILE_ACTION, text: { text: ModalsEnum.VIEW_DIFFS_ACTION_LABEL, - type: TextObjectType.PLAINTEXT + type: TextObjectType.PLAINTEXT, }, - value: pullData["diff_url"] - }) - }) - block.addContextBlock({ elements: [ block.newPlainTextObject(`Author: ${pullData?.user?.login} | `),block.newPlainTextObject(`State : ${pullData?.state} | `),block.newPlainTextObject(`Mergeable : ${pullData?.mergeable}`) ]}); + value: pullData["diff_url"], + }), + }); + block.addContextBlock({ + elements: [ + block.newPlainTextObject(`Author: ${pullData?.user?.login} | `), + block.newPlainTextObject(`State : ${pullData?.state} | `), + block.newPlainTextObject(`Mergeable : ${pullData?.mergeable}`), + ], + }); block.addDividerBlock(); - - let index=1; - + let index = 1; for (let file of pullRequestFiles) { - let fileName = file["filename"]; let rawUrl = file["raw_url"]; let status = file["status"]; let addition = file["additions"]; let deletions = file["deletions"]; - block.addSectionBlock({ - text: { text: `${index} ${fileName}`, type: TextObjectType.PLAINTEXT }, - accessory: block.newButtonElement({ - actionId: ModalsEnum.VIEW_FILE_ACTION, - text: { - text: ModalsEnum.VIEW_FILE_ACTION_LABEL, - type: TextObjectType.PLAINTEXT - }, - value: rawUrl - }) - }); - block.addContextBlock({ elements: [ block.newPlainTextObject(`Status: ${status} | `),block.newPlainTextObject(`Additions : ${addition} | `),block.newPlainTextObject(`Deletions : ${deletions}`) ]}); - - index++; + block.addSectionBlock({ + text: { + text: `${index} ${fileName}`, + type: TextObjectType.PLAINTEXT, + }, + accessory: block.newButtonElement({ + actionId: ModalsEnum.VIEW_FILE_ACTION, + text: { + text: ModalsEnum.VIEW_FILE_ACTION_LABEL, + type: TextObjectType.PLAINTEXT, + }, + value: rawUrl, + }), + }); + block.addContextBlock({ + elements: [ + block.newPlainTextObject(`Status: ${status} | `), + block.newPlainTextObject(`Additions : ${addition} | `), + block.newPlainTextObject(`Deletions : ${deletions}`), + ], + }); + + index++; } } - - block.addActionsBlock({ elements: [ block.newButtonElement({ actionId: ModalsEnum.MERGE_PULL_REQUEST_ACTION, - text: { text: ModalsEnum.MERGE_PULL_REQUEST_LABEL, type: TextObjectType.PLAINTEXT }, - value: room?.id + text: { + text: ModalsEnum.MERGE_PULL_REQUEST_LABEL, + type: TextObjectType.PLAINTEXT, + }, + value: room?.id, }), block.newButtonElement({ actionId: ModalsEnum.COMMENT_PR_ACTION, - text: { text: ModalsEnum.COMMENT_PR_LABEL, type: TextObjectType.PLAINTEXT }, - value: room?.id + text: { + text: ModalsEnum.COMMENT_PR_LABEL, + type: TextObjectType.PLAINTEXT, + }, + value: room?.id, }), - ] + ], }); return { @@ -99,9 +146,9 @@ export async function pullDetailsModal({ data, modify, read, persistence, http, close: block.newButtonElement({ text: { type: TextObjectType.PLAINTEXT, - text: 'Close', + text: "Close", }, }), blocks: block.getBlocks(), }; -} \ No newline at end of file +} diff --git a/github/modals/subscriptionsModal.ts b/github/modals/subscriptionsModal.ts index 4d6f01f..e91831b 100644 --- a/github/modals/subscriptionsModal.ts +++ b/github/modals/subscriptionsModal.ts @@ -63,37 +63,28 @@ export async function subsciptionsModal({ modify, read, persistence, http, slash } let index=1; for (let repository of repositoryData.values()) { - - let repoName = repository.repoName; let repoUser = repository.user; let events = repository.events; - - - - - block.addSectionBlock({ - text: { text: `${index}) ${repoName}`, type: TextObjectType.PLAINTEXT}, - accessory: block.newButtonElement({ - actionId: ModalsEnum.OPEN_REPO_ACTION, - text: { - text: ModalsEnum.OPEN_REPO_LABEL, - type: TextObjectType.PLAINTEXT - }, - value: repository.webhookId, - url:`https://github.com/${repoName}` - }) - }); - - - - let eventList : Array=[]; - eventList.push(block.newPlainTextObject("Events : ")); - for(let event of events){ - eventList.push(block.newPlainTextObject(`${event} `)); - } - block.addContextBlock({ elements: eventList}); - index++; + block.addSectionBlock({ + text: { text: `${index}) ${repoName}`, type: TextObjectType.PLAINTEXT}, + accessory: block.newButtonElement({ + actionId: ModalsEnum.OPEN_REPO_ACTION, + text: { + text: ModalsEnum.OPEN_REPO_LABEL, + type: TextObjectType.PLAINTEXT + }, + value: repository.webhookId, + url:`https://github.com/${repoName}` + }) + }); + let eventList : Array=[]; + eventList.push(block.newPlainTextObject("Events : ")); + for(let event of events){ + eventList.push(block.newPlainTextObject(`${event} `)); + } + block.addContextBlock({ elements: eventList}); + index++; } } diff --git a/github/persistance/auth.ts b/github/persistance/auth.ts index 9e8e498..a3f4b10 100644 --- a/github/persistance/auth.ts +++ b/github/persistance/auth.ts @@ -1,8 +1,19 @@ -import { IHttp, IHttpRequest, IPersistence, IRead } from "@rocket.chat/apps-engine/definition/accessors"; -import { RocketChatAssociationModel, RocketChatAssociationRecord } from "@rocket.chat/apps-engine/definition/metadata"; -import { IAuthData, IOAuth2ClientOptions } from "@rocket.chat/apps-engine/definition/oauth2/IOAuth2"; +import { + IHttp, + IHttpRequest, + IPersistence, + IRead, +} from "@rocket.chat/apps-engine/definition/accessors"; +import { + RocketChatAssociationModel, + RocketChatAssociationRecord, +} from "@rocket.chat/apps-engine/definition/metadata"; +import { + IAuthData, + IOAuth2ClientOptions, +} from "@rocket.chat/apps-engine/definition/oauth2/IOAuth2"; import { IUser } from "@rocket.chat/apps-engine/definition/users"; -import { URL } from 'url'; +import { URL } from "url"; // const assoc = new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, 'users'); @@ -43,64 +54,75 @@ import { URL } from 'url'; // } /** - * This function needed to be copied from the apps engine due to difficulties trying to - * get access to the auth client from inside a job processor. - * @NOTE It relies on hardcoded information (config alias's suffix) to work and it might break if - * the value changes - */ - -export async function getAccessTokenForUser(read: IRead, user: IUser, config: IOAuth2ClientOptions): Promise { - const associations = [ - new RocketChatAssociationRecord( - RocketChatAssociationModel.USER, - user.id, - ), - new RocketChatAssociationRecord( - RocketChatAssociationModel.MISC, - `${config.alias}-oauth-connection`, - ), - ]; + * This function needed to be copied from the apps engine due to difficulties trying to + * get access to the auth client from inside a job processor. + * @NOTE It relies on hardcoded information (config alias's suffix) to work and it might break if + * the value changes + */ + +export async function getAccessTokenForUser( + read: IRead, + user: IUser, + config: IOAuth2ClientOptions +): Promise { + const associations = [ + new RocketChatAssociationRecord( + RocketChatAssociationModel.USER, + user.id + ), + new RocketChatAssociationRecord( + RocketChatAssociationModel.MISC, + `${config.alias}-oauth-connection` + ), + ]; - const [ result ] = await read.getPersistenceReader().readByAssociations(associations) as unknown as Array; + const [result] = (await read + .getPersistenceReader() + .readByAssociations(associations)) as unknown as Array< + IAuthData | undefined + >; return result; } - - -export async function removeToken({ +export async function removeToken({ userId, persis, config, }: { - userId: string, - persis: IPersistence, - config: IOAuth2ClientOptions + userId: string; + persis: IPersistence; + config: IOAuth2ClientOptions; }): Promise { - const [ result ] = await persis.removeByAssociations([ + const [result] = (await persis.removeByAssociations([ new RocketChatAssociationRecord( RocketChatAssociationModel.USER, - userId, + userId ), new RocketChatAssociationRecord( RocketChatAssociationModel.MISC, - `${config.alias}-oauth-connection`, + `${config.alias}-oauth-connection` ), - ]) as unknown as Array; + ])) as unknown as Array; return result; } - -export async function revokeUserAccessToken(read:IRead,user: IUser, persis: IPersistence, http:IHttp, config: IOAuth2ClientOptions): Promise { +export async function revokeUserAccessToken( + read: IRead, + user: IUser, + persis: IPersistence, + http: IHttp, + config: IOAuth2ClientOptions +): Promise { try { - const tokenInfo = await getAccessTokenForUser(read,user,config); + const tokenInfo = await getAccessTokenForUser(read, user, config); if (!tokenInfo?.token) { - throw new Error('No access token available for this user.'); + throw new Error("No access token available for this user."); } - await removeToken({ userId: user.id, persis,config }); + await removeToken({ userId: user.id, persis, config }); return true; } catch (error) { - console.log('revokeTokenError : ',error); + console.log("revokeTokenError : ", error); return false; } } diff --git a/github/persistance/roomInteraction.ts b/github/persistance/roomInteraction.ts index 89824d0..c0b9abb 100644 --- a/github/persistance/roomInteraction.ts +++ b/github/persistance/roomInteraction.ts @@ -1,20 +1,51 @@ -import { IPersistence, IPersistenceRead } from '@rocket.chat/apps-engine/definition/accessors'; -import { RocketChatAssociationModel, RocketChatAssociationRecord } from '@rocket.chat/apps-engine/definition/metadata'; +import { + IPersistence, + IPersistenceRead, +} from "@rocket.chat/apps-engine/definition/accessors"; +import { + RocketChatAssociationModel, + RocketChatAssociationRecord, +} from "@rocket.chat/apps-engine/definition/metadata"; //functions needed ro persist room data while modal and other UI interactions -export const storeInteractionRoomData = async (persistence: IPersistence, userId: string, roomId: string): Promise => { - const association = new RocketChatAssociationRecord(RocketChatAssociationModel.USER, `${ userId }#RoomId`); - await persistence.updateByAssociation(association, {roomId : roomId} , true); +export const storeInteractionRoomData = async ( + persistence: IPersistence, + userId: string, + roomId: string +): Promise => { + const association = new RocketChatAssociationRecord( + RocketChatAssociationModel.USER, + `${userId}#RoomId` + ); + await persistence.updateByAssociation( + association, + { roomId: roomId }, + true + ); }; -export const getInteractionRoomData = async (persistenceRead: IPersistenceRead, userId:string): Promise => { - const association = new RocketChatAssociationRecord(RocketChatAssociationModel.USER, `${ userId }#RoomId`); - const result = await persistenceRead.readByAssociation(association) as Array; +export const getInteractionRoomData = async ( + persistenceRead: IPersistenceRead, + userId: string +): Promise => { + const association = new RocketChatAssociationRecord( + RocketChatAssociationModel.USER, + `${userId}#RoomId` + ); + const result = (await persistenceRead.readByAssociation( + association + )) as Array; return result && result.length ? result[0] : null; }; -export const clearInteractionRoomData = async (persistence: IPersistence, userId: string): Promise => { - const association = new RocketChatAssociationRecord(RocketChatAssociationModel.USER, `${ userId }#RoomId`); +export const clearInteractionRoomData = async ( + persistence: IPersistence, + userId: string +): Promise => { + const association = new RocketChatAssociationRecord( + RocketChatAssociationModel.USER, + `${userId}#RoomId` + ); await persistence.removeByAssociation(association); -}; \ No newline at end of file +}; diff --git a/github/persistance/subscriptions.ts b/github/persistance/subscriptions.ts index 76234ae..db45e77 100644 --- a/github/persistance/subscriptions.ts +++ b/github/persistance/subscriptions.ts @@ -1,95 +1,185 @@ -import { IPersistence, IPersistenceRead } from '@rocket.chat/apps-engine/definition/accessors'; -import { RocketChatAssociationModel, RocketChatAssociationRecord } from '@rocket.chat/apps-engine/definition/metadata'; -import { IRoom } from '@rocket.chat/apps-engine/definition/rooms'; -import { IUser } from '@rocket.chat/apps-engine/definition/users'; -import { ISubscription } from '../definitions/subscription'; +import { + IPersistence, + IPersistenceRead, +} from "@rocket.chat/apps-engine/definition/accessors"; +import { + RocketChatAssociationModel, + RocketChatAssociationRecord, +} from "@rocket.chat/apps-engine/definition/metadata"; +import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; +import { IUser } from "@rocket.chat/apps-engine/definition/users"; +import { ISubscription } from "../definitions/subscription"; export class Subscription { - constructor(private readonly persistence: IPersistence, private readonly persistenceRead: IPersistenceRead) { } - - public async createSubscription(repoName: string, event: string, webhookId: string, room: IRoom, user: IUser): Promise { + constructor( + private readonly persistence: IPersistence, + private readonly persistenceRead: IPersistenceRead + ) {} + public async createSubscription( + repoName: string, + event: string, + webhookId: string, + room: IRoom, + user: IUser + ): Promise { try { const associations: Array = [ - new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `subscription`), - new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `repo:${repoName}`), - new RocketChatAssociationRecord(RocketChatAssociationModel.ROOM, room.id), - new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, event), - new RocketChatAssociationRecord(RocketChatAssociationModel.USER, `${user.id}`) + new RocketChatAssociationRecord( + RocketChatAssociationModel.MISC, + `subscription` + ), + new RocketChatAssociationRecord( + RocketChatAssociationModel.MISC, + `repo:${repoName}` + ), + new RocketChatAssociationRecord( + RocketChatAssociationModel.ROOM, + room.id + ), + new RocketChatAssociationRecord( + RocketChatAssociationModel.MISC, + event + ), + new RocketChatAssociationRecord( + RocketChatAssociationModel.USER, + `${user.id}` + ), ]; let subscriptionRecord: ISubscription = { webhookId: webhookId, user: user.id, repoName: repoName, room: room.id, - event: event - } - await this.persistence.updateByAssociations(associations, subscriptionRecord, true); - + event: event, + }; + await this.persistence.updateByAssociations( + associations, + subscriptionRecord, + true + ); } catch (error) { - console.warn('Subsciption Error :', error) + console.warn("Subsciption Error :", error); return false; } return true; } - public async getSubscribedRooms(repoName: string, event: string): Promise> { + public async getSubscribedRooms( + repoName: string, + event: string + ): Promise> { try { const associations: Array = [ - new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `subscription`), - new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `repo:${repoName}`), - new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, event), + new RocketChatAssociationRecord( + RocketChatAssociationModel.MISC, + `subscription` + ), + new RocketChatAssociationRecord( + RocketChatAssociationModel.MISC, + `repo:${repoName}` + ), + new RocketChatAssociationRecord( + RocketChatAssociationModel.MISC, + event + ), ]; - let subsciptions: Array = await this.persistenceRead.readByAssociations(associations) as Array; + let subsciptions: Array = + (await this.persistenceRead.readByAssociations( + associations + )) as Array; return subsciptions; } catch (error) { - console.warn('Get Subscribed Rooms Error :', error) + console.warn("Get Subscribed Rooms Error :", error); let subsciptions: Array = []; return subsciptions; } } - public async getSubscriptions(roomId: string): Promise> { + public async getSubscriptions( + roomId: string + ): Promise> { try { const associations: Array = [ - new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `subscription`), - new RocketChatAssociationRecord(RocketChatAssociationModel.ROOM, roomId), + new RocketChatAssociationRecord( + RocketChatAssociationModel.MISC, + `subscription` + ), + new RocketChatAssociationRecord( + RocketChatAssociationModel.ROOM, + roomId + ), ]; - let subsciptions: Array = await this.persistenceRead.readByAssociations(associations) as Array; + let subsciptions: Array = + (await this.persistenceRead.readByAssociations( + associations + )) as Array; return subsciptions; } catch (error) { - console.warn('Get Subsciption Error :', error) + console.warn("Get Subsciption Error :", error); let subsciptions: Array = []; return subsciptions; } } - public async deleteSubscriptions(repoName: string, event: string, roomId: string): Promise { + public async deleteSubscriptions( + repoName: string, + event: string, + roomId: string + ): Promise { try { const associations: Array = [ - new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `subscription`), - new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `repo:${repoName}`), - new RocketChatAssociationRecord(RocketChatAssociationModel.ROOM, roomId), - new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, event) + new RocketChatAssociationRecord( + RocketChatAssociationModel.MISC, + `subscription` + ), + new RocketChatAssociationRecord( + RocketChatAssociationModel.MISC, + `repo:${repoName}` + ), + new RocketChatAssociationRecord( + RocketChatAssociationModel.ROOM, + roomId + ), + new RocketChatAssociationRecord( + RocketChatAssociationModel.MISC, + event + ), ]; await this.persistence.removeByAssociations(associations); } catch (error) { - console.warn('Delete Subsciption Error :', error) + console.warn("Delete Subsciption Error :", error); return false; } return true; } - public async deleteSubscriptionsByRepoUser(repoName: string, roomId: string, userId: string): Promise { + public async deleteSubscriptionsByRepoUser( + repoName: string, + roomId: string, + userId: string + ): Promise { try { const associations: Array = [ - new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `subscription`), - new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `repo:${repoName}`), - new RocketChatAssociationRecord(RocketChatAssociationModel.USER, `${userId}`), - new RocketChatAssociationRecord(RocketChatAssociationModel.ROOM, roomId), + new RocketChatAssociationRecord( + RocketChatAssociationModel.MISC, + `subscription` + ), + new RocketChatAssociationRecord( + RocketChatAssociationModel.MISC, + `repo:${repoName}` + ), + new RocketChatAssociationRecord( + RocketChatAssociationModel.USER, + `${userId}` + ), + new RocketChatAssociationRecord( + RocketChatAssociationModel.ROOM, + roomId + ), ]; await this.persistence.removeByAssociations(associations); } catch (error) { - console.warn('Delete Subsciption Error :', error) + console.warn("Delete Subsciption Error :", error); return false; } return true; @@ -98,31 +188,50 @@ export class Subscription { public async deleteAllRoomSubscriptions(roomId: string): Promise { try { const associations: Array = [ - new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `subscription`), - new RocketChatAssociationRecord(RocketChatAssociationModel.ROOM, roomId), + new RocketChatAssociationRecord( + RocketChatAssociationModel.MISC, + `subscription` + ), + new RocketChatAssociationRecord( + RocketChatAssociationModel.ROOM, + roomId + ), ]; await this.persistence.removeByAssociations(associations); } catch (error) { - console.warn('Delete All Room Subsciption Error :', error) + console.warn("Delete All Room Subsciption Error :", error); return false; } return true; } - public async getSubscriptionsByRepo(repoName: string, userId: string): Promise> { - let subsciptions: Array=[]; + public async getSubscriptionsByRepo( + repoName: string, + userId: string + ): Promise> { + let subsciptions: Array = []; try { const associations: Array = [ - new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `subscription`), - new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, `repo:${repoName}`), - new RocketChatAssociationRecord(RocketChatAssociationModel.USER, `${userId}`) + new RocketChatAssociationRecord( + RocketChatAssociationModel.MISC, + `subscription` + ), + new RocketChatAssociationRecord( + RocketChatAssociationModel.MISC, + `repo:${repoName}` + ), + new RocketChatAssociationRecord( + RocketChatAssociationModel.USER, + `${userId}` + ), ]; - subsciptions= await this.persistenceRead.readByAssociations(associations) as Array; + subsciptions = (await this.persistenceRead.readByAssociations( + associations + )) as Array; } catch (error) { - console.warn('Get Subsciptions By Repo Error :', error) + console.warn("Get Subsciptions By Repo Error :", error); return subsciptions; } return subsciptions; } - -} \ No newline at end of file +} diff --git a/github/processors/deleteOAthToken.ts b/github/processors/deleteOAthToken.ts index 6c2521f..5e7d1c5 100644 --- a/github/processors/deleteOAthToken.ts +++ b/github/processors/deleteOAthToken.ts @@ -9,21 +9,33 @@ import { sendDirectMessage } from "../lib/message"; import { IJobContext } from "@rocket.chat/apps-engine/definition/scheduler"; import { IUser } from "@rocket.chat/apps-engine/definition/users"; import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; -import { getAccessTokenForUser, revokeUserAccessToken } from "../persistance/auth"; +import { + getAccessTokenForUser, + revokeUserAccessToken, +} from "../persistance/auth"; import { IOAuth2ClientOptions } from "@rocket.chat/apps-engine/definition/oauth2/IOAuth2"; - -export async function deleteOathToken({user,config,read,modify,http,persis}: { - user:IUser,config:IOAuth2ClientOptions,read:IRead,modify:IModify,http:IHttp,persis:IPersistence +export async function deleteOathToken({ + user, + config, + read, + modify, + http, + persis, +}: { + user: IUser; + config: IOAuth2ClientOptions; + read: IRead; + modify: IModify; + http: IHttp; + persis: IPersistence; }) { try { - - - let token = await getAccessTokenForUser(read,user,config) + let token = await getAccessTokenForUser(read, user, config); if (token?.token) { - await revokeUserAccessToken(read,user,persis,http,config); + await revokeUserAccessToken(read, user, persis, http, config); } - token = await getAccessTokenForUser(read,user,config) + token = await getAccessTokenForUser(read, user, config); await sendDirectMessage( read, modify, @@ -35,5 +47,3 @@ export async function deleteOathToken({user,config,read,modify,http,persis}: { console.log("deleteOathToken error : ", error); } } -//https://community.liaison.edge.rocketchat.digital/ -//rc-apps deploy --url https://community.liaison.edge.rocketchat.digital --username samad.yar.khan --password samad --update \ No newline at end of file From fd47b668512f9169a8b6a18a7a46336ec415c35a Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Tue, 5 Jul 2022 07:35:18 +0530 Subject: [PATCH 24/32] Remove extra lines --- github/commands/GithubCommand.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/github/commands/GithubCommand.ts b/github/commands/GithubCommand.ts index e6fbbe4..fc4c0d6 100644 --- a/github/commands/GithubCommand.ts +++ b/github/commands/GithubCommand.ts @@ -9,7 +9,6 @@ import { IRead, } from "@rocket.chat/apps-engine/definition/accessors"; import { ProcessorsEnum } from "../enum/Processors"; - import { GithubApp } from "../GithubApp"; import { initiatorMessage } from "../lib/initiatorMessage"; import { helperMessage } from "../lib/helperMessage"; From 40bef12313c21e777e5986c1ff1c5d60a7d8beb9 Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Wed, 6 Jul 2022 00:40:49 +0530 Subject: [PATCH 25/32] Add Multiroom Same hook subscription logic --- github/commands/GithubCommand.ts | 89 ++++++++++++++++++-------- github/endpoints/githubEndpoints.ts | 6 ++ github/modals/addSubscriptionsModal.ts | 8 +++ 3 files changed, 78 insertions(+), 25 deletions(-) diff --git a/github/commands/GithubCommand.ts b/github/commands/GithubCommand.ts index fc4c0d6..a8a3640 100644 --- a/github/commands/GithubCommand.ts +++ b/github/commands/GithubCommand.ts @@ -22,7 +22,7 @@ import { removeToken } from "../persistance/auth"; import { getWebhookUrl } from "../helpers/getWebhookURL"; import { githubWebHooks } from "../endpoints/githubEndpoints"; import { sendDirectMessage, sendNotification } from "../lib/message"; -import { createSubscription, deleteSubscription } from "../helpers/githubSDK"; +import { createSubscription, deleteSubscription, updateSubscription } from "../helpers/githubSDK"; import { Subscription } from "../persistance/subscriptions"; import { ISubscription } from "../definitions/subscription"; import { subsciptionsModal } from "../modals/subscriptionsModal"; @@ -103,14 +103,41 @@ export class GithubCommand implements ISlashCommand { let accessToken = await getAccessTokenForUser(read,context.getSender(),this.app.oauth2Config); if(accessToken){ try { - let events: Array =["pull_request","push","issues","deployment_status"]; + let events: Array =["pull_request","push","issues","deployment_status","star"]; + //if hook exists we set its take its hook id and add our aditional events to it + let eventSusbcriptions = new Map;//this helps us mark the new events to be added + for(let event of events){ + eventSusbcriptions.set(event,false); + } let url = await getWebhookUrl(this.app); - let response = await createSubscription(http,repository,url,accessToken.token,events); - let subsciptionStorage = new Subscription(persistence,read.getPersistenceReader()) - + let subsciptionStorage = new Subscription(persistence,read.getPersistenceReader()); + let user = await context.getSender(); + let repositorySubscriptions = await subsciptionStorage.getSubscriptionsByRepo(repository,user.id); + let hookId = ""; + for(let susbcription of repositorySubscriptions){ + if(hookId==""){ + hookId=susbcription.webhookId; + } + eventSusbcriptions.set(susbcription.event,true); + } + let newEvents:Array=[]; + for(let [event,value] of eventSusbcriptions){ + if(!value){ + newEvents.push(event); + } + } let createdEntry = false ; + if(hookId==""){ + let response = await createSubscription(http,repository,url,accessToken.token,events); + hookId=response.id; + }else{ + if(newEvents.length){ + let response = await updateSubscription(http,repository,accessToken.token,hookId,events); + hookId=response.id; + } + } for(let event of events){ - createdEntry= await subsciptionStorage.createSubscription(repository,event,response?.id,room,context.getSender()); + createdEntry = await subsciptionStorage.createSubscription(repository,event,hookId,room,context.getSender()); } if(!createdEntry){ throw new Error("Error creating new susbcription entry"); @@ -131,29 +158,41 @@ export class GithubCommand implements ISlashCommand { let accessToken = await getAccessTokenForUser(read,context.getSender(),this.app.oauth2Config); if(accessToken){ try { - let events: Array =["pull_request","push","issues","deployment_status"]; + let user = await context.getSender(); + let events: Array =["pull_request","push","issues","deployment_status","star"]; let subsciptionStorage = new Subscription(persistence,read.getPersistenceReader()) - let roomSubscriptions: Array = await subsciptionStorage.getSubscriptions(room.id); - let hooksMap=new Map; - for(let event of events){ - for(let subscription of roomSubscriptions){ - let webhookId = subscription.webhookId; - if(subscription.repoName!==repository || subscription.event!==event){ - //skip entry if event and repo name doesnt match or if hook has been deleted - continue; - } - if(!hooksMap.has(webhookId)){ - hooksMap.set(webhookId,true); - await deleteSubscription(http,repository,accessToken.token,webhookId); - } - let deleted = await subsciptionStorage.deleteSubscriptions(repository,event,room.id); - if(!deleted){ - console.log("Cant delete unsubsribed hook"); + let oldSubscriptions = await subsciptionStorage.getSubscriptionsByRepo(repository,user.id); + await subsciptionStorage.deleteSubscriptionsByRepoUser(repository, room.id, user.id); + let hookId = ""; + //check if any subscription events of the repo is left in any other room + let eventSubscriptions = new Map; + for (let subsciption of oldSubscriptions) { + eventSubscriptions.set(subsciption.event, false); + if(hookId == ""){ + hookId = subsciption.webhookId; + } + } + let updatedsubscriptions = await subsciptionStorage.getSubscriptionsByRepo(repository, user.id); + if (updatedsubscriptions.length == 0) { + await deleteSubscription(http, repository, accessToken.token, hookId); + } else { + for (let subsciption of updatedsubscriptions) { + eventSubscriptions.set(subsciption.event, true); + } + let updatedEvents: Array = []; + let sameEvents = true; + for (let [event, present] of eventSubscriptions) { + sameEvents = sameEvents && present; + if (present) { + updatedEvents.push(event); } } + if (updatedEvents.length && !sameEvents) { + let response = await updateSubscription(http, repository, accessToken.token, hookId, updatedEvents); + } } - - await sendNotification(read,modify,context.getSender(),room,`Unsubscibed ${repository} ✔️`); + + await sendNotification(read, modify, user, room, `Unsubscribed to ${repository} 🔕`); } catch (error) { console.log("SubcommandError",error); diff --git a/github/endpoints/githubEndpoints.ts b/github/endpoints/githubEndpoints.ts index b3ea114..261b58b 100644 --- a/github/endpoints/githubEndpoints.ts +++ b/github/endpoints/githubEndpoints.ts @@ -60,6 +60,12 @@ export class githubWebHooks extends ApiEndpoint { messageText = `*[New Issue](${payload.issue.html_url})* *|* *#${payload.issue.number}* *${payload.issue.title}* *|* *[${payload.repository.full_name}](${payload.repository.html_url})* `; } else if (event == "deployment_status") { messageText = `*Deployment Status ${payload.deployment_status.state}* *|* *${payload.repository.full_name}*`; + } else if (event == "star"){ + if(payload?.action == "created"){ + messageText = `*New Stars on* *${payload.repository.full_name}* *|* *${payload.repository.stargazers_count}* ⭐`; + }else{ + return this.success(); + } } for (let subsciption of subsciptions) { diff --git a/github/modals/addSubscriptionsModal.ts b/github/modals/addSubscriptionsModal.ts index cb537c8..ac29e12 100644 --- a/github/modals/addSubscriptionsModal.ts +++ b/github/modals/addSubscriptionsModal.ts @@ -119,6 +119,14 @@ export async function AddSubscriptionModal({ emoji: true, }, }, + { + value: "star", + text: { + type: TextObjectType.PLAINTEXT, + text: "New Stars", + emoji: true, + }, + }, ], placeholder: { type: TextObjectType.PLAINTEXT, From 74c9fa3ab3d2237944b311c5b90f3328db7270bd Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Wed, 6 Jul 2022 02:26:49 +0530 Subject: [PATCH 26/32] Update Helper Message --- github/lib/helperMessage.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/github/lib/helperMessage.ts b/github/lib/helperMessage.ts index df6d8c1..d3b403a 100644 --- a/github/lib/helperMessage.ts +++ b/github/lib/helperMessage.ts @@ -29,6 +29,9 @@ export async function helperMessage({ 5) Get Recent Pull Request of a Repository -> /github Username/RepositoryName pulls 6) Review a Pull Request -> /github Username/RepositoryName pulls pullNumber 7) Login to GitHub -> /github login + 8) View/Add/Delete/Update Repository Subscriptions -> /github subscribe + 9) Subscribe to all repository events -> /github Username/RepositoryName subscribe + 10) Unsubscribe to all repository events -> /github Username/RepositoryName unsubscribe `; From dca2215a811e4f5e751f4a4350b83741774299c2 Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Wed, 6 Jul 2022 11:00:49 +0530 Subject: [PATCH 27/32] New Add Subscription Title --- github/enum/Modals.ts | 1 + github/modals/addSubscriptionsModal.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/github/enum/Modals.ts b/github/enum/Modals.ts index 3601424..f7e9184 100644 --- a/github/enum/Modals.ts +++ b/github/enum/Modals.ts @@ -28,6 +28,7 @@ export enum ModalsEnum { UPDATE_SUBSCRIPTION_ACTION='update-subscription', UPDATE_SUBSCRIPTION_LABEL='Update', DELETE_SUBSCIPTIONS_TITLE='Unsubscribe to Repositories', + ADD_SUBSCIPTIONS_TITLE='New Subscription', OPEN_REPO_ACTION='open-repo', OPEN_REPO_LABEL='Open', REPO_NAME_INPUT='repo-name-input', diff --git a/github/modals/addSubscriptionsModal.ts b/github/modals/addSubscriptionsModal.ts index ac29e12..c0b2154 100644 --- a/github/modals/addSubscriptionsModal.ts +++ b/github/modals/addSubscriptionsModal.ts @@ -150,7 +150,7 @@ export async function AddSubscriptionModal({ id: viewId, title: { type: TextObjectType.PLAINTEXT, - text: AppEnum.DEFAULT_TITLE, + text: ModalsEnum.ADD_SUBSCIPTIONS_TITLE, }, close: block.newButtonElement({ text: { From b74dc269b9c0bdc16e05c8889cb838f4614df6a5 Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Wed, 6 Jul 2022 23:03:47 +0530 Subject: [PATCH 28/32] Send Login Message as Notification in Same RToom --- github/GithubApp.ts | 16 +++++++++++++--- github/commands/GithubCommand.ts | 2 +- github/oath2/authentication.ts | 8 ++++++-- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/github/GithubApp.ts b/github/GithubApp.ts index a88986d..fc2c87a 100644 --- a/github/GithubApp.ts +++ b/github/GithubApp.ts @@ -27,7 +27,7 @@ import { } from "@rocket.chat/apps-engine/definition/oauth2/IOAuth2"; import { createOAuth2Client } from "@rocket.chat/apps-engine/definition/oauth2/OAuth2"; import { createSectionBlock } from "./lib/blocks"; -import { sendDirectMessage } from "./lib/message"; +import { sendDirectMessage, sendNotification } from "./lib/message"; import { OAuth2Client } from "@rocket.chat/apps-engine/server/oauth2/OAuth2Client"; import { deleteOathToken } from "./processors/deleteOAthToken"; import { ProcessorsEnum } from "./enum/Processors"; @@ -38,6 +38,7 @@ import { import { githubWebHooks } from "./endpoints/githubEndpoints"; import { IJobContext } from "@rocket.chat/apps-engine/definition/scheduler"; import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; +import { clearInteractionRoomData, getInteractionRoomData } from "./persistance/roomInteraction"; export class GithubApp extends App { constructor(info: IAppInfo, logger: ILogger, accessors: IAppAccessors) { @@ -61,14 +62,23 @@ export class GithubApp extends App { }, }; let text = `GitHub Authentication Succesfull 🚀`; - + let interactionData = await getInteractionRoomData(read.getPersistenceReader(),user.id) ; + if (token) { // await registerAuthorizedUser(read, persistence, user); await modify.getScheduler().scheduleOnce(deleteTokenTask); } else { text = `Authentication Failure 😔`; } - await sendDirectMessage(read, modify, user, text, persistence); + if(interactionData && interactionData.roomId){ + let roomId = interactionData.roomId as string; + let room = await read.getRoomReader().getById(roomId) as IRoom; + await clearInteractionRoomData(persistence,user.id); + await sendNotification(read,modify,user,room,text); + }else{ + await sendDirectMessage(read, modify, user, text, persistence); + } + } public oauth2ClientInstance: IOAuth2Client; public oauth2Config: IOAuth2ClientOptions = { diff --git a/github/commands/GithubCommand.ts b/github/commands/GithubCommand.ts index a8a3640..c2f40de 100644 --- a/github/commands/GithubCommand.ts +++ b/github/commands/GithubCommand.ts @@ -66,7 +66,7 @@ export class GithubCommand implements ISlashCommand { }else{ switch(command[0]){ case SubcommandEnum.LOGIN : { - await authorize(this.app, read, modify, context.getSender(), persistence); + await authorize(this.app, read, modify, context.getSender(),room, persistence); break; } case SubcommandEnum.TEST : { diff --git a/github/oath2/authentication.ts b/github/oath2/authentication.ts index eb395c3..4108fd2 100644 --- a/github/oath2/authentication.ts +++ b/github/oath2/authentication.ts @@ -3,16 +3,19 @@ import { IPersistence, IRead, } from "@rocket.chat/apps-engine/definition/accessors"; +import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; import { IUser } from "@rocket.chat/apps-engine/definition/users"; import { GithubApp } from "../GithubApp"; import { IButton, createSectionBlock } from "../lib/blocks"; -import { sendDirectMessage } from "../lib/message"; +import { sendNotification } from "../lib/message"; +import { storeInteractionRoomData } from "../persistance/roomInteraction"; export async function authorize( app: GithubApp, read: IRead, modify: IModify, user: IUser, + room: IRoom, persistence: IPersistence ): Promise { const url = await app @@ -25,5 +28,6 @@ export async function authorize( }; const message = `Login to GitHub`; const block = await createSectionBlock(modify, message, button); - await sendDirectMessage(read, modify, user, message, persistence, block); + await storeInteractionRoomData(persistence,user.id,room.id); + await sendNotification(read, modify, user, room, message, block); } From 5cb8dc181e852ee9a00959beaa8c7dd16e4bb2aa Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Tue, 12 Jul 2022 23:04:10 +0530 Subject: [PATCH 29/32] Rmoeve native module support --- github/modals/fileCodeModal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/github/modals/fileCodeModal.ts b/github/modals/fileCodeModal.ts index f0eaf4b..cc7dfca 100644 --- a/github/modals/fileCodeModal.ts +++ b/github/modals/fileCodeModal.ts @@ -15,7 +15,7 @@ import { UIKitBlockInteractionContext, UIKitInteractionContext, } from "@rocket.chat/apps-engine/definition/uikit"; -import { type } from "os"; + export async function fileCodeModal({ data, From d159ee2ea72cdc8e9008893f4a62cf377e010f8a Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Sat, 16 Jul 2022 04:48:57 +0530 Subject: [PATCH 30/32] Add More Isuue/PR payload actions --- github/endpoints/githubEndpoints.ts | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/github/endpoints/githubEndpoints.ts b/github/endpoints/githubEndpoints.ts index 261b58b..6091c47 100644 --- a/github/endpoints/githubEndpoints.ts +++ b/github/endpoints/githubEndpoints.ts @@ -55,9 +55,27 @@ export class githubWebHooks extends ApiEndpoint { if (event == "push") { messageText = `*New Commits to* *[${payload.repository.full_name}](${payload.repository.html_url}) by ${payload.pusher.name}*`; } else if (event == "pull_request") { - messageText = `*[New Pull Reqeust](${payload.pull_request.html_url})* *|* *#${payload.pull_request.number} ${payload.pull_request.title}* by *[${payload.user.login}](${payload.user.html_url})* *|* *[${payload.repository.full_name}]*`; + if(payload.action == "opened"){ + messageText = `*[New Pull Reqeust](${payload.pull_request.html_url})* *|* *#${payload.pull_request.number} ${payload.pull_request.title}* by *[${payload.user.login}](${payload.user.html_url})* *|* *[${payload.repository.full_name}]*`; + }else if(payload.action == "closed" && payload.pull_request.merged ){ + messageText = `*[Merged Pull Reqeust](${payload.pull_request.html_url})* *|* *#${payload.pull_request.number} ${payload.pull_request.title}* by *[${payload.user.login}](${payload.user.html_url})* *|* *[${payload.repository.full_name}]*`; + }else if(payload.action == "closed" && !payload.pull_request.merged){ + messageText = `*[Closed Pull Reqeust](${payload.pull_request.html_url})* *|* *#${payload.pull_request.number} ${payload.pull_request.title}* by *[${payload.user.login}](${payload.user.html_url})* *|* *[${payload.repository.full_name}]*`; + }else if(payload.action =="reopened"){ + messageText = `*[ReOpened Pull Reqeust](${payload.pull_request.html_url})* *|* *#${payload.pull_request.number} ${payload.pull_request.title}* by *[${payload.user.login}](${payload.user.html_url})* *|* *[${payload.repository.full_name}]*`; + }else{ + return this.success(); + } } else if (event == "issues") { - messageText = `*[New Issue](${payload.issue.html_url})* *|* *#${payload.issue.number}* *${payload.issue.title}* *|* *[${payload.repository.full_name}](${payload.repository.html_url})* `; + if(payload.action == "opened"){ + messageText = `*[New Issue](${payload.issue.html_url})* *|* *#${payload.issue.number}* *${payload.issue.title}* *|* *[${payload.repository.full_name}](${payload.repository.html_url})* `; + }else if(payload.action == "closed"){ + messageText = `*[Issue Closed](${payload.issue.html_url})* *|* *#${payload.issue.number}* *${payload.issue.title}* *|* *[${payload.repository.full_name}](${payload.repository.html_url})* `; + }else if(payload.action == "reopened"){ + messageText = `*[ReOpened Issue](${payload.issue.html_url})* *|* *#${payload.issue.number}* *${payload.issue.title}* *|* *[${payload.repository.full_name}](${payload.repository.html_url})* `; + }else{ + return this.success(); + } } else if (event == "deployment_status") { messageText = `*Deployment Status ${payload.deployment_status.state}* *|* *${payload.repository.full_name}*`; } else if (event == "star"){ @@ -67,7 +85,6 @@ export class githubWebHooks extends ApiEndpoint { return this.success(); } } - for (let subsciption of subsciptions) { let roomId = subsciption.room; if (!roomId) { From d25e6ad11515839ddc35236fca0b4cf33403fd9b Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Sun, 17 Jul 2022 02:22:05 +0530 Subject: [PATCH 31/32] Handlee unauthenticatd request --- github/commands/GithubCommand.ts | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/github/commands/GithubCommand.ts b/github/commands/GithubCommand.ts index c2f40de..7f2c6b8 100644 --- a/github/commands/GithubCommand.ts +++ b/github/commands/GithubCommand.ts @@ -75,12 +75,17 @@ export class GithubCommand implements ISlashCommand { } case SubcommandEnum.SUBSCRIBE :{ //modal - const triggerId= context.getTriggerId(); - if(triggerId){ - const modal = await subsciptionsModal({modify,read,persistence,http,slashcommandcontext:context}); - await modify.getUiController().openModalView(modal,{triggerId},context.getSender()); + let accessToken = await getAccessTokenForUser(read,context.getSender(),this.app.oauth2Config); + if(accessToken && accessToken.token){ + const triggerId= context.getTriggerId(); + if(triggerId){ + const modal = await subsciptionsModal({modify,read,persistence,http,slashcommandcontext:context}); + await modify.getUiController().openModalView(modal,{triggerId},context.getSender()); + }else{ + console.log("Inavlid Trigger ID !"); + } }else{ - console.log("Inavlid Trigger ID !"); + await sendNotification(read,modify,context.getSender(),room,"Login to subscribe to repository events ! `/github login`"); } break; } @@ -101,7 +106,7 @@ export class GithubCommand implements ISlashCommand { case SubcommandEnum.SUBSCRIBE : { //sub let accessToken = await getAccessTokenForUser(read,context.getSender(),this.app.oauth2Config); - if(accessToken){ + if(accessToken && accessToken?.token){ try { let events: Array =["pull_request","push","issues","deployment_status","star"]; //if hook exists we set its take its hook id and add our aditional events to it @@ -149,14 +154,14 @@ export class GithubCommand implements ISlashCommand { console.log("SubcommandError",error); } } else{ - await sendDirectMessage(read,modify,context.getSender(),"Login to subscribe to repository events ! `/github login`",persistence); + await sendNotification(read,modify,context.getSender(),room,"Login to subscribe to repository events ! `/github login`"); } break; } case SubcommandEnum.UNSUBSCRIBE : { //unsub let accessToken = await getAccessTokenForUser(read,context.getSender(),this.app.oauth2Config); - if(accessToken){ + if(accessToken && accessToken?.token){ try { let user = await context.getSender(); let events: Array =["pull_request","push","issues","deployment_status","star"]; @@ -198,7 +203,7 @@ export class GithubCommand implements ISlashCommand { console.log("SubcommandError",error); } } else{ - await sendDirectMessage(read,modify,context.getSender(),"Login to subscribe to repository events ! `/github login`",persistence); + await sendNotification(read,modify,context.getSender(),room,"Login to subscribe to repository events ! `/github login`"); } break; } From 4dc6c2d4f15c8d80d097ae0d28b9cb6e1b4b6711 Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Sun, 17 Jul 2022 03:04:38 +0530 Subject: [PATCH 32/32] Add Logout Slash Command --- github/commands/GithubCommand.ts | 15 +++++++++++++-- github/enum/Subcommands.ts | 1 + 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/github/commands/GithubCommand.ts b/github/commands/GithubCommand.ts index 7f2c6b8..cf3c242 100644 --- a/github/commands/GithubCommand.ts +++ b/github/commands/GithubCommand.ts @@ -16,7 +16,7 @@ import { basicQueryMessage } from "../helpers/basicQueryMessage"; import { pullDetailsModal } from "../modals/pullDetailsModal"; import { authorize } from "../oath2/authentication"; import { SubcommandEnum } from "../enum/Subcommands"; -import { getAccessTokenForUser } from "../persistance/auth"; +import { getAccessTokenForUser,revokeUserAccessToken } from "../persistance/auth"; import { IUser } from "@rocket.chat/apps-engine/definition/users"; import { removeToken } from "../persistance/auth"; import { getWebhookUrl } from "../helpers/getWebhookURL"; @@ -27,6 +27,7 @@ import { Subscription } from "../persistance/subscriptions"; import { ISubscription } from "../definitions/subscription"; import { subsciptionsModal } from "../modals/subscriptionsModal"; + export class GithubCommand implements ISlashCommand { public constructor(private readonly app: GithubApp) {} public command = "github"; @@ -69,6 +70,16 @@ export class GithubCommand implements ISlashCommand { await authorize(this.app, read, modify, context.getSender(),room, persistence); break; } + case SubcommandEnum.LOGOUT : { + let accessToken = await getAccessTokenForUser(read,context.getSender(),this.app.oauth2Config); + if(accessToken && accessToken?.token){ + await revokeUserAccessToken(read, sender, persistence, http, this.app.oauth2Config); + await sendNotification(read,modify,context.getSender(),room,"Logged out successfully !"); + }else{ + await sendNotification(read,modify,context.getSender(),room,"You are not logged in !"); + } + break; + } case SubcommandEnum.TEST : { //test command break; @@ -236,4 +247,4 @@ export class GithubCommand implements ISlashCommand { } - }}//rc-apps deploy --url https://community.liaison.edge.rocketchat.digital --username samad.yar.khan --password samad --update + }} diff --git a/github/enum/Subcommands.ts b/github/enum/Subcommands.ts index 0a266b0..5311664 100644 --- a/github/enum/Subcommands.ts +++ b/github/enum/Subcommands.ts @@ -1,5 +1,6 @@ export enum SubcommandEnum { LOGIN = 'login', + LOGOUT = 'logout', HELP = 'help', SUBSCRIBE = 'subscribe', UNSUBSCRIBE = 'unsubscribe',