819 lines
28 KiB
Markdown
819 lines
28 KiB
Markdown
# AgMission SaaS Platform Software Architecture
|
|
|
|
**Version:** 3.2.x
|
|
**Last updated:** April 2026
|
|
**Author:** AgNav Engineering
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
|
|
- [AgMission SaaS Platform Software Architecture](#agmission-saas-platform-software-architecture)
|
|
- [Table of Contents](#table-of-contents)
|
|
- [1 Product Overview](#1-product-overview)
|
|
- [2 System Components](#2-system-components)
|
|
- [3 High-Level Architecture](#3-high-level-architecture)
|
|
- [4 Component Details](#4-component-details)
|
|
- [4-1 Web Client Angular SPA](#4-1-web-client-angular-spa)
|
|
- [Module structure](#module-structure)
|
|
- [State management NgRx](#state-management-ngrx)
|
|
- [Key features](#key-features)
|
|
- [4-2 API Server](#4-2-api-server)
|
|
- [Request lifecycle](#request-lifecycle)
|
|
- [Route groups](#route-groups)
|
|
- [Key server-side helpers](#key-server-side-helpers)
|
|
- [4-3 GPS Server](#4-3-gps-server)
|
|
- [How it works](#how-it-works)
|
|
- [4-4 Track Server](#4-4-track-server)
|
|
- [4-5 SatLoc Integration Service](#4-5-satloc-integration-service)
|
|
- [4-6 Background Workers](#4-6-background-workers)
|
|
- [Worker overview](#worker-overview)
|
|
- [Job Worker](#job-worker)
|
|
- [Invoice Worker](#invoice-worker)
|
|
- [Partner Sync Worker](#partner-sync-worker)
|
|
- [Partner Data Polling Worker](#partner-data-polling-worker)
|
|
- [Cleanup Worker](#cleanup-worker)
|
|
- [4-7 Maintainer Service](#4-7-maintainer-service)
|
|
- [5 Data Architecture](#5-data-architecture)
|
|
- [MongoDB collections](#mongodb-collections)
|
|
- [User type hierarchy](#user-type-hierarchy)
|
|
- [6 Message Queue Architecture](#6-message-queue-architecture)
|
|
- [7 Partner Integration Architecture](#7-partner-integration-architecture)
|
|
- [Partner credential model](#partner-credential-model)
|
|
- [8 Billing and Subscription Architecture](#8-billing-and-subscription-architecture)
|
|
- [9 Authentication and Authorization](#9-authentication-and-authorization)
|
|
- [JWT authentication](#jwt-authentication)
|
|
- [API key authentication Public Export API](#api-key-authentication-public-export-api)
|
|
- [Authorization model](#authorization-model)
|
|
- [10 Infrastructure and Deployment](#10-infrastructure-and-deployment)
|
|
- [Production server topology](#production-server-topology)
|
|
- [PM2 managed processes](#pm2-managed-processes)
|
|
- [Deployment flow](#deployment-flow)
|
|
- [11 Key Data Flows](#11-key-data-flows)
|
|
- [Job creation and assignment](#job-creation-and-assignment)
|
|
- [Real-time GPS tracking](#real-time-gps-tracking)
|
|
- [12 Repository Layout](#12-repository-layout)
|
|
|
|
---
|
|
|
|
## 1 Product Overview
|
|
|
|
AgMission is a cloud-based SaaS platform for **precision aerial agriculture** management. It serves agricultural aviation operators (applicators), their clients (growers/farmers), and integrated partners with console navigation aid systems (such as AgNav—Platinum and Titanium—and SatLoc).
|
|
|
|
The platform manages the complete lifecycle of an aerial application job:
|
|
|
|
1. **Mission planning** — create jobs, define treatment areas, assign pilots and jobs to aircraft
|
|
2. **Job distribution** — push assigned jobs to guidance console systems, in aircraft, on the field
|
|
3. **Real-time tracking** — live GPS monitoring of aircraft during operations
|
|
4. **Post-flight processing** — parse binary GPS/spray logs from console systems, compute coverage statistics
|
|
5. **Reporting and billing** — generate application reports, manage invoices, and handle SaaS subscriptions
|
|
|
|
**Supported languages:** English, Portuguese (pt), Spanish (es)
|
|
|
|
---
|
|
|
|
## 2 System Components
|
|
|
|
| Component | Technology | Purpose |
|
|
|---|---|---|
|
|
| Web Client | Angular 9.1.13, NgRx 9, Leaflet 1.9, StimulSoftJS 2020.3.2 | Browser-based management UI and report viewer |
|
|
| API Server | Node.js 16.20.2 LTS, Express 4, Mongoose 6 | Core REST API, business logic |
|
|
| GPS Server | Node.js, TCP sockets | Receives GPS data from AgNav (Platinum, Titanium) and RAP devices |
|
|
| Track Server | Node.js, Express, SSE | Real-time GPS tracking feed to browser |
|
|
| SatLoc Service | Node.js | Partner integration with SatLoc Cloud |
|
|
| Job Worker | Node.js, RabbitMQ | Async processing of uploaded job files |
|
|
| Invoice Worker | Node.js, Cron | Job Invoicing—manages applicator invoices for jobs performed |
|
|
| Partner Sync Worker | Node.js, RabbitMQ | Partner job upload and log processing |
|
|
| Partner Polling Worker | Node.js, Cron | Polls partner systems for new flight data |
|
|
| Cleanup Worker | Node.js, Cron | Purges soft-deleted data |
|
|
| Maintainer | Node.js, Cron | Scheduled database maintenance |
|
|
|
|
---
|
|
|
|
## 3 High-Level Architecture
|
|
|
|
```mermaid
|
|
graph TD
|
|
subgraph "Field Devices"
|
|
AGN["AgNav Guidance<br/>System"]
|
|
RAP["RAP Guidance<br/>System"]
|
|
SLC["SatLoc Cloud<br/>Partner"]
|
|
end
|
|
|
|
subgraph "Browser"
|
|
UI["Angular SPA<br/>(Web Client)"]
|
|
end
|
|
|
|
subgraph "AgMission Backend (agnav.com server)"
|
|
NGINX["Nginx<br/>Reverse Proxy"]
|
|
API["API Server<br/>Node.js / Express"]
|
|
GPS["GPS Server<br/>TCP :6080 / :6082"]
|
|
TRK["Track Server<br/>HTTP/SSE"]
|
|
MNT["Maintainer<br/>Cron Service"]
|
|
JW["Job Worker"]
|
|
IW["Invoice Worker"]
|
|
PSW["Partner Sync Worker"]
|
|
PPW["Partner Polling Worker"]
|
|
CW["Cleanup Worker"]
|
|
end
|
|
|
|
subgraph "Data Layer"
|
|
MDB["MongoDB<br/>Replica Set"]
|
|
RBT["RabbitMQ<br/>Message Broker"]
|
|
RDS["Redis<br/>Cache"]
|
|
end
|
|
|
|
subgraph "External Services"
|
|
STR["Stripe<br/>Billing"]
|
|
SLC2["SatLoc Cloud API<br/>satloccloudfc.com"]
|
|
SMTP["Email / SMTP"]
|
|
end
|
|
|
|
AGN -- "TCP :6080" --> GPS
|
|
RAP -- "TCP :6082" --> GPS
|
|
UI -- "HTTPS" --> NGINX
|
|
NGINX -- "/" --> API
|
|
NGINX -- "/track" --> TRK
|
|
GPS -- "AMQP gdata queue" --> RBT
|
|
RBT -- "consume gdata" --> TRK
|
|
TRK -- "SSE" --> UI
|
|
API -- "AMQP jobs / partner_tasks" --> RBT
|
|
RBT -- "consume jobs" --> JW
|
|
RBT -- "consume partner_tasks" --> PSW
|
|
API --> MDB
|
|
JW --> MDB
|
|
IW --> MDB
|
|
PSW --> MDB
|
|
PPW --> MDB
|
|
CW --> MDB
|
|
MNT --> MDB
|
|
API --> RDS
|
|
JW --> RDS
|
|
API --> STR
|
|
PSW -- "REST" --> SLC2
|
|
PPW -- "REST / cron" --> SLC2
|
|
PPW -- "AMQP partner_tasks" --> RBT
|
|
```
|
|
|
|
---
|
|
|
|
## 4 Component Details
|
|
|
|
### 4-1 Web Client Angular SPA
|
|
|
|
**Location:** `trunk/Development/client/`
|
|
**Version:** 2.6.15
|
|
|
|
The frontend is a single-page application built with Angular 9 and served by Nginx. It communicates with the API Server over HTTPS and receives real-time GPS updates from the Track Server via Server-Sent Events (SSE).
|
|
|
|
#### Module structure
|
|
|
|
```mermaid
|
|
graph TD
|
|
ROOT["AppModule<br/>(app.module.ts)"]
|
|
ROOT --> AUTH["AuthModule<br/>Login, signup, password reset"]
|
|
ROOT --> DASH["DashboardModule<br/>Overview metrics"]
|
|
ROOT --> JOBS["JobModule<br/>Mission management<br/>Map editing, file upload"]
|
|
ROOT --> TRACK["TrackModule<br/>Live GPS tracking map"]
|
|
ROOT --> CUST["CustomerModule<br/>Client / grower management"]
|
|
ROOT --> BILL["BillingModule<br/>Subscription and invoices"]
|
|
ROOT --> INV["InvoicesModule<br/>Invoice listing and detail"]
|
|
ROOT --> PART["PartnersModule<br/>Partner system users"]
|
|
ROOT --> ADM["AdminModule<br/>Platform admin tools"]
|
|
ROOT --> SET["SettingsModule<br/>User and account settings"]
|
|
ROOT --> REP["ReportComponent<br/>PDF report viewer"]
|
|
ROOT --> SIGN["SignupModule<br/>Self-service subscription signup"]
|
|
```
|
|
|
|
#### State management NgRx
|
|
|
|
The app uses NgRx (Redux pattern) for global state:
|
|
|
|
- **Store** — single source of truth for session, entities, and UI state
|
|
- **Effects** — side-effects (HTTP calls) triggered by dispatched actions
|
|
- **Entities** — normalized collections (jobs, customers, pilots, vehicles, etc.)
|
|
- **Reducers** — pure state transitions
|
|
|
|
#### Key features
|
|
|
|
| Feature | Description |
|
|
|---|---|
|
|
| Job map editor | Leaflet map for defining treatment areas, waypoints, and obstacles |
|
|
| Live tracking | Real-time aircraft position overlay via SSE |
|
|
| Data playback | Replay completed flight paths from application logs |
|
|
| Invoicing | Create, send, and track invoices |
|
|
| Subscription management | Self-serve plan selection, trial, and upgrade; manage billing information; integrate with Stripe |
|
|
| Multi-language | English / Portuguese / Spanish via Angular i18n |
|
|
| Partner customers | View and manage SatLoc partner-linked customer accounts; support Satloc console systems, G4, Falcon in automated workflows |
|
|
|
|
---
|
|
|
|
### 4-2 API Server
|
|
|
|
**Location:** `trunk/Development/server/`
|
|
**Entry point:** `server.js`
|
|
**Port:** `AGM_PORT` (default 7000 in production)
|
|
|
|
The central Express application. All browser requests pass through Nginx which proxies to this server. It handles authentication, all business logic REST endpoints, file uploads, Stripe webhooks, and report generation.
|
|
|
|
#### Request lifecycle
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant C as Client (Browser)
|
|
participant N as Nginx
|
|
participant S as API Server
|
|
participant MW as Middlewares
|
|
participant R as Routes
|
|
participant CTL as Controller
|
|
participant DB as MongoDB
|
|
|
|
C->>N: HTTPS request
|
|
N->>S: HTTP proxy (X-Forwarded-*)
|
|
S->>MW: Rate limiter
|
|
MW->>MW: JWT checkUser
|
|
MW->>R: Router dispatch
|
|
R->>CTL: Handler function
|
|
CTL->>DB: Mongoose queries
|
|
DB-->>CTL: Documents
|
|
CTL-->>C: JSON response
|
|
```
|
|
|
|
#### Route groups
|
|
|
|
| Route prefix | Controller | Description |
|
|
|---|---|---|
|
|
| `/api/users` | user.js | User account CRUD |
|
|
| `/api/customers` | customer.js | Applicator / client management |
|
|
| `/api/jobs` | job.js | Mission lifecycle |
|
|
| `/api/upload` | upload_job.js | Job file upload (ZIP/KML/SHP) |
|
|
| `/api/pilots` | pilot.js | Pilot management |
|
|
| `/api/vehicles` | vehicle.js | Aircraft management |
|
|
| `/api/billing` | billing.js | Stripe subscription billing |
|
|
| `/api/subscription` | subscription.js | Subscription plans |
|
|
| `/api/invoices` | invoice.js | Invoice management |
|
|
| `/api/invoice_settings` | invoice_settings.js | Invoice templates |
|
|
| `/api/partners` | partner.js | Partner org and system users |
|
|
| `/api/dlq/:queue` | dlq.js | Dead Letter Queue management |
|
|
| `/api/export` | export.js | Data export (CSV/IIF) |
|
|
| `/api/v1` | api_pub.js | Public data export API (API-key auth) |
|
|
| `/api/health` | health.js | Health check endpoint |
|
|
| `/stripe_webhooks` | subscription_webhooks.js | Stripe event webhook |
|
|
|
|
#### Key server-side helpers
|
|
|
|
| Helper | Purpose |
|
|
|---|---|
|
|
| `helpers/constants.js` | Frozen enums (UserTypes, AppStatus, etc.) |
|
|
| `helpers/env.js` | Typed environment variable access |
|
|
| `helpers/subscription_util.js` | Stripe SDK wrappers |
|
|
| `helpers/job_util.js` | Job state machine logic |
|
|
| `helpers/geo_util.js` | Geospatial calculations (turf.js) |
|
|
| `helpers/satloc_log_parser.js` | Binary SatLoc log file parser |
|
|
| `helpers/satloc_application_processor.js` | Spray statistics from parsed logs |
|
|
| `helpers/mailer.js` | Transactional email |
|
|
| `helpers/logger.js` | Pino structured logging |
|
|
|
|
---
|
|
|
|
### 4-3 GPS Server
|
|
|
|
**Location:** `trunk/Development/gps-server/`
|
|
**Entry point:** `gps-server.js`
|
|
**Ports:** TCP 6080 (AgNav), TCP 6082 (RAP)
|
|
|
|
A low-level TCP socket server that receives binary GPS telemetry from field console navigation aid systems (AgNav, RAP) in real-time.
|
|
|
|
#### How it works
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant D as AgNav / RAP Console System
|
|
participant G as GPS Server (TCP)
|
|
participant MDB as MongoDB
|
|
participant RBT as RabbitMQ
|
|
|
|
D->>G: TCP binary packet
|
|
G->>G: Parse (AgNavParser / RAPParser)
|
|
G->>MDB: Upsert location + location_cache
|
|
G->>RBT: Publish to "gdata" queue
|
|
```
|
|
|
|
Two protocol variants are configured via `PROTOCOL` env var:
|
|
|
|
| Protocol | Port | Device type |
|
|
|---|---|---|
|
|
| `AGNAV` | 6080 | AgNav console system (Platinum, Titanium) |
|
|
| `RAP` | 6082 | RAP binary protocol (external tracking devices) |
|
|
|
|
The GPS Server writes every position to MongoDB (`locations` + `location_cache` collections) and simultaneously publishes to a RabbitMQ queue `gdata`, which the Track Server consumes for live SSE streaming.
|
|
|
|
---
|
|
|
|
### 4-4 Track Server
|
|
|
|
**Location:** `trunk/Development/track-server/`
|
|
**Entry point:** `track-server.js`
|
|
**Protocol:** HTTP/2 (via `spdy`) + Server-Sent Events
|
|
|
|
The Track Server bridges the real-time GPS queue to browser clients using SSE channels. Each client subscribes to one or more vehicle channels; the server pushes position updates as they arrive from RabbitMQ.
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant RBT as RabbitMQ gdata
|
|
participant TS as Track Server
|
|
participant BR as Browser (SSE client)
|
|
|
|
RBT-->>TS: GPS data message
|
|
TS->>TS: setVehGpsData(data)
|
|
TS->>BR: SSE event (vehicle position)
|
|
```
|
|
|
|
Authentication is JWT-based — clients obtain a short-lived track token from the API Server, then connect to Track Server with it.
|
|
|
|
---
|
|
|
|
### 4-5 SatLoc Integration Service
|
|
|
|
**Location:** `trunk/Development/satloc/`
|
|
**Purpose:** Batch job of importing completed flight logs from the SatLoc Cloud partner system.
|
|
|
|
SatLoc is an aerial guidance hardware vendor. When an applicator uses SatLoc hardware, their flight logs are stored in SatLoc's cloud. This service:
|
|
|
|
1. Authenticates with SatLoc Cloud (`satloccloudfc.com`)
|
|
2. Retrieves aircraft log metadata
|
|
3. Downloads binary log files
|
|
4. Parses the proprietary binary format
|
|
5. Creates `ApplicationDetail` records in AgMission's database
|
|
|
|
The core C# parsing logic (`frmMain.cs`, `APIObjects.cs`) is the original reference implementation; the production path is the Node.js port in `satloc-api.js` and the server-side `helpers/satloc_log_parser.js`.
|
|
|
|
---
|
|
|
|
### 4-6 Background Workers
|
|
|
|
All workers are independent Node.js processes managed by PM2. They communicate via RabbitMQ queues and share the same MongoDB instance as the API Server.
|
|
|
|
#### Worker overview
|
|
|
|
```mermaid
|
|
graph LR
|
|
API["API Server"] -- "publish jobs" --> JQ["jobs queue<br/>(RabbitMQ)"]
|
|
API -- "publish partner_tasks" --> PQ["partner_tasks queue<br/>(RabbitMQ)"]
|
|
JQ --> JW["Job Worker<br/>job_worker.js"]
|
|
PQ --> PSW["Partner Sync Worker<br/>partner_sync_worker.js"]
|
|
PPW["Partner Polling Worker<br/>(cron every 15 min)"] -- "publish partner_tasks" --> PQ
|
|
PPW -- "REST poll" --> SLCAPI["SatLoc Cloud API"]
|
|
IW["Invoice Worker<br/>(cron every 1 min)"] --> MDB["MongoDB"]
|
|
CW["Cleanup Worker<br/>(cron weekly)"] --> MDB
|
|
JW --> MDB
|
|
PSW --> MDB
|
|
PSW -- "REST" --> SLCAPI
|
|
```
|
|
|
|
#### Job Worker
|
|
|
|
Consumes the `jobs` RabbitMQ queue. Triggered when a user uploads a job file via the web UI.
|
|
|
|
Responsibilities:
|
|
- Extract ZIP archives containing job data files
|
|
- Parse AgNav binary (`.agn`), KML, and Shapefile formats
|
|
- Calculate sprayed area, coverage geometry, application statistics
|
|
- Create `Application`, `ApplicationFile`, and `ApplicationDetail` records
|
|
- Use Redis for deduplication and temporary state
|
|
|
|
#### Invoice Worker
|
|
|
|
Cron-driven (every minute). Manages Job Invoicing—the lifecycle of applicator invoices for jobs performed on behalf of clients. Handles invoice state transitions and late-payment notifications.
|
|
|
|
**Note:** Job Invoicing is separate from SaaS subscription management (which is handled by Stripe webhooks in the API Server).
|
|
|
|
```mermaid
|
|
stateDiagram-v2
|
|
direction LR
|
|
[*] --> Draft : invoice created
|
|
Draft --> Open : openDate reached
|
|
Open --> Overdue : dueDate passed
|
|
Open --> Paid : payment received
|
|
Overdue --> Paid : late payment
|
|
Paid --> [*]
|
|
```
|
|
|
|
#### Partner Sync Worker
|
|
|
|
Consumes the `partner_tasks` queue. Handles two task types:
|
|
|
|
| Task type | Action |
|
|
|---|---|
|
|
| `UPLOAD_PARTNER_JOB` | Pushes a job (waypoints, boundaries) to SatLoc Cloud for the assigned aircraft |
|
|
| `PROCESS_PARTNER_LOG` | Parses a downloaded SatLoc binary log file and creates `ApplicationDetail` records |
|
|
|
|
#### Partner Data Polling Worker
|
|
|
|
Cron-driven (every 15 min in production, every 1 min in development).
|
|
|
|
```mermaid
|
|
graph TD
|
|
A["Cron trigger"] --> B["Find JobAssigns<br/>status = UPLOADED"]
|
|
B --> C["Group by partner + customer"]
|
|
C --> D["Call SatLoc: GetAircraftLogs"]
|
|
D --> E{"New log<br/>files?"}
|
|
E -- No --> F["Done"]
|
|
E -- Yes --> G["Download log file<br/>to local storage"]
|
|
G --> H["Create PartnerLogTracker<br/>PENDING → DOWNLOADED"]
|
|
H --> I["Enqueue PROCESS_PARTNER_LOG<br/>to partner_tasks queue"]
|
|
```
|
|
|
|
#### Cleanup Worker
|
|
|
|
Weekly cron job. Hard-deletes records that have been soft-deleted (`markedDelete: true`) and older than a retention period. Applies to customers, jobs, pilots, vehicles, and related entities.
|
|
|
|
---
|
|
|
|
### 4-7 Maintainer Service
|
|
|
|
**Location:** `trunk/Development/maintainer/`
|
|
**Entry point:** `index.js`
|
|
|
|
A lightweight cron-based utility for database maintenance tasks not suitable for the main server. Scheduled tasks:
|
|
|
|
| Task | Schedule (prod) | Description |
|
|
|---|---|---|
|
|
| `cleanMarkedDeleteData` | Weekly (Sunday 01:00 UTC) | Remove soft-deleted customer records |
|
|
|
|
Connects to MongoDB using the same models as the API Server (shared `model/` layer).
|
|
|
|
---
|
|
|
|
## 5 Data Architecture
|
|
|
|
### MongoDB collections
|
|
|
|
```mermaid
|
|
erDiagram
|
|
USER {
|
|
ObjectId _id
|
|
string kind
|
|
string email
|
|
string name
|
|
string userType
|
|
boolean active
|
|
ObjectId parent
|
|
}
|
|
CUSTOMER {
|
|
ObjectId _id
|
|
string name
|
|
ObjectId byPuid
|
|
}
|
|
JOB {
|
|
number _id
|
|
string name
|
|
ObjectId customer
|
|
ObjectId byPuid
|
|
number status
|
|
Date startDate
|
|
}
|
|
JOB_ASSIGN {
|
|
ObjectId _id
|
|
number job
|
|
ObjectId user
|
|
number status
|
|
string extJobId
|
|
}
|
|
APPLICATION {
|
|
ObjectId _id
|
|
number jobId
|
|
string fileName
|
|
Date startDateTime
|
|
Date endDateTime
|
|
number totalSprayed
|
|
}
|
|
APPLICATION_DETAIL {
|
|
ObjectId _id
|
|
ObjectId appId
|
|
number lat
|
|
number lon
|
|
number rate
|
|
Date gdt
|
|
}
|
|
SUBSCRIPTION {
|
|
ObjectId _id
|
|
ObjectId byPuid
|
|
string stripeSubId
|
|
string planKey
|
|
string status
|
|
}
|
|
INVOICE {
|
|
ObjectId _id
|
|
ObjectId byPuid
|
|
number status
|
|
Date openDate
|
|
Date dueDate
|
|
}
|
|
PARTNER_LOG_TRACKER {
|
|
ObjectId _id
|
|
string status
|
|
ObjectId jobAssignId
|
|
string localFilePath
|
|
}
|
|
|
|
USER ||--o{ JOB : "creates (byPuid)"
|
|
CUSTOMER ||--o{ JOB : "associated"
|
|
JOB ||--o{ JOB_ASSIGN : "assigned to"
|
|
JOB_ASSIGN }o--|| USER : "pilot/device"
|
|
JOB ||--o{ APPLICATION : "contains"
|
|
APPLICATION ||--o{ APPLICATION_DETAIL : "detail records"
|
|
USER ||--o{ SUBSCRIPTION : "holds"
|
|
USER ||--o{ INVOICE : "receives"
|
|
JOB_ASSIGN ||--o{ PARTNER_LOG_TRACKER : "tracks"
|
|
```
|
|
|
|
### User type hierarchy
|
|
|
|
The `User` model uses a Mongoose discriminator pattern to represent multiple actor types from one collection:
|
|
|
|
| `userType` code | Kind | Description |
|
|
|---|---|---|
|
|
| `0` | ADMIN | Platform administrator |
|
|
| `1` | APP | Applicator (main operator account) |
|
|
| `2` | APP_ADM | Applicator admin |
|
|
| `3` | CLIENT | Client / grower (read-only) |
|
|
| `4` | OFFICER | Field officer |
|
|
| `5` | PILOT | Pilot |
|
|
| `6` | INSPECTOR | Inspector |
|
|
| `9` | DEVICE | Aircraft / guidance unit |
|
|
| `20` | PARTNER | Partner organization (e.g., SatLoc) |
|
|
| `21` | PARTNER_SYSTEM_USER | Customer account in partner system |
|
|
|
|
---
|
|
|
|
## 6 Message Queue Architecture
|
|
|
|
RabbitMQ is used for all async work. Queue names are auto-prefixed with `dev_` in non-production environments.
|
|
|
|
```mermaid
|
|
graph LR
|
|
subgraph "Producers"
|
|
API["API Server"]
|
|
PPW["Partner Polling Worker"]
|
|
GPS["GPS Server"]
|
|
end
|
|
|
|
subgraph "Queues (RabbitMQ)"
|
|
JQ["jobs"]
|
|
PQ["partner_tasks"]
|
|
PDLQ["partner_tasks_failed<br/>(DLQ)"]
|
|
GQ["gdata"]
|
|
end
|
|
|
|
subgraph "Consumers"
|
|
JW["Job Worker"]
|
|
PSW["Partner Sync Worker"]
|
|
TRK["Track Server"]
|
|
end
|
|
|
|
API --> JQ
|
|
API --> PQ
|
|
PPW --> PQ
|
|
GPS --> GQ
|
|
JQ --> JW
|
|
PQ --> PSW
|
|
PSW -- "on max retries" --> PDLQ
|
|
GQ --> TRK
|
|
```
|
|
|
|
The Dead Letter Queue (`partner_tasks_failed`) is managed through the `/api/dlq/:queueName/*` API endpoints, which provide list, retry, and purge operations.
|
|
|
|
---
|
|
|
|
## 7 Partner Integration Architecture
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant UI as Web Client
|
|
participant API as API Server
|
|
participant PSW as Partner Sync Worker
|
|
participant PPW as Partner Polling Worker
|
|
participant SLC as SatLoc Cloud
|
|
|
|
UI->>API: Assign job to SatLoc device
|
|
API->>API: Create JobAssign (status=NEW)
|
|
API->>PSW: Enqueue UPLOAD_PARTNER_JOB
|
|
PSW->>SLC: POST /api/Satloc/UploadJobData
|
|
SLC-->>PSW: extJobId
|
|
PSW->>API: Update JobAssign (status=UPLOADED, extJobId)
|
|
|
|
Note over PPW: Cron: every 15 min
|
|
PPW->>API: Find JobAssigns status=UPLOADED
|
|
PPW->>SLC: GET /api/Satloc/GetAircraftLogs
|
|
SLC-->>PPW: Log list
|
|
PPW->>SLC: GET /api/Satloc/GetAircraftLogData
|
|
SLC-->>PPW: Binary log file
|
|
PPW->>PPW: Store file locally (SATLOC_STORAGE_PATH)
|
|
PPW->>API: Create PartnerLogTracker (DOWNLOADED)
|
|
PPW->>PSW: Enqueue PROCESS_PARTNER_LOG
|
|
PSW->>PSW: Parse binary log (SatLocLogParser)
|
|
PSW->>API: Create ApplicationDetail records
|
|
```
|
|
|
|
### Partner credential model
|
|
|
|
Each customer that uses a SatLoc device has a dedicated `PartnerSystemUser` record (userType=21) that stores their SatLoc `companyId`, `partnerUserId`, and API key. This isolates customer data within the partner system.
|
|
|
|
---
|
|
|
|
## 8 Billing and Subscription Architecture
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant UI as Web Client
|
|
participant API as API Server
|
|
participant STR as Stripe
|
|
|
|
UI->>API: Select subscription plan
|
|
API->>STR: Create SetupIntent or Subscription
|
|
STR-->>API: Client secret
|
|
UI->>STR: Confirm card (3DS if needed)
|
|
STR->>API: POST /stripe_webhooks (subscription events)
|
|
API->>API: Update Subscription record
|
|
```
|
|
|
|
Note: SaaS subscription management (tiers, billing cycles) is handled through Stripe webhooks. Job Invoicing (applicators invoicing clients for jobs performed) is a separate function managed by the Invoice Worker.
|
|
|
|
**SaaS Subscription tiers** (mapped to Stripe price IDs via env vars):
|
|
|
|
| Tier | Env key prefix | Description |
|
|
|---|---|---|
|
|
| Essential | `ESS_1` … `ESS_5` | Entry-level operator plans |
|
|
| Enterprise | `ENT_1` … `ENT_4` | High-volume operator plans |
|
|
| Add-on | `ADDON_1` | Additional features, refer to Live Tracking service |
|
|
|
|
---
|
|
|
|
## 9 Authentication and Authorization
|
|
|
|
### JWT authentication
|
|
|
|
All API Server endpoints (except signup and Stripe webhooks) require a JWT Bearer token:
|
|
|
|
```
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
Tokens are issued on login and carry `userType`, `byPuid` (applicator ID), and `userId`. The `checkUser` middleware validates the token and attaches the user to `req.user`.
|
|
|
|
### API key authentication Public Export API
|
|
|
|
The `/api/v1/` public export endpoints use an `X-API-Key` header instead of JWT. Keys are bcrypt-hashed and stored in the `ApiKey` collection. Each key is scoped to a specific applicator (`byPuid`), so data access is automatically isolated.
|
|
|
|
### Authorization model
|
|
|
|
```
|
|
ADMIN — full platform access
|
|
APP — manages own organization (pilots, vehicles, jobs, customers)
|
|
APP_ADM — same as APP within parent organization
|
|
CLIENT — read-only access to own job results
|
|
PILOT — limited: download job assignments
|
|
OFFICER — field oversight
|
|
INSPECTOR — read-only job inspection
|
|
PARTNER — manages partner organization
|
|
PARTNER_SYSTEM_USER — customer credentials for a specific partner
|
|
```
|
|
|
|
---
|
|
|
|
## 10 Infrastructure and Deployment
|
|
|
|
### Production server topology
|
|
|
|
```mermaid
|
|
graph TD
|
|
INT["Internet"] --> NX["Nginx<br/>SSL termination<br/>port 443"]
|
|
NX -- "/ → :7000" --> API["agmission-prod<br/>(PM2)"]
|
|
NX -- "/track → :4200" --> TRK["track_server<br/>(PM2)"]
|
|
API --> MDB["MongoDB<br/>Replica Set<br/>rs0"]
|
|
API --> RBT["RabbitMQ"]
|
|
API --> RDS["Redis"]
|
|
GPS1["gps_server-agnav<br/>TCP :6080 (PM2)"] --> MDB
|
|
GPS1 --> RBT
|
|
GPS2["gps_server-rap<br/>TCP :6082 (PM2)"] --> MDB
|
|
GPS2 --> RBT
|
|
RBT --> JW["job_worker<br/>(PM2)"]
|
|
RBT --> PSW["partner_sync_worker<br/>(PM2)"]
|
|
PPW["partner_data_polling_worker<br/>(PM2)"] --> MDB
|
|
PPW --> RBT
|
|
IW["invoice_worker<br/>(PM2)"] --> MDB
|
|
CW["cleanup_worker<br/>(PM2)"] --> MDB
|
|
```
|
|
|
|
### PM2 managed processes
|
|
|
|
| PM2 name | Entry point | Description |
|
|
|---|---|---|
|
|
| `agmission-prod` | `server.js` | Main API server |
|
|
| `track_server` | `track-server.js` | Live tracking |
|
|
| `gps_server-agnav` | `gps-server.js` | AgNav TCP receiver |
|
|
| `gps_server-rap` | `gps-server.js` | RAP TCP receiver |
|
|
| `job_worker` / `job-importer` | `workers/job_worker.js` | Job file processing |
|
|
| `invoice_worker` | `workers/invoice_worker.js` | Invoice automation |
|
|
| `cleanup_worker` | `workers/cleanup_worker.js` | Soft-delete cleanup |
|
|
| `partner_sync_worker` | `workers/partner_sync_worker.js` | Partner job/log sync |
|
|
| `partner_data_polling_worker` | `workers/partner_data_polling_worker.js` | Partner log polling |
|
|
|
|
### Deployment flow
|
|
|
|
Deployments are performed using `trunk/Others/scripts/deploy/agm-deploy.sh`. See [DEPLOYMENT.md](DEPLOYMENT.md) for full instructions.
|
|
|
|
```mermaid
|
|
graph LR
|
|
DEV["Local dev machine<br/>(SVN trunk or branch)"] -- "rsync over SSH<br/>agm-deploy.sh" --> PROD["Production server<br/>agmission-1.agnav.com:22222"]
|
|
PROD --> PM2["pm2 reload<br/>agmission-prod"]
|
|
```
|
|
|
|
---
|
|
|
|
## 11 Key Data Flows
|
|
|
|
### Job creation and assignment
|
|
|
|
```mermaid
|
|
graph TD
|
|
A["Applicator creates job<br/>in Web UI"] --> B["POST /api/jobs"]
|
|
B --> C["Job record created<br/>in MongoDB"]
|
|
C --> D["Upload job file<br/>(ZIP / KML / SHP)"]
|
|
D --> E["POST /api/upload"]
|
|
E --> F["File stored on disk<br/>job-uploads/"]
|
|
F --> G["Job message published<br/>to 'jobs' queue"]
|
|
G --> H["Job Worker consumes<br/>message"]
|
|
H --> I["Unzip and parse files"]
|
|
I --> J["Calculate spray statistics"]
|
|
J --> K["Create Application +<br/>ApplicationDetail records"]
|
|
K --> L["Applicator assigns job<br/>to pilot / device / partner"]
|
|
L --> M{"Partner?"}
|
|
M -- "No (internal)" --> N["JobAssign created<br/>status=NEW"]
|
|
M -- "Yes (SatLoc)" --> O["UPLOAD_PARTNER_JOB<br/>enqueued"]
|
|
O --> P["Partner Sync Worker<br/>uploads to SatLoc Cloud"]
|
|
P --> Q["JobAssign status=UPLOADED<br/>+ extJobId stored"]
|
|
```
|
|
|
|
### Real-time GPS tracking
|
|
|
|
```mermaid
|
|
graph LR
|
|
HW["AgNav Device"] -- "binary TCP" --> GPS["GPS Server"]
|
|
GPS --> MDB["MongoDB<br/>locations"]
|
|
GPS --> RBT["RabbitMQ gdata"]
|
|
RBT --> TRK["Track Server"]
|
|
TRK -- "SSE push" --> UI["Browser Map"]
|
|
```
|
|
|
|
---
|
|
|
|
## 12 Repository Layout
|
|
|
|
```
|
|
AgMission/
|
|
├── trunk/
|
|
│ ├── Development/ ← Active source code (see below)
|
|
│ ├── Documents/ ← Architecture, requirements, design docs
|
|
│ └── Others/
|
|
│ ├── configs/ ← PM2 JSON configs for deployment target
|
|
│ └── scripts/ ← Operations scripts
|
|
│ ├── deploy/ ← Deployment automation (agm-deploy.sh)
|
|
│ ├── backup_agm.sh ← MongoDB backup + rsync to NAS
|
|
│ └── start_pm2_apps.sh
|
|
│
|
|
├── branches/ ← Feature branches (SVN layout)
|
|
│ ├── subscription-invoicing/
|
|
│ ├── subscription-signup/
|
|
│ ├── job-invoicing/
|
|
│ ├── data-export-api/
|
|
│ └── satloc-resume/
|
|
│
|
|
└── tags/ ← Release snapshots
|
|
└── release-3.2.1/
|
|
|
|
trunk/Development/
|
|
├── client/ Angular SPA
|
|
├── server/ Express API server + workers
|
|
│ ├── controllers/
|
|
│ ├── helpers/
|
|
│ ├── middlewares/
|
|
│ ├── model/
|
|
│ ├── routes/
|
|
│ ├── services/
|
|
│ └── workers/
|
|
├── gps-server/ TCP GPS receiver
|
|
├── track-server/ SSE live tracking
|
|
├── satloc/ SatLoc integration (batch import)
|
|
├── maintainer/ Scheduled DB maintenance
|
|
├── shared/ Shared DB utilities and models
|
|
└── libs/ Bundled third-party libs (shapefile, etc.)
|
|
```
|
|
|
|
---
|
|
|
|
*For deployment instructions see [DEPLOYMENT.md](DEPLOYMENT.md).*
|
|
*For the server-side REST API reference see [`server/docs/API_SPECIFICATION.md`](../Development/server/docs/API_SPECIFICATION.md).*
|
|
*For partner integration details see [`server/docs/PARTNER_INTEGRATION_ARCHITECTURE.md`](../Development/server/docs/PARTNER_INTEGRATION_ARCHITECTURE.md).*
|