Merge pull request #1300 from automatisch/miro-integration
feat(miro): add create board action
This commit is contained in:
94
packages/backend/src/apps/miro/actions/create-board/index.ts
Normal file
94
packages/backend/src/apps/miro/actions/create-board/index.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
|
||||
export default defineAction({
|
||||
name: 'Create board',
|
||||
key: 'createBoard',
|
||||
description: 'Creates a new board.',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Title',
|
||||
key: 'title',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'Title for the board.',
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'Description',
|
||||
key: 'description',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
description: 'Description of the board.',
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'Team Access',
|
||||
key: 'teamAccess',
|
||||
type: 'dropdown' as const,
|
||||
required: false,
|
||||
description:
|
||||
'Team access to the board. Can be private, view, comment or edit. Default: private.',
|
||||
variables: true,
|
||||
options: [
|
||||
{
|
||||
label: 'Private - nobody in the team can find and access the board',
|
||||
value: 'private',
|
||||
},
|
||||
{
|
||||
label: 'View - any team member can find and view the board',
|
||||
value: 'view',
|
||||
},
|
||||
{
|
||||
label: 'Comment - any team member can find and comment the board',
|
||||
value: 'comment',
|
||||
},
|
||||
{
|
||||
label: 'Edit - any team member can find and edit the board',
|
||||
value: 'edit',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Access Via Link',
|
||||
key: 'accessViaLink',
|
||||
type: 'dropdown' as const,
|
||||
required: false,
|
||||
description:
|
||||
'Access to the board by link. Can be private, view, comment. Default: private.',
|
||||
variables: true,
|
||||
options: [
|
||||
{
|
||||
label: 'Private - only you have access to the board',
|
||||
value: 'private',
|
||||
},
|
||||
{
|
||||
label: 'View - can view, no sign-in required',
|
||||
value: 'view',
|
||||
},
|
||||
{
|
||||
label: 'Comment - can comment, no sign-in required',
|
||||
value: 'comment',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const body = {
|
||||
name: $.step.parameters.title,
|
||||
description: $.step.parameters.description,
|
||||
policy: {
|
||||
sharingPolicy: {
|
||||
access: $.step.parameters.accessViaLink || 'private',
|
||||
teamAccess: $.step.parameters.teamAccess || 'private',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const { data } = await $.http.post('/v2/boards', body);
|
||||
|
||||
$.setActionItem({
|
||||
raw: data,
|
||||
});
|
||||
},
|
||||
});
|
3
packages/backend/src/apps/miro/actions/index.ts
Normal file
3
packages/backend/src/apps/miro/actions/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import createBoard from './create-board';
|
||||
|
||||
export default [createBoard];
|
1
packages/backend/src/apps/miro/assets/favicon.svg
Normal file
1
packages/backend/src/apps/miro/assets/favicon.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg" width="2500" height="2500"><path d="M0 0h48v48H0z" fill="#ffd02f"/><path d="M32.708 6.4h-5.124l4.549 7.05-9.617-7.05h-5.124l4.549 9.238L12.324 6.4H7.2l4.474 11.926L7.2 41.6h5.124l9.617-24.955L17.392 41.6h5.124l9.617-27.142-4.549 27.142h5.124L42.4 11.785z" fill="#050038"/></svg>
|
After Width: | Height: | Size: 338 B |
20
packages/backend/src/apps/miro/auth/generate-auth-url.ts
Normal file
20
packages/backend/src/apps/miro/auth/generate-auth-url.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { IField, IGlobalVariable } from '@automatisch/types';
|
||||
import { URLSearchParams } from 'url';
|
||||
|
||||
export default async function generateAuthUrl($: IGlobalVariable) {
|
||||
const oauthRedirectUrlField = $.app.auth.fields.find(
|
||||
(field: IField) => field.key == 'oAuthRedirectUrl'
|
||||
);
|
||||
const redirectUri = oauthRedirectUrlField.value as string;
|
||||
const searchParams = new URLSearchParams({
|
||||
response_type: 'code',
|
||||
client_id: $.auth.data.clientId as string,
|
||||
redirect_uri: redirectUri,
|
||||
});
|
||||
|
||||
const url = `https://miro.com/oauth/authorize?${searchParams.toString()}`;
|
||||
|
||||
await $.auth.set({
|
||||
url,
|
||||
});
|
||||
}
|
48
packages/backend/src/apps/miro/auth/index.ts
Normal file
48
packages/backend/src/apps/miro/auth/index.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import generateAuthUrl from './generate-auth-url';
|
||||
import verifyCredentials from './verify-credentials';
|
||||
import refreshToken from './refresh-token';
|
||||
import isStillVerified from './is-still-verified';
|
||||
|
||||
export default {
|
||||
fields: [
|
||||
{
|
||||
key: 'oAuthRedirectUrl',
|
||||
label: 'OAuth Redirect URL',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: true,
|
||||
value: '{WEB_APP_URL}/app/miro/connections/add',
|
||||
placeholder: null,
|
||||
description:
|
||||
'When asked to input a redirect URL in Miro, enter the URL above.',
|
||||
clickToCopy: true,
|
||||
},
|
||||
{
|
||||
key: 'clientId',
|
||||
label: 'Client ID',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
description: null,
|
||||
clickToCopy: false,
|
||||
},
|
||||
{
|
||||
key: 'clientSecret',
|
||||
label: 'Client Secret',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
description: null,
|
||||
clickToCopy: false,
|
||||
},
|
||||
],
|
||||
|
||||
generateAuthUrl,
|
||||
verifyCredentials,
|
||||
isStillVerified,
|
||||
refreshToken,
|
||||
};
|
9
packages/backend/src/apps/miro/auth/is-still-verified.ts
Normal file
9
packages/backend/src/apps/miro/auth/is-still-verified.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
import getCurrentUser from '../common/get-current-user';
|
||||
|
||||
const isStillVerified = async ($: IGlobalVariable) => {
|
||||
const currentUser = await getCurrentUser($);
|
||||
return !!currentUser;
|
||||
};
|
||||
|
||||
export default isStillVerified;
|
23
packages/backend/src/apps/miro/auth/refresh-token.ts
Normal file
23
packages/backend/src/apps/miro/auth/refresh-token.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { URLSearchParams } from 'node:url';
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const refreshToken = async ($: IGlobalVariable) => {
|
||||
const params = new URLSearchParams({
|
||||
grant_type: 'refresh_token',
|
||||
client_id: $.auth.data.clientId as string,
|
||||
client_secret: $.auth.data.clientSecret as string,
|
||||
refresh_token: $.auth.data.refreshToken as string,
|
||||
});
|
||||
|
||||
const { data } = await $.http.post('/v1/oauth/token', params.toString());
|
||||
|
||||
await $.auth.set({
|
||||
accessToken: data.access_token,
|
||||
expiresIn: data.expires_in,
|
||||
refreshToken: data.refresh_token,
|
||||
scope: data.scope,
|
||||
tokenType: data.token_type,
|
||||
});
|
||||
};
|
||||
|
||||
export default refreshToken;
|
40
packages/backend/src/apps/miro/auth/verify-credentials.ts
Normal file
40
packages/backend/src/apps/miro/auth/verify-credentials.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { IField, IGlobalVariable } from '@automatisch/types';
|
||||
import getCurrentUser from '../common/get-current-user';
|
||||
|
||||
const verifyCredentials = async ($: IGlobalVariable) => {
|
||||
const oauthRedirectUrlField = $.app.auth.fields.find(
|
||||
(field: IField) => field.key == 'oAuthRedirectUrl'
|
||||
);
|
||||
const redirectUri = oauthRedirectUrlField.value as string;
|
||||
const params = {
|
||||
grant_type: 'authorization_code',
|
||||
client_id: $.auth.data.clientId,
|
||||
client_secret: $.auth.data.clientSecret,
|
||||
code: $.auth.data.code,
|
||||
redirect_uri: redirectUri,
|
||||
};
|
||||
|
||||
const { data } = await $.http.post(`/v1/oauth/token`, null, {
|
||||
params,
|
||||
});
|
||||
|
||||
await $.auth.set({
|
||||
accessToken: data.access_token,
|
||||
tokenType: data.token_type,
|
||||
});
|
||||
|
||||
const currentUser = await getCurrentUser($);
|
||||
|
||||
await $.auth.set({
|
||||
userId: data.user_id,
|
||||
refreshToken: data.refresh_token,
|
||||
expiresIn: data.expires_in,
|
||||
teamId: data.team_id,
|
||||
scope: data.scope,
|
||||
clientId: $.auth.data.clientId,
|
||||
clientSecret: $.auth.data.clientSecret,
|
||||
screenName: currentUser.name,
|
||||
});
|
||||
};
|
||||
|
||||
export default verifyCredentials;
|
11
packages/backend/src/apps/miro/common/add-auth-header.ts
Normal file
11
packages/backend/src/apps/miro/common/add-auth-header.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { TBeforeRequest } from '@automatisch/types';
|
||||
|
||||
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
|
||||
if ($.auth.data?.accessToken) {
|
||||
requestConfig.headers.Authorization = `${$.auth.data.tokenType} ${$.auth.data.accessToken}`;
|
||||
}
|
||||
|
||||
return requestConfig;
|
||||
};
|
||||
|
||||
export default addAuthHeader;
|
10
packages/backend/src/apps/miro/common/get-current-user.ts
Normal file
10
packages/backend/src/apps/miro/common/get-current-user.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const getCurrentUser = async ($: IGlobalVariable) => {
|
||||
const { data } = await $.http.get(
|
||||
`https://api.miro.com/v1/oauth-token?access_token=${$.auth.data.accessToken}`
|
||||
);
|
||||
return data.user;
|
||||
};
|
||||
|
||||
export default getCurrentUser;
|
0
packages/backend/src/apps/miro/index.d.ts
vendored
Normal file
0
packages/backend/src/apps/miro/index.d.ts
vendored
Normal file
18
packages/backend/src/apps/miro/index.ts
Normal file
18
packages/backend/src/apps/miro/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import defineApp from '../../helpers/define-app';
|
||||
import addAuthHeader from './common/add-auth-header';
|
||||
import auth from './auth';
|
||||
import actions from './actions';
|
||||
|
||||
export default defineApp({
|
||||
name: 'Miro',
|
||||
key: 'miro',
|
||||
baseUrl: 'https://miro.com',
|
||||
apiBaseUrl: 'https://api.miro.com',
|
||||
iconUrl: '{BASE_URL}/apps/miro/assets/favicon.svg',
|
||||
authDocUrl: 'https://automatisch.io/docs/apps/miro/connection',
|
||||
primaryColor: 'F2CA02',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
actions,
|
||||
});
|
@@ -178,6 +178,15 @@ export default defineConfig({
|
||||
{ text: 'Connection', link: '/apps/mattermost/connection' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Miro',
|
||||
collapsible: true,
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: 'Actions', link: '/apps/miro/actions' },
|
||||
{ text: 'Connection', link: '/apps/miro/connection' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Notion',
|
||||
collapsible: true,
|
||||
|
12
packages/docs/pages/apps/miro/actions.md
Normal file
12
packages/docs/pages/apps/miro/actions.md
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
favicon: /favicons/miro.svg
|
||||
items:
|
||||
- name: Create board
|
||||
desc: Creates a new board.
|
||||
---
|
||||
|
||||
<script setup>
|
||||
import CustomListing from '../../components/CustomListing.vue'
|
||||
</script>
|
||||
|
||||
<CustomListing />
|
19
packages/docs/pages/apps/miro/connection.md
Normal file
19
packages/docs/pages/apps/miro/connection.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Miro
|
||||
|
||||
:::info
|
||||
This page explains the steps you need to follow to set up the Miro
|
||||
connection in Automatisch. If any of the steps are outdated, please let us know!
|
||||
:::
|
||||
|
||||
1. Go to [link](https://miro.com/signup/) to create a user account in Miro.
|
||||
2. After signin in, go to [link](https://miro.com/app/dashboard/?createDevTeam=1) to create a developer team.
|
||||
3. In the **Create new team** modal, select the checkbox and then click **Create team** button.
|
||||
4. After that, click **Create new app** in Your app section.
|
||||
5. Fill the field of **App Name**.
|
||||
6. Select the **Expire user authorization token** checkbox and click the **Create app**.
|
||||
7. Copy **OAuth Redirect URL** from Automatisch to the **Redirect URI for OAuth2.0** field.
|
||||
8. Give permissions for **boards**, **identity**, and **team** scopes in Permissions field.
|
||||
9. Copy the **Client ID** value to the `Client ID` field on Automatisch.
|
||||
10. Copy the **Client secret** value to the `Client Secret` field on Automatisch.
|
||||
11. Click **Submit** button on Automatisch.
|
||||
12. Congrats! Start using your new Miro connection within the flows.
|
@@ -18,6 +18,7 @@ The following integrations are currently supported by Automatisch.
|
||||
- [HTTP Request](/apps/http-request/actions)
|
||||
- [HubSpot](/apps/hubspot/actions)
|
||||
- [Mattermost](/apps/mattermost/actions)
|
||||
- [Miro](/apps/miro/actions)
|
||||
- [Notion](/apps/notion/triggers)
|
||||
- [Ntfy](/apps/ntfy/actions)
|
||||
- [Odoo](/apps/odoo/actions)
|
||||
|
1
packages/docs/pages/public/favicons/miro.svg
Normal file
1
packages/docs/pages/public/favicons/miro.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg" width="2500" height="2500"><path d="M0 0h48v48H0z" fill="#ffd02f"/><path d="M32.708 6.4h-5.124l4.549 7.05-9.617-7.05h-5.124l4.549 9.238L12.324 6.4H7.2l4.474 11.926L7.2 41.6h5.124l9.617-24.955L17.392 41.6h5.124l9.617-27.142-4.549 27.142h5.124L42.4 11.785z" fill="#050038"/></svg>
|
After Width: | Height: | Size: 338 B |
Reference in New Issue
Block a user