fix delete endpoint, add documentation using redoc instead of swagger

This commit is contained in:
Christian Fehmer 2024-06-23 08:59:34 +02:00
parent ff4d2b759b
commit 5c2e031942
No known key found for this signature in database
GPG key ID: FE53784A69964062
12 changed files with 2427 additions and 66 deletions

View file

@ -1,3 +1,4 @@
backend/build
backend/__migration__
docker
backend/scripts
docker

2341
backend/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -4,16 +4,17 @@
"license": "GPL-3.0",
"private": true,
"scripts": {
"build": "tsc --build",
"build": "npm run gen-docs && tsc --build",
"watch": "tsc --build --watch",
"clean": "tsc --build --clean",
"start": "npm run build && node ./build/server.js",
"test": "vitest run",
"test-coverage": "vitest run --coverage",
"dev": "ts-node-dev --transpile-only --inspect -- ./src/server.ts",
"dev": "npm run gen-docs && ts-node-dev --transpile-only --inspect -- ./src/server.ts",
"knip": "knip",
"docker-db-only": "docker compose -f docker/compose.db-only.yml up",
"docker": "docker compose -f docker/compose.yml up"
"docker": "docker compose -f docker/compose.yml up",
"gen-docs": "ts-node scripts/openapi.ts build/static/api/openapi.json && redocly build-docs all@v2 -o build/static/api/documentation-all.html"
},
"engines": {
"node": "18.19.1",
@ -60,6 +61,7 @@
"zod": "3.23.8"
},
"devDependencies": {
"@redocly/cli": "1.16.0",
"@types/bcrypt": "5.0.0",
"@types/cors": "2.8.12",
"@types/cron": "1.7.3",

38
backend/redocly.yaml Normal file
View file

@ -0,0 +1,38 @@
extends:
- recommended
apis:
all@v2:
root: build/static/api/openapi.json
features.openapi:
theme:
logo:
gutter: "10px"
colors:
primary:
main: "#e2b714"
border:
dark: "#e2b714"
light: "#e2b714"
error:
main: "#da3333"
success:
main: "#009400"
text:
primary: "#646669"
secondary: "#d1d0c5"
warning:
main: "#FF00FF"
http:
delete: "#da3333"
post: "#004D94"
patch: "#e2b714"
get: "#009400"
sidebar:
backgroundColor: "#323437"
textColor: "#d1d0c5"
activeTextColor: "#e2b714"
rightPanel:
backgroundColor: "#323437"
textColor: "#d1d0c5"

View file

@ -0,0 +1,48 @@
import { generateOpenApi } from "@ts-rest/open-api";
import { contract } from "../../shared/contract/index.contract";
import { writeFileSync, mkdirSync } from "fs";
export function getOpenApi() {
const openApiDocument = generateOpenApi(
contract,
{
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",
},
},
},
{ jsonQuery: true, setOperationId: "concatenated-path" }
);
return openApiDocument;
}
//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));
//console.log(JSON.stringify(openapi));
}

View file

@ -24,7 +24,7 @@ export async function saveConfig(
}
export async function deleteConfig(
req: MonkeyTypes.Request2
req: MonkeyTypes.Request2<undefined, void>
): Promise<MonkeyResponse2<undefined>> {
const { uid } = req.ctx.decodedToken;

View file

@ -38,6 +38,7 @@ import { configsRoutes } from "./configs";
import { ZodIssue } from "zod";
import { MonkeyValidationError } from "shared/contract/shared/types";
import { AppRoute, AppRouter } from "@ts-rest/core";
import { addRedocMiddlewares } from "./redoc";
const pathOverride = process.env["API_PATH_OVERRIDE"];
const BASE_ROUTE = pathOverride !== undefined ? `/${pathOverride}` : "";
@ -129,6 +130,7 @@ function applyApiRoutes(app: Application): void {
app.use("/configuration", configuration);
addSwaggerMiddlewares(app);
addRedocMiddlewares(app);
app.use(
(req: MonkeyTypes.Request, res: Response, next: NextFunction): void => {

View file

@ -0,0 +1,14 @@
import { Application } from "express";
export function addRedocMiddlewares(app: Application): void {
app.use("/v2/docs", (req, res) => {
res.sendFile("api/documentation-all.html", {
root: __dirname + "../../../../build/static",
});
});
app.use("/v2/docs.json", (req, res) => {
res.setHeader("Content-Type", "application/json");
res.sendFile("api/openapi.json", {
root: __dirname + "../../../../build/static",
});
});
}

View file

@ -1,11 +1,9 @@
import { generateOpenApi } from "@ts-rest/open-api";
import { Application } from "express";
import { getMiddleware as getSwaggerMiddleware } from "swagger-stats";
import * as swaggerUi from "swagger-ui-express";
import internalSwaggerSpec from "../../documentation/internal-swagger.json";
import publicSwaggerSpec from "../../documentation/public-swagger.json";
import { isDevEnvironment } from "../../utils/misc";
import { contract } from "shared/contract/index.contract";
const SWAGGER_UI_OPTIONS = {
customCss: ".swagger-ui .topbar { display: none } .try-out { display: none }",
@ -35,33 +33,6 @@ function addSwaggerMiddlewares(app: Application): void {
swaggerUi.serveFiles(publicSwaggerSpec, options),
swaggerUi.setup(publicSwaggerSpec, SWAGGER_UI_OPTIONS)
);
const openApiDocument = generateOpenApi(
contract,
{
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",
},
},
},
{ jsonQuery: true, setOperationId: "concatenated-path" }
);
app.use("/v2/docs.json", (req, res) => {
res.setHeader("Content-Type", "application/json");
res.status(200).send(openApiDocument);
});
app.use(
"/v2/docs",
swaggerUi.serveFiles(openApiDocument, options),
swaggerUi.setup(openApiDocument, SWAGGER_UI_OPTIONS)
);
}
export default addSwaggerMiddlewares;

View file

@ -81,7 +81,7 @@ const CONFIG_SCHEMA = joi.object({
.valid("lowercase", "uppercase", "blank", "dynamic"),
keymapLayout: joi
.string()
.regex(/[\w-_]+/)
.regex(/[\w\-_]+/)
.valid()
.max(50),
keymapShowTopRow: joi.string().valid("always", "layout", "never"),

View file

@ -189,10 +189,12 @@ export const configsContract = c.router(
delete: {
method: "DELETE",
path: "/",
body: z.never().or(z.object({}).strict()),
body: z.void(),
responses: {
200: MonkeyResponseSchema,
},
summary: "delete config",
description: "Delete/reset the config for the current user.",
},
},
{

View file

@ -261,7 +261,7 @@ export type Language = z.infer<typeof LanguageSchema>;
export const KeymapLayoutSchema = z
.string()
.max(50)
.regex(/[\w-_]+/);
.regex(/[\w\-_]+/);
export type KeymapLayout = z.infer<typeof KeymapLayoutSchema>;
export const LayoutSchema = token().max(50);