2024-03-12 18:31:17 +13:00
const path = require ( 'path' ) ;
const express = require ( 'express' ) ;
2024-03-13 03:46:50 +13:00
const protobufjs = require ( "protobufjs" ) ;
2024-03-12 18:31:17 +13:00
2024-03-13 03:46:50 +13:00
// create prisma db client
const { PrismaClient } = require ( "@prisma/client" ) ;
const prisma = new PrismaClient ( ) ;
2024-03-12 18:31:17 +13:00
2024-03-13 03:46:50 +13:00
// return big ints as string when using JSON.stringify
BigInt . prototype . toJSON = function ( ) {
return this . toString ( ) ;
}
2024-03-12 18:31:17 +13:00
2024-03-13 03:46:50 +13:00
// load protobufs
const root = new protobufjs . Root ( ) ;
root . resolvePath = ( origin , target ) => path . join ( _ _dirname , "protos" , target ) ;
root . loadSync ( 'meshtastic/mqtt.proto' ) ;
const HardwareModel = root . lookupEnum ( "HardwareModel" ) ;
const Role = root . lookupEnum ( "Config.DeviceConfig.Role" ) ;
2024-03-12 18:31:17 +13:00
2024-03-13 03:46:50 +13:00
const app = express ( ) ;
2024-03-12 18:31:17 +13:00
2024-03-13 03:46:50 +13:00
// serve files inside the public folder from /
app . use ( '/' , express . static ( path . join ( _ _dirname , 'public' ) ) ) ;
2024-03-12 18:31:17 +13:00
app . get ( '/' , async ( req , res ) => {
res . sendFile ( path . join ( _ _dirname , 'public/index.html' ) ) ;
} ) ;
app . get ( '/api' , async ( req , res ) => {
const links = [
{
"path" : "/api" ,
"description" : "This page" ,
} ,
{
"path" : "/api/v1/nodes" ,
"description" : "Meshtastic nodes in JSON format." ,
} ,
] ;
const html = links . map ( ( link ) => {
return ` <li><a href=" ${ link . path } "> ${ link . path } </a> - ${ link . description } </li> ` ;
} ) . join ( "" ) ;
res . send ( html ) ;
} ) ;
app . get ( '/api/v1/nodes' , async ( req , res ) => {
try {
2024-03-13 03:46:50 +13:00
// get nodes from db
const nodes = await prisma . node . findMany ( ) ;
2024-03-12 18:31:17 +13:00
2024-03-13 14:03:00 +13:00
const nodesWithNeighbourInfo = [ ] ;
for ( const node of nodes ) {
nodesWithNeighbourInfo . push ( {
... node ,
node _id _hex : "!" + node . node _id . toString ( 16 ) ,
hardware _model _name : HardwareModel . valuesById [ node . hardware _model ] ? ? "UNKNOWN" ,
role _name : Role . valuesById [ node . role ] ? ? "UNKNOWN" ,
neighbour _info : await prisma . neighbourInfo . findFirst ( {
where : {
node _id : node . node _id ,
} ,
orderBy : {
2024-03-18 22:58:51 +13:00
id : 'desc' ,
2024-03-13 14:03:00 +13:00
} ,
} ) ,
} )
}
2024-03-12 18:31:17 +13:00
res . json ( {
2024-03-13 14:03:00 +13:00
nodes : nodesWithNeighbourInfo ,
2024-03-12 18:31:17 +13:00
} ) ;
} catch ( err ) {
console . error ( err ) ;
res . status ( 500 ) . json ( {
message : "Something went wrong, try again later." ,
2024-03-14 02:39:41 +13:00
} ) ;
}
} ) ;
app . get ( '/api/v1/nodes/:nodeId/device-metrics' , async ( req , res ) => {
try {
const nodeId = parseInt ( req . params . nodeId ) ;
const count = req . query . count ? parseInt ( req . query . count ) : undefined ;
// find node
const node = await prisma . node . findFirst ( {
where : {
node _id : nodeId ,
} ,
} ) ;
// make sure node exists
if ( ! node ) {
res . status ( 404 ) . json ( {
message : "Not Found" ,
} ) ;
}
// get latest device metrics
const deviceMetrics = await prisma . deviceMetric . findMany ( {
where : {
node _id : node . node _id ,
} ,
orderBy : {
id : 'desc' ,
} ,
take : count ,
} ) ;
res . json ( {
device _metrics : deviceMetrics ,
} ) ;
} catch ( err ) {
console . error ( err ) ;
res . status ( 500 ) . json ( {
message : "Something went wrong, try again later." ,
2024-03-16 19:07:02 +13:00
} ) ;
}
} ) ;
2024-03-16 19:29:58 +13:00
app . get ( '/api/v1/nodes/:nodeId/mqtt-metrics' , async ( req , res ) => {
2024-03-16 19:07:02 +13:00
try {
const nodeId = parseInt ( req . params . nodeId ) ;
// find node
const node = await prisma . node . findFirst ( {
where : {
node _id : nodeId ,
} ,
} ) ;
// make sure node exists
if ( ! node ) {
res . status ( 404 ) . json ( {
message : "Not Found" ,
} ) ;
}
2024-03-16 19:29:58 +13:00
// get mqtt topics published to by this node
const queryResult = await prisma . $queryRaw ` select mqtt_topic, count(*) as packet_count, max(created_at) as last_packet_at from service_envelopes where gateway_id = ${ nodeId } group by mqtt_topic order by packet_count desc; ` ;
2024-03-16 19:07:02 +13:00
res . json ( {
2024-03-16 19:29:58 +13:00
mqtt _metrics : queryResult ,
2024-03-16 19:07:02 +13:00
} ) ;
} catch ( err ) {
console . error ( err ) ;
res . status ( 500 ) . json ( {
message : "Something went wrong, try again later." ,
2024-03-12 18:31:17 +13:00
} ) ;
}
} ) ;
2024-03-13 21:02:58 +13:00
app . get ( '/api/v1/stats/hardware-models' , async ( req , res ) => {
try {
// get nodes from db
const results = await prisma . node . groupBy ( {
by : [ 'hardware_model' ] ,
orderBy : {
_count : {
hardware _model : 'desc' ,
} ,
} ,
_count : {
hardware _model : true ,
} ,
} ) ;
const hardwareModelStats = results . map ( ( result ) => {
return {
count : result . _count . hardware _model ,
hardware _model : result . hardware _model ,
hardware _model _name : HardwareModel . valuesById [ result . hardware _model ] ? ? "UNKNOWN" ,
} ;
} ) ;
res . json ( {
hardware _model _stats : hardwareModelStats ,
} ) ;
} catch ( err ) {
console . error ( err ) ;
res . status ( 500 ) . json ( {
message : "Something went wrong, try again later." ,
} ) ;
}
} ) ;
2024-03-18 10:12:47 +13:00
app . get ( '/api/v1/waypoints' , async ( req , res ) => {
try {
// get waypoints from db that have not expired yet
const waypoints = await prisma . waypoint . findMany ( {
where : {
expire : {
gte : Math . floor ( Date . now ( ) / 1000 ) , // now in seconds
} ,
} ,
orderBy : {
id : 'desc' ,
} ,
} ) ;
// ensure we only have the latest unique waypoints
// since ordered by newest first, older entries will be ignored
const uniqueWaypoints = [ ] ;
for ( const waypoint of waypoints ) {
// skip if we already have a newer entry for this waypoint
if ( uniqueWaypoints . find ( ( w ) => w . from === waypoint . from && w . waypoint _id === waypoint . waypoint _id ) ) {
continue ;
}
// first time seeing this waypoint, add to unique list
uniqueWaypoints . push ( waypoint ) ;
}
res . json ( {
waypoints : uniqueWaypoints ,
} ) ;
} catch ( err ) {
res . status ( 500 ) . json ( {
message : "Something went wrong, try again later." ,
} ) ;
}
} ) ;
2024-03-12 18:31:17 +13:00
app . listen ( 8080 ) ;