Add documentation for the backend APIs

This commit is contained in:
Travis Ralston 2018-03-30 15:17:39 -06:00
parent 4365cb0753
commit 39a71429f3
13 changed files with 102 additions and 3 deletions

View File

@ -1,10 +1,32 @@
/**
* Thrown when there is a problem with a given API call. This is special-cased in the responder
* to create a JSON error response with the status code given.
*/
export class ApiError { export class ApiError {
/**
* The HTTP status code to return
*/
public statusCode: number; public statusCode: number;
public jsonResponse: any;
/**
* An object to be returned as JSON to the caller
*/
public jsonResponse: object;
/**
* The internal error code to describe what went wrong
*/
public errorCode: string; public errorCode: string;
constructor(statusCode: number, json: any, errCode = "D_UNKNOWN") { /**
* Creates a new API error
* @param {number} statusCode The HTTP status code to return
* @param {string|object} json An object to be returned as JSON or a message to be returned (which is
* then converted to JSON as {message: "your_message"})
* @param {string} errCode The internal error code to describe what went wrong
*/
constructor(statusCode: number, json: string | object, errCode = "D_UNKNOWN") {
// Because typescript is just plain dumb // Because typescript is just plain dumb
// https://stackoverflow.com/questions/31626231/custom-error-class-in-typescript // https://stackoverflow.com/questions/31626231/custom-error-class-in-typescript
Error.apply(this, ["ApiError"]); Error.apply(this, ["ApiError"]);

View File

@ -8,6 +8,9 @@ import * as _ from "lodash";
import config from "../config"; import config from "../config";
import { ApiError } from "./ApiError"; import { ApiError } from "./ApiError";
/**
* Web server for Dimension. Handles the API routes for the admin, scalar, dimension, and matrix APIs.
*/
export default class Webserver { export default class Webserver {
private app: express.Application; private app: express.Application;
@ -69,7 +72,10 @@ export default class Webserver {
}); });
} }
start() { /**
* Starts the webserver, bootstrapping the various API handlers
*/
public start() {
this.app.listen(config.web.port, config.web.address); this.app.listen(config.web.port, config.web.address);
LogService.info("Webserver", "API and UI listening on " + config.web.address + ":" + config.web.port); LogService.info("Webserver", "API and UI listening on " + config.web.address + ":" + config.web.port);
} }

View File

@ -17,6 +17,9 @@ interface AppserviceCreateRequest {
userPrefix: string; userPrefix: string;
} }
/**
* Administrative API for managing the appservices that Dimension operates.
*/
@Path("/api/v1/dimension/admin/appservices") @Path("/api/v1/dimension/admin/appservices")
export class AdminAppserviceService { export class AdminAppserviceService {

View File

@ -14,6 +14,10 @@ interface SetOptionsRequest {
options: any; options: any;
} }
/**
* Administrative API for managing the integrations for Dimension. This is to enable/disable integrations
* and set basic options. See the NEB APIs for configuring go-neb instances.
*/
@Path("/api/v1/dimension/admin/integrations") @Path("/api/v1/dimension/admin/integrations")
export class AdminIntegrationsService { export class AdminIntegrationsService {

View File

@ -20,6 +20,9 @@ interface SetEnabledRequest {
} }
/**
* Administrative API for configuring go-neb instances.
*/
@Path("/api/v1/dimension/admin/neb") @Path("/api/v1/dimension/admin/neb")
export class AdminNebService { export class AdminNebService {

View File

@ -21,13 +21,28 @@ interface DimensionConfigResponse {
}; };
} }
/**
* Administrative API for general information about Dimension
*/
@Path("/api/v1/dimension/admin") @Path("/api/v1/dimension/admin")
export class AdminService { export class AdminService {
/**
* Determines if a given user is an administrator
* @param {string} userId The user ID to validate
* @returns {boolean} True if the user is an administrator
*/
public static isAdmin(userId: string) { public static isAdmin(userId: string) {
return config.admins.indexOf(userId) >= 0; return config.admins.indexOf(userId) >= 0;
} }
/**
* Validates the given scalar token to ensure the owner is an administrator. If the
* given scalar token does not belong to an administrator, an ApiError is raised.
* @param {string} scalarToken The scalar token to validate
* @returns {Promise<string>} Resolves to the owner's user ID if they are an administrator
* @throws {ApiError} Thrown with a status code of 401 if the owner is not an administrator
*/
public static async validateAndGetAdminTokenOwner(scalarToken: string): Promise<string> { public static async validateAndGetAdminTokenOwner(scalarToken: string): Promise<string> {
const userId = await ScalarService.getTokenOwner(scalarToken, true); const userId = await ScalarService.getTokenOwner(scalarToken, true);
if (!AdminService.isAdmin(userId)) if (!AdminService.isAdmin(userId))

View File

@ -18,6 +18,10 @@ interface NewUpstreamRequest {
apiUrl: string; apiUrl: string;
} }
/**
* Administrative API for managing the instances upstream of this instance. Particularly
* useful for configuring the Modular upstream.
*/
@Path("/api/v1/dimension/admin/upstreams") @Path("/api/v1/dimension/admin/upstreams")
export class AdminUpstreamService { export class AdminUpstreamService {

View File

@ -1,5 +1,8 @@
import { GET, Path } from "typescript-rest"; import { GET, Path } from "typescript-rest";
/**
* API for the health of Dimension
*/
@Path("/api/v1/dimension/health") @Path("/api/v1/dimension/health")
export class DimensionHealthService { export class DimensionHealthService {

View File

@ -15,9 +15,17 @@ export interface IntegrationsResponse {
complexBots: ComplexBot[], complexBots: ComplexBot[],
} }
/**
* API for managing integrations, primarily for a given room
*/
@Path("/api/v1/dimension/integrations") @Path("/api/v1/dimension/integrations")
export class DimensionIntegrationsService { export class DimensionIntegrationsService {
/**
* 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[]> { public static async getWidgets(enabledOnly: boolean): Promise<Widget[]> {
const cached = Cache.for(CACHE_INTEGRATIONS).get("widgets"); const cached = Cache.for(CACHE_INTEGRATIONS).get("widgets");
if (cached) return cached; if (cached) return cached;
@ -27,6 +35,11 @@ export class DimensionIntegrationsService {
return widgets; return widgets;
} }
/**
* 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[]> { public static async getSimpleBots(userId: string): Promise<SimpleBot[]> {
const cached = Cache.for(CACHE_INTEGRATIONS).get("simple_bots"); const cached = Cache.for(CACHE_INTEGRATIONS).get("simple_bots");
if (cached) return cached; if (cached) return cached;
@ -36,6 +49,12 @@ export class DimensionIntegrationsService {
return 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[]> { public static async getComplexBots(userId: string, roomId: string): Promise<ComplexBot[]> {
const cached = Cache.for(CACHE_INTEGRATIONS).get("complex_bots_" + roomId); const cached = Cache.for(CACHE_INTEGRATIONS).get("complex_bots_" + roomId);
if (cached) return cached; if (cached) return cached;

View File

@ -4,6 +4,9 @@ import { ApiError } from "../ApiError";
import * as request from "request"; import * as request from "request";
import { LogService } from "matrix-js-snippets"; import { LogService } from "matrix-js-snippets";
/**
* API for proxying webhooks to other services.
*/
@Path("/api/v1/dimension/webhooks") @Path("/api/v1/dimension/webhooks")
export class DimensionWebhookService { export class DimensionWebhookService {

View File

@ -8,6 +8,9 @@ interface AppServiceTransaction {
events: SimplifiedMatrixEvent[]; events: SimplifiedMatrixEvent[];
} }
/**
* API for handling appservice traffic from a homeserver
*/
// Note: There's no actual defined prefix for this API. The following was chosen to be // Note: There's no actual defined prefix for this API. The following was chosen to be
// somewhat consistent with the other matrix APIs. In reality, the homeserver will just // somewhat consistent with the other matrix APIs. In reality, the homeserver will just
// hit the URL given in the registration - be sure to define it to match this prefix. // hit the URL given in the registration - be sure to define it to match this prefix.

View File

@ -19,9 +19,20 @@ interface RegisterRequest {
expires_in: number; expires_in: number;
} }
/**
* API for the minimum Scalar API we need to implement to be compatible with clients. Used for registration
* and general account management.
*/
@Path("/api/v1/scalar") @Path("/api/v1/scalar")
export class ScalarService { export class ScalarService {
/**
* Gets the owner of a given scalar token, throwing an ApiError if the token is invalid.
* @param {string} scalarToken The scalar token to validate
* @param {boolean} ignoreUpstreams True to consider the token valid if it is missing links to other upstreams
* @returns {Promise<string>} Resolves to the owner's user ID if the token is valid.
* @throws {ApiError} Thrown with a status code of 401 if the token is invalid.
*/
public static async getTokenOwner(scalarToken: string, ignoreUpstreams?: boolean): Promise<string> { public static async getTokenOwner(scalarToken: string, ignoreUpstreams?: boolean): Promise<string> {
const cachedUserId = Cache.for(CACHE_SCALAR_ACCOUNTS).get(scalarToken); const cachedUserId = Cache.for(CACHE_SCALAR_ACCOUNTS).get(scalarToken);
if (cachedUserId) return cachedUserId; if (cachedUserId) return cachedUserId;

View File

@ -18,6 +18,9 @@ interface UrlPreviewResponse {
}; };
} }
/**
* API for the minimum Scalar API for widget functionality in clients.
*/
@Path("/api/v1/scalar/widgets") @Path("/api/v1/scalar/widgets")
export class ScalarWidgetService { export class ScalarWidgetService {