Compare commits
	
		
			20 Commits
		
	
	
		
			93f0c71a6c
			...
			feat/syste
		
	
	| Author | SHA256 | Date | |
|---|---|---|---|
| 165c79e67b | |||
| c0ac71ebba | |||
| 10e8e14cd6 | |||
| a9c8525e6e | |||
| 39c07fcef0 | |||
| 52d521a6ad | |||
| d3848ac1aa | |||
| d04bdb3ac1 | |||
| 51112b5870 | |||
| 262b4718fc | |||
| 4d0e84b84a | |||
| f969b0a4c0 | |||
| 34167c4d99 | |||
| 79580e16b7 | |||
| abf6b9af82 | |||
| 85e3509731 | |||
| 8a4eadefcb | |||
| e8154d1e13 | |||
| 9b00853d5a | |||
| 574a63b2a3 | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | docker-compose.yml | ||||||
							
								
								
									
										11
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								README.md
									
									
									
									
									
								
							| @ -11,10 +11,11 @@ We recommend using [The Things Network](https://www.thethingsnetwork.org/) (TTN) | |||||||
|     - [Database](#database) |     - [Database](#database) | ||||||
|     - [Configuration](#configuration) |     - [Configuration](#configuration) | ||||||
| 2. [TTN Integration](#ttn-integration) | 2. [TTN Integration](#ttn-integration) | ||||||
| 3. [Add a Location Tracker](#add-a-location-tracker) | 3. [Prometheus Metrics](#prometheus-metrics) | ||||||
|  | 4. [Add a Location Tracker](#add-a-location-tracker) | ||||||
|     - [Onboard SenseCAP T1000-B](#onboard-sensecap-t1000-b) |     - [Onboard SenseCAP T1000-B](#onboard-sensecap-t1000-b) | ||||||
|     - [Register SenseCAP T1000-B](#register-sensecap-t1000-b) |     - [Register SenseCAP T1000-B](#register-sensecap-t1000-b) | ||||||
| 4. [Testing](#testing) | 5. [Testing](#testing) | ||||||
|     - [Testing Webhook](#testing-webhook) |     - [Testing Webhook](#testing-webhook) | ||||||
|     - [Emulating Wigle API](#emulating-wigle-api) |     - [Emulating Wigle API](#emulating-wigle-api) | ||||||
|  |  | ||||||
| @ -49,9 +50,15 @@ Add a addidtional header: | |||||||
| - Type: `authorization`  | - Type: `authorization`  | ||||||
| - Value: `Bearer your-very-secure-token-from-the-env-file` | - Value: `Bearer your-very-secure-token-from-the-env-file` | ||||||
|  |  | ||||||
|  | ### Prometheus Metrics | ||||||
|  | Use `https://your.domain.tld/api/metrics` to retrieve useful insides for monitoring. | ||||||
|  |  | ||||||
| ## Add a Location Tracker | ## Add a Location Tracker | ||||||
| We use the [SenseCAP T1000-B](https://www.seeedstudio.com/SenseCAP-Card-Tracker-T1000-B-p-5698.html) from seeedstudio because of the fair price and multiple location providers. However, you can use any LoRaWAN-enabled tracker that is compatible with TTN and supports the required payload fields. | We use the [SenseCAP T1000-B](https://www.seeedstudio.com/SenseCAP-Card-Tracker-T1000-B-p-5698.html) from seeedstudio because of the fair price and multiple location providers. However, you can use any LoRaWAN-enabled tracker that is compatible with TTN and supports the required payload fields. | ||||||
|  |  | ||||||
|  | ## Troubleshooting | ||||||
|  | Run `journalctl -u locationhub.service -f` to see log output. | ||||||
|  |  | ||||||
| ### Onboard SenseCAP T1000-B | ### Onboard SenseCAP T1000-B | ||||||
| 1. Download and install the App [SenseCraft](https://play.google.com/store/apps/details?id=cc.seeed.sensecapmate)  | 1. Download and install the App [SenseCraft](https://play.google.com/store/apps/details?id=cc.seeed.sensecapmate)  | ||||||
| 2. Skip the user account at startup with `Skip` in the upper right corner | 2. Skip the user account at startup with `Skip` in the upper right corner | ||||||
|  | |||||||
							
								
								
									
										3
									
								
								server/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								server/.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -309,6 +309,3 @@ cython_debug/ | |||||||
| *.vsix | *.vsix | ||||||
|  |  | ||||||
| config.py | config.py | ||||||
|  |  | ||||||
| #docker |  | ||||||
| docker-compose.yml |  | ||||||
							
								
								
									
										38
									
								
								server/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										38
									
								
								server/package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -14,6 +14,7 @@ | |||||||
|         "express": "^4.21.2", |         "express": "^4.21.2", | ||||||
|         "http-status-codes": "^2.3.0", |         "http-status-codes": "^2.3.0", | ||||||
|         "mariadb": "^3.4.0", |         "mariadb": "^3.4.0", | ||||||
|  |         "prom-client": "^15.1.3", | ||||||
|         "reflect-metadata": "^0.2.2", |         "reflect-metadata": "^0.2.2", | ||||||
|         "sequelize": "^6.37.5", |         "sequelize": "^6.37.5", | ||||||
|         "swagger-jsdoc": "^6.2.8", |         "swagger-jsdoc": "^6.2.8", | ||||||
| @ -120,6 +121,15 @@ | |||||||
|       "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", |       "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", | ||||||
|       "license": "MIT" |       "license": "MIT" | ||||||
|     }, |     }, | ||||||
|  |     "node_modules/@opentelemetry/api": { | ||||||
|  |       "version": "1.9.0", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", | ||||||
|  |       "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", | ||||||
|  |       "license": "Apache-2.0", | ||||||
|  |       "engines": { | ||||||
|  |         "node": ">=8.0.0" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "node_modules/@scarf/scarf": { |     "node_modules/@scarf/scarf": { | ||||||
|       "version": "1.4.0", |       "version": "1.4.0", | ||||||
|       "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", |       "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", | ||||||
| @ -386,6 +396,12 @@ | |||||||
|         "url": "https://github.com/sponsors/sindresorhus" |         "url": "https://github.com/sponsors/sindresorhus" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "node_modules/bintrees": { | ||||||
|  |       "version": "1.0.2", | ||||||
|  |       "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.2.tgz", | ||||||
|  |       "integrity": "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==", | ||||||
|  |       "license": "MIT" | ||||||
|  |     }, | ||||||
|     "node_modules/body-parser": { |     "node_modules/body-parser": { | ||||||
|       "version": "1.20.3", |       "version": "1.20.3", | ||||||
|       "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", |       "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", | ||||||
| @ -1434,6 +1450,19 @@ | |||||||
|         "url": "https://github.com/sponsors/jonschlinkert" |         "url": "https://github.com/sponsors/jonschlinkert" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "node_modules/prom-client": { | ||||||
|  |       "version": "15.1.3", | ||||||
|  |       "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-15.1.3.tgz", | ||||||
|  |       "integrity": "sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==", | ||||||
|  |       "license": "Apache-2.0", | ||||||
|  |       "dependencies": { | ||||||
|  |         "@opentelemetry/api": "^1.4.0", | ||||||
|  |         "tdigest": "^0.1.1" | ||||||
|  |       }, | ||||||
|  |       "engines": { | ||||||
|  |         "node": "^16 || ^18 || >=20" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "node_modules/proxy-addr": { |     "node_modules/proxy-addr": { | ||||||
|       "version": "2.0.7", |       "version": "2.0.7", | ||||||
|       "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", |       "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", | ||||||
| @ -1873,6 +1902,15 @@ | |||||||
|         "express": ">=4.0.0 || >=5.0.0-beta" |         "express": ">=4.0.0 || >=5.0.0-beta" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "node_modules/tdigest": { | ||||||
|  |       "version": "0.1.2", | ||||||
|  |       "resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz", | ||||||
|  |       "integrity": "sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==", | ||||||
|  |       "license": "MIT", | ||||||
|  |       "dependencies": { | ||||||
|  |         "bintrees": "1.0.2" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "node_modules/to-regex-range": { |     "node_modules/to-regex-range": { | ||||||
|       "version": "5.0.1", |       "version": "5.0.1", | ||||||
|       "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", |       "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", | ||||||
|  | |||||||
| @ -24,6 +24,7 @@ | |||||||
|     "express": "^4.21.2", |     "express": "^4.21.2", | ||||||
|     "http-status-codes": "^2.3.0", |     "http-status-codes": "^2.3.0", | ||||||
|     "mariadb": "^3.4.0", |     "mariadb": "^3.4.0", | ||||||
|  |     "prom-client": "^15.1.3", | ||||||
|     "reflect-metadata": "^0.2.2", |     "reflect-metadata": "^0.2.2", | ||||||
|     "sequelize": "^6.37.5", |     "sequelize": "^6.37.5", | ||||||
|     "swagger-jsdoc": "^6.2.8", |     "swagger-jsdoc": "^6.2.8", | ||||||
|  | |||||||
| @ -1,22 +1,29 @@ | |||||||
| [Unit] | [Unit] | ||||||
| Description=LocationHub | Description=LocationHub Service | ||||||
|  | Documentation=https://git.mosad.xyz/localhorst/LocationHub | ||||||
| After=network.target systemd-networkd-wait-online.service mysqld.service | After=network.target systemd-networkd-wait-online.service mysqld.service | ||||||
|  |  | ||||||
| [Service] | [Service] | ||||||
| Type=simple | Type=simple | ||||||
| User=locationhub | User=locationhub | ||||||
|  | Group=locationhub | ||||||
| WorkingDirectory=/home/locationhub/git/LocationHub/server/ | WorkingDirectory=/home/locationhub/git/LocationHub/server/ | ||||||
| ExecStart=/usr/bin/npm run dev |  | ||||||
|  | # Combine commands for build and start | ||||||
|  | ExecStart=/bin/bash -c "/usr/bin/npm run build && /usr/bin/npm run start" | ||||||
|  |  | ||||||
|  | # Restart policies | ||||||
| Restart=on-failure | Restart=on-failure | ||||||
| StandardOutput=append:/var/log/LocationHub.log | RestartSec=5s | ||||||
| StandardError=append:/var/log/LocationHub.log |  | ||||||
|  | # Logging configuration | ||||||
|  | StandardOutput=journal | ||||||
|  | StandardError=journal | ||||||
|  | SyslogIdentifier=locationhub | ||||||
|  |  | ||||||
|  | # Resource control (optional but helps stability) | ||||||
|  | MemoryLimit=512M | ||||||
|  | CPUQuota=50% | ||||||
|  |  | ||||||
| [Install] | [Install] | ||||||
| WantedBy=multi-user.target | WantedBy=multi-user.target | ||||||
| ``` |  | ||||||
| Activate Systemd Job |  | ||||||
| ``` |  | ||||||
| systemctl daemon-reload |  | ||||||
| systemctl enable locationhub.service |  | ||||||
| systemctl start locationhub.service |  | ||||||
| systemctl status locationhub.service |  | ||||||
							
								
								
									
										63
									
								
								server/src/controller/metricsController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								server/src/controller/metricsController.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,63 @@ | |||||||
|  | import express, { Request, Response } from "express"; | ||||||
|  | import { container } from "tsyringe"; | ||||||
|  | import { Counter, Gauge, collectDefaultMetrics, register } from "prom-client"; | ||||||
|  | import { LocationService } from "../services/locationService"; | ||||||
|  | import { WifiLocationService } from "../services/wifiLocationService"; | ||||||
|  | import { TtnGatewayReceptionService } from "../services/ttnGatewayReceptionService"; | ||||||
|  | import { MetricsService } from "../services/metricsService"; | ||||||
|  |  | ||||||
|  | const router = express.Router(); | ||||||
|  | const locationService = container.resolve(LocationService); | ||||||
|  | const wifiLocationService = container.resolve(WifiLocationService); | ||||||
|  | const ttnGatewayReceptionService = container.resolve(TtnGatewayReceptionService); | ||||||
|  | const metricsService = container.resolve(MetricsService); | ||||||
|  |  | ||||||
|  | const requestCounter = metricsService.getRequestCounter(); | ||||||
|  | const locationsTotal = metricsService.getLocationsTotal(); | ||||||
|  | const gnssLocationsTotal = metricsService.getGnssLocationsTotal() | ||||||
|  | const wifiLocationTotal = metricsService.getWifiLocationTotal(); | ||||||
|  | const wifiLocationNotResolvable = metricsService.getWifiLocationResolvable(); | ||||||
|  | const wifiLocationRequestLimitExceeded = metricsService.getLWifiLocationRequestLimitExceeded(); | ||||||
|  | const ttnGatewayReceptions = metricsService.getTnnGatewayReceptions(); | ||||||
|  |  | ||||||
|  | // Define the metrics endpoint | ||||||
|  | router.get("/", async (req: Request, res: Response) => { | ||||||
|  |   try { | ||||||
|  |     const [ | ||||||
|  |       allLocations, | ||||||
|  |       gnssLocations, | ||||||
|  |       allWifiLocations, | ||||||
|  |       wifiLocationsNotResolvable, | ||||||
|  |       wifiLocationsRequestLimitExceeded, | ||||||
|  |       allTtnGatewayReceptions | ||||||
|  |     ] = await Promise.all([ | ||||||
|  |       locationService.getAllLocations(), | ||||||
|  |       locationService.getAllGnssLocations(), | ||||||
|  |       wifiLocationService.getAllWifiLocations(), | ||||||
|  |       wifiLocationService.getAllWifiLocationsByNotResolvable(), | ||||||
|  |       wifiLocationService.getAllWifiLocationsByRequestLimitExceeded(), | ||||||
|  |       ttnGatewayReceptionService.getAllGatewayReceptions() | ||||||
|  |     ]); | ||||||
|  |  | ||||||
|  |     locationsTotal.set(allLocations.length); | ||||||
|  |     gnssLocationsTotal.set(gnssLocations.length); | ||||||
|  |     wifiLocationTotal.set(allWifiLocations.length); | ||||||
|  |     wifiLocationNotResolvable.set(wifiLocationsNotResolvable.length); | ||||||
|  |     wifiLocationRequestLimitExceeded.set(wifiLocationsRequestLimitExceeded.length); | ||||||
|  |     ttnGatewayReceptions.set(allTtnGatewayReceptions.length); | ||||||
|  |  | ||||||
|  |     // Increment the counter with labels | ||||||
|  |     requestCounter.inc({ method: req.method, route: req.route.path, status: 200 }); | ||||||
|  |  | ||||||
|  |     // Expose metrics in Prometheus format | ||||||
|  |     res.set("Content-Type", register.contentType); | ||||||
|  |     res.send(await register.metrics()); | ||||||
|  |   } catch (error) { | ||||||
|  |     // Increment the counter for errors | ||||||
|  |     requestCounter.inc({ method: req.method, route: req.route.path, status: 500 }); | ||||||
|  |     console.error("Error running metrics endpoint:", error); | ||||||
|  |     res.status(500).json({ error: "Error running metrics endpoint" }); | ||||||
|  |   } | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | export default router; | ||||||
| @ -56,24 +56,24 @@ router.post( | |||||||
|         timestamp: latitudeData?.timestamp |         timestamp: latitudeData?.timestamp | ||||||
|           ? new Date(latitudeData.timestamp) |           ? new Date(latitudeData.timestamp) | ||||||
|           : longitudeData?.timestamp |           : longitudeData?.timestamp | ||||||
|             ? new Date(longitudeData.timestamp) |           ? new Date(longitudeData.timestamp) | ||||||
|             : undefined, |           : undefined, | ||||||
|       }; |       }; | ||||||
|       const wifiTimestamp = (() => { |  | ||||||
|         const messages = message.uplink_message.decoded_payload?.messages?.[0]; |       const wifiMessage = | ||||||
|         const wifiScan = messages?.find((e: { type: string }) => e.type === "Wi-Fi Scan"); |         message.uplink_message.decoded_payload?.messages[0].find( | ||||||
|         return wifiScan?.timestamp ? new Date(wifiScan.timestamp) : undefined; |           (e) => e.type === "Wi-Fi Scan" | ||||||
|       })(); |         ); | ||||||
|  |  | ||||||
|       const wifiScans = |       const wifiScans = | ||||||
|         message.uplink_message.decoded_payload?.messages[0] |         wifiMessage?.measurementValue?.map((w) => ({ | ||||||
|           .find((e) => e.type === "Wi-Fi Scan") |           lp_ttn_end_device_uplinks_id, | ||||||
|           ?.measurementValue?.map((w) => ({ |           mac: w.mac, | ||||||
|             lp_ttn_end_device_uplinks_id, |           rssi: w.rssi, | ||||||
|             mac: w.mac, |           scanned_at_utc: wifiMessage?.timestamp | ||||||
|             rssi: w.rssi, |             ? new Date(wifiMessage.timestamp) | ||||||
|             scanned_at_timestamp: wifiTimestamp, |             : undefined, | ||||||
|           })) ?? []; |         })) ?? []; | ||||||
|  |  | ||||||
|       const ttnGatewayReceptions = message.uplink_message.rx_metadata.map( |       const ttnGatewayReceptions = message.uplink_message.rx_metadata.map( | ||||||
|         (g) => ({ |         (g) => ({ | ||||||
| @ -109,9 +109,9 @@ router.post( | |||||||
|           gnss: |           gnss: | ||||||
|             gnnsLocation.latitude && gnnsLocation.longitude |             gnnsLocation.latitude && gnnsLocation.longitude | ||||||
|               ? { |               ? { | ||||||
|                 latitude: gnnsLocation.latitude, |                   latitude: gnnsLocation.latitude, | ||||||
|                 longitude: gnnsLocation.longitude, |                   longitude: gnnsLocation.longitude, | ||||||
|               } |                 } | ||||||
|               : undefined, |               : undefined, | ||||||
|           gnss_timestamp: gnssTimestamp.timestamp, |           gnss_timestamp: gnssTimestamp.timestamp, | ||||||
|         }); |         }); | ||||||
|  | |||||||
| @ -10,6 +10,7 @@ import ttnGatewayReceptionRoutes from "./controller/ttnGatewayReceptionControlle | |||||||
| import wifiLocationRoutes from "./controller/wifiLocationController"; | import wifiLocationRoutes from "./controller/wifiLocationController"; | ||||||
| import wifiLocationHistoryRoutes from "./controller/wifiLocationHistoryController"; | import wifiLocationHistoryRoutes from "./controller/wifiLocationHistoryController"; | ||||||
| import wifiScanRoutes from "./controller/wifiScanController"; | import wifiScanRoutes from "./controller/wifiScanController"; | ||||||
|  | import metricsRoutes from "./controller/metricsController"; | ||||||
|  |  | ||||||
| dotenv.config(); | dotenv.config(); | ||||||
|  |  | ||||||
| @ -26,6 +27,7 @@ app.use("/api/wifi-location", wifiLocationRoutes); | |||||||
| app.use("/api/wifi-scans", wifiScanRoutes); | app.use("/api/wifi-scans", wifiScanRoutes); | ||||||
| app.use("/api/locations", locationRoutes); | app.use("/api/locations", locationRoutes); | ||||||
| app.use("/api/ttn", ttnRoutes); | app.use("/api/ttn", ttnRoutes); | ||||||
|  | app.use("/api/metrics", metricsRoutes); | ||||||
|  |  | ||||||
| app.listen(PORT, () => { | app.listen(PORT, () => { | ||||||
|   console.log(`🚀 Server runs here: http://localhost:${PORT}`); |   console.log(`🚀 Server runs here: http://localhost:${PORT}`); | ||||||
|  | |||||||
| @ -1,10 +1,11 @@ | |||||||
|  | import { Attributes, FindOptions } from "sequelize"; | ||||||
| import { injectable } from "tsyringe"; | import { injectable } from "tsyringe"; | ||||||
| import { Location } from "../models/location"; | import { Location } from "../models/location"; | ||||||
|  |  | ||||||
| @injectable() | @injectable() | ||||||
| export class LocationRepository { | export class LocationRepository { | ||||||
|   public async findAll() { |   public async findAll(options?: FindOptions<Attributes<Location>>) { | ||||||
|     return await Location.findAll(); |     return await Location.findAll(options); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public async findById(id: string) { |   public async findById(id: string) { | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| import { inject, injectable } from "tsyringe"; | import { inject, injectable } from "tsyringe"; | ||||||
|  | import { Op } from 'sequelize'; | ||||||
| import { Location } from "../models/location"; | import { Location } from "../models/location"; | ||||||
| import { LocationRepository } from "../repositories/locationRepository"; | import { LocationRepository } from "../repositories/locationRepository"; | ||||||
| import { WifiLocationService } from "./wifiLocationService"; | import { WifiLocationService } from "./wifiLocationService"; | ||||||
| @ -54,6 +55,15 @@ export class LocationService { | |||||||
|     return this.repository.findAll(); |     return this.repository.findAll(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   public async getAllGnssLocations() { | ||||||
|  |     return this.repository.findAll({ | ||||||
|  |       where: { | ||||||
|  |         gnss_latitude: { [Op.ne]: null }, | ||||||
|  |         gnss_longitude: { [Op.ne]: null } | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   public async getLocationById(id: string) { |   public async getLocationById(id: string) { | ||||||
|     return this.repository.findById(id); |     return this.repository.findById(id); | ||||||
|   } |   } | ||||||
|  | |||||||
							
								
								
									
										70
									
								
								server/src/services/metricsService.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								server/src/services/metricsService.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,70 @@ | |||||||
|  | import {injectable } from "tsyringe"; | ||||||
|  | import { Counter, Gauge, collectDefaultMetrics} from "prom-client"; | ||||||
|  |  | ||||||
|  | // Collect default system metrics (e.g., CPU, memory usage) | ||||||
|  | const prefix = 'locationhub_'; | ||||||
|  | collectDefaultMetrics({ prefix }); | ||||||
|  |  | ||||||
|  | @injectable() | ||||||
|  | export class MetricsService { | ||||||
|  |   constructor( | ||||||
|  |   ) { } | ||||||
|  |  | ||||||
|  |   public getRequestCounter() { | ||||||
|  |     const requestCounter = new Counter({ | ||||||
|  |       name: `${prefix}http_requests_total`, | ||||||
|  |       help: "Total number of HTTP requests", | ||||||
|  |       labelNames: ["method", "route", "status"], | ||||||
|  |     }); | ||||||
|  |     return requestCounter; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public getLocationsTotal() { | ||||||
|  |     const locationsTotal = new Gauge({ | ||||||
|  |       name: `${prefix}locations_total`, | ||||||
|  |       help: "Total number of location entries in database", | ||||||
|  |       labelNames: ["database"], | ||||||
|  |     }); | ||||||
|  |     return locationsTotal; | ||||||
|  |   } | ||||||
|  |   public getGnssLocationsTotal() { | ||||||
|  |     const gnssLocationsTotal = new Gauge({ | ||||||
|  |       name: `${prefix}gnss_locations_total`, | ||||||
|  |       help: "Total number of location entries with GNSS in database", | ||||||
|  |       labelNames: ["database"], | ||||||
|  |     }); | ||||||
|  |     return gnssLocationsTotal; | ||||||
|  |   } | ||||||
|  |   public getWifiLocationTotal() { | ||||||
|  |     const wifiLocationTotal = new Gauge({ | ||||||
|  |       name: `${prefix}wifi_locations_total`, | ||||||
|  |       help: "Total number of wifi location entries in database", | ||||||
|  |       labelNames: ["database"], | ||||||
|  |     }); | ||||||
|  |     return wifiLocationTotal; | ||||||
|  |   } | ||||||
|  |   public getWifiLocationResolvable() { | ||||||
|  |     const wifiLocationNotResolvable = new Gauge({ | ||||||
|  |       name: `${prefix}wifi_locations_not_resolvable`, | ||||||
|  |       help: "Unresolved number of wifi location entries in database", | ||||||
|  |       labelNames: ["database"], | ||||||
|  |     }); | ||||||
|  |     return wifiLocationNotResolvable; | ||||||
|  |   } | ||||||
|  |   public getLWifiLocationRequestLimitExceeded() { | ||||||
|  |     const wifiLocationRequestLimitExceeded = new Gauge({ | ||||||
|  |       name: `${prefix}wifi_locations_request_limit_exceeded`, | ||||||
|  |       help: "Unresolved number of wifi location because request limit exceeded entries in database", | ||||||
|  |       labelNames: ["database"], | ||||||
|  |     }); | ||||||
|  |     return wifiLocationRequestLimitExceeded; | ||||||
|  |   } | ||||||
|  |   public getTnnGatewayReceptions() { | ||||||
|  |     const ttnGatewayReceptions = new Gauge({ | ||||||
|  |       name: `${prefix}ttn_gateway_receptions`, | ||||||
|  |       help: "Total number of TTN Gateway receptions entries in database", | ||||||
|  |       labelNames: ["database"], | ||||||
|  |     }); | ||||||
|  |     return ttnGatewayReceptions; | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -29,6 +29,18 @@ export class WifiLocationService { | |||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   public async getAllWifiLocationsByNotResolvable() { | ||||||
|  |     return this.repository.findAll({ | ||||||
|  |       where: { location_not_resolvable: true }, | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public async getAllWifiLocationsByRequestLimitExceeded() { | ||||||
|  |     return this.repository.findAll({ | ||||||
|  |       where: { request_limit_exceeded: true }, | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   public async getWifiLocationByMac(mac: string) { |   public async getWifiLocationByMac(mac: string) { | ||||||
|     let wifiLocation = await this.repository.findById(mac); |     let wifiLocation = await this.repository.findById(mac); | ||||||
|  |  | ||||||
|  | |||||||
| @ -5,14 +5,14 @@ interface CreateWifiScanParams { | |||||||
|   lp_ttn_end_device_uplinks_id: string; |   lp_ttn_end_device_uplinks_id: string; | ||||||
|   mac: string; |   mac: string; | ||||||
|   rssi: number; |   rssi: number; | ||||||
|   scanned_at_timestamp?: Date; |   scanned_at_utc?: Date; | ||||||
| } | } | ||||||
|  |  | ||||||
| interface UpdateWifiScanParams { | interface UpdateWifiScanParams { | ||||||
|   wifi_scan_id: string; |   wifi_scan_id: string; | ||||||
|   mac?: string; |   mac?: string; | ||||||
|   rssi?: number; |   rssi?: number; | ||||||
|   scanned_at_timestamp?: Date; |   scanned_at_utc?: Date; | ||||||
| } | } | ||||||
|  |  | ||||||
| @injectable() | @injectable() | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user