Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] Notion Authorization with Rocket.Chat #5

Merged
merged 29 commits into from
Jun 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
83b4f9c
feat: credentials settings for authorization
Nabhag8848 May 31, 2023
fa0f9f4
feat(task): created IOAuth2Client and OAuth2Client
Nabhag8848 Jun 1, 2023
b8f2337
feat(task): created CommandUtility and Registered Command
Nabhag8848 Jun 1, 2023
fbe424e
feat(error): Created IError and Common ErrorHandler
Nabhag8848 Jun 1, 2023
57c4fd0
feat: Created getCredential Helper for Authorization
Nabhag8848 Jun 1, 2023
7111cfd
fix: getCredentials() to return siteUrlWithoutTrailingSlash
Nabhag8848 Jun 2, 2023
fef69d8
feat: created OAuth2Storage with Methods to connect, get and remove t…
Nabhag8848 Jun 2, 2023
955be9f
fix: changed property status to statusCode in IError
Nabhag8848 Jun 2, 2023
6c39dc1
feat: created and Registered NotionSDK
Nabhag8848 Jun 2, 2023
8da5050
feat: created RoomInteraction Class for persisting current interactio…
Nabhag8848 Jun 3, 2023
2a40dd8
fix: tranfered getAuthorizationUrl() to IOAuth2Client
Nabhag8848 Jun 4, 2023
8957c76
feat: created BlockBuilder and ElementBuilder Class
Nabhag8848 Jun 5, 2023
34c9aa7
feat: created sendNotification() which notifies User
Nabhag8848 Jun 5, 2023
ece7d2a
feat(ui): created reusable connectBlock()
Nabhag8848 Jun 5, 2023
0dacc50
feat(utils): registered AppUtils
Nabhag8848 Jun 5, 2023
cd867e7
feat: implemented Connect() and Handled Connect BlockAction
Nabhag8848 Jun 5, 2023
66826b8
feat(connect): created, Registered and Implemented Webhook get() Auth…
Nabhag8848 Jun 5, 2023
68cad40
feat: implemented disconnect() for workspace
Nabhag8848 Jun 5, 2023
26c59a6
feat(ui): enhance several auth notificiations
Nabhag8848 Jun 6, 2023
d128fee
feat(ui): created IContextBlock, IPreviewBlock and ImageElement
Nabhag8848 Jun 6, 2023
f5e5332
feat(ui): implemented addImage(), createPreviewblock() and createCont…
Nabhag8848 Jun 6, 2023
e6648e5
feat(ui): created previewLayout for connected workspace
Nabhag8848 Jun 6, 2023
74b44b8
feat(ui): enhanced connected to workspace notification with previewLa…
Nabhag8848 Jun 6, 2023
e1069e9
feat(ui): created and registered Custom Authorization HTML Page
Nabhag8848 Jun 6, 2023
bcbfabb
fix: Interface Design ICommandUtility and IOAuth2Client to support an…
Nabhag8848 Jun 7, 2023
c7287e1
fix(ui): connectPreviewLayout font-weight to black bold
Nabhag8848 Jun 7, 2023
5bc0374
feat: Empty Credentials in AppSettings Notifies Relevent message base…
Nabhag8848 Jun 7, 2023
f7257e9
feat: support param with any letter case
Nabhag8848 Jun 11, 2023
f9df28f
fix: NotionSDK design to avoid IHttp param in methods
Nabhag8848 Jun 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions NotionApp.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,99 @@
import {
IAppAccessors,
IConfigurationExtend,
IEnvironmentRead,
IHttp,
ILogger,
IModify,
IPersistence,
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 { settings } from "./config/settings";
import { OAuth2Client } from "./src/authorization/OAuth2Client";
import { NotionCommand } from "./src/commands/NotionCommand";
import { NotionSDK } from "./src/lib/NotionSDK";
import {
ApiSecurity,
ApiVisibility,
} from "@rocket.chat/apps-engine/definition/api";
import { WebHookEndpoint } from "./src/endpoints/webhook";
import { ElementBuilder } from "./src/lib/ElementBuilder";
import { BlockBuilder } from "./src/lib/BlockBuilder";
import {
IUIKitResponse,
UIKitBlockInteractionContext,
} from "@rocket.chat/apps-engine/definition/uikit";
import { RoomInteractionStorage } from "./src/storage/RoomInteraction";
import { OAuth2Action } from "./enum/OAuth2";
import { IAppUtils } from "./definition/lib/IAppUtils";

export class NotionApp extends App {
private oAuth2Client: OAuth2Client;
private NotionSdk: NotionSDK;
private elementBuilder: ElementBuilder;
private blockBuilder: BlockBuilder;
constructor(info: IAppInfo, logger: ILogger, accessors: IAppAccessors) {
super(info, logger, accessors);
}

public async initialize(
configurationExtend: IConfigurationExtend,
environmentRead: IEnvironmentRead
): Promise<void> {
await configurationExtend.slashCommands.provideSlashCommand(
new NotionCommand(this)
);
await Promise.all(
settings.map((setting) => {
configurationExtend.settings.provideSetting(setting);
})
);

await configurationExtend.api.provideApi({
visibility: ApiVisibility.PUBLIC,
security: ApiSecurity.UNSECURE,
endpoints: [new WebHookEndpoint(this)],
});

this.oAuth2Client = new OAuth2Client(this);
this.NotionSdk = new NotionSDK(this.getAccessors().http);
this.elementBuilder = new ElementBuilder(this.getID());
this.blockBuilder = new BlockBuilder(this.getID());
}

public getOAuth2Client(): OAuth2Client {
return this.oAuth2Client;
}
public getUtils(): IAppUtils {
return {
NotionSdk: this.NotionSdk,
elementBuilder: this.elementBuilder,
blockBuilder: this.blockBuilder,
};
}

public async executeBlockActionHandler(
context: UIKitBlockInteractionContext,
read: IRead,
http: IHttp,
persistence: IPersistence,
modify: IModify
): Promise<IUIKitResponse> {
// Todo[Week 2]: Make a Interface and Class
const { actionId, user, room } = context.getInteractionData();

if (actionId == OAuth2Action.CONNECT_TO_WORKSPACE) {
const persistenceRead = read.getPersistenceReader();
const roomId = room?.id as string;
const roomInteraction = new RoomInteractionStorage(
persistence,
persistenceRead
);
await roomInteraction.storeInteractionRoomId(user.id, roomId);
}

return context.getInteractionResponder().successResponse();
}
}
38 changes: 38 additions & 0 deletions config/settings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {
ISetting,
SettingType,
} from "@rocket.chat/apps-engine/definition/settings";

// The settings that will be available for the App
// warning(AppsEngine Error): Having OAuth2Setting in enums folder causing an error in deployment reason not known.
export enum OAuth2Setting {
CLIENT_ID = "notion-client-id",
CLIENT_SECRET = "notion-client-secret",
}

export const settings: Array<ISetting> = [
{
id: OAuth2Setting.CLIENT_ID,
type: SettingType.STRING,
packageValue: "",
required: true,
public: false,
section: "CredentialSettings",
i18nLabel: "ClientIdLabel",
i18nPlaceholder: "ClientIdPlaceholder",
hidden: false,
multiline: false,
},
{
id: OAuth2Setting.CLIENT_SECRET,
type: SettingType.PASSWORD,
packageValue: "",
required: true,
public: false,
section: "CredentialSettings",
i18nLabel: "ClientSecretLabel",
i18nPlaceholder: "ClientSecretPlaceholder",
hidden: false,
multiline: false,
},
];
5 changes: 5 additions & 0 deletions definition/authorization/ICredential.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface ICredential {
clientId: string;
clientSecret: string;
siteUrl: string;
}
39 changes: 39 additions & 0 deletions definition/authorization/IOAuth2Storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { NotionOwnerType, NotionTokenType } from "../../enum/Notion";

export interface IOAuth2Storage {
connectUserToWorkspace(
tokenInfo: ITokenInfo,
userId: string
): Promise<void>;
getCurrentWorkspace(userId: string): Promise<ITokenInfo | null>;
disconnectUserFromCurrentWorkspace(userId: string): Promise<ITokenInfo | null>;
}

export interface ITokenInfo {
access_token: string;
token_type: NotionTokenType.TOKEN_TYPE;
bot_id: string;
workspace_icon: string | null;
workspace_id: string;
workspace_name: string | null;
owner: INotionOwner;
duplicated_template_id: string | null;
}

interface INotionOwner {
type: NotionOwnerType.USER;
user: INotionUser;
}

interface INotionUser {
object: NotionOwnerType.USER;
id: string;
name: string | null;
avatar_url: string | null;
type: NotionOwnerType.PERSON;
person: INotionPerson;
}

interface INotionPerson {
email: string;
}
35 changes: 35 additions & 0 deletions definition/authorization/IOAuthClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {
IHttp,
IModify,
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";

export interface IOAuth2Client {
connect(
room: IRoom,
sender: IUser,
read: IRead,
modify: IModify,
http: IHttp,
persis: IPersistence
): Promise<void>;

disconnect(
room: IRoom,
sender: IUser,
read: IRead,
modify: IModify,
http: IHttp,
persis: IPersistence
): Promise<void>;

getAuthorizationUrl(
user: IUser,
read: IRead,
modify: IModify,
room: IRoom
): Promise<string | null>;
}
37 changes: 37 additions & 0 deletions definition/command/ICommandUtility.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import {
IHttp,
IModify,
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 { NotionApp } from "../../NotionApp";

export interface ICommandUtility {
app: NotionApp;
params: Array<string>;
sender: IUser;
room: IRoom;
read: IRead;
modify: IModify;
http: IHttp;
persis: IPersistence;
triggerId?: string;
threadId?: string;

resolveCommand(): Promise<void>;
}

export interface ICommandUtilityParams {
app: NotionApp;
params: Array<string>;
sender: IUser;
room: IRoom;
read: IRead;
modify: IModify;
http: IHttp;
persis: IPersistence;
triggerId?: string;
threadId?: string;
}
4 changes: 4 additions & 0 deletions definition/errors/IError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface IError extends Error {
statusCode: number;
additionalInfo?: string;
}
9 changes: 9 additions & 0 deletions definition/lib/IAppUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { BlockBuilder } from "../../src/lib/BlockBuilder";
import { ElementBuilder } from "../../src/lib/ElementBuilder";
import { NotionSDK } from "../../src/lib/NotionSDK";

export interface IAppUtils {
NotionSdk: NotionSDK;
elementBuilder: ElementBuilder;
blockBuilder: BlockBuilder;
}
19 changes: 19 additions & 0 deletions definition/lib/INotion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { IHttp } from "@rocket.chat/apps-engine/definition/accessors";
import { URL } from "url";
import { ITokenInfo } from "../authorization/IOAuth2Storage";
import { ClientError } from "../../errors/Error";
import { NotionApi } from "../../enum/Notion";

export interface INotion {
baseUrl: string;
NotionVersion: string;
}

export interface INotionSDK extends INotion {
http: IHttp;
createToken(
redirectUrl: URL,
code: string,
credentials: string
): Promise<ITokenInfo | ClientError>;
}
5 changes: 5 additions & 0 deletions definition/lib/IRoomInteraction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface IRoomInteractionStorage {
storeInteractionRoomId(userId: string, roomId: string): Promise<void>;
getInteractionRoomId(userId: string): Promise<string>;
clearInteractionRoomId(userId: string): Promise<void>;
}
3 changes: 3 additions & 0 deletions definition/ui-kit/Block/IActionBlock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { ActionsBlock } from "@rocket.chat/ui-kit";

export type ActionBlockParam = Pick<ActionsBlock, "blockId" | "elements">;
20 changes: 20 additions & 0 deletions definition/ui-kit/Block/IBlockBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {
SectionBlock,
ActionsBlock,
PreviewBlockBase,
PreviewBlockWithThumb,
ContextBlock,
} from "@rocket.chat/ui-kit";
import { SectionBlockParam } from "./ISectionBlock";
import { ActionBlockParam } from "./IActionBlock";
import { PreviewBlockParam } from "./IPreviewBlock";
import { ContextBlockParam } from "./IContextBlock";

export interface IBlockBuilder {
createSectionBlock(param: SectionBlockParam): SectionBlock;
createActionBlock(param: ActionBlockParam): ActionsBlock;
createPreviewBlock(
param: PreviewBlockParam
): PreviewBlockBase | PreviewBlockWithThumb;
createContextBlock(param: ContextBlockParam): ContextBlock;
}
7 changes: 7 additions & 0 deletions definition/ui-kit/Block/IContextBlock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { ImageParam } from "../Element/IImageElement";
import { ImageElement } from "@rocket.chat/ui-kit";

export type ContextBlockParam = {
contextElements: Array<string | ImageElement>;
blockId?: string;
};
8 changes: 8 additions & 0 deletions definition/ui-kit/Block/IPreviewBlock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { PreviewBlockWithThumb } from "@rocket.chat/ui-kit";

export type PreviewBlockParam = Partial<
Pick<PreviewBlockWithThumb, "footer" | "thumb">
> & {
title: Array<string>;
description: Array<string>;
};
6 changes: 6 additions & 0 deletions definition/ui-kit/Block/ISectionBlock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { SectionBlock } from "@rocket.chat/ui-kit";

export type SectionBlockParam = Pick<SectionBlock, "accessory" | "blockId"> & {
text?: string;
fields?: Array<string>;
};
5 changes: 5 additions & 0 deletions definition/ui-kit/Element/IButtonElement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { ButtonElement } from "@rocket.chat/ui-kit";

export type ButtonParam = Pick<ButtonElement, "value" | "style" | "url"> & {
text: string;
};
14 changes: 14 additions & 0 deletions definition/ui-kit/Element/IElementBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ButtonStyle } from "@rocket.chat/apps-engine/definition/uikit";
import { ButtonElement, ImageElement } from "@rocket.chat/ui-kit";
import { ButtonParam } from "./IButtonElement";
import { ImageParam } from "./IImageElement";

export interface IElementBuilder {
addButton(
param: ButtonParam,
interaction: ElementInteractionParam
): ButtonElement;
addImage(param: ImageParam): ImageElement;
}

export type ElementInteractionParam = { blockId: string; actionId: string };
3 changes: 3 additions & 0 deletions definition/ui-kit/Element/IImageElement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { ImageElement } from "@rocket.chat/ui-kit";

export type ImageParam = Pick<ImageElement, "imageUrl" | "altText">;
4 changes: 4 additions & 0 deletions enum/CommandParam.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum CommandParam {
CONNECT = "connect",
DISCONNECT = "disconnect",
}
8 changes: 8 additions & 0 deletions enum/Error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export enum ErrorName {
BAD_REQUEST = "Bad Request",
UNAUTHORIZED = "Unauthorized",
SERVER_ERROR = "Internal Server Error",
FORBIDDEN = "Forbidden",
MANY_REQUESTS = "Too Many Requests",
NOT_FOUND = "Not Found",
}
Loading