Compare commits
164 Commits
v0.8.0
...
gh-actions
Author | SHA1 | Date | |
---|---|---|---|
![]() |
dcc62c3233 | ||
![]() |
e43c083d50 | ||
![]() |
3cd9bdc1d4 | ||
![]() |
c0b8e6178d | ||
![]() |
410f9d0af5 | ||
![]() |
b1fedf28dc | ||
![]() |
b0df03dcd2 | ||
![]() |
2794e50a19 | ||
![]() |
365ae656f2 | ||
![]() |
98649dcba6 | ||
![]() |
213c8096d2 | ||
![]() |
398938f27e | ||
![]() |
6378e62645 | ||
![]() |
251885d4be | ||
![]() |
f53909355f | ||
![]() |
242b68889a | ||
![]() |
6a66b65f2a | ||
![]() |
f30ead6bcb | ||
![]() |
237ee72ca6 | ||
![]() |
3590d84ad6 | ||
![]() |
2dae8c162d | ||
![]() |
9a192b708e | ||
![]() |
c193f9334f | ||
![]() |
6e682dc752 | ||
![]() |
da86fe56bd | ||
![]() |
45865d701a | ||
![]() |
a66a31b474 | ||
![]() |
2661e7102f | ||
![]() |
224965b91e | ||
![]() |
a9c7375534 | ||
![]() |
e77f7ee0bf | ||
![]() |
ae5dd0cad6 | ||
![]() |
a128907a4e | ||
![]() |
d6453a8ed0 | ||
![]() |
dd1e8240b8 | ||
![]() |
b12f39916f | ||
![]() |
aae88fe1ad | ||
![]() |
83bb400df1 | ||
![]() |
8ea8067788 | ||
![]() |
9fbc9d59f5 | ||
![]() |
b96ba69a72 | ||
![]() |
c4ccab6a5d | ||
![]() |
f84f27bb56 | ||
![]() |
416cc0ffa9 | ||
![]() |
1fd5ec4db6 | ||
![]() |
4795c35c68 | ||
![]() |
25ce63b86d | ||
![]() |
5271033d34 | ||
![]() |
6ba8f33399 | ||
![]() |
7ab79bd815 | ||
![]() |
04a0a847c7 | ||
![]() |
436fa9af69 | ||
![]() |
ca0bbb0f08 | ||
![]() |
88996144a5 | ||
![]() |
44d5eee99e | ||
![]() |
0d1ff6074f | ||
![]() |
d63757634a | ||
![]() |
fd61cf3388 | ||
![]() |
a6a6b63e5a | ||
![]() |
c02c2def29 | ||
![]() |
ff66548462 | ||
![]() |
c9f292e252 | ||
![]() |
18cef5f3bd | ||
![]() |
e19340f1e0 | ||
![]() |
feb613cb6d | ||
![]() |
afa6bdfa44 | ||
![]() |
200e6d9905 | ||
![]() |
70772c49bd | ||
![]() |
762ea97e8b | ||
![]() |
8156b8b356 | ||
![]() |
3a2cbae0a0 | ||
![]() |
0ad8da097b | ||
![]() |
e2dcdd2811 | ||
![]() |
8074f9146b | ||
![]() |
df24bac913 | ||
![]() |
4d4091adcc | ||
![]() |
cac54c41a1 | ||
![]() |
130931d7af | ||
![]() |
d35b08b35e | ||
![]() |
82031da6a6 | ||
![]() |
9df5ee7b11 | ||
![]() |
2ed1a57cd9 | ||
![]() |
101450cba6 | ||
![]() |
6bab5b3f7c | ||
![]() |
ca3c0e00a7 | ||
![]() |
6d64daf324 | ||
![]() |
9ae4578e19 | ||
![]() |
e06b7ab87a | ||
![]() |
1e2adedcbf | ||
![]() |
adf763c1b0 | ||
![]() |
36ee0df256 | ||
![]() |
823d85b24a | ||
![]() |
a3b3038709 | ||
![]() |
ddeb18f626 | ||
![]() |
90cd11bd38 | ||
![]() |
e9ba37b8de | ||
![]() |
d5e4a1b1ad | ||
![]() |
129e6d60e5 | ||
![]() |
4b77f2f590 | ||
![]() |
a909966562 | ||
![]() |
fd184239d6 | ||
![]() |
52bc49dc6a | ||
![]() |
b9352ccc06 | ||
![]() |
525b2baf06 | ||
![]() |
a8edeb2459 | ||
![]() |
e3830d64e0 | ||
![]() |
91f3e2c2b4 | ||
![]() |
77b4408416 | ||
![]() |
cede96f018 | ||
![]() |
8e0a28d238 | ||
![]() |
da5d594428 | ||
![]() |
9f9ee0bb58 | ||
![]() |
163aca6179 | ||
![]() |
cb06d3b0ae | ||
![]() |
dbe18dd100 | ||
![]() |
217970667a | ||
![]() |
dace794167 | ||
![]() |
590780a539 | ||
![]() |
cbd1f47e87 | ||
![]() |
f89cff4e4a | ||
![]() |
cb08e0bf9f | ||
![]() |
3b54b29a99 | ||
![]() |
25983e046c | ||
![]() |
a6a124d2e6 | ||
![]() |
c7e1d30553 | ||
![]() |
6cc8c45634 | ||
![]() |
ee9a9114b7 | ||
![]() |
11f00f866c | ||
![]() |
03ea61ba81 | ||
![]() |
f6c500c998 | ||
![]() |
b590f0f98f | ||
![]() |
ef9359b208 | ||
![]() |
efd243a340 | ||
![]() |
bafb8b86db | ||
![]() |
84b701747f | ||
![]() |
ec42446daa | ||
![]() |
5046c4c911 | ||
![]() |
ce8c9906cb | ||
![]() |
6fb5482bba | ||
![]() |
58189963f5 | ||
![]() |
f488a71304 | ||
![]() |
4b706e004d | ||
![]() |
40e10cc270 | ||
![]() |
41db227eb3 | ||
![]() |
43eea965c5 | ||
![]() |
8101c9f0bc | ||
![]() |
b4cda90338 | ||
![]() |
7ca37c412e | ||
![]() |
e4e3356dc9 | ||
![]() |
0deaa03218 | ||
![]() |
a7104c41a2 | ||
![]() |
5176b8c322 | ||
![]() |
c37c70446d | ||
![]() |
63abc8a2c8 | ||
![]() |
ba5c038e3b | ||
![]() |
a6669415f5 | ||
![]() |
4086fad867 | ||
![]() |
8a71c13078 | ||
![]() |
5d77f64e76 | ||
![]() |
0d092b977f | ||
![]() |
69582ff83d | ||
![]() |
a5c7da331a | ||
![]() |
8e842296b7 | ||
![]() |
7db14d1df7 |
@@ -29,7 +29,6 @@ rm -rf .env
|
||||
echo "
|
||||
PORT=$WEB_PORT
|
||||
REACT_APP_GRAPHQL_URL=http://localhost:$BACKEND_PORT/graphql
|
||||
REACT_APP_NOTIFICATIONS_URL=https://notifications.automatisch.io
|
||||
" >> .env
|
||||
cd $CURRENT_DIR
|
||||
|
||||
|
@@ -33,7 +33,32 @@ services:
|
||||
- '6379:6379'
|
||||
expose:
|
||||
- 6379
|
||||
keycloak:
|
||||
image: quay.io/keycloak/keycloak:21.1
|
||||
restart: always
|
||||
container_name: keycloak
|
||||
environment:
|
||||
- KEYCLOAK_ADMIN=admin
|
||||
- KEYCLOAK_ADMIN_PASSWORD=admin
|
||||
- KC_DB=postgres
|
||||
- KC_DB_URL_HOST=postgres
|
||||
- KC_DB_URL_DATABASE=keycloak
|
||||
- KC_DB_USERNAME=automatisch_user
|
||||
- KC_DB_PASSWORD=automatisch_password
|
||||
- KC_HEALTH_ENABLED=true
|
||||
ports:
|
||||
- "8080:8080"
|
||||
command: start-dev
|
||||
depends_on:
|
||||
- postgres
|
||||
healthcheck:
|
||||
test: "curl -f http://localhost:8080/health/ready || exit 1"
|
||||
volumes:
|
||||
- keycloak:/opt/keycloak/data/
|
||||
expose:
|
||||
- 8080
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
keycloak:
|
||||
|
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@@ -1,5 +1,11 @@
|
||||
name: Automatisch CI
|
||||
on: [push]
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
linter:
|
||||
runs-on: ubuntu-latest
|
||||
|
88
.github/workflows/playwright.yml
vendored
Normal file
88
.github/workflows/playwright.yml
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
name: Automatisch UI Tests
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
ENCRYPTION_KEY: sample_encryption_key
|
||||
WEBHOOK_SECRET_KEY: sample_webhook_secret_key
|
||||
APP_SECRET_KEY: sample_app_secret_key
|
||||
POSTGRES_HOST: localhost
|
||||
POSTGRES_DATABASE: automatisch
|
||||
POSTGRES_PORT: 5432
|
||||
POSTGRES_USERNAME: automatisch_user
|
||||
POSTGRES_PASSWORD: automatisch_password
|
||||
REDIS_HOST: localhost
|
||||
APP_ENV: production
|
||||
LICENSE_KEY: ${{ secrets.E2E_LICENSE_KEY }}
|
||||
|
||||
jobs:
|
||||
test:
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:14.5-alpine
|
||||
env:
|
||||
POSTGRES_DB: automatisch
|
||||
POSTGRES_USER: automatisch_user
|
||||
POSTGRES_PASSWORD: automatisch_password
|
||||
options: >-
|
||||
--health-cmd "pg_isready -U automatisch_user -d automatisch"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 5432:5432
|
||||
redis:
|
||||
image: redis:7.0.4-alpine
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 6379:6379
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
- name: Install dependencies
|
||||
run: yarn && yarn lerna bootstrap
|
||||
- name: Install Playwright Browsers
|
||||
run: yarn playwright install --with-deps
|
||||
- name: Build Automatisch
|
||||
run: yarn lerna run --scope=@*/{web,backend,cli} build
|
||||
env:
|
||||
# Keep this until we clean up warnings in build processes
|
||||
CI: false
|
||||
- name: Migrate database
|
||||
working-directory: ./packages/backend
|
||||
run: yarn db:migrate --migrations-directory ./dist/src/db/migrations
|
||||
- name: Seed user
|
||||
working-directory: ./packages/backend
|
||||
run: yarn db:seed:user &
|
||||
- name: Run Automatisch
|
||||
run: yarn start &
|
||||
working-directory: ./packages/backend
|
||||
- name: Run Automatisch worker
|
||||
run: node dist/src/worker.js &
|
||||
working-directory: ./packages/backend
|
||||
- name: Run Playwright tests
|
||||
working-directory: ./packages/e2e-tests
|
||||
env:
|
||||
LOGIN_EMAIL: user@automatisch.io
|
||||
LOGIN_PASSWORD: sample
|
||||
BASE_URL: http://localhost:3000
|
||||
run: yarn test
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: ./packages/e2e-tests/test-results/**/*
|
||||
retention-days: 30
|
1
.node-version
Normal file
1
.node-version
Normal file
@@ -0,0 +1 @@
|
||||
16.15.0
|
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@@ -1,4 +1,7 @@
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
}
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@ WORKDIR /automatisch
|
||||
|
||||
RUN \
|
||||
apk --no-cache add --virtual build-dependencies python3 build-base && \
|
||||
yarn global add @automatisch/cli@0.8.0 --network-timeout 1000000 && \
|
||||
yarn global add @automatisch/cli@0.9.3 --network-timeout 1000000 && \
|
||||
rm -rf /usr/local/share/.cache/ && \
|
||||
apk del build-dependencies
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM automatischio/automatisch:0.8.0
|
||||
FROM automatischio/automatisch:0.9.3
|
||||
WORKDIR /automatisch
|
||||
|
||||
RUN apk add --no-cache openssl dos2unix
|
||||
|
@@ -2,7 +2,7 @@
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "0.8.0",
|
||||
"version": "0.9.3",
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"command": {
|
||||
|
16
packages/backend/.env-example.test
Normal file
16
packages/backend/.env-example.test
Normal file
@@ -0,0 +1,16 @@
|
||||
APP_ENV=test
|
||||
HOST=localhost
|
||||
PROTOCOL=http
|
||||
PORT=3000
|
||||
LOG_LEVEL=debug
|
||||
WEBHOOK_SECRET_KEY=secret
|
||||
POSTGRES_DATABASE=automatisch_test
|
||||
POSTGRES_PORT=5432
|
||||
POSTGRES_HOST=localhost
|
||||
POSTGRES_USERNAME=automatisch_test_user
|
||||
POSTGRES_PASSWORD=
|
||||
POSTGRES_ENABLE_SSL=false
|
||||
ENCRYPTION_KEY=secret
|
||||
APP_SECRET_KEY=secret
|
||||
REDIS_PORT=6379
|
||||
REDIS_HOST=127.0.0.1
|
5
packages/backend/ava.config.mjs
Normal file
5
packages/backend/ava.config.mjs
Normal file
@@ -0,0 +1,5 @@
|
||||
export default {
|
||||
require: ['ts-node/register', './src/config/app.ts'],
|
||||
files: ['**/*.test.ts'],
|
||||
extensions: ['ts'],
|
||||
};
|
@@ -2,18 +2,33 @@ import appConfig from '../../src/config/app';
|
||||
import logger from '../../src/helpers/logger';
|
||||
import client from './client';
|
||||
import User from '../../src/models/user';
|
||||
import Role from '../../src/models/role';
|
||||
import '../../src/config/orm';
|
||||
|
||||
async function fetchAdminRole() {
|
||||
const role = await Role
|
||||
.query()
|
||||
.where({
|
||||
key: 'admin'
|
||||
})
|
||||
.limit(1)
|
||||
.first();
|
||||
|
||||
return role;
|
||||
}
|
||||
|
||||
export async function createUser(
|
||||
email = 'user@automatisch.io',
|
||||
password = 'sample'
|
||||
) {
|
||||
const UNIQUE_VIOLATION_CODE = '23505';
|
||||
|
||||
const role = await fetchAdminRole();
|
||||
const userParams = {
|
||||
email,
|
||||
password,
|
||||
fullName: 'Initial admin',
|
||||
role: 'admin',
|
||||
roleId: role.id,
|
||||
};
|
||||
|
||||
try {
|
||||
|
@@ -12,6 +12,7 @@ const knexConfig = {
|
||||
database: appConfig.postgresDatabase,
|
||||
ssl: appConfig.postgresEnableSsl,
|
||||
},
|
||||
asyncStackTraces: appConfig.isDev,
|
||||
searchPath: [appConfig.postgresSchema],
|
||||
pool: { min: 0, max: 20 },
|
||||
migrations: {
|
||||
|
@@ -1,15 +1,16 @@
|
||||
{
|
||||
"name": "@automatisch/backend",
|
||||
"version": "0.8.0",
|
||||
"version": "0.9.3",
|
||||
"license": "See LICENSE file",
|
||||
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
||||
"scripts": {
|
||||
"dev": "ts-node-dev --exit-child src/server.ts",
|
||||
"dev": "ts-node-dev --watch 'src/graphql/schema.graphql' --exit-child src/server.ts",
|
||||
"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",
|
||||
"start": "node dist/src/server.js",
|
||||
"test": "ava",
|
||||
"pretest": "APP_ENV=test ts-node ./test/setup/prepare-test-env.ts",
|
||||
"test": "APP_ENV=test ava",
|
||||
"lint": "eslint . --ignore-path ../../.eslintignore",
|
||||
"db:create": "ts-node ./bin/database/create.ts",
|
||||
"db:seed:user": "ts-node ./bin/database/seed-user.ts",
|
||||
@@ -22,15 +23,20 @@
|
||||
"prebuild": "rm -rf ./dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"@automatisch/web": "^0.8.0",
|
||||
"@automatisch/web": "^0.9.3",
|
||||
"@bull-board/express": "^3.10.1",
|
||||
"@casl/ability": "^6.5.0",
|
||||
"@graphql-tools/graphql-file-loader": "^7.3.4",
|
||||
"@graphql-tools/load": "^7.5.2",
|
||||
"@node-saml/passport-saml": "^4.0.4",
|
||||
"@rudderstack/rudder-sdk-node": "^1.1.2",
|
||||
"@sentry/node": "^7.42.0",
|
||||
"@sentry/tracing": "^7.42.0",
|
||||
"@types/accounting": "^0.4.2",
|
||||
"@types/luxon": "^2.3.1",
|
||||
"@types/passport": "^1.0.12",
|
||||
"@types/xmlrpc": "^1.3.7",
|
||||
"accounting": "^0.4.1",
|
||||
"ajv-formats": "^2.1.1",
|
||||
"axios": "0.24.0",
|
||||
"bcrypt": "^5.0.1",
|
||||
@@ -59,11 +65,15 @@
|
||||
"memory-cache": "^0.2.0",
|
||||
"morgan": "^1.10.0",
|
||||
"multer": "1.4.5-lts.1",
|
||||
"node-html-markdown": "^1.3.0",
|
||||
"nodemailer": "6.7.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"objection": "^3.0.0",
|
||||
"passport": "^0.6.0",
|
||||
"pg": "^8.7.1",
|
||||
"php-serialize": "^4.0.2",
|
||||
"pluralize": "^8.0.0",
|
||||
"showdown": "^2.1.0",
|
||||
"stripe": "^11.13.0",
|
||||
"winston": "^3.7.1",
|
||||
"xmlrpc": "^1.3.2"
|
||||
@@ -104,7 +114,7 @@
|
||||
"url": "https://github.com/automatisch/automatisch/issues"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@automatisch/types": "^0.8.0",
|
||||
"@automatisch/types": "^0.9.3",
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
"@types/bull": "^3.15.8",
|
||||
"@types/cors": "^2.8.12",
|
||||
@@ -120,23 +130,14 @@
|
||||
"@types/nodemailer": "^6.4.4",
|
||||
"@types/pg": "^8.6.1",
|
||||
"@types/pino": "^7.0.5",
|
||||
"ava": "^3.15.0",
|
||||
"@types/pluralize": "^0.0.30",
|
||||
"@types/showdown": "^2.0.1",
|
||||
"ava": "^5.3.1",
|
||||
"nodemon": "^2.0.13",
|
||||
"sinon": "^11.1.2",
|
||||
"ts-node": "^10.2.1",
|
||||
"ts-node-dev": "^1.1.8"
|
||||
},
|
||||
"ava": {
|
||||
"files": [
|
||||
"test/**/*"
|
||||
],
|
||||
"extensions": [
|
||||
"ts"
|
||||
],
|
||||
"require": [
|
||||
"ts-node/register"
|
||||
]
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
|
@@ -17,6 +17,7 @@ import {
|
||||
} from './helpers/create-bull-board-handler';
|
||||
import injectBullBoardHandler from './helpers/inject-bull-board-handler';
|
||||
import router from './routes';
|
||||
import configurePassport from './helpers/passport';
|
||||
|
||||
createBullBoardHandler(serverAdapter);
|
||||
|
||||
@@ -50,6 +51,9 @@ app.use(
|
||||
})
|
||||
);
|
||||
app.use(cors(corsOptions));
|
||||
|
||||
configurePassport(app);
|
||||
|
||||
app.use('/', router);
|
||||
|
||||
webUIHandler(app);
|
||||
|
@@ -0,0 +1,49 @@
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
|
||||
import formatDateTime from './transformers/format-date-time';
|
||||
|
||||
const transformers = {
|
||||
formatDateTime,
|
||||
};
|
||||
|
||||
export default defineAction({
|
||||
name: 'Date / Time',
|
||||
key: 'date-time',
|
||||
description: 'Perform date and time related transformations on your data.',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Transform',
|
||||
key: 'transform',
|
||||
type: 'dropdown' as const,
|
||||
required: true,
|
||||
variables: true,
|
||||
options: [{ label: 'Format Date / Time', value: 'formatDateTime' }],
|
||||
additionalFields: {
|
||||
type: 'query',
|
||||
name: 'getDynamicFields',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listTransformOptions',
|
||||
},
|
||||
{
|
||||
name: 'parameters.transform',
|
||||
value: '{parameters.transform}',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const transformerName = $.step.parameters
|
||||
.transform as keyof typeof transformers;
|
||||
const output = transformers[transformerName]($);
|
||||
|
||||
$.setActionItem({
|
||||
raw: {
|
||||
output,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
@@ -0,0 +1,23 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
const formatDateTime = ($: IGlobalVariable) => {
|
||||
const input = $.step.parameters.input as string;
|
||||
|
||||
const fromFormat = $.step.parameters.fromFormat as string;
|
||||
const fromTimezone = $.step.parameters.fromTimezone as string;
|
||||
|
||||
const inputDateTime = DateTime.fromFormat(input, fromFormat, {
|
||||
zone: fromTimezone,
|
||||
setZone: true,
|
||||
});
|
||||
|
||||
const toFormat = $.step.parameters.toFormat as string;
|
||||
const toTimezone = $.step.parameters.toTimezone as string;
|
||||
|
||||
const outputDateTime = inputDateTime.setZone(toTimezone).toFormat(toFormat);
|
||||
|
||||
return outputDateTime;
|
||||
};
|
||||
|
||||
export default formatDateTime;
|
5
packages/backend/src/apps/formatter/actions/index.ts
Normal file
5
packages/backend/src/apps/formatter/actions/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import text from './text';
|
||||
import numbers from './numbers';
|
||||
import dateTime from './date-time';
|
||||
|
||||
export default [text, numbers, dateTime];
|
58
packages/backend/src/apps/formatter/actions/numbers/index.ts
Normal file
58
packages/backend/src/apps/formatter/actions/numbers/index.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
|
||||
import performMathOperation from './transformers/perform-math-operation';
|
||||
import randomNumber from './transformers/random-number';
|
||||
import formatNumber from './transformers/format-number';
|
||||
|
||||
const transformers = {
|
||||
performMathOperation,
|
||||
randomNumber,
|
||||
formatNumber,
|
||||
};
|
||||
|
||||
export default defineAction({
|
||||
name: 'Numbers',
|
||||
key: 'numbers',
|
||||
description:
|
||||
'Transform numbers to perform math operations, generate random numbers, format numbers, and much more.',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Transform',
|
||||
key: 'transform',
|
||||
type: 'dropdown' as const,
|
||||
required: true,
|
||||
variables: true,
|
||||
options: [
|
||||
{ label: 'Perform Math Operation', value: 'performMathOperation' },
|
||||
{ label: 'Random Number', value: 'randomNumber' },
|
||||
{ label: 'Format Number', value: 'formatNumber' },
|
||||
],
|
||||
additionalFields: {
|
||||
type: 'query',
|
||||
name: 'getDynamicFields',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listTransformOptions',
|
||||
},
|
||||
{
|
||||
name: 'parameters.transform',
|
||||
value: '{parameters.transform}',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const transformerName = $.step.parameters
|
||||
.transform as keyof typeof transformers;
|
||||
const output = transformers[transformerName]($);
|
||||
|
||||
$.setActionItem({
|
||||
raw: {
|
||||
output,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
@@ -0,0 +1,28 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
import accounting from 'accounting';
|
||||
|
||||
const formatNumber = ($: IGlobalVariable) => {
|
||||
const input = $.step.parameters.input as string;
|
||||
const inputDecimalMark = $.step.parameters.inputDecimalMark as string;
|
||||
const toFormat = $.step.parameters.toFormat as string;
|
||||
|
||||
const normalizedNumber = accounting.unformat(input, inputDecimalMark);
|
||||
const decimalPart = normalizedNumber.toString().split('.')[1];
|
||||
const precision = decimalPart ? decimalPart.length : 0;
|
||||
|
||||
if (toFormat === '0') {
|
||||
// Comma for grouping & period for decimal
|
||||
return accounting.formatNumber(normalizedNumber, precision, ',', '.');
|
||||
} else if (toFormat === '1') {
|
||||
// Period for grouping & comma for decimal
|
||||
return accounting.formatNumber(normalizedNumber, precision, '.', ',');
|
||||
} else if (toFormat === '2') {
|
||||
// Space for grouping & period for decimal
|
||||
return accounting.formatNumber(normalizedNumber, precision, ' ', '.');
|
||||
} else if (toFormat === '3') {
|
||||
// Space for grouping & comma for decimal
|
||||
return accounting.formatNumber(normalizedNumber, precision, ' ', ',');
|
||||
}
|
||||
};
|
||||
|
||||
export default formatNumber;
|
@@ -0,0 +1,23 @@
|
||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
||||
import { add, divide, multiply, subtract } from 'lodash';
|
||||
|
||||
const mathOperation = ($: IGlobalVariable) => {
|
||||
const mathOperation = $.step.parameters.mathOperation as string;
|
||||
const values = ($.step.parameters.values as IJSONObject[]).map((value) =>
|
||||
Number(value.input)
|
||||
) as number[];
|
||||
|
||||
if (mathOperation === 'add') {
|
||||
return values.reduce((acc, curr) => add(acc, curr), 0);
|
||||
} else if (mathOperation === 'divide') {
|
||||
return values.reduce((acc, curr) => divide(acc, curr));
|
||||
} else if (mathOperation === 'makeNegative') {
|
||||
return values.map((value) => -value);
|
||||
} else if (mathOperation === 'multiply') {
|
||||
return values.reduce((acc, curr) => multiply(acc, curr), 1);
|
||||
} else if (mathOperation === 'subtract') {
|
||||
return values.reduce((acc, curr) => subtract(acc, curr));
|
||||
}
|
||||
};
|
||||
|
||||
export default mathOperation;
|
@@ -0,0 +1,15 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const randomNumber = ($: IGlobalVariable) => {
|
||||
const lowerRange = Number($.step.parameters.lowerRange);
|
||||
const upperRange = Number($.step.parameters.upperRange);
|
||||
const decimalPoints = Number($.step.parameters.decimalPoints) || 0;
|
||||
|
||||
return Number(
|
||||
(Math.random() * (upperRange - lowerRange) + lowerRange).toFixed(
|
||||
decimalPoints
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export default randomNumber;
|
79
packages/backend/src/apps/formatter/actions/text/index.ts
Normal file
79
packages/backend/src/apps/formatter/actions/text/index.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
|
||||
import capitalize from './transformers/capitalize';
|
||||
import extractEmailAddress from './transformers/extract-email-address';
|
||||
import extractNumber from './transformers/extract-number';
|
||||
import htmlToMarkdown from './transformers/html-to-markdown';
|
||||
import lowercase from './transformers/lowercase';
|
||||
import markdownToHtml from './transformers/markdown-to-html';
|
||||
import pluralize from './transformers/pluralize';
|
||||
import replace from './transformers/replace';
|
||||
import trimWhitespace from './transformers/trim-whitespace';
|
||||
import useDefaultValue from './transformers/use-default-value';
|
||||
|
||||
const transformers = {
|
||||
capitalize,
|
||||
extractEmailAddress,
|
||||
extractNumber,
|
||||
htmlToMarkdown,
|
||||
lowercase,
|
||||
markdownToHtml,
|
||||
pluralize,
|
||||
replace,
|
||||
trimWhitespace,
|
||||
useDefaultValue,
|
||||
};
|
||||
|
||||
export default defineAction({
|
||||
name: 'Text',
|
||||
key: 'text',
|
||||
description:
|
||||
'Transform text data to capitalize, extract emails, apply default value, and much more.',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Transform',
|
||||
key: 'transform',
|
||||
type: 'dropdown' as const,
|
||||
required: true,
|
||||
variables: true,
|
||||
options: [
|
||||
{ label: 'Capitalize', value: 'capitalize' },
|
||||
{ label: 'Convert HTML to Markdown', value: 'htmlToMarkdown' },
|
||||
{ label: 'Convert Markdown to HTML', value: 'markdownToHtml' },
|
||||
{ label: 'Extract Email Address', value: 'extractEmailAddress' },
|
||||
{ label: 'Extract Number', value: 'extractNumber' },
|
||||
{ label: 'Lowercase', value: 'lowercase' },
|
||||
{ label: 'Pluralize', value: 'pluralize' },
|
||||
{ label: 'Replace', value: 'replace' },
|
||||
{ label: 'Trim Whitespace', value: 'trimWhitespace' },
|
||||
{ label: 'Use Default Value', value: 'useDefaultValue' },
|
||||
],
|
||||
additionalFields: {
|
||||
type: 'query',
|
||||
name: 'getDynamicFields',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listTransformOptions',
|
||||
},
|
||||
{
|
||||
name: 'parameters.transform',
|
||||
value: '{parameters.transform}',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const transformerName = $.step.parameters
|
||||
.transform as keyof typeof transformers;
|
||||
const output = transformers[transformerName]($);
|
||||
|
||||
$.setActionItem({
|
||||
raw: {
|
||||
output,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
@@ -0,0 +1,11 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
import { capitalize as lodashCapitalize } from 'lodash';
|
||||
|
||||
const capitalize = ($: IGlobalVariable) => {
|
||||
const input = $.step.parameters.input as string;
|
||||
const capitalizedInput = input.replace(/\w+/g, lodashCapitalize);
|
||||
|
||||
return capitalizedInput;
|
||||
};
|
||||
|
||||
export default capitalize;
|
@@ -0,0 +1,12 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const extractEmailAddress = ($: IGlobalVariable) => {
|
||||
const input = $.step.parameters.input as string;
|
||||
const emailRegexp =
|
||||
/[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/;
|
||||
|
||||
const email = input.match(emailRegexp);
|
||||
return email ? email[0] : '';
|
||||
};
|
||||
|
||||
export default extractEmailAddress;
|
@@ -0,0 +1,26 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const extractNumber = ($: IGlobalVariable) => {
|
||||
const input = $.step.parameters.input as string;
|
||||
|
||||
// Example numbers that's supported:
|
||||
// 123
|
||||
// -123
|
||||
// 123456
|
||||
// -123456
|
||||
// 121,234
|
||||
// -121,234
|
||||
// 121.234
|
||||
// -121.234
|
||||
// 1,234,567.89
|
||||
// -1,234,567.89
|
||||
// 1.234.567,89
|
||||
// -1.234.567,89
|
||||
|
||||
const numberRegexp = /-?((\d{1,3})+\.?,?)+/g;
|
||||
|
||||
const numbers = input.match(numberRegexp);
|
||||
return numbers ? numbers[0] : '';
|
||||
};
|
||||
|
||||
export default extractNumber;
|
@@ -0,0 +1,11 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
import { NodeHtmlMarkdown } from 'node-html-markdown';
|
||||
|
||||
const htmlToMarkdown = ($: IGlobalVariable) => {
|
||||
const input = $.step.parameters.input as string;
|
||||
|
||||
const markdown = NodeHtmlMarkdown.translate(input);
|
||||
return markdown;
|
||||
};
|
||||
|
||||
export default htmlToMarkdown;
|
@@ -0,0 +1,8 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const lowercase = ($: IGlobalVariable) => {
|
||||
const input = $.step.parameters.input as string;
|
||||
return input.toLowerCase();
|
||||
};
|
||||
|
||||
export default lowercase;
|
@@ -0,0 +1,13 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
import showdown from 'showdown';
|
||||
|
||||
const converter = new showdown.Converter();
|
||||
|
||||
const markdownToHtml = ($: IGlobalVariable) => {
|
||||
const input = $.step.parameters.input as string;
|
||||
|
||||
const html = converter.makeHtml(input);
|
||||
return html;
|
||||
};
|
||||
|
||||
export default markdownToHtml;
|
@@ -0,0 +1,9 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
import pluralizeLibrary from 'pluralize';
|
||||
|
||||
const pluralize = ($: IGlobalVariable) => {
|
||||
const input = $.step.parameters.input as string;
|
||||
return pluralizeLibrary(input);
|
||||
};
|
||||
|
||||
export default pluralize;
|
@@ -0,0 +1,12 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const replace = ($: IGlobalVariable) => {
|
||||
const input = $.step.parameters.input as string;
|
||||
|
||||
const find = $.step.parameters.find as string;
|
||||
const replace = $.step.parameters.replace as string;
|
||||
|
||||
return input.replaceAll(find, replace);
|
||||
};
|
||||
|
||||
export default replace;
|
@@ -0,0 +1,8 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const trimWhitespace = ($: IGlobalVariable) => {
|
||||
const input = $.step.parameters.input as string;
|
||||
return input.trim();
|
||||
};
|
||||
|
||||
export default trimWhitespace;
|
@@ -0,0 +1,13 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const useDefaultValue = ($: IGlobalVariable) => {
|
||||
const input = $.step.parameters.input as string;
|
||||
|
||||
if (input && input.trim().length > 0) {
|
||||
return input;
|
||||
}
|
||||
|
||||
return $.step.parameters.defaultValue as string;
|
||||
};
|
||||
|
||||
export default useDefaultValue;
|
3
packages/backend/src/apps/formatter/assets/favicon.svg
Normal file
3
packages/backend/src/apps/formatter/assets/favicon.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4 4H20M4 12H20M4 20H20M4 8H14M4 16H14" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 243 B |
@@ -0,0 +1,3 @@
|
||||
import listTransformOptions from './list-transform-options';
|
||||
|
||||
export default [listTransformOptions];
|
@@ -0,0 +1,51 @@
|
||||
import formatOptions from './options/format';
|
||||
import timezoneOptions from './options/timezone';
|
||||
|
||||
const formatDateTime = [
|
||||
{
|
||||
label: 'Input',
|
||||
key: 'input',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'The datetime you want to format.',
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'From Format',
|
||||
key: 'fromFormat',
|
||||
type: 'dropdown' as const,
|
||||
required: true,
|
||||
description: 'The format of the input.',
|
||||
variables: true,
|
||||
options: formatOptions,
|
||||
},
|
||||
{
|
||||
label: 'From Timezone',
|
||||
key: 'fromTimezone',
|
||||
type: 'dropdown' as const,
|
||||
required: true,
|
||||
description: 'The timezone of the input.',
|
||||
variables: true,
|
||||
options: timezoneOptions,
|
||||
},
|
||||
{
|
||||
label: 'To Format',
|
||||
key: 'toFormat',
|
||||
type: 'dropdown' as const,
|
||||
required: true,
|
||||
description: 'The format of the output.',
|
||||
variables: true,
|
||||
options: formatOptions,
|
||||
},
|
||||
{
|
||||
label: 'To Timezone',
|
||||
key: 'toTimezone',
|
||||
type: 'dropdown' as const,
|
||||
required: true,
|
||||
description: 'The timezone of the output.',
|
||||
variables: true,
|
||||
options: timezoneOptions,
|
||||
},
|
||||
];
|
||||
|
||||
export default formatDateTime;
|
@@ -0,0 +1,64 @@
|
||||
const formatOptions = [
|
||||
{
|
||||
label: 'ccc MMM dd HH:mm:ssZZZ yyyy (Wed Aug 23 12:25:36-0000 2023)',
|
||||
value: 'ccc MMM dd HH:mm:ssZZZ yyyy',
|
||||
},
|
||||
{
|
||||
label: 'MMMM dd yyyy HH:mm:ss (August 23 2023 12:25:36)',
|
||||
value: 'MMMM dd yyyy HH:mm:ss',
|
||||
},
|
||||
{
|
||||
label: 'MMMM dd yyyy (August 23 2023)',
|
||||
value: 'MMMM dd yyyy',
|
||||
},
|
||||
{
|
||||
label: 'MMM dd yyyy (Aug 23 2023)',
|
||||
value: 'MMM dd yyyy',
|
||||
},
|
||||
{
|
||||
label: 'yyyy-MM-dd HH:mm:ss ZZZ (2023-08-23 12:25:36 -0000)',
|
||||
value: 'yyyy-MM-dd HH:mm:ss ZZZ',
|
||||
},
|
||||
{
|
||||
label: 'yyyy-MM-dd (2023-08-23)',
|
||||
value: 'yyyy-MM-dd',
|
||||
},
|
||||
{
|
||||
label: 'MM-dd-yyyy (08-23-2023)',
|
||||
value: 'MM-dd-yyyy',
|
||||
},
|
||||
{
|
||||
label: 'MM/dd/yyyy (08/23/2023)',
|
||||
value: 'MM/dd/yyyy',
|
||||
},
|
||||
{
|
||||
label: 'MM/dd/yy (08/23/23)',
|
||||
value: 'MM/dd/yy',
|
||||
},
|
||||
{
|
||||
label: 'dd-MM-yyyy (23-08-2023)',
|
||||
value: 'dd-MM-yyyy',
|
||||
},
|
||||
{
|
||||
label: 'dd/MM/yyyy (23/08/2023)',
|
||||
value: 'dd/MM/yyyy',
|
||||
},
|
||||
{
|
||||
label: 'dd/MM/yy (23/08/23)',
|
||||
value: 'dd/MM/yy',
|
||||
},
|
||||
{
|
||||
label: 'MM-yyyy (08-2023)',
|
||||
value: 'MM-yyyy',
|
||||
},
|
||||
{
|
||||
label: 'Unix timestamp in seconds (1694008283)',
|
||||
value: 'X',
|
||||
},
|
||||
{
|
||||
label: 'Unix timestamp in milliseconds (1694008306315)',
|
||||
value: 'x',
|
||||
},
|
||||
];
|
||||
|
||||
export default formatOptions;
|
@@ -0,0 +1,449 @@
|
||||
// The list from Intl.supportedValuesOf('timeZone') which is used by Luxon.
|
||||
|
||||
const timezoneOptions = [
|
||||
{ label: 'Africa/Abidjan', value: 'Africa/Abidjan' },
|
||||
{ label: 'Africa/Accra', value: 'Africa/Accra' },
|
||||
{ label: 'Africa/Addis_Ababa', value: 'Africa/Addis_Ababa' },
|
||||
{ label: 'Africa/Algiers', value: 'Africa/Algiers' },
|
||||
{ label: 'Africa/Asmera', value: 'Africa/Asmera' },
|
||||
{ label: 'Africa/Bamako', value: 'Africa/Bamako' },
|
||||
{ label: 'Africa/Bangui', value: 'Africa/Bangui' },
|
||||
{ label: 'Africa/Banjul', value: 'Africa/Banjul' },
|
||||
{ label: 'Africa/Bissau', value: 'Africa/Bissau' },
|
||||
{ label: 'Africa/Blantyre', value: 'Africa/Blantyre' },
|
||||
{ label: 'Africa/Brazzaville', value: 'Africa/Brazzaville' },
|
||||
{ label: 'Africa/Bujumbura', value: 'Africa/Bujumbura' },
|
||||
{ label: 'Africa/Cairo', value: 'Africa/Cairo' },
|
||||
{ label: 'Africa/Casablanca', value: 'Africa/Casablanca' },
|
||||
{ label: 'Africa/Ceuta', value: 'Africa/Ceuta' },
|
||||
{ label: 'Africa/Conakry', value: 'Africa/Conakry' },
|
||||
{ label: 'Africa/Dakar', value: 'Africa/Dakar' },
|
||||
{ label: 'Africa/Dar_es_Salaam', value: 'Africa/Dar_es_Salaam' },
|
||||
{ label: 'Africa/Djibouti', value: 'Africa/Djibouti' },
|
||||
{ label: 'Africa/Douala', value: 'Africa/Douala' },
|
||||
{ label: 'Africa/El_Aaiun', value: 'Africa/El_Aaiun' },
|
||||
{ label: 'Africa/Freetown', value: 'Africa/Freetown' },
|
||||
{ label: 'Africa/Gaborone', value: 'Africa/Gaborone' },
|
||||
{ label: 'Africa/Harare', value: 'Africa/Harare' },
|
||||
{ label: 'Africa/Johannesburg', value: 'Africa/Johannesburg' },
|
||||
{ label: 'Africa/Juba', value: 'Africa/Juba' },
|
||||
{ label: 'Africa/Kampala', value: 'Africa/Kampala' },
|
||||
{ label: 'Africa/Khartoum', value: 'Africa/Khartoum' },
|
||||
{ label: 'Africa/Kigali', value: 'Africa/Kigali' },
|
||||
{ label: 'Africa/Kinshasa', value: 'Africa/Kinshasa' },
|
||||
{ label: 'Africa/Lagos', value: 'Africa/Lagos' },
|
||||
{ label: 'Africa/Libreville', value: 'Africa/Libreville' },
|
||||
{ label: 'Africa/Lome', value: 'Africa/Lome' },
|
||||
{ label: 'Africa/Luanda', value: 'Africa/Luanda' },
|
||||
{ label: 'Africa/Lubumbashi', value: 'Africa/Lubumbashi' },
|
||||
{ label: 'Africa/Lusaka', value: 'Africa/Lusaka' },
|
||||
{ label: 'Africa/Malabo', value: 'Africa/Malabo' },
|
||||
{ label: 'Africa/Maputo', value: 'Africa/Maputo' },
|
||||
{ label: 'Africa/Maseru', value: 'Africa/Maseru' },
|
||||
{ label: 'Africa/Mbabane', value: 'Africa/Mbabane' },
|
||||
{ label: 'Africa/Mogadishu', value: 'Africa/Mogadishu' },
|
||||
{ label: 'Africa/Monrovia', value: 'Africa/Monrovia' },
|
||||
{ label: 'Africa/Nairobi', value: 'Africa/Nairobi' },
|
||||
{ label: 'Africa/Ndjamena', value: 'Africa/Ndjamena' },
|
||||
{ label: 'Africa/Niamey', value: 'Africa/Niamey' },
|
||||
{ label: 'Africa/Nouakchott', value: 'Africa/Nouakchott' },
|
||||
{ label: 'Africa/Ouagadougou', value: 'Africa/Ouagadougou' },
|
||||
{ label: 'Africa/Porto-Novo', value: 'Africa/Porto-Novo' },
|
||||
{ label: 'Africa/Sao_Tome', value: 'Africa/Sao_Tome' },
|
||||
{ label: 'Africa/Tripoli', value: 'Africa/Tripoli' },
|
||||
{ label: 'Africa/Tunis', value: 'Africa/Tunis' },
|
||||
{ label: 'Africa/Windhoek', value: 'Africa/Windhoek' },
|
||||
{ label: 'America/Adak', value: 'America/Adak' },
|
||||
{ label: 'America/Anchorage', value: 'America/Anchorage' },
|
||||
{ label: 'America/Anguilla', value: 'America/Anguilla' },
|
||||
{ label: 'America/Antigua', value: 'America/Antigua' },
|
||||
{ label: 'America/Araguaina', value: 'America/Araguaina' },
|
||||
{ label: 'America/Argentina/La_Rioja', value: 'America/Argentina/La_Rioja' },
|
||||
{
|
||||
label: 'America/Argentina/Rio_Gallegos',
|
||||
value: 'America/Argentina/Rio_Gallegos',
|
||||
},
|
||||
{ label: 'America/Argentina/Salta', value: 'America/Argentina/Salta' },
|
||||
{ label: 'America/Argentina/San_Juan', value: 'America/Argentina/San_Juan' },
|
||||
{ label: 'America/Argentina/San_Luis', value: 'America/Argentina/San_Luis' },
|
||||
{ label: 'America/Argentina/Tucuman', value: 'America/Argentina/Tucuman' },
|
||||
{ label: 'America/Argentina/Ushuaia', value: 'America/Argentina/Ushuaia' },
|
||||
{ label: 'America/Aruba', value: 'America/Aruba' },
|
||||
{ label: 'America/Asuncion', value: 'America/Asuncion' },
|
||||
{ label: 'America/Bahia', value: 'America/Bahia' },
|
||||
{ label: 'America/Bahia_Banderas', value: 'America/Bahia_Banderas' },
|
||||
{ label: 'America/Barbados', value: 'America/Barbados' },
|
||||
{ label: 'America/Belem', value: 'America/Belem' },
|
||||
{ label: 'America/Belize', value: 'America/Belize' },
|
||||
{ label: 'America/Blanc-Sablon', value: 'America/Blanc-Sablon' },
|
||||
{ label: 'America/Boa_Vista', value: 'America/Boa_Vista' },
|
||||
{ label: 'America/Bogota', value: 'America/Bogota' },
|
||||
{ label: 'America/Boise', value: 'America/Boise' },
|
||||
{ label: 'America/Buenos_Aires', value: 'America/Buenos_Aires' },
|
||||
{ label: 'America/Cambridge_Bay', value: 'America/Cambridge_Bay' },
|
||||
{ label: 'America/Campo_Grande', value: 'America/Campo_Grande' },
|
||||
{ label: 'America/Cancun', value: 'America/Cancun' },
|
||||
{ label: 'America/Caracas', value: 'America/Caracas' },
|
||||
{ label: 'America/Catamarca', value: 'America/Catamarca' },
|
||||
{ label: 'America/Cayenne', value: 'America/Cayenne' },
|
||||
{ label: 'America/Cayman', value: 'America/Cayman' },
|
||||
{ label: 'America/Chicago', value: 'America/Chicago' },
|
||||
{ label: 'America/Chihuahua', value: 'America/Chihuahua' },
|
||||
{ label: 'America/Ciudad_Juarez', value: 'America/Ciudad_Juarez' },
|
||||
{ label: 'America/Coral_Harbour', value: 'America/Coral_Harbour' },
|
||||
{ label: 'America/Cordoba', value: 'America/Cordoba' },
|
||||
{ label: 'America/Costa_Rica', value: 'America/Costa_Rica' },
|
||||
{ label: 'America/Creston', value: 'America/Creston' },
|
||||
{ label: 'America/Cuiaba', value: 'America/Cuiaba' },
|
||||
{ label: 'America/Curacao', value: 'America/Curacao' },
|
||||
{ label: 'America/Danmarkshavn', value: 'America/Danmarkshavn' },
|
||||
{ label: 'America/Dawson', value: 'America/Dawson' },
|
||||
{ label: 'America/Dawson_Creek', value: 'America/Dawson_Creek' },
|
||||
{ label: 'America/Denver', value: 'America/Denver' },
|
||||
{ label: 'America/Detroit', value: 'America/Detroit' },
|
||||
{ label: 'America/Dominica', value: 'America/Dominica' },
|
||||
{ label: 'America/Edmonton', value: 'America/Edmonton' },
|
||||
{ label: 'America/Eirunepe', value: 'America/Eirunepe' },
|
||||
{ label: 'America/El_Salvador', value: 'America/El_Salvador' },
|
||||
{ label: 'America/Fort_Nelson', value: 'America/Fort_Nelson' },
|
||||
{ label: 'America/Fortaleza', value: 'America/Fortaleza' },
|
||||
{ label: 'America/Glace_Bay', value: 'America/Glace_Bay' },
|
||||
{ label: 'America/Godthab', value: 'America/Godthab' },
|
||||
{ label: 'America/Goose_Bay', value: 'America/Goose_Bay' },
|
||||
{ label: 'America/Grand_Turk', value: 'America/Grand_Turk' },
|
||||
{ label: 'America/Grenada', value: 'America/Grenada' },
|
||||
{ label: 'America/Guadeloupe', value: 'America/Guadeloupe' },
|
||||
{ label: 'America/Guatemala', value: 'America/Guatemala' },
|
||||
{ label: 'America/Guayaquil', value: 'America/Guayaquil' },
|
||||
{ label: 'America/Guyana', value: 'America/Guyana' },
|
||||
{ label: 'America/Halifax', value: 'America/Halifax' },
|
||||
{ label: 'America/Havana', value: 'America/Havana' },
|
||||
{ label: 'America/Hermosillo', value: 'America/Hermosillo' },
|
||||
{ label: 'America/Indiana/Knox', value: 'America/Indiana/Knox' },
|
||||
{ label: 'America/Indiana/Marengo', value: 'America/Indiana/Marengo' },
|
||||
{ label: 'America/Indiana/Petersburg', value: 'America/Indiana/Petersburg' },
|
||||
{ label: 'America/Indiana/Tell_City', value: 'America/Indiana/Tell_City' },
|
||||
{ label: 'America/Indiana/Vevay', value: 'America/Indiana/Vevay' },
|
||||
{ label: 'America/Indiana/Vincennes', value: 'America/Indiana/Vincennes' },
|
||||
{ label: 'America/Indiana/Winamac', value: 'America/Indiana/Winamac' },
|
||||
{ label: 'America/Indianapolis', value: 'America/Indianapolis' },
|
||||
{ label: 'America/Inuvik', value: 'America/Inuvik' },
|
||||
{ label: 'America/Iqaluit', value: 'America/Iqaluit' },
|
||||
{ label: 'America/Jamaica', value: 'America/Jamaica' },
|
||||
{ label: 'America/Jujuy', value: 'America/Jujuy' },
|
||||
{ label: 'America/Juneau', value: 'America/Juneau' },
|
||||
{
|
||||
label: 'America/Kentucky/Monticello',
|
||||
value: 'America/Kentucky/Monticello',
|
||||
},
|
||||
{ label: 'America/Kralendijk', value: 'America/Kralendijk' },
|
||||
{ label: 'America/La_Paz', value: 'America/La_Paz' },
|
||||
{ label: 'America/Lima', value: 'America/Lima' },
|
||||
{ label: 'America/Los_Angeles', value: 'America/Los_Angeles' },
|
||||
{ label: 'America/Louisville', value: 'America/Louisville' },
|
||||
{ label: 'America/Lower_Princes', value: 'America/Lower_Princes' },
|
||||
{ label: 'America/Maceio', value: 'America/Maceio' },
|
||||
{ label: 'America/Managua', value: 'America/Managua' },
|
||||
{ label: 'America/Manaus', value: 'America/Manaus' },
|
||||
{ label: 'America/Marigot', value: 'America/Marigot' },
|
||||
{ label: 'America/Martinique', value: 'America/Martinique' },
|
||||
{ label: 'America/Matamoros', value: 'America/Matamoros' },
|
||||
{ label: 'America/Mazatlan', value: 'America/Mazatlan' },
|
||||
{ label: 'America/Mendoza', value: 'America/Mendoza' },
|
||||
{ label: 'America/Menominee', value: 'America/Menominee' },
|
||||
{ label: 'America/Merida', value: 'America/Merida' },
|
||||
{ label: 'America/Metlakatla', value: 'America/Metlakatla' },
|
||||
{ label: 'America/Mexico_City', value: 'America/Mexico_City' },
|
||||
{ label: 'America/Miquelon', value: 'America/Miquelon' },
|
||||
{ label: 'America/Moncton', value: 'America/Moncton' },
|
||||
{ label: 'America/Monterrey', value: 'America/Monterrey' },
|
||||
{ label: 'America/Montevideo', value: 'America/Montevideo' },
|
||||
{ label: 'America/Montserrat', value: 'America/Montserrat' },
|
||||
{ label: 'America/Nassau', value: 'America/Nassau' },
|
||||
{ label: 'America/New_York', value: 'America/New_York' },
|
||||
{ label: 'America/Nipigon', value: 'America/Nipigon' },
|
||||
{ label: 'America/Nome', value: 'America/Nome' },
|
||||
{ label: 'America/Noronha', value: 'America/Noronha' },
|
||||
{
|
||||
label: 'America/North_Dakota/Beulah',
|
||||
value: 'America/North_Dakota/Beulah',
|
||||
},
|
||||
{
|
||||
label: 'America/North_Dakota/Center',
|
||||
value: 'America/North_Dakota/Center',
|
||||
},
|
||||
{
|
||||
label: 'America/North_Dakota/New_Salem',
|
||||
value: 'America/North_Dakota/New_Salem',
|
||||
},
|
||||
{ label: 'America/Ojinaga', value: 'America/Ojinaga' },
|
||||
{ label: 'America/Panama', value: 'America/Panama' },
|
||||
{ label: 'America/Pangnirtung', value: 'America/Pangnirtung' },
|
||||
{ label: 'America/Paramaribo', value: 'America/Paramaribo' },
|
||||
{ label: 'America/Phoenix', value: 'America/Phoenix' },
|
||||
{ label: 'America/Port-au-Prince', value: 'America/Port-au-Prince' },
|
||||
{ label: 'America/Port_of_Spain', value: 'America/Port_of_Spain' },
|
||||
{ label: 'America/Porto_Velho', value: 'America/Porto_Velho' },
|
||||
{ label: 'America/Puerto_Rico', value: 'America/Puerto_Rico' },
|
||||
{ label: 'America/Punta_Arenas', value: 'America/Punta_Arenas' },
|
||||
{ label: 'America/Rainy_River', value: 'America/Rainy_River' },
|
||||
{ label: 'America/Rankin_Inlet', value: 'America/Rankin_Inlet' },
|
||||
{ label: 'America/Recife', value: 'America/Recife' },
|
||||
{ label: 'America/Regina', value: 'America/Regina' },
|
||||
{ label: 'America/Resolute', value: 'America/Resolute' },
|
||||
{ label: 'America/Rio_Branco', value: 'America/Rio_Branco' },
|
||||
{ label: 'America/Santa_Isabel', value: 'America/Santa_Isabel' },
|
||||
{ label: 'America/Santarem', value: 'America/Santarem' },
|
||||
{ label: 'America/Santiago', value: 'America/Santiago' },
|
||||
{ label: 'America/Santo_Domingo', value: 'America/Santo_Domingo' },
|
||||
{ label: 'America/Sao_Paulo', value: 'America/Sao_Paulo' },
|
||||
{ label: 'America/Scoresbysund', value: 'America/Scoresbysund' },
|
||||
{ label: 'America/Sitka', value: 'America/Sitka' },
|
||||
{ label: 'America/St_Barthelemy', value: 'America/St_Barthelemy' },
|
||||
{ label: 'America/St_Johns', value: 'America/St_Johns' },
|
||||
{ label: 'America/St_Kitts', value: 'America/St_Kitts' },
|
||||
{ label: 'America/St_Lucia', value: 'America/St_Lucia' },
|
||||
{ label: 'America/St_Thomas', value: 'America/St_Thomas' },
|
||||
{ label: 'America/St_Vincent', value: 'America/St_Vincent' },
|
||||
{ label: 'America/Swift_Current', value: 'America/Swift_Current' },
|
||||
{ label: 'America/Tegucigalpa', value: 'America/Tegucigalpa' },
|
||||
{ label: 'America/Thule', value: 'America/Thule' },
|
||||
{ label: 'America/Thunder_Bay', value: 'America/Thunder_Bay' },
|
||||
{ label: 'America/Tijuana', value: 'America/Tijuana' },
|
||||
{ label: 'America/Toronto', value: 'America/Toronto' },
|
||||
{ label: 'America/Tortola', value: 'America/Tortola' },
|
||||
{ label: 'America/Vancouver', value: 'America/Vancouver' },
|
||||
{ label: 'America/Whitehorse', value: 'America/Whitehorse' },
|
||||
{ label: 'America/Winnipeg', value: 'America/Winnipeg' },
|
||||
{ label: 'America/Yakutat', value: 'America/Yakutat' },
|
||||
{ label: 'America/Yellowknife', value: 'America/Yellowknife' },
|
||||
{ label: 'Antarctica/Casey', value: 'Antarctica/Casey' },
|
||||
{ label: 'Antarctica/Davis', value: 'Antarctica/Davis' },
|
||||
{ label: 'Antarctica/DumontDUrville', value: 'Antarctica/DumontDUrville' },
|
||||
{ label: 'Antarctica/Macquarie', value: 'Antarctica/Macquarie' },
|
||||
{ label: 'Antarctica/Mawson', value: 'Antarctica/Mawson' },
|
||||
{ label: 'Antarctica/McMurdo', value: 'Antarctica/McMurdo' },
|
||||
{ label: 'Antarctica/Palmer', value: 'Antarctica/Palmer' },
|
||||
{ label: 'Antarctica/Rothera', value: 'Antarctica/Rothera' },
|
||||
{ label: 'Antarctica/Syowa', value: 'Antarctica/Syowa' },
|
||||
{ label: 'Antarctica/Troll', value: 'Antarctica/Troll' },
|
||||
{ label: 'Antarctica/Vostok', value: 'Antarctica/Vostok' },
|
||||
{ label: 'Arctic/Longyearbyen', value: 'Arctic/Longyearbyen' },
|
||||
{ label: 'Asia/Aden', value: 'Asia/Aden' },
|
||||
{ label: 'Asia/Almaty', value: 'Asia/Almaty' },
|
||||
{ label: 'Asia/Amman', value: 'Asia/Amman' },
|
||||
{ label: 'Asia/Anadyr', value: 'Asia/Anadyr' },
|
||||
{ label: 'Asia/Aqtau', value: 'Asia/Aqtau' },
|
||||
{ label: 'Asia/Aqtobe', value: 'Asia/Aqtobe' },
|
||||
{ label: 'Asia/Ashgabat', value: 'Asia/Ashgabat' },
|
||||
{ label: 'Asia/Atyrau', value: 'Asia/Atyrau' },
|
||||
{ label: 'Asia/Baghdad', value: 'Asia/Baghdad' },
|
||||
{ label: 'Asia/Bahrain', value: 'Asia/Bahrain' },
|
||||
{ label: 'Asia/Baku', value: 'Asia/Baku' },
|
||||
{ label: 'Asia/Bangkok', value: 'Asia/Bangkok' },
|
||||
{ label: 'Asia/Barnaul', value: 'Asia/Barnaul' },
|
||||
{ label: 'Asia/Beirut', value: 'Asia/Beirut' },
|
||||
{ label: 'Asia/Bishkek', value: 'Asia/Bishkek' },
|
||||
{ label: 'Asia/Brunei', value: 'Asia/Brunei' },
|
||||
{ label: 'Asia/Calcutta', value: 'Asia/Calcutta' },
|
||||
{ label: 'Asia/Chita', value: 'Asia/Chita' },
|
||||
{ label: 'Asia/Choibalsan', value: 'Asia/Choibalsan' },
|
||||
{ label: 'Asia/Colombo', value: 'Asia/Colombo' },
|
||||
{ label: 'Asia/Damascus', value: 'Asia/Damascus' },
|
||||
{ label: 'Asia/Dhaka', value: 'Asia/Dhaka' },
|
||||
{ label: 'Asia/Dili', value: 'Asia/Dili' },
|
||||
{ label: 'Asia/Dubai', value: 'Asia/Dubai' },
|
||||
{ label: 'Asia/Dushanbe', value: 'Asia/Dushanbe' },
|
||||
{ label: 'Asia/Famagusta', value: 'Asia/Famagusta' },
|
||||
{ label: 'Asia/Gaza', value: 'Asia/Gaza' },
|
||||
{ label: 'Asia/Hebron', value: 'Asia/Hebron' },
|
||||
{ label: 'Asia/Hong_Kong', value: 'Asia/Hong_Kong' },
|
||||
{ label: 'Asia/Hovd', value: 'Asia/Hovd' },
|
||||
{ label: 'Asia/Irkutsk', value: 'Asia/Irkutsk' },
|
||||
{ label: 'Asia/Jakarta', value: 'Asia/Jakarta' },
|
||||
{ label: 'Asia/Jayapura', value: 'Asia/Jayapura' },
|
||||
{ label: 'Asia/Jerusalem', value: 'Asia/Jerusalem' },
|
||||
{ label: 'Asia/Kabul', value: 'Asia/Kabul' },
|
||||
{ label: 'Asia/Kamchatka', value: 'Asia/Kamchatka' },
|
||||
{ label: 'Asia/Karachi', value: 'Asia/Karachi' },
|
||||
{ label: 'Asia/Katmandu', value: 'Asia/Katmandu' },
|
||||
{ label: 'Asia/Khandyga', value: 'Asia/Khandyga' },
|
||||
{ label: 'Asia/Krasnoyarsk', value: 'Asia/Krasnoyarsk' },
|
||||
{ label: 'Asia/Kuala_Lumpur', value: 'Asia/Kuala_Lumpur' },
|
||||
{ label: 'Asia/Kuching', value: 'Asia/Kuching' },
|
||||
{ label: 'Asia/Kuwait', value: 'Asia/Kuwait' },
|
||||
{ label: 'Asia/Macau', value: 'Asia/Macau' },
|
||||
{ label: 'Asia/Magadan', value: 'Asia/Magadan' },
|
||||
{ label: 'Asia/Makassar', value: 'Asia/Makassar' },
|
||||
{ label: 'Asia/Manila', value: 'Asia/Manila' },
|
||||
{ label: 'Asia/Muscat', value: 'Asia/Muscat' },
|
||||
{ label: 'Asia/Nicosia', value: 'Asia/Nicosia' },
|
||||
{ label: 'Asia/Novokuznetsk', value: 'Asia/Novokuznetsk' },
|
||||
{ label: 'Asia/Novosibirsk', value: 'Asia/Novosibirsk' },
|
||||
{ label: 'Asia/Omsk', value: 'Asia/Omsk' },
|
||||
{ label: 'Asia/Oral', value: 'Asia/Oral' },
|
||||
{ label: 'Asia/Phnom_Penh', value: 'Asia/Phnom_Penh' },
|
||||
{ label: 'Asia/Pontianak', value: 'Asia/Pontianak' },
|
||||
{ label: 'Asia/Pyongyang', value: 'Asia/Pyongyang' },
|
||||
{ label: 'Asia/Qatar', value: 'Asia/Qatar' },
|
||||
{ label: 'Asia/Qostanay', value: 'Asia/Qostanay' },
|
||||
{ label: 'Asia/Qyzylorda', value: 'Asia/Qyzylorda' },
|
||||
{ label: 'Asia/Rangoon', value: 'Asia/Rangoon' },
|
||||
{ label: 'Asia/Riyadh', value: 'Asia/Riyadh' },
|
||||
{ label: 'Asia/Saigon', value: 'Asia/Saigon' },
|
||||
{ label: 'Asia/Sakhalin', value: 'Asia/Sakhalin' },
|
||||
{ label: 'Asia/Samarkand', value: 'Asia/Samarkand' },
|
||||
{ label: 'Asia/Seoul', value: 'Asia/Seoul' },
|
||||
{ label: 'Asia/Shanghai', value: 'Asia/Shanghai' },
|
||||
{ label: 'Asia/Singapore', value: 'Asia/Singapore' },
|
||||
{ label: 'Asia/Srednekolymsk', value: 'Asia/Srednekolymsk' },
|
||||
{ label: 'Asia/Taipei', value: 'Asia/Taipei' },
|
||||
{ label: 'Asia/Tashkent', value: 'Asia/Tashkent' },
|
||||
{ label: 'Asia/Tbilisi', value: 'Asia/Tbilisi' },
|
||||
{ label: 'Asia/Tehran', value: 'Asia/Tehran' },
|
||||
{ label: 'Asia/Thimphu', value: 'Asia/Thimphu' },
|
||||
{ label: 'Asia/Tokyo', value: 'Asia/Tokyo' },
|
||||
{ label: 'Asia/Tomsk', value: 'Asia/Tomsk' },
|
||||
{ label: 'Asia/Ulaanbaatar', value: 'Asia/Ulaanbaatar' },
|
||||
{ label: 'Asia/Urumqi', value: 'Asia/Urumqi' },
|
||||
{ label: 'Asia/Ust-Nera', value: 'Asia/Ust-Nera' },
|
||||
{ label: 'Asia/Vientiane', value: 'Asia/Vientiane' },
|
||||
{ label: 'Asia/Vladivostok', value: 'Asia/Vladivostok' },
|
||||
{ label: 'Asia/Yakutsk', value: 'Asia/Yakutsk' },
|
||||
{ label: 'Asia/Yekaterinburg', value: 'Asia/Yekaterinburg' },
|
||||
{ label: 'Asia/Yerevan', value: 'Asia/Yerevan' },
|
||||
{ label: 'Atlantic/Azores', value: 'Atlantic/Azores' },
|
||||
{ label: 'Atlantic/Bermuda', value: 'Atlantic/Bermuda' },
|
||||
{ label: 'Atlantic/Canary', value: 'Atlantic/Canary' },
|
||||
{ label: 'Atlantic/Cape_Verde', value: 'Atlantic/Cape_Verde' },
|
||||
{ label: 'Atlantic/Faeroe', value: 'Atlantic/Faeroe' },
|
||||
{ label: 'Atlantic/Madeira', value: 'Atlantic/Madeira' },
|
||||
{ label: 'Atlantic/Reykjavik', value: 'Atlantic/Reykjavik' },
|
||||
{ label: 'Atlantic/South_Georgia', value: 'Atlantic/South_Georgia' },
|
||||
{ label: 'Atlantic/St_Helena', value: 'Atlantic/St_Helena' },
|
||||
{ label: 'Atlantic/Stanley', value: 'Atlantic/Stanley' },
|
||||
{ label: 'Australia/Adelaide', value: 'Australia/Adelaide' },
|
||||
{ label: 'Australia/Brisbane', value: 'Australia/Brisbane' },
|
||||
{ label: 'Australia/Broken_Hill', value: 'Australia/Broken_Hill' },
|
||||
{ label: 'Australia/Currie', value: 'Australia/Currie' },
|
||||
{ label: 'Australia/Darwin', value: 'Australia/Darwin' },
|
||||
{ label: 'Australia/Eucla', value: 'Australia/Eucla' },
|
||||
{ label: 'Australia/Hobart', value: 'Australia/Hobart' },
|
||||
{ label: 'Australia/Lindeman', value: 'Australia/Lindeman' },
|
||||
{ label: 'Australia/Lord_Howe', value: 'Australia/Lord_Howe' },
|
||||
{ label: 'Australia/Melbourne', value: 'Australia/Melbourne' },
|
||||
{ label: 'Australia/Perth', value: 'Australia/Perth' },
|
||||
{ label: 'Australia/Sydney', value: 'Australia/Sydney' },
|
||||
{ label: 'Europe/Amsterdam', value: 'Europe/Amsterdam' },
|
||||
{ label: 'Europe/Andorra', value: 'Europe/Andorra' },
|
||||
{ label: 'Europe/Astrakhan', value: 'Europe/Astrakhan' },
|
||||
{ label: 'Europe/Athens', value: 'Europe/Athens' },
|
||||
{ label: 'Europe/Belgrade', value: 'Europe/Belgrade' },
|
||||
{ label: 'Europe/Berlin', value: 'Europe/Berlin' },
|
||||
{ label: 'Europe/Bratislava', value: 'Europe/Bratislava' },
|
||||
{ label: 'Europe/Brussels', value: 'Europe/Brussels' },
|
||||
{ label: 'Europe/Bucharest', value: 'Europe/Bucharest' },
|
||||
{ label: 'Europe/Budapest', value: 'Europe/Budapest' },
|
||||
{ label: 'Europe/Busingen', value: 'Europe/Busingen' },
|
||||
{ label: 'Europe/Chisinau', value: 'Europe/Chisinau' },
|
||||
{ label: 'Europe/Copenhagen', value: 'Europe/Copenhagen' },
|
||||
{ label: 'Europe/Dublin', value: 'Europe/Dublin' },
|
||||
{ label: 'Europe/Gibraltar', value: 'Europe/Gibraltar' },
|
||||
{ label: 'Europe/Guernsey', value: 'Europe/Guernsey' },
|
||||
{ label: 'Europe/Helsinki', value: 'Europe/Helsinki' },
|
||||
{ label: 'Europe/Isle_of_Man', value: 'Europe/Isle_of_Man' },
|
||||
{ label: 'Europe/Istanbul', value: 'Europe/Istanbul' },
|
||||
{ label: 'Europe/Jersey', value: 'Europe/Jersey' },
|
||||
{ label: 'Europe/Kaliningrad', value: 'Europe/Kaliningrad' },
|
||||
{ label: 'Europe/Kiev', value: 'Europe/Kiev' },
|
||||
{ label: 'Europe/Kirov', value: 'Europe/Kirov' },
|
||||
{ label: 'Europe/Lisbon', value: 'Europe/Lisbon' },
|
||||
{ label: 'Europe/Ljubljana', value: 'Europe/Ljubljana' },
|
||||
{ label: 'Europe/London', value: 'Europe/London' },
|
||||
{ label: 'Europe/Luxembourg', value: 'Europe/Luxembourg' },
|
||||
{ label: 'Europe/Madrid', value: 'Europe/Madrid' },
|
||||
{ label: 'Europe/Malta', value: 'Europe/Malta' },
|
||||
{ label: 'Europe/Mariehamn', value: 'Europe/Mariehamn' },
|
||||
{ label: 'Europe/Minsk', value: 'Europe/Minsk' },
|
||||
{ label: 'Europe/Monaco', value: 'Europe/Monaco' },
|
||||
{ label: 'Europe/Moscow', value: 'Europe/Moscow' },
|
||||
{ label: 'Europe/Oslo', value: 'Europe/Oslo' },
|
||||
{ label: 'Europe/Paris', value: 'Europe/Paris' },
|
||||
{ label: 'Europe/Podgorica', value: 'Europe/Podgorica' },
|
||||
{ label: 'Europe/Prague', value: 'Europe/Prague' },
|
||||
{ label: 'Europe/Riga', value: 'Europe/Riga' },
|
||||
{ label: 'Europe/Rome', value: 'Europe/Rome' },
|
||||
{ label: 'Europe/Samara', value: 'Europe/Samara' },
|
||||
{ label: 'Europe/San_Marino', value: 'Europe/San_Marino' },
|
||||
{ label: 'Europe/Sarajevo', value: 'Europe/Sarajevo' },
|
||||
{ label: 'Europe/Saratov', value: 'Europe/Saratov' },
|
||||
{ label: 'Europe/Simferopol', value: 'Europe/Simferopol' },
|
||||
{ label: 'Europe/Skopje', value: 'Europe/Skopje' },
|
||||
{ label: 'Europe/Sofia', value: 'Europe/Sofia' },
|
||||
{ label: 'Europe/Stockholm', value: 'Europe/Stockholm' },
|
||||
{ label: 'Europe/Tallinn', value: 'Europe/Tallinn' },
|
||||
{ label: 'Europe/Tirane', value: 'Europe/Tirane' },
|
||||
{ label: 'Europe/Ulyanovsk', value: 'Europe/Ulyanovsk' },
|
||||
{ label: 'Europe/Uzhgorod', value: 'Europe/Uzhgorod' },
|
||||
{ label: 'Europe/Vaduz', value: 'Europe/Vaduz' },
|
||||
{ label: 'Europe/Vatican', value: 'Europe/Vatican' },
|
||||
{ label: 'Europe/Vienna', value: 'Europe/Vienna' },
|
||||
{ label: 'Europe/Vilnius', value: 'Europe/Vilnius' },
|
||||
{ label: 'Europe/Volgograd', value: 'Europe/Volgograd' },
|
||||
{ label: 'Europe/Warsaw', value: 'Europe/Warsaw' },
|
||||
{ label: 'Europe/Zagreb', value: 'Europe/Zagreb' },
|
||||
{ label: 'Europe/Zaporozhye', value: 'Europe/Zaporozhye' },
|
||||
{ label: 'Europe/Zurich', value: 'Europe/Zurich' },
|
||||
{ label: 'Indian/Antananarivo', value: 'Indian/Antananarivo' },
|
||||
{ label: 'Indian/Chagos', value: 'Indian/Chagos' },
|
||||
{ label: 'Indian/Christmas', value: 'Indian/Christmas' },
|
||||
{ label: 'Indian/Cocos', value: 'Indian/Cocos' },
|
||||
{ label: 'Indian/Comoro', value: 'Indian/Comoro' },
|
||||
{ label: 'Indian/Kerguelen', value: 'Indian/Kerguelen' },
|
||||
{ label: 'Indian/Mahe', value: 'Indian/Mahe' },
|
||||
{ label: 'Indian/Maldives', value: 'Indian/Maldives' },
|
||||
{ label: 'Indian/Mauritius', value: 'Indian/Mauritius' },
|
||||
{ label: 'Indian/Mayotte', value: 'Indian/Mayotte' },
|
||||
{ label: 'Indian/Reunion', value: 'Indian/Reunion' },
|
||||
{ label: 'Pacific/Apia', value: 'Pacific/Apia' },
|
||||
{ label: 'Pacific/Auckland', value: 'Pacific/Auckland' },
|
||||
{ label: 'Pacific/Bougainville', value: 'Pacific/Bougainville' },
|
||||
{ label: 'Pacific/Chatham', value: 'Pacific/Chatham' },
|
||||
{ label: 'Pacific/Easter', value: 'Pacific/Easter' },
|
||||
{ label: 'Pacific/Efate', value: 'Pacific/Efate' },
|
||||
{ label: 'Pacific/Enderbury', value: 'Pacific/Enderbury' },
|
||||
{ label: 'Pacific/Fakaofo', value: 'Pacific/Fakaofo' },
|
||||
{ label: 'Pacific/Fiji', value: 'Pacific/Fiji' },
|
||||
{ label: 'Pacific/Funafuti', value: 'Pacific/Funafuti' },
|
||||
{ label: 'Pacific/Galapagos', value: 'Pacific/Galapagos' },
|
||||
{ label: 'Pacific/Gambier', value: 'Pacific/Gambier' },
|
||||
{ label: 'Pacific/Guadalcanal', value: 'Pacific/Guadalcanal' },
|
||||
{ label: 'Pacific/Guam', value: 'Pacific/Guam' },
|
||||
{ label: 'Pacific/Honolulu', value: 'Pacific/Honolulu' },
|
||||
{ label: 'Pacific/Johnston', value: 'Pacific/Johnston' },
|
||||
{ label: 'Pacific/Kiritimati', value: 'Pacific/Kiritimati' },
|
||||
{ label: 'Pacific/Kosrae', value: 'Pacific/Kosrae' },
|
||||
{ label: 'Pacific/Kwajalein', value: 'Pacific/Kwajalein' },
|
||||
{ label: 'Pacific/Majuro', value: 'Pacific/Majuro' },
|
||||
{ label: 'Pacific/Marquesas', value: 'Pacific/Marquesas' },
|
||||
{ label: 'Pacific/Midway', value: 'Pacific/Midway' },
|
||||
{ label: 'Pacific/Nauru', value: 'Pacific/Nauru' },
|
||||
{ label: 'Pacific/Niue', value: 'Pacific/Niue' },
|
||||
{ label: 'Pacific/Norfolk', value: 'Pacific/Norfolk' },
|
||||
{ label: 'Pacific/Noumea', value: 'Pacific/Noumea' },
|
||||
{ label: 'Pacific/Pago_Pago', value: 'Pacific/Pago_Pago' },
|
||||
{ label: 'Pacific/Palau', value: 'Pacific/Palau' },
|
||||
{ label: 'Pacific/Pitcairn', value: 'Pacific/Pitcairn' },
|
||||
{ label: 'Pacific/Ponape', value: 'Pacific/Ponape' },
|
||||
{ label: 'Pacific/Port_Moresby', value: 'Pacific/Port_Moresby' },
|
||||
{ label: 'Pacific/Rarotonga', value: 'Pacific/Rarotonga' },
|
||||
{ label: 'Pacific/Saipan', value: 'Pacific/Saipan' },
|
||||
{ label: 'Pacific/Tahiti', value: 'Pacific/Tahiti' },
|
||||
{ label: 'Pacific/Tarawa', value: 'Pacific/Tarawa' },
|
||||
{ label: 'Pacific/Tongatapu', value: 'Pacific/Tongatapu' },
|
||||
{ label: 'Pacific/Truk', value: 'Pacific/Truk' },
|
||||
{ label: 'Pacific/Wake', value: 'Pacific/Wake' },
|
||||
{ label: 'Pacific/Wallis', value: 'Pacific/Wallis' },
|
||||
];
|
||||
|
||||
export default timezoneOptions;
|
@@ -0,0 +1,41 @@
|
||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
||||
import capitalize from './text/capitalize';
|
||||
import extractEmailAddress from './text/extract-email-address';
|
||||
import extractNumber from './text/extract-number';
|
||||
import htmlToMarkdown from './text/html-to-markdown';
|
||||
import lowercase from './text/lowercase';
|
||||
import markdownToHtml from './text/markdown-to-html';
|
||||
import pluralize from './text/pluralize';
|
||||
import replace from './text/replace';
|
||||
import trimWhitespace from './text/trim-whitespace';
|
||||
import useDefaultValue from './text/use-default-value';
|
||||
import performMathOperation from './numbers/perform-math-operation';
|
||||
import randomNumber from './numbers/random-number';
|
||||
import formatNumber from './numbers/format-number';
|
||||
import formatDateTime from './date-time/format-date-time';
|
||||
|
||||
const options: IJSONObject = {
|
||||
capitalize,
|
||||
extractEmailAddress,
|
||||
extractNumber,
|
||||
htmlToMarkdown,
|
||||
lowercase,
|
||||
markdownToHtml,
|
||||
pluralize,
|
||||
replace,
|
||||
trimWhitespace,
|
||||
useDefaultValue,
|
||||
performMathOperation,
|
||||
randomNumber,
|
||||
formatNumber,
|
||||
formatDateTime,
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'List fields after transform',
|
||||
key: 'listTransformOptions',
|
||||
|
||||
async run($: IGlobalVariable) {
|
||||
return options[$.step.parameters.transform as string];
|
||||
},
|
||||
};
|
@@ -0,0 +1,38 @@
|
||||
const formatNumber = [
|
||||
{
|
||||
label: 'Input',
|
||||
key: 'input',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'The number you want to format.',
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'Input Decimal Mark',
|
||||
key: 'inputDecimalMark',
|
||||
type: 'dropdown' as const,
|
||||
required: true,
|
||||
description: 'The decimal mark of the input number.',
|
||||
variables: true,
|
||||
options: [
|
||||
{ label: 'Comma', value: ',' },
|
||||
{ label: 'Period', value: '.' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'To Format',
|
||||
key: 'toFormat',
|
||||
type: 'dropdown' as const,
|
||||
required: true,
|
||||
description: 'The format you want to convert the number to.',
|
||||
variables: true,
|
||||
options: [
|
||||
{ label: 'Comma for grouping & period for decimal', value: '0' },
|
||||
{ label: 'Period for grouping & comma for decimal', value: '1' },
|
||||
{ label: 'Space for grouping & period for decimal', value: '2' },
|
||||
{ label: 'Space for grouping & comma for decimal', value: '3' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default formatNumber;
|
@@ -0,0 +1,36 @@
|
||||
const performMathOperation = [
|
||||
{
|
||||
label: 'Math Operation',
|
||||
key: 'mathOperation',
|
||||
type: 'dropdown' as const,
|
||||
required: true,
|
||||
description: 'The math operation to perform.',
|
||||
variables: true,
|
||||
options: [
|
||||
{ label: 'Add', value: 'add' },
|
||||
{ label: 'Divide', value: 'divide' },
|
||||
{ label: 'Make Negative', value: 'makeNegative' },
|
||||
{ label: 'Multiply', value: 'multiply' },
|
||||
{ label: 'Subtract', value: 'subtract' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Values',
|
||||
key: 'values',
|
||||
type: 'dynamic' as const,
|
||||
required: false,
|
||||
description: 'Add or remove numbers as needed.',
|
||||
fields: [
|
||||
{
|
||||
label: 'Input',
|
||||
key: 'input',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'The number to perform the math operation on.',
|
||||
variables: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default performMathOperation;
|
@@ -0,0 +1,29 @@
|
||||
const randomNumber = [
|
||||
{
|
||||
label: 'Lower range',
|
||||
key: 'lowerRange',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'The lowest number to generate.',
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'Upper range',
|
||||
key: 'upperRange',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'The highest number to generate.',
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'Decimal points',
|
||||
key: 'decimalPoints',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
description:
|
||||
'The number of digits after the decimal point. It can be an integer between 0 and 15.',
|
||||
variables: true,
|
||||
},
|
||||
];
|
||||
|
||||
export default randomNumber;
|
@@ -0,0 +1,12 @@
|
||||
const capitalize = [
|
||||
{
|
||||
label: 'Input',
|
||||
key: 'input',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'Text that will be capitalized.',
|
||||
variables: true,
|
||||
},
|
||||
];
|
||||
|
||||
export default capitalize;
|
@@ -0,0 +1,12 @@
|
||||
const extractEmailAddress = [
|
||||
{
|
||||
label: 'Input',
|
||||
key: 'input',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'Text that will be searched for an email address.',
|
||||
variables: true,
|
||||
},
|
||||
];
|
||||
|
||||
export default extractEmailAddress;
|
@@ -0,0 +1,12 @@
|
||||
const extractNumber = [
|
||||
{
|
||||
label: 'Input',
|
||||
key: 'input',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'Text that will be searched for a number.',
|
||||
variables: true,
|
||||
},
|
||||
];
|
||||
|
||||
export default extractNumber;
|
@@ -0,0 +1,12 @@
|
||||
const htmlToMarkdown = [
|
||||
{
|
||||
label: 'Input',
|
||||
key: 'input',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'HTML that will be converted to Markdown.',
|
||||
variables: true,
|
||||
},
|
||||
];
|
||||
|
||||
export default htmlToMarkdown;
|
@@ -0,0 +1,12 @@
|
||||
const lowercase = [
|
||||
{
|
||||
label: 'Input',
|
||||
key: 'input',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'Text that will be lowercased.',
|
||||
variables: true,
|
||||
},
|
||||
];
|
||||
|
||||
export default lowercase;
|
@@ -0,0 +1,12 @@
|
||||
const markdownToHtml = [
|
||||
{
|
||||
label: 'Input',
|
||||
key: 'input',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'Markdown text that will be converted to HTML.',
|
||||
variables: true,
|
||||
},
|
||||
];
|
||||
|
||||
export default markdownToHtml;
|
@@ -0,0 +1,12 @@
|
||||
const pluralize = [
|
||||
{
|
||||
label: 'Input',
|
||||
key: 'input',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'Text that will be pluralized.',
|
||||
variables: true,
|
||||
},
|
||||
];
|
||||
|
||||
export default pluralize;
|
@@ -0,0 +1,28 @@
|
||||
const replace = [
|
||||
{
|
||||
label: 'Input',
|
||||
key: 'input',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'Text that you want to search for and replace values.',
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'Find',
|
||||
key: 'find',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'Text that will be searched for.',
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'Replace',
|
||||
key: 'replace',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
description: 'Text that will replace the found text.',
|
||||
variables: true,
|
||||
},
|
||||
];
|
||||
|
||||
export default replace;
|
@@ -0,0 +1,12 @@
|
||||
const trimWhitespace = [
|
||||
{
|
||||
label: 'Input',
|
||||
key: 'input',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'Text you want to remove leading and trailing spaces.',
|
||||
variables: true,
|
||||
},
|
||||
];
|
||||
|
||||
export default trimWhitespace;
|
@@ -0,0 +1,21 @@
|
||||
const useDefaultValue = [
|
||||
{
|
||||
label: 'Input',
|
||||
key: 'input',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'Text you want to check whether it is empty or not.',
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'Default Value',
|
||||
key: 'defaultValue',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description:
|
||||
'Text that will be used as a default value if the input is empty.',
|
||||
variables: true,
|
||||
},
|
||||
];
|
||||
|
||||
export default useDefaultValue;
|
0
packages/backend/src/apps/formatter/index.d.ts
vendored
Normal file
0
packages/backend/src/apps/formatter/index.d.ts
vendored
Normal file
16
packages/backend/src/apps/formatter/index.ts
Normal file
16
packages/backend/src/apps/formatter/index.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import defineApp from '../../helpers/define-app';
|
||||
import actions from './actions';
|
||||
import dynamicFields from './dynamic-fields';
|
||||
|
||||
export default defineApp({
|
||||
name: 'Formatter',
|
||||
key: 'formatter',
|
||||
iconUrl: '{BASE_URL}/apps/formatter/assets/favicon.svg',
|
||||
authDocUrl: 'https://automatisch.io/docs/apps/formatter/connection',
|
||||
supportsConnections: false,
|
||||
baseUrl: '',
|
||||
apiBaseUrl: '',
|
||||
primaryColor: '001F52',
|
||||
actions,
|
||||
dynamicFields,
|
||||
});
|
@@ -25,6 +25,12 @@ const verifyCredentials = async ($: IGlobalVariable) => {
|
||||
$.auth.data.accessToken = data.access_token;
|
||||
|
||||
const currentUser = await getCurrentUser($);
|
||||
const screenName = [
|
||||
currentUser.username,
|
||||
$.auth.data.instanceUrl,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' @ ');
|
||||
|
||||
await $.auth.set({
|
||||
clientId: $.auth.data.clientId,
|
||||
@@ -34,7 +40,7 @@ const verifyCredentials = async ($: IGlobalVariable) => {
|
||||
scope: data.scope,
|
||||
tokenType: data.token_type,
|
||||
userId: currentUser.id,
|
||||
screenName: `${currentUser.username} @ ${$.auth.data.instanceUrl}`,
|
||||
screenName,
|
||||
});
|
||||
};
|
||||
|
||||
|
27
packages/backend/src/apps/google-calendar/assets/favicon.svg
Normal file
27
packages/backend/src/apps/google-calendar/assets/favicon.svg
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg version="1.1" id="Livello_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
|
||||
<g>
|
||||
<g transform="translate(3.75 3.75)">
|
||||
<path fill="#FFFFFF" d="M148.882,43.618l-47.368-5.263l-57.895,5.263L38.355,96.25l5.263,52.632l52.632,6.579l52.632-6.579
|
||||
l5.263-53.947L148.882,43.618z"/>
|
||||
<path fill="#1A73E8" d="M65.211,125.276c-3.934-2.658-6.658-6.539-8.145-11.671l9.132-3.763c0.829,3.158,2.276,5.605,4.342,7.342
|
||||
c2.053,1.737,4.553,2.592,7.474,2.592c2.987,0,5.553-0.908,7.697-2.724s3.224-4.132,3.224-6.934c0-2.868-1.132-5.211-3.395-7.026
|
||||
s-5.105-2.724-8.5-2.724h-5.276v-9.039H76.5c2.921,0,5.382-0.789,7.382-2.368c2-1.579,3-3.737,3-6.487
|
||||
c0-2.447-0.895-4.395-2.684-5.855s-4.053-2.197-6.803-2.197c-2.684,0-4.816,0.711-6.395,2.145s-2.724,3.197-3.447,5.276
|
||||
l-9.039-3.763c1.197-3.395,3.395-6.395,6.618-8.987c3.224-2.592,7.342-3.895,12.342-3.895c3.697,0,7.026,0.711,9.974,2.145
|
||||
c2.947,1.434,5.263,3.421,6.934,5.947c1.671,2.539,2.5,5.382,2.5,8.539c0,3.224-0.776,5.947-2.329,8.184
|
||||
c-1.553,2.237-3.461,3.947-5.724,5.145v0.539c2.987,1.25,5.421,3.158,7.342,5.724c1.908,2.566,2.868,5.632,2.868,9.211
|
||||
s-0.908,6.776-2.724,9.579c-1.816,2.803-4.329,5.013-7.513,6.618c-3.197,1.605-6.789,2.421-10.776,2.421
|
||||
C73.408,129.263,69.145,127.934,65.211,125.276z"/>
|
||||
<path fill="#1A73E8" d="M121.25,79.961l-9.974,7.25l-5.013-7.605l17.987-12.974h6.895v61.197h-9.895L121.25,79.961z"/>
|
||||
<path fill="#EA4335" d="M148.882,196.25l47.368-47.368l-23.684-10.526l-23.684,10.526l-10.526,23.684L148.882,196.25z"/>
|
||||
<path fill="#34A853" d="M33.092,172.566l10.526,23.684h105.263v-47.368H43.618L33.092,172.566z"/>
|
||||
<path fill="#4285F4" d="M12.039-3.75C3.316-3.75-3.75,3.316-3.75,12.039v136.842l23.684,10.526l23.684-10.526V43.618h105.263
|
||||
l10.526-23.684L148.882-3.75H12.039z"/>
|
||||
<path fill="#188038" d="M-3.75,148.882v31.579c0,8.724,7.066,15.789,15.789,15.789h31.579v-47.368H-3.75z"/>
|
||||
<path fill="#FBBC04" d="M148.882,43.618v105.263h47.368V43.618l-23.684-10.526L148.882,43.618z"/>
|
||||
<path fill="#1967D2" d="M196.25,43.618V12.039c0-8.724-7.066-15.789-15.789-15.789h-31.579v47.368H196.25z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1,24 @@
|
||||
import { IField, IGlobalVariable } from '@automatisch/types';
|
||||
import { URLSearchParams } from 'url';
|
||||
import authScope from '../common/auth-scope';
|
||||
|
||||
export default async function generateAuthUrl($: IGlobalVariable) {
|
||||
const oauthRedirectUrlField = $.app.auth.fields.find(
|
||||
(field: IField) => field.key == 'oAuthRedirectUrl'
|
||||
);
|
||||
const redirectUri = oauthRedirectUrlField.value as string;
|
||||
const searchParams = new URLSearchParams({
|
||||
client_id: $.auth.data.clientId as string,
|
||||
redirect_uri: redirectUri,
|
||||
prompt: 'select_account',
|
||||
scope: authScope.join(' '),
|
||||
response_type: 'code',
|
||||
access_type: 'offline',
|
||||
});
|
||||
|
||||
const url = `https://accounts.google.com/o/oauth2/v2/auth?${searchParams.toString()}`;
|
||||
|
||||
await $.auth.set({
|
||||
url,
|
||||
});
|
||||
}
|
48
packages/backend/src/apps/google-calendar/auth/index.ts
Normal file
48
packages/backend/src/apps/google-calendar/auth/index.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import generateAuthUrl from './generate-auth-url';
|
||||
import verifyCredentials from './verify-credentials';
|
||||
import refreshToken from './refresh-token';
|
||||
import isStillVerified from './is-still-verified';
|
||||
|
||||
export default {
|
||||
fields: [
|
||||
{
|
||||
key: 'oAuthRedirectUrl',
|
||||
label: 'OAuth Redirect URL',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: true,
|
||||
value: '{WEB_APP_URL}/app/google-calendar/connections/add',
|
||||
placeholder: null,
|
||||
description:
|
||||
'When asked to input a redirect URL in Google Cloud, enter the URL above.',
|
||||
clickToCopy: true,
|
||||
},
|
||||
{
|
||||
key: 'clientId',
|
||||
label: 'Client ID',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
description: null,
|
||||
clickToCopy: false,
|
||||
},
|
||||
{
|
||||
key: 'clientSecret',
|
||||
label: 'Client Secret',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
description: null,
|
||||
clickToCopy: false,
|
||||
},
|
||||
],
|
||||
|
||||
generateAuthUrl,
|
||||
verifyCredentials,
|
||||
isStillVerified,
|
||||
refreshToken,
|
||||
};
|
@@ -0,0 +1,9 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
import getCurrentUser from '../common/get-current-user';
|
||||
|
||||
const isStillVerified = async ($: IGlobalVariable) => {
|
||||
const currentUser = await getCurrentUser($);
|
||||
return !!currentUser.resourceName;
|
||||
};
|
||||
|
||||
export default isStillVerified;
|
@@ -0,0 +1,26 @@
|
||||
import { URLSearchParams } from 'node:url';
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
import authScope from '../common/auth-scope';
|
||||
|
||||
const refreshToken = async ($: IGlobalVariable) => {
|
||||
const params = new URLSearchParams({
|
||||
client_id: $.auth.data.clientId as string,
|
||||
client_secret: $.auth.data.clientSecret as string,
|
||||
grant_type: 'refresh_token',
|
||||
refresh_token: $.auth.data.refreshToken as string,
|
||||
});
|
||||
|
||||
const { data } = await $.http.post(
|
||||
'https://oauth2.googleapis.com/token',
|
||||
params.toString()
|
||||
);
|
||||
|
||||
await $.auth.set({
|
||||
accessToken: data.access_token,
|
||||
expiresIn: data.expires_in,
|
||||
scope: authScope.join(' '),
|
||||
tokenType: data.token_type,
|
||||
});
|
||||
};
|
||||
|
||||
export default refreshToken;
|
@@ -0,0 +1,57 @@
|
||||
import { IField, IGlobalVariable } from '@automatisch/types';
|
||||
import getCurrentUser from '../common/get-current-user';
|
||||
|
||||
type TUser = {
|
||||
displayName: string;
|
||||
metadata: {
|
||||
primary: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
type TEmailAddress = {
|
||||
value: string;
|
||||
metadata: {
|
||||
primary: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
const verifyCredentials = async ($: IGlobalVariable) => {
|
||||
const oauthRedirectUrlField = $.app.auth.fields.find(
|
||||
(field: IField) => field.key == 'oAuthRedirectUrl'
|
||||
);
|
||||
const redirectUri = oauthRedirectUrlField.value as string;
|
||||
const { data } = await $.http.post(`https://oauth2.googleapis.com/token`, {
|
||||
client_id: $.auth.data.clientId,
|
||||
client_secret: $.auth.data.clientSecret,
|
||||
code: $.auth.data.code,
|
||||
grant_type: 'authorization_code',
|
||||
redirect_uri: redirectUri,
|
||||
});
|
||||
|
||||
await $.auth.set({
|
||||
accessToken: data.access_token,
|
||||
tokenType: data.token_type,
|
||||
});
|
||||
|
||||
const currentUser = await getCurrentUser($);
|
||||
|
||||
const { displayName } = currentUser.names.find(
|
||||
(name: TUser) => name.metadata.primary
|
||||
);
|
||||
const { value: email } = currentUser.emailAddresses.find(
|
||||
(emailAddress: TEmailAddress) => emailAddress.metadata.primary
|
||||
);
|
||||
|
||||
await $.auth.set({
|
||||
clientId: $.auth.data.clientId,
|
||||
clientSecret: $.auth.data.clientSecret,
|
||||
scope: $.auth.data.scope,
|
||||
idToken: data.id_token,
|
||||
expiresIn: data.expires_in,
|
||||
refreshToken: data.refresh_token,
|
||||
resourceName: currentUser.resourceName,
|
||||
screenName: `${displayName} - ${email}`,
|
||||
});
|
||||
};
|
||||
|
||||
export default verifyCredentials;
|
@@ -0,0 +1,11 @@
|
||||
import { TBeforeRequest } from '@automatisch/types';
|
||||
|
||||
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
|
||||
if ($.auth.data?.accessToken) {
|
||||
requestConfig.headers.Authorization = `${$.auth.data.tokenType} ${$.auth.data.accessToken}`;
|
||||
}
|
||||
|
||||
return requestConfig;
|
||||
};
|
||||
|
||||
export default addAuthHeader;
|
@@ -0,0 +1,7 @@
|
||||
const authScope: string[] = [
|
||||
'https://www.googleapis.com/auth/calendar',
|
||||
'https://www.googleapis.com/auth/userinfo.email',
|
||||
'https://www.googleapis.com/auth/userinfo.profile',
|
||||
];
|
||||
|
||||
export default authScope;
|
@@ -0,0 +1,10 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const getCurrentUser = async ($: IGlobalVariable) => {
|
||||
const { data: currentUser } = await $.http.get(
|
||||
'https://people.googleapis.com/v1/people/me?personFields=names,emailAddresses'
|
||||
);
|
||||
return currentUser;
|
||||
};
|
||||
|
||||
export default getCurrentUser;
|
@@ -0,0 +1,3 @@
|
||||
import listCalendars from './list-calendars';
|
||||
|
||||
export default [listCalendars];
|
@@ -0,0 +1,36 @@
|
||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
||||
|
||||
export default {
|
||||
name: 'List calendars',
|
||||
key: 'listCalendars',
|
||||
|
||||
async run($: IGlobalVariable) {
|
||||
const drives: {
|
||||
data: IJSONObject[];
|
||||
} = {
|
||||
data: [],
|
||||
};
|
||||
|
||||
const params = {
|
||||
pageToken: undefined as unknown as string,
|
||||
};
|
||||
|
||||
do {
|
||||
const { data } = await $.http.get(`/v3/users/me/calendarList`, {
|
||||
params,
|
||||
});
|
||||
params.pageToken = data.nextPageToken;
|
||||
|
||||
if (data.items) {
|
||||
for (const calendar of data.items) {
|
||||
drives.data.push({
|
||||
value: calendar.id,
|
||||
name: calendar.summary,
|
||||
});
|
||||
}
|
||||
}
|
||||
} while (params.pageToken);
|
||||
|
||||
return drives;
|
||||
},
|
||||
};
|
0
packages/backend/src/apps/google-calendar/index.d.ts
vendored
Normal file
0
packages/backend/src/apps/google-calendar/index.d.ts
vendored
Normal file
20
packages/backend/src/apps/google-calendar/index.ts
Normal file
20
packages/backend/src/apps/google-calendar/index.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import defineApp from '../../helpers/define-app';
|
||||
import addAuthHeader from './common/add-auth-header';
|
||||
import auth from './auth';
|
||||
import triggers from './triggers';
|
||||
import dynamicData from './dynamic-data';
|
||||
|
||||
export default defineApp({
|
||||
name: 'Google Calendar',
|
||||
key: 'google-calendar',
|
||||
baseUrl: 'https://calendar.google.com',
|
||||
apiBaseUrl: 'https://www.googleapis.com/calendar',
|
||||
iconUrl: '{BASE_URL}/apps/google-calendar/assets/favicon.svg',
|
||||
authDocUrl: 'https://automatisch.io/docs/apps/google-calendar/connection',
|
||||
primaryColor: '448AFF',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
triggers,
|
||||
dynamicData,
|
||||
});
|
@@ -0,0 +1,4 @@
|
||||
import newCalendar from './new-calendar';
|
||||
import newEvent from './new-event';
|
||||
|
||||
export default [newCalendar, newEvent];
|
@@ -0,0 +1,34 @@
|
||||
import defineTrigger from '../../../../helpers/define-trigger';
|
||||
|
||||
export default defineTrigger({
|
||||
name: 'New calendar',
|
||||
key: 'newCalendar',
|
||||
pollInterval: 15,
|
||||
description: 'Triggers when a new calendar is created.',
|
||||
arguments: [],
|
||||
|
||||
async run($) {
|
||||
const params: Record<string, unknown> = {
|
||||
pageToken: undefined as unknown as string,
|
||||
maxResults: 250,
|
||||
};
|
||||
|
||||
do {
|
||||
const { data } = await $.http.get('/v3/users/me/calendarList', {
|
||||
params,
|
||||
});
|
||||
params.pageToken = data.nextPageToken;
|
||||
|
||||
if (data.items?.length) {
|
||||
for (const calendar of data.items.reverse()) {
|
||||
$.pushTriggerItem({
|
||||
raw: calendar,
|
||||
meta: {
|
||||
internalId: calendar.etag,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
} while (params.pageToken);
|
||||
},
|
||||
});
|
@@ -0,0 +1,55 @@
|
||||
import defineTrigger from '../../../../helpers/define-trigger';
|
||||
|
||||
export default defineTrigger({
|
||||
name: 'New event',
|
||||
key: 'newEvent',
|
||||
pollInterval: 15,
|
||||
description: 'Triggers when a new event is created.',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Calendar',
|
||||
key: 'calendarId',
|
||||
type: 'dropdown' as const,
|
||||
required: true,
|
||||
description: '',
|
||||
variables: false,
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listCalendars',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const calendarId = $.step.parameters.calendarId;
|
||||
|
||||
const params: Record<string, unknown> = {
|
||||
pageToken: undefined as unknown as string,
|
||||
orderBy: 'updated',
|
||||
};
|
||||
|
||||
do {
|
||||
const { data } = await $.http.get(`/v3/calendars/${calendarId}/events`, {
|
||||
params,
|
||||
});
|
||||
params.pageToken = data.nextPageToken;
|
||||
|
||||
if (data.items?.length) {
|
||||
for (const event of data.items.reverse()) {
|
||||
$.pushTriggerItem({
|
||||
raw: event,
|
||||
meta: {
|
||||
internalId: event.etag,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
} while (params.pageToken);
|
||||
},
|
||||
});
|
@@ -0,0 +1,83 @@
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
|
||||
export default defineAction({
|
||||
name: 'Create contact',
|
||||
key: 'createContact',
|
||||
description: `Create contact on user's account.`,
|
||||
arguments: [
|
||||
{
|
||||
label: 'Company name',
|
||||
key: 'company',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'Email',
|
||||
key: 'email',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'First name',
|
||||
key: 'firstName',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'Last name',
|
||||
key: 'lastName',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
description: 'Last name',
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'Phone',
|
||||
key: 'phone',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'Website URL',
|
||||
key: 'website',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'Owner ID',
|
||||
key: 'hubspotOwnerId',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const company = $.step.parameters.company as string;
|
||||
const email = $.step.parameters.email as string;
|
||||
const firstName = $.step.parameters.firstName as string;
|
||||
const lastName = $.step.parameters.lastName as string;
|
||||
const phone = $.step.parameters.phone as string;
|
||||
const website = $.step.parameters.website as string;
|
||||
const hubspotOwnerId = $.step.parameters.hubspotOwnerId as string;
|
||||
|
||||
const response = await $.http.post(`crm/v3/objects/contacts`, {
|
||||
properties: {
|
||||
company,
|
||||
email,
|
||||
firstname: firstName,
|
||||
lastname: lastName,
|
||||
phone,
|
||||
website,
|
||||
hubspot_owner_id: hubspotOwnerId,
|
||||
},
|
||||
});
|
||||
|
||||
$.setActionItem({ raw: response.data });
|
||||
},
|
||||
});
|
3
packages/backend/src/apps/hubspot/actions/index.ts
Normal file
3
packages/backend/src/apps/hubspot/actions/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import createContact from './create-contact';
|
||||
|
||||
export default [ createContact ];
|
8
packages/backend/src/apps/hubspot/assets/favicon.svg
Normal file
8
packages/backend/src/apps/hubspot/assets/favicon.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="27px" height="28px" viewBox="0 0 27 28" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g fill="#f95c35">
|
||||
<path d="M19.614233,20.1771162 C17.5228041,20.1771162 15.8274241,18.4993457 15.8274241,16.4299995 C15.8274241,14.3602937 17.5228041,12.6825232 19.614233,12.6825232 C21.7056619,12.6825232 23.4010418,14.3602937 23.4010418,16.4299995 C23.4010418,18.4993457 21.7056619,20.1771162 19.614233,20.1771162 M20.7478775,9.21551429 L20.7478775,5.88190722 C21.6271788,5.47091457 22.243053,4.59067833 22.243053,3.56912967 L22.243053,3.49218091 C22.243053,2.08229273 21.0774338,0.928780545 19.6527478,0.928780545 L19.5753548,0.928780545 C18.1506688,0.928780545 16.9850496,2.08229273 16.9850496,3.49218091 L16.9850496,3.56912967 C16.9850496,4.59067833 17.6009238,5.47127414 18.4802251,5.88226679 L18.4802251,9.21551429 C17.1710836,9.4157968 15.9749432,9.95012321 14.9884545,10.7365107 L5.73944086,3.61659339 C5.80048326,3.3846684 5.84335828,3.14591151 5.84372163,2.89492912 C5.84517502,1.29842223 4.53930368,0.00215931486 2.92531356,1.87311107e-06 C1.31205014,-0.00179599501 0.00181863138,1.29087118 1.8932965e-06,2.88773765 C-0.00181484479,4.48460412 1.30405649,5.78086703 2.91804661,5.7826649 C3.44381061,5.78338405 3.93069642,5.63559929 4.35726652,5.39540411 L13.4551275,12.3995387 C12.6815604,13.5552084 12.2281026,14.9395668 12.2281026,16.4299995 C12.2281026,17.9901894 12.7262522,19.433518 13.5677653,20.6204705 L10.8012365,23.3586237 C10.5825013,23.2935408 10.3557723,23.2482346 10.1152362,23.2482346 C8.78938076,23.2482346 7.71423516,24.3118533 7.71423516,25.6239375 C7.71423516,26.9363812 8.78938076,28 10.1152362,28 C11.441455,28 12.5162373,26.9363812 12.5162373,25.6239375 C12.5162373,25.3866189 12.4704555,25.1618854 12.4046896,24.9454221 L15.1414238,22.2371135 C16.3837093,23.1752411 17.9308435,23.7390526 19.614233,23.7390526 C23.6935367,23.7390526 27,20.466573 27,16.4299995 C27,12.7756527 24.2872467,9.7566726 20.7478775,9.21551429"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
20
packages/backend/src/apps/hubspot/auth/generate-auth-url.ts
Normal file
20
packages/backend/src/apps/hubspot/auth/generate-auth-url.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { IField, IGlobalVariable } from '@automatisch/types';
|
||||
import { URLSearchParams } from 'url';
|
||||
import scopes from '../common/scopes';
|
||||
|
||||
export default async function generateAuthUrl($: IGlobalVariable) {
|
||||
const oauthRedirectUrlField = $.app.auth.fields.find(
|
||||
(field: IField) => field.key == 'oAuthRedirectUrl'
|
||||
);
|
||||
const callbackUrl = oauthRedirectUrlField.value as string;
|
||||
|
||||
const searchParams = new URLSearchParams({
|
||||
client_id: $.auth.data.clientId as string,
|
||||
redirect_uri: callbackUrl,
|
||||
scope: scopes.join(' '),
|
||||
});
|
||||
|
||||
const url = `https://app.hubspot.com/oauth/authorize?${searchParams.toString()}`;
|
||||
|
||||
await $.auth.set({ url });
|
||||
}
|
48
packages/backend/src/apps/hubspot/auth/index.ts
Normal file
48
packages/backend/src/apps/hubspot/auth/index.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import generateAuthUrl from './generate-auth-url';
|
||||
import verifyCredentials from './verify-credentials';
|
||||
import isStillVerified from './is-still-verified';
|
||||
import refreshToken from './refresh-token';
|
||||
|
||||
export default {
|
||||
fields: [
|
||||
{
|
||||
key: 'oAuthRedirectUrl',
|
||||
label: 'OAuth Redirect URL',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: true,
|
||||
value: '{WEB_APP_URL}/app/hubspot/connections/add',
|
||||
placeholder: null,
|
||||
description:
|
||||
'When asked to input an OAuth callback or redirect URL in HubSpot OAuth, enter the URL above.',
|
||||
clickToCopy: true,
|
||||
},
|
||||
{
|
||||
key: 'clientId',
|
||||
label: 'Client ID',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
description: null,
|
||||
clickToCopy: false,
|
||||
},
|
||||
{
|
||||
key: 'clientSecret',
|
||||
label: 'Client Secret',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
description: null,
|
||||
clickToCopy: false,
|
||||
},
|
||||
],
|
||||
|
||||
generateAuthUrl,
|
||||
verifyCredentials,
|
||||
isStillVerified,
|
||||
refreshToken,
|
||||
};
|
10
packages/backend/src/apps/hubspot/auth/is-still-verified.ts
Normal file
10
packages/backend/src/apps/hubspot/auth/is-still-verified.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
import getAccessTokenInfo from '../common/get-access-token-info';
|
||||
|
||||
const isStillVerified = async ($: IGlobalVariable) => {
|
||||
await getAccessTokenInfo($);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export default isStillVerified;
|
28
packages/backend/src/apps/hubspot/auth/refresh-token.ts
Normal file
28
packages/backend/src/apps/hubspot/auth/refresh-token.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { IGlobalVariable, IField } from '@automatisch/types';
|
||||
import { URLSearchParams } from 'url';
|
||||
|
||||
const refreshToken = async ($: IGlobalVariable) => {
|
||||
const oauthRedirectUrlField = $.app.auth.fields.find(
|
||||
(field: IField) => field.key == 'oAuthRedirectUrl'
|
||||
);
|
||||
|
||||
const callbackUrl = oauthRedirectUrlField.value as string;
|
||||
|
||||
const params = new URLSearchParams({
|
||||
grant_type: 'refresh_token',
|
||||
client_id: $.auth.data.clientId as string,
|
||||
client_secret: $.auth.data.clientSecret as string,
|
||||
redirect_uri: callbackUrl,
|
||||
refresh_token: $.auth.data.refreshToken as string,
|
||||
});
|
||||
|
||||
const { data } = await $.http.post('/oauth/v1/token', params.toString());
|
||||
|
||||
await $.auth.set({
|
||||
accessToken: data.access_token,
|
||||
expiresIn: data.expires_in,
|
||||
refreshToken: data.refresh_token,
|
||||
});
|
||||
};
|
||||
|
||||
export default refreshToken;
|
52
packages/backend/src/apps/hubspot/auth/verify-credentials.ts
Normal file
52
packages/backend/src/apps/hubspot/auth/verify-credentials.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { IGlobalVariable, IField } from '@automatisch/types';
|
||||
import { URLSearchParams } from 'url';
|
||||
import getAccessTokenInfo from '../common/get-access-token-info';
|
||||
|
||||
const verifyCredentials = async ($: IGlobalVariable) => {
|
||||
const oauthRedirectUrlField = $.app.auth.fields.find(
|
||||
(field: IField) => field.key == 'oAuthRedirectUrl'
|
||||
);
|
||||
const callbackUrl = oauthRedirectUrlField.value as string;
|
||||
const params = new URLSearchParams({
|
||||
grant_type: 'authorization_code',
|
||||
client_id: $.auth.data.clientId as string,
|
||||
client_secret: $.auth.data.clientSecret as string,
|
||||
redirect_uri: callbackUrl,
|
||||
code: $.auth.data.code as string,
|
||||
});
|
||||
|
||||
const { data: verifiedCredentials } = await $.http.post(
|
||||
'/oauth/v1/token',
|
||||
params.toString()
|
||||
);
|
||||
|
||||
const {
|
||||
access_token: accessToken,
|
||||
refresh_token: refreshToken,
|
||||
expires_in: expiresIn,
|
||||
} = verifiedCredentials;
|
||||
|
||||
await $.auth.set({
|
||||
accessToken,
|
||||
refreshToken,
|
||||
expiresIn,
|
||||
});
|
||||
|
||||
const accessTokenInfo = await getAccessTokenInfo($);
|
||||
|
||||
await $.auth.set({
|
||||
screenName: accessTokenInfo.user,
|
||||
hubDomain: accessTokenInfo.hub_domain,
|
||||
scopes: accessTokenInfo.scopes,
|
||||
scopeToScopeGroupPks: accessTokenInfo.scope_to_scope_group_pks,
|
||||
trialScopes: accessTokenInfo.trial_scopes,
|
||||
trialScopeToScoreGroupPks: accessTokenInfo.trial_scope_to_scope_group_pks,
|
||||
hubId: accessTokenInfo.hub_id,
|
||||
appId: accessTokenInfo.app_id,
|
||||
userId: accessTokenInfo.user_id,
|
||||
expiresIn: accessTokenInfo.expires_in,
|
||||
tokenType: accessTokenInfo.token_type,
|
||||
});
|
||||
};
|
||||
|
||||
export default verifyCredentials;
|
14
packages/backend/src/apps/hubspot/common/add-auth-header.ts
Normal file
14
packages/backend/src/apps/hubspot/common/add-auth-header.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { TBeforeRequest } from '@automatisch/types';
|
||||
|
||||
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
|
||||
if (requestConfig.additionalProperties?.skipAddingAuthHeader) return requestConfig;
|
||||
|
||||
if ($.auth.data?.accessToken) {
|
||||
const authorizationHeader = `Bearer ${$.auth.data.accessToken}`;
|
||||
requestConfig.headers.Authorization = authorizationHeader;
|
||||
}
|
||||
|
||||
return requestConfig;
|
||||
};
|
||||
|
||||
export default addAuthHeader;
|
@@ -0,0 +1,11 @@
|
||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
||||
|
||||
const getAccessTokenInfo = async ($: IGlobalVariable): Promise<IJSONObject> => {
|
||||
const response = await $.http.get(
|
||||
`/oauth/v1/access-tokens/${$.auth.data.accessToken}`
|
||||
);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export default getAccessTokenInfo;
|
3
packages/backend/src/apps/hubspot/common/scopes.ts
Normal file
3
packages/backend/src/apps/hubspot/common/scopes.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
const scopes = ['crm.objects.contacts.read', 'crm.objects.contacts.write'];
|
||||
|
||||
export default scopes;
|
0
packages/backend/src/apps/hubspot/index.d.ts
vendored
Normal file
0
packages/backend/src/apps/hubspot/index.d.ts
vendored
Normal file
18
packages/backend/src/apps/hubspot/index.ts
Normal file
18
packages/backend/src/apps/hubspot/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import defineApp from '../../helpers/define-app';
|
||||
import addAuthHeader from './common/add-auth-header';
|
||||
import actions from './actions';
|
||||
import auth from './auth';
|
||||
|
||||
export default defineApp({
|
||||
name: 'HubSpot',
|
||||
key: 'hubspot',
|
||||
iconUrl: '{BASE_URL}/apps/hubspot/assets/favicon.svg',
|
||||
authDocUrl: 'https://automatisch.io/docs/apps/hubspot/connection',
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://www.hubspot.com',
|
||||
apiBaseUrl: 'https://api.hubapi.com',
|
||||
primaryColor: 'F95C35',
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
actions,
|
||||
});
|
1
packages/backend/src/apps/pipedrive/assets/favicon.svg
Normal file
1
packages/backend/src/apps/pipedrive/assets/favicon.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32" fill="#FFFFFF" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0_503_588)" fill="#017737"><path d="M32 16C32 24.8366 24.8366 32 16 32C7.16344 32 0 24.8366 0 16C0 7.16344 7.16344 0 16 0C24.8366 0 32 7.16344 32 16Z" fill="#017737"></path><path d="M24.9842 13.4564C24.9842 17.8851 22.1247 20.914 18.036 20.914C16.0923 20.914 14.4903 20.1136 13.8906 19.1134L13.9189 20.142V26.4847H9.74512V10.0846C9.74512 9.85644 9.68836 9.79843 9.4304 9.79843H8V6.31321H11.4889C13.0896 6.31321 13.4907 7.68461 13.6042 8.28525C14.2337 7.22834 15.8911 6 18.2359 6C22.2679 5.99871 24.9842 8.99802 24.9842 13.4564ZM20.724 13.4847C20.724 11.1131 19.1801 9.48523 17.2351 9.48523C15.6344 9.48523 13.8325 10.5421 13.8325 13.5144C13.8325 15.4568 14.9186 17.4855 17.1783 17.4855C18.837 17.4842 20.724 16.2843 20.724 13.4847Z" fill="#FFFFFF"></path></g></svg>
|
After Width: | Height: | Size: 929 B |
@@ -0,0 +1,19 @@
|
||||
import { IField, IGlobalVariable } from '@automatisch/types';
|
||||
import { URLSearchParams } from 'url';
|
||||
|
||||
export default async function generateAuthUrl($: IGlobalVariable) {
|
||||
const oauthRedirectUrlField = $.app.auth.fields.find(
|
||||
(field: IField) => field.key == 'oAuthRedirectUrl'
|
||||
);
|
||||
const redirectUri = oauthRedirectUrlField.value as string;
|
||||
const searchParams = new URLSearchParams({
|
||||
client_id: $.auth.data.clientId as string,
|
||||
redirect_uri: redirectUri,
|
||||
});
|
||||
|
||||
const url = `https://oauth.pipedrive.com/oauth/authorize?${searchParams.toString()}`;
|
||||
|
||||
await $.auth.set({
|
||||
url,
|
||||
});
|
||||
}
|
48
packages/backend/src/apps/pipedrive/auth/index.ts
Normal file
48
packages/backend/src/apps/pipedrive/auth/index.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import generateAuthUrl from './generate-auth-url';
|
||||
import verifyCredentials from './verify-credentials';
|
||||
import refreshToken from './refresh-token';
|
||||
import isStillVerified from './is-still-verified';
|
||||
|
||||
export default {
|
||||
fields: [
|
||||
{
|
||||
key: 'oAuthRedirectUrl',
|
||||
label: 'OAuth Redirect URL',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: true,
|
||||
value: '{WEB_APP_URL}/app/pipedrive/connections/add',
|
||||
placeholder: null,
|
||||
description:
|
||||
'When asked to input a redirect URL in Pipedrive, enter the URL above.',
|
||||
clickToCopy: true,
|
||||
},
|
||||
{
|
||||
key: 'clientId',
|
||||
label: 'Client ID',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
description: null,
|
||||
clickToCopy: false,
|
||||
},
|
||||
{
|
||||
key: 'clientSecret',
|
||||
label: 'Client Secret',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
description: null,
|
||||
clickToCopy: false,
|
||||
},
|
||||
],
|
||||
|
||||
generateAuthUrl,
|
||||
verifyCredentials,
|
||||
isStillVerified,
|
||||
refreshToken,
|
||||
};
|
@@ -0,0 +1,9 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
import getCurrentUser from '../common/get-current-user';
|
||||
|
||||
const isStillVerified = async ($: IGlobalVariable) => {
|
||||
const currentUser = await getCurrentUser($);
|
||||
return !!currentUser;
|
||||
};
|
||||
|
||||
export default isStillVerified;
|
33
packages/backend/src/apps/pipedrive/auth/refresh-token.ts
Normal file
33
packages/backend/src/apps/pipedrive/auth/refresh-token.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { URLSearchParams } from 'node:url';
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const refreshToken = async ($: IGlobalVariable) => {
|
||||
const params = new URLSearchParams({
|
||||
grant_type: 'refresh_token',
|
||||
refresh_token: $.auth.data.refreshToken as string,
|
||||
});
|
||||
|
||||
const headers = {
|
||||
Authorization: `Basic ${Buffer.from(
|
||||
$.auth.data.clientId + ':' + $.auth.data.clientSecret
|
||||
).toString('base64')}`,
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
};
|
||||
|
||||
const response = await $.http.post(
|
||||
'https://oauth.pipedrive.com/oauth/token',
|
||||
params.toString(),
|
||||
{
|
||||
headers,
|
||||
}
|
||||
);
|
||||
|
||||
await $.auth.set({
|
||||
accessToken: response.data.access_token,
|
||||
refreshToken: response.data.refresh_token,
|
||||
tokenType: response.data.token_type,
|
||||
expiresIn: response.data.expires_in,
|
||||
});
|
||||
};
|
||||
|
||||
export default refreshToken;
|
@@ -0,0 +1,59 @@
|
||||
import { IField, IGlobalVariable } from '@automatisch/types';
|
||||
import { URLSearchParams } from 'url';
|
||||
import getCurrentUser from '../common/get-current-user';
|
||||
|
||||
const verifyCredentials = async ($: IGlobalVariable) => {
|
||||
const oauthRedirectUrlField = $.app.auth.fields.find(
|
||||
(field: IField) => field.key == 'oAuthRedirectUrl'
|
||||
);
|
||||
const redirectUri = oauthRedirectUrlField.value as string;
|
||||
const params = new URLSearchParams({
|
||||
grant_type: 'authorization_code',
|
||||
code: $.auth.data.code as string,
|
||||
redirect_uri: redirectUri,
|
||||
});
|
||||
|
||||
const headers = {
|
||||
Authorization: `Basic ${Buffer.from(
|
||||
$.auth.data.clientId + ':' + $.auth.data.clientSecret
|
||||
).toString('base64')}`,
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
};
|
||||
|
||||
const response = await $.http.post(
|
||||
`https://oauth.pipedrive.com/oauth/token`,
|
||||
params.toString(),
|
||||
{ headers }
|
||||
);
|
||||
|
||||
const {
|
||||
access_token: accessToken,
|
||||
api_domain: apiDomain,
|
||||
expires_in: expiresIn,
|
||||
refresh_token: refreshToken,
|
||||
scope: scope,
|
||||
token_type: tokenType,
|
||||
} = response.data;
|
||||
|
||||
await $.auth.set({
|
||||
accessToken,
|
||||
apiDomain,
|
||||
expiresIn,
|
||||
refreshToken,
|
||||
scope,
|
||||
tokenType,
|
||||
});
|
||||
|
||||
const user = await getCurrentUser($);
|
||||
|
||||
const screenName = [user.name, user.company_domain]
|
||||
.filter(Boolean)
|
||||
.join(' @ ');
|
||||
|
||||
await $.auth.set({
|
||||
userId: user.id,
|
||||
screenName,
|
||||
});
|
||||
};
|
||||
|
||||
export default verifyCredentials;
|
@@ -0,0 +1,11 @@
|
||||
import { TBeforeRequest } from '@automatisch/types';
|
||||
|
||||
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
|
||||
if ($.auth.data?.accessToken) {
|
||||
requestConfig.headers.Authorization = `${$.auth.data.tokenType} ${$.auth.data.accessToken}`;
|
||||
}
|
||||
|
||||
return requestConfig;
|
||||
};
|
||||
|
||||
export default addAuthHeader;
|
@@ -0,0 +1,10 @@
|
||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
||||
|
||||
const getCurrentUser = async ($: IGlobalVariable): Promise<IJSONObject> => {
|
||||
const response = await $.http.get(`${$.auth.data.apiDomain}/api/v1/users/me`);
|
||||
const currentUser = response.data.data;
|
||||
|
||||
return currentUser;
|
||||
};
|
||||
|
||||
export default getCurrentUser;
|
0
packages/backend/src/apps/pipedrive/index.d.ts
vendored
Normal file
0
packages/backend/src/apps/pipedrive/index.d.ts
vendored
Normal file
18
packages/backend/src/apps/pipedrive/index.ts
Normal file
18
packages/backend/src/apps/pipedrive/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import defineApp from '../../helpers/define-app';
|
||||
import addAuthHeader from './common/add-auth-header';
|
||||
import auth from './auth';
|
||||
import triggers from './triggers';
|
||||
|
||||
export default defineApp({
|
||||
name: 'Pipedrive',
|
||||
key: 'pipedrive',
|
||||
baseUrl: '',
|
||||
apiBaseUrl: '',
|
||||
iconUrl: '{BASE_URL}/apps/pipedrive/assets/favicon.svg',
|
||||
authDocUrl: 'https://automatisch.io/docs/apps/pipedrive/connection',
|
||||
primaryColor: 'FFFFFF',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
triggers,
|
||||
});
|
3
packages/backend/src/apps/pipedrive/triggers/index.ts
Normal file
3
packages/backend/src/apps/pipedrive/triggers/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import newDeals from './new-deals';
|
||||
|
||||
export default [newDeals];
|
@@ -0,0 +1,56 @@
|
||||
import defineTrigger from '../../../../helpers/define-trigger';
|
||||
|
||||
type Payload = {
|
||||
start: number;
|
||||
limit: number;
|
||||
sort: string;
|
||||
};
|
||||
|
||||
type ResponseData = {
|
||||
data: {
|
||||
id: number;
|
||||
}[];
|
||||
additional_data: {
|
||||
pagination: {
|
||||
next_start: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export default defineTrigger({
|
||||
name: 'New deals',
|
||||
key: 'newDeals',
|
||||
pollInterval: 15,
|
||||
description: 'Triggers when a new deal is created.',
|
||||
arguments: [],
|
||||
|
||||
async run($) {
|
||||
const params: Payload = {
|
||||
start: 0,
|
||||
limit: 100,
|
||||
sort: 'add_time DESC',
|
||||
};
|
||||
|
||||
do {
|
||||
const { data } = await $.http.get<ResponseData>(
|
||||
`${$.auth.data.apiDomain}/api/v1/deals`,
|
||||
{ params }
|
||||
);
|
||||
|
||||
if (!data?.data?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
params.start = data.additional_data?.pagination?.next_start;
|
||||
|
||||
for (const deal of data.data) {
|
||||
$.pushTriggerItem({
|
||||
raw: deal,
|
||||
meta: {
|
||||
internalId: deal.id.toString(),
|
||||
},
|
||||
});
|
||||
}
|
||||
} while (params.start);
|
||||
},
|
||||
});
|
@@ -4,9 +4,13 @@ import bcrypt from 'bcrypt';
|
||||
|
||||
const getInternalId = async (item: IJSONObject): Promise<string> => {
|
||||
if (item.guid) {
|
||||
return item.guid.toString();
|
||||
return typeof item.guid === 'object'
|
||||
? (item.guid as IJSONObject)['#text'].toString()
|
||||
: item.guid.toString();
|
||||
} else if (item.id) {
|
||||
return item.id.toString();
|
||||
return typeof item.id === 'object'
|
||||
? (item.id as IJSONObject)['#text'].toString()
|
||||
: item.id.toString();
|
||||
}
|
||||
|
||||
return await hashItem(JSON.stringify(item));
|
||||
|
6
packages/backend/src/apps/wordpress/assets/favicon.svg
Normal file
6
packages/backend/src/apps/wordpress/assets/favicon.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
|
||||
<svg width="256px" height="255px" viewBox="0 0 256 255" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
||||
<g fill="#464342">
|
||||
<path d="M18.1239675,127.500488 C18.1239675,170.795707 43.284813,208.211252 79.7700163,225.941854 L27.5938862,82.985626 C21.524813,96.5890081 18.1239675,111.643057 18.1239675,127.500488 L18.1239675,127.500488 Z M201.345041,121.980878 C201.345041,108.462829 196.489366,99.1011382 192.324683,91.8145041 C186.780098,82.8045528 181.583089,75.1745041 181.583089,66.1645528 C181.583089,56.1097886 189.208976,46.7501789 199.950569,46.7501789 C200.435512,46.7501789 200.89548,46.8105366 201.367935,46.8375935 C181.907772,29.0091707 155.981008,18.1239675 127.50465,18.1239675 C89.2919675,18.1239675 55.6727154,37.7298211 36.1147317,67.4258211 C38.6809756,67.5028293 41.0994472,67.5569431 43.1536911,67.5569431 C54.5946016,67.5569431 72.3043902,66.1687154 72.3043902,66.1687154 C78.2007154,65.8211382 78.8958699,74.4814309 73.0057886,75.1786667 C73.0057886,75.1786667 67.0803252,75.8759024 60.4867642,76.2213984 L100.318699,194.699447 L124.25574,122.909138 L107.214049,76.2172358 C101.323967,75.8717398 95.744,75.1745041 95.744,75.1745041 C89.8497561,74.8290081 90.540748,65.8169756 96.4349919,66.1645528 C96.4349919,66.1645528 114.498602,67.5527805 125.246439,67.5527805 C136.685268,67.5527805 154.397138,66.1645528 154.397138,66.1645528 C160.297626,65.8169756 160.990699,74.4772683 155.098537,75.1745041 C155.098537,75.1745041 149.160585,75.8717398 142.579512,76.2172358 L182.107577,193.798244 L193.017756,157.340098 C197.746472,142.211122 201.345041,131.34465 201.345041,121.980878 L201.345041,121.980878 Z M129.42361,137.068228 L96.6056585,232.43135 C106.404423,235.31187 116.76722,236.887415 127.50465,236.887415 C140.242211,236.887415 152.457366,234.685398 163.827512,230.68722 C163.534049,230.218927 163.267642,229.721496 163.049106,229.180358 L129.42361,137.068228 L129.42361,137.068228 Z M223.481756,75.0225691 C223.95213,78.5066667 224.218537,82.2467642 224.218537,86.2699187 C224.218537,97.3694959 222.145561,109.846894 215.901659,125.448325 L182.490537,222.04774 C215.00878,203.085008 236.881171,167.854829 236.881171,127.502569 C236.883252,108.485724 232.025496,90.603187 223.481756,75.0225691 L223.481756,75.0225691 Z M127.50465,0 C57.2003902,0 0,57.1962276 0,127.500488 C0,197.813073 57.2003902,255.00722 127.50465,255.00722 C197.806829,255.00722 255.015545,197.813073 255.015545,127.500488 C255.013463,57.1962276 197.806829,0 127.50465,0 L127.50465,0 Z M127.50465,249.162927 C60.4243252,249.162927 5.84637398,194.584976 5.84637398,127.500488 C5.84637398,60.4201626 60.4222439,5.84637398 127.50465,5.84637398 C194.582894,5.84637398 249.156683,60.4201626 249.156683,127.500488 C249.156683,194.584976 194.582894,249.162927 127.50465,249.162927 L127.50465,249.162927 Z"></path>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.9 KiB |
@@ -0,0 +1,24 @@
|
||||
import { URL, URLSearchParams } from 'node:url';
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
import appConfig from '../../../config/app';
|
||||
import getInstanceUrl from '../common/get-instance-url';
|
||||
|
||||
export default async function generateAuthUrl($: IGlobalVariable) {
|
||||
const successUrl = new URL(
|
||||
'/app/wordpress/connections/add',
|
||||
appConfig.webAppUrl
|
||||
).toString();
|
||||
const baseUrl = getInstanceUrl($);
|
||||
|
||||
const searchParams = new URLSearchParams({
|
||||
app_name: 'automatisch',
|
||||
success_url: successUrl,
|
||||
});
|
||||
|
||||
const url = new URL(`/wp-admin/authorize-application.php?${searchParams}`, baseUrl).toString();
|
||||
|
||||
await $.auth.set({
|
||||
url,
|
||||
});
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user