12 KiB
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
// 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
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
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,errorflags - 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
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
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
// 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
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
-- 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
{
"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
// 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
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
<!-- 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!