Architecture — how annotations work
Data flow
JPA class + Portal annotations
│
▼
MetadataService (startup)
│ reads annotations via reflection
▼
JSON → /api/portal/metadata
│
▼
Frontend (Next.js)
│ dynamically generates: tables, forms, filters, actions
▼
Full CRUD interface — zero hand-written React componentsHow it works
- Every JPA entity annotated with
@PortalEntityis registered inPortalModuleConfig. - At backend startup,
MetadataServicescans all registered classes and builds aPortalMetadataJSON object. - The frontend fetches the JSON from
/api/portal/metadataand dynamically renders the entire UI. - No per-entity React components are needed.
Key components
| Component | Description |
|---|---|
@PortalEntity | Registers a JPA class with the portal — label, module, icon, tabs, permissions |
@PortalField | Declares a UI field — renderer, filter, validation, visibility |
@PortalRelation + @PortalLookup | Configures ManyToOne / OneToMany relations and pickers |
@PortalDependency | Conditional visibility and range rules for fields |
@PortalAction | Custom action buttons backed by CDI handlers |
@PortalSecurity | JWT role-based access control |
PortalModuleConfig | Registers modules and entities — sidebar navigation config |
Metadata endpoint
GET /api/portal/metadataReturns a full JSON describing all entities, fields, relations, actions, and UI config. Supports ETag/304 — the frontend only re-fetches when metadata changes.
Sample response (abbreviated)
{
"modules": [
{
"name": "CRM",
"label": "CRM",
"icon": "users",
"entities": [
{
"name": "Customer",
"label": "Customer",
"fields": [
{
"name": "name",
"label": "Name",
"renderer": "TEXT",
"filterType": "CONTAINS",
"required": true
}
],
"actions": [],
"security": { "viewRoles": [], "editRoles": [] }
}
]
}
],
"ui": {
"title": "Quatrion Portal",
"theme": { "primaryColor": "#2563eb" }
}
}Metadata is built once at startup. Changes to annotations require restarting the backend.
UI rendering flow
When a user opens the Customer entity:
- Frontend checks metadata cache (or fetches
/api/portal/metadata) - Table — renders columns based on
@PortalField.showInTable - Form — renders fields based on
@PortalField.rendererand@PortalField.tab - Filters — renders the filter panel based on
@PortalField.filterType - Actions — renders buttons based on
@PortalAction
All CRUD operations are performed through standardised REST endpoints (/api/portal/data/{entityName}).
Last updated on