configuration for how fields look and EnhancedTable component for enhanced table display
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import SwaggerParser from "@apidevtools/swagger-parser";
|
||||
import { AppConfig, ResourceConfig, ResourceField, FieldType } from "../types/config";
|
||||
import { configuration } from "../configuration";
|
||||
|
||||
/**
|
||||
* Maps OpenAPI property types to our internal FieldType
|
||||
@@ -11,37 +12,78 @@ function mapOpenApiType(prop: any): FieldType {
|
||||
if (format === "date-time") return "datetime";
|
||||
if (format === "date") return "date";
|
||||
if (prop.enum) return "enum";
|
||||
if (type === "string" && (prop.description?.toLowerCase().includes("image") || prop.name?.toLowerCase().includes("icon"))) return "image";
|
||||
|
||||
if (
|
||||
type === "string" &&
|
||||
(prop.description?.toLowerCase().includes("image") ||
|
||||
prop.name?.toLowerCase().includes("icon"))
|
||||
)
|
||||
return "image";
|
||||
|
||||
switch (type) {
|
||||
case "integer":
|
||||
case "number": return "number";
|
||||
case "boolean": return "boolean";
|
||||
case "object": return "object";
|
||||
case "array": return "array";
|
||||
default: return "string";
|
||||
case "number":
|
||||
return "number";
|
||||
case "boolean":
|
||||
return "boolean";
|
||||
case "object":
|
||||
return "object";
|
||||
case "array":
|
||||
return "array";
|
||||
default:
|
||||
return "string";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively converts OpenAPI schemas to ResourceField map
|
||||
*/
|
||||
function parseSchemaFields(schema: any): Record<string, ResourceField> {
|
||||
function parseSchemaFields(
|
||||
schema: any,
|
||||
resourceName: string,
|
||||
allResources: string[]
|
||||
): Record<string, ResourceField> {
|
||||
const fields: Record<string, ResourceField> = {};
|
||||
const properties = schema.properties || {};
|
||||
const required = schema.required || [];
|
||||
const overrides = configuration[resourceName]?.fields || {};
|
||||
|
||||
for (const [key, prop] of Object.entries(properties) as any) {
|
||||
const type = mapOpenApiType(prop);
|
||||
const override = overrides[key];
|
||||
|
||||
fields[key] = {
|
||||
type: mapOpenApiType(prop),
|
||||
label: prop.title || key.charAt(0).toUpperCase() + key.slice(1).replace(/_/g, " "),
|
||||
type,
|
||||
label:
|
||||
prop.title ||
|
||||
key.charAt(0).toUpperCase() + key.slice(1).replace(/_/g, " "),
|
||||
required: required.includes(key),
|
||||
options: prop.enum,
|
||||
readOnly: prop.readOnly || key === "id" || key === "created_at" || key === "updated_at",
|
||||
readOnly:
|
||||
prop.readOnly ||
|
||||
key === "id" ||
|
||||
key === "created_at" ||
|
||||
key === "updated_at",
|
||||
...override,
|
||||
};
|
||||
|
||||
// Schema-based Relation Detection
|
||||
// If it's an object/string and matches a resource name, it might be a relation
|
||||
const potentialRelation = allResources.find(
|
||||
(res) =>
|
||||
key === res ||
|
||||
key === `${res}_id` ||
|
||||
prop.title?.toLowerCase() === res ||
|
||||
prop["x-resource"] === res
|
||||
);
|
||||
|
||||
if (potentialRelation) {
|
||||
if (type === "string" || (type === "object" && prop.properties?.id)) {
|
||||
fields[key].relation = potentialRelation;
|
||||
}
|
||||
}
|
||||
|
||||
if (fields[key].type === "object" && prop.properties) {
|
||||
fields[key].schema = parseSchemaFields(prop);
|
||||
fields[key].schema = parseSchemaFields(prop, resourceName, allResources);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,13 +112,15 @@ export async function loadConfigFromOpenApi(baseUrl: string): Promise<AppConfig>
|
||||
if (!resourcePaths[base]) resourcePaths[base] = { path, methods: [] };
|
||||
const methods = Object.keys(paths[path] || {});
|
||||
resourcePaths[base].methods.push(...methods);
|
||||
|
||||
|
||||
// We prefer the plural GET path for schema extraction
|
||||
if (!path.includes("{") && paths[path]?.get?.responses?.["200"]) {
|
||||
resourcePaths[base].listPath = path;
|
||||
resourcePaths[base].listPath = path;
|
||||
}
|
||||
}
|
||||
|
||||
const allResourceNames = Object.keys(resourcePaths);
|
||||
|
||||
// Generate ResourceConfig for each identified base path
|
||||
for (const [name, info] of Object.entries(resourcePaths)) {
|
||||
const listPath = info.listPath || `/${name}`;
|
||||
@@ -89,7 +133,8 @@ export async function loadConfigFromOpenApi(baseUrl: string): Promise<AppConfig>
|
||||
|
||||
// Extract schema from the 200 response of the list endpoint
|
||||
let schema: any = null;
|
||||
const responseSchema = listOp.responses?.["200"]?.content?.["application/json"]?.schema;
|
||||
const responseSchema =
|
||||
listOp.responses?.["200"]?.content?.["application/json"]?.schema;
|
||||
|
||||
if (responseSchema?.type === "array" && responseSchema.items) {
|
||||
schema = responseSchema.items;
|
||||
@@ -103,14 +148,15 @@ export async function loadConfigFromOpenApi(baseUrl: string): Promise<AppConfig>
|
||||
label: schema.title || label,
|
||||
pluralLabel: pluralLabel,
|
||||
endpoint: listPath,
|
||||
primaryKey: "id", // assume 'id' as default or look for 'required' + 'unique'
|
||||
fields: parseSchemaFields(schema),
|
||||
primaryKey: "_id", // assume 'id' as default or look for 'required' + 'unique'
|
||||
fields: parseSchemaFields(schema, name, allResourceNames),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
baseUrl: import.meta.env.VITE_API_BASE_URL || (api.servers?.[0]?.url ?? ""),
|
||||
baseUrl:
|
||||
import.meta.env.VITE_API_BASE_URL || (api.servers?.[0]?.url ?? ""),
|
||||
authBaseUrl: import.meta.env.VITE_AUTH_BASE_URL || "",
|
||||
resources,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user