Compare commits

..

4 Commits

Author SHA1 Message Date
Rıdvan Akca
9b00cff2d9 feat(monday): add create item action 2024-05-09 14:02:47 +02:00
Rıdvan Akca
ff0dd0b415 feat(monday): add create board action 2024-05-08 12:14:32 +02:00
Rıdvan Akca
f0b5d85a7a feat(monday): add new boards trigger 2024-05-03 14:46:07 +02:00
Rıdvan Akca
2295507a2c feat(monday): add monday integration 2024-05-03 14:04:57 +02:00
223 changed files with 491 additions and 9334 deletions

View File

@@ -36,6 +36,7 @@ services:
keycloak:
image: quay.io/keycloak/keycloak:21.1
restart: always
container_name: keycloak
environment:
- KEYCLOAK_ADMIN=admin
- KEYCLOAK_ADMIN_PASSWORD=admin

24
docker/Dockerfile.cloud Normal file
View File

@@ -0,0 +1,24 @@
# syntax=docker/dockerfile:1
FROM node:18-alpine
ENV PORT 3000
RUN \
apk --no-cache add --virtual build-dependencies python3 build-base git
RUN git clone https://github.com/automatisch/automatisch.git
WORKDIR /automatisch
RUN yarn install
RUN if [ "$WORKER" != "true" ]; then cd packages/web && yarn build; fi
RUN \
rm -rf /usr/local/share/.cache/ && \
apk del build-dependencies
COPY ./docker/entrypoint-cloud.sh /entrypoint-cloud.sh
EXPOSE 3000
ENTRYPOINT ["sh", "/entrypoint-cloud.sh"]

View File

@@ -1,5 +1,5 @@
# syntax=docker/dockerfile:1
FROM automatischio/automatisch:latest
FROM automatischio/automatisch:0.10.0
WORKDIR /automatisch
RUN apk add --no-cache openssl dos2unix

13
docker/entrypoint-cloud.sh Executable file
View File

@@ -0,0 +1,13 @@
#!/bin/sh
set -e
cd packages/backend
if [ -n "$WORKER" ]; then
yarn start:worker
else
yarn db:migrate
yarn db:seed:user
yarn start
fi

View File

@@ -2,7 +2,6 @@ import appConfig from '../../src/config/app.js';
import logger from '../../src/helpers/logger.js';
import client from './client.js';
import User from '../../src/models/user.js';
import Config from '../../src/models/config.js';
import Role from '../../src/models/role.js';
import '../../src/config/orm.js';
import process from 'process';
@@ -22,14 +21,6 @@ export async function createUser(
email = 'user@automatisch.io',
password = 'sample'
) {
if (appConfig.disableSeedUser) {
logger.info('Seed user is disabled.');
process.exit(0);
return;
}
const UNIQUE_VIOLATION_CODE = '23505';
const role = await fetchAdminRole();
@@ -46,8 +37,6 @@ export async function createUser(
if (userCount === 0) {
const user = await User.query().insertAndFetch(userParams);
logger.info(`User has been saved: ${user.email}`);
await Config.markInstallationCompleted();
} else {
logger.info('No need to seed a user.');
}

View File

@@ -67,7 +67,6 @@
"pluralize": "^8.0.0",
"raw-body": "^2.5.2",
"showdown": "^2.1.0",
"uuid": "^9.0.1",
"winston": "^3.7.1",
"xmlrpc": "^1.3.2"
},

View File

@@ -1,92 +0,0 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Create record',
key: 'createRecord',
description: 'Creates a new record with fields that automatically populate.',
arguments: [
{
label: 'Base',
key: 'baseId',
type: 'dropdown',
required: true,
description: 'Base in which to create the record.',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listBases',
},
],
},
},
{
label: 'Table',
key: 'tableId',
type: 'dropdown',
required: true,
dependsOn: ['parameters.baseId'],
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listTables',
},
{
name: 'parameters.baseId',
value: '{parameters.baseId}',
},
],
},
additionalFields: {
type: 'query',
name: 'getDynamicFields',
arguments: [
{
name: 'key',
value: 'listFields',
},
{
name: 'parameters.baseId',
value: '{parameters.baseId}',
},
{
name: 'parameters.tableId',
value: '{parameters.tableId}',
},
],
},
},
],
async run($) {
const { baseId, tableId, ...rest } = $.step.parameters;
const fields = Object.entries(rest).reduce((result, [key, value]) => {
if (Array.isArray(value)) {
result[key] = value.map((item) => item.value);
} else if (value !== '') {
result[key] = value;
}
return result;
}, {});
const body = {
typecast: true,
fields,
};
const { data } = await $.http.post(`/v0/${baseId}/${tableId}`, body);
$.setActionItem({
raw: data,
});
},
});

View File

@@ -1,174 +0,0 @@
import defineAction from '../../../../helpers/define-action.js';
import { URLSearchParams } from 'url';
export default defineAction({
name: 'Find record',
key: 'findRecord',
description:
"Finds a record using simple field search or use Airtable's formula syntax to find a matching record.",
arguments: [
{
label: 'Base',
key: 'baseId',
type: 'dropdown',
required: true,
description: 'Base in which to create the record.',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listBases',
},
],
},
},
{
label: 'Table',
key: 'tableId',
type: 'dropdown',
required: true,
dependsOn: ['parameters.baseId'],
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listTables',
},
{
name: 'parameters.baseId',
value: '{parameters.baseId}',
},
],
},
},
{
label: 'Search by field',
key: 'tableField',
type: 'dropdown',
required: false,
dependsOn: ['parameters.baseId', 'parameters.tableId'],
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listTableFields',
},
{
name: 'parameters.baseId',
value: '{parameters.baseId}',
},
{
name: 'parameters.tableId',
value: '{parameters.tableId}',
},
],
},
},
{
label: 'Search Value',
key: 'searchValue',
type: 'string',
required: false,
variables: true,
description:
'The value of unique identifier for the record. For date values, please use the ISO format (e.g., "YYYY-MM-DD").',
},
{
label: 'Search for exact match?',
key: 'exactMatch',
type: 'dropdown',
required: true,
description: '',
variables: true,
options: [
{ label: 'Yes', value: 'true' },
{ label: 'No', value: 'false' },
],
},
{
label: 'Search Formula',
key: 'searchFormula',
type: 'string',
required: false,
variables: true,
description:
'Instead, you have the option to use an Airtable search formula for locating records according to sophisticated criteria and across various fields.',
},
{
label: 'Limit to View',
key: 'limitToView',
type: 'dropdown',
required: false,
dependsOn: ['parameters.baseId', 'parameters.tableId'],
description:
'You have the choice to restrict the search to a particular view ID if desired.',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listTableViews',
},
{
name: 'parameters.baseId',
value: '{parameters.baseId}',
},
{
name: 'parameters.tableId',
value: '{parameters.tableId}',
},
],
},
},
],
async run($) {
const {
baseId,
tableId,
tableField,
searchValue,
exactMatch,
searchFormula,
limitToView,
} = $.step.parameters;
let filterByFormula;
if (tableField && searchValue) {
filterByFormula =
exactMatch === 'true'
? `{${tableField}} = '${searchValue}'`
: `LOWER({${tableField}}) = LOWER('${searchValue}')`;
} else {
filterByFormula = searchFormula;
}
const body = new URLSearchParams({
filterByFormula,
view: limitToView,
});
const { data } = await $.http.post(
`/v0/${baseId}/${tableId}/listRecords`,
body
);
$.setActionItem({
raw: data,
});
},
});

View File

@@ -1,4 +0,0 @@
import createRecord from './create-record/index.js';
import findRecord from './find-record/index.js';
export default [createRecord, findRecord];

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="256px" height="215px" viewBox="0 0 256 215" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
<g>
<path d="M114.25873,2.70101695 L18.8604023,42.1756384 C13.5552723,44.3711638 13.6102328,51.9065311 18.9486282,54.0225085 L114.746142,92.0117514 C123.163769,95.3498757 132.537419,95.3498757 140.9536,92.0117514 L236.75256,54.0225085 C242.08951,51.9065311 242.145916,44.3711638 236.83934,42.1756384 L141.442459,2.70101695 C132.738459,-0.900338983 122.961284,-0.900338983 114.25873,2.70101695" fill="#FFBF00"></path>
<path d="M136.349071,112.756863 L136.349071,207.659101 C136.349071,212.173089 140.900664,215.263892 145.096461,213.600615 L251.844122,172.166219 C254.281184,171.200072 255.879376,168.845451 255.879376,166.224705 L255.879376,71.3224678 C255.879376,66.8084791 251.327783,63.7176768 247.131986,65.3809537 L140.384325,106.815349 C137.94871,107.781496 136.349071,110.136118 136.349071,112.756863" fill="#26B5F8"></path>
<path d="M111.422771,117.65355 L79.742409,132.949912 L76.5257763,134.504714 L9.65047684,166.548104 C5.4112904,168.593211 0.000578531073,165.503855 0.000578531073,160.794612 L0.000578531073,71.7210757 C0.000578531073,70.0173017 0.874160452,68.5463864 2.04568588,67.4384994 C2.53454463,66.9481944 3.08848814,66.5446689 3.66412655,66.2250305 C5.26231864,65.2661153 7.54173107,65.0101153 9.47981017,65.7766689 L110.890522,105.957098 C116.045234,108.002206 116.450206,115.225166 111.422771,117.65355" fill="#ED3049"></path>
<path d="M111.422771,117.65355 L79.742409,132.949912 L2.04568588,67.4384994 C2.53454463,66.9481944 3.08848814,66.5446689 3.66412655,66.2250305 C5.26231864,65.2661153 7.54173107,65.0101153 9.47981017,65.7766689 L110.890522,105.957098 C116.045234,108.002206 116.450206,115.225166 111.422771,117.65355" fill-opacity="0.25" fill="#000000"></path>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -1,38 +0,0 @@
import crypto from 'crypto';
import { URLSearchParams } from 'url';
import authScope from '../common/auth-scope.js';
export default async function generateAuthUrl($) {
const oauthRedirectUrlField = $.app.auth.fields.find(
(field) => field.key == 'oAuthRedirectUrl'
);
const redirectUri = oauthRedirectUrlField.value;
const state = crypto.randomBytes(100).toString('base64url');
const codeVerifier = crypto.randomBytes(96).toString('base64url');
const codeChallenge = crypto
.createHash('sha256')
.update(codeVerifier)
.digest('base64')
.replace(/=/g, '')
.replace(/\+/g, '-')
.replace(/\//g, '_');
const searchParams = new URLSearchParams({
client_id: $.auth.data.clientId,
redirect_uri: redirectUri,
response_type: 'code',
scope: authScope.join(' '),
state,
code_challenge: codeChallenge,
code_challenge_method: 'S256',
});
const url = `https://airtable.com/oauth2/v1/authorize?${searchParams.toString()}`;
await $.auth.set({
url,
originalCodeChallenge: codeChallenge,
originalState: state,
codeVerifier,
});
}

View File

@@ -1,48 +0,0 @@
import generateAuthUrl from './generate-auth-url.js';
import verifyCredentials from './verify-credentials.js';
import refreshToken from './refresh-token.js';
import isStillVerified from './is-still-verified.js';
export default {
fields: [
{
key: 'oAuthRedirectUrl',
label: 'OAuth Redirect URL',
type: 'string',
required: true,
readOnly: true,
value: '{WEB_APP_URL}/app/airtable/connections/add',
placeholder: null,
description:
'When asked to input a redirect URL in Airtable, enter the URL above.',
clickToCopy: true,
},
{
key: 'clientId',
label: 'Client ID',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
clickToCopy: false,
},
{
key: 'clientSecret',
label: 'Client Secret',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
clickToCopy: false,
},
],
generateAuthUrl,
verifyCredentials,
isStillVerified,
refreshToken,
};

View File

@@ -1,8 +0,0 @@
import getCurrentUser from '../common/get-current-user.js';
const isStillVerified = async ($) => {
const currentUser = await getCurrentUser($);
return !!currentUser.id;
};
export default isStillVerified;

View File

@@ -1,40 +0,0 @@
import { URLSearchParams } from 'node:url';
import authScope from '../common/auth-scope.js';
const refreshToken = async ($) => {
const params = new URLSearchParams({
client_id: $.auth.data.clientId,
grant_type: 'refresh_token',
refresh_token: $.auth.data.refreshToken,
});
const basicAuthToken = Buffer.from(
$.auth.data.clientId + ':' + $.auth.data.clientSecret
).toString('base64');
const { data } = await $.http.post(
'https://airtable.com/oauth2/v1/token',
params.toString(),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Basic ${basicAuthToken}`,
},
additionalProperties: {
skipAddingAuthHeader: true,
},
}
);
await $.auth.set({
accessToken: data.access_token,
refreshToken: data.refresh_token,
expiresIn: data.expires_in,
refreshExpiresIn: data.refresh_expires_in,
scope: authScope.join(' '),
tokenType: data.token_type,
});
};
export default refreshToken;

View File

@@ -1,56 +0,0 @@
import getCurrentUser from '../common/get-current-user.js';
const verifyCredentials = async ($) => {
if ($.auth.data.originalState !== $.auth.data.state) {
throw new Error("The 'state' parameter does not match.");
}
if ($.auth.data.originalCodeChallenge !== $.auth.data.code_challenge) {
throw new Error("The 'code challenge' parameter does not match.");
}
const oauthRedirectUrlField = $.app.auth.fields.find(
(field) => field.key == 'oAuthRedirectUrl'
);
const redirectUri = oauthRedirectUrlField.value;
const basicAuthToken = Buffer.from(
$.auth.data.clientId + ':' + $.auth.data.clientSecret
).toString('base64');
const { data } = await $.http.post(
'https://airtable.com/oauth2/v1/token',
{
code: $.auth.data.code,
client_id: $.auth.data.clientId,
redirect_uri: redirectUri,
grant_type: 'authorization_code',
code_verifier: $.auth.data.codeVerifier,
},
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Basic ${basicAuthToken}`,
},
additionalProperties: {
skipAddingAuthHeader: true,
},
}
);
await $.auth.set({
accessToken: data.access_token,
tokenType: data.token_type,
});
const currentUser = await getCurrentUser($);
await $.auth.set({
clientId: $.auth.data.clientId,
clientSecret: $.auth.data.clientSecret,
scope: $.auth.data.scope,
expiresIn: data.expires_in,
refreshExpiresIn: data.refresh_expires_in,
refreshToken: data.refresh_token,
screenName: currentUser.email,
});
};
export default verifyCredentials;

View File

@@ -1,12 +0,0 @@
const addAuthHeader = ($, requestConfig) => {
if (
!requestConfig.additionalProperties?.skipAddingAuthHeader &&
$.auth.data?.accessToken
) {
requestConfig.headers.Authorization = `${$.auth.data.tokenType} ${$.auth.data.accessToken}`;
}
return requestConfig;
};
export default addAuthHeader;

View File

@@ -1,12 +0,0 @@
const authScope = [
'data.records:read',
'data.records:write',
'data.recordComments:read',
'data.recordComments:write',
'schema.bases:read',
'schema.bases:write',
'user.email:read',
'webhook:manage',
];
export default authScope;

View File

@@ -1,6 +0,0 @@
const getCurrentUser = async ($) => {
const { data: currentUser } = await $.http.get('/v0/meta/whoami');
return currentUser;
};
export default getCurrentUser;

View File

@@ -1,6 +0,0 @@
import listBases from './list-bases/index.js';
import listTableFields from './list-table-fields/index.js';
import listTableViews from './list-table-views/index.js';
import listTables from './list-tables/index.js';
export default [listBases, listTableFields, listTableViews, listTables];

View File

@@ -1,28 +0,0 @@
export default {
name: 'List bases',
key: 'listBases',
async run($) {
const bases = {
data: [],
};
const params = {};
do {
const { data } = await $.http.get('/v0/meta/bases', { params });
params.offset = data.offset;
if (data?.bases) {
for (const base of data.bases) {
bases.data.push({
value: base.id,
name: base.name,
});
}
}
} while (params.offset);
return bases;
},
};

View File

@@ -1,39 +0,0 @@
export default {
name: 'List table fields',
key: 'listTableFields',
async run($) {
const tableFields = {
data: [],
};
const { baseId, tableId } = $.step.parameters;
if (!baseId) {
return tableFields;
}
const params = {};
do {
const { data } = await $.http.get(`/v0/meta/bases/${baseId}/tables`, {
params,
});
params.offset = data.offset;
if (data?.tables) {
for (const table of data.tables) {
if (table.id === tableId) {
table.fields.forEach((field) => {
tableFields.data.push({
value: field.name,
name: field.name,
});
});
}
}
}
} while (params.offset);
return tableFields;
},
};

View File

@@ -1,39 +0,0 @@
export default {
name: 'List table views',
key: 'listTableViews',
async run($) {
const tableViews = {
data: [],
};
const { baseId, tableId } = $.step.parameters;
if (!baseId) {
return tableViews;
}
const params = {};
do {
const { data } = await $.http.get(`/v0/meta/bases/${baseId}/tables`, {
params,
});
params.offset = data.offset;
if (data?.tables) {
for (const table of data.tables) {
if (table.id === tableId) {
table.views.forEach((view) => {
tableViews.data.push({
value: view.id,
name: view.name,
});
});
}
}
}
} while (params.offset);
return tableViews;
},
};

View File

@@ -1,35 +0,0 @@
export default {
name: 'List tables',
key: 'listTables',
async run($) {
const tables = {
data: [],
};
const baseId = $.step.parameters.baseId;
if (!baseId) {
return tables;
}
const params = {};
do {
const { data } = await $.http.get(`/v0/meta/bases/${baseId}/tables`, {
params,
});
params.offset = data.offset;
if (data?.tables) {
for (const table of data.tables) {
tables.data.push({
value: table.id,
name: table.name,
});
}
}
} while (params.offset);
return tables;
},
};

View File

@@ -1,3 +0,0 @@
import listFields from './list-fields/index.js';
export default [listFields];

View File

@@ -1,86 +0,0 @@
const hasValue = (value) => value !== null && value !== undefined;
export default {
name: 'List fields',
key: 'listFields',
async run($) {
const options = [];
const { baseId, tableId } = $.step.parameters;
if (!hasValue(baseId) || !hasValue(tableId)) {
return;
}
const { data } = await $.http.get(`/v0/meta/bases/${baseId}/tables`);
const selectedTable = data.tables.find((table) => table.id === tableId);
if (!selectedTable) return;
selectedTable.fields.forEach((field) => {
if (field.type === 'singleSelect') {
options.push({
label: field.name,
key: field.name,
type: 'dropdown',
required: false,
variables: true,
options: field.options.choices.map((choice) => ({
label: choice.name,
value: choice.id,
})),
});
} else if (field.type === 'multipleSelects') {
options.push({
label: field.name,
key: field.name,
type: 'dynamic',
required: false,
variables: true,
fields: [
{
label: 'Value',
key: 'value',
type: 'dropdown',
required: false,
variables: true,
options: field.options.choices.map((choice) => ({
label: choice.name,
value: choice.id,
})),
},
],
});
} else if (field.type === 'checkbox') {
options.push({
label: field.name,
key: field.name,
type: 'dropdown',
required: false,
variables: true,
options: [
{
label: 'Yes',
value: 'true',
},
{
label: 'No',
value: 'false',
},
],
});
} else {
options.push({
label: field.name,
key: field.name,
type: 'string',
required: false,
variables: true,
});
}
});
return options;
},
};

View File

@@ -1,22 +0,0 @@
import defineApp from '../../helpers/define-app.js';
import addAuthHeader from './common/add-auth-header.js';
import auth from './auth/index.js';
import actions from './actions/index.js';
import dynamicData from './dynamic-data/index.js';
import dynamicFields from './dynamic-fields/index.js';
export default defineApp({
name: 'Airtable',
key: 'airtable',
baseUrl: 'https://airtable.com',
apiBaseUrl: 'https://api.airtable.com',
iconUrl: '{BASE_URL}/apps/airtable/assets/favicon.svg',
authDocUrl: '{DOCS_URL}/apps/airtable/connection',
primaryColor: 'FFBF00',
supportsConnections: true,
beforeRequest: [addAuthHeader],
auth,
actions,
dynamicData,
dynamicFields,
});

View File

@@ -1 +0,0 @@
<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" width="132" height="24" fill="none" viewBox="0 0 132 24"><path fill="#19191C" d="M38.557 19.495c2.16 0 3.25-1.113 3.725-1.87h.214c.094.805.664 1.562 1.779 1.562h2.111V16.82h-.545c-.38 0-.57-.213-.57-.545V6.776h-2.8v1.516h-.213c-.545-.758-1.684-1.824-3.772-1.824-3.321 0-5.789 2.748-5.789 6.514s2.515 6.513 5.86 6.513m.498-2.7c-1.969 0-3.51-1.445-3.51-3.79 0-2.297 1.494-3.86 3.487-3.86 1.898 0 3.487 1.397 3.487 3.86 0 2.108-1.352 3.79-3.463 3.79M48.04 24h2.799v-6.376h.213c.522.758 1.637 1.871 3.844 1.871 3.321 0 5.741-2.795 5.741-6.513 0-3.743-2.586-6.514-5.931-6.514-2.135 0-3.18 1.16-3.678 1.8h-.213V6.776h-2.776V24m6.263-7.134c-1.922 0-3.512-1.42-3.512-3.884 0-2.108 1.353-3.885 3.464-3.885 1.97 0 3.511 1.54 3.511 3.885 0 2.297-1.494 3.884-3.463 3.884M62.082 24h2.8v-6.376h.213c.522.758 1.637 1.871 3.843 1.871 3.321 0 5.51-2.795 5.51-6.513 0-3.743-2.355-6.514-5.7-6.514-2.135 0-3.179 1.16-3.677 1.8h-.214V6.776h-2.775zm6.263-7.134c-1.922 0-3.511-1.42-3.511-3.884 0-2.108 1.352-3.885 3.463-3.885 1.97 0 3.512 1.54 3.512 3.885 0 2.297-1.495 3.884-3.464 3.884m9.805 2.61h3.961l2.254-9.735h.143l2.253 9.735H90.7l3.153-12.412h-2.821l-2.254 9.759h-.214l-2.253-9.759h-3.725l-2.278 9.759h-.213l-2.23-9.759h-2.99l3.274 12.412m17.123 0h2.8V13.34c0-2.345 1.09-3.79 3.131-3.79h1.233V6.756h-.925c-1.59 0-2.8 1.09-3.274 2.132h-.19V7.064h-2.775zm21.057 0h2.183v-2.487h-2.159c-.854 0-1.21-.38-1.21-1.256V9.528h3.511V7.064h-3.511V3.582h-2.657v3.482h-2.325v2.464h2.159v6.229c0 2.63 1.589 3.719 4.009 3.719m9.693.019c2.586 0 4.864-1.279 5.67-3.86l-2.562-.616c-.451 1.373-1.755 2.084-3.131 2.084-2.041 0-3.393-1.326-3.417-3.41h9.419v-.782c0-3.695-2.301-6.443-6.097-6.443-3.346 0-6.216 2.63-6.216 6.537 0 3.79 2.538 6.49 6.334 6.49m-3.416-7.84c.166-1.492 1.518-2.747 3.298-2.747 1.708 0 3.108 1.066 3.25 2.747h-6.548"/><path fill="#19191C" fill-rule="evenodd" d="M108.916 19.476h-2.8V9.528h-2.182V7.064h4.982z" clip-rule="evenodd"/><path fill="#19191C" d="M107.309 5.342c1.02 0 1.779-.758 1.779-1.753 0-.971-.759-1.73-1.779-1.73-1.021 0-1.78.759-1.78 1.73 0 .995.759 1.753 1.78 1.753"/><path fill="#FD366E" d="M24.443 16.432v5.478H10.752c-3.989 0-7.472-2.203-9.335-5.478A11.041 11.041 0 0 1 0 11.695v-1.48a10.97 10.97 0 0 1 .381-2.247C1.661 3.368 5.82 0 10.751 0c4.934 0 9.092 3.37 10.371 7.967h-5.854c-.96-1.499-2.624-2.49-4.516-2.49s-3.555.991-4.516 2.49a5.47 5.47 0 0 0-.67 1.494 5.562 5.562 0 0 0-.202 1.494 5.5 5.5 0 0 0 1.69 3.983 5.32 5.32 0 0 0 3.698 1.494h13.69"/><path fill="#FD366E" d="M24.443 9.46v5.478h-9.994a5.5 5.5 0 0 0 1.691-3.983 5.56 5.56 0 0 0-.203-1.494h8.506"/></svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -1,65 +0,0 @@
import verifyCredentials from './verify-credentials.js';
import isStillVerified from './is-still-verified.js';
export default {
fields: [
{
key: 'screenName',
label: 'Screen Name',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description:
'Screen name of your connection to be used on Automatisch UI.',
clickToCopy: false,
},
{
key: 'projectId',
label: 'Project ID',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description: 'Project ID of your Appwrite project.',
clickToCopy: false,
},
{
key: 'apiKey',
label: 'API Key',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description: 'API key of your Appwrite project.',
clickToCopy: false,
},
{
key: 'instanceUrl',
label: 'Appwrite instance URL',
type: 'string',
required: false,
readOnly: false,
placeholder: '',
description: '',
clickToCopy: true,
},
{
key: 'host',
label: 'Host Name',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description: 'Host name of your Appwrite project.',
clickToCopy: false,
},
],
verifyCredentials,
isStillVerified,
};

View File

@@ -1,5 +0,0 @@
const verifyCredentials = async ($) => {
await $.http.get('/v1/users');
};
export default verifyCredentials;

View File

@@ -1,16 +0,0 @@
const addAuthHeader = ($, requestConfig) => {
requestConfig.headers['Content-Type'] = 'application/json';
if ($.auth.data?.apiKey && $.auth.data?.projectId) {
requestConfig.headers['X-Appwrite-Project'] = $.auth.data.projectId;
requestConfig.headers['X-Appwrite-Key'] = $.auth.data.apiKey;
}
if ($.auth.data?.host) {
requestConfig.headers['Host'] = $.auth.data.host;
}
return requestConfig;
};
export default addAuthHeader;

View File

@@ -1,13 +0,0 @@
const setBaseUrl = ($, requestConfig) => {
const instanceUrl = $.auth.data.instanceUrl;
if (instanceUrl) {
requestConfig.baseURL = instanceUrl;
} else if ($.app.apiBaseUrl) {
requestConfig.baseURL = $.app.apiBaseUrl;
}
return requestConfig;
};
export default setBaseUrl;

View File

@@ -1,4 +0,0 @@
import listCollections from './list-collections/index.js';
import listDatabases from './list-databases/index.js';
export default [listCollections, listDatabases];

View File

@@ -1,44 +0,0 @@
export default {
name: 'List collections',
key: 'listCollections',
async run($) {
const collections = {
data: [],
};
const databaseId = $.step.parameters.databaseId;
if (!databaseId) {
return collections;
}
const params = {
queries: [
JSON.stringify({
method: 'orderAsc',
attribute: 'name',
}),
JSON.stringify({
method: 'limit',
values: [100],
}),
],
};
const { data } = await $.http.get(
`/v1/databases/${databaseId}/collections`,
{ params }
);
if (data?.collections) {
for (const collection of data.collections) {
collections.data.push({
value: collection.$id,
name: collection.name,
});
}
}
return collections;
},
};

View File

@@ -1,36 +0,0 @@
export default {
name: 'List databases',
key: 'listDatabases',
async run($) {
const databases = {
data: [],
};
const params = {
queries: [
JSON.stringify({
method: 'orderAsc',
attribute: 'name',
}),
JSON.stringify({
method: 'limit',
values: [100],
}),
],
};
const { data } = await $.http.get('/v1/databases', { params });
if (data?.databases) {
for (const database of data.databases) {
databases.data.push({
value: database.$id,
name: database.name,
});
}
}
return databases;
},
};

View File

@@ -1,21 +0,0 @@
import defineApp from '../../helpers/define-app.js';
import addAuthHeader from './common/add-auth-header.js';
import setBaseUrl from './common/set-base-url.js';
import auth from './auth/index.js';
import triggers from './triggers/index.js';
import dynamicData from './dynamic-data/index.js';
export default defineApp({
name: 'Appwrite',
key: 'appwrite',
baseUrl: 'https://appwrite.io',
apiBaseUrl: 'https://cloud.appwrite.io',
iconUrl: '{BASE_URL}/apps/appwrite/assets/favicon.svg',
authDocUrl: '{DOCS_URL}/apps/appwrite/connection',
primaryColor: 'FD366E',
supportsConnections: true,
beforeRequest: [setBaseUrl, addAuthHeader],
auth,
triggers,
dynamicData,
});

View File

@@ -1,3 +0,0 @@
import newDocuments from './new-documents/index.js';
export default [newDocuments];

View File

@@ -1,104 +0,0 @@
import defineTrigger from '../../../../helpers/define-trigger.js';
export default defineTrigger({
name: 'New documents',
key: 'newDocuments',
pollInterval: 15,
description: 'Triggers when a new document is created.',
arguments: [
{
label: 'Database',
key: 'databaseId',
type: 'dropdown',
required: true,
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listDatabases',
},
],
},
},
{
label: 'Collection',
key: 'collectionId',
type: 'dropdown',
required: true,
dependsOn: ['parameters.databaseId'],
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listCollections',
},
{
name: 'parameters.databaseId',
value: '{parameters.databaseId}',
},
],
},
},
],
async run($) {
const { databaseId, collectionId } = $.step.parameters;
const limit = 1;
let lastDocumentId = undefined;
let offset = 0;
let documentCount = 0;
do {
const params = {
queries: [
JSON.stringify({
method: 'orderDesc',
attribute: '$createdAt',
}),
JSON.stringify({
method: 'limit',
values: [limit],
}),
// An invalid cursor shouldn't be sent.
lastDocumentId &&
JSON.stringify({
method: 'cursorAfter',
values: [lastDocumentId],
}),
].filter(Boolean),
};
const { data } = await $.http.get(
`/v1/databases/${databaseId}/collections/${collectionId}/documents`,
{ params }
);
const documents = data?.documents;
documentCount = documents?.length;
offset = offset + limit;
lastDocumentId = documents[documentCount - 1]?.$id;
if (!documentCount) {
return;
}
for (const document of documents) {
$.pushTriggerItem({
raw: document,
meta: {
internalId: document.$id,
},
});
}
} while (documentCount === limit);
},
});

View File

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="200px" height="200px" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
<g id="background">
<rect fill="#2E9FFF" width="200" height="200"/>
</g>
<g id="Layer_2">
</g>
<path fill="#FFFFFF" d="M102.535,167.5c-16.518,0-31.621-6.036-43.298-16.021L30.5,155.405l11.102-27.401
c-3.868-8.535-6.038-18.01-6.038-28.004c0-37.277,29.984-67.5,66.971-67.5c36.984,0,66.965,30.223,66.965,67.5
C169.5,137.284,139.52,167.5,102.535,167.5z M139.102,99.807v-0.188c0-19.479-13.736-33.367-37.42-33.367h-25.58v67.5h25.201
C125.171,133.753,139.102,119.284,139.102,99.807L139.102,99.807z M101.964,117.168h-7.482V82.841h7.482
c10.989,0,18.283,6.265,18.283,17.07v0.188C120.247,110.995,112.953,117.168,101.964,117.168z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,21 +0,0 @@
import { URLSearchParams } from 'url';
import authScope from '../common/auth-scope.js';
export default async function generateAuthUrl($) {
const oauthRedirectUrlField = $.app.auth.fields.find(
(field) => field.key == 'oAuthRedirectUrl'
);
const redirectUri = oauthRedirectUrlField.value;
const searchParams = new URLSearchParams({
client_id: $.auth.data.apiKey,
scope: authScope.join(','),
response_type: 'code',
redirect_uri: redirectUri,
});
const url = `https://disqus.com/api/oauth/2.0/authorize/?${searchParams.toString()}`;
await $.auth.set({
url,
});
}

View File

@@ -1,48 +0,0 @@
import generateAuthUrl from './generate-auth-url.js';
import verifyCredentials from './verify-credentials.js';
import refreshToken from './refresh-token.js';
import isStillVerified from './is-still-verified.js';
export default {
fields: [
{
key: 'oAuthRedirectUrl',
label: 'OAuth Redirect URL',
type: 'string',
required: true,
readOnly: true,
value: '{WEB_APP_URL}/app/disqus/connections/add',
placeholder: null,
description:
'When asked to input a redirect URL in Disqus, enter the URL above.',
clickToCopy: true,
},
{
key: 'apiKey',
label: 'API Key',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
clickToCopy: false,
},
{
key: 'apiSecret',
label: 'API Secret',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
clickToCopy: false,
},
],
generateAuthUrl,
verifyCredentials,
isStillVerified,
refreshToken,
};

View File

@@ -1,8 +0,0 @@
import getCurrentUser from '../common/get-current-user.js';
const isStillVerified = async ($) => {
const currentUser = await getCurrentUser($);
return !!currentUser.response.username;
};
export default isStillVerified;

View File

@@ -1,26 +0,0 @@
import { URLSearchParams } from 'node:url';
import authScope from '../common/auth-scope.js';
const refreshToken = async ($) => {
const params = new URLSearchParams({
grant_type: 'refresh_token',
client_id: $.auth.data.apiKey,
client_secret: $.auth.data.apiSecret,
refresh_token: $.auth.data.refreshToken,
});
const { data } = await $.http.post(
`https://disqus.com/api/oauth/2.0/access_token/`,
params.toString()
);
await $.auth.set({
accessToken: data.access_token,
refreshToken: data.refresh_token,
expiresIn: data.expires_in,
scope: authScope.join(','),
tokenType: data.token_type,
});
};
export default refreshToken;

View File

@@ -1,34 +0,0 @@
import { URLSearchParams } from 'url';
const verifyCredentials = async ($) => {
const oauthRedirectUrlField = $.app.auth.fields.find(
(field) => field.key == 'oAuthRedirectUrl'
);
const redirectUri = oauthRedirectUrlField.value;
const params = new URLSearchParams({
grant_type: 'authorization_code',
client_id: $.auth.data.apiKey,
client_secret: $.auth.data.apiSecret,
redirect_uri: redirectUri,
code: $.auth.data.code,
});
const { data } = await $.http.post(
`https://disqus.com/api/oauth/2.0/access_token/`,
params.toString()
);
await $.auth.set({
accessToken: data.access_token,
tokenType: data.token_type,
apiKey: $.auth.data.apiKey,
apiSecret: $.auth.data.apiSecret,
scope: $.auth.data.scope,
userId: data.user_id,
expiresIn: data.expires_in,
refreshToken: data.refresh_token,
screenName: data.username,
});
};
export default verifyCredentials;

View File

@@ -1,15 +0,0 @@
import { URLSearchParams } from 'url';
const addAuthHeader = ($, requestConfig) => {
const params = new URLSearchParams({
access_token: $.auth.data.accessToken,
api_key: $.auth.data.apiKey,
api_secret: $.auth.data.apiSecret,
});
requestConfig.params = params;
return requestConfig;
};
export default addAuthHeader;

View File

@@ -1,3 +0,0 @@
const authScope = ['read', 'write', 'admin', 'email'];
export default authScope;

View File

@@ -1,10 +0,0 @@
const getCurrentUser = async ($) => {
try {
const { data: currentUser } = await $.http.get('/3.0/users/details.json');
return currentUser;
} catch (error) {
throw new Error('You are not authenticated.');
}
};
export default getCurrentUser;

View File

@@ -1,3 +0,0 @@
import listForums from './list-forums/index.js';
export default [listForums];

View File

@@ -1,36 +0,0 @@
export default {
name: 'List forums',
key: 'listForums',
async run($) {
const forums = {
data: [],
};
const params = {
limit: 100,
order: 'desc',
cursor: undefined,
};
let more;
do {
const { data } = await $.http.get('/3.0/users/listForums.json', {
params,
});
params.cursor = data.cursor.next;
more = data.cursor.hasNext;
if (data.response?.length) {
for (const forum of data.response) {
forums.data.push({
value: forum.id,
name: forum.id,
});
}
}
} while (more);
return forums;
},
};

View File

@@ -1,20 +0,0 @@
import defineApp from '../../helpers/define-app.js';
import addAuthHeader from './common/add-auth-header.js';
import auth from './auth/index.js';
import dynamicData from './dynamic-data/index.js';
import triggers from './triggers/index.js';
export default defineApp({
name: 'Disqus',
key: 'disqus',
baseUrl: 'https://disqus.com',
apiBaseUrl: 'https://disqus.com/api',
iconUrl: '{BASE_URL}/apps/disqus/assets/favicon.svg',
authDocUrl: '{DOCS_URL}/apps/disqus/connection',
primaryColor: '2E9FFF',
supportsConnections: true,
beforeRequest: [addAuthHeader],
auth,
dynamicData,
triggers,
});

View File

@@ -1,4 +0,0 @@
import newComments from './new-comments/index.js';
import newFlaggedComments from './new-flagged-comments/index.js';
export default [newComments, newFlaggedComments];

View File

@@ -1,92 +0,0 @@
import defineTrigger from '../../../../helpers/define-trigger.js';
import { URLSearchParams } from 'url';
export default defineTrigger({
name: 'New comments',
key: 'newComments',
pollInterval: 15,
description: 'Triggers when a new comment is posted in a forum using Disqus.',
arguments: [
{
label: 'Post Types',
key: 'postTypes',
type: 'dynamic',
required: false,
description:
'Which posts should be considered for inclusion in the trigger?',
fields: [
{
label: 'Type',
key: 'type',
type: 'dropdown',
required: false,
description: '',
variables: true,
options: [
{ label: 'Unapproved Posts', value: 'unapproved' },
{ label: 'Approved Posts', value: 'approved' },
{ label: 'Spam Posts', value: 'spam' },
{ label: 'Deleted Posts', value: 'deleted' },
{ label: 'Flagged Posts', value: 'flagged' },
{ label: 'Highlighted Posts', value: 'highlighted' },
],
},
],
},
{
label: 'Forum',
key: 'forumId',
type: 'dropdown',
required: true,
description: 'Select the forum where you want comments to be triggered.',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listForums',
},
],
},
},
],
async run($) {
const forumId = $.step.parameters.forumId;
const postTypes = $.step.parameters.postTypes;
const formattedCommentTypes = postTypes
.filter((type) => type.type !== '')
.map((type) => type.type);
const params = new URLSearchParams({
limit: '100',
forum: forumId,
});
if (formattedCommentTypes.length) {
formattedCommentTypes.forEach((type) => params.append('include', type));
}
let more;
do {
const { data } = await $.http.get(
`/3.0/posts/list.json?${params.toString()}`
);
params.set('cursor', data.cursor.next);
more = data.cursor.hasNext;
if (data.response?.length) {
for (const comment of data.response) {
$.pushTriggerItem({
raw: comment,
meta: {
internalId: comment.id,
},
});
}
}
} while (more);
},
});

View File

@@ -1,60 +0,0 @@
import defineTrigger from '../../../../helpers/define-trigger.js';
import { URLSearchParams } from 'url';
export default defineTrigger({
name: 'New flagged comments',
key: 'newFlaggedComments',
pollInterval: 15,
description: 'Triggers when a Disqus comment is marked with a flag',
arguments: [
{
label: 'Forum',
key: 'forumId',
type: 'dropdown',
required: true,
description: 'Select the forum where you want comments to be triggered.',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listForums',
},
],
},
},
],
async run($) {
const forumId = $.step.parameters.forumId;
const isFlaggedFilter = 5;
const params = new URLSearchParams({
limit: 100,
forum: forumId,
filters: [isFlaggedFilter],
});
let more;
do {
const { data } = await $.http.get(
`/3.0/posts/list.json?${params.toString()}`
);
params.set('cursor', data.cursor.next);
more = data.cursor.hasNext;
if (data.response?.length) {
for (const comment of data.response) {
$.pushTriggerItem({
raw: comment,
meta: {
internalId: comment.id,
},
});
}
}
} while (more);
},
});

View File

@@ -1,21 +0,0 @@
<!--
- maskable-icon.svg
- Copyright (c) 2022 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<svg height="377.95276" width="377.95276" xmlns="http://www.w3.org/2000/svg"><path d="m0 0h377.95276v377.95276h-377.95276z" fill="#cd5029" stroke-width="1.96129"/><g transform="matrix(.77452773 0 0 .77452773 21.636074 21.374655)"><path d="m140.49013 78.646381 2.249 53.017999s-40.103 29.566-45.538 68l-16.001 1.231s-11.539 2.564-11.539 14.103v37.18s3.846 11.538 12.82 11.538l16.487-.319s8 30.5 36.5 50.5v25.5s-2 8.5 15.5 11 40.75 2.25 44.5-1.5 3.75-4.5 3.75-9c0 0 21.25 5 60.25 0v5s3.5 7 29 7 33-3 37.5-12v-25s37.009-36.264 35.75-91.75c-1.083-47.75-15.901-64.299-35.806-82.96-22.67-21.254-69.944-31.165-117.944-25.353.001-.001-24.341-43.937999-67.478-36.187999z" fill="#fff"/><circle cx="135.46912" cy="214.39638" fill="#cd5029" r="9.5"/><path d="m360.08113 190.51238s-18.218-8.742-40.662 3.996c0 0-26.711-8.987-40.99 2.593-14.828 12.025-16.299 26.115-15.525 42.785 0 0 12.837-43.915 45.252-32.571 0 0-22.947 40.43 12.761 47.508 0 0 8.436-.05 15.401-4.256 6.644-4.011 11.842-11.433 9.711-24.814 0 0-4.348-13.336-15.569-21.42 0 0 11.042-7.806 31.988-2.209z" fill="#cd5029"/><path d="m320.19013 213.01938s-16.689 31.461 5.607 29.767c0 0 11.838-5.656 4.887-17.127-7.147-11.796-10.494-12.64-10.494-12.64z" fill="#fff"/></g><path d="m188.97638 175.70052s4.01698 13.60604-3.69586 21.52748c-7.713 7.92145-6.8792 16.6767-3.75227 20.84588 3.12692 4.16917 2.91831 7.29593.41674 9.58905-2.50141 2.29312-4.58608 3.96073-6.04523.20846-1.45916-3.75228-3.12676-3.75228-3.75228-5.62834-.62552-1.87605-1.87622-5.21142-1.87622-5.21142s-3.96072 6.25384-6.46229 10.00611c-2.50157 3.75228-2.50141 9.58922-.83381 12.71598 1.66761 3.12676 1.04226 6.87903-.20845 12.09046-1.2507 5.21143.4169 13.13288 6.25369 16.2598 5.83678 3.12692 12.92459 5.62833 16.05135 8.5468s10.42301 5.62833 19.80362 3.54382c9.3806-2.0845 21.26294-11.67355 23.34744-18.13585 0 0 5.41988-6.04523 4.37763-13.96668s-4.79469-7.71316-6.4623-13.75839c-1.6676-6.04523 3.60854-4.55469-.8338-14.93382 0 0-1.98012-4.94005-9.50352-8.49899-4.83404-2.28661-1.54469-12.63061-10.09149-23.05347s-16.73295-12.14688-16.73295-12.14688z" fill="#ffa284" stroke-width=".162598"/></svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -1,20 +0,0 @@
import { URLSearchParams } from 'url';
export default async function generateAuthUrl($) {
const oauthRedirectUrlField = $.app.auth.fields.find(
(field) => field.key == 'oAuthRedirectUrl'
);
const redirectUri = oauthRedirectUrlField.value;
const searchParams = new URLSearchParams({
client_id: $.auth.data.clientId,
redirect_uri: redirectUri,
response_type: 'code',
});
const instanceUrl = $.auth.data.instanceUrl;
const url = `${instanceUrl}/oauth/authorize?${searchParams.toString()}`;
await $.auth.set({
url,
});
}

View File

@@ -1,58 +0,0 @@
import generateAuthUrl from './generate-auth-url.js';
import verifyCredentials from './verify-credentials.js';
import refreshToken from './refresh-token.js';
import isStillVerified from './is-still-verified.js';
export default {
fields: [
{
key: 'oAuthRedirectUrl',
label: 'OAuth Redirect URL',
type: 'string',
required: true,
readOnly: true,
value: '{WEB_APP_URL}/app/firefly-iii/connections/add',
placeholder: null,
description: '',
clickToCopy: true,
},
{
key: 'instanceUrl',
label: 'Instance URL',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description: '',
clickToCopy: true,
},
{
key: 'clientId',
label: 'Client ID',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
clickToCopy: false,
},
{
key: 'clientSecret',
label: 'Client Secret',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
clickToCopy: false,
},
],
generateAuthUrl,
verifyCredentials,
isStillVerified,
refreshToken,
};

View File

@@ -1,8 +0,0 @@
import getCurrentUser from '../common/get-current-user.js';
const isStillVerified = async ($) => {
const currentUser = await getCurrentUser($);
return !!currentUser.attributes.email;
};
export default isStillVerified;

View File

@@ -1,20 +0,0 @@
import { URLSearchParams } from 'node:url';
const refreshToken = async ($) => {
const params = new URLSearchParams({
client_id: $.auth.data.clientId,
client_secret: $.auth.data.clientSecret,
grant_type: 'refresh_token',
refresh_token: $.auth.data.refreshToken,
});
const { data } = await $.http.post(`/oauth/token`, params.toString());
await $.auth.set({
accessToken: data.access_token,
expiresIn: data.expires_in,
tokenType: data.token_type,
});
};
export default refreshToken;

View File

@@ -1,35 +0,0 @@
import getCurrentUser from '../common/get-current-user.js';
const verifyCredentials = async ($) => {
const oauthRedirectUrlField = $.app.auth.fields.find(
(field) => field.key == 'oAuthRedirectUrl'
);
const redirectUri = oauthRedirectUrlField.value;
const { data } = await $.http.post('/oauth/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($);
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: currentUser.attributes.email,
});
};
export default verifyCredentials;

View File

@@ -1,9 +0,0 @@
const addAuthHeader = ($, requestConfig) => {
if ($.auth.data?.accessToken) {
requestConfig.headers.Authorization = `${$.auth.data.tokenType} ${$.auth.data.accessToken}`;
}
return requestConfig;
};
export default addAuthHeader;

View File

@@ -1,10 +0,0 @@
const getCurrentUser = async ($) => {
const { data: currentUser } = await $.http.get('/api/v1/about/user', {
Headers: {
Accept: 'application/vnd.api+json',
},
});
return currentUser.data;
};
export default getCurrentUser;

View File

@@ -1,10 +0,0 @@
const setBaseUrl = ($, requestConfig) => {
const instanceUrl = $.auth.data.instanceUrl;
if (instanceUrl) {
requestConfig.baseURL = instanceUrl;
}
return requestConfig;
};
export default setBaseUrl;

View File

@@ -1,19 +0,0 @@
import defineApp from '../../helpers/define-app.js';
import addAuthHeader from './common/add-auth-header.js';
import auth from './auth/index.js';
import setBaseUrl from './common/set-base-url.js';
import triggers from './triggers/index.js';
export default defineApp({
name: 'Firefly III',
key: 'firefly-iii',
baseUrl: '',
apiBaseUrl: '',
iconUrl: '{BASE_URL}/apps/firefly-iii/assets/favicon.svg',
authDocUrl: '{DOCS_URL}/apps/firefly-iii/connection',
primaryColor: 'CD5029',
supportsConnections: true,
beforeRequest: [setBaseUrl, addAuthHeader],
auth,
triggers,
});

View File

@@ -1,5 +0,0 @@
import transactionCreated from './transaction-created/index.js';
import transactionDeleted from './transaction-deleted/index.js';
import transactionUpdated from './transaction-updated/index.js';
export default [transactionCreated, transactionDeleted, transactionUpdated];

View File

@@ -1,89 +0,0 @@
import Crypto from 'crypto';
import defineTrigger from '../../../../helpers/define-trigger.js';
export default defineTrigger({
name: 'Transaction created',
key: 'transactionCreated',
type: 'webhook',
description: 'Triggers when a new transaction is created.',
arguments: [
{
label: 'Title of the webhook',
key: 'title',
type: 'string',
required: false,
description: '',
variables: true,
},
],
async run($) {
const dataItem = {
raw: $.request.body,
meta: {
internalId: Crypto.randomUUID(),
},
};
$.pushTriggerItem(dataItem);
},
async testRun($) {
const { data: transactions } = await $.http.get(`/api/v1/transactions`);
if (transactions.data.length === 0) {
return;
}
const { data: transaction } = await $.http.get(
`/api/v1/transactions/${transactions.data[0].id}`
);
const lastTransaction = transaction.data;
if (!lastTransaction) {
return;
}
const computedWebhookEvent = {
url: '',
uuid: Crypto.randomUUID(),
content: lastTransaction.attributes,
trigger: 'STORE_TRANSACTION',
user_id: lastTransaction.attributes.user,
version: '',
response: 'TRANSACTIONS',
};
const dataItem = {
raw: computedWebhookEvent,
meta: {
internalId: computedWebhookEvent.uuid,
},
};
$.pushTriggerItem(dataItem);
},
async registerHook($) {
const title = $.step.parameters.title;
const payload = {
active: true,
title: title || `Flow ID: ${$.flow.id}`,
trigger: 'STORE_TRANSACTION',
response: 'TRANSACTIONS',
delivery: 'JSON',
url: $.webhookUrl,
};
const response = await $.http.post('/api/v1/webhooks', payload);
const id = response.data.data.id;
await $.flow.setRemoteWebhookId(id);
},
async unregisterHook($) {
await $.http.delete(`/api/v1/webhooks/${$.flow.remoteWebhookId}`);
},
});

View File

@@ -1,89 +0,0 @@
import Crypto from 'crypto';
import defineTrigger from '../../../../helpers/define-trigger.js';
export default defineTrigger({
name: 'Transaction deleted',
key: 'transactionDeleted',
type: 'webhook',
description: 'Triggers when a transaction is deleted.',
arguments: [
{
label: 'Title of the webhook',
key: 'title',
type: 'string',
required: false,
description: '',
variables: true,
},
],
async run($) {
const dataItem = {
raw: $.request.body,
meta: {
internalId: Crypto.randomUUID(),
},
};
$.pushTriggerItem(dataItem);
},
async testRun($) {
const { data: transactions } = await $.http.get(`/api/v1/transactions`);
if (transactions.data.length === 0) {
return;
}
const { data: transaction } = await $.http.get(
`/api/v1/transactions/${transactions.data[0].id}`
);
const lastTransaction = transaction.data;
if (!lastTransaction) {
return;
}
const computedWebhookEvent = {
url: '',
uuid: Crypto.randomUUID(),
content: lastTransaction.attributes,
trigger: 'DESTROY_TRANSACTION',
user_id: lastTransaction.attributes.user,
version: '',
response: 'TRANSACTIONS',
};
const dataItem = {
raw: computedWebhookEvent,
meta: {
internalId: computedWebhookEvent.uuid,
},
};
$.pushTriggerItem(dataItem);
},
async registerHook($) {
const title = $.step.parameters.title;
const payload = {
active: true,
title: title || `Flow ID: ${$.flow.id}`,
trigger: 'DESTROY_TRANSACTION',
response: 'TRANSACTIONS',
delivery: 'JSON',
url: $.webhookUrl,
};
const response = await $.http.post('/api/v1/webhooks', payload);
const id = response.data.data.id;
await $.flow.setRemoteWebhookId(id);
},
async unregisterHook($) {
await $.http.delete(`/api/v1/webhooks/${$.flow.remoteWebhookId}`);
},
});

View File

@@ -1,89 +0,0 @@
import Crypto from 'crypto';
import defineTrigger from '../../../../helpers/define-trigger.js';
export default defineTrigger({
name: 'Transaction updated',
key: 'transactionUpdated',
type: 'webhook',
description: 'Triggers when a transaction is updated.',
arguments: [
{
label: 'Title of the webhook',
key: 'title',
type: 'string',
required: false,
description: '',
variables: true,
},
],
async run($) {
const dataItem = {
raw: $.request.body,
meta: {
internalId: Crypto.randomUUID(),
},
};
$.pushTriggerItem(dataItem);
},
async testRun($) {
const { data: transactions } = await $.http.get(`/api/v1/transactions`);
if (transactions.data.length === 0) {
return;
}
const { data: transaction } = await $.http.get(
`/api/v1/transactions/${transactions.data[0].id}`
);
const lastTransaction = transaction.data;
if (!lastTransaction) {
return;
}
const computedWebhookEvent = {
url: '',
uuid: Crypto.randomUUID(),
content: lastTransaction.attributes,
trigger: 'UPDATE_TRANSACTION',
user_id: lastTransaction.attributes.user,
version: '',
response: 'TRANSACTIONS',
};
const dataItem = {
raw: computedWebhookEvent,
meta: {
internalId: computedWebhookEvent.uuid,
},
};
$.pushTriggerItem(dataItem);
},
async registerHook($) {
const title = $.step.parameters.title;
const payload = {
active: true,
title: title || `Flow ID: ${$.flow.id}`,
trigger: 'UPDATE_TRANSACTION',
response: 'TRANSACTIONS',
delivery: 'JSON',
url: $.webhookUrl,
};
const response = await $.http.post('/api/v1/webhooks', payload);
const id = response.data.data.id;
await $.flow.setRemoteWebhookId(id);
},
async unregisterHook($) {
await $.http.delete(`/api/v1/webhooks/${$.flow.remoteWebhookId}`);
},
});

View File

@@ -5,24 +5,11 @@ const formatDateTime = ($) => {
const fromFormat = $.step.parameters.fromFormat;
const fromTimezone = $.step.parameters.fromTimezone;
let inputDateTime;
if (fromFormat === 'X') {
inputDateTime = DateTime.fromSeconds(Number(input), fromFormat, {
zone: fromTimezone,
setZone: true,
});
} else if (fromFormat === 'x') {
inputDateTime = DateTime.fromMillis(Number(input), fromFormat, {
zone: fromTimezone,
setZone: true,
});
} else {
inputDateTime = DateTime.fromFormat(input, fromFormat, {
zone: fromTimezone,
setZone: true,
});
}
const inputDateTime = DateTime.fromFormat(input, fromFormat, {
zone: fromTimezone,
setZone: true,
});
const toFormat = $.step.parameters.toFormat;
const toTimezone = $.step.parameters.toTimezone;

View File

@@ -2,7 +2,6 @@ import defineAction from '../../../../helpers/define-action.js';
import base64ToString from './transformers/base64-to-string.js';
import capitalize from './transformers/capitalize.js';
import encodeUriComponent from './transformers/encode-uri-component.js';
import extractEmailAddress from './transformers/extract-email-address.js';
import extractNumber from './transformers/extract-number.js';
import htmlToMarkdown from './transformers/html-to-markdown.js';
@@ -11,14 +10,12 @@ import markdownToHtml from './transformers/markdown-to-html.js';
import pluralize from './transformers/pluralize.js';
import replace from './transformers/replace.js';
import stringToBase64 from './transformers/string-to-base64.js';
import encodeUri from './transformers/encode-uri.js';
import trimWhitespace from './transformers/trim-whitespace.js';
import useDefaultValue from './transformers/use-default-value.js';
const transformers = {
base64ToString,
capitalize,
encodeUriComponent,
extractEmailAddress,
extractNumber,
htmlToMarkdown,
@@ -27,7 +24,6 @@ const transformers = {
pluralize,
replace,
stringToBase64,
encodeUri,
trimWhitespace,
useDefaultValue,
};
@@ -47,10 +43,6 @@ export default defineAction({
options: [
{ label: 'Base64 to String', value: 'base64ToString' },
{ label: 'Capitalize', value: 'capitalize' },
{
label: 'Encode URI Component',
value: 'encodeUriComponent',
},
{ label: 'Convert HTML to Markdown', value: 'htmlToMarkdown' },
{ label: 'Convert Markdown to HTML', value: 'markdownToHtml' },
{ label: 'Extract Email Address', value: 'extractEmailAddress' },
@@ -59,7 +51,6 @@ export default defineAction({
{ label: 'Pluralize', value: 'pluralize' },
{ label: 'Replace', value: 'replace' },
{ label: 'String to Base64', value: 'stringToBase64' },
{ label: 'Encode URI', value: 'encodeUri' },
{ label: 'Trim Whitespace', value: 'trimWhitespace' },
{ label: 'Use Default Value', value: 'useDefaultValue' },
],

View File

@@ -1,8 +0,0 @@
const encodeUriComponent = ($) => {
const input = $.step.parameters.input;
const encodedString = encodeURIComponent(input);
return encodedString;
};
export default encodeUriComponent;

View File

@@ -1,8 +0,0 @@
const encodeUri = ($) => {
const input = $.step.parameters.input;
const encodedString = encodeURI(input);
return encodedString;
};
export default encodeUri;

View File

@@ -1,6 +1,5 @@
import base64ToString from './text/base64-to-string.js';
import capitalize from './text/capitalize.js';
import encodeUriComponent from './text/encode-uri-component.js';
import extractEmailAddress from './text/extract-email-address.js';
import extractNumber from './text/extract-number.js';
import htmlToMarkdown from './text/html-to-markdown.js';
@@ -9,7 +8,6 @@ import markdownToHtml from './text/markdown-to-html.js';
import pluralize from './text/pluralize.js';
import replace from './text/replace.js';
import stringToBase64 from './text/string-to-base64.js';
import encodeUri from './text/encode-uri.js';
import trimWhitespace from './text/trim-whitespace.js';
import useDefaultValue from './text/use-default-value.js';
import performMathOperation from './numbers/perform-math-operation.js';
@@ -21,7 +19,6 @@ import formatDateTime from './date-time/format-date-time.js';
const options = {
base64ToString,
capitalize,
encodeUriComponent,
extractEmailAddress,
extractNumber,
htmlToMarkdown,
@@ -30,7 +27,6 @@ const options = {
pluralize,
replace,
stringToBase64,
encodeUri,
trimWhitespace,
useDefaultValue,
performMathOperation,

View File

@@ -1,12 +0,0 @@
const encodeUriComponent = [
{
label: 'Input',
key: 'input',
type: 'string',
required: true,
description: 'URI Component to encode',
variables: true,
},
];
export default encodeUriComponent;

View File

@@ -1,12 +0,0 @@
const encodeUri = [
{
label: 'Input',
key: 'input',
type: 'string',
required: true,
description: 'URI to encode',
variables: true,
},
];
export default encodeUri;

View File

@@ -1,31 +0,0 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Create task list',
key: 'createTaskList',
description: 'Creates a new task list.',
arguments: [
{
label: 'List Title',
key: 'listTitle',
type: 'string',
required: true,
description: '',
variables: true,
},
],
async run($) {
const listTitle = $.step.parameters.listTitle;
const body = {
title: listTitle,
};
const { data } = await $.http.post('/tasks/v1/users/@me/lists', body);
$.setActionItem({
raw: data,
});
},
});

View File

@@ -1,70 +0,0 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Create task',
key: 'createTask',
description: 'Creates a new task.',
arguments: [
{
label: 'Task List',
key: 'taskListId',
type: 'dropdown',
required: true,
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listTaskLists',
},
],
},
},
{
label: 'Title',
key: 'title',
type: 'string',
required: true,
description: '',
variables: true,
},
{
label: 'Notes',
key: 'notes',
type: 'string',
required: false,
description: '',
variables: true,
},
{
label: 'Due Date',
key: 'due',
type: 'string',
required: false,
description: 'RFC 3339 timestamp.',
variables: true,
},
],
async run($) {
const { taskListId, title, notes, due } = $.step.parameters;
const body = {
title,
notes,
due,
};
const { data } = await $.http.post(
`/tasks/v1/lists/${taskListId}/tasks`,
body
);
$.setActionItem({
raw: data,
});
},
});

View File

@@ -1,57 +0,0 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Find task',
key: 'findTask',
description: 'Looking for a specific task.',
arguments: [
{
label: 'Task List',
key: 'taskListId',
type: 'dropdown',
required: true,
description: 'The list to be searched.',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listTaskLists',
},
],
},
},
{
label: 'Title',
key: 'title',
type: 'string',
required: true,
description: '',
variables: true,
},
],
async run($) {
const taskListId = $.step.parameters.taskListId;
const title = $.step.parameters.title;
const params = {
showCompleted: true,
showHidden: true,
};
const { data } = await $.http.get(`/tasks/v1/lists/${taskListId}/tasks`, {
params,
});
const filteredTask = data.items?.filter((task) =>
task.title.includes(title)
);
$.setActionItem({
raw: filteredTask[0],
});
},
});

View File

@@ -1,6 +0,0 @@
import createTask from './create-task/index.js';
import createTaskList from './create-task-list/index.js';
import findTask from './find-task/index.js';
import updateTask from './update-task/index.js';
export default [createTask, createTaskList, findTask, updateTask];

View File

@@ -1,108 +0,0 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Update task',
key: 'updateTask',
description: 'Updates an existing task.',
arguments: [
{
label: 'Task List',
key: 'taskListId',
type: 'dropdown',
required: true,
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listTaskLists',
},
],
},
},
{
label: 'Task',
key: 'taskId',
type: 'dropdown',
required: true,
description: 'Ensure that you choose a list before proceeding.',
variables: true,
dependsOn: ['parameters.taskListId'],
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listTasks',
},
{
name: 'parameters.taskListId',
value: '{parameters.taskListId}',
},
],
},
},
{
label: 'Title',
key: 'title',
type: 'string',
required: false,
description: 'Provide a new title for the revised task.',
variables: true,
},
{
label: 'Status',
key: 'status',
type: 'dropdown',
required: false,
description:
'Specify the status of the updated task. If you opt for a custom value, enter either "needsAttention" or "completed."',
variables: true,
options: [
{ label: 'Incomplete', value: 'needsAction' },
{ label: 'Complete', value: 'completed' },
],
},
{
label: 'Notes',
key: 'notes',
type: 'string',
required: false,
description: 'Provide a note for the revised task.',
variables: true,
},
{
label: 'Due Date',
key: 'due',
type: 'string',
required: false,
description:
'Specify the deadline for the task (as a RFC 3339 timestamp).',
variables: true,
},
],
async run($) {
const { taskListId, taskId, title, status, notes, due } = $.step.parameters;
const body = {
title,
status,
notes,
due,
};
const { data } = await $.http.patch(
`/tasks/v1/lists/${taskListId}/tasks/${taskId}`,
body
);
$.setActionItem({
raw: data,
});
},
});

View File

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1"
id="svg8849" inkscape:version="1.1 (c68e22c387, 2021-05-23)" sodipodi:docname="google tasks.svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="527.1px" height="500px"
viewBox="0 0 527.1 500" enable-background="new 0 0 527.1 500" xml:space="preserve">
<sodipodi:namedview bordercolor="#eeeeee" borderopacity="1" id="namedview8851" inkscape:bbox-nodes="true" inkscape:bbox-paths="true" inkscape:current-layer="layer1" inkscape:cx="280.62992" inkscape:cy="277.14384" inkscape:document-units="mm" inkscape:object-paths="true" inkscape:pagecheckerboard="0" inkscape:pageopacity="0" inkscape:pageshadow="0" inkscape:snap-bbox="true" inkscape:snap-bbox-edge-midpoints="true" inkscape:snap-bbox-midpoints="true" inkscape:snap-center="true" inkscape:snap-global="true" inkscape:snap-intersection-paths="true" inkscape:snap-midpoints="true" inkscape:snap-object-midpoints="true" inkscape:snap-page="true" inkscape:snap-smooth-nodes="true" inkscape:snap-text-baseline="true" inkscape:window-height="1009" inkscape:window-maximized="1" inkscape:window-width="1920" inkscape:window-x="1912" inkscape:window-y="760" inkscape:zoom="1.4342733" pagecolor="#505050" showgrid="false" units="px">
</sodipodi:namedview>
<g>
<polygon fill="#0066DA" points="410.4,58.3 368.8,81.2 348.2,120.6 368.8,168.8 407.8,211 450,187.5 475.9,142.8 450,87.5 "/>
<path fill="#2684FC" d="M249.3,219.4l98.9-98.9c29.1,22.1,50.5,53.8,59.6,90.4L272.1,346.7c-12.2,12.2-32,12.2-44.2,0l-91.5-91.5
c-9.8-9.8-9.8-25.6,0-35.3l39-39c9.8-9.8,25.6-9.8,35.3,0L249.3,219.4z M519.8,63.6l-39.7-39.7c-9.7-9.7-25.6-9.7-35.3,0
l-34.4,34.4c27.5,23,49.9,51.8,65.5,84.5l43.9-43.9C529.6,89.2,529.6,73.3,519.8,63.6z M412.5,250c0,89.8-72.8,162.5-162.5,162.5
S87.5,339.8,87.5,250S160.2,87.5,250,87.5c36.9,0,70.9,12.3,98.2,33.1l62.2-62.2C367,21.9,311.1,0,250,0C111.9,0,0,111.9,0,250
s111.9,250,250,250s250-111.9,250-250c0-38.3-8.7-74.7-24.1-107.2L407.8,211C410.8,223.5,412.5,236.6,412.5,250z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -1,23 +0,0 @@
import { URLSearchParams } from 'url';
import authScope from '../common/auth-scope.js';
export default async function generateAuthUrl($) {
const oauthRedirectUrlField = $.app.auth.fields.find(
(field) => field.key == 'oAuthRedirectUrl'
);
const redirectUri = oauthRedirectUrlField.value;
const searchParams = new URLSearchParams({
client_id: $.auth.data.clientId,
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,
});
}

View File

@@ -1,48 +0,0 @@
import generateAuthUrl from './generate-auth-url.js';
import verifyCredentials from './verify-credentials.js';
import refreshToken from './refresh-token.js';
import isStillVerified from './is-still-verified.js';
export default {
fields: [
{
key: 'oAuthRedirectUrl',
label: 'OAuth Redirect URL',
type: 'string',
required: true,
readOnly: true,
value: '{WEB_APP_URL}/app/google-tasks/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',
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
clickToCopy: false,
},
{
key: 'clientSecret',
label: 'Client Secret',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
clickToCopy: false,
},
],
generateAuthUrl,
verifyCredentials,
isStillVerified,
refreshToken,
};

View File

@@ -1,8 +0,0 @@
import getCurrentUser from '../common/get-current-user.js';
const isStillVerified = async ($) => {
const currentUser = await getCurrentUser($);
return !!currentUser.resourceName;
};
export default isStillVerified;

View File

@@ -1,25 +0,0 @@
import { URLSearchParams } from 'node:url';
import authScope from '../common/auth-scope.js';
const refreshToken = async ($) => {
const params = new URLSearchParams({
client_id: $.auth.data.clientId,
client_secret: $.auth.data.clientSecret,
grant_type: 'refresh_token',
refresh_token: $.auth.data.refreshToken,
});
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;

View File

@@ -1,42 +0,0 @@
import getCurrentUser from '../common/get-current-user.js';
const verifyCredentials = async ($) => {
const oauthRedirectUrlField = $.app.auth.fields.find(
(field) => field.key == 'oAuthRedirectUrl'
);
const redirectUri = oauthRedirectUrlField.value;
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) => name.metadata.primary
);
const { value: email } = currentUser.emailAddresses.find(
(emailAddress) => 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;

View File

@@ -1,9 +0,0 @@
const addAuthHeader = ($, requestConfig) => {
if ($.auth.data?.accessToken) {
requestConfig.headers.Authorization = `${$.auth.data.tokenType} ${$.auth.data.accessToken}`;
}
return requestConfig;
};
export default addAuthHeader;

View File

@@ -1,7 +0,0 @@
const authScope = [
'https://www.googleapis.com/auth/tasks',
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/userinfo.profile',
];
export default authScope;

View File

@@ -1,8 +0,0 @@
const getCurrentUser = async ($) => {
const { data: currentUser } = await $.http.get(
'https://people.googleapis.com/v1/people/me?personFields=names,emailAddresses'
);
return currentUser;
};
export default getCurrentUser;

View File

@@ -1,4 +0,0 @@
import listTaskLists from './list-task-lists/index.js';
import listTasks from './list-tasks/index.js';
export default [listTaskLists, listTasks];

View File

@@ -1,33 +0,0 @@
export default {
name: 'List task lists',
key: 'listTaskLists',
async run($) {
const taskLists = {
data: [],
};
const params = {
maxResults: 100,
pageToken: undefined,
};
do {
const { data } = await $.http.get('/tasks/v1/users/@me/lists', {
params,
});
params.pageToken = data.nextPageToken;
if (data.items) {
for (const taskList of data.items) {
taskLists.data.push({
value: taskList.id,
name: taskList.title,
});
}
}
} while (params.pageToken);
return taskLists;
},
};

View File

@@ -1,40 +0,0 @@
export default {
name: 'List tasks',
key: 'listTasks',
async run($) {
const tasks = {
data: [],
};
const taskListId = $.step.parameters.taskListId;
const params = {
maxResults: 100,
pageToken: undefined,
};
if (!taskListId) {
return tasks;
}
do {
const { data } = await $.http.get(`/tasks/v1/lists/${taskListId}/tasks`, {
params,
});
params.pageToken = data.nextPageToken;
if (data.items) {
for (const task of data.items) {
if (task.title !== '') {
tasks.data.push({
value: task.id,
name: task.title,
});
}
}
}
} while (params.pageToken);
return tasks;
},
};

View File

@@ -1,5 +0,0 @@
import newCompletedTasks from './new-completed-tasks/index.js';
import newTaskLists from './new-task-lists/index.js';
import newTasks from './new-tasks/index.js';
export default [newCompletedTasks, newTaskLists, newTasks];

View File

@@ -1,59 +0,0 @@
import defineTrigger from '../../../../helpers/define-trigger.js';
export default defineTrigger({
name: 'New completed tasks',
key: 'newCompletedTasks',
pollInterval: 15,
description: 'Triggers when a task is finished within a specified task list.',
arguments: [
{
label: 'Task List',
key: 'taskListId',
type: 'dropdown',
required: true,
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listTaskLists',
},
],
},
},
],
async run($) {
const taskListId = $.step.parameters.taskListId;
const params = {
maxResults: 100,
showCompleted: true,
showHidden: true,
pageToken: undefined,
};
do {
const { data } = await $.http.get(`/tasks/v1/lists/${taskListId}/tasks`, {
params,
});
params.pageToken = data.nextPageToken;
if (data.items?.length) {
for (const task of data.items) {
if (task.status === 'completed') {
$.pushTriggerItem({
raw: task,
meta: {
internalId: task.id,
},
});
}
}
}
} while (params.pageToken);
},
});

View File

@@ -1,31 +0,0 @@
import defineTrigger from '../../../../helpers/define-trigger.js';
export default defineTrigger({
name: 'New task lists',
key: 'newTaskLists',
pollInterval: 15,
description: 'Triggers when a new task list is created.',
async run($) {
const params = {
maxResults: 100,
pageToken: undefined,
};
do {
const { data } = await $.http.get('/tasks/v1/users/@me/lists');
params.pageToken = data.nextPageToken;
if (data.items?.length) {
for (const taskList of data.items.reverse()) {
$.pushTriggerItem({
raw: taskList,
meta: {
internalId: taskList.id,
},
});
}
}
} while (params.pageToken);
},
});

View File

@@ -1,53 +0,0 @@
import defineTrigger from '../../../../helpers/define-trigger.js';
export default defineTrigger({
name: 'New tasks',
key: 'newTasks',
pollInterval: 15,
description: 'Triggers when a new task is created.',
arguments: [
{
label: 'Task List',
key: 'taskListId',
type: 'dropdown',
required: true,
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listTaskLists',
},
],
},
},
],
async run($) {
const taskListId = $.step.parameters.taskListId;
const params = {
maxResults: 100,
pageToken: undefined,
};
do {
const { data } = await $.http.get(`/tasks/v1/lists/${taskListId}/tasks`);
params.pageToken = data.nextPageToken;
if (data.items?.length) {
for (const task of data.items) {
$.pushTriggerItem({
raw: task,
meta: {
internalId: task.id,
},
});
}
}
} while (params.pageToken);
},
});

View File

@@ -0,0 +1,70 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Create board',
key: 'createBoard',
description: 'Creates a new board.',
arguments: [
{
label: 'Board Name',
key: 'boardName',
type: 'string',
required: true,
description: 'Title for the board.',
variables: true,
},
{
label: 'Board Kind',
key: 'boardKind',
type: 'dropdown',
required: true,
description: '',
variables: true,
options: [
{
label: 'Main',
value: 'public',
},
{
label: 'Private',
value: 'private',
},
{
label: 'Shareable',
value: 'share',
},
],
},
{
label: 'Template ID',
key: 'templateId',
type: 'string',
required: false,
description:
"When you switch on developer mode, you'll spot the template IDs in your template store. Additionally, you have the option to utilize the Board ID from any board you've saved as a template.",
variables: true,
},
],
async run($) {
const { boardName, boardKind, templateId } = $.step.parameters;
const body = {
query: `mutation {
create_board (board_name: "${boardName}", board_kind: ${boardKind}${
templateId ? `, template_id: ${templateId}` : ''
}) {
id
name
board_kind
}
}`,
};
const { data } = await $.http.post('/', body);
$.setActionItem({
raw: data,
});
},
});

View File

@@ -0,0 +1,112 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Create item',
key: 'createItem',
description: 'Creates a new item in a board.',
arguments: [
{
label: 'Board',
key: 'boardId',
type: 'dropdown',
required: true,
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listBoards',
},
],
},
},
{
label: 'Group',
key: 'groupId',
type: 'dropdown',
required: false,
description: '',
dependsOn: ['parameters.boardId'],
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listGroups',
},
{
name: 'parameters.boardId',
value: '{parameters.boardId}',
},
],
},
},
{
label: 'Item Name',
key: 'itemName',
type: 'string',
required: true,
description: '',
variables: true,
},
{
label: 'Subitem Names',
key: 'subitemNames',
type: 'dynamic',
required: false,
description: '',
fields: [
{
label: 'Subitem Name',
key: 'subitemName',
type: 'string',
required: false,
description: '',
variables: true,
},
],
},
],
async run($) {
const { boardId, groupId, itemName, subitemNames } = $.step.parameters;
const allSubitems = subitemNames.map((entry) => entry.subitemName);
const body = {
query: `
mutation {
create_item (board_id: ${boardId}${
groupId ? `, group_id: "${groupId}"` : ''
}, item_name: "${itemName}") {
id
}
}`,
};
const { data } = await $.http.post('/', body);
const itemId = data.data.create_item.id;
for (let subitemName of allSubitems) {
let body = {
query: `
mutation {
create_subitem (parent_item_id:${itemId}, item_name:"${subitemName}") {
id
}
}`,
};
await $.http.post('/', body);
}
$.setActionItem({
raw: data,
});
},
});

View File

@@ -0,0 +1,4 @@
import createBoard from './create-board/index.js';
import createItem from './create-item/index.js';
export default [createBoard, createItem];

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="256px" height="156px" viewBox="0 0 256 156" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
<g>
<path d="M31.8458633,153.488694 C20.3244423,153.513586 9.68073708,147.337265 3.98575204,137.321731 C-1.62714067,127.367831 -1.29055839,115.129325 4.86093879,105.498969 L62.2342919,15.4033556 C68.2125882,5.54538256 79.032489,-0.333585033 90.5563073,0.0146553508 C102.071737,0.290611552 112.546041,6.74705604 117.96667,16.9106216 C123.315033,27.0238906 122.646488,39.1914174 116.240607,48.6847625 L58.9037201,138.780375 C52.9943022,147.988884 42.7873202,153.537154 31.8458633,153.488694 L31.8458633,153.488694 Z" fill="#F62B54"></path>
<path d="M130.25575,153.488484 C118.683837,153.488484 108.035731,147.301291 102.444261,137.358197 C96.8438154,127.431292 97.1804475,115.223704 103.319447,105.620522 L160.583402,15.7315506 C166.47539,5.73210989 177.327374,-0.284878136 188.929728,0.0146553508 C200.598885,0.269918151 211.174058,6.7973526 216.522421,17.0078646 C221.834319,27.2183766 221.056375,39.4588356 214.456008,48.9278699 L157.204209,138.816842 C151.313487,147.985468 141.153618,153.5168 130.25575,153.488484 Z" fill="#FFCC00"></path>
<ellipse fill="#00CA72" cx="226.465527" cy="125.324379" rx="29.5375538" ry="28.9176274"></ellipse>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,21 @@
import verifyCredentials from './verify-credentials.js';
import isStillVerified from './is-still-verified.js';
export default {
fields: [
{
key: 'apiToken',
label: 'API Token',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description: 'Monday.com API token of your account.',
clickToCopy: false,
},
],
verifyCredentials,
isStillVerified,
};

Some files were not shown because too many files have changed in this diff Show More