Add ability to link Slack <-> Outline accounts (#6682)

This commit is contained in:
Tom Moor
2024-03-18 19:21:38 -06:00
committed by GitHub
parent e294fafd4f
commit cbdacc7cfd
23 changed files with 647 additions and 421 deletions

View File

@@ -1,8 +1,4 @@
import {
IntegrationService,
UserCreatableIntegrationService,
IntegrationType,
} from "@shared/types";
import { IntegrationService, IntegrationType } from "@shared/types";
import { IntegrationAuthentication, User } from "@server/models";
import Integration from "@server/models/Integration";
import {
@@ -108,7 +104,7 @@ describe("#integrations.create", () => {
body: {
token: admin.getJwtToken(),
type: IntegrationType.Embed,
service: UserCreatableIntegrationService.Diagrams,
service: IntegrationService.Diagrams,
settings: { url: "not a url" },
},
});
@@ -124,16 +120,14 @@ describe("#integrations.create", () => {
body: {
token: admin.getJwtToken(),
type: IntegrationType.Analytics,
service: UserCreatableIntegrationService.GoogleAnalytics,
service: IntegrationService.GoogleAnalytics,
settings: { measurementId: "123" },
},
});
const body = await res.json();
expect(res.status).toEqual(200);
expect(body.data.type).toEqual(IntegrationType.Analytics);
expect(body.data.service).toEqual(
UserCreatableIntegrationService.GoogleAnalytics
);
expect(body.data.service).toEqual(IntegrationService.GoogleAnalytics);
expect(body.data.settings).not.toBeFalsy();
expect(body.data.settings.measurementId).toEqual("123");
});
@@ -145,14 +139,14 @@ describe("#integrations.create", () => {
body: {
token: admin.getJwtToken(),
type: IntegrationType.Embed,
service: UserCreatableIntegrationService.Grist,
service: IntegrationService.Grist,
settings: { url: "https://grist.example.com" },
},
});
const body = await res.json();
expect(res.status).toEqual(200);
expect(body.data.type).toEqual(IntegrationType.Embed);
expect(body.data.service).toEqual(UserCreatableIntegrationService.Grist);
expect(body.data.service).toEqual(IntegrationService.Grist);
expect(body.data.settings).not.toBeFalsy();
expect(body.data.settings.url).toEqual("https://grist.example.com");
});
@@ -183,9 +177,7 @@ describe("#integrations.delete", () => {
id: integration.id,
},
});
const body = await res.json();
expect(res.status).toEqual(403);
expect(body.message).toEqual("Admin role required");
});
it("should fail with status 400 bad request when id is not sent", async () => {
@@ -200,6 +192,23 @@ describe("#integrations.delete", () => {
expect(body.message).toEqual("id: Required");
});
it("should succeed as user deleting own linked account integration", async () => {
const user = await buildUser();
const linkedAccount = await buildIntegration({
userId: user.id,
teamId: user.teamId,
service: IntegrationService.Slack,
type: IntegrationType.LinkedAccount,
});
const res = await server.post("/api/integrations.delete", {
body: {
token: user.getJwtToken(),
id: linkedAccount.id,
},
});
expect(res.status).toEqual(200);
});
it("should succeed with status 200 ok when integration is deleted", async () => {
const res = await server.post("/api/integrations.delete", {
body: {

View File

@@ -1,5 +1,5 @@
import Router from "koa-router";
import { WhereOptions } from "sequelize";
import { WhereOptions, Op } from "sequelize";
import { IntegrationType } from "@shared/types";
import auth from "@server/middlewares/authentication";
import { transaction } from "@server/middlewares/transaction";
@@ -20,19 +20,31 @@ router.post(
pagination(),
validate(T.IntegrationsListSchema),
async (ctx: APIContext<T.IntegrationsListReq>) => {
const { direction, type, sort } = ctx.input.body;
const { direction, service, type, sort } = ctx.input.body;
const { user } = ctx.state.auth;
let where: WhereOptions<Integration> = {
teamId: user.teamId,
};
if (type) {
where = {
...where,
type,
};
where = { ...where, type };
}
if (service) {
where = { ...where, service };
}
// Linked account is special as these are user-specific, other integrations are workspace-wide.
where = {
...where,
[Op.or]: [
{ userId: user.id, type: IntegrationType.LinkedAccount },
{
type: {
[Op.not]: IntegrationType.LinkedAccount,
},
},
],
};
const integrations = await Integration.findAll({
where,
@@ -121,7 +133,7 @@ router.post(
router.post(
"integrations.delete",
auth({ admin: true }),
auth(),
validate(T.IntegrationsDeleteSchema),
transaction(),
async (ctx: APIContext<T.IntegrationsDeleteReq>) => {

View File

@@ -1,6 +1,7 @@
import { z } from "zod";
import {
IntegrationType,
IntegrationService,
UserCreatableIntegrationService,
} from "@shared/types";
import { Integration } from "@server/models";
@@ -24,6 +25,9 @@ export const IntegrationsListSchema = BaseSchema.extend({
/** Integration type */
type: z.nativeEnum(IntegrationType).optional(),
/** Integration service */
service: z.nativeEnum(IntegrationService).optional(),
}),
});