Compare commits

..

59 Commits

Author SHA1 Message Date
Faruk AYDIN
6c7470472f fix: Update the branch for dockerfile cloud 2024-02-28 02:34:57 +01:00
Faruk AYDIN
54282ba7e0 feat: Use new API endpoint from Helix 2024-02-28 02:23:50 +01:00
Ömer Faruk Aydın
7f324abd44 Merge pull request #1669 from automatisch/helix-attemtps
fix: Add artificial delay to Helix API attempts
2024-02-27 23:49:05 +01:00
Faruk AYDIN
65a0c3b40a fix: Add artificial delay to Helix API attempts 2024-02-27 23:44:52 +01:00
Ömer Faruk Aydın
2449baac5b Merge pull request #1668 from automatisch/document-datastore
docs: Add datastore app to the integration list
2024-02-27 22:10:52 +01:00
Faruk AYDIN
0ab03e1856 docs: Add datastore app to the integration list 2024-02-27 21:44:51 +01:00
Ömer Faruk Aydın
9a3f85106c Merge pull request #1667 from automatisch/fix-helix-integration
fix: Stop asking to helix server after 50 attempts
2024-02-27 19:17:04 +01:00
Faruk AYDIN
42c495d8ab fix: Stop asking to helix server after 50 attempts 2024-02-27 19:14:19 +01:00
Ömer Faruk Aydın
58def585f1 Merge pull request #1666 from automatisch/datastore-app
feat: Implement datastore built-in app
2024-02-27 19:13:26 +01:00
Faruk AYDIN
047034d831 fix: Remove min length validation from value of datastore 2024-02-27 19:05:27 +01:00
Faruk AYDIN
bdb2f24a81 feat: Implement datastore built-in app 2024-02-27 19:01:46 +01:00
Ömer Faruk Aydın
636870a075 Merge pull request #1661 from automatisch/get-app
feat: Introduce app serializer
2024-02-26 22:32:06 +01:00
Faruk AYDIN
8981174302 feat: Introduce app serializer 2024-02-26 22:25:03 +01:00
Faruk AYDIN
dd5f05334b feat: Allow renderer to use explicitly defined serializers 2024-02-26 22:17:21 +01:00
Ömer Faruk Aydın
929b626b51 Merge pull request #1660 from automatisch/rest-get-app
feat: Implement get app API endpoint
2024-02-26 21:44:23 +01:00
Faruk AYDIN
7d5b2ec81e feat: Implement get app API endpoint 2024-02-26 17:59:48 +01:00
Ömer Faruk Aydın
f0e2d36c34 Merge pull request #1657 from automatisch/timestamp-serializer
feat: Use timestamp for serializer date objects
2024-02-26 14:36:34 +01:00
Faruk AYDIN
94f171d757 feat: Use timestamp for serializer date objects 2024-02-26 14:11:56 +01:00
Ömer Faruk Aydın
04e06db430 Merge pull request #1656 from automatisch/api-controller-tests
test: Cover not found responses for API endpoint tests
2024-02-26 13:36:48 +01:00
Faruk AYDIN
d74b215169 test: Cover bad request responses for API endpoint tests 2024-02-26 13:30:30 +01:00
Faruk AYDIN
404ea94dd2 test: Cover not found responses for API endpoint tests 2024-02-26 01:40:20 +01:00
Faruk AYDIN
4afe7c6b46 feat: Handle bad request for invalid UUID 2024-02-26 01:26:04 +01:00
Ömer Faruk Aydın
60b20c4d01 Merge pull request #1655 from automatisch/add-async-handler
feat: Implement async handler for routes
2024-02-26 01:25:35 +01:00
Faruk AYDIN
58658c6b1a feat: Do not expose unknown error message to client 2024-02-26 01:07:31 +01:00
Faruk AYDIN
ec444317b3 feat: Catch not found error message for objection 2024-02-26 01:06:54 +01:00
Faruk AYDIN
8b4aee1afa feat: Implement async handler for routes 2024-02-26 01:03:15 +01:00
Ömer Faruk Aydın
51abd74304 Merge pull request #1654 from automatisch/get-flow
feat: Implement get flow API endpoint
2024-02-26 01:02:38 +01:00
Faruk AYDIN
b93b465f09 feat: Implement get flow API endpoint 2024-02-26 00:52:02 +01:00
Faruk AYDIN
5aad68ec62 test: Use nested serializers explicitly for serializer tests 2024-02-25 23:34:41 +01:00
Faruk AYDIN
74fbc937a1 feat: Introduce flow serializer 2024-02-25 23:31:22 +01:00
Faruk AYDIN
7e35f544eb feat: Introduce step serializer 2024-02-25 23:01:55 +01:00
Ömer Faruk Aydın
ed1c3cffc1 Merge pull request #1653 from automatisch/rest-automatisch-license
faet: Implement automatisch license API endpoint
2024-02-25 18:36:42 +01:00
Ömer Faruk Aydın
c4983a9f9b Merge pull request #1652 from automatisch/rest-automatisch-info
feat: Implement automatisch info API endpoint
2024-02-25 18:36:27 +01:00
Ömer Faruk Aydın
5b43262e7a Merge pull request #1651 from automatisch/remove-role-join
chore: No need to join role since we don't expose roleId anymore
2024-02-25 18:36:10 +01:00
Ömer Faruk Aydın
dad4408679 Merge pull request #1650 from automatisch/rest-get-invoices
feat: Implement get invoices API endpoint
2024-02-25 18:35:52 +01:00
Ömer Faruk Aydın
a78c4d12b4 Merge pull request #1607 from automatisch/AUT-681
feat: implement app-auth-client endpoint
2024-02-25 18:35:33 +01:00
Ömer Faruk Aydın
74664a9df8 Merge pull request #1649 from automatisch/move-get-user
feat: Move get user API endpoint to admin namespace
2024-02-25 18:34:48 +01:00
Ömer Faruk Aydın
fce5281a03 Merge pull request #1648 from automatisch/move-get-users
feat: Move get users API endpoint to admin namespace
2024-02-25 18:32:24 +01:00
Faruk AYDIN
de0bd2f486 faet: Implement automatisch license API endpoint 2024-02-25 03:28:20 +01:00
Faruk AYDIN
079fb5d108 feat: Implement automatisch info API endpoint 2024-02-25 03:27:17 +01:00
Faruk AYDIN
1c7435a32b chore: No need to join role since we don't expose roleId anymore 2024-02-25 02:02:02 +01:00
Faruk AYDIN
1afd374cf6 feat: Implement get invoices API endpoint 2024-02-25 01:31:36 +01:00
Faruk AYDIN
3adf549915 feat: Extract get invoices logic to user model 2024-02-25 01:30:29 +01:00
Faruk AYDIN
e94d669eca fix: Cover empty array case for renderer helper 2024-02-25 01:29:59 +01:00
Faruk AYDIN
5fac0b4689 test: Add tests for app auth client serializer 2024-02-24 02:51:34 +01:00
Faruk AYDIN
832d323a6e refactor: Remove redundant query string from get app auth client tests 2024-02-24 01:25:46 +01:00
Faruk AYDIN
03f1dbd5b2 feat: Add check enterprise middleware to get app auth clients 2024-02-24 01:24:41 +01:00
Faruk AYDIN
c0a216f109 refactor: Remove license check for admin, since it is middleware responsibility 2024-02-24 01:22:27 +01:00
Faruk AYDIN
ad67b13270 fix: Add missing middleware imports for admin app auth clients 2024-02-24 01:18:30 +01:00
Faruk AYDIN
5d420c08c6 fix: Remove forgotten character in the routes 2024-02-24 01:14:56 +01:00
Faruk AYDIN
3d8235c670 refactor: Use kebab-case for app auth client serializer filename 2024-02-24 01:10:59 +01:00
Faruk AYDIN
5a209f81d1 feat: Add missing middleware checks to admin app auth clients 2024-02-24 01:08:08 +01:00
Rıdvan Akca
d17d8e2805 feat: implement app-auth-client endpoint 2024-02-24 01:02:28 +01:00
Faruk AYDIN
ca7636e7bc feat: Move get user API endpoint to admin namespace 2024-02-24 00:40:54 +01:00
Faruk AYDIN
532cfc10d0 feat: Move get users API endpoint to admin namespace 2024-02-24 00:31:15 +01:00
Ömer Faruk Aydın
72d68c4377 Merge pull request #1647 from automatisch/error-logger-for-queues
feat: Add logger for errors happened in queues
2024-02-23 23:57:00 +01:00
Faruk AYDIN
00f5964aa4 feat: Add logger for errors happened in queues 2024-02-23 23:50:50 +01:00
Ömer Faruk Aydın
fcf345abab Merge pull request #1642 from automatisch/delete-step
fix: Allow permitted users to delete others steps
2024-02-23 14:15:29 +01:00
Faruk AYDIN
24ad43d3e4 fix: Allow permitted users to delete others steps 2024-02-23 13:45:50 +01:00
98 changed files with 1494 additions and 435 deletions

View File

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

View File

@@ -38,6 +38,7 @@
"debug": "~2.6.9", "debug": "~2.6.9",
"dotenv": "^10.0.0", "dotenv": "^10.0.0",
"express": "~4.18.2", "express": "~4.18.2",
"express-async-handler": "^1.2.0",
"express-basic-auth": "^1.2.1", "express-basic-auth": "^1.2.1",
"express-graphql": "^0.12.0", "express-graphql": "^0.12.0",
"fast-xml-parser": "^4.0.11", "fast-xml-parser": "^4.0.11",
@@ -94,6 +95,7 @@
"url": "https://github.com/automatisch/automatisch/issues" "url": "https://github.com/automatisch/automatisch/issues"
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/utils": "^7.0.2",
"nodemon": "^2.0.13", "nodemon": "^2.0.13",
"supertest": "^6.3.3", "supertest": "^6.3.3",
"vitest": "^1.1.3" "vitest": "^1.1.3"

View File

@@ -0,0 +1,27 @@
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

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

View File

@@ -0,0 +1,36 @@
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

@@ -0,0 +1,13 @@
<?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>

After

Width:  |  Height:  |  Size: 704 B

View File

@@ -0,0 +1,14 @@
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

@@ -1,120 +0,0 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Change a scheduled event',
key: 'changeScheduledEvent',
description: 'Changes a scheduled event',
arguments: [
{
label: 'Scheduled Event',
key: 'scheduledEventId',
type: 'dropdown',
required: true,
description: '',
variables: false,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listScheduledEvents',
},
],
},
},
{
label: 'Status',
key: 'status',
type: 'dropdown',
required: false,
description:
'After the status has been changed to COMPLETED or CANCELED, it becomes immutable and cannot be modified further.',
variables: true,
options: [
{ label: 'SCHEDULED', value: 1 },
{ label: 'ACTIVE', value: 2 },
{ label: 'COMPLETED', value: 3 },
{ label: 'CANCELED', value: 4 },
],
},
{
label: 'Type',
key: 'entityType',
type: 'dropdown',
required: true,
variables: true,
options: [
{ label: 'Stage channel', value: 1 },
{ label: 'Voice channel', value: 2 },
{ label: 'External', value: 3 },
],
additionalFields: {
type: 'query',
name: 'getDynamicFields',
arguments: [
{
name: 'key',
value: 'listScheduledEventFieldsForChange',
},
{
name: 'parameters.entityType',
value: '{parameters.entityType}',
},
],
},
},
{
label: 'Name',
key: 'name',
type: 'string',
required: false,
variables: true,
},
{
label: 'Description',
key: 'description',
type: 'string',
required: false,
variables: true,
},
{
label: 'Image',
key: 'image',
type: 'string',
required: false,
description:
'Image as DataURI scheme [data:image/<jpeg/png/gif>;base64,BASE64_ENCODED_<JPEG/PNG/GIF>_IMAGE_DATA]',
variables: true,
},
],
async run($) {
const data = {
channel_id: $.step.parameters.channel_id,
name: $.step.parameters.name,
scheduled_start_time: $.step.parameters.scheduledStartTime,
scheduled_end_time: $.step.parameters.scheduledEndTime,
description: $.step.parameters.description,
entity_type: $.step.parameters.entityType,
image: $.step.parameters.image,
};
const isExternal = $.step.parameters.entityType === 3;
if (isExternal) {
data.entity_metadata = {
location: $.step.parameters.location,
};
data.channel_id = null;
}
const response = await $.http?.patch(
`/guilds/${$.auth.data.guildId}/scheduled-events/${$.step.parameters.scheduledEventId}`,
data
);
$.setActionItem({ raw: response.data });
},
});

View File

@@ -1,9 +1,4 @@
import changeScheduledEvent from './change-scheduled-event/index.js';
import sendMessageToChannel from './send-message-to-channel/index.js'; import sendMessageToChannel from './send-message-to-channel/index.js';
import createScheduledEvent from './create-scheduled-event/index.js'; import createScheduledEvent from './create-scheduled-event/index.js';
export default [ export default [sendMessageToChannel, createScheduledEvent];
changeScheduledEvent,
sendMessageToChannel,
createScheduledEvent,
];

View File

@@ -1,5 +1,4 @@
import listChannels from './list-channels/index.js'; import listChannels from './list-channels/index.js';
import listScheduledEvents from './list-scheduled-events/index.js';
import listVoiceChannels from './list-voice-channels/index.js'; import listVoiceChannels from './list-voice-channels/index.js';
export default [listChannels, listScheduledEvents, listVoiceChannels]; export default [listChannels, listVoiceChannels];

View File

@@ -1,24 +0,0 @@
export default {
name: 'List scheduled events',
key: 'listScheduledEvents',
async run($) {
const scheduledEvents = {
data: [],
error: null,
};
const response = await $.http.get(
`/guilds/${$.auth.data.guildId}/scheduled-events`
);
scheduledEvents.data = response.data.map((scheduledEvent) => {
return {
value: scheduledEvent.id,
name: scheduledEvent.name,
};
});
return scheduledEvents;
},
};

View File

@@ -1,7 +1,3 @@
import listExternalScheduledEventFields from './list-external-scheduled-event-fields/index.js'; import listExternalScheduledEventFields from './list-external-scheduled-event-fields/index.js';
import listScheduledEventFieldsForChange from './list-scheduled-event-fields-for-change/index.js';
export default [ export default [listExternalScheduledEventFields];
listExternalScheduledEventFields,
listScheduledEventFieldsForChange,
];

View File

@@ -1,87 +0,0 @@
export default {
name: 'List scheduled event fields for change',
key: 'listScheduledEventFieldsForChange',
async run($) {
const isExternal = $.step.parameters.entityType === 3;
if (isExternal) {
return [
{
label: 'Location',
key: 'location',
type: 'string',
required: true,
description:
'The location of the event (1-100 characters). This will be omitted if type is NOT EXTERNAL',
variables: true,
},
{
label: 'Start-Time',
key: 'scheduledStartTime',
type: 'string',
required: false,
description: 'The time the event will start [ISO8601]',
variables: true,
},
{
label: 'End-Time',
key: 'scheduledEndTime',
type: 'string',
required: true,
description:
'The time the event will end [ISO8601]. This will be omitted if type is NOT EXTERNAL',
variables: true,
},
];
}
return [
{
label: 'Channel',
key: 'channel_id',
type: 'dropdown',
required: true,
description:
'Pick a voice or stage channel to link the event to. This will be omitted if type is EXTERNAL',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listVoiceChannels',
},
],
},
},
{
label: 'Location',
key: 'location',
type: 'string',
required: false,
description:
'The location of the event (1-100 characters). This will be omitted if type is NOT EXTERNAL',
variables: true,
},
{
label: 'Start-Time',
key: 'scheduledStartTime',
type: 'string',
required: false,
description: 'The time the event will start [ISO8601]',
variables: true,
},
{
label: 'End-Time',
key: 'scheduledEndTime',
type: 'string',
required: false,
description:
'The time the event will end [ISO8601]. This will be omitted if type is NOT EXTERNAL',
variables: true,
},
];
},
};

View File

@@ -1,4 +1,3 @@
import FormData from 'form-data';
import defineAction from '../../../../helpers/define-action.js'; import defineAction from '../../../../helpers/define-action.js';
export default defineAction({ export default defineAction({
@@ -17,34 +16,21 @@ export default defineAction({
], ],
async run($) { async run($) {
const formData = new FormData(); const response = await $.http.post('/api/v1/sessions/chat', {
formData.append('input', $.step.parameters.input); session_id: '',
formData.append('mode', 'inference'); messages: [
formData.append('type', 'text'); {
role: 'user',
const sessionResponse = await $.http.post('/api/v1/sessions', formData, { content: {
headers: { content_type: 'text',
...formData.getHeaders(), parts: [$.step.parameters.input],
}, },
},
],
}); });
const sessionId = sessionResponse.data.id; $.setActionItem({
raw: response.data,
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

@@ -0,0 +1,10 @@
import { renderObject } from '../../../../../helpers/renderer.js';
import AppAuthClient from '../../../../../models/app-auth-client.js';
export default async (request, response) => {
const appAuthClient = await AppAuthClient.query()
.findById(request.params.appAuthClientId)
.throwIfNotFound();
renderObject(response, appAuthClient);
};

View File

@@ -0,0 +1,52 @@
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';
import getAdminAppAuthClientMock from '../../../../../../test/mocks/rest/api/v1/admin/get-app-auth-client.js';
import { createAppAuthClient } from '../../../../../../test/factories/app-auth-client.js';
import { createRole } from '../../../../../../test/factories/role.js';
import * as license from '../../../../../helpers/license.ee.js';
describe('GET /api/v1/admin/app-auth-clients/:appAuthClientId', () => {
let currentUser, currentUserRole, currentAppAuthClient, token;
describe('with valid license key', () => {
beforeEach(async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
currentUserRole = await createRole({ key: 'admin' });
currentUser = await createUser({ roleId: currentUserRole.id });
currentAppAuthClient = await createAppAuthClient();
token = createAuthTokenByUserId(currentUser.id);
});
it('should return specified app auth client info', async () => {
const response = await request(app)
.get(`/api/v1/admin/app-auth-clients/${currentAppAuthClient.id}`)
.set('Authorization', token)
.expect(200);
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,5 +1,6 @@
import { vi, describe, it, expect, beforeEach } from 'vitest'; import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest'; import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../../app.js'; import app from '../../../../../app.js';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js'; import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
import { createRole } from '../../../../../../test/factories/role.js'; import { createRole } from '../../../../../../test/factories/role.js';
@@ -20,7 +21,7 @@ describe('GET /api/v1/admin/roles/:roleId', () => {
token = createAuthTokenByUserId(currentUser.id); token = createAuthTokenByUserId(currentUser.id);
}); });
it('should return roles', async () => { it('should return role', async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true); vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
const response = await request(app) const response = await request(app)
@@ -35,4 +36,24 @@ describe('GET /api/v1/admin/roles/:roleId', () => {
expect(response.body).toEqual(expectedPayload); 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,5 +1,6 @@
import { vi, describe, it, expect, beforeEach } from 'vitest'; import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest'; import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../../app.js'; import app from '../../../../../app.js';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js'; import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
import { createRole } from '../../../../../../test/factories/role.js'; import { createRole } from '../../../../../../test/factories/role.js';
@@ -31,4 +32,26 @@ describe('GET /api/v1/admin/saml-auth-provider/:samlAuthProviderId', () => {
expect(response.body).toEqual(expectedPayload); 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,11 +1,8 @@
import { renderObject } from '../../../../helpers/renderer.js'; import { renderObject } from '../../../../../helpers/renderer.js';
import User from '../../../../models/user.js'; import User from '../../../../../models/user.js';
export default async (request, response) => { export default async (request, response) => {
const user = await User.query() const user = await User.query()
.leftJoinRelated({
role: true,
})
.withGraphFetched({ .withGraphFetched({
role: true, role: true,
}) })

View File

@@ -0,0 +1,55 @@
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';
import { createRole } from '../../../../../../test/factories/role';
import getUserMock from '../../../../../../test/mocks/rest/api/v1/admin/users/get-user.js';
import * as license from '../../../../../helpers/license.ee.js';
describe('GET /api/v1/admin/users/:userId', () => {
let currentUser, currentUserRole, anotherUser, anotherUserRole, token;
beforeEach(async () => {
currentUserRole = await createRole({ key: 'admin' });
currentUser = await createUser({ roleId: currentUserRole.id });
anotherUser = await createUser();
anotherUserRole = await anotherUser.$relatedQuery('role');
token = createAuthTokenByUserId(currentUser.id);
});
it('should return specified user info', async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
const response = await request(app)
.get(`/api/v1/admin/users/${anotherUser.id}`)
.set('Authorization', token)
.expect(200);
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,12 +1,9 @@
import { renderObject } from '../../../../helpers/renderer.js'; import { renderObject } from '../../../../../helpers/renderer.js';
import User from '../../../../models/user.js'; import User from '../../../../../models/user.js';
import paginateRest from '../../../../helpers/pagination-rest.js'; import paginateRest from '../../../../../helpers/pagination-rest.js';
export default async (request, response) => { export default async (request, response) => {
const usersQuery = User.query() const usersQuery = User.query()
.leftJoinRelated({
role: true,
})
.withGraphFetched({ .withGraphFetched({
role: true, role: true,
}) })

View File

@@ -1,26 +1,17 @@
import { describe, it, expect, beforeEach } from 'vitest'; import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest'; import request from 'supertest';
import app from '../../../../app'; import app from '../../../../../app';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id'; import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id';
import { createRole } from '../../../../../test/factories/role'; import { createRole } from '../../../../../../test/factories/role';
import { createPermission } from '../../../../../test/factories/permission'; import { createUser } from '../../../../../../test/factories/user';
import { createUser } from '../../../../../test/factories/user'; import getUsersMock from '../../../../../../test/mocks/rest/api/v1/admin/users/get-users.js';
import getUsersMock from '../../../../../test/mocks/rest/api/v1/users/get-users'; import * as license from '../../../../../helpers/license.ee.js';
describe('GET /api/v1/users', () => { describe('GET /api/v1/admin/users', () => {
let currentUser, currentUserRole, anotherUser, anotherUserRole, token; let currentUser, currentUserRole, anotherUser, anotherUserRole, token;
beforeEach(async () => { beforeEach(async () => {
currentUserRole = await createRole({ currentUserRole = await createRole({ key: 'admin' });
key: 'currentUser',
name: 'Current user role',
});
await createPermission({
action: 'read',
subject: 'User',
roleId: currentUserRole.id,
});
currentUser = await createUser({ currentUser = await createUser({
roleId: currentUserRole.id, roleId: currentUserRole.id,
@@ -41,8 +32,10 @@ describe('GET /api/v1/users', () => {
}); });
it('should return users data', async () => { it('should return users data', async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
const response = await request(app) const response = await request(app)
.get('/api/v1/users') .get('/api/v1/admin/users')
.set('Authorization', token) .set('Authorization', token)
.expect(200); .expect(200);

View File

@@ -0,0 +1,11 @@
import { renderObject } from '../../../../helpers/renderer.js';
import AppAuthClient from '../../../../models/app-auth-client.js';
export default async (request, response) => {
const appAuthClient = await AppAuthClient.query()
.findById(request.params.appAuthClientId)
.where({ active: true })
.throwIfNotFound();
renderObject(response, appAuthClient);
};

View File

@@ -0,0 +1,48 @@
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';
import getAppAuthClientMock from '../../../../../test/mocks/rest/api/v1/admin/get-app-auth-client.js';
import { createAppAuthClient } from '../../../../../test/factories/app-auth-client.js';
import * as license from '../../../../helpers/license.ee.js';
describe('GET /api/v1/app-auth-clients/:id', () => {
let currentUser, currentAppAuthClient, token;
beforeEach(async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
currentUser = await createUser();
currentAppAuthClient = await createAppAuthClient();
token = createAuthTokenByUserId(currentUser.id);
});
it('should return specified app auth client info', async () => {
const response = await request(app)
.get(`/api/v1/app-auth-clients/${currentAppAuthClient.id}`)
.set('Authorization', token)
.expect(200);
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

@@ -0,0 +1,8 @@
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

@@ -0,0 +1,35 @@
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

@@ -0,0 +1,13 @@
import appConfig from '../../../../config/app.js';
import { hasValidLicense } from '../../../../helpers/license.ee.js';
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const info = {
isCloud: appConfig.isCloud,
isMation: appConfig.isMation,
isEnterprise: await hasValidLicense(),
};
renderObject(response, info);
};

View File

@@ -0,0 +1,22 @@
import { vi, expect, describe, it } from 'vitest';
import request from 'supertest';
import appConfig from '../../../../config/app.js';
import app from '../../../../app.js';
import infoMock from '../../../../../test/mocks/rest/api/v1/automatisch/info.js';
import * as license from '../../../../helpers/license.ee.js';
describe('GET /api/v1/automatisch/info', () => {
it('should return Automatisch info', async () => {
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(false);
vi.spyOn(appConfig, 'isMation', 'get').mockReturnValue(false);
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
const response = await request(app)
.get('/api/v1/automatisch/info')
.expect(200);
const expectedPayload = infoMock();
expect(response.body).toEqual(expectedPayload);
});
});

View File

@@ -0,0 +1,15 @@
import { getLicense } from '../../../../helpers/license.ee.js';
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const license = await getLicense();
const computedLicense = {
id: license ? license.id : null,
name: license ? license.name : null,
expireAt: license ? license.expireAt : null,
verified: license ? true : false,
};
renderObject(response, computedLicense);
};

View File

@@ -0,0 +1,23 @@
import { vi, expect, describe, it } from 'vitest';
import request from 'supertest';
import app from '../../../../app.js';
import licenseMock from '../../../../../test/mocks/rest/api/v1/automatisch/license.js';
import * as license from '../../../../helpers/license.ee.js';
describe('GET /api/v1/automatisch/license', () => {
it('should return Automatisch license info', async () => {
vi.spyOn(license, 'getLicense').mockResolvedValue({
id: '123',
name: 'license-name',
expireAt: '2025-12-31T23:59:59Z',
});
const response = await request(app)
.get('/api/v1/automatisch/license')
.expect(200);
const expectedPayload = licenseMock();
expect(response.body).toEqual(expectedPayload);
});
});

View File

@@ -0,0 +1,11 @@
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const flow = await request.currentUser.authorizedFlows
.withGraphJoined({ steps: true })
.orderBy('steps.position', 'asc')
.findOne({ 'flows.id': request.params.flowId })
.throwIfNotFound();
renderObject(response, flow);
};

View File

@@ -0,0 +1,102 @@
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';
import { createFlow } from '../../../../../test/factories/flow';
import { createStep } from '../../../../../test/factories/step';
import { createPermission } from '../../../../../test/factories/permission';
import getFlowMock from '../../../../../test/mocks/rest/api/v1/flows/get-flow';
describe('GET /api/v1/flows/:flowId', () => {
let currentUser, currentUserRole, token;
beforeEach(async () => {
currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role');
token = createAuthTokenByUserId(currentUser.id);
});
it('should return the flow data of current user', async () => {
const currentUserflow = await createFlow({ userId: currentUser.id });
const triggerStep = await createStep({ flowId: currentUserflow.id });
const actionStep = await createStep({ flowId: currentUserflow.id });
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const response = await request(app)
.get(`/api/v1/flows/${currentUserflow.id}`)
.set('Authorization', token)
.expect(200);
const expectedPayload = await getFlowMock(currentUserflow, [
triggerStep,
actionStep,
]);
expect(response.body).toEqual(expectedPayload);
});
it('should return the flow data of another user', async () => {
const anotherUser = await createUser();
const anotherUserFlow = await createFlow({ userId: anotherUser.id });
const triggerStep = await createStep({ flowId: anotherUserFlow.id });
const actionStep = await createStep({ flowId: anotherUserFlow.id });
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
const response = await request(app)
.get(`/api/v1/flows/${anotherUserFlow.id}`)
.set('Authorization', token)
.expect(200);
const expectedPayload = await getFlowMock(anotherUserFlow, [
triggerStep,
actionStep,
]);
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

@@ -0,0 +1,7 @@
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const invoices = await request.currentUser.getInvoices();
renderObject(response, invoices);
};

View File

@@ -0,0 +1,34 @@
import { describe, it, expect, beforeEach, vi } from 'vitest';
import request from 'supertest';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
import User from '../../../../models/user';
import getInvoicesMock from '../../../../../test/mocks/rest/api/v1/users/get-invoices.ee';
describe('GET /api/v1/user/invoices', () => {
let currentUser, token;
beforeEach(async () => {
currentUser = await createUser();
token = createAuthTokenByUserId(currentUser.id);
});
it('should return current user invoices', async () => {
const invoices = [
{ id: 1, amount: 100, description: 'Invoice 1' },
{ id: 2, amount: 200, description: 'Invoice 2' },
];
vi.spyOn(User.prototype, 'getInvoices').mockResolvedValue(invoices);
const response = await request(app)
.get('/api/v1/users/invoices')
.set('Authorization', token)
.expect(200);
const expectedPayload = await getInvoicesMock(invoices);
expect(response.body).toEqual(expectedPayload);
});
});

View File

@@ -1,36 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
import { createPermission } from '../../../../../test/factories/permission';
import getUserMock from '../../../../../test/mocks/rest/api/v1/users/get-user';
describe('GET /api/v1/users/:userId', () => {
let currentUser, currentUserRole, anotherUser, anotherUserRole, token;
beforeEach(async () => {
currentUser = await createUser();
anotherUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role');
anotherUserRole = await anotherUser.$relatedQuery('role');
await createPermission({
roleId: currentUserRole.id,
action: 'read',
subject: 'User',
});
token = createAuthTokenByUserId(currentUser.id);
});
it('should return specified user info', async () => {
const response = await request(app)
.get(`/api/v1/users/${anotherUser.id}`)
.set('Authorization', token)
.expect(200);
const expectedPayload = getUserMock(anotherUser, anotherUserRole);
expect(response.body).toEqual(expectedPayload);
});
});

View File

@@ -0,0 +1,16 @@
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,8 +1,13 @@
const deleteStep = async (_parent, params, context) => { import Step from '../../models/flow.js';
context.currentUser.can('update', 'Flow');
const step = await context.currentUser const deleteStep = async (_parent, params, context) => {
.$relatedQuery('steps') const conditions = context.currentUser.can('update', 'Flow');
const isCreator = conditions.isCreator;
const allSteps = Step.query();
const userSteps = context.currentUser.$relatedQuery('steps');
const baseQuery = isCreator ? userSteps : allSteps;
const step = await baseQuery
.withGraphFetched('flow') .withGraphFetched('flow')
.findOne({ .findOne({
'steps.id': params.input.id, 'steps.id': params.input.id,

View File

@@ -7,6 +7,10 @@ const authorizationList = {
action: 'read', action: 'read',
subject: 'User', subject: 'User',
}, },
'GET /api/v1/flows/:flowId': {
action: 'read',
subject: 'Flow',
},
}; };
export const authorizeUser = async (request, response, next) => { export const authorizeUser = async (request, response, next) => {

View File

@@ -1,14 +1,31 @@
import logger from './logger.js'; 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 // Do not remove `next` argument as the function signature will not fit for an error handler middleware
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
const errorHandler = (err, req, res, next) => { const errorHandler = (error, request, response, next) => {
if (err.message === 'Not Found') { if (error.message === 'Not Found' || error instanceof NotFoundError) {
res.status(404).end(); response.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; export default errorHandler;

View File

@@ -1,6 +1,7 @@
import createHttpClient from './http-client/index.js'; import createHttpClient from './http-client/index.js';
import EarlyExitError from '../errors/early-exit.js'; import EarlyExitError from '../errors/early-exit.js';
import AlreadyProcessedError from '../errors/already-processed.js'; import AlreadyProcessedError from '../errors/already-processed.js';
import Datastore from '../models/datastore.js';
const globalVariable = async (options) => { const globalVariable = async (options) => {
const { const {
@@ -88,6 +89,43 @@ const globalVariable = async (options) => {
setActionItem: (actionItem) => { setActionItem: (actionItem) => {
$.actionOutput.data = 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) { if (request) {

View File

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

View File

@@ -0,0 +1,24 @@
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

@@ -15,6 +15,7 @@ import Role from './role.js';
import Step from './step.js'; import Step from './step.js';
import Subscription from './subscription.ee.js'; import Subscription from './subscription.ee.js';
import UsageData from './usage-data.ee.js'; import UsageData from './usage-data.ee.js';
import Billing from '../helpers/billing/index.ee.js';
class User extends Base { class User extends Base {
static tableName = 'users'; static tableName = 'users';
@@ -143,6 +144,11 @@ class User extends Base {
}, },
}); });
get authorizedFlows() {
const conditions = this.can('read', 'Flow');
return conditions.isCreator ? this.$relatedQuery('flows') : Flow.query();
}
login(password) { login(password) {
return bcrypt.compare(password, this.password); return bcrypt.compare(password, this.password);
} }
@@ -237,6 +243,20 @@ class User extends Base {
return currentUsageData.consumedTaskCount < plan.quota; return currentUsageData.consumedTaskCount < plan.quota;
} }
async getInvoices() {
const subscription = await this.$relatedQuery('currentSubscription');
if (!subscription) {
return [];
}
const invoices = await Billing.paddleClient.getInvoices(
Number(subscription.paddleSubscriptionId)
);
return invoices;
}
async $beforeInsert(queryContext) { async $beforeInsert(queryContext) {
await super.$beforeInsert(queryContext); await super.$beforeInsert(queryContext);

View File

@@ -15,11 +15,17 @@ process.on('SIGTERM', async () => {
await actionQueue.close(); await actionQueue.close();
}); });
actionQueue.on('error', (err) => { actionQueue.on('error', (error) => {
if (err.code === CONNECTION_REFUSED) { if (error.code === CONNECTION_REFUSED) {
logger.error('Make sure you have installed Redis and it is running.', err); logger.error(
'Make sure you have installed Redis and it is running.',
error
);
process.exit(); process.exit();
} }
logger.error('Error happened in action queue!', error);
}); });
export default actionQueue; export default actionQueue;

View File

@@ -15,11 +15,17 @@ process.on('SIGTERM', async () => {
await deleteUserQueue.close(); await deleteUserQueue.close();
}); });
deleteUserQueue.on('error', (err) => { deleteUserQueue.on('error', (error) => {
if (err.code === CONNECTION_REFUSED) { if (error.code === CONNECTION_REFUSED) {
logger.error('Make sure you have installed Redis and it is running.', err); logger.error(
'Make sure you have installed Redis and it is running.',
error
);
process.exit(); process.exit();
} }
logger.error('Error happened in delete user queue!', error);
}); });
export default deleteUserQueue; export default deleteUserQueue;

View File

@@ -15,11 +15,17 @@ process.on('SIGTERM', async () => {
await emailQueue.close(); await emailQueue.close();
}); });
emailQueue.on('error', (err) => { emailQueue.on('error', (error) => {
if (err.code === CONNECTION_REFUSED) { if (error.code === CONNECTION_REFUSED) {
logger.error('Make sure you have installed Redis and it is running.', err); logger.error(
'Make sure you have installed Redis and it is running.',
error
);
process.exit(); process.exit();
} }
logger.error('Error happened in email queue!', error);
}); });
export default emailQueue; export default emailQueue;

View File

@@ -15,11 +15,17 @@ process.on('SIGTERM', async () => {
await flowQueue.close(); await flowQueue.close();
}); });
flowQueue.on('error', (err) => { flowQueue.on('error', (error) => {
if (err.code === CONNECTION_REFUSED) { if (error.code === CONNECTION_REFUSED) {
logger.error('Make sure you have installed Redis and it is running.', err); logger.error(
'Make sure you have installed Redis and it is running.',
error
);
process.exit(); process.exit();
} }
logger.error('Error happened in flow queue!', error);
}); });
export default flowQueue; export default flowQueue;

View File

@@ -18,11 +18,20 @@ process.on('SIGTERM', async () => {
await removeCancelledSubscriptionsQueue.close(); await removeCancelledSubscriptionsQueue.close();
}); });
removeCancelledSubscriptionsQueue.on('error', (err) => { removeCancelledSubscriptionsQueue.on('error', (error) => {
if (err.code === CONNECTION_REFUSED) { if (error.code === CONNECTION_REFUSED) {
logger.error('Make sure you have installed Redis and it is running.', err); logger.error(
'Make sure you have installed Redis and it is running.',
error
);
process.exit(); process.exit();
} }
logger.error(
'Error happened in remove cancelled subscriptions queue!',
error
);
}); });
removeCancelledSubscriptionsQueue.add('remove-cancelled-subscriptions', null, { removeCancelledSubscriptionsQueue.add('remove-cancelled-subscriptions', null, {

View File

@@ -15,11 +15,17 @@ process.on('SIGTERM', async () => {
await triggerQueue.close(); await triggerQueue.close();
}); });
triggerQueue.on('error', (err) => { triggerQueue.on('error', (error) => {
if (err.code === CONNECTION_REFUSED) { if (error.code === CONNECTION_REFUSED) {
logger.error('Make sure you have installed Redis and it is running.', err); logger.error(
'Make sure you have installed Redis and it is running.',
error
);
process.exit(); process.exit();
} }
logger.error('Error happened in trigger queue!', error);
}); });
export default triggerQueue; export default triggerQueue;

View File

@@ -0,0 +1,18 @@
import { Router } from 'express';
import asyncHandler from 'express-async-handler';
import { authenticateUser } from '../../../../helpers/authentication.js';
import { authorizeAdmin } from '../../../../helpers/authorization.js';
import { checkIsEnterprise } from '../../../../helpers/check-is-enterprise.js';
import getAdminAppAuthClientsAction from '../../../../controllers/api/v1/admin/app-auth-clients/get-app-auth-client.js';
const router = Router();
router.get(
'/:appAuthClientId',
authenticateUser,
authorizeAdmin,
checkIsEnterprise,
asyncHandler(getAdminAppAuthClientsAction)
);
export default router;

View File

@@ -1,4 +1,5 @@
import { Router } from 'express'; import { Router } from 'express';
import asyncHandler from 'express-async-handler';
import { authenticateUser } from '../../../../helpers/authentication.js'; import { authenticateUser } from '../../../../helpers/authentication.js';
import { authorizeAdmin } from '../../../../helpers/authorization.js'; import { authorizeAdmin } from '../../../../helpers/authorization.js';
import { checkIsEnterprise } from '../../../../helpers/check-is-enterprise.js'; import { checkIsEnterprise } from '../../../../helpers/check-is-enterprise.js';
@@ -11,7 +12,7 @@ router.get(
authenticateUser, authenticateUser,
authorizeAdmin, authorizeAdmin,
checkIsEnterprise, checkIsEnterprise,
getPermissionsCatalogAction asyncHandler(getPermissionsCatalogAction)
); );
export default router; export default router;

View File

@@ -1,4 +1,5 @@
import { Router } from 'express'; import { Router } from 'express';
import asyncHandler from 'express-async-handler';
import { authenticateUser } from '../../../../helpers/authentication.js'; import { authenticateUser } from '../../../../helpers/authentication.js';
import { authorizeAdmin } from '../../../../helpers/authorization.js'; import { authorizeAdmin } from '../../../../helpers/authorization.js';
import { checkIsEnterprise } from '../../../../helpers/check-is-enterprise.js'; import { checkIsEnterprise } from '../../../../helpers/check-is-enterprise.js';
@@ -12,7 +13,7 @@ router.get(
authenticateUser, authenticateUser,
authorizeAdmin, authorizeAdmin,
checkIsEnterprise, checkIsEnterprise,
getRolesAction asyncHandler(getRolesAction)
); );
router.get( router.get(
@@ -20,7 +21,7 @@ router.get(
authenticateUser, authenticateUser,
authorizeAdmin, authorizeAdmin,
checkIsEnterprise, checkIsEnterprise,
getRoleAction asyncHandler(getRoleAction)
); );
export default router; export default router;

View File

@@ -1,4 +1,5 @@
import { Router } from 'express'; import { Router } from 'express';
import asyncHandler from 'express-async-handler';
import { authenticateUser } from '../../../../helpers/authentication.js'; import { authenticateUser } from '../../../../helpers/authentication.js';
import { authorizeAdmin } from '../../../../helpers/authorization.js'; import { authorizeAdmin } from '../../../../helpers/authorization.js';
import { checkIsEnterprise } from '../../../../helpers/check-is-enterprise.js'; import { checkIsEnterprise } from '../../../../helpers/check-is-enterprise.js';
@@ -12,7 +13,7 @@ router.get(
authenticateUser, authenticateUser,
authorizeAdmin, authorizeAdmin,
checkIsEnterprise, checkIsEnterprise,
getSamlAuthProvidersAction asyncHandler(getSamlAuthProvidersAction)
); );
router.get( router.get(
@@ -20,7 +21,7 @@ router.get(
authenticateUser, authenticateUser,
authorizeAdmin, authorizeAdmin,
checkIsEnterprise, checkIsEnterprise,
getSamlAuthProviderAction asyncHandler(getSamlAuthProviderAction)
); );
export default router; export default router;

View File

@@ -0,0 +1,27 @@
import { Router } from 'express';
import asyncHandler from 'express-async-handler';
import { authenticateUser } from '../../../../helpers/authentication.js';
import { authorizeAdmin } from '../../../../helpers/authorization.js';
import { checkIsEnterprise } from '../../../../helpers/check-is-enterprise.js';
import getUsersAction from '../../../../controllers/api/v1/admin/users/get-users.ee.js';
import getUserAction from '../../../../controllers/api/v1/admin/users/get-user.ee.js';
const router = Router();
router.get(
'/',
authenticateUser,
authorizeAdmin,
checkIsEnterprise,
asyncHandler(getUsersAction)
);
router.get(
'/:userId',
authenticateUser,
authorizeAdmin,
checkIsEnterprise,
asyncHandler(getUserAction)
);
export default router;

View File

@@ -0,0 +1,16 @@
import { Router } from 'express';
import asyncHandler from 'express-async-handler';
import { authenticateUser } from '../../../helpers/authentication.js';
import { checkIsEnterprise } from '../../../helpers/check-is-enterprise.js';
import getAppAuthClientAction from '../../../controllers/api/v1/app-auth-clients/get-app-auth-client.js';
const router = Router();
router.get(
'/:appAuthClientId',
authenticateUser,
checkIsEnterprise,
asyncHandler(getAppAuthClientAction)
);
export default router;

View File

@@ -0,0 +1,10 @@
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

@@ -1,10 +1,15 @@
import { Router } from 'express'; import { Router } from 'express';
import asyncHandler from 'express-async-handler';
import versionAction from '../../../controllers/api/v1/automatisch/version.js'; import versionAction from '../../../controllers/api/v1/automatisch/version.js';
import notificationsAction from '../../../controllers/api/v1/automatisch/notifications.js'; import notificationsAction from '../../../controllers/api/v1/automatisch/notifications.js';
import infoAction from '../../../controllers/api/v1/automatisch/info.js';
import licenseAction from '../../../controllers/api/v1/automatisch/license.js';
const router = Router(); const router = Router();
router.get('/version', versionAction); router.get('/version', asyncHandler(versionAction));
router.get('/notifications', notificationsAction); router.get('/notifications', asyncHandler(notificationsAction));
router.get('/info', asyncHandler(infoAction));
router.get('/license', asyncHandler(licenseAction));
export default router; export default router;

View File

@@ -0,0 +1,16 @@
import { Router } from 'express';
import asyncHandler from 'express-async-handler';
import { authenticateUser } from '../../../helpers/authentication.js';
import { authorizeUser } from '../../../helpers/authorization.js';
import getFlowAction from '../../../controllers/api/v1/flows/get-flow.js';
const router = Router();
router.get(
'/:flowId',
authenticateUser,
authorizeUser,
asyncHandler(getFlowAction)
);
export default router;

View File

@@ -1,4 +1,5 @@
import { Router } from 'express'; import { Router } from 'express';
import asyncHandler from 'express-async-handler';
import { authenticateUser } from '../../../helpers/authentication.js'; import { authenticateUser } from '../../../helpers/authentication.js';
import checkIsCloud from '../../../helpers/check-is-cloud.js'; import checkIsCloud from '../../../helpers/check-is-cloud.js';
import getPlansAction from '../../../controllers/api/v1/payment/get-plans.ee.js'; import getPlansAction from '../../../controllers/api/v1/payment/get-plans.ee.js';
@@ -6,7 +7,18 @@ import getPaddleInfoAction from '../../../controllers/api/v1/payment/get-paddle-
const router = Router(); const router = Router();
router.get('/plans', authenticateUser, checkIsCloud, getPlansAction); router.get(
router.get('/paddle-info', authenticateUser, checkIsCloud, getPaddleInfoAction); '/plans',
authenticateUser,
checkIsCloud,
asyncHandler(getPlansAction)
);
router.get(
'/paddle-info',
authenticateUser,
checkIsCloud,
asyncHandler(getPaddleInfoAction)
);
export default router; export default router;

View File

@@ -1,22 +1,26 @@
import { Router } from 'express'; import { Router } from 'express';
import asyncHandler from 'express-async-handler';
import { authenticateUser } from '../../../helpers/authentication.js'; import { authenticateUser } from '../../../helpers/authentication.js';
import { authorizeUser } from '../../../helpers/authorization.js';
import checkIsCloud from '../../../helpers/check-is-cloud.js'; import checkIsCloud from '../../../helpers/check-is-cloud.js';
import getCurrentUserAction from '../../../controllers/api/v1/users/get-current-user.js'; import getCurrentUserAction from '../../../controllers/api/v1/users/get-current-user.js';
import getUserAction from '../../../controllers/api/v1/users/get-user.js';
import getUsersAction from '../../../controllers/api/v1/users/get-users.js';
import getUserTrialAction from '../../../controllers/api/v1/users/get-user-trial.ee.js'; import getUserTrialAction from '../../../controllers/api/v1/users/get-user-trial.ee.js';
import getInvoicesAction from '../../../controllers/api/v1/users/get-invoices.ee.js';
const router = Router(); const router = Router();
router.get('/', authenticateUser, authorizeUser, getUsersAction); router.get('/me', authenticateUser, asyncHandler(getCurrentUserAction));
router.get('/me', authenticateUser, getCurrentUserAction); router.get(
router.get('/:userId', authenticateUser, authorizeUser, getUserAction); '/invoices',
authenticateUser,
checkIsCloud,
asyncHandler(getInvoicesAction)
);
router.get( router.get(
'/:userId/trial', '/:userId/trial',
authenticateUser, authenticateUser,
checkIsCloud, checkIsCloud,
getUserTrialAction asyncHandler(getUserTrialAction)
); );
export default router; export default router;

View File

@@ -1,8 +1,9 @@
import { Router } from 'express'; import { Router } from 'express';
import asyncHandler from 'express-async-handler';
import indexAction from '../controllers/healthcheck/index.js'; import indexAction from '../controllers/healthcheck/index.js';
const router = Router(); const router = Router();
router.get('/', indexAction); router.get('/', asyncHandler(indexAction));
export default router; export default router;

View File

@@ -6,9 +6,14 @@ import healthcheckRouter from './healthcheck.js';
import automatischRouter from './api/v1/automatisch.js'; import automatischRouter from './api/v1/automatisch.js';
import usersRouter from './api/v1/users.js'; import usersRouter from './api/v1/users.js';
import paymentRouter from './api/v1/payment.ee.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 samlAuthProvidersRouter from './api/v1/admin/saml-auth-providers.ee.js';
import rolesRouter from './api/v1/admin/roles.ee.js'; import rolesRouter from './api/v1/admin/roles.ee.js';
import permissionsRouter from './api/v1/admin/permissions.ee.js'; import permissionsRouter from './api/v1/admin/permissions.ee.js';
import adminUsersRouter from './api/v1/admin/users.ee.js';
import adminAppAuthClientsRouter from './api/v1/admin/app-auth-clients.js';
const router = Router(); const router = Router();
@@ -19,8 +24,13 @@ router.use('/healthcheck', healthcheckRouter);
router.use('/api/v1/automatisch', automatischRouter); router.use('/api/v1/automatisch', automatischRouter);
router.use('/api/v1/users', usersRouter); router.use('/api/v1/users', usersRouter);
router.use('/api/v1/payment', paymentRouter); 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/saml-auth-providers', samlAuthProvidersRouter);
router.use('/api/v1/admin/roles', rolesRouter); router.use('/api/v1/admin/roles', rolesRouter);
router.use('/api/v1/admin/permissions', permissionsRouter); router.use('/api/v1/admin/permissions', permissionsRouter);
router.use('/api/v1/admin/users', adminUsersRouter);
router.use('/api/v1/admin/app-auth-clients', adminAppAuthClientsRouter);
export default router; export default router;

View File

@@ -1,16 +1,9 @@
import { Router } from 'express'; import { Router } from 'express';
import asyncHandler from 'express-async-handler';
import webhooksHandler from '../controllers/paddle/webhooks.ee.js'; import webhooksHandler from '../controllers/paddle/webhooks.ee.js';
const router = Router(); const router = Router();
const exposeError = (handler) => async (req, res, next) => { router.post('/webhooks', asyncHandler(webhooksHandler));
try {
await handler(req, res, next);
} catch (err) {
next(err);
}
};
router.post('/webhooks', exposeError(webhooksHandler));
export default router; export default router;

View File

@@ -0,0 +1,10 @@
const appAuthClientSerializer = (appAuthClient) => {
return {
id: appAuthClient.id,
appConfigId: appAuthClient.appConfigId,
name: appAuthClient.name,
active: appAuthClient.active,
};
};
export default appAuthClientSerializer;

View File

@@ -0,0 +1,22 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { createAppAuthClient } from '../../test/factories/app-auth-client';
import appAuthClientSerializer from './app-auth-client';
describe('appAuthClient serializer', () => {
let appAuthClient;
beforeEach(async () => {
appAuthClient = await createAppAuthClient();
});
it('should return app auth client data', async () => {
const expectedPayload = {
id: appAuthClient.id,
appConfigId: appAuthClient.appConfigId,
name: appAuthClient.name,
active: appAuthClient.active,
};
expect(appAuthClientSerializer(appAuthClient)).toEqual(expectedPayload);
});
});

View File

@@ -0,0 +1,12 @@
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

@@ -0,0 +1,20 @@
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

@@ -0,0 +1,18 @@
import stepSerializer from './step.js';
const flowSerializer = (flow) => {
let flowData = {
id: flow.id,
name: flow.name,
active: flow.active,
status: flow.status,
};
if (flow.steps) {
flowData.steps = flow.steps.map((step) => stepSerializer(step));
}
return flowData;
};
export default flowSerializer;

View File

@@ -0,0 +1,44 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { createFlow } from '../../test/factories/flow';
import flowSerializer from './flow';
import stepSerializer from './step';
import { createStep } from '../../test/factories/step';
describe('flowSerializer', () => {
let flow, stepOne, stepTwo;
beforeEach(async () => {
flow = await createFlow();
stepOne = await createStep({
flowId: flow.id,
type: 'trigger',
});
stepTwo = await createStep({
flowId: flow.id,
type: 'action',
});
});
it('should return flow data', async () => {
const expectedPayload = {
id: flow.id,
name: flow.name,
active: flow.active,
status: flow.status,
};
expect(flowSerializer(flow)).toEqual(expectedPayload);
});
it('should return flow data with the steps', async () => {
flow.steps = [stepOne, stepTwo];
const expectedPayload = {
steps: [stepSerializer(stepOne), stepSerializer(stepTwo)],
};
expect(flowSerializer(flow)).toMatchObject(expectedPayload);
});
});

View File

@@ -2,12 +2,20 @@ import userSerializer from './user.js';
import roleSerializer from './role.js'; import roleSerializer from './role.js';
import permissionSerializer from './permission.js'; import permissionSerializer from './permission.js';
import samlAuthProviderSerializer from './saml-auth-provider.ee.js'; 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 = { const serializers = {
User: userSerializer, User: userSerializer,
Role: roleSerializer, Role: roleSerializer,
Permission: permissionSerializer, Permission: permissionSerializer,
SamlAuthProvider: samlAuthProviderSerializer, SamlAuthProvider: samlAuthProviderSerializer,
AppAuthClient: appAuthClientSerializer,
Flow: flowSerializer,
Step: stepSerializer,
App: appSerializer,
}; };
export default serializers; export default serializers;

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
import { describe, it, expect, beforeEach } from 'vitest'; import { describe, it, expect, beforeEach } from 'vitest';
import { createRole } from '../../test/factories/role'; import { createRole } from '../../test/factories/role';
import roleSerializer from './role'; import roleSerializer from './role';
import permissionSerializer from './permission';
import { createPermission } from '../../test/factories/permission'; import { createPermission } from '../../test/factories/permission';
describe('roleSerializer', () => { describe('roleSerializer', () => {
@@ -28,8 +29,8 @@ describe('roleSerializer', () => {
name: role.name, name: role.name,
key: role.key, key: role.key,
description: role.description, description: role.description,
createdAt: role.createdAt, createdAt: role.createdAt.getTime(),
updatedAt: role.updatedAt, updatedAt: role.updatedAt.getTime(),
isAdmin: role.isAdmin, isAdmin: role.isAdmin,
}; };
@@ -40,7 +41,10 @@ describe('roleSerializer', () => {
role.permissions = [permissionOne, permissionTwo]; role.permissions = [permissionOne, permissionTwo];
const expectedPayload = { const expectedPayload = {
permissions: [permissionOne, permissionTwo], permissions: [
permissionSerializer(permissionOne),
permissionSerializer(permissionTwo),
],
}; };
expect(roleSerializer(role)).toMatchObject(expectedPayload); expect(roleSerializer(role)).toMatchObject(expectedPayload);

View File

@@ -0,0 +1,15 @@
const stepSerializer = (step) => {
return {
id: step.id,
type: step.type,
key: step.key,
appKey: step.appKey,
iconUrl: step.iconUrl,
webhookUrl: step.webhookUrl,
status: step.status,
position: step.position,
parameters: step.parameters,
};
};
export default stepSerializer;

View File

@@ -0,0 +1,27 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { createStep } from '../../test/factories/step';
import stepSerializer from './step';
describe('stepSerializer', () => {
let step;
beforeEach(async () => {
step = await createStep();
});
it('should return step data', async () => {
const expectedPayload = {
id: step.id,
type: step.type,
key: step.key,
appKey: step.appKey,
iconUrl: step.iconUrl,
webhookUrl: step.webhookUrl,
status: step.status,
position: step.position,
parameters: step.parameters,
};
expect(stepSerializer(step)).toEqual(expectedPayload);
});
});

View File

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

View File

@@ -4,6 +4,8 @@ import appConfig from '../config/app';
import { createUser } from '../../test/factories/user'; import { createUser } from '../../test/factories/user';
import { createPermission } from '../../test/factories/permission'; import { createPermission } from '../../test/factories/permission';
import userSerializer from './user'; import userSerializer from './user';
import roleSerializer from './role';
import permissionSerializer from './permission';
describe('userSerializer', () => { describe('userSerializer', () => {
let user, role, permissionOne, permissionTwo; let user, role, permissionOne, permissionTwo;
@@ -29,11 +31,11 @@ describe('userSerializer', () => {
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(false); vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(false);
const expectedPayload = { const expectedPayload = {
createdAt: user.createdAt, createdAt: user.createdAt.getTime(),
email: user.email, email: user.email,
fullName: user.fullName, fullName: user.fullName,
id: user.id, id: user.id,
updatedAt: user.updatedAt, updatedAt: user.updatedAt.getTime(),
}; };
expect(userSerializer(user)).toEqual(expectedPayload); expect(userSerializer(user)).toEqual(expectedPayload);
@@ -43,7 +45,7 @@ describe('userSerializer', () => {
user.role = role; user.role = role;
const expectedPayload = { const expectedPayload = {
role, role: roleSerializer(role),
}; };
expect(userSerializer(user)).toMatchObject(expectedPayload); expect(userSerializer(user)).toMatchObject(expectedPayload);
@@ -53,7 +55,10 @@ describe('userSerializer', () => {
user.permissions = [permissionOne, permissionTwo]; user.permissions = [permissionOne, permissionTwo];
const expectedPayload = { const expectedPayload = {
permissions: [permissionOne, permissionTwo], permissions: [
permissionSerializer(permissionOne),
permissionSerializer(permissionTwo),
],
}; };
expect(userSerializer(user)).toMatchObject(expectedPayload); expect(userSerializer(user)).toMatchObject(expectedPayload);
@@ -62,7 +67,7 @@ describe('userSerializer', () => {
it('should return user data with trial expiry date', async () => { it('should return user data with trial expiry date', async () => {
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(true); vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(true);
await user.$query().patch({ await user.$query().patchAndFetch({
trialExpiryDate: DateTime.now().plus({ days: 30 }).toISODate(), trialExpiryDate: DateTime.now().plus({ days: 30 }).toISODate(),
}); });

View File

@@ -0,0 +1,25 @@
import { faker } from '@faker-js/faker';
import { createAppConfig } from './app-config.js';
import AppAuthClient from '../../src/models/app-auth-client';
const formattedAuthDefaults = {
oAuthRedirectUrl: faker.internet.url(),
instanceUrl: faker.internet.url(),
clientId: faker.string.uuid(),
clientSecret: faker.string.uuid(),
};
export const createAppAuthClient = async (params = {}) => {
params.name = params?.name || faker.person.fullName();
params.id = params?.id || faker.string.uuid();
params.appConfigId = params?.appConfigId || (await createAppConfig()).id;
params.active = params?.active ?? true;
params.formattedAuthDefaults =
params?.formattedAuthDefaults || formattedAuthDefaults;
const appAuthClient = await AppAuthClient.query()
.insert(params)
.returning('*');
return appAuthClient;
};

View File

@@ -0,0 +1,13 @@
import AppConfig from '../../src/models/app-config.js';
export const createAppConfig = async (params = {}) => {
const appConfigData = {
key: params?.key || 'gitlab',
};
const appConfig = await AppConfig.query()
.insert(appConfigData)
.returning('*');
return appConfig;
};

View File

@@ -0,0 +1,19 @@
const getAdminAppAuthClientMock = (appAuthClient) => {
return {
data: {
appConfigId: appAuthClient.appConfigId,
name: appAuthClient.name,
id: appAuthClient.id,
active: appAuthClient.active,
},
meta: {
count: 1,
currentPage: null,
isArray: false,
totalPages: null,
type: 'AppAuthClient',
},
};
};
export default getAdminAppAuthClientMock;

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,21 @@
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

@@ -0,0 +1,18 @@
const infoMock = () => {
return {
data: {
isCloud: false,
isMation: false,
isEnterprise: true,
},
meta: {
count: 1,
currentPage: null,
isArray: false,
totalPages: null,
type: 'Object',
},
};
};
export default infoMock;

View File

@@ -0,0 +1,19 @@
const licenseMock = () => {
return {
data: {
expireAt: '2025-12-31T23:59:59Z',
id: '123',
name: 'license-name',
verified: true,
},
meta: {
count: 1,
currentPage: null,
isArray: false,
totalPages: null,
type: 'Object',
},
};
};
export default licenseMock;

View File

@@ -0,0 +1,32 @@
const getFlowMock = async (flow, steps) => {
const data = {
active: flow.active,
id: flow.id,
name: flow.name,
status: flow.active ? 'published' : 'draft',
steps: steps.map((step) => ({
appKey: step.appKey,
iconUrl: step.iconUrl,
id: step.id,
key: step.key,
parameters: step.parameters,
position: step.position,
status: step.status,
type: step.type,
webhookUrl: step.webhookUrl,
})),
};
return {
data: data,
meta: {
count: 1,
currentPage: null,
isArray: false,
totalPages: null,
type: 'Flow',
},
};
};
export default getFlowMock;

View File

@@ -0,0 +1,19 @@
const getAppAuthClientMock = (appAuthClient) => {
return {
data: {
name: appAuthClient.name,
id: appAuthClient.id,
appConfigId: appAuthClient.appConfigId,
active: appAuthClient.active,
},
meta: {
count: 1,
currentPage: null,
isArray: false,
totalPages: null,
type: 'AppAuthClient',
},
};
};
export default getAppAuthClientMock;

View File

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

View File

@@ -0,0 +1,14 @@
const getInvoicesMock = async (invoices) => {
return {
data: invoices,
meta: {
count: invoices.length,
currentPage: null,
isArray: true,
totalPages: null,
type: 'Object',
},
};
};
export default getInvoicesMock;

View File

@@ -41,6 +41,15 @@ export default defineConfig({
{ text: 'Connection', link: '/apps/carbone/connection' }, { 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', text: 'DeepL',
collapsible: true, collapsible: true,
@@ -305,7 +314,7 @@ export default defineConfig({
collapsed: true, collapsed: true,
items: [ items: [
{ text: 'Actions', link: '/apps/removebg/actions' }, { text: 'Actions', link: '/apps/removebg/actions' },
{ text: 'Connection', link: '/apps/removebg/connection' } { text: 'Connection', link: '/apps/removebg/connection' },
], ],
}, },
{ {

View File

@@ -0,0 +1,14 @@
---
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

@@ -0,0 +1,3 @@
# 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

@@ -1,8 +1,6 @@
--- ---
favicon: /favicons/discord.svg favicon: /favicons/discord.svg
items: items:
- name: Change a scheduled event
desc: Changes a scheduled event.
- name: Send a message to channel - name: Send a message to channel
desc: Sends a message to a specific channel you specify. desc: Sends a message to a specific channel you specify.
- name: Create a scheduled event - name: Create a scheduled event

View File

@@ -0,0 +1,13 @@
<?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>

After

Width:  |  Height:  |  Size: 704 B

View File

@@ -1742,6 +1742,13 @@
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.11.tgz#a5d300008960bb39677c46bf16f53ec70d8dee04" resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.11.tgz#a5d300008960bb39677c46bf16f53ec70d8dee04"
integrity sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw== integrity sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw==
"@eslint-community/eslint-utils@^4.4.0":
version "4.4.0"
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==
dependencies:
eslint-visitor-keys "^3.3.0"
"@eslint/eslintrc@^1.0.5": "@eslint/eslintrc@^1.0.5":
version "1.0.5" version "1.0.5"
resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.5.tgz" resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.5.tgz"
@@ -3902,6 +3909,11 @@
resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz" resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz"
integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==
"@types/json-schema@^7.0.12":
version "7.0.15"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
"@types/json5@^0.0.29": "@types/json5@^0.0.29":
version "0.0.29" version "0.0.29"
resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz"
@@ -4078,6 +4090,11 @@
resolved "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz" resolved "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz"
integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==
"@types/semver@^7.5.0":
version "7.5.8"
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e"
integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==
"@types/send@*": "@types/send@*":
version "0.17.1" version "0.17.1"
resolved "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz" resolved "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz"
@@ -4233,6 +4250,14 @@
"@typescript-eslint/types" "5.10.0" "@typescript-eslint/types" "5.10.0"
"@typescript-eslint/visitor-keys" "5.10.0" "@typescript-eslint/visitor-keys" "5.10.0"
"@typescript-eslint/scope-manager@7.0.2":
version "7.0.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.0.2.tgz#6ec4cc03752758ddd1fdaae6fbd0ed9a2ca4fe63"
integrity sha512-l6sa2jF3h+qgN2qUMjVR3uCNGjWw4ahGfzIYsCtFrQJCjhbrDPdiihYT8FnnqFwsWX+20hK592yX9I2rxKTP4g==
dependencies:
"@typescript-eslint/types" "7.0.2"
"@typescript-eslint/visitor-keys" "7.0.2"
"@typescript-eslint/type-utils@5.10.0": "@typescript-eslint/type-utils@5.10.0":
version "5.10.0" version "5.10.0"
resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.10.0.tgz" resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.10.0.tgz"
@@ -4247,6 +4272,11 @@
resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.10.0.tgz" resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.10.0.tgz"
integrity sha512-wUljCgkqHsMZbw60IbOqT/puLfyqqD5PquGiBo1u1IS3PLxdi3RDGlyf032IJyh+eQoGhz9kzhtZa+VC4eWTlQ== integrity sha512-wUljCgkqHsMZbw60IbOqT/puLfyqqD5PquGiBo1u1IS3PLxdi3RDGlyf032IJyh+eQoGhz9kzhtZa+VC4eWTlQ==
"@typescript-eslint/types@7.0.2":
version "7.0.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.0.2.tgz#b6edd108648028194eb213887d8d43ab5750351c"
integrity sha512-ZzcCQHj4JaXFjdOql6adYV4B/oFOFjPOC9XYwCaZFRvqN8Llfvv4gSxrkQkd2u4Ci62i2c6W6gkDwQJDaRc4nA==
"@typescript-eslint/typescript-estree@5.10.0": "@typescript-eslint/typescript-estree@5.10.0":
version "5.10.0" version "5.10.0"
resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.10.0.tgz" resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.10.0.tgz"
@@ -4260,6 +4290,20 @@
semver "^7.3.5" semver "^7.3.5"
tsutils "^3.21.0" tsutils "^3.21.0"
"@typescript-eslint/typescript-estree@7.0.2":
version "7.0.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.0.2.tgz#3c6dc8a3b9799f4ef7eca0d224ded01974e4cb39"
integrity sha512-3AMc8khTcELFWcKcPc0xiLviEvvfzATpdPj/DXuOGIdQIIFybf4DMT1vKRbuAEOFMwhWt7NFLXRkbjsvKZQyvw==
dependencies:
"@typescript-eslint/types" "7.0.2"
"@typescript-eslint/visitor-keys" "7.0.2"
debug "^4.3.4"
globby "^11.1.0"
is-glob "^4.0.3"
minimatch "9.0.3"
semver "^7.5.4"
ts-api-utils "^1.0.1"
"@typescript-eslint/utils@5.10.0": "@typescript-eslint/utils@5.10.0":
version "5.10.0" version "5.10.0"
resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.10.0.tgz" resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.10.0.tgz"
@@ -4272,6 +4316,19 @@
eslint-scope "^5.1.1" eslint-scope "^5.1.1"
eslint-utils "^3.0.0" eslint-utils "^3.0.0"
"@typescript-eslint/utils@^7.0.2":
version "7.0.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.0.2.tgz#8756123054cd934c8ba7db6a6cffbc654b10b5c4"
integrity sha512-PZPIONBIB/X684bhT1XlrkjNZJIEevwkKDsdwfiu1WeqBxYEEdIgVDgm8/bbKHVu+6YOpeRqcfImTdImx/4Bsw==
dependencies:
"@eslint-community/eslint-utils" "^4.4.0"
"@types/json-schema" "^7.0.12"
"@types/semver" "^7.5.0"
"@typescript-eslint/scope-manager" "7.0.2"
"@typescript-eslint/types" "7.0.2"
"@typescript-eslint/typescript-estree" "7.0.2"
semver "^7.5.4"
"@typescript-eslint/visitor-keys@5.10.0": "@typescript-eslint/visitor-keys@5.10.0":
version "5.10.0" version "5.10.0"
resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.10.0.tgz" resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.10.0.tgz"
@@ -4280,6 +4337,14 @@
"@typescript-eslint/types" "5.10.0" "@typescript-eslint/types" "5.10.0"
eslint-visitor-keys "^3.0.0" eslint-visitor-keys "^3.0.0"
"@typescript-eslint/visitor-keys@7.0.2":
version "7.0.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.0.2.tgz#2899b716053ad7094962beb895d11396fc12afc7"
integrity sha512-8Y+YiBmqPighbm5xA2k4wKTxRzx9EkBu7Rlw+WHqMvRJ3RPz/BMBO9b2ru0LUNmXg120PHUXD5+SWFy2R8DqlQ==
dependencies:
"@typescript-eslint/types" "7.0.2"
eslint-visitor-keys "^3.4.1"
"@ucast/core@^1.0.0", "@ucast/core@^1.4.1", "@ucast/core@^1.6.1": "@ucast/core@^1.0.0", "@ucast/core@^1.4.1", "@ucast/core@^1.6.1":
version "1.10.2" version "1.10.2"
resolved "https://registry.npmjs.org/@ucast/core/-/core-1.10.2.tgz" resolved "https://registry.npmjs.org/@ucast/core/-/core-1.10.2.tgz"
@@ -7750,6 +7815,11 @@ eslint-visitor-keys@^3.3.0:
resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz" resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz"
integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==
eslint-visitor-keys@^3.4.1:
version "3.4.3"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800"
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
eslint-webpack-plugin@^3.1.1: eslint-webpack-plugin@^3.1.1:
version "3.1.1" version "3.1.1"
resolved "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-3.1.1.tgz" resolved "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-3.1.1.tgz"
@@ -7977,6 +8047,11 @@ expect@^27.4.6:
jest-matcher-utils "^27.4.6" jest-matcher-utils "^27.4.6"
jest-message-util "^27.4.6" jest-message-util "^27.4.6"
express-async-handler@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/express-async-handler/-/express-async-handler-1.2.0.tgz#ffc9896061d90f8d2e71a2d2b8668db5b0934391"
integrity sha512-rCSVtPXRmQSW8rmik/AIb2P0op6l7r1fMW538yyvTMltCO4xQEWMmobfrIxN2V1/mVrgxB8Az3reYF6yUZw37w==
express-basic-auth@^1.2.1: express-basic-auth@^1.2.1:
version "1.2.1" version "1.2.1"
resolved "https://registry.npmjs.org/express-basic-auth/-/express-basic-auth-1.2.1.tgz" resolved "https://registry.npmjs.org/express-basic-auth/-/express-basic-auth-1.2.1.tgz"
@@ -8695,7 +8770,7 @@ globals@^13.6.0, globals@^13.9.0:
dependencies: dependencies:
type-fest "^0.20.2" type-fest "^0.20.2"
globby@^11.0.1, globby@^11.0.2, globby@^11.0.3, globby@^11.0.4: globby@^11.0.1, globby@^11.0.2, globby@^11.0.3, globby@^11.0.4, globby@^11.1.0:
version "11.1.0" version "11.1.0"
resolved "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz" resolved "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz"
integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==
@@ -11032,6 +11107,13 @@ minimatch@3.0.4, minimatch@^3.0.4:
dependencies: dependencies:
brace-expansion "^1.1.7" brace-expansion "^1.1.7"
minimatch@9.0.3:
version "9.0.3"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825"
integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==
dependencies:
brace-expansion "^2.0.1"
minimatch@^5.0.1: minimatch@^5.0.1:
version "5.1.0" version "5.1.0"
resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz" resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz"
@@ -14127,6 +14209,13 @@ semver@^7.1.1, semver@^7.1.3, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semve
dependencies: dependencies:
lru-cache "^6.0.0" lru-cache "^6.0.0"
semver@^7.5.4:
version "7.6.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d"
integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==
dependencies:
lru-cache "^6.0.0"
send@0.17.2: send@0.17.2:
version "0.17.2" version "0.17.2"
resolved "https://registry.npmjs.org/send/-/send-0.17.2.tgz" resolved "https://registry.npmjs.org/send/-/send-0.17.2.tgz"
@@ -15263,6 +15352,11 @@ tryer@^1.0.1:
resolved "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz" resolved "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz"
integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA== integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==
ts-api-utils@^1.0.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.2.1.tgz#f716c7e027494629485b21c0df6180f4d08f5e8b"
integrity sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA==
ts-invariant@^0.10.3: ts-invariant@^0.10.3:
version "0.10.3" version "0.10.3"
resolved "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.10.3.tgz" resolved "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.10.3.tgz"