Architecture
High-Level Overview
┌──────────────────────────────────────────────────────────┐
│ Browser │
│ Vue 3 SPA (apps/frontend) │
│ Element Plus · Pinia · Vue Router · ECharts │
└───────────────────────────┬──────────────────────────────┘
│ HTTPS + Cookies
▼
┌──────────────────────────────────────────────────────────┐
│ nginx (EC2) │
│ app.intecoglogistech.com → port 3500 │
└───────────────────────────┬──────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ Express API (apps/backend) │
│ Routes → Controllers → Business Classes → DB Layer │
├──────────────────────────────────────────────────────────┤
│ MongoDB (Mongoose) AWS SES (email alerts) │
└──────────────────────────────────────────────────────────┘
▲
│ HTTP POST (sensor data)
│ Authorization: static token
┌──────────────────────────────────────────────────────────┐
│ IoT Hardware Devices │
│ POST /api/v1/devices/sensor-data │
└──────────────────────────────────────────────────────────┘Monorepo Build Pipeline
Turborepo orchestrates the build in strict dependency order:
@my-app/config (no deps — builds first)
│
▼
@my-app/shared (deps: config)
│
├──────────────────▼
│ @my-app/ui (deps: config)
│
├──────────────────▼
│ @my-app/frontend (deps: shared, ui, config)
│
└──────────────────▼
@my-app/backend (deps: shared, config)Turborepo caches build artifacts so unchanged packages are never rebuilt needlessly.
Backend Request Flow
HTTP Request
│
▼
Express Router (routes/)
│
▼
Auth Middleware (middleware/auth.middleware.js)
│ authCheck / adminAuthCheck / deviceAuthCheck
▼
Controller (controllers/) ← thin layer, minimal logic
│
▼
Business Class (controllers/*.business.js) ← all domain logic
│
▼
Database Layer (database/mongodb/) ← BaseDB + domain DB classes
│
▼
Mongoose Model (models/)
│
▼
MongoDBKey Patterns
| Pattern | Description |
|---|---|
| Business classes | All domain logic lives in *.business.js files, not controllers. Controllers only parse HTTP and call business methods. |
| DB abstraction | DatabaseInterface → MongoDBDatabase → BaseDB subclasses. Swappable without touching business logic. |
| Dependency injection | Business classes receive the db instance via constructor: new UserBusiness(db). Simplifies testing. |
| Response helpers | All responses go through helpers/response.helper.js. No raw res.status().json() in controllers. |
| Transactions | MongoDB sessions used for multi-document atomic operations (signup, unit updates, recipient delete). |
Frontend Architecture
src/
├── main.ts → app bootstrap (Pinia + Router + Element Plus icons)
├── App.vue → root (el-config-provider + router-view)
├── router/ → Vue Router 4 (hash history)
│ └── index.ts → route definitions + beforeEach auth guard
├── store/ → Pinia stores
│ ├── authStore.js → user session (persisted)
│ ├── menuStore.ts → sidebar state
│ ├── themeStore.ts → theme colors
│ ├── tabs.ts → tab navigation
│ └── sidebar.ts → sidebar collapse + colors
├── views/ → page-level components (lazy-loaded)
├── components/ → reusable UI components
├── utils/
│ ├── request.ts → Axios instance (withCredentials + 401 redirect)
│ ├── notifications.ts → ElNotification helpers
│ └── env.ts → Vite env helpers
└── api/ → thin API wrapper (mostly fetch* helpers)Auto-Import (Vite Plugins)
unplugin-auto-import and unplugin-vue-components with ElementPlusResolver means:
- All Element Plus components (
el-button,el-form, etc.) are auto-imported — noimportstatements needed in templates. - Vue Composition API helpers (
ref,reactive,computed, etc.) are auto-imported — noimport { ref } from 'vue'needed in<script setup>.
Authentication Architecture
POST /api/v1/auth/signin
│
▼
user.controller.js :: signin()
│
┌────────────┼────────────────────┐
│ │ │
Find user bcrypt.compare jwt.sign()
by email password with JWT_SECRET
│ │ │
└────────────┼────────────────────┘
│
db.user.createToken() → saves to user_tokens collection
│
res.cookie('userAccessToken', token, {
httpOnly: false,
secure: true (prod),
sameSite: 'none' (prod),
domain: '.intecoglogistech.com' (prod)
})
─────────────────────────────────────────────────
Every Protected Request → auth.middleware.js :: authCheck()
│
req.cookies['userAccessToken']
│
jwt.verify(token)
│
UserToken.findOne({ token }) ← revocation check
│
User.findOne({ _id: decoded })
│
req.user = user
req.token = tokenThe server-side token store (user_tokens collection) means logout is immediate and complete — the token is deleted on logout, and all future requests with it are rejected.
IoT Data Flow
IoT Device → POST /api/v1/devices/sensor-data
Header: Authorization: <static token>
Body: { id: deviceCode, temperature, humidity, volt, ... }
│
deviceAuthCheck (static token validation)
│
deviceSensorData.controller.js :: saveSensorData()
│
1. DeviceModel.findOne({ code: body.id })
2. UnitModel.findOne({ device_id })
3. generateErrorAlerts(recentData, unit)
→ compares vs. min_temperature, max_temperature
→ compares vs. min_humidity, max_humidity
→ compares vs. min_volt, max_volt
→ produces live_alerts[]
4. unit.save() ← recent_sensor_data + live_alerts
5. device.save() ← recent_sensor_data + last_communicated_at
6. Save SensorDataModel record
7. Save NetworkDataModel record (if present)