refactor: clean up github and rewrite its auth

This commit is contained in:
Ali BARIN
2022-10-16 19:49:45 +02:00
parent a69cd51dda
commit 314787f39c
37 changed files with 398 additions and 1756 deletions

View File

@@ -0,0 +1,21 @@
import { IField, IGlobalVariable } from '@automatisch/types';
import { URLSearchParams } from 'url';
export default async function createAuthData($: IGlobalVariable) {
const scopes = ['read:org', 'repo', 'user'];
const oauthRedirectUrlField = $.app.auth.fields.find(
(field: IField) => field.key == 'oAuthRedirectUrl'
);
const redirectUri = oauthRedirectUrlField.value as string;
const searchParams = new URLSearchParams({
client_id: $.auth.data.consumerKey as string,
redirect_uri: redirectUri,
scope: scopes.join(','),
});
const url = `${$.app.baseUrl}/login/oauth/authorize?${searchParams.toString()}`;
await $.auth.set({
url,
});
}

View File

@@ -0,0 +1,221 @@
import createAuthData from './create-auth-data';
import verifyCredentials from './verify-credentials';
import isStillVerified from './is-still-verified';
export default {
fields: [
{
key: 'oAuthRedirectUrl',
label: 'OAuth Redirect URL',
type: 'string',
required: true,
readOnly: true,
value: '{WEB_APP_URL}/app/github/connections/add',
placeholder: null,
description: 'When asked to input an OAuth callback or redirect URL in Github OAuth, enter the URL above.',
docUrl: 'https://automatisch.io/docs/github#oauth-redirect-url',
clickToCopy: true
},
{
key: 'consumerKey',
label: 'Client ID',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
docUrl: 'https://automatisch.io/docs/github#client-id',
clickToCopy: false
},
{
key: 'consumerSecret',
label: 'Client Secret',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
docUrl: 'https://automatisch.io/docs/github#client-secret',
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.code}'
}
]
}
]
},
{
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.code}'
}
]
}
]
},
{
step: 6,
type: 'mutation',
name: 'verifyConnection',
arguments: [
{
name: 'id',
value: '{connection.id}'
}
]
}
],
createAuthData,
verifyCredentials,
isStillVerified,
};

View File

@@ -0,0 +1,13 @@
import { IGlobalVariable } from '@automatisch/types';
import getCurrentUser from '../common/get-current-user';
const isStillVerified = async ($: IGlobalVariable) => {
try {
const user = await getCurrentUser($);
return !!user;
} catch (error) {
return false;
}
};
export default isStillVerified;

View File

@@ -0,0 +1,59 @@
import { IGlobalVariable } from '@automatisch/types';
import getCurrentUser from '../common/get-current-user';
async function getTokenInfo($: IGlobalVariable) {
const basicAuthToken = Buffer.from(
$.auth.data.consumerKey + ':' + $.auth.data.consumerSecret
).toString('base64');
const headers = {
Authorization: `Basic ${basicAuthToken}`,
};
const body = {
access_token: $.auth.data.accessToken,
};
return await $.http.post(
`${$.app.baseUrl}/applications/${$.auth.data.consumerKey}/token`,
body,
{ headers }
);
}
const verifyCredentials = async ($: IGlobalVariable) => {
try {
const response = await $.http.post(
`${$.app.baseUrl}/login/oauth/access_token`,
{
client_id: $.auth.data.consumerKey,
client_secret: $.auth.data.consumerSecret,
code: $.auth.data.oauthVerifier,
},
{
headers: {
Accept: 'application/json'
}
});
const data = response.data;
$.auth.data.accessToken = data.access_token;
const currentUser = await getCurrentUser($);
await $.auth.set({
consumerKey: $.auth.data.consumerKey,
consumerSecret: $.auth.data.consumerSecret,
accessToken: data.access_token,
scope: data.scope,
tokenType: data.token_type,
userId: currentUser.id,
screenName: currentUser.login,
});
} catch (error) {
throw new Error(error.response.data);
}
};
export default verifyCredentials;

View File

@@ -1,95 +0,0 @@
import type {
IAuthentication,
IApp,
IField,
IJSONObject,
} from '@automatisch/types';
import createHttpClient, { IHttpClient } from '../../helpers/http-client';
import { URLSearchParams } from 'url';
export default class Authentication implements IAuthentication {
appData: IApp;
connectionData: IJSONObject;
scopes: string[] = ['read:org', 'repo', 'user'];
client: IHttpClient;
constructor(appData: IApp, connectionData: IJSONObject) {
this.connectionData = connectionData;
this.appData = appData;
this.client = createHttpClient({ baseURL: 'https://github.com' });
}
get oauthRedirectUrl(): string {
return this.appData.fields.find(
(field: IField) => field.key == 'oAuthRedirectUrl'
).value;
}
async createAuthData(): Promise<{ url: string }> {
const searchParams = new URLSearchParams({
client_id: this.connectionData.consumerKey as string,
redirect_uri: this.oauthRedirectUrl,
scope: this.scopes.join(','),
});
const url = `https://github.com/login/oauth/authorize?${searchParams.toString()}`;
return {
url,
};
}
async verifyCredentials() {
const response = await this.client.post('/login/oauth/access_token', {
client_id: this.connectionData.consumerKey,
client_secret: this.connectionData.consumerSecret,
code: this.connectionData.oauthVerifier,
});
const data = Object.fromEntries(new URLSearchParams(response.data));
this.connectionData.accessToken = data.access_token;
const tokenInfo = await this.getTokenInfo();
return {
consumerKey: this.connectionData.consumerKey,
consumerSecret: this.connectionData.consumerSecret,
accessToken: data.access_token,
scope: data.scope,
tokenType: data.token_type,
userId: tokenInfo.data.user.id,
screenName: tokenInfo.data.user.login,
};
}
async getTokenInfo() {
const basicAuthToken = Buffer.from(
this.connectionData.consumerKey + ':' + this.connectionData.consumerSecret
).toString('base64');
const headers = {
Authorization: `Basic ${basicAuthToken}`,
};
const body = {
access_token: this.connectionData.accessToken,
};
return await this.client.post(
`https://api.github.com/applications/${this.connectionData.consumerKey}/token`,
body,
{ headers }
);
}
async isStillVerified() {
try {
await this.getTokenInfo();
return true;
} catch {
return false;
}
}
}

View File

@@ -0,0 +1,28 @@
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
import { Method } from 'axios';
type IGenereateRequestOptons = {
requestPath: string;
method: string;
data?: IJSONObject;
};
const generateRequest = async (
$: IGlobalVariable,
options: IGenereateRequestOptons
) => {
const { requestPath, method, data } = options;
const response = await $.http.request({
url: requestPath,
method: method as Method,
data,
headers: {
Authorization: `Bearer ${$.auth.data.accessToken}`
},
});
return response;
};
export default generateRequest;

View File

@@ -0,0 +1,14 @@
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
import generateRequest from './generate-request';
const getCurrentUser = async ($: IGlobalVariable): Promise<IJSONObject> => {
const response = await generateRequest($, {
requestPath: '/user',
method: 'GET',
});
const currentUser = response.data;
return currentUser;
};
export default getCurrentUser;

View File

@@ -0,0 +1,13 @@
type TRepoOwnerAndRepo = {
repoOwner: string;
repo: string;
}
export function getRepoOwnerAndRepo(repoFullName: string): TRepoOwnerAndRepo {
const [repoOwner, repo] = repoFullName.split('/');
return {
repoOwner,
repo
};
}

View File

@@ -1,16 +0,0 @@
import { IJSONObject } from '@automatisch/types';
import ListRepos from './data/list-repos';
import ListBranches from './data/list-branches';
import ListLabels from './data/list-labels';
export default class Data {
listRepos: ListRepos;
listBranches: ListBranches;
listLabels: ListLabels;
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
this.listRepos = new ListRepos(connectionData);
this.listBranches = new ListBranches(connectionData, parameters);
this.listLabels = new ListLabels(connectionData, parameters);
}
}

View File

@@ -1,36 +0,0 @@
import { Octokit } from 'octokit';
import type { IJSONObject } from '@automatisch/types';
import { assignOwnerAndRepo } from '../utils';
export default class ListBranches {
client?: Octokit;
repoOwner?: string;
repo?: string;
constructor(connectionData: IJSONObject, parameters?: IJSONObject) {
if (connectionData.accessToken) {
this.client = new Octokit({
auth: connectionData.accessToken as string,
});
}
assignOwnerAndRepo(this, parameters?.repo as string);
}
get options() {
return {
owner: this.repoOwner,
repo: this.repo,
};
}
async run() {
const branches = await this.client.paginate(this.client.rest.repos.listBranches, this.options);
return branches.map((branch) => ({
value: branch.name,
name: branch.name,
}));
}
}

View File

@@ -1,36 +0,0 @@
import { Octokit } from 'octokit';
import type { IJSONObject } from '@automatisch/types';
import { assignOwnerAndRepo } from '../utils';
export default class ListLabels {
client?: Octokit;
repoOwner?: string;
repo?: string;
constructor(connectionData: IJSONObject, parameters?: IJSONObject) {
if (connectionData.accessToken) {
this.client = new Octokit({
auth: connectionData.accessToken as string,
});
}
assignOwnerAndRepo(this, parameters?.repo as string);
}
get options() {
return {
owner: this.repoOwner,
repo: this.repo,
};
}
async run() {
const labels = await this.client.paginate(this.client.rest.issues.listLabelsForRepo, this.options);
return labels.map((label) => ({
value: label.name,
name: label.name,
}));
}
}

View File

@@ -1,23 +0,0 @@
import { Octokit } from 'octokit';
import type { IJSONObject } from '@automatisch/types';
export default class ListRepos {
client?: Octokit;
constructor(connectionData: IJSONObject) {
if (connectionData.accessToken) {
this.client = new Octokit({
auth: connectionData.accessToken as string,
});
}
}
async run() {
const repos = await this.client.paginate(this.client.rest.repos.listForAuthenticatedUser);
return repos.map((repo) => ({
value: repo.full_name,
name: repo.full_name,
}));
}
}

View File

@@ -1,25 +1,10 @@
import {
IService,
IAuthentication,
IApp,
IJSONObject,
} from '@automatisch/types';
import Authentication from './authentication';
import Triggers from './triggers';
import Data from './data';
export default class Github implements IService {
authenticationClient: IAuthentication;
triggers: Triggers;
data: Data;
constructor(
appData: IApp,
connectionData: IJSONObject,
parameters: IJSONObject
) {
this.authenticationClient = new Authentication(appData, connectionData);
this.data = new Data(connectionData, parameters);
this.triggers = new Triggers(connectionData, parameters);
}
}
export default {
name: 'Github',
key: 'github',
baseUrl: 'https://github.com',
apiBaseUrl: 'https://api.github.com',
iconUrl: '{BASE_URL}/apps/github/assets/favicon.svg',
authDocUrl: 'https://automatisch.io/docs/connections/github',
primaryColor: '000000',
supportsConnections: true,
};

View File

@@ -1,747 +0,0 @@
{
"name": "Github",
"key": "github",
"iconUrl": "{BASE_URL}/apps/github/assets/favicon.svg",
"docUrl": "https://automatisch.io/docs/github",
"primaryColor": "000000",
"supportsConnections": true,
"fields": [
{
"key": "oAuthRedirectUrl",
"label": "OAuth Redirect URL",
"type": "string",
"required": true,
"readOnly": true,
"value": "{WEB_APP_URL}/app/github/connections/add",
"placeholder": null,
"description": "When asked to input an OAuth callback or redirect URL in Github OAuth, enter the URL above.",
"docUrl": "https://automatisch.io/docs/github#oauth-redirect-url",
"clickToCopy": true
},
{
"key": "consumerKey",
"label": "Client ID",
"type": "string",
"required": true,
"readOnly": false,
"value": null,
"placeholder": null,
"description": null,
"docUrl": "https://automatisch.io/docs/github#client-id",
"clickToCopy": false
},
{
"key": "consumerSecret",
"label": "Client Secret",
"type": "string",
"required": true,
"readOnly": false,
"value": null,
"placeholder": null,
"description": null,
"docUrl": "https://automatisch.io/docs/github#client-secret",
"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.code}"
}
]
}
]
},
{
"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.code}"
}
]
}
]
},
{
"step": 6,
"type": "mutation",
"name": "verifyConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
}
]
}
],
"triggers": [
{
"name": "New repository",
"key": "newRepository",
"description": "Triggers when a new repository is created",
"substeps": [
{
"key": "chooseConnection",
"name": "Choose connection"
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New organization",
"key": "newOrganization",
"description": "Triggers when a new organization is created",
"substeps": [
{
"key": "chooseConnection",
"name": "Choose connection"
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New branch",
"key": "newBranch",
"description": "Triggers when a new branch is created",
"substeps": [
{
"key": "chooseConnection",
"name": "Choose connection"
},
{
"key": "chooseTrigger",
"name": "Set up a trigger",
"arguments": [
{
"label": "Repo",
"key": "repo",
"type": "dropdown",
"required": true,
"variables": false,
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listRepos"
}
]
}
}
]
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New notification",
"key": "newNotification",
"description": "Triggers when a new notification is created",
"substeps": [
{
"key": "chooseConnection",
"name": "Choose connection"
},
{
"key": "chooseTrigger",
"name": "Set up a trigger",
"arguments": [
{
"label": "Repo",
"key": "repo",
"type": "dropdown",
"required": false,
"variables": false,
"description": "If blank, we will retrieve all notifications.",
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listRepos"
}
]
}
}
]
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New pull request",
"key": "newPullRequest",
"description": "Triggers when a new pull request is created",
"substeps": [
{
"key": "chooseConnection",
"name": "Choose connection"
},
{
"key": "chooseTrigger",
"name": "Set up a trigger",
"arguments": [
{
"label": "Repo",
"key": "repo",
"type": "dropdown",
"required": true,
"variables": false,
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listRepos"
}
]
}
}
]
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New watcher",
"key": "newWatcher",
"description": "Triggers when a new watcher is added to a repo",
"substeps": [
{
"key": "chooseConnection",
"name": "Choose connection"
},
{
"key": "chooseTrigger",
"name": "Set up a trigger",
"arguments": [
{
"label": "Repo",
"key": "repo",
"type": "dropdown",
"required": true,
"variables": false,
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listRepos"
}
]
}
}
]
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New milestone",
"key": "newMilestone",
"description": "Triggers when a new milestone is created",
"substeps": [
{
"key": "chooseConnection",
"name": "Choose connection"
},
{
"key": "chooseTrigger",
"name": "Set up a trigger",
"arguments": [
{
"label": "Repo",
"key": "repo",
"type": "dropdown",
"required": true,
"variables": false,
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listRepos"
}
]
}
}
]
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New commit comment",
"key": "newCommitComment",
"description": "Triggers when a new commit comment is created",
"substeps": [
{
"key": "chooseConnection",
"name": "Choose connection"
},
{
"key": "chooseTrigger",
"name": "Set up a trigger",
"arguments": [
{
"label": "Repo",
"key": "repo",
"type": "dropdown",
"required": true,
"variables": false,
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listRepos"
}
]
}
}
]
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New label",
"key": "newLabel",
"description": "Triggers when a new label is created",
"substeps": [
{
"key": "chooseConnection",
"name": "Choose connection"
},
{
"key": "chooseTrigger",
"name": "Set up a trigger",
"arguments": [
{
"label": "Repo",
"key": "repo",
"type": "dropdown",
"required": true,
"variables": false,
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listRepos"
}
]
}
}
]
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New collaborator",
"key": "newCollaborator",
"description": "Triggers when a new collaborator is added to a repo",
"substeps": [
{
"key": "chooseConnection",
"name": "Choose connection"
},
{
"key": "chooseTrigger",
"name": "Set up a trigger",
"arguments": [
{
"label": "Repo",
"key": "repo",
"type": "dropdown",
"required": true,
"variables": false,
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listRepos"
}
]
}
}
]
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New release",
"key": "newRelease",
"description": "Triggers when a new release is created",
"substeps": [
{
"key": "chooseConnection",
"name": "Choose connection"
},
{
"key": "chooseTrigger",
"name": "Set up a trigger",
"arguments": [
{
"label": "Repo",
"key": "repo",
"type": "dropdown",
"required": true,
"variables": false,
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listRepos"
}
]
}
}
]
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New commit",
"key": "newCommit",
"description": "Triggers when a new commit is created",
"substeps": [
{
"key": "chooseConnection",
"name": "Choose connection"
},
{
"key": "chooseTrigger",
"name": "Set up a trigger",
"arguments": [
{
"label": "Repo",
"key": "repo",
"type": "dropdown",
"required": true,
"variables": false,
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listRepos"
}
]
}
},
{
"label": "Head",
"key": "head",
"type": "dropdown",
"description": "Branch to pull commits from. If unspecified, will use the repository's default branch (usually main or develop).",
"required": false,
"variables": false,
"dependsOn": ["parameters.repo"],
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listBranches"
},
{
"name": "parameters.repo",
"value": "{parameters.repo}"
}
]
}
}
]
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New issue",
"key": "newIssue",
"description": "Triggers when a new issue is created",
"substeps": [
{
"key": "chooseConnection",
"name": "Choose connection"
},
{
"key": "chooseTrigger",
"name": "Set up a trigger",
"arguments": [
{
"label": "Repo",
"key": "repo",
"type": "dropdown",
"required": false,
"variables": false,
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listRepos"
}
]
}
},
{
"label": "Which types of issues should this trigger on?",
"key": "issueType",
"type": "dropdown",
"description": "Defaults to any issue you can see.",
"required": true,
"variables": false,
"value": "all",
"options": [
{
"label": "Any issue you can see",
"value": "all"
},
{
"label": "Only issues assigned to you",
"value": "assigned"
},
{
"label": "Only issues created by you",
"value": "created"
},
{
"label": "Only issues you're mentioned in",
"value": "mentioned"
},
{
"label": "Only issues you're subscribed to",
"value": "subscribed"
}
]
},
{
"label": "Label",
"key": "label",
"type": "dropdown",
"description": "Only trigger on issues when this label is added.",
"required": false,
"variables": false,
"dependsOn": ["parameters.repo"],
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listLabels"
},
{
"name": "parameters.repo",
"value": "{parameters.repo}"
}
]
}
}
]
},
{
"key": "testStep",
"name": "Test trigger"
}
]
}
]
}

View File

@@ -1,46 +0,0 @@
import { IJSONObject } from '@automatisch/types';
import NewRepository from './triggers/new-repository';
import NewOrganization from './triggers/new-organization';
import NewBranch from './triggers/new-branch';
import NewNotification from './triggers/new-notification';
import NewPullRequest from './triggers/new-pull-request';
import NewWatcher from './triggers/new-watcher';
import NewMilestone from './triggers/new-milestone';
import NewCommit from './triggers/new-commit';
import NewCommitComment from './triggers/new-commit-comment';
import NewLabel from './triggers/new-label';
import NewCollaborator from './triggers/new-collaborator';
import NewRelease from './triggers/new-release';
import NewIssue from './triggers/new-issue';
export default class Triggers {
newRepository: NewRepository;
newOrganization: NewOrganization;
newBranch: NewBranch;
newNotification: NewNotification;
newPullRequest: NewPullRequest;
newWatcher: NewWatcher;
newMilestone: NewMilestone;
newCommit: NewCommit;
newCommitComment: NewCommitComment;
newLabel: NewLabel;
newCollaborator: NewCollaborator;
newRelease: NewRelease;
newIssue: NewIssue;
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
this.newRepository = new NewRepository(connectionData);
this.newOrganization = new NewOrganization(connectionData);
this.newBranch = new NewBranch(connectionData, parameters);
this.newNotification = new NewNotification(connectionData, parameters);
this.newPullRequest = new NewPullRequest(connectionData, parameters);
this.newWatcher = new NewWatcher(connectionData, parameters);
this.newMilestone = new NewMilestone(connectionData, parameters);
this.newCommit = new NewCommit(connectionData, parameters);
this.newCommitComment = new NewCommitComment(connectionData, parameters);
this.newLabel = new NewLabel(connectionData, parameters);
this.newCollaborator = new NewCollaborator(connectionData, parameters);
this.newRelease = new NewRelease(connectionData, parameters);
this.newIssue = new NewIssue(connectionData, parameters);
}
}

View File

@@ -1,42 +0,0 @@
import { Octokit } from 'octokit';
import { IJSONObject } from '@automatisch/types';
import { assignOwnerAndRepo } from '../utils';
export default class NewBranch {
client?: Octokit;
repoOwner?: string;
repo?: string;
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
if (connectionData.accessToken) {
this.client = new Octokit({
auth: connectionData.accessToken as string,
});
}
assignOwnerAndRepo(this, parameters?.repo as string);
}
get options() {
return {
owner: this.repoOwner,
repo: this.repo,
};
}
async run() {
// TODO: implement pagination on undated entires
return await this.client.paginate(this.client.rest.repos.listBranches, this.options);
}
async testRun() {
const options = {
...this.options,
per_page: 1,
};
const { data: branches } = await this.client.rest.repos.listBranches(options);
return branches;
}
}

View File

@@ -1,44 +0,0 @@
import { Octokit } from 'octokit';
import { IJSONObject } from '@automatisch/types';
import { assignOwnerAndRepo } from '../utils';
export default class NewCollaborator {
client?: Octokit;
repoOwner?: string;
repo?: string;
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
if (connectionData.accessToken) {
this.client = new Octokit({
auth: connectionData.accessToken as string,
});
}
assignOwnerAndRepo(this, parameters?.repo as string);
}
get options() {
return {
owner: this.repoOwner,
repo: this.repo,
};
}
async run() {
// TODO: implement pagination on undated entries
return await this.client.paginate(
this.client.rest.repos.listCollaborators,
this.options
);
}
async testRun() {
const options = {
...this.options,
per_page: 1,
};
return (await this.client.rest.repos.listCollaborators(options)).data;
}
}

View File

@@ -1,59 +0,0 @@
import { Octokit } from 'octokit';
import { DateTime } from 'luxon';
import { IJSONObject } from '@automatisch/types';
import { assignOwnerAndRepo } from '../utils';
export default class NewCommitComment {
client?: Octokit;
repoOwner?: string;
repo?: string;
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
if (connectionData.accessToken) {
this.client = new Octokit({
auth: connectionData.accessToken as string,
});
}
assignOwnerAndRepo(this, parameters?.repo as string);
}
get options() {
return {
owner: this.repoOwner,
repo: this.repo,
};
}
async run(startTime: Date) {
const iterator = await this.client.paginate.iterator(this.client.rest.repos.listCommitCommentsForRepo, this.options);
const newCommitComments = [];
const startTimeDateObject = DateTime.fromJSDate(startTime);
commitCommentIterator:
for await (const { data: commitComments } of iterator) {
for (const commitComment of commitComments) {
const createdAtDateObject = DateTime.fromISO(commitComment.created_at);
if (createdAtDateObject < startTimeDateObject) {
break commitCommentIterator;
}
newCommitComments.push(commitComment);
}
}
return newCommitComments;
}
async testRun() {
const options = {
...this.options,
per_page: 1,
};
return (await this.client.rest.repos.listCommitCommentsForRepo(options)).data;
}
}

View File

@@ -1,59 +0,0 @@
import { Octokit } from 'octokit';
import { DateTime } from 'luxon';
import { IJSONObject } from '@automatisch/types';
import { assignOwnerAndRepo } from '../utils';
export default class NewCommit {
client?: Octokit;
repoOwner?: string;
repo?: string;
head?: string;
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
if (connectionData.accessToken) {
this.client = new Octokit({
auth: connectionData.accessToken as string,
});
}
if (parameters?.head) {
this.head = parameters.head as string;
}
assignOwnerAndRepo(this, parameters?.repo as string);
}
get options() {
const options = {
owner: this.repoOwner,
repo: this.repo,
};
if (this.head) {
return {
...options,
sha: this.head,
};
}
return options;
}
async run(startTime: Date) {
const options = {
...this.options,
since: DateTime.fromJSDate(startTime).toISO(),
};
return await this.client.paginate(this.client.rest.repos.listCommits, options);
}
async testRun() {
const options = {
...this.options,
per_page: 1,
};
return (await this.client.rest.repos.listCommits(options)).data;
}
}

View File

@@ -1,88 +0,0 @@
import { Octokit } from 'octokit';
import { DateTime } from 'luxon';
import { IJSONObject } from '@automatisch/types';
import { assignOwnerAndRepo } from '../utils';
export default class NewIssue {
client?: Octokit;
connectionData?: IJSONObject;
repoOwner?: string;
repo?: string;
hasRepo?: boolean;
label?: string;
issueType?: string;
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
if (connectionData.accessToken) {
this.client = new Octokit({
auth: connectionData.accessToken as string,
});
}
assignOwnerAndRepo(this, parameters?.repo as string);
}
get options() {
return {
labels: this.label,
}
}
async listRepoIssues(options = {}, paginate = false) {
const listRepoIssues = this.client.rest.issues.listForRepo;
const extendedOptions = {
...this.options,
repo: this.repo,
owner: this.repoOwner,
filter: this.issueType,
...options,
};
if (paginate) {
return await this.client.paginate(listRepoIssues, extendedOptions);
}
return (await listRepoIssues(extendedOptions)).data;
}
async listIssues(options = {}, paginate = false) {
const listIssues = this.client.rest.issues.listForAuthenticatedUser;
const extendedOptions = {
...this.options,
...options,
};
if (paginate) {
return await this.client.paginate(listIssues, extendedOptions);
}
return (await listIssues(extendedOptions)).data;
}
async run(startTime: Date) {
const options = {
since: DateTime.fromJSDate(startTime).toISO(),
};
if (this.hasRepo) {
return await this.listRepoIssues(options, true);
}
return await this.listIssues(options, true);
}
async testRun() {
const options = {
per_page: 1,
};
if (this.hasRepo) {
return await this.listRepoIssues(options, false);
}
return await this.listIssues(options, false);
}
}

View File

@@ -1,44 +0,0 @@
import { Octokit } from 'octokit';
import { IJSONObject } from '@automatisch/types';
import { assignOwnerAndRepo } from '../utils';
export default class NewLabel {
client?: Octokit;
repoOwner?: string;
repo?: string;
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
if (connectionData.accessToken) {
this.client = new Octokit({
auth: connectionData.accessToken as string,
});
}
assignOwnerAndRepo(this, parameters?.repo as string);
}
get options() {
return {
owner: this.repoOwner,
repo: this.repo,
};
}
async run() {
// TODO: implement pagination on undated entires
return await this.client.paginate(
this.client.rest.issues.listLabelsForRepo,
this.options
);
}
async testRun() {
const options = {
...this.options,
per_page: 1,
};
return (await this.client.rest.issues.listLabelsForRepo(options)).data;
}
}

View File

@@ -1,60 +0,0 @@
import { Octokit } from 'octokit';
import { DateTime } from 'luxon';
import { IJSONObject } from '@automatisch/types';
import { assignOwnerAndRepo } from '../utils';
export default class NewMilestone {
client?: Octokit;
repoOwner?: string;
repo?: string;
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
if (connectionData.accessToken) {
this.client = new Octokit({
auth: connectionData.accessToken as string,
});
}
assignOwnerAndRepo(this, parameters?.repo as string);
}
get options() {
return {
owner: this.repoOwner,
repo: this.repo,
state: 'open' as const,
};
}
async run(startTime: Date) {
const iterator = await this.client.paginate.iterator(this.client.rest.issues.listMilestones, this.options);
const newMilestones = [];
const startTimeDateObject = DateTime.fromJSDate(startTime);
milestoneIterator:
for await (const { data: milestones } of iterator) {
for (const milestone of milestones) {
const createdAtDateObject = DateTime.fromISO(milestone.created_at);
if (createdAtDateObject < startTimeDateObject) {
break milestoneIterator;
}
newMilestones.push(milestone);
}
}
return newMilestones;
}
async testRun() {
const options = {
...this.options,
per_page: 1,
};
return (await this.client.rest.issues.listMilestones(options)).data;
}
}

View File

@@ -1,83 +0,0 @@
import { Octokit } from 'octokit';
import { DateTime } from 'luxon';
import { IJSONObject } from '@automatisch/types';
import { assignOwnerAndRepo } from '../utils';
export default class NewNotification {
client?: Octokit;
connectionData?: IJSONObject;
repoOwner?: string;
repo?: string;
hasRepo?: boolean;
baseOptions = {
all: true,
participating: false,
};
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
if (connectionData.accessToken) {
this.client = new Octokit({
auth: connectionData.accessToken as string,
});
}
assignOwnerAndRepo(this, parameters?.repo as string);
}
async listRepoNotifications(options = {}, paginate = false) {
const listRepoNotifications = this.client.rest.activity.listRepoNotificationsForAuthenticatedUser;
const extendedOptions = {
...this.baseOptions,
repo: this.repo,
owner: this.repoOwner,
...options,
};
if (paginate) {
return await this.client.paginate(listRepoNotifications, extendedOptions);
}
return (await listRepoNotifications(extendedOptions)).data;
}
async listNotifications(options = {}, paginate = false) {
const listNotifications = this.client.rest.activity.listNotificationsForAuthenticatedUser;
const extendedOptions = {
...this.baseOptions,
...options,
};
if (paginate) {
return await this.client.paginate(listNotifications, extendedOptions);
}
return (await listNotifications(extendedOptions)).data;
}
async run(startTime: Date) {
const options = {
since: DateTime.fromJSDate(startTime).toISO(),
};
if (this.hasRepo) {
return await this.listRepoNotifications(options, true);
}
return await this.listNotifications(options, true);
}
async testRun() {
const options = {
per_page: 1,
};
if (this.hasRepo) {
return await this.listRepoNotifications(options, false);
}
return await this.listNotifications(options, false);
}
}

View File

@@ -1,32 +0,0 @@
import { Octokit } from 'octokit';
import { IJSONObject } from '@automatisch/types';
export default class NewOrganization {
client?: Octokit;
baseOptions = {
per_page: 100,
};
constructor(connectionData: IJSONObject) {
if (
connectionData.consumerKey &&
connectionData.consumerSecret &&
connectionData.accessToken
) {
this.client = new Octokit({
auth: connectionData.accessToken as string,
});
}
}
async run() {
// TODO: implement pagination on undated entires
return await this.client.paginate(this.client.rest.orgs.listForAuthenticatedUser);
}
async testRun() {
const { data: orgs } = await this.client.rest.orgs.listForAuthenticatedUser({ per_page: 1 });
return orgs;
}
}

View File

@@ -1,63 +0,0 @@
import { Octokit } from 'octokit';
import { DateTime } from 'luxon';
import { IJSONObject } from '@automatisch/types';
import { assignOwnerAndRepo } from '../utils';
export default class NewPullRequest {
client?: Octokit;
repoOwner?: string;
repo?: string;
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
if (connectionData.accessToken) {
this.client = new Octokit({
auth: connectionData.accessToken as string,
});
}
assignOwnerAndRepo(this, parameters?.repo as string);
}
get options() {
return {
owner: this.repoOwner,
repo: this.repo,
sort: 'created' as const,
direction: 'desc' as const,
};
}
async run(startTime: Date) {
const iterator = await this.client.paginate.iterator(
this.client.rest.pulls.list,
this.options
);
const newPullRequests = [];
const startTimeDateObject = DateTime.fromJSDate(startTime);
pullRequestIterator: for await (const { data: pullRequests } of iterator) {
for (const pullRequest of pullRequests) {
const createdAtDateObject = DateTime.fromISO(pullRequest.created_at);
if (createdAtDateObject < startTimeDateObject) {
break pullRequestIterator;
}
newPullRequests.push(pullRequest);
}
}
return newPullRequests;
}
async testRun() {
const options = {
...this.options,
per_page: 1,
};
return (await this.client.rest.pulls.list(options)).data;
}
}

View File

@@ -1,59 +0,0 @@
import { Octokit } from 'octokit';
import { DateTime } from 'luxon';
import { IJSONObject } from '@automatisch/types';
import { assignOwnerAndRepo } from '../utils';
export default class NewRelease {
client?: Octokit;
repoOwner?: string;
repo?: string;
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
if (connectionData.accessToken) {
this.client = new Octokit({
auth: connectionData.accessToken as string,
});
}
assignOwnerAndRepo(this, parameters?.repo as string);
}
get options() {
return {
owner: this.repoOwner,
repo: this.repo,
};
}
async run(startTime: Date) {
const iterator = await this.client.paginate.iterator(this.client.rest.repos.listReleases, this.options);
const newReleases = [];
const startTimeDateObject = DateTime.fromJSDate(startTime);
releaseIterator:
for await (const { data: releases } of iterator) {
for (const release of releases) {
const createdAtDateObject = DateTime.fromISO(release.created_at);
if (createdAtDateObject < startTimeDateObject) {
break releaseIterator;
}
newReleases.push(release);
}
}
return newReleases;
}
async testRun() {
const options = {
...this.options,
per_page: 1,
};
return (await this.client.rest.repos.listReleases(options)).data;
}
}

View File

@@ -1,32 +0,0 @@
import { Octokit } from 'octokit';
import { DateTime } from 'luxon';
import { IJSONObject } from '@automatisch/types';
export default class NewRepository {
client?: Octokit;
connectionData?: IJSONObject;
constructor(connectionData: IJSONObject) {
if (connectionData.accessToken) {
this.client = new Octokit({
auth: connectionData.accessToken as string,
});
}
}
async run(startTime: Date) {
const options = {
since: DateTime.fromJSDate(startTime).toISO(),
};
return await this.client.paginate(this.client.rest.repos.listForAuthenticatedUser, options);
}
async testRun() {
const options = {
per_page: 1,
};
const { data: repos } = await this.client.rest.repos.listForAuthenticatedUser(options);
return repos;
}
}

View File

@@ -1,45 +0,0 @@
import { Octokit } from 'octokit';
import { IJSONObject } from '@automatisch/types';
import { assignOwnerAndRepo } from '../utils';
export default class NewWatcher {
client?: Octokit;
repoOwner?: string;
repo?: string;
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
if (connectionData.accessToken) {
this.client = new Octokit({
auth: connectionData.accessToken as string,
});
}
assignOwnerAndRepo(this, parameters?.repo as string);
}
get options() {
return {
owner: this.repoOwner,
repo: this.repo,
};
}
async run() {
// TODO: implement pagination on undated entries
return await this.client.paginate(
this.client.rest.activity.listWatchersForRepo,
this.options
);
}
async testRun() {
return await this.run();
const options = {
...this.options,
per_page: 1,
};
return (await this.client.rest.activity.listWatchersForRepo(options)).data;
}
}

View File

@@ -1,10 +0,0 @@
export function assignOwnerAndRepo<T extends { repoOwner?: string; repo?: string; hasRepo?: boolean; }>(object: T, repoFullName: string): T {
if (object && repoFullName) {
const [repoOwner, repo] = repoFullName.split('/');
object.repoOwner = repoOwner;
object.repo = repo;
object.hasRepo = true;
}
return object;
}

View File

@@ -3,8 +3,8 @@ import getCurrentUser from '../common/get-current-user';
const isStillVerified = async ($: IGlobalVariable) => {
try {
await getCurrentUser($);
return true;
const user = await getCurrentUser($);
return !!user;
} catch (error) {
return false;
}

View File

@@ -4,5 +4,6 @@ export default {
iconUrl: '{BASE_URL}/apps/twitter/assets/favicon.svg',
authDocUrl: 'https://automatisch.io/docs/connections/twitter',
supportsConnections: true,
baseUrl: 'https://api.twitter.com',
baseUrl: 'https://twitter.com',
apiBaseUrl: 'https://api.twitter.com',
};

View File

@@ -34,10 +34,10 @@ const getConnectedApps = async (
const usedApps = [...new Set([...duplicatedUsedApps, ...connectionKeys])];
apps = apps
.filter((app: IApp) => {
.filter((app) => {
return usedApps.includes(app.key);
})
.map((app: IApp) => {
.map((app) => {
const connection = connections.find(
(connection) => (connection as IConnection).key === app.key
);
@@ -54,7 +54,8 @@ const getConnectedApps = async (
});
return app;
});
})
.sort((appA, appB) => appA.name.localeCompare(appB.name));
return apps;
};

View File

@@ -41,7 +41,7 @@ const globalVariable = async (
data: connection?.formattedData,
},
app: app,
http: createHttpClient({ baseURL: app.baseUrl }),
http: createHttpClient({ baseURL: app.apiBaseUrl }),
flow: {
id: flow?.id,
lastInternalId,

View File

@@ -10,7 +10,12 @@ class App {
// Temporaryly restrict the apps we expose until
// their actions/triggers are implemented!
static temporaryList = ['slack', 'twitter', 'scheduler'];
static temporaryList = [
'github',
'scheduler',
'slack',
'twitter',
];
static async findAll(name?: string, stripFuncs = true): Promise<IApp[]> {
if (!name)

View File

@@ -26,7 +26,6 @@
"src/apps/discord",
"src/apps/firebase",
"src/apps/flickr",
"src/apps/github",
"src/apps/gitlab",
"src/apps/twitch",
"src/apps/typeform"

View File

@@ -29,11 +29,11 @@ export default defineConfig({
text: 'Connections',
collapsible: true,
items: [
{ text: 'Twitter', link: '/connections/twitter' },
{ text: 'Slack', link: '/connections/slack' },
{ text: 'Github', link: '/connections/github' },
{ text: 'Scheduler', link: '/connections/scheduler' },
{ text: 'Slack', link: '/connections/slack' },
{ text: 'Twitter', link: '/connections/twitter' },
// Temporarily disable following pages until we release github and typeform integrations
// { text: 'Github', link: '/connections/github' },
// { text: 'Typeform', link: '/connections/typeform' },
],
},

View File

@@ -157,6 +157,7 @@ export interface IApp {
authDocUrl: string;
primaryColor: string;
supportsConnections: boolean;
apiBaseUrl: string;
baseUrl: string;
auth: IAuth;
connectionCount: number;