@premieroctet/next-admin
Version:
Next-Admin provides a customizable and turnkey admin dashboard for applications built with Next.js and powered by the Prisma ORM. It aims to simplify the development process by providing a turnkey admin system that can be easily integrated into your proje
719 lines (718 loc) • 37.9 kB
JavaScript
import external_formidable_default from "formidable";
import { Writable } from "node:stream";
import { getResources, getSchema } from "./globals.mjs";
import { getRawData } from "./prisma.mjs";
import { isFileUploadFormat, isNativeFunction, isUploadFile, pipe, uncapitalize } from "./tools.mjs";
const getJsonSchema = ()=>getSchema();
const enumValueForEnumType = (definition, value)=>{
if (definition.enum) return definition.enum.find((enumValue)=>enumValue === value);
return false;
};
const getEnableToExecuteActions = async (resource, prisma, ids, actions)=>{
if (!actions?.some((action)=>action.canExecute)) return actions?.map((action)=>{
const { canExecute, ...restAction } = action;
return restAction;
});
{
const maxDepth = Math.max(0, ...actions.map((action)=>action.depth ?? 0));
const data = await getRawData({
prisma,
resource,
resourceIds: ids,
maxDepth: maxDepth || void 0
});
return actions?.reduce(async (acc, action)=>{
const accResolved = await acc;
const { canExecute, ...restAction } = action;
if (canExecute) {
const allowedIds = data.filter((item)=>canExecute(item)).map((item)=>item[getModelIdProperty(resource)]);
accResolved.push({
...restAction,
allowedIds
});
} else accResolved.push(restAction);
return Promise.resolve(accResolved);
}, Promise.resolve([]));
}
};
const getModelIdProperty = (model)=>{
const schemaModel = getSchema().definitions[model];
if (!schemaModel || !schemaModel.properties) return "id";
const schemaModelProperty = schemaModel.properties;
return Object.keys(schemaModelProperty).find((property)=>schemaModelProperty[property]?.__nextadmin?.primaryKey) ?? "id";
};
const getDeepRelationModel = (model, property)=>{
const schemaModel = getSchema().definitions[model];
const schemaModelProperty = schemaModel.properties;
const relationField = schemaModelProperty[property];
return relationField;
};
const modelHasIdField = (model)=>{
const schemaModel = getSchema().definitions[model];
const schemaModelProperty = schemaModel.properties;
return Object.entries(schemaModelProperty).some(([, value])=>value.__nextadmin?.primaryKey);
};
const server_getResources = (options)=>{
const definedModels = options?.model ? Object.keys(options.model) : [];
return definedModels.length > 0 ? definedModels : getResources();
};
const getToStringForRelations = (modelName, fieldName, modelNameRelation, options)=>{
const editOptions = options?.model?.[modelName]?.edit;
const relationOptions = options?.model?.[modelNameRelation];
const explicitManyToManyRelationField = editOptions?.fields?.[fieldName]?.relationshipSearchField;
const nonCheckedToString = editOptions?.fields?.[fieldName]?.[explicitManyToManyRelationField ? "relationOptionFormatter" : "optionFormatter"] || relationOptions?.toString;
const modelRelationIdField = getModelIdProperty(modelNameRelation);
const toStringForRelations = nonCheckedToString && !isNativeFunction(nonCheckedToString) ? nonCheckedToString : (item)=>item[modelRelationIdField];
return toStringForRelations;
};
const getToStringForModel = (options)=>{
const nonCheckedToString = options?.toString;
const toStringForRelations = nonCheckedToString && !isNativeFunction(nonCheckedToString) ? nonCheckedToString : void 0;
return toStringForRelations;
};
const orderSchema = (resource, options)=>(schema)=>{
const modelName = resource;
if (!schema.definitions[resource]) return schema;
const edit = options?.model?.[modelName]?.edit;
const display = edit?.display;
if (display) {
const properties = schema.definitions[modelName].properties;
const propertiesOrdered = {};
display.forEach((property)=>{
if ("string" == typeof property) propertiesOrdered[property] = properties[property];
else propertiesOrdered[property.id] = {
type: "null",
title: property.title,
description: property.description
};
});
schema.definitions[modelName].properties = propertiesOrdered;
}
return schema;
};
const fillRelationInSchema = (resource, options)=>async (schema)=>{
const modelName = resource;
const modelSchema = schema.definitions[modelName];
const modelProperties = modelSchema?.properties;
const display = options?.model?.[modelName]?.edit?.display;
let fields;
fields = modelProperties && display ? Object.entries(modelProperties).filter(([field])=>display.includes(field)) : Object.entries(modelProperties);
if (!modelSchema || !fields) return schema;
await Promise.all(fields.map(async ([name, value])=>{
const fieldName = name;
const fieldNextAdmin = value.__nextadmin;
const fieldType = fieldNextAdmin?.type;
const fieldKind = fieldNextAdmin?.kind;
const relationFromField = fieldNextAdmin?.relation?.fromField;
if ("enum" === fieldKind) {
const fieldValue = schema.definitions[modelName].properties[name];
if (fieldValue) fieldValue.enum = fieldValue.enum?.map((item)=>"object" != typeof item ? {
label: item,
value: item
} : item);
if (fieldValue?.default) fieldValue.default = "object" != typeof fieldValue.default ? {
label: fieldValue.default,
value: fieldValue.default
} : fieldValue.default;
}
if ("object" === fieldKind) {
const modelNameRelation = fieldType;
let fieldValue = schema.definitions[modelName].properties[name];
if (!fieldValue) return;
delete fieldValue.$ref;
const enumeration = [];
const required = schema.definitions[modelName].required;
const relationFromFieldsRequired = required?.includes(relationFromField);
if (relationFromFieldsRequired) {
required?.push(fieldName);
schema.definitions[modelName].required = required;
}
if ("array" !== fieldValue.type) {
fieldValue.type = "string";
fieldValue.relation = modelNameRelation;
fieldValue.enum = enumeration;
fieldValue.__nextadmin = fieldNextAdmin;
} else {
fieldValue.items = {
type: "string",
relation: modelNameRelation,
enum: enumeration
};
delete fieldValue.anyOf;
}
}
}));
return schema;
};
const transformData = (data, resource, editOptions, options)=>{
const modelName = resource;
const model = getSchema().definitions[modelName];
if (!model) return data;
const schemaProperties = model.properties;
return Object.keys(data).reduce(async (accP, key)=>{
const acc = await accP;
const field = schemaProperties[key];
const fieldKind = field?.__nextadmin?.kind;
const get = editOptions?.fields?.[key]?.handler?.get;
const explicitManyToManyRelationField = editOptions?.fields?.[key]?.relationshipSearchField;
if (get) acc[key] = await get(data[key]);
else if ("enum" === fieldKind) {
const value = data[key];
if (Array.isArray(value)) acc[key] = value.map((item)=>({
label: item,
value: item
}));
else acc[key] = value ? {
label: value,
value
} : null;
} else if ("object" === fieldKind) {
const modelRelation = field?.__nextadmin?.type;
const modelRelationIdField = getModelIdProperty(modelRelation);
let deepRelationModel;
let deepModelRelationIdField;
if (explicitManyToManyRelationField) {
deepRelationModel = getDeepRelationModel(modelRelation, explicitManyToManyRelationField);
deepModelRelationIdField = getModelIdProperty(deepRelationModel?.__nextadmin?.type);
}
const toStringForRelations = getToStringForRelations(modelName, key, explicitManyToManyRelationField ? deepRelationModel?.__nextadmin?.type : modelRelation, options);
if (Array.isArray(data[key])) acc[key] = data[key].map((item)=>{
if (!!editOptions?.fields?.[key] && "display" in editOptions.fields[key] && "table" === editOptions.fields[key].display) return {
data: item,
value: item[modelRelationIdField].value
};
return {
label: explicitManyToManyRelationField ? toStringForRelations(item[explicitManyToManyRelationField]) : toStringForRelations(item),
value: explicitManyToManyRelationField ? item[explicitManyToManyRelationField]?.[deepModelRelationIdField] : item[modelRelationIdField],
data: {
modelName: deepRelationModel?.__nextadmin?.type ?? null
}
};
});
else acc[key] = data[key] ? {
label: toStringForRelations(data[key]),
value: data[key][modelRelationIdField]
} : null;
} else if (field?.__nextadmin?.isList && field.__nextadmin?.kind === "scalar") acc[key] = data[key];
else {
const fieldTypes = field?.__nextadmin?.type;
if ("DateTime" === fieldTypes) acc[key] = data[key] ? data[key].toISOString() : null;
else if ("Json" === fieldTypes) acc[key] = data[key] ? JSON.stringify(data[key]) : null;
else if ("Decimal" === fieldTypes) acc[key] = data[key] ? Number(data[key]) : null;
else if ("BigInt" === fieldTypes) acc[key] = data[key] ? BigInt(data[key]).toString() : null;
else acc[key] = data[key] ? data[key] : null;
}
return acc;
}, Promise.resolve({}));
};
const findRelationInData = (data, schema)=>{
Object.entries(schema.properties).forEach(([property, value])=>{
const propertyType = value.__nextadmin?.type;
const propertyKind = value.__nextadmin?.kind;
const propertyRelationFrom = value.__nextadmin?.relation?.fromField;
const propertyRelationToField = value.__nextadmin?.relation?.toField;
const isList = value.__nextadmin?.isList;
if ("object" === propertyKind) if (propertyRelationFrom && propertyRelationToField && !isList) {
const idProperty = getModelIdProperty(propertyType);
data.forEach((item)=>{
if (item[property]) item[property] = {
type: "link",
value: {
label: item[property],
url: `${uncapitalize(propertyType)}/${item[property][idProperty]}`
}
};
return item;
});
} else data.forEach((item)=>{
if (item[property]) item[property] = {
type: "count",
value: item[property].length
};
return item;
});
if ([
"scalar",
"enum"
].includes(propertyKind ?? "") && isList) data.forEach((item)=>{
if (item[property]) item[property] = {
type: "count",
value: item[property].length
};
return item;
});
if ("DateTime" === propertyType || "Decimal" === propertyType || "BigInt" === propertyType) data.forEach((item)=>{
if (!item[property]) return item;
if ("DateTime" === propertyType) item[property] = {
type: "date",
value: item[property].toISOString()
};
else if ("Decimal" === propertyType) item[property] = Number(item[property]);
else if ("BigInt" === propertyType) item[property] = BigInt(item[property]).toString();
});
});
return data;
};
const parseFormData = (formData, schemaResource, editFieldOptions)=>{
const parsedData = {};
Object.entries(schemaResource.properties).forEach(([property, value])=>{
if (property in formData) {
const formPropertyName = property;
const propertyNextAdminData = value.__nextadmin;
const propertyType = propertyNextAdminData?.type;
const propertyKind = propertyNextAdminData?.kind;
if ("object" === propertyKind) if (formData[formPropertyName]) parsedData[formPropertyName] = formData[formPropertyName];
else parsedData[formPropertyName] = null;
else if (propertyNextAdminData?.isList && "scalar" === propertyNextAdminData.kind && !isFileUploadFormat(editFieldOptions?.[property]?.format ?? "")) parsedData[formPropertyName] = JSON.parse(formData[formPropertyName]);
else if ("Int" === propertyType) {
const value = Number(formData[formPropertyName]);
parsedData[formPropertyName] = isNaN(value) ? void 0 : value;
} else if ("Boolean" === propertyType) parsedData[formPropertyName] = "on" === formData[formPropertyName];
else parsedData[formPropertyName] = formData[formPropertyName];
}
});
return parsedData;
};
const formatId = (resource, id)=>{
const model = getSchema().definitions[resource];
const modelProperties = model.properties;
const idProperty = getModelIdProperty(resource);
return Object.entries(modelProperties).find(([name])=>name === idProperty)?.[1].__nextadmin?.type === "Int" ? Number(id) : id;
};
const getExplicitManyToManyTableFields = (manyToManyResource)=>{
const model = getSchema().definitions[manyToManyResource];
const modelProperties = model.properties;
const relationFields = Object.entries(modelProperties).filter(([, value])=>value.__nextadmin?.kind === "object");
return relationFields;
};
const getExplicitManyToManyTablePrimaryKey = (resource)=>{
const model = getSchema().definitions[resource];
return {
name: model?.__nextadmin?.primaryKeyField?.name,
fields: model?.__nextadmin?.primaryKeyField?.fields
};
};
const handleUploadProperty = async ({ files, resourceId, editOptions, property })=>{
const uploadHandler = editOptions?.[property]?.handler?.upload;
if (uploadHandler) return Promise.all(files.map(async (file)=>{
if ("string" == typeof file) return;
const uploadResult = await uploadHandler(file.buffer, file.infos, {
resourceId
});
if ("string" != typeof uploadResult) return void console.warn("Upload handler must return a string, fallback to no-op for field " + property.toString());
return uploadResult;
}));
console.warn("You need to provide an upload handler for data-url format");
};
const handleFileDeletion = async ({ fileUris, editOptions, property })=>{
const deleteHandler = editOptions?.[property]?.handler?.deleteFile;
if (deleteHandler) {
const deletedFiles = await Promise.all(fileUris.map(async (uri)=>{
try {
const isDeleted = await deleteHandler(uri);
if (isDeleted) return uri;
} catch (e) {
console.error("An error occured while deleting file", e);
}
}));
return deletedFiles.filter(Boolean);
}
console.warn("Delete handler not provided, files will not be deleted from your remote storage");
return fileUris;
};
const formattedFormData = async (formData, schema, resource, resourceId, editOptions, prisma)=>{
const formattedData = {};
const complementaryFormattedData = {};
const modelName = resource;
const errors = [];
const creating = void 0 === resourceId;
const resourceSchema = schema.definitions[modelName];
const resourceIdProperty = getModelIdProperty(resource);
const resourceIdField = getModelIdProperty(resource);
const currentRecord = resourceId ? await prisma[uncapitalize(resource)].findUnique({
where: {
[resourceIdProperty]: resourceId
}
}) : void 0;
const results = await Promise.allSettled(Object.entries(resourceSchema.properties).map(async ([property, value])=>{
if (property in formData) {
const propertyNextAdminData = value.__nextadmin;
const propertyType = propertyNextAdminData?.type;
const propertyKind = propertyNextAdminData?.kind;
const isList = propertyNextAdminData?.isList;
if ("object" === propertyKind) {
const propertyName = property;
const propertyTypeTyped = propertyType;
const fieldValue = schema.definitions[modelName].properties[propertyName];
if (fieldValue?.type === "array") {
formData[propertyName] = JSON.parse(formData[propertyName]);
const fieldOptions = editOptions?.[propertyName];
const orderField = fieldOptions && "orderField" in fieldOptions && fieldOptions.orderField;
if (fieldOptions && "relationshipSearchField" in fieldOptions && fieldOptions?.relationshipSearchField) {
const relationFields = getExplicitManyToManyTableFields(propertyTypeTyped);
const currentResourceField = relationFields.filter(([, field])=>field.__nextadmin?.type === resource)[0];
const externalResourceField = relationFields.filter(([, field])=>field.__nextadmin?.type !== resource)[0];
if (creating) formattedData[propertyName] = {
create: formData[propertyName].map((item, index)=>{
const data = {
[externalResourceField[0]]: {
connect: {
[resourceIdField]: formatId(externalResourceField[1].__nextadmin?.type, item)
}
}
};
if (orderField) data[orderField] = index;
return data;
})
};
else {
const resourcePrimaryKey = getExplicitManyToManyTablePrimaryKey(propertyTypeTyped);
const resourcePrimaryKeyCurrentResourceField = resourcePrimaryKey.fields.find((field)=>field === currentResourceField[1].__nextadmin?.relation?.fromFieldDbName);
const resourcePrimaryKeyExternalResourceField = resourcePrimaryKey.fields.find((field)=>field === externalResourceField[1].__nextadmin?.relation?.fromFieldDbName);
formattedData[propertyName] = {
upsert: formData[propertyName].map((item, index)=>{
const formattedItem = {
create: {
[externalResourceField[0]]: {
connect: {
id: formatId(externalResourceField[1].__nextadmin?.type, item)
}
}
},
where: {
[resourcePrimaryKey.name]: {
[resourcePrimaryKeyCurrentResourceField]: formatId(resource, resourceId.toString()),
[resourcePrimaryKeyExternalResourceField]: formatId(externalResourceField[1]?.__nextadmin?.type, item)
}
},
update: {
[externalResourceField[0]]: {
connect: {
id: formatId(externalResourceField[1]?.__nextadmin?.type, item)
}
}
}
};
if (orderField) {
formattedItem.create[orderField] = index;
formattedItem.update[orderField] = index;
}
return formattedItem;
}),
deleteMany: {
[resourcePrimaryKeyCurrentResourceField]: formatId(resource, resourceId.toString()),
[resourcePrimaryKeyExternalResourceField]: {
notIn: formData[propertyName].map((item)=>formatId(externalResourceField[1]?.__nextadmin?.type, item))
}
}
};
}
} else {
const updateRelatedField = {
...orderField && {
update: formData[propertyName]?.map((item, index)=>({
where: {
id: formatId(propertyType, item)
},
data: {
...orderField && {
[orderField]: index
}
}
}))
}
};
formattedData[propertyName] = {
[creating ? "connect" : "set"]: formData[propertyName].map((item)=>({
id: formatId(propertyType, item)
})),
...!creating && updateRelatedField
};
if (creating) complementaryFormattedData[propertyName] = updateRelatedField;
}
} else {
const connect = Boolean(formData[propertyName]);
if (connect) formattedData[propertyName] = {
connect: {
id: formatId(propertyType, formData[propertyName])
}
};
else if (!creating) formattedData[propertyName] = {
disconnect: true
};
}
} else if ("scalar" === propertyKind && isList) {
const propertyName = property;
const formDataValue = "string" == typeof formData[propertyName] ? JSON.parse(formData[propertyName]) : formData[propertyName];
if ("Int" === propertyType || "Float" === propertyType || "Decimal" === propertyType) formattedData[propertyName] = {
set: formDataValue.map((item)=>isNaN(Number(item)) ? void 0 : Number(item)).filter(Boolean)
};
else if ("String" === propertyType && isFileUploadFormat(editOptions?.[propertyName]?.format ?? "")) {
const uploadErrorMessage = editOptions?.[propertyName]?.handler?.uploadErrorMessage;
const deletedFiles = currentRecord[propertyName]?.filter((existing)=>!formData[propertyName]?.includes(existing));
try {
const unsetFiles = await handleFileDeletion({
fileUris: deletedFiles,
editOptions,
property: propertyName
});
const filteredFiles = currentRecord[propertyName].filter((name)=>!unsetFiles.includes(name));
const uploadedFiles = await handleUploadProperty({
files: formData[propertyName].filter(isUploadFile),
resourceId,
editOptions,
property: propertyName
});
formattedData[propertyName] = {
set: [
...filteredFiles,
...uploadedFiles?.filter(Boolean) ?? []
]
};
} catch (e) {
errors.push({
field: propertyName.toString(),
message: uploadErrorMessage ?? `Upload failed: ${e.message}`
});
}
} else formattedData[propertyName] = {
set: formDataValue
};
} else if ("enum" === propertyKind && isList) {
const propertyName = property;
const data = JSON.parse(formData[propertyName] ?? "[]");
formattedData[propertyName] = {
set: data
};
} else {
const propertyName = property;
if ("" === formData[propertyName]) formattedData[propertyName] = null;
else if ("Int" === propertyType || "Float" === propertyType || "Decimal" === propertyType) formattedData[propertyName] = isNaN(Number(formData[propertyName])) ? void 0 : Number(formData[propertyName]);
else if ("Boolean" === propertyType) formattedData[propertyName] = "on" === formData[propertyName];
else if ("DateTime" === propertyType) formattedData[propertyName] = formData[propertyName] ? new Date(formData[propertyName]) : null;
else if ("Json" === propertyType) try {
formattedData[propertyName] = formData[propertyName] ? JSON.parse(formData[propertyName]) : null;
} catch {}
else if ("BigInt" === propertyType) formattedData[propertyName] = formData[propertyName] ? BigInt(formData[propertyName]) : null;
else if ("String" === propertyType && isFileUploadFormat(editOptions?.[propertyName]?.format ?? "")) {
const uploadErrorMessage = editOptions?.[propertyName]?.handler?.uploadErrorMessage;
try {
if (currentRecord?.[propertyName]) await handleFileDeletion({
fileUris: [
currentRecord[propertyName]
],
property: propertyName,
editOptions
});
const uploaded = await handleUploadProperty({
files: formData[propertyName].filter(isUploadFile),
resourceId,
editOptions,
property: propertyName
});
if (uploaded?.length) formattedData[propertyName] = uploaded[0];
else formattedData[propertyName] = null;
} catch (e) {
errors.push({
field: propertyName.toString(),
message: uploadErrorMessage ?? `Upload failed: ${e.message}`
});
}
} else formattedData[propertyName] = formData[propertyName];
}
}
}));
results.forEach((result)=>{
if ("rejected" === result.status) console.error(result.reason);
});
return {
formattedData,
complementaryFormattedData,
errors
};
};
const formatSearchFields = (uri)=>Object.fromEntries(new URLSearchParams(uri.split("?")[1]));
const transformSchema = (resource, edit, options)=>pipe(removeHiddenProperties(resource, edit), changeFormatInSchema(resource, edit), fillRelationInSchema(resource, options), fillDescriptionInSchema(resource, edit), addCustomProperties(resource, edit), orderSchema(resource, options), applyArrayMaxLength(resource, edit));
const applyVisiblePropertiesInSchema = (resource, edit, data, schema)=>{
const modelName = resource;
const modelSchema = schema.definitions[modelName];
if (!modelSchema) return schema;
const display = edit?.display;
const fields = edit?.fields;
if (display) display.forEach((property)=>{
if (schema.definitions?.[modelName]?.properties && fields?.[property]?.visible?.(data) === false) delete schema.definitions[modelName].properties[property];
});
return schema;
};
const fillDescriptionInSchema = (resource, editOptions)=>(schema)=>{
const modelName = resource;
const modelSchema = schema.definitions[modelName];
if (!modelSchema) return schema;
Object.entries(modelSchema.properties).forEach(([name, value])=>{
const propertyName = name;
const fieldValue = schema.definitions[modelName].properties[propertyName];
if (fieldValue && editOptions?.fields?.[propertyName]?.helperText) fieldValue.description = editOptions?.fields?.[propertyName]?.helperText;
});
return schema;
};
const changeFormatInSchema = (resource, editOptions)=>(schema)=>{
const modelName = resource;
const modelSchema = schema.definitions[modelName];
if (!modelSchema) return schema;
Object.entries(modelSchema.properties).forEach(([name, value])=>{
const propertyName = name;
const fieldValue = schema.definitions[modelName].properties[propertyName];
if (fieldValue && value.__nextadmin?.type === "Json") fieldValue.type = "string";
if (fieldValue && editOptions?.fields?.[propertyName]?.input) fieldValue.format = "string";
else if (editOptions?.fields?.[propertyName]?.format) {
if (fieldValue) if (editOptions?.fields?.[propertyName]?.format === "file") fieldValue.format = "data-url";
else fieldValue.format = editOptions?.fields?.[propertyName]?.format;
}
});
return schema;
};
const removeHiddenProperties = (resource, editOptions)=>(schema)=>{
if (!editOptions?.display) return schema;
const properties = schema.definitions[resource].properties;
Object.keys(properties).forEach((property)=>{
if (!editOptions?.display?.includes(property)) delete properties[property];
});
return schema;
};
const addCustomProperties = (resource, editOptions)=>(schema)=>{
const customFieldKeys = Object.keys(editOptions?.customFields ?? {});
customFieldKeys.forEach((property)=>{
const fieldOptions = editOptions?.customFields?.[property];
if (fieldOptions) {
schema.definitions[resource].properties[property] = {
type: "string",
description: fieldOptions?.helperText ?? ""
};
if (fieldOptions.required) schema.definitions[resource].required?.push(property);
if (fieldOptions.format) schema.definitions[resource].properties[property].format = fieldOptions.format;
}
});
return schema;
};
const applyArrayMaxLength = (resource, editOptions)=>(schema)=>{
const modelName = resource;
const modelSchema = schema.definitions[modelName];
if (!modelSchema) return schema;
Object.entries(modelSchema.properties).forEach(([name])=>{
const propertyName = name;
const fieldValue = schema.definitions[modelName].properties[propertyName];
if (fieldValue && editOptions?.fields?.[propertyName]?.maxLength) fieldValue.maxItems = editOptions?.fields?.[propertyName]?.maxLength;
});
return schema;
};
const getResourceFromParams = (params, resources)=>resources.find((r)=>{
const slugifiedResource = r.toLowerCase();
return params.some((param)=>param.toLowerCase() === slugifiedResource);
}) ?? null;
const getParamsFromUrl = (url, basePath)=>{
let urlWithoutParams = url.split("?")[0];
if (!urlWithoutParams.startsWith("/_next")) return url.replace(basePath, "").split("?")[0].split("/").filter(Boolean);
urlWithoutParams = urlWithoutParams.slice(urlWithoutParams.indexOf(basePath) + basePath.length).replace(".json", "");
return urlWithoutParams.split("/").filter(Boolean);
};
const getResourceIdFromParam = (param, resource)=>{
if ("new" === param) return;
const idProperty = formatId(resource, param);
return "number" == typeof idProperty && isNaN(idProperty) ? void 0 : idProperty;
};
const getFormDataValues = async (req)=>{
const form = external_formidable_default({
allowEmptyFiles: true,
minFileSize: 0,
fileWriteStreamHandler: ()=>new Writable({
write (_chunk, _encoding, callback) {
callback();
}
})
});
return new Promise((resolve, reject)=>{
const files = {};
form.on("fileBegin", (name, file)=>{
file.createFileWriteStream = ()=>{
const chunks = [];
return new Writable({
write (chunk, _encoding, callback) {
chunks.push(chunk);
callback();
},
final (callback) {
if (file.originalFilename) {
if (!files[name]) files[name] = [];
files[name].push({
buffer: Buffer.concat(chunks),
infos: {
name: file.originalFilename,
type: file.mimetype
}
});
}
callback();
}
});
};
});
form.parse(req, (err, fields)=>{
if (err) reject(err);
resolve({
...Object.entries(fields).reduce((acc, [key, value])=>{
if (Array.isArray(value)) acc[key] = value[0];
return acc;
}, {}),
...Object.entries(files).reduce((acc, [key, value])=>{
if (key in fields && Array.isArray(fields[key])) acc[key] = [
...fields[key],
...value
];
return acc;
}, files)
});
});
});
};
const getFormValuesFromFormData = async (formData, editFieldOptions)=>{
const tmpFormValues = {};
formData.forEach((val, key)=>{
if (key.startsWith("$ACTION")) return;
if (isFileUploadFormat(editFieldOptions?.[key]?.format ?? "")) tmpFormValues[key] = formData.getAll(key);
else tmpFormValues[key] = val;
});
const formValues = {};
await Promise.allSettled(Object.entries(tmpFormValues).map(async ([key, value])=>{
if (isFileUploadFormat(editFieldOptions?.[key]?.format ?? "") && Array.isArray(value)) {
const parameters = await Promise.all(value.map(async (file)=>{
if ("string" == typeof file) return file;
const buffer = await file.arrayBuffer();
return {
buffer: Buffer.from(buffer),
infos: {
name: file.name,
type: file.type
}
};
}));
formValues[key] = parameters;
} else formValues[key] = value;
}));
return formValues;
};
const getJsonBody = async (req)=>{
let body = await getBody(req);
if (body && "string" == typeof body && "application/json" === req.headers["content-type"]) body = JSON.parse(body);
return body;
};
const getBody = async (req)=>new Promise((resolve)=>{
const bodyParts = [];
let body;
req.on("data", (chunk)=>{
bodyParts.push(chunk);
}).on("end", ()=>{
body = Buffer.concat(bodyParts).toString();
resolve(body);
});
});
export { addCustomProperties, applyArrayMaxLength, applyVisiblePropertiesInSchema, changeFormatInSchema, enumValueForEnumType, fillRelationInSchema, findRelationInData, formatId, formatSearchFields, formattedFormData, getBody, getEnableToExecuteActions, getFormDataValues, getFormValuesFromFormData, getJsonBody, getJsonSchema, getModelIdProperty, getParamsFromUrl, getResourceFromParams, getResourceIdFromParam, server_getResources as getResources, getToStringForModel, getToStringForRelations, modelHasIdField, parseFormData, removeHiddenProperties, transformData, transformSchema };