diff --git a/packages/backend/src/controllers/api/v1/apps/get-apps.ee.js b/packages/backend/src/controllers/api/v1/apps/get-apps.ee.js new file mode 100644 index 00000000..be6e112e --- /dev/null +++ b/packages/backend/src/controllers/api/v1/apps/get-apps.ee.js @@ -0,0 +1,16 @@ +import App from '../../../../models/app.js'; +import { renderObject } from '../../../../helpers/renderer.js'; + +export default async (request, response) => { + let apps = await App.findAll(request.query.name); + + if (request.query.onlyWithTriggers) { + apps = apps.filter((app) => app.triggers?.length); + } + + if (request.query.onlyWithActions) { + apps = apps.filter((app) => app.actions?.length); + } + + renderObject(response, apps, { serializer: 'App' }); +}; diff --git a/packages/backend/src/controllers/api/v1/apps/get-apps.ee.test.js b/packages/backend/src/controllers/api/v1/apps/get-apps.ee.test.js new file mode 100644 index 00000000..20a94ee1 --- /dev/null +++ b/packages/backend/src/controllers/api/v1/apps/get-apps.ee.test.js @@ -0,0 +1,63 @@ +import { vi, describe, it, expect, beforeEach } from 'vitest'; +import request from 'supertest'; +import App from '../../../../models/app.js'; +import app from '../../../../app.js'; +import { createApiToken } from '../../../../../test/factories/api-token.js'; +import getAppsMock from '../../../../../test/mocks/rest/api/v1/apps/get-apps.js'; +import * as license from '../../../../helpers/license.ee.js'; + +describe('GET /api/v1/apps', () => { + let apps, token; + + beforeEach(async () => { + vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true); + token = (await createApiToken()).token; + apps = await App.findAll(); + }); + + it('should return all apps', async () => { + const response = await request(app) + .get('/api/v1/apps') + .set('x-api-token', token) + .expect(200); + + const expectedPayload = getAppsMock(apps); + expect(response.body).toStrictEqual(expectedPayload); + }); + + it('should return all apps filtered by name', async () => { + const appsWithNameGit = apps.filter((app) => app.name.includes('Git')); + + const response = await request(app) + .get('/api/v1/apps?name=Git') + .set('x-api-token', token) + .expect(200); + + const expectedPayload = getAppsMock(appsWithNameGit); + expect(response.body).toStrictEqual(expectedPayload); + }); + + it('should return only the apps with triggers', async () => { + const appsWithTriggers = apps.filter((app) => app.triggers?.length > 0); + + const response = await request(app) + .get('/api/v1/apps?onlyWithTriggers=true') + .set('x-api-token', token) + .expect(200); + + const expectedPayload = getAppsMock(appsWithTriggers); + expect(response.body).toStrictEqual(expectedPayload); + }); + + it('should return only the apps with actions', async () => { + const appsWithActions = apps.filter((app) => app.actions?.length > 0); + + const response = await request(app) + .get('/api/v1/apps?onlyWithActions=true') + .set('x-api-token', token) + .expect(200); + + const expectedPayload = getAppsMock(appsWithActions); + expect(response.body).toStrictEqual(expectedPayload); + }); +}); diff --git a/packages/backend/src/routes/api/index.js b/packages/backend/src/routes/api/index.js new file mode 100644 index 00000000..f05545e6 --- /dev/null +++ b/packages/backend/src/routes/api/index.js @@ -0,0 +1,8 @@ +import { Router } from 'express'; +import appsRouter from './v1/apps.js'; + +const router = Router(); + +router.use('/v1/apps', appsRouter); + +export default router; diff --git a/packages/backend/src/routes/api/v1/apps.js b/packages/backend/src/routes/api/v1/apps.js new file mode 100644 index 00000000..addfb8eb --- /dev/null +++ b/packages/backend/src/routes/api/v1/apps.js @@ -0,0 +1,8 @@ +import { Router } from 'express'; +import getAppsAction from '../../../controllers/api/v1/apps/get-apps.ee.js'; + +const router = Router(); + +router.get('/', getAppsAction); + +export default router; diff --git a/packages/backend/src/routes/index.js b/packages/backend/src/routes/index.js index 74cf5301..ba058330 100644 --- a/packages/backend/src/routes/index.js +++ b/packages/backend/src/routes/index.js @@ -2,57 +2,17 @@ import { Router } from 'express'; import webhooksRouter from './webhooks.js'; import paddleRouter from './paddle.ee.js'; import healthcheckRouter from './healthcheck.js'; -import automatischRouter from './internal/api/v1/automatisch.js'; -import accessTokensRouter from './internal/api/v1/access-tokens.js'; -import usersRouter from './internal/api/v1/users.js'; -import paymentRouter from './internal/api/v1/payment.ee.js'; -import flowsRouter from './internal/api/v1/flows.js'; -import stepsRouter from './internal/api/v1/steps.js'; -import appsRouter from './internal/api/v1/apps.js'; -import connectionsRouter from './internal/api/v1/connections.js'; -import executionsRouter from './internal/api/v1/executions.js'; -import samlAuthProvidersRouter from './internal/api/v1/saml-auth-providers.ee.js'; -import adminAppsRouter from './internal/api/v1/admin/apps.ee.js'; -import adminConfigRouter from './internal/api/v1/admin/config.ee.js'; -import adminSamlAuthProvidersRouter from './internal/api/v1/admin/saml-auth-providers.ee.js'; -import adminTemplatesRouter from './internal/api/v1/admin/templates.ee.js'; -import adminApiTokensRouter from './internal/api/v1/admin/api-tokens.ee.js'; -import templatesRouter from './internal/api/v1/templates.ee.js'; -import rolesRouter from './internal/api/v1/admin/roles.ee.js'; -import permissionsRouter from './internal/api/v1/admin/permissions.ee.js'; -import adminUsersRouter from './internal/api/v1/admin/users.ee.js'; -import installationUsersRouter from './internal/api/v1/installation/users.js'; -import foldersRouter from './internal/api/v1/folders.js'; +import apiRouter from './api/index.js'; +import internalApiRouter from './internal/api/index.js'; +import { checkIsEnterprise } from '../helpers/check-is-enterprise.js'; +import { authenticateApiToken } from '../helpers/authenticate-api-token.ee.js'; const router = Router(); router.use('/webhooks', webhooksRouter); router.use('/paddle', paddleRouter); router.use('/healthcheck', healthcheckRouter); - -router.use('/internal/api/v1/automatisch', automatischRouter); -router.use('/internal/api/v1/access-tokens', accessTokensRouter); -router.use('/internal/api/v1/users', usersRouter); -router.use('/internal/api/v1/payment', paymentRouter); -router.use('/internal/api/v1/apps', appsRouter); -router.use('/internal/api/v1/connections', connectionsRouter); -router.use('/internal/api/v1/flows', flowsRouter); -router.use('/internal/api/v1/steps', stepsRouter); -router.use('/internal/api/v1/executions', executionsRouter); -router.use('/internal/api/v1/saml-auth-providers', samlAuthProvidersRouter); -router.use('/internal/api/v1/admin/apps', adminAppsRouter); -router.use('/internal/api/v1/admin/config', adminConfigRouter); -router.use('/internal/api/v1/admin/users', adminUsersRouter); -router.use('/internal/api/v1/admin/roles', rolesRouter); -router.use('/internal/api/v1/admin/permissions', permissionsRouter); -router.use( - '/internal/api/v1/admin/saml-auth-providers', - adminSamlAuthProvidersRouter -); -router.use('/internal/api/v1/admin/templates', adminTemplatesRouter); -router.use('/internal/api/v1/admin/api-tokens', adminApiTokensRouter); -router.use('/internal/api/v1/templates', templatesRouter); -router.use('/internal/api/v1/installation/users', installationUsersRouter); -router.use('/internal/api/v1/folders', foldersRouter); +router.use('/api', checkIsEnterprise, authenticateApiToken, apiRouter); +router.use('/internal/api', internalApiRouter); export default router; diff --git a/packages/backend/src/routes/internal/api/index.js b/packages/backend/src/routes/internal/api/index.js new file mode 100644 index 00000000..14794960 --- /dev/null +++ b/packages/backend/src/routes/internal/api/index.js @@ -0,0 +1,49 @@ +import { Router } from 'express'; + +import automatischRouter from './v1/automatisch.js'; +import accessTokensRouter from './v1/access-tokens.js'; +import usersRouter from './v1/users.js'; +import paymentRouter from './v1/payment.ee.js'; +import flowsRouter from './v1/flows.js'; +import stepsRouter from './v1/steps.js'; +import appsRouter from './v1/apps.js'; +import connectionsRouter from './v1/connections.js'; +import executionsRouter from './v1/executions.js'; +import samlAuthProvidersRouter from './v1/saml-auth-providers.ee.js'; +import adminAppsRouter from './v1/admin/apps.ee.js'; +import adminConfigRouter from './v1/admin/config.ee.js'; +import adminSamlAuthProvidersRouter from './v1/admin/saml-auth-providers.ee.js'; +import adminTemplatesRouter from './v1/admin/templates.ee.js'; +import adminApiTokensRouter from './v1/admin/api-tokens.ee.js'; +import templatesRouter from './v1/templates.ee.js'; +import rolesRouter from './v1/admin/roles.ee.js'; +import permissionsRouter from './v1/admin/permissions.ee.js'; +import adminUsersRouter from './v1/admin/users.ee.js'; +import installationUsersRouter from './v1/installation/users.js'; +import foldersRouter from './v1/folders.js'; + +const router = Router(); + +router.use('/v1/automatisch', automatischRouter); +router.use('/v1/access-tokens', accessTokensRouter); +router.use('/v1/users', usersRouter); +router.use('/v1/payment', paymentRouter); +router.use('/v1/apps', appsRouter); +router.use('/v1/connections', connectionsRouter); +router.use('/v1/flows', flowsRouter); +router.use('/v1/steps', stepsRouter); +router.use('/v1/executions', executionsRouter); +router.use('/v1/saml-auth-providers', samlAuthProvidersRouter); +router.use('/v1/admin/apps', adminAppsRouter); +router.use('/v1/admin/config', adminConfigRouter); +router.use('/v1/admin/users', adminUsersRouter); +router.use('/v1/admin/roles', rolesRouter); +router.use('/v1/admin/permissions', permissionsRouter); +router.use('/v1/admin/saml-auth-providers', adminSamlAuthProvidersRouter); +router.use('/v1/admin/templates', adminTemplatesRouter); +router.use('/v1/admin/api-tokens', adminApiTokensRouter); +router.use('/v1/templates', templatesRouter); +router.use('/v1/installation/users', installationUsersRouter); +router.use('/v1/folders', foldersRouter); + +export default router; diff --git a/packages/backend/src/serializers/index.js b/packages/backend/src/serializers/index.js index 11e9755f..b441fa41 100644 --- a/packages/backend/src/serializers/index.js +++ b/packages/backend/src/serializers/index.js @@ -26,32 +26,32 @@ import configSerializer from './config.js'; import folderSerializer from './folder.js'; const serializers = { - AdminUser: adminUserSerializer, - User: userSerializer, - Role: roleSerializer, - Permission: permissionSerializer, - AdminSamlAuthProvider: adminSamlAuthProviderSerializer, - AdminTemplate: adminTemplateSerializer, + Action: actionSerializer, AdminApiToken: adminApiTokenSerializer, AdminApiTokenFull: adminApiTokenFullSerializer, - Template: templateSerializer, - SamlAuthProvider: samlAuthProviderSerializer, - RoleMapping: samlAuthProviderRoleMappingSerializer, - OAuthClient: oauthClientSerializer, - AppConfig: appConfigSerializer, - Flow: flowSerializer, - Step: stepSerializer, - Connection: connectionSerializer, + AdminSamlAuthProvider: adminSamlAuthProviderSerializer, + AdminTemplate: adminTemplateSerializer, + AdminUser: adminUserSerializer, App: appSerializer, - UserApp: userAppSerializer, + AppConfig: appConfigSerializer, Auth: authSerializer, - Trigger: triggerSerializer, - Action: actionSerializer, + Config: configSerializer, + Connection: connectionSerializer, Execution: executionSerializer, ExecutionStep: executionStepSerializer, - Subscription: subscriptionSerializer, - Config: configSerializer, + Flow: flowSerializer, Folder: folderSerializer, + OAuthClient: oauthClientSerializer, + Permission: permissionSerializer, + Role: roleSerializer, + RoleMapping: samlAuthProviderRoleMappingSerializer, + SamlAuthProvider: samlAuthProviderSerializer, + Step: stepSerializer, + Subscription: subscriptionSerializer, + Template: templateSerializer, + Trigger: triggerSerializer, + User: userSerializer, + UserApp: userAppSerializer, }; export default serializers; diff --git a/packages/backend/test/mocks/rest/api/v1/apps/get-apps.js b/packages/backend/test/mocks/rest/api/v1/apps/get-apps.js new file mode 100644 index 00000000..e1892d42 --- /dev/null +++ b/packages/backend/test/mocks/rest/api/v1/apps/get-apps.js @@ -0,0 +1,24 @@ +const getAppsMock = (apps) => { + const appsData = apps.map((app) => ({ + authDocUrl: app.authDocUrl, + iconUrl: app.iconUrl, + key: app.key, + name: app.name, + primaryColor: app.primaryColor, + supportsConnections: app.supportsConnections, + supportsOauthClients: app?.auth?.generateAuthUrl ? true : false, + })); + + return { + data: appsData, + meta: { + count: appsData.length, + currentPage: null, + isArray: true, + totalPages: null, + type: 'Object', + }, + }; +}; + +export default getAppsMock;