Files
2026-07-26 18:25:37 +09:00

201 lines
6.9 KiB
JavaScript

import endpoints from "../endpoints.js";
import config from "../../../config/index.js";
import { errors as basicErrors } from "./errors.js";
import { schemas, convertSchemaToOpenApiSchema } from "./schemas.js";
export function genOpenapiSpec() {
const spec = {
openapi: "3.0.0",
info: {
version: "v1",
title: "FrozenFriendsYume API",
"x-logo": {
url: "/static-assets/api-doc.png"
}
},
externalDocs: {
description: "Repository",
url: "https://iceshrimp.dev/iceshrimp/iceshrimp"
},
servers: [
{
url: config.apiUrl
}
],
paths: {},
components: {
schemas: schemas,
securitySchemes: {
ApiKeyAuth: {
type: "apiKey",
in: "body",
name: "i"
},
// TODO: change this to oauth2 when the remaining oauth stuff is set up
Bearer: {
type: "http",
scheme: "bearer"
}
}
}
};
for (const endpoint of endpoints.filter((ep)=>!ep.meta.secure)){
const errors = {};
if (endpoint.meta.errors) {
for (const e of Object.values(endpoint.meta.errors)){
errors[e.code] = {
value: {
error: e
}
};
}
}
const resSchema = endpoint.meta.res ? convertSchemaToOpenApiSchema(endpoint.meta.res) : {};
let desc = (endpoint.meta.description ? endpoint.meta.description : "No description provided.") + "\n\n";
desc += `**Credential required**: *${endpoint.meta.requireCredential ? "Yes" : "No"}*`;
if (endpoint.meta.kind) {
const kind = endpoint.meta.kind;
desc += ` / **Permission**: *${kind}*`;
}
const requestType = endpoint.meta.requireFile ? "multipart/form-data" : "application/json";
const schema = endpoint.params;
if (endpoint.meta.requireFile) {
schema.properties.file = {
type: "string",
format: "binary",
description: "The file contents."
};
schema.required.push("file");
}
const security = [
{
ApiKeyAuth: []
},
{
Bearer: []
}
];
if (!endpoint.meta.requireCredential) {
// add this to make authentication optional
security.push({});
}
const info = {
operationId: endpoint.name,
summary: endpoint.name,
description: desc,
externalDocs: {
description: "Source code",
url: `https://iceshrimp.dev/iceshrimp/iceshrimp/src/branch/dev/packages/backend/src/server/api/endpoints/${endpoint.name}.ts`
},
tags: endpoint.meta.tags || undefined,
security,
requestBody: {
required: true,
content: {
[requestType]: {
schema
}
}
},
responses: {
...endpoint.meta.res ? {
"200": {
description: "OK (with results)",
content: {
"application/json": {
schema: resSchema
}
}
}
} : {
"204": {
description: "OK (without any results)"
}
},
"400": {
description: "Client error",
content: {
"application/json": {
schema: {
$ref: "#/components/schemas/Error"
},
examples: {
...errors,
...basicErrors["400"]
}
}
}
},
"401": {
description: "Authentication error",
content: {
"application/json": {
schema: {
$ref: "#/components/schemas/Error"
},
examples: basicErrors["401"]
}
}
},
"403": {
description: "Forbidden error",
content: {
"application/json": {
schema: {
$ref: "#/components/schemas/Error"
},
examples: basicErrors["403"]
}
}
},
"418": {
description: "I'm Calc",
content: {
"application/json": {
schema: {
$ref: "#/components/schemas/Error"
},
examples: basicErrors["418"]
}
}
},
...endpoint.meta.limit ? {
"429": {
description: "Too many requests",
content: {
"application/json": {
schema: {
$ref: "#/components/schemas/Error"
},
examples: basicErrors["429"]
}
}
}
} : {},
"500": {
description: "Internal server error",
content: {
"application/json": {
schema: {
$ref: "#/components/schemas/Error"
},
examples: basicErrors["500"]
}
}
}
}
};
const path = {
post: info
};
if (endpoint.meta.allowGet) {
path.get = {
...info
};
// API Key authentication is not permitted for GET requests
path.get.security = path.get.security.filter((elem)=>!Object.prototype.hasOwnProperty.call(elem, "ApiKeyAuth"));
}
spec.paths[`/${endpoint.name}`] = path;
}
return spec;
}