Compare commits

..

6 Commits

Author SHA1 Message Date
Rıdvan Akca
c7e887dbfa feat(monday): add new users trigger 2024-05-09 15:23:26 +02:00
Rıdvan Akca
8c0a5a3bd3 feat(monday): add create column action 2024-05-09 14:48:20 +02:00
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
423 changed files with 1359 additions and 13210 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

View File

@@ -71,6 +71,9 @@ jobs:
- name: Migrate database
working-directory: ./packages/backend
run: yarn db:migrate
- name: Seed user
working-directory: ./packages/backend
run: yarn db:seed:user &
- name: Install certutils
run: sudo apt install -y libnss3-tools
- name: Install mkcert

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,64 +0,0 @@
import { createHmac } from 'node:crypto';
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Create HMAC',
key: 'createHmac',
description: 'Create a Hash-based Message Authentication Code (HMAC) using the specified algorithm, secret key, and message.',
arguments: [
{
label: 'Algorithm',
key: 'algorithm',
type: 'dropdown',
required: true,
value: 'sha256',
description: 'Specifies the cryptographic hash function to use for HMAC generation.',
options: [
{ label: 'SHA-256', value: 'sha256' },
],
variables: true,
},
{
label: 'Message',
key: 'message',
type: 'string',
required: true,
description: 'The input message to be hashed. This is the value that will be processed to generate the HMAC.',
variables: true,
},
{
label: 'Secret Key',
key: 'secretKey',
type: 'string',
required: true,
description: 'The secret key used to create the HMAC.',
variables: true,
},
{
label: 'Output Encoding',
key: 'outputEncoding',
type: 'dropdown',
required: true,
value: 'hex',
description: 'Specifies the encoding format for the HMAC digest output.',
options: [
{ label: 'base64', value: 'base64' },
{ label: 'base64url', value: 'base64url' },
{ label: 'hex', value: 'hex' },
],
variables: true,
},
],
async run($) {
const hash = createHmac($.step.parameters.algorithm, $.step.parameters.secretKey)
.update($.step.parameters.message)
.digest($.step.parameters.outputEncoding);
$.setActionItem({
raw: {
hash
},
});
},
});

View File

@@ -1,65 +0,0 @@
import crypto from 'node:crypto';
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Create Signature',
key: 'createSignature',
description: 'Create a digital signature using the specified algorithm, secret key, and message.',
arguments: [
{
label: 'Algorithm',
key: 'algorithm',
type: 'dropdown',
required: true,
value: 'RSA-SHA256',
description: 'Specifies the cryptographic hash function to use for HMAC generation.',
options: [
{ label: 'RSA-SHA256', value: 'RSA-SHA256' },
],
variables: true,
},
{
label: 'Message',
key: 'message',
type: 'string',
required: true,
description: 'The input message to be signed.',
variables: true,
},
{
label: 'Private Key',
key: 'privateKey',
type: 'string',
required: true,
description: 'The RSA private key in PEM format used for signing.',
variables: true,
},
{
label: 'Output Encoding',
key: 'outputEncoding',
type: 'dropdown',
required: true,
value: 'hex',
description: 'Specifies the encoding format for the digital signature output. This determines how the generated signature will be represented as a string.',
options: [
{ label: 'base64', value: 'base64' },
{ label: 'base64url', value: 'base64url' },
{ label: 'hex', value: 'hex' },
],
variables: true,
},
],
async run($) {
const signer = crypto.createSign($.step.parameters.algorithm);
signer.update($.step.parameters.message);
signer.end();
const signature = signer.sign($.step.parameters.privateKey, $.step.parameters.outputEncoding);
$.setActionItem({
raw: {
signature
},
});
},
});

View File

@@ -1,4 +0,0 @@
import createHmac from './create-hmac/index.js';
import createRsaSha256Signature from './create-rsa-sha256-signature/index.js';
export default [createHmac, createRsaSha256Signature];

View File

@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="100pt" height="100pt" version="1.1" viewBox="0 0 100 100">
<path d="m66.012 33h-3.0117v-11c0-7.1719-5.8281-13-13-13s-13 5.8281-13 13v11h-3.0117c-2.75 0-4.9883 2.2383-4.9883 4.9883v28.012c0 2.75 2.2383 4.9883 4.9883 4.9883h32.012c2.75 0 4.9883-2.2383 4.9883-4.9883v-28.012c0.011719-2.75-2.2266-4.9883-4.9766-4.9883zm-27.012-11c0-6.0703 4.9297-11 11-11s11 4.9297 11 11v11h-22zm30 44.012c0 1.6484-1.3398 2.9883-2.9883 2.9883h-32.023c-1.6484 0-2.9883-1.3398-2.9883-2.9883v-28.023c0-1.6484 1.3398-2.9883 2.9883-2.9883h32.023c1.6484 0 2.9883 1.3398 2.9883 2.9883zm-18 9.9883v14c0 0.55078-0.44922 1-1 1s-1-0.44922-1-1v-14c0-0.55078 0.44922-1 1-1s1 0.44922 1 1zm20 8c0 0.55078-0.44922 1-1 1h-8c-0.55078 0-1-0.44922-1-1v-8c0-0.55078 0.44922-1 1-1s1 0.44922 1 1v7h7c0.55078 0 1 0.44922 1 1zm-32-8v8c0 0.55078-0.44922 1-1 1h-8c-0.55078 0-1-0.44922-1-1s0.44922-1 1-1h7v-7c0-0.55078 0.44922-1 1-1s1 0.44922 1 1zm-14-26c0 0.55078-0.44922 1-1 1h-14c-0.55078 0-1-0.44922-1-1s0.44922-1 1-1h14c0.55078 0 1 0.44922 1 1zm0-12c0 0.55078-0.44922 1-1 1h-8c-0.55078 0-1-0.44922-1-1v-8c0-0.55078 0.44922-1 1-1s1 0.44922 1 1v7h7c0.55078 0 1 0.44922 1 1zm0 24c0 0.55078-0.44922 1-1 1h-7v7c0 0.55078-0.44922 1-1 1s-1-0.44922-1-1v-8c0-0.55078 0.44922-1 1-1h8c0.55078 0 1 0.44922 1 1zm66-12c0 0.55078-0.44922 1-1 1h-14c-0.55078 0-1-0.44922-1-1s0.44922-1 1-1h14c0.55078 0 1 0.44922 1 1zm-16-12c0-0.55078 0.44922-1 1-1h7v-7c0-0.55078 0.44922-1 1-1s1 0.44922 1 1v8c0 0.55078-0.44922 1-1 1h-8c-0.55078 0-1-0.44922-1-1zm10 24v8c0 0.55078-0.44922 1-1 1s-1-0.44922-1-1v-7h-7c-0.55078 0-1-0.44922-1-1s0.44922-1 1-1h8c0.55078 0 1 0.44922 1 1zm-35-17c-2.7617 0-5 2.2383-5 5 0 2.4102 1.7188 4.4297 4 4.8984v5.1016c0 0.55078 0.44922 1 1 1s1-0.44922 1-1v-5.1016c2.2812-0.46094 4-2.4805 4-4.8984 0-2.7617-2.2383-5-5-5zm0 8c-1.6484 0-3-1.3516-3-3s1.3516-3 3-3 3 1.3516 3 3-1.3516 3-3 3z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -1,14 +0,0 @@
import defineApp from '../../helpers/define-app.js';
import actions from './actions/index.js';
export default defineApp({
name: 'Cryptography',
key: 'cryptography',
iconUrl: '{BASE_URL}/apps/cryptography/assets/favicon.svg',
authDocUrl: '{DOCS_URL}/apps/cryptography/connection',
supportsConnections: false,
baseUrl: '',
apiBaseUrl: '',
primaryColor: '001F52',
actions,
});

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,10 +1,8 @@
import defineAction from '../../../../helpers/define-action.js';
import formatDateTime from './transformers/format-date-time.js';
import getCurrentTimestamp from './transformers/get-current-timestamp.js';
const transformers = {
formatDateTime,
getCurrentTimestamp,
};
export default defineAction({
@@ -18,16 +16,7 @@ export default defineAction({
type: 'dropdown',
required: true,
variables: true,
options: [
{
label: 'Get current timestamp',
value: 'getCurrentTimestamp',
},
{
label: 'Format Date / Time',
value: 'formatDateTime',
},
],
options: [{ label: 'Format Date / Time', value: 'formatDateTime' }],
additionalFields: {
type: 'query',
name: 'getDynamicFields',

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

@@ -1,5 +0,0 @@
const getCurrentTimestamp = () => {
return Date.now();
};
export default getCurrentTimestamp;

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,16 +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';
import parseStringifiedJson from './transformers/parse-stringified-json.js';
import createUuid from './transformers/create-uuid.js';
const transformers = {
base64ToString,
capitalize,
encodeUriComponent,
extractEmailAddress,
extractNumber,
htmlToMarkdown,
@@ -29,11 +24,8 @@ const transformers = {
pluralize,
replace,
stringToBase64,
encodeUri,
trimWhitespace,
useDefaultValue,
parseStringifiedJson,
createUuid,
};
export default defineAction({
@@ -53,16 +45,9 @@ export default defineAction({
{ label: 'Capitalize', value: 'capitalize' },
{ label: 'Convert HTML to Markdown', value: 'htmlToMarkdown' },
{ label: 'Convert Markdown to HTML', value: 'markdownToHtml' },
{ label: 'Create UUID', value: 'createUuid' },
{ label: 'Encode URI', value: 'encodeUri' },
{
label: 'Encode URI Component',
value: 'encodeUriComponent',
},
{ label: 'Extract Email Address', value: 'extractEmailAddress' },
{ label: 'Extract Number', value: 'extractNumber' },
{ label: 'Lowercase', value: 'lowercase' },
{ label: 'Parse stringified JSON', value: 'parseStringifiedJson' },
{ label: 'Pluralize', value: 'pluralize' },
{ label: 'Replace', value: 'replace' },
{ label: 'String to Base64', value: 'stringToBase64' },

View File

@@ -1,7 +0,0 @@
import { v4 as uuidv4 } from 'uuid';
const createUuidV4 = () => {
return uuidv4();
};
export default createUuidV4;

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,7 +0,0 @@
const parseStringifiedJson = ($) => {
const input = $.step.parameters.input;
return JSON.parse(input);
};
export default parseStringifiedJson;

View File

@@ -1,26 +1,8 @@
const replace = ($) => {
const input = $.step.parameters.input;
const find = $.step.parameters.find;
const replace = $.step.parameters.replace;
const useRegex = $.step.parameters.useRegex;
if (useRegex) {
const ignoreCase = $.step.parameters.ignoreCase;
const flags = [ignoreCase && 'i', 'g'].filter(Boolean).join('');
const timeoutId = setTimeout(() => {
$.execution.exit();
}, 100);
const regex = new RegExp(find, flags);
const replacedValue = input.replaceAll(regex, replace);
clearTimeout(timeoutId);
return replacedValue;
}
return input.replaceAll(find, replace);
};

View File

@@ -1,4 +1,3 @@
import listTransformOptions from './list-transform-options/index.js';
import listReplaceRegexOptions from './list-replace-regex-options/index.js';
export default [listTransformOptions, listReplaceRegexOptions];
export default [listTransformOptions];

View File

@@ -1,23 +0,0 @@
export default {
name: 'List replace regex options',
key: 'listReplaceRegexOptions',
async run($) {
if (!$.step.parameters.useRegex) return [];
return [
{
label: 'Ignore case',
key: 'ignoreCase',
type: 'dropdown',
required: true,
description: 'Ignore case sensitivity.',
variables: true,
options: [
{ label: 'Yes', value: true },
{ label: 'No', value: false },
],
},
];
},
};

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,10 +8,8 @@ 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 parseStringifiedJson from './text/parse-stringified-json.js';
import performMathOperation from './numbers/perform-math-operation.js';
import randomNumber from './numbers/random-number.js';
import formatNumber from './numbers/format-number.js';
@@ -22,7 +19,6 @@ import formatDateTime from './date-time/format-date-time.js';
const options = {
base64ToString,
capitalize,
encodeUriComponent,
extractEmailAddress,
extractNumber,
htmlToMarkdown,
@@ -31,7 +27,6 @@ const options = {
pluralize,
replace,
stringToBase64,
encodeUri,
trimWhitespace,
useDefaultValue,
performMathOperation,
@@ -39,7 +34,6 @@ const options = {
formatNumber,
formatPhoneNumber,
formatDateTime,
parseStringifiedJson,
};
export default {

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,12 +0,0 @@
const useDefaultValue = [
{
label: 'Input',
key: 'input',
type: 'string',
required: true,
description: 'Stringified JSON you want to parse.',
variables: true,
},
];
export default useDefaultValue;

View File

@@ -23,33 +23,6 @@ const replace = [
description: 'Text that will replace the found text.',
variables: true,
},
{
label: 'Use Regular Expression',
key: 'useRegex',
type: 'dropdown',
required: true,
description: 'Use regex to search values.',
variables: true,
value: false,
options: [
{ label: 'Yes', value: true },
{ label: 'No', value: false },
],
additionalFields: {
type: 'query',
name: 'getDynamicFields',
arguments: [
{
name: 'key',
value: 'listReplaceRegexOptions',
},
{
name: 'parameters.useRegex',
value: '{parameters.useRegex}',
},
],
},
},
];
export default replace;

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,98 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Create column',
key: 'createColumn',
description: 'Creates a new column 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: 'Column Title',
key: 'columnTitle',
type: 'string',
required: true,
description: '',
variables: true,
},
{
label: 'Column Type',
key: 'columnType',
type: 'dropdown',
required: true,
description: '',
variables: true,
options: [
{ label: 'Button', value: 'button' },
{ label: 'Checkbox', value: 'checkbox' },
{ label: 'Color Picker', value: 'color_picker' },
{ label: 'Connect Boards', value: 'board_relation' },
{ label: 'Country', value: 'country' },
{ label: 'Creation Log', value: 'creation_log' },
{ label: 'Date', value: 'date' },
{ label: 'Dependency', value: 'dependency' },
{ label: 'Dropdown', value: 'dropdown' },
{ label: 'Email', value: 'email' },
{ label: 'Files', value: 'file' },
{ label: 'Formula', value: 'formula' },
{ label: 'Hour', value: 'hour' },
{ label: 'Item ID', value: 'item_id' },
{ label: 'Last Updated', value: 'last_updated' },
{ label: 'Link', value: 'link' },
{ label: 'Location', value: 'location' },
{ label: 'Long Text', value: 'long_text' },
{ label: 'Mirror', value: 'mirror' },
{ label: 'monday Doc', value: 'doc' },
{ label: 'Name', value: 'name' },
{ label: 'Numbers', value: 'numbers' },
{ label: 'People', value: 'people' },
{ label: 'Phone', value: 'phone' },
{ label: 'Rating', value: 'rating' },
{ label: 'Status', value: 'status' },
{ label: 'Tags', value: 'tags' },
{ label: 'Text', value: 'text' },
{ label: 'Timeline', value: 'timeline' },
{ label: 'Time Tracking', value: 'time_tracking' },
{ label: 'Vote', value: 'vote' },
{ label: 'Week', value: 'week' },
{ label: 'World Clock', value: 'world_clock' },
],
},
],
async run($) {
const { boardId, columnTitle, columnType } = $.step.parameters;
const body = {
query: `
mutation{
create_column (board_id: ${boardId}, title: "${columnTitle}", column_type: ${columnType}) {
id
title
}
}`,
};
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,5 @@
import createBoard from './create-board/index.js';
import createColumn from './create-column/index.js';
import createItem from './create-item/index.js';
export default [createBoard, createColumn, 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

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