matrix-dimension/src/api/dimension/DimensionIntegrationsService.ts
2019-07-10 22:10:10 -06:00

165 lines
7.4 KiB
TypeScript

import { Context, DELETE, GET, Path, PathParam, POST, Security, ServiceContext } from "typescript-rest";
import { Widget } from "../../integrations/Widget";
import { Cache, CACHE_INTEGRATIONS } from "../../MemoryCache";
import { Integration } from "../../integrations/Integration";
import { ApiError } from "../ApiError";
import { WidgetStore } from "../../db/WidgetStore";
import { SimpleBot } from "../../integrations/SimpleBot";
import { NebStore } from "../../db/NebStore";
import { ComplexBot } from "../../integrations/ComplexBot";
import { Bridge } from "../../integrations/Bridge";
import { BridgeStore } from "../../db/BridgeStore";
import { BotStore } from "../../db/BotStore";
import { ROLE_USER } from "../security/MatrixSecurity";
export interface IntegrationsResponse {
widgets: Widget[],
bots: SimpleBot[],
complexBots: ComplexBot[],
bridges: Bridge[],
}
/**
* API for managing integrations, primarily for a given room
*/
@Path("/api/v1/dimension/integrations")
export class DimensionIntegrationsService {
@Context
private context: ServiceContext;
/**
* Gets a list of widgets
* @param {boolean} enabledOnly True to only return the enabled widgets
* @returns {Promise<Widget[]>} Resolves to the widget list
*/
public static async getWidgets(enabledOnly: boolean): Promise<Widget[]> {
const cached = Cache.for(CACHE_INTEGRATIONS).get("widgets");
if (cached) return cached;
const widgets = await WidgetStore.listAll(enabledOnly ? true : null);
Cache.for(CACHE_INTEGRATIONS).put("widgets", widgets);
return widgets;
}
/**
* Gets a list of bridges
* @param {boolean} enabledOnly True to only return the enabled bridges
* @param {string} forUserId The requesting user ID
* @param {string} inRoomId If specified, the room ID to list the bridges in
* @returns {Promise<Bridge[]>} Resolves to the bridge list
*/
public static async getBridges(enabledOnly: boolean, forUserId: string, inRoomId?: string): Promise<Bridge[]> {
return BridgeStore.listAll(forUserId, enabledOnly ? true : null, inRoomId);
}
/**
* Gets a list of simple bots
* @param {string} userId The requesting user ID
* @returns {Promise<SimpleBot[]>} Resolves to the simple bot list
*/
public static async getSimpleBots(userId: string): Promise<SimpleBot[]> {
const cached = Cache.for(CACHE_INTEGRATIONS).get("simple_bots");
if (cached) return cached;
const nebs = await NebStore.listSimpleBots(userId);
const custom = (await BotStore.getCustomBots())
.filter(b => b.isEnabled)
.map(b => SimpleBot.fromCached(b));
const bots = [...nebs, ...custom];
Cache.for(CACHE_INTEGRATIONS).put("simple_bots", bots);
return bots;
}
/**
* Gets a list of complex bots
* @param {string} userId The requesting user ID
* @param {string} roomId The room ID to get the complex bots for
* @returns {Promise<ComplexBot[]>} Resolves to the complex bot list
*/
public static async getComplexBots(userId: string, roomId: string): Promise<ComplexBot[]> {
const cached = Cache.for(CACHE_INTEGRATIONS).get("complex_bots_" + roomId);
if (cached) return cached;
const bots = await NebStore.listComplexBots(userId, roomId);
Cache.for(CACHE_INTEGRATIONS).put("complex_bots_" + roomId, bots);
return bots;
}
@GET
@Path("room/:roomId")
@Security(ROLE_USER)
public async getIntegrationsInRoom(@PathParam("roomId") roomId: string): Promise<IntegrationsResponse> {
const userId = this.context.request.user.userId;
return {
widgets: await DimensionIntegrationsService.getWidgets(true),
bots: await DimensionIntegrationsService.getSimpleBots(userId),
complexBots: await DimensionIntegrationsService.getComplexBots(userId, roomId),
bridges: await DimensionIntegrationsService.getBridges(true, userId, roomId),
};
}
@GET
@Path("room/:roomId/integrations/:category/:type")
@Security(ROLE_USER)
public async getIntegrationInRoom(@PathParam("roomId") roomId: string, @PathParam("category") category: string, @PathParam("type") integrationType: string): Promise<any> {
const roomConfig = await this.getIntegrationsInRoom(roomId); // does auth for us
if (category === "widget") return roomConfig.widgets.find(i => i.type === integrationType);
else if (category === "bot") return roomConfig.bots.find(i => i.type === integrationType);
else if (category === "complex-bot") return roomConfig.complexBots.find(i => i.type === integrationType);
else if (category === "bridge") return roomConfig.bridges.find(i => i.type === integrationType);
else throw new ApiError(400, "Unrecognized category");
}
@POST
@Path("room/:roomId/integrations/:category/:type/config")
@Security(ROLE_USER)
public async setIntegrationConfigurationInRoom(@PathParam("roomId") roomId: string, @PathParam("category") category: string, @PathParam("type") integrationType: string, newConfig: any): Promise<any> {
const userId = this.context.request.user.userId;
if (category === "complex-bot") await NebStore.setComplexBotConfig(userId, integrationType, roomId, newConfig);
else if (category === "bridge") await BridgeStore.setBridgeRoomConfig(userId, integrationType, roomId, newConfig);
else throw new ApiError(400, "Unrecognized category");
Cache.for(CACHE_INTEGRATIONS).clear(); // TODO: Improve which cache we invalidate
return {}; // 200 OK
}
@DELETE
@Path("room/:roomId/integrations/:category/:type")
@Security(ROLE_USER)
public async removeIntegrationInRoom(@PathParam("roomId") roomId: string, @PathParam("category") category: string, @PathParam("type") integrationType: string): Promise<any> {
const userId = this.context.request.user.userId;
if (category === "widget") throw new ApiError(400, "Widgets should be removed client-side");
else if (category === "bot") {
if (integrationType.startsWith(BotStore.TYPE_PREFIX)) {
await BotStore.removeCustomByTypeFromRoom(integrationType, roomId);
} else await NebStore.removeSimpleBot(integrationType, roomId, userId);
} else if (category === "complex-bot") throw new ApiError(400, "Complex bots should be removed automatically");
else if (category === "bridge") throw new ApiError(400, "Bridges should be removed automatically");
else throw new ApiError(400, "Unrecognized category");
Cache.for(CACHE_INTEGRATIONS).clear(); // TODO: Improve which cache we invalidate
return {}; // 200 OK
}
@GET
@Path(":category/:type")
public async getIntegration(@PathParam("category") category: string, @PathParam("type") type: string): Promise<Integration> {
// This is intentionally an unauthed endpoint to ensure we can use it in widgets
let integrations: Integration[] = [];
if (category === "widget") integrations = await DimensionIntegrationsService.getWidgets(true);
else throw new ApiError(400, "Unsupported category");
for (const integration of integrations) {
if (integration.category === category && integration.type === type) {
return integration;
}
}
throw new ApiError(404, "Integration not found");
}
}