From 0825eb36e4564b50a4a3fdbcc5ddc6470e97acec Mon Sep 17 00:00:00 2001 From: Faruk AYDIN Date: Thu, 6 Oct 2022 15:49:05 +0300 Subject: [PATCH] chore: Use get app helper to get application data --- packages/backend/src/apps/slack/actions.ts | 15 - .../src/apps/slack/actions/find-message.ts | 26 -- .../slack/actions/send-message-to-channel.ts | 18 - .../backend/src/apps/slack/assets/favicon.svg | 7 - .../backend/src/apps/slack/authentication.ts | 36 -- .../slack/client/endpoints/find-messages.ts | 44 --- .../endpoints/post-message-to-channel.ts | 44 --- .../client/endpoints/verify-access-token.ts | 35 -- .../backend/src/apps/slack/client/index.ts | 29 -- packages/backend/src/apps/slack/data.ts | 12 - .../src/apps/slack/data/list-channels.ts | 31 -- packages/backend/src/apps/slack/index.d.ts | 0 packages/backend/src/apps/slack/index.ts | 30 -- packages/backend/src/apps/slack/info.json | 278 -------------- packages/backend/src/apps/slack/triggers.ts | 13 - .../slack/triggers/new-message-to-channel.ts | 47 --- packages/backend/src/apps/twitter/actions.ts | 12 - .../src/apps/twitter/actions/create-tweet.ts | 17 - .../src/apps/twitter/assets/favicon.svg | 4 - .../src/apps/twitter/authentication.ts | 51 --- .../twitter/client/endpoints/create-tweet.ts | 40 --- .../client/endpoints/get-current-user.ts | 35 -- .../client/endpoints/get-user-by-username.ts | 45 --- .../client/endpoints/get-user-followers.ts | 70 ---- .../client/endpoints/get-user-tweets.ts | 71 ---- .../client/endpoints/oauth-request-token.ts | 42 --- .../twitter/client/endpoints/search-tweets.ts | 74 ---- .../client/endpoints/verify-access-token.ts | 20 -- .../backend/src/apps/twitter/client/index.ts | 64 ---- packages/backend/src/apps/twitter/index.d.ts | 0 packages/backend/src/apps/twitter/index.ts | 27 -- packages/backend/src/apps/twitter/info.json | 339 ------------------ packages/backend/src/apps/twitter/triggers.ts | 21 -- .../src/apps/twitter/triggers/my-followers.ts | 28 -- .../src/apps/twitter/triggers/my-tweets.ts | 25 -- .../apps/twitter/triggers/search-tweets.ts | 26 -- .../src/apps/twitter/triggers/user-tweets.ts | 27 -- .../apps/twitter2/auth/create-auth-data.ts | 2 +- .../new-follower-of-me/my-followers.ts | 14 +- .../src/graphql/mutations/create-auth-data.ts | 2 +- .../graphql/mutations/create-connection.ts | 2 +- .../graphql/mutations/verify-connection.ts | 2 +- .../backend/src/graphql/queries/get-app.ts | 2 +- .../backend/src/graphql/queries/get-apps.ts | 4 +- .../src/graphql/queries/get-connected-apps.ts | 2 +- .../backend/src/helpers/app-assets-handler.ts | 15 +- .../backend/src/helpers/app-info-converter.ts | 18 +- packages/backend/src/helpers/get-app.ts | 67 ++++ packages/backend/src/models/app.ts | 32 +- packages/backend/src/models/connection.ts | 4 - packages/backend/src/models/step.ts | 4 - packages/types/index.d.ts | 27 +- 52 files changed, 139 insertions(+), 1761 deletions(-) delete mode 100644 packages/backend/src/apps/slack/actions.ts delete mode 100644 packages/backend/src/apps/slack/actions/find-message.ts delete mode 100644 packages/backend/src/apps/slack/actions/send-message-to-channel.ts delete mode 100644 packages/backend/src/apps/slack/assets/favicon.svg delete mode 100644 packages/backend/src/apps/slack/authentication.ts delete mode 100644 packages/backend/src/apps/slack/client/endpoints/find-messages.ts delete mode 100644 packages/backend/src/apps/slack/client/endpoints/post-message-to-channel.ts delete mode 100644 packages/backend/src/apps/slack/client/endpoints/verify-access-token.ts delete mode 100644 packages/backend/src/apps/slack/client/index.ts delete mode 100644 packages/backend/src/apps/slack/data.ts delete mode 100644 packages/backend/src/apps/slack/data/list-channels.ts delete mode 100644 packages/backend/src/apps/slack/index.d.ts delete mode 100644 packages/backend/src/apps/slack/index.ts delete mode 100644 packages/backend/src/apps/slack/info.json delete mode 100644 packages/backend/src/apps/slack/triggers.ts delete mode 100644 packages/backend/src/apps/slack/triggers/new-message-to-channel.ts delete mode 100644 packages/backend/src/apps/twitter/actions.ts delete mode 100644 packages/backend/src/apps/twitter/actions/create-tweet.ts delete mode 100644 packages/backend/src/apps/twitter/assets/favicon.svg delete mode 100644 packages/backend/src/apps/twitter/authentication.ts delete mode 100644 packages/backend/src/apps/twitter/client/endpoints/create-tweet.ts delete mode 100644 packages/backend/src/apps/twitter/client/endpoints/get-current-user.ts delete mode 100644 packages/backend/src/apps/twitter/client/endpoints/get-user-by-username.ts delete mode 100644 packages/backend/src/apps/twitter/client/endpoints/get-user-followers.ts delete mode 100644 packages/backend/src/apps/twitter/client/endpoints/get-user-tweets.ts delete mode 100644 packages/backend/src/apps/twitter/client/endpoints/oauth-request-token.ts delete mode 100644 packages/backend/src/apps/twitter/client/endpoints/search-tweets.ts delete mode 100644 packages/backend/src/apps/twitter/client/endpoints/verify-access-token.ts delete mode 100644 packages/backend/src/apps/twitter/client/index.ts delete mode 100644 packages/backend/src/apps/twitter/index.d.ts delete mode 100644 packages/backend/src/apps/twitter/index.ts delete mode 100644 packages/backend/src/apps/twitter/info.json delete mode 100644 packages/backend/src/apps/twitter/triggers.ts delete mode 100644 packages/backend/src/apps/twitter/triggers/my-followers.ts delete mode 100644 packages/backend/src/apps/twitter/triggers/my-tweets.ts delete mode 100644 packages/backend/src/apps/twitter/triggers/search-tweets.ts delete mode 100644 packages/backend/src/apps/twitter/triggers/user-tweets.ts create mode 100644 packages/backend/src/helpers/get-app.ts diff --git a/packages/backend/src/apps/slack/actions.ts b/packages/backend/src/apps/slack/actions.ts deleted file mode 100644 index e4063b76..00000000 --- a/packages/backend/src/apps/slack/actions.ts +++ /dev/null @@ -1,15 +0,0 @@ -import SendMessageToChannel from './actions/send-message-to-channel'; -import FindMessage from './actions/find-message'; -import SlackClient from './client'; - -export default class Actions { - client: SlackClient; - sendMessageToChannel: SendMessageToChannel; - findMessage: FindMessage; - - constructor(client: SlackClient) { - this.client = client; - this.sendMessageToChannel = new SendMessageToChannel(client); - this.findMessage = new FindMessage(client); - } -} diff --git a/packages/backend/src/apps/slack/actions/find-message.ts b/packages/backend/src/apps/slack/actions/find-message.ts deleted file mode 100644 index 4185d7b5..00000000 --- a/packages/backend/src/apps/slack/actions/find-message.ts +++ /dev/null @@ -1,26 +0,0 @@ -import SlackClient from '../client'; - -export default class FindMessage { - client: SlackClient; - - constructor(client: SlackClient) { - this.client = client; - } - - async run() { - const parameters = this.client.step.parameters; - const query = parameters.query as string; - const sortBy = parameters.sortBy as string; - const sortDirection = parameters.sortDirection as string; - const count = 1; - - const messages = await this.client.findMessages.run( - query, - sortBy, - sortDirection, - count, - ); - - return messages; - } -} diff --git a/packages/backend/src/apps/slack/actions/send-message-to-channel.ts b/packages/backend/src/apps/slack/actions/send-message-to-channel.ts deleted file mode 100644 index e5f290fe..00000000 --- a/packages/backend/src/apps/slack/actions/send-message-to-channel.ts +++ /dev/null @@ -1,18 +0,0 @@ -import SlackClient from '../client'; - -export default class SendMessageToChannel { - client: SlackClient; - - constructor(client: SlackClient) { - this.client = client; - } - - async run() { - const channelId = this.client.step.parameters.channel as string; - const text = this.client.step.parameters.message as string; - - const message = await this.client.postMessageToChannel.run(channelId, text); - - return message; - } -} diff --git a/packages/backend/src/apps/slack/assets/favicon.svg b/packages/backend/src/apps/slack/assets/favicon.svg deleted file mode 100644 index 3fbb3e78..00000000 --- a/packages/backend/src/apps/slack/assets/favicon.svg +++ /dev/null @@ -1,7 +0,0 @@ - - diff --git a/packages/backend/src/apps/slack/authentication.ts b/packages/backend/src/apps/slack/authentication.ts deleted file mode 100644 index b08394f9..00000000 --- a/packages/backend/src/apps/slack/authentication.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { IAuthentication, IJSONObject } from '@automatisch/types'; -import SlackClient from './client'; - -export default class Authentication implements IAuthentication { - client: SlackClient; - - static requestOptions: IJSONObject = { - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - }; - - constructor(client: SlackClient) { - this.client = client; - } - - async verifyCredentials() { - const { bot_id: botId, user: screenName } = - await this.client.verifyAccessToken.run(); - - return { - botId, - screenName, - token: this.client.connection.formattedData.accessToken, - }; - } - - async isStillVerified() { - try { - await this.client.verifyAccessToken.run(); - return true; - } catch (error) { - return false; - } - } -} diff --git a/packages/backend/src/apps/slack/client/endpoints/find-messages.ts b/packages/backend/src/apps/slack/client/endpoints/find-messages.ts deleted file mode 100644 index 50d99819..00000000 --- a/packages/backend/src/apps/slack/client/endpoints/find-messages.ts +++ /dev/null @@ -1,44 +0,0 @@ -import SlackClient from '../index'; - -export default class FindMessages { - client: SlackClient; - - constructor(client: SlackClient) { - this.client = client; - } - - async run(query: string, sortBy: string, sortDirection: string, count = 1) { - const headers = { - Authorization: `Bearer ${this.client.connection.formattedData.accessToken}`, - }; - - const params = { - query, - sort: sortBy, - sort_dir: sortDirection, - count, - }; - - const response = await this.client.httpClient.get('/search.messages', { - headers, - params, - }); - - const data = response.data; - - if (!data.ok) { - if (data.error === 'missing_scope') { - throw new Error( - `Error occured while finding messages; ${data.error}: ${data.needed}` - ); - } - - throw new Error(`Error occured while finding messages; ${data.error}`); - } - - const messages = data.messages.matches; - const message = messages?.[0]; - - return message; - } -} diff --git a/packages/backend/src/apps/slack/client/endpoints/post-message-to-channel.ts b/packages/backend/src/apps/slack/client/endpoints/post-message-to-channel.ts deleted file mode 100644 index c8f54d7e..00000000 --- a/packages/backend/src/apps/slack/client/endpoints/post-message-to-channel.ts +++ /dev/null @@ -1,44 +0,0 @@ -import SlackClient from '../index'; -import { IJSONObject } from '@automatisch/types'; - -export default class PostMessageToChannel { - client: SlackClient; - - constructor(client: SlackClient) { - this.client = client; - } - - async run(channelId: string, text: string) { - const message: { - data: IJSONObject | null; - error: IJSONObject | null; - } = { - data: null, - error: null, - }; - - const headers = { - Authorization: `Bearer ${this.client.connection.formattedData.accessToken}`, - }; - - const params = { - channel: channelId, - text, - }; - - const response = await this.client.httpClient.post( - '/chat.postMessage', - params, - { headers } - ); - - message.error = response?.integrationError; - message.data = response?.data?.message; - - if (response.data.ok === false) { - message.error = response.data; - } - - return message; - } -} diff --git a/packages/backend/src/apps/slack/client/endpoints/verify-access-token.ts b/packages/backend/src/apps/slack/client/endpoints/verify-access-token.ts deleted file mode 100644 index d5b3bfb5..00000000 --- a/packages/backend/src/apps/slack/client/endpoints/verify-access-token.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { IJSONObject } from '@automatisch/types'; -import qs from 'qs'; -import SlackClient from '../index'; - -export default class VerifyAccessToken { - client: SlackClient; - - static requestOptions: IJSONObject = { - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - }; - - constructor(client: SlackClient) { - this.client = client; - } - - async run() { - const response = await this.client.httpClient.post( - '/auth.test', - qs.stringify({ - token: this.client.connection.formattedData.accessToken, - }), - VerifyAccessToken.requestOptions - ); - - if (response.data.ok === false) { - throw new Error( - `Error occured while verifying credentials: ${response.data.error}.(More info: https://api.slack.com/methods/auth.test#errors)` - ); - } - - return response.data; - } -} diff --git a/packages/backend/src/apps/slack/client/index.ts b/packages/backend/src/apps/slack/client/index.ts deleted file mode 100644 index 5e5d79e3..00000000 --- a/packages/backend/src/apps/slack/client/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { IFlow, IStep, IConnection } from '@automatisch/types'; -import createHttpClient, { IHttpClient } from '../../../helpers/http-client'; -import VerifyAccessToken from './endpoints/verify-access-token'; -import PostMessageToChannel from './endpoints/post-message-to-channel'; -import FindMessages from './endpoints/find-messages'; - -export default class SlackClient { - flow: IFlow; - step: IStep; - connection: IConnection; - httpClient: IHttpClient; - - verifyAccessToken: VerifyAccessToken; - postMessageToChannel: PostMessageToChannel; - findMessages: FindMessages; - - static baseUrl = 'https://slack.com/api'; - - constructor(connection: IConnection, flow?: IFlow, step?: IStep) { - this.connection = connection; - this.flow = flow; - this.step = step; - - this.httpClient = createHttpClient({ baseURL: SlackClient.baseUrl }); - this.verifyAccessToken = new VerifyAccessToken(this); - this.postMessageToChannel = new PostMessageToChannel(this); - this.findMessages = new FindMessages(this); - } -} diff --git a/packages/backend/src/apps/slack/data.ts b/packages/backend/src/apps/slack/data.ts deleted file mode 100644 index d2743b00..00000000 --- a/packages/backend/src/apps/slack/data.ts +++ /dev/null @@ -1,12 +0,0 @@ -import ListChannels from './data/list-channels'; -import SlackClient from './client'; - -export default class Data { - client: SlackClient; - listChannels: ListChannels; - - constructor(client: SlackClient) { - this.client = client; - this.listChannels = new ListChannels(client); - } -} diff --git a/packages/backend/src/apps/slack/data/list-channels.ts b/packages/backend/src/apps/slack/data/list-channels.ts deleted file mode 100644 index e6b8593b..00000000 --- a/packages/backend/src/apps/slack/data/list-channels.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { IJSONObject } from '@automatisch/types'; -import SlackClient from '../client'; - -export default class ListChannels { - client: SlackClient; - - constructor(client: SlackClient) { - this.client = client; - } - - async run() { - const response = await this.client.httpClient.get('/conversations.list', { - headers: { - Authorization: `Bearer ${this.client.connection.formattedData.accessToken}`, - }, - }); - - if (response.data.ok === 'false') { - throw new Error( - `Error occured while fetching slack channels: ${response.data.error}` - ); - } - - return response.data.channels.map((channel: IJSONObject) => { - return { - value: channel.id, - name: channel.name, - }; - }); - } -} diff --git a/packages/backend/src/apps/slack/index.d.ts b/packages/backend/src/apps/slack/index.d.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/backend/src/apps/slack/index.ts b/packages/backend/src/apps/slack/index.ts deleted file mode 100644 index 6736e09a..00000000 --- a/packages/backend/src/apps/slack/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { - IService, - IAuthentication, - IConnection, - IFlow, - IStep, -} from '@automatisch/types'; -import Authentication from './authentication'; -import Triggers from './triggers'; -import Actions from './actions'; -import Data from './data'; -import SlackClient from './client'; - -export default class Slack implements IService { - client: SlackClient; - - authenticationClient: IAuthentication; - triggers: Triggers; - actions: Actions; - data: Data; - - constructor(connection: IConnection, flow?: IFlow, step?: IStep) { - this.client = new SlackClient(connection, flow, step); - - this.authenticationClient = new Authentication(this.client); - // this.triggers = new Triggers(this.client); - this.actions = new Actions(this.client); - this.data = new Data(this.client); - } -} diff --git a/packages/backend/src/apps/slack/info.json b/packages/backend/src/apps/slack/info.json deleted file mode 100644 index 45a4ccd2..00000000 --- a/packages/backend/src/apps/slack/info.json +++ /dev/null @@ -1,278 +0,0 @@ -{ - "name": "Slack", - "key": "slack", - "iconUrl": "{BASE_URL}/apps/slack/assets/favicon.svg", - "docUrl": "https://automatisch.io/docs/slack", - "authDocUrl": "https://automatisch.io/docs/connections/slack", - "primaryColor": "2DAAE1", - "supportsConnections": true, - "baseUrl": "https://slack.com/api", - "fields": [ - { - "key": "accessToken", - "label": "Access Token", - "type": "string", - "required": true, - "readOnly": false, - "value": null, - "placeholder": null, - "description": "Access token of slack that Automatisch will connect to.", - "clickToCopy": false - } - ], - "authenticationSteps": [ - { - "step": 1, - "type": "mutation", - "name": "createConnection", - "arguments": [ - { - "name": "key", - "value": "{key}" - }, - { - "name": "formattedData", - "value": null, - "properties": [ - { - "name": "accessToken", - "value": "{fields.accessToken}" - } - ] - } - ] - }, - { - "step": 2, - "type": "mutation", - "name": "verifyConnection", - "arguments": [ - { - "name": "id", - "value": "{createConnection.id}" - } - ] - } - ], - "reconnectionSteps": [ - { - "step": 1, - "type": "mutation", - "name": "resetConnection", - "arguments": [ - { - "name": "id", - "value": "{connection.id}" - } - ] - }, - { - "step": 2, - "type": "mutation", - "name": "updateConnection", - "arguments": [ - { - "name": "id", - "value": "{connection.id}" - }, - { - "name": "formattedData", - "value": null, - "properties": [ - { - "name": "accessToken", - "value": "{fields.accessToken}" - } - ] - } - ] - }, - { - "step": 3, - "type": "mutation", - "name": "verifyConnection", - "arguments": [ - { - "name": "id", - "value": "{connection.id}" - } - ] - } - ], - "triggers": [ - { - "name": "New message posted to a channel", - "key": "newMessageToChannel", - "pollInterval": 15, - "description": "Triggers when a new message is posted to a channel", - "substeps": [ - { - "key": "chooseConnection", - "name": "Choose connection" - }, - { - "key": "chooseTrigger", - "name": "Set up a trigger", - "arguments": [ - { - "label": "Channel", - "key": "channel", - "type": "dropdown", - "required": true, - "variables": false, - "source": { - "type": "query", - "name": "getData", - "arguments": [ - { - "name": "key", - "value": "listChannels" - } - ] - } - }, - { - "label": "Trigger for Bot Messages?", - "key": "triggerForBotMessages", - "type": "dropdown", - "description": "Should this flow trigger for bot messages?", - "required": true, - "value": true, - "variables": false, - "options": [ - { - "label": "Yes", - "value": true - }, - { - "label": "No", - "value": false - } - ] - } - ] - }, - { - "key": "testStep", - "name": "Test trigger" - } - ] - } - ], - "actions": [ - { - "name": "Send a message to channel", - "key": "sendMessageToChannel", - "description": "Send a message to a specific channel you specify.", - "substeps": [ - { - "key": "chooseConnection", - "name": "Choose connection" - }, - { - "key": "setupAction", - "name": "Set up action", - "arguments": [ - { - "label": "Channel", - "key": "channel", - "type": "dropdown", - "required": true, - "description": "Pick a channel to send the message to.", - "variables": false, - "source": { - "type": "query", - "name": "getData", - "arguments": [ - { - "name": "key", - "value": "listChannels" - } - ] - } - }, - { - "label": "Message text", - "key": "message", - "type": "string", - "required": true, - "description": "The content of your new message.", - "variables": true - } - ] - }, - { - "key": "testStep", - "name": "Test action" - } - ] - }, - { - "name": "Find message", - "key": "findMessage", - "description": "Find a Slack message using the Slack Search feature.", - "substeps": [ - { - "key": "chooseConnection", - "name": "Choose connection" - }, - { - "key": "setupAction", - "name": "Set up action", - "arguments": [ - { - "label": "Search Query", - "key": "query", - "type": "string", - "required": true, - "description": "Search query to use for finding matching messages. See the Slack Search Documentation for more information on constructing a query.", - "variables": true - }, - { - "label": "Sort by", - "key": "sortBy", - "type": "dropdown", - "description": "Sort messages by their match strength or by their date. Default is score.", - "required": true, - "value": "score", - "variables": false, - "options": [ - { - "label": "Match strength", - "value": "score" - }, - { - "label": "Message date time", - "value": "timestamp" - } - ] - }, - { - "label": "Sort direction", - "key": "sortDirection", - "type": "dropdown", - "description": "Sort matching messages in ascending or descending order. Default is descending.", - "required": true, - "value": "desc", - "variables": false, - "options": [ - { - "label": "Descending (newest or best match first)", - "value": "desc" - }, - { - "label": "Ascending (oldest or worst match first)", - "value": "asc" - } - ] - } - ] - }, - { - "key": "testStep", - "name": "Test action" - } - ] - } - ] -} diff --git a/packages/backend/src/apps/slack/triggers.ts b/packages/backend/src/apps/slack/triggers.ts deleted file mode 100644 index 8e5862e8..00000000 --- a/packages/backend/src/apps/slack/triggers.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { IJSONObject } from '@automatisch/types'; -import NewMessageToChannel from './triggers/new-message-to-channel'; - -export default class Triggers { - newMessageToChannel: NewMessageToChannel; - - constructor(connectionData: IJSONObject, parameters: IJSONObject) { - this.newMessageToChannel = new NewMessageToChannel( - connectionData, - parameters - ); - } -} diff --git a/packages/backend/src/apps/slack/triggers/new-message-to-channel.ts b/packages/backend/src/apps/slack/triggers/new-message-to-channel.ts deleted file mode 100644 index af32c0d7..00000000 --- a/packages/backend/src/apps/slack/triggers/new-message-to-channel.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { IJSONObject } from '@automatisch/types'; -import axios, { AxiosInstance } from 'axios'; - -export default class NewMessageToChannel { - httpClient: AxiosInstance; - parameters: IJSONObject; - connectionData: IJSONObject; - BASE_URL = 'https://slack.com/api'; - - constructor(connectionData: IJSONObject, parameters: IJSONObject) { - this.httpClient = axios.create({ baseURL: this.BASE_URL }); - this.connectionData = connectionData; - this.parameters = parameters; - } - - async run() { - // TODO: Fix after webhook implementation. - } - - async testRun() { - const headers = { - Authorization: `Bearer ${this.connectionData.accessToken}`, - }; - - const params = { - channel: this.parameters.channel, - }; - - const response = await this.httpClient.get('/conversations.history', { - headers, - params, - }); - - let lastMessage; - - if (this.parameters.triggerForBotMessages) { - lastMessage = response.data.messages[0]; - } else { - lastMessage = response.data.messages.find( - (message: IJSONObject) => - !Object.prototype.hasOwnProperty.call(message, 'bot_id') - ); - } - - return [lastMessage]; - } -} diff --git a/packages/backend/src/apps/twitter/actions.ts b/packages/backend/src/apps/twitter/actions.ts deleted file mode 100644 index 2b3aa93d..00000000 --- a/packages/backend/src/apps/twitter/actions.ts +++ /dev/null @@ -1,12 +0,0 @@ -import TwitterClient from './client'; -import CreateTweet from './actions/create-tweet'; - -export default class Actions { - client: TwitterClient; - createTweet: CreateTweet; - - constructor(client: TwitterClient) { - this.client = client; - this.createTweet = new CreateTweet(client); - } -} diff --git a/packages/backend/src/apps/twitter/actions/create-tweet.ts b/packages/backend/src/apps/twitter/actions/create-tweet.ts deleted file mode 100644 index 482ae0b4..00000000 --- a/packages/backend/src/apps/twitter/actions/create-tweet.ts +++ /dev/null @@ -1,17 +0,0 @@ -import TwitterClient from '../client'; - -export default class CreateTweet { - client: TwitterClient; - - constructor(client: TwitterClient) { - this.client = client; - } - - async run() { - const tweet = await this.client.createTweet.run( - this.client.step.parameters.tweet as string - ); - - return tweet; - } -} diff --git a/packages/backend/src/apps/twitter/assets/favicon.svg b/packages/backend/src/apps/twitter/assets/favicon.svg deleted file mode 100644 index 752cdc8d..00000000 --- a/packages/backend/src/apps/twitter/assets/favicon.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/backend/src/apps/twitter/authentication.ts b/packages/backend/src/apps/twitter/authentication.ts deleted file mode 100644 index 5ddb83d7..00000000 --- a/packages/backend/src/apps/twitter/authentication.ts +++ /dev/null @@ -1,51 +0,0 @@ -import type { IAuthentication, IField } from '@automatisch/types'; -import { URLSearchParams } from 'url'; -import TwitterClient from './client'; - -export default class Authentication implements IAuthentication { - client: TwitterClient; - - constructor(client: TwitterClient) { - this.client = client; - } - - async createAuthData() { - const appFields = this.client.connection.appData.fields.find( - (field: IField) => field.key == 'oAuthRedirectUrl' - ); - const callbackUrl = appFields.value; - - const response = await this.client.oauthRequestToken.run(callbackUrl); - const responseData = Object.fromEntries(new URLSearchParams(response.data)); - - return { - url: `${TwitterClient.baseUrl}/oauth/authorize?oauth_token=${responseData.oauth_token}`, - accessToken: responseData.oauth_token, - accessSecret: responseData.oauth_token_secret, - }; - } - - async verifyCredentials() { - const response = await this.client.verifyAccessToken.run(); - const responseData = Object.fromEntries(new URLSearchParams(response.data)); - - return { - consumerKey: this.client.connection.formattedData.consumerKey as string, - consumerSecret: this.client.connection.formattedData - .consumerSecret as string, - accessToken: responseData.oauth_token, - accessSecret: responseData.oauth_token_secret, - userId: responseData.user_id, - screenName: responseData.screen_name, - }; - } - - async isStillVerified() { - try { - await this.client.getCurrentUser.run(); - return true; - } catch (error) { - return false; - } - } -} diff --git a/packages/backend/src/apps/twitter/client/endpoints/create-tweet.ts b/packages/backend/src/apps/twitter/client/endpoints/create-tweet.ts deleted file mode 100644 index 52eade01..00000000 --- a/packages/backend/src/apps/twitter/client/endpoints/create-tweet.ts +++ /dev/null @@ -1,40 +0,0 @@ -import TwitterClient from '../index'; - -export default class CreateTweet { - client: TwitterClient; - - constructor(client: TwitterClient) { - this.client = client; - } - - async run(text: string) { - try { - const token = { - key: this.client.connection.formattedData.accessToken as string, - secret: this.client.connection.formattedData.accessSecret as string, - }; - - const requestData = { - url: `${TwitterClient.baseUrl}/2/tweets`, - method: 'POST', - }; - - const authHeader = this.client.oauthClient.toHeader( - this.client.oauthClient.authorize(requestData, token) - ); - - const response = await this.client.httpClient.post( - `/2/tweets`, - { text }, - { headers: { ...authHeader } } - ); - - const tweet = response.data.data; - - return tweet; - } catch (error) { - const errorMessage = error.response.data.detail; - throw new Error(`Error occured while creating a tweet: ${errorMessage}`); - } - } -} diff --git a/packages/backend/src/apps/twitter/client/endpoints/get-current-user.ts b/packages/backend/src/apps/twitter/client/endpoints/get-current-user.ts deleted file mode 100644 index 4ccaa575..00000000 --- a/packages/backend/src/apps/twitter/client/endpoints/get-current-user.ts +++ /dev/null @@ -1,35 +0,0 @@ -import TwitterClient from '../index'; - -export default class GetCurrentUser { - client: TwitterClient; - - constructor(client: TwitterClient) { - this.client = client; - } - - async run() { - const token = { - key: this.client.connection.formattedData.accessToken as string, - secret: this.client.connection.formattedData.accessSecret as string, - }; - - const requestPath = '/2/users/me'; - - const requestData = { - url: `${TwitterClient.baseUrl}${requestPath}`, - method: 'GET', - }; - - const authHeader = this.client.oauthClient.toHeader( - this.client.oauthClient.authorize(requestData, token) - ); - - const response = await this.client.httpClient.get(requestPath, { - headers: { ...authHeader }, - }); - - const currentUser = response.data.data; - - return currentUser; - } -} diff --git a/packages/backend/src/apps/twitter/client/endpoints/get-user-by-username.ts b/packages/backend/src/apps/twitter/client/endpoints/get-user-by-username.ts deleted file mode 100644 index 23ac8b83..00000000 --- a/packages/backend/src/apps/twitter/client/endpoints/get-user-by-username.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { IJSONObject } from '@automatisch/types'; -import TwitterClient from '../index'; - -export default class GetUserByUsername { - client: TwitterClient; - - constructor(client: TwitterClient) { - this.client = client; - } - - async run(username: string) { - const token = { - key: this.client.connection.formattedData.accessToken as string, - secret: this.client.connection.formattedData.accessSecret as string, - }; - - const requestPath = `/2/users/by/username/${username}`; - - const requestData = { - url: `${TwitterClient.baseUrl}${requestPath}`, - method: 'GET', - }; - - const authHeader = this.client.oauthClient.toHeader( - this.client.oauthClient.authorize(requestData, token) - ); - - const response = await this.client.httpClient.get(requestPath, { - headers: { ...authHeader }, - }); - - if (response.data?.errors) { - const errorMessages = response.data.errors - .map((error: IJSONObject) => error.detail) - .join(' '); - - throw new Error( - `Error occured while fetching user data: ${errorMessages}` - ); - } - - const user = response.data.data; - return user; - } -} diff --git a/packages/backend/src/apps/twitter/client/endpoints/get-user-followers.ts b/packages/backend/src/apps/twitter/client/endpoints/get-user-followers.ts deleted file mode 100644 index 543a007f..00000000 --- a/packages/backend/src/apps/twitter/client/endpoints/get-user-followers.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { IJSONObject } from '@automatisch/types'; -import { URLSearchParams } from 'url'; -import TwitterClient from '../index'; -import omitBy from 'lodash/omitBy'; -import isEmpty from 'lodash/isEmpty'; - -export default class GetUserFollowers { - client: TwitterClient; - - constructor(client: TwitterClient) { - this.client = client; - } - - async run(userId: string, lastInternalId?: string) { - const token = { - key: this.client.connection.formattedData.accessToken as string, - secret: this.client.connection.formattedData.accessSecret as string, - }; - - let response; - const followers: IJSONObject[] = []; - - do { - const params: IJSONObject = { - pagination_token: response?.data?.meta?.next_token, - }; - - const queryParams = new URLSearchParams(omitBy(params, isEmpty)); - - const requestPath = `/2/users/${userId}/followers${ - queryParams.toString() ? `?${queryParams.toString()}` : '' - }`; - - const requestData = { - url: `${TwitterClient.baseUrl}${requestPath}`, - method: 'GET', - }; - - const authHeader = this.client.oauthClient.toHeader( - this.client.oauthClient.authorize(requestData, token) - ); - - response = await this.client.httpClient.get(requestPath, { - headers: { ...authHeader }, - }); - - if (response.data.meta.result_count > 0) { - response.data.data.forEach((tweet: IJSONObject) => { - if (!lastInternalId || Number(tweet.id) > Number(lastInternalId)) { - followers.push(tweet); - } else { - return; - } - }); - } - } while (response.data.meta.next_token && lastInternalId); - - if (response.data?.errors) { - const errorMessages = response.data.errors - .map((error: IJSONObject) => error.detail) - .join(' '); - - throw new Error( - `Error occured while fetching user data: ${errorMessages}` - ); - } - - return followers; - } -} diff --git a/packages/backend/src/apps/twitter/client/endpoints/get-user-tweets.ts b/packages/backend/src/apps/twitter/client/endpoints/get-user-tweets.ts deleted file mode 100644 index b6343ced..00000000 --- a/packages/backend/src/apps/twitter/client/endpoints/get-user-tweets.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { IJSONObject } from '@automatisch/types'; -import { URLSearchParams } from 'url'; -import TwitterClient from '../index'; -import omitBy from 'lodash/omitBy'; -import isEmpty from 'lodash/isEmpty'; - -export default class GetUserTweets { - client: TwitterClient; - - constructor(client: TwitterClient) { - this.client = client; - } - - async run(userId: string, lastInternalId?: string) { - const token = { - key: this.client.connection.formattedData.accessToken as string, - secret: this.client.connection.formattedData.accessSecret as string, - }; - - let response; - const tweets: IJSONObject[] = []; - - do { - const params: IJSONObject = { - since_id: lastInternalId, - pagination_token: response?.data?.meta?.next_token, - }; - - const queryParams = new URLSearchParams(omitBy(params, isEmpty)); - - const requestPath = `/2/users/${userId}/tweets${ - queryParams.toString() ? `?${queryParams.toString()}` : '' - }`; - - const requestData = { - url: `${TwitterClient.baseUrl}${requestPath}`, - method: 'GET', - }; - - const authHeader = this.client.oauthClient.toHeader( - this.client.oauthClient.authorize(requestData, token) - ); - - response = await this.client.httpClient.get(requestPath, { - headers: { ...authHeader }, - }); - - if (response.data.meta.result_count > 0) { - response.data.data.forEach((tweet: IJSONObject) => { - if (!lastInternalId || Number(tweet.id) > Number(lastInternalId)) { - tweets.push(tweet); - } else { - return; - } - }); - } - } while (response.data.meta.next_token && lastInternalId); - - if (response.data?.errors) { - const errorMessages = response.data.errors - .map((error: IJSONObject) => error.detail) - .join(' '); - - throw new Error( - `Error occured while fetching user data: ${errorMessages}` - ); - } - - return tweets; - } -} diff --git a/packages/backend/src/apps/twitter/client/endpoints/oauth-request-token.ts b/packages/backend/src/apps/twitter/client/endpoints/oauth-request-token.ts deleted file mode 100644 index 6ef9d899..00000000 --- a/packages/backend/src/apps/twitter/client/endpoints/oauth-request-token.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { IJSONObject } from '@automatisch/types'; -import TwitterClient from '../index'; - -export default class OAuthRequestToken { - client: TwitterClient; - - constructor(client: TwitterClient) { - this.client = client; - } - - async run(callbackUrl: string) { - try { - const requestData = { - url: `${TwitterClient.baseUrl}/oauth/request_token`, - method: 'POST', - data: { oauth_callback: callbackUrl }, - }; - - const authHeader = this.client.oauthClient.toHeader( - this.client.oauthClient.authorize(requestData) - ); - - const response = await this.client.httpClient.post( - `/oauth/request_token`, - null, - { - headers: { ...authHeader }, - } - ); - - return response; - } catch (error) { - const errorMessages = error.response.data.errors - .map((error: IJSONObject) => error.message) - .join(' '); - - throw new Error( - `Error occured while verifying credentials: ${errorMessages}` - ); - } - } -} diff --git a/packages/backend/src/apps/twitter/client/endpoints/search-tweets.ts b/packages/backend/src/apps/twitter/client/endpoints/search-tweets.ts deleted file mode 100644 index 89eddeba..00000000 --- a/packages/backend/src/apps/twitter/client/endpoints/search-tweets.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { IJSONObject } from '@automatisch/types'; -import { URLSearchParams } from 'url'; -import TwitterClient from '../index'; -import omitBy from 'lodash/omitBy'; -import isEmpty from 'lodash/isEmpty'; -import qs from 'qs'; - -export default class SearchTweets { - client: TwitterClient; - - constructor(client: TwitterClient) { - this.client = client; - } - - async run(searchTerm: string, lastInternalId?: string) { - const token = { - key: this.client.connection.formattedData.accessToken as string, - secret: this.client.connection.formattedData.accessSecret as string, - }; - - let response; - const tweets: { - data: IJSONObject[]; - error: IJSONObject | null; - } = { - data: [], - error: null, - }; - - do { - const params: IJSONObject = { - query: searchTerm, - since_id: lastInternalId, - pagination_token: response?.data?.meta?.next_token, - }; - - const queryParams = qs.stringify(omitBy(params, isEmpty)); - - const requestPath = `/2/tweets/search/recent${ - queryParams.toString() ? `?${queryParams.toString()}` : '' - }`; - - const requestData = { - url: `${TwitterClient.baseUrl}${requestPath}`, - method: 'GET', - }; - - const authHeader = this.client.oauthClient.toHeader( - this.client.oauthClient.authorize(requestData, token) - ); - - response = await this.client.httpClient.get(requestPath, { - headers: { ...authHeader }, - }); - - if (response.integrationError) { - tweets.error = response.integrationError; - return tweets; - } - - if (response.data.meta.result_count > 0) { - response.data.data.forEach((tweet: IJSONObject) => { - if (!lastInternalId || Number(tweet.id) > Number(lastInternalId)) { - tweets.data.push(tweet); - } else { - return; - } - }); - } - } while (response.data.meta.next_token && lastInternalId); - - return tweets; - } -} diff --git a/packages/backend/src/apps/twitter/client/endpoints/verify-access-token.ts b/packages/backend/src/apps/twitter/client/endpoints/verify-access-token.ts deleted file mode 100644 index 7c51a52f..00000000 --- a/packages/backend/src/apps/twitter/client/endpoints/verify-access-token.ts +++ /dev/null @@ -1,20 +0,0 @@ -import TwitterClient from '../index'; - -export default class VerifyAccessToken { - client: TwitterClient; - - constructor(client: TwitterClient) { - this.client = client; - } - - async run() { - try { - return await this.client.httpClient.post( - `/oauth/access_token?oauth_verifier=${this.client.connection.formattedData.oauthVerifier}&oauth_token=${this.client.connection.formattedData.accessToken}`, - null - ); - } catch (error) { - throw new Error(error.response.data); - } - } -} diff --git a/packages/backend/src/apps/twitter/client/index.ts b/packages/backend/src/apps/twitter/client/index.ts deleted file mode 100644 index 4cb899b5..00000000 --- a/packages/backend/src/apps/twitter/client/index.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { IFlow, IStep, IConnection } from '@automatisch/types'; -import OAuth from 'oauth-1.0a'; -import crypto from 'crypto'; -import createHttpClient, { IHttpClient } from '../../../helpers/http-client'; -import OAuthRequestToken from './endpoints/oauth-request-token'; -import VerifyAccessToken from './endpoints/verify-access-token'; -import GetCurrentUser from './endpoints/get-current-user'; -import GetUserByUsername from './endpoints/get-user-by-username'; -import GetUserTweets from './endpoints/get-user-tweets'; -import CreateTweet from './endpoints/create-tweet'; -import SearchTweets from './endpoints/search-tweets'; -import GetUserFollowers from './endpoints/get-user-followers'; - -export default class TwitterClient { - flow: IFlow; - step: IStep; - connection: IConnection; - oauthClient: OAuth; - httpClient: IHttpClient; - - oauthRequestToken: OAuthRequestToken; - verifyAccessToken: VerifyAccessToken; - getCurrentUser: GetCurrentUser; - getUserByUsername: GetUserByUsername; - getUserTweets: GetUserTweets; - createTweet: CreateTweet; - searchTweets: SearchTweets; - getUserFollowers: GetUserFollowers; - - static baseUrl = 'https://api.twitter.com'; - - constructor(connection: IConnection, flow?: IFlow, step?: IStep) { - this.connection = connection; - this.flow = flow; - this.step = step; - - this.httpClient = createHttpClient({ baseURL: TwitterClient.baseUrl }); - - const consumerData = { - key: this.connection.formattedData.consumerKey as string, - secret: this.connection.formattedData.consumerSecret as string, - }; - - this.oauthClient = new OAuth({ - consumer: consumerData, - signature_method: 'HMAC-SHA1', - hash_function(base_string, key) { - return crypto - .createHmac('sha1', key) - .update(base_string) - .digest('base64'); - }, - }); - - this.oauthRequestToken = new OAuthRequestToken(this); - this.verifyAccessToken = new VerifyAccessToken(this); - this.getCurrentUser = new GetCurrentUser(this); - this.getUserByUsername = new GetUserByUsername(this); - this.getUserTweets = new GetUserTweets(this); - this.createTweet = new CreateTweet(this); - this.searchTweets = new SearchTweets(this); - this.getUserFollowers = new GetUserFollowers(this); - } -} diff --git a/packages/backend/src/apps/twitter/index.d.ts b/packages/backend/src/apps/twitter/index.d.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/backend/src/apps/twitter/index.ts b/packages/backend/src/apps/twitter/index.ts deleted file mode 100644 index ee8e1005..00000000 --- a/packages/backend/src/apps/twitter/index.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { - IService, - IAuthentication, - IFlow, - IStep, - IConnection, -} from '@automatisch/types'; -import Authentication from './authentication'; -import Triggers from './triggers'; -import Actions from './actions'; -import TwitterClient from './client'; - -export default class Twitter implements IService { - client: TwitterClient; - - authenticationClient: IAuthentication; - triggers: Triggers; - actions: Actions; - - constructor(connection: IConnection, flow?: IFlow, step?: IStep) { - this.client = new TwitterClient(connection, flow, step); - - this.authenticationClient = new Authentication(this.client); - this.triggers = new Triggers(this.client); - this.actions = new Actions(this.client); - } -} diff --git a/packages/backend/src/apps/twitter/info.json b/packages/backend/src/apps/twitter/info.json deleted file mode 100644 index c268e290..00000000 --- a/packages/backend/src/apps/twitter/info.json +++ /dev/null @@ -1,339 +0,0 @@ -{ - "name": "Twitter", - "key": "twitter", - "iconUrl": "{BASE_URL}/apps/twitter/assets/favicon.svg", - "docUrl": "https://automatisch.io/docs/twitter", - "authDocUrl": "https://automatisch.io/docs/connections/twitter", - "primaryColor": "2DAAE1", - "supportsConnections": true, - "baseUrl": "https://api.twitter.com", - "fields": [ - { - "key": "oAuthRedirectUrl", - "label": "OAuth Redirect URL", - "type": "string", - "required": true, - "readOnly": true, - "value": "{WEB_APP_URL}/app/twitter/connections/add", - "placeholder": null, - "description": "When asked to input an OAuth callback or redirect URL in Twitter OAuth, enter the URL above.", - "clickToCopy": true - }, - { - "key": "consumerKey", - "label": "API Key", - "type": "string", - "required": true, - "readOnly": false, - "value": null, - "placeholder": null, - "description": null, - "clickToCopy": false - }, - { - "key": "consumerSecret", - "label": "API Secret", - "type": "string", - "required": true, - "readOnly": false, - "value": null, - "placeholder": null, - "description": null, - "clickToCopy": false - } - ], - "authenticationSteps": [ - { - "step": 1, - "type": "mutation", - "name": "createConnection", - "arguments": [ - { - "name": "key", - "value": "{key}" - }, - { - "name": "formattedData", - "value": null, - "properties": [ - { - "name": "consumerKey", - "value": "{fields.consumerKey}" - }, - { - "name": "consumerSecret", - "value": "{fields.consumerSecret}" - } - ] - } - ] - }, - { - "step": 2, - "type": "mutation", - "name": "createAuthData", - "arguments": [ - { - "name": "id", - "value": "{createConnection.id}" - } - ] - }, - { - "step": 3, - "type": "openWithPopup", - "name": "openAuthPopup", - "arguments": [ - { - "name": "url", - "value": "{createAuthData.url}" - } - ] - }, - { - "step": 4, - "type": "mutation", - "name": "updateConnection", - "arguments": [ - { - "name": "id", - "value": "{createConnection.id}" - }, - { - "name": "formattedData", - "value": null, - "properties": [ - { - "name": "oauthVerifier", - "value": "{openAuthPopup.oauth_verifier}" - } - ] - } - ] - }, - { - "step": 5, - "type": "mutation", - "name": "verifyConnection", - "arguments": [ - { - "name": "id", - "value": "{createConnection.id}" - } - ] - } - ], - "reconnectionSteps": [ - { - "step": 1, - "type": "mutation", - "name": "resetConnection", - "arguments": [ - { - "name": "id", - "value": "{connection.id}" - } - ] - }, - { - "step": 2, - "type": "mutation", - "name": "updateConnection", - "arguments": [ - { - "name": "id", - "value": "{connection.id}" - }, - { - "name": "formattedData", - "value": null, - "properties": [ - { - "name": "consumerKey", - "value": "{fields.consumerKey}" - }, - { - "name": "consumerSecret", - "value": "{fields.consumerSecret}" - } - ] - } - ] - }, - { - "step": 3, - "type": "mutation", - "name": "createAuthData", - "arguments": [ - { - "name": "id", - "value": "{connection.id}" - } - ] - }, - { - "step": 4, - "type": "openWithPopup", - "name": "openAuthPopup", - "arguments": [ - { - "name": "url", - "value": "{createAuthData.url}" - } - ] - }, - { - "step": 5, - "type": "mutation", - "name": "updateConnection", - "arguments": [ - { - "name": "id", - "value": "{connection.id}" - }, - { - "name": "formattedData", - "value": null, - "properties": [ - { - "name": "oauthVerifier", - "value": "{openAuthPopup.oauth_verifier}" - } - ] - } - ] - }, - { - "step": 6, - "type": "mutation", - "name": "verifyConnection", - "arguments": [ - { - "name": "id", - "value": "{connection.id}" - } - ] - } - ], - "triggers": [ - { - "name": "My Tweets", - "key": "myTweets", - "pollInterval": 15, - "description": "Will be triggered when you tweet something new.", - "substeps": [ - { - "key": "chooseConnection", - "name": "Choose connection" - }, - { - "key": "testStep", - "name": "Test trigger" - } - ] - }, - { - "name": "User Tweets", - "key": "userTweets", - "pollInterval": 15, - "description": "Will be triggered when a specific user tweet something new.", - "substeps": [ - { - "key": "chooseConnection", - "name": "Choose connection" - }, - { - "key": "chooseTrigger", - "name": "Set up a trigger", - "arguments": [ - { - "label": "Username", - "key": "username", - "type": "string", - "required": true - } - ] - }, - { - "key": "testStep", - "name": "Test trigger" - } - ] - }, - { - "name": "Search Tweets", - "key": "searchTweets", - "pollInterval": 15, - "description": "Will be triggered when any user tweet something containing a specific keyword, phrase, username or hashtag.", - "substeps": [ - { - "key": "chooseConnection", - "name": "Choose connection" - }, - { - "key": "chooseTrigger", - "name": "Set up a trigger", - "arguments": [ - { - "label": "Search Term", - "key": "searchTerm", - "type": "string", - "required": true - } - ] - }, - { - "key": "testStep", - "name": "Test trigger" - } - ] - }, - { - "name": "New follower of me", - "key": "myFollowers", - "pollInterval": 15, - "description": "Will be triggered when you have a new follower.", - "substeps": [ - { - "key": "chooseConnection", - "name": "Choose connection" - }, - { - "key": "testStep", - "name": "Test trigger" - } - ] - } - ], - "actions": [ - { - "name": "Create Tweet", - "key": "createTweet", - "description": "Will create a tweet.", - "substeps": [ - { - "key": "chooseConnection", - "name": "Choose connection" - }, - { - "key": "chooseAction", - "name": "Set up action", - "arguments": [ - { - "label": "Tweet body", - "key": "tweet", - "type": "string", - "required": true, - "description": "The content of your new tweet.", - "variables": true - } - ] - }, - { - "key": "testStep", - "name": "Test action" - } - ] - } - ] -} diff --git a/packages/backend/src/apps/twitter/triggers.ts b/packages/backend/src/apps/twitter/triggers.ts deleted file mode 100644 index 2d611581..00000000 --- a/packages/backend/src/apps/twitter/triggers.ts +++ /dev/null @@ -1,21 +0,0 @@ -import TwitterClient from './client'; -import UserTweets from './triggers/user-tweets'; -import SearchTweets from './triggers/search-tweets'; -import MyTweets from './triggers/my-tweets'; -import MyFollowers from './triggers/my-followers'; - -export default class Triggers { - client: TwitterClient; - userTweets: UserTweets; - searchTweets: SearchTweets; - myTweets: MyTweets; - myFollowers: MyFollowers; - - constructor(client: TwitterClient) { - this.client = client; - this.userTweets = new UserTweets(client); - this.searchTweets = new SearchTweets(client); - this.myTweets = new MyTweets(client); - this.myFollowers = new MyFollowers(client); - } -} diff --git a/packages/backend/src/apps/twitter/triggers/my-followers.ts b/packages/backend/src/apps/twitter/triggers/my-followers.ts deleted file mode 100644 index 8aaaba51..00000000 --- a/packages/backend/src/apps/twitter/triggers/my-followers.ts +++ /dev/null @@ -1,28 +0,0 @@ -import TwitterClient from '../client'; - -export default class MyFollowers { - client: TwitterClient; - - constructor(client: TwitterClient) { - this.client = client; - } - - async run(lastInternalId: string) { - return this.getFollowers(lastInternalId); - } - - async testRun() { - return this.getFollowers(); - } - - async getFollowers(lastInternalId?: string) { - const { username } = await this.client.getCurrentUser.run(); - const user = await this.client.getUserByUsername.run(username as string); - - const tweets = await this.client.getUserFollowers.run( - user.id, - lastInternalId - ); - return tweets; - } -} diff --git a/packages/backend/src/apps/twitter/triggers/my-tweets.ts b/packages/backend/src/apps/twitter/triggers/my-tweets.ts deleted file mode 100644 index 60b049b7..00000000 --- a/packages/backend/src/apps/twitter/triggers/my-tweets.ts +++ /dev/null @@ -1,25 +0,0 @@ -import TwitterClient from '../client'; - -export default class MyTweets { - client: TwitterClient; - - constructor(client: TwitterClient) { - this.client = client; - } - - async run() { - return this.getTweets(lastInternalId); - } - - async testRun() { - return this.getTweets(); - } - - async getTweets(lastInternalId?: string) { - const { username } = await this.client.getCurrentUser.run(); - const user = await this.client.getUserByUsername.run(username as string); - - const tweets = await this.client.getUserTweets.run(user.id, lastInternalId); - return tweets; - } -} diff --git a/packages/backend/src/apps/twitter/triggers/search-tweets.ts b/packages/backend/src/apps/twitter/triggers/search-tweets.ts deleted file mode 100644 index 44ccb5df..00000000 --- a/packages/backend/src/apps/twitter/triggers/search-tweets.ts +++ /dev/null @@ -1,26 +0,0 @@ -import TwitterClient from '../client'; - -export default class SearchTweets { - client: TwitterClient; - - constructor(client: TwitterClient) { - this.client = client; - } - - async run(lastInternalId: string) { - return this.getTweets(lastInternalId); - } - - async testRun() { - return this.getTweets(); - } - - async getTweets(lastInternalId?: string) { - const tweets = await this.client.searchTweets.run( - this.client.step.parameters.searchTerm as string, - lastInternalId - ); - - return tweets; - } -} diff --git a/packages/backend/src/apps/twitter/triggers/user-tweets.ts b/packages/backend/src/apps/twitter/triggers/user-tweets.ts deleted file mode 100644 index bd4919b0..00000000 --- a/packages/backend/src/apps/twitter/triggers/user-tweets.ts +++ /dev/null @@ -1,27 +0,0 @@ -import TwitterClient from '../client'; - -export default class UserTweets { - client: TwitterClient; - - constructor(client: TwitterClient) { - this.client = client; - } - - async run(lastInternalId: string) { - return this.getTweets(lastInternalId); - } - - async testRun() { - return this.getTweets(); - } - - async getTweets(lastInternalId?: string) { - const user = await this.client.getUserByUsername.run( - this.client.step.parameters.username as string - ); - - const tweets = await this.client.getUserTweets.run(user.id, lastInternalId); - - return tweets; - } -} diff --git a/packages/backend/src/apps/twitter2/auth/create-auth-data.ts b/packages/backend/src/apps/twitter2/auth/create-auth-data.ts index cb37e833..b8fe115f 100644 --- a/packages/backend/src/apps/twitter2/auth/create-auth-data.ts +++ b/packages/backend/src/apps/twitter2/auth/create-auth-data.ts @@ -4,7 +4,7 @@ import { URLSearchParams } from 'url'; export default async function createAuthData($: IGlobalVariable) { try { - const oauthRedirectUrlField = $.app.fields.find( + const oauthRedirectUrlField = $.app.auth.fields.find( (field: IField) => field.key == 'oAuthRedirectUrl' ); diff --git a/packages/backend/src/apps/twitter2/triggers/new-follower-of-me/my-followers.ts b/packages/backend/src/apps/twitter2/triggers/new-follower-of-me/my-followers.ts index 11665a5a..a4d0fa0e 100644 --- a/packages/backend/src/apps/twitter2/triggers/new-follower-of-me/my-followers.ts +++ b/packages/backend/src/apps/twitter2/triggers/new-follower-of-me/my-followers.ts @@ -1,17 +1,17 @@ -import { IGlobalVariable } from "@automatisch/types"; -import getCurrentUser from "../../common/get-current-user"; -import getUserByUsername from "../../common/get-user-by-username"; -import getUserFollowers from "../../common/get-user-followers"; +import { IGlobalVariable } from '@automatisch/types'; +import getCurrentUser from '../../common/get-current-user'; +import getUserByUsername from '../../common/get-user-by-username'; +import getUserFollowers from '../../common/get-user-followers'; -const myFollowers = async( $: IGlobalVariable, lastInternalId?: string) => { +const myFollowers = async ($: IGlobalVariable, lastInternalId?: string) => { const { username } = await getCurrentUser($); const user = await getUserByUsername($, username as string); const tweets = await getUserFollowers($, { userId: user.id, - lastInternalId + lastInternalId, }); return tweets; -}); +}; export default myFollowers; diff --git a/packages/backend/src/graphql/mutations/create-auth-data.ts b/packages/backend/src/graphql/mutations/create-auth-data.ts index 6d0972ad..c29444d3 100644 --- a/packages/backend/src/graphql/mutations/create-auth-data.ts +++ b/packages/backend/src/graphql/mutations/create-auth-data.ts @@ -27,7 +27,7 @@ const createAuthData = async ( const authInstance = (await import(`../../apps/${connection.key}2/auth`)) .default; - const app = App.findOneByKey(connection.key); + const app = await App.findOneByKey(connection.key); const $ = globalVariable(connection, app); await authInstance.createAuthData($); diff --git a/packages/backend/src/graphql/mutations/create-connection.ts b/packages/backend/src/graphql/mutations/create-connection.ts index ac84cd74..fcaf093d 100644 --- a/packages/backend/src/graphql/mutations/create-connection.ts +++ b/packages/backend/src/graphql/mutations/create-connection.ts @@ -13,7 +13,7 @@ const createConnection = async ( params: Params, context: Context ) => { - App.findOneByKey(params.input.key); + await App.findOneByKey(params.input.key); return await context.currentUser.$relatedQuery('connections').insert({ key: params.input.key, diff --git a/packages/backend/src/graphql/mutations/verify-connection.ts b/packages/backend/src/graphql/mutations/verify-connection.ts index 358afd3e..8cc02c78 100644 --- a/packages/backend/src/graphql/mutations/verify-connection.ts +++ b/packages/backend/src/graphql/mutations/verify-connection.ts @@ -20,7 +20,7 @@ const verifyConnection = async ( }) .throwIfNotFound(); - const app = App.findOneByKey(connection.key); + const app = await App.findOneByKey(connection.key); const authInstance = (await import(`../../apps/${connection.key}2/auth`)) .default; diff --git a/packages/backend/src/graphql/queries/get-app.ts b/packages/backend/src/graphql/queries/get-app.ts index 32479096..83f116d9 100644 --- a/packages/backend/src/graphql/queries/get-app.ts +++ b/packages/backend/src/graphql/queries/get-app.ts @@ -6,7 +6,7 @@ type Params = { }; const getApp = async (_parent: unknown, params: Params, context: Context) => { - const app = App.findOneByKey(params.key); + const app = await App.findOneByKey(params.key); if (context.currentUser) { const connections = await context.currentUser diff --git a/packages/backend/src/graphql/queries/get-apps.ts b/packages/backend/src/graphql/queries/get-apps.ts index 351fc394..d0928b44 100644 --- a/packages/backend/src/graphql/queries/get-apps.ts +++ b/packages/backend/src/graphql/queries/get-apps.ts @@ -6,8 +6,8 @@ type Params = { onlyWithTriggers: boolean; }; -const getApps = (_parent: unknown, params: Params) => { - const apps = App.findAll(params.name); +const getApps = async (_parent: unknown, params: Params) => { + const apps = await App.findAll(params.name); if (params.onlyWithTriggers) { return apps.filter((app: IApp) => app.triggers?.length); diff --git a/packages/backend/src/graphql/queries/get-connected-apps.ts b/packages/backend/src/graphql/queries/get-connected-apps.ts index ecf18847..a988e60c 100644 --- a/packages/backend/src/graphql/queries/get-connected-apps.ts +++ b/packages/backend/src/graphql/queries/get-connected-apps.ts @@ -11,7 +11,7 @@ const getConnectedApps = async ( params: Params, context: Context ) => { - let apps = App.findAll(params.name); + let apps = await App.findAll(params.name); const connections = await context.currentUser .$relatedQuery('connections') diff --git a/packages/backend/src/helpers/app-assets-handler.ts b/packages/backend/src/helpers/app-assets-handler.ts index dd85808b..bb8d38cf 100644 --- a/packages/backend/src/helpers/app-assets-handler.ts +++ b/packages/backend/src/helpers/app-assets-handler.ts @@ -2,10 +2,10 @@ import express, { Application } from 'express'; import App from '../models/app'; const appAssetsHandler = async (app: Application) => { - const appNames = App.list; + const appList = await App.findAll(); - appNames.forEach(appName => { - const svgPath = `${__dirname}/../apps/${appName}/assets/favicon.svg`; + appList.forEach((appItem) => { + const svgPath = `${__dirname}/../apps/${appItem.name}/assets/favicon.svg`; const staticFileHandlerOptions = { /** * Disabling fallthrough is important to respond with HTTP 404. @@ -15,11 +15,8 @@ const appAssetsHandler = async (app: Application) => { }; const staticFileHandler = express.static(svgPath, staticFileHandlerOptions); - app.use( - `/apps/${appName}/assets/favicon.svg`, - staticFileHandler - ) - }) -} + app.use(`/apps/${appItem.name}/assets/favicon.svg`, staticFileHandler); + }); +}; export default appAssetsHandler; diff --git a/packages/backend/src/helpers/app-info-converter.ts b/packages/backend/src/helpers/app-info-converter.ts index df736006..a58287d8 100644 --- a/packages/backend/src/helpers/app-info-converter.ts +++ b/packages/backend/src/helpers/app-info-converter.ts @@ -1,12 +1,20 @@ import type { IApp } from '@automatisch/types'; import appConfig from '../config/app'; -const appInfoConverter = (rawAppData: string) => { - let computedRawData = rawAppData.replace('{BASE_URL}', appConfig.baseUrl); - computedRawData = computedRawData.replace('{WEB_APP_URL}', appConfig.webAppUrl); +const appInfoConverter = (rawAppData: IApp) => { + const stringifiedRawAppData = JSON.stringify(rawAppData); - const computedJSONData: IApp = JSON.parse(computedRawData) + let computedRawData = stringifiedRawAppData.replace( + '{BASE_URL}', + appConfig.baseUrl + ); + computedRawData = computedRawData.replace( + '{WEB_APP_URL}', + appConfig.webAppUrl + ); + + const computedJSONData: IApp = JSON.parse(computedRawData); return computedJSONData; -} +}; export default appInfoConverter; diff --git a/packages/backend/src/helpers/get-app.ts b/packages/backend/src/helpers/get-app.ts new file mode 100644 index 00000000..c86ec73f --- /dev/null +++ b/packages/backend/src/helpers/get-app.ts @@ -0,0 +1,67 @@ +import fs from 'fs'; +import { join } from 'path'; +import { IApp } from '@automatisch/types'; + +const folderPath = join(__dirname, '../apps'); + +const getApp = async (appKey: string) => { + const appData: IApp = (await import(`../apps/${appKey}`)).default; + + let rawAuthData = (await import(`../apps/${appKey}/auth/index.ts`)).default; + + rawAuthData = Object.fromEntries( + Object.entries(rawAuthData).filter( + ([key]) => typeof rawAuthData[key] !== 'function' + ) + ); + + appData.auth = rawAuthData; + + appData.triggers = []; + + const triggersPath = join(folderPath, appKey, 'triggers'); + + if (fs.existsSync(triggersPath)) { + const triggersFolder = fs.readdirSync(join(folderPath, appKey, 'triggers')); + + for (const triggerName of triggersFolder) { + let rawTriggerData = ( + await import(`../apps/${appKey}/triggers/${triggerName}/index.ts`) + ).default; + + rawTriggerData = Object.fromEntries( + Object.entries(rawTriggerData).filter( + ([key]) => typeof rawTriggerData[key] !== 'function' + ) + ); + + appData.triggers.push(rawTriggerData); + } + } + + appData.actions = []; + + const actionsPath = join(folderPath, appKey, 'actions'); + + if (fs.existsSync(actionsPath)) { + const actionsFolder = fs.readdirSync(join(folderPath, appKey, 'actions')); + + for await (const actionName of actionsFolder) { + let rawActionData = ( + await import(`../apps/${appKey}/actions/${actionName}/index.ts`) + ).default; + + rawActionData = Object.fromEntries( + Object.entries(rawActionData).filter( + ([key]) => typeof rawActionData[key] !== 'function' + ) + ); + + appData.actions.push(rawActionData); + } + } + + return appData; +}; + +export default getApp; diff --git a/packages/backend/src/models/app.ts b/packages/backend/src/models/app.ts index 62151633..25ba834c 100644 --- a/packages/backend/src/models/app.ts +++ b/packages/backend/src/models/app.ts @@ -2,6 +2,7 @@ import fs from 'fs'; import { join } from 'path'; import { IApp } from '@automatisch/types'; import appInfoConverter from '../helpers/app-info-converter'; +import getApp from '../helpers/get-app'; class App { static folderPath = join(__dirname, '../apps'); @@ -9,30 +10,29 @@ class App { // Temporaryly restrict the apps we expose until // their actions/triggers are implemented! - static temporaryList = ['slack', 'twitter', 'scheduler']; + static temporaryList = ['slack2', 'twitter2']; + // static temporaryList = ['slack', 'twitter', 'scheduler']; - static findAll(name?: string): IApp[] { + static async findAll(name?: string): Promise { if (!name) - return this.temporaryList.map((name) => this.findOneByName(name)); + return Promise.all( + this.temporaryList.map(async (name) => await this.findOneByName(name)) + ); - return this.temporaryList - .filter((app) => app.includes(name.toLowerCase())) - .map((name) => this.findOneByName(name)); + return Promise.all( + this.temporaryList + .filter((app) => app.includes(name.toLowerCase())) + .map((name) => this.findOneByName(name)) + ); } - static findOneByName(name: string): IApp { - const rawAppData = fs.readFileSync( - this.folderPath + `/${name}/info.json`, - 'utf-8' - ); + static async findOneByName(name: string): Promise { + const rawAppData = await getApp(name.toLocaleLowerCase()); return appInfoConverter(rawAppData); } - static findOneByKey(key: string): IApp { - const rawAppData = fs.readFileSync( - this.folderPath + `/${key}/info.json`, - 'utf-8' - ); + static async findOneByKey(key: string): Promise { + const rawAppData = await getApp(key); return appInfoConverter(rawAppData); } } diff --git a/packages/backend/src/models/connection.ts b/packages/backend/src/models/connection.ts index c4d26513..34968a19 100644 --- a/packages/backend/src/models/connection.ts +++ b/packages/backend/src/models/connection.ts @@ -56,10 +56,6 @@ class Connection extends Base { }, }); - get appData() { - return App.findOneByKey(this.key); - } - encryptData(): void { if (!this.eligibleForEncryption()) return; diff --git a/packages/backend/src/models/step.ts b/packages/backend/src/models/step.ts index 70662981..7f121bb0 100644 --- a/packages/backend/src/models/step.ts +++ b/packages/backend/src/models/step.ts @@ -78,10 +78,6 @@ class Step extends Base { return `${appConfig.baseUrl}/apps/${this.appKey}/assets/favicon.svg`; } - get appData() { - return App.findOneByKey(this.appKey); - } - async $afterInsert(queryContext: QueryContext) { await super.$afterInsert(queryContext); Telemetry.stepCreated(this); diff --git a/packages/types/index.d.ts b/packages/types/index.d.ts index af658d4f..38b04952 100644 --- a/packages/types/index.d.ts +++ b/packages/types/index.d.ts @@ -158,14 +158,18 @@ export interface IApp { primaryColor: string; supportsConnections: boolean; baseUrl: string; + auth: IAuth; + connectionCount: number; + flowCount: number; + triggers: ITrigger[]; + actions: IAction[]; + connections: IConnection[]; +} + +export interface IAuth { fields: IField[]; authenticationSteps: IAuthenticationStep[]; reconnectionSteps: IAuthenticationStep[]; - connectionCount: number; - flowCount: number; - triggers: any[]; - actions: any[]; - connections: IConnection[]; } export interface IService { @@ -176,10 +180,23 @@ export interface IService { } export interface ITrigger { + name: string; + key: string, + pollInterval: number; + description: string; + substeps: ISubstep[]; run(startTime?: Date): Promise; testRun(startTime?: Date): Promise; } +export interface IAction { + name: string; + key: string, + description: string; + substeps: ISubstep[]; + run(startTime?: Date): Promise; +} + export interface IAuthentication { client: unknown; verifyCredentials(): Promise;