Documentation

Complete guide to PatrolLink architecture, API, and deployment.

Project Summary

PatrolLink is a comprehensive security management platform that enables organizations to manage their security guards, track patrols in real time, and maintain operational oversight through an intuitive dashboard. The system consists of a cross-platform mobile app for guards, a web-based admin dashboard, and a robust RESTful API backend.

The platform solves the challenge of managing distributed security teams by providing real-time GPS tracking, structured patrol logging, incident reporting, and role-based access control — all from a single integrated system.

Core Capabilities

Technology Stack

React Native + Expo

Cross-platform mobile app with GPS, camera, and push notifications.

Express.js

Node.js backend providing RESTful API, session management, and middleware.

PostgreSQL

Relational database for users, patrols, assignments, logs, and sessions.

Directus

Headless CMS providing auto-generated REST API for content management.

JWT + bcryptjs

Token-based auth with password hashing for secure access control.

EJS

Server-side templating for web views and documentation.

Technology Justification

TechnologyJustification
React Native + ExpoSingle codebase for iOS and Android; built-in GPS, camera, and notification APIs.
Express.jsMinimal overhead, flexible middleware, well-suited for REST APIs.
PostgreSQLACID-compliant, excellent with geospatial data, reliable for security data.
DirectusHeadless CMS auto-generates REST APIs; provides admin UI without building CRUD views.
JWTStateless authentication, works well with mobile clients.
EJSSimple server-side rendering; no client framework needed for docs and auth pages.

Why PatrolLink?

Frontend Architecture

The PatrolLink frontend consists of two distinct surfaces: the mobile app for guards and server-rendered web views for documentation and authentication.

Mobile App (React Native + Expo)

The guard-facing mobile application is built with React Native using the Expo managed workflow. This provides cross-platform compatibility (iOS and Android) with access to native device features:

The app uses Expo Router for file-based navigation and communicates with the backend via fetch() calls to the Express.js API.

Web Views (EJS)

Documentation, authentication (login/signup), pricing, and the admin dashboard are server-rendered using EJS templates. This approach avoids the complexity of a separate frontend framework for pages that are primarily content or form-based. Templates are served directly by the Express server with inline CSS and minimal client-side JavaScript.

Express.js Server

The backend is built on Express.js 5 and serves as the central API gateway. It handles authentication, session management, routing, rate limiting, and communication with the database and Directus CMS.

Server Configuration

Authentication & Authorization

PatrolLink uses a dual authentication system:

Auth APIs

All API responses follow the format { "message": "...", "data": ... } for success or { "error": "...", "message": "..." } for errors.

POST /api/register PUBLIC
Register a new user account.
ParameterTypeRequiredDescription
firstNamestringYesUser's first name
lastNamestringYesUser's last name
phonestringYesPhone number (e.g., +254712345678)
passwordstringYesPassword (min 8 chars, must include uppercase, lowercase, digit, special char)
rolestringNoUser role (default: "guard")
companyCodestringNoOrganization invite code
// Example response (201 Created)
{
  "message": "User registered successfully",
  "user": { "id": "abc123", "name": "John Doe", "phone": "+254712345678", "role": "guard" }
}
201 Created400 Bad Request409 Conflict
POST /api/login PUBLIC
Authenticate and receive a JWT token.
ParameterTypeRequiredDescription
phonestringYesRegistered phone number
passwordstringYesAccount password
// Example response (200 OK)
{
  "message": "Login successful",
  "user": { "id": "abc123", "role": "admin" },
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "returnTo": "/admin/dashboard"
}
200 OK400 Bad Request401 Unauthorized
POST /api/logout PUBLIC
End user session and logout.
{ "message": "Logout successful" }
GET /api/me PROTECTED
Get the current authenticated user's profile.
200 OK401 Unauthorized
POST /api/verify-token PUBLIC
Verify whether a JWT token is valid.
ParameterTypeRequiredDescription
tokenstringYesJWT token to validate
{ "valid": true, "user": { "id": "abc123", "role": "guard" } }
200 OK400 Bad Request401 Unauthorized
DELETE /api/account PROTECTED
Delete the authenticated user's account and all associated data.
GET /api/health PUBLIC
Health check endpoint.

Guard APIs

Endpoints used by the mobile app for assignments, patrol lifecycle, location tracking, and logs.

GET/api/my-assignmentsPROTECTED
Fetch assignments for the authenticated guard.
{ "assignments": [ { "id": "asg1", "location": "loc1", "assigned_areas": "Gate A", "start_time": "09:00", "end_time": "17:00" } ] }
PUT/api/my-assignmentsPROTECTED
Update the authenticated guard's assignment details.
ParameterTypeRequiredDescription
locationstringYesLocation ID
assigned_areasstringYesComma-separated areas
start_timestringYesShift start time
end_timestringYesShift end time
GET/api/locationsPROTECTED
Fetch all locations in the authenticated user's organization.
GET/api/patrolsPROTECTED
Fetch guard patrol history. Supports ?limit=10 and ?sort=-start_time.
POST/api/patrolsPROTECTED
Start a new patrol session.
ParameterTypeRequiredDescription
start_timestringYesPatrol start timestamp
user_idstringYesGuard user ID
durationnumberNoExpected duration in minutes
location_dataarrayNoStarting GPS coordinates
PATCH/api/patrols/:idPROTECTED
Update or end a patrol. Set end_time and status to complete.
PATCH/api/patrols/:id/locationPROTECTED
Append incremental GPS points to an active patrol route.
GET/api/logsPROTECTED
Fetch logs created by the authenticated guard. Supports ?limit=50 and ?sort=-timestamp.
POST/api/logsPROTECTED
Create a new guard log entry.
ParameterTypeRequiredDescription
titlestringYesLog title
descriptionstringYesLog details
categorystringYesactivity, unusual, incident, checkpoint, other
imagesstring|arrayNoImage data or URIs
patrol_idstringNoRelated patrol ID
POST/api/checkpoint/latePROTECTED
Report a late checkpoint arrival.

Admin APIs

Endpoints for the admin dashboard to manage guards, assignments, locations, patrols, and logs. All scoped to the authenticated admin's organization via invite_code.

GET/api/admin/dashboardPROTECTED
Admin dashboard overview with organization stats.
GET/api/admin/dashboard/guardsPROTECTED
Guard list with online status, last seen, and assignment info.
GET/api/admin/dashboard/supervisorsPROTECTED
List supervisors in the organization.
GET/api/admin/dashboard/organizationsPROTECTED
List organizations (super-admin only).
GET/api/admin/guardsPROTECTED
List all guards with enriched assignment and patrol data.
FieldTypeDescription
idstringGuard user ID
first_namestringGuard first name
last_namestringGuard last name
phonestringPhone number
locationstringResolved location name
is_onlinebooleanCurrently on active patrol
last_seen_displaystringOnline status or timestamp
DELETE/api/admin/guards/:idPROTECTED
Remove a guard and cascade-delete assignments, logs, and patrols.
{ "message": "Guard removed successfully", "deleted": { "assignments": 2, "logs": 8, "patrols": 5 } }
POST/api/admin/assignmentsPROTECTED
Create a new assignment for a guard.
ParameterTypeRequiredDescription
user_idstringYesGuard user ID
locationstringYesLocation ID
assigned_areasstringYesComma-separated areas
start_timestringYesStart time (e.g., 09:00)
end_timestringYesEnd time (e.g., 17:00)
GET/api/admin/patrolsPROTECTED
Admin patrol feed with guard names. Supports ?limit=50 and ?sort=-start_time.
GET/api/admin/logsPROTECTED
Admin logs feed with location resolution. Supports ?limit=50 and ?sort=-timestamp.
GET/api/admin/locationsPROTECTED
List all locations in the organization.
POST/api/admin/locationsPROTECTED
Create a new location.
ParameterTypeRequiredDescription
namestringYesLocation name
assigned_areasstringNoComma-separated areas
PATCH/api/admin/locations/:idPROTECTED
Update a location's name or assigned areas.
DELETE/api/admin/locations/:idPROTECTED
Delete a location from the organization.
GET/api/admin/notificationsPROTECTED
Admin notifications feed.
POST/api/admin/push-tokenPROTECTED
Register a push notification token.
DELETE/api/admin/push-tokenPROTECTED
Remove a push notification token.
GET/api/admin/dashboard/searchPROTECTED
Search organizations by name.
GET/api/admin/dashboard/payments/:orgIdPROTECTED
Get payment records for an organization.
POST/api/admin/dashboard/payments/:id/mark-paidPROTECTED
Mark a payment as paid.

Additional APIs

GET/api/assignmentsPROTECTED
Fetch all assignment records.
GET/api/organizations/invite-codesPROTECTED
List invite codes for all registered organizations.
POST/api/organizations/validate-invite-codePUBLIC
Validate whether an organization invite code exists.
{ "valid": true, "message": "Invite code is valid" }
GET/api/assignmentsPROTECTED
Fetch all assignment records from backend storage.

Middleware

MiddlewarePurpose
verifyTokenMiddlewareValidates JWT from Authorization: Bearer <token> header and attaches decoded user to req.user
requireRole(...roles)Restricts access to specified roles (e.g., requireRole('admin', 'supervisor'))
requireAuthSession-based auth guard for web views; redirects unauthenticated users to login
loginLimiterRate limiter for login/register endpoints to prevent brute-force attacks
patrolLimiterRate limiter for patrol creation to prevent spam
logLimiterRate limiter for log creation
checkpointLateLimiterRate limiter for late checkpoint reports

Error Handling

All error responses follow a consistent format:

{ "error": "ErrorType", "message": "Human-readable description" }
StatusErrorDescription
400Validation ErrorMissing required fields or invalid data
401UnauthorizedInvalid credentials or missing token
403ForbiddenInsufficient role permissions
404Not FoundResource not found
409ConflictDuplicate entry or conflicting state
500Internal Server ErrorServer-side failure

Database — PostgreSQL

PatrolLink uses PostgreSQL as its primary database for storing sessions and executing direct queries. User and content data is managed through Directus, which also uses PostgreSQL as its backing store.

Schema Overview

The database schema is managed by Directus and includes custom collections for PatrolLink-specific data. The pg library is used for direct PostgreSQL queries, primarily for session storage and admin dashboard queries.

Key Tables / Collections

CollectionDescription
usersUser accounts with roles (guard, supervisor, admin), phone numbers, and organization codes
organizationsOrganization records with invite codes, subscription tiers, and rates
assignmentsGuard-to-location assignments with operating hours and areas
locationsSite locations with assigned areas linked to organizations
patrolsPatrol sessions with start/end times, status, and GPS route data
logsGuard log entries with categories, descriptions, and optional images
notificationsPush notification records for admin alerts and guard updates
sessionExpress session store for web authentication

CMS — Directus

Directus serves as the headless CMS layer, providing auto-generated REST APIs for CRUD operations on all data collections. It eliminates the need to build custom admin interfaces for data management while offering a powerful SDK for programmatic access.

Configuration

Environment Variables

Create a .env file in the backend root with the following:

# Server
APIPORT=5000
NODE_ENV=development

# JWT Secret (change in production)
JWT_SECRET=your-super-secret-jwt-key

# Directus Configuration
DIRECTUS_URL=http://your-directus-instance.com
DIRECTUS_TOKEN=your-directus-static-token

# PostgreSQL
DB_USER=postgres
DB_HOST=localhost
DB_NAME=omniwatch
DB_PASSWORD=your-password
DB_PORT=5432
DB_SSL=false

# CORS Allowed Origins (optional, comma-separated)
CORS_ALLOWED_ORIGINS=http://localhost:5000,http://localhost:8081

Running the App

# Install dependencies
npm install

# Development mode with auto-reload
npm run dev

# Production mode with PM2
npm run prod

# Or start directly
npm start