mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-01-16 23:00:51 +00:00
1226 lines
37 KiB
TypeScript
1226 lines
37 KiB
TypeScript
import {
|
|
OpenAPIRegistry,
|
|
OpenApiGeneratorV3,
|
|
} from "@asteasolutions/zod-to-openapi";
|
|
import type {
|
|
ParameterObject,
|
|
ReferenceObject,
|
|
} from "@asteasolutions/zod-to-openapi/dist/types";
|
|
import DatabaseBaseModel from "../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
|
|
import Models from "../../Models/DatabaseModels/Index";
|
|
import AnalyticsBaseModel from "../../Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel";
|
|
import AnalyticsModels from "../../Models/AnalyticsModels/Index";
|
|
import { JSONObject, JSONValue } from "../../Types/JSON";
|
|
import { ModelSchema, ModelSchemaType } from "../../Utils/Schema/ModelSchema";
|
|
import {
|
|
AnalyticsModelSchema,
|
|
AnalyticsModelSchemaType,
|
|
} from "../../Utils/Schema/AnalyticsModelSchema";
|
|
import LocalCache from "../Infrastructure/LocalCache";
|
|
import { Host, HttpProtocol } from "../EnvironmentConfig";
|
|
import Permission from "../../Types/Permission";
|
|
|
|
export default class OpenAPIUtil {
|
|
/**
|
|
* Helper method to check if permissions should exclude API generation.
|
|
* Returns true if:
|
|
* 1. The permissions array is empty or undefined, OR
|
|
* 2. The permissions array contains Permission.Public or Permission.CurrentUser
|
|
*/
|
|
private static shouldExcludeApiForPermissions(
|
|
permissions: Array<Permission> | undefined,
|
|
): boolean {
|
|
if (!permissions || permissions.length === 0) {
|
|
return true;
|
|
}
|
|
|
|
return (
|
|
permissions.length > 0 &&
|
|
permissions.every((permission: Permission) => {
|
|
return (
|
|
permission === Permission.Public ||
|
|
permission === Permission.CurrentUser
|
|
);
|
|
})
|
|
);
|
|
}
|
|
|
|
public static generateOpenAPISpec(): JSONObject {
|
|
// check if the cache is already in LocalCache
|
|
const cachedSpec: JSONValue | undefined = LocalCache.getJSON(
|
|
"openapi",
|
|
"spec",
|
|
);
|
|
|
|
if (cachedSpec) {
|
|
return cachedSpec as JSONObject;
|
|
}
|
|
|
|
const registry: OpenAPIRegistry = new OpenAPIRegistry();
|
|
const tags: Array<{ name: string; description: string }> = [];
|
|
|
|
// Register schemas and paths for all models
|
|
for (const ModelClass of Models) {
|
|
const model: DatabaseBaseModel = new ModelClass();
|
|
const modelName: string | null = model.tableName;
|
|
|
|
if (!modelName) {
|
|
continue;
|
|
}
|
|
|
|
// check if enable documentation is enabled
|
|
|
|
if (!model.enableDocumentation) {
|
|
continue;
|
|
}
|
|
|
|
if (!model.crudApiPath) {
|
|
continue;
|
|
}
|
|
|
|
// Add tag for this model
|
|
const singularModelName: string = model.singularName || modelName;
|
|
tags.push({
|
|
name: singularModelName,
|
|
description:
|
|
model.tableDescription || `API endpoints for ${singularModelName}`,
|
|
});
|
|
|
|
// register the model schema
|
|
OpenAPIUtil.registerModelSchemas(registry, model);
|
|
|
|
this.generateListApiSpec({
|
|
modelType: ModelClass,
|
|
registry,
|
|
});
|
|
|
|
this.generateCountApiSpec({
|
|
modelType: ModelClass,
|
|
registry,
|
|
});
|
|
this.generateCreateApiSpec({
|
|
modelType: ModelClass,
|
|
registry,
|
|
});
|
|
this.generateGetApiSpec({
|
|
modelType: ModelClass,
|
|
registry,
|
|
});
|
|
this.generateUpdateApiSpec({
|
|
modelType: ModelClass,
|
|
registry,
|
|
});
|
|
this.generateDeleteApiSpec({
|
|
modelType: ModelClass,
|
|
registry,
|
|
});
|
|
}
|
|
|
|
// Register schemas and paths for all Analytics models
|
|
for (const AnalyticsModelClass of AnalyticsModels) {
|
|
const analyticsModel: AnalyticsBaseModel = new AnalyticsModelClass();
|
|
const modelName: string | null = analyticsModel.tableName;
|
|
|
|
if (!modelName) {
|
|
continue;
|
|
}
|
|
|
|
if (!analyticsModel.crudApiPath) {
|
|
continue;
|
|
}
|
|
|
|
// Add tag for this model
|
|
const singularModelName: string =
|
|
analyticsModel.singularName || modelName;
|
|
tags.push({
|
|
name: singularModelName,
|
|
description: `API endpoints for ${singularModelName}`,
|
|
});
|
|
|
|
// register the analytics model schema
|
|
OpenAPIUtil.registerAnalyticsModelSchemas(registry, analyticsModel);
|
|
|
|
this.generateAnalyticsListApiSpec({
|
|
modelType: AnalyticsModelClass,
|
|
registry,
|
|
});
|
|
|
|
this.generateAnalyticsCountApiSpec({
|
|
modelType: AnalyticsModelClass,
|
|
registry,
|
|
});
|
|
|
|
this.generateAnalyticsCreateApiSpec({
|
|
modelType: AnalyticsModelClass,
|
|
registry,
|
|
});
|
|
|
|
this.generateAnalyticsGetApiSpec({
|
|
modelType: AnalyticsModelClass,
|
|
registry,
|
|
});
|
|
|
|
this.generateAnalyticsUpdateApiSpec({
|
|
modelType: AnalyticsModelClass,
|
|
registry,
|
|
});
|
|
|
|
this.generateAnalyticsDeleteApiSpec({
|
|
modelType: AnalyticsModelClass,
|
|
registry,
|
|
});
|
|
}
|
|
|
|
const generator: OpenApiGeneratorV3 = new OpenApiGeneratorV3(
|
|
registry.definitions,
|
|
);
|
|
|
|
const spec: JSONObject = generator.generateDocument({
|
|
openapi: "3.0.0",
|
|
info: {
|
|
title: "OneUptime OpenAPI Specification",
|
|
version: "1.0.0",
|
|
description:
|
|
"OpenAPI specification for OneUptime. This document describes the API endpoints, request and response formats, and other details necessary for developers to interact with the OneUptime API.",
|
|
},
|
|
servers: [
|
|
{
|
|
url: `${HttpProtocol.toString()}${Host.toString()}/api`,
|
|
description: "API Server",
|
|
},
|
|
],
|
|
tags: tags.sort(
|
|
(
|
|
a: { name: string; description: string },
|
|
b: { name: string; description: string },
|
|
) => {
|
|
return a.name.localeCompare(b.name);
|
|
},
|
|
),
|
|
}) as unknown as JSONObject;
|
|
|
|
// Add security schemes and global security requirement
|
|
if (!spec["components"]) {
|
|
spec["components"] = {};
|
|
}
|
|
|
|
(spec["components"] as JSONObject)["securitySchemes"] = {
|
|
ApiKey: {
|
|
type: "apiKey",
|
|
in: "header",
|
|
name: "APIKey",
|
|
description:
|
|
"API key for authentication. Required for all API requests.",
|
|
},
|
|
};
|
|
|
|
spec["security"] = [
|
|
{
|
|
ApiKey: [],
|
|
},
|
|
];
|
|
|
|
LocalCache.setJSON("openapi", "spec", spec as JSONObject);
|
|
|
|
return spec;
|
|
}
|
|
|
|
public static generateListApiSpec(data: {
|
|
modelType: new () => DatabaseBaseModel;
|
|
registry: OpenAPIRegistry;
|
|
}): void {
|
|
const modelType: new () => DatabaseBaseModel = data.modelType;
|
|
const model: DatabaseBaseModel = new modelType();
|
|
const tableName: string = model.tableName || "UnknownModel";
|
|
const singularModelName: string = model.singularName || tableName;
|
|
|
|
// Use schema names that are already registered
|
|
const querySchemaName: string = `${tableName}QuerySchema`;
|
|
const selectSchemaName: string = `${tableName}SelectSchema`;
|
|
const sortSchemaName: string = `${tableName}SortSchema`;
|
|
|
|
data.registry.registerPath({
|
|
method: "post",
|
|
path: `${model.crudApiPath}/get-list`,
|
|
operationId: `list${tableName}`,
|
|
summary: `List ${singularModelName}`,
|
|
description: `Endpoint to list all ${singularModelName} items`,
|
|
tags: [singularModelName],
|
|
parameters: [...OpenAPIUtil.getDefaultApiHeaders()],
|
|
requestBody: {
|
|
required: false,
|
|
content: {
|
|
"application/json": {
|
|
schema: {
|
|
type: "object",
|
|
properties: {
|
|
query: { $ref: `#/components/schemas/${querySchemaName}` },
|
|
select: { $ref: `#/components/schemas/${selectSchemaName}` },
|
|
sort: { $ref: `#/components/schemas/${sortSchemaName}` },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
"200": {
|
|
description: "Successful response",
|
|
content: {
|
|
"application/json": {
|
|
schema: {
|
|
type: "object",
|
|
properties: {
|
|
data: {
|
|
type: "array",
|
|
items: {
|
|
$ref: `#/components/schemas/${tableName}`,
|
|
},
|
|
},
|
|
count: { type: "number" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
...this.getGenericStatusResponseSchema(),
|
|
},
|
|
});
|
|
}
|
|
|
|
public static generateCountApiSpec(data: {
|
|
modelType: new () => DatabaseBaseModel;
|
|
registry: OpenAPIRegistry;
|
|
}): void {
|
|
const modelType: new () => DatabaseBaseModel = data.modelType;
|
|
const model: DatabaseBaseModel = new modelType();
|
|
const tableName: string = model.tableName || "UnknownModel";
|
|
const singularModelName: string = model.singularName || tableName;
|
|
|
|
// Use schema name that is already registered
|
|
const querySchemaName: string = `${tableName}QuerySchema`;
|
|
|
|
data.registry.registerPath({
|
|
method: "post",
|
|
path: `${model.crudApiPath}/count`,
|
|
operationId: `count${tableName}`,
|
|
summary: `Count ${singularModelName}`,
|
|
description: `Endpoint to count ${singularModelName} items`,
|
|
tags: [singularModelName],
|
|
parameters: [...OpenAPIUtil.getDefaultApiHeaders()],
|
|
requestBody: {
|
|
required: false,
|
|
content: {
|
|
"application/json": {
|
|
schema: {
|
|
type: "object",
|
|
properties: {
|
|
query: { $ref: `#/components/schemas/${querySchemaName}` },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
"200": {
|
|
description: "Successful response",
|
|
content: {
|
|
"application/json": {
|
|
schema: {
|
|
type: "object",
|
|
properties: {
|
|
count: { type: "number" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
...this.getGenericStatusResponseSchema(),
|
|
},
|
|
});
|
|
}
|
|
|
|
public static generateCreateApiSpec(data: {
|
|
modelType: new () => DatabaseBaseModel;
|
|
registry: OpenAPIRegistry;
|
|
}): void {
|
|
const modelType: new () => DatabaseBaseModel = data.modelType;
|
|
const model: DatabaseBaseModel = new modelType();
|
|
const tableName: string = model.tableName || "UnknownModel";
|
|
const singularModelName: string = model.singularName || tableName;
|
|
|
|
// Skip generating create API if model has no create permissions or contains Public/CurrentUser permissions
|
|
if (this.shouldExcludeApiForPermissions(model.createRecordPermissions)) {
|
|
return;
|
|
}
|
|
|
|
// Use schema names that are already registered
|
|
const createSchemaName: string = `${tableName}CreateSchema`;
|
|
const readSchemaName: string = `${tableName}ReadSchema`;
|
|
|
|
data.registry.registerPath({
|
|
method: "post",
|
|
path: `${model.crudApiPath}`,
|
|
operationId: `create${tableName}`,
|
|
summary: `Create ${singularModelName}`,
|
|
description: `Endpoint to create a new ${singularModelName}`,
|
|
tags: [singularModelName],
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
"application/json": {
|
|
schema: {
|
|
type: "object",
|
|
properties: {
|
|
data: {
|
|
$ref: `#/components/schemas/${createSchemaName}`,
|
|
},
|
|
},
|
|
required: ["data"],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
"201": {
|
|
description: "Created successfully",
|
|
content: {
|
|
"application/json": {
|
|
schema: {
|
|
type: "object",
|
|
properties: {
|
|
data: {
|
|
$ref: `#/components/schemas/${readSchemaName}`,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
...this.getGenericStatusResponseSchema(),
|
|
},
|
|
});
|
|
}
|
|
|
|
public static getGenericStatusResponseSchema(): JSONObject {
|
|
return {
|
|
"400": {
|
|
description:
|
|
"Bad request. This response indicates that the request was malformed or contained invalid data.",
|
|
},
|
|
"401": {
|
|
description:
|
|
"Unauthorized. This response indicates that the request requires user authentication.",
|
|
},
|
|
"402": {
|
|
description:
|
|
"Payment Required. This response indicates that the request requires payment or a valid subscription.",
|
|
},
|
|
"403": {
|
|
description:
|
|
"Forbidden. This response indicates that the server understood the request, but refuses to authorize it.",
|
|
},
|
|
"408": {
|
|
description:
|
|
"Request Timeout. This response indicates that the server timed out waiting for the request.",
|
|
},
|
|
"405": {
|
|
description:
|
|
"Project not found. This response indicates that the requested project does not exist or is not accessible.",
|
|
},
|
|
"415": {
|
|
description:
|
|
"Unable to reach server. This response indicates that the server is currently unreachable or down.",
|
|
},
|
|
"422": {
|
|
description:
|
|
"Not authorized. This response indicates that the user does not have permission to access the requested resource.",
|
|
},
|
|
"500": {
|
|
description:
|
|
"Internal Server Error. This response indicates that the server encountered an unexpected condition that prevented it from fulfilling the request.",
|
|
},
|
|
};
|
|
}
|
|
|
|
public static generateGetApiSpec(data: {
|
|
modelType: new () => DatabaseBaseModel;
|
|
registry: OpenAPIRegistry;
|
|
}): void {
|
|
const modelType: new () => DatabaseBaseModel = data.modelType;
|
|
const model: DatabaseBaseModel = new modelType();
|
|
const tableName: string = model.tableName || "UnknownModel";
|
|
const singularModelName: string = model.singularName || tableName;
|
|
|
|
// Skip generating get API if model has no read permissions or contains Public/CurrentUser permissions
|
|
if (this.shouldExcludeApiForPermissions(model.readRecordPermissions)) {
|
|
return;
|
|
}
|
|
|
|
// Use schema name that is already registered
|
|
const selectSchemaName: string = `${tableName}SelectSchema`;
|
|
|
|
data.registry.registerPath({
|
|
method: "post",
|
|
path: `${model.crudApiPath}/{id}/get-item`,
|
|
operationId: `get${tableName}`,
|
|
summary: `Get ${singularModelName}`,
|
|
description: `Endpoint to retrieve a single ${singularModelName} by ID`,
|
|
tags: [singularModelName],
|
|
parameters: [
|
|
...OpenAPIUtil.getDefaultApiHeaders(),
|
|
{
|
|
name: "id",
|
|
in: "path",
|
|
required: true,
|
|
schema: {
|
|
type: "string",
|
|
format: "uuid",
|
|
},
|
|
description: `ID of the ${singularModelName} to retrieve`,
|
|
},
|
|
],
|
|
requestBody: {
|
|
required: false,
|
|
content: {
|
|
"application/json": {
|
|
schema: {
|
|
type: "object",
|
|
properties: {
|
|
select: { $ref: `#/components/schemas/${selectSchemaName}` },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
"200": {
|
|
description: "Successful response",
|
|
content: {
|
|
"application/json": {
|
|
schema: {
|
|
type: "object",
|
|
properties: {
|
|
data: {
|
|
$ref: `#/components/schemas/${tableName}`,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
...this.getGenericStatusResponseSchema(),
|
|
},
|
|
});
|
|
}
|
|
|
|
public static getDefaultApiHeaders(): Array<
|
|
ParameterObject | ReferenceObject
|
|
> {
|
|
return [];
|
|
}
|
|
|
|
public static generateUpdateApiSpec(data: {
|
|
modelType: new () => DatabaseBaseModel;
|
|
registry: OpenAPIRegistry;
|
|
}): void {
|
|
const modelType: new () => DatabaseBaseModel = data.modelType;
|
|
const model: DatabaseBaseModel = new modelType();
|
|
const tableName: string = model.tableName || "UnknownModel";
|
|
const singularModelName: string = model.singularName || tableName;
|
|
|
|
// Skip generating update API if model has no update permissions or contains Public/CurrentUser permissions
|
|
if (this.shouldExcludeApiForPermissions(model.updateRecordPermissions)) {
|
|
return;
|
|
}
|
|
|
|
// Use schema names that are already registered
|
|
const updateSchemaName: string = `${tableName}UpdateSchema`;
|
|
const readSchemaName: string = `${tableName}ReadSchema`;
|
|
|
|
data.registry.registerPath({
|
|
method: "put",
|
|
path: `${model.crudApiPath}/{id}`,
|
|
operationId: `update${tableName}`,
|
|
summary: `Update ${singularModelName}`,
|
|
description: `Endpoint to update an existing ${singularModelName}`,
|
|
tags: [singularModelName],
|
|
parameters: [
|
|
...OpenAPIUtil.getDefaultApiHeaders(),
|
|
{
|
|
name: "id",
|
|
in: "path",
|
|
required: true,
|
|
schema: {
|
|
type: "string",
|
|
format: "uuid",
|
|
},
|
|
description: `ID of the ${singularModelName} to update`,
|
|
},
|
|
],
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
"application/json": {
|
|
schema: {
|
|
type: "object",
|
|
properties: {
|
|
data: {
|
|
$ref: `#/components/schemas/${updateSchemaName}`,
|
|
},
|
|
},
|
|
required: ["data"],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
"200": {
|
|
description: "Updated successfully",
|
|
content: {
|
|
"application/json": {
|
|
schema: {
|
|
type: "object",
|
|
properties: {
|
|
data: {
|
|
$ref: `#/components/schemas/${readSchemaName}`,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
...this.getGenericStatusResponseSchema(),
|
|
},
|
|
});
|
|
}
|
|
|
|
public static generateDeleteApiSpec(data: {
|
|
modelType: new () => DatabaseBaseModel;
|
|
registry: OpenAPIRegistry;
|
|
}): void {
|
|
const modelType: new () => DatabaseBaseModel = data.modelType;
|
|
const model: DatabaseBaseModel = new modelType();
|
|
const tableName: string = model.tableName || "UnknownModel";
|
|
const singularModelName: string = model.singularName || tableName;
|
|
|
|
// Skip generating delete API if model has no delete permissions or contains Public/CurrentUser permissions
|
|
if (this.shouldExcludeApiForPermissions(model.deleteRecordPermissions)) {
|
|
return;
|
|
}
|
|
|
|
data.registry.registerPath({
|
|
method: "delete",
|
|
path: `${model.crudApiPath}/{id}`,
|
|
operationId: `delete${tableName}`,
|
|
summary: `Delete ${singularModelName}`,
|
|
description: `Endpoint to delete a ${singularModelName}`,
|
|
tags: [singularModelName],
|
|
parameters: [
|
|
...OpenAPIUtil.getDefaultApiHeaders(),
|
|
{
|
|
name: "id",
|
|
in: "path",
|
|
required: true,
|
|
schema: {
|
|
type: "string",
|
|
format: "uuid",
|
|
},
|
|
description: `ID of the ${singularModelName} to delete`,
|
|
},
|
|
],
|
|
responses: {
|
|
"200": {
|
|
description: "Deleted successfully",
|
|
},
|
|
...this.getGenericStatusResponseSchema(),
|
|
},
|
|
});
|
|
}
|
|
|
|
private static registerModelSchemas(
|
|
registry: OpenAPIRegistry,
|
|
model: DatabaseBaseModel,
|
|
): void {
|
|
const tableName: string = model.tableName || "UnknownModel";
|
|
const modelType: new () => DatabaseBaseModel =
|
|
model.constructor as new () => DatabaseBaseModel;
|
|
|
|
// Register the main model schema (for backwards compatibility)
|
|
const modelSchema: ModelSchemaType = ModelSchema.getModelSchema({
|
|
modelType: modelType,
|
|
});
|
|
registry.register(tableName, modelSchema);
|
|
|
|
// Register operation-specific schemas based on permissions
|
|
this.registerOperationSpecificSchemas(
|
|
registry,
|
|
tableName,
|
|
modelType,
|
|
model,
|
|
);
|
|
|
|
// Register query, select, and sort schemas
|
|
this.registerQuerySchemas(registry, tableName, modelType);
|
|
}
|
|
|
|
private static registerOperationSpecificSchemas(
|
|
registry: OpenAPIRegistry,
|
|
tableName: string,
|
|
modelType: new () => DatabaseBaseModel,
|
|
model: DatabaseBaseModel,
|
|
): void {
|
|
const canCreate: boolean = !this.shouldExcludeApiForPermissions(
|
|
model.createRecordPermissions,
|
|
);
|
|
const canRead: boolean = !this.shouldExcludeApiForPermissions(
|
|
model.readRecordPermissions,
|
|
);
|
|
const canUpdate: boolean = !this.shouldExcludeApiForPermissions(
|
|
model.updateRecordPermissions,
|
|
);
|
|
const canDelete: boolean = !this.shouldExcludeApiForPermissions(
|
|
model.deleteRecordPermissions,
|
|
);
|
|
|
|
if (canCreate) {
|
|
const createSchema: ModelSchemaType = ModelSchema.getCreateModelSchema({
|
|
modelType,
|
|
});
|
|
registry.register(`${tableName}CreateSchema`, createSchema);
|
|
}
|
|
|
|
// Register read schema whenever any operation might return it (create/update/read)
|
|
if (canRead || canCreate || canUpdate) {
|
|
const readSchema: ModelSchemaType = ModelSchema.getReadModelSchema({
|
|
modelType,
|
|
});
|
|
registry.register(`${tableName}ReadSchema`, readSchema);
|
|
}
|
|
|
|
if (canUpdate) {
|
|
const updateSchema: ModelSchemaType = ModelSchema.getUpdateModelSchema({
|
|
modelType,
|
|
});
|
|
registry.register(`${tableName}UpdateSchema`, updateSchema);
|
|
}
|
|
|
|
if (canDelete) {
|
|
const deleteSchema: ModelSchemaType = ModelSchema.getDeleteModelSchema({
|
|
modelType,
|
|
});
|
|
registry.register(`${tableName}DeleteSchema`, deleteSchema);
|
|
}
|
|
}
|
|
|
|
private static registerQuerySchemas(
|
|
registry: OpenAPIRegistry,
|
|
tableName: string,
|
|
modelType: new () => DatabaseBaseModel,
|
|
): void {
|
|
const querySchemaName: string = `${tableName}QuerySchema`;
|
|
const selectSchemaName: string = `${tableName}SelectSchema`;
|
|
const sortSchemaName: string = `${tableName}SortSchema`;
|
|
const groupBySchemaName: string = `${tableName}GroupBySchema`;
|
|
|
|
const querySchema: ModelSchemaType = ModelSchema.getQueryModelSchema({
|
|
modelType: modelType,
|
|
});
|
|
const selectSchema: ModelSchemaType = ModelSchema.getSelectModelSchema({
|
|
modelType: modelType,
|
|
});
|
|
const sortSchema: ModelSchemaType = ModelSchema.getSortModelSchema({
|
|
modelType: modelType,
|
|
});
|
|
const groupBySchema: ModelSchemaType = ModelSchema.getGroupByModelSchema({
|
|
modelType: modelType,
|
|
});
|
|
|
|
registry.register(querySchemaName, querySchema);
|
|
registry.register(selectSchemaName, selectSchema);
|
|
registry.register(sortSchemaName, sortSchema);
|
|
registry.register(groupBySchemaName, groupBySchema);
|
|
}
|
|
|
|
// Analytics-specific methods
|
|
|
|
private static registerAnalyticsModelSchemas(
|
|
registry: OpenAPIRegistry,
|
|
model: AnalyticsBaseModel,
|
|
): void {
|
|
const tableName: string = model.tableName || "UnknownAnalyticsModel";
|
|
const modelType: new () => AnalyticsBaseModel =
|
|
model.constructor as new () => AnalyticsBaseModel;
|
|
|
|
// Register the main analytics model schema
|
|
const modelSchema: AnalyticsModelSchemaType =
|
|
AnalyticsModelSchema.getModelSchema({
|
|
modelType: modelType,
|
|
});
|
|
registry.register(tableName, modelSchema);
|
|
|
|
// Register operation-specific schemas based on permissions
|
|
this.registerAnalyticsOperationSpecificSchemas(
|
|
registry,
|
|
tableName,
|
|
modelType,
|
|
model,
|
|
);
|
|
|
|
// Register query, select, and sort schemas
|
|
this.registerAnalyticsQuerySchemas(registry, tableName, modelType);
|
|
}
|
|
|
|
private static registerAnalyticsOperationSpecificSchemas(
|
|
registry: OpenAPIRegistry,
|
|
tableName: string,
|
|
modelType: new () => AnalyticsBaseModel,
|
|
model: AnalyticsBaseModel,
|
|
): void {
|
|
const canCreate: boolean = !this.shouldExcludeApiForPermissions(
|
|
model.getCreatePermissions(),
|
|
);
|
|
const canRead: boolean = !this.shouldExcludeApiForPermissions(
|
|
model.getReadPermissions(),
|
|
);
|
|
const canUpdate: boolean = !this.shouldExcludeApiForPermissions(
|
|
model.getUpdatePermissions(),
|
|
);
|
|
const canDelete: boolean = !this.shouldExcludeApiForPermissions(
|
|
model.getDeletePermissions(),
|
|
);
|
|
|
|
if (canCreate) {
|
|
const createSchema: AnalyticsModelSchemaType =
|
|
AnalyticsModelSchema.getCreateModelSchema({
|
|
modelType,
|
|
});
|
|
registry.register(`${tableName}CreateSchema`, createSchema);
|
|
}
|
|
|
|
if (canRead || canCreate || canUpdate) {
|
|
const readSchema: AnalyticsModelSchemaType =
|
|
AnalyticsModelSchema.getModelSchema({
|
|
modelType,
|
|
});
|
|
registry.register(`${tableName}ReadSchema`, readSchema);
|
|
}
|
|
|
|
if (canUpdate) {
|
|
const updateSchema: AnalyticsModelSchemaType =
|
|
AnalyticsModelSchema.getCreateModelSchema({
|
|
modelType,
|
|
});
|
|
registry.register(`${tableName}UpdateSchema`, updateSchema);
|
|
}
|
|
|
|
if (canDelete) {
|
|
const deleteSchema: AnalyticsModelSchemaType =
|
|
AnalyticsModelSchema.getModelSchema({
|
|
modelType,
|
|
});
|
|
registry.register(`${tableName}DeleteSchema`, deleteSchema);
|
|
}
|
|
}
|
|
|
|
private static registerAnalyticsQuerySchemas(
|
|
registry: OpenAPIRegistry,
|
|
tableName: string,
|
|
modelType: new () => AnalyticsBaseModel,
|
|
): void {
|
|
const querySchemaName: string = `${tableName}QuerySchema`;
|
|
const selectSchemaName: string = `${tableName}SelectSchema`;
|
|
const sortSchemaName: string = `${tableName}SortSchema`;
|
|
const groupBySchemaName: string = `${tableName}GroupBySchema`;
|
|
|
|
const querySchema: AnalyticsModelSchemaType =
|
|
AnalyticsModelSchema.getQueryModelSchema({
|
|
modelType: modelType,
|
|
});
|
|
const selectSchema: AnalyticsModelSchemaType =
|
|
AnalyticsModelSchema.getSelectModelSchema({
|
|
modelType: modelType,
|
|
});
|
|
const sortSchema: AnalyticsModelSchemaType =
|
|
AnalyticsModelSchema.getSortModelSchema({
|
|
modelType: modelType,
|
|
});
|
|
const groupBySchema: AnalyticsModelSchemaType =
|
|
AnalyticsModelSchema.getGroupByModelSchema({
|
|
modelType: modelType,
|
|
});
|
|
|
|
registry.register(querySchemaName, querySchema);
|
|
registry.register(selectSchemaName, selectSchema);
|
|
registry.register(sortSchemaName, sortSchema);
|
|
registry.register(groupBySchemaName, groupBySchema);
|
|
}
|
|
|
|
// Analytics API generation methods
|
|
|
|
private static generateAnalyticsListApiSpec(data: {
|
|
modelType: new () => AnalyticsBaseModel;
|
|
registry: OpenAPIRegistry;
|
|
}): void {
|
|
const modelType: new () => AnalyticsBaseModel = data.modelType;
|
|
const model: AnalyticsBaseModel = new modelType();
|
|
const tableName: string = model.tableName || "UnknownAnalyticsModel";
|
|
const singularModelName: string = model.singularName || tableName;
|
|
|
|
// Use schema names that are already registered
|
|
const querySchemaName: string = `${tableName}QuerySchema`;
|
|
const selectSchemaName: string = `${tableName}SelectSchema`;
|
|
const sortSchemaName: string = `${tableName}SortSchema`;
|
|
const groupBySchemaName: string = `${tableName}GroupBySchema`;
|
|
|
|
data.registry.registerPath({
|
|
method: "post",
|
|
path: `${model.crudApiPath}/get-list`,
|
|
operationId: `list${tableName}`,
|
|
summary: `List ${singularModelName}`,
|
|
description: `Endpoint to list all ${singularModelName} items`,
|
|
tags: [singularModelName],
|
|
parameters: [...OpenAPIUtil.getDefaultApiHeaders()],
|
|
requestBody: {
|
|
required: false,
|
|
content: {
|
|
"application/json": {
|
|
schema: {
|
|
type: "object",
|
|
properties: {
|
|
query: { $ref: `#/components/schemas/${querySchemaName}` },
|
|
select: { $ref: `#/components/schemas/${selectSchemaName}` },
|
|
sort: { $ref: `#/components/schemas/${sortSchemaName}` },
|
|
groupBy: { $ref: `#/components/schemas/${groupBySchemaName}` },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
"200": {
|
|
description: "Successful response",
|
|
content: {
|
|
"application/json": {
|
|
schema: {
|
|
type: "object",
|
|
properties: {
|
|
data: {
|
|
type: "array",
|
|
items: {
|
|
$ref: `#/components/schemas/${tableName}`,
|
|
},
|
|
},
|
|
count: { type: "number" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
...this.getGenericStatusResponseSchema(),
|
|
},
|
|
});
|
|
}
|
|
|
|
private static generateAnalyticsCountApiSpec(data: {
|
|
modelType: new () => AnalyticsBaseModel;
|
|
registry: OpenAPIRegistry;
|
|
}): void {
|
|
const modelType: new () => AnalyticsBaseModel = data.modelType;
|
|
const model: AnalyticsBaseModel = new modelType();
|
|
const tableName: string = model.tableName || "UnknownAnalyticsModel";
|
|
const singularModelName: string = model.singularName || tableName;
|
|
|
|
// Use schema name that is already registered
|
|
const querySchemaName: string = `${tableName}QuerySchema`;
|
|
|
|
data.registry.registerPath({
|
|
method: "post",
|
|
path: `${model.crudApiPath}/count`,
|
|
operationId: `count${tableName}`,
|
|
summary: `Count ${singularModelName}`,
|
|
description: `Endpoint to count ${singularModelName} items`,
|
|
tags: [singularModelName],
|
|
parameters: [...OpenAPIUtil.getDefaultApiHeaders()],
|
|
requestBody: {
|
|
required: false,
|
|
content: {
|
|
"application/json": {
|
|
schema: {
|
|
type: "object",
|
|
properties: {
|
|
query: { $ref: `#/components/schemas/${querySchemaName}` },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
"200": {
|
|
description: "Successful response",
|
|
content: {
|
|
"application/json": {
|
|
schema: {
|
|
type: "object",
|
|
properties: {
|
|
count: { type: "number" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
...this.getGenericStatusResponseSchema(),
|
|
},
|
|
});
|
|
}
|
|
|
|
private static generateAnalyticsCreateApiSpec(data: {
|
|
modelType: new () => AnalyticsBaseModel;
|
|
registry: OpenAPIRegistry;
|
|
}): void {
|
|
const modelType: new () => AnalyticsBaseModel = data.modelType;
|
|
const model: AnalyticsBaseModel = new modelType();
|
|
const tableName: string = model.tableName || "UnknownAnalyticsModel";
|
|
const singularModelName: string = model.singularName || tableName;
|
|
|
|
// Skip generating create API if model has no create permissions or contains Public/CurrentUser permissions
|
|
if (this.shouldExcludeApiForPermissions(model.getCreatePermissions())) {
|
|
return;
|
|
}
|
|
|
|
// Use schema names that are already registered
|
|
const createSchemaName: string = `${tableName}CreateSchema`;
|
|
const readSchemaName: string = `${tableName}ReadSchema`;
|
|
|
|
data.registry.registerPath({
|
|
method: "post",
|
|
path: `${model.crudApiPath}`,
|
|
operationId: `create${tableName}`,
|
|
summary: `Create ${singularModelName}`,
|
|
description: `Endpoint to create a new ${singularModelName}`,
|
|
tags: [singularModelName],
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
"application/json": {
|
|
schema: {
|
|
type: "object",
|
|
properties: {
|
|
data: {
|
|
$ref: `#/components/schemas/${createSchemaName}`,
|
|
},
|
|
},
|
|
required: ["data"],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
"201": {
|
|
description: "Created successfully",
|
|
content: {
|
|
"application/json": {
|
|
schema: {
|
|
type: "object",
|
|
properties: {
|
|
data: {
|
|
$ref: `#/components/schemas/${readSchemaName}`,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
...this.getGenericStatusResponseSchema(),
|
|
},
|
|
});
|
|
}
|
|
|
|
private static generateAnalyticsGetApiSpec(data: {
|
|
modelType: new () => AnalyticsBaseModel;
|
|
registry: OpenAPIRegistry;
|
|
}): void {
|
|
const modelType: new () => AnalyticsBaseModel = data.modelType;
|
|
const model: AnalyticsBaseModel = new modelType();
|
|
const tableName: string = model.tableName || "UnknownAnalyticsModel";
|
|
const singularModelName: string = model.singularName || tableName;
|
|
|
|
// Skip generating get API if model has no read permissions or contains Public/CurrentUser permissions
|
|
if (this.shouldExcludeApiForPermissions(model.getReadPermissions())) {
|
|
return;
|
|
}
|
|
|
|
// Use schema name that is already registered
|
|
const selectSchemaName: string = `${tableName}SelectSchema`;
|
|
|
|
data.registry.registerPath({
|
|
method: "post",
|
|
path: `${model.crudApiPath}/{id}`,
|
|
operationId: `get${tableName}`,
|
|
summary: `Get ${singularModelName}`,
|
|
description: `Endpoint to retrieve a single ${singularModelName} by ID`,
|
|
tags: [singularModelName],
|
|
parameters: [
|
|
...OpenAPIUtil.getDefaultApiHeaders(),
|
|
{
|
|
name: "id",
|
|
in: "path",
|
|
required: true,
|
|
schema: {
|
|
type: "string",
|
|
format: "uuid",
|
|
},
|
|
description: `ID of the ${singularModelName} to retrieve`,
|
|
},
|
|
],
|
|
requestBody: {
|
|
required: false,
|
|
content: {
|
|
"application/json": {
|
|
schema: {
|
|
type: "object",
|
|
properties: {
|
|
select: { $ref: `#/components/schemas/${selectSchemaName}` },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
"200": {
|
|
description: "Successful response",
|
|
content: {
|
|
"application/json": {
|
|
schema: {
|
|
type: "object",
|
|
properties: {
|
|
data: {
|
|
$ref: `#/components/schemas/${tableName}`,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
...this.getGenericStatusResponseSchema(),
|
|
},
|
|
});
|
|
}
|
|
|
|
private static generateAnalyticsUpdateApiSpec(data: {
|
|
modelType: new () => AnalyticsBaseModel;
|
|
registry: OpenAPIRegistry;
|
|
}): void {
|
|
const modelType: new () => AnalyticsBaseModel = data.modelType;
|
|
const model: AnalyticsBaseModel = new modelType();
|
|
const tableName: string = model.tableName || "UnknownAnalyticsModel";
|
|
const singularModelName: string = model.singularName || tableName;
|
|
|
|
// Skip generating update API if model has no update permissions or contains Public/CurrentUser permissions
|
|
if (this.shouldExcludeApiForPermissions(model.getUpdatePermissions())) {
|
|
return;
|
|
}
|
|
|
|
// Use schema names that are already registered
|
|
const updateSchemaName: string = `${tableName}UpdateSchema`;
|
|
const readSchemaName: string = `${tableName}ReadSchema`;
|
|
|
|
data.registry.registerPath({
|
|
method: "put",
|
|
path: `${model.crudApiPath}/{id}`,
|
|
operationId: `update${tableName}`,
|
|
summary: `Update ${singularModelName}`,
|
|
description: `Endpoint to update an existing ${singularModelName}`,
|
|
tags: [singularModelName],
|
|
parameters: [
|
|
...OpenAPIUtil.getDefaultApiHeaders(),
|
|
{
|
|
name: "id",
|
|
in: "path",
|
|
required: true,
|
|
schema: {
|
|
type: "string",
|
|
format: "uuid",
|
|
},
|
|
description: `ID of the ${singularModelName} to update`,
|
|
},
|
|
],
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
"application/json": {
|
|
schema: {
|
|
type: "object",
|
|
properties: {
|
|
data: {
|
|
$ref: `#/components/schemas/${updateSchemaName}`,
|
|
},
|
|
},
|
|
required: ["data"],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
"200": {
|
|
description: "Updated successfully",
|
|
content: {
|
|
"application/json": {
|
|
schema: {
|
|
type: "object",
|
|
properties: {
|
|
data: {
|
|
$ref: `#/components/schemas/${readSchemaName}`,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
...this.getGenericStatusResponseSchema(),
|
|
},
|
|
});
|
|
}
|
|
|
|
private static generateAnalyticsDeleteApiSpec(data: {
|
|
modelType: new () => AnalyticsBaseModel;
|
|
registry: OpenAPIRegistry;
|
|
}): void {
|
|
const modelType: new () => AnalyticsBaseModel = data.modelType;
|
|
const model: AnalyticsBaseModel = new modelType();
|
|
const tableName: string = model.tableName || "UnknownAnalyticsModel";
|
|
const singularModelName: string = model.singularName || tableName;
|
|
|
|
// Skip generating delete API if model has no delete permissions or contains Public/CurrentUser permissions
|
|
if (this.shouldExcludeApiForPermissions(model.getDeletePermissions())) {
|
|
return;
|
|
}
|
|
|
|
data.registry.registerPath({
|
|
method: "delete",
|
|
path: `${model.crudApiPath}/{id}`,
|
|
operationId: `delete${tableName}`,
|
|
summary: `Delete ${singularModelName}`,
|
|
description: `Endpoint to delete a ${singularModelName}`,
|
|
tags: [singularModelName],
|
|
parameters: [
|
|
...OpenAPIUtil.getDefaultApiHeaders(),
|
|
{
|
|
name: "id",
|
|
in: "path",
|
|
required: true,
|
|
schema: {
|
|
type: "string",
|
|
format: "uuid",
|
|
},
|
|
description: `ID of the ${singularModelName} to delete`,
|
|
},
|
|
],
|
|
responses: {
|
|
"200": {
|
|
description: "Deleted successfully",
|
|
},
|
|
...this.getGenericStatusResponseSchema(),
|
|
},
|
|
});
|
|
}
|
|
}
|