# Architecture

This document provides a detailed overview of the ON Internal API system architecture, data flows, and technical design decisions.

# New Page (will be separated)

## Table of Contents

- [System Overview](#system-overview)
- [Architecture Layers](#architecture-layers)
- [Database Architecture](#database-architecture)
- [Authentication & Authorization](#authentication--authorization)
- [API Design](#api-design)
- [WebSocket Architecture](#websocket-architecture)
- [File Management](#file-management)
- [Scheduled Tasks](#scheduled-tasks)
- [Security Architecture](#security-architecture)
- [Scalability Considerations](#scalability-considerations)

## System Overview

ON Internal API is a multi-tenant, microservices-style backend system built on Node.js and Express.js. It manages multiple business domains through a unified API gateway while maintaining separation of concerns through modular architecture.

### Design Principles

1. **Modularity**: Each business domain (employee, asset, fleet, etc.) is isolated in its own module
2. **Separation of Concerns**: Clear separation between routes, controllers, models, and utilities
3. **Database Segregation**: Different databases for different domains to ensure data isolation
4. **Security First**: Multiple layers of security (Helmet, CORS, JWT, validation)
5. **Real-time Capable**: WebSocket support for live updates
6. **Stateless**: JWT-based authentication allows horizontal scaling

## Architecture Layers

### 1. Entry Point Layer (server.js)

```
┌─────────────────────────────────────────────────────┐
│                    server.js                        │
├─────────────────────────────────────────────────────┤
│  - Load environment variables (dotenv)              │
│  - Initialize Express application                   │
│  - Configure security middleware (Helmet, CORS)     │
│  - Setup logging (Morgan with colored output)       │
│  - Connect to all databases (Sequelize/Mongoose)    │
│  - Create directory structure                       │
│  - Load all routes                                  │
│  - Initialize WebSocket server                      │
│  - Start HTTP server                                │
│  - Load scheduled tasks                             │
└─────────────────────────────────────────────────────┘
```

#### Key Responsibilities:
- Application bootstrap
- Global configuration
- Middleware stack setup
- Database connection management
- Route registration
- Server initialization

### 2. Middleware Layer

```
┌────────────────────────────────────────────────────┐
│              Middleware Pipeline                   │
├────────────────────────────────────────────────────┤
│  1. CORS (Cross-Origin Resource Sharing)           │
│  2. Helmet (Security headers)                      │
│  3. Morgan (Request logging with colors)           │
│  4. Express JSON/URL-encoded parsers (10MB limit)  │
│  5. Custom middleware (per route):                 │
│     - authJwt.verifyToken (JWT validation)         │
│     - contentTypeValid (Content-Type check)        │
│     - validator.validate (Schema validation)       │
│     - checkEmployee/checkFleet (Role validation)   │
│  6. Error handler (errorParamHandler)              │
└────────────────────────────────────────────────────┘
```

#### Middleware Components:

**Security Middleware** (`app/middleware/authJwt.js`):
```javascript
verifyToken(req, res, next) {
  // Extract token from header
  // Verify JWT signature
  // Decode and attach user to request
  // Continue or reject
}
```

**Validation Middleware** (`app/schemas/`):
- Uses AJV for JSON Schema validation
- Validates request body, query params, and URL params
- Returns structured error messages

**Error Handler** (`app/middleware/errorParamHandler.js`):
- Catches all errors
- Formats error responses
- Logs errors
- Returns consistent JSON error format

### 3. Route Layer

Routes define API endpoints and map them to controllers:

```javascript
// Example: app/routes/auth.routes.js
module.exports = function (app) {
  app.post(
    "/api/auth/login",
    [
      contentTypeValid("application/json"),
      validator.validate({ body: auth.login }),
    ],
    authController.login
  );
  
  app.get(
    "/api/auth/account",
    authJwt.verifyToken,
    authController.loadAccount
  );
};
```

**Route Organization**:
- One route file per module
- Middleware applied declaratively
- Clear HTTP method usage
- RESTful design patterns

### 4. Controller Layer

Controllers contain business logic:

```
┌─────────────────────────────────────────────────────┐
│              Controller Responsibilities            │
├─────────────────────────────────────────────────────┤
│  1. Extract data from request (body, query, params) │
│  2. Call database operations via models             │
│  3. Process business logic                          │
│  4. Format response data                            │
│  5. Handle errors                                   │
│  6. Return HTTP response                            │
└─────────────────────────────────────────────────────┘
```

**Controller Pattern**:
```javascript
exports.methodName = async (req, res) => {
  try {
    // 1. Extract data
    const { param } = req.body;
    
    // 2. Validate business rules
    if (!param) throw new Error("Missing parameter");
    
    // 3. Database operations
    const result = await Model.findOne({ where: { id } });
    
    // 4. Process data
    const processed = processData(result);
    
    // 5. Return response
    res.status(200).json({
      success: true,
      data: processed
    });
  } catch (error) {
    // 6. Error handling
    res.status(400).json({
      success: false,
      message: error.message
    });
  }
};
```

### 5. Model Layer

Models define database schemas using Sequelize (PostgreSQL) or Mongoose (MongoDB):

```javascript
// Example Sequelize model
module.exports = (sequelize, Sequelize) => {
  const Model = sequelize.define("table_name", {
    id: {
      type: Sequelize.INTEGER,
      primaryKey: true,
      autoIncrement: true
    },
    name: {
      type: Sequelize.STRING,
      allowNull: false
    },
    // ... other fields
  }, {
    timestamps: true,
    tableName: "table_name"
  });
  
  return Model;
};
```

## Database Architecture

### Multi-Database Strategy

The system uses four separate databases:

```
┌────────────────────────────────────────────────────┐
│                 Database Layer                     │
├────────────────────────────────────────────────────┤
│                                                    │
│  ┌─────────────────┐  ┌─────────────────┐        │
│  │   Sunshine DB   │  │    Fleet DB     │        │
│  │  (PostgreSQL)   │  │  (PostgreSQL)   │        │
│  ├─────────────────┤  ├─────────────────┤        │
│  │ - Users         │  │ - Drivers       │        │
│  │ - Employees     │  │ - Vehicles      │        │
│  │ - Attendance    │  │ - Routes        │        │
│  │ - Leave/Permit  │  │ - Tracking      │        │
│  │ - Assets        │  │                 │        │
│  │ - Ticketing     │  │                 │        │
│  │ - Shifts        │  │                 │        │
│  └─────────────────┘  └─────────────────┘        │
│                                                    │
│  ┌─────────────────┐  ┌─────────────────┐        │
│  │     CMS DB      │  │    Mitra DB     │        │
│  │  (PostgreSQL)   │  │ (PostgreSQL +   │        │
│  ├─────────────────┤  │    MongoDB)     │        │
│  │ - Blog Posts    │  ├─────────────────┤        │
│  │ - Ads           │  │ - Mitra data    │        │
│  │ - Training      │  │ - Documents     │        │
│  │ - Career        │  │                 │        │
│  │ - Departments   │  │                 │        │
│  │ - Drop Points   │  │                 │        │
│  └─────────────────┘  └─────────────────┘        │
└────────────────────────────────────────────────────┘
```

### Database Configuration

**Connection Pooling** (for performance):
```javascript
pool: {
  max: 5,      // Maximum connections
  min: 0,      // Minimum connections
  acquire: 30000,  // Max time to get connection (ms)
  idle: 10000  // Max idle time before closing (ms)
}
```

### Database Selection Pattern

```javascript
// In models/index.js
const sequelizeSunshine = new Sequelize(sunshineDB.DB, ...);
const sequelizeFleet = new Sequelize(fleetDB.DB, ...);
const sequelizeCms = new Sequelize(cmsDB.DB, ...);

// Models are registered to specific connections
db.employee = require("./employee.model")(sequelizeSunshine, Sequelize);
db.driver = require("./driver.model")(sequelizeFleet, Sequelize);
```

### Data Relationships

**Cross-Database Relationships**:
- Avoided when possible for better independence
- When needed, handled at application layer (not DB constraints)
- Use foreign keys within same database

**Example**:
```javascript
// Employee (Sunshine DB) -> Asset Assignment (Sunshine DB) ✓
// Employee has Assets relationship

// Employee (Sunshine DB) -> Driver (Fleet DB) ✗
// Handled via employee_id field and application logic
```

## Authentication & Authorization

### JWT Authentication Flow

```
┌─────────┐           ┌─────────┐           ┌──────────┐
│ Client  │           │  API    │           │ Database │
└────┬────┘           └────┬────┘           └─────┬────┘
     │                     │                      │
     │  POST /api/auth/login                     │
     │  { username, password }                   │
     ├──────────────────────>                    │
     │                     │                      │
     │                     │  Query user          │
     │                     ├──────────────────────>
     │                     │                      │
     │                     │  User data           │
     │                     <──────────────────────┤
     │                     │                      │
     │                     │  Verify password     │
     │                     │  (bcrypt.compare)    │
     │                     │                      │
     │                     │  Generate JWT        │
     │                     │  (sign with secret)  │
     │                     │                      │
     │  { token, user }    │                      │
     <──────────────────────┤                     │
     │                     │                      │
     │                     │                      │
     │  GET /api/protected │                      │
     │  Header: x-access-token: <JWT>            │
     ├──────────────────────>                    │
     │                     │                      │
     │                     │  Verify JWT          │
     │                     │  (verify signature)  │
     │                     │                      │
     │                     │  Decode payload      │
     │                     │  Attach to req.user  │
     │                     │                      │
     │                     │  Process request     │
     │                     ├──────────────────────>
     │                     │                      │
     │  Response data      │                      │
     <──────────────────────┤                     │
     │                     │                      │
```

### Authentication Components

**Login Process** (`app/controllers/auth.controller.js`):
1. Receive username/password
2. Query database for user
3. Verify password with bcrypt
4. Generate JWT token with user data
5. Return token and user info

**Token Verification** (`app/middleware/authJwt.js`):
1. Extract token from header (`x-access-token`)
2. Verify token signature with JWT_SECRET
3. Decode payload (user ID, roles, etc.)
4. Attach decoded data to `req.user`
5. Continue to next middleware/controller

**Token Structure**:
```javascript
{
  id: userId,
  username: "user@example.com",
  role: "admin",
  iat: 1234567890,  // Issued at
  exp: 1234654290   // Expires at
}
```

### Authorization Patterns

**Role-Based Access Control**:
```javascript
// In middleware
if (req.user.role !== 'admin') {
  return res.status(403).json({
    message: "Unauthorized access"
  });
}
```

**Resource-Based Authorization**:
```javascript
// Verify user owns resource
const resource = await Model.findOne({
  where: { id: resourceId, userId: req.user.id }
});

if (!resource) {
  return res.status(403).json({
    message: "Access denied"
  });
}
```

## API Design

### RESTful Principles

**Resource Naming**:
- Plural nouns: `/api/employees`, `/api/assets`
- Nested resources: `/api/employees/:id/shifts`
- Actions as sub-resources: `/api/tickets/:id/assign`

**HTTP Methods**:
- `GET`: Read/retrieve data
- `POST`: Create new resource
- `PUT`: Update entire resource
- `PATCH`: Partial update
- `DELETE`: Remove resource

**Status Codes**:
- `200`: Success
- `201`: Created
- `400`: Bad request (validation error)
- `401`: Unauthorized (not authenticated)
- `403`: Forbidden (not authorized)
- `404`: Not found
- `500`: Internal server error

### Response Format

**Success Response**:
```json
{
  "success": true,
  "data": {
    "id": 1,
    "name": "Example"
  },
  "message": "Operation successful"
}
```

**Error Response**:
```json
{
  "success": false,
  "message": "Validation failed",
  "errors": [
    {
      "field": "email",
      "message": "Invalid email format"
    }
  ]
}
```

**Pagination** (when applicable):
```json
{
  "success": true,
  "data": [...],
  "pagination": {
    "page": 1,
    "perPage": 20,
    "total": 150,
    "totalPages": 8
  }
}
```

### Input Validation

Using AJV JSON Schema:

```javascript
// app/schemas/example.schema.js
module.exports = {
  create: {
    type: "object",
    properties: {
      name: { type: "string", minLength: 1 },
      email: { type: "string", format: "email" },
      age: { type: "integer", minimum: 18 }
    },
    required: ["name", "email"],
    additionalProperties: false
  }
};
```

Applied in routes:
```javascript
app.post(
  "/api/example",
  validator.validate({ body: exampleSchema.create }),
  controller.create
);
```

## WebSocket Architecture

### WebSocket Server Setup

```javascript
// app/routes/ws.routes.js
const WebSocket = require("ws");

module.exports = (server, app) => {
  const wss = new WebSocket.Server({ noServer: true });
  
  server.on("upgrade", (request, socket, head) => {
    // Parse URL to route to correct WebSocket handler
    const pathname = url.parse(request.url).pathname;
    
    if (pathname === "/ws/branding-approval") {
      wss.handleUpgrade(request, socket, head, (ws) => {
        wss.emit("connection", ws, request);
      });
    }
  });
  
  wss.on("connection", (ws, request) => {
    // Handle connection
    ws.on("message", (message) => {
      // Handle message
    });
    
    ws.on("close", () => {
      // Handle disconnect
    });
  });
};
```

### WebSocket Endpoints

1. **Branding Approval** (`/ws/branding-approval`)
   - Real-time approval status updates
   - Mobile and web clients
   - Notification delivery

2. **Ticketing** (`/ws/ticketing`)
   - Ticket status updates
   - Assignment notifications
   - Real-time chat (if applicable)

### Connection Management

**Heartbeat (Ping/Pong)**:
```javascript
// interval.js
setInterval(function ping() {
  wss.clients.forEach(function each(ws) {
    if (ws.isAlive === false) {
      return ws.terminate();
    }
    ws.isAlive = false;
    ws.ping();
  });
}, 30000);  // Every 30 seconds
```

**Client Tracking**:
```javascript
// Track connected clients
const clients = new Map();

wss.on("connection", (ws, request) => {
  const userId = extractUserId(request);
  clients.set(userId, ws);
  
  ws.on("close", () => {
    clients.delete(userId);
  });
});
```

**Broadcasting**:
```javascript
// Send to all clients
wss.clients.forEach((client) => {
  if (client.readyState === WebSocket.OPEN) {
    client.send(JSON.stringify(data));
  }
});

// Send to specific user
const userWs = clients.get(userId);
if (userWs && userWs.readyState === WebSocket.OPEN) {
  userWs.send(JSON.stringify(data));
}
```

## File Management

### Upload Flow

```
┌─────────┐           ┌─────────┐           ┌──────────┐
│ Client  │           │  API    │           │ Storage  │
└────┬────┘           └────┬────┘           └─────┬────┘
     │                     │                      │
     │  POST /api/upload/temp                    │
     │  Content-Type: multipart/form-data        │
     │  Body: file                               │
     ├──────────────────────>                    │
     │                     │                      │
     │                     │  Multer processes    │
     │                     │  file upload         │
     │                     │                      │
     │                     │  Save to disk        │
     │                     ├──────────────────────>
     │                     │                      │
     │                     │  File saved          │
     │                     <──────────────────────┤
     │                     │                      │
     │  { url, name }      │                      │
     <──────────────────────┤                     │
     │                     │                      │
```

### Storage Structure

```
resources/
├── static/              # Permanent files
│   ├── employee/        # Employee photos, documents
│   ├── ticketing/       # Ticket attachments
│   ├── cms/
│   │   ├── ad/          # Advertisement images
│   │   ├── blog/        # Blog post images
│   │   ├── training/    # Training materials
│   │   └── department/  # Department logos
│   ├── branding-approval/  # Branding assets
│   └── onapps/
│       └── voucher/     # Voucher images
└── temp/                # Temporary files (auto-deleted)
```

### File Upload Middleware

```javascript
// app/middleware/uploadTemp.js
const multer = require("multer");
const path = require("path");

const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, path.join(__remoteDir, __stageEnv, "temp"));
  },
  filename: (req, file, cb) => {
    const uniqueName = `${Date.now()}-${file.originalname}`;
    cb(null, uniqueName);
  }
});

const upload = multer({
  storage: storage,
  limits: { fileSize: 10 * 1024 * 1024 },  // 10MB
  fileFilter: (req, file, cb) => {
    // Validate file type if needed
    cb(null, true);
  }
});
```

### File Serving

**Temporary Files**:
- Served via `/api/temp/:filename`
- Automatically deleted after first download
- Used for reports, exports, etc.

**Static Files**:
- Served via `/api/static/:filename`
- Permanent storage
- Used for user uploads, images, etc.

## Scheduled Tasks

### Task Scheduler Architecture

```javascript
// app/schedulers/file-temporary.scheduler.js
const cron = require("node-cron");

exports.cleanTempFile = () => {
  // Run every hour
  cron.schedule("0 * * * *", async () => {
    const tempDir = path.join(__remoteDir, __stageEnv, "temp");
    const files = fs.readdirSync(tempDir);
    
    files.forEach(file => {
      const filePath = path.join(tempDir, file);
      const stats = fs.statSync(filePath);
      const now = Date.now();
      const age = now - stats.mtimeMs;
      
      // Delete files older than 24 hours
      if (age > 24 * 60 * 60 * 1000) {
        fs.unlinkSync(filePath);
      }
    });
  });
};
```

### Scheduled Tasks

1. **Temporary File Cleanup**
   - Schedule: Every hour
   - Action: Delete temp files older than 24 hours
   - Purpose: Prevent disk space issues

2. **Leave Auto-Rejection**
   - Schedule: Daily
   - Action: Auto-reject pending leave requests past deadline
   - Purpose: Workflow automation

### Adding New Scheduled Tasks

```javascript
// 1. Create scheduler file
// app/schedulers/your-task.scheduler.js
const cron = require("node-cron");

exports.yourTask = () => {
  cron.schedule("0 0 * * *", async () => {  // Daily at midnight
    // Your task logic
  });
};

// 2. Load in server.js
const yourTaskScheduler = require("./app/schedulers/your-task.scheduler");
yourTaskScheduler.yourTask();
```

## Security Architecture

### Security Layers

```
┌─────────────────────────────────────────────────────┐
│              Security Layers                        │
├─────────────────────────────────────────────────────┤
│  1. Transport Layer (HTTPS in production)           │
│  2. CORS (Cross-Origin Resource Sharing)            │
│  3. Helmet.js (Security headers)                    │
│  4. Request Size Limits (10MB)                      │
│  5. JWT Authentication                              │
│  6. Input Validation (AJV JSON Schema)              │
│  7. SQL Injection Protection (Sequelize ORM)        │
│  8. Password Hashing (bcryptjs)                     │
│  9. Error Sanitization (no stack traces in prod)    │
└─────────────────────────────────────────────────────┘
```

### Security Headers (Helmet.js)

Automatically applied headers:
- `X-DNS-Prefetch-Control`
- `X-Frame-Options`
- `X-Content-Type-Options`
- `X-XSS-Protection`
- `Strict-Transport-Security` (HTTPS only)

### CORS Configuration

```javascript
app.use(cors());  // Allow all origins (configure for production)

// Custom CORS for specific resources
app.use((req, res, next) => {
  res.setHeader("Cross-Origin-Resource-Policy", "cross-origin");
  next();
});
```

**Production CORS** (recommended):
```javascript
const corsOptions = {
  origin: [
    "https://yourdomain.com",
    "https://app.yourdomain.com"
  ],
  credentials: true
};
app.use(cors(corsOptions));
```

### SQL Injection Prevention

Using Sequelize ORM with parameterized queries:

```javascript
// ✓ Safe - parameterized
User.findOne({
  where: { email: userInput }
});

// ✗ Unsafe - raw query without parameters
sequelize.query(`SELECT * FROM users WHERE email = '${userInput}'`);

// ✓ Safe - raw query with parameters
sequelize.query(
  "SELECT * FROM users WHERE email = :email",
  {
    replacements: { email: userInput },
    type: QueryTypes.SELECT
  }
);
```

### Password Security

```javascript
const bcrypt = require("bcryptjs");

// Hashing (on user creation)
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(password, salt);

// Verification (on login)
const isValid = await bcrypt.compare(password, user.password);
```

## Scalability Considerations

### Horizontal Scaling

**Stateless Design**:
- JWT tokens (no server-side sessions)
- No in-memory state (use database/cache)
- Load balancer compatible

**Database Connection Pooling**:
- Limited connections per instance
- Reuse existing connections
- Automatic cleanup of idle connections

### Performance Optimization

**Database Queries**:
- Use indexes on frequently queried fields
- Limit result sets (pagination)
- Use `select` to fetch only needed fields
- Avoid N+1 queries (use `include` for joins)

**Caching** (future consideration):
```javascript
// Example with Redis
const redis = require("redis");
const client = redis.createClient();

// Cache frequently accessed data
const cached = await client.get(key);
if (cached) return JSON.parse(cached);

const data = await database.query();
await client.setex(key, 3600, JSON.stringify(data));
```

**Request Rate Limiting** (future consideration):
```javascript
const rateLimit = require("express-rate-limit");

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // 15 minutes
  max: 100  // Limit each IP to 100 requests per windowMs
});

app.use("/api/", limiter);
```

### Monitoring & Logging

**Current Logging**:
- Morgan for HTTP request logging
- Colored console output for readability
- Sequelize query logging

**Production Logging** (recommended):
- Use Winston or Bunyan
- Log to files/external service
- Structured logging (JSON format)
- Different log levels (error, warn, info, debug)

**Metrics** (future consideration):
- Response times
- Error rates
- Database query performance
- Active connections
- Memory/CPU usage

---

**Document Version**: 1.0.0  
**Last Updated**: 2026-02-04