mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2024-09-20 07:16:17 +08:00
fix delete endpoint, add documentation using redoc instead of swagger
This commit is contained in:
parent
ff4d2b759b
commit
5c2e031942
|
@ -1,3 +1,4 @@
|
|||
backend/build
|
||||
backend/__migration__
|
||||
docker
|
||||
backend/scripts
|
||||
docker
|
||||
|
|
2341
backend/package-lock.json
generated
2341
backend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -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
38
backend/redocly.yaml
Normal 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"
|
48
backend/scripts/openapi.ts
Normal file
48
backend/scripts/openapi.ts
Normal 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));
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 => {
|
||||
|
|
14
backend/src/api/routes/redoc.ts
Normal file
14
backend/src/api/routes/redoc.ts
Normal 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",
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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.",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue