Compare commits
1 Commits
v0.1.1
...
new-find-p
Author | SHA1 | Date | |
---|---|---|---|
![]() |
69ae448d64 |
@@ -1,76 +0,0 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our community include:
|
||||
|
||||
Demonstrating empathy and kindness toward other people
|
||||
Being respectful of differing opinions, viewpoints, and experiences
|
||||
Giving and gracefully accepting constructive feedback
|
||||
Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
|
||||
Focusing on what is best not just for us as individuals, but for the overall community
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
The use of sexualized language or imagery, and sexual attention or advances of any kind
|
||||
Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
Public or private harassment
|
||||
Publishing others’ private information, such as a physical or email address, without their explicit permission
|
||||
Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at ali@automatisch.io. All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
Community Impact: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
|
||||
|
||||
Consequence: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
Community Impact: A violation through a single incident or series of actions.
|
||||
|
||||
Consequence: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
Community Impact: A serious violation of community standards, including sustained inappropriate behavior.
|
||||
|
||||
Consequence: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
Community Impact: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
Consequence: A permanent ban from any sort of public interaction within the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the Contributor Covenant, version 2.1, available at https://www.contributor-covenant.org/version/2/1/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by Mozilla’s code of conduct enforcement ladder.
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
|
56
README.md
56
README.md
@@ -1,56 +0,0 @@
|
||||
# Automatisch - Open Source Zapier Alternative
|
||||
|
||||

|
||||
|
||||
🧐 Automatisch is a business automation tool that lets you connect different services like Twitter, Slack, and more to automate your business processes.
|
||||
|
||||
💸 Automating your workflows doesn't have to be a difficult or expensive process. You also don't need any programming knowledge to use Automatisch.
|
||||
|
||||
## Advantages
|
||||
|
||||
There are other existing solutions in the market, like Zapier and Integromat, so you might be wondering why you should use Automatisch.
|
||||
|
||||
✅ The most significant advantage of having Automatisch is keeping your data on your own servers. Not all companies want to use an automation service in the cloud, and the current open-source or self-hosted solutions mainly focus on developers rather than a user without a technical background.
|
||||
|
||||
🤓 Your contributions are vital to the development of Automatisch. As an open-source software, anyone can have an impact on how it is being developed.
|
||||
|
||||
💙 No vendor lock-in. If you ever decide that Automatisch is no longer helpful for your business, you can switch to any other provider, which will be easier than switching from the one cloud provider to another since you have all data and flexibility.
|
||||
|
||||
## Documentation
|
||||
|
||||
The official documentation can be found here: [https://automatisch.io/docs](https://automatisch.io/docs)
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone git@github.com:automatisch/automatisch.git
|
||||
|
||||
# Go to the repository folder
|
||||
cd automatisch/docker/compose
|
||||
|
||||
# Start
|
||||
docker compose up
|
||||
```
|
||||
|
||||
You can use `user@automatisch.io` email address and `sample` password to login to Automatisch. You can also change your email and password later on from the settings page.
|
||||
|
||||
## Community Links
|
||||
|
||||
- [Github](https://github.com/automatisch/automatisch)
|
||||
- [Discord](https://discord.gg/dJSah9CVrC)
|
||||
- [Twitter](https://twitter.com/automatischio)
|
||||
|
||||
## Support
|
||||
|
||||
If you have any questions or problems, please visit our GitHub discussions page, and we'll try to help you as soon as possible.
|
||||
|
||||
[https://github.com/automatisch/automatisch/discussions](https://github.com/automatisch/automatisch/discussions)
|
||||
|
||||
## Contribution Guide
|
||||
|
||||
You can access to the [contribution guide](https://docs.automatisch.io) page from the documentation website.
|
||||
|
||||
## License
|
||||
|
||||
Automatisch is an open-source software with an [AGPL 3.0 license](https://github.com/automatisch/automatisch/blob/main/LICENSE.md).
|
@@ -1,47 +0,0 @@
|
||||
version: "3.9"
|
||||
services:
|
||||
main:
|
||||
build:
|
||||
context: ../images/wait-for-postgres
|
||||
network: host
|
||||
ports:
|
||||
- "3000:3000"
|
||||
depends_on:
|
||||
- postgres
|
||||
- redis
|
||||
environment:
|
||||
- HOST=localhost
|
||||
- PROTOCOL=http
|
||||
- PORT=3000
|
||||
- APP_ENV=production
|
||||
- REDIS_HOST=redis
|
||||
- POSTGRES_HOST=postgres
|
||||
- POSTGRES_DATABASE=automatisch
|
||||
- POSTGRES_USERNAME=automatisch_user
|
||||
volumes:
|
||||
- automatisch_storage:/automatisch/storage
|
||||
worker:
|
||||
build:
|
||||
context: ../images/plain
|
||||
network: host
|
||||
depends_on:
|
||||
- main
|
||||
environment:
|
||||
- APP_ENV=production
|
||||
- REDIS_HOST=redis
|
||||
- POSTGRES_HOST=postgres
|
||||
- POSTGRES_DATABASE=automatisch
|
||||
- POSTGRES_USERNAME=automatisch_user
|
||||
command: automatisch start-worker --env-file /automatisch/storage/.env
|
||||
volumes:
|
||||
- automatisch_storage:/automatisch/storage
|
||||
postgres:
|
||||
image: "postgres:14.5"
|
||||
environment:
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
POSTGRES_DB: automatisch
|
||||
POSTGRES_USER: automatisch_user
|
||||
redis:
|
||||
image: "redis:7.0.4"
|
||||
volumes:
|
||||
automatisch_storage:
|
@@ -1,5 +0,0 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM node:16
|
||||
WORKDIR /automatisch
|
||||
|
||||
RUN yarn global add @automatisch/cli
|
@@ -1,15 +0,0 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM node:16
|
||||
WORKDIR /automatisch
|
||||
|
||||
RUN apt-get update && apt-get install -y postgresql-client
|
||||
COPY ./wait-for-postgres.sh /automatisch/wait-for-postgres.sh
|
||||
|
||||
RUN mkdir -p /automatisch/storage
|
||||
RUN touch /automatisch/storage/.env
|
||||
RUN echo "ENCRYPTION_KEY=$(openssl rand -base64 36)" >> /automatisch/storage/.env
|
||||
RUN echo "APP_SECRET_KEY=$(openssl rand -base64 36)" >> /automatisch/storage/.env
|
||||
RUN yarn global add @automatisch/cli
|
||||
|
||||
EXPOSE 3000
|
||||
CMD sh /automatisch/wait-for-postgres.sh automatisch start --env-file=/automatisch/storage/.env
|
@@ -1,11 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
until psql -h "$POSTGRES_HOST" -U "$POSTGRES_USERNAME" -d "$POSTGRES_HOST" -c '\q'; do
|
||||
>&2 echo "Postgres is unavailable - sleeping"
|
||||
sleep 1
|
||||
done
|
||||
|
||||
>&2 echo "Postgres is up - executing command"
|
||||
exec "$@"
|
@@ -2,12 +2,16 @@
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "0.1.1",
|
||||
"private": true,
|
||||
"version": "independent",
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"command": {
|
||||
"publish": {
|
||||
"registry": "http://localhost:5000"
|
||||
},
|
||||
"add": {
|
||||
"exact": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"name": "@automatisch/root",
|
||||
"license": "AGPL-3.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "lerna run --stream --parallel --scope=@*/{web,backend} dev",
|
||||
@@ -32,6 +31,6 @@
|
||||
"prettier": "^2.5.1"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
"registry": "http://localhost:5000"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -13,4 +13,3 @@ ENCRYPTION_KEY=sample-encryption-key
|
||||
APP_SECRET_KEY=sample-app-secret-key
|
||||
REDIS_PORT=6379
|
||||
REDIS_HOST=127.0.0.1
|
||||
ENABLE_BULLMQ_DASHBOARD=false
|
||||
|
@@ -1,7 +1,6 @@
|
||||
# `backend`
|
||||
|
||||
The open source Zapier alternative. Build workflow automation without spending
|
||||
time and money.
|
||||
> TODO: description
|
||||
|
||||
## Usage
|
||||
|
||||
|
@@ -12,14 +12,8 @@ export async function createUser(email = 'user@automatisch.io', password = 'samp
|
||||
};
|
||||
|
||||
try {
|
||||
const userCount = await User.query().resultSize();
|
||||
|
||||
if (userCount === 0) {
|
||||
const user = await User.query().insertAndFetch(userParams);
|
||||
logger.info(`User has been saved: ${user.email}`);
|
||||
} else {
|
||||
logger.info('No need to seed a user.');
|
||||
}
|
||||
const user = await User.query().insertAndFetch(userParams);
|
||||
logger.info(`User has been saved: ${user.email}`);
|
||||
} catch (err) {
|
||||
if ((err as any).nativeError.code !== UNIQUE_VIOLATION_CODE) {
|
||||
throw err;
|
||||
|
1
packages/backend/database-utils.d.ts
vendored
1
packages/backend/database-utils.d.ts
vendored
@@ -1 +0,0 @@
|
||||
export * from './dist/bin/database/utils';
|
@@ -1,2 +0,0 @@
|
||||
/* eslint-disable */
|
||||
module.exports = require('./dist/bin/database/utils');
|
3
packages/backend/database.d.ts
vendored
3
packages/backend/database.d.ts
vendored
@@ -1 +1,2 @@
|
||||
export * from './dist/src/config/database';
|
||||
export * as utils from './dist/bin/database/utils';
|
||||
export * as database from './dist/src/config/database';
|
||||
|
@@ -1,2 +1,3 @@
|
||||
/* eslint-disable */
|
||||
module.exports = require('./dist/src/config/database');
|
||||
module.exports.utils = require('./dist/bin/database/utils');
|
||||
module.exports.database = require('./dist/src/config/database');
|
||||
|
@@ -1,10 +1,9 @@
|
||||
{
|
||||
"name": "@automatisch/backend",
|
||||
"version": "0.1.1",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
||||
"version": "0.1.0",
|
||||
"description": "> TODO: description",
|
||||
"scripts": {
|
||||
"dev": "ts-node-dev src/server.ts",
|
||||
"dev": "nodemon --watch 'src/**/*.ts' --watch 'bin/**/*.ts' --exec 'ts-node' src/server.ts --ext ts,json",
|
||||
"worker": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/worker.ts",
|
||||
"build": "tsc && yarn copy-statics",
|
||||
"build:watch": "nodemon --watch 'src/**/*.ts' --watch 'bin/**/*.ts' --exec yarn build --ext ts",
|
||||
@@ -17,15 +16,15 @@
|
||||
"db:migration:create": "knex migrate:make",
|
||||
"db:rollback": "knex migrate:rollback",
|
||||
"db:migrate": "knex migrate:latest",
|
||||
"copy-statics": "copyfiles src/**/*.{graphql,json,svg} dist",
|
||||
"prepack": "yarn build"
|
||||
"copy-statics": "copyfiles src/**/*.{graphql,json,svg} dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"@automatisch/web": "^0.1.1",
|
||||
"@automatisch/web": "0.1.0",
|
||||
"@bull-board/express": "^3.10.1",
|
||||
"@gitbeaker/node": "^35.6.0",
|
||||
"@graphql-tools/graphql-file-loader": "^7.3.4",
|
||||
"@graphql-tools/load": "^7.5.2",
|
||||
"@octokit/oauth-methods": "^1.2.6",
|
||||
"@rudderstack/rudder-sdk-node": "^1.1.2",
|
||||
"@slack/bolt": "3.10.0",
|
||||
"@types/luxon": "^2.3.1",
|
||||
@@ -54,7 +53,6 @@
|
||||
"luxon": "2.3.1",
|
||||
"morgan": "^1.10.0",
|
||||
"nodemailer": "6.7.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"objection": "^3.0.0",
|
||||
"octokit": "^1.7.1",
|
||||
"pg": "^8.7.1",
|
||||
@@ -77,19 +75,14 @@
|
||||
"test": "__tests__"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"bin",
|
||||
"src",
|
||||
"server.js",
|
||||
"server.d.ts",
|
||||
"worker.js",
|
||||
"worker.d.ts",
|
||||
"logger.js",
|
||||
"logger.d.ts",
|
||||
"database.js",
|
||||
"database.d.ts",
|
||||
"database-utils.js",
|
||||
"database-utils.d.ts"
|
||||
"database.d.ts"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -99,7 +92,7 @@
|
||||
"url": "https://github.com/automatisch/automatisch/issues"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@automatisch/types": "^0.1.1",
|
||||
"@automatisch/types": "0.1.0",
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
"@types/bull": "^3.15.8",
|
||||
"@types/cors": "^2.8.12",
|
||||
@@ -116,8 +109,7 @@
|
||||
"ava": "^3.15.0",
|
||||
"nodemon": "^2.0.13",
|
||||
"sinon": "^11.1.2",
|
||||
"ts-node": "^10.2.1",
|
||||
"ts-node-dev": "^1.1.8"
|
||||
"ts-node": "^10.2.1"
|
||||
},
|
||||
"ava": {
|
||||
"files": [
|
||||
@@ -129,8 +121,5 @@
|
||||
"require": [
|
||||
"ts-node/register"
|
||||
]
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
|
@@ -15,13 +15,13 @@ import {
|
||||
} from './helpers/create-bull-board-handler';
|
||||
import injectBullBoardHandler from './helpers/inject-bull-board-handler';
|
||||
|
||||
if (appConfig.enableBullMQDashboard) {
|
||||
if (appConfig.appEnv === 'development') {
|
||||
createBullBoardHandler(serverAdapter);
|
||||
}
|
||||
|
||||
const app = express();
|
||||
|
||||
if (appConfig.enableBullMQDashboard) {
|
||||
if (appConfig.appEnv === 'development') {
|
||||
injectBullBoardHandler(app, serverAdapter);
|
||||
}
|
||||
|
||||
|
@@ -4,7 +4,6 @@
|
||||
"iconUrl": "{BASE_URL}/apps/discord/assets/favicon.svg",
|
||||
"docUrl": "https://automatisch.io/docs/discord",
|
||||
"primaryColor": "5865f2",
|
||||
"supportsConnections": true,
|
||||
"fields": [
|
||||
{
|
||||
"key": "oAuthRedirectUrl",
|
||||
|
@@ -4,7 +4,6 @@
|
||||
"iconUrl": "{BASE_URL}/apps/firebase/assets/favicon.svg",
|
||||
"docUrl": "https://automatisch.io/docs/firebase",
|
||||
"primaryColor": "ffca28",
|
||||
"supportsConnections": true,
|
||||
"fields": [
|
||||
{
|
||||
"key": "oAuthRedirectUrl",
|
||||
|
@@ -4,7 +4,6 @@
|
||||
"iconUrl": "{BASE_URL}/apps/flickr/assets/favicon.svg",
|
||||
"docUrl": "https://automatisch.io/docs/flickr",
|
||||
"primaryColor": "000000",
|
||||
"supportsConnections": true,
|
||||
"fields": [
|
||||
{
|
||||
"key": "oAuthRedirectUrl",
|
||||
@@ -223,8 +222,8 @@
|
||||
"description": "Triggers when you favorite a photo.",
|
||||
"substeps": [
|
||||
{
|
||||
"key": "chooseConnection",
|
||||
"name": "Choose connection"
|
||||
"key": "chooseAccount",
|
||||
"name": "Choose account"
|
||||
},
|
||||
{
|
||||
"key": "testStep",
|
||||
@@ -238,8 +237,8 @@
|
||||
"description": "Triggers when you add a new photo in an album.",
|
||||
"substeps": [
|
||||
{
|
||||
"key": "chooseConnection",
|
||||
"name": "Choose connection"
|
||||
"key": "chooseAccount",
|
||||
"name": "Choose account"
|
||||
},
|
||||
{
|
||||
"key": "chooseTrigger",
|
||||
@@ -276,8 +275,8 @@
|
||||
"description": "Triggers when you add a new photo.",
|
||||
"substeps": [
|
||||
{
|
||||
"key": "chooseConnection",
|
||||
"name": "Choose connection"
|
||||
"key": "chooseAccount",
|
||||
"name": "Choose account"
|
||||
},
|
||||
{
|
||||
"key": "testStep",
|
||||
@@ -291,8 +290,8 @@
|
||||
"description": "Triggers when you create a new album.",
|
||||
"substeps": [
|
||||
{
|
||||
"key": "chooseConnection",
|
||||
"name": "Choose connection"
|
||||
"key": "chooseAccount",
|
||||
"name": "Choose account"
|
||||
},
|
||||
{
|
||||
"key": "testStep",
|
||||
|
@@ -4,19 +4,35 @@ import type {
|
||||
IField,
|
||||
IJSONObject,
|
||||
} from '@automatisch/types';
|
||||
import createHttpClient, { IHttpClient } from '../../helpers/http-client';
|
||||
import { URLSearchParams } from 'url';
|
||||
import {
|
||||
getWebFlowAuthorizationUrl,
|
||||
exchangeWebFlowCode,
|
||||
checkToken,
|
||||
} from '@octokit/oauth-methods';
|
||||
|
||||
export default class Authentication implements IAuthentication {
|
||||
appData: IApp;
|
||||
connectionData: IJSONObject;
|
||||
scopes: string[] = ['read:org', 'repo', 'user'];
|
||||
client: IHttpClient;
|
||||
scopes: string[] = [
|
||||
'read:org',
|
||||
'repo',
|
||||
'user',
|
||||
];
|
||||
client: {
|
||||
getWebFlowAuthorizationUrl: typeof getWebFlowAuthorizationUrl;
|
||||
exchangeWebFlowCode: typeof exchangeWebFlowCode;
|
||||
checkToken: typeof checkToken;
|
||||
};
|
||||
|
||||
constructor(appData: IApp, connectionData: IJSONObject) {
|
||||
this.connectionData = connectionData;
|
||||
this.appData = appData;
|
||||
this.client = createHttpClient({ baseURL: 'https://github.com' });
|
||||
|
||||
this.client = {
|
||||
getWebFlowAuthorizationUrl,
|
||||
exchangeWebFlowCode,
|
||||
checkToken,
|
||||
};
|
||||
}
|
||||
|
||||
get oauthRedirectUrl(): string {
|
||||
@@ -26,28 +42,26 @@ export default class Authentication implements IAuthentication {
|
||||
}
|
||||
|
||||
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 } = await this.client.getWebFlowAuthorizationUrl({
|
||||
clientType: 'oauth-app',
|
||||
clientId: this.connectionData.consumerKey as string,
|
||||
redirectUrl: this.oauthRedirectUrl,
|
||||
scopes: this.scopes,
|
||||
});
|
||||
|
||||
const url = `https://github.com/login/oauth/authorize?${searchParams.toString()}`;
|
||||
|
||||
return {
|
||||
url,
|
||||
url: 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 } = await this.client.exchangeWebFlowCode({
|
||||
clientType: 'oauth-app',
|
||||
clientId: this.connectionData.consumerKey as string,
|
||||
clientSecret: this.connectionData.consumerSecret as string,
|
||||
code: this.connectionData.oauthVerifier as string,
|
||||
});
|
||||
|
||||
const data = Object.fromEntries(new URLSearchParams(response.data));
|
||||
|
||||
this.connectionData.accessToken = data.access_token;
|
||||
|
||||
const tokenInfo = await this.getTokenInfo();
|
||||
@@ -64,23 +78,12 @@ export default class Authentication implements IAuthentication {
|
||||
}
|
||||
|
||||
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 }
|
||||
);
|
||||
return this.client.checkToken({
|
||||
clientType: 'oauth-app',
|
||||
clientId: this.connectionData.consumerKey as string,
|
||||
clientSecret: this.connectionData.consumerSecret as string,
|
||||
token: this.connectionData.accessToken as string,
|
||||
});
|
||||
}
|
||||
|
||||
async isStillVerified() {
|
||||
|
@@ -1,16 +1,13 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@@ -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,
|
||||
}));
|
||||
}
|
||||
}
|
@@ -4,7 +4,6 @@
|
||||
"iconUrl": "{BASE_URL}/apps/github/assets/favicon.svg",
|
||||
"docUrl": "https://automatisch.io/docs/github",
|
||||
"primaryColor": "000000",
|
||||
"supportsConnections": true,
|
||||
"fields": [
|
||||
{
|
||||
"key": "oAuthRedirectUrl",
|
||||
@@ -20,26 +19,26 @@
|
||||
},
|
||||
{
|
||||
"key": "consumerKey",
|
||||
"label": "Client ID",
|
||||
"label": "Consumer Key",
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"readOnly": false,
|
||||
"value": null,
|
||||
"placeholder": null,
|
||||
"description": null,
|
||||
"docUrl": "https://automatisch.io/docs/github#client-id",
|
||||
"docUrl": "https://automatisch.io/docs/github#consumer-key",
|
||||
"clickToCopy": false
|
||||
},
|
||||
{
|
||||
"key": "consumerSecret",
|
||||
"label": "Client Secret",
|
||||
"label": "Consumer Secret",
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"readOnly": false,
|
||||
"value": null,
|
||||
"placeholder": null,
|
||||
"description": null,
|
||||
"docUrl": "https://automatisch.io/docs/github#client-secret",
|
||||
"docUrl": "https://automatisch.io/docs/github#consumer-secret",
|
||||
"clickToCopy": false
|
||||
}
|
||||
],
|
||||
@@ -223,8 +222,8 @@
|
||||
"description": "Triggers when a new repository is created",
|
||||
"substeps": [
|
||||
{
|
||||
"key": "chooseConnection",
|
||||
"name": "Choose connection"
|
||||
"key": "chooseAccount",
|
||||
"name": "Choose account"
|
||||
},
|
||||
{
|
||||
"key": "testStep",
|
||||
@@ -238,8 +237,8 @@
|
||||
"description": "Triggers when a new organization is created",
|
||||
"substeps": [
|
||||
{
|
||||
"key": "chooseConnection",
|
||||
"name": "Choose connection"
|
||||
"key": "chooseAccount",
|
||||
"name": "Choose account"
|
||||
},
|
||||
{
|
||||
"key": "testStep",
|
||||
@@ -253,8 +252,8 @@
|
||||
"description": "Triggers when a new branch is created",
|
||||
"substeps": [
|
||||
{
|
||||
"key": "chooseConnection",
|
||||
"name": "Choose connection"
|
||||
"key": "chooseAccount",
|
||||
"name": "Choose account"
|
||||
},
|
||||
{
|
||||
"key": "chooseTrigger",
|
||||
@@ -291,8 +290,8 @@
|
||||
"description": "Triggers when a new notification is created",
|
||||
"substeps": [
|
||||
{
|
||||
"key": "chooseConnection",
|
||||
"name": "Choose connection"
|
||||
"key": "chooseAccount",
|
||||
"name": "Choose account"
|
||||
},
|
||||
{
|
||||
"key": "chooseTrigger",
|
||||
@@ -330,8 +329,8 @@
|
||||
"description": "Triggers when a new pull request is created",
|
||||
"substeps": [
|
||||
{
|
||||
"key": "chooseConnection",
|
||||
"name": "Choose connection"
|
||||
"key": "chooseAccount",
|
||||
"name": "Choose account"
|
||||
},
|
||||
{
|
||||
"key": "chooseTrigger",
|
||||
@@ -368,8 +367,8 @@
|
||||
"description": "Triggers when a new watcher is added to a repo",
|
||||
"substeps": [
|
||||
{
|
||||
"key": "chooseConnection",
|
||||
"name": "Choose connection"
|
||||
"key": "chooseAccount",
|
||||
"name": "Choose account"
|
||||
},
|
||||
{
|
||||
"key": "chooseTrigger",
|
||||
@@ -406,8 +405,8 @@
|
||||
"description": "Triggers when a new milestone is created",
|
||||
"substeps": [
|
||||
{
|
||||
"key": "chooseConnection",
|
||||
"name": "Choose connection"
|
||||
"key": "chooseAccount",
|
||||
"name": "Choose account"
|
||||
},
|
||||
{
|
||||
"key": "chooseTrigger",
|
||||
@@ -444,8 +443,8 @@
|
||||
"description": "Triggers when a new commit comment is created",
|
||||
"substeps": [
|
||||
{
|
||||
"key": "chooseConnection",
|
||||
"name": "Choose connection"
|
||||
"key": "chooseAccount",
|
||||
"name": "Choose account"
|
||||
},
|
||||
{
|
||||
"key": "chooseTrigger",
|
||||
@@ -482,8 +481,8 @@
|
||||
"description": "Triggers when a new label is created",
|
||||
"substeps": [
|
||||
{
|
||||
"key": "chooseConnection",
|
||||
"name": "Choose connection"
|
||||
"key": "chooseAccount",
|
||||
"name": "Choose account"
|
||||
},
|
||||
{
|
||||
"key": "chooseTrigger",
|
||||
@@ -520,8 +519,8 @@
|
||||
"description": "Triggers when a new collaborator is added to a repo",
|
||||
"substeps": [
|
||||
{
|
||||
"key": "chooseConnection",
|
||||
"name": "Choose connection"
|
||||
"key": "chooseAccount",
|
||||
"name": "Choose account"
|
||||
},
|
||||
{
|
||||
"key": "chooseTrigger",
|
||||
@@ -558,8 +557,8 @@
|
||||
"description": "Triggers when a new release is created",
|
||||
"substeps": [
|
||||
{
|
||||
"key": "chooseConnection",
|
||||
"name": "Choose connection"
|
||||
"key": "chooseAccount",
|
||||
"name": "Choose account"
|
||||
},
|
||||
{
|
||||
"key": "chooseTrigger",
|
||||
@@ -596,8 +595,8 @@
|
||||
"description": "Triggers when a new commit is created",
|
||||
"substeps": [
|
||||
{
|
||||
"key": "chooseConnection",
|
||||
"name": "Choose connection"
|
||||
"key": "chooseAccount",
|
||||
"name": "Choose account"
|
||||
},
|
||||
{
|
||||
"key": "chooseTrigger",
|
||||
@@ -650,98 +649,6 @@
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@@ -11,7 +11,6 @@ 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;
|
||||
@@ -26,7 +25,6 @@ export default class Triggers {
|
||||
newLabel: NewLabel;
|
||||
newCollaborator: NewCollaborator;
|
||||
newRelease: NewRelease;
|
||||
newIssue: NewIssue;
|
||||
|
||||
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
|
||||
this.newRepository = new NewRepository(connectionData);
|
||||
@@ -41,6 +39,5 @@ export default class Triggers {
|
||||
this.newLabel = new NewLabel(connectionData, parameters);
|
||||
this.newCollaborator = new NewCollaborator(connectionData, parameters);
|
||||
this.newRelease = new NewRelease(connectionData, parameters);
|
||||
this.newIssue = new NewIssue(connectionData, parameters);
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -9,7 +9,6 @@ export default class NewNotification {
|
||||
connectionData?: IJSONObject;
|
||||
repoOwner?: string;
|
||||
repo?: string;
|
||||
hasRepo?: boolean;
|
||||
baseOptions = {
|
||||
all: true,
|
||||
participating: false,
|
||||
@@ -25,6 +24,10 @@ export default class NewNotification {
|
||||
assignOwnerAndRepo(this, parameters?.repo as string);
|
||||
}
|
||||
|
||||
get hasRepo() {
|
||||
return this.repoOwner && this.repo;
|
||||
}
|
||||
|
||||
async listRepoNotifications(options = {}, paginate = false) {
|
||||
const listRepoNotifications = this.client.rest.activity.listRepoNotificationsForAuthenticatedUser;
|
||||
|
||||
|
@@ -1,9 +1,8 @@
|
||||
export function assignOwnerAndRepo<T extends { repoOwner?: string; repo?: string; hasRepo?: boolean; }>(object: T, repoFullName: string): T {
|
||||
export function assignOwnerAndRepo<T extends { repoOwner?: string; repo?: string; }>(object: T, repoFullName: string): T {
|
||||
if (object && repoFullName) {
|
||||
const [repoOwner, repo] = repoFullName.split('/');
|
||||
object.repoOwner = repoOwner;
|
||||
object.repo = repo;
|
||||
object.hasRepo = true;
|
||||
}
|
||||
|
||||
return object;
|
||||
|
13
packages/backend/src/apps/gitlab/actions.ts
Normal file
13
packages/backend/src/apps/gitlab/actions.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { IJSONObject } from '@automatisch/types';
|
||||
import FindProjectMergeRequests from './actions/find-project-merge-requests';
|
||||
|
||||
export default class Actions {
|
||||
findProjectMergeRequests: FindProjectMergeRequests;
|
||||
|
||||
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
|
||||
this.findProjectMergeRequests = new FindProjectMergeRequests(
|
||||
connectionData,
|
||||
parameters
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
import { Gitlab } from '@gitbeaker/node';
|
||||
import { IJSONObject } from '@automatisch/types';
|
||||
|
||||
export default class FindProjectMergeRequests {
|
||||
client: any;
|
||||
projectId: number;
|
||||
state: string;
|
||||
|
||||
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
|
||||
if (connectionData?.accessToken) {
|
||||
this.client = new Gitlab({
|
||||
host: `https://${connectionData.host}`,
|
||||
oauthToken: connectionData?.accessToken as string,
|
||||
});
|
||||
}
|
||||
|
||||
if (parameters.project) {
|
||||
this.projectId = parameters.project as number;
|
||||
}
|
||||
|
||||
if (parameters.state) {
|
||||
this.state = parameters.state as string;
|
||||
}
|
||||
}
|
||||
|
||||
async run() {
|
||||
const mergeRequests = await this.client.MergeRequests.all({
|
||||
state: this.state,
|
||||
projectId: this.projectId,
|
||||
maxPages: 1,
|
||||
});
|
||||
|
||||
return { data: mergeRequests };
|
||||
}
|
||||
}
|
10
packages/backend/src/apps/gitlab/data.ts
Normal file
10
packages/backend/src/apps/gitlab/data.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { IJSONObject } from '@automatisch/types';
|
||||
import ListProjects from './data/list-projects';
|
||||
|
||||
export default class Data {
|
||||
listProjects: ListProjects;
|
||||
|
||||
constructor(connectionData: IJSONObject, parameters?: IJSONObject) {
|
||||
this.listProjects = new ListProjects(connectionData, parameters);
|
||||
}
|
||||
}
|
26
packages/backend/src/apps/gitlab/data/list-projects.ts
Normal file
26
packages/backend/src/apps/gitlab/data/list-projects.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Gitlab } from '@gitbeaker/node';
|
||||
import type { IJSONObject } from '@automatisch/types';
|
||||
|
||||
export default class ListProjects {
|
||||
client?: any;
|
||||
|
||||
constructor(connectionData: IJSONObject, parameters?: IJSONObject) {
|
||||
if (connectionData?.accessToken) {
|
||||
this.client = new Gitlab({
|
||||
host: `https://${connectionData.host}`,
|
||||
oauthToken: connectionData?.accessToken as string,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async run() {
|
||||
const projects = await this.client.Projects.all({
|
||||
membership: true,
|
||||
});
|
||||
|
||||
return projects.map((project: any) => ({
|
||||
value: project.id,
|
||||
name: project.name_with_namespace,
|
||||
}));
|
||||
}
|
||||
}
|
@@ -1,15 +1,25 @@
|
||||
import Authentication from './authentication';
|
||||
import {
|
||||
IService,
|
||||
IAuthentication,
|
||||
IApp,
|
||||
IJSONObject,
|
||||
} from '@automatisch/types';
|
||||
import Authentication from './authentication';
|
||||
import Actions from './actions';
|
||||
import Data from './data';
|
||||
|
||||
export default class Gitlab implements IService {
|
||||
authenticationClient: IAuthentication;
|
||||
actions: Actions;
|
||||
data: Data;
|
||||
|
||||
constructor(appData: IApp, connectionData: IJSONObject) {
|
||||
constructor(
|
||||
appData: IApp,
|
||||
connectionData: IJSONObject,
|
||||
parameters: IJSONObject
|
||||
) {
|
||||
this.authenticationClient = new Authentication(appData, connectionData);
|
||||
this.actions = new Actions(connectionData, parameters);
|
||||
this.data = new Data(connectionData, parameters);
|
||||
}
|
||||
}
|
||||
|
@@ -4,7 +4,6 @@
|
||||
"iconUrl": "{BASE_URL}/apps/gitlab/assets/favicon.svg",
|
||||
"docUrl": "https://automatisch.io/docs/gitlab",
|
||||
"primaryColor": "2DAAE1",
|
||||
"supportsConnections": true,
|
||||
"fields": [
|
||||
{
|
||||
"key": "oAuthRedirectUrl",
|
||||
@@ -235,5 +234,76 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"name": "Find project merge requests",
|
||||
"key": "findProjectMergeRequests",
|
||||
"description": "Find merge requests for a project.",
|
||||
"substeps": [
|
||||
{
|
||||
"key": "chooseAccount",
|
||||
"name": "Choose account"
|
||||
},
|
||||
{
|
||||
"key": "setupAction",
|
||||
"name": "Set up action",
|
||||
"arguments": [
|
||||
{
|
||||
"label": "Project",
|
||||
"key": "project",
|
||||
"type": "dropdown",
|
||||
"required": true,
|
||||
"description": "Search for merge requests in this project.",
|
||||
"variables": false,
|
||||
"source": {
|
||||
"type": "query",
|
||||
"name": "getData",
|
||||
"arguments": [
|
||||
{
|
||||
"name": "key",
|
||||
"value": "listProjects"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "State",
|
||||
"key": "state",
|
||||
"type": "dropdown",
|
||||
"required": true,
|
||||
"description": "Filter merge requests by their state.",
|
||||
"variables": false,
|
||||
"options": [
|
||||
{
|
||||
"label": "All",
|
||||
"value": "all"
|
||||
},
|
||||
{
|
||||
"label": "Opened",
|
||||
"value": "opened"
|
||||
},
|
||||
{
|
||||
"label": "Closed",
|
||||
"value": "closed"
|
||||
},
|
||||
{
|
||||
"label": "Locked",
|
||||
"value": "locked"
|
||||
},
|
||||
{
|
||||
"label": "Merged",
|
||||
"value": "merged"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "testStep",
|
||||
"name": "Test action"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@@ -4,7 +4,6 @@
|
||||
"iconUrl": "{BASE_URL}/apps/postgresql/assets/favicon.svg",
|
||||
"docUrl": "https://automatisch.io/docs/postgresql",
|
||||
"primaryColor": "2DAAE1",
|
||||
"supportsConnections": true,
|
||||
"fields": [
|
||||
{
|
||||
"key": "host",
|
||||
|
Before Width: | Height: | Size: 345 B After Width: | Height: | Size: 345 B |
18
packages/backend/src/apps/schedule/index.ts
Normal file
18
packages/backend/src/apps/schedule/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import Triggers from './triggers';
|
||||
import {
|
||||
IService,
|
||||
IApp,
|
||||
IJSONObject,
|
||||
} from '@automatisch/types';
|
||||
|
||||
export default class Schedule implements IService {
|
||||
triggers: Triggers;
|
||||
|
||||
constructor(
|
||||
appData: IApp,
|
||||
connectionData: IJSONObject,
|
||||
parameters: IJSONObject
|
||||
) {
|
||||
this.triggers = new Triggers(connectionData, parameters);
|
||||
}
|
||||
}
|
606
packages/backend/src/apps/schedule/info.json
Normal file
606
packages/backend/src/apps/schedule/info.json
Normal file
@@ -0,0 +1,606 @@
|
||||
{
|
||||
"name": "Schedule",
|
||||
"key": "schedule",
|
||||
"iconUrl": "{BASE_URL}/apps/schedule/assets/favicon.svg",
|
||||
"docUrl": "https://automatisch.io/docs/schedule",
|
||||
"primaryColor": "0059F7",
|
||||
"requiresAuthentication": false,
|
||||
"triggers": [
|
||||
{
|
||||
"name": "Every hour",
|
||||
"key": "everyHour",
|
||||
"description": "Triggers every hour.",
|
||||
"substeps": [
|
||||
{
|
||||
"key": "chooseTrigger",
|
||||
"name": "Set up a trigger",
|
||||
"arguments": [
|
||||
{
|
||||
"label": "Trigger on weekends?",
|
||||
"key": "triggersOnWeekend",
|
||||
"type": "dropdown",
|
||||
"description": "Should this flow trigger on Saturday and Sunday?",
|
||||
"required": true,
|
||||
"value": true,
|
||||
"variables": false,
|
||||
"options": [
|
||||
{
|
||||
"label": "Yes",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"label": "No",
|
||||
"value": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "testStep",
|
||||
"name": "Test trigger"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Every day",
|
||||
"key": "everyDay",
|
||||
"description": "Triggers every day.",
|
||||
"substeps": [
|
||||
{
|
||||
"key": "chooseTrigger",
|
||||
"name": "Set up a trigger",
|
||||
"arguments": [
|
||||
{
|
||||
"label": "Trigger on weekends?",
|
||||
"key": "triggersOnWeekend",
|
||||
"type": "dropdown",
|
||||
"description": "Should this flow trigger on Saturday and Sunday?",
|
||||
"required": true,
|
||||
"value": true,
|
||||
"variables": false,
|
||||
"options": [
|
||||
{
|
||||
"label": "Yes",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"label": "No",
|
||||
"value": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Time of day",
|
||||
"key": "hour",
|
||||
"type": "dropdown",
|
||||
"required": true,
|
||||
"value": null,
|
||||
"variables": false,
|
||||
"options": [
|
||||
{
|
||||
"label": "00:00",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"label": "01:00",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"label": "02:00",
|
||||
"value": 2
|
||||
},
|
||||
{
|
||||
"label": "03:00",
|
||||
"value": 3
|
||||
},
|
||||
{
|
||||
"label": "04:00",
|
||||
"value": 4
|
||||
},
|
||||
{
|
||||
"label": "05:00",
|
||||
"value": 5
|
||||
},
|
||||
{
|
||||
"label": "06:00",
|
||||
"value": 6
|
||||
},
|
||||
{
|
||||
"label": "07:00",
|
||||
"value": 7
|
||||
},
|
||||
{
|
||||
"label": "08:00",
|
||||
"value": 8
|
||||
},
|
||||
{
|
||||
"label": "09:00",
|
||||
"value": 9
|
||||
},
|
||||
{
|
||||
"label": "10:00",
|
||||
"value": 10
|
||||
},
|
||||
{
|
||||
"label": "11:00",
|
||||
"value": 11
|
||||
},
|
||||
{
|
||||
"label": "12:00",
|
||||
"value": 12
|
||||
},
|
||||
{
|
||||
"label": "13:00",
|
||||
"value": 13
|
||||
},
|
||||
{
|
||||
"label": "14:00",
|
||||
"value": 14
|
||||
},
|
||||
{
|
||||
"label": "15:00",
|
||||
"value": 15
|
||||
},
|
||||
{
|
||||
"label": "16:00",
|
||||
"value": 16
|
||||
},
|
||||
{
|
||||
"label": "17:00",
|
||||
"value": 17
|
||||
},
|
||||
{
|
||||
"label": "18:00",
|
||||
"value": 18
|
||||
},
|
||||
{
|
||||
"label": "19:00",
|
||||
"value": 19
|
||||
},
|
||||
{
|
||||
"label": "20:00",
|
||||
"value": 20
|
||||
},
|
||||
{
|
||||
"label": "21:00",
|
||||
"value": 21
|
||||
},
|
||||
{
|
||||
"label": "22:00",
|
||||
"value": 22
|
||||
},
|
||||
{
|
||||
"label": "23:00",
|
||||
"value": 23
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "testStep",
|
||||
"name": "Test trigger"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Every week",
|
||||
"key": "everyWeek",
|
||||
"description": "Triggers every week.",
|
||||
"substeps": [
|
||||
{
|
||||
"key": "chooseTrigger",
|
||||
"name": "Set up a trigger",
|
||||
"arguments": [
|
||||
{
|
||||
"label": "Day of the week",
|
||||
"key": "weekday",
|
||||
"type": "dropdown",
|
||||
"required": true,
|
||||
"value": null,
|
||||
"variables": false,
|
||||
"options": [
|
||||
{
|
||||
"label": "Monday",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"label": "Tuesday",
|
||||
"value": 2
|
||||
},
|
||||
{
|
||||
"label": "Wednesday",
|
||||
"value": 3
|
||||
},
|
||||
{
|
||||
"label": "Thursday",
|
||||
"value": 4
|
||||
},
|
||||
{
|
||||
"label": "Friday",
|
||||
"value": 5
|
||||
},
|
||||
{
|
||||
"label": "Saturday",
|
||||
"value": 6
|
||||
},
|
||||
{
|
||||
"label": "Sunday",
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Time of day",
|
||||
"key": "hour",
|
||||
"type": "dropdown",
|
||||
"required": true,
|
||||
"value": null,
|
||||
"variables": false,
|
||||
"options": [
|
||||
{
|
||||
"label": "00:00",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"label": "01:00",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"label": "02:00",
|
||||
"value": 2
|
||||
},
|
||||
{
|
||||
"label": "03:00",
|
||||
"value": 3
|
||||
},
|
||||
{
|
||||
"label": "04:00",
|
||||
"value": 4
|
||||
},
|
||||
{
|
||||
"label": "05:00",
|
||||
"value": 5
|
||||
},
|
||||
{
|
||||
"label": "06:00",
|
||||
"value": 6
|
||||
},
|
||||
{
|
||||
"label": "07:00",
|
||||
"value": 7
|
||||
},
|
||||
{
|
||||
"label": "08:00",
|
||||
"value": 8
|
||||
},
|
||||
{
|
||||
"label": "09:00",
|
||||
"value": 9
|
||||
},
|
||||
{
|
||||
"label": "10:00",
|
||||
"value": 10
|
||||
},
|
||||
{
|
||||
"label": "11:00",
|
||||
"value": 11
|
||||
},
|
||||
{
|
||||
"label": "12:00",
|
||||
"value": 12
|
||||
},
|
||||
{
|
||||
"label": "13:00",
|
||||
"value": 13
|
||||
},
|
||||
{
|
||||
"label": "14:00",
|
||||
"value": 14
|
||||
},
|
||||
{
|
||||
"label": "15:00",
|
||||
"value": 15
|
||||
},
|
||||
{
|
||||
"label": "16:00",
|
||||
"value": 16
|
||||
},
|
||||
{
|
||||
"label": "17:00",
|
||||
"value": 17
|
||||
},
|
||||
{
|
||||
"label": "18:00",
|
||||
"value": 18
|
||||
},
|
||||
{
|
||||
"label": "19:00",
|
||||
"value": 19
|
||||
},
|
||||
{
|
||||
"label": "20:00",
|
||||
"value": 20
|
||||
},
|
||||
{
|
||||
"label": "21:00",
|
||||
"value": 21
|
||||
},
|
||||
{
|
||||
"label": "22:00",
|
||||
"value": 22
|
||||
},
|
||||
{
|
||||
"label": "23:00",
|
||||
"value": 23
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "testStep",
|
||||
"name": "Test trigger"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Every month",
|
||||
"key": "everyMonth",
|
||||
"description": "Triggers every month.",
|
||||
"substeps": [
|
||||
{
|
||||
"key": "chooseTrigger",
|
||||
"name": "Set up a trigger",
|
||||
"arguments": [
|
||||
{
|
||||
"label": "Day of the month",
|
||||
"key": "day",
|
||||
"type": "dropdown",
|
||||
"required": true,
|
||||
"value": null,
|
||||
"variables": false,
|
||||
"options": [
|
||||
{
|
||||
"label": 1,
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"label": 2,
|
||||
"value": 2
|
||||
},
|
||||
{
|
||||
"label": 3,
|
||||
"value": 3
|
||||
},
|
||||
{
|
||||
"label": 4,
|
||||
"value": 4
|
||||
},
|
||||
{
|
||||
"label": 5,
|
||||
"value": 5
|
||||
},
|
||||
{
|
||||
"label": 6,
|
||||
"value": 6
|
||||
},
|
||||
{
|
||||
"label": 7,
|
||||
"value": 7
|
||||
},
|
||||
{
|
||||
"label": 8,
|
||||
"value": 8
|
||||
},
|
||||
{
|
||||
"label": 9,
|
||||
"value": 9
|
||||
},
|
||||
{
|
||||
"label": 10,
|
||||
"value": 10
|
||||
},
|
||||
{
|
||||
"label": 11,
|
||||
"value": 11
|
||||
},
|
||||
{
|
||||
"label": 12,
|
||||
"value": 12
|
||||
},
|
||||
{
|
||||
"label": 13,
|
||||
"value": 13
|
||||
},
|
||||
{
|
||||
"label": 14,
|
||||
"value": 14
|
||||
},
|
||||
{
|
||||
"label": 15,
|
||||
"value": 15
|
||||
},
|
||||
{
|
||||
"label": 16,
|
||||
"value": 16
|
||||
},
|
||||
{
|
||||
"label": 17,
|
||||
"value": 17
|
||||
},
|
||||
{
|
||||
"label": 18,
|
||||
"value": 18
|
||||
},
|
||||
{
|
||||
"label": 19,
|
||||
"value": 19
|
||||
},
|
||||
{
|
||||
"label": 20,
|
||||
"value": 20
|
||||
},
|
||||
{
|
||||
"label": 21,
|
||||
"value": 21
|
||||
},
|
||||
{
|
||||
"label": 22,
|
||||
"value": 22
|
||||
},
|
||||
{
|
||||
"label": 23,
|
||||
"value": 23
|
||||
},
|
||||
{
|
||||
"label": 24,
|
||||
"value": 24
|
||||
},
|
||||
{
|
||||
"label": 25,
|
||||
"value": 25
|
||||
},
|
||||
{
|
||||
"label": 26,
|
||||
"value": 26
|
||||
},
|
||||
{
|
||||
"label": 27,
|
||||
"value": 27
|
||||
},
|
||||
{
|
||||
"label": 28,
|
||||
"value": 28
|
||||
},
|
||||
{
|
||||
"label": 29,
|
||||
"value": 29
|
||||
},
|
||||
{
|
||||
"label": 30,
|
||||
"value": 30
|
||||
},
|
||||
{
|
||||
"label": 31,
|
||||
"value": 31
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Time of day",
|
||||
"key": "hour",
|
||||
"type": "dropdown",
|
||||
"required": true,
|
||||
"value": null,
|
||||
"variables": false,
|
||||
"options": [
|
||||
{
|
||||
"label": "00:00",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"label": "01:00",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"label": "02:00",
|
||||
"value": 2
|
||||
},
|
||||
{
|
||||
"label": "03:00",
|
||||
"value": 3
|
||||
},
|
||||
{
|
||||
"label": "04:00",
|
||||
"value": 4
|
||||
},
|
||||
{
|
||||
"label": "05:00",
|
||||
"value": 5
|
||||
},
|
||||
{
|
||||
"label": "06:00",
|
||||
"value": 6
|
||||
},
|
||||
{
|
||||
"label": "07:00",
|
||||
"value": 7
|
||||
},
|
||||
{
|
||||
"label": "08:00",
|
||||
"value": 8
|
||||
},
|
||||
{
|
||||
"label": "09:00",
|
||||
"value": 9
|
||||
},
|
||||
{
|
||||
"label": "10:00",
|
||||
"value": 10
|
||||
},
|
||||
{
|
||||
"label": "11:00",
|
||||
"value": 11
|
||||
},
|
||||
{
|
||||
"label": "12:00",
|
||||
"value": 12
|
||||
},
|
||||
{
|
||||
"label": "13:00",
|
||||
"value": 13
|
||||
},
|
||||
{
|
||||
"label": "14:00",
|
||||
"value": 14
|
||||
},
|
||||
{
|
||||
"label": "15:00",
|
||||
"value": 15
|
||||
},
|
||||
{
|
||||
"label": "16:00",
|
||||
"value": 16
|
||||
},
|
||||
{
|
||||
"label": "17:00",
|
||||
"value": 17
|
||||
},
|
||||
{
|
||||
"label": "18:00",
|
||||
"value": 18
|
||||
},
|
||||
{
|
||||
"label": "19:00",
|
||||
"value": 19
|
||||
},
|
||||
{
|
||||
"label": "20:00",
|
||||
"value": 20
|
||||
},
|
||||
{
|
||||
"label": "21:00",
|
||||
"value": 21
|
||||
},
|
||||
{
|
||||
"label": "22:00",
|
||||
"value": 22
|
||||
},
|
||||
{
|
||||
"label": "23:00",
|
||||
"value": 23
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "testStep",
|
||||
"name": "Test trigger"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
19
packages/backend/src/apps/schedule/triggers.ts
Normal file
19
packages/backend/src/apps/schedule/triggers.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { IJSONObject } from '@automatisch/types';
|
||||
import EveryHour from './triggers/every-hour';
|
||||
import EveryDay from './triggers/every-day';
|
||||
import EveryWeek from './triggers/every-week';
|
||||
import EveryMonth from './triggers/every-month';
|
||||
|
||||
export default class Triggers {
|
||||
everyHour: EveryHour;
|
||||
everyDay: EveryDay;
|
||||
everyWeek: EveryWeek;
|
||||
everyMonth: EveryMonth;
|
||||
|
||||
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
|
||||
this.everyHour = new EveryHour(parameters);
|
||||
this.everyDay = new EveryDay(parameters);
|
||||
this.everyWeek = new EveryWeek(parameters);
|
||||
this.everyMonth = new EveryMonth(parameters);
|
||||
}
|
||||
}
|
40
packages/backend/src/apps/schedule/triggers/every-day.ts
Normal file
40
packages/backend/src/apps/schedule/triggers/every-day.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { DateTime } from 'luxon';
|
||||
import type { IJSONObject, IJSONValue, ITrigger } from '@automatisch/types';
|
||||
import { cronTimes, getNextCronDateTime, getDateTimeObjectRepresentation } from '../utils';
|
||||
|
||||
export default class EveryDay implements ITrigger {
|
||||
triggersOnWeekend?: boolean;
|
||||
hour?: number;
|
||||
|
||||
constructor(parameters: IJSONObject) {
|
||||
if (parameters.triggersOnWeekend) {
|
||||
this.triggersOnWeekend = parameters.triggersOnWeekend as boolean;
|
||||
}
|
||||
|
||||
if (parameters.hour) {
|
||||
this.hour = parameters.hour as number;
|
||||
}
|
||||
}
|
||||
|
||||
get interval() {
|
||||
if (this.triggersOnWeekend) {
|
||||
return cronTimes.everyDayAt(this.hour);
|
||||
}
|
||||
|
||||
return cronTimes.everyDayExcludingWeekendsAt(this.hour);
|
||||
}
|
||||
|
||||
async run(startDateTime: Date) {
|
||||
const dateTime = DateTime.fromJSDate(startDateTime);
|
||||
const dateTimeObjectRepresentation = getDateTimeObjectRepresentation(dateTime) as IJSONValue;
|
||||
|
||||
return [dateTimeObjectRepresentation] as IJSONValue;
|
||||
}
|
||||
|
||||
async testRun() {
|
||||
const nextCronDateTime = getNextCronDateTime(this.interval);
|
||||
const dateTimeObjectRepresentation = getDateTimeObjectRepresentation(nextCronDateTime) as IJSONValue;
|
||||
|
||||
return [dateTimeObjectRepresentation] as IJSONValue;
|
||||
}
|
||||
}
|
35
packages/backend/src/apps/schedule/triggers/every-hour.ts
Normal file
35
packages/backend/src/apps/schedule/triggers/every-hour.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { DateTime } from 'luxon';
|
||||
import type { IJSONObject, IJSONValue, ITrigger } from '@automatisch/types';
|
||||
import { cronTimes, getNextCronDateTime, getDateTimeObjectRepresentation } from '../utils';
|
||||
|
||||
export default class EveryHour implements ITrigger {
|
||||
triggersOnWeekend?: boolean | string;
|
||||
|
||||
constructor(parameters: IJSONObject) {
|
||||
if (parameters.triggersOnWeekend) {
|
||||
this.triggersOnWeekend = parameters.triggersOnWeekend as string;
|
||||
}
|
||||
}
|
||||
|
||||
get interval() {
|
||||
if (this.triggersOnWeekend) {
|
||||
return cronTimes.everyHour;
|
||||
}
|
||||
|
||||
return cronTimes.everyHourExcludingWeekends;
|
||||
}
|
||||
|
||||
async run(startDateTime: Date) {
|
||||
const dateTime = DateTime.fromJSDate(startDateTime);
|
||||
const dateTimeObjectRepresentation = getDateTimeObjectRepresentation(dateTime) as IJSONValue;
|
||||
|
||||
return [dateTimeObjectRepresentation] as IJSONValue;
|
||||
}
|
||||
|
||||
async testRun() {
|
||||
const nextCronDateTime = getNextCronDateTime(this.interval);
|
||||
const dateTimeObjectRepresentation = getDateTimeObjectRepresentation(nextCronDateTime) as IJSONValue;
|
||||
|
||||
return [dateTimeObjectRepresentation] as IJSONValue;
|
||||
}
|
||||
}
|
36
packages/backend/src/apps/schedule/triggers/every-month.ts
Normal file
36
packages/backend/src/apps/schedule/triggers/every-month.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { DateTime } from 'luxon';
|
||||
import type { IJSONObject, IJSONValue, ITrigger } from '@automatisch/types';
|
||||
import { cronTimes, getNextCronDateTime, getDateTimeObjectRepresentation } from '../utils';
|
||||
|
||||
export default class EveryMonth implements ITrigger {
|
||||
day?: number;
|
||||
hour?: number;
|
||||
|
||||
constructor(parameters: IJSONObject) {
|
||||
if (parameters.day) {
|
||||
this.day = parameters.day as number;
|
||||
}
|
||||
|
||||
if (parameters.hour) {
|
||||
this.hour = parameters.hour as number;
|
||||
}
|
||||
}
|
||||
|
||||
get interval() {
|
||||
return cronTimes.everyMonthOnAndAt(this.day, this.hour);
|
||||
}
|
||||
|
||||
async run(startDateTime: Date) {
|
||||
const dateTime = DateTime.fromJSDate(startDateTime);
|
||||
const dateTimeObjectRepresentation = getDateTimeObjectRepresentation(dateTime) as IJSONValue;
|
||||
|
||||
return [dateTimeObjectRepresentation] as IJSONValue;
|
||||
}
|
||||
|
||||
async testRun() {
|
||||
const nextCronDateTime = getNextCronDateTime(this.interval);
|
||||
const dateTimeObjectRepresentation = getDateTimeObjectRepresentation(nextCronDateTime) as IJSONValue;
|
||||
|
||||
return [dateTimeObjectRepresentation] as IJSONValue;
|
||||
}
|
||||
}
|
36
packages/backend/src/apps/schedule/triggers/every-week.ts
Normal file
36
packages/backend/src/apps/schedule/triggers/every-week.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { DateTime } from 'luxon';
|
||||
import type { IJSONObject, IJSONValue, ITrigger } from '@automatisch/types';
|
||||
import { cronTimes, getNextCronDateTime, getDateTimeObjectRepresentation } from '../utils';
|
||||
|
||||
export default class EveryWeek implements ITrigger {
|
||||
weekday?: number;
|
||||
hour?: number;
|
||||
|
||||
constructor(parameters: IJSONObject) {
|
||||
if (parameters.weekday) {
|
||||
this.weekday = parameters.weekday as number;
|
||||
}
|
||||
|
||||
if (parameters.hour) {
|
||||
this.hour = parameters.hour as number;
|
||||
}
|
||||
}
|
||||
|
||||
get interval() {
|
||||
return cronTimes.everyWeekOnAndAt(this.weekday, this.hour);
|
||||
}
|
||||
|
||||
async run(startDateTime: Date) {
|
||||
const dateTime = DateTime.fromJSDate(startDateTime);
|
||||
const dateTimeObjectRepresentation = getDateTimeObjectRepresentation(dateTime) as IJSONValue;
|
||||
|
||||
return [dateTimeObjectRepresentation] as IJSONValue;
|
||||
}
|
||||
|
||||
async testRun() {
|
||||
const nextCronDateTime = getNextCronDateTime(this.interval);
|
||||
const dateTimeObjectRepresentation = getDateTimeObjectRepresentation(nextCronDateTime) as IJSONValue;
|
||||
|
||||
return [dateTimeObjectRepresentation] as IJSONValue;
|
||||
}
|
||||
}
|
32
packages/backend/src/apps/schedule/utils.ts
Normal file
32
packages/backend/src/apps/schedule/utils.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { DateTime } from 'luxon';
|
||||
import cronParser from 'cron-parser';
|
||||
|
||||
export const cronTimes = {
|
||||
everyHour: '0 * * * *',
|
||||
everyHourExcludingWeekends: '0 * * * 1-5',
|
||||
everyDayAt: (hour: number) => `0 ${hour} * * *`,
|
||||
everyDayExcludingWeekendsAt: (hour: number) => `0 ${hour} * * 1-5`,
|
||||
everyWeekOnAndAt: (weekday: number, hour: number) => `0 ${hour} * * ${weekday}`,
|
||||
everyMonthOnAndAt: (day: number, hour: number) => `0 ${hour} ${day} * *`,
|
||||
};
|
||||
|
||||
export function getNextCronDateTime(cronString: string) {
|
||||
const cronDate = cronParser.parseExpression(cronString);
|
||||
const matchingNextCronDateTime = cronDate.next();
|
||||
const matchingNextDateTime = DateTime.fromJSDate(matchingNextCronDateTime.toDate());
|
||||
|
||||
return matchingNextDateTime;
|
||||
};
|
||||
|
||||
export function getDateTimeObjectRepresentation(dateTime: DateTime) {
|
||||
const defaults = dateTime.toObject();
|
||||
|
||||
return {
|
||||
...defaults,
|
||||
ISO_date_time: dateTime.toISO(),
|
||||
pretty_date: dateTime.toLocaleString(DateTime.DATE_MED),
|
||||
pretty_time: dateTime.toLocaleString(DateTime.TIME_WITH_SECONDS),
|
||||
pretty_day_of_week: dateTime.toFormat('cccc'),
|
||||
day_of_week: dateTime.weekday,
|
||||
};
|
||||
}
|
@@ -1,10 +0,0 @@
|
||||
const cronTimes = {
|
||||
everyHour: '0 * * * *',
|
||||
everyHourExcludingWeekends: '0 * * * 1-5',
|
||||
everyDayAt: (hour: number) => `0 ${hour} * * *`,
|
||||
everyDayExcludingWeekendsAt: (hour: number) => `0 ${hour} * * 1-5`,
|
||||
everyWeekOnAndAt: (weekday: number, hour: number) => `0 ${hour} * * ${weekday}`,
|
||||
everyMonthOnAndAt: (day: number, hour: number) => `0 ${hour} ${day} * *`,
|
||||
};
|
||||
|
||||
export default cronTimes;
|
@@ -1,14 +0,0 @@
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
export default function getDateTimeObjectRepresentation(dateTime: DateTime) {
|
||||
const defaults = dateTime.toObject();
|
||||
|
||||
return {
|
||||
...defaults,
|
||||
ISO_date_time: dateTime.toISO(),
|
||||
pretty_date: dateTime.toLocaleString(DateTime.DATE_MED),
|
||||
pretty_time: dateTime.toLocaleString(DateTime.TIME_WITH_SECONDS),
|
||||
pretty_day_of_week: dateTime.toFormat('cccc'),
|
||||
day_of_week: dateTime.weekday,
|
||||
};
|
||||
}
|
@@ -1,10 +0,0 @@
|
||||
import { DateTime } from 'luxon';
|
||||
import cronParser from 'cron-parser';
|
||||
|
||||
export default function getNextCronDateTime(cronString: string) {
|
||||
const cronDate = cronParser.parseExpression(cronString);
|
||||
const matchingNextCronDateTime = cronDate.next();
|
||||
const matchingNextDateTime = DateTime.fromJSDate(matchingNextCronDateTime.toDate());
|
||||
|
||||
return matchingNextDateTime;
|
||||
};
|
@@ -1,10 +0,0 @@
|
||||
export default {
|
||||
name: "Scheduler",
|
||||
key: "scheduler",
|
||||
iconUrl: "{BASE_URL}/apps/scheduler/assets/favicon.svg",
|
||||
docUrl: "https://automatisch.io/docs/scheduler",
|
||||
authDocUrl: "https://automatisch.io/docs/connections/scheduler",
|
||||
primaryColor: "0059F7",
|
||||
supportsConnections: false,
|
||||
requiresAuthentication: false,
|
||||
};
|
@@ -1,170 +0,0 @@
|
||||
import { DateTime } from 'luxon';
|
||||
import { IGlobalVariable, IJSONValue } from '@automatisch/types';
|
||||
import cronTimes from '../../common/cron-times';
|
||||
import getNextCronDateTime from '../../common/get-next-cron-date-time';
|
||||
import getDateTimeObjectRepresentation from '../../common/get-date-time-object';
|
||||
|
||||
export default {
|
||||
name: 'Every day',
|
||||
key: 'everyDay',
|
||||
description: 'Triggers every day.',
|
||||
substeps: [
|
||||
{
|
||||
key: 'chooseTrigger',
|
||||
name: 'Set up a trigger',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Trigger on weekends?',
|
||||
key: 'triggersOnWeekend',
|
||||
type: 'dropdown',
|
||||
description: 'Should this flow trigger on Saturday and Sunday?',
|
||||
required: true,
|
||||
value: true,
|
||||
variables: false,
|
||||
options: [
|
||||
{
|
||||
label: 'Yes',
|
||||
value: true
|
||||
},
|
||||
{
|
||||
label: 'No',
|
||||
value: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Time of day',
|
||||
key: 'hour',
|
||||
type: 'dropdown',
|
||||
required: true,
|
||||
value: null,
|
||||
variables: false,
|
||||
options: [
|
||||
{
|
||||
label: '00:00',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
label: '01:00',
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
label: '02:00',
|
||||
value: 2
|
||||
},
|
||||
{
|
||||
label: '03:00',
|
||||
value: 3
|
||||
},
|
||||
{
|
||||
label: '04:00',
|
||||
value: 4
|
||||
},
|
||||
{
|
||||
label: '05:00',
|
||||
value: 5
|
||||
},
|
||||
{
|
||||
label: '06:00',
|
||||
value: 6
|
||||
},
|
||||
{
|
||||
label: '07:00',
|
||||
value: 7
|
||||
},
|
||||
{
|
||||
label: '08:00',
|
||||
value: 8
|
||||
},
|
||||
{
|
||||
label: '09:00',
|
||||
value: 9
|
||||
},
|
||||
{
|
||||
label: '10:00',
|
||||
value: 10
|
||||
},
|
||||
{
|
||||
label: '11:00',
|
||||
value: 11
|
||||
},
|
||||
{
|
||||
label: '12:00',
|
||||
value: 12
|
||||
},
|
||||
{
|
||||
label: '13:00',
|
||||
value: 13
|
||||
},
|
||||
{
|
||||
label: '14:00',
|
||||
value: 14
|
||||
},
|
||||
{
|
||||
label: '15:00',
|
||||
value: 15
|
||||
},
|
||||
{
|
||||
label: '16:00',
|
||||
value: 16
|
||||
},
|
||||
{
|
||||
label: '17:00',
|
||||
value: 17
|
||||
},
|
||||
{
|
||||
label: '18:00',
|
||||
value: 18
|
||||
},
|
||||
{
|
||||
label: '19:00',
|
||||
value: 19
|
||||
},
|
||||
{
|
||||
label: '20:00',
|
||||
value: 20
|
||||
},
|
||||
{
|
||||
label: '21:00',
|
||||
value: 21
|
||||
},
|
||||
{
|
||||
label: '22:00',
|
||||
value: 22
|
||||
},
|
||||
{
|
||||
label: '23:00',
|
||||
value: 23
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'testStep',
|
||||
name: 'Test trigger'
|
||||
}
|
||||
],
|
||||
|
||||
getInterval(parameters: IGlobalVariable["db"]["step"]["parameters"]) {
|
||||
if (parameters.triggersOnWeekend as boolean) {
|
||||
return cronTimes.everyDayAt(parameters.hour as number);
|
||||
}
|
||||
|
||||
return cronTimes.everyDayExcludingWeekendsAt(parameters.hour as number);
|
||||
},
|
||||
|
||||
async run($: IGlobalVariable, startDateTime: Date) {
|
||||
const dateTime = DateTime.fromJSDate(startDateTime);
|
||||
const dateTimeObjectRepresentation = getDateTimeObjectRepresentation(dateTime) as IJSONValue;
|
||||
|
||||
return { data: [dateTimeObjectRepresentation] };
|
||||
},
|
||||
|
||||
async testRun($: IGlobalVariable) {
|
||||
const nextCronDateTime = getNextCronDateTime(this.getInterval($.db.step.parameters));
|
||||
const dateTimeObjectRepresentation = getDateTimeObjectRepresentation(nextCronDateTime) as IJSONValue;
|
||||
|
||||
return { data: [dateTimeObjectRepresentation] };
|
||||
},
|
||||
};
|
@@ -1,64 +0,0 @@
|
||||
import { DateTime } from 'luxon';
|
||||
import { IGlobalVariable, IJSONValue } from '@automatisch/types';
|
||||
import cronTimes from '../../common/cron-times';
|
||||
import getNextCronDateTime from '../../common/get-next-cron-date-time';
|
||||
import getDateTimeObjectRepresentation from '../../common/get-date-time-object';
|
||||
|
||||
export default {
|
||||
name: 'Every hour',
|
||||
key: 'everyHour',
|
||||
description: 'Triggers every hour.',
|
||||
substeps: [
|
||||
{
|
||||
key: 'chooseTrigger',
|
||||
name: 'Set up a trigger',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Trigger on weekends?',
|
||||
key: 'triggersOnWeekend',
|
||||
type: 'dropdown',
|
||||
description: 'Should this flow trigger on Saturday and Sunday?',
|
||||
required: true,
|
||||
value: true,
|
||||
variables: false,
|
||||
options: [
|
||||
{
|
||||
label: 'Yes',
|
||||
value: true
|
||||
},
|
||||
{
|
||||
label: 'No',
|
||||
value: false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'testStep',
|
||||
name: 'Test trigger'
|
||||
}
|
||||
],
|
||||
|
||||
getInterval(parameters: IGlobalVariable["db"]["step"]["parameters"]) {
|
||||
if (parameters.triggersOnWeekend) {
|
||||
return cronTimes.everyHour
|
||||
}
|
||||
|
||||
return cronTimes.everyHourExcludingWeekends;
|
||||
},
|
||||
|
||||
async run($: IGlobalVariable, startDateTime: Date) {
|
||||
const dateTime = DateTime.fromJSDate(startDateTime);
|
||||
const dateTimeObjectRepresentation = getDateTimeObjectRepresentation(dateTime) as IJSONValue;
|
||||
|
||||
return { data: [dateTimeObjectRepresentation] };
|
||||
},
|
||||
|
||||
async testRun($: IGlobalVariable) {
|
||||
const nextCronDateTime = getNextCronDateTime(this.getInterval($.db.step.parameters));
|
||||
const dateTimeObjectRepresentation = getDateTimeObjectRepresentation(nextCronDateTime) as IJSONValue;
|
||||
|
||||
return { data: [dateTimeObjectRepresentation] };
|
||||
},
|
||||
};
|
@@ -1,283 +0,0 @@
|
||||
import { DateTime } from 'luxon';
|
||||
import { IGlobalVariable, IJSONValue } from '@automatisch/types';
|
||||
import cronTimes from '../../common/cron-times';
|
||||
import getNextCronDateTime from '../../common/get-next-cron-date-time';
|
||||
import getDateTimeObjectRepresentation from '../../common/get-date-time-object';
|
||||
|
||||
export default {
|
||||
name: 'Every month',
|
||||
key: 'everyMonth',
|
||||
description: 'Triggers every month.',
|
||||
substeps: [
|
||||
{
|
||||
key: 'chooseTrigger',
|
||||
name: 'Set up a trigger',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Day of the month',
|
||||
key: 'day',
|
||||
type: 'dropdown',
|
||||
required: true,
|
||||
value: null,
|
||||
variables: false,
|
||||
options: [
|
||||
{
|
||||
label: 1,
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
label: 2,
|
||||
value: 2
|
||||
},
|
||||
{
|
||||
label: 3,
|
||||
value: 3
|
||||
},
|
||||
{
|
||||
label: 4,
|
||||
value: 4
|
||||
},
|
||||
{
|
||||
label: 5,
|
||||
value: 5
|
||||
},
|
||||
{
|
||||
label: 6,
|
||||
value: 6
|
||||
},
|
||||
{
|
||||
label: 7,
|
||||
value: 7
|
||||
},
|
||||
{
|
||||
label: 8,
|
||||
value: 8
|
||||
},
|
||||
{
|
||||
label: 9,
|
||||
value: 9
|
||||
},
|
||||
{
|
||||
label: 10,
|
||||
value: 10
|
||||
},
|
||||
{
|
||||
label: 11,
|
||||
value: 11
|
||||
},
|
||||
{
|
||||
label: 12,
|
||||
value: 12
|
||||
},
|
||||
{
|
||||
label: 13,
|
||||
value: 13
|
||||
},
|
||||
{
|
||||
label: 14,
|
||||
value: 14
|
||||
},
|
||||
{
|
||||
label: 15,
|
||||
value: 15
|
||||
},
|
||||
{
|
||||
label: 16,
|
||||
value: 16
|
||||
},
|
||||
{
|
||||
label: 17,
|
||||
value: 17
|
||||
},
|
||||
{
|
||||
label: 18,
|
||||
value: 18
|
||||
},
|
||||
{
|
||||
label: 19,
|
||||
value: 19
|
||||
},
|
||||
{
|
||||
label: 20,
|
||||
value: 20
|
||||
},
|
||||
{
|
||||
label: 21,
|
||||
value: 21
|
||||
},
|
||||
{
|
||||
label: 22,
|
||||
value: 22
|
||||
},
|
||||
{
|
||||
label: 23,
|
||||
value: 23
|
||||
},
|
||||
{
|
||||
label: 24,
|
||||
value: 24
|
||||
},
|
||||
{
|
||||
label: 25,
|
||||
value: 25
|
||||
},
|
||||
{
|
||||
label: 26,
|
||||
value: 26
|
||||
},
|
||||
{
|
||||
label: 27,
|
||||
value: 27
|
||||
},
|
||||
{
|
||||
label: 28,
|
||||
value: 28
|
||||
},
|
||||
{
|
||||
label: 29,
|
||||
value: 29
|
||||
},
|
||||
{
|
||||
label: 30,
|
||||
value: 30
|
||||
},
|
||||
{
|
||||
label: 31,
|
||||
value: 31
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Time of day',
|
||||
key: 'hour',
|
||||
type: 'dropdown',
|
||||
required: true,
|
||||
value: null,
|
||||
variables: false,
|
||||
options: [
|
||||
{
|
||||
label: '00:00',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
label: '01:00',
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
label: '02:00',
|
||||
value: 2
|
||||
},
|
||||
{
|
||||
label: '03:00',
|
||||
value: 3
|
||||
},
|
||||
{
|
||||
label: '04:00',
|
||||
value: 4
|
||||
},
|
||||
{
|
||||
label: '05:00',
|
||||
value: 5
|
||||
},
|
||||
{
|
||||
label: '06:00',
|
||||
value: 6
|
||||
},
|
||||
{
|
||||
label: '07:00',
|
||||
value: 7
|
||||
},
|
||||
{
|
||||
label: '08:00',
|
||||
value: 8
|
||||
},
|
||||
{
|
||||
label: '09:00',
|
||||
value: 9
|
||||
},
|
||||
{
|
||||
label: '10:00',
|
||||
value: 10
|
||||
},
|
||||
{
|
||||
label: '11:00',
|
||||
value: 11
|
||||
},
|
||||
{
|
||||
label: '12:00',
|
||||
value: 12
|
||||
},
|
||||
{
|
||||
label: '13:00',
|
||||
value: 13
|
||||
},
|
||||
{
|
||||
label: '14:00',
|
||||
value: 14
|
||||
},
|
||||
{
|
||||
label: '15:00',
|
||||
value: 15
|
||||
},
|
||||
{
|
||||
label: '16:00',
|
||||
value: 16
|
||||
},
|
||||
{
|
||||
label: '17:00',
|
||||
value: 17
|
||||
},
|
||||
{
|
||||
label: '18:00',
|
||||
value: 18
|
||||
},
|
||||
{
|
||||
label: '19:00',
|
||||
value: 19
|
||||
},
|
||||
{
|
||||
label: '20:00',
|
||||
value: 20
|
||||
},
|
||||
{
|
||||
label: '21:00',
|
||||
value: 21
|
||||
},
|
||||
{
|
||||
label: '22:00',
|
||||
value: 22
|
||||
},
|
||||
{
|
||||
label: '23:00',
|
||||
value: 23
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'testStep',
|
||||
name: 'Test trigger'
|
||||
}
|
||||
],
|
||||
|
||||
getInterval(parameters: IGlobalVariable["db"]["step"]["parameters"]) {
|
||||
const interval = cronTimes.everyMonthOnAndAt(parameters.day as number, parameters.hour as number);
|
||||
|
||||
return interval;
|
||||
},
|
||||
|
||||
async run($: IGlobalVariable, startDateTime: Date) {
|
||||
const dateTime = DateTime.fromJSDate(startDateTime);
|
||||
const dateTimeObjectRepresentation = getDateTimeObjectRepresentation(dateTime) as IJSONValue;
|
||||
|
||||
return { data: [dateTimeObjectRepresentation] };
|
||||
},
|
||||
|
||||
async testRun($: IGlobalVariable) {
|
||||
const nextCronDateTime = getNextCronDateTime(this.getInterval($.db.step.parameters));
|
||||
const dateTimeObjectRepresentation = getDateTimeObjectRepresentation(nextCronDateTime) as IJSONValue;
|
||||
|
||||
return { data: [dateTimeObjectRepresentation] };
|
||||
},
|
||||
};
|
@@ -1,187 +0,0 @@
|
||||
import { DateTime } from 'luxon';
|
||||
import { IGlobalVariable, IJSONValue } from '@automatisch/types';
|
||||
import cronTimes from '../../common/cron-times';
|
||||
import getNextCronDateTime from '../../common/get-next-cron-date-time';
|
||||
import getDateTimeObjectRepresentation from '../../common/get-date-time-object';
|
||||
|
||||
export default {
|
||||
name: 'Every week',
|
||||
key: 'everyWeek',
|
||||
description: 'Triggers every week.',
|
||||
substeps: [
|
||||
{
|
||||
key: 'chooseTrigger',
|
||||
name: 'Set up a trigger',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Day of the week',
|
||||
key: 'weekday',
|
||||
type: 'dropdown',
|
||||
required: true,
|
||||
value: null,
|
||||
variables: false,
|
||||
options: [
|
||||
{
|
||||
label: 'Monday',
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
label: 'Tuesday',
|
||||
value: 2
|
||||
},
|
||||
{
|
||||
label: 'Wednesday',
|
||||
value: 3
|
||||
},
|
||||
{
|
||||
label: 'Thursday',
|
||||
value: 4
|
||||
},
|
||||
{
|
||||
label: 'Friday',
|
||||
value: 5
|
||||
},
|
||||
{
|
||||
label: 'Saturday',
|
||||
value: 6
|
||||
},
|
||||
{
|
||||
label: 'Sunday',
|
||||
value: 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Time of day',
|
||||
key: 'hour',
|
||||
type: 'dropdown',
|
||||
required: true,
|
||||
value: null,
|
||||
variables: false,
|
||||
options: [
|
||||
{
|
||||
label: '00:00',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
label: '01:00',
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
label: '02:00',
|
||||
value: 2
|
||||
},
|
||||
{
|
||||
label: '03:00',
|
||||
value: 3
|
||||
},
|
||||
{
|
||||
label: '04:00',
|
||||
value: 4
|
||||
},
|
||||
{
|
||||
label: '05:00',
|
||||
value: 5
|
||||
},
|
||||
{
|
||||
label: '06:00',
|
||||
value: 6
|
||||
},
|
||||
{
|
||||
label: '07:00',
|
||||
value: 7
|
||||
},
|
||||
{
|
||||
label: '08:00',
|
||||
value: 8
|
||||
},
|
||||
{
|
||||
label: '09:00',
|
||||
value: 9
|
||||
},
|
||||
{
|
||||
label: '10:00',
|
||||
value: 10
|
||||
},
|
||||
{
|
||||
label: '11:00',
|
||||
value: 11
|
||||
},
|
||||
{
|
||||
label: '12:00',
|
||||
value: 12
|
||||
},
|
||||
{
|
||||
label: '13:00',
|
||||
value: 13
|
||||
},
|
||||
{
|
||||
label: '14:00',
|
||||
value: 14
|
||||
},
|
||||
{
|
||||
label: '15:00',
|
||||
value: 15
|
||||
},
|
||||
{
|
||||
label: '16:00',
|
||||
value: 16
|
||||
},
|
||||
{
|
||||
label: '17:00',
|
||||
value: 17
|
||||
},
|
||||
{
|
||||
label: '18:00',
|
||||
value: 18
|
||||
},
|
||||
{
|
||||
label: '19:00',
|
||||
value: 19
|
||||
},
|
||||
{
|
||||
label: '20:00',
|
||||
value: 20
|
||||
},
|
||||
{
|
||||
label: '21:00',
|
||||
value: 21
|
||||
},
|
||||
{
|
||||
label: '22:00',
|
||||
value: 22
|
||||
},
|
||||
{
|
||||
label: '23:00',
|
||||
value: 23
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'testStep',
|
||||
name: 'Test trigger'
|
||||
}
|
||||
],
|
||||
|
||||
getInterval(parameters: IGlobalVariable["db"]["step"]["parameters"]) {
|
||||
const interval = cronTimes.everyWeekOnAndAt(parameters.weekday as number, parameters.hour as number);
|
||||
|
||||
return interval;
|
||||
},
|
||||
|
||||
async run($: IGlobalVariable, startDateTime: Date) {
|
||||
const dateTime = DateTime.fromJSDate(startDateTime);
|
||||
const dateTimeObjectRepresentation = getDateTimeObjectRepresentation(dateTime) as IJSONValue;
|
||||
|
||||
return { data: [dateTimeObjectRepresentation] };
|
||||
},
|
||||
|
||||
async testRun($: IGlobalVariable) {
|
||||
const nextCronDateTime = getNextCronDateTime(this.getInterval($.db.step.parameters));
|
||||
const dateTimeObjectRepresentation = getDateTimeObjectRepresentation(nextCronDateTime) as IJSONValue;
|
||||
|
||||
return { data: [dateTimeObjectRepresentation] };
|
||||
},
|
||||
};
|
13
packages/backend/src/apps/slack/actions.ts
Normal file
13
packages/backend/src/apps/slack/actions.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import SendMessageToChannel from './actions/send-message-to-channel';
|
||||
import { IJSONObject } from '@automatisch/types';
|
||||
|
||||
export default class Actions {
|
||||
sendMessageToChannel: SendMessageToChannel;
|
||||
|
||||
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
|
||||
this.sendMessageToChannel = new SendMessageToChannel(
|
||||
connectionData,
|
||||
parameters
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,50 +0,0 @@
|
||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
||||
|
||||
type FindMessageOptions = {
|
||||
query: string;
|
||||
sortBy: string;
|
||||
sortDirection: string;
|
||||
count: number;
|
||||
};
|
||||
|
||||
const findMessage = async ($: IGlobalVariable, options: FindMessageOptions) => {
|
||||
const message: {
|
||||
data?: IJSONObject;
|
||||
error?: IJSONObject;
|
||||
} = {};
|
||||
|
||||
const headers = {
|
||||
Authorization: `Bearer ${$.auth.data.accessToken}`,
|
||||
};
|
||||
|
||||
const params = {
|
||||
query: options.query,
|
||||
sort: options.sortBy,
|
||||
sort_dir: options.sortDirection,
|
||||
count: options.count || 1,
|
||||
};
|
||||
|
||||
const response = await $.http.get('/search.messages', {
|
||||
headers,
|
||||
params,
|
||||
});
|
||||
|
||||
if (response.integrationError) {
|
||||
message.error = response.integrationError;
|
||||
return message;
|
||||
}
|
||||
|
||||
const data = response.data;
|
||||
|
||||
if (!data.ok) {
|
||||
message.error = data;
|
||||
return message;
|
||||
}
|
||||
|
||||
const messages = data.messages.matches;
|
||||
message.data = messages?.[0];
|
||||
|
||||
return message;
|
||||
};
|
||||
|
||||
export default findMessage;
|
@@ -1,90 +0,0 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
import findMessage from './find-message';
|
||||
|
||||
export default {
|
||||
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',
|
||||
},
|
||||
],
|
||||
|
||||
async run($: IGlobalVariable) {
|
||||
const parameters = $.db.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 findMessage($, {
|
||||
query,
|
||||
sortBy,
|
||||
sortDirection,
|
||||
count,
|
||||
});
|
||||
|
||||
return messages;
|
||||
},
|
||||
};
|
@@ -1,59 +0,0 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
import postMessage from './post-message';
|
||||
|
||||
export default {
|
||||
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',
|
||||
},
|
||||
],
|
||||
|
||||
async run($: IGlobalVariable) {
|
||||
const channelId = $.db.step.parameters.channel as string;
|
||||
const text = $.db.step.parameters.message as string;
|
||||
|
||||
const message = await postMessage($, channelId, text);
|
||||
|
||||
return message;
|
||||
},
|
||||
};
|
@@ -1,37 +0,0 @@
|
||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
||||
|
||||
const postMessage = async (
|
||||
$: IGlobalVariable,
|
||||
channelId: string,
|
||||
text: string
|
||||
) => {
|
||||
const message: {
|
||||
data: IJSONObject | null | undefined;
|
||||
error: IJSONObject | null | undefined;
|
||||
} = {
|
||||
data: null,
|
||||
error: null,
|
||||
};
|
||||
|
||||
const headers = {
|
||||
Authorization: `Bearer ${$.auth.data.accessToken}`,
|
||||
};
|
||||
|
||||
const params = {
|
||||
channel: channelId,
|
||||
text,
|
||||
};
|
||||
|
||||
const response = await $.http.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;
|
||||
};
|
||||
|
||||
export default postMessage;
|
@@ -0,0 +1,21 @@
|
||||
import { WebClient } from '@slack/web-api';
|
||||
import { IJSONObject } from '@automatisch/types';
|
||||
|
||||
export default class SendMessageToChannel {
|
||||
client: WebClient;
|
||||
parameters: IJSONObject;
|
||||
|
||||
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
|
||||
this.client = new WebClient(connectionData.accessToken as string);
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
async run() {
|
||||
const result = await this.client.chat.postMessage({
|
||||
channel: this.parameters.channel as string,
|
||||
text: this.parameters.message as string,
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
aria-label="Slack" role="img"
|
||||
viewBox="0 0 512 512"><rect
|
||||
width="512" height="512"
|
||||
rx="15%"
|
||||
fill="#fff"/><g fill="#e01e5a"><path id="a" d="M149 305a39 39 0 01-78 0c0-22 17 -39 39 -39h39zM168 305a39 39 0 0178 0v97a39 39 0 01-78 0z"/></g><use xlink:href="#a" fill="#36c5f0" transform="rotate(90,256,256)"/><use xlink:href="#a" fill="#2eb67d" transform="rotate(180,256,256)"/><use xlink:href="#a" fill="#ecb22e" transform="rotate(270,256,256)"/></svg>
|
||||
fill="#fff"/><g fill="#e01e5a"><path id="a" d="M149 305a39 39 0 01-78 0c0-22 17 -39 39 -39h39zM168 305a39 39 0 0178 0v97a39 39 0 01-78 0z"/></g><use xlink:href="#a" fill="#36c5f0" transform="rotate(90,256,256)"/><use xlink:href="#a" fill="#2eb67d" transform="rotate(180,256,256)"/><use xlink:href="#a" fill="#ecb22e" transform="rotate(270,256,256)"/></svg>
|
||||
|
Before Width: | Height: | Size: 531 B After Width: | Height: | Size: 533 B |
@@ -1,100 +0,0 @@
|
||||
import verifyCredentials from './verify-credentials';
|
||||
import isStillVerified from './is-still-verified';
|
||||
|
||||
export default {
|
||||
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}',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
verifyCredentials,
|
||||
isStillVerified,
|
||||
};
|
@@ -1,12 +0,0 @@
|
||||
import verifyCredentials from './verify-credentials';
|
||||
|
||||
const isStillVerified = async ($: any) => {
|
||||
try {
|
||||
await verifyCredentials($);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export default isStillVerified;
|
@@ -1,34 +0,0 @@
|
||||
import qs from 'qs';
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const verifyCredentials = async ($: IGlobalVariable) => {
|
||||
const headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
};
|
||||
|
||||
const stringifiedBody = qs.stringify({
|
||||
token: $.auth.data.accessToken,
|
||||
});
|
||||
|
||||
const response = await $.http.post('/auth.test', stringifiedBody, {
|
||||
headers,
|
||||
});
|
||||
|
||||
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)`
|
||||
);
|
||||
}
|
||||
|
||||
const { bot_id: botId, user: screenName } = response.data;
|
||||
|
||||
$.auth.set({
|
||||
botId,
|
||||
screenName,
|
||||
token: $.auth.data.accessToken,
|
||||
});
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export default verifyCredentials;
|
39
packages/backend/src/apps/slack/authentication.ts
Normal file
39
packages/backend/src/apps/slack/authentication.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import type { IAuthentication, IApp, IJSONObject } from '@automatisch/types';
|
||||
import { WebClient } from '@slack/web-api';
|
||||
|
||||
export default class Authentication implements IAuthentication {
|
||||
appData: IApp;
|
||||
connectionData: IJSONObject;
|
||||
client: WebClient;
|
||||
|
||||
constructor(appData: IApp, connectionData: IJSONObject) {
|
||||
this.client = new WebClient();
|
||||
|
||||
this.connectionData = connectionData;
|
||||
this.appData = appData;
|
||||
}
|
||||
|
||||
async verifyCredentials() {
|
||||
const { bot_id: botId, user: screenName } = await this.client.auth.test({
|
||||
token: this.connectionData.accessToken as string,
|
||||
});
|
||||
|
||||
return {
|
||||
botId,
|
||||
screenName,
|
||||
token: this.connectionData.accessToken,
|
||||
};
|
||||
}
|
||||
|
||||
async isStillVerified() {
|
||||
try {
|
||||
await this.client.auth.test({
|
||||
token: this.connectionData.accessToken as string,
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
10
packages/backend/src/apps/slack/data.ts
Normal file
10
packages/backend/src/apps/slack/data.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import ListChannels from './data/list-channels';
|
||||
import { IJSONObject } from '@automatisch/types';
|
||||
|
||||
export default class Data {
|
||||
listChannels: ListChannels;
|
||||
|
||||
constructor(connectionData: IJSONObject) {
|
||||
this.listChannels = new ListChannels(connectionData);
|
||||
}
|
||||
}
|
21
packages/backend/src/apps/slack/data/list-channels.ts
Normal file
21
packages/backend/src/apps/slack/data/list-channels.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { IJSONObject } from '@automatisch/types';
|
||||
import { WebClient } from '@slack/web-api';
|
||||
|
||||
export default class ListChannels {
|
||||
client: WebClient;
|
||||
|
||||
constructor(connectionData: IJSONObject) {
|
||||
this.client = new WebClient(connectionData.accessToken as string);
|
||||
}
|
||||
|
||||
async run() {
|
||||
const { channels } = await this.client.conversations.list();
|
||||
|
||||
return channels.map((channel) => {
|
||||
return {
|
||||
value: channel.id,
|
||||
name: channel.name,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,41 +0,0 @@
|
||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
||||
|
||||
export default {
|
||||
name: 'List channels',
|
||||
key: 'listChannels',
|
||||
|
||||
async run($: IGlobalVariable) {
|
||||
const channels: {
|
||||
data: IJSONObject[];
|
||||
error: IJSONObject | null;
|
||||
} = {
|
||||
data: [],
|
||||
error: null,
|
||||
};
|
||||
|
||||
const response = await $.http.get('/conversations.list', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${$.auth.data.accessToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (response.integrationError) {
|
||||
channels.error = response.integrationError;
|
||||
return channels;
|
||||
}
|
||||
|
||||
if (response.data.ok === 'false') {
|
||||
channels.error = response.data.error;
|
||||
return channels;
|
||||
}
|
||||
|
||||
channels.data = response.data.channels.map((channel: IJSONObject) => {
|
||||
return {
|
||||
value: channel.id,
|
||||
name: channel.name,
|
||||
};
|
||||
});
|
||||
|
||||
return channels;
|
||||
},
|
||||
};
|
@@ -1,8 +1,25 @@
|
||||
export default {
|
||||
name: 'Slack',
|
||||
key: 'slack',
|
||||
iconUrl: '{BASE_URL}/apps/slack/assets/favicon.svg',
|
||||
authDocUrl: 'https://automatisch.io/docs/connections/slack',
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://slack.com/api',
|
||||
};
|
||||
import {
|
||||
IService,
|
||||
IAuthentication,
|
||||
IApp,
|
||||
IJSONObject,
|
||||
} from '@automatisch/types';
|
||||
import Authentication from './authentication';
|
||||
import Actions from './actions';
|
||||
import Data from './data';
|
||||
|
||||
export default class Slack implements IService {
|
||||
authenticationClient: IAuthentication;
|
||||
actions: Actions;
|
||||
data: Data;
|
||||
|
||||
constructor(
|
||||
appData: IApp,
|
||||
connectionData: IJSONObject,
|
||||
parameters: IJSONObject
|
||||
) {
|
||||
this.authenticationClient = new Authentication(appData, connectionData);
|
||||
this.data = new Data(connectionData);
|
||||
this.actions = new Actions(connectionData, parameters);
|
||||
}
|
||||
}
|
||||
|
149
packages/backend/src/apps/slack/info.json
Normal file
149
packages/backend/src/apps/slack/info.json
Normal file
@@ -0,0 +1,149 @@
|
||||
{
|
||||
"name": "Slack",
|
||||
"key": "slack",
|
||||
"iconUrl": "{BASE_URL}/apps/slack/assets/favicon.svg",
|
||||
"docUrl": "https://automatisch.io/docs/slack",
|
||||
"primaryColor": "2DAAE1",
|
||||
"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.",
|
||||
"docUrl": "https://automatisch.io/docs/slack#access-token",
|
||||
"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}"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"name": "Send a message to channel",
|
||||
"key": "sendMessageToChannel",
|
||||
"description": "Send a message to a specific channel you specify.",
|
||||
"substeps": [
|
||||
{
|
||||
"key": "chooseAccount",
|
||||
"name": "Choose account"
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@@ -4,7 +4,6 @@
|
||||
"iconUrl": "{BASE_URL}/apps/smtp/assets/favicon.svg",
|
||||
"docUrl": "https://automatisch.io/docs/smtp",
|
||||
"primaryColor": "2DAAE1",
|
||||
"supportsConnections": true,
|
||||
"fields": [
|
||||
{
|
||||
"key": "host",
|
||||
|
@@ -4,7 +4,6 @@
|
||||
"iconUrl": "{BASE_URL}/apps/twilio/assets/favicon.svg",
|
||||
"docUrl": "https://automatisch.io/docs/twilio",
|
||||
"primaryColor": "f22f46",
|
||||
"supportsConnections": true,
|
||||
"fields": [
|
||||
{
|
||||
"key": "accountSid",
|
||||
|
@@ -4,7 +4,6 @@
|
||||
"iconUrl": "{BASE_URL}/apps/twitch/assets/favicon.svg",
|
||||
"docUrl": "https://automatisch.io/docs/twitch",
|
||||
"primaryColor": "2DAAE1",
|
||||
"supportsConnections": true,
|
||||
"fields": [
|
||||
{
|
||||
"key": "oAuthRedirectUrl",
|
||||
|
10
packages/backend/src/apps/twitter/actions.ts
Normal file
10
packages/backend/src/apps/twitter/actions.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import CreateTweet from './actions/create-tweet';
|
||||
import { IJSONObject } from '@automatisch/types';
|
||||
|
||||
export default class Actions {
|
||||
createTweet: CreateTweet;
|
||||
|
||||
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
|
||||
this.createTweet = new CreateTweet(connectionData, parameters);
|
||||
}
|
||||
}
|
23
packages/backend/src/apps/twitter/actions/create-tweet.ts
Normal file
23
packages/backend/src/apps/twitter/actions/create-tweet.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import TwitterApi, { TwitterApiTokens } from 'twitter-api-v2';
|
||||
import { IJSONObject } from '@automatisch/types';
|
||||
|
||||
export default class CreateTweet {
|
||||
client: TwitterApi;
|
||||
parameters: IJSONObject;
|
||||
|
||||
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
|
||||
this.client = new TwitterApi({
|
||||
appKey: connectionData.consumerKey,
|
||||
appSecret: connectionData.consumerSecret,
|
||||
accessToken: connectionData.accessToken,
|
||||
accessSecret: connectionData.accessSecret,
|
||||
} as TwitterApiTokens);
|
||||
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
async run() {
|
||||
const tweet = await this.client.v1.tweet(this.parameters.tweet as string);
|
||||
return tweet;
|
||||
}
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-label="Twitter" role="img" viewBox="0 0 512 512">
|
||||
<rect width="512" height="512" rx="15%" fill="#1da1f2"/>
|
||||
<path fill="#fff" d="M437 152a72 72 0 01-40 12a72 72 0 0032-40a72 72 0 01-45 17a72 72 0 00-122 65a200 200 0 01-145-74a72 72 0 0022 94a72 72 0 01-32-7a72 72 0 0056 69a72 72 0 01-32 1a72 72 0 0067 50a200 200 0 01-105 29a200 200 0 00309-179a200 200 0 0035-37"/>
|
||||
</svg>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 421 B After Width: | Height: | Size: 422 B |
@@ -1,35 +0,0 @@
|
||||
import generateRequest from '../common/generate-request';
|
||||
import { IJSONObject, IField, IGlobalVariable } from '@automatisch/types';
|
||||
import { URLSearchParams } from 'url';
|
||||
|
||||
export default async function createAuthData($: IGlobalVariable) {
|
||||
try {
|
||||
const oauthRedirectUrlField = $.app.auth.fields.find(
|
||||
(field: IField) => field.key == 'oAuthRedirectUrl'
|
||||
);
|
||||
|
||||
const callbackUrl = oauthRedirectUrlField.value;
|
||||
|
||||
const response = await generateRequest($, {
|
||||
requestPath: '/oauth/request_token',
|
||||
method: 'POST',
|
||||
data: { oauth_callback: callbackUrl },
|
||||
});
|
||||
|
||||
const responseData = Object.fromEntries(new URLSearchParams(response.data));
|
||||
|
||||
await $.auth.set({
|
||||
url: `${$.app.baseUrl}/oauth/authorize?oauth_token=${responseData.oauth_token}`,
|
||||
accessToken: responseData.oauth_token,
|
||||
accessSecret: responseData.oauth_token_secret,
|
||||
});
|
||||
} catch (error) {
|
||||
const errorMessages = error.response.data.errors
|
||||
.map((error: IJSONObject) => error.message)
|
||||
.join(' ');
|
||||
|
||||
throw new Error(
|
||||
`Error occured while verifying credentials: ${errorMessages}`
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,219 +0,0 @@
|
||||
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/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}',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
createAuthData,
|
||||
verifyCredentials,
|
||||
isStillVerified,
|
||||
};
|
@@ -1,13 +0,0 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
import getCurrentUser from '../common/get-current-user';
|
||||
|
||||
const isStillVerified = async ($: IGlobalVariable) => {
|
||||
try {
|
||||
await getCurrentUser($);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export default isStillVerified;
|
@@ -1,24 +0,0 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
import { URLSearchParams } from 'url';
|
||||
|
||||
const verifyCredentials = async ($: IGlobalVariable) => {
|
||||
try {
|
||||
const response = await $.http.post(
|
||||
`/oauth/access_token?oauth_verifier=${$.auth.data.oauthVerifier}&oauth_token=${$.auth.data.accessToken}`,
|
||||
null
|
||||
);
|
||||
|
||||
const responseData = Object.fromEntries(new URLSearchParams(response.data));
|
||||
|
||||
await $.auth.set({
|
||||
accessToken: responseData.oauth_token,
|
||||
accessSecret: responseData.oauth_token_secret,
|
||||
userId: responseData.user_id,
|
||||
screenName: responseData.screen_name,
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error(error.response.data);
|
||||
}
|
||||
};
|
||||
|
||||
export default verifyCredentials;
|
66
packages/backend/src/apps/twitter/authentication.ts
Normal file
66
packages/backend/src/apps/twitter/authentication.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import type {
|
||||
IAuthentication,
|
||||
IApp,
|
||||
IField,
|
||||
IJSONObject,
|
||||
} from '@automatisch/types';
|
||||
import TwitterApi, { TwitterApiTokens } from 'twitter-api-v2';
|
||||
|
||||
export default class Authentication implements IAuthentication {
|
||||
appData: IApp;
|
||||
connectionData: IJSONObject;
|
||||
client: TwitterApi;
|
||||
|
||||
constructor(appData: IApp, connectionData: IJSONObject) {
|
||||
this.appData = appData;
|
||||
this.connectionData = connectionData;
|
||||
|
||||
const clientParams = {
|
||||
appKey: connectionData.consumerKey,
|
||||
appSecret: connectionData.consumerSecret,
|
||||
accessToken: connectionData.accessToken,
|
||||
accessSecret: connectionData.accessSecret,
|
||||
} as TwitterApiTokens;
|
||||
|
||||
this.client = new TwitterApi(clientParams);
|
||||
}
|
||||
|
||||
async createAuthData() {
|
||||
const appFields = this.appData.fields.find(
|
||||
(field: IField) => field.key == 'oAuthRedirectUrl'
|
||||
);
|
||||
const callbackUrl = appFields.value;
|
||||
|
||||
const authLink = await this.client.generateAuthLink(callbackUrl);
|
||||
|
||||
return {
|
||||
url: authLink.url,
|
||||
accessToken: authLink.oauth_token,
|
||||
accessSecret: authLink.oauth_token_secret,
|
||||
};
|
||||
}
|
||||
|
||||
async verifyCredentials() {
|
||||
const verifiedCredentials = await this.client.login(
|
||||
this.connectionData.oauthVerifier as string
|
||||
);
|
||||
|
||||
return {
|
||||
consumerKey: this.connectionData.consumerKey,
|
||||
consumerSecret: this.connectionData.consumerSecret,
|
||||
accessToken: verifiedCredentials.accessToken,
|
||||
accessSecret: verifiedCredentials.accessSecret,
|
||||
userId: verifiedCredentials.userId,
|
||||
screenName: verifiedCredentials.screenName,
|
||||
};
|
||||
}
|
||||
|
||||
async isStillVerified() {
|
||||
try {
|
||||
await this.client.currentUser();
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,44 +0,0 @@
|
||||
import { Token } from 'oauth-1.0a';
|
||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
||||
import oauthClient from './oauth-client';
|
||||
import { Method } from 'axios';
|
||||
|
||||
type IGenereateRequestOptons = {
|
||||
requestPath: string;
|
||||
method: string;
|
||||
data?: IJSONObject;
|
||||
};
|
||||
|
||||
const generateRequest = async (
|
||||
$: IGlobalVariable,
|
||||
options: IGenereateRequestOptons
|
||||
) => {
|
||||
const { requestPath, method, data } = options;
|
||||
|
||||
const token: Token = {
|
||||
key: $.auth.data.accessToken as string,
|
||||
secret: $.auth.data.accessSecret as string,
|
||||
};
|
||||
|
||||
const requestData = {
|
||||
url: `${$.app.baseUrl}${requestPath}`,
|
||||
method,
|
||||
data,
|
||||
};
|
||||
|
||||
const authHeader = oauthClient($).toHeader(
|
||||
oauthClient($).authorize(requestData, token)
|
||||
);
|
||||
|
||||
const response = await $.http.request({
|
||||
url: requestData.url,
|
||||
method: requestData.method as Method,
|
||||
headers: {
|
||||
...authHeader,
|
||||
},
|
||||
});
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
export default generateRequest;
|
@@ -1,14 +0,0 @@
|
||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
||||
import generateRequest from './generate-request';
|
||||
|
||||
const getCurrentUser = async ($: IGlobalVariable): Promise<IJSONObject> => {
|
||||
const response = await generateRequest($, {
|
||||
requestPath: '/2/users/me',
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
const currentUser = response.data.data;
|
||||
return currentUser;
|
||||
};
|
||||
|
||||
export default getCurrentUser;
|
@@ -1,22 +0,0 @@
|
||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
||||
import generateRequest from './generate-request';
|
||||
|
||||
const getUserByUsername = async ($: IGlobalVariable, username: string) => {
|
||||
const response = await generateRequest($, {
|
||||
requestPath: `/2/users/by/username/${username}`,
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
export default getUserByUsername;
|
@@ -1,68 +0,0 @@
|
||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
||||
import { URLSearchParams } from 'url';
|
||||
import { omitBy, isEmpty } from 'lodash';
|
||||
import generateRequest from './generate-request';
|
||||
|
||||
type GetUserFollowersOptions = {
|
||||
userId: string;
|
||||
lastInternalId?: string;
|
||||
};
|
||||
|
||||
const getUserFollowers = async (
|
||||
$: IGlobalVariable,
|
||||
options: GetUserFollowersOptions
|
||||
) => {
|
||||
let response;
|
||||
|
||||
const followers: {
|
||||
data: IJSONObject[];
|
||||
error: IJSONObject | null;
|
||||
} = {
|
||||
data: [],
|
||||
error: null,
|
||||
};
|
||||
|
||||
do {
|
||||
const params: IJSONObject = {
|
||||
pagination_token: response?.data?.meta?.next_token,
|
||||
};
|
||||
|
||||
const queryParams = new URLSearchParams(omitBy(params, isEmpty));
|
||||
|
||||
const requestPath = `/2/users/${options.userId}/followers${
|
||||
queryParams.toString() ? `?${queryParams.toString()}` : ''
|
||||
}`;
|
||||
|
||||
response = await generateRequest($, {
|
||||
requestPath,
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
if (response.integrationError) {
|
||||
followers.error = response.integrationError;
|
||||
return followers;
|
||||
}
|
||||
|
||||
if (response.data?.errors) {
|
||||
followers.error = response.data.errors;
|
||||
return followers;
|
||||
}
|
||||
|
||||
if (response.data.meta.result_count > 0) {
|
||||
response.data.data.forEach((tweet: IJSONObject) => {
|
||||
if (
|
||||
!options.lastInternalId ||
|
||||
Number(tweet.id) > Number(options.lastInternalId)
|
||||
) {
|
||||
followers.data.push(tweet);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
} while (response.data.meta.next_token && options.lastInternalId);
|
||||
|
||||
return followers;
|
||||
};
|
||||
|
||||
export default getUserFollowers;
|
@@ -1,79 +0,0 @@
|
||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
||||
import { URLSearchParams } from 'url';
|
||||
import omitBy from 'lodash/omitBy';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import generateRequest from './generate-request';
|
||||
import getCurrentUser from './get-current-user';
|
||||
import getUserByUsername from './get-user-by-username';
|
||||
|
||||
type IGetUserTweetsOptions = {
|
||||
currentUser: boolean;
|
||||
userId?: string;
|
||||
lastInternalId?: string;
|
||||
};
|
||||
|
||||
const getUserTweets = async (
|
||||
$: IGlobalVariable,
|
||||
options: IGetUserTweetsOptions
|
||||
) => {
|
||||
let username: string;
|
||||
|
||||
if (options.currentUser) {
|
||||
const currentUser = await getCurrentUser($);
|
||||
username = currentUser.username as string;
|
||||
} else {
|
||||
username = $.db.step.parameters.username as string;
|
||||
}
|
||||
|
||||
const user = await getUserByUsername($, username);
|
||||
|
||||
let response;
|
||||
|
||||
const tweets: {
|
||||
data: IJSONObject[];
|
||||
error: IJSONObject | null;
|
||||
} = {
|
||||
data: [],
|
||||
error: null,
|
||||
};
|
||||
|
||||
do {
|
||||
const params: IJSONObject = {
|
||||
since_id: options.lastInternalId,
|
||||
pagination_token: response?.data?.meta?.next_token,
|
||||
};
|
||||
|
||||
const queryParams = new URLSearchParams(omitBy(params, isEmpty));
|
||||
|
||||
const requestPath = `/2/users/${user.id}/tweets${
|
||||
queryParams.toString() ? `?${queryParams.toString()}` : ''
|
||||
}`;
|
||||
|
||||
response = await generateRequest($, {
|
||||
requestPath,
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
if (response.integrationError) {
|
||||
tweets.error = response.integrationError;
|
||||
return tweets;
|
||||
}
|
||||
|
||||
if (response.data.meta.result_count > 0) {
|
||||
response.data.data.forEach((tweet: IJSONObject) => {
|
||||
if (
|
||||
!options.lastInternalId ||
|
||||
Number(tweet.id) > Number(options.lastInternalId)
|
||||
) {
|
||||
tweets.data.push(tweet);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
} while (response.data.meta.next_token && options.lastInternalId);
|
||||
|
||||
return tweets;
|
||||
};
|
||||
|
||||
export default getUserTweets;
|
@@ -1,23 +0,0 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
import crypto from 'crypto';
|
||||
import OAuth from 'oauth-1.0a';
|
||||
|
||||
const oauthClient = ($: IGlobalVariable) => {
|
||||
const consumerData = {
|
||||
key: $.auth.data.consumerKey as string,
|
||||
secret: $.auth.data.consumerSecret as string,
|
||||
};
|
||||
|
||||
return new OAuth({
|
||||
consumer: consumerData,
|
||||
signature_method: 'HMAC-SHA1',
|
||||
hash_function(base_string, key) {
|
||||
return crypto
|
||||
.createHmac('sha1', key)
|
||||
.update(base_string)
|
||||
.digest('base64');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export default oauthClient;
|
@@ -1,8 +1,25 @@
|
||||
export default {
|
||||
name: 'Twitter',
|
||||
key: 'twitter',
|
||||
iconUrl: '{BASE_URL}/apps/twitter/assets/favicon.svg',
|
||||
authDocUrl: 'https://automatisch.io/docs/connections/twitter',
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://api.twitter.com',
|
||||
};
|
||||
import {
|
||||
IService,
|
||||
IAuthentication,
|
||||
IApp,
|
||||
IJSONObject,
|
||||
} from '@automatisch/types';
|
||||
import Authentication from './authentication';
|
||||
import Triggers from './triggers';
|
||||
import Actions from './actions';
|
||||
|
||||
export default class Twitter implements IService {
|
||||
authenticationClient: IAuthentication;
|
||||
triggers: Triggers;
|
||||
actions: Actions;
|
||||
|
||||
constructor(
|
||||
appData: IApp,
|
||||
connectionData: IJSONObject,
|
||||
parameters: IJSONObject
|
||||
) {
|
||||
this.authenticationClient = new Authentication(appData, connectionData);
|
||||
this.triggers = new Triggers(connectionData, parameters);
|
||||
this.actions = new Actions(connectionData, parameters);
|
||||
}
|
||||
}
|
||||
|
320
packages/backend/src/apps/twitter/info.json
Normal file
320
packages/backend/src/apps/twitter/info.json
Normal file
@@ -0,0 +1,320 @@
|
||||
{
|
||||
"name": "Twitter",
|
||||
"key": "twitter",
|
||||
"iconUrl": "{BASE_URL}/apps/twitter/assets/favicon.svg",
|
||||
"docUrl": "https://automatisch.io/docs/twitter",
|
||||
"primaryColor": "2DAAE1",
|
||||
"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.",
|
||||
"docUrl": "https://automatisch.io/docs/twitter#oauth-redirect-url",
|
||||
"clickToCopy": true
|
||||
},
|
||||
{
|
||||
"key": "consumerKey",
|
||||
"label": "Consumer Key",
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"readOnly": false,
|
||||
"value": null,
|
||||
"placeholder": null,
|
||||
"description": null,
|
||||
"docUrl": "https://automatisch.io/docs/twitter#consumer-key",
|
||||
"clickToCopy": false
|
||||
},
|
||||
{
|
||||
"key": "consumerSecret",
|
||||
"label": "Consumer Secret",
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"readOnly": false,
|
||||
"value": null,
|
||||
"placeholder": null,
|
||||
"description": null,
|
||||
"docUrl": "https://automatisch.io/docs/twitter#consumer-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.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 Tweet",
|
||||
"key": "myTweet",
|
||||
"description": "Will be triggered when you tweet something new.",
|
||||
"substeps": [
|
||||
{
|
||||
"key": "chooseAccount",
|
||||
"name": "Choose account"
|
||||
},
|
||||
{
|
||||
"key": "testStep",
|
||||
"name": "Test trigger"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "User Tweet",
|
||||
"key": "userTweet",
|
||||
"description": "Will be triggered when a specific user tweet something new.",
|
||||
"substeps": [
|
||||
{
|
||||
"key": "chooseAccount",
|
||||
"name": "Choose account"
|
||||
},
|
||||
{
|
||||
"key": "chooseTrigger",
|
||||
"name": "Set up a trigger",
|
||||
"arguments": [
|
||||
{
|
||||
"label": "Username",
|
||||
"key": "username",
|
||||
"type": "string",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "testStep",
|
||||
"name": "Test trigger"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Search Tweet",
|
||||
"key": "searchTweet",
|
||||
"description": "Will be triggered when any user tweet something containing a specific keyword, phrase, username or hashtag.",
|
||||
"substeps": [
|
||||
{
|
||||
"key": "chooseAccount",
|
||||
"name": "Choose account"
|
||||
},
|
||||
{
|
||||
"key": "chooseTrigger",
|
||||
"name": "Set up a trigger",
|
||||
"arguments": [
|
||||
{
|
||||
"label": "Search Term",
|
||||
"key": "searchTerm",
|
||||
"type": "string",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "testStep",
|
||||
"name": "Test trigger"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"name": "Create Tweet",
|
||||
"key": "createTweet",
|
||||
"description": "Will create a tweet.",
|
||||
"substeps": [
|
||||
{
|
||||
"key": "chooseAccount",
|
||||
"name": "Choose account"
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
13
packages/backend/src/apps/twitter/triggers.ts
Normal file
13
packages/backend/src/apps/twitter/triggers.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { IJSONObject } from '@automatisch/types';
|
||||
import MyTweet from './triggers/my-tweet';
|
||||
import SearchTweet from './triggers/search-tweet';
|
||||
|
||||
export default class Triggers {
|
||||
myTweet: MyTweet;
|
||||
searchTweet: SearchTweet;
|
||||
|
||||
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
|
||||
this.myTweet = new MyTweet(connectionData);
|
||||
this.searchTweet = new SearchTweet(connectionData, parameters);
|
||||
}
|
||||
}
|
25
packages/backend/src/apps/twitter/triggers/my-tweet.ts
Normal file
25
packages/backend/src/apps/twitter/triggers/my-tweet.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import TwitterApi, { TwitterApiTokens } from 'twitter-api-v2';
|
||||
import { IJSONObject } from '@automatisch/types';
|
||||
|
||||
export default class MyTweet {
|
||||
client: TwitterApi;
|
||||
|
||||
constructor(connectionData: IJSONObject) {
|
||||
this.client = new TwitterApi({
|
||||
appKey: connectionData.consumerKey,
|
||||
appSecret: connectionData.consumerSecret,
|
||||
accessToken: connectionData.accessToken,
|
||||
accessSecret: connectionData.accessSecret,
|
||||
} as TwitterApiTokens);
|
||||
}
|
||||
|
||||
async run() {
|
||||
const response = await this.client.currentUser();
|
||||
const username = response.screen_name;
|
||||
|
||||
const userTimeline = await this.client.v1.userTimelineByUsername(username);
|
||||
const fetchedTweets = userTimeline.tweets;
|
||||
|
||||
return fetchedTweets;
|
||||
}
|
||||
}
|
@@ -1,30 +0,0 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
import getUserTweets from '../../common/get-user-tweets';
|
||||
|
||||
export default {
|
||||
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',
|
||||
},
|
||||
],
|
||||
|
||||
async run($: IGlobalVariable) {
|
||||
return await getUserTweets($, {
|
||||
currentUser: true,
|
||||
lastInternalId: $.db.flow.lastInternalId,
|
||||
});
|
||||
},
|
||||
|
||||
async testRun($: IGlobalVariable) {
|
||||
return await getUserTweets($, { currentUser: true });
|
||||
},
|
||||
};
|
@@ -1,27 +0,0 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
import myFollowers from './my-followers';
|
||||
|
||||
export default {
|
||||
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',
|
||||
},
|
||||
],
|
||||
|
||||
async run($: IGlobalVariable) {
|
||||
return await myFollowers($, $.db.flow.lastInternalId);
|
||||
},
|
||||
|
||||
async testRun($: IGlobalVariable) {
|
||||
return await myFollowers($);
|
||||
},
|
||||
};
|
@@ -1,17 +0,0 @@
|
||||
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 { username } = await getCurrentUser($);
|
||||
const user = await getUserByUsername($, username as string);
|
||||
|
||||
const tweets = await getUserFollowers($, {
|
||||
userId: user.id,
|
||||
lastInternalId,
|
||||
});
|
||||
return tweets;
|
||||
};
|
||||
|
||||
export default myFollowers;
|
58
packages/backend/src/apps/twitter/triggers/search-tweet.ts
Normal file
58
packages/backend/src/apps/twitter/triggers/search-tweet.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import TwitterApi, { TwitterApiTokens } from 'twitter-api-v2';
|
||||
import { IJSONObject } from '@automatisch/types';
|
||||
|
||||
export default class SearchTweet {
|
||||
client: TwitterApi;
|
||||
parameters: IJSONObject;
|
||||
|
||||
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
|
||||
this.client = new TwitterApi({
|
||||
appKey: connectionData.consumerKey,
|
||||
appSecret: connectionData.consumerSecret,
|
||||
accessToken: connectionData.accessToken,
|
||||
accessSecret: connectionData.accessSecret,
|
||||
} as TwitterApiTokens);
|
||||
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
async run(startTime: Date) {
|
||||
const tweets = [];
|
||||
|
||||
const response = await this.client.v2.search(
|
||||
this.parameters.searchTerm as string,
|
||||
{
|
||||
max_results: 50,
|
||||
'tweet.fields': 'created_at',
|
||||
}
|
||||
);
|
||||
|
||||
for await (const tweet of response.data.data) {
|
||||
if (new Date(tweet.created_at).getTime() <= startTime.getTime()) {
|
||||
break;
|
||||
}
|
||||
|
||||
tweets.push(tweet);
|
||||
|
||||
if (response.data.meta.next_token) {
|
||||
await response.fetchNext();
|
||||
}
|
||||
}
|
||||
|
||||
return tweets;
|
||||
}
|
||||
|
||||
async testRun() {
|
||||
const response = await this.client.v2.search(
|
||||
this.parameters.searchTerm as string,
|
||||
{
|
||||
max_results: 10,
|
||||
'tweet.fields': 'created_at',
|
||||
}
|
||||
);
|
||||
|
||||
const mostRecentTweet = response.data.data[0];
|
||||
|
||||
return [mostRecentTweet];
|
||||
}
|
||||
}
|
@@ -1,45 +0,0 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
import searchTweets from './search-tweets';
|
||||
|
||||
export default {
|
||||
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',
|
||||
},
|
||||
],
|
||||
|
||||
async run($: IGlobalVariable) {
|
||||
return await searchTweets($, {
|
||||
searchTerm: $.db.step.parameters.searchTerm as string,
|
||||
lastInternalId: $.db.flow.lastInternalId,
|
||||
});
|
||||
},
|
||||
|
||||
async testRun($: IGlobalVariable) {
|
||||
return await searchTweets($, {
|
||||
searchTerm: $.db.step.parameters.searchTerm as string,
|
||||
});
|
||||
},
|
||||
};
|
@@ -1,70 +0,0 @@
|
||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
||||
import qs from 'qs';
|
||||
import generateRequest from '../../common/generate-request';
|
||||
import { omitBy, isEmpty } from 'lodash';
|
||||
|
||||
type ISearchTweetsOptions = {
|
||||
searchTerm: string;
|
||||
lastInternalId?: string;
|
||||
};
|
||||
|
||||
const searchTweets = async (
|
||||
$: IGlobalVariable,
|
||||
options: ISearchTweetsOptions
|
||||
) => {
|
||||
let response;
|
||||
|
||||
const tweets: {
|
||||
data: IJSONObject[];
|
||||
error: IJSONObject | null;
|
||||
} = {
|
||||
data: [],
|
||||
error: null,
|
||||
};
|
||||
|
||||
do {
|
||||
const params: IJSONObject = {
|
||||
query: options.searchTerm,
|
||||
since_id: options.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()}` : ''
|
||||
}`;
|
||||
|
||||
response = await generateRequest($, {
|
||||
requestPath,
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
if (response.integrationError) {
|
||||
tweets.error = response.integrationError;
|
||||
return tweets;
|
||||
}
|
||||
|
||||
if (response.data.errors) {
|
||||
tweets.error = response.data.errors;
|
||||
return tweets;
|
||||
}
|
||||
|
||||
if (response.data.meta.result_count > 0) {
|
||||
response.data.data.forEach((tweet: IJSONObject) => {
|
||||
if (
|
||||
!options.lastInternalId ||
|
||||
Number(tweet.id) > Number(options.lastInternalId)
|
||||
) {
|
||||
tweets.data.push(tweet);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
} while (response.data.meta.next_token && options.lastInternalId);
|
||||
|
||||
return tweets;
|
||||
};
|
||||
|
||||
export default searchTweets;
|
@@ -1,46 +0,0 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
import getUserTweets from '../../common/get-user-tweets';
|
||||
|
||||
export default {
|
||||
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',
|
||||
},
|
||||
],
|
||||
|
||||
async run($: IGlobalVariable) {
|
||||
return await getUserTweets($, {
|
||||
currentUser: false,
|
||||
userId: $.db.step.parameters.username as string,
|
||||
lastInternalId: $.db.flow.lastInternalId,
|
||||
});
|
||||
},
|
||||
|
||||
async testRun($: IGlobalVariable) {
|
||||
return await getUserTweets($, {
|
||||
currentUser: false,
|
||||
userId: $.db.step.parameters.username as string,
|
||||
});
|
||||
},
|
||||
};
|
@@ -5,12 +5,14 @@ import type {
|
||||
IJSONObject,
|
||||
} from '@automatisch/types';
|
||||
import { URLSearchParams } from 'url';
|
||||
import createHttpClient, { IHttpClient } from '../../helpers/http-client';
|
||||
import axios, { AxiosInstance } from 'axios';
|
||||
|
||||
export default class Authentication implements IAuthentication {
|
||||
appData: IApp;
|
||||
connectionData: IJSONObject;
|
||||
client: IHttpClient;
|
||||
client: AxiosInstance = axios.create({
|
||||
baseURL: 'https://api.typeform.com',
|
||||
});
|
||||
|
||||
scope: string[] = [
|
||||
'forms:read',
|
||||
@@ -25,7 +27,6 @@ export default class Authentication implements IAuthentication {
|
||||
constructor(appData: IApp, connectionData: IJSONObject) {
|
||||
this.connectionData = connectionData;
|
||||
this.appData = appData;
|
||||
this.client = createHttpClient({ baseURL: 'https://api.typeform.com' });
|
||||
}
|
||||
|
||||
get oauthRedirectUrl() {
|
||||
|
@@ -4,7 +4,6 @@
|
||||
"iconUrl": "{BASE_URL}/apps/typeform/assets/favicon.svg",
|
||||
"docUrl": "https://automatisch.io/docs/typeform",
|
||||
"primaryColor": "5865f2",
|
||||
"supportsConnections": true,
|
||||
"fields": [
|
||||
{
|
||||
"key": "oAuthRedirectUrl",
|
||||
|
@@ -13,7 +13,6 @@ type AppConfig = {
|
||||
postgresHost: string;
|
||||
postgresUsername: string;
|
||||
postgresPassword?: string;
|
||||
version: string;
|
||||
postgresEnableSsl: boolean;
|
||||
baseUrl: string;
|
||||
encryptionKey: string;
|
||||
@@ -21,15 +20,12 @@ type AppConfig = {
|
||||
serveWebAppSeparately: boolean;
|
||||
redisHost: string;
|
||||
redisPort: number;
|
||||
enableBullMQDashboard: boolean;
|
||||
telemetryEnabled: boolean;
|
||||
};
|
||||
|
||||
const host = process.env.HOST || 'localhost';
|
||||
const protocol = process.env.PROTOCOL || 'http';
|
||||
const port = process.env.PORT || '3000';
|
||||
const serveWebAppSeparately =
|
||||
process.env.SERVE_WEB_APP_SEPARATELY === 'true' ? true : false;
|
||||
const serveWebAppSeparately = process.env.SERVE_WEB_APP_SEPARATELY === 'true' ? true : false;
|
||||
|
||||
let webAppUrl = `${protocol}://${host}:${port}`;
|
||||
if (serveWebAppSeparately) {
|
||||
@@ -46,9 +42,8 @@ const appConfig: AppConfig = {
|
||||
port,
|
||||
appEnv: appEnv,
|
||||
isDev: appEnv === 'development',
|
||||
version: process.env.npm_package_version,
|
||||
postgresDatabase: process.env.POSTGRES_DATABASE || 'automatisch_development',
|
||||
postgresPort: parseInt(process.env.POSTGRES_PORT || '5432'),
|
||||
postgresPort: parseInt(process.env.POSTGRES_PORT|| '5432'),
|
||||
postgresHost: process.env.POSTGRES_HOST || 'localhost',
|
||||
postgresUsername:
|
||||
process.env.POSTGRES_USERNAME || 'automatisch_development_user',
|
||||
@@ -59,11 +54,8 @@ const appConfig: AppConfig = {
|
||||
serveWebAppSeparately,
|
||||
redisHost: process.env.REDIS_HOST || '127.0.0.1',
|
||||
redisPort: parseInt(process.env.REDIS_PORT || '6379'),
|
||||
enableBullMQDashboard:
|
||||
process.env.ENABLE_BULLMQ_DASHBOARD === 'true' ? true : false,
|
||||
baseUrl,
|
||||
webAppUrl,
|
||||
telemetryEnabled: process.env.TELEMETRY_ENABLED === 'false' ? false : true,
|
||||
};
|
||||
|
||||
if (!appConfig.encryptionKey) {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user