diff --git a/package-lock.json b/package-lock.json index bf010aa4a..0d99bade4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,6 @@ "@types/jsonwebtoken": "^8.5.8", "@types/lodash": "^4.14.182", "@types/microseconds": "^0.2.0", - "@types/multer": "^1.4.7", "@types/nodemailer": "^6.4.6", "@types/prismjs": "^1.26.0", "@types/qs": "^6.9.7", @@ -41,7 +40,6 @@ "latest-version": "^7.0.0", "lodash": "^4.17.21", "microseconds": "^0.2.0", - "multer": "^1.4.5-lts.1", "mustache": "^4.2.0", "nanoid": "^4.0.0", "next": "12.3.1", @@ -8849,14 +8847,6 @@ "tarn": "^3.0.1" } }, - "node_modules/@types/multer": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.7.tgz", - "integrity": "sha512-/SNsDidUFCvqqcWDwxv2feww/yqhNeTRL5CVoL3jU4Goc4kKEL10T7Eye65ZqPNi4HRx8sAEX59pV1aEH7drNA==", - "dependencies": { - "@types/express": "*" - } - }, "node_modules/@types/node": { "version": "18.0.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.3.tgz", @@ -22903,75 +22893,6 @@ "node": ">=12" } }, - "node_modules/multer": { - "version": "1.4.5-lts.1", - "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", - "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", - "dependencies": { - "append-field": "^1.0.0", - "busboy": "^1.0.0", - "concat-stream": "^1.5.2", - "mkdirp": "^0.5.4", - "object-assign": "^4.1.1", - "type-is": "^1.6.4", - "xtend": "^4.0.0" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/multer/node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "engines": [ - "node >= 0.8" - ], - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/multer/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/multer/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/multer/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/multer/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/mustache": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", @@ -39465,14 +39386,6 @@ "tarn": "^3.0.1" } }, - "@types/multer": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.7.tgz", - "integrity": "sha512-/SNsDidUFCvqqcWDwxv2feww/yqhNeTRL5CVoL3jU4Goc4kKEL10T7Eye65ZqPNi4HRx8sAEX59pV1aEH7drNA==", - "requires": { - "@types/express": "*" - } - }, "@types/node": { "version": "18.0.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.3.tgz", @@ -49963,68 +49876,6 @@ } } }, - "multer": { - "version": "1.4.5-lts.1", - "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", - "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", - "requires": { - "append-field": "^1.0.0", - "busboy": "^1.0.0", - "concat-stream": "^1.5.2", - "mkdirp": "^0.5.4", - "object-assign": "^4.1.1", - "type-is": "^1.6.4", - "xtend": "^4.0.0" - }, - "dependencies": { - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "requires": { - "minimist": "^1.2.6" - } - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, "mustache": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", diff --git a/src/__tests__/api/_test-utils/_activated-storage.ts b/src/__tests__/api/_test-utils/_activated-storage.ts deleted file mode 100644 index af973c799..000000000 --- a/src/__tests__/api/_test-utils/_activated-storage.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { createConfigDomainPersistenceService } from "backend/lib/config-persistence"; -import { IActivatedStorage } from "shared/types/storage"; - -const TEST_ACTIVATED_STORAGE: IActivatedStorage[] = [ - { - key: "s3", - }, - { - key: "google", - }, -]; - -export const setupActivatedStorageTestData = async ( - testActivatedStorage: IActivatedStorage[] = TEST_ACTIVATED_STORAGE -) => { - const configPersistenceService = - createConfigDomainPersistenceService( - "activated-storage" - ); - - await configPersistenceService.resetState("key", testActivatedStorage); -}; diff --git a/src/__tests__/api/_test-utils/_all.ts b/src/__tests__/api/_test-utils/_all.ts index 87d688bad..3bf43828a 100644 --- a/src/__tests__/api/_test-utils/_all.ts +++ b/src/__tests__/api/_test-utils/_all.ts @@ -10,7 +10,6 @@ import { setupUsersTestData } from "./_users"; import { setupIntegrationsEnvTestData } from "./_integrations-env"; import { setupActivatedActionTestData } from "./_activated-actions"; import { setupActionInstanceTestData } from "./_action-instances"; -import { setupActivatedStorageTestData } from "./_activated-storage"; import { setupTestDatabaseData } from "./_data"; import { portalTestData } from "./portal"; @@ -25,7 +24,6 @@ export const setupAllTestData = async (domains: DomainTypes[]) => { ["schema", setupSchemaTestData], ["data", setupTestDatabaseData], ["activated-actions", setupActivatedActionTestData], - ["activated-storage", setupActivatedStorageTestData], ["action-instances", setupActionInstanceTestData], ["constants", setupIntegrationsConstantsTestData], ["environment-variables", setupIntegrationsEnvTestData], diff --git a/src/__tests__/api/integrations/storage/[key]/index.spec.ts b/src/__tests__/api/integrations/storage/[key]/index.spec.ts index 9dc281d75..8d12f4074 100644 --- a/src/__tests__/api/integrations/storage/[key]/index.spec.ts +++ b/src/__tests__/api/integrations/storage/[key]/index.spec.ts @@ -1,16 +1,14 @@ import handler from "pages/api/integrations/storage/[key]/index"; import activeHandler from "pages/api/integrations/storage/active"; -import credentialsHandler from "pages/api/integrations/storage/[key]/credentials"; +import credentialsHandler from "pages/api/integrations/storage/credentials"; import { createAuthenticatedMocks, setupAllTestData, } from "__tests__/api/_test-utils"; -import { setupActivatedStorageTestData } from "__tests__/api/_test-utils/_activated-storage"; describe("/api/integrations/actions/[key]/index", () => { beforeAll(async () => { await setupAllTestData(["users"]); - await setupActivatedStorageTestData([{ key: "google" }, { key: "minio" }]); }); describe("POST", () => { @@ -43,21 +41,6 @@ describe("/api/integrations/actions/[key]/index", () => { `); }); - it("should not show the activated storage in list", async () => { - const { req: activeReq, res: activeRes } = createAuthenticatedMocks({ - method: "GET", - }); - - await activeHandler(activeReq, activeRes); - - expect(activeRes._getJSONData()).toMatchInlineSnapshot(` - [ - "google", - "minio", - ] - `); - }); - it("should not save the credentials", async () => { const { req: credentialsReq, res: credentialsRes } = createAuthenticatedMocks({ @@ -237,58 +220,4 @@ describe("/api/integrations/actions/[key]/index", () => { `); }); }); - - describe("DELETE", () => { - it("should deactivate activated storage", async () => { - const { req, res } = createAuthenticatedMocks({ - method: "DELETE", - query: { - key: "s3", - }, - }); - await handler(req, res); - - expect(res._getStatusCode()).toBe(204); - }); - - it("should remove activated action", async () => { - const { req: activeReq, res: activeRes } = createAuthenticatedMocks({ - method: "GET", - }); - - await activeHandler(activeReq, activeRes); - - expect(activeRes._getJSONData()).toMatchInlineSnapshot(` - [ - "google", - "minio", - ] - `); - }); - - it("should remove access to credentials", async () => { - const { req: credentialsReq, res: credentialsRes } = - createAuthenticatedMocks({ - method: "POST", - query: { - key: "s3", - }, - body: { - _password: "password", - }, - }); - await credentialsHandler(credentialsReq, credentialsRes); - - expect(credentialsRes._getStatusCode()).toBe(400); - expect(credentialsRes._getJSONData()).toMatchInlineSnapshot(` - { - "message": "No credentials available for AWS_S3", - "method": "POST", - "name": "BadRequestError", - "path": "", - "statusCode": 400, - } - `); - }); - }); }); diff --git a/src/__tests__/api/integrations/storage/active.spec.ts b/src/__tests__/api/integrations/storage/active.spec.ts deleted file mode 100644 index a595bdee4..000000000 --- a/src/__tests__/api/integrations/storage/active.spec.ts +++ /dev/null @@ -1,26 +0,0 @@ -import handler from "pages/api/integrations/storage/active"; -import { - createAuthenticatedMocks, - setupAllTestData, -} from "__tests__/api/_test-utils"; - -describe("/api/integrations/storage/active", () => { - beforeAll(async () => { - await setupAllTestData(["activated-storage"]); - }); - - it("should show all activated storage", async () => { - const { req, res } = createAuthenticatedMocks({ - method: "GET", - }); - - await handler(req, res); - - expect(res._getJSONData()).toMatchInlineSnapshot(` - [ - "s3", - "google", - ] - `); - }); -}); diff --git a/src/backend/actions/actions.service.ts b/src/backend/actions/actions.service.ts index 13a1a635e..e86fa74f0 100644 --- a/src/backend/actions/actions.service.ts +++ b/src/backend/actions/actions.service.ts @@ -26,6 +26,7 @@ import { } from "shared/types/actions"; import { IAccountProfile } from "shared/types/user"; import { compileTemplateString } from "shared/lib/strings/templates"; +import { sluggify } from "shared/lib/strings"; import { ACTION_INTEGRATIONS } from "./integrations"; export class ActionsApiService implements IApplicationService { @@ -206,8 +207,8 @@ export class ActionsApiService implements IApplicationService { ); const activationId = nanoid(); - const credentialsGroupKey = - ACTION_INTEGRATIONS[integrationKey].credentialsKey; + + const credentialsGroupKey = this.makeCredentialsGroupKey(integrationKey); await this._activatedActionsPersistenceService.createItem(activationId, { activationId, @@ -226,6 +227,10 @@ export class ActionsApiService implements IApplicationService { ); } + private makeCredentialsGroupKey(integrationKey: ActionIntegrationKeys) { + return sluggify(`ACTION__${integrationKey}`).toUpperCase(); + } + private async getIntegrationKeyFromActivatedActionId( activatedActionId: string ): Promise { diff --git a/src/backend/actions/integrations/http/index.ts b/src/backend/actions/integrations/http/index.ts index bd0cccbeb..f12418e41 100644 --- a/src/backend/actions/integrations/http/index.ts +++ b/src/backend/actions/integrations/http/index.ts @@ -36,7 +36,6 @@ const ACTION_CONFIGURATION_SCHEMA: IAppliedSchemaFormConfig = { export const HTTP_ACTION_INTEGRATION: IActionIntegrationsImplemention = { title: "HTTP", - credentialsKey: "HTTP", description: "Performs HTTP request", configurationSchema: {}, connect: async () => {}, diff --git a/src/backend/actions/integrations/index.ts b/src/backend/actions/integrations/index.ts index 60c04d32f..430a79483 100644 --- a/src/backend/actions/integrations/index.ts +++ b/src/backend/actions/integrations/index.ts @@ -23,8 +23,4 @@ export const ACTION_INTEGRATIONS: Record< [ActionIntegrationKeys.TWILIO]: TWILIO_ACTION_INTEGRATION, [ActionIntegrationKeys.POSTMARK]: POST_MARK_ACTION_INTEGRATION, [ActionIntegrationKeys.SEND_IN_BLUE]: SENDINBLUE_ACTION_INTEGRATION, - // zapier: SLACK_ACTION_INTEGRATION, }; - -// https://github.com/novuhq/novu/tree/next/providers -// MS teams diff --git a/src/backend/actions/integrations/mailgun/index.ts b/src/backend/actions/integrations/mailgun/index.ts index 12dadc142..79bd0c03a 100644 --- a/src/backend/actions/integrations/mailgun/index.ts +++ b/src/backend/actions/integrations/mailgun/index.ts @@ -24,7 +24,6 @@ const CONFIGURATION_SCHEMA: IAppliedSchemaFormConfig = { export const MAIL_GUN_ACTION_INTEGRATION: IActionIntegrationsImplemention = { title: "Mail Gun", - credentialsKey: "MAILGUN", description: "Send emails through Mailgun", configurationSchema: CONFIGURATION_SCHEMA, connect: async (config: IActionConfig) => config, diff --git a/src/backend/actions/integrations/postmark/index.ts b/src/backend/actions/integrations/postmark/index.ts index 09f387c6f..a83e1cf0d 100644 --- a/src/backend/actions/integrations/postmark/index.ts +++ b/src/backend/actions/integrations/postmark/index.ts @@ -16,7 +16,6 @@ const CONFIGURATION_SCHEMA: IAppliedSchemaFormConfig = { export const POST_MARK_ACTION_INTEGRATION: IActionIntegrationsImplemention = { title: "Postmark", - credentialsKey: "POSTMARK", description: "Send emails through Postmark", configurationSchema: CONFIGURATION_SCHEMA, connect: async (config: IActionConfig) => config, diff --git a/src/backend/actions/integrations/sendgrid/index.ts b/src/backend/actions/integrations/sendgrid/index.ts index 11e770270..6a9a83bea 100644 --- a/src/backend/actions/integrations/sendgrid/index.ts +++ b/src/backend/actions/integrations/sendgrid/index.ts @@ -16,7 +16,6 @@ const CONFIGURATION_SCHEMA: IAppliedSchemaFormConfig = { export const SEND_GRID_ACTION_INTEGRATION: IActionIntegrationsImplemention = { title: "SendGrid", - credentialsKey: "SENDGRID", description: "Send emails through SendGrid", configurationSchema: CONFIGURATION_SCHEMA, connect: async (config: IActionConfig) => config, diff --git a/src/backend/actions/integrations/sendinblue/index.ts b/src/backend/actions/integrations/sendinblue/index.ts index 4f66aa5d8..04e872e33 100644 --- a/src/backend/actions/integrations/sendinblue/index.ts +++ b/src/backend/actions/integrations/sendinblue/index.ts @@ -16,7 +16,6 @@ const CONFIGURATION_SCHEMA: IAppliedSchemaFormConfig = { export const SENDINBLUE_ACTION_INTEGRATION: IActionIntegrationsImplemention = { title: "SendInBlue", - credentialsKey: "SENDINBLUE", description: "Send emails through SendInBlue", configurationSchema: CONFIGURATION_SCHEMA, connect: async (config: IActionConfig) => config, diff --git a/src/backend/actions/integrations/slack/index.ts b/src/backend/actions/integrations/slack/index.ts index 29fd001dd..594c4c612 100644 --- a/src/backend/actions/integrations/slack/index.ts +++ b/src/backend/actions/integrations/slack/index.ts @@ -16,7 +16,6 @@ const CONFIGURATION_SCHEMA: IAppliedSchemaFormConfig = { export const SLACK_ACTION_INTEGRATION: IActionIntegrationsImplemention = { title: "Slack", - credentialsKey: "SLACK", description: "Send messages to your Slack channels", configurationSchema: CONFIGURATION_SCHEMA, connect: async (config: IActionConfig) => config, diff --git a/src/backend/actions/integrations/smtp/index.ts b/src/backend/actions/integrations/smtp/index.ts index 62d3723e6..bf848b2f9 100644 --- a/src/backend/actions/integrations/smtp/index.ts +++ b/src/backend/actions/integrations/smtp/index.ts @@ -41,7 +41,6 @@ const CONFIGURATION_SCHEMA: IAppliedSchemaFormConfig = { export const SMTP_ACTION_INTEGRATION: IActionIntegrationsImplemention = { title: "SMTP", - credentialsKey: "SMTP", description: "Send emails through SMTP", configurationSchema: CONFIGURATION_SCHEMA, connect: async (config: IActionConfig) => { diff --git a/src/backend/actions/integrations/twilio/index.ts b/src/backend/actions/integrations/twilio/index.ts index ca66e4df0..b366d2e12 100644 --- a/src/backend/actions/integrations/twilio/index.ts +++ b/src/backend/actions/integrations/twilio/index.ts @@ -25,7 +25,6 @@ const CONFIGURATION_SCHEMA: IAppliedSchemaFormConfig = { export const TWILIO_ACTION_INTEGRATION: IActionIntegrationsImplemention = { title: "Twilio", - credentialsKey: "TWILIO", description: "Send SMS through Twilio", configurationSchema: CONFIGURATION_SCHEMA, connect: async (config: IActionConfig) => config, diff --git a/src/backend/lib/config-persistence/types.ts b/src/backend/lib/config-persistence/types.ts index 8f8305d4f..af9a50f6a 100644 --- a/src/backend/lib/config-persistence/types.ts +++ b/src/backend/lib/config-persistence/types.ts @@ -14,7 +14,6 @@ export type ConfigDomain = | "key-value" | "action-instances" | "activated-actions" - | "activated-storage" | "roles"; export enum ConfigAdaptorTypes { diff --git a/src/backend/lib/key-value/types.ts b/src/backend/lib/key-value/types.ts index 3876774a3..c08103a27 100644 --- a/src/backend/lib/key-value/types.ts +++ b/src/backend/lib/key-value/types.ts @@ -1,3 +1,3 @@ import { PortalKeyValueDomain } from "./portal"; -export type KeyValueDomain = PortalKeyValueDomain; +export type KeyValueDomain = PortalKeyValueDomain | "current-storage"; diff --git a/src/backend/menu/menu.service.ts b/src/backend/menu/menu.service.ts index 0d924087e..6c0937798 100644 --- a/src/backend/menu/menu.service.ts +++ b/src/backend/menu/menu.service.ts @@ -101,7 +101,7 @@ export class NavigationMenuApiService navItems = navItems.concat([ { id: nanoid(), - title: "Actions", + title: "Integrations", icon: "Zap", type: NavigationMenuItemType.System, link: SystemLinks.Actions, diff --git a/src/backend/storage/integrations/aws/index.ts b/src/backend/storage/integrations/aws/index.ts index 3d5ce079c..dd565f74d 100644 --- a/src/backend/storage/integrations/aws/index.ts +++ b/src/backend/storage/integrations/aws/index.ts @@ -1,20 +1,11 @@ import { IStorageIntegrationsImplemention } from "backend/storage/types"; -// https://www.npmjs.com/package/multer-s3 - -export const AWS_STORAGE_INTEGRATION: IStorageIntegrationsImplemention< - { - accessKeyId: string; - secretAccessKey: string; - region: string; - }, - { - bucket: string; - } -> = { +export const AWS_STORAGE_INTEGRATION: IStorageIntegrationsImplemention<{ + accessKeyId: string; + secretAccessKey: string; + region: string; +}> = { title: "AWS S3", - credentialsGroupKey: "AWS_S3", - packages: ["aws-sdk"], integrationConfigurationSchema: { accessKeyId: { type: "text", @@ -41,22 +32,12 @@ export const AWS_STORAGE_INTEGRATION: IStorageIntegrationsImplemention< ], }, }, - uploadConfigurationSchema: { - bucket: { - type: "text", - validations: [ - { - validationType: "required", - }, - ], - }, - }, - store: async (integrationConfig, uploadConfig, file: File) => { + store: async (integrationConfig, file: File) => { // const foo = new S3Client({ // region: "string", // }); // eslint-disable-next-line no-console - console.log(integrationConfig, uploadConfig, file); + console.log(integrationConfig, file); return ""; }, }; diff --git a/src/backend/storage/integrations/cloudinary/index.ts b/src/backend/storage/integrations/cloudinary/index.ts index 08fa16879..0a615b1bc 100644 --- a/src/backend/storage/integrations/cloudinary/index.ts +++ b/src/backend/storage/integrations/cloudinary/index.ts @@ -1,40 +1,35 @@ import { IStorageIntegrationsImplemention } from "backend/storage/types"; -export const CLOUDINARY_STORAGE_INTEGRATION: IStorageIntegrationsImplemention< - {}, +export const CLOUDINARY_STORAGE_INTEGRATION: IStorageIntegrationsImplemention<{}> = { - folder: string; - } -> = { - title: "Cloudinary", - packages: ["cloudinary", "multer-storage-cloudinary"], - credentialsGroupKey: "CLOUDINARY", - integrationConfigurationSchema: {}, - uploadConfigurationSchema: { - folder: { - type: "text", - validations: [ - { - validationType: "required", - }, - ], + title: "Cloudinary", + integrationConfigurationSchema: { + accessKeyId: { + type: "text", + validations: [ + { + validationType: "required", + }, + ], + }, + secretAccessKey: { + type: "text", + validations: [ + { + validationType: "required", + }, + ], + }, + region: { + type: "text", + validations: [ + { + validationType: "required", + }, + ], + }, }, - }, - store: async (integrationConfig, uploadConfig, file) => { - // const cloudinary = require("cloudinary").v2; - // const { CloudinaryStorage } = require("multer-storage-cloudinary"); - - // const storage = new CloudinaryStorage({ - // cloudinary, - // params: { - // folder: "some-folder-name", - // format: async (req, file) => "png", - // public_id: (req, file) => "computed-filename-using-request", - // }, - // }); - - // storage._handleFile(req, file, (details) => {}); - - return `${integrationConfig} ${uploadConfig} ${file}`; - }, -}; + store: async (integrationConfig, file) => { + return `${integrationConfig} ${file}`; + }, + }; diff --git a/src/backend/storage/integrations/firebase/index.ts b/src/backend/storage/integrations/firebase/index.ts deleted file mode 100644 index 08498a3e6..000000000 --- a/src/backend/storage/integrations/firebase/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { IStorageIntegrationsImplemention } from "backend/storage/types"; - -// https://github.com/khaosdoctor/multer-firebase-storage - -export const FIREBASE_STORAGE_INTEGRATION: IStorageIntegrationsImplemention< - {}, - { - folder: string; - } -> = { - title: "Firebase Storage", - credentialsGroupKey: "FIREBASE_STORAGE", - packages: [], - uploadConfigurationSchema: { - folder: { - type: "text", - validations: [ - { - validationType: "required", - }, - ], - }, - }, - integrationConfigurationSchema: {}, - store: async (integrationConfig, uploadConfig, file: File) => { - // eslint-disable-next-line no-console - console.log(integrationConfig, uploadConfig, file); - return ""; - }, -}; diff --git a/src/backend/storage/integrations/google/index.ts b/src/backend/storage/integrations/google/index.ts index c1654a65f..c3bc7b1a9 100644 --- a/src/backend/storage/integrations/google/index.ts +++ b/src/backend/storage/integrations/google/index.ts @@ -1,30 +1,37 @@ import { IStorageIntegrationsImplemention } from "backend/storage/types"; -// https://www.npmjs.com/package/multer-cloud-storage - -export const GOOGLE_STORAGE_INTEGRATION: IStorageIntegrationsImplemention< - {}, +export const GOOGLE_STORAGE_INTEGRATION: IStorageIntegrationsImplemention<{}> = { - folder: string; - } -> = { - title: "Google Cloud Storage", - credentialsGroupKey: "GOOGLE_CLOUD_STORAGE", - packages: ["aws-sdk"], - integrationConfigurationSchema: {}, - uploadConfigurationSchema: { - folder: { - type: "text", - validations: [ - { - validationType: "required", - }, - ], + title: "Google Cloud Storage", + integrationConfigurationSchema: { + accessKeyId: { + type: "text", + validations: [ + { + validationType: "required", + }, + ], + }, + secretAccessKey: { + type: "text", + validations: [ + { + validationType: "required", + }, + ], + }, + region: { + type: "text", + validations: [ + { + validationType: "required", + }, + ], + }, + }, + store: async (integrationConfig, file) => { + // eslint-disable-next-line no-console + console.log(integrationConfig, file); + return ""; }, - }, - store: async (integrationConfig, uploadConfig, file) => { - // eslint-disable-next-line no-console - console.log(integrationConfig, uploadConfig, file); - return ""; - }, -}; + }; diff --git a/src/backend/storage/integrations/index.ts b/src/backend/storage/integrations/index.ts index 403c3be54..ac2046d47 100644 --- a/src/backend/storage/integrations/index.ts +++ b/src/backend/storage/integrations/index.ts @@ -1,35 +1,14 @@ import { IStorageIntegrationsImplemention } from "../types"; import { AWS_STORAGE_INTEGRATION } from "./aws"; import { CLOUDINARY_STORAGE_INTEGRATION } from "./cloudinary"; -import { FIREBASE_STORAGE_INTEGRATION } from "./firebase"; import { GOOGLE_STORAGE_INTEGRATION } from "./google"; -import { LOCAL_STORAGE_INTEGRATION } from "./local"; -import { MINIO_STORAGE_INTEGRATION } from "./minio"; +import { StorageIntegrationKeys } from "./types"; export const STORAGE_INTEGRATIONS: Record< string, - IStorageIntegrationsImplemention + IStorageIntegrationsImplemention > = { - file: LOCAL_STORAGE_INTEGRATION, - s3: AWS_STORAGE_INTEGRATION, - firebase: FIREBASE_STORAGE_INTEGRATION, - minio: MINIO_STORAGE_INTEGRATION, - cloudinary: CLOUDINARY_STORAGE_INTEGRATION, - google: GOOGLE_STORAGE_INTEGRATION, - // digital: Digital Ocean Space + [StorageIntegrationKeys.S3]: AWS_STORAGE_INTEGRATION, + [StorageIntegrationKeys.CLOUDINARY]: CLOUDINARY_STORAGE_INTEGRATION, + [StorageIntegrationKeys.GOOGLE]: GOOGLE_STORAGE_INTEGRATION, }; - -// const AWS = require("aws-sdk"); -// const fs = require("fs"); -// const dotenv = require("dotenv"); - -// dotenv.configure(); -// const spacesEndpoint = new AWS.Endpoint(process.env.DO_SPACES_ENDPOINT); -// const s3 = new AWS.S3({endpoint: spacesEndpoint, accessKeyId: process.env.DO_SPACES_KEY, secretAccessKey: process.env.DO_SPACES_SECRET}); - -// const file = fs.readFileSync("path/to/file.jpg"); - -// s3.putObjet({Bucket: process.env.DO_SPACES_NAME, Key: "any_file_or_path_name.jpg", Body: file, ACL: "public"}, (err, data) => { -// if (err) return console.log(err); -// console.log("Your file has been uploaded successfully!", data); -// }); diff --git a/src/backend/storage/integrations/local/index.ts b/src/backend/storage/integrations/local/index.ts deleted file mode 100644 index 5fa7df228..000000000 --- a/src/backend/storage/integrations/local/index.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { IStorageIntegrationsImplemention } from "backend/storage/types"; - -export const LOCAL_STORAGE_INTEGRATION: IStorageIntegrationsImplemention< - {}, - { - folder: string; - } -> = { - title: "Local Storage", - credentialsGroupKey: "FILE", - packages: [], - integrationConfigurationSchema: {}, - uploadConfigurationSchema: { - folder: { - type: "text", - validations: [ - { - validationType: "required", - }, - ], - }, - }, - store: async (integrationConfig, uploadConfig, file) => { - // eslint-disable-next-line no-console - console.log(integrationConfig, uploadConfig, file); - return ""; - }, -}; diff --git a/src/backend/storage/integrations/minio/index.ts b/src/backend/storage/integrations/minio/index.ts deleted file mode 100644 index 893812256..000000000 --- a/src/backend/storage/integrations/minio/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { IStorageIntegrationsImplemention } from "backend/storage/types"; - -// https://www.npmjs.com/package/multer-minio-storage-engine - -export const MINIO_STORAGE_INTEGRATION: IStorageIntegrationsImplemention< - {}, - { - folder: string; - } -> = { - title: "Minio", - credentialsGroupKey: "MINIO", - packages: ["multer-minio-storage-engine@1.0.0", "minio"], - integrationConfigurationSchema: {}, - uploadConfigurationSchema: { - folder: { - type: "text", - validations: [ - { - validationType: "required", - }, - ], - }, - }, - store: async (integrationConfig, uploadConfig, file) => { - // eslint-disable-next-line no-console - console.log(integrationConfig, uploadConfig, file); - return ""; - }, -}; diff --git a/src/backend/storage/integrations/types.ts b/src/backend/storage/integrations/types.ts new file mode 100644 index 000000000..94f19581e --- /dev/null +++ b/src/backend/storage/integrations/types.ts @@ -0,0 +1,5 @@ +export enum StorageIntegrationKeys { + S3 = "s3", + CLOUDINARY = "cloudinary", + GOOGLE = "google", +} diff --git a/src/backend/storage/storage.controller.ts b/src/backend/storage/storage.controller.ts index ff4d2f35a..580021fae 100644 --- a/src/backend/storage/storage.controller.ts +++ b/src/backend/storage/storage.controller.ts @@ -7,33 +7,24 @@ export class StorageApiController { return this._storageApiService.listStorageIntegrations(); } - async listActivatedStorage() { - return await this._storageApiService.listActivatedStorage(); - } - - async activateStorage( - storageKey: string, - configuration: Record - ) { + async activateStorage({ + storageKey, + configuration, + }: { + storageKey: string; + configuration: Record; + }) { await this._storageApiService.activateStorage(storageKey, configuration); } - async showStorageConfig(storageKey: string) { - return await this._storageApiService.showStorageConfig(storageKey); - } - - async updateStorageConfig( - storageKey: string, - configuration: Record - ) { - await this._storageApiService.updateStorageConfig( - storageKey, - configuration - ); + async getCurrentActivatedStorage() { + return { + data: await this._storageApiService.getCurrentActivatedStorage(), + }; } - async deactivateStorage(storageKey: string) { - await this._storageApiService.deactivateStorage(storageKey); + async showStorageCredentials() { + return await this._storageApiService.showStorageCredentials(); } } diff --git a/src/backend/storage/storage.service.ts b/src/backend/storage/storage.service.ts index 204a1804c..5312b4d4f 100644 --- a/src/backend/storage/storage.service.ts +++ b/src/backend/storage/storage.service.ts @@ -2,25 +2,23 @@ import { credentialsApiService, CredentialsApiService, } from "backend/integrations-configurations"; -import { - createConfigDomainPersistenceService, - AbstractConfigDataPersistenceService, -} from "backend/lib/config-persistence"; import { validateSchemaRequestBody } from "backend/lib/errors/validate-schema-request-input"; +import { + createKeyValueDomainPersistenceService, + KeyValueStoreApiService, +} from "backend/lib/key-value"; import { IApplicationService } from "backend/types"; import { IIntegrationsList } from "shared/types/actions"; -import { IActivatedStorage } from "shared/types/storage"; +import { sluggify } from "shared/lib/strings"; import { STORAGE_INTEGRATIONS } from "./integrations"; export class StorageApiService implements IApplicationService { constructor( - private readonly _activatedStoragePersistenceService: AbstractConfigDataPersistenceService, + private readonly _currentStorageKeyValueStoreApiService: KeyValueStoreApiService, private readonly _credentialsApiService: CredentialsApiService ) {} - async bootstrap() { - await this._activatedStoragePersistenceService.setup(); - } + async bootstrap() {} listStorageIntegrations(): IIntegrationsList[] { return Object.entries(STORAGE_INTEGRATIONS).map( @@ -33,12 +31,6 @@ export class StorageApiService implements IApplicationService { ); } - async listActivatedStorage(): Promise { - const activatedStorage = - await this._activatedStoragePersistenceService.getAllItems(); - return activatedStorage.map(({ key }) => key); - } - async activateStorage( storageKey: string, configuration: Record @@ -48,44 +40,22 @@ export class StorageApiService implements IApplicationService { configuration ); - await this._activatedStoragePersistenceService.createItem(storageKey, { - key: storageKey, - }); - - await this._credentialsApiService.upsertGroup( - { - key: STORAGE_INTEGRATIONS[storageKey].credentialsGroupKey, + const previousStorageKey = await this.getCurrentActivatedStorage(); + if (previousStorageKey) { + await this._credentialsApiService.deleteGroup({ + key: this.makeCredentialsGroupKey(storageKey), fields: Object.keys( - STORAGE_INTEGRATIONS[storageKey].integrationConfigurationSchema + STORAGE_INTEGRATIONS[previousStorageKey] + .integrationConfigurationSchema ), - }, - configuration - ); - } - - async showStorageConfig( - storageKey: string - ): Promise> { - return await this._credentialsApiService.useGroupValue({ - key: STORAGE_INTEGRATIONS[storageKey].credentialsGroupKey, - fields: Object.keys( - STORAGE_INTEGRATIONS[storageKey].integrationConfigurationSchema - ), - }); - } + }); + } - async updateStorageConfig( - storageKey: string, - configuration: Record - ): Promise { - validateSchemaRequestBody( - STORAGE_INTEGRATIONS[storageKey].integrationConfigurationSchema, - configuration - ); + await this._currentStorageKeyValueStoreApiService.persistItem(storageKey); await this._credentialsApiService.upsertGroup( { - key: STORAGE_INTEGRATIONS[storageKey].credentialsGroupKey, + key: this.makeCredentialsGroupKey(storageKey), fields: Object.keys( STORAGE_INTEGRATIONS[storageKey].integrationConfigurationSchema ), @@ -94,22 +64,32 @@ export class StorageApiService implements IApplicationService { ); } - async deactivateStorage(storageKey: string): Promise { - await this._credentialsApiService.deleteGroup({ - key: STORAGE_INTEGRATIONS[storageKey].credentialsGroupKey, + async getCurrentActivatedStorage() { + return await this._currentStorageKeyValueStoreApiService.getItem(); + } + + async showStorageCredentials(): Promise> { + const storageKey = await this.getCurrentActivatedStorage(); + if (!storageKey) { + return {}; + } + return await this._credentialsApiService.useGroupValue({ + key: this.makeCredentialsGroupKey(storageKey), fields: Object.keys( STORAGE_INTEGRATIONS[storageKey].integrationConfigurationSchema ), }); + } - await this._activatedStoragePersistenceService.removeItem(storageKey); + private makeCredentialsGroupKey(storageKey: string) { + return sluggify(`FILE_STORAGE__${storageKey}`).toUpperCase(); } } -const activatedStoragePersistenceService = - createConfigDomainPersistenceService("activated-storage"); +const _currentStorageKeyValueStoreApiService = + createKeyValueDomainPersistenceService("current-storage"); export const storageApiService = new StorageApiService( - activatedStoragePersistenceService, + _currentStorageKeyValueStoreApiService, credentialsApiService ); diff --git a/src/backend/storage/types.ts b/src/backend/storage/types.ts index cac1ac487..a9d564096 100644 --- a/src/backend/storage/types.ts +++ b/src/backend/storage/types.ts @@ -1,12 +1,9 @@ import { IAppliedSchemaFormConfig } from "shared/form-schemas/types"; -export interface IStorageIntegrationsImplemention { +export interface IStorageIntegrationsImplemention { title: string; - credentialsGroupKey: string; - packages: string[]; - uploadConfigurationSchema: IAppliedSchemaFormConfig; integrationConfigurationSchema: IAppliedSchemaFormConfig; - store: (integrationConfig: T, uploadConfig: T, file: File) => Promise; + store: (integrationConfig: T, file: File) => Promise; } export const FOR_CODE_COV = 1; diff --git a/src/frontend/components/SchemaForm/index.tsx b/src/frontend/components/SchemaForm/index.tsx index bb1bf72d4..1f25aa28c 100644 --- a/src/frontend/components/SchemaForm/index.tsx +++ b/src/frontend/components/SchemaForm/index.tsx @@ -17,7 +17,7 @@ interface IProps { fields: IAppliedSchemaFormConfig; onSubmit: (data: T) => Promise; initialValues?: Partial; - buttonText: (submitting: boolean) => string; + buttonText?: (submitting: boolean) => string; action?: string; icon: "add" | "save" | "eye" | "no-icon" | "check" | "logIn"; onChange?: (data: T) => void; @@ -105,12 +105,14 @@ export function SchemaForm>({ )} ))} - + {buttonText && ( + + )} ); }} diff --git a/src/frontend/design-system/components/Form/FormFileInput/index.tsx b/src/frontend/design-system/components/Form/FormFileInput/index.tsx index b20a35e31..ba1a02973 100644 --- a/src/frontend/design-system/components/Form/FormFileInput/index.tsx +++ b/src/frontend/design-system/components/Form/FormFileInput/index.tsx @@ -9,7 +9,6 @@ import { Presentation } from "./Presentation"; interface IFormFileInput extends ISharedFormInput { uploadUrl: string; metadata?: Record; - maxSize?: number; } function FileInput({ @@ -18,12 +17,11 @@ function FileInput({ disabled, uploadUrl, metadata, - maxSize, }: IFormFileInput) { const [progress, setProgress] = useState(0); const [error, setError] = useState(""); const { value, onChange } = input; - + // Get the fiel settings const onDrop = useCallback( (acceptedFiles: File[]) => { input.onChange(null); @@ -68,9 +66,9 @@ function FileInput({ const dropZoneProps = useDropzone({ onDrop, multiple: false, - accept: { image: ["jpeg, png"] }, + // accept: { image: ["jpeg, png"] }, + // maxSize, disabled, - maxSize, }); return ( diff --git a/src/frontend/lib/routing/links.ts b/src/frontend/lib/routing/links.ts index 38e2701d8..a52f3540a 100644 --- a/src/frontend/lib/routing/links.ts +++ b/src/frontend/lib/routing/links.ts @@ -27,7 +27,7 @@ export const NAVIGATION_LINKS = { INTEGRATIONS: { VARIABLES: "/integrations/variables", ACTIONS: (actionId: string) => `/integrations/actions/${actionId}`, - STORAGE: (storageId: string) => `/integrations/storage/${storageId}`, + STORAGE: "/integrations/storage", }, USERS: { LIST: "/users", diff --git a/src/frontend/views/integrations/Password.tsx b/src/frontend/views/integrations/Password.tsx new file mode 100644 index 000000000..0a788bd2b --- /dev/null +++ b/src/frontend/views/integrations/Password.tsx @@ -0,0 +1,52 @@ +import { SchemaForm } from "frontend/components/SchemaForm"; +import { Spacer } from "frontend/design-system/primitives/Spacer"; +import { Typo } from "frontend/design-system/primitives/Typo"; +import { usePasswordStore } from "./password.store"; + +export function PasswordMessage() { + return ( + + All the values provided from this form will encrypted using `aes-256-gcm` + before been saved. + + ); +} + +export function PasswordToReveal({ + label, + isLoading, +}: { + label: string; + isLoading: boolean; +}) { + const passwordStore = usePasswordStore(); + + return ( + <> + + For security reasons, Please input your account password to be able to + reveal {label} + + + { + passwordStore.setPassword(password); + }} + icon="eye" + buttonText={() => + isLoading ? `Revealing ${label}` : `Reveal ${label}` + } + /> + + ); +} diff --git a/src/frontend/views/integrations/Variables/index.tsx b/src/frontend/views/integrations/Variables/index.tsx index 9623fd49a..3a648d01e 100644 --- a/src/frontend/views/integrations/Variables/index.tsx +++ b/src/frontend/views/integrations/Variables/index.tsx @@ -1,15 +1,15 @@ import { useSetPageDetails } from "frontend/lib/routing/usePageDetails"; -import { USER_PERMISSIONS } from "shared/constants/user"; import { BaseManageVariables, - MangeVariablesPageTitle, + ManageVariablesPageTitle, } from "frontend/views/settings/Variables/Base"; -import { BaseActionsLayout } from "../_Base"; +import { USER_PERMISSIONS } from "shared/constants/user"; import { ACTIONS_VIEW_KEY } from "../constants"; +import { BaseActionsLayout } from "../_Base"; export function ManageVariables() { useSetPageDetails({ - pageTitle: MangeVariablesPageTitle, + pageTitle: ManageVariablesPageTitle, viewKey: ACTIONS_VIEW_KEY, permission: USER_PERMISSIONS.CAN_MANAGE_INTEGRATIONS, }); diff --git a/src/frontend/views/integrations/_Base.tsx b/src/frontend/views/integrations/_Base.tsx index e2e01cdf1..03a89527f 100644 --- a/src/frontend/views/integrations/_Base.tsx +++ b/src/frontend/views/integrations/_Base.tsx @@ -2,7 +2,7 @@ import { useRouteParam } from "frontend/lib/routing/useRouteParam"; import { ViewStateMachine } from "frontend/components/ViewStateMachine"; import { useRouter } from "next/router"; import { ReactNode } from "react"; -import { Book, Cloud, UploadCloud, Zap, ZapOff } from "react-feather"; +import { Book, UploadCloud, Zap, ZapOff } from "react-feather"; import { ContentLayout } from "frontend/design-system/components/Section/SectionDivider"; import { SectionBox } from "frontend/design-system/components/Section/SectionBox"; import { ListSkeleton } from "frontend/design-system/components/Skeleton/List"; @@ -16,10 +16,6 @@ import { useActionIntegrationsList, useActiveActionList, } from "./actions/actions.store"; -import { - useActiveStorageIntegrationList, - useStorageIntegrationsList, -} from "./storage/storage.store"; import { ACTION_INTEGRATIONS_CRUD_CONFIG } from "./actions/constants"; interface IProps { @@ -32,9 +28,6 @@ export function BaseActionsLayout({ children }: IProps) { const actionIntegrationsList = useActionIntegrationsList(); const activeActionList = useActiveActionList(); - const storageIntegrationsList = useStorageIntegrationsList(); - const activeStorageIntegrationList = useActiveStorageIntegrationList(); - const router = useRouter(); const activeList = [ @@ -76,48 +69,17 @@ export function BaseActionsLayout({ children }: IProps) { /> - - {process.env.NEXT_PUBLIC_SHOW_UNFINISHED_FEATURES && ( - - } - > - ({ - name: title, - key, - }))} - render={(menuItem) => { - const isActive = activeStorageIntegrationList.data.includes( - menuItem.key - ); - return ( - - ); - }} - /> - - - )} - + @@ -40,49 +38,25 @@ export function Configure({ activationId, integrationDetail }: IProps) { ); } - if ( - activationConfiguration.error || - activationConfiguration.isLoading || - !passwordStore.password - ) { + if (activationConfiguration.data === undefined) { return ( - <> - - For security reasons, Please input your account password to reveal - this action configuration - - - { - passwordStore.setPassword(password); - }} - icon="eye" - buttonText={() => { - return activationConfiguration.isLoading - ? `Revealing ${integrationDetail.title}'s Configuration` - : `Reveal ${integrationDetail.title}'s Configuration`; - }} - /> - + ); } return ( - + <> + + + + ); } diff --git a/src/frontend/views/integrations/actions/View/index.tsx b/src/frontend/views/integrations/actions/View/index.tsx index 6133bd7b3..ff3895eba 100644 --- a/src/frontend/views/integrations/actions/View/index.tsx +++ b/src/frontend/views/integrations/actions/View/index.tsx @@ -7,6 +7,7 @@ import { Tabs } from "frontend/design-system/components/Tabs"; import { useActivateActionMutation } from "../actions.store"; import { Deactivate } from "./Deactivate"; import { Configure } from "./Configure"; +import { PasswordMessage } from "../../Password"; interface IProps { integrationDetail?: IIntegrationsList; @@ -28,10 +29,7 @@ export function ActionSettingsView({ if (!activeAction) { return ( <> - - All the values provided from this form will encrypted using - `aes-256-gcm` before been saved. - + { !!activationId && !!rootPassword && activationId !== ActionIntegrationKeys.HTTP, - defaultData: {}, + defaultData: undefined, } ); }; diff --git a/src/frontend/views/integrations/actions/constants.ts b/src/frontend/views/integrations/actions/constants.ts index 76c772323..b46de4ab5 100644 --- a/src/frontend/views/integrations/actions/constants.ts +++ b/src/frontend/views/integrations/actions/constants.ts @@ -2,6 +2,6 @@ import { MAKE_CRUD_CONFIG } from "frontend/lib/crud-config"; export const ACTION_INTEGRATIONS_CRUD_CONFIG = MAKE_CRUD_CONFIG({ path: "N/A", - plural: "Integrations", - singular: "Integration", + plural: "Actions", + singular: "Action", }); diff --git a/src/frontend/views/integrations/storage/Credentials.tsx b/src/frontend/views/integrations/storage/Credentials.tsx new file mode 100644 index 000000000..ff00cb966 --- /dev/null +++ b/src/frontend/views/integrations/storage/Credentials.tsx @@ -0,0 +1,115 @@ +import { SchemaForm } from "frontend/components/SchemaForm"; +import { useEffect, useState } from "react"; +import { ToastService } from "frontend/lib/toast"; +import { ViewStateMachine } from "frontend/components/ViewStateMachine"; +import { + FormSkeleton, + FormSkeletonSchema, +} from "frontend/design-system/components/Skeleton/Form"; +import { ISchemaFormConfig } from "shared/form-schemas/types"; +import { noop } from "shared/lib/noop"; +import { IIntegrationsList } from "shared/types/actions"; +import { STORAGE_INTEGRATIONS_CRUD_CONFIG } from "./constants"; +import { + useActivateStorageMutation, + useActiveStorageIntegration, + useStorageCredentialsConfiguration, + useStorageIntegrationsList, +} from "./storage.store"; +import { PasswordToReveal } from "../Password"; + +export function StorageCredentialsSettings() { + const storageList = useStorageIntegrationsList(); + const activeStorageIntegration = useActiveStorageIntegration(); + + const activateStorageMutation = useActivateStorageMutation(); + const storageCredentialsConfiguration = useStorageCredentialsConfiguration(); + + const [currentStorage, setCurrentStorage] = useState(""); + + const currentStorageDetails: IIntegrationsList | undefined = + storageList.data.find((datum) => datum.key === currentStorage); + + useEffect(() => { + setCurrentStorage(activeStorageIntegration.data.data); + }, [activeStorageIntegration.data]); + + useEffect(() => { + if (storageCredentialsConfiguration.error) { + ToastService.error(storageCredentialsConfiguration.error); + } + }, [storageCredentialsConfiguration.error]); + + const storageFormConfig: ISchemaFormConfig = { + type: "text", + selections: storageList.data.map((datum) => ({ + label: datum.title, + value: datum.key, + })), + validations: [ + { + validationType: "required", + errorMessage: "Required", + }, + ], + }; + + return ( + + } + > + {storageCredentialsConfiguration.data === undefined ? ( + <> + + fields={{ + storageKey: storageFormConfig, + }} + onSubmit={async () => noop()} + initialValues={{ + storageKey: currentStorage, + }} + buttonText={null} + icon="save" + /> + + + ) : ( + + onChange={(data) => { + setCurrentStorage(data.storageKey); + }} + fields={{ + storageKey: storageFormConfig, + ...(currentStorageDetails?.configurationSchema || {}), + }} + onSubmit={async ({ storageKey, ...data }) => + await activateStorageMutation.mutateAsync({ + storageKey, + configuration: data, + }) + } + initialValues={{ + ...(storageCredentialsConfiguration.data || {}), + storageKey: currentStorage, + }} + buttonText={STORAGE_INTEGRATIONS_CRUD_CONFIG.FORM_LANG.UPSERT} + icon="save" + /> + )} + + ); +} diff --git a/src/frontend/views/integrations/storage/General.tsx b/src/frontend/views/integrations/storage/General.tsx new file mode 100644 index 000000000..989608ede --- /dev/null +++ b/src/frontend/views/integrations/storage/General.tsx @@ -0,0 +1,81 @@ +import { SchemaForm } from "frontend/components/SchemaForm"; +import { ViewStateMachine } from "frontend/components/ViewStateMachine"; +import { + useAppConfiguration, + useUpsertConfigurationMutation, +} from "frontend/hooks/configuration/configuration.store"; +import { IFileUploadSettings } from "shared/types/file"; +import { + FormSkeleton, + FormSkeletonSchema, +} from "frontend/design-system/components/Skeleton/Form"; +import { MAKE_APP_CONFIGURATION_CRUD_CONFIG } from "frontend/hooks/configuration/configuration.constant"; + +export function GeneralStorageSettings() { + const fileUploadSettings = useAppConfiguration( + "file_upload_settings" + ); + + const upsertFileUploadSettingsMutation = useUpsertConfigurationMutation( + "file_upload_settings" + ); + + return ( + + } + > + + fields={{ + filePathFormat: { + type: "text", + validations: [ + { + validationType: "required", + errorMessage: "Required", + }, + ], + }, + + fileNameFormat: { + type: "text", + validations: [ + { + validationType: "required", + errorMessage: "Required", + }, + ], + }, + + defaultMaxFileSizeInMB: { + type: "number", + label: "Maximum file size to upload in MB", + validations: [ + { + validationType: "required", + errorMessage: "Required", + }, + ], + }, + }} + onSubmit={async (data) => { + await upsertFileUploadSettingsMutation.mutateAsync(data); + }} + buttonText={ + MAKE_APP_CONFIGURATION_CRUD_CONFIG("file_upload_settings").FORM_LANG + .UPSERT + } + icon="save" + /> + + ); +} diff --git a/src/frontend/views/integrations/storage/constants.ts b/src/frontend/views/integrations/storage/constants.ts index e4a7284f1..f08a4942e 100644 --- a/src/frontend/views/integrations/storage/constants.ts +++ b/src/frontend/views/integrations/storage/constants.ts @@ -2,6 +2,6 @@ import { MAKE_CRUD_CONFIG } from "frontend/lib/crud-config"; export const STORAGE_INTEGRATIONS_CRUD_CONFIG = MAKE_CRUD_CONFIG({ path: "N/A", - plural: "Storage Integrations", - singular: "Storage Integration", + plural: "File Storage", + singular: "File Storage", }); diff --git a/src/frontend/views/integrations/storage/index.tsx b/src/frontend/views/integrations/storage/index.tsx new file mode 100644 index 000000000..303cc0870 --- /dev/null +++ b/src/frontend/views/integrations/storage/index.tsx @@ -0,0 +1,37 @@ +import { USER_PERMISSIONS } from "shared/constants/user"; +import { useSetPageDetails } from "frontend/lib/routing/usePageDetails"; +import { SectionBox } from "frontend/design-system/components/Section/SectionBox"; +import { Tabs } from "frontend/design-system/components/Tabs"; +import { BaseActionsLayout } from "../_Base"; +import { ACTIONS_VIEW_KEY } from "../constants"; +import { STORAGE_INTEGRATIONS_CRUD_CONFIG } from "./constants"; + +import { GeneralStorageSettings } from "./General"; +import { StorageCredentialsSettings } from "./Credentials"; + +export function StorageIntegrations() { + useSetPageDetails({ + pageTitle: STORAGE_INTEGRATIONS_CRUD_CONFIG.TEXT_LANG.TITLE, + viewKey: ACTIONS_VIEW_KEY, + permission: USER_PERMISSIONS.CAN_MANAGE_INTEGRATIONS, + }); + + return ( + + + , + }, + { + label: "General", + content: , + }, + ]} + /> + + + ); +} diff --git a/src/frontend/views/integrations/storage/storage.store.ts b/src/frontend/views/integrations/storage/storage.store.ts index a3552f12b..ec8444f15 100644 --- a/src/frontend/views/integrations/storage/storage.store.ts +++ b/src/frontend/views/integrations/storage/storage.store.ts @@ -16,84 +16,51 @@ export const useStorageIntegrationsList = () => const ACTIVE_STORAGE_INTEGRATIONS_ENDPOINT = "/api/integrations/storage/active"; -const ACTIVATION_CONFIG = (storageKey: string) => { - return `/api/integrations/storage/${storageKey}/credentials`; -}; +const STORAGE_CREDENTIALS_CONFIG = `/api/integrations/storage/credentials`; -export const useActiveStorageIntegrationList = () => - useApi(ACTIVE_STORAGE_INTEGRATIONS_ENDPOINT, { +export const useActiveStorageIntegration = () => + useApi<{ data: string }>(ACTIVE_STORAGE_INTEGRATIONS_ENDPOINT, { errorMessage: CRUD_CONFIG_NOT_FOUND("Active Storage Integrations"), - defaultData: [], + defaultData: { data: "" }, }); -export const useStorageIntegrationConfiguration = (storageKey: string) => { +export const useStorageCredentialsConfiguration = () => { const rootPassword = usePasswordStore((state) => state.password); return useApi>( - `${ACTIVATION_CONFIG(storageKey)}?${reduceStringToNumber(rootPassword)}`, + `${STORAGE_CREDENTIALS_CONFIG}?${reduceStringToNumber(rootPassword)}`, { request: { body: { - password: rootPassword, + _password: rootPassword, }, method: "POST", }, errorMessage: CRUD_CONFIG_NOT_FOUND("Storage Credentials"), - enabled: !!storageKey && !!rootPassword, - defaultData: {}, + enabled: !!rootPassword, + defaultData: undefined, } ); }; -export function useDeactivateStorageMutation() { - const apiMutateOptions = useWaitForResponseMutationOptions< - Record - >({ - endpoints: [ACTIVE_STORAGE_INTEGRATIONS_ENDPOINT], - successMessage: STORAGE_INTEGRATIONS_CRUD_CONFIG.MUTATION_LANG.DE_ACTIVATED, - }); - - return useMutation( - async (storageKey: string) => - await makeActionRequest( - "DELETE", - `/api/integrations/storage/${storageKey}` - ), - apiMutateOptions - ); -} - -export function useActivateStorageMutation(integrationKey: string) { +export function useActivateStorageMutation() { const apiMutateOptions = useWaitForResponseMutationOptions< Record >({ - endpoints: [ACTIVE_STORAGE_INTEGRATIONS_ENDPOINT], + endpoints: [ + ACTIVE_STORAGE_INTEGRATIONS_ENDPOINT, + STORAGE_CREDENTIALS_CONFIG, + ], successMessage: STORAGE_INTEGRATIONS_CRUD_CONFIG.MUTATION_LANG.ACTIVATED, }); return useMutation( - async (configuration: Record) => + async (configuration: { + storageKey: string; + configuration: Record; + }) => await makeActionRequest( "POST", - `/api/integrations/storage/${integrationKey}`, - configuration - ), - apiMutateOptions - ); -} - -export function useUpdateActivatedStorageMutation(storageKey: string) { - const apiMutateOptions = useWaitForResponseMutationOptions< - Record - >({ - endpoints: [ACTIVE_STORAGE_INTEGRATIONS_ENDPOINT], - successMessage: STORAGE_INTEGRATIONS_CRUD_CONFIG.MUTATION_LANG.EDIT, - }); - - return useMutation( - async (configuration: Record) => - await makeActionRequest( - "PATCH", - `/api/integrations/storage/${storageKey}`, + ACTIVE_STORAGE_INTEGRATIONS_ENDPOINT, configuration ), apiMutateOptions diff --git a/src/frontend/views/settings/Variables/Base.tsx b/src/frontend/views/settings/Variables/Base.tsx index 5bc827619..f1b27a30c 100644 --- a/src/frontend/views/settings/Variables/Base.tsx +++ b/src/frontend/views/settings/Variables/Base.tsx @@ -46,4 +46,4 @@ export function BaseManageVariables() { ); } -export const MangeVariablesPageTitle = "Manage Variables"; +export const ManageVariablesPageTitle = "Manage Variables"; diff --git a/src/frontend/views/settings/Variables/ManageCredentialGroup.tsx b/src/frontend/views/settings/Variables/ManageCredentialGroup.tsx index 643c0ee7d..342a87b9c 100644 --- a/src/frontend/views/settings/Variables/ManageCredentialGroup.tsx +++ b/src/frontend/views/settings/Variables/ManageCredentialGroup.tsx @@ -10,7 +10,6 @@ import { useSetCurrentActionItems, } from "frontend/lib/routing/usePageDetails"; import { HelpCircle, Plus } from "react-feather"; -import { SchemaForm } from "frontend/components/SchemaForm"; import { USER_PERMISSIONS } from "shared/constants/user"; import { usePasswordStore } from "frontend/views/integrations/password.store"; import { useUserHasPermission } from "frontend/hooks/auth/user.store"; @@ -25,6 +24,10 @@ import { DeleteButton } from "frontend/design-system/components/Button/DeleteBut import { Spacer } from "frontend/design-system/primitives/Spacer"; import { Typo } from "frontend/design-system/primitives/Typo"; import { OffCanvas } from "frontend/design-system/components/OffCanvas"; +import { + PasswordMessage, + PasswordToReveal, +} from "frontend/views/integrations/Password"; import { INTEGRATIONS_GROUP_ENDPOINT, useIntegrationConfigurationDeletionMutation, @@ -178,29 +181,9 @@ export function ManageCredentialGroup({ userHasPermission(USER_PERMISSIONS.CAN_MANAGE_INTEGRATIONS) && revealedCredentials.data === undefined && ( - - Please input your account password to be able to see secret - values and manage them - - - { - passwordStore.setPassword(password); - }} - icon="eye" - buttonText={(isSubmitting) => - isSubmitting ? "Revealing Secrets" : "Reveal Secrets" - } + )} @@ -231,6 +214,12 @@ export function ManageCredentialGroup({ onClose={closeConfigItem} show={!!currentConfigItem} > + {group === IntegrationsConfigurationGroup.Credentials && ( + <> + + + + )} { - const validatedRequest = await getValidatedRequest([ - { - _type: "requestQuery", - options: REQUEST_KEY_FIELD, - }, - { - _type: "requestBody", - options: {}, - }, - ]); - - return await storageApiController.activateStorage( - validatedRequest.requestQuery, - validatedRequest.requestBody - ); - }, - PATCH: async (getValidatedRequest) => { - const validatedRequest = await getValidatedRequest([ - { - _type: "requestQuery", - options: REQUEST_KEY_FIELD, - }, - { - _type: "requestBody", - options: {}, - }, - ]); - - return await storageApiController.updateStorageConfig( - validatedRequest.requestQuery, - validatedRequest.requestBody - ); - }, - DELETE: async (getValidatedRequest) => { - const validatedRequest = await getValidatedRequest([ - { - _type: "requestQuery", - options: REQUEST_KEY_FIELD, - }, - ]); - - return await storageApiController.deactivateStorage( - validatedRequest.requestQuery - ); - }, - }, - [ - { - _type: "canUser", - body: USER_PERMISSIONS.CAN_MANAGE_INTEGRATIONS, - }, - ] -); diff --git a/src/pages/api/integrations/storage/active.ts b/src/pages/api/integrations/storage/active.ts index 9c87d83e9..d9c0ef378 100644 --- a/src/pages/api/integrations/storage/active.ts +++ b/src/pages/api/integrations/storage/active.ts @@ -5,7 +5,19 @@ import { requestHandler } from "backend/lib/request"; export default requestHandler( { GET: async () => { - return await storageApiController.listActivatedStorage(); + return await storageApiController.getCurrentActivatedStorage(); + }, + POST: async (getValidatedRequest) => { + const validatedRequest = await getValidatedRequest([ + { + _type: "requestBody", + options: {}, + }, + ]); + + return await storageApiController.activateStorage( + validatedRequest.requestBody + ); }, }, [ diff --git a/src/pages/api/integrations/storage/[key]/credentials.ts b/src/pages/api/integrations/storage/credentials.ts similarity index 52% rename from src/pages/api/integrations/storage/[key]/credentials.ts rename to src/pages/api/integrations/storage/credentials.ts index c11a8b962..a67f285df 100644 --- a/src/pages/api/integrations/storage/[key]/credentials.ts +++ b/src/pages/api/integrations/storage/credentials.ts @@ -2,21 +2,10 @@ import { USER_PERMISSIONS } from "shared/constants/user"; import { storageApiController } from "backend/storage/storage.controller"; import { requestHandler } from "backend/lib/request"; -const REQUEST_KEY_FIELD = "key"; - export default requestHandler( { - POST: async (getValidatedRequest) => { - const validatedRequest = await getValidatedRequest([ - { - _type: "requestQuery", - options: REQUEST_KEY_FIELD, - }, - ]); - - return await storageApiController.showStorageConfig( - validatedRequest.requestQuery - ); + POST: async () => { + return await storageApiController.showStorageCredentials(); }, }, [ diff --git a/src/pages/integrations/storage.tsx b/src/pages/integrations/storage.tsx new file mode 100644 index 000000000..48d2806a3 --- /dev/null +++ b/src/pages/integrations/storage.tsx @@ -0,0 +1,3 @@ +import { StorageIntegrations } from "frontend/views/integrations/storage"; + +export default StorageIntegrations; diff --git a/src/shared/configurations/base-types.ts b/src/shared/configurations/base-types.ts index f0e5088b6..c6d3302d0 100644 --- a/src/shared/configurations/base-types.ts +++ b/src/shared/configurations/base-types.ts @@ -23,6 +23,7 @@ export type BaseAppConfigurationKeys = | "entity_form_extension" | "system_settings" | "hidden_entity_relations" + | "file_upload_settings" | "entity_relations_order"; export const FOR_CODE_COV = 1; diff --git a/src/shared/configurations/constants.ts b/src/shared/configurations/constants.ts index 889bd42bd..a64423f2e 100644 --- a/src/shared/configurations/constants.ts +++ b/src/shared/configurations/constants.ts @@ -1,4 +1,5 @@ import { ISingularPlural } from "shared/types/config"; +import { IFileUploadSettings } from "shared/types/file"; import { BaseAppConfigurationKeys } from "./base-types"; import { PortalConfigurationKeys, PORTAL_CONFIGURATION_KEYS } from "./portal"; import { DEFAULT_SYSTEM_SETTINGS } from "./system"; @@ -23,6 +24,7 @@ export const CONFIGURATION_KEYS: Record< requireEntity: true, defaultValue: [], }, + hidden_entity_update_columns: { crudConfigLabel: "Update Columns Settings", requireEntity: true, @@ -66,6 +68,14 @@ export const CONFIGURATION_KEYS: Record< beforeSubmit: "", }, }, + file_upload_settings: { + crudConfigLabel: "File Uploads Settings", + defaultValue: { + defaultMaxFileSizeInMB: 5, + fileNameFormat: "", + filePathFormat: "", + } as IFileUploadSettings, + }, entity_presentation_script: { crudConfigLabel: "Presentation Scripts", requireEntity: true, diff --git a/src/shared/types/actions.ts b/src/shared/types/actions.ts index c4f099c2f..1d2991c36 100644 --- a/src/shared/types/actions.ts +++ b/src/shared/types/actions.ts @@ -43,7 +43,6 @@ export interface IPerformsImplementation { export interface IActionIntegrationsImplemention { title: string; description: string; - credentialsKey: string; configurationSchema: IAppliedSchemaFormConfig; connect: (config: Record) => Promise; performsImplementation: Record; diff --git a/src/shared/types/file.ts b/src/shared/types/file.ts new file mode 100644 index 000000000..e7a9991ee --- /dev/null +++ b/src/shared/types/file.ts @@ -0,0 +1,5 @@ +export type IFileUploadSettings = { + filePathFormat: string; + fileNameFormat: string; + defaultMaxFileSizeInMB: number; +}; diff --git a/src/shared/types/storage.ts b/src/shared/types/storage.ts deleted file mode 100644 index b13e0e710..000000000 --- a/src/shared/types/storage.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface IActivatedStorage { - key: string; -} - -export const FOR_CODE_COV = 1;