Add record access strategy
This commit is contained in:
390
docs/PAGE_LAYOUTS_ARCHITECTURE.md
Normal file
390
docs/PAGE_LAYOUTS_ARCHITECTURE.md
Normal file
@@ -0,0 +1,390 @@
|
||||
# Page Layouts Architecture Diagram
|
||||
|
||||
## System Overview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ FRONTEND (Vue 3 + Nuxt) │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌───────────────────────────────────────────────────────────┐ │
|
||||
│ │ Setup → Objects → [Object] → Layouts Tab │ │
|
||||
│ ├───────────────────────────────────────────────────────────┤ │
|
||||
│ │ │ │
|
||||
│ │ ┌─────────────┐ ┌───────────────────────────────┐ │ │
|
||||
│ │ │ Layouts │ │ PageLayoutEditor │ │ │
|
||||
│ │ │ List │ --> │ ┌─────────────────────────┐ │ │ │
|
||||
│ │ │ │ │ │ 6-Column Grid │ │ │ │
|
||||
│ │ │ • Standard │ │ │ ┌───┬───┬───┬───┬───┐ │ │ │ │
|
||||
│ │ │ • Compact │ │ │ │ F │ F │ F │ F │ F │ │ │ │ │
|
||||
│ │ │ • Detailed │ │ │ ├───┴───┴───┴───┴───┤ │ │ │ │
|
||||
│ │ │ │ │ │ │ Field 1 (w:5) │ │ │ │ │
|
||||
│ │ │ [+ New] │ │ │ └─────────────────── │ │ │ │ │
|
||||
│ │ └─────────────┘ │ └─────────────────────────┘ │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ Sidebar: │ │ │
|
||||
│ │ │ ┌─────────────────────────┐ │ │ │
|
||||
│ │ │ │ Available Fields │ │ │ │
|
||||
│ │ │ │ □ Email │ │ │ │
|
||||
│ │ │ │ □ Phone │ │ │ │
|
||||
│ │ │ │ □ Status │ │ │ │
|
||||
│ │ │ └─────────────────────────┘ │ │ │
|
||||
│ │ └───────────────────────────────┘ │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ Record Detail/Edit Views │ │
|
||||
│ ├───────────────────────────────────────────────────────┤ │
|
||||
│ │ │ │
|
||||
│ │ DetailViewEnhanced / EditViewEnhanced │ │
|
||||
│ │ ↓ │ │
|
||||
│ │ ┌─────────────────────────────────────────────────┐ │ │
|
||||
│ │ │ PageLayoutRenderer │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ Fetches default layout for object │ │ │
|
||||
│ │ │ Renders fields in custom grid positions │ │ │
|
||||
│ │ │ Fallback to 2-column if no layout │ │ │
|
||||
│ │ └─────────────────────────────────────────────────┘ │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ Composables (usePageLayouts) │ │
|
||||
│ ├───────────────────────────────────────────────────────┤ │
|
||||
│ │ • getPageLayouts() • createPageLayout() │ │
|
||||
│ │ • getPageLayout() • updatePageLayout() │ │
|
||||
│ │ • getDefaultPageLayout()• deletePageLayout() │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↕ HTTP REST API
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ BACKEND (NestJS) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ PageLayoutController (API Layer) │ │
|
||||
│ ├───────────────────────────────────────────────────────┤ │
|
||||
│ │ POST /page-layouts │ │
|
||||
│ │ GET /page-layouts?objectId={id} │ │
|
||||
│ │ GET /page-layouts/:id │ │
|
||||
│ │ GET /page-layouts/default/:objectId │ │
|
||||
│ │ PATCH /page-layouts/:id │ │
|
||||
│ │ DELETE /page-layouts/:id │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
│ ↕ │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ PageLayoutService (Business Logic) │ │
|
||||
│ ├───────────────────────────────────────────────────────┤ │
|
||||
│ │ • Tenant isolation │ │
|
||||
│ │ • Default layout management │ │
|
||||
│ │ • CRUD operations │ │
|
||||
│ │ • Validation │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
│ ↕ │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ PrismaService (Data Layer) │ │
|
||||
│ ├───────────────────────────────────────────────────────┤ │
|
||||
│ │ • Raw SQL queries │ │
|
||||
│ │ • Tenant database routing │ │
|
||||
│ │ • Transaction management │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↕
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ DATABASE (PostgreSQL) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ Table: page_layouts │ │
|
||||
│ ├───────────────────────────────────────────────────────┤ │
|
||||
│ │ id UUID PRIMARY KEY │ │
|
||||
│ │ name VARCHAR(255) │ │
|
||||
│ │ object_id UUID → object_definitions(id) │ │
|
||||
│ │ is_default BOOLEAN │ │
|
||||
│ │ layout_config JSONB │ │
|
||||
│ │ description TEXT │ │
|
||||
│ │ created_at TIMESTAMP │ │
|
||||
│ │ updated_at TIMESTAMP │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Example layout_config JSONB: │
|
||||
│ { │
|
||||
│ "fields": [ │
|
||||
│ { │
|
||||
│ "fieldId": "uuid-123", │
|
||||
│ "x": 0, // Column start (0-5) │
|
||||
│ "y": 0, // Row start │
|
||||
│ "w": 3, // Width (1-6 columns) │
|
||||
│ "h": 1 // Height (fixed at 1) │
|
||||
│ } │
|
||||
│ ] │
|
||||
│ } │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Data Flow Diagrams
|
||||
|
||||
### Creating a Layout
|
||||
|
||||
```
|
||||
Admin User
|
||||
│
|
||||
├─→ Navigates to Setup → Objects → [Object] → Page Layouts
|
||||
│
|
||||
├─→ Clicks "New Layout"
|
||||
│
|
||||
├─→ Enters layout name
|
||||
│
|
||||
├─→ PageLayoutEditor mounts
|
||||
│ │
|
||||
│ ├─→ Loads object fields
|
||||
│ ├─→ Initializes GridStack with 6 columns
|
||||
│ └─→ Shows available fields in sidebar
|
||||
│
|
||||
├─→ Drags fields from sidebar to grid
|
||||
│ │
|
||||
│ ├─→ GridStack handles positioning
|
||||
│ ├─→ User resizes field width (1-6 columns)
|
||||
│ └─→ User arranges fields
|
||||
│
|
||||
├─→ Clicks "Save Layout"
|
||||
│
|
||||
├─→ usePageLayouts.createPageLayout()
|
||||
│ │
|
||||
│ └─→ POST /page-layouts
|
||||
│ │
|
||||
│ └─→ PageLayoutController.create()
|
||||
│ │
|
||||
│ └─→ PageLayoutService.create()
|
||||
│ │
|
||||
│ ├─→ If is_default, unset others
|
||||
│ └─→ INSERT INTO page_layouts
|
||||
│
|
||||
└─→ Layout saved ✓
|
||||
```
|
||||
|
||||
### Rendering a Layout in Detail View
|
||||
|
||||
```
|
||||
User Opens Record
|
||||
│
|
||||
├─→ Navigates to /[object]/[id]/detail
|
||||
│
|
||||
├─→ DetailViewEnhanced mounts
|
||||
│ │
|
||||
│ └─→ onMounted() hook
|
||||
│ │
|
||||
│ └─→ usePageLayouts.getDefaultPageLayout(objectId)
|
||||
│ │
|
||||
│ └─→ GET /page-layouts/default/:objectId
|
||||
│ │
|
||||
│ └─→ PageLayoutService.findDefaultByObject()
|
||||
│ │
|
||||
│ └─→ SELECT * FROM page_layouts
|
||||
│ WHERE object_id = $1
|
||||
│ AND is_default = true
|
||||
│
|
||||
├─→ Layout received
|
||||
│ │
|
||||
│ ├─→ If layout exists:
|
||||
│ │ │
|
||||
│ │ └─→ PageLayoutRenderer renders with layout
|
||||
│ │ │
|
||||
│ │ ├─→ Creates CSS Grid (6 columns)
|
||||
│ │ ├─→ Positions fields based on x, y, w, h
|
||||
│ │ └─→ Renders FieldRenderer for each field
|
||||
│ │
|
||||
│ └─→ If no layout:
|
||||
│ │
|
||||
│ └─→ Falls back to 2-column layout
|
||||
│
|
||||
└─→ Record displayed with custom layout ✓
|
||||
```
|
||||
|
||||
## Grid Layout System
|
||||
|
||||
### 6-Column Grid Structure
|
||||
|
||||
```
|
||||
┌──────┬──────┬──────┬──────┬──────┬──────┐
|
||||
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ ← Column indices
|
||||
└──────┴──────┴──────┴──────┴──────┴──────┘
|
||||
Each column = 16.67% of container width
|
||||
```
|
||||
|
||||
### Example Layouts
|
||||
|
||||
#### Two-Column Layout (Default)
|
||||
```
|
||||
┌─────────────────────┬─────────────────────┐
|
||||
│ Name (w:3) │ Email (w:3) │
|
||||
├─────────────────────┼─────────────────────┤
|
||||
│ Phone (w:3) │ Company (w:3) │
|
||||
├─────────────────────┴─────────────────────┤
|
||||
│ Description (w:6) │
|
||||
└───────────────────────────────────────────┘
|
||||
|
||||
Field configs:
|
||||
- Name: {x:0, y:0, w:3, h:1}
|
||||
- Email: {x:3, y:0, w:3, h:1}
|
||||
- Phone: {x:0, y:1, w:3, h:1}
|
||||
- Company: {x:3, y:1, w:3, h:1}
|
||||
- Description: {x:0, y:2, w:6, h:1}
|
||||
```
|
||||
|
||||
#### Three-Column Layout
|
||||
```
|
||||
┌───────────┬───────────┬───────────┐
|
||||
│ F1 (w:2) │ F2 (w:2) │ F3 (w:2) │
|
||||
├───────────┴───────────┴───────────┤
|
||||
│ F4 (w:6) │
|
||||
└───────────────────────────────────┘
|
||||
|
||||
Field configs:
|
||||
- F1: {x:0, y:0, w:2, h:1}
|
||||
- F2: {x:2, y:0, w:2, h:1}
|
||||
- F3: {x:4, y:0, w:2, h:1}
|
||||
- F4: {x:0, y:1, w:6, h:1}
|
||||
```
|
||||
|
||||
#### Mixed Width Layout
|
||||
```
|
||||
┌───────────────┬───────┬───────────┐
|
||||
│ Title (w:3) │ ID(1) │ Type (w:2)│
|
||||
├───────────────┴───────┴───────────┤
|
||||
│ Address (w:6) │
|
||||
├──────────┬────────────────────────┤
|
||||
│ City(2) │ State/ZIP (w:4) │
|
||||
└──────────┴────────────────────────┘
|
||||
|
||||
Field configs:
|
||||
- Title: {x:0, y:0, w:3, h:1}
|
||||
- ID: {x:3, y:0, w:1, h:1}
|
||||
- Type: {x:4, y:0, w:2, h:1}
|
||||
- Address: {x:0, y:1, w:6, h:1}
|
||||
- City: {x:0, y:2, w:2, h:1}
|
||||
- State: {x:2, y:2, w:4, h:1}
|
||||
```
|
||||
|
||||
## Component Hierarchy
|
||||
|
||||
```
|
||||
App.vue
|
||||
│
|
||||
└─→ NuxtLayout (default)
|
||||
│
|
||||
├─→ Setup Pages
|
||||
│ │
|
||||
│ └─→ pages/setup/objects/[apiName].vue
|
||||
│ │
|
||||
│ └─→ Tabs Component
|
||||
│ │
|
||||
│ ├─→ Tab: Fields (existing)
|
||||
│ │
|
||||
│ └─→ Tab: Page Layouts
|
||||
│ │
|
||||
│ ├─→ Layout List View
|
||||
│ │ └─→ Card per layout
|
||||
│ │
|
||||
│ └─→ Layout Editor View
|
||||
│ │
|
||||
│ └─→ PageLayoutEditor
|
||||
│ │
|
||||
│ ├─→ GridStack (6 columns)
|
||||
│ │ └─→ Field items
|
||||
│ │
|
||||
│ └─→ Sidebar
|
||||
│ └─→ Available fields
|
||||
│
|
||||
└─→ Record Pages
|
||||
│
|
||||
└─→ pages/[objectName]/[[recordId]]/[[view]].vue
|
||||
│
|
||||
├─→ DetailViewEnhanced
|
||||
│ │
|
||||
│ └─→ PageLayoutRenderer
|
||||
│ └─→ FieldRenderer (per field)
|
||||
│
|
||||
└─→ EditViewEnhanced
|
||||
│
|
||||
└─→ PageLayoutRenderer
|
||||
└─→ FieldRenderer (per field)
|
||||
```
|
||||
|
||||
## State Management
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ Component State (ref/reactive) │
|
||||
├─────────────────────────────────────┤
|
||||
│ • selectedLayout │
|
||||
│ • layouts[] │
|
||||
│ • loadingLayouts │
|
||||
│ • pageLayout (current) │
|
||||
│ • formData │
|
||||
│ • gridItems │
|
||||
│ • placedFieldIds │
|
||||
└─────────────────────────────────────┘
|
||||
↕
|
||||
┌─────────────────────────────────────┐
|
||||
│ Composables (Reactive) │
|
||||
├─────────────────────────────────────┤
|
||||
│ • usePageLayouts() - API calls │
|
||||
│ • useApi() - HTTP client │
|
||||
│ • useAuth() - Authentication │
|
||||
│ • useToast() - Notifications │
|
||||
└─────────────────────────────────────┘
|
||||
↕
|
||||
┌─────────────────────────────────────┐
|
||||
│ Browser Storage │
|
||||
├─────────────────────────────────────┤
|
||||
│ • localStorage: token, tenantId │
|
||||
│ • SessionStorage: (none yet) │
|
||||
│ • Cookies: (managed by server) │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Security Flow
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────┐
|
||||
│ 1. User Login │
|
||||
│ → Receives JWT token │
|
||||
│ → Token stored in localStorage │
|
||||
└────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌────────────────────────────────────────────────┐
|
||||
│ 2. API Request │
|
||||
│ → useApi() adds Authorization header │
|
||||
│ → useApi() adds x-tenant-id header │
|
||||
└────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌────────────────────────────────────────────────┐
|
||||
│ 3. Backend Validation │
|
||||
│ → JwtAuthGuard validates token │
|
||||
│ → Extracts user info (userId, tenantId) │
|
||||
│ → Attaches to request object │
|
||||
└────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌────────────────────────────────────────────────┐
|
||||
│ 4. Service Layer │
|
||||
│ → Receives tenantId from request │
|
||||
│ → All queries scoped to tenant │
|
||||
│ → Tenant isolation enforced │
|
||||
└────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌────────────────────────────────────────────────┐
|
||||
│ 5. Database │
|
||||
│ → Tenant-specific database selected │
|
||||
│ → Query executed in tenant context │
|
||||
│ → Results returned │
|
||||
└────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Legend:**
|
||||
- `→` : Data flow direction
|
||||
- `↕` : Bidirectional communication
|
||||
- `├─→` : Hierarchical relationship
|
||||
- `└─→` : Terminal branch
|
||||
- `✓` : Successful operation
|
||||
Reference in New Issue
Block a user