Compare commits

..

3 Commits

Author SHA1 Message Date
Rıdvan Akca
e4078d661e feat(dropbox): add new files in folder trigger 2024-02-27 15:12:21 +03:00
Rıdvan Akca
64755695a1 feat(dropbox): add create text file action 2024-02-26 18:47:35 +03:00
Rıdvan Akca
51e96b832d feat(dropbox): add new folders trigger 2024-02-26 17:43:01 +03:00
50 changed files with 313 additions and 528 deletions

View File

@@ -6,7 +6,7 @@ ENV PORT 3000
RUN \
apk --no-cache add --virtual build-dependencies python3 build-base git
RUN git clone -b helix-new-endpoint https://github.com/automatisch/automatisch.git
RUN git clone https://github.com/automatisch/automatisch.git
WORKDIR /automatisch

View File

@@ -1,27 +0,0 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Get value',
key: 'getValue',
description: 'Get value from the persistent datastore.',
arguments: [
{
label: 'Key',
key: 'key',
type: 'string',
required: true,
description: 'The key of your value to get.',
variables: true,
},
],
async run($) {
const keyValuePair = await $.datastore.get({
key: $.step.parameters.key,
});
$.setActionItem({
raw: keyValuePair,
});
},
});

View File

@@ -1,4 +0,0 @@
import getValue from './get-value/index.js';
import setValue from './set-value/index.js';
export default [getValue, setValue];

View File

@@ -1,36 +0,0 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Set value',
key: 'setValue',
description: 'Set value to the persistent datastore.',
arguments: [
{
label: 'Key',
key: 'key',
type: 'string',
required: true,
description: 'The key of your value to set.',
variables: true,
},
{
label: 'Value',
key: 'value',
type: 'string',
required: true,
description: 'The value to set.',
variables: true,
},
],
async run($) {
const keyValuePair = await $.datastore.set({
key: $.step.parameters.key,
value: $.step.parameters.value,
});
$.setActionItem({
raw: keyValuePair,
});
},
});

View File

@@ -1,13 +0,0 @@
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" fill="#000000" width="800px" height="800px" viewBox="0 0 32 32" id="icon">
<defs>
<style>.cls-1{fill:none;}</style>
</defs>
<title>datastore</title>
<circle cx="23" cy="23" r="1"/>
<rect x="8" y="22" width="12" height="2"/>
<circle cx="23" cy="9" r="1"/>
<rect x="8" y="8" width="12" height="2"/>
<path d="M26,14a2,2,0,0,0,2-2V6a2,2,0,0,0-2-2H6A2,2,0,0,0,4,6v6a2,2,0,0,0,2,2H8v4H6a2,2,0,0,0-2,2v6a2,2,0,0,0,2,2H26a2,2,0,0,0,2-2V20a2,2,0,0,0-2-2H24V14ZM6,6H26v6H6ZM26,26H6V20H26Zm-4-8H10V14H22Z"/>
<rect id="_Transparent_Rectangle_" data-name="&lt;Transparent Rectangle&gt;" class="cls-1" width="32" height="32"/>
</svg>

Before

Width:  |  Height:  |  Size: 704 B

View File

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

View File

@@ -0,0 +1,82 @@
import path from 'node:path';
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Create a text file',
key: 'createTextFile',
description: 'Create a new text file from plain text content you specify.',
arguments: [
{
label: 'Folder',
key: 'parentFolder',
type: 'string',
required: true,
description:
'Enter the folder path that file will be saved, like /TextFiles/ or /Documents/Taxes/',
variables: true,
},
{
label: 'Folder Name',
key: 'folderName',
type: 'string',
required: true,
description:
"Enter the name for the new file. The file extension will be '.txt'.",
variables: true,
},
{
label: 'File Content',
key: 'fileContent',
type: 'string',
required: true,
description: 'Plain text content to insert into the new text file.',
variables: true,
},
{
label: 'Overwrite',
key: 'overwrite',
type: 'dropdown',
required: true,
description:
'Overwrite this file (if one of the same name exists) or not.',
variables: true,
options: [
{ label: 'False', value: false },
{ label: 'True', value: true },
],
},
],
async run($) {
const fileContent = $.step.parameters.fileContent;
const overwrite = $.step.parameters.overwrite;
const parentFolder = $.step.parameters.parentFolder;
const folderName = $.step.parameters.folderName;
const folderPath = path.join(parentFolder, folderName);
const headers = {
Authorization: `Bearer ${$.auth.data.accessToken}`,
'Content-Type': 'application/octet-stream',
'Dropbox-API-Arg': JSON.stringify({
autorename: false,
mode: overwrite ? 'overwrite' : 'add',
mute: false,
path: `${folderPath}.txt`,
strict_conflict: false,
}),
};
const response = await $.http.post(
'https://content.dropboxapi.com/2/files/upload',
fileContent,
{
headers,
additionalProperties: {
skipAddingAuthHeader: true,
},
}
);
$.setActionItem({ raw: response.data });
},
});

View File

@@ -1,4 +1,5 @@
import createFolder from './create-folder/index.js';
import createTextFile from './create-text-file/index.js';
import renameFile from './rename-file/index.js';
export default [createFolder, renameFile];
export default [createFolder, createTextFile, renameFile];

View File

@@ -1,10 +1,10 @@
const addAuthHeader = ($, requestConfig) => {
requestConfig.headers['Content-Type'] = 'application/json';
if (
!requestConfig.additionalProperties?.skipAddingAuthHeader &&
$.auth.data?.accessToken
) {
requestConfig.headers['Content-Type'] = 'application/json';
requestConfig.headers.Authorization = `Bearer ${$.auth.data.accessToken}`;
}

View File

@@ -2,6 +2,7 @@ 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 triggers from './triggers/index.js';
export default defineApp({
name: 'Dropbox',
@@ -15,4 +16,5 @@ export default defineApp({
beforeRequest: [addAuthHeader],
auth,
actions,
triggers,
});

View File

@@ -0,0 +1,4 @@
import newFilesInFolder from './new-files-in-folder/index.js';
import newFolders from './new-folders/index.js';
export default [newFilesInFolder, newFolders];

View File

@@ -0,0 +1,74 @@
import defineTrigger from '../../../../helpers/define-trigger.js';
export default defineTrigger({
name: 'New files in folder',
key: 'newFilesInFolder',
pollInterval: 15,
description:
'Triggers when a new file is added to a folder. Ensure that the number of files/folders within the monitored directory remains below 4000.',
arguments: [
{
label: 'Folder',
key: 'folderPath',
type: 'string',
required: true,
description:
'Enter the folder path that you want to follow, like /TextFiles or /Documents/Taxes.',
variables: true,
},
{
label: 'Include File Contents?',
key: 'includeFileContents',
type: 'dropdown',
required: true,
description:
'Please be advised that files exceeding 100MB in size may result in an error. To prevent errors and exclude file contents, set this option to NO.',
variables: true,
options: [
{ label: 'No', value: false },
{ label: 'Yes', value: true },
],
},
],
async run($) {
const folderPath = $.step.parameters.folderPath;
let endpoint = '/2/files/list_folder';
let next = false;
const params = {
path: folderPath,
recursive: false,
include_deleted: false,
include_has_explicit_shared_members: false,
include_mounted_folders: false,
limit: 2000,
include_non_downloadable_files: true,
};
do {
const { data } = await $.http.post(endpoint, params);
if (data.has_more) {
endpoint += '/continue';
params.cursor = data.cursor;
next = data.has_more;
} else {
next = false;
}
if (data.entries?.length) {
for (const entry of data.entries.reverse()) {
if (entry['.tag'] === 'file') {
$.pushTriggerItem({
raw: entry,
meta: {
internalId: entry.id,
},
});
}
}
}
} while (next);
},
});

View File

@@ -0,0 +1,61 @@
import defineTrigger from '../../../../helpers/define-trigger.js';
export default defineTrigger({
name: 'New folders',
key: 'newFolders',
pollInterval: 15,
description:
'Triggers when any new folder is added. Ensure that the number of files/folders within the monitored directory remains below 4000.',
arguments: [
{
label: 'Folder',
key: 'folderPath',
type: 'string',
required: true,
description:
'Enter the folder path that you want to follow, like /TextFiles or /Documents/Taxes.',
variables: true,
},
],
async run($) {
const folderPath = $.step.parameters.folderPath;
let endpoint = '/2/files/list_folder';
let next = false;
const params = {
path: folderPath,
recursive: false,
include_deleted: false,
include_has_explicit_shared_members: false,
include_mounted_folders: true,
limit: 2000,
include_non_downloadable_files: true,
};
do {
const { data } = await $.http.post(endpoint, params);
if (data.has_more) {
endpoint += '/continue';
params.cursor = data.cursor;
next = data.has_more;
} else {
next = false;
}
if (data.entries?.length) {
for (const entry of data.entries.reverse()) {
if (entry['.tag'] === 'folder') {
$.pushTriggerItem({
raw: entry,
meta: {
internalId: entry.id,
},
});
}
}
}
} while (next);
},
});

View File

@@ -1,3 +1,4 @@
import FormData from 'form-data';
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
@@ -16,21 +17,34 @@ export default defineAction({
],
async run($) {
const response = await $.http.post('/api/v1/sessions/chat', {
session_id: '',
messages: [
{
role: 'user',
content: {
content_type: 'text',
parts: [$.step.parameters.input],
},
},
],
const formData = new FormData();
formData.append('input', $.step.parameters.input);
formData.append('mode', 'inference');
formData.append('type', 'text');
const sessionResponse = await $.http.post('/api/v1/sessions', formData, {
headers: {
...formData.getHeaders(),
},
});
$.setActionItem({
raw: response.data,
});
const sessionId = sessionResponse.data.id;
let chatGenerated = false;
while (!chatGenerated) {
const response = await $.http.get(`/api/v1/sessions/${sessionId}`);
const message =
response.data.interactions[response.data.interactions.length - 1];
if (message.creator === 'system' && message.state === 'complete') {
$.setActionItem({
raw: message,
});
chatGenerated = true;
}
}
},
});

View File

@@ -1,6 +1,5 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../../app.js';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../../test/factories/user.js';
@@ -32,21 +31,5 @@ describe('GET /api/v1/admin/app-auth-clients/:appAuthClientId', () => {
const expectedPayload = getAdminAppAuthClientMock(currentAppAuthClient);
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for not existing app auth client UUID', async () => {
const notExistingAppAuthClientUUID = Crypto.randomUUID();
await request(app)
.get(`/api/v1/admin/app-auth-clients/${notExistingAppAuthClientUUID}`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
await request(app)
.get('/api/v1/admin/app-auth-clients/invalidAppAuthClientUUID')
.set('Authorization', token)
.expect(400);
});
});
});

View File

@@ -1,6 +1,5 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../../app.js';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
import { createRole } from '../../../../../../test/factories/role.js';
@@ -21,7 +20,7 @@ describe('GET /api/v1/admin/roles/:roleId', () => {
token = createAuthTokenByUserId(currentUser.id);
});
it('should return role', async () => {
it('should return roles', async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
const response = await request(app)
@@ -36,24 +35,4 @@ describe('GET /api/v1/admin/roles/:roleId', () => {
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for not existing role UUID', async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
const notExistingRoleUUID = Crypto.randomUUID();
await request(app)
.get(`/api/v1/admin/roles/${notExistingRoleUUID}`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
await request(app)
.get('/api/v1/admin/roles/invalidRoleUUID')
.set('Authorization', token)
.expect(400);
});
});

View File

@@ -1,6 +1,5 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../../app.js';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
import { createRole } from '../../../../../../test/factories/role.js';
@@ -32,26 +31,4 @@ describe('GET /api/v1/admin/saml-auth-provider/:samlAuthProviderId', () => {
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for not existing saml auth provider UUID', async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
const notExistingSamlAuthProviderUUID = Crypto.randomUUID();
await request(app)
.get(
`/api/v1/admin/saml-auth-providers/${notExistingSamlAuthProviderUUID}`
)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
await request(app)
.get('/api/v1/admin/saml-auth-providers/invalidSamlAuthProviderUUID')
.set('Authorization', token)
.expect(400);
});
});

View File

@@ -1,6 +1,5 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../../app.js';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../../test/factories/user';
@@ -32,24 +31,4 @@ describe('GET /api/v1/admin/users/:userId', () => {
const expectedPayload = getUserMock(anotherUser, anotherUserRole);
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for not existing user UUID', async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
const notExistingUserUUID = Crypto.randomUUID();
await request(app)
.get(`/api/v1/admin/users/${notExistingUserUUID}`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
await request(app)
.get('/api/v1/admin/users/invalidUserUUID')
.set('Authorization', token)
.expect(400);
});
});

View File

@@ -1,6 +1,5 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../test/factories/user.js';
@@ -29,20 +28,4 @@ describe('GET /api/v1/app-auth-clients/:id', () => {
const expectedPayload = getAppAuthClientMock(currentAppAuthClient);
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for not existing app auth client ID', async () => {
const notExistingAppAuthClientUUID = Crypto.randomUUID();
await request(app)
.get(`/api/v1/app-auth-clients/${notExistingAppAuthClientUUID}`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
await request(app)
.get('/api/v1/app-auth-clients/invalidAppAuthClientUUID')
.set('Authorization', token)
.expect(400);
});
});

View File

@@ -1,8 +0,0 @@
import App from '../../../../models/app.js';
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const app = await App.findOneByKey(request.params.appKey);
renderObject(response, app, { serializer: 'App' });
};

View File

@@ -1,35 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import App from '../../../../models/app';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
import getAppMock from '../../../../../test/mocks/rest/api/v1/apps/get-app.js';
describe('GET /api/v1/apps/:appKey', () => {
let currentUser, token;
beforeEach(async () => {
currentUser = await createUser();
token = createAuthTokenByUserId(currentUser.id);
});
it('should return the app info', async () => {
const exampleApp = await App.findOneByKey('github');
const response = await request(app)
.get(`/api/v1/apps/${exampleApp.key}`)
.set('Authorization', token)
.expect(200);
const expectedPayload = getAppMock(exampleApp);
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for invalid app key', async () => {
await request(app)
.get('/api/v1/apps/invalid-app-key')
.set('Authorization', token)
.expect(404);
});
});

View File

@@ -1,6 +1,5 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
@@ -69,34 +68,4 @@ describe('GET /api/v1/flows/:flowId', () => {
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for not existing flow UUID', async () => {
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
const notExistingFlowUUID = Crypto.randomUUID();
await request(app)
.get(`/api/v1/flows/${notExistingFlowUUID}`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await request(app)
.get('/api/v1/flows/invalidFlowUUID')
.set('Authorization', token)
.expect(400);
});
});

View File

@@ -1,16 +0,0 @@
export async function up(knex) {
return knex.schema.createTable('datastore', (table) => {
table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()'));
table.string('key').notNullable();
table.string('value');
table.string('scope').notNullable();
table.uuid('scope_id').notNullable();
table.index(['key', 'scope', 'scope_id']);
table.timestamps(true, true);
});
}
export async function down(knex) {
return knex.schema.dropTable('datastore');
}

View File

@@ -1,31 +1,14 @@
import logger from './logger.js';
import objection from 'objection';
const { NotFoundError, DataError } = objection;
// Do not remove `next` argument as the function signature will not fit for an error handler middleware
// eslint-disable-next-line no-unused-vars
const errorHandler = (error, request, response, next) => {
if (error.message === 'Not Found' || error instanceof NotFoundError) {
response.status(404).end();
const errorHandler = (err, req, res, next) => {
if (err.message === 'Not Found') {
res.status(404).end();
} else {
logger.error(err.message + '\n' + err.stack);
res.status(err.statusCode || 500).send(err.message);
}
if (notFoundAppError(error)) {
response.status(404).end();
}
if (error instanceof DataError) {
response.status(400).end();
}
logger.error(error.message + '\n' + error.stack);
response.status(error.statusCode || 500).end();
};
const notFoundAppError = (error) => {
return (
error.message.includes('An application with the') ||
error.message.includes("key couldn't be found.")
);
};
export default errorHandler;

View File

@@ -1,7 +1,6 @@
import createHttpClient from './http-client/index.js';
import EarlyExitError from '../errors/early-exit.js';
import AlreadyProcessedError from '../errors/already-processed.js';
import Datastore from '../models/datastore.js';
const globalVariable = async (options) => {
const {
@@ -89,43 +88,6 @@ const globalVariable = async (options) => {
setActionItem: (actionItem) => {
$.actionOutput.data = actionItem;
},
datastore: {
get: async ({ key }) => {
const datastore = await Datastore.query().findOne({
key,
scope: 'flow',
scope_id: $.flow.id,
});
return {
key: datastore.key,
value: datastore.value,
[datastore.key]: datastore.value,
};
},
set: async ({ key, value }) => {
let datastore = await Datastore.query()
.where({ key, scope: 'flow', scope_id: $.flow.id })
.first();
if (datastore) {
await datastore.$query().patchAndFetch({ value: value });
} else {
datastore = await Datastore.query().insert({
key,
value,
scope: 'flow',
scopeId: $.flow.id,
});
}
return {
key: datastore.key,
value: datastore.value,
[datastore.key]: datastore.value,
};
},
},
};
if (request) {

View File

@@ -11,7 +11,7 @@ const isArray = (object) =>
const totalCount = (object) =>
isPaginated(object) ? object.totalCount : isArray(object) ? object.length : 1;
const renderObject = (response, object, options) => {
const renderObject = (response, object) => {
let data = isPaginated(object) ? object.records : object;
const type = isPaginated(object)
@@ -20,9 +20,7 @@ const renderObject = (response, object, options) => {
? object?.[0]?.constructor?.name || 'Object'
: object.constructor.name;
const serializer = options?.serializer
? serializers[options.serializer]
: serializers[type];
const serializer = serializers[type];
if (serializer) {
data = Array.isArray(data)

View File

@@ -1,24 +0,0 @@
import Base from './base.js';
class Datastore extends Base {
static tableName = 'datastore';
static jsonSchema = {
type: 'object',
required: ['key', 'value', 'scope', 'scopeId'],
properties: {
id: { type: 'string', format: 'uuid' },
key: { type: 'string', minLength: 1 },
value: { type: 'string' },
scope: {
type: 'string',
enum: ['flow'],
default: 'flow',
},
scopeId: { type: 'string', format: 'uuid' },
},
};
}
export default Datastore;

View File

@@ -1,10 +0,0 @@
import { Router } from 'express';
import asyncHandler from 'express-async-handler';
import { authenticateUser } from '../../../helpers/authentication.js';
import getAppAction from '../../../controllers/api/v1/apps/get-app.js';
const router = Router();
router.get('/:appKey', authenticateUser, asyncHandler(getAppAction));
export default router;

View File

@@ -8,7 +8,6 @@ import usersRouter from './api/v1/users.js';
import paymentRouter from './api/v1/payment.ee.js';
import appAuthClientsRouter from './api/v1/app-auth-clients.js';
import flowsRouter from './api/v1/flows.js';
import appsRouter from './api/v1/apps.js';
import samlAuthProvidersRouter from './api/v1/admin/saml-auth-providers.ee.js';
import rolesRouter from './api/v1/admin/roles.ee.js';
import permissionsRouter from './api/v1/admin/permissions.ee.js';
@@ -26,7 +25,6 @@ router.use('/api/v1/users', usersRouter);
router.use('/api/v1/payment', paymentRouter);
router.use('/api/v1/app-auth-clients', appAuthClientsRouter);
router.use('/api/v1/flows', flowsRouter);
router.use('/api/v1/apps', appsRouter);
router.use('/api/v1/admin/saml-auth-providers', samlAuthProvidersRouter);
router.use('/api/v1/admin/roles', rolesRouter);
router.use('/api/v1/admin/permissions', permissionsRouter);

View File

@@ -1,12 +0,0 @@
const appSerializer = (app) => {
return {
name: app.name,
key: app.key,
iconUrl: app.iconUrl,
authDocUrl: app.authDocUrl,
supportsConnections: app.supportsConnections,
primaryColor: app.primaryColor,
};
};
export default appSerializer;

View File

@@ -1,20 +0,0 @@
import { describe, it, expect } from 'vitest';
import App from '../models/app';
import appSerializer from './app';
describe('appSerializer', () => {
it('should return permission data', async () => {
const app = await App.findOneByKey('deepl');
const expectedPayload = {
name: app.name,
key: app.key,
iconUrl: app.iconUrl,
authDocUrl: app.authDocUrl,
supportsConnections: app.supportsConnections,
primaryColor: app.primaryColor,
};
expect(appSerializer(app)).toEqual(expectedPayload);
});
});

View File

@@ -5,7 +5,6 @@ import samlAuthProviderSerializer from './saml-auth-provider.ee.js';
import appAuthClientSerializer from './app-auth-client.js';
import flowSerializer from './flow.js';
import stepSerializer from './step.js';
import appSerializer from './app.js';
const serializers = {
User: userSerializer,
@@ -15,7 +14,6 @@ const serializers = {
AppAuthClient: appAuthClientSerializer,
Flow: flowSerializer,
Step: stepSerializer,
App: appSerializer,
};
export default serializers;

View File

@@ -5,8 +5,8 @@ const permissionSerializer = (permission) => {
action: permission.action,
subject: permission.subject,
conditions: permission.conditions,
createdAt: permission.createdAt.getTime(),
updatedAt: permission.updatedAt.getTime(),
createdAt: permission.createdAt,
updatedAt: permission.updatedAt,
};
};

View File

@@ -16,8 +16,8 @@ describe('permissionSerializer', () => {
action: permission.action,
subject: permission.subject,
conditions: permission.conditions,
createdAt: permission.createdAt.getTime(),
updatedAt: permission.updatedAt.getTime(),
createdAt: permission.createdAt,
updatedAt: permission.updatedAt,
};
expect(permissionSerializer(permission)).toEqual(expectedPayload);

View File

@@ -6,8 +6,8 @@ const roleSerializer = (role) => {
name: role.name,
key: role.key,
description: role.description,
createdAt: role.createdAt.getTime(),
updatedAt: role.updatedAt.getTime(),
createdAt: role.createdAt,
updatedAt: role.updatedAt,
isAdmin: role.isAdmin,
};

View File

@@ -29,8 +29,8 @@ describe('roleSerializer', () => {
name: role.name,
key: role.key,
description: role.description,
createdAt: role.createdAt.getTime(),
updatedAt: role.updatedAt.getTime(),
createdAt: role.createdAt,
updatedAt: role.updatedAt,
isAdmin: role.isAdmin,
};

View File

@@ -6,8 +6,8 @@ const userSerializer = (user) => {
let userData = {
id: user.id,
email: user.email,
createdAt: user.createdAt.getTime(),
updatedAt: user.updatedAt.getTime(),
createdAt: user.createdAt,
updatedAt: user.updatedAt,
fullName: user.fullName,
};

View File

@@ -31,11 +31,11 @@ describe('userSerializer', () => {
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(false);
const expectedPayload = {
createdAt: user.createdAt.getTime(),
createdAt: user.createdAt,
email: user.email,
fullName: user.fullName,
id: user.id,
updatedAt: user.updatedAt.getTime(),
updatedAt: user.updatedAt,
};
expect(userSerializer(user)).toEqual(expectedPayload);
@@ -67,7 +67,7 @@ describe('userSerializer', () => {
it('should return user data with trial expiry date', async () => {
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(true);
await user.$query().patchAndFetch({
await user.$query().patch({
trialExpiryDate: DateTime.now().plus({ days: 30 }).toISODate(),
});

View File

@@ -5,16 +5,16 @@ const getRoleMock = async (role, permissions) => {
name: role.name,
isAdmin: role.isAdmin,
description: role.description,
createdAt: role.createdAt.getTime(),
updatedAt: role.updatedAt.getTime(),
createdAt: role.createdAt.toISOString(),
updatedAt: role.updatedAt.toISOString(),
permissions: permissions.map((permission) => ({
id: permission.id,
action: permission.action,
conditions: permission.conditions,
roleId: permission.roleId,
subject: permission.subject,
createdAt: permission.createdAt.getTime(),
updatedAt: permission.updatedAt.getTime(),
createdAt: permission.createdAt.toISOString(),
updatedAt: permission.updatedAt.toISOString(),
})),
};

View File

@@ -6,8 +6,8 @@ const getRolesMock = async (roles) => {
name: role.name,
isAdmin: role.isAdmin,
description: role.description,
createdAt: role.createdAt.getTime(),
updatedAt: role.updatedAt.getTime(),
createdAt: role.createdAt.toISOString(),
updatedAt: role.updatedAt.toISOString(),
};
});

View File

@@ -1,21 +1,21 @@
const getUserMock = (currentUser, role) => {
return {
data: {
createdAt: currentUser.createdAt.getTime(),
createdAt: currentUser.createdAt.toISOString(),
email: currentUser.email,
fullName: currentUser.fullName,
id: currentUser.id,
role: {
createdAt: role.createdAt.getTime(),
createdAt: role.createdAt.toISOString(),
description: null,
id: role.id,
isAdmin: role.isAdmin,
key: role.key,
name: role.name,
updatedAt: role.updatedAt.getTime(),
updatedAt: role.updatedAt.toISOString(),
},
trialExpiryDate: currentUser.trialExpiryDate.toISOString(),
updatedAt: currentUser.updatedAt.getTime(),
updatedAt: currentUser.updatedAt.toISOString(),
},
meta: {
count: 1,

View File

@@ -3,23 +3,23 @@ const getUsersMock = async (users, roles) => {
const role = roles.find((r) => r.id === user.roleId);
return {
createdAt: user.createdAt.getTime(),
createdAt: user.createdAt.toISOString(),
email: user.email,
fullName: user.fullName,
id: user.id,
role: role
? {
createdAt: role.createdAt.getTime(),
createdAt: role.createdAt.toISOString(),
description: role.description,
id: role.id,
isAdmin: role.isAdmin,
key: role.key,
name: role.name,
updatedAt: role.updatedAt.getTime(),
updatedAt: role.updatedAt.toISOString(),
}
: null,
trialExpiryDate: user.trialExpiryDate.toISOString(),
updatedAt: user.updatedAt.getTime(),
updatedAt: user.updatedAt.toISOString(),
};
});

View File

@@ -1,21 +0,0 @@
const getAppMock = (app) => {
return {
data: {
authDocUrl: app.authDocUrl,
iconUrl: app.iconUrl,
key: app.key,
name: app.name,
primaryColor: app.primaryColor,
supportsConnections: app.supportsConnections,
},
meta: {
count: 1,
currentPage: null,
isArray: false,
totalPages: null,
type: 'Object',
},
};
};
export default getAppMock;

View File

@@ -1,22 +1,22 @@
const getCurrentUserMock = (currentUser, role) => {
return {
data: {
createdAt: currentUser.createdAt.getTime(),
createdAt: currentUser.createdAt.toISOString(),
email: currentUser.email,
fullName: currentUser.fullName,
id: currentUser.id,
permissions: [],
role: {
createdAt: role.createdAt.getTime(),
createdAt: role.createdAt.toISOString(),
description: null,
id: role.id,
isAdmin: role.isAdmin,
key: role.key,
name: role.name,
updatedAt: role.updatedAt.getTime(),
updatedAt: role.updatedAt.toISOString(),
},
trialExpiryDate: currentUser.trialExpiryDate.toISOString(),
updatedAt: currentUser.updatedAt.getTime(),
updatedAt: currentUser.updatedAt.toISOString(),
},
meta: {
count: 1,

View File

@@ -41,15 +41,6 @@ export default defineConfig({
{ text: 'Connection', link: '/apps/carbone/connection' },
],
},
{
text: 'Datastore',
collapsible: true,
collapsed: true,
items: [
{ text: 'Actions', link: '/apps/datastore/actions' },
{ text: 'Connection', link: '/apps/datastore/connection' },
],
},
{
text: 'DeepL',
collapsible: true,
@@ -82,6 +73,7 @@ export default defineConfig({
collapsible: true,
collapsed: true,
items: [
{ text: 'Triggers', link: '/apps/dropbox/triggers' },
{ text: 'Actions', link: '/apps/dropbox/actions' },
{ text: 'Connection', link: '/apps/dropbox/connection' },
],

View File

@@ -1,14 +0,0 @@
---
favicon: /favicons/datastore.svg
items:
- name: Get value
desc: Get value from the persistent datastore.
- name: Set value
desc: Set value to the persistent datastore.
---
<script setup>
import CustomListing from '../../components/CustomListing.vue'
</script>
<CustomListing />

View File

@@ -1,3 +0,0 @@
# Datastore
Datastore is a persistent key-value storage system that allows you to store and retrieve data. Currently you can use it within the scope of the flow, meaning you can store and retrieve data within the same flow.

View File

@@ -3,6 +3,8 @@ favicon: /favicons/dropbox.svg
items:
- name: Create a folder
desc: Creates a new folder with the given parent folder and folder name.
- name: Create a text file
desc: Create a new text file from plain text content you specify.
- name: Rename a file
desc: Rename a file with the given file path and new name.
---

View File

@@ -0,0 +1,14 @@
---
favicon: /favicons/dropbox.svg
items:
- name: New files in folder
desc: Triggers when a new file is added to a folder. Ensure that the number of files/folders within the monitored directory remains below 4000.
- name: New folders
desc: Triggers when any new folder is added. Ensure that the number of files/folders within the monitored directory remains below 4000.
---
<script setup>
import CustomListing from '../../components/CustomListing.vue'
</script>
<CustomListing />

View File

@@ -1,13 +0,0 @@
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" fill="#000000" width="800px" height="800px" viewBox="0 0 32 32" id="icon">
<defs>
<style>.cls-1{fill:none;}</style>
</defs>
<title>datastore</title>
<circle cx="23" cy="23" r="1"/>
<rect x="8" y="22" width="12" height="2"/>
<circle cx="23" cy="9" r="1"/>
<rect x="8" y="8" width="12" height="2"/>
<path d="M26,14a2,2,0,0,0,2-2V6a2,2,0,0,0-2-2H6A2,2,0,0,0,4,6v6a2,2,0,0,0,2,2H8v4H6a2,2,0,0,0-2,2v6a2,2,0,0,0,2,2H26a2,2,0,0,0,2-2V20a2,2,0,0,0-2-2H24V14ZM6,6H26v6H6ZM26,26H6V20H26Zm-4-8H10V14H22Z"/>
<rect id="_Transparent_Rectangle_" data-name="&lt;Transparent Rectangle&gt;" class="cls-1" width="32" height="32"/>
</svg>

Before

Width:  |  Height:  |  Size: 704 B