import { ActionCheckSource, AdminForthPlugin, interpretResource } from "adminforth";
import type { IAdminForth, IHttpServer, AdminForthResourcePages, AdminForthResourceColumn, AdminForthDataTypes, AdminForthResource } from "adminforth";
import type { PluginOptions } from './types.js';
import { z } from "zod";
const createBodySchema = z.object({
resourceId: z.string(),
record: z.record(z.string(), z.unknown()).nullish(),
}).strict();
export default class InlineCreatePlugin extends AdminForthPlugin {
options: PluginOptions;
constructor(options: PluginOptions) {
super(options, import.meta.url);
this.options = options;
}
async modifyResourceConfig(adminforth: IAdminForth, resourceConfig: AdminForthResource) {
super.modifyResourceConfig(adminforth, resourceConfig);
if (!resourceConfig.options.pageInjections) {
resourceConfig.options.pageInjections = {};
}
if (!resourceConfig.options.pageInjections.list) {
resourceConfig.options.pageInjections.list = {};
}
if (this.resourceConfig.options.allowedActions.create === false) {
throw new Error(`InlineCreatePlugin cannot be used on resource "${resourceConfig.resourceId}" because create action is disabled`);
}
resourceConfig.options.pageInjections.list.tableBodyStart = [{
file: this.componentPath('InlineCreateForm.vue'),
meta: {
pluginInstanceId: this.pluginInstanceId
}
}];
}
validateConfigAfterDiscover(adminforth: IAdminForth, resourceConfig: AdminForthResource) {
// Check each column for potential configuration issues
for (const column of resourceConfig.columns) {
if (column.backendOnly) continue;
const isRequiredForCreate = column.required?.create === true;
const isVisibleInList = column.showIn?.list !== false;
const hasFillOnCreate = column.fillOnCreate !== undefined;
const isVisibleInCreate = column.showIn?.create !== false;
if (isRequiredForCreate && !isVisibleInList && !hasFillOnCreate) {
throw new Error(
`Column "${column.name}" in resource "${resourceConfig.resourceId}" is required for create but not visible in list view. ` +
'Either:\n' +
'1) Set showIn.list: true, or\n' +
'2) Set required.create: false and ensure a database default exists, or\n' +
'3) Add fillOnCreate property and set showIn.create: false'
);
}
if (hasFillOnCreate && isVisibleInCreate) {
throw new Error(
`Column "${column.name}" in resource "${resourceConfig.resourceId}" has fillOnCreate but is still visible in create form. ` +
'When using fillOnCreate, set showIn.create: false'
);
}
}
}
instanceUniqueRepresentation() {
return 'inline-create';
}
setupEndpoints(server: IHttpServer) {
server.endpoint({
method: 'POST',
path: `/plugin/${this.pluginInstanceId}/create`,
request_schema: createBodySchema,
handler: async ({ body, adminUser }) => {
const data = body as z.infer;
const { record, resourceId } = data;
if (!record) {
return { error: 'No record provided' };
}
if ( this.resourceConfig.resourceId !== resourceId) {
return { error: 'Resource ID mismatch' };
}
const resource = this.adminforth.config.resources.find(r => r.resourceId === resourceId);
const { allowedActions } = await interpretResource(adminUser, resource, {}, ActionCheckSource.DisplayButtons, this.adminforth);
if (!allowedActions.create) {
return { error: 'User does not have permission to create records for this resource' };
}
for (const column of resource.columns) {
if (column.backendOnly) {
if (record[column.name] !== undefined) {
return { error: `Column "${column.name}" is backend-only and cannot be set by the user` };
}
};
}
const cleanRecord = resource.columns.reduce((acc, field) => {
if (record[field.name] !== undefined && record[field.name] !== null) {
acc[field.name] = record[field.name];
}
return acc;
}, {});
const result = await this.adminforth.createResourceRecord({
resource,
record: cleanRecord,
adminUser
});
if (result.error) {
return { error: result.error };
}
const createdRecord = result.createdRecord;
//filter createdRecord to only include columns that are not backendOnly
const safe = {};
for (const col of resource.columns) {
if (col.backendOnly) continue;
if (col.showIn?.list === false && col.showIn?.show === false) continue;
safe[col.name] = result.createdRecord[col.name];
}
return { record: safe };
}
});
}
}