Add ability to request all supported types

This commit is contained in:
Will Hunt 2021-11-28 14:49:30 +00:00
parent 3b732145e5
commit 87e5020469
5 changed files with 77 additions and 36 deletions

View File

@ -13,7 +13,7 @@ import { GitLabClient } from "./Gitlab/Client";
import { JiraProject } from "./Jira/Types";
import LogWrapper from "./LogWrapper";
import { MessageSenderClient } from "./MatrixSender";
import { ApiError } from "./provisioning/api";
import { ApiError, GetConnectionTypeResponseItem } from "./provisioning/api";
import { UserTokenStore } from "./UserTokenStore";
const log = new LogWrapper("ConnectionManager");
@ -289,4 +289,12 @@ export class ConnectionManager {
const connectionIndex = this.connections.indexOf(connection);
this.connections.splice(connectionIndex, 1);
}
public getConnectionTypesProvisioningDetails(): {[eventType: string]: GetConnectionTypeResponseItem} {
const results: {[eventType: string]: GetConnectionTypeResponseItem} = {};
// TODO: Do this dynamically.
const jiraProject = JiraProjectConnection.getProvisionerDetails(this.as.botUserId);
results[jiraProject.eventType] = jiraProject;
return results;
}
}

View File

@ -13,8 +13,7 @@ import { MatrixMessageContent } from "../MatrixEvent";
import { CommandConnection } from "./CommandConnection";
import { UserTokenStore } from "../UserTokenStore";
import { CommandError, NotLoggedInError } from "../errors";
import { ApiError } from "../provisioning/api";
import { stat } from "fs";
import { ApiError, ErrCode } from "../provisioning/api";
type JiraAllowedEventsNames = "issue.created";
const JiraAllowedEvents: JiraAllowedEventsNames[] = ["issue.created"];
@ -29,18 +28,18 @@ export interface JiraProjectConnectionState {
function validateJiraConnectionState(state: JiraProjectConnectionState) {
const {url, commandPrefix, events} = state as JiraProjectConnectionState;
if (url === undefined) {
throw new ApiError("Expected a 'url' property", 400);
throw new ApiError("Expected a 'url' property", ErrCode.BadValue);
}
if (commandPrefix) {
if (typeof commandPrefix !== "string") {
throw new ApiError("Expected 'commandPrefix' to be a string", 400);
throw new ApiError("Expected 'commandPrefix' to be a string", ErrCode.BadValue);
}
if (commandPrefix.length < 2 || commandPrefix.length > 24) {
throw new ApiError("Expected 'commandPrefix' to be between 2-24 characters", 400);
throw new ApiError("Expected 'commandPrefix' to be between 2-24 characters", ErrCode.BadValue);
}
}
if (events?.find((ev) => !JiraAllowedEvents.includes(ev))?.length) {
throw new ApiError(`'events' can only contain ${JiraAllowedEvents.join(", ")}`, 400);
throw new ApiError(`'events' can only contain ${JiraAllowedEvents.join(", ")}`, ErrCode.BadValue);
}
return {url, commandPrefix, events};
}
@ -160,6 +159,16 @@ export class JiraProjectConnection extends CommandConnection implements IConnect
return `${this.roomId}/${JiraProjectConnection.CanonicalEventType}/${this.stateKey}`;
}
public static getProvisionerDetails(botUserId: string) {
return {
service: "jira",
eventType: JiraProjectConnection.CanonicalEventType,
type: "JiraProject",
// TODO: Add ability to configure the bot per connnection type.
botUserId: botUserId,
}
}
public getProvisionerDetails() {
return {
service: "jira",

View File

@ -33,6 +33,22 @@ HTTP 200
Any other response should be considered a failed request (e.g. 404, 502 etc).
## GET /v1/connectiontypes
Request the connection types enabled for this bridge.
### Response
```json5
{
"uk.half-shot.matrix-hookshot.jira.project": {
"type": "uk.half-shot.matrix-hookshot.jira.project", // Corresponds to the state type in the connection
"service": "jira", // or github, webhook. A human-readable service name to make things look pretty
"botUserId": "@hookshot:yourdomain.com", // The bot mxid for the service. Currently this is the sender_localpart, but may change in the future.
}
}
```
## GET /v1/{roomId}/connections
Request the connections for a given room. The `{roomId}` parameter is the target Matrix room.
@ -44,12 +60,14 @@ Request the connections for a given room. The `{roomId}` parameter is the target
"type": "uk.half-shot.matrix-hookshot.jira.project", // Corresponds to the state type in the connection
"id": "opaque-unique-id", // An opaque ID used to refer to this connection. Should **NOT** be assumed to be stable.
"service": "jira", // or github, webhook. A human-readable service name to make things look pretty
"details": {
"botUserId": "@hookshot:yourdomain.com", // The bot mxid for the service. Currently this is the sender_localpart, but may change in the future.
"config": {
// ... connection specific details, can be configured.
}
}]
```
## GET /v1/{roomId}/connections/{id}
Request details of a single connection. The `{roomId}` parameter is the target Matrix room.
@ -61,7 +79,8 @@ Request details of a single connection. The `{roomId}` parameter is the target M
"type": "uk.half-shot.matrix-hookshot.jira.project", // Corresponds to the state type in the connection
"id": "opaque-unique-id", // An opaque ID used to refer to this connection. Should **NOT** be assumed to be stable.
"service": "jira", // or github, webhook. A human-readable service name to make things look pretty
"details": {
"botUserId": "@hookshot:yourdomain.com", // The bot mxid for the service. Currently this is the sender_localpart, but may change in the future.
"config": {
// ... connection specific details, can be configured.
}
}
@ -86,7 +105,8 @@ The body of the request is the configuration for the connection, which will be t
"type": "uk.half-shot.matrix-hookshot.jira.project", // Corresponds to the state type in the connection
"id": "opaque-unique-id", // An opaque ID used to refer to this connection. Should **NOT** be assumed to be stable.
"service": "jira", // or github, webhook. A human-readable service name to make things look pretty
"details": {
"botUserId": "@hookshot:yourdomain.com", // The bot mxid for the service. Currently this is the sender_localpart, but may change in the future.
"config": {
// ... connection specific details, can be configured.
}
}
@ -111,7 +131,8 @@ The body of the request is the configuration for the connection, which will be t
"type": "uk.half-shot.matrix-hookshot.jira.project", // Corresponds to the state type in the connection
"id": "opaque-unique-id", // An opaque ID used to refer to this connection. Should **NOT** be assumed to be stable.
"service": "jira", // or github, webhook. A human-readable service name to make things look pretty
"details": {
"botUserId": "@hookshot:yourdomain.com", // The bot mxid for the service. Currently this is the sender_localpart, but may change in the future.
"config": {
// ... connection specific details, can be configured.
}
}

View File

@ -1,12 +1,17 @@
import { Response } from "express";
export interface GetConnectionsResponseItem {
export interface GetConnectionTypeResponseItem {
type: string;
service: string;
botUserId: string;
}
export interface GetConnectionsResponseItem extends GetConnectionTypeResponseItem {
id: string;
config: Record<string, unknown>;
}
export enum ErrCode {
// Errors are prefixed with HS_
/**

View File

@ -3,7 +3,7 @@ import { Application, default as express, NextFunction, Request, Response, Route
import { ConnectionManager } from "../ConnectionManager";
import LogWrapper from "../LogWrapper";
import { Server } from "http";
import { ApiError, ErrCode, GetConnectionsResponseItem } from "./api";
import { ApiError, ErrCode, GetConnectionsResponseItem, GetConnectionTypeResponseItem } from "./api";
import { Intent, MembershipEventContent, PowerLevelsEventContent } from "matrix-bot-sdk";
const log = new LogWrapper("Provisioner");
@ -30,11 +30,15 @@ export class Provisioner {
this.expressApp = express();
this.expressApp.get("/v1/health", this.getHealth);
this.expressApp.use(this.checkAuth.bind(this));
// Room Routes
this.expressApp.get(
"/v1/connectiontypes",
this.getConnectionTypes.bind(this),
);
this.expressApp.use(this.checkUserId.bind(this));
additionalRoutes.forEach(route => {
this.expressApp.use(route.route, route.router);
});
// Room Routes
this.expressApp.get<{roomId: string}, unknown, unknown, {userId: string}>(
"/v1/:roomId/connections",
this.checkRoomId.bind(this),
@ -153,31 +157,25 @@ export class Provisioner {
return res.send({})
}
private async getConnections(req: Request<{roomId: string}>, res: Response<GetConnectionsResponseItem[]>, next: NextFunction) {
try {
const connections = await this.connMan.getAllConnectionsForRoom(req.params.roomId);
const details = connections.map(c => c.getProvisionerDetails?.()).filter(c => !!c) as GetConnectionsResponseItem[];
return res.send(details);
} catch (ex) {
log.warn(`Failed to fetch connections for ${req.params.roomId}`, ex);
return next(new ApiError(`An internal issue occured while trying to fetch connections`));
}
private getConnectionTypes(_req: Request, res: Response<Record<string, GetConnectionTypeResponseItem>>) {
return res.send(this.connMan.getConnectionTypesProvisioningDetails());
}
private async getConnection(req: Request<{roomId: string, connectionId: string}>, res: Response<GetConnectionsResponseItem>, next: NextFunction) {
try {
const connection = await this.connMan.getConnectionById(req.params.roomId, req.params.connectionId);
if (!connection) {
return next(new ApiError("Connection does not exist", ErrCode.NotFound));
}
if (!connection.getProvisionerDetails) {
return next(new ApiError("Connection type does not support updates", ErrCode.UnsupportedOperation));
}
return res.send(connection.getProvisionerDetails());
} catch (ex) {
log.warn(`Failed to fetch connections for ${req.params.roomId}`, ex);
return next(new ApiError(`An internal issue occured while trying to fetch connections`));
private getConnections(req: Request<{roomId: string}>, res: Response<GetConnectionsResponseItem[]>) {
const connections = this.connMan.getAllConnectionsForRoom(req.params.roomId);
const details = connections.map(c => c.getProvisionerDetails?.()).filter(c => !!c) as GetConnectionsResponseItem[];
return res.send(details);
}
private getConnection(req: Request<{roomId: string, connectionId: string}>, res: Response<GetConnectionsResponseItem>) {
const connection = this.connMan.getConnectionById(req.params.roomId, req.params.connectionId);
if (!connection) {
throw new ApiError("Connection does not exist", ErrCode.NotFound);
}
if (!connection.getProvisionerDetails) {
throw new ApiError("Connection type does not support updates", ErrCode.UnsupportedOperation);
}
return res.send(connection.getProvisionerDetails());
}
private async putConnection(req: Request<{roomId: string, type: string}, unknown, Record<string, unknown>, {userId: string}>, res: Response, next: NextFunction) {