357 lines
12 KiB
Markdown
357 lines
12 KiB
Markdown
# App Data Flow Documentation
|
|
|
|
This document explains the complete data flow architecture of the Enterprise Asset Management application, using the Assets view as an example.
|
|
|
|
## 📊 Data Flow Architecture Overview
|
|
|
|
```
|
|
Assets View → Assets Store → Asset Repository → Base Repository → Directus API → Database
|
|
↓ ↓ ↓ ↓ ↓ ↓
|
|
User UI State Mgmt Business Logic HTTP Client REST API PostgreSQL
|
|
```
|
|
|
|
## 🔄 Step-by-Step Data Journey
|
|
|
|
### **Step 1: User Navigates to Assets View**
|
|
**File:** `/frontend/src/views/Assets.vue`
|
|
```javascript
|
|
// When component mounts
|
|
onMounted(async () => {
|
|
uiStore.setPageTitle('Assets');
|
|
await loadData(); // This starts the data flow
|
|
});
|
|
```
|
|
|
|
### **Step 2: View Calls the Assets Store**
|
|
**File:** `/frontend/src/views/Assets.vue` → Lines 427-429
|
|
```javascript
|
|
const loadData = async () => {
|
|
// Authentication happens first
|
|
await authService.ensureAuthenticated();
|
|
|
|
// Then data loading begins
|
|
await assetsStore.fetchAssets(); // 🎯 This starts the chain
|
|
await assetsStore.fetchCategories();
|
|
await assetsStore.fetchLocations();
|
|
};
|
|
```
|
|
|
|
### **Step 3: Assets Store Coordinates the Request**
|
|
**File:** `/frontend/src/stores/assets.js` → Lines 85-105
|
|
```javascript
|
|
async fetchAssets(params = {}) {
|
|
this.isLoading = true; // 📊 Update UI state
|
|
this.error = null;
|
|
|
|
try {
|
|
const queryParams = {
|
|
page: this.pagination.page,
|
|
limit: this.pagination.limit,
|
|
...params,
|
|
};
|
|
|
|
// 🔗 Delegate to repository layer
|
|
const response = await assetRepository.getAll(queryParams);
|
|
|
|
// 📦 Store data in state
|
|
this.assets = response.data || [];
|
|
this.pagination.total = response.meta?.total_count || 0;
|
|
} catch (error) {
|
|
this.error = error.message;
|
|
throw error;
|
|
} finally {
|
|
this.isLoading = false; // 📊 Update UI state
|
|
}
|
|
}
|
|
```
|
|
|
|
**🎯 Store Responsibilities:**
|
|
- **State Management** - Holds assets data in reactive state
|
|
- **Loading States** - Manages `isLoading`, `error` flags
|
|
- **Pagination** - Tracks page, limit, total count
|
|
- **Cache Invalidation** - Calls cache service when data changes
|
|
- **Error Handling** - Catches and stores error messages
|
|
|
|
### **Step 4: Asset Repository Adds Business Logic**
|
|
**File:** `/frontend/src/repositories/AssetRepository.js` → Lines 9-30
|
|
```javascript
|
|
async getAll(params = {}) {
|
|
try {
|
|
const searchParams = {
|
|
...params,
|
|
// 🔗 Define what related data to fetch
|
|
fields: [
|
|
'*', // All asset fields
|
|
'category_id.id', // Category relationship
|
|
'category_id.name',
|
|
'category_id.color',
|
|
'location_id.id', // Location relationship
|
|
'location_id.name',
|
|
'location_id.building',
|
|
'location_id.floor',
|
|
'vendor_id.id', // Vendor relationship
|
|
'vendor_id.name',
|
|
],
|
|
};
|
|
|
|
// 🔗 Delegate to base repository
|
|
return await super.getAll(searchParams);
|
|
} catch (error) {
|
|
throw this.handleError(error);
|
|
}
|
|
}
|
|
```
|
|
|
|
**🎯 Repository Responsibilities:**
|
|
- **Business Logic** - Asset-specific query parameters
|
|
- **Relationship Mapping** - Define what related data to fetch
|
|
- **Error Transformation** - Convert API errors to user-friendly messages
|
|
- **QR Code Generation** - Asset-specific features
|
|
- **Data Validation** - Business rule enforcement
|
|
|
|
### **Step 5: Base Repository Handles HTTP Communication**
|
|
**File:** `/frontend/src/repositories/BaseRepository.js` → Lines 10-18
|
|
```javascript
|
|
async getAll(params = {}) {
|
|
try {
|
|
// 🌐 Make HTTP request to Directus API
|
|
const response = await this.api.get(`/items/${this.collection}`, {
|
|
params, // Query parameters (fields, filters, pagination)
|
|
});
|
|
return response.data; // Return Directus response
|
|
} catch (error) {
|
|
throw this.handleError(error);
|
|
}
|
|
}
|
|
```
|
|
|
|
**🎯 Base Repository Responsibilities:**
|
|
- **HTTP Communication** - Axios-based API calls
|
|
- **URL Construction** - Build REST endpoints (`/items/assets`)
|
|
- **Parameter Serialization** - Convert objects to query strings
|
|
- **Response Handling** - Extract data from HTTP responses
|
|
- **Generic CRUD** - Reusable create, read, update, delete operations
|
|
|
|
### **Step 6: Directus Service Manages Authentication**
|
|
**File:** `/frontend/src/services/directus.js` → Lines 15-27
|
|
```javascript
|
|
// Request interceptor adds auth token
|
|
directusApi.interceptors.request.use(
|
|
(config) => {
|
|
const token = localStorage.getItem('directus_token');
|
|
if (token) {
|
|
config.headers.Authorization = `Bearer ${token}`; // 🔐 Add auth
|
|
}
|
|
return config;
|
|
}
|
|
);
|
|
```
|
|
|
|
**🎯 Directus Service Responsibilities:**
|
|
- **Authentication** - Add Bearer tokens to requests
|
|
- **Token Refresh** - Automatic token renewal
|
|
- **Base URL Configuration** - API endpoint setup
|
|
- **Request/Response Interceptors** - Global middleware
|
|
- **Development Auto-auth** - Seamless development experience
|
|
|
|
### **Step 7: HTTP Request to Directus API**
|
|
```http
|
|
GET http://localhost:8055/items/assets?fields=*,category_id.id,category_id.name,category_id.color,location_id.id,location_id.name,location_id.building,location_id.floor,vendor_id.id,vendor_id.name
|
|
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
|
```
|
|
|
|
### **Step 8: Directus Queries PostgreSQL Database**
|
|
**Database Schema:** From `/schema/init.sql`
|
|
```sql
|
|
-- Directus executes this SQL query
|
|
SELECT
|
|
a.*,
|
|
c.id as "category_id.id",
|
|
c.name as "category_id.name",
|
|
c.color as "category_id.color",
|
|
l.id as "location_id.id",
|
|
l.name as "location_id.name",
|
|
l.building as "location_id.building",
|
|
l.floor as "location_id.floor",
|
|
v.id as "vendor_id.id",
|
|
v.name as "vendor_id.name"
|
|
FROM assets a
|
|
LEFT JOIN asset_categories c ON a.category_id = c.id
|
|
LEFT JOIN locations l ON a.location_id = l.id
|
|
LEFT JOIN vendors v ON a.vendor_id = v.id
|
|
WHERE a.organization_id = '70562576-ac3e-4d84-b3a6-6d0d52b8cc10';
|
|
```
|
|
|
|
### **Step 9: Database Returns Raw Data**
|
|
```json
|
|
{
|
|
"data": [
|
|
{
|
|
"id": "cd89112d-510d-4f02-acb2-bdcd6242cd2d",
|
|
"name": "Frontend Test Laptop",
|
|
"asset_identifier": "TEST-FRONTEND-1752412612",
|
|
"status": "active",
|
|
"acquisition_cost": "1200.00",
|
|
"category_id": {
|
|
"id": "4a607979-0b7c-4f04-b42a-f78616922312",
|
|
"name": "Office Furniture",
|
|
"color": "#4CAF50"
|
|
},
|
|
"location_id": {
|
|
"id": "0ead8a26-b691-45a5-be55-2ad5143f078e",
|
|
"name": "Conference Room",
|
|
"building": "Building A"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### **Step 10: Data Flows Back Through the Chain**
|
|
```javascript
|
|
// BaseRepository receives HTTP response
|
|
response.data ← HTTP Response
|
|
|
|
// AssetRepository receives structured data
|
|
return response.data ← BaseRepository
|
|
|
|
// Store receives and processes data
|
|
this.assets = response.data || [] ← AssetRepository
|
|
|
|
// Vue reactive system triggers re-render
|
|
assets.value ← Store (Pinia reactive state)
|
|
```
|
|
|
|
### **Step 11: Assets View Processes Data for Display**
|
|
**File:** `/frontend/src/views/Assets.vue` → Lines 333-362
|
|
```javascript
|
|
const displayedAssets = computed(() => {
|
|
// 🔄 Transform raw data for UI display
|
|
let filtered = assets.value.map(asset => ({
|
|
...asset,
|
|
// Extract nested relationship data for display
|
|
category: asset.category_id?.name || 'Unknown',
|
|
category_color: asset.category_id?.color || '#9E9E9E',
|
|
location: asset.location_id ?
|
|
`${asset.location_id.name}${asset.location_id.building ? ` - ${asset.location_id.building}` : ''}` :
|
|
'Unknown'
|
|
}));
|
|
|
|
// Apply filters (search, category, status)
|
|
if (search.value) {
|
|
const searchTerm = search.value.toLowerCase();
|
|
filtered = filtered.filter(asset =>
|
|
asset.name.toLowerCase().includes(searchTerm) ||
|
|
asset.asset_identifier.toLowerCase().includes(searchTerm)
|
|
);
|
|
}
|
|
|
|
return filtered;
|
|
});
|
|
```
|
|
|
|
### **Step 12: Vue Renders the Data**
|
|
**File:** `/frontend/src/views/Assets.vue` → Lines 104-113
|
|
```vue
|
|
<!-- Grid View -->
|
|
<v-row v-if="viewMode === 'grid'">
|
|
<v-col
|
|
v-for="asset in displayedAssets" <!-- 🎨 Reactive data renders -->
|
|
:key="asset.id"
|
|
cols="12" sm="6" md="4" lg="3"
|
|
>
|
|
<AssetCard :asset="asset" @click="viewAsset(asset.id)" />
|
|
</v-col>
|
|
</v-row>
|
|
```
|
|
|
|
## 🎯 Role Summary of Each Layer
|
|
|
|
### **1. 📄 Views/Components (Presentation Layer)**
|
|
- **User Interface** - Display data and handle user interactions
|
|
- **Event Handling** - Respond to clicks, form submissions
|
|
- **Data Transformation** - Format data for display (dates, currency, etc.)
|
|
- **Loading States** - Show spinners, empty states, errors
|
|
|
|
### **2. 🗂️ Stores (State Management Layer)**
|
|
- **Global State** - Hold application data accessible across components
|
|
- **Reactivity** - Trigger UI updates when data changes
|
|
- **Caching** - Avoid redundant API calls
|
|
- **State Coordination** - Manage loading, error, and success states
|
|
|
|
### **3. 🏢 Repositories (Business Logic Layer)**
|
|
- **Domain Logic** - Asset-specific business rules and operations
|
|
- **Data Mapping** - Transform between API and application models
|
|
- **Relationship Handling** - Define what related data to fetch
|
|
- **Validation** - Ensure data integrity and business rules
|
|
|
|
### **4. ⚡ Base Repository (Infrastructure Layer)**
|
|
- **HTTP Communication** - Generic REST API operations
|
|
- **Error Handling** - Convert HTTP errors to application errors
|
|
- **Request Configuration** - Headers, timeouts, interceptors
|
|
- **Response Processing** - Extract data from HTTP responses
|
|
|
|
### **5. 🔐 Services (Cross-Cutting Concerns)**
|
|
- **Authentication** - Token management and security
|
|
- **Caching** - Performance optimization
|
|
- **Permissions** - Access control
|
|
- **Configuration** - Environment-specific settings
|
|
|
|
## 📁 File Structure Reference
|
|
|
|
```
|
|
enterprise-asset-management/
|
|
├── frontend/
|
|
│ ├── src/
|
|
│ │ ├── views/
|
|
│ │ │ ├── Assets.vue # 📄 Presentation Layer
|
|
│ │ │ ├── AddAsset.vue
|
|
│ │ │ └── EditAsset.vue
|
|
│ │ ├── stores/
|
|
│ │ │ ├── assets.js # 🗂️ State Management
|
|
│ │ │ ├── ui.js
|
|
│ │ │ └── auth.js
|
|
│ │ ├── repositories/
|
|
│ │ │ ├── BaseRepository.js # ⚡ Infrastructure Layer
|
|
│ │ │ ├── AssetRepository.js # 🏢 Business Logic
|
|
│ │ │ └── AuthRepository.js
|
|
│ │ ├── services/
|
|
│ │ │ ├── directus.js # 🔐 API Service
|
|
│ │ │ ├── auth.js # 🔐 Auth Service
|
|
│ │ │ ├── permissions.js # 🔐 Permission Service
|
|
│ │ │ └── cache.js # 🔐 Cache Service
|
|
│ │ └── components/
|
|
│ │ └── assets/
|
|
│ │ ├── AssetForm.vue # 📄 Reusable Component
|
|
│ │ └── AssetCard.vue
|
|
├── schema/
|
|
│ └── init.sql # 🗄️ Database Schema
|
|
└── scripts/
|
|
├── setup-permissions-enhanced.sh # 🔧 Permission Setup
|
|
└── verify-permissions.sh # 🔧 Permission Verification
|
|
```
|
|
|
|
## 🔄 Data Flow Patterns
|
|
|
|
### **Create Asset Flow**
|
|
```
|
|
AssetForm → emit('submit') → AddAsset.vue → assetsStore.createAsset() →
|
|
AssetRepository.create() → BaseRepository.create() → POST /items/assets →
|
|
Database INSERT → Success Response → Cache Invalidation → UI Update
|
|
```
|
|
|
|
### **Update Asset Flow**
|
|
```
|
|
AssetForm → emit('submit') → EditAsset.vue → assetsStore.updateAsset() →
|
|
AssetRepository.update() → BaseRepository.update() → PATCH /items/assets/{id} →
|
|
Database UPDATE → Success Response → Cache Invalidation → UI Update
|
|
```
|
|
|
|
### **Authentication Flow**
|
|
```
|
|
View → authService.ensureAuthenticated() → Check localStorage token →
|
|
If expired: authService.refreshToken() → directusApi interceptor →
|
|
Add Bearer token to request headers → Continue with API call
|
|
```
|
|
|
|
This architecture provides **separation of concerns**, **testability**, **maintainability**, and **scalability** for the asset management application! |