Skip to content

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/)


MongoDB

Key Patterns

PatternDescription
Business classesAll domain logic lives in *.business.js files, not controllers. Controllers only parse HTTP and call business methods.
DB abstractionDatabaseInterfaceMongoDBDatabaseBaseDB subclasses. Swappable without touching business logic.
Dependency injectionBusiness classes receive the db instance via constructor: new UserBusiness(db). Simplifies testing.
Response helpersAll responses go through helpers/response.helper.js. No raw res.status().json() in controllers.
TransactionsMongoDB 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 — no import statements needed in templates.
  • Vue Composition API helpers (ref, reactive, computed, etc.) are auto-imported — no import { 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 = token

The 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)

Intecog Logistech IoT Monitoring Platform