mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 22:28:15 +00:00
misc: added root workflow integration structure
This commit is contained in:
Vendored
+2
@@ -77,6 +77,7 @@ import { TUserDALFactory } from "@app/services/user/user-dal";
|
|||||||
import { TUserServiceFactory } from "@app/services/user/user-service";
|
import { TUserServiceFactory } from "@app/services/user/user-service";
|
||||||
import { TUserEngagementServiceFactory } from "@app/services/user-engagement/user-engagement-service";
|
import { TUserEngagementServiceFactory } from "@app/services/user-engagement/user-engagement-service";
|
||||||
import { TWebhookServiceFactory } from "@app/services/webhook/webhook-service";
|
import { TWebhookServiceFactory } from "@app/services/webhook/webhook-service";
|
||||||
|
import { TWorkflowIntegrationServiceFactory } from "@app/services/workflow-integration/workflow-integration-service";
|
||||||
|
|
||||||
declare module "fastify" {
|
declare module "fastify" {
|
||||||
interface FastifyRequest {
|
interface FastifyRequest {
|
||||||
@@ -179,6 +180,7 @@ declare module "fastify" {
|
|||||||
externalKms: TExternalKmsServiceFactory;
|
externalKms: TExternalKmsServiceFactory;
|
||||||
orgAdmin: TOrgAdminServiceFactory;
|
orgAdmin: TOrgAdminServiceFactory;
|
||||||
slack: TSlackServiceFactory;
|
slack: TSlackServiceFactory;
|
||||||
|
workflowIntegration: TWorkflowIntegrationServiceFactory;
|
||||||
};
|
};
|
||||||
// this is exclusive use for middlewares in which we need to inject data
|
// this is exclusive use for middlewares in which we need to inject data
|
||||||
// everywhere else access using service layer
|
// everywhere else access using service layer
|
||||||
|
|||||||
Vendored
+9
-1
@@ -331,7 +331,10 @@ import {
|
|||||||
TUsersUpdate,
|
TUsersUpdate,
|
||||||
TWebhooks,
|
TWebhooks,
|
||||||
TWebhooksInsert,
|
TWebhooksInsert,
|
||||||
TWebhooksUpdate
|
TWebhooksUpdate,
|
||||||
|
TWorkflowIntegrations,
|
||||||
|
TWorkflowIntegrationsInsert,
|
||||||
|
TWorkflowIntegrationsUpdate
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import {
|
import {
|
||||||
TSecretV2TagJunction,
|
TSecretV2TagJunction,
|
||||||
@@ -800,5 +803,10 @@ declare module "knex/types/tables" {
|
|||||||
TAdminSlackConfigsInsert,
|
TAdminSlackConfigsInsert,
|
||||||
TAdminSlackConfigsUpdate
|
TAdminSlackConfigsUpdate
|
||||||
>;
|
>;
|
||||||
|
[TableName.WorkflowIntegrations]: KnexOriginal.CompositeTableType<
|
||||||
|
TWorkflowIntegrations,
|
||||||
|
TWorkflowIntegrationsInsert,
|
||||||
|
TWorkflowIntegrationsUpdate
|
||||||
|
>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,25 @@ import { TableName } from "../schemas";
|
|||||||
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||||
|
|
||||||
export async function up(knex: Knex): Promise<void> {
|
export async function up(knex: Knex): Promise<void> {
|
||||||
if (!(await knex.schema.hasTable(TableName.SlackIntegrations))) {
|
if (!(await knex.schema.hasTable(TableName.WorkflowIntegrations))) {
|
||||||
await knex.schema.createTable(TableName.SlackIntegrations, (tb) => {
|
await knex.schema.createTable(TableName.WorkflowIntegrations, (tb) => {
|
||||||
tb.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
tb.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
tb.string("integration").notNullable();
|
||||||
tb.string("slug").notNullable();
|
tb.string("slug").notNullable();
|
||||||
tb.uuid("orgId").notNullable();
|
tb.uuid("orgId").notNullable();
|
||||||
tb.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
tb.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
||||||
tb.string("description");
|
tb.string("description");
|
||||||
|
tb.unique(["orgId", "slug"]);
|
||||||
|
tb.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.WorkflowIntegrations);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasTable(TableName.SlackIntegrations))) {
|
||||||
|
await knex.schema.createTable(TableName.SlackIntegrations, (tb) => {
|
||||||
|
tb.uuid("id", { primaryKey: true }).notNullable();
|
||||||
|
tb.foreign("id").references("id").inTable(TableName.WorkflowIntegrations).onDelete("CASCADE");
|
||||||
tb.string("teamId").notNullable();
|
tb.string("teamId").notNullable();
|
||||||
tb.string("teamName").notNullable();
|
tb.string("teamName").notNullable();
|
||||||
tb.string("slackUserId").notNullable();
|
tb.string("slackUserId").notNullable();
|
||||||
@@ -18,7 +30,6 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
tb.binary("encryptedBotAccessToken").notNullable();
|
tb.binary("encryptedBotAccessToken").notNullable();
|
||||||
tb.string("slackBotId").notNullable();
|
tb.string("slackBotId").notNullable();
|
||||||
tb.string("slackBotUserId").notNullable();
|
tb.string("slackBotUserId").notNullable();
|
||||||
tb.unique(["orgId", "slug"]);
|
|
||||||
tb.timestamps(true, true, true);
|
tb.timestamps(true, true, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -58,9 +69,12 @@ export async function down(knex: Knex): Promise<void> {
|
|||||||
await knex.schema.dropTableIfExists(TableName.ProjectSlackConfigs);
|
await knex.schema.dropTableIfExists(TableName.ProjectSlackConfigs);
|
||||||
await dropOnUpdateTrigger(knex, TableName.ProjectSlackConfigs);
|
await dropOnUpdateTrigger(knex, TableName.ProjectSlackConfigs);
|
||||||
|
|
||||||
|
await knex.schema.dropTableIfExists(TableName.SlackIntegrations);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.SlackIntegrations);
|
||||||
|
|
||||||
await knex.schema.dropTableIfExists(TableName.AdminSlackConfig);
|
await knex.schema.dropTableIfExists(TableName.AdminSlackConfig);
|
||||||
await dropOnUpdateTrigger(knex, TableName.AdminSlackConfig);
|
await dropOnUpdateTrigger(knex, TableName.AdminSlackConfig);
|
||||||
|
|
||||||
await knex.schema.dropTableIfExists(TableName.SlackIntegrations);
|
await knex.schema.dropTableIfExists(TableName.WorkflowIntegrations);
|
||||||
await dropOnUpdateTrigger(knex, TableName.SlackIntegrations);
|
await dropOnUpdateTrigger(knex, TableName.WorkflowIntegrations);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,3 +112,4 @@ export * from "./user-encryption-keys";
|
|||||||
export * from "./user-group-membership";
|
export * from "./user-group-membership";
|
||||||
export * from "./users";
|
export * from "./users";
|
||||||
export * from "./webhooks";
|
export * from "./webhooks";
|
||||||
|
export * from "./workflow-integrations";
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ export enum TableName {
|
|||||||
InternalKmsKeyVersion = "internal_kms_key_version",
|
InternalKmsKeyVersion = "internal_kms_key_version",
|
||||||
// @depreciated
|
// @depreciated
|
||||||
KmsKeyVersion = "kms_key_versions",
|
KmsKeyVersion = "kms_key_versions",
|
||||||
|
WorkflowIntegrations = "workflow_integrations",
|
||||||
SlackIntegrations = "slack_integrations",
|
SlackIntegrations = "slack_integrations",
|
||||||
ProjectSlackConfigs = "project_slack_configs",
|
ProjectSlackConfigs = "project_slack_configs",
|
||||||
AdminSlackConfig = "admin_slack_configs"
|
AdminSlackConfig = "admin_slack_configs"
|
||||||
|
|||||||
@@ -11,9 +11,6 @@ import { TImmutableDBKeys } from "./models";
|
|||||||
|
|
||||||
export const SlackIntegrationsSchema = z.object({
|
export const SlackIntegrationsSchema = z.object({
|
||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
slug: z.string(),
|
|
||||||
orgId: z.string().uuid(),
|
|
||||||
description: z.string().nullable().optional(),
|
|
||||||
teamId: z.string(),
|
teamId: z.string(),
|
||||||
teamName: z.string(),
|
teamName: z.string(),
|
||||||
slackUserId: z.string(),
|
slackUserId: z.string(),
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const WorkflowIntegrationsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
integration: z.string(),
|
||||||
|
slug: z.string(),
|
||||||
|
orgId: z.string().uuid(),
|
||||||
|
description: z.string().nullable().optional(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TWorkflowIntegrations = z.infer<typeof WorkflowIntegrationsSchema>;
|
||||||
|
export type TWorkflowIntegrationsInsert = Omit<z.input<typeof WorkflowIntegrationsSchema>, TImmutableDBKeys>;
|
||||||
|
export type TWorkflowIntegrationsUpdate = Partial<Omit<z.input<typeof WorkflowIntegrationsSchema>, TImmutableDBKeys>>;
|
||||||
@@ -198,6 +198,8 @@ import { userAliasDALFactory } from "@app/services/user-alias/user-alias-dal";
|
|||||||
import { userEngagementServiceFactory } from "@app/services/user-engagement/user-engagement-service";
|
import { userEngagementServiceFactory } from "@app/services/user-engagement/user-engagement-service";
|
||||||
import { webhookDALFactory } from "@app/services/webhook/webhook-dal";
|
import { webhookDALFactory } from "@app/services/webhook/webhook-dal";
|
||||||
import { webhookServiceFactory } from "@app/services/webhook/webhook-service";
|
import { webhookServiceFactory } from "@app/services/webhook/webhook-service";
|
||||||
|
import { workflowIntegrationDALFactory } from "@app/services/workflow-integration/workflow-integration-dal";
|
||||||
|
import { workflowIntegrationServiceFactory } from "@app/services/workflow-integration/workflow-integration-service";
|
||||||
|
|
||||||
import { injectAuditLogInfo } from "../plugins/audit-log";
|
import { injectAuditLogInfo } from "../plugins/audit-log";
|
||||||
import { injectIdentity } from "../plugins/auth/inject-identity";
|
import { injectIdentity } from "../plugins/auth/inject-identity";
|
||||||
@@ -329,6 +331,7 @@ export const registerRoutes = async (
|
|||||||
const slackIntegrationDAL = slackIntegrationDALFactory(db);
|
const slackIntegrationDAL = slackIntegrationDALFactory(db);
|
||||||
const projectSlackConfigDAL = projectSlackConfigDALFactory(db);
|
const projectSlackConfigDAL = projectSlackConfigDALFactory(db);
|
||||||
const adminSlackConfigDAL = adminSlackConfigDALFactory(db);
|
const adminSlackConfigDAL = adminSlackConfigDALFactory(db);
|
||||||
|
const workflowIntegrationDAL = workflowIntegrationDALFactory(db);
|
||||||
|
|
||||||
const permissionService = permissionServiceFactory({
|
const permissionService = permissionServiceFactory({
|
||||||
permissionDAL,
|
permissionDAL,
|
||||||
@@ -1170,7 +1173,13 @@ export const registerRoutes = async (
|
|||||||
permissionService,
|
permissionService,
|
||||||
kmsService,
|
kmsService,
|
||||||
slackIntegrationDAL,
|
slackIntegrationDAL,
|
||||||
adminSlackConfigDAL
|
adminSlackConfigDAL,
|
||||||
|
workflowIntegrationDAL
|
||||||
|
});
|
||||||
|
|
||||||
|
const workflowIntegrationService = workflowIntegrationServiceFactory({
|
||||||
|
permissionService,
|
||||||
|
workflowIntegrationDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
await superAdminService.initServerCfg();
|
await superAdminService.initServerCfg();
|
||||||
@@ -1255,7 +1264,8 @@ export const registerRoutes = async (
|
|||||||
userEngagement: userEngagementService,
|
userEngagement: userEngagementService,
|
||||||
externalKms: externalKmsService,
|
externalKms: externalKmsService,
|
||||||
orgAdmin: orgAdminService,
|
orgAdmin: orgAdminService,
|
||||||
slack: slackService
|
slack: slackService,
|
||||||
|
workflowIntegration: workflowIntegrationService
|
||||||
});
|
});
|
||||||
|
|
||||||
const cronJobs: CronJob[] = [];
|
const cronJobs: CronJob[] = [];
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import { registerUserActionRouter } from "./user-action-router";
|
|||||||
import { registerUserEngagementRouter } from "./user-engagement-router";
|
import { registerUserEngagementRouter } from "./user-engagement-router";
|
||||||
import { registerUserRouter } from "./user-router";
|
import { registerUserRouter } from "./user-router";
|
||||||
import { registerWebhookRouter } from "./webhook-router";
|
import { registerWebhookRouter } from "./webhook-router";
|
||||||
|
import { registerWorkflowIntegrationRouter } from "./workflow-integration-router";
|
||||||
|
|
||||||
export const registerV1Routes = async (server: FastifyZodProvider) => {
|
export const registerV1Routes = async (server: FastifyZodProvider) => {
|
||||||
await server.register(registerSsoRouter, { prefix: "/sso" });
|
await server.register(registerSsoRouter, { prefix: "/sso" });
|
||||||
@@ -64,6 +65,7 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
|
|||||||
|
|
||||||
await server.register(
|
await server.register(
|
||||||
async (workflowIntegrationRouter) => {
|
async (workflowIntegrationRouter) => {
|
||||||
|
await workflowIntegrationRouter.register(registerWorkflowIntegrationRouter);
|
||||||
await workflowIntegrationRouter.register(registerSlackRouter, { prefix: "/slack" });
|
await workflowIntegrationRouter.register(registerSlackRouter, { prefix: "/slack" });
|
||||||
},
|
},
|
||||||
{ prefix: "/workflow-integrations" }
|
{ prefix: "/workflow-integrations" }
|
||||||
|
|||||||
@@ -1,12 +1,23 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { SlackIntegrationsSchema } from "@app/db/schemas";
|
import { SlackIntegrationsSchema, WorkflowIntegrationsSchema } from "@app/db/schemas";
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
const sanitizedSlackIntegrationSchema = WorkflowIntegrationsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
description: true,
|
||||||
|
slug: true,
|
||||||
|
integration: true
|
||||||
|
}).merge(
|
||||||
|
SlackIntegrationsSchema.pick({
|
||||||
|
teamName: true
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
|
|
||||||
@@ -69,7 +80,7 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
slackIntegrationId: z.string()
|
id: z.string()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.string()
|
200: z.string()
|
||||||
@@ -82,7 +93,7 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
id: req.query.slackIntegrationId
|
id: req.query.id
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
@@ -91,7 +102,7 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
|||||||
event: {
|
event: {
|
||||||
type: EventType.ATTEMPT_REINSTALL_SLACK_INTEGRATION,
|
type: EventType.ATTEMPT_REINSTALL_SLACK_INTEGRATION,
|
||||||
metadata: {
|
metadata: {
|
||||||
id: req.query.slackIntegrationId
|
id: req.query.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -113,12 +124,7 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
response: {
|
response: {
|
||||||
200: SlackIntegrationsSchema.pick({
|
200: sanitizedSlackIntegrationSchema.array()
|
||||||
id: true,
|
|
||||||
slug: true,
|
|
||||||
description: true,
|
|
||||||
teamName: true
|
|
||||||
}).array()
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
@@ -136,7 +142,7 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
|||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
url: "/:slackIntegrationId",
|
url: "/:id",
|
||||||
config: {
|
config: {
|
||||||
rateLimit: writeLimit
|
rateLimit: writeLimit
|
||||||
},
|
},
|
||||||
@@ -147,15 +153,10 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
slackIntegrationId: z.string()
|
id: z.string()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: SlackIntegrationsSchema.pick({
|
200: sanitizedSlackIntegrationSchema
|
||||||
id: true,
|
|
||||||
slug: true,
|
|
||||||
description: true,
|
|
||||||
teamName: true
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
@@ -165,7 +166,7 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
id: req.params.slackIntegrationId
|
id: req.params.id
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
@@ -185,7 +186,7 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
|||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:slackIntegrationId",
|
url: "/:id",
|
||||||
config: {
|
config: {
|
||||||
rateLimit: readLimit
|
rateLimit: readLimit
|
||||||
},
|
},
|
||||||
@@ -196,15 +197,10 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
slackIntegrationId: z.string()
|
id: z.string()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: SlackIntegrationsSchema.pick({
|
200: sanitizedSlackIntegrationSchema
|
||||||
id: true,
|
|
||||||
slug: true,
|
|
||||||
description: true,
|
|
||||||
teamName: true
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
@@ -214,7 +210,7 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
id: req.params.slackIntegrationId
|
id: req.params.id
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
@@ -234,7 +230,7 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
|||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:slackIntegrationId/channels",
|
url: "/:id/channels",
|
||||||
config: {
|
config: {
|
||||||
rateLimit: readLimit
|
rateLimit: readLimit
|
||||||
},
|
},
|
||||||
@@ -245,7 +241,7 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
slackIntegrationId: z.string()
|
id: z.string()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z
|
200: z
|
||||||
@@ -263,7 +259,7 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
id: req.params.slackIntegrationId
|
id: req.params.id
|
||||||
});
|
});
|
||||||
|
|
||||||
return slackChannels;
|
return slackChannels;
|
||||||
@@ -272,7 +268,7 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
|||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
url: "/:slackIntegrationId",
|
url: "/:id",
|
||||||
config: {
|
config: {
|
||||||
rateLimit: readLimit
|
rateLimit: readLimit
|
||||||
},
|
},
|
||||||
@@ -283,19 +279,14 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
slackIntegrationId: z.string()
|
id: z.string()
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
slug: z.string().optional(),
|
slug: z.string().optional(),
|
||||||
description: z.string().optional()
|
description: z.string().optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: SlackIntegrationsSchema.pick({
|
200: sanitizedSlackIntegrationSchema
|
||||||
id: true,
|
|
||||||
slug: true,
|
|
||||||
description: true,
|
|
||||||
teamName: true
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
@@ -305,7 +296,7 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
id: req.params.slackIntegrationId,
|
id: req.params.id,
|
||||||
...req.body
|
...req.body
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { WorkflowIntegrationsSchema } from "@app/db/schemas";
|
||||||
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
const sanitizedWorkflowIntegrationSchema = WorkflowIntegrationsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
description: true,
|
||||||
|
slug: true,
|
||||||
|
integration: true
|
||||||
|
});
|
||||||
|
|
||||||
|
export const registerWorkflowIntegrationRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
response: {
|
||||||
|
200: sanitizedWorkflowIntegrationSchema.array()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const workflowIntegrations = await server.services.workflowIntegration.getIntegrationsByOrg({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
return workflowIntegrations;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -81,7 +81,7 @@ type TProjectServiceFactoryDep = {
|
|||||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "create" | "findLatestProjectKey" | "delete" | "find" | "insertMany">;
|
projectKeyDAL: Pick<TProjectKeyDALFactory, "create" | "findLatestProjectKey" | "delete" | "find" | "insertMany">;
|
||||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "create" | "findProjectGhostUser" | "findOne">;
|
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "create" | "findProjectGhostUser" | "findOne">;
|
||||||
projectSlackConfigDAL: Pick<TProjectSlackConfigDALFactory, "findOne" | "transaction" | "updateById" | "create">;
|
projectSlackConfigDAL: Pick<TProjectSlackConfigDALFactory, "findOne" | "transaction" | "updateById" | "create">;
|
||||||
slackIntegrationDAL: Pick<TSlackIntegrationDALFactory, "findById">;
|
slackIntegrationDAL: Pick<TSlackIntegrationDALFactory, "findById" | "findByIdWithWorkflowIntegrationDetails">;
|
||||||
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "create">;
|
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "create">;
|
||||||
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "find">;
|
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "find">;
|
||||||
certificateDAL: Pick<TCertificateDALFactory, "find" | "countCertificatesInProject">;
|
certificateDAL: Pick<TCertificateDALFactory, "find" | "countCertificatesInProject">;
|
||||||
@@ -965,7 +965,7 @@ export const projectServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const slackIntegration = await slackIntegrationDAL.findById(slackIntegrationId);
|
const slackIntegration = await slackIntegrationDAL.findByIdWithWorkflowIntegrationDetails(slackIntegrationId);
|
||||||
if (!slackIntegration) {
|
if (!slackIntegration) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Slack integration not found"
|
message: "Slack integration not found"
|
||||||
|
|||||||
@@ -1,11 +1,56 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { TableName } from "@app/db/schemas";
|
import { TableName, TSlackIntegrations, TWorkflowIntegrations } from "@app/db/schemas";
|
||||||
import { ormify } from "@app/lib/knex";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
|
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
||||||
|
|
||||||
export type TSlackIntegrationDALFactory = ReturnType<typeof slackIntegrationDALFactory>;
|
export type TSlackIntegrationDALFactory = ReturnType<typeof slackIntegrationDALFactory>;
|
||||||
|
|
||||||
export const slackIntegrationDALFactory = (db: TDbClient) => {
|
export const slackIntegrationDALFactory = (db: TDbClient) => {
|
||||||
const slackIntegrationOrm = ormify(db, TableName.SlackIntegrations);
|
const slackIntegrationOrm = ormify(db, TableName.SlackIntegrations);
|
||||||
|
|
||||||
return slackIntegrationOrm;
|
const findByIdWithWorkflowIntegrationDetails = async (id: string, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
return await (tx || db.replicaNode())(TableName.SlackIntegrations)
|
||||||
|
.join(
|
||||||
|
TableName.WorkflowIntegrations,
|
||||||
|
`${TableName.SlackIntegrations}.id`,
|
||||||
|
`${TableName.WorkflowIntegrations}.id`
|
||||||
|
)
|
||||||
|
.select(selectAllTableCols(TableName.SlackIntegrations))
|
||||||
|
.select(db.ref("orgId").withSchema(TableName.WorkflowIntegrations))
|
||||||
|
.select(db.ref("description").withSchema(TableName.WorkflowIntegrations))
|
||||||
|
.select(db.ref("integration").withSchema(TableName.WorkflowIntegrations))
|
||||||
|
.select(db.ref("slug").withSchema(TableName.WorkflowIntegrations))
|
||||||
|
.where(`${TableName.WorkflowIntegrations}.id`, "=", id)
|
||||||
|
.first();
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Find by ID with Workflow integration details" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const findWithWorkflowIntegrationDetails = async (
|
||||||
|
filter: Partial<TSlackIntegrations> & Partial<TWorkflowIntegrations>,
|
||||||
|
tx?: Knex
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
return await (tx || db.replicaNode())(TableName.SlackIntegrations)
|
||||||
|
.join(
|
||||||
|
TableName.WorkflowIntegrations,
|
||||||
|
`${TableName.SlackIntegrations}.id`,
|
||||||
|
`${TableName.WorkflowIntegrations}.id`
|
||||||
|
)
|
||||||
|
.select(selectAllTableCols(TableName.SlackIntegrations))
|
||||||
|
.select(db.ref("orgId").withSchema(TableName.WorkflowIntegrations))
|
||||||
|
.select(db.ref("description").withSchema(TableName.WorkflowIntegrations))
|
||||||
|
.select(db.ref("integration").withSchema(TableName.WorkflowIntegrations))
|
||||||
|
.select(db.ref("slug").withSchema(TableName.WorkflowIntegrations))
|
||||||
|
.where(filter);
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Find with Workflow integration details" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return { ...slackIntegrationOrm, findByIdWithWorkflowIntegrationDetails, findWithWorkflowIntegrationDetails };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
|||||||
|
|
||||||
import { TKmsServiceFactory } from "../kms/kms-service";
|
import { TKmsServiceFactory } from "../kms/kms-service";
|
||||||
import { KmsDataKey } from "../kms/kms-types";
|
import { KmsDataKey } from "../kms/kms-types";
|
||||||
|
import { TWorkflowIntegrationDALFactory } from "../workflow-integration/workflow-integration-dal";
|
||||||
|
import { WorkflowIntegration } from "../workflow-integration/workflow-integration-types";
|
||||||
import { TAdminSlackConfigDALFactory } from "./admin-slack-config-dal";
|
import { TAdminSlackConfigDALFactory } from "./admin-slack-config-dal";
|
||||||
import { fetchSlackChannels, getAdminSlackCredentials } from "./slack-fns";
|
import { fetchSlackChannels, getAdminSlackCredentials } from "./slack-fns";
|
||||||
import { TSlackIntegrationDALFactory } from "./slack-integration-dal";
|
import { TSlackIntegrationDALFactory } from "./slack-integration-dal";
|
||||||
@@ -24,10 +26,18 @@ import {
|
|||||||
} from "./slack-types";
|
} from "./slack-types";
|
||||||
|
|
||||||
type TSlackServiceFactoryDep = {
|
type TSlackServiceFactoryDep = {
|
||||||
slackIntegrationDAL: Pick<TSlackIntegrationDALFactory, "find" | "findById" | "deleteById" | "updateById" | "create">;
|
slackIntegrationDAL: Pick<
|
||||||
|
TSlackIntegrationDALFactory,
|
||||||
|
| "deleteById"
|
||||||
|
| "updateById"
|
||||||
|
| "create"
|
||||||
|
| "findByIdWithWorkflowIntegrationDetails"
|
||||||
|
| "findWithWorkflowIntegrationDetails"
|
||||||
|
>;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getOrgPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getOrgPermission">;
|
||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey" | "encryptWithRootKey" | "decryptWithRootKey">;
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey" | "encryptWithRootKey" | "decryptWithRootKey">;
|
||||||
adminSlackConfigDAL: Pick<TAdminSlackConfigDALFactory, "findById">;
|
adminSlackConfigDAL: Pick<TAdminSlackConfigDALFactory, "findById">;
|
||||||
|
workflowIntegrationDAL: Pick<TWorkflowIntegrationDALFactory, "transaction" | "create" | "updateById" | "deleteById">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSlackServiceFactory = ReturnType<typeof slackServiceFactory>;
|
export type TSlackServiceFactory = ReturnType<typeof slackServiceFactory>;
|
||||||
@@ -36,7 +46,8 @@ export const slackServiceFactory = ({
|
|||||||
permissionService,
|
permissionService,
|
||||||
slackIntegrationDAL,
|
slackIntegrationDAL,
|
||||||
kmsService,
|
kmsService,
|
||||||
adminSlackConfigDAL
|
adminSlackConfigDAL,
|
||||||
|
workflowIntegrationDAL
|
||||||
}: TSlackServiceFactoryDep) => {
|
}: TSlackServiceFactoryDep) => {
|
||||||
const completeSlackIntegration = async ({
|
const completeSlackIntegration = async ({
|
||||||
orgId,
|
orgId,
|
||||||
@@ -59,17 +70,31 @@ export const slackServiceFactory = ({
|
|||||||
plainText: Buffer.from(botAccessToken, "utf8")
|
plainText: Buffer.from(botAccessToken, "utf8")
|
||||||
});
|
});
|
||||||
|
|
||||||
await slackIntegrationDAL.create({
|
await workflowIntegrationDAL.transaction(async (tx) => {
|
||||||
orgId,
|
const workflowIntegration = await workflowIntegrationDAL.create(
|
||||||
slug,
|
{
|
||||||
description,
|
description,
|
||||||
teamId,
|
orgId,
|
||||||
teamName,
|
slug,
|
||||||
slackUserId,
|
integration: WorkflowIntegration.SLACK
|
||||||
slackAppId,
|
},
|
||||||
slackBotId,
|
tx
|
||||||
slackBotUserId,
|
);
|
||||||
encryptedBotAccessToken
|
|
||||||
|
await slackIntegrationDAL.create(
|
||||||
|
{
|
||||||
|
// @ts-expect-error id is kept as fixed because it is always equal to the workflow integration ID
|
||||||
|
id: workflowIntegration.id,
|
||||||
|
teamId,
|
||||||
|
teamName,
|
||||||
|
slackUserId,
|
||||||
|
slackAppId,
|
||||||
|
slackBotId,
|
||||||
|
slackBotUserId,
|
||||||
|
encryptedBotAccessToken
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -83,7 +108,13 @@ export const slackServiceFactory = ({
|
|||||||
slackBotId,
|
slackBotId,
|
||||||
slackBotUserId
|
slackBotUserId
|
||||||
}: TReinstallSlackIntegrationDTO) => {
|
}: TReinstallSlackIntegrationDTO) => {
|
||||||
const slackIntegration = await slackIntegrationDAL.findById(id);
|
const slackIntegration = await slackIntegrationDAL.findByIdWithWorkflowIntegrationDetails(id);
|
||||||
|
|
||||||
|
if (!slackIntegration) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: "Slack integration not found"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const { encryptor: orgDataKeyEncryptor } = await kmsService.createCipherPairWithDataKey({
|
const { encryptor: orgDataKeyEncryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
orgId: slackIntegration.orgId,
|
orgId: slackIntegration.orgId,
|
||||||
@@ -228,7 +259,7 @@ export const slackServiceFactory = ({
|
|||||||
|
|
||||||
const getReinstallUrl = async ({ actorId, actor, actorOrgId, actorAuthMethod, id }: TGetReinstallUrlDTO) => {
|
const getReinstallUrl = async ({ actorId, actor, actorOrgId, actorAuthMethod, id }: TGetReinstallUrlDTO) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
const slackIntegration = await slackIntegrationDAL.findById(id);
|
const slackIntegration = await slackIntegrationDAL.findByIdWithWorkflowIntegrationDetails(id);
|
||||||
|
|
||||||
if (!slackIntegration) {
|
if (!slackIntegration) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
@@ -275,7 +306,7 @@ export const slackServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Settings);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Settings);
|
||||||
|
|
||||||
const slackIntegrations = await slackIntegrationDAL.find({
|
const slackIntegrations = await slackIntegrationDAL.findWithWorkflowIntegrationDetails({
|
||||||
orgId: actorOrgId
|
orgId: actorOrgId
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -289,7 +320,7 @@ export const slackServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
id
|
id
|
||||||
}: TGetSlackIntegrationByIdDTO) => {
|
}: TGetSlackIntegrationByIdDTO) => {
|
||||||
const slackIntegration = await slackIntegrationDAL.findById(id);
|
const slackIntegration = await slackIntegrationDAL.findByIdWithWorkflowIntegrationDetails(id);
|
||||||
if (!slackIntegration) {
|
if (!slackIntegration) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Slack integration not found."
|
message: "Slack integration not found."
|
||||||
@@ -316,7 +347,7 @@ export const slackServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
id
|
id
|
||||||
}: TGetSlackIntegrationChannelsDTO) => {
|
}: TGetSlackIntegrationChannelsDTO) => {
|
||||||
const slackIntegration = await slackIntegrationDAL.findById(id);
|
const slackIntegration = await slackIntegrationDAL.findByIdWithWorkflowIntegrationDetails(id);
|
||||||
if (!slackIntegration) {
|
if (!slackIntegration) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Slack integration not found."
|
message: "Slack integration not found."
|
||||||
@@ -354,7 +385,7 @@ export const slackServiceFactory = ({
|
|||||||
slug,
|
slug,
|
||||||
description
|
description
|
||||||
}: TUpdateSlackIntegrationDTO) => {
|
}: TUpdateSlackIntegrationDTO) => {
|
||||||
const slackIntegration = await slackIntegrationDAL.findById(id);
|
const slackIntegration = await slackIntegrationDAL.findByIdWithWorkflowIntegrationDetails(id);
|
||||||
if (!slackIntegration) {
|
if (!slackIntegration) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Slack integration not found"
|
message: "Slack integration not found"
|
||||||
@@ -371,9 +402,22 @@ export const slackServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
|
||||||
|
|
||||||
return slackIntegrationDAL.updateById(slackIntegration.id, {
|
return workflowIntegrationDAL.transaction(async (tx) => {
|
||||||
slug,
|
await workflowIntegrationDAL.updateById(
|
||||||
description
|
slackIntegration.id,
|
||||||
|
{
|
||||||
|
slug,
|
||||||
|
description
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
const updatedIntegration = await slackIntegrationDAL.findByIdWithWorkflowIntegrationDetails(
|
||||||
|
slackIntegration.id,
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
return updatedIntegration!;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -384,7 +428,7 @@ export const slackServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
id
|
id
|
||||||
}: TDeleteSlackIntegrationDTO) => {
|
}: TDeleteSlackIntegrationDTO) => {
|
||||||
const slackIntegration = await slackIntegrationDAL.findById(id);
|
const slackIntegration = await slackIntegrationDAL.findByIdWithWorkflowIntegrationDetails(id);
|
||||||
if (!slackIntegration) {
|
if (!slackIntegration) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Slack integration not found"
|
message: "Slack integration not found"
|
||||||
@@ -401,7 +445,9 @@ export const slackServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Settings);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Settings);
|
||||||
|
|
||||||
return slackIntegrationDAL.deleteById(id);
|
await workflowIntegrationDAL.deleteById(id);
|
||||||
|
|
||||||
|
return slackIntegration;
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TWorkflowIntegrationDALFactory = ReturnType<typeof workflowIntegrationDALFactory>;
|
||||||
|
|
||||||
|
export const workflowIntegrationDALFactory = (db: TDbClient) => {
|
||||||
|
const workflowIntegrationOrm = ormify(db, TableName.WorkflowIntegrations);
|
||||||
|
|
||||||
|
return workflowIntegrationOrm;
|
||||||
|
};
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
|
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||||
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
|
|
||||||
|
import { TWorkflowIntegrationDALFactory } from "./workflow-integration-dal";
|
||||||
|
import { TGetWorkflowIntegrationsByOrg } from "./workflow-integration-types";
|
||||||
|
|
||||||
|
type TWorkflowIntegrationServiceFactoryDep = {
|
||||||
|
workflowIntegrationDAL: Pick<TWorkflowIntegrationDALFactory, "find">;
|
||||||
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getOrgPermission">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TWorkflowIntegrationServiceFactory = ReturnType<typeof workflowIntegrationServiceFactory>;
|
||||||
|
|
||||||
|
export const workflowIntegrationServiceFactory = ({
|
||||||
|
workflowIntegrationDAL,
|
||||||
|
permissionService
|
||||||
|
}: TWorkflowIntegrationServiceFactoryDep) => {
|
||||||
|
const getIntegrationsByOrg = async ({
|
||||||
|
actorId,
|
||||||
|
actor,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod
|
||||||
|
}: TGetWorkflowIntegrationsByOrg) => {
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
|
||||||
|
|
||||||
|
return workflowIntegrationDAL.find({
|
||||||
|
orgId: actorOrgId
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
getIntegrationsByOrg
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { TOrgPermission } from "@app/lib/types";
|
||||||
|
|
||||||
|
export enum WorkflowIntegration {
|
||||||
|
SLACK = "slack"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TGetWorkflowIntegrationsByOrg = Omit<TOrgPermission, "orgId">;
|
||||||
@@ -8,5 +8,6 @@ export {
|
|||||||
fetchSlackReinstallUrl,
|
fetchSlackReinstallUrl,
|
||||||
useGetSlackIntegrationById,
|
useGetSlackIntegrationById,
|
||||||
useGetSlackIntegrationChannels,
|
useGetSlackIntegrationChannels,
|
||||||
useGetSlackIntegrations
|
useGetSlackIntegrations,
|
||||||
|
useGetWorkflowIntegrations
|
||||||
} from "./queries";
|
} from "./queries";
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export const useDeleteSlackIntegration = () => {
|
|||||||
},
|
},
|
||||||
onSuccess: (_, { orgId, id }) => {
|
onSuccess: (_, { orgId, id }) => {
|
||||||
queryClient.invalidateQueries(workflowIntegrationKeys.getSlackIntegration(id));
|
queryClient.invalidateQueries(workflowIntegrationKeys.getSlackIntegration(id));
|
||||||
queryClient.invalidateQueries(workflowIntegrationKeys.getSlackIntegrations(orgId));
|
queryClient.invalidateQueries(workflowIntegrationKeys.getIntegrations(orgId));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ import { useQuery } from "@tanstack/react-query";
|
|||||||
|
|
||||||
import { apiRequest } from "@app/config/request";
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
import { SlackIntegration, SlackIntegrationChannel } from "./types";
|
import { SlackIntegration, SlackIntegrationChannel, WorkflowIntegration } from "./types";
|
||||||
|
|
||||||
export const workflowIntegrationKeys = {
|
export const workflowIntegrationKeys = {
|
||||||
|
getIntegrations: (orgId?: string) => [{ orgId }, "workflow-integrations"],
|
||||||
getSlackIntegrations: (orgId?: string) => [{ orgId }, "slack-workflow-integrations"],
|
getSlackIntegrations: (orgId?: string) => [{ orgId }, "slack-workflow-integrations"],
|
||||||
getSlackIntegration: (id?: string) => [{ id }, "slack-workflow-integration"],
|
getSlackIntegration: (id?: string) => [{ id }, "slack-workflow-integration"],
|
||||||
getSlackIntegrationChannels: (id?: string) => [{ id }, "slack-workflow-integration-channels"]
|
getSlackIntegrationChannels: (id?: string) => [{ id }, "slack-workflow-integration-channels"]
|
||||||
@@ -27,14 +28,10 @@ export const fetchSlackInstallUrl = async ({
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchSlackReinstallUrl = async ({
|
export const fetchSlackReinstallUrl = async ({ id }: { id: string }) => {
|
||||||
slackIntegrationId
|
|
||||||
}: {
|
|
||||||
slackIntegrationId: string;
|
|
||||||
}) => {
|
|
||||||
const { data } = await apiRequest.get<string>("/api/v1/workflow-integrations/slack/reinstall", {
|
const { data } = await apiRequest.get<string>("/api/v1/workflow-integrations/slack/reinstall", {
|
||||||
params: {
|
params: {
|
||||||
slackIntegrationId
|
id
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -63,6 +60,12 @@ export const fetchSlackIntegrationChannels = async (id?: string) => {
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const fetchWorkflowIntegrations = async () => {
|
||||||
|
const { data } = await apiRequest.get<WorkflowIntegration[]>("/api/v1/workflow-integrations");
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
export const useGetSlackIntegrations = (orgId?: string) =>
|
export const useGetSlackIntegrations = (orgId?: string) =>
|
||||||
useQuery({
|
useQuery({
|
||||||
queryKey: workflowIntegrationKeys.getSlackIntegrations(orgId),
|
queryKey: workflowIntegrationKeys.getSlackIntegrations(orgId),
|
||||||
@@ -83,3 +86,10 @@ export const useGetSlackIntegrationChannels = (id?: string) =>
|
|||||||
queryFn: () => fetchSlackIntegrationChannels(id),
|
queryFn: () => fetchSlackIntegrationChannels(id),
|
||||||
enabled: Boolean(id)
|
enabled: Boolean(id)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const useGetWorkflowIntegrations = (id?: string) =>
|
||||||
|
useQuery({
|
||||||
|
queryKey: workflowIntegrationKeys.getIntegrations(id),
|
||||||
|
queryFn: () => fetchWorkflowIntegrations(),
|
||||||
|
enabled: Boolean(id)
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
export enum WorkflowIntegrationPlatform {
|
export enum WorkflowIntegrationPlatform {
|
||||||
SLACK = "Slack"
|
SLACK = "slack"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type WorkflowIntegration = {
|
||||||
|
id: string;
|
||||||
|
slug: string;
|
||||||
|
description: string;
|
||||||
|
integration: WorkflowIntegrationPlatform;
|
||||||
|
};
|
||||||
|
|
||||||
export type SlackIntegration = {
|
export type SlackIntegration = {
|
||||||
id: string;
|
id: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
|
|||||||
+4
-1
@@ -11,9 +11,12 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const IntegrationFormDetails = ({ isOpen, id, onOpenChange, workflowPlatform }: Props) => {
|
export const IntegrationFormDetails = ({ isOpen, id, onOpenChange, workflowPlatform }: Props) => {
|
||||||
|
const modalTitle =
|
||||||
|
workflowPlatform === WorkflowIntegrationPlatform.SLACK ? "Slack integration" : "Integration";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
||||||
<ModalContent title={`${WorkflowIntegrationPlatform.SLACK} integration`}>
|
<ModalContent title={modalTitle}>
|
||||||
{workflowPlatform === WorkflowIntegrationPlatform.SLACK && (
|
{workflowPlatform === WorkflowIntegrationPlatform.SLACK && (
|
||||||
<SlackIntegrationForm id={id} onClose={() => onOpenChange(false)} />
|
<SlackIntegrationForm id={id} onClose={() => onOpenChange(false)} />
|
||||||
)}
|
)}
|
||||||
|
|||||||
+26
-25
@@ -29,7 +29,7 @@ import { usePopUp } from "@app/hooks";
|
|||||||
import {
|
import {
|
||||||
fetchSlackReinstallUrl,
|
fetchSlackReinstallUrl,
|
||||||
useDeleteSlackIntegration,
|
useDeleteSlackIntegration,
|
||||||
useGetSlackIntegrations
|
useGetWorkflowIntegrations
|
||||||
} from "@app/hooks/api";
|
} from "@app/hooks/api";
|
||||||
import { WorkflowIntegrationPlatform } from "@app/hooks/api/workflowIntegrations/types";
|
import { WorkflowIntegrationPlatform } from "@app/hooks/api/workflowIntegrations/types";
|
||||||
|
|
||||||
@@ -46,8 +46,9 @@ export const OrgWorkflowIntegrationTab = withPermission(
|
|||||||
|
|
||||||
const { currentOrg } = useOrganization();
|
const { currentOrg } = useOrganization();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { data: slackIntegrations, isLoading: isSlackIntegrationsLoading } =
|
const { data: workflowIntegrations, isLoading: isWorkflowIntegrationsLoading } =
|
||||||
useGetSlackIntegrations(currentOrg?.id);
|
useGetWorkflowIntegrations(currentOrg?.id);
|
||||||
|
|
||||||
const { mutateAsync: deleteSlackIntegration } = useDeleteSlackIntegration();
|
const { mutateAsync: deleteSlackIntegration } = useDeleteSlackIntegration();
|
||||||
|
|
||||||
const handleRemoveIntegration = async () => {
|
const handleRemoveIntegration = async () => {
|
||||||
@@ -74,7 +75,7 @@ export const OrgWorkflowIntegrationTab = withPermission(
|
|||||||
if (platform === WorkflowIntegrationPlatform.SLACK) {
|
if (platform === WorkflowIntegrationPlatform.SLACK) {
|
||||||
try {
|
try {
|
||||||
const slackReinstallUrl = await fetchSlackReinstallUrl({
|
const slackReinstallUrl = await fetchSlackReinstallUrl({
|
||||||
slackIntegrationId: id
|
id
|
||||||
});
|
});
|
||||||
|
|
||||||
if (slackReinstallUrl) {
|
if (slackReinstallUrl) {
|
||||||
@@ -91,8 +92,6 @@ export const OrgWorkflowIntegrationTab = withPermission(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const isIntegrationsLoading = isSlackIntegrationsLoading;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
@@ -123,23 +122,25 @@ export const OrgWorkflowIntegrationTab = withPermission(
|
|||||||
</Tr>
|
</Tr>
|
||||||
</THead>
|
</THead>
|
||||||
<TBody>
|
<TBody>
|
||||||
{isIntegrationsLoading && (
|
{isWorkflowIntegrationsLoading && (
|
||||||
<TableSkeleton columns={2} innerKey="integrations-loading" />
|
<TableSkeleton columns={2} innerKey="integrations-loading" />
|
||||||
)}
|
)}
|
||||||
{!isIntegrationsLoading && slackIntegrations && slackIntegrations.length === 0 && (
|
{!isWorkflowIntegrationsLoading &&
|
||||||
<Tr>
|
workflowIntegrations &&
|
||||||
<Td colSpan={5}>
|
workflowIntegrations.length === 0 && (
|
||||||
<EmptyState title="No workflow integrations found" icon={faGear} />
|
<Tr>
|
||||||
</Td>
|
<Td colSpan={5}>
|
||||||
</Tr>
|
<EmptyState title="No workflow integrations found" icon={faGear} />
|
||||||
)}
|
</Td>
|
||||||
{slackIntegrations?.map((slackIntegration) => (
|
</Tr>
|
||||||
<Tr key={slackIntegration.id}>
|
)}
|
||||||
|
{workflowIntegrations?.map((workflowIntegration) => (
|
||||||
|
<Tr key={workflowIntegration.id}>
|
||||||
<Td className="flex max-w-xs items-center overflow-hidden text-ellipsis hover:overflow-auto hover:break-all">
|
<Td className="flex max-w-xs items-center overflow-hidden text-ellipsis hover:overflow-auto hover:break-all">
|
||||||
<FontAwesomeIcon icon={faSlack} />
|
<FontAwesomeIcon icon={faSlack} />
|
||||||
<div className="ml-2">SLACK</div>
|
<div className="ml-2">{workflowIntegration.integration.toUpperCase()}</div>
|
||||||
</Td>
|
</Td>
|
||||||
<Td>{slackIntegration.slug}</Td>
|
<Td>{workflowIntegration.slug}</Td>
|
||||||
<Td>
|
<Td>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild className="rounded-lg">
|
<DropdownMenuTrigger asChild className="rounded-lg">
|
||||||
@@ -153,8 +154,8 @@ export const OrgWorkflowIntegrationTab = withPermission(
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
handlePopUpOpen("integrationDetails", {
|
handlePopUpOpen("integrationDetails", {
|
||||||
id: slackIntegration.id,
|
id: workflowIntegration.id,
|
||||||
platform: WorkflowIntegrationPlatform.SLACK
|
platform: workflowIntegration.integration
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -174,8 +175,8 @@ export const OrgWorkflowIntegrationTab = withPermission(
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
triggerReinstall(
|
triggerReinstall(
|
||||||
WorkflowIntegrationPlatform.SLACK,
|
workflowIntegration.integration,
|
||||||
slackIntegration.id
|
workflowIntegration.id
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -197,9 +198,9 @@ export const OrgWorkflowIntegrationTab = withPermission(
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
handlePopUpOpen("removeIntegration", {
|
handlePopUpOpen("removeIntegration", {
|
||||||
id: slackIntegration.id,
|
id: workflowIntegration.id,
|
||||||
slug: slackIntegration.slug,
|
slug: workflowIntegration.slug,
|
||||||
platform: WorkflowIntegrationPlatform.SLACK
|
platform: workflowIntegration.integration
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user