mirror of
https://github.com/johndoe6345789/postgres.git
synced 2026-04-24 13:55:00 +00:00
12 KiB
12 KiB
Building Apps with features.json
With a good enough features.json, you could build half the app with it!
This example demonstrates how the enhanced configuration system enables declarative application building.
Complete CRUD Interface Generator
import {
getFormSchema,
getTableLayout,
getTableFeatures,
getColumnLayout,
getColumnFeatures,
getColumnTranslation,
getActionTranslation,
getApiEndpoints,
getPermissions,
getRelationships,
getUiViews,
hasPermission,
} from '@/utils/featureConfig';
/**
* Generates a complete CRUD interface from configuration
* This demonstrates how features.json can drive application generation
*/
export function generateCRUDInterface(
resourceName: string,
locale: 'en' | 'fr' = 'en',
userRole: string = 'user'
) {
// Get all configurations
const formSchema = getFormSchema(resourceName);
const tableLayout = getTableLayout(resourceName);
const tableFeatures = getTableFeatures(resourceName);
const apiEndpoints = getApiEndpoints(resourceName);
const permissions = getPermissions(resourceName);
const relationships = getRelationships(resourceName);
const uiViews = getUiViews(resourceName);
// Build column definitions
const columns = tableLayout?.columns.map(columnName => {
const layout = getColumnLayout(columnName);
const features = getColumnFeatures(columnName);
const label = getColumnTranslation(columnName, locale) || columnName;
return {
field: columnName,
label,
width: tableLayout.columnWidths[columnName],
align: layout?.align || 'left',
format: layout?.format || 'text',
editable: layout?.editable ?? true,
sortable: features?.sortable ?? true,
filterable: features?.filterable ?? true,
searchable: features?.searchable ?? true,
hidden: tableLayout.hiddenColumns?.includes(columnName) ?? false,
frozen: tableLayout.frozenColumns?.includes(columnName) ?? false,
};
});
// Build action buttons with permission checks
const actions = tableFeatures?.allowedActions
.filter(action => hasPermission(resourceName, action, userRole))
.map(action => ({
name: action,
label: getActionTranslation(action, locale),
endpoint: apiEndpoints?.[action],
permitted: true,
}));
// Build form configuration
const form = formSchema ? {
fields: formSchema.fields.map(field => ({
...field,
label: getColumnTranslation(field.name, locale) || field.label,
})),
submitLabel: formSchema.submitLabel,
cancelLabel: formSchema.cancelLabel,
} : null;
// Build complete interface configuration
return {
resource: resourceName,
locale,
userRole,
// List view
list: {
component: uiViews?.list?.component || 'DataGrid',
columns,
actions: actions?.filter(a => a.name === 'create'),
features: {
pagination: tableFeatures?.enablePagination ?? true,
search: tableFeatures?.enableSearch ?? true,
filters: tableFeatures?.enableFilters ?? true,
export: tableFeatures?.enableExport ?? false,
rowsPerPage: tableFeatures?.rowsPerPage || 25,
},
sorting: tableLayout?.defaultSort,
api: apiEndpoints?.list,
},
// Detail view
detail: {
component: uiViews?.detail?.component || 'DetailView',
columns,
actions: actions?.filter(a => ['update', 'delete'].includes(a.name)),
relationships: relationships,
tabs: uiViews?.detail?.tabs || ['info'],
api: apiEndpoints?.get,
},
// Create form
create: {
component: uiViews?.create?.component || 'FormDialog',
form,
api: apiEndpoints?.create,
redirect: uiViews?.create?.redirect || 'list',
enabled: hasPermission(resourceName, 'create', userRole),
},
// Edit form
edit: {
component: uiViews?.edit?.component || 'FormDialog',
form,
api: apiEndpoints?.update,
redirect: uiViews?.edit?.redirect || 'detail',
enabled: hasPermission(resourceName, 'update', userRole),
},
// Delete confirmation
delete: {
component: 'ConfirmDialog',
api: apiEndpoints?.delete,
enabled: hasPermission(resourceName, 'delete', userRole),
},
permissions,
relationships,
};
}
// Usage example
const usersInterface = generateCRUDInterface('users', 'en', 'admin');
console.log(usersInterface);
Auto-Generated Form Component
import { getFormSchema, getValidationRule } from '@/utils/featureConfig';
export function renderForm(resourceName: string) {
const schema = getFormSchema(resourceName);
if (!schema) return null;
return schema.fields.map(field => {
const validationRule = field.validation
? getValidationRule(field.validation)
: null;
return {
name: field.name,
type: field.type,
label: field.label,
placeholder: field.placeholder,
required: field.required,
validation: validationRule,
// Field-specific props
...(field.type === 'select' && { options: field.options }),
...(field.type === 'number' && {
min: field.min,
max: field.max,
step: field.step,
prefix: field.prefix,
suffix: field.suffix,
}),
...(field.type === 'text' && {
minLength: field.minLength,
maxLength: field.maxLength,
}),
...(field.type === 'textarea' && { rows: field.rows }),
...(field.type === 'checkbox' && { defaultValue: field.defaultValue }),
};
});
}
Auto-Generated API Routes
import { getApiEndpoint } from '@/utils/featureConfig';
export function makeApiCall(
resourceName: string,
action: string,
data?: any,
params?: Record<string, string>
) {
const endpoint = getApiEndpoint(resourceName, action);
if (!endpoint) {
throw new Error(`Endpoint not found: ${resourceName}.${action}`);
}
// Replace path parameters
let path = endpoint.path;
if (params) {
Object.entries(params).forEach(([key, value]) => {
path = path.replace(`:${key}`, value);
});
}
// Make the API call
return fetch(path, {
method: endpoint.method,
headers: {
'Content-Type': 'application/json',
},
...(data && { body: JSON.stringify(data) }),
});
}
// Usage
await makeApiCall('users', 'list');
await makeApiCall('users', 'get', null, { id: '123' });
await makeApiCall('users', 'create', { name: 'John', email: 'john@example.com' });
await makeApiCall('users', 'update', { name: 'Jane' }, { id: '123' });
await makeApiCall('users', 'delete', null, { id: '123' });
Permission-Based UI Rendering
import { hasPermission, getPermissions } from '@/utils/featureConfig';
export function renderResourceActions(
resourceName: string,
userRole: string
) {
const permissions = getPermissions(resourceName);
const actions = [
{
name: 'create',
label: 'Create New',
icon: 'Add',
visible: hasPermission(resourceName, 'create', userRole),
},
{
name: 'update',
label: 'Edit',
icon: 'Edit',
visible: hasPermission(resourceName, 'update', userRole),
},
{
name: 'delete',
label: 'Delete',
icon: 'Delete',
visible: hasPermission(resourceName, 'delete', userRole),
},
];
return actions.filter(action => action.visible);
}
// Usage in React component
function UsersList({ userRole }: { userRole: string }) {
const actions = renderResourceActions('users', userRole);
return (
<div>
{actions.map(action => (
<Button key={action.name} startIcon={<Icon>{action.icon}</Icon>}>
{action.label}
</Button>
))}
</div>
);
}
Relationship-Based Data Loading
import { getRelationships, getApiEndpoint } from '@/utils/featureConfig';
export async function loadResourceWithRelations(
resourceName: string,
resourceId: string
) {
const relationships = getRelationships(resourceName);
const endpoint = getApiEndpoint(resourceName, 'get');
// Load main resource
const mainData = await fetch(
endpoint!.path.replace(':id', resourceId)
).then(r => r.json());
// Load related resources
const relatedData: Record<string, any> = {};
if (relationships?.hasMany) {
for (const relation of relationships.hasMany) {
const relationEndpoint = getApiEndpoint(relation, 'list');
if (relationEndpoint) {
relatedData[relation] = await fetch(
`${relationEndpoint.path}?${resourceName}_id=${resourceId}`
).then(r => r.json());
}
}
}
if (relationships?.belongsTo) {
for (const relation of relationships.belongsTo) {
const relationId = mainData[`${relation}_id`];
if (relationId) {
const relationEndpoint = getApiEndpoint(relation, 'get');
if (relationEndpoint) {
relatedData[relation] = await fetch(
relationEndpoint.path.replace(':id', relationId)
).then(r => r.json());
}
}
}
}
return {
...mainData,
_relations: relatedData,
};
}
// Usage
const userWithRelations = await loadResourceWithRelations('users', '123');
// Returns: { id: 123, name: 'John', _relations: { orders: [...], reviews: [...] } }
Complete Page Generator
import { generateCRUDInterface } from './crudGenerator';
/**
* Generates an entire CRUD page from configuration
* This is the ultimate example of configuration-driven development
*/
export function generateResourcePage(
resourceName: string,
locale: 'en' | 'fr',
userRole: string
) {
const config = generateCRUDInterface(resourceName, locale, userRole);
return {
// Page metadata
title: `${resourceName.charAt(0).toUpperCase() + resourceName.slice(1)} Management`,
breadcrumbs: ['Home', 'Admin', resourceName],
// Layout
layout: 'AdminLayout',
// Components to render
components: [
{
type: config.list.component,
props: {
columns: config.list.columns,
api: config.list.api,
features: config.list.features,
actions: config.list.actions,
sorting: config.list.sorting,
},
},
config.create.enabled && {
type: config.create.component,
props: {
fields: config.create.form?.fields,
submitLabel: config.create.form?.submitLabel,
cancelLabel: config.create.form?.cancelLabel,
api: config.create.api,
redirect: config.create.redirect,
},
},
config.edit.enabled && {
type: config.edit.component,
props: {
fields: config.edit.form?.fields,
api: config.edit.api,
redirect: config.edit.redirect,
},
},
config.delete.enabled && {
type: config.delete.component,
props: {
api: config.delete.api,
},
},
].filter(Boolean),
// Data loading
dataLoader: async () => {
const response = await fetch(config.list.api!.path);
return response.json();
},
// Permissions
requiredRole: userRole,
permissions: config.permissions,
};
}
// Generate entire pages from configuration
const usersPage = generateResourcePage('users', 'en', 'admin');
const productsPage = generateResourcePage('products', 'fr', 'editor');
Benefits of Configuration-Driven Architecture
- Rapid Development: Add new resources by just updating JSON
- Consistency: All CRUD interfaces follow the same patterns
- Maintainability: Changes to one config affect all resources
- Type Safety: TypeScript types ensure config validity
- Testability: Easy to test configuration vs. hardcoded logic
- Internationalization: Built-in translation support
- Permission Management: Centralized access control
- API Documentation: Config serves as API documentation
- UI Generation: Automatic form and table generation
- Flexibility: Override defaults when needed
What You Can Build from features.json
- ✅ Complete CRUD interfaces
- ✅ Forms with validation
- ✅ Data tables with sorting, filtering, pagination
- ✅ API routes and endpoints
- ✅ Permission-based UI
- ✅ Relationship loading
- ✅ Multi-language support
- ✅ Navigation menus
- ✅ Admin panels
- ✅ Resource management pages
Truly, with a good features.json, you can build half the app!