WIP - field types
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
exports.up = function(knex) {
|
||||
return knex.schema.table('field_definitions', (table) => {
|
||||
table.jsonb('ui_metadata').nullable().comment('JSON metadata for UI rendering including display options, validation rules, and field-specific configurations');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
exports.down = function(knex) {
|
||||
return knex.schema.table('field_definitions', (table) => {
|
||||
table.dropColumn('ui_metadata');
|
||||
});
|
||||
};
|
||||
349
backend/seeds/example_contact_fields_with_ui_metadata.js
Normal file
349
backend/seeds/example_contact_fields_with_ui_metadata.js
Normal file
@@ -0,0 +1,349 @@
|
||||
/**
|
||||
* Example seed data for Contact object with UI metadata
|
||||
* Run this after creating the object definition
|
||||
*/
|
||||
|
||||
exports.seed = async function(knex) {
|
||||
// Get or create the Contact object
|
||||
const [contactObj] = await knex('object_definitions')
|
||||
.where({ api_name: 'Contact' })
|
||||
.select('id');
|
||||
|
||||
if (!contactObj) {
|
||||
console.log('Contact object not found. Please create it first.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Define fields with UI metadata
|
||||
const fields = [
|
||||
{
|
||||
object_definition_id: contactObj.id,
|
||||
api_name: 'firstName',
|
||||
label: 'First Name',
|
||||
type: 'text',
|
||||
is_required: true,
|
||||
is_system: false,
|
||||
is_custom: false,
|
||||
display_order: 1,
|
||||
ui_metadata: {
|
||||
placeholder: 'Enter first name',
|
||||
helpText: 'The contact\'s given name',
|
||||
showOnList: true,
|
||||
showOnDetail: true,
|
||||
showOnEdit: true,
|
||||
sortable: true,
|
||||
validationRules: [
|
||||
{ type: 'min', value: 2, message: 'First name must be at least 2 characters' },
|
||||
{ type: 'max', value: 50, message: 'First name cannot exceed 50 characters' }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
object_definition_id: contactObj.id,
|
||||
api_name: 'lastName',
|
||||
label: 'Last Name',
|
||||
type: 'text',
|
||||
is_required: true,
|
||||
is_system: false,
|
||||
is_custom: false,
|
||||
display_order: 2,
|
||||
ui_metadata: {
|
||||
placeholder: 'Enter last name',
|
||||
helpText: 'The contact\'s family name',
|
||||
showOnList: true,
|
||||
showOnDetail: true,
|
||||
showOnEdit: true,
|
||||
sortable: true,
|
||||
validationRules: [
|
||||
{ type: 'min', value: 2, message: 'Last name must be at least 2 characters' },
|
||||
{ type: 'max', value: 50, message: 'Last name cannot exceed 50 characters' }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
object_definition_id: contactObj.id,
|
||||
api_name: 'email',
|
||||
label: 'Email',
|
||||
type: 'email',
|
||||
is_required: true,
|
||||
is_unique: true,
|
||||
is_system: false,
|
||||
is_custom: false,
|
||||
display_order: 3,
|
||||
ui_metadata: {
|
||||
placeholder: 'email@example.com',
|
||||
helpText: 'Primary email address',
|
||||
showOnList: true,
|
||||
showOnDetail: true,
|
||||
showOnEdit: true,
|
||||
sortable: true,
|
||||
validationRules: [
|
||||
{ type: 'email', message: 'Please enter a valid email address' }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
object_definition_id: contactObj.id,
|
||||
api_name: 'phone',
|
||||
label: 'Phone',
|
||||
type: 'text',
|
||||
is_required: false,
|
||||
is_system: false,
|
||||
is_custom: false,
|
||||
display_order: 4,
|
||||
ui_metadata: {
|
||||
placeholder: '+1 (555) 000-0000',
|
||||
helpText: 'Primary phone number',
|
||||
showOnList: true,
|
||||
showOnDetail: true,
|
||||
showOnEdit: true,
|
||||
sortable: false,
|
||||
validationRules: [
|
||||
{ type: 'pattern', value: '^\\+?[0-9\\s\\-\\(\\)]+$', message: 'Please enter a valid phone number' }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
object_definition_id: contactObj.id,
|
||||
api_name: 'company',
|
||||
label: 'Company',
|
||||
type: 'text',
|
||||
is_required: false,
|
||||
is_system: false,
|
||||
is_custom: false,
|
||||
display_order: 5,
|
||||
ui_metadata: {
|
||||
placeholder: 'Company name',
|
||||
helpText: 'The organization this contact works for',
|
||||
showOnList: true,
|
||||
showOnDetail: true,
|
||||
showOnEdit: true,
|
||||
sortable: true
|
||||
}
|
||||
},
|
||||
{
|
||||
object_definition_id: contactObj.id,
|
||||
api_name: 'jobTitle',
|
||||
label: 'Job Title',
|
||||
type: 'text',
|
||||
is_required: false,
|
||||
is_system: false,
|
||||
is_custom: false,
|
||||
display_order: 6,
|
||||
ui_metadata: {
|
||||
placeholder: 'e.g., Senior Manager',
|
||||
showOnList: false,
|
||||
showOnDetail: true,
|
||||
showOnEdit: true,
|
||||
sortable: false
|
||||
}
|
||||
},
|
||||
{
|
||||
object_definition_id: contactObj.id,
|
||||
api_name: 'status',
|
||||
label: 'Status',
|
||||
type: 'picklist',
|
||||
is_required: true,
|
||||
is_system: false,
|
||||
is_custom: false,
|
||||
display_order: 7,
|
||||
default_value: 'active',
|
||||
ui_metadata: {
|
||||
showOnList: true,
|
||||
showOnDetail: true,
|
||||
showOnEdit: true,
|
||||
sortable: true,
|
||||
options: [
|
||||
{ label: 'Active', value: 'active' },
|
||||
{ label: 'Inactive', value: 'inactive' },
|
||||
{ label: 'Pending', value: 'pending' },
|
||||
{ label: 'Archived', value: 'archived' }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
object_definition_id: contactObj.id,
|
||||
api_name: 'leadSource',
|
||||
label: 'Lead Source',
|
||||
type: 'picklist',
|
||||
is_required: false,
|
||||
is_system: false,
|
||||
is_custom: false,
|
||||
display_order: 8,
|
||||
ui_metadata: {
|
||||
placeholder: 'Select lead source',
|
||||
showOnList: false,
|
||||
showOnDetail: true,
|
||||
showOnEdit: true,
|
||||
sortable: true,
|
||||
options: [
|
||||
{ label: 'Website', value: 'website' },
|
||||
{ label: 'Referral', value: 'referral' },
|
||||
{ label: 'Social Media', value: 'social' },
|
||||
{ label: 'Conference', value: 'conference' },
|
||||
{ label: 'Cold Call', value: 'cold_call' },
|
||||
{ label: 'Other', value: 'other' }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
object_definition_id: contactObj.id,
|
||||
api_name: 'isVip',
|
||||
label: 'VIP Customer',
|
||||
type: 'boolean',
|
||||
is_required: false,
|
||||
is_system: false,
|
||||
is_custom: false,
|
||||
display_order: 9,
|
||||
default_value: 'false',
|
||||
ui_metadata: {
|
||||
helpText: 'Mark as VIP for priority support',
|
||||
showOnList: true,
|
||||
showOnDetail: true,
|
||||
showOnEdit: true,
|
||||
sortable: true
|
||||
}
|
||||
},
|
||||
{
|
||||
object_definition_id: contactObj.id,
|
||||
api_name: 'birthDate',
|
||||
label: 'Birth Date',
|
||||
type: 'date',
|
||||
is_required: false,
|
||||
is_system: false,
|
||||
is_custom: false,
|
||||
display_order: 10,
|
||||
ui_metadata: {
|
||||
showOnList: false,
|
||||
showOnDetail: true,
|
||||
showOnEdit: true,
|
||||
sortable: true,
|
||||
format: 'yyyy-MM-dd'
|
||||
}
|
||||
},
|
||||
{
|
||||
object_definition_id: contactObj.id,
|
||||
api_name: 'website',
|
||||
label: 'Website',
|
||||
type: 'url',
|
||||
is_required: false,
|
||||
is_system: false,
|
||||
is_custom: false,
|
||||
display_order: 11,
|
||||
ui_metadata: {
|
||||
placeholder: 'https://example.com',
|
||||
showOnList: false,
|
||||
showOnDetail: true,
|
||||
showOnEdit: true,
|
||||
sortable: false,
|
||||
validationRules: [
|
||||
{ type: 'url', message: 'Please enter a valid URL starting with http:// or https://' }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
object_definition_id: contactObj.id,
|
||||
api_name: 'mailingAddress',
|
||||
label: 'Mailing Address',
|
||||
type: 'textarea',
|
||||
is_required: false,
|
||||
is_system: false,
|
||||
is_custom: false,
|
||||
display_order: 12,
|
||||
ui_metadata: {
|
||||
placeholder: 'Enter full mailing address',
|
||||
rows: 3,
|
||||
showOnList: false,
|
||||
showOnDetail: true,
|
||||
showOnEdit: true,
|
||||
sortable: false
|
||||
}
|
||||
},
|
||||
{
|
||||
object_definition_id: contactObj.id,
|
||||
api_name: 'notes',
|
||||
label: 'Notes',
|
||||
type: 'textarea',
|
||||
is_required: false,
|
||||
is_system: false,
|
||||
is_custom: false,
|
||||
display_order: 13,
|
||||
ui_metadata: {
|
||||
placeholder: 'Additional notes about this contact...',
|
||||
rows: 5,
|
||||
showOnList: false,
|
||||
showOnDetail: true,
|
||||
showOnEdit: true,
|
||||
sortable: false
|
||||
}
|
||||
},
|
||||
{
|
||||
object_definition_id: contactObj.id,
|
||||
api_name: 'annualRevenue',
|
||||
label: 'Annual Revenue',
|
||||
type: 'currency',
|
||||
is_required: false,
|
||||
is_system: false,
|
||||
is_custom: false,
|
||||
display_order: 14,
|
||||
ui_metadata: {
|
||||
prefix: '$',
|
||||
step: 0.01,
|
||||
min: 0,
|
||||
showOnList: false,
|
||||
showOnDetail: true,
|
||||
showOnEdit: true,
|
||||
sortable: true
|
||||
}
|
||||
},
|
||||
{
|
||||
object_definition_id: contactObj.id,
|
||||
api_name: 'numberOfEmployees',
|
||||
label: 'Number of Employees',
|
||||
type: 'integer',
|
||||
is_required: false,
|
||||
is_system: false,
|
||||
is_custom: false,
|
||||
display_order: 15,
|
||||
ui_metadata: {
|
||||
min: 1,
|
||||
step: 1,
|
||||
showOnList: false,
|
||||
showOnDetail: true,
|
||||
showOnEdit: true,
|
||||
sortable: true
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
// Insert or update fields
|
||||
for (const field of fields) {
|
||||
const existing = await knex('field_definitions')
|
||||
.where({
|
||||
object_definition_id: field.object_definition_id,
|
||||
api_name: field.api_name
|
||||
})
|
||||
.first();
|
||||
|
||||
if (existing) {
|
||||
await knex('field_definitions')
|
||||
.where({ id: existing.id })
|
||||
.update({
|
||||
...field,
|
||||
ui_metadata: JSON.stringify(field.ui_metadata),
|
||||
updated_at: knex.fn.now()
|
||||
});
|
||||
console.log(`Updated field: ${field.api_name}`);
|
||||
} else {
|
||||
await knex('field_definitions').insert({
|
||||
...field,
|
||||
ui_metadata: JSON.stringify(field.ui_metadata),
|
||||
created_at: knex.fn.now(),
|
||||
updated_at: knex.fn.now()
|
||||
});
|
||||
console.log(`Created field: ${field.api_name}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Contact fields seeded successfully!');
|
||||
};
|
||||
@@ -1,5 +1,49 @@
|
||||
import { BaseModel } from './base.model';
|
||||
|
||||
export interface FieldOption {
|
||||
label: string;
|
||||
value: string | number | boolean;
|
||||
}
|
||||
|
||||
export interface ValidationRule {
|
||||
type: 'required' | 'min' | 'max' | 'email' | 'url' | 'pattern' | 'custom';
|
||||
value?: any;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export interface UIMetadata {
|
||||
// Display properties
|
||||
placeholder?: string;
|
||||
helpText?: string;
|
||||
|
||||
// View visibility
|
||||
showOnList?: boolean;
|
||||
showOnDetail?: boolean;
|
||||
showOnEdit?: boolean;
|
||||
sortable?: boolean;
|
||||
|
||||
// Field type specific options
|
||||
options?: FieldOption[]; // For select, multi-select
|
||||
rows?: number; // For textarea
|
||||
min?: number; // For number, date
|
||||
max?: number; // For number, date
|
||||
step?: number; // For number
|
||||
accept?: string; // For file/image
|
||||
relationDisplayField?: string; // Which field to display for relations
|
||||
|
||||
// Formatting
|
||||
format?: string; // Date format, number format, etc.
|
||||
prefix?: string; // Currency symbol, etc.
|
||||
suffix?: string;
|
||||
|
||||
// Validation
|
||||
validationRules?: ValidationRule[];
|
||||
|
||||
// Advanced
|
||||
dependsOn?: string[]; // Field dependencies
|
||||
computedValue?: string; // Formula for computed fields
|
||||
}
|
||||
|
||||
export class FieldDefinition extends BaseModel {
|
||||
static tableName = 'field_definitions';
|
||||
|
||||
@@ -19,6 +63,7 @@ export class FieldDefinition extends BaseModel {
|
||||
isSystem!: boolean;
|
||||
isCustom!: boolean;
|
||||
displayOrder!: number;
|
||||
uiMetadata?: UIMetadata;
|
||||
|
||||
static relationMappings = {
|
||||
objectDefinition: {
|
||||
|
||||
295
backend/src/object/field-mapper.service.ts
Normal file
295
backend/src/object/field-mapper.service.ts
Normal file
@@ -0,0 +1,295 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { FieldDefinition } from '../models/field-definition.model';
|
||||
|
||||
export interface FieldConfigDTO {
|
||||
id: string;
|
||||
apiName: string;
|
||||
label: string;
|
||||
type: string;
|
||||
placeholder?: string;
|
||||
helpText?: string;
|
||||
defaultValue?: any;
|
||||
isRequired?: boolean;
|
||||
isReadOnly?: boolean;
|
||||
showOnList?: boolean;
|
||||
showOnDetail?: boolean;
|
||||
showOnEdit?: boolean;
|
||||
sortable?: boolean;
|
||||
options?: Array<{ label: string; value: any }>;
|
||||
rows?: number;
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
accept?: string;
|
||||
relationObject?: string;
|
||||
relationDisplayField?: string;
|
||||
format?: string;
|
||||
prefix?: string;
|
||||
suffix?: string;
|
||||
validationRules?: Array<{
|
||||
type: string;
|
||||
value?: any;
|
||||
message?: string;
|
||||
}>;
|
||||
dependsOn?: string[];
|
||||
computedValue?: string;
|
||||
}
|
||||
|
||||
export interface ObjectDefinitionDTO {
|
||||
id: string;
|
||||
apiName: string;
|
||||
label: string;
|
||||
pluralLabel?: string;
|
||||
description?: string;
|
||||
isSystem: boolean;
|
||||
fields: FieldConfigDTO[];
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class FieldMapperService {
|
||||
/**
|
||||
* Convert a field definition from the database to a frontend-friendly FieldConfig
|
||||
*/
|
||||
mapFieldToDTO(field: any): FieldConfigDTO {
|
||||
const uiMetadata = field.uiMetadata || {};
|
||||
|
||||
return {
|
||||
id: field.id,
|
||||
apiName: field.apiName,
|
||||
label: field.label,
|
||||
type: this.mapFieldType(field.type),
|
||||
|
||||
// Display properties
|
||||
placeholder: uiMetadata.placeholder || field.description,
|
||||
helpText: uiMetadata.helpText || field.description,
|
||||
defaultValue: field.defaultValue,
|
||||
|
||||
// Validation
|
||||
isRequired: field.isRequired || false,
|
||||
isReadOnly: field.isSystem || uiMetadata.isReadOnly || false,
|
||||
|
||||
// View visibility
|
||||
showOnList: uiMetadata.showOnList !== false,
|
||||
showOnDetail: uiMetadata.showOnDetail !== false,
|
||||
showOnEdit: uiMetadata.showOnEdit !== false && !field.isSystem,
|
||||
sortable: uiMetadata.sortable !== false,
|
||||
|
||||
// Field type specific options
|
||||
options: uiMetadata.options,
|
||||
rows: uiMetadata.rows,
|
||||
min: uiMetadata.min,
|
||||
max: uiMetadata.max,
|
||||
step: uiMetadata.step,
|
||||
accept: uiMetadata.accept,
|
||||
relationObject: field.referenceObject,
|
||||
relationDisplayField: uiMetadata.relationDisplayField,
|
||||
|
||||
// Formatting
|
||||
format: uiMetadata.format,
|
||||
prefix: uiMetadata.prefix,
|
||||
suffix: uiMetadata.suffix,
|
||||
|
||||
// Validation rules
|
||||
validationRules: this.buildValidationRules(field, uiMetadata),
|
||||
|
||||
// Advanced
|
||||
dependsOn: uiMetadata.dependsOn,
|
||||
computedValue: uiMetadata.computedValue,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Map database field type to frontend FieldType enum
|
||||
*/
|
||||
private mapFieldType(dbType: string): string {
|
||||
const typeMap: Record<string, string> = {
|
||||
'string': 'text',
|
||||
'text': 'textarea',
|
||||
'integer': 'number',
|
||||
'decimal': 'number',
|
||||
'boolean': 'boolean',
|
||||
'date': 'date',
|
||||
'datetime': 'datetime',
|
||||
'time': 'time',
|
||||
'email': 'email',
|
||||
'url': 'url',
|
||||
'phone': 'text',
|
||||
'picklist': 'select',
|
||||
'multipicklist': 'multiSelect',
|
||||
'lookup': 'belongsTo',
|
||||
'master-detail': 'belongsTo',
|
||||
'currency': 'currency',
|
||||
'percent': 'number',
|
||||
'textarea': 'textarea',
|
||||
'richtext': 'markdown',
|
||||
'file': 'file',
|
||||
'image': 'image',
|
||||
'json': 'json',
|
||||
};
|
||||
|
||||
return typeMap[dbType.toLowerCase()] || 'text';
|
||||
}
|
||||
|
||||
/**
|
||||
* Build validation rules array
|
||||
*/
|
||||
private buildValidationRules(field: any, uiMetadata: any): Array<any> {
|
||||
const rules = uiMetadata.validationRules || [];
|
||||
|
||||
// Add required rule if field is required and not already in rules
|
||||
if (field.isRequired && !rules.some(r => r.type === 'required')) {
|
||||
rules.unshift({
|
||||
type: 'required',
|
||||
message: `${field.label} is required`,
|
||||
});
|
||||
}
|
||||
|
||||
// Add length validation for string fields
|
||||
if (field.length && field.type === 'string') {
|
||||
rules.push({
|
||||
type: 'max',
|
||||
value: field.length,
|
||||
message: `${field.label} must not exceed ${field.length} characters`,
|
||||
});
|
||||
}
|
||||
|
||||
// Add email validation
|
||||
if (field.type === 'email' && !rules.some(r => r.type === 'email')) {
|
||||
rules.push({
|
||||
type: 'email',
|
||||
message: `${field.label} must be a valid email address`,
|
||||
});
|
||||
}
|
||||
|
||||
// Add URL validation
|
||||
if (field.type === 'url' && !rules.some(r => r.type === 'url')) {
|
||||
rules.push({
|
||||
type: 'url',
|
||||
message: `${field.label} must be a valid URL`,
|
||||
});
|
||||
}
|
||||
|
||||
return rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert object definition with fields to DTO
|
||||
*/
|
||||
mapObjectDefinitionToDTO(objectDef: any): ObjectDefinitionDTO {
|
||||
return {
|
||||
id: objectDef.id,
|
||||
apiName: objectDef.apiName,
|
||||
label: objectDef.label,
|
||||
pluralLabel: objectDef.pluralLabel,
|
||||
description: objectDef.description,
|
||||
isSystem: objectDef.isSystem || false,
|
||||
fields: (objectDef.fields || [])
|
||||
.filter((f: any) => f.isActive !== false)
|
||||
.sort((a: any, b: any) => (a.displayOrder || 0) - (b.displayOrder || 0))
|
||||
.map((f: any) => this.mapFieldToDTO(f)),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate default UI metadata for a field type
|
||||
*/
|
||||
generateDefaultUIMetadata(fieldType: string): any {
|
||||
const defaults: Record<string, any> = {
|
||||
text: {
|
||||
showOnList: true,
|
||||
showOnDetail: true,
|
||||
showOnEdit: true,
|
||||
sortable: true,
|
||||
},
|
||||
textarea: {
|
||||
showOnList: false,
|
||||
showOnDetail: true,
|
||||
showOnEdit: true,
|
||||
sortable: false,
|
||||
rows: 4,
|
||||
},
|
||||
number: {
|
||||
showOnList: true,
|
||||
showOnDetail: true,
|
||||
showOnEdit: true,
|
||||
sortable: true,
|
||||
},
|
||||
currency: {
|
||||
showOnList: true,
|
||||
showOnDetail: true,
|
||||
showOnEdit: true,
|
||||
sortable: true,
|
||||
prefix: '$',
|
||||
step: 0.01,
|
||||
},
|
||||
boolean: {
|
||||
showOnList: true,
|
||||
showOnDetail: true,
|
||||
showOnEdit: true,
|
||||
sortable: true,
|
||||
},
|
||||
date: {
|
||||
showOnList: true,
|
||||
showOnDetail: true,
|
||||
showOnEdit: true,
|
||||
sortable: true,
|
||||
format: 'yyyy-MM-dd',
|
||||
},
|
||||
datetime: {
|
||||
showOnList: false,
|
||||
showOnDetail: true,
|
||||
showOnEdit: true,
|
||||
sortable: true,
|
||||
format: 'yyyy-MM-dd HH:mm:ss',
|
||||
},
|
||||
email: {
|
||||
showOnList: true,
|
||||
showOnDetail: true,
|
||||
showOnEdit: true,
|
||||
sortable: true,
|
||||
validationRules: [{ type: 'email' }],
|
||||
},
|
||||
url: {
|
||||
showOnList: false,
|
||||
showOnDetail: true,
|
||||
showOnEdit: true,
|
||||
sortable: false,
|
||||
validationRules: [{ type: 'url' }],
|
||||
},
|
||||
select: {
|
||||
showOnList: true,
|
||||
showOnDetail: true,
|
||||
showOnEdit: true,
|
||||
sortable: true,
|
||||
options: [],
|
||||
},
|
||||
multiSelect: {
|
||||
showOnList: false,
|
||||
showOnDetail: true,
|
||||
showOnEdit: true,
|
||||
sortable: false,
|
||||
options: [],
|
||||
},
|
||||
image: {
|
||||
showOnList: false,
|
||||
showOnDetail: true,
|
||||
showOnEdit: true,
|
||||
sortable: false,
|
||||
accept: 'image/*',
|
||||
},
|
||||
file: {
|
||||
showOnList: false,
|
||||
showOnDetail: true,
|
||||
showOnEdit: true,
|
||||
sortable: false,
|
||||
},
|
||||
};
|
||||
|
||||
return defaults[fieldType] || {
|
||||
showOnList: true,
|
||||
showOnDetail: true,
|
||||
showOnEdit: true,
|
||||
sortable: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,13 @@ import { ObjectService } from './object.service';
|
||||
import { RuntimeObjectController } from './runtime-object.controller';
|
||||
import { SetupObjectController } from './setup-object.controller';
|
||||
import { SchemaManagementService } from './schema-management.service';
|
||||
import { FieldMapperService } from './field-mapper.service';
|
||||
import { TenantModule } from '../tenant/tenant.module';
|
||||
|
||||
@Module({
|
||||
imports: [TenantModule],
|
||||
providers: [ObjectService, SchemaManagementService],
|
||||
providers: [ObjectService, SchemaManagementService, FieldMapperService],
|
||||
controllers: [RuntimeObjectController, SetupObjectController],
|
||||
exports: [ObjectService, SchemaManagementService],
|
||||
exports: [ObjectService, SchemaManagementService, FieldMapperService],
|
||||
})
|
||||
export class ObjectModule {}
|
||||
|
||||
@@ -7,13 +7,17 @@ import {
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { ObjectService } from './object.service';
|
||||
import { FieldMapperService } from './field-mapper.service';
|
||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||
import { TenantId } from '../tenant/tenant.decorator';
|
||||
|
||||
@Controller('setup/objects')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
export class SetupObjectController {
|
||||
constructor(private objectService: ObjectService) {}
|
||||
constructor(
|
||||
private objectService: ObjectService,
|
||||
private fieldMapperService: FieldMapperService,
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
async getObjectDefinitions(@TenantId() tenantId: string) {
|
||||
@@ -28,6 +32,18 @@ export class SetupObjectController {
|
||||
return this.objectService.getObjectDefinition(tenantId, objectApiName);
|
||||
}
|
||||
|
||||
@Get(':objectApiName/ui-config')
|
||||
async getObjectUIConfig(
|
||||
@TenantId() tenantId: string,
|
||||
@Param('objectApiName') objectApiName: string,
|
||||
) {
|
||||
const objectDef = await this.objectService.getObjectDefinition(
|
||||
tenantId,
|
||||
objectApiName,
|
||||
);
|
||||
return this.fieldMapperService.mapObjectDefinitionToDTO(objectDef);
|
||||
}
|
||||
|
||||
@Post()
|
||||
async createObjectDefinition(
|
||||
@TenantId() tenantId: string,
|
||||
|
||||
Reference in New Issue
Block a user