monkeytype/backend/scripts/openapi.ts
2024-07-30 12:58:22 +02:00

126 lines
3.6 KiB
TypeScript

import { generateOpenApi } from "@ts-rest/open-api";
import { contract } from "@monkeytype/contracts/index";
import { writeFileSync, mkdirSync } from "fs";
import { EndpointMetadata } from "@monkeytype/contracts/schemas/api";
import type { OpenAPIObject } from "openapi3-ts";
type SecurityRequirementObject = {
[name: string]: string[];
};
export function getOpenApi(): OpenAPIObject {
const openApiDocument = generateOpenApi(
contract,
{
openapi: "3.1.0",
info: {
title: "Monkeytype API",
description:
"Documentation for the public endpoints provided by the Monkeytype API server.\n\nNote that authentication is performed with the Authorization HTTP header in the format `Authorization: ApeKey YOUR_APE_KEY`\n\nThere is a rate limit of `30 requests per minute` across all endpoints with some endpoints being more strict. Rate limit rates are shared across all ape keys.",
version: "2.0.0",
termsOfService: "https://monkeytype.com/terms-of-service",
contact: {
name: "Support",
email: "support@monkeytype.com",
},
"x-logo": {
url: "https://monkeytype.com/images/mtfulllogo.png",
},
license: {
name: "GPL-3.0",
url: "https://www.gnu.org/licenses/gpl-3.0.html",
},
},
servers: [
{
url: "https://api.monkeytype.com",
description: "Production server",
},
],
components: {
securitySchemes: {
BearerAuth: {
type: "http",
scheme: "bearer",
},
ApeKey: {
type: "http",
scheme: "ApeKey",
},
},
},
tags: [
{
name: "configs",
description:
"User specific configurations like test settings, theme or tags.",
"x-displayName": "User configuration",
},
{
name: "presets",
description: "User specific configuration presets.",
"x-displayName": "User presets",
},
],
},
{
jsonQuery: true,
setOperationId: "concatenated-path",
operationMapper: (operation, route) => ({
...operation,
...addAuth(route.metadata as EndpointMetadata),
...addTags(route.metadata as EndpointMetadata),
}),
}
);
return openApiDocument;
}
function addAuth(metadata: EndpointMetadata | undefined): object {
const auth = metadata?.["authenticationOptions"] ?? {};
const security: SecurityRequirementObject[] = [];
if (!auth.isPublic === true) {
security.push({ BearerAuth: [] });
if (auth.acceptApeKeys === true) {
security.push({ ApeKey: [] });
}
}
const includeInPublic = auth.isPublic === true || auth.acceptApeKeys === true;
return {
"x-public": includeInPublic ? "yes" : "no",
security,
};
}
function addTags(metadata: EndpointMetadata | undefined): object {
if (metadata === undefined || metadata.openApiTags === undefined) return {};
return {
tags: Array.isArray(metadata.openApiTags)
? metadata.openApiTags
: [metadata.openApiTags],
};
}
//detect if we run this as a main
if (require.main === module) {
const args = process.argv.slice(2);
if (args.length !== 1) {
console.error("Provide filename.");
process.exit(1);
}
const outFile = args[0] as string;
//create directories if needed
const lastSlash = outFile.lastIndexOf("/");
if (lastSlash > 1) {
const dir = outFile.substring(0, lastSlash);
mkdirSync(dir, { recursive: true });
}
const openapi = getOpenApi();
writeFileSync(args[0] as string, JSON.stringify(openapi, null, 2));
}