From 9de117b7eccc49b36dae52ee5b63a14e123abcbb Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 7 Nov 2021 15:28:54 +1030 Subject: [PATCH 001/410] Add Signal K standard resource path handling --- package.json | 2 + src/@types/geojson-validation.d.ts | 1 + src/api/resources/index.ts | 265 +++++++++++++++++++++++++++++ src/api/resources/validate.ts | 106 ++++++++++++ src/index.js | 6 + src/put.js | 5 + 6 files changed, 385 insertions(+) create mode 100644 src/@types/geojson-validation.d.ts create mode 100644 src/api/resources/index.ts create mode 100644 src/api/resources/validate.ts diff --git a/package.json b/package.json index b35220c2f..6bf9a6048 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "figlet": "^1.2.0", "file-timestamp-stream": "^2.1.2", "flatmap": "0.0.3", + "geojson-validation": "^1.0.2", "geolib": "3.2.2", "get-installed-path": "^4.0.8", "inquirer": "^7.0.0", @@ -139,6 +140,7 @@ "@types/semver": "^7.1.0", "@types/serialport": "^8.0.1", "@types/split": "^1.0.0", + "@types/uuid": "^8.3.1", "chai": "^4.0.0", "chai-json-equal": "0.0.1", "chai-things": "^0.2.0", diff --git a/src/@types/geojson-validation.d.ts b/src/@types/geojson-validation.d.ts new file mode 100644 index 000000000..a2e92c5c9 --- /dev/null +++ b/src/@types/geojson-validation.d.ts @@ -0,0 +1 @@ +declare module 'geojson-validation' diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts new file mode 100644 index 000000000..838f5b962 --- /dev/null +++ b/src/api/resources/index.ts @@ -0,0 +1,265 @@ +import Debug from 'debug' +import { v4 as uuidv4 } from 'uuid' +import { validate } from './validate' + +const debug = Debug('signalk:resources') + +interface ResourceRequest { + method: 'GET' | 'PUT' | 'POST' | 'DELETE' + body: any + query: {[key:string]: any} + resourceType: string + resourceId: string +} + +interface ResourceProvider { + listResources: (type:string, query: {[key:string]:any})=> Promise + getResource: (type:string, id:string)=> Promise + setResource: (type:string, id:string, value:{[key:string]:any})=> Promise + deleteResource: (type:string, id:string)=> Promise +} + +const SIGNALK_API_PATH= `/signalk/v1/api` +const UUID_PREFIX= 'urn:mrn:signalk:uuid:' + +export class Resources { + + // ** in-scope resource types ** + private resourceTypes:Array= [ + 'routes', + 'waypoints', + 'notes', + 'regions', + 'charts' + ] + + resProvider: {[key:string]: any}= {} + server: any + + public start(app:any) { + debug(`** Initialise ${SIGNALK_API_PATH}/resources path handler **`) + this.server= app + this.initResourceRoutes() + } + + public checkForProviders(rescan:boolean= false) { + if(rescan || Object.keys(this.resProvider).length==0) { + debug('** Checking for providers....') + this.resProvider= {} + this.resourceTypes.forEach( (rt:string)=> { + this.resProvider[rt]= this.getResourceProviderFor(rt) + }) + debug(this.resProvider) + } + } + + public getResource(type:string, id:string) { + debug(`** getResource(${type}, ${id})`) + this.checkForProviders() + return this.actionResourceRequest({ + method: 'GET', + body: {}, + query: {}, + resourceType: type, + resourceId: id + }) + } + + + // ** initialise handler for in-scope resource types ** + private initResourceRoutes() { + this.server.get(`${SIGNALK_API_PATH}/resources`, (req:any, res:any) => { + this.checkForProviders() + // list all serviced paths under resources + res.json(this.getResourcePaths()) + }) + this.server.use(`${SIGNALK_API_PATH}/resources/*`, async (req:any, res:any, next: any) => { + let result= this.parseResourceRequest(req) + if(result) { + let ar= await this.actionResourceRequest(result) + if(typeof ar.statusCode!== 'undefined'){ + debug(`${JSON.stringify(ar)}`) + res.status= ar.statusCode + res.send(ar.message) + } + else { + res.json(ar) + } + } + else { + debug('** No provider found... calling next()...') + next() + } + }) + } + + // ** return all paths serviced under ./resources *8 + private getResourcePaths(): {[key:string]:any} { + let resPaths:{[key:string]:any}= {} + Object.entries(this.resProvider).forEach( (p:any)=> { + if(p[1]) { resPaths[p[0]]= `Path containing ${p[0]}, each named with a UUID` } + }) + // check for other plugins servicing paths under ./resources + this.server._router.stack.forEach((i:any)=> { + if(i.route && i.route.path && typeof i.route.path==='string') { + if(i.route.path.indexOf(`${SIGNALK_API_PATH}/resources`)!=-1) { + let r= i.route.path.split('/') + if( r.length>5 && !(r[5] in resPaths) ) { + resPaths[r[5]]= `Path containing ${r[5]} resources (provided by plug-in)` + } + } + } + }) + return resPaths + } + + // ** parse api path request and return ResourceRequest object ** + private parseResourceRequest(req:any): ResourceRequest | undefined { + debug('** req.originalUrl:', req.originalUrl) + debug('** req.method:', req.method) + debug('** req.body:', req.body) + debug('** req.query:', req.query) + debug('** req.params:', req.params) + let p= req.params[0].split('/') + let resType= (typeof req.params[0]!=='undefined') ? p[0] : '' + let resId= p.length>1 ? p[1] : '' + debug('** resType:', resType) + debug('** resId:', resId) + + this.checkForProviders() + if(this.resourceTypes.includes(resType) && this.resProvider[resType]) { + return { + method: req.method, + body: req.body, + query: req.query, + resourceType: resType, + resourceId: resId + } + } + else { + debug('Invalid resource type or no provider for this type!') + return undefined + } + } + + // ** action an in-scope resource request ** + private async actionResourceRequest (req:ResourceRequest):Promise { + debug('********* action request *************') + debug(req) + + // check for registered resource providers + if(!this.resProvider) { + return {statusCode: 501, message: `No Provider`} + } + if(!this.resourceTypes.includes(req.resourceType) || !this.resProvider[req.resourceType]) { + return {statusCode: 501, message: `No Provider`} + } + + if(req.method==='GET') { + let retVal: any + if(!req.resourceId) { + retVal= await this.resProvider[req.resourceType].listResources(req.resourceType, req.query) + return (retVal) ? + retVal : + {statusCode: 404, message: `Error retrieving resources!` } + } + if(!validate.uuid(req.resourceId)) { + return {statusCode: 406, message: `Invalid resource id provided (${req.resourceId})` } + } + retVal= await this.resProvider[req.resourceType].getResource(req.resourceType, req.resourceId) + return (retVal) ? + retVal : + {statusCode: 404, message: `Resource not found (${req.resourceId})!` } + } + + if(req.method==='DELETE' || req.method==='PUT') { + if(!req.resourceId) { + return {statusCode: 406, value: `No resource id provided!` } + } + if(!validate.uuid(req.resourceId)) { + return {statusCode: 406, message: `Invalid resource id provided (${req.resourceId})!` } + } + if( + req.method==='DELETE' || + (req.method==='PUT' && typeof req.body.value!=='undefined' && req.body.value==null) + ) { + let retVal= await this.resProvider[req.resourceType].deleteResource(req.resourceType, req.resourceId) + if(retVal){ + this.sendDelta(req.resourceType, req.resourceId, null) + return {statusCode: 200, message: `Resource (${req.resourceId}) deleted.`} + } + else { + return {statusCode: 400, message: `Error deleting resource (${req.resourceId})!` } + } + } + + } + + if(req.method==='POST' || req.method==='PUT') { + // check for supplied value + if( typeof req.body.value==='undefined' || req.body.value==null) { + return {statusCode: 406, message: `No resource data supplied!`} + } + // validate supplied request data + if(!validate.resource(req.resourceType, req.body.value)) { + return {statusCode: 406, message: `Invalid resource data supplied!`} + } + if(req.method==='POST') { + let id= UUID_PREFIX + uuidv4() + let retVal= await this.resProvider[req.resourceType].setResource(req.resourceType, id, req.body.value) + if(retVal){ + this.sendDelta(req.resourceType, id, req.body.value) + return {statusCode: 200, message: `Resource (${id}) saved.`} + } + else { + return {statusCode: 400, message: `Error saving resource (${id})!` } + } + } + if(req.method==='PUT') { + if(!req.resourceId) { + return {statusCode: 406, message: `No resource id provided!` } + } + let retVal= await this.resProvider[req.resourceType].setResource(req.resourceType, req.resourceId, req.body.value) + if(retVal){ + this.sendDelta(req.resourceType, req.resourceId, req.body.value) + return {statusCode: 200, message: `Resource (${req.resourceId}) updated.`} + } + else { + return {statusCode: 400, message: `Error updating resource (${req.resourceId})!` } + } + } + } + } + + private sendDelta(type:string, id:string, value:any):void { + debug(`** Sending Delta: resources.${type}.${id}`) + this.server.handleMessage('signalk-resources', { + updates: [ + { + values: [ + { + path: `resources.${type}.${id}`, + value: value + } + ] + } + ] + }) + } + + // ** get reference to installed resource provider (plug-in). returns null if none found + private getResourceProviderFor(resType:string): ResourceProvider | null { + if(!this.server.plugins) { return null} + let pSource: ResourceProvider | null= null + this.server.plugins.forEach((plugin:any)=> { + if(typeof plugin.resourceProvider !== 'undefined') { + pSource= plugin.resourceProvider.types.includes(resType) ? + plugin.resourceProvider.methods : + null + } + }) + debug(`** Checking for ${resType} provider.... ${pSource ? 'Found' : 'Not found'}`) + return pSource + } + +} diff --git a/src/api/resources/validate.ts b/src/api/resources/validate.ts new file mode 100644 index 000000000..91d8b28f9 --- /dev/null +++ b/src/api/resources/validate.ts @@ -0,0 +1,106 @@ +//import { GeoHash, GeoBounds } from './geo'; +import geoJSON from 'geojson-validation'; + +export const validate= { + resource: (type:string, value:any):boolean=> { + if(!type) { return false } + switch(type) { + case 'routes': + return validateRoute(value); + break + case 'waypoints': + return validateWaypoint(value) + break + case 'notes': + return validateNote(value) + break; + case 'regions': + return validateRegion(value) + break + default: + return true + } + }, + + // ** returns true if id is a valid Signal K UUID ** + uuid: (id:string): boolean=> { + let uuid= RegExp("^urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$") + return uuid.test(id) + } +} + +// ** validate route data +const validateRoute= (r:any):boolean=> { + //if(typeof r.name === 'undefined') { return false } + //if(typeof r.description === 'undefined') { return false } + if(typeof r.distance === 'undefined' || typeof r.distance !=='number') { return false } + if(r.start) { + let l= r.start.split('/') + if(!validate.uuid(l[l.length-1])) { return false } + } + if(r.end) { + let l= r.end.split('/') + if(!validate.uuid(l[l.length-1])) { return false } + } + try { + if(!r.feature || !geoJSON.valid(r.feature)) { + return false + } + if(r.feature.geometry.type!=='LineString') { return false } + } + catch(err) { return false } + return true +} + +// ** validate waypoint data +const validateWaypoint= (r:any):boolean=> { + if(typeof r.position === 'undefined') { return false } + if(typeof r.position.latitude === 'undefined' || typeof r.position.longitude === 'undefined') { + return false + } + if(typeof r.position.latitude !== 'number' || typeof r.position.longitude !== 'number') { + return false + } + try { + if(!r.feature || !geoJSON.valid(r.feature)) { + return false + } + if(r.feature.geometry.type!=='Point') { return false } + } + catch(e) { return false } + return true +} + +// ** validate note data +const validateNote= (r:any):boolean=> { + if(!r.region && !r.position && !r.geohash ) { return false } + if(typeof r.position!== 'undefined') { + if(typeof r.position.latitude === 'undefined' || typeof r.position.longitude === 'undefined') { + return false + } + if(typeof r.position.latitude !== 'number' || typeof r.position.longitude !== 'number') { + return false + } + } + if(r.region) { + let l= r.region.split('/') + if(!validate.uuid(l[l.length-1])) { return false } + } + return true +} + +// ** validate region data +const validateRegion= (r:any):boolean=> { + if(!r.geohash && !r.feature) { return false } + if(r.feature ) { + try { + if(!geoJSON.valid(r.feature)) { return false } + if(r.feature.geometry.type!=='Polygon' && r.feature.geometry.type!=='MultiPolygon') { + return false + } + } + catch(e) { return false } + } + return true +} + diff --git a/src/index.js b/src/index.js index d1c06e5f8..503537045 100644 --- a/src/index.js +++ b/src/index.js @@ -37,6 +37,8 @@ import { checkForNewServerVersion } from './modules' import { getToPreferredDelta } from './deltaPriority' import { PropertyValues } from '@signalk/server-api' +import { Resources } from './api/resources' + const { StreamBundle } = require('./streambundle') const { startSecurity, @@ -68,6 +70,10 @@ function Server(opts) { require('./serverroutes')(app, saveSecurityConfig, getSecurityConfig) require('./put').start(app) + // ** initialise resources API ** + app.resourcesApi= new Resources() + app.resourcesApi.start(app) + app.signalk = new FullSignalK(app.selfId, app.selfType) app.propertyValues = new PropertyValues() diff --git a/src/put.js b/src/put.js index 8db74fac8..c77bb3177 100644 --- a/src/put.js +++ b/src/put.js @@ -30,6 +30,11 @@ module.exports = { app.deRegisterActionHandler = deRegisterActionHandler app.put(apiPathPrefix + '*', function(req, res, next) { + // ** ignore resources paths ** + if(req.path.split('/')[4]==='resources') { + next() + return + } let path = String(req.path).replace(apiPathPrefix, '') const value = req.body From 73c100482c8a8309d752d586d939f99bcff4dbf7 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 7 Nov 2021 17:27:25 +1030 Subject: [PATCH 002/410] add OpenApi definition file --- src/api/resources/openApi.json | 830 +++++++++++++++++++++++++++++++++ 1 file changed, 830 insertions(+) create mode 100644 src/api/resources/openApi.json diff --git a/src/api/resources/openApi.json b/src/api/resources/openApi.json new file mode 100644 index 000000000..c14f53d5a --- /dev/null +++ b/src/api/resources/openApi.json @@ -0,0 +1,830 @@ +{ + "openapi": "3.0.2", + "info": { + "version": "1.0.0", + "title": "Signal K Resources API" + }, + + "paths": { + + "/resources": { + "get": { + "tags": ["resources"], + "summary": "List available resource types" + + } + }, + + "/resources/{resourceClass}": { + "get": { + "tags": ["resources"], + "summary": "Retrieve resources", + "parameters": [ + { + "name": "resourceClass", + "in": "path", + "description": "resource class", + "required": true, + "schema": { + "type": "string", + "enum": ["routes", "waypoints", "notes", "regions", "charts"], + "example": "waypoints" + } + }, + { + "name": "limit", + "in": "query", + "description": "Maximum number of records to return", + "schema": { + "type": "integer", + "format": "int32", + "minimum": 1, + "example": 100 + } + }, + { + "in": "query", + "name": "radius", + "description": "Limit results to resources that fall within a square area, centered around the vessel's position, the edges of which are the sepecified distance in meters from the vessel.", + "schema": { + "type": "integer", + "format": "int32", + "minimum": 100, + "example": 2000 + } + }, + { + "in": "query", + "name": "geohash", + "description": "limit results to resources that fall within an area sepecified by the geohash.", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "bbox", + "description": "Limit results to resources that fall within the bounded area defined as lower left and upper right x,y coordinates x1,y1,x2,y2", + "style": "form", + "explode": false, + "schema": { + "type": "array", + "minItems": 4, + "maxItems": 4, + "items": { + "type": "number", + "format": "float", + "example": [135.5,-25.2,138.1,-28.0] + } + } + } + ], + "responses": { + "200": { + "description": "List of resources identified by their UUID", + "content": { + "application/json": { + "schema": { + "description": "List of Signal K resources", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + } + } + }, + + "/resources/routes/": { + "post": { + "tags": ["resources/routes"], + "summary": "Add a new Route", + "requestBody": { + "description": "Route details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Route resource", + "required": ["feature"], + "properties": { + "name": { + "type": "string", + "description": "Route's common name" + }, + "description": { + "type": "string", + "description": "A description of the route" + }, + "distance": { + "description": "Total distance from start to end", + "type": "number" + }, + "start": { + "description": "The waypoint UUID at the start of the route", + "type": "string", + "pattern": "/resources/waypoints/urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$" + }, + "end": { + "description": "The waypoint UUID at the end of the route", + "type": "string", + "pattern": "/resources//waypoints/urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$" + }, + "feature": { + "type": "object", + "title": "Feature", + "description": "A GeoJSON feature object which describes a route", + "properties": { + "geometry": { + "type": { + "type": "string", + "enum": ["LineString"] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } + } + } + }, + "properties": { + "description": "Additional feature properties", + "type": "object", + "additionalProperties": true + }, + "id": { + "type": "string" + } + } + } + } + + } + } + } + } + } + }, + + "/resources/routes/{id}": { + "parameters": { + "name": "id", + "in": "path", + "description": "route id", + "required": true, + "schema": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$" + } + }, + + "get": { + "tags": ["resources/routes"], + "summary": "Retrieve route with supplied id" + }, + + "put": { + "tags": ["resources/routes"], + "summary": "Add / update a new Route with supplied id", + "requestBody": { + "description": "Route details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Route resource", + "required": ["feature"], + "properties": { + "name": { + "type": "string", + "description": "Route's common name" + }, + "description": { + "type": "string", + "description": "A description of the route" + }, + "distance": { + "description": "Total distance from start to end", + "type": "number" + }, + "start": { + "description": "The waypoint UUID at the start of the route", + "type": "string", + "pattern": "/resources/waypoints/urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$" + }, + "end": { + "description": "The waypoint UUID at the end of the route", + "type": "string", + "pattern": "/resources/waypoints/urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$" + }, + "feature": { + "type": "object", + "title": "Feature", + "description": "A GeoJSON feature object which describes a route", + "properties": { + "geometry": { + "type": { + "type": "string", + "enum": ["LineString"] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } + } + } + }, + "properties": { + "description": "Additional feature properties", + "type": "object", + "additionalProperties": true + }, + "id": { + "type": "string" + } + } + } + } + + } + } + } + } + }, + + "delete": { + "tags": ["resources/routes"], + "summary": "Remove Route with supplied id" + } + + }, + + "/resources/waypoints/": { + "post": { + "tags": ["resources/waypoints"], + "summary": "Add a new Waypoint", + "requestBody": { + "description": "Waypoint details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Waypoint resource", + "required": ["feature"], + "properties": { + "position": { + "description": "The waypoint position", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + }, + "altitude": { + "type": "number", + "format": "float" + } + } + }, + "feature": { + "type": "object", + "title": "Feature", + "description": "A GeoJSON feature object which describes a waypoint", + "properties": { + "geometry": { + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["Point"] + } + }, + "coordinates": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } + } + }, + "properties": { + "description": "Additional feature properties", + "type": "object", + "additionalProperties": true + }, + "id": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + + "/resources/waypoints/{id}": { + "parameters": { + "name": "id", + "in": "path", + "description": "waypoint id", + "required": true, + "schema": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$" + } + }, + + "get": { + "tags": ["resources/waypoints"], + "summary": "Retrieve waypoint with supplied id" + }, + + "put": { + "tags": ["resources/waypoints"], + "summary": "Add / update a new Waypoint with supplied id", + "requestBody": { + "description": "Waypoint details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Waypoint resource", + "required": ["feature"], + "properties": { + "position": { + "description": "The waypoint position", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + }, + "altitude": { + "type": "number", + "format": "float" + } + } + }, + "feature": { + "type": "object", + "title": "Feature", + "description": "A GeoJSON feature object which describes a waypoint", + "properties": { + "geometry": { + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["Point"] + } + }, + "coordinates": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } + } + }, + "properties": { + "description": "Additional feature properties", + "type": "object", + "additionalProperties": true + }, + "id": { + "type": "string" + } + } + } + } + } + } + } + } + }, + + "delete": { + "tags": ["resources/waypoints"], + "summary": "Remove Waypoint with supplied id" + } + + }, + + "/resources/notes/": { + "post": { + "tags": ["resources/notes"], + "summary": "Add a new Note", + "requestBody": { + "description": "Note details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Note resource", + "required": ["feature"], + "properties": { + "title": { + "type": "string", + "description": "Common Note name" + }, + "description": { + "type": "string", + "description": "A description of the note" + }, + "position": { + "description": "Position related to note. Alternative to region or geohash.", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + }, + "altitude": { + "type": "number", + "format": "float" + } + } + }, + "region": { + "description": "Pointer / path to Region related to note (e.g. /resources/routes/{uuid}. Alternative to position or geohash", + "type": "string", + "pattern": "/resources/regions/urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$" + }, + "geohash": { + "description": "Area related to note. Alternative to region or position", + "type": "string" + }, + "mimeType": { + "description": "MIME type of the note", + "type": "string" + }, + "url": { + "description": "Location of the note content", + "type": "string" + } + } + } + } + } + } + } + }, + + "/resources/notes/{id}": { + "parameters": { + "name": "id", + "in": "path", + "description": "note id", + "required": true, + "schema": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$" + } + }, + + "get": { + "tags": ["resources/notes"], + "summary": "Retrieve Note with supplied id" + }, + + "put": { + "tags": ["resources/notes"], + "summary": "Add / update a new Note with supplied id", + "requestBody": { + "description": "Note details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Note resource", + "required": ["feature"], + "properties": { + "title": { + "type": "string", + "description": "Common Note name" + }, + "description": { + "type": "string", + "description": "A description of the note" + }, + "position": { + "description": "Position related to note. Alternative to region or geohash.", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + }, + "altitude": { + "type": "number", + "format": "float" + } + } + }, + "region": { + "description": "Pointer / path to Region related to note (e.g. /resources/routes/{uuid}. Alternative to position or geohash", + "type": "string", + "pattern": "/resources/regions/urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$" + }, + "geohash": { + "description": "Area related to note. Alternative to region or position", + "type": "string" + }, + "mimeType": { + "description": "MIME type of the note", + "type": "string" + }, + "url": { + "description": "Location of the note content", + "type": "string" + } + } + } + } + } + } + }, + + "delete": { + "tags": ["resources/notes"], + "summary": "Remove Note with supplied id" + } + + }, + + "/resources/regions/": { + "post": { + "tags": ["resources/regions"], + "summary": "Add a new Regkion", + "requestBody": { + "description": "Region details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Region resource", + "required": ["feature"], + "properties": { + "geohash": { + "description": "geohash of the approximate boundary of this region", + "type": "string" + }, + "feature": { + "type": "object", + "title": "Feature", + "description": "A Geo JSON feature object which describes the regions boundary", + "properties": { + "geometry": { + "oneOf": [ + { + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["Polygon"] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } + } + } + } + } + }, + { + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["MultiPolygon"] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } + } + } + } + } + } + } + ] + }, + "properties": { + "description": "Additional feature properties", + "type": "object", + "additionalProperties": true + }, + "id": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + + "/resources/regions/{id}": { + "parameters": { + "name": "id", + "in": "path", + "description": "region id", + "required": true, + "schema": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$" + } + }, + + "get": { + "tags": ["resources/regions"], + "summary": "Retrieve Region with supplied id" + }, + + "put": { + "tags": ["resources/regions"], + "summary": "Add / update a new Region with supplied id", + "requestBody": { + "description": "Region details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Region resource", + "required": ["feature"], + "properties": { + "geohash": { + "description": "geohash of the approximate boundary of this region", + "type": "string" + }, + "feature": { + "type": "object", + "title": "Feature", + "description": "A Geo JSON feature object which describes the regions boundary", + "properties": { + "geometry": { + "oneOf": [ + { + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["Polygon"] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } + } + } + } + } + }, + { + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["MultiPolygon"] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } + } + } + } + } + } + } + ] + }, + "properties": { + "description": "Additional feature properties", + "type": "object", + "additionalProperties": true + }, + "id": { + "type": "string" + } + } + } + } + } + } + } + } + }, + + "delete": { + "tags": ["resources/regions"], + "summary": "Remove Region with supplied id" + } + + } + + } + +} + \ No newline at end of file From 9612002e5ac394129cb7228deaf0c30e3a2fc2cb Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 8 Nov 2021 09:04:45 +1030 Subject: [PATCH 003/410] chore: fix lint errors --- src/api/resources/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 838f5b962..ae0495e08 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -43,7 +43,7 @@ export class Resources { } public checkForProviders(rescan:boolean= false) { - if(rescan || Object.keys(this.resProvider).length==0) { + if(rescan || Object.keys(this.resProvider).length===0) { debug('** Checking for providers....') this.resProvider= {} this.resourceTypes.forEach( (rt:string)=> { @@ -102,7 +102,7 @@ export class Resources { // check for other plugins servicing paths under ./resources this.server._router.stack.forEach((i:any)=> { if(i.route && i.route.path && typeof i.route.path==='string') { - if(i.route.path.indexOf(`${SIGNALK_API_PATH}/resources`)!=-1) { + if(i.route.path.indexOf(`${SIGNALK_API_PATH}/resources`)!==-1) { let r= i.route.path.split('/') if( r.length>5 && !(r[5] in resPaths) ) { resPaths[r[5]]= `Path containing ${r[5]} resources (provided by plug-in)` From 18e187701d887ba42fe7136c5749e27c9ef11fdb Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Tue, 9 Nov 2021 15:13:44 +1030 Subject: [PATCH 004/410] addressed comments re parameters --- src/api/resources/openApi.json | 664 ++++++++++++++++++++++++++------- 1 file changed, 527 insertions(+), 137 deletions(-) diff --git a/src/api/resources/openApi.json b/src/api/resources/openApi.json index c14f53d5a..e23a100f3 100644 --- a/src/api/resources/openApi.json +++ b/src/api/resources/openApi.json @@ -10,8 +10,22 @@ "/resources": { "get": { "tags": ["resources"], - "summary": "List available resource types" - + "summary": "List available resource types", + "responses": { + "default": { + "description": "List of available resource types", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + } } }, @@ -43,8 +57,8 @@ } }, { + "name": "distance", "in": "query", - "name": "radius", "description": "Limit results to resources that fall within a square area, centered around the vessel's position, the edges of which are the sepecified distance in meters from the vessel.", "schema": { "type": "integer", @@ -53,18 +67,10 @@ "example": 2000 } }, - { - "in": "query", - "name": "geohash", - "description": "limit results to resources that fall within an area sepecified by the geohash.", - "schema": { - "type": "string" - } - }, { "in": "query", "name": "bbox", - "description": "Limit results to resources that fall within the bounded area defined as lower left and upper right x,y coordinates x1,y1,x2,y2", + "description": "Limit results to resources that fall within the bounded area defined as lower left (south west) and upper right (north east) coordinates [swlon,swlat,nelon,nelat]", "style": "form", "explode": false, "schema": { @@ -140,18 +146,21 @@ "description": "A GeoJSON feature object which describes a route", "properties": { "geometry": { - "type": { - "type": "string", - "enum": ["LineString"] - }, - "coordinates": { - "type": "array", - "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["LineString"] + }, + "coordinates": { "type": "array", - "maxItems": 3, - "minItems": 2, "items": { - "type": "number" + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } } } } @@ -171,7 +180,8 @@ } } } - } + }, + "responses": {} } }, @@ -189,7 +199,8 @@ "get": { "tags": ["resources/routes"], - "summary": "Retrieve route with supplied id" + "summary": "Retrieve route with supplied id", + "responses": {} }, "put": { @@ -233,18 +244,21 @@ "description": "A GeoJSON feature object which describes a route", "properties": { "geometry": { - "type": { - "type": "string", - "enum": ["LineString"] - }, - "coordinates": { - "type": "array", - "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["LineString"] + }, + "coordinates": { "type": "array", - "maxItems": 3, - "minItems": 2, "items": { - "type": "number" + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } } } } @@ -264,12 +278,14 @@ } } } - } + }, + "responses": {} }, "delete": { "tags": ["resources/routes"], - "summary": "Remove Route with supplied id" + "summary": "Remove Route with supplied id", + "responses": {} } }, @@ -317,19 +333,22 @@ "properties": { "geometry": { "type": "object", - "description": "GeoJSon geometry", "properties": { - "type": { - "type": "string", - "enum": ["Point"] - } - }, - "coordinates": { - "type": "array", - "maxItems": 3, - "minItems": 2, - "items": { - "type": "number" + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["Point"] + } + }, + "coordinates": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } } } }, @@ -347,7 +366,8 @@ } } } - } + }, + "responses": {} } }, @@ -365,7 +385,8 @@ "get": { "tags": ["resources/waypoints"], - "summary": "Retrieve waypoint with supplied id" + "summary": "Retrieve waypoint with supplied id", + "responses": {} }, "put": { @@ -410,19 +431,22 @@ "properties": { "geometry": { "type": "object", - "description": "GeoJSon geometry", "properties": { - "type": { - "type": "string", - "enum": ["Point"] - } - }, - "coordinates": { - "type": "array", - "maxItems": 3, - "minItems": 2, - "items": { - "type": "number" + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["Point"] + } + }, + "coordinates": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } } } }, @@ -440,12 +464,14 @@ } } } - } + }, + "responses": {} }, "delete": { "tags": ["resources/waypoints"], - "summary": "Remove Waypoint with supplied id" + "summary": "Remove Waypoint with supplied id", + "responses": {} } }, @@ -533,7 +559,8 @@ "get": { "tags": ["resources/notes"], - "summary": "Retrieve Note with supplied id" + "summary": "Retrieve Note with supplied id", + "responses": {} }, "put": { @@ -600,12 +627,14 @@ } } } - } + }, + "responses": {} }, "delete": { "tags": ["resources/notes"], - "summary": "Remove Note with supplied id" + "summary": "Remove Note with supplied id", + "responses": {} } }, @@ -634,59 +663,62 @@ "description": "A Geo JSON feature object which describes the regions boundary", "properties": { "geometry": { - "oneOf": [ - { - "type": "object", - "description": "GeoJSon geometry", - "properties": { - "type": { - "type": "string", - "enum": ["Polygon"] - }, - "coordinates": { - "type": "array", - "items": { + "type": "object", + "properties": { + "oneOf": [ + { + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["Polygon"] + }, + "coordinates": { "type": "array", "items": { "type": "array", - "maxItems": 3, - "minItems": 2, "items": { - "type": "number" + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } } } } } - } - }, - { - "type": "object", - "description": "GeoJSon geometry", - "properties": { - "type": { - "type": "string", - "enum": ["MultiPolygon"] - }, - "coordinates": { - "type": "array", - "items": { + }, + { + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["MultiPolygon"] + }, + "coordinates": { "type": "array", "items": { "type": "array", "items": { "type": "array", - "maxItems": 3, - "minItems": 2, "items": { - "type": "number" + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } } } } } } } - } - ] + ] + } }, "properties": { "description": "Additional feature properties", @@ -702,7 +734,8 @@ } } } - } + }, + "responses": {} } }, @@ -720,7 +753,8 @@ "get": { "tags": ["resources/regions"], - "summary": "Retrieve Region with supplied id" + "summary": "Retrieve Region with supplied id", + "responses": {} }, "put": { @@ -746,59 +780,62 @@ "description": "A Geo JSON feature object which describes the regions boundary", "properties": { "geometry": { - "oneOf": [ - { - "type": "object", - "description": "GeoJSon geometry", - "properties": { - "type": { - "type": "string", - "enum": ["Polygon"] - }, - "coordinates": { - "type": "array", - "items": { + "type": "object", + "properties": { + "oneOf": [ + { + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["Polygon"] + }, + "coordinates": { "type": "array", "items": { "type": "array", - "maxItems": 3, - "minItems": 2, "items": { - "type": "number" + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } } } } } - } - }, - { - "type": "object", - "description": "GeoJSon geometry", - "properties": { - "type": { - "type": "string", - "enum": ["MultiPolygon"] - }, - "coordinates": { - "type": "array", - "items": { + }, + { + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["MultiPolygon"] + }, + "coordinates": { "type": "array", "items": { "type": "array", "items": { "type": "array", - "maxItems": 3, - "minItems": 2, "items": { - "type": "number" + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } } } } } } } - } - ] + ] + } }, "properties": { "description": "Additional feature properties", @@ -814,14 +851,367 @@ } } } - } + }, + "responses": {} }, "delete": { "tags": ["resources/regions"], - "summary": "Remove Region with supplied id" + "summary": "Remove Region with supplied id", + "responses": {} + } + + }, + + "/resources/setWaypoint": { + "put": { + "tags": ["resources/api"], + "summary": "Add / update a Waypoint", + "requestBody": { + "description": "Waypoint attributes", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["position"], + "properties": { + "id": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", + "description": "Waypoint identifier" + }, + "name": { + "type": "string", + "description": "Waypoint name" + }, + "description": { + "type": "string", + "description": "Textual description of the waypoint" + }, + "position": { + "description": "The waypoint position", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + } + } + } + } + } + } + }, + "responses": {} + } + }, + + "/resources/deleteWaypoint": { + "put": { + "tags": ["resources/api"], + "summary": "Remove a Waypoint", + "requestBody": { + "description": "Waypoint identifier", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["id"], + "properties": { + "id": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", + "description": "Waypoint identifier" + } + } + } + } + } + }, + "responses": {} + } + }, + + "/resources/setRoute": { + "put": { + "tags": ["resources/api"], + "summary": "Add / update a Route", + "requestBody": { + "description": "Note attributes", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["position"], + "properties": { + "id": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", + "description": "Route identifier" + }, + "name": { + "type": "string", + "description": "Route name" + }, + "description": { + "type": "string", + "description": "Textual description of the route" + }, + "points": { + "description": "Route points", + "type": "array", + "items": { + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + } + } + } + } + } + } + } + }, + "responses": {} + } + }, + + "/resources/deleteRoute": { + "put": { + "tags": ["resources/api"], + "summary": "Remove a Route", + "requestBody": { + "description": "Route identifier", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["id"], + "properties": { + "id": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", + "description": "Route identifier" + } + } + } + } + } + }, + "responses": {} } + }, + "/resources/setNote": { + "put": { + "tags": ["resources/api"], + "summary": "Add / update a Note", + "requestBody": { + "description": "Note attributes", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["position"], + "properties": { + "id": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", + "description": "Note identifier" + }, + "title": { + "type": "string", + "description": "Note's common name" + }, + "description": { + "type": "string", + "description": " Textual description of the note" + }, + "oneOf":[ + { + "position": { + "description": "Position related to note. Alternative to region or geohash", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + } + }, + "geohash": { + "type": "string", + "description": "Position related to note. Alternative to region or position" + }, + "region": { + "type": "string", + "description": "Region related to note. A pointer to a region UUID. Alternative to position or geohash", + "example": "/resources/regions/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a" + } + } + ], + "mimeType": { + "type": "string", + "description": "MIME type of the note" + }, + "url": { + "type": "string", + "description": "Location of the note contents" + } + } + } + } + } + }, + "responses": {} + } + }, + + "/resources/deleteNote": { + "put": { + "tags": ["resources/api"], + "summary": "Remove a Note", + "requestBody": { + "description": "Note identifier", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["id"], + "properties": { + "id": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", + "description": "Note identifier" + } + } + } + } + } + }, + "responses": {} + } + }, + + "/resources/setRegion": { + "put": { + "tags": ["resources/api"], + "summary": "Add / update a Region", + "requestBody": { + "description": "Region attributes", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["position"], + "properties": { + "id": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", + "description": "Region identifier" + }, + "name": { + "type": "string", + "description": "Region name" + }, + "description": { + "type": "string", + "description": "Textual description of region" + }, + "geohash": { + "type": "string", + "description": "Area related to region. Alternative to points." + }, + "points": { + "description": "Region boundary points", + "type": "array", + "items": { + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + } + } + } + } + } + } + } + }, + "responses": {} + } + }, + + "/resources/deleteRegion": { + "put": { + "tags": ["resources/api"], + "summary": "Remove a Region", + "requestBody": { + "description": "Region identifier", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["id"], + "properties": { + "id": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", + "description": "Region identifier" + } + } + } + } + } + }, + "responses": {} + } } } From 85c7adb147e0fd7cbc95bd3f6466fd7887351b32 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Tue, 9 Nov 2021 15:28:15 +1030 Subject: [PATCH 005/410] add API definitions --- src/api/resources/openApi.json | 350 ++++++++++++++++++++++++++++++--- 1 file changed, 326 insertions(+), 24 deletions(-) diff --git a/src/api/resources/openApi.json b/src/api/resources/openApi.json index e23a100f3..d7a7f6e35 100644 --- a/src/api/resources/openApi.json +++ b/src/api/resources/openApi.json @@ -12,7 +12,7 @@ "tags": ["resources"], "summary": "List available resource types", "responses": { - "default": { + "200": { "description": "List of available resource types", "content": { "application/json": { @@ -181,7 +181,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -200,7 +211,22 @@ "get": { "tags": ["resources/routes"], "summary": "Retrieve route with supplied id", - "responses": {} + "responses": { + "200": { + "description": "List of resources identified by their UUID", + "content": { + "application/json": { + "schema": { + "description": "List of Signal K resources", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + } }, "put": { @@ -279,13 +305,35 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } }, "delete": { "tags": ["resources/routes"], "summary": "Remove Route with supplied id", - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -367,7 +415,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -386,7 +445,22 @@ "get": { "tags": ["resources/waypoints"], "summary": "Retrieve waypoint with supplied id", - "responses": {} + "responses": { + "200": { + "description": "List of resources identified by their UUID", + "content": { + "application/json": { + "schema": { + "description": "List of Signal K resources", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + } }, "put": { @@ -465,13 +539,35 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } }, "delete": { "tags": ["resources/waypoints"], "summary": "Remove Waypoint with supplied id", - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -541,6 +637,18 @@ } } } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } } } }, @@ -560,7 +668,22 @@ "get": { "tags": ["resources/notes"], "summary": "Retrieve Note with supplied id", - "responses": {} + "responses": { + "200": { + "description": "List of resources identified by their UUID", + "content": { + "application/json": { + "schema": { + "description": "List of Signal K resources", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + } }, "put": { @@ -628,13 +751,35 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } }, "delete": { "tags": ["resources/notes"], "summary": "Remove Note with supplied id", - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -735,7 +880,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -754,7 +910,22 @@ "get": { "tags": ["resources/regions"], "summary": "Retrieve Region with supplied id", - "responses": {} + "responses": { + "200": { + "description": "List of resources identified by their UUID", + "content": { + "application/json": { + "schema": { + "description": "List of Signal K resources", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + } }, "put": { @@ -852,13 +1023,35 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } }, "delete": { "tags": ["resources/regions"], "summary": "Remove Region with supplied id", - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -889,6 +1082,13 @@ "type": "string", "description": "Textual description of the waypoint" }, + "attributes": { + "type": "object", + "description": "Additional attributes as name:value pairs.", + "additionalProperties": { + "type": "string" + } + }, "position": { "description": "The waypoint position", "type": "object", @@ -912,7 +1112,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -939,7 +1150,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -969,6 +1191,13 @@ "type": "string", "description": "Textual description of the route" }, + "attributes": { + "type": "object", + "description": "Additional attributes as name:value pairs.", + "additionalProperties": { + "type": "string" + } + }, "points": { "description": "Route points", "type": "array", @@ -995,7 +1224,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -1022,7 +1262,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -1096,7 +1347,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -1123,7 +1385,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -1153,6 +1426,13 @@ "type": "string", "description": "Textual description of region" }, + "attributes": { + "type": "object", + "description": "Additional attributes as name:value pairs.", + "additionalProperties": { + "type": "string" + } + }, "geohash": { "type": "string", "description": "Area related to region. Alternative to points." @@ -1183,7 +1463,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -1210,7 +1501,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } } From c4eaec3a1e2b8edd7ec891cfc2b9de3af18b6867 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Tue, 9 Nov 2021 15:29:13 +1030 Subject: [PATCH 006/410] add geohash library --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index 6bf9a6048..629f3e645 100644 --- a/package.json +++ b/package.json @@ -109,6 +109,7 @@ "morgan": "^1.5.0", "ms": "^2.1.2", "ncp": "^2.0.0", + "ngeohash": "^0.6.3", "node-fetch": "^2.6.0", "pem": "^1.14.3", "primus": "^7.0.0", @@ -136,6 +137,7 @@ "@types/express": "^4.17.1", "@types/lodash": "^4.14.139", "@types/mocha": "^8.2.0", + "@types/ngeohash": "^0.6.4", "@types/node-fetch": "^2.5.3", "@types/semver": "^7.1.0", "@types/serialport": "^8.0.1", From 5e54a9179ba0f0ecdec0c4e0f2d184cd01eb2249 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Tue, 9 Nov 2021 15:30:30 +1030 Subject: [PATCH 007/410] add API endpoint processing --- src/api/resources/index.ts | 81 ++++++++++++++-- src/api/resources/resources.ts | 167 +++++++++++++++++++++++++++++++++ src/api/resources/validate.ts | 1 - 3 files changed, 239 insertions(+), 10 deletions(-) create mode 100644 src/api/resources/resources.ts diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index ae0495e08..d84cd29a8 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -1,6 +1,7 @@ import Debug from 'debug' import { v4 as uuidv4 } from 'uuid' import { validate } from './validate' +import { buildResource } from './resources' const debug = Debug('signalk:resources') @@ -9,7 +10,8 @@ interface ResourceRequest { body: any query: {[key:string]: any} resourceType: string - resourceId: string + resourceId: string, + apiMethod?: string | null } interface ResourceProvider { @@ -22,6 +24,17 @@ interface ResourceProvider { const SIGNALK_API_PATH= `/signalk/v1/api` const UUID_PREFIX= 'urn:mrn:signalk:uuid:' +const API_METHODS= [ + 'setWaypoint', + 'deleteWaypoint', + 'setRoute', + 'deleteRoute', + 'setNote', + 'deleteNote', + 'setRegion', + 'deleteRegion' +] + export class Resources { // ** in-scope resource types ** @@ -114,7 +127,7 @@ export class Resources { } // ** parse api path request and return ResourceRequest object ** - private parseResourceRequest(req:any): ResourceRequest | undefined { + private parseResourceRequest(req:any):ResourceRequest | undefined { debug('** req.originalUrl:', req.originalUrl) debug('** req.method:', req.method) debug('** req.body:', req.body) @@ -125,16 +138,35 @@ export class Resources { let resId= p.length>1 ? p[1] : '' debug('** resType:', resType) debug('** resId:', resId) + + let apiMethod= (API_METHODS.includes(resType)) ? resType : null + if(apiMethod) { + if(apiMethod.toLowerCase().indexOf('waypoint')!==-1) { + resType= 'waypoints' + } + if(apiMethod.toLowerCase().indexOf('route')!==-1) { + resType= 'routes' + } + if(apiMethod.toLowerCase().indexOf('note')!==-1) { + resType= 'notes' + } + if(apiMethod.toLowerCase().indexOf('region')!==-1) { + resType= 'regions' + } + } this.checkForProviders() + let retReq= { + method: req.method, + body: req.body, + query: req.query, + resourceType: resType, + resourceId: resId, + apiMethod: apiMethod + } + if(this.resourceTypes.includes(resType) && this.resProvider[resType]) { - return { - method: req.method, - body: req.body, - query: req.query, - resourceType: resType, - resourceId: resId - } + return retReq } else { debug('Invalid resource type or no provider for this type!') @@ -151,10 +183,41 @@ export class Resources { if(!this.resProvider) { return {statusCode: 501, message: `No Provider`} } + if(!this.resourceTypes.includes(req.resourceType) || !this.resProvider[req.resourceType]) { return {statusCode: 501, message: `No Provider`} } + // check for API method request + if(req.apiMethod && API_METHODS.includes(req.apiMethod)) { + debug(`API Method (${req.apiMethod})`) + req= this.transformApiRequest(req) + } + + return await this.execResourceRequest(req) + } + + // ** transform API request to ResourceRequest ** + private transformApiRequest(req: ResourceRequest):ResourceRequest { + if(req.apiMethod?.indexOf('delete')!==-1) { + req.method= 'DELETE' + } + if(req.apiMethod?.indexOf('set')!==-1) { + if(!req.body.value?.id) { + req.method= 'POST' + } + else { + req.resourceId= req.body.value.id + } + req.body.value= buildResource(req.resourceType, req.body.value) ?? {} + } + console.log(req) + return req + } + + // ** action an in-scope resource request ** + private async execResourceRequest (req:ResourceRequest):Promise { + if(req.method==='GET') { let retVal: any if(!req.resourceId) { diff --git a/src/api/resources/resources.ts b/src/api/resources/resources.ts new file mode 100644 index 000000000..590a6591b --- /dev/null +++ b/src/api/resources/resources.ts @@ -0,0 +1,167 @@ +import { getDistance } from 'geolib' +import ngeohash from 'ngeohash' +import geolib from 'geolib' + +// ** build resource item ** +export const buildResource= (resType:string, data:any):any=> { + console.log(`** resType: ${resType}, data: ${JSON.stringify(data)}`) + if(resType==='routes') { return buildRoute(data) } + if(resType==='waypoints') { return buildWaypoint(data) } + if(resType==='notes') { return buildNote(data) } + if(resType==='regions') { return buildRegion(data) } +} + +// ** build route +const buildRoute= (rData:any):any=> { + let rte:any= { + feature: { + type: 'Feature', + geometry:{ + type: 'LineString', + coordinates :[] + }, + properties:{} + } + } + if(typeof rData.name !== 'undefined') { + rte.name= rData.name + rte.feature.properties.name= rData.name + } + if(typeof rData.description !== 'undefined') { + rte.description= rData.description + rte.feature.properties.description= rData.description + } + if(typeof rData.points === 'undefined') { return null } + if(!Array.isArray(rData.points)) { return null } + let isValid:boolean= true; + rData.points.forEach( (p:any)=> { + if(!geolib.isValidCoordinate(p)) { isValid= false } + }) + if(!isValid) { return null } + rData.points.forEach( (p:any)=> { + rte.feature.geometry.coordinates.push([p.longitude, p.latitude]) + }) + rte.distance= 0 + for(let i=0; i { + let wpt:any= { + position: { + latitude: 0, + longitude: 0 + }, + feature: { + type: 'Feature', + geometry:{ + type: 'Point', + coordinates :[] + }, + properties:{} + } + } + if(typeof rData.name !== 'undefined') { + wpt.feature.properties.name= rData.name + } + if(typeof rData.description !== 'undefined') { + wpt.feature.properties.description= rData.description + } + if(typeof rData.position === 'undefined') { return null } + if(!geolib.isValidCoordinate(rData.position)) { return null } + + wpt.position= rData.position + wpt.feature.geometry.coordinates= [rData.position.longitude, rData.position.latitude] + + return wpt +} + +// ** build note +const buildNote= (rData:any):any=> { + let note:any= {} + if(typeof rData.title !== 'undefined') { + note.title= rData.title + note.feature.properties.title= rData.title + } + if(typeof rData.description !== 'undefined') { + note.description= rData.description + note.feature.properties.description= rData.description + } + if(typeof rData.position === 'undefined' + && typeof rData.region === 'undefined' + && typeof rData.geohash === 'undefined') { return null } + + if(typeof rData.position !== 'undefined') { + if(!geolib.isValidCoordinate(rData.position)) { return null } + note.position= rData.position + } + if(typeof rData.region !== 'undefined') { + note.region= rData.region + } + if(typeof rData.geohash !== 'undefined') { + note.geohash= rData.geohash + } + if(typeof rData.url !== 'undefined') { + note.url= rData.url + } + if(typeof rData.mimeType !== 'undefined') { + note.mimeType= rData.mimeType + } + + return note +} + +// ** build region +const buildRegion= (rData:any):any=> { + let reg:any= { + feature: { + type: 'Feature', + geometry:{ + type: 'Polygon', + coordinates :[] + }, + properties:{} + } + } + let coords:Array<[number,number]>= [] + + if(typeof rData.name !== 'undefined') { + reg.feature.properties.name= rData.name + } + if(typeof rData.description !== 'undefined') { + reg.feature.properties.description= rData.description + } + if(typeof rData.points=== 'undefined' && rData.geohash=== 'undefined') { return null } + if(typeof rData.geohash!== 'undefined') { + reg.geohash= rData.geohash + + let bounds= ngeohash.decode_bbox(rData.geohash) + coords= [ + [bounds[1], bounds[0]], + [bounds[3], bounds[0]], + [bounds[3], bounds[2]], + [bounds[1], bounds[2]], + [bounds[1], bounds[0]], + ] + reg.feature.geometry.coordinates.push(coords) + } + if(typeof rData.points!== 'undefined' && coords.length===0 ) { + if(!Array.isArray(rData.points)) { return null } + let isValid:boolean= true; + rData.points.forEach( (p:any)=> { + if(!geolib.isValidCoordinate(p)) { isValid= false } + }) + if(!isValid) { return null } + rData.points.forEach( (p:any)=> { + coords.push([p.longitude, p.latitude]) + }) + reg.feature.geometry.coordinates.push(coords) + } + + return reg +} diff --git a/src/api/resources/validate.ts b/src/api/resources/validate.ts index 91d8b28f9..341b7465f 100644 --- a/src/api/resources/validate.ts +++ b/src/api/resources/validate.ts @@ -1,4 +1,3 @@ -//import { GeoHash, GeoBounds } from './geo'; import geoJSON from 'geojson-validation'; export const validate= { From 447ac5515155847698a54aa40ae51446c94d4ced Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Tue, 9 Nov 2021 16:44:49 +1030 Subject: [PATCH 008/410] align with openapi definitions --- src/api/resources/index.ts | 10 +++++----- src/api/resources/resources.ts | 35 ++++++++++++++++++++++------------ src/api/resources/validate.ts | 14 +++----------- 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index d84cd29a8..8d453cbc9 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -203,21 +203,21 @@ export class Resources { req.method= 'DELETE' } if(req.apiMethod?.indexOf('set')!==-1) { - if(!req.body.value?.id) { + if(!req.body.id) { req.method= 'POST' } else { - req.resourceId= req.body.value.id + req.resourceId= req.body.id } - req.body.value= buildResource(req.resourceType, req.body.value) ?? {} + req.body= { value: buildResource(req.resourceType, req.body) ?? {} } } - console.log(req) return req } // ** action an in-scope resource request ** private async execResourceRequest (req:ResourceRequest):Promise { - + debug('********* execute request *************') + debug(req) if(req.method==='GET') { let retVal: any if(!req.resourceId) { diff --git a/src/api/resources/resources.ts b/src/api/resources/resources.ts index 590a6591b..e76cce323 100644 --- a/src/api/resources/resources.ts +++ b/src/api/resources/resources.ts @@ -1,10 +1,8 @@ -import { getDistance } from 'geolib' +import { getDistance, isValidCoordinate } from 'geolib' import ngeohash from 'ngeohash' -import geolib from 'geolib' // ** build resource item ** export const buildResource= (resType:string, data:any):any=> { - console.log(`** resType: ${resType}, data: ${JSON.stringify(data)}`) if(resType==='routes') { return buildRoute(data) } if(resType==='waypoints') { return buildWaypoint(data) } if(resType==='notes') { return buildNote(data) } @@ -12,7 +10,7 @@ export const buildResource= (resType:string, data:any):any=> { } // ** build route -const buildRoute= (rData:any):any=> { +const buildRoute= (rData:any):any=> { let rte:any= { feature: { type: 'Feature', @@ -31,22 +29,27 @@ const buildRoute= (rData:any):any=> { rte.description= rData.description rte.feature.properties.description= rData.description } + if(typeof rData.attributes !== 'undefined') { + Object.assign(rte.feature.properties, rData.attributes) + } + if(typeof rData.points === 'undefined') { return null } if(!Array.isArray(rData.points)) { return null } let isValid:boolean= true; rData.points.forEach( (p:any)=> { - if(!geolib.isValidCoordinate(p)) { isValid= false } + if(!isValidCoordinate(p)) { isValid= false } }) if(!isValid) { return null } rData.points.forEach( (p:any)=> { rte.feature.geometry.coordinates.push([p.longitude, p.latitude]) }) + rte.distance= 0 - for(let i=0; i { if(typeof rData.description !== 'undefined') { wpt.feature.properties.description= rData.description } + if(typeof rData.attributes !== 'undefined') { + Object.assign(wpt.feature.properties, rData.attributes) + } + if(typeof rData.position === 'undefined') { return null } - if(!geolib.isValidCoordinate(rData.position)) { return null } + if(!isValidCoordinate(rData.position)) { return null } wpt.position= rData.position wpt.feature.geometry.coordinates= [rData.position.longitude, rData.position.latitude] @@ -97,7 +104,7 @@ const buildNote= (rData:any):any=> { && typeof rData.geohash === 'undefined') { return null } if(typeof rData.position !== 'undefined') { - if(!geolib.isValidCoordinate(rData.position)) { return null } + if(!isValidCoordinate(rData.position)) { return null } note.position= rData.position } if(typeof rData.region !== 'undefined') { @@ -136,6 +143,10 @@ const buildRegion= (rData:any):any=> { if(typeof rData.description !== 'undefined') { reg.feature.properties.description= rData.description } + if(typeof rData.attributes !== 'undefined') { + Object.assign(reg.feature.properties, rData.attributes) + } + if(typeof rData.points=== 'undefined' && rData.geohash=== 'undefined') { return null } if(typeof rData.geohash!== 'undefined') { reg.geohash= rData.geohash @@ -154,7 +165,7 @@ const buildRegion= (rData:any):any=> { if(!Array.isArray(rData.points)) { return null } let isValid:boolean= true; rData.points.forEach( (p:any)=> { - if(!geolib.isValidCoordinate(p)) { isValid= false } + if(!isValidCoordinate(p)) { isValid= false } }) if(!isValid) { return null } rData.points.forEach( (p:any)=> { diff --git a/src/api/resources/validate.ts b/src/api/resources/validate.ts index 341b7465f..840d944b2 100644 --- a/src/api/resources/validate.ts +++ b/src/api/resources/validate.ts @@ -1,4 +1,5 @@ import geoJSON from 'geojson-validation'; +import { isValidCoordinate } from 'geolib' export const validate= { resource: (type:string, value:any):boolean=> { @@ -30,9 +31,6 @@ export const validate= { // ** validate route data const validateRoute= (r:any):boolean=> { - //if(typeof r.name === 'undefined') { return false } - //if(typeof r.description === 'undefined') { return false } - if(typeof r.distance === 'undefined' || typeof r.distance !=='number') { return false } if(r.start) { let l= r.start.split('/') if(!validate.uuid(l[l.length-1])) { return false } @@ -54,12 +52,9 @@ const validateRoute= (r:any):boolean=> { // ** validate waypoint data const validateWaypoint= (r:any):boolean=> { if(typeof r.position === 'undefined') { return false } - if(typeof r.position.latitude === 'undefined' || typeof r.position.longitude === 'undefined') { + if(!isValidCoordinate(r.position)) { return false } - if(typeof r.position.latitude !== 'number' || typeof r.position.longitude !== 'number') { - return false - } try { if(!r.feature || !geoJSON.valid(r.feature)) { return false @@ -74,12 +69,9 @@ const validateWaypoint= (r:any):boolean=> { const validateNote= (r:any):boolean=> { if(!r.region && !r.position && !r.geohash ) { return false } if(typeof r.position!== 'undefined') { - if(typeof r.position.latitude === 'undefined' || typeof r.position.longitude === 'undefined') { + if(!isValidCoordinate(r.position)) { return false } - if(typeof r.position.latitude !== 'number' || typeof r.position.longitude !== 'number') { - return false - } } if(r.region) { let l= r.region.split('/') From 6450ae3143c7e834801e792947f4de670d08b872 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Thu, 11 Nov 2021 16:51:47 +1030 Subject: [PATCH 009/410] Added Resource_Provider documentation --- RESOURCE_PROVIDER_PLUGINS.md | 192 +++++++++++++++++++++++++++++++++++ SERVERPLUGINS.md | 2 + 2 files changed, 194 insertions(+) create mode 100644 RESOURCE_PROVIDER_PLUGINS.md diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md new file mode 100644 index 000000000..64ac8276a --- /dev/null +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -0,0 +1,192 @@ +# Resource Provider plugins + +## Overview + +This document should be read in conjunction with [SERVERPLUGINS.md](./SERVERPLUGINS.md) as it contains additional information regarding the development of plugins that facilitate the storage and retrieval of resource data (e.g. routes, waypoints, notes, regions and charts). + +The Signal K Node server will handle all requests to the following paths: + +`/signalk/v1/api/resources` +`/signalk/v1/api/resources/routes` +`/signalk/v1/api/resources/waypoints` +`/signalk/v1/api/resources/notes` +`/signalk/v1/api/resources/regions` +`/signalk/v1/api/resources/charts` + +This means all requests (GET, PUT, POST and DELETE) are captured and any supplied data is validated prior to being dispatched for processing. + +The server itself has no built-in functionality to save or retrieve resource data from storage, this is the responsibility of a `resource provider plugin`. + +If there are no registered providers for the resource type for which the request is made, then no action is taken. + +This architecture de-couples the resource storage from core server function to provide flexibility to implement the appropriate storage solution for the various resource types for your Signal K implementation. + +## Resource Providers + +A `resource provider plugin` is responsible for the storage and retrieval of resource data. +This allows the method for persisting resource data to be tailored to the Signal K implementation e.g. the local file system, a database service, cloud service, etc. + +It is similar to any other Server Plugin except that it implements the __resourceProvider__ interface. + +```JAVASCRIPT +resourceProvider: { + types: [], + methods: { + listResources: (type:string, query: {[key:string]:any})=> Promise + getResource: (type:string, id:string)=> Promise + setResource: (type:string, id:string, value:{[key:string]:any})=> Promise + deleteResource: (type:string, id:string)=> Promise + } +} +``` + +This interface exposes the following information to the server enabling it to direct requests to the plugin: +- `types`: The resource types for which requests should be directed to the plugin. These can be one or all of `routes, waypoints, notes, regions, charts`. +- `methods`: The methods to which the server dispatches requests. The plugin will implement these methods to perform the necessary save or retrieval operation. + +_Example: Plugin acting as resource provider for routes & waypoints._ +```JAVASCRIPT +let plugin= { + id: 'mypluginid', + name: 'My Resource Providerplugin', + start: (options, restart)=> { ... }, + stop: ()=> { ... }, + resourceProvider: { + types: ['routes','waypoints'], + methods: { + listResources: (type, params)=> { + return Promise.resolve() { ... }; + }, + getResource: (type:string, id:string)=> { + return Promise.resolve() { ... }; + } , + setResource: (type:string, id:string, value:any)=> { + return Promise.resolve() { ... }; + }, + deleteResource: (type:string, id:string)=> { + return Promise.resolve() { ... }; ; + } + } + } +} +``` + +### Methods: + +The Server will dispatch requests to `/signalk/v1/api/resources/` to the methods defined in the resourceProvider interface. + +Each method must have a signature as defined in the interface and return a `Promise` containing the resource data or `null` if the operation was unsuccessful. + +--- +`GET` requests that are not for a specific resource will be dispatched to the `listResources` method passing the resource type and query data as parameters. + +Returns: Object listing resources by id. + +_Example: List all routes._ +```JAVASCRIPT +GET /signalk/v1/api/resources/routes + +listResources('routes', {}) + +returns { + "resource_id1": { ... }, + "resource_id2": { ... }, + ... + "resource_idn": { ... } +} +``` + +_Example: List routes within the bounded area._ +```JAVASCRIPT +GET /signalk/v1/api/resources/routes?bbox=5.4,25.7,6.9,31.2 + +listResources('routes', {bbox: '5.4,25.7,6.9,31.2'}) + +returns { + "resource_id1": { ... }, + "resource_id2": { ... } +} +``` + +`GET` requests for a specific resource will be dispatched to the `getResource` method passing the resource type and id as parameters. + +Returns: Object containing resourcesdata. + +_Example: Retrieve route._ +```JAVASCRIPT +GET /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a + +getResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a') + +returns { + "name": "route name", + ... + "feature": { ... } +} +``` +--- + +`PUT` requests for a specific resource will be dispatched to the `setResource` method passing the resource type, id and resource data as parameters. + +Returns: `true` on success, `null` on failure. + +_Example: Update route data._ +```JAVASCRIPT +PUT /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a {resource data} + +setResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) +``` + +`POST` requests will be dispatched to the `setResource` method passing the resource type, a generated id and resource data as parameters. + +Returns: `true` on success, `null` on failure. + +_Example: New route._ +```JAVASCRIPT +POST /signalk/v1/api/resources/routes/ {resource data} + +setResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) +``` +--- + +`DELETE` requests for a specific resource will be dispatched to the `deleteResource` method passing the resource type and id as parameters. + +Returns: `true` on success, `null` on failure. + +_Example: Delete route._ +```JAVASCRIPT +DELETE /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a {resource data} + +deleteResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a') +``` +--- + +### Plugin Startup: + +If your plugin provides the ability for the user to choose which resource types are handled, then the plugin will need to notify the server that the `types` attribute of the `resourceProvider` interface has been modified. + +The server exposes `resourcesApi` which has the following method: +```JAVASCRIPT +checkForProviders(rescan:boolean) +``` +which can be called within the plugin `start()` function with `rescan= true`. + +This will cause the server to `rescan` for resource providers and register the new list of resource types being handled. + +_Example:_ +```JAVASCRIPT +module.exports = function (app) { + let plugin= { + id: 'mypluginid', + name: 'My Resource Providerplugin', + start: (options, restart)=> { + ... + setTimeout( ()=> { app.resourcesApi.checkForProviders(true) }, 1000) + ... + }, + stop: ()=> { ... }, + ... + } +} +``` + diff --git a/SERVERPLUGINS.md b/SERVERPLUGINS.md index 72e8f7634..306921a4e 100644 --- a/SERVERPLUGINS.md +++ b/SERVERPLUGINS.md @@ -24,6 +24,8 @@ The plugin module must export a single `function(app)` that must return an objec To get started with SignalK plugin development, you can follow the following guide. +_Note: For plugins acting as a `provider` for one or more of the resource types listed in the Signal K specification (e.g. routes, waypoints, notes, regions or charts) there are additional requirementss which are detailed in __[RESOURCE_PROVIDER_PLUGINS.md](./RESOURCE_PROVIDER_PLUGINS.md)__._ + ### Project setup First, create a new directory and initialize a new module: From 91f2e2400b265f4e8b9b8520d64750e29f178be1 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 14 Nov 2021 19:32:38 +1030 Subject: [PATCH 010/410] Add register / unregister --- RESOURCE_PROVIDER_PLUGINS.md | 258 ++++++++++++++++++++++------------- SERVERPLUGINS.md | 37 ++++- src/api/resources/index.ts | 28 +++- 3 files changed, 221 insertions(+), 102 deletions(-) diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index 64ac8276a..4b80ba853 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -4,31 +4,34 @@ This document should be read in conjunction with [SERVERPLUGINS.md](./SERVERPLUGINS.md) as it contains additional information regarding the development of plugins that facilitate the storage and retrieval of resource data (e.g. routes, waypoints, notes, regions and charts). -The Signal K Node server will handle all requests to the following paths: +Resource storage and retrieval is de-coupled from core server function to provide the flexibility to implement the appropriate resource storage solution for specific Signal K implementations. -`/signalk/v1/api/resources` -`/signalk/v1/api/resources/routes` -`/signalk/v1/api/resources/waypoints` -`/signalk/v1/api/resources/notes` -`/signalk/v1/api/resources/regions` -`/signalk/v1/api/resources/charts` +The Signal K Node server will pass requests made to the following paths to registered resource providers: +- `/signalk/v1/api/resources` +- `/signalk/v1/api/resources/routes` +- `/signalk/v1/api/resources/waypoints` +- `/signalk/v1/api/resources/notes` +- `/signalk/v1/api/resources/regions` +- `/signalk/v1/api/resources/charts` -This means all requests (GET, PUT, POST and DELETE) are captured and any supplied data is validated prior to being dispatched for processing. +Resource providers will receive request data via a `ResourceProvider` interface which they implement. It is the responsibility of the resource provider to persist resource data in storage (PUT, POST), retrieve the requested resources (GET) and remove resource entries (DELETE). -The server itself has no built-in functionality to save or retrieve resource data from storage, this is the responsibility of a `resource provider plugin`. +Resource data passed to the resource provider plugin has been validated by the server and can be considered ready for storage. -If there are no registered providers for the resource type for which the request is made, then no action is taken. - -This architecture de-couples the resource storage from core server function to provide flexibility to implement the appropriate storage solution for the various resource types for your Signal K implementation. ## Resource Providers A `resource provider plugin` is responsible for the storage and retrieval of resource data. -This allows the method for persisting resource data to be tailored to the Signal K implementation e.g. the local file system, a database service, cloud service, etc. -It is similar to any other Server Plugin except that it implements the __resourceProvider__ interface. +It should implement the necessary functions to: +- Persist each resource with its associated id +- Retrieve an individual resource with the supplied id +- Retrieve a list of resources that match the supplied qery criteria. + +Data is passed to and from the plugin via the methods defined in the __resourceProvider__ interface which the plugin must implement. -```JAVASCRIPT +_Definition: `resourceProvider` interface._ +```javascript resourceProvider: { types: [], methods: { @@ -40,50 +43,130 @@ resourceProvider: { } ``` -This interface exposes the following information to the server enabling it to direct requests to the plugin: -- `types`: The resource types for which requests should be directed to the plugin. These can be one or all of `routes, waypoints, notes, regions, charts`. -- `methods`: The methods to which the server dispatches requests. The plugin will implement these methods to perform the necessary save or retrieval operation. +This interface is used by the server to direct requests to the plugin. + +It contains the following attributes: +- `types`: An array containing the names of the resource types the plugin is a provider for. Names of the resource types are: `routes, waypoints, notes, regions, charts`. + +- `methods`: The methods to which the server dispatches requests. The plugin will implement these methods to perform the necessary save or retrieval operation. Each method returns a promise containing either resource data or `null` if an error is encountered. _Example: Plugin acting as resource provider for routes & waypoints._ -```JAVASCRIPT -let plugin= { +```javascript +module.exports = function (app) { + let plugin= { + id: 'mypluginid', + name: 'My Resource Providerplugin', + resourceProvider: { + types: ['routes','waypoints'], + methods: { + listResources: (type, params)=> { + return Promise.resolve() { ... }; + }, + getResource: (type:string, id:string)=> { + return Promise.resolve() { ... }; + } , + setResource: (type:string, id:string, value:any)=> { + return Promise.resolve() { ... }; + }, + deleteResource: (type:string, id:string)=> { + return Promise.resolve() { ... }; ; + } + } + }, + start: (options, restart)=> { + ... + app.resourceApi.register(this.resourceProvider); + }, + stop: ()=> { + app.resourceApi.unRegister(this.resourceProvider.types); + ... + } + } +} +``` + +--- + +### Plugin Startup - Registering the Resource Provider: + +To register your plugin as a resource provider the server's `resourcesApi.register()` function should be called within the plugin `start()` function passing the `resourceProvider` interface. + +This registers the resource types and the methods with the server so they are called when requests to resource paths are made. + +_Example:_ +```javascript +module.exports = function (app) { + let plugin= { id: 'mypluginid', name: 'My Resource Providerplugin', - start: (options, restart)=> { ... }, - stop: ()=> { ... }, resourceProvider: { - types: ['routes','waypoints'], - methods: { - listResources: (type, params)=> { - return Promise.resolve() { ... }; - }, - getResource: (type:string, id:string)=> { - return Promise.resolve() { ... }; - } , - setResource: (type:string, id:string, value:any)=> { - return Promise.resolve() { ... }; - }, - deleteResource: (type:string, id:string)=> { - return Promise.resolve() { ... }; ; - } + types: ['routes','waypoints'], + methods: { + listResources: (type, params)=> { + return Promise.resolve() { ... }; + }, + getResource: (type:string, id:string)=> { + return Promise.resolve() { ... }; + } , + setResource: (type:string, id:string, value:any)=> { + return Promise.resolve() { ... }; + }, + deleteResource: (type:string, id:string)=> { + return Promise.resolve() { ... }; ; } + } } + } + + plugin.start = function(options) { + ... + app.resourcesApi.register(plugin.resourceProvider); + } } ``` +--- -### Methods: +### Plugin Stop - Un-registering the Resource Provider: -The Server will dispatch requests to `/signalk/v1/api/resources/` to the methods defined in the resourceProvider interface. +When a resource provider plugin is disabled / stopped it should un-register as a provider so resource requests are not directed to it. This is done by calling the server's `resourcesApi.unRegister()` function passing `resourceProvider.types` within the plugin's `stop()` function. -Each method must have a signature as defined in the interface and return a `Promise` containing the resource data or `null` if the operation was unsuccessful. +_Example:_ +```javascript +module.exports = function (app) { + let plugin= { + id: 'mypluginid', + name: 'My Resource Providerplugin', + resourceProvider: { + types: ['routes','waypoints'], + methods: { ... } + } + } + plugin.stop = function(options) { + ... + app.resourcesApi.unRegister(plugin.resourceProvider.types); + } +} +``` --- -`GET` requests that are not for a specific resource will be dispatched to the `listResources` method passing the resource type and query data as parameters. -Returns: Object listing resources by id. +### Operation: + +The Server will dispatch requests made to `/signalk/v1/api/resources/` to the plugin's `resourceProvider.methods` for each resource type listed in `resourceProvider.types`. + +Each method defined in the plugin must have a signature as specified in the interface. Each method returns a `Promise` containing the resource data or `null` if an error occurrred. + + +### __List Resources:__ + +`GET` requests that are not for a specific resource will be dispatched to the `listResources` method passing the resource type and any query data as parameters. + +It is the responsibility of the resource provider plugin to filter the resources returned as per the supplied query parameters. + +`listResources()` should return a JSON object listing resources by id. _Example: List all routes._ -```JAVASCRIPT +```javascript GET /signalk/v1/api/resources/routes listResources('routes', {}) @@ -96,11 +179,11 @@ returns { } ``` -_Example: List routes within the bounded area._ -```JAVASCRIPT -GET /signalk/v1/api/resources/routes?bbox=5.4,25.7,6.9,31.2 +_Example: List waypoints within the bounded area._ +```javascript +GET /signalk/v1/api/resources/waypoints?bbox=5.4,25.7,6.9,31.2 -listResources('routes', {bbox: '5.4,25.7,6.9,31.2'}) +listResources('waypoints', {bbox: '5.4,25.7,6.9,31.2'}) returns { "resource_id1": { ... }, @@ -108,85 +191,66 @@ returns { } ``` +### __Get specific resource:__ + `GET` requests for a specific resource will be dispatched to the `getResource` method passing the resource type and id as parameters. -Returns: Object containing resourcesdata. +`getResource()` should returns a JSON object containing the resource data. _Example: Retrieve route._ -```JAVASCRIPT +```javascript GET /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a getResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a') returns { - "name": "route name", - ... + "name": "Name of the route", + "description": "Description of the route", + "distance": 18345, "feature": { ... } } ``` ---- -`PUT` requests for a specific resource will be dispatched to the `setResource` method passing the resource type, id and resource data as parameters. +### __Saving Resources:__ -Returns: `true` on success, `null` on failure. +`PUT` requests to a path containing the resource id are used to store data associated with the resource id. These will be dispatched to the `setResource` method passing the resource type, id and data as parameters. -_Example: Update route data._ -```JAVASCRIPT -PUT /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a {resource data} +`setResource() ` returns `true` on success and `null` on failure. -setResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) -``` - -`POST` requests will be dispatched to the `setResource` method passing the resource type, a generated id and resource data as parameters. - -Returns: `true` on success, `null` on failure. +_Example: Update / add waypoint with the supplied id._ +```javascript +PUT /signalk/v1/api/resources/waypoints/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a {} -_Example: New route._ -```JAVASCRIPT -POST /signalk/v1/api/resources/routes/ {resource data} +setResource('waypoints', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) -setResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) +returns true | null ``` ---- -`DELETE` requests for a specific resource will be dispatched to the `deleteResource` method passing the resource type and id as parameters. +`POST` requests to a resource path that do not contina the resource id will be dispatched to the `setResource` method passing the resource type, an id (generated by the server) and resource data as parameters. -Returns: `true` on success, `null` on failure. +`setResource() ` returns `true` on success and `null` on failure. -_Example: Delete route._ -```JAVASCRIPT -DELETE /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a {resource data} +_Example: New route record._ +```javascript +POST /signalk/v1/api/resources/routes {} + +setResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) -deleteResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a') +returns true | null ``` ---- -### Plugin Startup: +### __Deleting Resources:__ -If your plugin provides the ability for the user to choose which resource types are handled, then the plugin will need to notify the server that the `types` attribute of the `resourceProvider` interface has been modified. +`DELETE` requests to a path containing the resource id will be dispatched to the `deleteResource` method passing the resource type and id as parameters. -The server exposes `resourcesApi` which has the following method: -```JAVASCRIPT -checkForProviders(rescan:boolean) -``` -which can be called within the plugin `start()` function with `rescan= true`. +`deleteResource()` returns `true` on success, `null` on failure. -This will cause the server to `rescan` for resource providers and register the new list of resource types being handled. +_Example: Delete region with supplied id._ +```javascript +DELETE /signalk/v1/api/resources/regions/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a -_Example:_ -```JAVASCRIPT -module.exports = function (app) { - let plugin= { - id: 'mypluginid', - name: 'My Resource Providerplugin', - start: (options, restart)=> { - ... - setTimeout( ()=> { app.resourcesApi.checkForProviders(true) }, 1000) - ... - }, - stop: ()=> { ... }, - ... - } -} +deleteResource('regions', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a') + +returns true | null ``` diff --git a/SERVERPLUGINS.md b/SERVERPLUGINS.md index 306921a4e..729293d5c 100644 --- a/SERVERPLUGINS.md +++ b/SERVERPLUGINS.md @@ -24,7 +24,7 @@ The plugin module must export a single `function(app)` that must return an objec To get started with SignalK plugin development, you can follow the following guide. -_Note: For plugins acting as a `provider` for one or more of the resource types listed in the Signal K specification (e.g. routes, waypoints, notes, regions or charts) there are additional requirementss which are detailed in __[RESOURCE_PROVIDER_PLUGINS.md](./RESOURCE_PROVIDER_PLUGINS.md)__._ +_Note: For plugins acting as a provider for one or more of the resource types listed in the Signal K specification (e.g. routes, waypoints, notes, regions or charts) refer to __[RESOURCE_PROVIDER_PLUGINS.md](./RESOURCE_PROVIDER_PLUGINS.md)__ for details. ### Project setup @@ -700,6 +700,41 @@ app.registerDeltaInputHandler((delta, next) => { }) ``` +### `app.resourcesApi.register(provider)` + +If a plugin wants to act as a resource provider, it will need to register its provider methods during startup using this function. + +See [`RESOURCE_PROVIDER_PLUGINS.md`](./RESOURCE_PROVIDER_PLUGINS.md) for details. + + +```javascript +plugin.start = function(options) { + ... + // plugin_provider is the plugin's `ResourceProvider` interface. + app.resourcesApi.register(plugin_provider); +} + +``` + + + +### `app.resourcesApi.unRegister(resource_types)` + +When a resource provider plugin is disabled it will need to un-register its provider methods for the resource types it manages. This should be done in the plugin's `stop()` function. + +See [`RESOURCE_PROVIDER_PLUGINS.md`](./RESOURCE_PROVIDER_PLUGINS.md) for details. + + +```javascript +plugin.stop = function(options) { + // resource_types example: ['routes',waypoints'] + app.resourcesApi.unRegister(resource_types); + ... +} + +``` + + ### `app.setPluginStatus(msg)` Set the current status of the plugin. The `msg` should be a short message describing the current status of the plugin and will be displayed in the plugin configuration UI and the Dashboard. diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 8d453cbc9..4375f2301 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -55,9 +55,32 @@ export class Resources { this.initResourceRoutes() } + public register(provider:any) { + debug(`** Registering provider(s)....${provider?.types}`) + if(!provider ) { return } + if(provider.types && !Array.isArray(provider.types)) { return } + provider.types.forEach( (i:string)=>{ + if(!this.resProvider[i]) { + this.resProvider[i]= provider.methods + } + }) + debug(this.resProvider) + } + + public unRegister(resourceTypes:string[]) { + debug(`** Un-registering provider(s)....${resourceTypes}`) + if(!Array.isArray(resourceTypes)) { return } + resourceTypes.forEach( (i:string)=>{ + if(this.resProvider[i]) { + delete this.resProvider[i] + } + }) + debug(JSON.stringify(this.resProvider)) + } + public checkForProviders(rescan:boolean= false) { if(rescan || Object.keys(this.resProvider).length===0) { - debug('** Checking for providers....') + debug(`** Checking for providers....(rescan=${rescan})`) this.resProvider= {} this.resourceTypes.forEach( (rt:string)=> { this.resProvider[rt]= this.getResourceProviderFor(rt) @@ -68,7 +91,6 @@ export class Resources { public getResource(type:string, id:string) { debug(`** getResource(${type}, ${id})`) - this.checkForProviders() return this.actionResourceRequest({ method: 'GET', body: {}, @@ -82,7 +104,6 @@ export class Resources { // ** initialise handler for in-scope resource types ** private initResourceRoutes() { this.server.get(`${SIGNALK_API_PATH}/resources`, (req:any, res:any) => { - this.checkForProviders() // list all serviced paths under resources res.json(this.getResourcePaths()) }) @@ -155,7 +176,6 @@ export class Resources { } } - this.checkForProviders() let retReq= { method: req.method, body: req.body, From b8eefceeec022efe3e5ad796eb9db8dbadbea817 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 15 Nov 2021 10:10:21 +1030 Subject: [PATCH 011/410] add constructor --- src/api/resources/index.ts | 67 +++++++++++++++++++++++++------------- src/index.js | 3 +- 2 files changed, 46 insertions(+), 24 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 4375f2301..fba887dc8 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -15,6 +15,11 @@ interface ResourceRequest { } interface ResourceProvider { + types: Array + methods: ResourceProviderMethods +} + +interface ResourceProviderMethods { listResources: (type:string, query: {[key:string]:any})=> Promise getResource: (type:string, id:string)=> Promise setResource: (type:string, id:string, value:{[key:string]:any})=> Promise @@ -46,16 +51,22 @@ export class Resources { 'charts' ] - resProvider: {[key:string]: any}= {} + resProvider: {[key:string]: ResourceProviderMethods | null}= {} server: any - public start(app:any) { + constructor(app:any) { + this.start(app) + } + + // ** initialise resourcesApi ** + private start(app:any) { debug(`** Initialise ${SIGNALK_API_PATH}/resources path handler **`) this.server= app this.initResourceRoutes() } - public register(provider:any) { + // ** register resource provider ** + public register(provider:ResourceProvider) { debug(`** Registering provider(s)....${provider?.types}`) if(!provider ) { return } if(provider.types && !Array.isArray(provider.types)) { return } @@ -67,6 +78,7 @@ export class Resources { debug(this.resProvider) } + // ** un-register resource provider for the supplied types ** public unRegister(resourceTypes:string[]) { debug(`** Un-registering provider(s)....${resourceTypes}`) if(!Array.isArray(resourceTypes)) { return } @@ -76,19 +88,15 @@ export class Resources { } }) debug(JSON.stringify(this.resProvider)) - } - public checkForProviders(rescan:boolean= false) { - if(rescan || Object.keys(this.resProvider).length===0) { - debug(`** Checking for providers....(rescan=${rescan})`) - this.resProvider= {} - this.resourceTypes.forEach( (rt:string)=> { - this.resProvider[rt]= this.getResourceProviderFor(rt) - }) - debug(this.resProvider) - } + /** scan plugins in case there is more than one plugin that can service + * a particular resource type. **/ + debug('** RESCANNING **') + this.checkForProviders() + debug(JSON.stringify(this.resProvider)) } + // ** return resource with supplied type and id ** public getResource(type:string, id:string) { debug(`** getResource(${type}, ${id})`) return this.actionResourceRequest({ @@ -100,6 +108,20 @@ export class Resources { }) } + /** Scan plugins for resource providers and register them + * rescan= false: only add providers for types where no provider is registered + * rescan= true: clear providers for all types prior to commencing scan. + **/ + private checkForProviders(rescan:boolean= false) { + if(rescan) { this.resProvider= {} } + debug(`** Checking for providers....(rescan=${rescan})`) + this.resProvider= {} + this.resourceTypes.forEach( (rt:string)=> { + this.resProvider[rt]= this.getResourceProviderFor(rt) + }) + debug(this.resProvider) + + } // ** initialise handler for in-scope resource types ** private initResourceRoutes() { @@ -127,7 +149,7 @@ export class Resources { }) } - // ** return all paths serviced under ./resources *8 + // ** return all paths serviced under SIGNALK_API_PATH/resources ** private getResourcePaths(): {[key:string]:any} { let resPaths:{[key:string]:any}= {} Object.entries(this.resProvider).forEach( (p:any)=> { @@ -241,7 +263,7 @@ export class Resources { if(req.method==='GET') { let retVal: any if(!req.resourceId) { - retVal= await this.resProvider[req.resourceType].listResources(req.resourceType, req.query) + retVal= await this.resProvider[req.resourceType]?.listResources(req.resourceType, req.query) return (retVal) ? retVal : {statusCode: 404, message: `Error retrieving resources!` } @@ -249,7 +271,7 @@ export class Resources { if(!validate.uuid(req.resourceId)) { return {statusCode: 406, message: `Invalid resource id provided (${req.resourceId})` } } - retVal= await this.resProvider[req.resourceType].getResource(req.resourceType, req.resourceId) + retVal= await this.resProvider[req.resourceType]?.getResource(req.resourceType, req.resourceId) return (retVal) ? retVal : {statusCode: 404, message: `Resource not found (${req.resourceId})!` } @@ -266,7 +288,7 @@ export class Resources { req.method==='DELETE' || (req.method==='PUT' && typeof req.body.value!=='undefined' && req.body.value==null) ) { - let retVal= await this.resProvider[req.resourceType].deleteResource(req.resourceType, req.resourceId) + let retVal= await this.resProvider[req.resourceType]?.deleteResource(req.resourceType, req.resourceId) if(retVal){ this.sendDelta(req.resourceType, req.resourceId, null) return {statusCode: 200, message: `Resource (${req.resourceId}) deleted.`} @@ -289,7 +311,7 @@ export class Resources { } if(req.method==='POST') { let id= UUID_PREFIX + uuidv4() - let retVal= await this.resProvider[req.resourceType].setResource(req.resourceType, id, req.body.value) + let retVal= await this.resProvider[req.resourceType]?.setResource(req.resourceType, id, req.body.value) if(retVal){ this.sendDelta(req.resourceType, id, req.body.value) return {statusCode: 200, message: `Resource (${id}) saved.`} @@ -302,7 +324,7 @@ export class Resources { if(!req.resourceId) { return {statusCode: 406, message: `No resource id provided!` } } - let retVal= await this.resProvider[req.resourceType].setResource(req.resourceType, req.resourceId, req.body.value) + let retVal= await this.resProvider[req.resourceType]?.setResource(req.resourceType, req.resourceId, req.body.value) if(retVal){ this.sendDelta(req.resourceType, req.resourceId, req.body.value) return {statusCode: 200, message: `Resource (${req.resourceId}) updated.`} @@ -314,6 +336,7 @@ export class Resources { } } + // ** send delta message with resource PUT, POST, DELETE action result private sendDelta(type:string, id:string, value:any):void { debug(`** Sending Delta: resources.${type}.${id}`) this.server.handleMessage('signalk-resources', { @@ -330,10 +353,10 @@ export class Resources { }) } - // ** get reference to installed resource provider (plug-in). returns null if none found - private getResourceProviderFor(resType:string): ResourceProvider | null { + // ** Get provider methods for supplied resource type. Returns null if none found ** + private getResourceProviderFor(resType:string): ResourceProviderMethods | null { if(!this.server.plugins) { return null} - let pSource: ResourceProvider | null= null + let pSource: ResourceProviderMethods | null= null this.server.plugins.forEach((plugin:any)=> { if(typeof plugin.resourceProvider !== 'undefined') { pSource= plugin.resourceProvider.types.includes(resType) ? diff --git a/src/index.js b/src/index.js index 503537045..52472cb04 100644 --- a/src/index.js +++ b/src/index.js @@ -71,8 +71,7 @@ function Server(opts) { require('./put').start(app) // ** initialise resources API ** - app.resourcesApi= new Resources() - app.resourcesApi.start(app) + app.resourcesApi= new Resources(app) app.signalk = new FullSignalK(app.selfId, app.selfType) From aa9c223897f2a65fdc7e120d93d7d3ef0e469b09 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 15 Nov 2021 10:10:47 +1030 Subject: [PATCH 012/410] add getResource function --- SERVERPLUGINS.md | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/SERVERPLUGINS.md b/SERVERPLUGINS.md index 729293d5c..6f130d9d4 100644 --- a/SERVERPLUGINS.md +++ b/SERVERPLUGINS.md @@ -700,6 +700,30 @@ app.registerDeltaInputHandler((delta, next) => { }) ``` +### `app.resourcesApi.getResource(resource_type, resource_id)` + +Retrieve resource data for the supplied resource type and id. + + data for the full path of the directory where the plugin can persist its internal data, like data files.If a plugin wants to act as a resource provider, it will need to register its provider methods during startup using this function. + + + +```javascript +let myRoute= app.resourcesApi.getResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a'); +``` +Will return the route resource data or `null` if a route with the supplied id cannot be found. + +_Example:_ +```json +{ + "name": "Name of the route", + "description": "Description of the route", + "distance": 18345, + "feature": { ... } +} + +``` + ### `app.resourcesApi.register(provider)` If a plugin wants to act as a resource provider, it will need to register its provider methods during startup using this function. @@ -716,8 +740,6 @@ plugin.start = function(options) { ``` - - ### `app.resourcesApi.unRegister(resource_types)` When a resource provider plugin is disabled it will need to un-register its provider methods for the resource types it manages. This should be done in the plugin's `stop()` function. From f4429c04df906752f53f6a6ae356a6ca55f0dc12 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 15 Nov 2021 10:31:19 +1030 Subject: [PATCH 013/410] chore: fix formatting --- src/api/resources/index.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index fba887dc8..05c05761b 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -89,8 +89,7 @@ export class Resources { }) debug(JSON.stringify(this.resProvider)) - /** scan plugins in case there is more than one plugin that can service - * a particular resource type. **/ + //** scan plugins in case there is more than one plugin that can service a particular resource type. ** debug('** RESCANNING **') this.checkForProviders() debug(JSON.stringify(this.resProvider)) @@ -108,10 +107,9 @@ export class Resources { }) } - /** Scan plugins for resource providers and register them - * rescan= false: only add providers for types where no provider is registered - * rescan= true: clear providers for all types prior to commencing scan. - **/ + // Scan plugins for resource providers and register them + // rescan= false: only add providers for types where no provider is registered + // rescan= true: clear providers for all types prior to commencing scan. private checkForProviders(rescan:boolean= false) { if(rescan) { this.resProvider= {} } debug(`** Checking for providers....(rescan=${rescan})`) From 4624e4b5038d051032e078aa1f00d81eb7476959 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 15 Nov 2021 10:41:56 +1030 Subject: [PATCH 014/410] OpenApi descriptions --- src/api/resources/openApi.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/api/resources/openApi.json b/src/api/resources/openApi.json index d7a7f6e35..8eddcac32 100644 --- a/src/api/resources/openApi.json +++ b/src/api/resources/openApi.json @@ -29,15 +29,15 @@ } }, - "/resources/{resourceClass}": { + "/resources/{resourceType}": { "get": { "tags": ["resources"], "summary": "Retrieve resources", "parameters": [ { - "name": "resourceClass", + "name": "resourceType", "in": "path", - "description": "resource class", + "description": "Type of resources to retrieve. Valid values are: routes, waypoints, notes, regions, charts", "required": true, "schema": { "type": "string", @@ -68,8 +68,8 @@ } }, { - "in": "query", "name": "bbox", + "in": "query", "description": "Limit results to resources that fall within the bounded area defined as lower left (south west) and upper right (north east) coordinates [swlon,swlat,nelon,nelat]", "style": "form", "explode": false, From 8c17182f8c1053479179e048ee9e7d834fcf9242 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 20 Nov 2021 13:46:56 +1030 Subject: [PATCH 015/410] add pluginId to register() function --- RESOURCE_PROVIDER_PLUGINS.md | 4 ++-- SERVERPLUGINS.md | 22 +++++++++++++++------- src/api/resources/index.ts | 4 +++- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index 4b80ba853..24331b387 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -75,7 +75,7 @@ module.exports = function (app) { }, start: (options, restart)=> { ... - app.resourceApi.register(this.resourceProvider); + app.resourceApi.register(this.id, this.resourceProvider); }, stop: ()=> { app.resourceApi.unRegister(this.resourceProvider.types); @@ -120,7 +120,7 @@ module.exports = function (app) { plugin.start = function(options) { ... - app.resourcesApi.register(plugin.resourceProvider); + app.resourcesApi.register(plugin.id, plugin.resourceProvider); } } ``` diff --git a/SERVERPLUGINS.md b/SERVERPLUGINS.md index 6f130d9d4..b6374fe6a 100644 --- a/SERVERPLUGINS.md +++ b/SERVERPLUGINS.md @@ -724,7 +724,7 @@ _Example:_ ``` -### `app.resourcesApi.register(provider)` +### `app.resourcesApi.register(pluginId, provider)` If a plugin wants to act as a resource provider, it will need to register its provider methods during startup using this function. @@ -732,12 +732,20 @@ See [`RESOURCE_PROVIDER_PLUGINS.md`](./RESOURCE_PROVIDER_PLUGINS.md) for details ```javascript -plugin.start = function(options) { - ... - // plugin_provider is the plugin's `ResourceProvider` interface. - app.resourcesApi.register(plugin_provider); -} - +module.exports = function (app) { + let plugin= { + id: 'mypluginid', + name: 'My Resource Providerplugin', + resourceProvider: { + types: ['routes','waypoints'], + methods: { ... } + } + start: function(options) { + ... + app.resourcesApi.register(this.id, this.resourceProvider); + } + ... + } ``` ### `app.resourcesApi.unRegister(resource_types)` diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 05c05761b..24568de8e 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -20,6 +20,7 @@ interface ResourceProvider { } interface ResourceProviderMethods { + pluginId: string listResources: (type:string, query: {[key:string]:any})=> Promise getResource: (type:string, id:string)=> Promise setResource: (type:string, id:string, value:{[key:string]:any})=> Promise @@ -66,12 +67,13 @@ export class Resources { } // ** register resource provider ** - public register(provider:ResourceProvider) { + public register(pluginId:string, provider:ResourceProvider) { debug(`** Registering provider(s)....${provider?.types}`) if(!provider ) { return } if(provider.types && !Array.isArray(provider.types)) { return } provider.types.forEach( (i:string)=>{ if(!this.resProvider[i]) { + provider.methods.pluginId= pluginId this.resProvider[i]= provider.methods } }) From a8715177748e2aa73a518f58f590290061ad0d9a Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 20 Nov 2021 14:17:32 +1030 Subject: [PATCH 016/410] add pluginId to unRegister function --- RESOURCE_PROVIDER_PLUGINS.md | 10 ++++----- SERVERPLUGINS.md | 24 +++++++++++++++------- src/api/resources/index.ts | 40 +++--------------------------------- 3 files changed, 25 insertions(+), 49 deletions(-) diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index 24331b387..e90b45d08 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -73,12 +73,12 @@ module.exports = function (app) { } } }, - start: (options, restart)=> { + start: (options)=> { ... app.resourceApi.register(this.id, this.resourceProvider); }, stop: ()=> { - app.resourceApi.unRegister(this.resourceProvider.types); + app.resourceApi.unRegister(this.id, this.resourceProvider.types); ... } } @@ -89,7 +89,7 @@ module.exports = function (app) { ### Plugin Startup - Registering the Resource Provider: -To register your plugin as a resource provider the server's `resourcesApi.register()` function should be called within the plugin `start()` function passing the `resourceProvider` interface. +To register your plugin as a resource provider the server's `resourcesApi.register()` function should be called within the plugin `start()` function passing the `resourceProvider` interface. This registers the resource types and the methods with the server so they are called when requests to resource paths are made. @@ -128,7 +128,7 @@ module.exports = function (app) { ### Plugin Stop - Un-registering the Resource Provider: -When a resource provider plugin is disabled / stopped it should un-register as a provider so resource requests are not directed to it. This is done by calling the server's `resourcesApi.unRegister()` function passing `resourceProvider.types` within the plugin's `stop()` function. +When a resource provider plugin is disabled it should un-register as a provider so resource requests are not directed to it. This is done by calling the server's `resourcesApi.unRegister()` function passing `resourceProvider.types` within the plugin's `stop()` function. _Example:_ ```javascript @@ -144,7 +144,7 @@ module.exports = function (app) { plugin.stop = function(options) { ... - app.resourcesApi.unRegister(plugin.resourceProvider.types); + app.resourcesApi.unRegister(plugin.id, plugin.resourceProvider.types); } } ``` diff --git a/SERVERPLUGINS.md b/SERVERPLUGINS.md index b6374fe6a..fa0c0f7b9 100644 --- a/SERVERPLUGINS.md +++ b/SERVERPLUGINS.md @@ -746,22 +746,32 @@ module.exports = function (app) { } ... } +} ``` -### `app.resourcesApi.unRegister(resource_types)` +### `app.resourcesApi.unRegister(pluginId, resource_types)` -When a resource provider plugin is disabled it will need to un-register its provider methods for the resource types it manages. This should be done in the plugin's `stop()` function. +When a resource provider plugin is disabled it will need to un-register its provider methods for all of the resource types it manages. This should be done in the plugin's `stop()` function. See [`RESOURCE_PROVIDER_PLUGINS.md`](./RESOURCE_PROVIDER_PLUGINS.md) for details. ```javascript -plugin.stop = function(options) { - // resource_types example: ['routes',waypoints'] - app.resourcesApi.unRegister(resource_types); - ... +module.exports = function (app) { + let plugin= { + id: 'mypluginid', + name: 'My Resource Providerplugin', + resourceProvider: { + types: ['routes','waypoints'], + methods: { ... } + } + ... + stop: function(options) { + app.resourcesApi.unRegister(this.id, this.resourceProvider.types); + ... + } + } } - ``` diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 24568de8e..ae2bb68c0 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -81,20 +81,15 @@ export class Resources { } // ** un-register resource provider for the supplied types ** - public unRegister(resourceTypes:string[]) { - debug(`** Un-registering provider(s)....${resourceTypes}`) + public unRegister(pluginId:string, resourceTypes:string[]) { + debug(`** Un-registering ${pluginId} provider(s)....${resourceTypes}`) if(!Array.isArray(resourceTypes)) { return } resourceTypes.forEach( (i:string)=>{ - if(this.resProvider[i]) { + if(this.resProvider[i] && this.resProvider[i]?.pluginId===pluginId) { delete this.resProvider[i] } }) debug(JSON.stringify(this.resProvider)) - - //** scan plugins in case there is more than one plugin that can service a particular resource type. ** - debug('** RESCANNING **') - this.checkForProviders() - debug(JSON.stringify(this.resProvider)) } // ** return resource with supplied type and id ** @@ -109,20 +104,6 @@ export class Resources { }) } - // Scan plugins for resource providers and register them - // rescan= false: only add providers for types where no provider is registered - // rescan= true: clear providers for all types prior to commencing scan. - private checkForProviders(rescan:boolean= false) { - if(rescan) { this.resProvider= {} } - debug(`** Checking for providers....(rescan=${rescan})`) - this.resProvider= {} - this.resourceTypes.forEach( (rt:string)=> { - this.resProvider[rt]= this.getResourceProviderFor(rt) - }) - debug(this.resProvider) - - } - // ** initialise handler for in-scope resource types ** private initResourceRoutes() { this.server.get(`${SIGNALK_API_PATH}/resources`, (req:any, res:any) => { @@ -353,19 +334,4 @@ export class Resources { }) } - // ** Get provider methods for supplied resource type. Returns null if none found ** - private getResourceProviderFor(resType:string): ResourceProviderMethods | null { - if(!this.server.plugins) { return null} - let pSource: ResourceProviderMethods | null= null - this.server.plugins.forEach((plugin:any)=> { - if(typeof plugin.resourceProvider !== 'undefined') { - pSource= plugin.resourceProvider.types.includes(resType) ? - plugin.resourceProvider.methods : - null - } - }) - debug(`** Checking for ${resType} provider.... ${pSource ? 'Found' : 'Not found'}`) - return pSource - } - } From 0a54d2a0c576db4f3da6f90bd096976bb0e1e5f6 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 20 Nov 2021 14:25:51 +1030 Subject: [PATCH 017/410] set plugin id as delta source --- src/api/resources/index.ts | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index ae2bb68c0..3238001f7 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -271,7 +271,11 @@ export class Resources { ) { let retVal= await this.resProvider[req.resourceType]?.deleteResource(req.resourceType, req.resourceId) if(retVal){ - this.sendDelta(req.resourceType, req.resourceId, null) + this.sendDelta( + this.resProvider[req.resourceType]?.pluginId as string, + req.resourceType, req.resourceId, + null + ) return {statusCode: 200, message: `Resource (${req.resourceId}) deleted.`} } else { @@ -294,7 +298,12 @@ export class Resources { let id= UUID_PREFIX + uuidv4() let retVal= await this.resProvider[req.resourceType]?.setResource(req.resourceType, id, req.body.value) if(retVal){ - this.sendDelta(req.resourceType, id, req.body.value) + this.sendDelta( + this.resProvider[req.resourceType]?.pluginId as string, + req.resourceType, + id, + req.body.value + ) return {statusCode: 200, message: `Resource (${id}) saved.`} } else { @@ -307,7 +316,12 @@ export class Resources { } let retVal= await this.resProvider[req.resourceType]?.setResource(req.resourceType, req.resourceId, req.body.value) if(retVal){ - this.sendDelta(req.resourceType, req.resourceId, req.body.value) + this.sendDelta( + this.resProvider[req.resourceType]?.pluginId as string, + req.resourceType, + req.resourceId, + req.body.value + ) return {statusCode: 200, message: `Resource (${req.resourceId}) updated.`} } else { @@ -318,9 +332,9 @@ export class Resources { } // ** send delta message with resource PUT, POST, DELETE action result - private sendDelta(type:string, id:string, value:any):void { + private sendDelta(providerId:string, type:string, id:string, value:any):void { debug(`** Sending Delta: resources.${type}.${id}`) - this.server.handleMessage('signalk-resources', { + this.server.handleMessage(providerId, { updates: [ { values: [ From 0a26d02aeb5fa1edb0033f50c2369d2cedfc16c8 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 20 Nov 2021 14:59:03 +1030 Subject: [PATCH 018/410] unregister only requires plugin id --- src/api/resources/index.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 3238001f7..9ce3cc88b 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -81,14 +81,15 @@ export class Resources { } // ** un-register resource provider for the supplied types ** - public unRegister(pluginId:string, resourceTypes:string[]) { - debug(`** Un-registering ${pluginId} provider(s)....${resourceTypes}`) - if(!Array.isArray(resourceTypes)) { return } - resourceTypes.forEach( (i:string)=>{ - if(this.resProvider[i] && this.resProvider[i]?.pluginId===pluginId) { + public unRegister(pluginId:string) { + if(!pluginId) { return } + debug(`** Un-registering ${pluginId} resource provider(s)....`) + for( let i in this.resProvider ) { + if(this.resProvider[i]?.pluginId===pluginId) { + debug(`** Un-registering ${i}....`) delete this.resProvider[i] } - }) + } debug(JSON.stringify(this.resProvider)) } From 0a2f5da6ceaa71df9d061e13d49664e0f85583ce Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 20 Nov 2021 14:59:16 +1030 Subject: [PATCH 019/410] update docs --- RESOURCE_PROVIDER_PLUGINS.md | 26 ++++++++++++++++---------- SERVERPLUGINS.md | 6 +++--- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index e90b45d08..a5b73fdf8 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -78,7 +78,7 @@ module.exports = function (app) { app.resourceApi.register(this.id, this.resourceProvider); }, stop: ()=> { - app.resourceApi.unRegister(this.id, this.resourceProvider.types); + app.resourceApi.unRegister(this.id); ... } } @@ -87,7 +87,7 @@ module.exports = function (app) { --- -### Plugin Startup - Registering the Resource Provider: +## Plugin Startup - Registering the Resource Provider: To register your plugin as a resource provider the server's `resourcesApi.register()` function should be called within the plugin `start()` function passing the `resourceProvider` interface. @@ -126,9 +126,9 @@ module.exports = function (app) { ``` --- -### Plugin Stop - Un-registering the Resource Provider: +## Plugin Stop - Un-registering the Resource Provider: -When a resource provider plugin is disabled it should un-register as a provider so resource requests are not directed to it. This is done by calling the server's `resourcesApi.unRegister()` function passing `resourceProvider.types` within the plugin's `stop()` function. +When a resource provider plugin is disabled it should un-register as a provider so resource requests are not directed to it. This is done by calling the server's `resourcesApi.unRegister()` function passing the `plugin.id` within the plugin's `stop()` function. _Example:_ ```javascript @@ -137,24 +137,30 @@ module.exports = function (app) { id: 'mypluginid', name: 'My Resource Providerplugin', resourceProvider: { - types: ['routes','waypoints'], + types: [ ... ], methods: { ... } } } plugin.stop = function(options) { + app.resourcesApi.unRegister(plugin.id); ... - app.resourcesApi.unRegister(plugin.id, plugin.resourceProvider.types); } } ``` --- -### Operation: +## Operation: + +The Server will dispatch requests made to: +- `/signalk/v1/api/resources/` + +OR +- the `resources API` endpoints -The Server will dispatch requests made to `/signalk/v1/api/resources/` to the plugin's `resourceProvider.methods` for each resource type listed in `resourceProvider.types`. +to the plugin's `resourceProvider.methods` for each of the resource types listed in `resourceProvider.types`. -Each method defined in the plugin must have a signature as specified in the interface. Each method returns a `Promise` containing the resource data or `null` if an error occurrred. +Each method defined in `resourceProvider.methods` must have a signature as specified in the __resourceProvider interface__. Each method returns a `Promise` containing the resultant resource data or `null` if an error occurrred or the operation is incomplete. ### __List Resources:__ @@ -191,7 +197,7 @@ returns { } ``` -### __Get specific resource:__ +### __Retrieve a specific resource:__ `GET` requests for a specific resource will be dispatched to the `getResource` method passing the resource type and id as parameters. diff --git a/SERVERPLUGINS.md b/SERVERPLUGINS.md index fa0c0f7b9..8a8e6613c 100644 --- a/SERVERPLUGINS.md +++ b/SERVERPLUGINS.md @@ -724,7 +724,7 @@ _Example:_ ``` -### `app.resourcesApi.register(pluginId, provider)` +### `app.resourcesApi.register(pluginId, resourceProvider)` If a plugin wants to act as a resource provider, it will need to register its provider methods during startup using this function. @@ -749,7 +749,7 @@ module.exports = function (app) { } ``` -### `app.resourcesApi.unRegister(pluginId, resource_types)` +### `app.resourcesApi.unRegister(pluginId)` When a resource provider plugin is disabled it will need to un-register its provider methods for all of the resource types it manages. This should be done in the plugin's `stop()` function. @@ -767,7 +767,7 @@ module.exports = function (app) { } ... stop: function(options) { - app.resourcesApi.unRegister(this.id, this.resourceProvider.types); + app.resourcesApi.unRegister(this.id); ... } } From 3ffa3b16337acc0ff6dd174d1db850fe47916efb Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 21 Nov 2021 11:26:43 +1030 Subject: [PATCH 020/410] add resource attribute req to query object --- src/api/resources/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 9ce3cc88b..9aaf0b86d 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -161,8 +161,12 @@ export class Resources { let p= req.params[0].split('/') let resType= (typeof req.params[0]!=='undefined') ? p[0] : '' let resId= p.length>1 ? p[1] : '' + let resAttrib= p.length>2 ? p.slice(2) : [] + req.query.resAttrib= resAttrib debug('** resType:', resType) debug('** resId:', resId) + debug('** resAttrib:', resAttrib) + debug('** req.params + attribs:', req.query) let apiMethod= (API_METHODS.includes(resType)) ? resType : null if(apiMethod) { From 008ca93f79b9f90bc7dc92bff35419fbad42b41b Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 21 Nov 2021 11:45:14 +1030 Subject: [PATCH 021/410] chore: update docs with query object examples. --- RESOURCE_PROVIDER_PLUGINS.md | 41 ++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index a5b73fdf8..2d20f1689 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -167,6 +167,16 @@ Each method defined in `resourceProvider.methods` must have a signature as speci `GET` requests that are not for a specific resource will be dispatched to the `listResources` method passing the resource type and any query data as parameters. +Query parameters are passed as an object conatining `key | value` pairs. + +_Example: GET /signalk/v1/api/resources/waypoints?bbox=5.4,25.7,6.9,31.2&distance=30000_ +```javascript +query= { + bbox: '5.4,25.7,6.9,31.2', + distance: 30000 +} +``` + It is the responsibility of the resource provider plugin to filter the resources returned as per the supplied query parameters. `listResources()` should return a JSON object listing resources by id. @@ -207,9 +217,16 @@ _Example: Retrieve route._ ```javascript GET /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a -getResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a') +getResource( + 'routes', + 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', + {} +) +``` -returns { +_Returns the result:_ +```json +{ "name": "Name of the route", "description": "Description of the route", "distance": 18345, @@ -217,6 +234,26 @@ returns { } ``` +A request for a resource attribute will pass the attibute path as an array in the query object with the key `resAttrib`. + +_Example: Get waypoint geometry._ +```javascript +GET /signalk/v1/api/resources/waypoints/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a/feature/geometry + +getResource( + 'waypoints', + 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', + { resAttrib: ['feature','geometry'] } +) +``` +_Returns the value of `geometry` attribute of the waypoint._ +```json +{ + "type": "Point", + "coordinates": [70.4,6.45] +} +``` + ### __Saving Resources:__ `PUT` requests to a path containing the resource id are used to store data associated with the resource id. These will be dispatched to the `setResource` method passing the resource type, id and data as parameters. From 19c3dffae422d26c2dcc1fccf4501ca08a81beb0 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 21 Nov 2021 15:40:58 +1030 Subject: [PATCH 022/410] chore: linted --- src/api/resources/index.ts | 662 ++++++++++++++++++--------------- src/api/resources/resources.ts | 344 +++++++++-------- src/api/resources/validate.ts | 173 +++++---- src/index.js | 2 +- src/put.js | 4 +- 5 files changed, 653 insertions(+), 532 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 9aaf0b86d..7afc84dc6 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -1,356 +1,412 @@ import Debug from 'debug' import { v4 as uuidv4 } from 'uuid' -import { validate } from './validate' import { buildResource } from './resources' +import { validate } from './validate' const debug = Debug('signalk:resources') interface ResourceRequest { - method: 'GET' | 'PUT' | 'POST' | 'DELETE' - body: any - query: {[key:string]: any} - resourceType: string - resourceId: string, - apiMethod?: string | null + method: 'GET' | 'PUT' | 'POST' | 'DELETE' + body: any + query: { [key: string]: any } + resourceType: string + resourceId: string + apiMethod?: string | null } interface ResourceProvider { - types: Array - methods: ResourceProviderMethods + types: string[] + methods: ResourceProviderMethods } interface ResourceProviderMethods { - pluginId: string - listResources: (type:string, query: {[key:string]:any})=> Promise - getResource: (type:string, id:string)=> Promise - setResource: (type:string, id:string, value:{[key:string]:any})=> Promise - deleteResource: (type:string, id:string)=> Promise + pluginId: string + listResources: (type: string, query: { [key: string]: any }) => Promise + getResource: (type: string, id: string) => Promise + setResource: ( + type: string, + id: string, + value: { [key: string]: any } + ) => Promise + deleteResource: (type: string, id: string) => Promise } -const SIGNALK_API_PATH= `/signalk/v1/api` -const UUID_PREFIX= 'urn:mrn:signalk:uuid:' +const SIGNALK_API_PATH = `/signalk/v1/api` +const UUID_PREFIX = 'urn:mrn:signalk:uuid:' -const API_METHODS= [ - 'setWaypoint', - 'deleteWaypoint', - 'setRoute', - 'deleteRoute', - 'setNote', - 'deleteNote', - 'setRegion', - 'deleteRegion' +const API_METHODS = [ + 'setWaypoint', + 'deleteWaypoint', + 'setRoute', + 'deleteRoute', + 'setNote', + 'deleteNote', + 'setRegion', + 'deleteRegion' ] export class Resources { + resProvider: { [key: string]: ResourceProviderMethods | null } = {} + server: any - // ** in-scope resource types ** - private resourceTypes:Array= [ - 'routes', - 'waypoints', - 'notes', - 'regions', - 'charts' - ] + // ** in-scope resource types ** + private resourceTypes: string[] = [ + 'routes', + 'waypoints', + 'notes', + 'regions', + 'charts' + ] - resProvider: {[key:string]: ResourceProviderMethods | null}= {} - server: any + constructor(app: any) { + this.start(app) + } - constructor(app:any) { - this.start(app) + // ** register resource provider ** + register(pluginId: string, provider: ResourceProvider) { + debug(`** Registering provider(s)....${provider?.types}`) + if (!provider) { + return } - - // ** initialise resourcesApi ** - private start(app:any) { - debug(`** Initialise ${SIGNALK_API_PATH}/resources path handler **`) - this.server= app - this.initResourceRoutes() + if (provider.types && !Array.isArray(provider.types)) { + return } + provider.types.forEach((i: string) => { + if (!this.resProvider[i]) { + provider.methods.pluginId = pluginId + this.resProvider[i] = provider.methods + } + }) + debug(this.resProvider) + } - // ** register resource provider ** - public register(pluginId:string, provider:ResourceProvider) { - debug(`** Registering provider(s)....${provider?.types}`) - if(!provider ) { return } - if(provider.types && !Array.isArray(provider.types)) { return } - provider.types.forEach( (i:string)=>{ - if(!this.resProvider[i]) { - provider.methods.pluginId= pluginId - this.resProvider[i]= provider.methods - } - }) - debug(this.resProvider) + // ** un-register resource provider for the supplied types ** + unRegister(pluginId: string) { + if (!pluginId) { + return + } + debug(`** Un-registering ${pluginId} resource provider(s)....`) + for (const i in this.resProvider) { + if (this.resProvider[i]?.pluginId === pluginId) { + debug(`** Un-registering ${i}....`) + delete this.resProvider[i] + } } + debug(JSON.stringify(this.resProvider)) + } - // ** un-register resource provider for the supplied types ** - public unRegister(pluginId:string) { - if(!pluginId) { return } - debug(`** Un-registering ${pluginId} resource provider(s)....`) - for( let i in this.resProvider ) { - if(this.resProvider[i]?.pluginId===pluginId) { - debug(`** Un-registering ${i}....`) - delete this.resProvider[i] - } + // ** return resource with supplied type and id ** + getResource(type: string, id: string) { + debug(`** getResource(${type}, ${id})`) + return this.actionResourceRequest({ + method: 'GET', + body: {}, + query: {}, + resourceType: type, + resourceId: id + }) + } + + // ** initialise resourcesApi ** + private start(app: any) { + debug(`** Initialise ${SIGNALK_API_PATH}/resources path handler **`) + this.server = app + this.initResourceRoutes() + } + + // ** initialise handler for in-scope resource types ** + private initResourceRoutes() { + this.server.get(`${SIGNALK_API_PATH}/resources`, (req: any, res: any) => { + // list all serviced paths under resources + res.json(this.getResourcePaths()) + }) + this.server.use( + `${SIGNALK_API_PATH}/resources/*`, + async (req: any, res: any, next: any) => { + const result = this.parseResourceRequest(req) + if (result) { + const ar = await this.actionResourceRequest(result) + if (typeof ar.statusCode !== 'undefined') { + debug(`${JSON.stringify(ar)}`) + res.status = ar.statusCode + res.send(ar.message) + } else { + res.json(ar) + } + } else { + debug('** No provider found... calling next()...') + next() } - debug(JSON.stringify(this.resProvider)) - } + } + ) + } + + // ** return all paths serviced under SIGNALK_API_PATH/resources ** + private getResourcePaths(): { [key: string]: any } { + const resPaths: { [key: string]: any } = {} + Object.entries(this.resProvider).forEach((p: any) => { + if (p[1]) { + resPaths[p[0]] = `Path containing ${p[0]}, each named with a UUID` + } + }) + // check for other plugins servicing paths under ./resources + this.server._router.stack.forEach((i: any) => { + if (i.route && i.route.path && typeof i.route.path === 'string') { + if (i.route.path.indexOf(`${SIGNALK_API_PATH}/resources`) !== -1) { + const r = i.route.path.split('/') + if (r.length > 5 && !(r[5] in resPaths)) { + resPaths[ + r[5] + ] = `Path containing ${r[5]} resources (provided by plug-in)` + } + } + } + }) + return resPaths + } + + // ** parse api path request and return ResourceRequest object ** + private parseResourceRequest(req: any): ResourceRequest | undefined { + debug('** req.originalUrl:', req.originalUrl) + debug('** req.method:', req.method) + debug('** req.body:', req.body) + debug('** req.query:', req.query) + debug('** req.params:', req.params) + const p = req.params[0].split('/') + let resType = typeof req.params[0] !== 'undefined' ? p[0] : '' + const resId = p.length > 1 ? p[1] : '' + const resAttrib = p.length > 2 ? p.slice(2) : [] + req.query.resAttrib = resAttrib + debug('** resType:', resType) + debug('** resId:', resId) + debug('** resAttrib:', resAttrib) + debug('** req.params + attribs:', req.query) - // ** return resource with supplied type and id ** - public getResource(type:string, id:string) { - debug(`** getResource(${type}, ${id})`) - return this.actionResourceRequest({ - method: 'GET', - body: {}, - query: {}, - resourceType: type, - resourceId: id - }) + const apiMethod = API_METHODS.includes(resType) ? resType : null + if (apiMethod) { + if (apiMethod.toLowerCase().indexOf('waypoint') !== -1) { + resType = 'waypoints' + } + if (apiMethod.toLowerCase().indexOf('route') !== -1) { + resType = 'routes' + } + if (apiMethod.toLowerCase().indexOf('note') !== -1) { + resType = 'notes' + } + if (apiMethod.toLowerCase().indexOf('region') !== -1) { + resType = 'regions' + } } - // ** initialise handler for in-scope resource types ** - private initResourceRoutes() { - this.server.get(`${SIGNALK_API_PATH}/resources`, (req:any, res:any) => { - // list all serviced paths under resources - res.json(this.getResourcePaths()) - }) - this.server.use(`${SIGNALK_API_PATH}/resources/*`, async (req:any, res:any, next: any) => { - let result= this.parseResourceRequest(req) - if(result) { - let ar= await this.actionResourceRequest(result) - if(typeof ar.statusCode!== 'undefined'){ - debug(`${JSON.stringify(ar)}`) - res.status= ar.statusCode - res.send(ar.message) - } - else { - res.json(ar) - } - } - else { - debug('** No provider found... calling next()...') - next() - } - }) + const retReq = { + method: req.method, + body: req.body, + query: req.query, + resourceType: resType, + resourceId: resId, + apiMethod } - // ** return all paths serviced under SIGNALK_API_PATH/resources ** - private getResourcePaths(): {[key:string]:any} { - let resPaths:{[key:string]:any}= {} - Object.entries(this.resProvider).forEach( (p:any)=> { - if(p[1]) { resPaths[p[0]]= `Path containing ${p[0]}, each named with a UUID` } - }) - // check for other plugins servicing paths under ./resources - this.server._router.stack.forEach((i:any)=> { - if(i.route && i.route.path && typeof i.route.path==='string') { - if(i.route.path.indexOf(`${SIGNALK_API_PATH}/resources`)!==-1) { - let r= i.route.path.split('/') - if( r.length>5 && !(r[5] in resPaths) ) { - resPaths[r[5]]= `Path containing ${r[5]} resources (provided by plug-in)` - } - } - } - }) - return resPaths + if (this.resourceTypes.includes(resType) && this.resProvider[resType]) { + return retReq + } else { + debug('Invalid resource type or no provider for this type!') + return undefined } + } - // ** parse api path request and return ResourceRequest object ** - private parseResourceRequest(req:any):ResourceRequest | undefined { - debug('** req.originalUrl:', req.originalUrl) - debug('** req.method:', req.method) - debug('** req.body:', req.body) - debug('** req.query:', req.query) - debug('** req.params:', req.params) - let p= req.params[0].split('/') - let resType= (typeof req.params[0]!=='undefined') ? p[0] : '' - let resId= p.length>1 ? p[1] : '' - let resAttrib= p.length>2 ? p.slice(2) : [] - req.query.resAttrib= resAttrib - debug('** resType:', resType) - debug('** resId:', resId) - debug('** resAttrib:', resAttrib) - debug('** req.params + attribs:', req.query) - - let apiMethod= (API_METHODS.includes(resType)) ? resType : null - if(apiMethod) { - if(apiMethod.toLowerCase().indexOf('waypoint')!==-1) { - resType= 'waypoints' - } - if(apiMethod.toLowerCase().indexOf('route')!==-1) { - resType= 'routes' - } - if(apiMethod.toLowerCase().indexOf('note')!==-1) { - resType= 'notes' - } - if(apiMethod.toLowerCase().indexOf('region')!==-1) { - resType= 'regions' - } - } + // ** action an in-scope resource request ** + private async actionResourceRequest(req: ResourceRequest): Promise { + debug('********* action request *************') + debug(req) - let retReq= { - method: req.method, - body: req.body, - query: req.query, - resourceType: resType, - resourceId: resId, - apiMethod: apiMethod - } + // check for registered resource providers + if (!this.resProvider) { + return { statusCode: 501, message: `No Provider` } + } - if(this.resourceTypes.includes(resType) && this.resProvider[resType]) { - return retReq - } - else { - debug('Invalid resource type or no provider for this type!') - return undefined - } + if ( + !this.resourceTypes.includes(req.resourceType) || + !this.resProvider[req.resourceType] + ) { + return { statusCode: 501, message: `No Provider` } } - // ** action an in-scope resource request ** - private async actionResourceRequest (req:ResourceRequest):Promise { - debug('********* action request *************') - debug(req) - - // check for registered resource providers - if(!this.resProvider) { - return {statusCode: 501, message: `No Provider`} - } - - if(!this.resourceTypes.includes(req.resourceType) || !this.resProvider[req.resourceType]) { - return {statusCode: 501, message: `No Provider`} - } + // check for API method request + if (req.apiMethod && API_METHODS.includes(req.apiMethod)) { + debug(`API Method (${req.apiMethod})`) + req = this.transformApiRequest(req) + } - // check for API method request - if(req.apiMethod && API_METHODS.includes(req.apiMethod)) { - debug(`API Method (${req.apiMethod})`) - req= this.transformApiRequest(req) - } + return await this.execResourceRequest(req) + } - return await this.execResourceRequest(req) + // ** transform API request to ResourceRequest ** + private transformApiRequest(req: ResourceRequest): ResourceRequest { + if (req.apiMethod?.indexOf('delete') !== -1) { + req.method = 'DELETE' } - - // ** transform API request to ResourceRequest ** - private transformApiRequest(req: ResourceRequest):ResourceRequest { - if(req.apiMethod?.indexOf('delete')!==-1) { - req.method= 'DELETE' - } - if(req.apiMethod?.indexOf('set')!==-1) { - if(!req.body.id) { - req.method= 'POST' - } - else { - req.resourceId= req.body.id - } - req.body= { value: buildResource(req.resourceType, req.body) ?? {} } - } - return req + if (req.apiMethod?.indexOf('set') !== -1) { + if (!req.body.id) { + req.method = 'POST' + } else { + req.resourceId = req.body.id + } + req.body = { value: buildResource(req.resourceType, req.body) ?? {} } } + return req + } - // ** action an in-scope resource request ** - private async execResourceRequest (req:ResourceRequest):Promise { - debug('********* execute request *************') - debug(req) - if(req.method==='GET') { - let retVal: any - if(!req.resourceId) { - retVal= await this.resProvider[req.resourceType]?.listResources(req.resourceType, req.query) - return (retVal) ? - retVal : - {statusCode: 404, message: `Error retrieving resources!` } - } - if(!validate.uuid(req.resourceId)) { - return {statusCode: 406, message: `Invalid resource id provided (${req.resourceId})` } - } - retVal= await this.resProvider[req.resourceType]?.getResource(req.resourceType, req.resourceId) - return (retVal) ? - retVal : - {statusCode: 404, message: `Resource not found (${req.resourceId})!` } + // ** action an in-scope resource request ** + private async execResourceRequest(req: ResourceRequest): Promise { + debug('********* execute request *************') + debug(req) + if (req.method === 'GET') { + let retVal: any + if (!req.resourceId) { + retVal = await this.resProvider[req.resourceType]?.listResources( + req.resourceType, + req.query + ) + return retVal + ? retVal + : { statusCode: 404, message: `Error retrieving resources!` } + } + if (!validate.uuid(req.resourceId)) { + return { + statusCode: 406, + message: `Invalid resource id provided (${req.resourceId})` } + } + retVal = await this.resProvider[req.resourceType]?.getResource( + req.resourceType, + req.resourceId + ) + return retVal + ? retVal + : { + statusCode: 404, + message: `Resource not found (${req.resourceId})!` + } + } - if(req.method==='DELETE' || req.method==='PUT') { - if(!req.resourceId) { - return {statusCode: 406, value: `No resource id provided!` } - } - if(!validate.uuid(req.resourceId)) { - return {statusCode: 406, message: `Invalid resource id provided (${req.resourceId})!` } - } - if( - req.method==='DELETE' || - (req.method==='PUT' && typeof req.body.value!=='undefined' && req.body.value==null) - ) { - let retVal= await this.resProvider[req.resourceType]?.deleteResource(req.resourceType, req.resourceId) - if(retVal){ - this.sendDelta( - this.resProvider[req.resourceType]?.pluginId as string, - req.resourceType, req.resourceId, - null - ) - return {statusCode: 200, message: `Resource (${req.resourceId}) deleted.`} - } - else { - return {statusCode: 400, message: `Error deleting resource (${req.resourceId})!` } - } - } - + if (req.method === 'DELETE' || req.method === 'PUT') { + if (!req.resourceId) { + return { statusCode: 406, value: `No resource id provided!` } + } + if (!validate.uuid(req.resourceId)) { + return { + statusCode: 406, + message: `Invalid resource id provided (${req.resourceId})!` } - - if(req.method==='POST' || req.method==='PUT') { - // check for supplied value - if( typeof req.body.value==='undefined' || req.body.value==null) { - return {statusCode: 406, message: `No resource data supplied!`} - } - // validate supplied request data - if(!validate.resource(req.resourceType, req.body.value)) { - return {statusCode: 406, message: `Invalid resource data supplied!`} - } - if(req.method==='POST') { - let id= UUID_PREFIX + uuidv4() - let retVal= await this.resProvider[req.resourceType]?.setResource(req.resourceType, id, req.body.value) - if(retVal){ - this.sendDelta( - this.resProvider[req.resourceType]?.pluginId as string, - req.resourceType, - id, - req.body.value - ) - return {statusCode: 200, message: `Resource (${id}) saved.`} - } - else { - return {statusCode: 400, message: `Error saving resource (${id})!` } - } - } - if(req.method==='PUT') { - if(!req.resourceId) { - return {statusCode: 406, message: `No resource id provided!` } - } - let retVal= await this.resProvider[req.resourceType]?.setResource(req.resourceType, req.resourceId, req.body.value) - if(retVal){ - this.sendDelta( - this.resProvider[req.resourceType]?.pluginId as string, - req.resourceType, - req.resourceId, - req.body.value - ) - return {statusCode: 200, message: `Resource (${req.resourceId}) updated.`} - } - else { - return {statusCode: 400, message: `Error updating resource (${req.resourceId})!` } - } - } + } + if ( + req.method === 'DELETE' || + (req.method === 'PUT' && + typeof req.body.value !== 'undefined' && + req.body.value == null) + ) { + const retVal = await this.resProvider[req.resourceType]?.deleteResource( + req.resourceType, + req.resourceId + ) + if (retVal) { + this.sendDelta( + this.resProvider[req.resourceType]?.pluginId as string, + req.resourceType, + req.resourceId, + null + ) + return { + statusCode: 200, + message: `Resource (${req.resourceId}) deleted.` + } + } else { + return { + statusCode: 400, + message: `Error deleting resource (${req.resourceId})!` + } } + } } - // ** send delta message with resource PUT, POST, DELETE action result - private sendDelta(providerId:string, type:string, id:string, value:any):void { - debug(`** Sending Delta: resources.${type}.${id}`) - this.server.handleMessage(providerId, { - updates: [ - { - values: [ - { - path: `resources.${type}.${id}`, - value: value - } - ] - } - ] - }) + if (req.method === 'POST' || req.method === 'PUT') { + // check for supplied value + if (typeof req.body.value === 'undefined' || req.body.value == null) { + return { statusCode: 406, message: `No resource data supplied!` } + } + // validate supplied request data + if (!validate.resource(req.resourceType, req.body.value)) { + return { statusCode: 406, message: `Invalid resource data supplied!` } + } + if (req.method === 'POST') { + const id = UUID_PREFIX + uuidv4() + const retVal = await this.resProvider[req.resourceType]?.setResource( + req.resourceType, + id, + req.body.value + ) + if (retVal) { + this.sendDelta( + this.resProvider[req.resourceType]?.pluginId as string, + req.resourceType, + id, + req.body.value + ) + return { statusCode: 200, message: `Resource (${id}) saved.` } + } else { + return { statusCode: 400, message: `Error saving resource (${id})!` } + } + } + if (req.method === 'PUT') { + if (!req.resourceId) { + return { statusCode: 406, message: `No resource id provided!` } + } + const retVal = await this.resProvider[req.resourceType]?.setResource( + req.resourceType, + req.resourceId, + req.body.value + ) + if (retVal) { + this.sendDelta( + this.resProvider[req.resourceType]?.pluginId as string, + req.resourceType, + req.resourceId, + req.body.value + ) + return { + statusCode: 200, + message: `Resource (${req.resourceId}) updated.` + } + } else { + return { + statusCode: 400, + message: `Error updating resource (${req.resourceId})!` + } + } + } } + } + // ** send delta message with resource PUT, POST, DELETE action result + private sendDelta( + providerId: string, + type: string, + id: string, + value: any + ): void { + debug(`** Sending Delta: resources.${type}.${id}`) + this.server.handleMessage(providerId, { + updates: [ + { + values: [ + { + path: `resources.${type}.${id}`, + value + } + ] + } + ] + }) + } } diff --git a/src/api/resources/resources.ts b/src/api/resources/resources.ts index e76cce323..1857c23cd 100644 --- a/src/api/resources/resources.ts +++ b/src/api/resources/resources.ts @@ -2,177 +2,215 @@ import { getDistance, isValidCoordinate } from 'geolib' import ngeohash from 'ngeohash' // ** build resource item ** -export const buildResource= (resType:string, data:any):any=> { - if(resType==='routes') { return buildRoute(data) } - if(resType==='waypoints') { return buildWaypoint(data) } - if(resType==='notes') { return buildNote(data) } - if(resType==='regions') { return buildRegion(data) } +export const buildResource = (resType: string, data: any): any => { + if (resType === 'routes') { + return buildRoute(data) + } + if (resType === 'waypoints') { + return buildWaypoint(data) + } + if (resType === 'notes') { + return buildNote(data) + } + if (resType === 'regions') { + return buildRegion(data) + } } // ** build route -const buildRoute= (rData:any):any=> { - let rte:any= { - feature: { - type: 'Feature', - geometry:{ - type: 'LineString', - coordinates :[] - }, - properties:{} - } - } - if(typeof rData.name !== 'undefined') { - rte.name= rData.name - rte.feature.properties.name= rData.name - } - if(typeof rData.description !== 'undefined') { - rte.description= rData.description - rte.feature.properties.description= rData.description - } - if(typeof rData.attributes !== 'undefined') { - Object.assign(rte.feature.properties, rData.attributes) - } +const buildRoute = (rData: any): any => { + const rte: any = { + feature: { + type: 'Feature', + geometry: { + type: 'LineString', + coordinates: [] + }, + properties: {} + } + } + if (typeof rData.name !== 'undefined') { + rte.name = rData.name + rte.feature.properties.name = rData.name + } + if (typeof rData.description !== 'undefined') { + rte.description = rData.description + rte.feature.properties.description = rData.description + } + if (typeof rData.attributes !== 'undefined') { + Object.assign(rte.feature.properties, rData.attributes) + } - if(typeof rData.points === 'undefined') { return null } - if(!Array.isArray(rData.points)) { return null } - let isValid:boolean= true; - rData.points.forEach( (p:any)=> { - if(!isValidCoordinate(p)) { isValid= false } - }) - if(!isValid) { return null } - rData.points.forEach( (p:any)=> { - rte.feature.geometry.coordinates.push([p.longitude, p.latitude]) - }) + if (typeof rData.points === 'undefined') { + return null + } + if (!Array.isArray(rData.points)) { + return null + } + let isValid: boolean = true + rData.points.forEach((p: any) => { + if (!isValidCoordinate(p)) { + isValid = false + } + }) + if (!isValid) { + return null + } + rte.feature.geometry.coordinates = rData.points.map((p: any) => { + return [p.longitude, p.latitude] + }) - rte.distance= 0 - for(let i=0; i { - let wpt:any= { - position: { - latitude: 0, - longitude: 0 - }, - feature: { - type: 'Feature', - geometry:{ - type: 'Point', - coordinates :[] - }, - properties:{} - } - } - if(typeof rData.name !== 'undefined') { - wpt.feature.properties.name= rData.name - } - if(typeof rData.description !== 'undefined') { - wpt.feature.properties.description= rData.description - } - if(typeof rData.attributes !== 'undefined') { - Object.assign(wpt.feature.properties, rData.attributes) - } +const buildWaypoint = (rData: any): any => { + const wpt: any = { + position: { + latitude: 0, + longitude: 0 + }, + feature: { + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [] + }, + properties: {} + } + } + if (typeof rData.name !== 'undefined') { + wpt.feature.properties.name = rData.name + } + if (typeof rData.description !== 'undefined') { + wpt.feature.properties.description = rData.description + } + if (typeof rData.attributes !== 'undefined') { + Object.assign(wpt.feature.properties, rData.attributes) + } + + if (typeof rData.position === 'undefined') { + return null + } + if (!isValidCoordinate(rData.position)) { + return null + } - if(typeof rData.position === 'undefined') { return null } - if(!isValidCoordinate(rData.position)) { return null } - - wpt.position= rData.position - wpt.feature.geometry.coordinates= [rData.position.longitude, rData.position.latitude] + wpt.position = rData.position + wpt.feature.geometry.coordinates = [ + rData.position.longitude, + rData.position.latitude + ] - return wpt + return wpt } // ** build note -const buildNote= (rData:any):any=> { - let note:any= {} - if(typeof rData.title !== 'undefined') { - note.title= rData.title - note.feature.properties.title= rData.title - } - if(typeof rData.description !== 'undefined') { - note.description= rData.description - note.feature.properties.description= rData.description - } - if(typeof rData.position === 'undefined' - && typeof rData.region === 'undefined' - && typeof rData.geohash === 'undefined') { return null } +const buildNote = (rData: any): any => { + const note: any = {} + if (typeof rData.title !== 'undefined') { + note.title = rData.title + note.feature.properties.title = rData.title + } + if (typeof rData.description !== 'undefined') { + note.description = rData.description + note.feature.properties.description = rData.description + } + if ( + typeof rData.position === 'undefined' && + typeof rData.region === 'undefined' && + typeof rData.geohash === 'undefined' + ) { + return null + } - if(typeof rData.position !== 'undefined') { - if(!isValidCoordinate(rData.position)) { return null } - note.position= rData.position - } - if(typeof rData.region !== 'undefined') { - note.region= rData.region - } - if(typeof rData.geohash !== 'undefined') { - note.geohash= rData.geohash - } - if(typeof rData.url !== 'undefined') { - note.url= rData.url - } - if(typeof rData.mimeType !== 'undefined') { - note.mimeType= rData.mimeType - } - - return note + if (typeof rData.position !== 'undefined') { + if (!isValidCoordinate(rData.position)) { + return null + } + note.position = rData.position + } + if (typeof rData.region !== 'undefined') { + note.region = rData.region + } + if (typeof rData.geohash !== 'undefined') { + note.geohash = rData.geohash + } + if (typeof rData.url !== 'undefined') { + note.url = rData.url + } + if (typeof rData.mimeType !== 'undefined') { + note.mimeType = rData.mimeType + } + + return note } // ** build region -const buildRegion= (rData:any):any=> { - let reg:any= { - feature: { - type: 'Feature', - geometry:{ - type: 'Polygon', - coordinates :[] - }, - properties:{} - } - } - let coords:Array<[number,number]>= [] +const buildRegion = (rData: any): any => { + const reg: any = { + feature: { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [] + }, + properties: {} + } + } + let coords: Array<[number, number]> = [] - if(typeof rData.name !== 'undefined') { - reg.feature.properties.name= rData.name - } - if(typeof rData.description !== 'undefined') { - reg.feature.properties.description= rData.description - } - if(typeof rData.attributes !== 'undefined') { - Object.assign(reg.feature.properties, rData.attributes) - } + if (typeof rData.name !== 'undefined') { + reg.feature.properties.name = rData.name + } + if (typeof rData.description !== 'undefined') { + reg.feature.properties.description = rData.description + } + if (typeof rData.attributes !== 'undefined') { + Object.assign(reg.feature.properties, rData.attributes) + } - if(typeof rData.points=== 'undefined' && rData.geohash=== 'undefined') { return null } - if(typeof rData.geohash!== 'undefined') { - reg.geohash= rData.geohash - - let bounds= ngeohash.decode_bbox(rData.geohash) - coords= [ - [bounds[1], bounds[0]], - [bounds[3], bounds[0]], - [bounds[3], bounds[2]], - [bounds[1], bounds[2]], - [bounds[1], bounds[0]], - ] - reg.feature.geometry.coordinates.push(coords) - } - if(typeof rData.points!== 'undefined' && coords.length===0 ) { - if(!Array.isArray(rData.points)) { return null } - let isValid:boolean= true; - rData.points.forEach( (p:any)=> { - if(!isValidCoordinate(p)) { isValid= false } - }) - if(!isValid) { return null } - rData.points.forEach( (p:any)=> { - coords.push([p.longitude, p.latitude]) - }) - reg.feature.geometry.coordinates.push(coords) + if (typeof rData.points === 'undefined' && rData.geohash === 'undefined') { + return null + } + if (typeof rData.geohash !== 'undefined') { + reg.geohash = rData.geohash + + const bounds = ngeohash.decode_bbox(rData.geohash) + coords = [ + [bounds[1], bounds[0]], + [bounds[3], bounds[0]], + [bounds[3], bounds[2]], + [bounds[1], bounds[2]], + [bounds[1], bounds[0]] + ] + reg.feature.geometry.coordinates.push(coords) + } + if (typeof rData.points !== 'undefined' && coords.length === 0) { + if (!Array.isArray(rData.points)) { + return null + } + let isValid: boolean = true + rData.points.forEach((p: any) => { + if (!isValidCoordinate(p)) { + isValid = false + } + }) + if (!isValid) { + return null } - - return reg + coords = rData.points.map((p: any) => { + return [p.longitude, p.latitude] + }) + reg.feature.geometry.coordinates.push(coords) + } + + return reg } diff --git a/src/api/resources/validate.ts b/src/api/resources/validate.ts index 840d944b2..0d8d6cffe 100644 --- a/src/api/resources/validate.ts +++ b/src/api/resources/validate.ts @@ -1,97 +1,124 @@ -import geoJSON from 'geojson-validation'; +import geoJSON from 'geojson-validation' import { isValidCoordinate } from 'geolib' -export const validate= { - resource: (type:string, value:any):boolean=> { - if(!type) { return false } - switch(type) { - case 'routes': - return validateRoute(value); - break - case 'waypoints': - return validateWaypoint(value) - break - case 'notes': - return validateNote(value) - break; - case 'regions': - return validateRegion(value) - break - default: - return true - } - }, - - // ** returns true if id is a valid Signal K UUID ** - uuid: (id:string): boolean=> { - let uuid= RegExp("^urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$") - return uuid.test(id) +export const validate = { + resource: (type: string, value: any): boolean => { + if (!type) { + return false + } + switch (type) { + case 'routes': + return validateRoute(value) + break + case 'waypoints': + return validateWaypoint(value) + break + case 'notes': + return validateNote(value) + break + case 'regions': + return validateRegion(value) + break + default: + return true } + }, + + // ** returns true if id is a valid Signal K UUID ** + uuid: (id: string): boolean => { + const uuid = RegExp( + '^urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$' + ) + return uuid.test(id) + } } // ** validate route data -const validateRoute= (r:any):boolean=> { - if(r.start) { - let l= r.start.split('/') - if(!validate.uuid(l[l.length-1])) { return false } +const validateRoute = (r: any): boolean => { + if (r.start) { + const l = r.start.split('/') + if (!validate.uuid(l[l.length - 1])) { + return false } - if(r.end) { - let l= r.end.split('/') - if(!validate.uuid(l[l.length-1])) { return false } + } + if (r.end) { + const l = r.end.split('/') + if (!validate.uuid(l[l.length - 1])) { + return false } - try { - if(!r.feature || !geoJSON.valid(r.feature)) { - return false - } - if(r.feature.geometry.type!=='LineString') { return false } + } + try { + if (!r.feature || !geoJSON.valid(r.feature)) { + return false } - catch(err) { return false } - return true + if (r.feature.geometry.type !== 'LineString') { + return false + } + } catch (err) { + return false + } + return true } // ** validate waypoint data -const validateWaypoint= (r:any):boolean=> { - if(typeof r.position === 'undefined') { return false } - if(!isValidCoordinate(r.position)) { - return false +const validateWaypoint = (r: any): boolean => { + if (typeof r.position === 'undefined') { + return false + } + if (!isValidCoordinate(r.position)) { + return false + } + try { + if (!r.feature || !geoJSON.valid(r.feature)) { + return false } - try { - if(!r.feature || !geoJSON.valid(r.feature)) { - return false - } - if(r.feature.geometry.type!=='Point') { return false } + if (r.feature.geometry.type !== 'Point') { + return false } - catch(e) { return false } - return true + } catch (e) { + return false + } + return true } // ** validate note data -const validateNote= (r:any):boolean=> { - if(!r.region && !r.position && !r.geohash ) { return false } - if(typeof r.position!== 'undefined') { - if(!isValidCoordinate(r.position)) { - return false - } +const validateNote = (r: any): boolean => { + if (!r.region && !r.position && !r.geohash) { + return false + } + if (typeof r.position !== 'undefined') { + if (!isValidCoordinate(r.position)) { + return false } - if(r.region) { - let l= r.region.split('/') - if(!validate.uuid(l[l.length-1])) { return false } + } + if (r.region) { + const l = r.region.split('/') + if (!validate.uuid(l[l.length - 1])) { + return false } - return true + } + return true } // ** validate region data -const validateRegion= (r:any):boolean=> { - if(!r.geohash && !r.feature) { return false } - if(r.feature ) { - try { - if(!geoJSON.valid(r.feature)) { return false } - if(r.feature.geometry.type!=='Polygon' && r.feature.geometry.type!=='MultiPolygon') { - return false - } - } - catch(e) { return false } +const validateRegion = (r: any): boolean => { + if (!r.geohash && !r.feature) { + return false + } + if (r.feature) { + try { + if (!geoJSON.valid(r.feature)) { + return false + } + if ( + r.feature.geometry.type !== 'Polygon' && + r.feature.geometry.type !== 'MultiPolygon' + ) { + return false + } + } catch (e) { + return false } - return true + } + return true } - diff --git a/src/index.js b/src/index.js index 52472cb04..d751b4d9f 100644 --- a/src/index.js +++ b/src/index.js @@ -71,7 +71,7 @@ function Server(opts) { require('./put').start(app) // ** initialise resources API ** - app.resourcesApi= new Resources(app) + app.resourcesApi = new Resources(app) app.signalk = new FullSignalK(app.selfId, app.selfType) diff --git a/src/put.js b/src/put.js index c77bb3177..e6e14a965 100644 --- a/src/put.js +++ b/src/put.js @@ -31,10 +31,10 @@ module.exports = { app.put(apiPathPrefix + '*', function(req, res, next) { // ** ignore resources paths ** - if(req.path.split('/')[4]==='resources') { + if (req.path.split('/')[4] === 'resources') { next() return - } + } let path = String(req.path).replace(apiPathPrefix, '') const value = req.body From 11cd210b33425038f09df75ec856f435b437d3cf Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 21 Nov 2021 15:59:33 +1030 Subject: [PATCH 023/410] chore: return value descriptions to show a Promise --- RESOURCE_PROVIDER_PLUGINS.md | 48 +++++++++++++++++----------------- src/api/resources/resources.ts | 15 +++++++---- 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index 2d20f1689..4e2c7312d 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -179,7 +179,7 @@ query= { It is the responsibility of the resource provider plugin to filter the resources returned as per the supplied query parameters. -`listResources()` should return a JSON object listing resources by id. +`listResources()` returns a Promise containing a JSON object listing resources by id. _Example: List all routes._ ```javascript @@ -187,34 +187,34 @@ GET /signalk/v1/api/resources/routes listResources('routes', {}) -returns { +returns Promise<{ "resource_id1": { ... }, "resource_id2": { ... }, ... "resource_idn": { ... } -} +}> ``` _Example: List waypoints within the bounded area._ -```javascript +```typescript GET /signalk/v1/api/resources/waypoints?bbox=5.4,25.7,6.9,31.2 listResources('waypoints', {bbox: '5.4,25.7,6.9,31.2'}) -returns { +returns Promise<{ "resource_id1": { ... }, "resource_id2": { ... } -} +}> ``` ### __Retrieve a specific resource:__ `GET` requests for a specific resource will be dispatched to the `getResource` method passing the resource type and id as parameters. -`getResource()` should returns a JSON object containing the resource data. +`getResource()` returns a Promise containing a JSON object with the resource data. _Example: Retrieve route._ -```javascript +```typescript GET /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a getResource( @@ -224,14 +224,14 @@ getResource( ) ``` -_Returns the result:_ -```json -{ +_Returns a Promise containing the resource data:_ +```typescript +Promise<{ "name": "Name of the route", "description": "Description of the route", "distance": 18345, "feature": { ... } -} +}> ``` A request for a resource attribute will pass the attibute path as an array in the query object with the key `resAttrib`. @@ -246,19 +246,19 @@ getResource( { resAttrib: ['feature','geometry'] } ) ``` -_Returns the value of `geometry` attribute of the waypoint._ -```json -{ +_Returns a Promise containing the value of `geometry` attribute of the waypoint._ +```typescript +Promise<{ "type": "Point", "coordinates": [70.4,6.45] -} +}> ``` ### __Saving Resources:__ `PUT` requests to a path containing the resource id are used to store data associated with the resource id. These will be dispatched to the `setResource` method passing the resource type, id and data as parameters. -`setResource() ` returns `true` on success and `null` on failure. +`setResource() ` returns Promise on success and Promise on failure. _Example: Update / add waypoint with the supplied id._ ```javascript @@ -266,34 +266,34 @@ PUT /signalk/v1/api/resources/waypoints/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25- setResource('waypoints', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) -returns true | null +returns Promise ``` `POST` requests to a resource path that do not contina the resource id will be dispatched to the `setResource` method passing the resource type, an id (generated by the server) and resource data as parameters. -`setResource() ` returns `true` on success and `null` on failure. +`setResource()` returns `true` on success and `null` on failure. _Example: New route record._ -```javascript +```typescript POST /signalk/v1/api/resources/routes {} setResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) -returns true | null +returns Promise ``` ### __Deleting Resources:__ `DELETE` requests to a path containing the resource id will be dispatched to the `deleteResource` method passing the resource type and id as parameters. -`deleteResource()` returns `true` on success, `null` on failure. +`deleteResource()` returns `true` on success and `null` on failure. _Example: Delete region with supplied id._ -```javascript +```typescript DELETE /signalk/v1/api/resources/regions/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a deleteResource('regions', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a') -returns true | null +returns Promise ``` diff --git a/src/api/resources/resources.ts b/src/api/resources/resources.ts index 1857c23cd..3b79bc271 100644 --- a/src/api/resources/resources.ts +++ b/src/api/resources/resources.ts @@ -1,7 +1,7 @@ import { getDistance, isValidCoordinate } from 'geolib' import ngeohash from 'ngeohash' -// ** build resource item ** + export const buildResource = (resType: string, data: any): any => { if (resType === 'routes') { return buildRoute(data) @@ -17,7 +17,6 @@ export const buildResource = (resType: string, data: any): any => { } } -// ** build route const buildRoute = (rData: any): any => { const rte: any = { feature: { @@ -70,7 +69,7 @@ const buildRoute = (rData: any): any => { return rte } -// ** build waypoint + const buildWaypoint = (rData: any): any => { const wpt: any = { position: { @@ -112,7 +111,7 @@ const buildWaypoint = (rData: any): any => { return wpt } -// ** build note + const buildNote = (rData: any): any => { const note: any = {} if (typeof rData.title !== 'undefined') { @@ -153,7 +152,7 @@ const buildNote = (rData: any): any => { return note } -// ** build region + const buildRegion = (rData: any): any => { const reg: any = { feature: { @@ -206,6 +205,12 @@ const buildRegion = (rData: any): any => { if (!isValid) { return null } + if ( + rData.points[0].latitude !== rData.points[rData.points.length-1].latitude && + rData.points[0].longitude !== rData.points[rData.points.length-1].longitude + ) { + rData.points.push( rData.points[0]) + } coords = rData.points.map((p: any) => { return [p.longitude, p.latitude] }) From 311582d1fb06854890a3c15216950fb8b319c317 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 21 Nov 2021 16:44:07 +1030 Subject: [PATCH 024/410] specify SignalKResourceType --- src/api/resources/index.ts | 44 ++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 7afc84dc6..d73919678 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -14,8 +14,10 @@ interface ResourceRequest { apiMethod?: string | null } +type SignalKResourceType= 'routes' | 'waypoints' |'notes' |'regions' |'charts' + interface ResourceProvider { - types: string[] + types: SignalKResourceType[] methods: ResourceProviderMethods } @@ -49,8 +51,8 @@ export class Resources { resProvider: { [key: string]: ResourceProviderMethods | null } = {} server: any - // ** in-scope resource types ** - private resourceTypes: string[] = [ + // in-scope resource types + private resourceTypes: SignalKResourceType[] = [ 'routes', 'waypoints', 'notes', @@ -62,7 +64,7 @@ export class Resources { this.start(app) } - // ** register resource provider ** + // register plugin with supplied id as resource provider register(pluginId: string, provider: ResourceProvider) { debug(`** Registering provider(s)....${provider?.types}`) if (!provider) { @@ -80,22 +82,22 @@ export class Resources { debug(this.resProvider) } - // ** un-register resource provider for the supplied types ** + // un-register plugin with supplied id as resource provider unRegister(pluginId: string) { if (!pluginId) { return } debug(`** Un-registering ${pluginId} resource provider(s)....`) - for (const i in this.resProvider) { - if (this.resProvider[i]?.pluginId === pluginId) { - debug(`** Un-registering ${i}....`) - delete this.resProvider[i] + for (const resourceType in this.resProvider) { + if (this.resProvider[resourceType]?.pluginId === pluginId) { + debug(`** Un-registering ${resourceType}....`) + delete this.resProvider[resourceType] } } debug(JSON.stringify(this.resProvider)) } - // ** return resource with supplied type and id ** + // Return resource with supplied type and id getResource(type: string, id: string) { debug(`** getResource(${type}, ${id})`) return this.actionResourceRequest({ @@ -107,17 +109,17 @@ export class Resources { }) } - // ** initialise resourcesApi ** + // initialise resourcesApi private start(app: any) { debug(`** Initialise ${SIGNALK_API_PATH}/resources path handler **`) this.server = app this.initResourceRoutes() } - // ** initialise handler for in-scope resource types ** + // initialise handler for in-scope resource types private initResourceRoutes() { + // list all serviced paths under resources this.server.get(`${SIGNALK_API_PATH}/resources`, (req: any, res: any) => { - // list all serviced paths under resources res.json(this.getResourcePaths()) }) this.server.use( @@ -141,7 +143,7 @@ export class Resources { ) } - // ** return all paths serviced under SIGNALK_API_PATH/resources ** + // return all paths serviced under SIGNALK_API_PATH/resources private getResourcePaths(): { [key: string]: any } { const resPaths: { [key: string]: any } = {} Object.entries(this.resProvider).forEach((p: any) => { @@ -165,7 +167,7 @@ export class Resources { return resPaths } - // ** parse api path request and return ResourceRequest object ** + // parse api path request and return ResourceRequest object private parseResourceRequest(req: any): ResourceRequest | undefined { debug('** req.originalUrl:', req.originalUrl) debug('** req.method:', req.method) @@ -215,7 +217,7 @@ export class Resources { } } - // ** action an in-scope resource request ** + // action an in-scope resource request private async actionResourceRequest(req: ResourceRequest): Promise { debug('********* action request *************') debug(req) @@ -241,7 +243,7 @@ export class Resources { return await this.execResourceRequest(req) } - // ** transform API request to ResourceRequest ** + // transform API request to ResourceRequest private transformApiRequest(req: ResourceRequest): ResourceRequest { if (req.apiMethod?.indexOf('delete') !== -1) { req.method = 'DELETE' @@ -257,7 +259,7 @@ export class Resources { return req } - // ** action an in-scope resource request ** + // action an in-scope resource request private async execResourceRequest(req: ResourceRequest): Promise { debug('********* execute request *************') debug(req) @@ -331,14 +333,14 @@ export class Resources { } if (req.method === 'POST' || req.method === 'PUT') { - // check for supplied value if (typeof req.body.value === 'undefined' || req.body.value == null) { return { statusCode: 406, message: `No resource data supplied!` } } - // validate supplied request data + if (!validate.resource(req.resourceType, req.body.value)) { return { statusCode: 406, message: `Invalid resource data supplied!` } } + if (req.method === 'POST') { const id = UUID_PREFIX + uuidv4() const retVal = await this.resProvider[req.resourceType]?.setResource( @@ -388,7 +390,7 @@ export class Resources { } } - // ** send delta message with resource PUT, POST, DELETE action result + // Send delta message. Used by resource PUT, POST, DELETE actions private sendDelta( providerId: string, type: string, From 176645bbc8de6f9a15f2804f73b3c975f0d69934 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 21 Nov 2021 17:45:53 +1030 Subject: [PATCH 025/410] move interfaces to server-api --- src/api/resources/index.ts | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index d73919678..ec92cb81b 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -3,20 +3,9 @@ import { v4 as uuidv4 } from 'uuid' import { buildResource } from './resources' import { validate } from './validate' -const debug = Debug('signalk:resources') - -interface ResourceRequest { - method: 'GET' | 'PUT' | 'POST' | 'DELETE' - body: any - query: { [key: string]: any } - resourceType: string - resourceId: string - apiMethod?: string | null -} - -type SignalKResourceType= 'routes' | 'waypoints' |'notes' |'regions' |'charts' +/*export type SignalKResourceType= 'routes' | 'waypoints' |'notes' |'regions' |'charts' -interface ResourceProvider { +export interface ResourceProvider { types: SignalKResourceType[] methods: ResourceProviderMethods } @@ -31,6 +20,19 @@ interface ResourceProviderMethods { value: { [key: string]: any } ) => Promise deleteResource: (type: string, id: string) => Promise +}*/ + +import { SignalKResourceType, ResourceProvider, ResourceProviderMethods } from '@signalk/server-api' + +const debug = Debug('signalk:resources') + +interface ResourceRequest { + method: 'GET' | 'PUT' | 'POST' | 'DELETE' + body: any + query: { [key: string]: any } + resourceType: string + resourceId: string + apiMethod?: string | null } const SIGNALK_API_PATH = `/signalk/v1/api` @@ -48,8 +50,8 @@ const API_METHODS = [ ] export class Resources { - resProvider: { [key: string]: ResourceProviderMethods | null } = {} - server: any + private resProvider: { [key: string]: ResourceProviderMethods | null } = {} + private server: any // in-scope resource types private resourceTypes: SignalKResourceType[] = [ @@ -200,7 +202,7 @@ export class Resources { } } - const retReq = { + const retReq:any = { method: req.method, body: req.body, query: req.query, @@ -228,7 +230,7 @@ export class Resources { } if ( - !this.resourceTypes.includes(req.resourceType) || + !this.resourceTypes.includes(req.resourceType as SignalKResourceType) || !this.resProvider[req.resourceType] ) { return { statusCode: 501, message: `No Provider` } From 1f1047141947431a27ac064b978ef79277fe24bc Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 21 Nov 2021 10:21:25 +0200 Subject: [PATCH 026/410] refactor: use Express types --- src/api/resources/index.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index ec92cb81b..d6d7c18c9 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -23,6 +23,7 @@ interface ResourceProviderMethods { }*/ import { SignalKResourceType, ResourceProvider, ResourceProviderMethods } from '@signalk/server-api' +import { Application, Handler, NextFunction, Request, Response } from 'express' const debug = Debug('signalk:resources') @@ -49,9 +50,13 @@ const API_METHODS = [ 'deleteRegion' ] +// FIXME use types from https://github.com/SignalK/signalk-server/pull/1358 +interface ResourceApplication extends Application { + handleMessage: any +} export class Resources { private resProvider: { [key: string]: ResourceProviderMethods | null } = {} - private server: any + private server: ResourceApplication // in-scope resource types private resourceTypes: SignalKResourceType[] = [ @@ -62,7 +67,8 @@ export class Resources { 'charts' ] - constructor(app: any) { + constructor(app: ResourceApplication) { + this.server = app this.start(app) } @@ -121,12 +127,12 @@ export class Resources { // initialise handler for in-scope resource types private initResourceRoutes() { // list all serviced paths under resources - this.server.get(`${SIGNALK_API_PATH}/resources`, (req: any, res: any) => { + this.server.get(`${SIGNALK_API_PATH}/resources`, (req: Request, res: Response) => { res.json(this.getResourcePaths()) }) this.server.use( `${SIGNALK_API_PATH}/resources/*`, - async (req: any, res: any, next: any) => { + async (req: Request, res: Response, next: NextFunction) => { const result = this.parseResourceRequest(req) if (result) { const ar = await this.actionResourceRequest(result) @@ -170,7 +176,7 @@ export class Resources { } // parse api path request and return ResourceRequest object - private parseResourceRequest(req: any): ResourceRequest | undefined { + private parseResourceRequest(req: Request): ResourceRequest | undefined { debug('** req.originalUrl:', req.originalUrl) debug('** req.method:', req.method) debug('** req.body:', req.body) From d59c83c86abebc3547c9238b5f8f501f929dde31 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 22 Nov 2021 10:29:39 +1030 Subject: [PATCH 027/410] cleanup express route handling --- src/api/resources/index.ts | 139 ++++++++++++++++++++----------------- 1 file changed, 74 insertions(+), 65 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index ec92cb81b..8c980fa6b 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -3,25 +3,6 @@ import { v4 as uuidv4 } from 'uuid' import { buildResource } from './resources' import { validate } from './validate' -/*export type SignalKResourceType= 'routes' | 'waypoints' |'notes' |'regions' |'charts' - -export interface ResourceProvider { - types: SignalKResourceType[] - methods: ResourceProviderMethods -} - -interface ResourceProviderMethods { - pluginId: string - listResources: (type: string, query: { [key: string]: any }) => Promise - getResource: (type: string, id: string) => Promise - setResource: ( - type: string, - id: string, - value: { [key: string]: any } - ) => Promise - deleteResource: (type: string, id: string) => Promise -}*/ - import { SignalKResourceType, ResourceProvider, ResourceProviderMethods } from '@signalk/server-api' const debug = Debug('signalk:resources') @@ -30,7 +11,7 @@ interface ResourceRequest { method: 'GET' | 'PUT' | 'POST' | 'DELETE' body: any query: { [key: string]: any } - resourceType: string + resourceType: SignalKResourceType resourceId: string apiMethod?: string | null } @@ -66,7 +47,6 @@ export class Resources { this.start(app) } - // register plugin with supplied id as resource provider register(pluginId: string, provider: ResourceProvider) { debug(`** Registering provider(s)....${provider?.types}`) if (!provider) { @@ -84,7 +64,6 @@ export class Resources { debug(this.resProvider) } - // un-register plugin with supplied id as resource provider unRegister(pluginId: string) { if (!pluginId) { return @@ -99,8 +78,7 @@ export class Resources { debug(JSON.stringify(this.resProvider)) } - // Return resource with supplied type and id - getResource(type: string, id: string) { + getResource(type: SignalKResourceType, id: string) { debug(`** getResource(${type}, ${id})`) return this.actionResourceRequest({ method: 'GET', @@ -111,21 +89,60 @@ export class Resources { }) } - // initialise resourcesApi private start(app: any) { debug(`** Initialise ${SIGNALK_API_PATH}/resources path handler **`) this.server = app this.initResourceRoutes() } - // initialise handler for in-scope resource types private initResourceRoutes() { // list all serviced paths under resources this.server.get(`${SIGNALK_API_PATH}/resources`, (req: any, res: any) => { res.json(this.getResourcePaths()) }) + + this.server.get( + `${SIGNALK_API_PATH}/resources/:resourceType/:resourceId/*`, + async (req: any, res: any, next: any) => { + const result = this.parseResourceRequest(req) + if (result) { + const ar = await this.actionResourceRequest(result) + if (typeof ar.statusCode !== 'undefined') { + debug(`${JSON.stringify(ar)}`) + res.status = ar.statusCode + res.send(ar.message) + } else { + res.json(ar) + } + } else { + debug('** No provider found... calling next()...') + next() + } + } + ) + this.server.use( - `${SIGNALK_API_PATH}/resources/*`, + `${SIGNALK_API_PATH}/resources/:resourceType/:resourceId`, + async (req: any, res: any, next: any) => { + const result = this.parseResourceRequest(req) + if (result) { + const ar = await this.actionResourceRequest(result) + if (typeof ar.statusCode !== 'undefined') { + debug(`${JSON.stringify(ar)}`) + res.status = ar.statusCode + res.send(ar.message) + } else { + res.json(ar) + } + } else { + debug('** No provider found... calling next()...') + next() + } + } + ) + + this.server.use( + `${SIGNALK_API_PATH}/resources/:resourceType`, async (req: any, res: any, next: any) => { const result = this.parseResourceRequest(req) if (result) { @@ -145,7 +162,6 @@ export class Resources { ) } - // return all paths serviced under SIGNALK_API_PATH/resources private getResourcePaths(): { [key: string]: any } { const resPaths: { [key: string]: any } = {} Object.entries(this.resProvider).forEach((p: any) => { @@ -169,57 +185,53 @@ export class Resources { return resPaths } - // parse api path request and return ResourceRequest object private parseResourceRequest(req: any): ResourceRequest | undefined { - debug('** req.originalUrl:', req.originalUrl) + debug('********* parse request *************') debug('** req.method:', req.method) debug('** req.body:', req.body) debug('** req.query:', req.query) debug('** req.params:', req.params) - const p = req.params[0].split('/') - let resType = typeof req.params[0] !== 'undefined' ? p[0] : '' - const resId = p.length > 1 ? p[1] : '' - const resAttrib = p.length > 2 ? p.slice(2) : [] - req.query.resAttrib = resAttrib - debug('** resType:', resType) - debug('** resId:', resId) - debug('** resAttrib:', resAttrib) - debug('** req.params + attribs:', req.query) - - const apiMethod = API_METHODS.includes(resType) ? resType : null - if (apiMethod) { - if (apiMethod.toLowerCase().indexOf('waypoint') !== -1) { - resType = 'waypoints' + + const resReq:any = { + method: req.method, + body: req.body, + query: req.query, + resourceType: req.params.resourceType ?? null, + resourceId: req.params.resourceId ?? null, + apiMethod: API_METHODS.includes(req.params.resourceType) ? req.params.resourceType : null + } + + if (resReq.apiMethod) { + if (resReq.apiMethod.toLowerCase().indexOf('waypoint') !== -1) { + resReq.resourceType = 'waypoints' } - if (apiMethod.toLowerCase().indexOf('route') !== -1) { - resType = 'routes' + if (resReq.apiMethod.toLowerCase().indexOf('route') !== -1) { + resReq.resourceType = 'routes' } - if (apiMethod.toLowerCase().indexOf('note') !== -1) { - resType = 'notes' + if (resReq.apiMethod.toLowerCase().indexOf('note') !== -1) { + resReq.resourceType = 'notes' } - if (apiMethod.toLowerCase().indexOf('region') !== -1) { - resType = 'regions' + if (resReq.apiMethod.toLowerCase().indexOf('region') !== -1) { + resReq.resourceType = 'regions' } + } else { + const resAttrib = req.params['0'] ? req.params['0'].split('/') : [] + req.query.attrib = resAttrib } - const retReq:any = { - method: req.method, - body: req.body, - query: req.query, - resourceType: resType, - resourceId: resId, - apiMethod - } + debug('** resReq:', resReq) - if (this.resourceTypes.includes(resType) && this.resProvider[resType]) { - return retReq + if ( + this.resourceTypes.includes(resReq.resourceType) && + this.resProvider[resReq.resourceType] + ) { + return resReq } else { debug('Invalid resource type or no provider for this type!') return undefined } } - // action an in-scope resource request private async actionResourceRequest(req: ResourceRequest): Promise { debug('********* action request *************') debug(req) @@ -230,7 +242,7 @@ export class Resources { } if ( - !this.resourceTypes.includes(req.resourceType as SignalKResourceType) || + !this.resourceTypes.includes(req.resourceType) || !this.resProvider[req.resourceType] ) { return { statusCode: 501, message: `No Provider` } @@ -245,7 +257,6 @@ export class Resources { return await this.execResourceRequest(req) } - // transform API request to ResourceRequest private transformApiRequest(req: ResourceRequest): ResourceRequest { if (req.apiMethod?.indexOf('delete') !== -1) { req.method = 'DELETE' @@ -261,7 +272,6 @@ export class Resources { return req } - // action an in-scope resource request private async execResourceRequest(req: ResourceRequest): Promise { debug('********* execute request *************') debug(req) @@ -392,7 +402,6 @@ export class Resources { } } - // Send delta message. Used by resource PUT, POST, DELETE actions private sendDelta( providerId: string, type: string, From 51d93f425e0f2e32e65f4867d91e1b395016b32a Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 22 Nov 2021 10:33:00 +1030 Subject: [PATCH 028/410] add ResourceProvider types to server-api --- packages/server-api/src/index.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages/server-api/src/index.ts b/packages/server-api/src/index.ts index 7b9489495..2ccfc0bae 100644 --- a/packages/server-api/src/index.ts +++ b/packages/server-api/src/index.ts @@ -3,6 +3,26 @@ import { PropertyValues, PropertyValuesCallback } from './propertyvalues' export { PropertyValue, PropertyValues, PropertyValuesCallback } from './propertyvalues' + +export type SignalKResourceType= 'routes' | 'waypoints' |'notes' |'regions' |'charts' + +export interface ResourceProviderMethods { + pluginId: string + listResources: (type: string, query: { [key: string]: any }) => Promise + getResource: (type: string, id: string) => Promise + setResource: ( + type: string, + id: string, + value: { [key: string]: any } + ) => Promise + deleteResource: (type: string, id: string) => Promise +} + +export interface ResourceProvider { + types: SignalKResourceType[] + methods: ResourceProviderMethods +} + type Unsubscribe = () => {} export interface PropertyValuesEmitter { emitPropertyValue: (name: string, value: any) => void @@ -54,4 +74,5 @@ export interface Plugin { registerWithRouter?: (router: IRouter) => void signalKApiRoutes?: (router: IRouter) => IRouter enabledByDefault?: boolean + resourceProvider: ResourceProvider } From e5556d760543a23f81a848d7c5c921fdb3078f4c Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 22 Nov 2021 15:16:48 +1030 Subject: [PATCH 029/410] fix type --- src/api/resources/resources.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/api/resources/resources.ts b/src/api/resources/resources.ts index 3b79bc271..756610774 100644 --- a/src/api/resources/resources.ts +++ b/src/api/resources/resources.ts @@ -1,8 +1,9 @@ +import { SignalKResourceType } from '@signalk/server-api' import { getDistance, isValidCoordinate } from 'geolib' import ngeohash from 'ngeohash' -export const buildResource = (resType: string, data: any): any => { +export const buildResource = (resType: SignalKResourceType, data: any): any => { if (resType === 'routes') { return buildRoute(data) } From 2fdb990d8862af96e3a1ff5f06209f537523988c Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 22 Nov 2021 16:18:02 +1030 Subject: [PATCH 030/410] chore: lint --- src/api/resources/resources.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/api/resources/resources.ts b/src/api/resources/resources.ts index 756610774..6a45f736c 100644 --- a/src/api/resources/resources.ts +++ b/src/api/resources/resources.ts @@ -2,7 +2,6 @@ import { SignalKResourceType } from '@signalk/server-api' import { getDistance, isValidCoordinate } from 'geolib' import ngeohash from 'ngeohash' - export const buildResource = (resType: SignalKResourceType, data: any): any => { if (resType === 'routes') { return buildRoute(data) @@ -70,7 +69,6 @@ const buildRoute = (rData: any): any => { return rte } - const buildWaypoint = (rData: any): any => { const wpt: any = { position: { @@ -112,7 +110,6 @@ const buildWaypoint = (rData: any): any => { return wpt } - const buildNote = (rData: any): any => { const note: any = {} if (typeof rData.title !== 'undefined') { @@ -153,7 +150,6 @@ const buildNote = (rData: any): any => { return note } - const buildRegion = (rData: any): any => { const reg: any = { feature: { @@ -207,10 +203,12 @@ const buildRegion = (rData: any): any => { return null } if ( - rData.points[0].latitude !== rData.points[rData.points.length-1].latitude && - rData.points[0].longitude !== rData.points[rData.points.length-1].longitude + rData.points[0].latitude !== + rData.points[rData.points.length - 1].latitude && + rData.points[0].longitude !== + rData.points[rData.points.length - 1].longitude ) { - rData.points.push( rData.points[0]) + rData.points.push(rData.points[0]) } coords = rData.points.map((p: any) => { return [p.longitude, p.latitude] From c8dceafbb37220d2651c6114e07d8b63b88388a2 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 22 Nov 2021 17:04:43 +1030 Subject: [PATCH 031/410] chore: update return type --- SERVERPLUGINS.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/SERVERPLUGINS.md b/SERVERPLUGINS.md index 8a8e6613c..8fbcb6737 100644 --- a/SERVERPLUGINS.md +++ b/SERVERPLUGINS.md @@ -704,6 +704,9 @@ app.registerDeltaInputHandler((delta, next) => { Retrieve resource data for the supplied resource type and id. +This method invokes the `registered Resource Provider` for the supplied `resource_type` and returns a __resovled Promise__ containing the route resource data if successful or +a __rejected Promise__ containing an Error object if unsuccessful. + data for the full path of the directory where the plugin can persist its internal data, like data files.If a plugin wants to act as a resource provider, it will need to register its provider methods during startup using this function. @@ -711,9 +714,7 @@ Retrieve resource data for the supplied resource type and id. ```javascript let myRoute= app.resourcesApi.getResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a'); ``` -Will return the route resource data or `null` if a route with the supplied id cannot be found. - -_Example:_ +_Returns resolved Promise containing:_ ```json { "name": "Name of the route", From 10323b26f956d50c575fcc5eb2cc8f4d45f6dfcf Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 22 Nov 2021 17:05:39 +1030 Subject: [PATCH 032/410] Use Express routing params for processing requests --- src/api/resources/index.ts | 540 ++++++++++++++++++------------------- 1 file changed, 263 insertions(+), 277 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 2ec338dd2..f0e1f204f 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -3,7 +3,11 @@ import { v4 as uuidv4 } from 'uuid' import { buildResource } from './resources' import { validate } from './validate' -import { SignalKResourceType, ResourceProvider, ResourceProviderMethods } from '@signalk/server-api' +import { + ResourceProvider, + ResourceProviderMethods, + SignalKResourceType +} from '@signalk/server-api' import { Application, Handler, NextFunction, Request, Response } from 'express' const debug = Debug('signalk:resources') @@ -33,7 +37,7 @@ const API_METHODS = [ // FIXME use types from https://github.com/SignalK/signalk-server/pull/1358 interface ResourceApplication extends Application { - handleMessage: any + handleMessage: (id: string, data: any) => void } export class Resources { private resProvider: { [key: string]: ResourceProviderMethods | null } = {} @@ -84,15 +88,12 @@ export class Resources { debug(JSON.stringify(this.resProvider)) } - getResource(type: SignalKResourceType, id: string) { - debug(`** getResource(${type}, ${id})`) - return this.actionResourceRequest({ - method: 'GET', - body: {}, - query: {}, - resourceType: type, - resourceId: id - }) + getResource(resType: SignalKResourceType, resId: string) { + debug(`** getResource(${resType}, ${resId})`) + if (!this.checkForProvider(resType)) { + return Promise.reject(new Error(`No provider for ${resType}`)) + } + return this.resProvider[resType]?.getResource(resType, resId) } private start(app: any) { @@ -103,66 +104,265 @@ export class Resources { private initResourceRoutes() { // list all serviced paths under resources - this.server.get(`${SIGNALK_API_PATH}/resources`, (req: Request, res: Response) => { - res.json(this.getResourcePaths()) - }) + this.server.get( + `${SIGNALK_API_PATH}/resources`, + (req: Request, res: Response) => { + res.json(this.getResourcePaths()) + } + ) + // facilitate retrieval of a specific resource this.server.get( - `${SIGNALK_API_PATH}/resources/:resourceType/:resourceId/*`, - async (req: any, res: any, next: any) => { - const result = this.parseResourceRequest(req) - if (result) { - const ar = await this.actionResourceRequest(result) - if (typeof ar.statusCode !== 'undefined') { - debug(`${JSON.stringify(ar)}`) - res.status = ar.statusCode - res.send(ar.message) - } else { - res.json(ar) - } + `${SIGNALK_API_PATH}/resources/:resourceType/:resourceId`, + async (req: Request, res: Response, next: NextFunction) => { + debug(`** GET ${SIGNALK_API_PATH}/resources/:resourceType/:resourceId`) + if ( + !this.checkForProvider(req.params.resourceType as SignalKResourceType) + ) { + debug('** No provider found... calling next()...') + next() + return + } + if (!validate.uuid(req.params.resourceId)) { + res + .status(406) + .send(`Invalid resource id provided (${req.params.resourceId})`) + return + } + const retVal = await this.resProvider[ + req.params.resourceType + ]?.getResource(req.params.resourceType, req.params.resourceId) + if (retVal) { + res.json(retVal) } else { + res.status(404).send(`Resource not found! (${req.params.resourceId})`) + } + } + ) + + // facilitate retrieval of a collection of resource entries + this.server.get( + `${SIGNALK_API_PATH}/resources/:resourceType`, + async (req: Request, res: Response, next: NextFunction) => { + debug(`** GET ${SIGNALK_API_PATH}/resources/:resourceType`) + if ( + !this.checkForProvider(req.params.resourceType as SignalKResourceType) + ) { debug('** No provider found... calling next()...') next() + return + } + const retVal = await this.resProvider[ + req.params.resourceType + ]?.listResources(req.params.resourceType, req.query) + if (retVal) { + res.json(retVal) + } else { + res.status(404).send(`Error retrieving resources!`) + } + } + ) + + // facilitate creation of new resource entry of supplied type + this.server.post( + `${SIGNALK_API_PATH}/resources/:resourceType`, + async (req: Request, res: Response, next: NextFunction) => { + debug(`** POST ${SIGNALK_API_PATH}/resources/:resourceType`) + if ( + !this.checkForProvider(req.params.resourceType as SignalKResourceType) + ) { + debug('** No provider found... calling next()...') + next() + return + } + if (!validate.resource(req.params.resourceType, req.body.value)) { + res.status(406).send(`Invalid resource data supplied!`) + return + } + const id = UUID_PREFIX + uuidv4() + const retVal = await this.resProvider[ + req.params.resourceType + ]?.setResource(req.params.resourceType, id, req.body.value) + if (retVal) { + this.server.handleMessage( + this.resProvider[req.params.resourceType]?.pluginId as string, + this.buildDeltaMsg( + req.params.resourceType as SignalKResourceType, + id, + req.body.value + ) + ) + res + .status(200) + .send(`New ${req.params.resourceType} resource (${id}) saved.`) + } else { + res + .status(404) + .send(`Error saving ${req.params.resourceType} resource (${id})!`) } } ) - this.server.use( + // facilitate creation / update of resource entry at supplied id + this.server.put( `${SIGNALK_API_PATH}/resources/:resourceType/:resourceId`, - async (req: any, res: any, next: any) => { - const result = this.parseResourceRequest(req) - if (result) { - const ar = await this.actionResourceRequest(result) - if (typeof ar.statusCode !== 'undefined') { - debug(`${JSON.stringify(ar)}`) - res.status = ar.statusCode - res.send(ar.message) - } else { - res.json(ar) - } + async (req: Request, res: Response, next: NextFunction) => { + debug(`** PUT ${SIGNALK_API_PATH}/resources/:resourceType/:resourceId`) + if ( + !this.checkForProvider(req.params.resourceType as SignalKResourceType) + ) { + debug('** No provider found... calling next()...') + next() + return + } + if (!validate.resource(req.params.resourceType, req.body.value)) { + res.status(406).send(`Invalid resource data supplied!`) + return + } + const retVal = await this.resProvider[ + req.params.resourceType + ]?.setResource( + req.params.resourceType, + req.params.resourceId, + req.body.value + ) + if (retVal) { + this.server.handleMessage( + this.resProvider[req.params.resourceType]?.pluginId as string, + this.buildDeltaMsg( + req.params.resourceType as SignalKResourceType, + req.params.resourceId, + req.body.value + ) + ) + res + .status(200) + .send( + `${req.params.resourceType} resource (${req.params.resourceId}) saved.` + ) } else { + res + .status(404) + .send( + `Error saving ${req.params.resourceType} resource (${req.params.resourceId})!` + ) + } + } + ) + + // facilitate deletion of specific of resource entry at supplied id + this.server.delete( + `${SIGNALK_API_PATH}/resources/:resourceType/:resourceId`, + async (req: Request, res: Response, next: NextFunction) => { + debug( + `** DELETE ${SIGNALK_API_PATH}/resources/:resourceType/:resourceId` + ) + if ( + !this.checkForProvider(req.params.resourceType as SignalKResourceType) + ) { debug('** No provider found... calling next()...') next() + return + } + if (!validate.uuid(req.params.resourceId)) { + res + .status(406) + .send(`Invalid resource id provided (${req.params.resourceId})`) + return + } + const retVal = await this.resProvider[ + req.params.resourceType + ]?.deleteResource(req.params.resourceType, req.params.resourceId) + if (retVal) { + this.server.handleMessage( + this.resProvider[req.params.resourceType]?.pluginId as string, + this.buildDeltaMsg( + req.params.resourceType as SignalKResourceType, + req.params.resourceId, + null + ) + ) + res.status(200).send(`Resource (${req.params.resourceId}) deleted.`) + } else { + res + .status(400) + .send(`Error deleting resource (${req.params.resourceId})!`) } } ) - this.server.use( - `${SIGNALK_API_PATH}/resources/:resourceType`, + // facilitate API requests + this.server.put( + `${SIGNALK_API_PATH}/resources/:apiFunction`, async (req: Request, res: Response, next: NextFunction) => { - const result = this.parseResourceRequest(req) - if (result) { - const ar = await this.actionResourceRequest(result) - if (typeof ar.statusCode !== 'undefined') { - debug(`${JSON.stringify(ar)}`) - res.status = ar.statusCode - res.send(ar.message) + debug(`** PUT ${SIGNALK_API_PATH}/resources/:apiFunction`) + + // check for valid API method request + if (!API_METHODS.includes(req.params.apiFunction)) { + res.status(501).send(`Invalid API method ${req.params.apiFunction}!`) + return + } + let resType: SignalKResourceType = 'waypoints' + if (req.params.apiFunction.toLowerCase().indexOf('waypoint') !== -1) { + resType = 'waypoints' + } + if (req.params.apiFunction.toLowerCase().indexOf('route') !== -1) { + resType = 'routes' + } + if (req.params.apiFunction.toLowerCase().indexOf('note') !== -1) { + resType = 'notes' + } + if (req.params.apiFunction.toLowerCase().indexOf('region') !== -1) { + resType = 'regions' + } + if (!this.checkForProvider(resType)) { + res.status(501).send(`No provider for ${resType}!`) + return + } + let resId: string = '' + let resValue: any = null + + if (req.params.apiFunction.toLowerCase().indexOf('set') !== -1) { + resValue = buildResource(resType, req.body) + if (!resValue) { + res.status(406).send(`Invalid resource data supplied!`) + return + } + if (!req.body.id) { + resId = UUID_PREFIX + uuidv4() } else { - res.json(ar) + if (!validate.uuid(req.body.id)) { + res.status(406).send(`Invalid resource id supplied!`) + return + } + resId = req.body.id } + } + if (req.params.apiFunction.toLowerCase().indexOf('delete') !== -1) { + resValue = null + if (!req.body.id) { + res.status(406).send(`No resource id supplied!`) + return + } + if (!validate.uuid(req.body.id)) { + res.status(406).send(`Invalid resource id supplied!`) + return + } + resId = req.body.id + } + const retVal = await this.resProvider[resType]?.setResource( + resType, + resId, + resValue + ) + if (retVal) { + this.server.handleMessage( + this.resProvider[resType]?.pluginId as string, + this.buildDeltaMsg(resType, resId, resValue) + ) + res.status(200).send(`SUCCESS: ${req.params.apiFunction} complete.`) } else { - debug('** No provider found... calling next()...') - next() + res.status(404).send(`ERROR: ${req.params.apiFunction} incomplete!`) } } ) @@ -191,242 +391,28 @@ export class Resources { return resPaths } - - private parseResourceRequest(req: Request): ResourceRequest | undefined { - debug('********* parse request *************') - debug('** req.method:', req.method) - debug('** req.body:', req.body) - debug('** req.query:', req.query) - debug('** req.params:', req.params) - - const resReq:any = { - method: req.method, - body: req.body, - query: req.query, - resourceType: req.params.resourceType ?? null, - resourceId: req.params.resourceId ?? null, - apiMethod: API_METHODS.includes(req.params.resourceType) ? req.params.resourceType : null - } - - if (resReq.apiMethod) { - if (resReq.apiMethod.toLowerCase().indexOf('waypoint') !== -1) { - resReq.resourceType = 'waypoints' - } - if (resReq.apiMethod.toLowerCase().indexOf('route') !== -1) { - resReq.resourceType = 'routes' - } - if (resReq.apiMethod.toLowerCase().indexOf('note') !== -1) { - resReq.resourceType = 'notes' - } - if (resReq.apiMethod.toLowerCase().indexOf('region') !== -1) { - resReq.resourceType = 'regions' - } - } else { - const resAttrib = req.params['0'] ? req.params['0'].split('/') : [] - req.query.attrib = resAttrib - } - - debug('** resReq:', resReq) - - if ( - this.resourceTypes.includes(resReq.resourceType) && - this.resProvider[resReq.resourceType] - ) { - return resReq - } else { - debug('Invalid resource type or no provider for this type!') - return undefined - } + private checkForProvider(resType: SignalKResourceType): boolean { + return this.resourceTypes.includes(resType) && this.resProvider[resType] + ? true + : false } - private async actionResourceRequest(req: ResourceRequest): Promise { - debug('********* action request *************') - debug(req) - - // check for registered resource providers - if (!this.resProvider) { - return { statusCode: 501, message: `No Provider` } - } - - if ( - !this.resourceTypes.includes(req.resourceType) || - !this.resProvider[req.resourceType] - ) { - return { statusCode: 501, message: `No Provider` } - } - - // check for API method request - if (req.apiMethod && API_METHODS.includes(req.apiMethod)) { - debug(`API Method (${req.apiMethod})`) - req = this.transformApiRequest(req) - } - - return await this.execResourceRequest(req) - } - - private transformApiRequest(req: ResourceRequest): ResourceRequest { - if (req.apiMethod?.indexOf('delete') !== -1) { - req.method = 'DELETE' - } - if (req.apiMethod?.indexOf('set') !== -1) { - if (!req.body.id) { - req.method = 'POST' - } else { - req.resourceId = req.body.id - } - req.body = { value: buildResource(req.resourceType, req.body) ?? {} } - } - return req - } - - private async execResourceRequest(req: ResourceRequest): Promise { - debug('********* execute request *************') - debug(req) - if (req.method === 'GET') { - let retVal: any - if (!req.resourceId) { - retVal = await this.resProvider[req.resourceType]?.listResources( - req.resourceType, - req.query - ) - return retVal - ? retVal - : { statusCode: 404, message: `Error retrieving resources!` } - } - if (!validate.uuid(req.resourceId)) { - return { - statusCode: 406, - message: `Invalid resource id provided (${req.resourceId})` - } - } - retVal = await this.resProvider[req.resourceType]?.getResource( - req.resourceType, - req.resourceId - ) - return retVal - ? retVal - : { - statusCode: 404, - message: `Resource not found (${req.resourceId})!` - } - } - - if (req.method === 'DELETE' || req.method === 'PUT') { - if (!req.resourceId) { - return { statusCode: 406, value: `No resource id provided!` } - } - if (!validate.uuid(req.resourceId)) { - return { - statusCode: 406, - message: `Invalid resource id provided (${req.resourceId})!` - } - } - if ( - req.method === 'DELETE' || - (req.method === 'PUT' && - typeof req.body.value !== 'undefined' && - req.body.value == null) - ) { - const retVal = await this.resProvider[req.resourceType]?.deleteResource( - req.resourceType, - req.resourceId - ) - if (retVal) { - this.sendDelta( - this.resProvider[req.resourceType]?.pluginId as string, - req.resourceType, - req.resourceId, - null - ) - return { - statusCode: 200, - message: `Resource (${req.resourceId}) deleted.` - } - } else { - return { - statusCode: 400, - message: `Error deleting resource (${req.resourceId})!` - } - } - } - } - - if (req.method === 'POST' || req.method === 'PUT') { - if (typeof req.body.value === 'undefined' || req.body.value == null) { - return { statusCode: 406, message: `No resource data supplied!` } - } - - if (!validate.resource(req.resourceType, req.body.value)) { - return { statusCode: 406, message: `Invalid resource data supplied!` } - } - - if (req.method === 'POST') { - const id = UUID_PREFIX + uuidv4() - const retVal = await this.resProvider[req.resourceType]?.setResource( - req.resourceType, - id, - req.body.value - ) - if (retVal) { - this.sendDelta( - this.resProvider[req.resourceType]?.pluginId as string, - req.resourceType, - id, - req.body.value - ) - return { statusCode: 200, message: `Resource (${id}) saved.` } - } else { - return { statusCode: 400, message: `Error saving resource (${id})!` } - } - } - if (req.method === 'PUT') { - if (!req.resourceId) { - return { statusCode: 406, message: `No resource id provided!` } - } - const retVal = await this.resProvider[req.resourceType]?.setResource( - req.resourceType, - req.resourceId, - req.body.value - ) - if (retVal) { - this.sendDelta( - this.resProvider[req.resourceType]?.pluginId as string, - req.resourceType, - req.resourceId, - req.body.value - ) - return { - statusCode: 200, - message: `Resource (${req.resourceId}) updated.` - } - } else { - return { - statusCode: 400, - message: `Error updating resource (${req.resourceId})!` - } - } - } - } - } - - private sendDelta( - providerId: string, - type: string, - id: string, - value: any - ): void { - debug(`** Sending Delta: resources.${type}.${id}`) - this.server.handleMessage(providerId, { + private buildDeltaMsg( + resType: SignalKResourceType, + resid: string, + resValue: any + ): any { + return { updates: [ { values: [ { - path: `resources.${type}.${id}`, - value + path: `resources.${resType}.${resid}`, + value: resValue } ] } ] - }) + } } } From 404db118c7838c3ea3f893c260f6765d065abf23 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 24 Nov 2021 09:13:43 +1030 Subject: [PATCH 033/410] throw on error --- src/api/resources/index.ts | 94 ++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 49 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index f0e1f204f..d8c65b4cb 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -12,15 +12,6 @@ import { Application, Handler, NextFunction, Request, Response } from 'express' const debug = Debug('signalk:resources') -interface ResourceRequest { - method: 'GET' | 'PUT' | 'POST' | 'DELETE' - body: any - query: { [key: string]: any } - resourceType: SignalKResourceType - resourceId: string - apiMethod?: string | null -} - const SIGNALK_API_PATH = `/signalk/v1/api` const UUID_PREFIX = 'urn:mrn:signalk:uuid:' @@ -35,10 +26,10 @@ const API_METHODS = [ 'deleteRegion' ] -// FIXME use types from https://github.com/SignalK/signalk-server/pull/1358 interface ResourceApplication extends Application { handleMessage: (id: string, data: any) => void } + export class Resources { private resProvider: { [key: string]: ResourceProviderMethods | null } = {} private server: ResourceApplication @@ -129,14 +120,15 @@ export class Resources { .send(`Invalid resource id provided (${req.params.resourceId})`) return } - const retVal = await this.resProvider[ - req.params.resourceType - ]?.getResource(req.params.resourceType, req.params.resourceId) - if (retVal) { - res.json(retVal) - } else { + try { + const retVal = await this.resProvider[ + req.params.resourceType + ]?.getResource(req.params.resourceType, req.params.resourceId) + res.json(retVal) + } catch (err) { res.status(404).send(`Resource not found! (${req.params.resourceId})`) } + } ) @@ -152,12 +144,12 @@ export class Resources { next() return } - const retVal = await this.resProvider[ - req.params.resourceType - ]?.listResources(req.params.resourceType, req.query) - if (retVal) { - res.json(retVal) - } else { + try { + const retVal = await this.resProvider[ + req.params.resourceType + ]?.listResources(req.params.resourceType, req.query) + res.json(retVal) + } catch (err) { res.status(404).send(`Error retrieving resources!`) } } @@ -180,10 +172,11 @@ export class Resources { return } const id = UUID_PREFIX + uuidv4() - const retVal = await this.resProvider[ - req.params.resourceType - ]?.setResource(req.params.resourceType, id, req.body.value) - if (retVal) { + try { + const retVal = await this.resProvider[ + req.params.resourceType + ]?.setResource(req.params.resourceType, id, req.body.value) + this.server.handleMessage( this.resProvider[req.params.resourceType]?.pluginId as string, this.buildDeltaMsg( @@ -195,11 +188,11 @@ export class Resources { res .status(200) .send(`New ${req.params.resourceType} resource (${id}) saved.`) - } else { + } catch (err) { res .status(404) .send(`Error saving ${req.params.resourceType} resource (${id})!`) - } + } } ) @@ -219,14 +212,15 @@ export class Resources { res.status(406).send(`Invalid resource data supplied!`) return } - const retVal = await this.resProvider[ - req.params.resourceType - ]?.setResource( - req.params.resourceType, - req.params.resourceId, - req.body.value - ) - if (retVal) { + try { + const retVal = await this.resProvider[ + req.params.resourceType + ]?.setResource( + req.params.resourceType, + req.params.resourceId, + req.body.value + ) + this.server.handleMessage( this.resProvider[req.params.resourceType]?.pluginId as string, this.buildDeltaMsg( @@ -240,7 +234,7 @@ export class Resources { .send( `${req.params.resourceType} resource (${req.params.resourceId}) saved.` ) - } else { + } catch (err) { res .status(404) .send( @@ -270,10 +264,11 @@ export class Resources { .send(`Invalid resource id provided (${req.params.resourceId})`) return } - const retVal = await this.resProvider[ - req.params.resourceType - ]?.deleteResource(req.params.resourceType, req.params.resourceId) - if (retVal) { + try { + const retVal = await this.resProvider[ + req.params.resourceType + ]?.deleteResource(req.params.resourceType, req.params.resourceId) + this.server.handleMessage( this.resProvider[req.params.resourceType]?.pluginId as string, this.buildDeltaMsg( @@ -283,7 +278,7 @@ export class Resources { ) ) res.status(200).send(`Resource (${req.params.resourceId}) deleted.`) - } else { + } catch (err) { res .status(400) .send(`Error deleting resource (${req.params.resourceId})!`) @@ -350,18 +345,19 @@ export class Resources { } resId = req.body.id } - const retVal = await this.resProvider[resType]?.setResource( - resType, - resId, - resValue - ) - if (retVal) { + + try { + const retVal = await this.resProvider[resType]?.setResource( + resType, + resId, + resValue + ) this.server.handleMessage( this.resProvider[resType]?.pluginId as string, this.buildDeltaMsg(resType, resId, resValue) ) res.status(200).send(`SUCCESS: ${req.params.apiFunction} complete.`) - } else { + } catch (err) { res.status(404).send(`ERROR: ${req.params.apiFunction} incomplete!`) } } From a5f6d6321dbc3d6e008e5b057e824b15c3f13818 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 24 Nov 2021 09:34:21 +1030 Subject: [PATCH 034/410] PUT/POST payloads directly in `body`..not `value:` --- src/api/resources/index.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index d8c65b4cb..fd4bbf484 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -167,7 +167,7 @@ export class Resources { next() return } - if (!validate.resource(req.params.resourceType, req.body.value)) { + if (!validate.resource(req.params.resourceType, req.body)) { res.status(406).send(`Invalid resource data supplied!`) return } @@ -175,14 +175,14 @@ export class Resources { try { const retVal = await this.resProvider[ req.params.resourceType - ]?.setResource(req.params.resourceType, id, req.body.value) + ]?.setResource(req.params.resourceType, id, req.body) this.server.handleMessage( this.resProvider[req.params.resourceType]?.pluginId as string, this.buildDeltaMsg( req.params.resourceType as SignalKResourceType, id, - req.body.value + req.body ) ) res @@ -208,7 +208,7 @@ export class Resources { next() return } - if (!validate.resource(req.params.resourceType, req.body.value)) { + if (!validate.resource(req.params.resourceType, req.body)) { res.status(406).send(`Invalid resource data supplied!`) return } @@ -218,7 +218,7 @@ export class Resources { ]?.setResource( req.params.resourceType, req.params.resourceId, - req.body.value + req.body ) this.server.handleMessage( @@ -226,7 +226,7 @@ export class Resources { this.buildDeltaMsg( req.params.resourceType as SignalKResourceType, req.params.resourceId, - req.body.value + req.body ) ) res From 4d0c227d83f82eb708d6c2349f157a9843c5e773 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 24 Nov 2021 10:50:23 +1030 Subject: [PATCH 035/410] remove resourceId validity check on GET --- src/api/resources/index.ts | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index fd4bbf484..09a40f422 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -114,12 +114,6 @@ export class Resources { next() return } - if (!validate.uuid(req.params.resourceId)) { - res - .status(406) - .send(`Invalid resource id provided (${req.params.resourceId})`) - return - } try { const retVal = await this.resProvider[ req.params.resourceType @@ -208,6 +202,14 @@ export class Resources { next() return } + if (req.params.resourceType !== 'charts') { + if(!validate.uuid(req.params.resourceId)) { + res + .status(406) + .send(`Invalid resource id provided (${req.params.resourceId})`) + return + } + } if (!validate.resource(req.params.resourceType, req.body)) { res.status(406).send(`Invalid resource data supplied!`) return @@ -258,12 +260,7 @@ export class Resources { next() return } - if (!validate.uuid(req.params.resourceId)) { - res - .status(406) - .send(`Invalid resource id provided (${req.params.resourceId})`) - return - } + try { const retVal = await this.resProvider[ req.params.resourceType From 63c35e72a9b990880e726011edb6a2ff2234f17f Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 24 Nov 2021 15:01:01 +1030 Subject: [PATCH 036/410] chore: Updated documentation --- RESOURCE_PROVIDER_PLUGINS.md | 493 +++++++++++++++++++++++------------ SERVERPLUGINS.md | 39 +-- 2 files changed, 340 insertions(+), 192 deletions(-) diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index 4e2c7312d..0b493fb05 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -1,39 +1,65 @@ # Resource Provider plugins +_This document should be read in conjunction with [SERVERPLUGINS.md](./SERVERPLUGINS.md) as it contains additional information regarding the development of plugins that facilitate the storage and retrieval of resource data._ + +--- + ## Overview -This document should be read in conjunction with [SERVERPLUGINS.md](./SERVERPLUGINS.md) as it contains additional information regarding the development of plugins that facilitate the storage and retrieval of resource data (e.g. routes, waypoints, notes, regions and charts). +The SignalK specification defines the path `/signalk/v1/api/resources` for accessing resources to aid in navigation and operation of the vessel. -Resource storage and retrieval is de-coupled from core server function to provide the flexibility to implement the appropriate resource storage solution for specific Signal K implementations. +It also defines the schema for the following __Common__ resource types: +- routes +- waypoints +- notes +- regions +- charts -The Signal K Node server will pass requests made to the following paths to registered resource providers: -- `/signalk/v1/api/resources` -- `/signalk/v1/api/resources/routes` -- `/signalk/v1/api/resources/waypoints` -- `/signalk/v1/api/resources/notes` -- `/signalk/v1/api/resources/regions` -- `/signalk/v1/api/resources/charts` +each of with its own path under the root `resources` path _(e.g. `/signalk/v1/api/resources/routes`)_. -Resource providers will receive request data via a `ResourceProvider` interface which they implement. It is the responsibility of the resource provider to persist resource data in storage (PUT, POST), retrieve the requested resources (GET) and remove resource entries (DELETE). +It should also be noted that the `/signalk/v1/api/resources` path can also host other types of resource data which can be grouped within a __Custom__ path name _(e.g. `/signalk/v1/api/resources/fishingZones`)_. -Resource data passed to the resource provider plugin has been validated by the server and can be considered ready for storage. +The SignalK server does not natively provide the ability to store or retrieve resource data for either __Common__ and __Custom__ resource types. +This functionality needs to be provided by one or more server plugins that handle the data for specific resource types. +These plugins are called __Resource Providers__. -## Resource Providers +This de-coupling of resource request handling and storage / retrieval provides great flexibility to ensure that an appropriate resource storage solution can be configured for your SignalK implementation. -A `resource provider plugin` is responsible for the storage and retrieval of resource data. +It is important to understand that the SignalK server handles requests for __Custom__ resource types differently to requests for __Common__ types, please ensure you refer to the relevant section of this document. -It should implement the necessary functions to: -- Persist each resource with its associated id -- Retrieve an individual resource with the supplied id -- Retrieve a list of resources that match the supplied qery criteria. +--- -Data is passed to and from the plugin via the methods defined in the __resourceProvider__ interface which the plugin must implement. +## Common Resource Type Provider: -_Definition: `resourceProvider` interface._ -```javascript -resourceProvider: { - types: [], +As detailed earlier in this document, the __Common__ resource types are: +`routes`, `waypoints`, `notes`, `regions` & `charts`. + +For the `/signalk/v1/api/resources` path and the __Common__ resource type sub-paths, the Signal K server will pre-process the request before passing on the request details on to the registered resource provider. + +The SignalK server performs the following tasks when pre-processing a request: +- Checks for a registered provider for the resource type +- Checks the validity of the supplied resource id +- For requests to store data, the submitted resource data is validated. + +Only when all pre-processing tasks have completed successfully will the request be passed on to the resource provider plugin. + +Resource providers for __Common__ resource types need to implement the `ResourceProvider` interface which registers with the SignalK server the: +- Resource types provided for by the plugin +- Methods to use when requests are passed to the plugin. It is these methods that implement the saving / deletion of resource data to storage and the retrieval of requested resources. + + +### Resource Provider Interface + +--- +The `ResourceProvider` interface defines the contract between the the Resource Provider plugin and the SignalK server and has the following definition _(which it and other related types can be imported from `@signalk/server-api`)_: + +```typescript +import { SignalKResourceType } from '@signalk/server-api' +// SignalKResourceType= 'routes' | 'waypoints' |'notes' |'regions' |'charts' + +interface ResourceProvider: { + types: SignalKResourceType[], methods: { listResources: (type:string, query: {[key:string]:any})=> Promise getResource: (type:string, id:string)=> Promise @@ -42,41 +68,189 @@ resourceProvider: { } } ``` +where: + +- `types`: An array containing a list of __Common__ resource types provided for by the plugin +- `methods`: An object containing the methods resource requests are passed to by the SignalK server. The plugin __MUST__ implement each method, even if that operation is not supported by the plugin! + +#### __Method Details:__ + +--- +__`listResources(type, query)`__: This method is called when a request is made for resource entries of a specific resource type that match a specifiec criteria. + +_Note: It is the responsibility of the resource provider plugin to filter the resources returned as per the supplied query parameters._ + +`type:` Array of __Common__ resource types provied for by the plugin _e.g. ['routes','waypoints']_ + +`query:` Object contining `key | value` pairs repesenting the parameters by which to filter the returned entries _e.g. {distance,'50000}_ + +`returns:` +- Resolved Promise containing a list of resource entries on completion. +- Rejected Promise containing an Error if incomplete or not implemented. + + +_Example resource request:_ +``` +GET /signalk/v1/api/resources/waypoints?bbox=5.4,25.7,6.9,31.2&distance=30000 +``` +_ResourceProvider method invocation:_ + +```javascript +listResources( + 'waypoints', + { + bbox: '5.4,25.7,6.9,31.2', + distance: 30000 + } +); +``` + +--- +__`getResource(type, id)`__: This method is called when a request is made for a specific resource entry of the supplied resource type and id. + +`type:` String containing one of the __Common__ resource types _e.g. 'waypoints'_ + +`id:` String containing the target resource entry id _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ + +`returns:` +- Resolved Promise containing the resource entry on completion. +- Rejected Promise containing an Error if incomplete or not implemented. + +_Example resource request:_ +``` +GET /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99 +``` +_ResourceProvider method invocation:_ -This interface is used by the server to direct requests to the plugin. +```javascript +getResource( + 'routes', + 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99' +); +``` + +--- +__`setResource(type, id, value)`__: This method is called when a request is made to save / update a resource entry of the specified resource type, with the supplied id and data. + +`type:` Array of __Common__ resource types provied for by the plugin _e.g. ['routes','waypoints']_ + +`id:` String containing the target resource entry id _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ + +`value:` Resource data to be stored. + +`returns:` +- Resolved Promise containing a list of resource entries on completion. +- Rejected Promise containing an Error if incomplete or not implemented. + +_Example PUT resource request:_ +``` +PUT /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99 {resource_data} +``` +_ResourceProvider method invocation:_ + +```javascript +setResource( + 'routes', + 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99', + {} +); +``` + +_Example POST resource request:_ +``` +POST /signalk/v1/api/resources/routes {resource_data} +``` +_ResourceProvider method invocation:_ -It contains the following attributes: -- `types`: An array containing the names of the resource types the plugin is a provider for. Names of the resource types are: `routes, waypoints, notes, regions, charts`. +```javascript +setResource( + 'routes', + '', + {} +); +``` + +--- +__`deleteResource(type, id)`__: This method is called when a request is made to remove the specific resource entry of the supplied resource type and id. + +`type:` String containing one of the __Common__ resource types _e.g. 'waypoints'_ + +`id:` String containing the target resource entry id _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ + +`returns:` +- Resolved Promise on completion. +- Rejected Promise containing an Error if incomplete or not implemented. + +_Example resource request:_ +``` +DELETE /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99 +``` +_ResourceProvider method invocation:_ + +```javascript +deleteResource( + 'routes', + 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99' +); +``` -- `methods`: The methods to which the server dispatches requests. The plugin will implement these methods to perform the necessary save or retrieval operation. Each method returns a promise containing either resource data or `null` if an error is encountered. +--- + +### Example: -_Example: Plugin acting as resource provider for routes & waypoints._ +_Defintion for Resource Provider plugin providing for the retrieval routes & waypoints._ ```javascript +// SignalK server plugin module.exports = function (app) { + let plugin= { id: 'mypluginid', name: 'My Resource Providerplugin', + // ResourceProvider interface resourceProvider: { types: ['routes','waypoints'], methods: { listResources: (type, params)=> { - return Promise.resolve() { ... }; + return new Promise( (resolve, reject) => { + // fetch resource entries from storage + .... + if(ok) { // success + resolve({ + 'id1': { ... }, + 'id2': { ... }, + }); + } else { // error + reject(new Error('Error encountered!') + } + } }, - getResource: (type:string, id:string)=> { - return Promise.resolve() { ... }; - } , - setResource: (type:string, id:string, value:any)=> { - return Promise.resolve() { ... }; + getResource: (type, id)=> { + // fetch resource entries from storage + .... + if(ok) { // success + return Promise.resolve({ + ... + }); + } else { // error + reject(new Error('Error encountered!') + } }, - deleteResource: (type:string, id:string)=> { - return Promise.resolve() { ... }; ; + setResource: (type, id, value)=> { + // not implemented + return Promise.reject(new Error('NOT IMPLEMENTED!')); + }, + deleteResource: (type, id)=> { + // not implemented + return Promise.reject(new Error('NOT IMPLEMENTED!')); } } }, + start: (options)=> { ... app.resourceApi.register(this.id, this.resourceProvider); }, + stop: ()=> { app.resourceApi.unRegister(this.id); ... @@ -85,35 +259,37 @@ module.exports = function (app) { } ``` + +### Registering the Resource Provider: --- -## Plugin Startup - Registering the Resource Provider: +For the plugin to be registered by the SignalK server, it needs to call the server's `resourcesApi.register()` function during plugin startup. + +The server `resourcesApi.register()` function has the following signature: -To register your plugin as a resource provider the server's `resourcesApi.register()` function should be called within the plugin `start()` function passing the `resourceProvider` interface. +```typescript +app.resourcesApi.register(pluginId: string, resourceProvider: ResourceProvider) +``` +where: +- `pluginId`: is the plugin's id +- `resourceProvider`: is a reference to the plugins ResourceProvider interface. -This registers the resource types and the methods with the server so they are called when requests to resource paths are made. +_Note: A resource type can only have one registered plugin, so if more than one installed plugin attempts to register as a provider for the same resource type, only the first one is registered and it's implemented methods will service requests._ _Example:_ ```javascript module.exports = function (app) { + let plugin= { id: 'mypluginid', name: 'My Resource Providerplugin', resourceProvider: { types: ['routes','waypoints'], methods: { - listResources: (type, params)=> { - return Promise.resolve() { ... }; - }, - getResource: (type:string, id:string)=> { - return Promise.resolve() { ... }; - } , - setResource: (type:string, id:string, value:any)=> { - return Promise.resolve() { ... }; - }, - deleteResource: (type:string, id:string)=> { - return Promise.resolve() { ... }; ; - } + listResources: (type, params)=> { ... }, + getResource: (type:string, id:string)=> { ... } , + setResource: (type:string, id:string, value:any)=> { ... }, + deleteResource: (type:string, id:string)=> { ... } } } } @@ -124,15 +300,25 @@ module.exports = function (app) { } } ``` + +### Un-registering the Resource Provider: --- -## Plugin Stop - Un-registering the Resource Provider: +When a resource provider plugin is disabled, it should un-register as a provider to ensure resource requests are no longer directed to it by calling the SignalK server's `resourcesApi.unRegister()` function during shutdown. + +The server `resourcesApi.unRegister()` function has the following signature: + +```typescript +app.resourcesApi.unRegister(pluginId: string) +``` +where: +- `pluginId`: is the plugin's id -When a resource provider plugin is disabled it should un-register as a provider so resource requests are not directed to it. This is done by calling the server's `resourcesApi.unRegister()` function passing the `plugin.id` within the plugin's `stop()` function. _Example:_ ```javascript module.exports = function (app) { + let plugin= { id: 'mypluginid', name: 'My Resource Providerplugin', @@ -150,150 +336,111 @@ module.exports = function (app) { ``` --- -## Operation: - -The Server will dispatch requests made to: -- `/signalk/v1/api/resources/` - -OR -- the `resources API` endpoints +## Custom Resource Type Provider: -to the plugin's `resourceProvider.methods` for each of the resource types listed in `resourceProvider.types`. +Custom resource types are collections of resource entries residing within a sub-path with a user defined name under `/signalk/v1/api/resources`. -Each method defined in `resourceProvider.methods` must have a signature as specified in the __resourceProvider interface__. Each method returns a `Promise` containing the resultant resource data or `null` if an error occurrred or the operation is incomplete. +_Example:_ +``` +/signalk/v1/api/resources/fishingZones +``` +_Note: A custom resource path name __CANNOT__ be one of the __Common__ resource types i.e. `routes`, `waypoints`, `notes`, `regions` or `charts`.__ -### __List Resources:__ +Unlike the __Common Resource Type Providers__: +- The plugin __DOES NOT__ implement the `ResourceProvider` interface +- Requests to __Custom__ resource type sub-paths and any submitted data __WILL NOT__ be pre-processed or validated by the Signal K server +- The plugin __WILL__ need to implement a route handler for the necessary path(s) +- The plugin __WILL__ need to implement any necessary data validation. -`GET` requests that are not for a specific resource will be dispatched to the `listResources` method passing the resource type and any query data as parameters. -Query parameters are passed as an object conatining `key | value` pairs. +### Router Path Handlers +--- -_Example: GET /signalk/v1/api/resources/waypoints?bbox=5.4,25.7,6.9,31.2&distance=30000_ -```javascript -query= { - bbox: '5.4,25.7,6.9,31.2', - distance: 30000 -} -``` +To set up a router path handler, you use the reference to the SignalK server `app` passed to the plugin to register the paths for which requests will be passed to the plugin. -It is the responsibility of the resource provider plugin to filter the resources returned as per the supplied query parameters. +This should be done during plugin startup within the plugin `start()` function. -`listResources()` returns a Promise containing a JSON object listing resources by id. +_Note: To ensure that your resource path is listed when a request to `/signalk/v1/api/resources` is made ensure you use the full path as outlined in the example below._ -_Example: List all routes._ +_Example:_ ```javascript -GET /signalk/v1/api/resources/routes -listResources('routes', {}) - -returns Promise<{ - "resource_id1": { ... }, - "resource_id2": { ... }, - ... - "resource_idn": { ... } -}> -``` - -_Example: List waypoints within the bounded area._ -```typescript -GET /signalk/v1/api/resources/waypoints?bbox=5.4,25.7,6.9,31.2 +module.exports = function (app) { -listResources('waypoints', {bbox: '5.4,25.7,6.9,31.2'}) + let plugin= { + id: 'mypluginid', + name: 'My Resource Providerplugin', + start: (options) => { + // setup router path handlers + initPathHandlers(app); + ... + } + } + + function initPathHandlers(app) { + app.get( + `/signalk/v1/api/resources/myResType`, + (request, response)=> { + // retrieve resource(s) + let result= getMyResources(); + response.status(200).json(result); + } + ); + app.post( + `/signalk/v1/api/resources/myResType`, + (request, response)=> { + // create new resource + ... + } + ); + router.put( + `/signalk/v1/api/resources/myResType/:id`, + (request, response)=> { + // create / update resource with supplied id + ... + } + ); + router.delete( + `/signalk/v1/api/resources/myResType/:id`, + (request, response)=> { + // delete the resource with supplied id + ... + } + ); + } -returns Promise<{ - "resource_id1": { ... }, - "resource_id2": { ... } -}> ``` -### __Retrieve a specific resource:__ +Once registered requests to `/signalk/v1/api/resources/myResType` will be passed to the respective handler based on the request type _(i.e. GET, PUT, POST, DELETE)_ and the request details are available in the `request` parameter. -`GET` requests for a specific resource will be dispatched to the `getResource` method passing the resource type and id as parameters. +For more information regarding the `request` and `response` parameters see the [Express Routing](https://expressjs.com/en/guide/routing.html) documentation. -`getResource()` returns a Promise containing a JSON object with the resource data. +### Data Validation +--- -_Example: Retrieve route._ -```typescript -GET /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a +When providing for resource data to be persisted to storage (PUT, POST) it is recommended that data validation implemented within the `app.put()` and `app.post()` route handlers and that the appropriate `status` is returned. -getResource( - 'routes', - 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', - {} -) -``` - -_Returns a Promise containing the resource data:_ -```typescript -Promise<{ - "name": "Name of the route", - "description": "Description of the route", - "distance": 18345, - "feature": { ... } -}> -``` - -A request for a resource attribute will pass the attibute path as an array in the query object with the key `resAttrib`. - -_Example: Get waypoint geometry._ +_Example:_ ```javascript -GET /signalk/v1/api/resources/waypoints/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a/feature/geometry - -getResource( - 'waypoints', - 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', - { resAttrib: ['feature','geometry'] } +app.post( + `/signalk/v1/api/resources/myResType`, + (request, response)=> { + // validate submitted data + let ok= validate(request.body); + if (ok) { //valid data + if (saveResource(request.body)) { + response.status(200).send('OK'); + } else { + response.status(404).send('ERROR svaing resource!'); + } + } else { + response.status(406).send('ERROR: Invalid data!'); + } + } ) -``` -_Returns a Promise containing the value of `geometry` attribute of the waypoint._ -```typescript -Promise<{ - "type": "Point", - "coordinates": [70.4,6.45] -}> -``` - -### __Saving Resources:__ - -`PUT` requests to a path containing the resource id are used to store data associated with the resource id. These will be dispatched to the `setResource` method passing the resource type, id and data as parameters. -`setResource() ` returns Promise on success and Promise on failure. - -_Example: Update / add waypoint with the supplied id._ -```javascript -PUT /signalk/v1/api/resources/waypoints/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a {} - -setResource('waypoints', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) - -returns Promise ``` +--- -`POST` requests to a resource path that do not contina the resource id will be dispatched to the `setResource` method passing the resource type, an id (generated by the server) and resource data as parameters. - -`setResource()` returns `true` on success and `null` on failure. - -_Example: New route record._ -```typescript -POST /signalk/v1/api/resources/routes {} - -setResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) - -returns Promise -``` - -### __Deleting Resources:__ - -`DELETE` requests to a path containing the resource id will be dispatched to the `deleteResource` method passing the resource type and id as parameters. - -`deleteResource()` returns `true` on success and `null` on failure. - -_Example: Delete region with supplied id._ -```typescript -DELETE /signalk/v1/api/resources/regions/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a - -deleteResource('regions', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a') - -returns Promise -``` diff --git a/SERVERPLUGINS.md b/SERVERPLUGINS.md index 8fbcb6737..bad366eb2 100644 --- a/SERVERPLUGINS.md +++ b/SERVERPLUGINS.md @@ -22,9 +22,9 @@ The plugin module must export a single `function(app)` that must return an objec ## Getting Started with Plugin Development -To get started with SignalK plugin development, you can follow the following guide. +To get started with SignalK plugin development, you can follow this guide. -_Note: For plugins acting as a provider for one or more of the resource types listed in the Signal K specification (e.g. routes, waypoints, notes, regions or charts) refer to __[RESOURCE_PROVIDER_PLUGINS.md](./RESOURCE_PROVIDER_PLUGINS.md)__ for details. +_Note: For plugins acting as a provider for one or more of the SignalK resource types listed in the specification (`routes`, `waypoints`, `notes`, `regions` or `charts`) please refer to __[RESOURCE_PROVIDER_PLUGINS.md](./RESOURCE_PROVIDER_PLUGINS.md)__ for additional details._ ### Project setup @@ -702,29 +702,30 @@ app.registerDeltaInputHandler((delta, next) => { ### `app.resourcesApi.getResource(resource_type, resource_id)` -Retrieve resource data for the supplied resource type and id. +Retrieve resource data for the supplied SignalK resource type and resource id. -This method invokes the `registered Resource Provider` for the supplied `resource_type` and returns a __resovled Promise__ containing the route resource data if successful or -a __rejected Promise__ containing an Error object if unsuccessful. - - data for the full path of the directory where the plugin can persist its internal data, like data files.If a plugin wants to act as a resource provider, it will need to register its provider methods during startup using this function. +_Valid resource types are `routes`, `waypoints`, `notes`, `regions` & `charts`._ +This method invokes the `registered Resource Provider` for the supplied `resource_type` and returns a `resovled` __Promise__ containing the resource data if successful or +a `rejected` __Promise__ containing an __Error__ object if unsuccessful. +_Example:_ ```javascript -let myRoute= app.resourcesApi.getResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a'); -``` -_Returns resolved Promise containing:_ -```json -{ - "name": "Name of the route", - "description": "Description of the route", - "distance": 18345, - "feature": { ... } -} +let resource= app.resourcesApi.getResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a'); +resource.then ( (data)=> { + // route data + console.log(data); + ... +}).catch (error) { + // handle error + console.log(error.message); + ... +} ``` + ### `app.resourcesApi.register(pluginId, resourceProvider)` If a plugin wants to act as a resource provider, it will need to register its provider methods during startup using this function. @@ -742,7 +743,7 @@ module.exports = function (app) { methods: { ... } } start: function(options) { - ... + // do plugin start up app.resourcesApi.register(this.id, this.resourceProvider); } ... @@ -769,7 +770,7 @@ module.exports = function (app) { ... stop: function(options) { app.resourcesApi.unRegister(this.id); - ... + // do plugin shutdown } } } From e1c3a45bb8e849b0d1815d5aa20dc77b0a5600a5 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 24 Nov 2021 17:22:29 +1030 Subject: [PATCH 037/410] add chartId test & require alignment with spec. --- src/api/resources/index.ts | 50 +++++++++++++++++++++++------------ src/api/resources/validate.ts | 28 ++++++++++++++++---- 2 files changed, 56 insertions(+), 22 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 09a40f422..ea888053c 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -118,11 +118,10 @@ export class Resources { const retVal = await this.resProvider[ req.params.resourceType ]?.getResource(req.params.resourceType, req.params.resourceId) - res.json(retVal) + res.json(retVal) } catch (err) { res.status(404).send(`Resource not found! (${req.params.resourceId})`) } - } ) @@ -142,7 +141,7 @@ export class Resources { const retVal = await this.resProvider[ req.params.resourceType ]?.listResources(req.params.resourceType, req.query) - res.json(retVal) + res.json(retVal) } catch (err) { res.status(404).send(`Error retrieving resources!`) } @@ -165,12 +164,23 @@ export class Resources { res.status(406).send(`Invalid resource data supplied!`) return } - const id = UUID_PREFIX + uuidv4() + if (!validate.resource(req.params.resourceType, req.body)) { + res.status(406).send(`Invalid resource data supplied!`) + return + } + + let id: string + if (req.params.resourceType === 'charts') { + id = req.body.identifier + } else { + id = UUID_PREFIX + uuidv4() + } + try { const retVal = await this.resProvider[ req.params.resourceType ]?.setResource(req.params.resourceType, id, req.body) - + this.server.handleMessage( this.resProvider[req.params.resourceType]?.pluginId as string, this.buildDeltaMsg( @@ -186,7 +196,7 @@ export class Resources { res .status(404) .send(`Error saving ${req.params.resourceType} resource (${id})!`) - } + } } ) @@ -202,14 +212,20 @@ export class Resources { next() return } - if (req.params.resourceType !== 'charts') { - if(!validate.uuid(req.params.resourceId)) { - res - .status(406) - .send(`Invalid resource id provided (${req.params.resourceId})`) - return - } + + let isValidId: boolean + if (req.params.resourceType === 'charts') { + isValidId = validate.chartId(req.params.resourceId) + } else { + isValidId = validate.uuid(req.params.resourceId) + } + if (isValidId) { + res + .status(406) + .send(`Invalid resource id provided (${req.params.resourceId})`) + return } + if (!validate.resource(req.params.resourceType, req.body)) { res.status(406).send(`Invalid resource data supplied!`) return @@ -222,7 +238,7 @@ export class Resources { req.params.resourceId, req.body ) - + this.server.handleMessage( this.resProvider[req.params.resourceType]?.pluginId as string, this.buildDeltaMsg( @@ -236,7 +252,7 @@ export class Resources { .send( `${req.params.resourceType} resource (${req.params.resourceId}) saved.` ) - } catch (err) { + } catch (err) { res .status(404) .send( @@ -260,12 +276,12 @@ export class Resources { next() return } - + try { const retVal = await this.resProvider[ req.params.resourceType ]?.deleteResource(req.params.resourceType, req.params.resourceId) - + this.server.handleMessage( this.resProvider[req.params.resourceType]?.pluginId as string, this.buildDeltaMsg( diff --git a/src/api/resources/validate.ts b/src/api/resources/validate.ts index 0d8d6cffe..16eb3c9a8 100644 --- a/src/api/resources/validate.ts +++ b/src/api/resources/validate.ts @@ -19,21 +19,29 @@ export const validate = { case 'regions': return validateRegion(value) break + case 'charts': + return validateChart(value) + break default: return true } }, - // ** returns true if id is a valid Signal K UUID ** + // returns true if id is a valid Signal K UUID uuid: (id: string): boolean => { const uuid = RegExp( '^urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$' ) return uuid.test(id) + }, + + // returns true if id is a valid Signal K Chart resource id + chartId: (id: string): boolean => { + const uuid = RegExp('(^[A-Za-z0-9_-]{8,}$)') + return uuid.test(id) } } -// ** validate route data const validateRoute = (r: any): boolean => { if (r.start) { const l = r.start.split('/') @@ -60,7 +68,6 @@ const validateRoute = (r: any): boolean => { return true } -// ** validate waypoint data const validateWaypoint = (r: any): boolean => { if (typeof r.position === 'undefined') { return false @@ -81,7 +88,7 @@ const validateWaypoint = (r: any): boolean => { return true } -// ** validate note data +// validate note data const validateNote = (r: any): boolean => { if (!r.region && !r.position && !r.geohash) { return false @@ -100,7 +107,6 @@ const validateNote = (r: any): boolean => { return true } -// ** validate region data const validateRegion = (r: any): boolean => { if (!r.geohash && !r.feature) { return false @@ -122,3 +128,15 @@ const validateRegion = (r: any): boolean => { } return true } + +const validateChart = (r: any): boolean => { + if (!r.name || !r.identifier || !r.chartFormat) { + return false + } + + if (!r.tilemapUrl && !r.chartUrl) { + return false + } + + return true +} From 69335f85ad512b8802e6c0335bcf26d03c336f25 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 24 Nov 2021 17:44:05 +1030 Subject: [PATCH 038/410] add charts API methods --- src/api/resources/openApi.json | 474 ++++++++++++++++++++++++++++++++- 1 file changed, 473 insertions(+), 1 deletion(-) diff --git a/src/api/resources/openApi.json b/src/api/resources/openApi.json index 8eddcac32..80350d21f 100644 --- a/src/api/resources/openApi.json +++ b/src/api/resources/openApi.json @@ -787,7 +787,7 @@ "/resources/regions/": { "post": { "tags": ["resources/regions"], - "summary": "Add a new Regkion", + "summary": "Add a new Region", "requestBody": { "description": "Region details", "required": true, @@ -1056,6 +1056,308 @@ }, + "/resources/charts/": { + "post": { + "tags": ["resources/charts"], + "summary": "Add a new Chart", + "requestBody": { + "description": "Chart details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Chart resource", + "required": ["feature"], + "properties": { + "name": { + "description": "Chart common name", + "example":"NZ615 Marlborough Sounds", + "type": "string" + }, + "identifier": { + "type": "string", + "description": "Chart number", + "example":"NZ615" + }, + "description": { + "type": "string", + "description": "A description of the chart" + }, + "tilemapUrl": { + "type": "string", + "description": "A url to the tilemap of the chart for use in TMS chartplotting apps", + "example":"http://{server}:8080/mapcache/NZ615" + }, + "region": { + "type": "string", + "description": "Region related to note. A pointer to a region UUID. Alternative to geohash" + }, + "geohash": { + "description": "Position related to chart. Alternative to region", + "type": "string" + }, + "chartUrl": { + "type": "string", + "description": "A url to the chart file's storage location", + "example":"file:///home/pi/freeboard/mapcache/NZ615" + }, + "scale": { + "type": "integer", + "description": "The scale of the chart, the larger number from 1:200000" + }, + "chartLayers": { + "type": "array", + "description": "If the chart format is WMS, the layers enabled for the chart.", + "items": { + "type": "string", + "description": "Identifier for the layer." + } + }, + "bounds": { + "type": "array", + "description": "The bounds of the chart. An array containing the position of the upper left corner, and the lower right corner. Useful when the chart isn't inherently geo-referenced.", + "items": { + "description": "Position of a corner of the chart", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + }, + "altitude": { + "type": "number", + "format": "float" + } + } + } + }, + "chartFormat": { + "type": "string", + "description": "The format of the chart", + "enum": [ + "gif", + "geotiff", + "kap", + "png", + "jpg", + "kml", + "wkt", + "topojson", + "geojson", + "gpx", + "tms", + "wms", + "S-57", + "S-63", + "svg", + "other" + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + + "/resources/charts/{id}": { + "parameters": { + "name": "id", + "in": "path", + "description": "Chart id", + "required": true, + "schema": { + "type": "string", + "pattern": "(^[A-Za-z0-9_-]{8,}$)" + } + }, + + "get": { + "tags": ["resources/charts"], + "summary": "Retrieve Chart with supplied id", + "responses": { + "200": { + "description": "List of resources identified by their UUID", + "content": { + "application/json": { + "schema": { + "description": "List of Signal K resources", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + } + }, + + "put": { + "tags": ["resources/charts"], + "summary": "Add / update a new Chart with supplied id", + "requestBody": { + "description": "Chart details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Chart resource", + "required": ["feature"], + "properties": { + "name": { + "description": "Chart common name", + "example":"NZ615 Marlborough Sounds", + "type": "string" + }, + "identifier": { + "type": "string", + "description": "Chart number", + "example":"NZ615" + }, + "description": { + "type": "string", + "description": "A description of the chart" + }, + "tilemapUrl": { + "type": "string", + "description": "A url to the tilemap of the chart for use in TMS chartplotting apps", + "example":"http://{server}:8080/mapcache/NZ615" + }, + "region": { + "type": "string", + "description": "Region related to note. A pointer to a region UUID. Alternative to geohash" + }, + "geohash": { + "description": "Position related to chart. Alternative to region", + "type": "string" + }, + "chartUrl": { + "type": "string", + "description": "A url to the chart file's storage location", + "example":"file:///home/pi/freeboard/mapcache/NZ615" + }, + "scale": { + "type": "integer", + "description": "The scale of the chart, the larger number from 1:200000" + }, + "chartLayers": { + "type": "array", + "description": "If the chart format is WMS, the layers enabled for the chart.", + "items": { + "type": "string", + "description": "Identifier for the layer." + } + }, + "bounds": { + "type": "array", + "description": "The bounds of the chart. An array containing the position of the upper left corner, and the lower right corner. Useful when the chart isn't inherently geo-referenced.", + "items": { + "description": "Position of a corner of the chart", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + }, + "altitude": { + "type": "number", + "format": "float" + } + } + } + }, + "chartFormat": { + "type": "string", + "description": "The format of the chart", + "enum": [ + "gif", + "geotiff", + "kap", + "png", + "jpg", + "kml", + "wkt", + "topojson", + "geojson", + "gpx", + "tms", + "wms", + "S-57", + "S-63", + "svg", + "other" + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + }, + + "delete": { + "tags": ["resources/charts"], + "summary": "Remove Chart with supplied id", + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + + }, + "/resources/setWaypoint": { "put": { "tags": ["resources/api"], @@ -1514,6 +1816,176 @@ } } } + }, + + "/resources/setChart": { + "put": { + "tags": ["resources/api"], + "summary": "Add / update a Chart", + "requestBody": { + "description": "Chart attributes", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Chart resource", + "required": ["identifier","tilemapUrl","chartUrl","chartFormat"], + "properties": { + "name": { + "description": "Chart common name", + "example":"NZ615 Marlborough Sounds", + "type": "string" + }, + "identifier": { + "type": "string", + "description": "Chart number", + "example":"NZ615" + }, + "description": { + "type": "string", + "description": "A description of the chart" + }, + "oneOf": [ + { + "tilemapUrl": { + "type": "string", + "description": "A url to the tilemap of the chart for use in TMS chartplotting apps", + "example":"http://{server}:8080/mapcache/NZ615" + } + }, + { + "chartUrl": { + "type": "string", + "description": "A url to the chart file's storage location", + "example":"file:///home/pi/freeboard/mapcache/NZ615" + } + } + ], + "region": { + "type": "string", + "description": "Region related to note. A pointer to a region UUID. Alternative to geohash" + }, + "geohash": { + "description": "Position related to chart. Alternative to region", + "type": "string" + }, + "scale": { + "type": "integer", + "description": "The scale of the chart, the larger number from 1:200000" + }, + "chartLayers": { + "type": "array", + "description": "If the chart format is WMS, the layers enabled for the chart.", + "items": { + "type": "string", + "description": "Identifier for the layer." + } + }, + "bounds": { + "type": "array", + "description": "The bounds of the chart. An array containing the position of the upper left corner, and the lower right corner. Useful when the chart isn't inherently geo-referenced.", + "items": { + "description": "Position of a corner of the chart", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + }, + "altitude": { + "type": "number", + "format": "float" + } + } + } + }, + "chartFormat": { + "type": "string", + "description": "The format of the chart", + "enum": [ + "gif", + "geotiff", + "kap", + "png", + "jpg", + "kml", + "wkt", + "topojson", + "geojson", + "gpx", + "tms", + "wms", + "S-57", + "S-63", + "svg", + "other" + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + + "/resources/deleteChart": { + "put": { + "tags": ["resources/api"], + "summary": "Remove a Chart", + "requestBody": { + "description": "Chart identifier", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["id"], + "properties": { + "id": { + "type": "string", + "pattern": "(^[A-Za-z0-9_-]{8,}$)", + "description": "Chart identifier" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } } } From 8fa38202240114ad6802591e127603185fa3e229 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Thu, 25 Nov 2021 13:21:23 +1030 Subject: [PATCH 039/410] allow registering custom resource types --- src/api/resources/index.ts | 95 ++++++++++++++++++++------------------ 1 file changed, 50 insertions(+), 45 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index ea888053c..a3e32cb19 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -34,8 +34,7 @@ export class Resources { private resProvider: { [key: string]: ResourceProviderMethods | null } = {} private server: ResourceApplication - // in-scope resource types - private resourceTypes: SignalKResourceType[] = [ + private signalkResTypes: SignalKResourceType[] = [ 'routes', 'waypoints', 'notes', @@ -160,13 +159,12 @@ export class Resources { next() return } - if (!validate.resource(req.params.resourceType, req.body)) { - res.status(406).send(`Invalid resource data supplied!`) - return - } - if (!validate.resource(req.params.resourceType, req.body)) { - res.status(406).send(`Invalid resource data supplied!`) - return + + if (this.signalkResTypes.includes(req.params.resourceType as SignalKResourceType)) { + if (!validate.resource(req.params.resourceType, req.body)) { + res.status(406).send(`Invalid resource data supplied!`) + return + } } let id: string @@ -213,23 +211,26 @@ export class Resources { return } - let isValidId: boolean - if (req.params.resourceType === 'charts') { - isValidId = validate.chartId(req.params.resourceId) - } else { - isValidId = validate.uuid(req.params.resourceId) - } - if (isValidId) { - res - .status(406) - .send(`Invalid resource id provided (${req.params.resourceId})`) - return - } + if (this.signalkResTypes.includes(req.params.resourceType as SignalKResourceType)) { + let isValidId: boolean + if (req.params.resourceType === 'charts') { + isValidId = validate.chartId(req.params.resourceId) + } else { + isValidId = validate.uuid(req.params.resourceId) + } + if (isValidId) { + res + .status(406) + .send(`Invalid resource id provided (${req.params.resourceId})`) + return + } - if (!validate.resource(req.params.resourceType, req.body)) { - res.status(406).send(`Invalid resource data supplied!`) - return + if (!validate.resource(req.params.resourceType, req.body)) { + res.status(406).send(`Invalid resource data supplied!`) + return + } } + try { const retVal = await this.resProvider[ req.params.resourceType @@ -379,31 +380,35 @@ export class Resources { private getResourcePaths(): { [key: string]: any } { const resPaths: { [key: string]: any } = {} - Object.entries(this.resProvider).forEach((p: any) => { - if (p[1]) { - resPaths[p[0]] = `Path containing ${p[0]}, each named with a UUID` - } - }) - // check for other plugins servicing paths under ./resources - this.server._router.stack.forEach((i: any) => { - if (i.route && i.route.path && typeof i.route.path === 'string') { - if (i.route.path.indexOf(`${SIGNALK_API_PATH}/resources`) !== -1) { - const r = i.route.path.split('/') - if (r.length > 5 && !(r[5] in resPaths)) { - resPaths[ - r[5] - ] = `Path containing ${r[5]} resources (provided by plug-in)` - } - } - } - }) + for( let i in this.resProvider) { + resPaths[i] = `Path containing ${i.slice(-1)==='s' ? i.slice(0, i.length-1) : i} resources (provided by ${this.resProvider[i]?.pluginId})` + } return resPaths } private checkForProvider(resType: SignalKResourceType): boolean { - return this.resourceTypes.includes(resType) && this.resProvider[resType] - ? true - : false + debug(`** checkForProvider(${resType})`) + debug(this.resProvider[resType]) + + if(this.resProvider[resType]) { + if( + !this.resProvider[resType]?.listResources || + !this.resProvider[resType]?.getResource || + !this.resProvider[resType]?.setResource || + !this.resProvider[resType]?.deleteResource || + typeof this.resProvider[resType]?.listResources !== 'function' || + typeof this.resProvider[resType]?.getResource !== 'function' || + typeof this.resProvider[resType]?.setResource !== 'function' || + typeof this.resProvider[resType]?.deleteResource !== 'function' ) + { + return false + } else { + return true + } + } + else { + return false + } } private buildDeltaMsg( From 6adb5899e323546d27f9734e048021042f495d21 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Thu, 25 Nov 2021 14:16:04 +1030 Subject: [PATCH 040/410] chore: update docs --- RESOURCE_PROVIDER_PLUGINS.md | 270 +++++++++++------------------------ 1 file changed, 81 insertions(+), 189 deletions(-) diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index 0b493fb05..eb31cd98f 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -26,27 +26,32 @@ These plugins are called __Resource Providers__. This de-coupling of resource request handling and storage / retrieval provides great flexibility to ensure that an appropriate resource storage solution can be configured for your SignalK implementation. -It is important to understand that the SignalK server handles requests for __Custom__ resource types differently to requests for __Common__ types, please ensure you refer to the relevant section of this document. +SignalK server handles requests for both __Common__ and __Custom__ resource types in a similar manner, the only difference being that it does not perform any validation on __Custom__ resource data, so a plugin can act a s a provider for both types. --- +## Server Operation: -## Common Resource Type Provider: +The Signal K server handles all requests to `/signalk/v1/api/resources` and all sub-paths, before passing on the request to the registered resource provider plugin. -As detailed earlier in this document, the __Common__ resource types are: -`routes`, `waypoints`, `notes`, `regions` & `charts`. +The following operations are performed by the server when a request is received: +- Checks for a registered provider for the resource type +- Checks that ResourceProvider methods are defined +- For __Common__ resource types, checks the validity of the: + - Resource id + - Submitted resource data. -For the `/signalk/v1/api/resources` path and the __Common__ resource type sub-paths, the Signal K server will pre-process the request before passing on the request details on to the registered resource provider. +Upon successful completion of these operations the request will then be passed to the registered resource provider plugin. -The SignalK server performs the following tasks when pre-processing a request: -- Checks for a registered provider for the resource type -- Checks the validity of the supplied resource id -- For requests to store data, the submitted resource data is validated. +--- +## Resource Provider plugin: -Only when all pre-processing tasks have completed successfully will the request be passed on to the resource provider plugin. +For a plugin to be considered a Resource Provider it needs to implement the `ResourceProvider` interface. -Resource providers for __Common__ resource types need to implement the `ResourceProvider` interface which registers with the SignalK server the: +By implementing this interface the plugin is able to register with the SignalK server the: - Resource types provided for by the plugin -- Methods to use when requests are passed to the plugin. It is these methods that implement the saving / deletion of resource data to storage and the retrieval of requested resources. +- Methods to used to action requests. + +It is these methods that perform the retrival, saving and deletion of resources from storage. ### Resource Provider Interface @@ -55,11 +60,8 @@ Resource providers for __Common__ resource types need to implement the `Resource The `ResourceProvider` interface defines the contract between the the Resource Provider plugin and the SignalK server and has the following definition _(which it and other related types can be imported from `@signalk/server-api`)_: ```typescript -import { SignalKResourceType } from '@signalk/server-api' -// SignalKResourceType= 'routes' | 'waypoints' |'notes' |'regions' |'charts' - interface ResourceProvider: { - types: SignalKResourceType[], + types: string[], methods: { listResources: (type:string, query: {[key:string]:any})=> Promise getResource: (type:string, id:string)=> Promise @@ -70,7 +72,7 @@ interface ResourceProvider: { ``` where: -- `types`: An array containing a list of __Common__ resource types provided for by the plugin +- `types`: An array containing a list of resource types provided for by the plugin. These can be a mixture of both __Common__ and __Custom__ resource types. - `methods`: An object containing the methods resource requests are passed to by the SignalK server. The plugin __MUST__ implement each method, even if that operation is not supported by the plugin! #### __Method Details:__ @@ -80,9 +82,9 @@ __`listResources(type, query)`__: This method is called when a request is made f _Note: It is the responsibility of the resource provider plugin to filter the resources returned as per the supplied query parameters._ -`type:` Array of __Common__ resource types provied for by the plugin _e.g. ['routes','waypoints']_ +`type:` String containing the type of resource to retrieve. -`query:` Object contining `key | value` pairs repesenting the parameters by which to filter the returned entries _e.g. {distance,'50000}_ +`query:` Object contining `key | value` pairs repesenting the parameters by which to filter the returned entries. _e.g. {distance,'50000}_ `returns:` - Resolved Promise containing a list of resource entries on completion. @@ -108,9 +110,9 @@ listResources( --- __`getResource(type, id)`__: This method is called when a request is made for a specific resource entry of the supplied resource type and id. -`type:` String containing one of the __Common__ resource types _e.g. 'waypoints'_ +`type:` String containing the type of resource to retrieve. -`id:` String containing the target resource entry id _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ +`id:` String containing the target resource entry id. _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ `returns:` - Resolved Promise containing the resource entry on completion. @@ -132,9 +134,9 @@ getResource( --- __`setResource(type, id, value)`__: This method is called when a request is made to save / update a resource entry of the specified resource type, with the supplied id and data. -`type:` Array of __Common__ resource types provied for by the plugin _e.g. ['routes','waypoints']_ +`type:` String containing the type of resource to store. -`id:` String containing the target resource entry id _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ +`id:` String containing the target resource entry id. _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ `value:` Resource data to be stored. @@ -173,9 +175,9 @@ setResource( --- __`deleteResource(type, id)`__: This method is called when a request is made to remove the specific resource entry of the supplied resource type and id. -`type:` String containing one of the __Common__ resource types _e.g. 'waypoints'_ +`type:` String containing the type of resource to delete. -`id:` String containing the target resource entry id _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ +`id:` String containing the target resource entry id. _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ `returns:` - Resolved Promise on completion. @@ -194,76 +196,10 @@ deleteResource( ); ``` +### Registering a Resource Provider: --- -### Example: - -_Defintion for Resource Provider plugin providing for the retrieval routes & waypoints._ -```javascript -// SignalK server plugin -module.exports = function (app) { - - let plugin= { - id: 'mypluginid', - name: 'My Resource Providerplugin', - // ResourceProvider interface - resourceProvider: { - types: ['routes','waypoints'], - methods: { - listResources: (type, params)=> { - return new Promise( (resolve, reject) => { - // fetch resource entries from storage - .... - if(ok) { // success - resolve({ - 'id1': { ... }, - 'id2': { ... }, - }); - } else { // error - reject(new Error('Error encountered!') - } - } - }, - getResource: (type, id)=> { - // fetch resource entries from storage - .... - if(ok) { // success - return Promise.resolve({ - ... - }); - } else { // error - reject(new Error('Error encountered!') - } - }, - setResource: (type, id, value)=> { - // not implemented - return Promise.reject(new Error('NOT IMPLEMENTED!')); - }, - deleteResource: (type, id)=> { - // not implemented - return Promise.reject(new Error('NOT IMPLEMENTED!')); - } - } - }, - - start: (options)=> { - ... - app.resourceApi.register(this.id, this.resourceProvider); - }, - - stop: ()=> { - app.resourceApi.unRegister(this.id); - ... - } - } -} -``` - - -### Registering the Resource Provider: ---- - -For the plugin to be registered by the SignalK server, it needs to call the server's `resourcesApi.register()` function during plugin startup. +To register the resource provider plugin with the SignalK server, the server's `resourcesApi.register()` function should be called during plugin startup. The server `resourcesApi.register()` function has the following signature: @@ -274,7 +210,7 @@ where: - `pluginId`: is the plugin's id - `resourceProvider`: is a reference to the plugins ResourceProvider interface. -_Note: A resource type can only have one registered plugin, so if more than one installed plugin attempts to register as a provider for the same resource type, only the first one is registered and it's implemented methods will service requests._ +_Note: A resource type can only have one registered plugin, so if more than one plugin attempts to register as a provider for the same resource type, the first plugin to call the `register()` function will be registered by the server for the resource types defined in the ResourceProvider interface!_ _Example:_ ```javascript @@ -304,7 +240,7 @@ module.exports = function (app) { ### Un-registering the Resource Provider: --- -When a resource provider plugin is disabled, it should un-register as a provider to ensure resource requests are no longer directed to it by calling the SignalK server's `resourcesApi.unRegister()` function during shutdown. +When a resource provider plugin is disabled, it should un-register itself to ensure resource requests are no longer directed to it by calling the SignalK server. This should be done by calling the server's `resourcesApi.unRegister()` function during shutdown. The server `resourcesApi.unRegister()` function has the following signature: @@ -334,113 +270,69 @@ module.exports = function (app) { } } ``` ---- -## Custom Resource Type Provider: - -Custom resource types are collections of resource entries residing within a sub-path with a user defined name under `/signalk/v1/api/resources`. - -_Example:_ -``` -/signalk/v1/api/resources/fishingZones -``` - -_Note: A custom resource path name __CANNOT__ be one of the __Common__ resource types i.e. `routes`, `waypoints`, `notes`, `regions` or `charts`.__ - -Unlike the __Common Resource Type Providers__: -- The plugin __DOES NOT__ implement the `ResourceProvider` interface -- Requests to __Custom__ resource type sub-paths and any submitted data __WILL NOT__ be pre-processed or validated by the Signal K server -- The plugin __WILL__ need to implement a route handler for the necessary path(s) -- The plugin __WILL__ need to implement any necessary data validation. - - -### Router Path Handlers ---- - -To set up a router path handler, you use the reference to the SignalK server `app` passed to the plugin to register the paths for which requests will be passed to the plugin. +--- -This should be done during plugin startup within the plugin `start()` function. +### __Example:__ -_Note: To ensure that your resource path is listed when a request to `/signalk/v1/api/resources` is made ensure you use the full path as outlined in the example below._ +Resource Provider plugin providing for the retrieval of routes & waypoints. -_Example:_ ```javascript - +// SignalK server plugin module.exports = function (app) { let plugin= { id: 'mypluginid', name: 'My Resource Providerplugin', - start: (options) => { - // setup router path handlers - initPathHandlers(app); - ... - } - } - - function initPathHandlers(app) { - app.get( - `/signalk/v1/api/resources/myResType`, - (request, response)=> { - // retrieve resource(s) - let result= getMyResources(); - response.status(200).json(result); - } - ); - app.post( - `/signalk/v1/api/resources/myResType`, - (request, response)=> { - // create new resource - ... - } - ); - router.put( - `/signalk/v1/api/resources/myResType/:id`, - (request, response)=> { - // create / update resource with supplied id - ... - } - ); - router.delete( - `/signalk/v1/api/resources/myResType/:id`, - (request, response)=> { - // delete the resource with supplied id - ... + // ResourceProvider interface + resourceProvider: { + types: ['routes','waypoints'], + methods: { + listResources: (type, params)=> { + return new Promise( (resolve, reject) => { + // fetch resource entries from storage + .... + if(ok) { // success + resolve({ + 'id1': { ... }, + 'id2': { ... }, + }); + } else { // error + reject(new Error('Error encountered!') + } + } + }, + getResource: (type, id)=> { + // fetch resource entries from storage + .... + if(ok) { // success + return Promise.resolve({ + ... + }); + } else { // error + reject(new Error('Error encountered!') + } + }, + setResource: (type, id, value)=> { + // not implemented + return Promise.reject(new Error('NOT IMPLEMENTED!')); + }, + deleteResource: (type, id)=> { + // not implemented + return Promise.reject(new Error('NOT IMPLEMENTED!')); + } } - ); - } - -``` - -Once registered requests to `/signalk/v1/api/resources/myResType` will be passed to the respective handler based on the request type _(i.e. GET, PUT, POST, DELETE)_ and the request details are available in the `request` parameter. - -For more information regarding the `request` and `response` parameters see the [Express Routing](https://expressjs.com/en/guide/routing.html) documentation. - -### Data Validation ---- + }, -When providing for resource data to be persisted to storage (PUT, POST) it is recommended that data validation implemented within the `app.put()` and `app.post()` route handlers and that the appropriate `status` is returned. + start: (options)=> { + ... + app.resourceApi.register(this.id, this.resourceProvider); + }, -_Example:_ -```javascript -app.post( - `/signalk/v1/api/resources/myResType`, - (request, response)=> { - // validate submitted data - let ok= validate(request.body); - if (ok) { //valid data - if (saveResource(request.body)) { - response.status(200).send('OK'); - } else { - response.status(404).send('ERROR svaing resource!'); - } - } else { - response.status(406).send('ERROR: Invalid data!'); + stop: ()=> { + app.resourceApi.unRegister(this.id); + ... } } -) - +} ``` ---- - - From 345fbf4cee37fa0b6c3928a799c34510f4b2d5ad Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Thu, 25 Nov 2021 14:34:17 +1030 Subject: [PATCH 041/410] chore: lint --- src/api/resources/index.ts | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index a3e32cb19..80b313962 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -160,7 +160,11 @@ export class Resources { return } - if (this.signalkResTypes.includes(req.params.resourceType as SignalKResourceType)) { + if ( + this.signalkResTypes.includes( + req.params.resourceType as SignalKResourceType + ) + ) { if (!validate.resource(req.params.resourceType, req.body)) { res.status(406).send(`Invalid resource data supplied!`) return @@ -172,7 +176,7 @@ export class Resources { id = req.body.identifier } else { id = UUID_PREFIX + uuidv4() - } + } try { const retVal = await this.resProvider[ @@ -211,7 +215,11 @@ export class Resources { return } - if (this.signalkResTypes.includes(req.params.resourceType as SignalKResourceType)) { + if ( + this.signalkResTypes.includes( + req.params.resourceType as SignalKResourceType + ) + ) { let isValidId: boolean if (req.params.resourceType === 'charts') { isValidId = validate.chartId(req.params.resourceId) @@ -380,8 +388,10 @@ export class Resources { private getResourcePaths(): { [key: string]: any } { const resPaths: { [key: string]: any } = {} - for( let i in this.resProvider) { - resPaths[i] = `Path containing ${i.slice(-1)==='s' ? i.slice(0, i.length-1) : i} resources (provided by ${this.resProvider[i]?.pluginId})` + for (const i in this.resProvider) { + resPaths[i] = `Path containing ${ + i.slice(-1) === 's' ? i.slice(0, i.length - 1) : i + } resources (provided by ${this.resProvider[i]?.pluginId})` } return resPaths } @@ -390,8 +400,8 @@ export class Resources { debug(`** checkForProvider(${resType})`) debug(this.resProvider[resType]) - if(this.resProvider[resType]) { - if( + if (this.resProvider[resType]) { + if ( !this.resProvider[resType]?.listResources || !this.resProvider[resType]?.getResource || !this.resProvider[resType]?.setResource || @@ -399,14 +409,13 @@ export class Resources { typeof this.resProvider[resType]?.listResources !== 'function' || typeof this.resProvider[resType]?.getResource !== 'function' || typeof this.resProvider[resType]?.setResource !== 'function' || - typeof this.resProvider[resType]?.deleteResource !== 'function' ) - { + typeof this.resProvider[resType]?.deleteResource !== 'function' + ) { return false } else { return true } - } - else { + } else { return false } } From 4267f27ed79f67108269dd6eb67e03346ae8a8c0 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 27 Nov 2021 16:06:13 +1030 Subject: [PATCH 042/410] update types --- packages/server-api/src/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/server-api/src/index.ts b/packages/server-api/src/index.ts index 2ccfc0bae..3f3b45925 100644 --- a/packages/server-api/src/index.ts +++ b/packages/server-api/src/index.ts @@ -5,9 +5,10 @@ export { PropertyValue, PropertyValues, PropertyValuesCallback } from './propert export type SignalKResourceType= 'routes' | 'waypoints' |'notes' |'regions' |'charts' +export type ResourceTypes= SignalKResourceType[] | string[] export interface ResourceProviderMethods { - pluginId: string + pluginId?: string listResources: (type: string, query: { [key: string]: any }) => Promise getResource: (type: string, id: string) => Promise setResource: ( @@ -19,7 +20,7 @@ export interface ResourceProviderMethods { } export interface ResourceProvider { - types: SignalKResourceType[] + types: ResourceTypes methods: ResourceProviderMethods } From 6a1031741a673d677a4bec7af0234bc2b893fa74 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 27 Nov 2021 16:08:14 +1030 Subject: [PATCH 043/410] align response formatting forGET ./resources/ --- src/api/resources/index.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 80b313962..52a23aaa6 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -389,9 +389,12 @@ export class Resources { private getResourcePaths(): { [key: string]: any } { const resPaths: { [key: string]: any } = {} for (const i in this.resProvider) { - resPaths[i] = `Path containing ${ - i.slice(-1) === 's' ? i.slice(0, i.length - 1) : i - } resources (provided by ${this.resProvider[i]?.pluginId})` + resPaths[i] = { + description: `Path containing ${ + i.slice(-1) === 's' ? i.slice(0, i.length - 1) : i + } resources`, + $source: this.resProvider[i]?.pluginId + } } return resPaths } From 79e6f0f6d5ce7537b9db331b170c5245dc40fe43 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 7 Nov 2021 15:28:54 +1030 Subject: [PATCH 044/410] Add Signal K standard resource path handling --- package.json | 1 + src/@types/geojson-validation.d.ts | 1 + src/api/resources/index.ts | 265 +++++++++++++++++++++++++++++ src/api/resources/validate.ts | 106 ++++++++++++ src/put.js | 5 + 5 files changed, 378 insertions(+) create mode 100644 src/@types/geojson-validation.d.ts create mode 100644 src/api/resources/index.ts create mode 100644 src/api/resources/validate.ts diff --git a/package.json b/package.json index 1eb9f574a..09f1d1ad9 100644 --- a/package.json +++ b/package.json @@ -96,6 +96,7 @@ "figlet": "^1.2.0", "file-timestamp-stream": "^2.1.2", "flatmap": "0.0.3", + "geojson-validation": "^1.0.2", "geolib": "3.2.2", "get-installed-path": "^4.0.8", "inquirer": "^7.0.0", diff --git a/src/@types/geojson-validation.d.ts b/src/@types/geojson-validation.d.ts new file mode 100644 index 000000000..a2e92c5c9 --- /dev/null +++ b/src/@types/geojson-validation.d.ts @@ -0,0 +1 @@ +declare module 'geojson-validation' diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts new file mode 100644 index 000000000..838f5b962 --- /dev/null +++ b/src/api/resources/index.ts @@ -0,0 +1,265 @@ +import Debug from 'debug' +import { v4 as uuidv4 } from 'uuid' +import { validate } from './validate' + +const debug = Debug('signalk:resources') + +interface ResourceRequest { + method: 'GET' | 'PUT' | 'POST' | 'DELETE' + body: any + query: {[key:string]: any} + resourceType: string + resourceId: string +} + +interface ResourceProvider { + listResources: (type:string, query: {[key:string]:any})=> Promise + getResource: (type:string, id:string)=> Promise + setResource: (type:string, id:string, value:{[key:string]:any})=> Promise + deleteResource: (type:string, id:string)=> Promise +} + +const SIGNALK_API_PATH= `/signalk/v1/api` +const UUID_PREFIX= 'urn:mrn:signalk:uuid:' + +export class Resources { + + // ** in-scope resource types ** + private resourceTypes:Array= [ + 'routes', + 'waypoints', + 'notes', + 'regions', + 'charts' + ] + + resProvider: {[key:string]: any}= {} + server: any + + public start(app:any) { + debug(`** Initialise ${SIGNALK_API_PATH}/resources path handler **`) + this.server= app + this.initResourceRoutes() + } + + public checkForProviders(rescan:boolean= false) { + if(rescan || Object.keys(this.resProvider).length==0) { + debug('** Checking for providers....') + this.resProvider= {} + this.resourceTypes.forEach( (rt:string)=> { + this.resProvider[rt]= this.getResourceProviderFor(rt) + }) + debug(this.resProvider) + } + } + + public getResource(type:string, id:string) { + debug(`** getResource(${type}, ${id})`) + this.checkForProviders() + return this.actionResourceRequest({ + method: 'GET', + body: {}, + query: {}, + resourceType: type, + resourceId: id + }) + } + + + // ** initialise handler for in-scope resource types ** + private initResourceRoutes() { + this.server.get(`${SIGNALK_API_PATH}/resources`, (req:any, res:any) => { + this.checkForProviders() + // list all serviced paths under resources + res.json(this.getResourcePaths()) + }) + this.server.use(`${SIGNALK_API_PATH}/resources/*`, async (req:any, res:any, next: any) => { + let result= this.parseResourceRequest(req) + if(result) { + let ar= await this.actionResourceRequest(result) + if(typeof ar.statusCode!== 'undefined'){ + debug(`${JSON.stringify(ar)}`) + res.status= ar.statusCode + res.send(ar.message) + } + else { + res.json(ar) + } + } + else { + debug('** No provider found... calling next()...') + next() + } + }) + } + + // ** return all paths serviced under ./resources *8 + private getResourcePaths(): {[key:string]:any} { + let resPaths:{[key:string]:any}= {} + Object.entries(this.resProvider).forEach( (p:any)=> { + if(p[1]) { resPaths[p[0]]= `Path containing ${p[0]}, each named with a UUID` } + }) + // check for other plugins servicing paths under ./resources + this.server._router.stack.forEach((i:any)=> { + if(i.route && i.route.path && typeof i.route.path==='string') { + if(i.route.path.indexOf(`${SIGNALK_API_PATH}/resources`)!=-1) { + let r= i.route.path.split('/') + if( r.length>5 && !(r[5] in resPaths) ) { + resPaths[r[5]]= `Path containing ${r[5]} resources (provided by plug-in)` + } + } + } + }) + return resPaths + } + + // ** parse api path request and return ResourceRequest object ** + private parseResourceRequest(req:any): ResourceRequest | undefined { + debug('** req.originalUrl:', req.originalUrl) + debug('** req.method:', req.method) + debug('** req.body:', req.body) + debug('** req.query:', req.query) + debug('** req.params:', req.params) + let p= req.params[0].split('/') + let resType= (typeof req.params[0]!=='undefined') ? p[0] : '' + let resId= p.length>1 ? p[1] : '' + debug('** resType:', resType) + debug('** resId:', resId) + + this.checkForProviders() + if(this.resourceTypes.includes(resType) && this.resProvider[resType]) { + return { + method: req.method, + body: req.body, + query: req.query, + resourceType: resType, + resourceId: resId + } + } + else { + debug('Invalid resource type or no provider for this type!') + return undefined + } + } + + // ** action an in-scope resource request ** + private async actionResourceRequest (req:ResourceRequest):Promise { + debug('********* action request *************') + debug(req) + + // check for registered resource providers + if(!this.resProvider) { + return {statusCode: 501, message: `No Provider`} + } + if(!this.resourceTypes.includes(req.resourceType) || !this.resProvider[req.resourceType]) { + return {statusCode: 501, message: `No Provider`} + } + + if(req.method==='GET') { + let retVal: any + if(!req.resourceId) { + retVal= await this.resProvider[req.resourceType].listResources(req.resourceType, req.query) + return (retVal) ? + retVal : + {statusCode: 404, message: `Error retrieving resources!` } + } + if(!validate.uuid(req.resourceId)) { + return {statusCode: 406, message: `Invalid resource id provided (${req.resourceId})` } + } + retVal= await this.resProvider[req.resourceType].getResource(req.resourceType, req.resourceId) + return (retVal) ? + retVal : + {statusCode: 404, message: `Resource not found (${req.resourceId})!` } + } + + if(req.method==='DELETE' || req.method==='PUT') { + if(!req.resourceId) { + return {statusCode: 406, value: `No resource id provided!` } + } + if(!validate.uuid(req.resourceId)) { + return {statusCode: 406, message: `Invalid resource id provided (${req.resourceId})!` } + } + if( + req.method==='DELETE' || + (req.method==='PUT' && typeof req.body.value!=='undefined' && req.body.value==null) + ) { + let retVal= await this.resProvider[req.resourceType].deleteResource(req.resourceType, req.resourceId) + if(retVal){ + this.sendDelta(req.resourceType, req.resourceId, null) + return {statusCode: 200, message: `Resource (${req.resourceId}) deleted.`} + } + else { + return {statusCode: 400, message: `Error deleting resource (${req.resourceId})!` } + } + } + + } + + if(req.method==='POST' || req.method==='PUT') { + // check for supplied value + if( typeof req.body.value==='undefined' || req.body.value==null) { + return {statusCode: 406, message: `No resource data supplied!`} + } + // validate supplied request data + if(!validate.resource(req.resourceType, req.body.value)) { + return {statusCode: 406, message: `Invalid resource data supplied!`} + } + if(req.method==='POST') { + let id= UUID_PREFIX + uuidv4() + let retVal= await this.resProvider[req.resourceType].setResource(req.resourceType, id, req.body.value) + if(retVal){ + this.sendDelta(req.resourceType, id, req.body.value) + return {statusCode: 200, message: `Resource (${id}) saved.`} + } + else { + return {statusCode: 400, message: `Error saving resource (${id})!` } + } + } + if(req.method==='PUT') { + if(!req.resourceId) { + return {statusCode: 406, message: `No resource id provided!` } + } + let retVal= await this.resProvider[req.resourceType].setResource(req.resourceType, req.resourceId, req.body.value) + if(retVal){ + this.sendDelta(req.resourceType, req.resourceId, req.body.value) + return {statusCode: 200, message: `Resource (${req.resourceId}) updated.`} + } + else { + return {statusCode: 400, message: `Error updating resource (${req.resourceId})!` } + } + } + } + } + + private sendDelta(type:string, id:string, value:any):void { + debug(`** Sending Delta: resources.${type}.${id}`) + this.server.handleMessage('signalk-resources', { + updates: [ + { + values: [ + { + path: `resources.${type}.${id}`, + value: value + } + ] + } + ] + }) + } + + // ** get reference to installed resource provider (plug-in). returns null if none found + private getResourceProviderFor(resType:string): ResourceProvider | null { + if(!this.server.plugins) { return null} + let pSource: ResourceProvider | null= null + this.server.plugins.forEach((plugin:any)=> { + if(typeof plugin.resourceProvider !== 'undefined') { + pSource= plugin.resourceProvider.types.includes(resType) ? + plugin.resourceProvider.methods : + null + } + }) + debug(`** Checking for ${resType} provider.... ${pSource ? 'Found' : 'Not found'}`) + return pSource + } + +} diff --git a/src/api/resources/validate.ts b/src/api/resources/validate.ts new file mode 100644 index 000000000..91d8b28f9 --- /dev/null +++ b/src/api/resources/validate.ts @@ -0,0 +1,106 @@ +//import { GeoHash, GeoBounds } from './geo'; +import geoJSON from 'geojson-validation'; + +export const validate= { + resource: (type:string, value:any):boolean=> { + if(!type) { return false } + switch(type) { + case 'routes': + return validateRoute(value); + break + case 'waypoints': + return validateWaypoint(value) + break + case 'notes': + return validateNote(value) + break; + case 'regions': + return validateRegion(value) + break + default: + return true + } + }, + + // ** returns true if id is a valid Signal K UUID ** + uuid: (id:string): boolean=> { + let uuid= RegExp("^urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$") + return uuid.test(id) + } +} + +// ** validate route data +const validateRoute= (r:any):boolean=> { + //if(typeof r.name === 'undefined') { return false } + //if(typeof r.description === 'undefined') { return false } + if(typeof r.distance === 'undefined' || typeof r.distance !=='number') { return false } + if(r.start) { + let l= r.start.split('/') + if(!validate.uuid(l[l.length-1])) { return false } + } + if(r.end) { + let l= r.end.split('/') + if(!validate.uuid(l[l.length-1])) { return false } + } + try { + if(!r.feature || !geoJSON.valid(r.feature)) { + return false + } + if(r.feature.geometry.type!=='LineString') { return false } + } + catch(err) { return false } + return true +} + +// ** validate waypoint data +const validateWaypoint= (r:any):boolean=> { + if(typeof r.position === 'undefined') { return false } + if(typeof r.position.latitude === 'undefined' || typeof r.position.longitude === 'undefined') { + return false + } + if(typeof r.position.latitude !== 'number' || typeof r.position.longitude !== 'number') { + return false + } + try { + if(!r.feature || !geoJSON.valid(r.feature)) { + return false + } + if(r.feature.geometry.type!=='Point') { return false } + } + catch(e) { return false } + return true +} + +// ** validate note data +const validateNote= (r:any):boolean=> { + if(!r.region && !r.position && !r.geohash ) { return false } + if(typeof r.position!== 'undefined') { + if(typeof r.position.latitude === 'undefined' || typeof r.position.longitude === 'undefined') { + return false + } + if(typeof r.position.latitude !== 'number' || typeof r.position.longitude !== 'number') { + return false + } + } + if(r.region) { + let l= r.region.split('/') + if(!validate.uuid(l[l.length-1])) { return false } + } + return true +} + +// ** validate region data +const validateRegion= (r:any):boolean=> { + if(!r.geohash && !r.feature) { return false } + if(r.feature ) { + try { + if(!geoJSON.valid(r.feature)) { return false } + if(r.feature.geometry.type!=='Polygon' && r.feature.geometry.type!=='MultiPolygon') { + return false + } + } + catch(e) { return false } + } + return true +} + diff --git a/src/put.js b/src/put.js index 8db74fac8..c77bb3177 100644 --- a/src/put.js +++ b/src/put.js @@ -30,6 +30,11 @@ module.exports = { app.deRegisterActionHandler = deRegisterActionHandler app.put(apiPathPrefix + '*', function(req, res, next) { + // ** ignore resources paths ** + if(req.path.split('/')[4]==='resources') { + next() + return + } let path = String(req.path).replace(apiPathPrefix, '') const value = req.body From 8764399ccb5366ab32cb3ad98116b60d67808ac5 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 7 Nov 2021 17:27:25 +1030 Subject: [PATCH 045/410] add OpenApi definition file --- src/api/resources/openApi.json | 830 +++++++++++++++++++++++++++++++++ 1 file changed, 830 insertions(+) create mode 100644 src/api/resources/openApi.json diff --git a/src/api/resources/openApi.json b/src/api/resources/openApi.json new file mode 100644 index 000000000..c14f53d5a --- /dev/null +++ b/src/api/resources/openApi.json @@ -0,0 +1,830 @@ +{ + "openapi": "3.0.2", + "info": { + "version": "1.0.0", + "title": "Signal K Resources API" + }, + + "paths": { + + "/resources": { + "get": { + "tags": ["resources"], + "summary": "List available resource types" + + } + }, + + "/resources/{resourceClass}": { + "get": { + "tags": ["resources"], + "summary": "Retrieve resources", + "parameters": [ + { + "name": "resourceClass", + "in": "path", + "description": "resource class", + "required": true, + "schema": { + "type": "string", + "enum": ["routes", "waypoints", "notes", "regions", "charts"], + "example": "waypoints" + } + }, + { + "name": "limit", + "in": "query", + "description": "Maximum number of records to return", + "schema": { + "type": "integer", + "format": "int32", + "minimum": 1, + "example": 100 + } + }, + { + "in": "query", + "name": "radius", + "description": "Limit results to resources that fall within a square area, centered around the vessel's position, the edges of which are the sepecified distance in meters from the vessel.", + "schema": { + "type": "integer", + "format": "int32", + "minimum": 100, + "example": 2000 + } + }, + { + "in": "query", + "name": "geohash", + "description": "limit results to resources that fall within an area sepecified by the geohash.", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "bbox", + "description": "Limit results to resources that fall within the bounded area defined as lower left and upper right x,y coordinates x1,y1,x2,y2", + "style": "form", + "explode": false, + "schema": { + "type": "array", + "minItems": 4, + "maxItems": 4, + "items": { + "type": "number", + "format": "float", + "example": [135.5,-25.2,138.1,-28.0] + } + } + } + ], + "responses": { + "200": { + "description": "List of resources identified by their UUID", + "content": { + "application/json": { + "schema": { + "description": "List of Signal K resources", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + } + } + }, + + "/resources/routes/": { + "post": { + "tags": ["resources/routes"], + "summary": "Add a new Route", + "requestBody": { + "description": "Route details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Route resource", + "required": ["feature"], + "properties": { + "name": { + "type": "string", + "description": "Route's common name" + }, + "description": { + "type": "string", + "description": "A description of the route" + }, + "distance": { + "description": "Total distance from start to end", + "type": "number" + }, + "start": { + "description": "The waypoint UUID at the start of the route", + "type": "string", + "pattern": "/resources/waypoints/urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$" + }, + "end": { + "description": "The waypoint UUID at the end of the route", + "type": "string", + "pattern": "/resources//waypoints/urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$" + }, + "feature": { + "type": "object", + "title": "Feature", + "description": "A GeoJSON feature object which describes a route", + "properties": { + "geometry": { + "type": { + "type": "string", + "enum": ["LineString"] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } + } + } + }, + "properties": { + "description": "Additional feature properties", + "type": "object", + "additionalProperties": true + }, + "id": { + "type": "string" + } + } + } + } + + } + } + } + } + } + }, + + "/resources/routes/{id}": { + "parameters": { + "name": "id", + "in": "path", + "description": "route id", + "required": true, + "schema": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$" + } + }, + + "get": { + "tags": ["resources/routes"], + "summary": "Retrieve route with supplied id" + }, + + "put": { + "tags": ["resources/routes"], + "summary": "Add / update a new Route with supplied id", + "requestBody": { + "description": "Route details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Route resource", + "required": ["feature"], + "properties": { + "name": { + "type": "string", + "description": "Route's common name" + }, + "description": { + "type": "string", + "description": "A description of the route" + }, + "distance": { + "description": "Total distance from start to end", + "type": "number" + }, + "start": { + "description": "The waypoint UUID at the start of the route", + "type": "string", + "pattern": "/resources/waypoints/urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$" + }, + "end": { + "description": "The waypoint UUID at the end of the route", + "type": "string", + "pattern": "/resources/waypoints/urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$" + }, + "feature": { + "type": "object", + "title": "Feature", + "description": "A GeoJSON feature object which describes a route", + "properties": { + "geometry": { + "type": { + "type": "string", + "enum": ["LineString"] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } + } + } + }, + "properties": { + "description": "Additional feature properties", + "type": "object", + "additionalProperties": true + }, + "id": { + "type": "string" + } + } + } + } + + } + } + } + } + }, + + "delete": { + "tags": ["resources/routes"], + "summary": "Remove Route with supplied id" + } + + }, + + "/resources/waypoints/": { + "post": { + "tags": ["resources/waypoints"], + "summary": "Add a new Waypoint", + "requestBody": { + "description": "Waypoint details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Waypoint resource", + "required": ["feature"], + "properties": { + "position": { + "description": "The waypoint position", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + }, + "altitude": { + "type": "number", + "format": "float" + } + } + }, + "feature": { + "type": "object", + "title": "Feature", + "description": "A GeoJSON feature object which describes a waypoint", + "properties": { + "geometry": { + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["Point"] + } + }, + "coordinates": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } + } + }, + "properties": { + "description": "Additional feature properties", + "type": "object", + "additionalProperties": true + }, + "id": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + + "/resources/waypoints/{id}": { + "parameters": { + "name": "id", + "in": "path", + "description": "waypoint id", + "required": true, + "schema": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$" + } + }, + + "get": { + "tags": ["resources/waypoints"], + "summary": "Retrieve waypoint with supplied id" + }, + + "put": { + "tags": ["resources/waypoints"], + "summary": "Add / update a new Waypoint with supplied id", + "requestBody": { + "description": "Waypoint details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Waypoint resource", + "required": ["feature"], + "properties": { + "position": { + "description": "The waypoint position", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + }, + "altitude": { + "type": "number", + "format": "float" + } + } + }, + "feature": { + "type": "object", + "title": "Feature", + "description": "A GeoJSON feature object which describes a waypoint", + "properties": { + "geometry": { + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["Point"] + } + }, + "coordinates": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } + } + }, + "properties": { + "description": "Additional feature properties", + "type": "object", + "additionalProperties": true + }, + "id": { + "type": "string" + } + } + } + } + } + } + } + } + }, + + "delete": { + "tags": ["resources/waypoints"], + "summary": "Remove Waypoint with supplied id" + } + + }, + + "/resources/notes/": { + "post": { + "tags": ["resources/notes"], + "summary": "Add a new Note", + "requestBody": { + "description": "Note details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Note resource", + "required": ["feature"], + "properties": { + "title": { + "type": "string", + "description": "Common Note name" + }, + "description": { + "type": "string", + "description": "A description of the note" + }, + "position": { + "description": "Position related to note. Alternative to region or geohash.", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + }, + "altitude": { + "type": "number", + "format": "float" + } + } + }, + "region": { + "description": "Pointer / path to Region related to note (e.g. /resources/routes/{uuid}. Alternative to position or geohash", + "type": "string", + "pattern": "/resources/regions/urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$" + }, + "geohash": { + "description": "Area related to note. Alternative to region or position", + "type": "string" + }, + "mimeType": { + "description": "MIME type of the note", + "type": "string" + }, + "url": { + "description": "Location of the note content", + "type": "string" + } + } + } + } + } + } + } + }, + + "/resources/notes/{id}": { + "parameters": { + "name": "id", + "in": "path", + "description": "note id", + "required": true, + "schema": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$" + } + }, + + "get": { + "tags": ["resources/notes"], + "summary": "Retrieve Note with supplied id" + }, + + "put": { + "tags": ["resources/notes"], + "summary": "Add / update a new Note with supplied id", + "requestBody": { + "description": "Note details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Note resource", + "required": ["feature"], + "properties": { + "title": { + "type": "string", + "description": "Common Note name" + }, + "description": { + "type": "string", + "description": "A description of the note" + }, + "position": { + "description": "Position related to note. Alternative to region or geohash.", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + }, + "altitude": { + "type": "number", + "format": "float" + } + } + }, + "region": { + "description": "Pointer / path to Region related to note (e.g. /resources/routes/{uuid}. Alternative to position or geohash", + "type": "string", + "pattern": "/resources/regions/urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$" + }, + "geohash": { + "description": "Area related to note. Alternative to region or position", + "type": "string" + }, + "mimeType": { + "description": "MIME type of the note", + "type": "string" + }, + "url": { + "description": "Location of the note content", + "type": "string" + } + } + } + } + } + } + }, + + "delete": { + "tags": ["resources/notes"], + "summary": "Remove Note with supplied id" + } + + }, + + "/resources/regions/": { + "post": { + "tags": ["resources/regions"], + "summary": "Add a new Regkion", + "requestBody": { + "description": "Region details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Region resource", + "required": ["feature"], + "properties": { + "geohash": { + "description": "geohash of the approximate boundary of this region", + "type": "string" + }, + "feature": { + "type": "object", + "title": "Feature", + "description": "A Geo JSON feature object which describes the regions boundary", + "properties": { + "geometry": { + "oneOf": [ + { + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["Polygon"] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } + } + } + } + } + }, + { + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["MultiPolygon"] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } + } + } + } + } + } + } + ] + }, + "properties": { + "description": "Additional feature properties", + "type": "object", + "additionalProperties": true + }, + "id": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + + "/resources/regions/{id}": { + "parameters": { + "name": "id", + "in": "path", + "description": "region id", + "required": true, + "schema": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$" + } + }, + + "get": { + "tags": ["resources/regions"], + "summary": "Retrieve Region with supplied id" + }, + + "put": { + "tags": ["resources/regions"], + "summary": "Add / update a new Region with supplied id", + "requestBody": { + "description": "Region details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Region resource", + "required": ["feature"], + "properties": { + "geohash": { + "description": "geohash of the approximate boundary of this region", + "type": "string" + }, + "feature": { + "type": "object", + "title": "Feature", + "description": "A Geo JSON feature object which describes the regions boundary", + "properties": { + "geometry": { + "oneOf": [ + { + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["Polygon"] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } + } + } + } + } + }, + { + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["MultiPolygon"] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } + } + } + } + } + } + } + ] + }, + "properties": { + "description": "Additional feature properties", + "type": "object", + "additionalProperties": true + }, + "id": { + "type": "string" + } + } + } + } + } + } + } + } + }, + + "delete": { + "tags": ["resources/regions"], + "summary": "Remove Region with supplied id" + } + + } + + } + +} + \ No newline at end of file From 4fea4c273adf913b77ff4071dc96bb90420b287e Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 8 Nov 2021 09:04:45 +1030 Subject: [PATCH 046/410] chore: fix lint errors --- src/api/resources/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 838f5b962..ae0495e08 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -43,7 +43,7 @@ export class Resources { } public checkForProviders(rescan:boolean= false) { - if(rescan || Object.keys(this.resProvider).length==0) { + if(rescan || Object.keys(this.resProvider).length===0) { debug('** Checking for providers....') this.resProvider= {} this.resourceTypes.forEach( (rt:string)=> { @@ -102,7 +102,7 @@ export class Resources { // check for other plugins servicing paths under ./resources this.server._router.stack.forEach((i:any)=> { if(i.route && i.route.path && typeof i.route.path==='string') { - if(i.route.path.indexOf(`${SIGNALK_API_PATH}/resources`)!=-1) { + if(i.route.path.indexOf(`${SIGNALK_API_PATH}/resources`)!==-1) { let r= i.route.path.split('/') if( r.length>5 && !(r[5] in resPaths) ) { resPaths[r[5]]= `Path containing ${r[5]} resources (provided by plug-in)` From a497615942c842b4ad52a2a4db78174eed73512b Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Tue, 9 Nov 2021 15:13:44 +1030 Subject: [PATCH 047/410] addressed comments re parameters --- src/api/resources/openApi.json | 664 ++++++++++++++++++++++++++------- 1 file changed, 527 insertions(+), 137 deletions(-) diff --git a/src/api/resources/openApi.json b/src/api/resources/openApi.json index c14f53d5a..e23a100f3 100644 --- a/src/api/resources/openApi.json +++ b/src/api/resources/openApi.json @@ -10,8 +10,22 @@ "/resources": { "get": { "tags": ["resources"], - "summary": "List available resource types" - + "summary": "List available resource types", + "responses": { + "default": { + "description": "List of available resource types", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + } } }, @@ -43,8 +57,8 @@ } }, { + "name": "distance", "in": "query", - "name": "radius", "description": "Limit results to resources that fall within a square area, centered around the vessel's position, the edges of which are the sepecified distance in meters from the vessel.", "schema": { "type": "integer", @@ -53,18 +67,10 @@ "example": 2000 } }, - { - "in": "query", - "name": "geohash", - "description": "limit results to resources that fall within an area sepecified by the geohash.", - "schema": { - "type": "string" - } - }, { "in": "query", "name": "bbox", - "description": "Limit results to resources that fall within the bounded area defined as lower left and upper right x,y coordinates x1,y1,x2,y2", + "description": "Limit results to resources that fall within the bounded area defined as lower left (south west) and upper right (north east) coordinates [swlon,swlat,nelon,nelat]", "style": "form", "explode": false, "schema": { @@ -140,18 +146,21 @@ "description": "A GeoJSON feature object which describes a route", "properties": { "geometry": { - "type": { - "type": "string", - "enum": ["LineString"] - }, - "coordinates": { - "type": "array", - "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["LineString"] + }, + "coordinates": { "type": "array", - "maxItems": 3, - "minItems": 2, "items": { - "type": "number" + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } } } } @@ -171,7 +180,8 @@ } } } - } + }, + "responses": {} } }, @@ -189,7 +199,8 @@ "get": { "tags": ["resources/routes"], - "summary": "Retrieve route with supplied id" + "summary": "Retrieve route with supplied id", + "responses": {} }, "put": { @@ -233,18 +244,21 @@ "description": "A GeoJSON feature object which describes a route", "properties": { "geometry": { - "type": { - "type": "string", - "enum": ["LineString"] - }, - "coordinates": { - "type": "array", - "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["LineString"] + }, + "coordinates": { "type": "array", - "maxItems": 3, - "minItems": 2, "items": { - "type": "number" + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } } } } @@ -264,12 +278,14 @@ } } } - } + }, + "responses": {} }, "delete": { "tags": ["resources/routes"], - "summary": "Remove Route with supplied id" + "summary": "Remove Route with supplied id", + "responses": {} } }, @@ -317,19 +333,22 @@ "properties": { "geometry": { "type": "object", - "description": "GeoJSon geometry", "properties": { - "type": { - "type": "string", - "enum": ["Point"] - } - }, - "coordinates": { - "type": "array", - "maxItems": 3, - "minItems": 2, - "items": { - "type": "number" + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["Point"] + } + }, + "coordinates": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } } } }, @@ -347,7 +366,8 @@ } } } - } + }, + "responses": {} } }, @@ -365,7 +385,8 @@ "get": { "tags": ["resources/waypoints"], - "summary": "Retrieve waypoint with supplied id" + "summary": "Retrieve waypoint with supplied id", + "responses": {} }, "put": { @@ -410,19 +431,22 @@ "properties": { "geometry": { "type": "object", - "description": "GeoJSon geometry", "properties": { - "type": { - "type": "string", - "enum": ["Point"] - } - }, - "coordinates": { - "type": "array", - "maxItems": 3, - "minItems": 2, - "items": { - "type": "number" + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["Point"] + } + }, + "coordinates": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } } } }, @@ -440,12 +464,14 @@ } } } - } + }, + "responses": {} }, "delete": { "tags": ["resources/waypoints"], - "summary": "Remove Waypoint with supplied id" + "summary": "Remove Waypoint with supplied id", + "responses": {} } }, @@ -533,7 +559,8 @@ "get": { "tags": ["resources/notes"], - "summary": "Retrieve Note with supplied id" + "summary": "Retrieve Note with supplied id", + "responses": {} }, "put": { @@ -600,12 +627,14 @@ } } } - } + }, + "responses": {} }, "delete": { "tags": ["resources/notes"], - "summary": "Remove Note with supplied id" + "summary": "Remove Note with supplied id", + "responses": {} } }, @@ -634,59 +663,62 @@ "description": "A Geo JSON feature object which describes the regions boundary", "properties": { "geometry": { - "oneOf": [ - { - "type": "object", - "description": "GeoJSon geometry", - "properties": { - "type": { - "type": "string", - "enum": ["Polygon"] - }, - "coordinates": { - "type": "array", - "items": { + "type": "object", + "properties": { + "oneOf": [ + { + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["Polygon"] + }, + "coordinates": { "type": "array", "items": { "type": "array", - "maxItems": 3, - "minItems": 2, "items": { - "type": "number" + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } } } } } - } - }, - { - "type": "object", - "description": "GeoJSon geometry", - "properties": { - "type": { - "type": "string", - "enum": ["MultiPolygon"] - }, - "coordinates": { - "type": "array", - "items": { + }, + { + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["MultiPolygon"] + }, + "coordinates": { "type": "array", "items": { "type": "array", "items": { "type": "array", - "maxItems": 3, - "minItems": 2, "items": { - "type": "number" + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } } } } } } } - } - ] + ] + } }, "properties": { "description": "Additional feature properties", @@ -702,7 +734,8 @@ } } } - } + }, + "responses": {} } }, @@ -720,7 +753,8 @@ "get": { "tags": ["resources/regions"], - "summary": "Retrieve Region with supplied id" + "summary": "Retrieve Region with supplied id", + "responses": {} }, "put": { @@ -746,59 +780,62 @@ "description": "A Geo JSON feature object which describes the regions boundary", "properties": { "geometry": { - "oneOf": [ - { - "type": "object", - "description": "GeoJSon geometry", - "properties": { - "type": { - "type": "string", - "enum": ["Polygon"] - }, - "coordinates": { - "type": "array", - "items": { + "type": "object", + "properties": { + "oneOf": [ + { + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["Polygon"] + }, + "coordinates": { "type": "array", "items": { "type": "array", - "maxItems": 3, - "minItems": 2, "items": { - "type": "number" + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } } } } } - } - }, - { - "type": "object", - "description": "GeoJSon geometry", - "properties": { - "type": { - "type": "string", - "enum": ["MultiPolygon"] - }, - "coordinates": { - "type": "array", - "items": { + }, + { + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["MultiPolygon"] + }, + "coordinates": { "type": "array", "items": { "type": "array", "items": { "type": "array", - "maxItems": 3, - "minItems": 2, "items": { - "type": "number" + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } } } } } } } - } - ] + ] + } }, "properties": { "description": "Additional feature properties", @@ -814,14 +851,367 @@ } } } - } + }, + "responses": {} }, "delete": { "tags": ["resources/regions"], - "summary": "Remove Region with supplied id" + "summary": "Remove Region with supplied id", + "responses": {} + } + + }, + + "/resources/setWaypoint": { + "put": { + "tags": ["resources/api"], + "summary": "Add / update a Waypoint", + "requestBody": { + "description": "Waypoint attributes", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["position"], + "properties": { + "id": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", + "description": "Waypoint identifier" + }, + "name": { + "type": "string", + "description": "Waypoint name" + }, + "description": { + "type": "string", + "description": "Textual description of the waypoint" + }, + "position": { + "description": "The waypoint position", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + } + } + } + } + } + } + }, + "responses": {} + } + }, + + "/resources/deleteWaypoint": { + "put": { + "tags": ["resources/api"], + "summary": "Remove a Waypoint", + "requestBody": { + "description": "Waypoint identifier", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["id"], + "properties": { + "id": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", + "description": "Waypoint identifier" + } + } + } + } + } + }, + "responses": {} + } + }, + + "/resources/setRoute": { + "put": { + "tags": ["resources/api"], + "summary": "Add / update a Route", + "requestBody": { + "description": "Note attributes", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["position"], + "properties": { + "id": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", + "description": "Route identifier" + }, + "name": { + "type": "string", + "description": "Route name" + }, + "description": { + "type": "string", + "description": "Textual description of the route" + }, + "points": { + "description": "Route points", + "type": "array", + "items": { + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + } + } + } + } + } + } + } + }, + "responses": {} + } + }, + + "/resources/deleteRoute": { + "put": { + "tags": ["resources/api"], + "summary": "Remove a Route", + "requestBody": { + "description": "Route identifier", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["id"], + "properties": { + "id": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", + "description": "Route identifier" + } + } + } + } + } + }, + "responses": {} } + }, + "/resources/setNote": { + "put": { + "tags": ["resources/api"], + "summary": "Add / update a Note", + "requestBody": { + "description": "Note attributes", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["position"], + "properties": { + "id": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", + "description": "Note identifier" + }, + "title": { + "type": "string", + "description": "Note's common name" + }, + "description": { + "type": "string", + "description": " Textual description of the note" + }, + "oneOf":[ + { + "position": { + "description": "Position related to note. Alternative to region or geohash", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + } + }, + "geohash": { + "type": "string", + "description": "Position related to note. Alternative to region or position" + }, + "region": { + "type": "string", + "description": "Region related to note. A pointer to a region UUID. Alternative to position or geohash", + "example": "/resources/regions/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a" + } + } + ], + "mimeType": { + "type": "string", + "description": "MIME type of the note" + }, + "url": { + "type": "string", + "description": "Location of the note contents" + } + } + } + } + } + }, + "responses": {} + } + }, + + "/resources/deleteNote": { + "put": { + "tags": ["resources/api"], + "summary": "Remove a Note", + "requestBody": { + "description": "Note identifier", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["id"], + "properties": { + "id": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", + "description": "Note identifier" + } + } + } + } + } + }, + "responses": {} + } + }, + + "/resources/setRegion": { + "put": { + "tags": ["resources/api"], + "summary": "Add / update a Region", + "requestBody": { + "description": "Region attributes", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["position"], + "properties": { + "id": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", + "description": "Region identifier" + }, + "name": { + "type": "string", + "description": "Region name" + }, + "description": { + "type": "string", + "description": "Textual description of region" + }, + "geohash": { + "type": "string", + "description": "Area related to region. Alternative to points." + }, + "points": { + "description": "Region boundary points", + "type": "array", + "items": { + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + } + } + } + } + } + } + } + }, + "responses": {} + } + }, + + "/resources/deleteRegion": { + "put": { + "tags": ["resources/api"], + "summary": "Remove a Region", + "requestBody": { + "description": "Region identifier", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["id"], + "properties": { + "id": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", + "description": "Region identifier" + } + } + } + } + } + }, + "responses": {} + } } } From c29d46275f16d06a5b95482b6bef24582cbcb1dc Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Tue, 9 Nov 2021 15:28:15 +1030 Subject: [PATCH 048/410] add API definitions --- src/api/resources/openApi.json | 350 ++++++++++++++++++++++++++++++--- 1 file changed, 326 insertions(+), 24 deletions(-) diff --git a/src/api/resources/openApi.json b/src/api/resources/openApi.json index e23a100f3..d7a7f6e35 100644 --- a/src/api/resources/openApi.json +++ b/src/api/resources/openApi.json @@ -12,7 +12,7 @@ "tags": ["resources"], "summary": "List available resource types", "responses": { - "default": { + "200": { "description": "List of available resource types", "content": { "application/json": { @@ -181,7 +181,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -200,7 +211,22 @@ "get": { "tags": ["resources/routes"], "summary": "Retrieve route with supplied id", - "responses": {} + "responses": { + "200": { + "description": "List of resources identified by their UUID", + "content": { + "application/json": { + "schema": { + "description": "List of Signal K resources", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + } }, "put": { @@ -279,13 +305,35 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } }, "delete": { "tags": ["resources/routes"], "summary": "Remove Route with supplied id", - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -367,7 +415,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -386,7 +445,22 @@ "get": { "tags": ["resources/waypoints"], "summary": "Retrieve waypoint with supplied id", - "responses": {} + "responses": { + "200": { + "description": "List of resources identified by their UUID", + "content": { + "application/json": { + "schema": { + "description": "List of Signal K resources", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + } }, "put": { @@ -465,13 +539,35 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } }, "delete": { "tags": ["resources/waypoints"], "summary": "Remove Waypoint with supplied id", - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -541,6 +637,18 @@ } } } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } } } }, @@ -560,7 +668,22 @@ "get": { "tags": ["resources/notes"], "summary": "Retrieve Note with supplied id", - "responses": {} + "responses": { + "200": { + "description": "List of resources identified by their UUID", + "content": { + "application/json": { + "schema": { + "description": "List of Signal K resources", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + } }, "put": { @@ -628,13 +751,35 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } }, "delete": { "tags": ["resources/notes"], "summary": "Remove Note with supplied id", - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -735,7 +880,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -754,7 +910,22 @@ "get": { "tags": ["resources/regions"], "summary": "Retrieve Region with supplied id", - "responses": {} + "responses": { + "200": { + "description": "List of resources identified by their UUID", + "content": { + "application/json": { + "schema": { + "description": "List of Signal K resources", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + } }, "put": { @@ -852,13 +1023,35 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } }, "delete": { "tags": ["resources/regions"], "summary": "Remove Region with supplied id", - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -889,6 +1082,13 @@ "type": "string", "description": "Textual description of the waypoint" }, + "attributes": { + "type": "object", + "description": "Additional attributes as name:value pairs.", + "additionalProperties": { + "type": "string" + } + }, "position": { "description": "The waypoint position", "type": "object", @@ -912,7 +1112,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -939,7 +1150,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -969,6 +1191,13 @@ "type": "string", "description": "Textual description of the route" }, + "attributes": { + "type": "object", + "description": "Additional attributes as name:value pairs.", + "additionalProperties": { + "type": "string" + } + }, "points": { "description": "Route points", "type": "array", @@ -995,7 +1224,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -1022,7 +1262,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -1096,7 +1347,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -1123,7 +1385,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -1153,6 +1426,13 @@ "type": "string", "description": "Textual description of region" }, + "attributes": { + "type": "object", + "description": "Additional attributes as name:value pairs.", + "additionalProperties": { + "type": "string" + } + }, "geohash": { "type": "string", "description": "Area related to region. Alternative to points." @@ -1183,7 +1463,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -1210,7 +1501,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } } From 317cd20891b97113d944207de88635202605d6cf Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Tue, 9 Nov 2021 15:29:13 +1030 Subject: [PATCH 049/410] add geohash library --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index 09f1d1ad9..77e772783 100644 --- a/package.json +++ b/package.json @@ -110,6 +110,7 @@ "morgan": "^1.5.0", "ms": "^2.1.2", "ncp": "^2.0.0", + "ngeohash": "^0.6.3", "node-fetch": "^2.6.0", "pem": "^1.14.3", "primus": "^7.0.0", @@ -136,6 +137,7 @@ "@types/express": "^4.17.1", "@types/lodash": "^4.14.139", "@types/mocha": "^8.2.0", + "@types/ngeohash": "^0.6.4", "@types/node-fetch": "^2.5.3", "@types/semver": "^7.1.0", "@types/serialport": "^8.0.1", From 47619182adbe8fe3fb0f91d89359aea4eb06a192 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Tue, 9 Nov 2021 15:30:30 +1030 Subject: [PATCH 050/410] add API endpoint processing --- src/api/resources/index.ts | 81 ++++++++++++++-- src/api/resources/resources.ts | 167 +++++++++++++++++++++++++++++++++ src/api/resources/validate.ts | 1 - 3 files changed, 239 insertions(+), 10 deletions(-) create mode 100644 src/api/resources/resources.ts diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index ae0495e08..d84cd29a8 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -1,6 +1,7 @@ import Debug from 'debug' import { v4 as uuidv4 } from 'uuid' import { validate } from './validate' +import { buildResource } from './resources' const debug = Debug('signalk:resources') @@ -9,7 +10,8 @@ interface ResourceRequest { body: any query: {[key:string]: any} resourceType: string - resourceId: string + resourceId: string, + apiMethod?: string | null } interface ResourceProvider { @@ -22,6 +24,17 @@ interface ResourceProvider { const SIGNALK_API_PATH= `/signalk/v1/api` const UUID_PREFIX= 'urn:mrn:signalk:uuid:' +const API_METHODS= [ + 'setWaypoint', + 'deleteWaypoint', + 'setRoute', + 'deleteRoute', + 'setNote', + 'deleteNote', + 'setRegion', + 'deleteRegion' +] + export class Resources { // ** in-scope resource types ** @@ -114,7 +127,7 @@ export class Resources { } // ** parse api path request and return ResourceRequest object ** - private parseResourceRequest(req:any): ResourceRequest | undefined { + private parseResourceRequest(req:any):ResourceRequest | undefined { debug('** req.originalUrl:', req.originalUrl) debug('** req.method:', req.method) debug('** req.body:', req.body) @@ -125,16 +138,35 @@ export class Resources { let resId= p.length>1 ? p[1] : '' debug('** resType:', resType) debug('** resId:', resId) + + let apiMethod= (API_METHODS.includes(resType)) ? resType : null + if(apiMethod) { + if(apiMethod.toLowerCase().indexOf('waypoint')!==-1) { + resType= 'waypoints' + } + if(apiMethod.toLowerCase().indexOf('route')!==-1) { + resType= 'routes' + } + if(apiMethod.toLowerCase().indexOf('note')!==-1) { + resType= 'notes' + } + if(apiMethod.toLowerCase().indexOf('region')!==-1) { + resType= 'regions' + } + } this.checkForProviders() + let retReq= { + method: req.method, + body: req.body, + query: req.query, + resourceType: resType, + resourceId: resId, + apiMethod: apiMethod + } + if(this.resourceTypes.includes(resType) && this.resProvider[resType]) { - return { - method: req.method, - body: req.body, - query: req.query, - resourceType: resType, - resourceId: resId - } + return retReq } else { debug('Invalid resource type or no provider for this type!') @@ -151,10 +183,41 @@ export class Resources { if(!this.resProvider) { return {statusCode: 501, message: `No Provider`} } + if(!this.resourceTypes.includes(req.resourceType) || !this.resProvider[req.resourceType]) { return {statusCode: 501, message: `No Provider`} } + // check for API method request + if(req.apiMethod && API_METHODS.includes(req.apiMethod)) { + debug(`API Method (${req.apiMethod})`) + req= this.transformApiRequest(req) + } + + return await this.execResourceRequest(req) + } + + // ** transform API request to ResourceRequest ** + private transformApiRequest(req: ResourceRequest):ResourceRequest { + if(req.apiMethod?.indexOf('delete')!==-1) { + req.method= 'DELETE' + } + if(req.apiMethod?.indexOf('set')!==-1) { + if(!req.body.value?.id) { + req.method= 'POST' + } + else { + req.resourceId= req.body.value.id + } + req.body.value= buildResource(req.resourceType, req.body.value) ?? {} + } + console.log(req) + return req + } + + // ** action an in-scope resource request ** + private async execResourceRequest (req:ResourceRequest):Promise { + if(req.method==='GET') { let retVal: any if(!req.resourceId) { diff --git a/src/api/resources/resources.ts b/src/api/resources/resources.ts new file mode 100644 index 000000000..590a6591b --- /dev/null +++ b/src/api/resources/resources.ts @@ -0,0 +1,167 @@ +import { getDistance } from 'geolib' +import ngeohash from 'ngeohash' +import geolib from 'geolib' + +// ** build resource item ** +export const buildResource= (resType:string, data:any):any=> { + console.log(`** resType: ${resType}, data: ${JSON.stringify(data)}`) + if(resType==='routes') { return buildRoute(data) } + if(resType==='waypoints') { return buildWaypoint(data) } + if(resType==='notes') { return buildNote(data) } + if(resType==='regions') { return buildRegion(data) } +} + +// ** build route +const buildRoute= (rData:any):any=> { + let rte:any= { + feature: { + type: 'Feature', + geometry:{ + type: 'LineString', + coordinates :[] + }, + properties:{} + } + } + if(typeof rData.name !== 'undefined') { + rte.name= rData.name + rte.feature.properties.name= rData.name + } + if(typeof rData.description !== 'undefined') { + rte.description= rData.description + rte.feature.properties.description= rData.description + } + if(typeof rData.points === 'undefined') { return null } + if(!Array.isArray(rData.points)) { return null } + let isValid:boolean= true; + rData.points.forEach( (p:any)=> { + if(!geolib.isValidCoordinate(p)) { isValid= false } + }) + if(!isValid) { return null } + rData.points.forEach( (p:any)=> { + rte.feature.geometry.coordinates.push([p.longitude, p.latitude]) + }) + rte.distance= 0 + for(let i=0; i { + let wpt:any= { + position: { + latitude: 0, + longitude: 0 + }, + feature: { + type: 'Feature', + geometry:{ + type: 'Point', + coordinates :[] + }, + properties:{} + } + } + if(typeof rData.name !== 'undefined') { + wpt.feature.properties.name= rData.name + } + if(typeof rData.description !== 'undefined') { + wpt.feature.properties.description= rData.description + } + if(typeof rData.position === 'undefined') { return null } + if(!geolib.isValidCoordinate(rData.position)) { return null } + + wpt.position= rData.position + wpt.feature.geometry.coordinates= [rData.position.longitude, rData.position.latitude] + + return wpt +} + +// ** build note +const buildNote= (rData:any):any=> { + let note:any= {} + if(typeof rData.title !== 'undefined') { + note.title= rData.title + note.feature.properties.title= rData.title + } + if(typeof rData.description !== 'undefined') { + note.description= rData.description + note.feature.properties.description= rData.description + } + if(typeof rData.position === 'undefined' + && typeof rData.region === 'undefined' + && typeof rData.geohash === 'undefined') { return null } + + if(typeof rData.position !== 'undefined') { + if(!geolib.isValidCoordinate(rData.position)) { return null } + note.position= rData.position + } + if(typeof rData.region !== 'undefined') { + note.region= rData.region + } + if(typeof rData.geohash !== 'undefined') { + note.geohash= rData.geohash + } + if(typeof rData.url !== 'undefined') { + note.url= rData.url + } + if(typeof rData.mimeType !== 'undefined') { + note.mimeType= rData.mimeType + } + + return note +} + +// ** build region +const buildRegion= (rData:any):any=> { + let reg:any= { + feature: { + type: 'Feature', + geometry:{ + type: 'Polygon', + coordinates :[] + }, + properties:{} + } + } + let coords:Array<[number,number]>= [] + + if(typeof rData.name !== 'undefined') { + reg.feature.properties.name= rData.name + } + if(typeof rData.description !== 'undefined') { + reg.feature.properties.description= rData.description + } + if(typeof rData.points=== 'undefined' && rData.geohash=== 'undefined') { return null } + if(typeof rData.geohash!== 'undefined') { + reg.geohash= rData.geohash + + let bounds= ngeohash.decode_bbox(rData.geohash) + coords= [ + [bounds[1], bounds[0]], + [bounds[3], bounds[0]], + [bounds[3], bounds[2]], + [bounds[1], bounds[2]], + [bounds[1], bounds[0]], + ] + reg.feature.geometry.coordinates.push(coords) + } + if(typeof rData.points!== 'undefined' && coords.length===0 ) { + if(!Array.isArray(rData.points)) { return null } + let isValid:boolean= true; + rData.points.forEach( (p:any)=> { + if(!geolib.isValidCoordinate(p)) { isValid= false } + }) + if(!isValid) { return null } + rData.points.forEach( (p:any)=> { + coords.push([p.longitude, p.latitude]) + }) + reg.feature.geometry.coordinates.push(coords) + } + + return reg +} diff --git a/src/api/resources/validate.ts b/src/api/resources/validate.ts index 91d8b28f9..341b7465f 100644 --- a/src/api/resources/validate.ts +++ b/src/api/resources/validate.ts @@ -1,4 +1,3 @@ -//import { GeoHash, GeoBounds } from './geo'; import geoJSON from 'geojson-validation'; export const validate= { From 68c8f2d0adf7ab734460255aa8177c9a9447ac5b Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Tue, 9 Nov 2021 16:44:49 +1030 Subject: [PATCH 051/410] align with openapi definitions --- src/api/resources/index.ts | 10 +++++----- src/api/resources/resources.ts | 35 ++++++++++++++++++++++------------ src/api/resources/validate.ts | 14 +++----------- 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index d84cd29a8..8d453cbc9 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -203,21 +203,21 @@ export class Resources { req.method= 'DELETE' } if(req.apiMethod?.indexOf('set')!==-1) { - if(!req.body.value?.id) { + if(!req.body.id) { req.method= 'POST' } else { - req.resourceId= req.body.value.id + req.resourceId= req.body.id } - req.body.value= buildResource(req.resourceType, req.body.value) ?? {} + req.body= { value: buildResource(req.resourceType, req.body) ?? {} } } - console.log(req) return req } // ** action an in-scope resource request ** private async execResourceRequest (req:ResourceRequest):Promise { - + debug('********* execute request *************') + debug(req) if(req.method==='GET') { let retVal: any if(!req.resourceId) { diff --git a/src/api/resources/resources.ts b/src/api/resources/resources.ts index 590a6591b..e76cce323 100644 --- a/src/api/resources/resources.ts +++ b/src/api/resources/resources.ts @@ -1,10 +1,8 @@ -import { getDistance } from 'geolib' +import { getDistance, isValidCoordinate } from 'geolib' import ngeohash from 'ngeohash' -import geolib from 'geolib' // ** build resource item ** export const buildResource= (resType:string, data:any):any=> { - console.log(`** resType: ${resType}, data: ${JSON.stringify(data)}`) if(resType==='routes') { return buildRoute(data) } if(resType==='waypoints') { return buildWaypoint(data) } if(resType==='notes') { return buildNote(data) } @@ -12,7 +10,7 @@ export const buildResource= (resType:string, data:any):any=> { } // ** build route -const buildRoute= (rData:any):any=> { +const buildRoute= (rData:any):any=> { let rte:any= { feature: { type: 'Feature', @@ -31,22 +29,27 @@ const buildRoute= (rData:any):any=> { rte.description= rData.description rte.feature.properties.description= rData.description } + if(typeof rData.attributes !== 'undefined') { + Object.assign(rte.feature.properties, rData.attributes) + } + if(typeof rData.points === 'undefined') { return null } if(!Array.isArray(rData.points)) { return null } let isValid:boolean= true; rData.points.forEach( (p:any)=> { - if(!geolib.isValidCoordinate(p)) { isValid= false } + if(!isValidCoordinate(p)) { isValid= false } }) if(!isValid) { return null } rData.points.forEach( (p:any)=> { rte.feature.geometry.coordinates.push([p.longitude, p.latitude]) }) + rte.distance= 0 - for(let i=0; i { if(typeof rData.description !== 'undefined') { wpt.feature.properties.description= rData.description } + if(typeof rData.attributes !== 'undefined') { + Object.assign(wpt.feature.properties, rData.attributes) + } + if(typeof rData.position === 'undefined') { return null } - if(!geolib.isValidCoordinate(rData.position)) { return null } + if(!isValidCoordinate(rData.position)) { return null } wpt.position= rData.position wpt.feature.geometry.coordinates= [rData.position.longitude, rData.position.latitude] @@ -97,7 +104,7 @@ const buildNote= (rData:any):any=> { && typeof rData.geohash === 'undefined') { return null } if(typeof rData.position !== 'undefined') { - if(!geolib.isValidCoordinate(rData.position)) { return null } + if(!isValidCoordinate(rData.position)) { return null } note.position= rData.position } if(typeof rData.region !== 'undefined') { @@ -136,6 +143,10 @@ const buildRegion= (rData:any):any=> { if(typeof rData.description !== 'undefined') { reg.feature.properties.description= rData.description } + if(typeof rData.attributes !== 'undefined') { + Object.assign(reg.feature.properties, rData.attributes) + } + if(typeof rData.points=== 'undefined' && rData.geohash=== 'undefined') { return null } if(typeof rData.geohash!== 'undefined') { reg.geohash= rData.geohash @@ -154,7 +165,7 @@ const buildRegion= (rData:any):any=> { if(!Array.isArray(rData.points)) { return null } let isValid:boolean= true; rData.points.forEach( (p:any)=> { - if(!geolib.isValidCoordinate(p)) { isValid= false } + if(!isValidCoordinate(p)) { isValid= false } }) if(!isValid) { return null } rData.points.forEach( (p:any)=> { diff --git a/src/api/resources/validate.ts b/src/api/resources/validate.ts index 341b7465f..840d944b2 100644 --- a/src/api/resources/validate.ts +++ b/src/api/resources/validate.ts @@ -1,4 +1,5 @@ import geoJSON from 'geojson-validation'; +import { isValidCoordinate } from 'geolib' export const validate= { resource: (type:string, value:any):boolean=> { @@ -30,9 +31,6 @@ export const validate= { // ** validate route data const validateRoute= (r:any):boolean=> { - //if(typeof r.name === 'undefined') { return false } - //if(typeof r.description === 'undefined') { return false } - if(typeof r.distance === 'undefined' || typeof r.distance !=='number') { return false } if(r.start) { let l= r.start.split('/') if(!validate.uuid(l[l.length-1])) { return false } @@ -54,12 +52,9 @@ const validateRoute= (r:any):boolean=> { // ** validate waypoint data const validateWaypoint= (r:any):boolean=> { if(typeof r.position === 'undefined') { return false } - if(typeof r.position.latitude === 'undefined' || typeof r.position.longitude === 'undefined') { + if(!isValidCoordinate(r.position)) { return false } - if(typeof r.position.latitude !== 'number' || typeof r.position.longitude !== 'number') { - return false - } try { if(!r.feature || !geoJSON.valid(r.feature)) { return false @@ -74,12 +69,9 @@ const validateWaypoint= (r:any):boolean=> { const validateNote= (r:any):boolean=> { if(!r.region && !r.position && !r.geohash ) { return false } if(typeof r.position!== 'undefined') { - if(typeof r.position.latitude === 'undefined' || typeof r.position.longitude === 'undefined') { + if(!isValidCoordinate(r.position)) { return false } - if(typeof r.position.latitude !== 'number' || typeof r.position.longitude !== 'number') { - return false - } } if(r.region) { let l= r.region.split('/') From 699d0a8abb87c518eb31360efbd4b8e9a97a180b Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Thu, 11 Nov 2021 16:51:47 +1030 Subject: [PATCH 052/410] Added Resource_Provider documentation --- RESOURCE_PROVIDER_PLUGINS.md | 192 +++++++++++++++++++++++++++++++++++ SERVERPLUGINS.md | 2 + 2 files changed, 194 insertions(+) create mode 100644 RESOURCE_PROVIDER_PLUGINS.md diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md new file mode 100644 index 000000000..64ac8276a --- /dev/null +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -0,0 +1,192 @@ +# Resource Provider plugins + +## Overview + +This document should be read in conjunction with [SERVERPLUGINS.md](./SERVERPLUGINS.md) as it contains additional information regarding the development of plugins that facilitate the storage and retrieval of resource data (e.g. routes, waypoints, notes, regions and charts). + +The Signal K Node server will handle all requests to the following paths: + +`/signalk/v1/api/resources` +`/signalk/v1/api/resources/routes` +`/signalk/v1/api/resources/waypoints` +`/signalk/v1/api/resources/notes` +`/signalk/v1/api/resources/regions` +`/signalk/v1/api/resources/charts` + +This means all requests (GET, PUT, POST and DELETE) are captured and any supplied data is validated prior to being dispatched for processing. + +The server itself has no built-in functionality to save or retrieve resource data from storage, this is the responsibility of a `resource provider plugin`. + +If there are no registered providers for the resource type for which the request is made, then no action is taken. + +This architecture de-couples the resource storage from core server function to provide flexibility to implement the appropriate storage solution for the various resource types for your Signal K implementation. + +## Resource Providers + +A `resource provider plugin` is responsible for the storage and retrieval of resource data. +This allows the method for persisting resource data to be tailored to the Signal K implementation e.g. the local file system, a database service, cloud service, etc. + +It is similar to any other Server Plugin except that it implements the __resourceProvider__ interface. + +```JAVASCRIPT +resourceProvider: { + types: [], + methods: { + listResources: (type:string, query: {[key:string]:any})=> Promise + getResource: (type:string, id:string)=> Promise + setResource: (type:string, id:string, value:{[key:string]:any})=> Promise + deleteResource: (type:string, id:string)=> Promise + } +} +``` + +This interface exposes the following information to the server enabling it to direct requests to the plugin: +- `types`: The resource types for which requests should be directed to the plugin. These can be one or all of `routes, waypoints, notes, regions, charts`. +- `methods`: The methods to which the server dispatches requests. The plugin will implement these methods to perform the necessary save or retrieval operation. + +_Example: Plugin acting as resource provider for routes & waypoints._ +```JAVASCRIPT +let plugin= { + id: 'mypluginid', + name: 'My Resource Providerplugin', + start: (options, restart)=> { ... }, + stop: ()=> { ... }, + resourceProvider: { + types: ['routes','waypoints'], + methods: { + listResources: (type, params)=> { + return Promise.resolve() { ... }; + }, + getResource: (type:string, id:string)=> { + return Promise.resolve() { ... }; + } , + setResource: (type:string, id:string, value:any)=> { + return Promise.resolve() { ... }; + }, + deleteResource: (type:string, id:string)=> { + return Promise.resolve() { ... }; ; + } + } + } +} +``` + +### Methods: + +The Server will dispatch requests to `/signalk/v1/api/resources/` to the methods defined in the resourceProvider interface. + +Each method must have a signature as defined in the interface and return a `Promise` containing the resource data or `null` if the operation was unsuccessful. + +--- +`GET` requests that are not for a specific resource will be dispatched to the `listResources` method passing the resource type and query data as parameters. + +Returns: Object listing resources by id. + +_Example: List all routes._ +```JAVASCRIPT +GET /signalk/v1/api/resources/routes + +listResources('routes', {}) + +returns { + "resource_id1": { ... }, + "resource_id2": { ... }, + ... + "resource_idn": { ... } +} +``` + +_Example: List routes within the bounded area._ +```JAVASCRIPT +GET /signalk/v1/api/resources/routes?bbox=5.4,25.7,6.9,31.2 + +listResources('routes', {bbox: '5.4,25.7,6.9,31.2'}) + +returns { + "resource_id1": { ... }, + "resource_id2": { ... } +} +``` + +`GET` requests for a specific resource will be dispatched to the `getResource` method passing the resource type and id as parameters. + +Returns: Object containing resourcesdata. + +_Example: Retrieve route._ +```JAVASCRIPT +GET /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a + +getResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a') + +returns { + "name": "route name", + ... + "feature": { ... } +} +``` +--- + +`PUT` requests for a specific resource will be dispatched to the `setResource` method passing the resource type, id and resource data as parameters. + +Returns: `true` on success, `null` on failure. + +_Example: Update route data._ +```JAVASCRIPT +PUT /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a {resource data} + +setResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) +``` + +`POST` requests will be dispatched to the `setResource` method passing the resource type, a generated id and resource data as parameters. + +Returns: `true` on success, `null` on failure. + +_Example: New route._ +```JAVASCRIPT +POST /signalk/v1/api/resources/routes/ {resource data} + +setResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) +``` +--- + +`DELETE` requests for a specific resource will be dispatched to the `deleteResource` method passing the resource type and id as parameters. + +Returns: `true` on success, `null` on failure. + +_Example: Delete route._ +```JAVASCRIPT +DELETE /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a {resource data} + +deleteResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a') +``` +--- + +### Plugin Startup: + +If your plugin provides the ability for the user to choose which resource types are handled, then the plugin will need to notify the server that the `types` attribute of the `resourceProvider` interface has been modified. + +The server exposes `resourcesApi` which has the following method: +```JAVASCRIPT +checkForProviders(rescan:boolean) +``` +which can be called within the plugin `start()` function with `rescan= true`. + +This will cause the server to `rescan` for resource providers and register the new list of resource types being handled. + +_Example:_ +```JAVASCRIPT +module.exports = function (app) { + let plugin= { + id: 'mypluginid', + name: 'My Resource Providerplugin', + start: (options, restart)=> { + ... + setTimeout( ()=> { app.resourcesApi.checkForProviders(true) }, 1000) + ... + }, + stop: ()=> { ... }, + ... + } +} +``` + diff --git a/SERVERPLUGINS.md b/SERVERPLUGINS.md index 72e8f7634..306921a4e 100644 --- a/SERVERPLUGINS.md +++ b/SERVERPLUGINS.md @@ -24,6 +24,8 @@ The plugin module must export a single `function(app)` that must return an objec To get started with SignalK plugin development, you can follow the following guide. +_Note: For plugins acting as a `provider` for one or more of the resource types listed in the Signal K specification (e.g. routes, waypoints, notes, regions or charts) there are additional requirementss which are detailed in __[RESOURCE_PROVIDER_PLUGINS.md](./RESOURCE_PROVIDER_PLUGINS.md)__._ + ### Project setup First, create a new directory and initialize a new module: From aacbc62e0cdc2504d9bc339a2dadb7c70951a646 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 14 Nov 2021 19:32:38 +1030 Subject: [PATCH 053/410] Add register / unregister --- RESOURCE_PROVIDER_PLUGINS.md | 258 ++++++++++++++++++++++------------- SERVERPLUGINS.md | 37 ++++- src/api/resources/index.ts | 28 +++- 3 files changed, 221 insertions(+), 102 deletions(-) diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index 64ac8276a..4b80ba853 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -4,31 +4,34 @@ This document should be read in conjunction with [SERVERPLUGINS.md](./SERVERPLUGINS.md) as it contains additional information regarding the development of plugins that facilitate the storage and retrieval of resource data (e.g. routes, waypoints, notes, regions and charts). -The Signal K Node server will handle all requests to the following paths: +Resource storage and retrieval is de-coupled from core server function to provide the flexibility to implement the appropriate resource storage solution for specific Signal K implementations. -`/signalk/v1/api/resources` -`/signalk/v1/api/resources/routes` -`/signalk/v1/api/resources/waypoints` -`/signalk/v1/api/resources/notes` -`/signalk/v1/api/resources/regions` -`/signalk/v1/api/resources/charts` +The Signal K Node server will pass requests made to the following paths to registered resource providers: +- `/signalk/v1/api/resources` +- `/signalk/v1/api/resources/routes` +- `/signalk/v1/api/resources/waypoints` +- `/signalk/v1/api/resources/notes` +- `/signalk/v1/api/resources/regions` +- `/signalk/v1/api/resources/charts` -This means all requests (GET, PUT, POST and DELETE) are captured and any supplied data is validated prior to being dispatched for processing. +Resource providers will receive request data via a `ResourceProvider` interface which they implement. It is the responsibility of the resource provider to persist resource data in storage (PUT, POST), retrieve the requested resources (GET) and remove resource entries (DELETE). -The server itself has no built-in functionality to save or retrieve resource data from storage, this is the responsibility of a `resource provider plugin`. +Resource data passed to the resource provider plugin has been validated by the server and can be considered ready for storage. -If there are no registered providers for the resource type for which the request is made, then no action is taken. - -This architecture de-couples the resource storage from core server function to provide flexibility to implement the appropriate storage solution for the various resource types for your Signal K implementation. ## Resource Providers A `resource provider plugin` is responsible for the storage and retrieval of resource data. -This allows the method for persisting resource data to be tailored to the Signal K implementation e.g. the local file system, a database service, cloud service, etc. -It is similar to any other Server Plugin except that it implements the __resourceProvider__ interface. +It should implement the necessary functions to: +- Persist each resource with its associated id +- Retrieve an individual resource with the supplied id +- Retrieve a list of resources that match the supplied qery criteria. + +Data is passed to and from the plugin via the methods defined in the __resourceProvider__ interface which the plugin must implement. -```JAVASCRIPT +_Definition: `resourceProvider` interface._ +```javascript resourceProvider: { types: [], methods: { @@ -40,50 +43,130 @@ resourceProvider: { } ``` -This interface exposes the following information to the server enabling it to direct requests to the plugin: -- `types`: The resource types for which requests should be directed to the plugin. These can be one or all of `routes, waypoints, notes, regions, charts`. -- `methods`: The methods to which the server dispatches requests. The plugin will implement these methods to perform the necessary save or retrieval operation. +This interface is used by the server to direct requests to the plugin. + +It contains the following attributes: +- `types`: An array containing the names of the resource types the plugin is a provider for. Names of the resource types are: `routes, waypoints, notes, regions, charts`. + +- `methods`: The methods to which the server dispatches requests. The plugin will implement these methods to perform the necessary save or retrieval operation. Each method returns a promise containing either resource data or `null` if an error is encountered. _Example: Plugin acting as resource provider for routes & waypoints._ -```JAVASCRIPT -let plugin= { +```javascript +module.exports = function (app) { + let plugin= { + id: 'mypluginid', + name: 'My Resource Providerplugin', + resourceProvider: { + types: ['routes','waypoints'], + methods: { + listResources: (type, params)=> { + return Promise.resolve() { ... }; + }, + getResource: (type:string, id:string)=> { + return Promise.resolve() { ... }; + } , + setResource: (type:string, id:string, value:any)=> { + return Promise.resolve() { ... }; + }, + deleteResource: (type:string, id:string)=> { + return Promise.resolve() { ... }; ; + } + } + }, + start: (options, restart)=> { + ... + app.resourceApi.register(this.resourceProvider); + }, + stop: ()=> { + app.resourceApi.unRegister(this.resourceProvider.types); + ... + } + } +} +``` + +--- + +### Plugin Startup - Registering the Resource Provider: + +To register your plugin as a resource provider the server's `resourcesApi.register()` function should be called within the plugin `start()` function passing the `resourceProvider` interface. + +This registers the resource types and the methods with the server so they are called when requests to resource paths are made. + +_Example:_ +```javascript +module.exports = function (app) { + let plugin= { id: 'mypluginid', name: 'My Resource Providerplugin', - start: (options, restart)=> { ... }, - stop: ()=> { ... }, resourceProvider: { - types: ['routes','waypoints'], - methods: { - listResources: (type, params)=> { - return Promise.resolve() { ... }; - }, - getResource: (type:string, id:string)=> { - return Promise.resolve() { ... }; - } , - setResource: (type:string, id:string, value:any)=> { - return Promise.resolve() { ... }; - }, - deleteResource: (type:string, id:string)=> { - return Promise.resolve() { ... }; ; - } + types: ['routes','waypoints'], + methods: { + listResources: (type, params)=> { + return Promise.resolve() { ... }; + }, + getResource: (type:string, id:string)=> { + return Promise.resolve() { ... }; + } , + setResource: (type:string, id:string, value:any)=> { + return Promise.resolve() { ... }; + }, + deleteResource: (type:string, id:string)=> { + return Promise.resolve() { ... }; ; } + } } + } + + plugin.start = function(options) { + ... + app.resourcesApi.register(plugin.resourceProvider); + } } ``` +--- -### Methods: +### Plugin Stop - Un-registering the Resource Provider: -The Server will dispatch requests to `/signalk/v1/api/resources/` to the methods defined in the resourceProvider interface. +When a resource provider plugin is disabled / stopped it should un-register as a provider so resource requests are not directed to it. This is done by calling the server's `resourcesApi.unRegister()` function passing `resourceProvider.types` within the plugin's `stop()` function. -Each method must have a signature as defined in the interface and return a `Promise` containing the resource data or `null` if the operation was unsuccessful. +_Example:_ +```javascript +module.exports = function (app) { + let plugin= { + id: 'mypluginid', + name: 'My Resource Providerplugin', + resourceProvider: { + types: ['routes','waypoints'], + methods: { ... } + } + } + plugin.stop = function(options) { + ... + app.resourcesApi.unRegister(plugin.resourceProvider.types); + } +} +``` --- -`GET` requests that are not for a specific resource will be dispatched to the `listResources` method passing the resource type and query data as parameters. -Returns: Object listing resources by id. +### Operation: + +The Server will dispatch requests made to `/signalk/v1/api/resources/` to the plugin's `resourceProvider.methods` for each resource type listed in `resourceProvider.types`. + +Each method defined in the plugin must have a signature as specified in the interface. Each method returns a `Promise` containing the resource data or `null` if an error occurrred. + + +### __List Resources:__ + +`GET` requests that are not for a specific resource will be dispatched to the `listResources` method passing the resource type and any query data as parameters. + +It is the responsibility of the resource provider plugin to filter the resources returned as per the supplied query parameters. + +`listResources()` should return a JSON object listing resources by id. _Example: List all routes._ -```JAVASCRIPT +```javascript GET /signalk/v1/api/resources/routes listResources('routes', {}) @@ -96,11 +179,11 @@ returns { } ``` -_Example: List routes within the bounded area._ -```JAVASCRIPT -GET /signalk/v1/api/resources/routes?bbox=5.4,25.7,6.9,31.2 +_Example: List waypoints within the bounded area._ +```javascript +GET /signalk/v1/api/resources/waypoints?bbox=5.4,25.7,6.9,31.2 -listResources('routes', {bbox: '5.4,25.7,6.9,31.2'}) +listResources('waypoints', {bbox: '5.4,25.7,6.9,31.2'}) returns { "resource_id1": { ... }, @@ -108,85 +191,66 @@ returns { } ``` +### __Get specific resource:__ + `GET` requests for a specific resource will be dispatched to the `getResource` method passing the resource type and id as parameters. -Returns: Object containing resourcesdata. +`getResource()` should returns a JSON object containing the resource data. _Example: Retrieve route._ -```JAVASCRIPT +```javascript GET /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a getResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a') returns { - "name": "route name", - ... + "name": "Name of the route", + "description": "Description of the route", + "distance": 18345, "feature": { ... } } ``` ---- -`PUT` requests for a specific resource will be dispatched to the `setResource` method passing the resource type, id and resource data as parameters. +### __Saving Resources:__ -Returns: `true` on success, `null` on failure. +`PUT` requests to a path containing the resource id are used to store data associated with the resource id. These will be dispatched to the `setResource` method passing the resource type, id and data as parameters. -_Example: Update route data._ -```JAVASCRIPT -PUT /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a {resource data} +`setResource() ` returns `true` on success and `null` on failure. -setResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) -``` - -`POST` requests will be dispatched to the `setResource` method passing the resource type, a generated id and resource data as parameters. - -Returns: `true` on success, `null` on failure. +_Example: Update / add waypoint with the supplied id._ +```javascript +PUT /signalk/v1/api/resources/waypoints/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a {} -_Example: New route._ -```JAVASCRIPT -POST /signalk/v1/api/resources/routes/ {resource data} +setResource('waypoints', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) -setResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) +returns true | null ``` ---- -`DELETE` requests for a specific resource will be dispatched to the `deleteResource` method passing the resource type and id as parameters. +`POST` requests to a resource path that do not contina the resource id will be dispatched to the `setResource` method passing the resource type, an id (generated by the server) and resource data as parameters. -Returns: `true` on success, `null` on failure. +`setResource() ` returns `true` on success and `null` on failure. -_Example: Delete route._ -```JAVASCRIPT -DELETE /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a {resource data} +_Example: New route record._ +```javascript +POST /signalk/v1/api/resources/routes {} + +setResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) -deleteResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a') +returns true | null ``` ---- -### Plugin Startup: +### __Deleting Resources:__ -If your plugin provides the ability for the user to choose which resource types are handled, then the plugin will need to notify the server that the `types` attribute of the `resourceProvider` interface has been modified. +`DELETE` requests to a path containing the resource id will be dispatched to the `deleteResource` method passing the resource type and id as parameters. -The server exposes `resourcesApi` which has the following method: -```JAVASCRIPT -checkForProviders(rescan:boolean) -``` -which can be called within the plugin `start()` function with `rescan= true`. +`deleteResource()` returns `true` on success, `null` on failure. -This will cause the server to `rescan` for resource providers and register the new list of resource types being handled. +_Example: Delete region with supplied id._ +```javascript +DELETE /signalk/v1/api/resources/regions/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a -_Example:_ -```JAVASCRIPT -module.exports = function (app) { - let plugin= { - id: 'mypluginid', - name: 'My Resource Providerplugin', - start: (options, restart)=> { - ... - setTimeout( ()=> { app.resourcesApi.checkForProviders(true) }, 1000) - ... - }, - stop: ()=> { ... }, - ... - } -} +deleteResource('regions', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a') + +returns true | null ``` diff --git a/SERVERPLUGINS.md b/SERVERPLUGINS.md index 306921a4e..729293d5c 100644 --- a/SERVERPLUGINS.md +++ b/SERVERPLUGINS.md @@ -24,7 +24,7 @@ The plugin module must export a single `function(app)` that must return an objec To get started with SignalK plugin development, you can follow the following guide. -_Note: For plugins acting as a `provider` for one or more of the resource types listed in the Signal K specification (e.g. routes, waypoints, notes, regions or charts) there are additional requirementss which are detailed in __[RESOURCE_PROVIDER_PLUGINS.md](./RESOURCE_PROVIDER_PLUGINS.md)__._ +_Note: For plugins acting as a provider for one or more of the resource types listed in the Signal K specification (e.g. routes, waypoints, notes, regions or charts) refer to __[RESOURCE_PROVIDER_PLUGINS.md](./RESOURCE_PROVIDER_PLUGINS.md)__ for details. ### Project setup @@ -700,6 +700,41 @@ app.registerDeltaInputHandler((delta, next) => { }) ``` +### `app.resourcesApi.register(provider)` + +If a plugin wants to act as a resource provider, it will need to register its provider methods during startup using this function. + +See [`RESOURCE_PROVIDER_PLUGINS.md`](./RESOURCE_PROVIDER_PLUGINS.md) for details. + + +```javascript +plugin.start = function(options) { + ... + // plugin_provider is the plugin's `ResourceProvider` interface. + app.resourcesApi.register(plugin_provider); +} + +``` + + + +### `app.resourcesApi.unRegister(resource_types)` + +When a resource provider plugin is disabled it will need to un-register its provider methods for the resource types it manages. This should be done in the plugin's `stop()` function. + +See [`RESOURCE_PROVIDER_PLUGINS.md`](./RESOURCE_PROVIDER_PLUGINS.md) for details. + + +```javascript +plugin.stop = function(options) { + // resource_types example: ['routes',waypoints'] + app.resourcesApi.unRegister(resource_types); + ... +} + +``` + + ### `app.setPluginStatus(msg)` Set the current status of the plugin. The `msg` should be a short message describing the current status of the plugin and will be displayed in the plugin configuration UI and the Dashboard. diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 8d453cbc9..4375f2301 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -55,9 +55,32 @@ export class Resources { this.initResourceRoutes() } + public register(provider:any) { + debug(`** Registering provider(s)....${provider?.types}`) + if(!provider ) { return } + if(provider.types && !Array.isArray(provider.types)) { return } + provider.types.forEach( (i:string)=>{ + if(!this.resProvider[i]) { + this.resProvider[i]= provider.methods + } + }) + debug(this.resProvider) + } + + public unRegister(resourceTypes:string[]) { + debug(`** Un-registering provider(s)....${resourceTypes}`) + if(!Array.isArray(resourceTypes)) { return } + resourceTypes.forEach( (i:string)=>{ + if(this.resProvider[i]) { + delete this.resProvider[i] + } + }) + debug(JSON.stringify(this.resProvider)) + } + public checkForProviders(rescan:boolean= false) { if(rescan || Object.keys(this.resProvider).length===0) { - debug('** Checking for providers....') + debug(`** Checking for providers....(rescan=${rescan})`) this.resProvider= {} this.resourceTypes.forEach( (rt:string)=> { this.resProvider[rt]= this.getResourceProviderFor(rt) @@ -68,7 +91,6 @@ export class Resources { public getResource(type:string, id:string) { debug(`** getResource(${type}, ${id})`) - this.checkForProviders() return this.actionResourceRequest({ method: 'GET', body: {}, @@ -82,7 +104,6 @@ export class Resources { // ** initialise handler for in-scope resource types ** private initResourceRoutes() { this.server.get(`${SIGNALK_API_PATH}/resources`, (req:any, res:any) => { - this.checkForProviders() // list all serviced paths under resources res.json(this.getResourcePaths()) }) @@ -155,7 +176,6 @@ export class Resources { } } - this.checkForProviders() let retReq= { method: req.method, body: req.body, From dff92197c92da5f26f758ad690a1ad32adffbbfc Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 15 Nov 2021 10:10:21 +1030 Subject: [PATCH 054/410] add constructor --- src/api/resources/index.ts | 67 +++++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 22 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 4375f2301..fba887dc8 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -15,6 +15,11 @@ interface ResourceRequest { } interface ResourceProvider { + types: Array + methods: ResourceProviderMethods +} + +interface ResourceProviderMethods { listResources: (type:string, query: {[key:string]:any})=> Promise getResource: (type:string, id:string)=> Promise setResource: (type:string, id:string, value:{[key:string]:any})=> Promise @@ -46,16 +51,22 @@ export class Resources { 'charts' ] - resProvider: {[key:string]: any}= {} + resProvider: {[key:string]: ResourceProviderMethods | null}= {} server: any - public start(app:any) { + constructor(app:any) { + this.start(app) + } + + // ** initialise resourcesApi ** + private start(app:any) { debug(`** Initialise ${SIGNALK_API_PATH}/resources path handler **`) this.server= app this.initResourceRoutes() } - public register(provider:any) { + // ** register resource provider ** + public register(provider:ResourceProvider) { debug(`** Registering provider(s)....${provider?.types}`) if(!provider ) { return } if(provider.types && !Array.isArray(provider.types)) { return } @@ -67,6 +78,7 @@ export class Resources { debug(this.resProvider) } + // ** un-register resource provider for the supplied types ** public unRegister(resourceTypes:string[]) { debug(`** Un-registering provider(s)....${resourceTypes}`) if(!Array.isArray(resourceTypes)) { return } @@ -76,19 +88,15 @@ export class Resources { } }) debug(JSON.stringify(this.resProvider)) - } - public checkForProviders(rescan:boolean= false) { - if(rescan || Object.keys(this.resProvider).length===0) { - debug(`** Checking for providers....(rescan=${rescan})`) - this.resProvider= {} - this.resourceTypes.forEach( (rt:string)=> { - this.resProvider[rt]= this.getResourceProviderFor(rt) - }) - debug(this.resProvider) - } + /** scan plugins in case there is more than one plugin that can service + * a particular resource type. **/ + debug('** RESCANNING **') + this.checkForProviders() + debug(JSON.stringify(this.resProvider)) } + // ** return resource with supplied type and id ** public getResource(type:string, id:string) { debug(`** getResource(${type}, ${id})`) return this.actionResourceRequest({ @@ -100,6 +108,20 @@ export class Resources { }) } + /** Scan plugins for resource providers and register them + * rescan= false: only add providers for types where no provider is registered + * rescan= true: clear providers for all types prior to commencing scan. + **/ + private checkForProviders(rescan:boolean= false) { + if(rescan) { this.resProvider= {} } + debug(`** Checking for providers....(rescan=${rescan})`) + this.resProvider= {} + this.resourceTypes.forEach( (rt:string)=> { + this.resProvider[rt]= this.getResourceProviderFor(rt) + }) + debug(this.resProvider) + + } // ** initialise handler for in-scope resource types ** private initResourceRoutes() { @@ -127,7 +149,7 @@ export class Resources { }) } - // ** return all paths serviced under ./resources *8 + // ** return all paths serviced under SIGNALK_API_PATH/resources ** private getResourcePaths(): {[key:string]:any} { let resPaths:{[key:string]:any}= {} Object.entries(this.resProvider).forEach( (p:any)=> { @@ -241,7 +263,7 @@ export class Resources { if(req.method==='GET') { let retVal: any if(!req.resourceId) { - retVal= await this.resProvider[req.resourceType].listResources(req.resourceType, req.query) + retVal= await this.resProvider[req.resourceType]?.listResources(req.resourceType, req.query) return (retVal) ? retVal : {statusCode: 404, message: `Error retrieving resources!` } @@ -249,7 +271,7 @@ export class Resources { if(!validate.uuid(req.resourceId)) { return {statusCode: 406, message: `Invalid resource id provided (${req.resourceId})` } } - retVal= await this.resProvider[req.resourceType].getResource(req.resourceType, req.resourceId) + retVal= await this.resProvider[req.resourceType]?.getResource(req.resourceType, req.resourceId) return (retVal) ? retVal : {statusCode: 404, message: `Resource not found (${req.resourceId})!` } @@ -266,7 +288,7 @@ export class Resources { req.method==='DELETE' || (req.method==='PUT' && typeof req.body.value!=='undefined' && req.body.value==null) ) { - let retVal= await this.resProvider[req.resourceType].deleteResource(req.resourceType, req.resourceId) + let retVal= await this.resProvider[req.resourceType]?.deleteResource(req.resourceType, req.resourceId) if(retVal){ this.sendDelta(req.resourceType, req.resourceId, null) return {statusCode: 200, message: `Resource (${req.resourceId}) deleted.`} @@ -289,7 +311,7 @@ export class Resources { } if(req.method==='POST') { let id= UUID_PREFIX + uuidv4() - let retVal= await this.resProvider[req.resourceType].setResource(req.resourceType, id, req.body.value) + let retVal= await this.resProvider[req.resourceType]?.setResource(req.resourceType, id, req.body.value) if(retVal){ this.sendDelta(req.resourceType, id, req.body.value) return {statusCode: 200, message: `Resource (${id}) saved.`} @@ -302,7 +324,7 @@ export class Resources { if(!req.resourceId) { return {statusCode: 406, message: `No resource id provided!` } } - let retVal= await this.resProvider[req.resourceType].setResource(req.resourceType, req.resourceId, req.body.value) + let retVal= await this.resProvider[req.resourceType]?.setResource(req.resourceType, req.resourceId, req.body.value) if(retVal){ this.sendDelta(req.resourceType, req.resourceId, req.body.value) return {statusCode: 200, message: `Resource (${req.resourceId}) updated.`} @@ -314,6 +336,7 @@ export class Resources { } } + // ** send delta message with resource PUT, POST, DELETE action result private sendDelta(type:string, id:string, value:any):void { debug(`** Sending Delta: resources.${type}.${id}`) this.server.handleMessage('signalk-resources', { @@ -330,10 +353,10 @@ export class Resources { }) } - // ** get reference to installed resource provider (plug-in). returns null if none found - private getResourceProviderFor(resType:string): ResourceProvider | null { + // ** Get provider methods for supplied resource type. Returns null if none found ** + private getResourceProviderFor(resType:string): ResourceProviderMethods | null { if(!this.server.plugins) { return null} - let pSource: ResourceProvider | null= null + let pSource: ResourceProviderMethods | null= null this.server.plugins.forEach((plugin:any)=> { if(typeof plugin.resourceProvider !== 'undefined') { pSource= plugin.resourceProvider.types.includes(resType) ? From 27fdd180340e4a0bc96e51a0b2a8e8a4df9652c8 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 15 Nov 2021 10:10:47 +1030 Subject: [PATCH 055/410] add getResource function --- SERVERPLUGINS.md | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/SERVERPLUGINS.md b/SERVERPLUGINS.md index 729293d5c..6f130d9d4 100644 --- a/SERVERPLUGINS.md +++ b/SERVERPLUGINS.md @@ -700,6 +700,30 @@ app.registerDeltaInputHandler((delta, next) => { }) ``` +### `app.resourcesApi.getResource(resource_type, resource_id)` + +Retrieve resource data for the supplied resource type and id. + + data for the full path of the directory where the plugin can persist its internal data, like data files.If a plugin wants to act as a resource provider, it will need to register its provider methods during startup using this function. + + + +```javascript +let myRoute= app.resourcesApi.getResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a'); +``` +Will return the route resource data or `null` if a route with the supplied id cannot be found. + +_Example:_ +```json +{ + "name": "Name of the route", + "description": "Description of the route", + "distance": 18345, + "feature": { ... } +} + +``` + ### `app.resourcesApi.register(provider)` If a plugin wants to act as a resource provider, it will need to register its provider methods during startup using this function. @@ -716,8 +740,6 @@ plugin.start = function(options) { ``` - - ### `app.resourcesApi.unRegister(resource_types)` When a resource provider plugin is disabled it will need to un-register its provider methods for the resource types it manages. This should be done in the plugin's `stop()` function. From 959b14714a95e6072cce318395251e5602f7ce80 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 15 Nov 2021 10:31:19 +1030 Subject: [PATCH 056/410] chore: fix formatting --- src/api/resources/index.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index fba887dc8..05c05761b 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -89,8 +89,7 @@ export class Resources { }) debug(JSON.stringify(this.resProvider)) - /** scan plugins in case there is more than one plugin that can service - * a particular resource type. **/ + //** scan plugins in case there is more than one plugin that can service a particular resource type. ** debug('** RESCANNING **') this.checkForProviders() debug(JSON.stringify(this.resProvider)) @@ -108,10 +107,9 @@ export class Resources { }) } - /** Scan plugins for resource providers and register them - * rescan= false: only add providers for types where no provider is registered - * rescan= true: clear providers for all types prior to commencing scan. - **/ + // Scan plugins for resource providers and register them + // rescan= false: only add providers for types where no provider is registered + // rescan= true: clear providers for all types prior to commencing scan. private checkForProviders(rescan:boolean= false) { if(rescan) { this.resProvider= {} } debug(`** Checking for providers....(rescan=${rescan})`) From ee7560d84a0d46e57414ddd7ff77be7a705706ef Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 15 Nov 2021 10:41:56 +1030 Subject: [PATCH 057/410] OpenApi descriptions --- src/api/resources/openApi.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/api/resources/openApi.json b/src/api/resources/openApi.json index d7a7f6e35..8eddcac32 100644 --- a/src/api/resources/openApi.json +++ b/src/api/resources/openApi.json @@ -29,15 +29,15 @@ } }, - "/resources/{resourceClass}": { + "/resources/{resourceType}": { "get": { "tags": ["resources"], "summary": "Retrieve resources", "parameters": [ { - "name": "resourceClass", + "name": "resourceType", "in": "path", - "description": "resource class", + "description": "Type of resources to retrieve. Valid values are: routes, waypoints, notes, regions, charts", "required": true, "schema": { "type": "string", @@ -68,8 +68,8 @@ } }, { - "in": "query", "name": "bbox", + "in": "query", "description": "Limit results to resources that fall within the bounded area defined as lower left (south west) and upper right (north east) coordinates [swlon,swlat,nelon,nelat]", "style": "form", "explode": false, From c79327c11b8a5aaa061c57cc18036f56730373f6 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 20 Nov 2021 13:46:56 +1030 Subject: [PATCH 058/410] add pluginId to register() function --- RESOURCE_PROVIDER_PLUGINS.md | 4 ++-- SERVERPLUGINS.md | 22 +++++++++++++++------- src/api/resources/index.ts | 4 +++- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index 4b80ba853..24331b387 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -75,7 +75,7 @@ module.exports = function (app) { }, start: (options, restart)=> { ... - app.resourceApi.register(this.resourceProvider); + app.resourceApi.register(this.id, this.resourceProvider); }, stop: ()=> { app.resourceApi.unRegister(this.resourceProvider.types); @@ -120,7 +120,7 @@ module.exports = function (app) { plugin.start = function(options) { ... - app.resourcesApi.register(plugin.resourceProvider); + app.resourcesApi.register(plugin.id, plugin.resourceProvider); } } ``` diff --git a/SERVERPLUGINS.md b/SERVERPLUGINS.md index 6f130d9d4..b6374fe6a 100644 --- a/SERVERPLUGINS.md +++ b/SERVERPLUGINS.md @@ -724,7 +724,7 @@ _Example:_ ``` -### `app.resourcesApi.register(provider)` +### `app.resourcesApi.register(pluginId, provider)` If a plugin wants to act as a resource provider, it will need to register its provider methods during startup using this function. @@ -732,12 +732,20 @@ See [`RESOURCE_PROVIDER_PLUGINS.md`](./RESOURCE_PROVIDER_PLUGINS.md) for details ```javascript -plugin.start = function(options) { - ... - // plugin_provider is the plugin's `ResourceProvider` interface. - app.resourcesApi.register(plugin_provider); -} - +module.exports = function (app) { + let plugin= { + id: 'mypluginid', + name: 'My Resource Providerplugin', + resourceProvider: { + types: ['routes','waypoints'], + methods: { ... } + } + start: function(options) { + ... + app.resourcesApi.register(this.id, this.resourceProvider); + } + ... + } ``` ### `app.resourcesApi.unRegister(resource_types)` diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 05c05761b..24568de8e 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -20,6 +20,7 @@ interface ResourceProvider { } interface ResourceProviderMethods { + pluginId: string listResources: (type:string, query: {[key:string]:any})=> Promise getResource: (type:string, id:string)=> Promise setResource: (type:string, id:string, value:{[key:string]:any})=> Promise @@ -66,12 +67,13 @@ export class Resources { } // ** register resource provider ** - public register(provider:ResourceProvider) { + public register(pluginId:string, provider:ResourceProvider) { debug(`** Registering provider(s)....${provider?.types}`) if(!provider ) { return } if(provider.types && !Array.isArray(provider.types)) { return } provider.types.forEach( (i:string)=>{ if(!this.resProvider[i]) { + provider.methods.pluginId= pluginId this.resProvider[i]= provider.methods } }) From 1c53058f6defd39a53b80c0a3608d15948141138 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 20 Nov 2021 14:17:32 +1030 Subject: [PATCH 059/410] add pluginId to unRegister function --- RESOURCE_PROVIDER_PLUGINS.md | 10 ++++----- SERVERPLUGINS.md | 24 +++++++++++++++------- src/api/resources/index.ts | 40 +++--------------------------------- 3 files changed, 25 insertions(+), 49 deletions(-) diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index 24331b387..e90b45d08 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -73,12 +73,12 @@ module.exports = function (app) { } } }, - start: (options, restart)=> { + start: (options)=> { ... app.resourceApi.register(this.id, this.resourceProvider); }, stop: ()=> { - app.resourceApi.unRegister(this.resourceProvider.types); + app.resourceApi.unRegister(this.id, this.resourceProvider.types); ... } } @@ -89,7 +89,7 @@ module.exports = function (app) { ### Plugin Startup - Registering the Resource Provider: -To register your plugin as a resource provider the server's `resourcesApi.register()` function should be called within the plugin `start()` function passing the `resourceProvider` interface. +To register your plugin as a resource provider the server's `resourcesApi.register()` function should be called within the plugin `start()` function passing the `resourceProvider` interface. This registers the resource types and the methods with the server so they are called when requests to resource paths are made. @@ -128,7 +128,7 @@ module.exports = function (app) { ### Plugin Stop - Un-registering the Resource Provider: -When a resource provider plugin is disabled / stopped it should un-register as a provider so resource requests are not directed to it. This is done by calling the server's `resourcesApi.unRegister()` function passing `resourceProvider.types` within the plugin's `stop()` function. +When a resource provider plugin is disabled it should un-register as a provider so resource requests are not directed to it. This is done by calling the server's `resourcesApi.unRegister()` function passing `resourceProvider.types` within the plugin's `stop()` function. _Example:_ ```javascript @@ -144,7 +144,7 @@ module.exports = function (app) { plugin.stop = function(options) { ... - app.resourcesApi.unRegister(plugin.resourceProvider.types); + app.resourcesApi.unRegister(plugin.id, plugin.resourceProvider.types); } } ``` diff --git a/SERVERPLUGINS.md b/SERVERPLUGINS.md index b6374fe6a..fa0c0f7b9 100644 --- a/SERVERPLUGINS.md +++ b/SERVERPLUGINS.md @@ -746,22 +746,32 @@ module.exports = function (app) { } ... } +} ``` -### `app.resourcesApi.unRegister(resource_types)` +### `app.resourcesApi.unRegister(pluginId, resource_types)` -When a resource provider plugin is disabled it will need to un-register its provider methods for the resource types it manages. This should be done in the plugin's `stop()` function. +When a resource provider plugin is disabled it will need to un-register its provider methods for all of the resource types it manages. This should be done in the plugin's `stop()` function. See [`RESOURCE_PROVIDER_PLUGINS.md`](./RESOURCE_PROVIDER_PLUGINS.md) for details. ```javascript -plugin.stop = function(options) { - // resource_types example: ['routes',waypoints'] - app.resourcesApi.unRegister(resource_types); - ... +module.exports = function (app) { + let plugin= { + id: 'mypluginid', + name: 'My Resource Providerplugin', + resourceProvider: { + types: ['routes','waypoints'], + methods: { ... } + } + ... + stop: function(options) { + app.resourcesApi.unRegister(this.id, this.resourceProvider.types); + ... + } + } } - ``` diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 24568de8e..ae2bb68c0 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -81,20 +81,15 @@ export class Resources { } // ** un-register resource provider for the supplied types ** - public unRegister(resourceTypes:string[]) { - debug(`** Un-registering provider(s)....${resourceTypes}`) + public unRegister(pluginId:string, resourceTypes:string[]) { + debug(`** Un-registering ${pluginId} provider(s)....${resourceTypes}`) if(!Array.isArray(resourceTypes)) { return } resourceTypes.forEach( (i:string)=>{ - if(this.resProvider[i]) { + if(this.resProvider[i] && this.resProvider[i]?.pluginId===pluginId) { delete this.resProvider[i] } }) debug(JSON.stringify(this.resProvider)) - - //** scan plugins in case there is more than one plugin that can service a particular resource type. ** - debug('** RESCANNING **') - this.checkForProviders() - debug(JSON.stringify(this.resProvider)) } // ** return resource with supplied type and id ** @@ -109,20 +104,6 @@ export class Resources { }) } - // Scan plugins for resource providers and register them - // rescan= false: only add providers for types where no provider is registered - // rescan= true: clear providers for all types prior to commencing scan. - private checkForProviders(rescan:boolean= false) { - if(rescan) { this.resProvider= {} } - debug(`** Checking for providers....(rescan=${rescan})`) - this.resProvider= {} - this.resourceTypes.forEach( (rt:string)=> { - this.resProvider[rt]= this.getResourceProviderFor(rt) - }) - debug(this.resProvider) - - } - // ** initialise handler for in-scope resource types ** private initResourceRoutes() { this.server.get(`${SIGNALK_API_PATH}/resources`, (req:any, res:any) => { @@ -353,19 +334,4 @@ export class Resources { }) } - // ** Get provider methods for supplied resource type. Returns null if none found ** - private getResourceProviderFor(resType:string): ResourceProviderMethods | null { - if(!this.server.plugins) { return null} - let pSource: ResourceProviderMethods | null= null - this.server.plugins.forEach((plugin:any)=> { - if(typeof plugin.resourceProvider !== 'undefined') { - pSource= plugin.resourceProvider.types.includes(resType) ? - plugin.resourceProvider.methods : - null - } - }) - debug(`** Checking for ${resType} provider.... ${pSource ? 'Found' : 'Not found'}`) - return pSource - } - } From cd94f5b61927f59632a852ccbbc185d9e715e3ae Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 20 Nov 2021 14:25:51 +1030 Subject: [PATCH 060/410] set plugin id as delta source --- src/api/resources/index.ts | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index ae2bb68c0..3238001f7 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -271,7 +271,11 @@ export class Resources { ) { let retVal= await this.resProvider[req.resourceType]?.deleteResource(req.resourceType, req.resourceId) if(retVal){ - this.sendDelta(req.resourceType, req.resourceId, null) + this.sendDelta( + this.resProvider[req.resourceType]?.pluginId as string, + req.resourceType, req.resourceId, + null + ) return {statusCode: 200, message: `Resource (${req.resourceId}) deleted.`} } else { @@ -294,7 +298,12 @@ export class Resources { let id= UUID_PREFIX + uuidv4() let retVal= await this.resProvider[req.resourceType]?.setResource(req.resourceType, id, req.body.value) if(retVal){ - this.sendDelta(req.resourceType, id, req.body.value) + this.sendDelta( + this.resProvider[req.resourceType]?.pluginId as string, + req.resourceType, + id, + req.body.value + ) return {statusCode: 200, message: `Resource (${id}) saved.`} } else { @@ -307,7 +316,12 @@ export class Resources { } let retVal= await this.resProvider[req.resourceType]?.setResource(req.resourceType, req.resourceId, req.body.value) if(retVal){ - this.sendDelta(req.resourceType, req.resourceId, req.body.value) + this.sendDelta( + this.resProvider[req.resourceType]?.pluginId as string, + req.resourceType, + req.resourceId, + req.body.value + ) return {statusCode: 200, message: `Resource (${req.resourceId}) updated.`} } else { @@ -318,9 +332,9 @@ export class Resources { } // ** send delta message with resource PUT, POST, DELETE action result - private sendDelta(type:string, id:string, value:any):void { + private sendDelta(providerId:string, type:string, id:string, value:any):void { debug(`** Sending Delta: resources.${type}.${id}`) - this.server.handleMessage('signalk-resources', { + this.server.handleMessage(providerId, { updates: [ { values: [ From 83b01816d830513b6ac43ca14bab3c2ce52e9329 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 20 Nov 2021 14:59:03 +1030 Subject: [PATCH 061/410] unregister only requires plugin id --- src/api/resources/index.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 3238001f7..9ce3cc88b 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -81,14 +81,15 @@ export class Resources { } // ** un-register resource provider for the supplied types ** - public unRegister(pluginId:string, resourceTypes:string[]) { - debug(`** Un-registering ${pluginId} provider(s)....${resourceTypes}`) - if(!Array.isArray(resourceTypes)) { return } - resourceTypes.forEach( (i:string)=>{ - if(this.resProvider[i] && this.resProvider[i]?.pluginId===pluginId) { + public unRegister(pluginId:string) { + if(!pluginId) { return } + debug(`** Un-registering ${pluginId} resource provider(s)....`) + for( let i in this.resProvider ) { + if(this.resProvider[i]?.pluginId===pluginId) { + debug(`** Un-registering ${i}....`) delete this.resProvider[i] } - }) + } debug(JSON.stringify(this.resProvider)) } From 28e04542a2cfaade3a936281bdede18e2d3d5e82 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 20 Nov 2021 14:59:16 +1030 Subject: [PATCH 062/410] update docs --- RESOURCE_PROVIDER_PLUGINS.md | 26 ++++++++++++++++---------- SERVERPLUGINS.md | 6 +++--- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index e90b45d08..a5b73fdf8 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -78,7 +78,7 @@ module.exports = function (app) { app.resourceApi.register(this.id, this.resourceProvider); }, stop: ()=> { - app.resourceApi.unRegister(this.id, this.resourceProvider.types); + app.resourceApi.unRegister(this.id); ... } } @@ -87,7 +87,7 @@ module.exports = function (app) { --- -### Plugin Startup - Registering the Resource Provider: +## Plugin Startup - Registering the Resource Provider: To register your plugin as a resource provider the server's `resourcesApi.register()` function should be called within the plugin `start()` function passing the `resourceProvider` interface. @@ -126,9 +126,9 @@ module.exports = function (app) { ``` --- -### Plugin Stop - Un-registering the Resource Provider: +## Plugin Stop - Un-registering the Resource Provider: -When a resource provider plugin is disabled it should un-register as a provider so resource requests are not directed to it. This is done by calling the server's `resourcesApi.unRegister()` function passing `resourceProvider.types` within the plugin's `stop()` function. +When a resource provider plugin is disabled it should un-register as a provider so resource requests are not directed to it. This is done by calling the server's `resourcesApi.unRegister()` function passing the `plugin.id` within the plugin's `stop()` function. _Example:_ ```javascript @@ -137,24 +137,30 @@ module.exports = function (app) { id: 'mypluginid', name: 'My Resource Providerplugin', resourceProvider: { - types: ['routes','waypoints'], + types: [ ... ], methods: { ... } } } plugin.stop = function(options) { + app.resourcesApi.unRegister(plugin.id); ... - app.resourcesApi.unRegister(plugin.id, plugin.resourceProvider.types); } } ``` --- -### Operation: +## Operation: + +The Server will dispatch requests made to: +- `/signalk/v1/api/resources/` + +OR +- the `resources API` endpoints -The Server will dispatch requests made to `/signalk/v1/api/resources/` to the plugin's `resourceProvider.methods` for each resource type listed in `resourceProvider.types`. +to the plugin's `resourceProvider.methods` for each of the resource types listed in `resourceProvider.types`. -Each method defined in the plugin must have a signature as specified in the interface. Each method returns a `Promise` containing the resource data or `null` if an error occurrred. +Each method defined in `resourceProvider.methods` must have a signature as specified in the __resourceProvider interface__. Each method returns a `Promise` containing the resultant resource data or `null` if an error occurrred or the operation is incomplete. ### __List Resources:__ @@ -191,7 +197,7 @@ returns { } ``` -### __Get specific resource:__ +### __Retrieve a specific resource:__ `GET` requests for a specific resource will be dispatched to the `getResource` method passing the resource type and id as parameters. diff --git a/SERVERPLUGINS.md b/SERVERPLUGINS.md index fa0c0f7b9..8a8e6613c 100644 --- a/SERVERPLUGINS.md +++ b/SERVERPLUGINS.md @@ -724,7 +724,7 @@ _Example:_ ``` -### `app.resourcesApi.register(pluginId, provider)` +### `app.resourcesApi.register(pluginId, resourceProvider)` If a plugin wants to act as a resource provider, it will need to register its provider methods during startup using this function. @@ -749,7 +749,7 @@ module.exports = function (app) { } ``` -### `app.resourcesApi.unRegister(pluginId, resource_types)` +### `app.resourcesApi.unRegister(pluginId)` When a resource provider plugin is disabled it will need to un-register its provider methods for all of the resource types it manages. This should be done in the plugin's `stop()` function. @@ -767,7 +767,7 @@ module.exports = function (app) { } ... stop: function(options) { - app.resourcesApi.unRegister(this.id, this.resourceProvider.types); + app.resourcesApi.unRegister(this.id); ... } } From 4c640e17c17f1d9cd2824b7f7f39177ce505d2e9 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 21 Nov 2021 11:26:43 +1030 Subject: [PATCH 063/410] add resource attribute req to query object --- src/api/resources/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 9ce3cc88b..9aaf0b86d 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -161,8 +161,12 @@ export class Resources { let p= req.params[0].split('/') let resType= (typeof req.params[0]!=='undefined') ? p[0] : '' let resId= p.length>1 ? p[1] : '' + let resAttrib= p.length>2 ? p.slice(2) : [] + req.query.resAttrib= resAttrib debug('** resType:', resType) debug('** resId:', resId) + debug('** resAttrib:', resAttrib) + debug('** req.params + attribs:', req.query) let apiMethod= (API_METHODS.includes(resType)) ? resType : null if(apiMethod) { From 7577a83f73392decaced7d8822d2a39294878370 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 21 Nov 2021 11:45:14 +1030 Subject: [PATCH 064/410] chore: update docs with query object examples. --- RESOURCE_PROVIDER_PLUGINS.md | 41 ++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index a5b73fdf8..2d20f1689 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -167,6 +167,16 @@ Each method defined in `resourceProvider.methods` must have a signature as speci `GET` requests that are not for a specific resource will be dispatched to the `listResources` method passing the resource type and any query data as parameters. +Query parameters are passed as an object conatining `key | value` pairs. + +_Example: GET /signalk/v1/api/resources/waypoints?bbox=5.4,25.7,6.9,31.2&distance=30000_ +```javascript +query= { + bbox: '5.4,25.7,6.9,31.2', + distance: 30000 +} +``` + It is the responsibility of the resource provider plugin to filter the resources returned as per the supplied query parameters. `listResources()` should return a JSON object listing resources by id. @@ -207,9 +217,16 @@ _Example: Retrieve route._ ```javascript GET /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a -getResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a') +getResource( + 'routes', + 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', + {} +) +``` -returns { +_Returns the result:_ +```json +{ "name": "Name of the route", "description": "Description of the route", "distance": 18345, @@ -217,6 +234,26 @@ returns { } ``` +A request for a resource attribute will pass the attibute path as an array in the query object with the key `resAttrib`. + +_Example: Get waypoint geometry._ +```javascript +GET /signalk/v1/api/resources/waypoints/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a/feature/geometry + +getResource( + 'waypoints', + 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', + { resAttrib: ['feature','geometry'] } +) +``` +_Returns the value of `geometry` attribute of the waypoint._ +```json +{ + "type": "Point", + "coordinates": [70.4,6.45] +} +``` + ### __Saving Resources:__ `PUT` requests to a path containing the resource id are used to store data associated with the resource id. These will be dispatched to the `setResource` method passing the resource type, id and data as parameters. From b0c29e06b374c49f162642ec46b7aedfb7c6269f Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 21 Nov 2021 15:40:58 +1030 Subject: [PATCH 065/410] chore: linted --- src/api/resources/index.ts | 662 ++++++++++++++++++--------------- src/api/resources/resources.ts | 344 +++++++++-------- src/api/resources/validate.ts | 173 +++++---- src/put.js | 4 +- 4 files changed, 652 insertions(+), 531 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 9aaf0b86d..7afc84dc6 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -1,356 +1,412 @@ import Debug from 'debug' import { v4 as uuidv4 } from 'uuid' -import { validate } from './validate' import { buildResource } from './resources' +import { validate } from './validate' const debug = Debug('signalk:resources') interface ResourceRequest { - method: 'GET' | 'PUT' | 'POST' | 'DELETE' - body: any - query: {[key:string]: any} - resourceType: string - resourceId: string, - apiMethod?: string | null + method: 'GET' | 'PUT' | 'POST' | 'DELETE' + body: any + query: { [key: string]: any } + resourceType: string + resourceId: string + apiMethod?: string | null } interface ResourceProvider { - types: Array - methods: ResourceProviderMethods + types: string[] + methods: ResourceProviderMethods } interface ResourceProviderMethods { - pluginId: string - listResources: (type:string, query: {[key:string]:any})=> Promise - getResource: (type:string, id:string)=> Promise - setResource: (type:string, id:string, value:{[key:string]:any})=> Promise - deleteResource: (type:string, id:string)=> Promise + pluginId: string + listResources: (type: string, query: { [key: string]: any }) => Promise + getResource: (type: string, id: string) => Promise + setResource: ( + type: string, + id: string, + value: { [key: string]: any } + ) => Promise + deleteResource: (type: string, id: string) => Promise } -const SIGNALK_API_PATH= `/signalk/v1/api` -const UUID_PREFIX= 'urn:mrn:signalk:uuid:' +const SIGNALK_API_PATH = `/signalk/v1/api` +const UUID_PREFIX = 'urn:mrn:signalk:uuid:' -const API_METHODS= [ - 'setWaypoint', - 'deleteWaypoint', - 'setRoute', - 'deleteRoute', - 'setNote', - 'deleteNote', - 'setRegion', - 'deleteRegion' +const API_METHODS = [ + 'setWaypoint', + 'deleteWaypoint', + 'setRoute', + 'deleteRoute', + 'setNote', + 'deleteNote', + 'setRegion', + 'deleteRegion' ] export class Resources { + resProvider: { [key: string]: ResourceProviderMethods | null } = {} + server: any - // ** in-scope resource types ** - private resourceTypes:Array= [ - 'routes', - 'waypoints', - 'notes', - 'regions', - 'charts' - ] + // ** in-scope resource types ** + private resourceTypes: string[] = [ + 'routes', + 'waypoints', + 'notes', + 'regions', + 'charts' + ] - resProvider: {[key:string]: ResourceProviderMethods | null}= {} - server: any + constructor(app: any) { + this.start(app) + } - constructor(app:any) { - this.start(app) + // ** register resource provider ** + register(pluginId: string, provider: ResourceProvider) { + debug(`** Registering provider(s)....${provider?.types}`) + if (!provider) { + return } - - // ** initialise resourcesApi ** - private start(app:any) { - debug(`** Initialise ${SIGNALK_API_PATH}/resources path handler **`) - this.server= app - this.initResourceRoutes() + if (provider.types && !Array.isArray(provider.types)) { + return } + provider.types.forEach((i: string) => { + if (!this.resProvider[i]) { + provider.methods.pluginId = pluginId + this.resProvider[i] = provider.methods + } + }) + debug(this.resProvider) + } - // ** register resource provider ** - public register(pluginId:string, provider:ResourceProvider) { - debug(`** Registering provider(s)....${provider?.types}`) - if(!provider ) { return } - if(provider.types && !Array.isArray(provider.types)) { return } - provider.types.forEach( (i:string)=>{ - if(!this.resProvider[i]) { - provider.methods.pluginId= pluginId - this.resProvider[i]= provider.methods - } - }) - debug(this.resProvider) + // ** un-register resource provider for the supplied types ** + unRegister(pluginId: string) { + if (!pluginId) { + return + } + debug(`** Un-registering ${pluginId} resource provider(s)....`) + for (const i in this.resProvider) { + if (this.resProvider[i]?.pluginId === pluginId) { + debug(`** Un-registering ${i}....`) + delete this.resProvider[i] + } } + debug(JSON.stringify(this.resProvider)) + } - // ** un-register resource provider for the supplied types ** - public unRegister(pluginId:string) { - if(!pluginId) { return } - debug(`** Un-registering ${pluginId} resource provider(s)....`) - for( let i in this.resProvider ) { - if(this.resProvider[i]?.pluginId===pluginId) { - debug(`** Un-registering ${i}....`) - delete this.resProvider[i] - } + // ** return resource with supplied type and id ** + getResource(type: string, id: string) { + debug(`** getResource(${type}, ${id})`) + return this.actionResourceRequest({ + method: 'GET', + body: {}, + query: {}, + resourceType: type, + resourceId: id + }) + } + + // ** initialise resourcesApi ** + private start(app: any) { + debug(`** Initialise ${SIGNALK_API_PATH}/resources path handler **`) + this.server = app + this.initResourceRoutes() + } + + // ** initialise handler for in-scope resource types ** + private initResourceRoutes() { + this.server.get(`${SIGNALK_API_PATH}/resources`, (req: any, res: any) => { + // list all serviced paths under resources + res.json(this.getResourcePaths()) + }) + this.server.use( + `${SIGNALK_API_PATH}/resources/*`, + async (req: any, res: any, next: any) => { + const result = this.parseResourceRequest(req) + if (result) { + const ar = await this.actionResourceRequest(result) + if (typeof ar.statusCode !== 'undefined') { + debug(`${JSON.stringify(ar)}`) + res.status = ar.statusCode + res.send(ar.message) + } else { + res.json(ar) + } + } else { + debug('** No provider found... calling next()...') + next() } - debug(JSON.stringify(this.resProvider)) - } + } + ) + } + + // ** return all paths serviced under SIGNALK_API_PATH/resources ** + private getResourcePaths(): { [key: string]: any } { + const resPaths: { [key: string]: any } = {} + Object.entries(this.resProvider).forEach((p: any) => { + if (p[1]) { + resPaths[p[0]] = `Path containing ${p[0]}, each named with a UUID` + } + }) + // check for other plugins servicing paths under ./resources + this.server._router.stack.forEach((i: any) => { + if (i.route && i.route.path && typeof i.route.path === 'string') { + if (i.route.path.indexOf(`${SIGNALK_API_PATH}/resources`) !== -1) { + const r = i.route.path.split('/') + if (r.length > 5 && !(r[5] in resPaths)) { + resPaths[ + r[5] + ] = `Path containing ${r[5]} resources (provided by plug-in)` + } + } + } + }) + return resPaths + } + + // ** parse api path request and return ResourceRequest object ** + private parseResourceRequest(req: any): ResourceRequest | undefined { + debug('** req.originalUrl:', req.originalUrl) + debug('** req.method:', req.method) + debug('** req.body:', req.body) + debug('** req.query:', req.query) + debug('** req.params:', req.params) + const p = req.params[0].split('/') + let resType = typeof req.params[0] !== 'undefined' ? p[0] : '' + const resId = p.length > 1 ? p[1] : '' + const resAttrib = p.length > 2 ? p.slice(2) : [] + req.query.resAttrib = resAttrib + debug('** resType:', resType) + debug('** resId:', resId) + debug('** resAttrib:', resAttrib) + debug('** req.params + attribs:', req.query) - // ** return resource with supplied type and id ** - public getResource(type:string, id:string) { - debug(`** getResource(${type}, ${id})`) - return this.actionResourceRequest({ - method: 'GET', - body: {}, - query: {}, - resourceType: type, - resourceId: id - }) + const apiMethod = API_METHODS.includes(resType) ? resType : null + if (apiMethod) { + if (apiMethod.toLowerCase().indexOf('waypoint') !== -1) { + resType = 'waypoints' + } + if (apiMethod.toLowerCase().indexOf('route') !== -1) { + resType = 'routes' + } + if (apiMethod.toLowerCase().indexOf('note') !== -1) { + resType = 'notes' + } + if (apiMethod.toLowerCase().indexOf('region') !== -1) { + resType = 'regions' + } } - // ** initialise handler for in-scope resource types ** - private initResourceRoutes() { - this.server.get(`${SIGNALK_API_PATH}/resources`, (req:any, res:any) => { - // list all serviced paths under resources - res.json(this.getResourcePaths()) - }) - this.server.use(`${SIGNALK_API_PATH}/resources/*`, async (req:any, res:any, next: any) => { - let result= this.parseResourceRequest(req) - if(result) { - let ar= await this.actionResourceRequest(result) - if(typeof ar.statusCode!== 'undefined'){ - debug(`${JSON.stringify(ar)}`) - res.status= ar.statusCode - res.send(ar.message) - } - else { - res.json(ar) - } - } - else { - debug('** No provider found... calling next()...') - next() - } - }) + const retReq = { + method: req.method, + body: req.body, + query: req.query, + resourceType: resType, + resourceId: resId, + apiMethod } - // ** return all paths serviced under SIGNALK_API_PATH/resources ** - private getResourcePaths(): {[key:string]:any} { - let resPaths:{[key:string]:any}= {} - Object.entries(this.resProvider).forEach( (p:any)=> { - if(p[1]) { resPaths[p[0]]= `Path containing ${p[0]}, each named with a UUID` } - }) - // check for other plugins servicing paths under ./resources - this.server._router.stack.forEach((i:any)=> { - if(i.route && i.route.path && typeof i.route.path==='string') { - if(i.route.path.indexOf(`${SIGNALK_API_PATH}/resources`)!==-1) { - let r= i.route.path.split('/') - if( r.length>5 && !(r[5] in resPaths) ) { - resPaths[r[5]]= `Path containing ${r[5]} resources (provided by plug-in)` - } - } - } - }) - return resPaths + if (this.resourceTypes.includes(resType) && this.resProvider[resType]) { + return retReq + } else { + debug('Invalid resource type or no provider for this type!') + return undefined } + } - // ** parse api path request and return ResourceRequest object ** - private parseResourceRequest(req:any):ResourceRequest | undefined { - debug('** req.originalUrl:', req.originalUrl) - debug('** req.method:', req.method) - debug('** req.body:', req.body) - debug('** req.query:', req.query) - debug('** req.params:', req.params) - let p= req.params[0].split('/') - let resType= (typeof req.params[0]!=='undefined') ? p[0] : '' - let resId= p.length>1 ? p[1] : '' - let resAttrib= p.length>2 ? p.slice(2) : [] - req.query.resAttrib= resAttrib - debug('** resType:', resType) - debug('** resId:', resId) - debug('** resAttrib:', resAttrib) - debug('** req.params + attribs:', req.query) - - let apiMethod= (API_METHODS.includes(resType)) ? resType : null - if(apiMethod) { - if(apiMethod.toLowerCase().indexOf('waypoint')!==-1) { - resType= 'waypoints' - } - if(apiMethod.toLowerCase().indexOf('route')!==-1) { - resType= 'routes' - } - if(apiMethod.toLowerCase().indexOf('note')!==-1) { - resType= 'notes' - } - if(apiMethod.toLowerCase().indexOf('region')!==-1) { - resType= 'regions' - } - } + // ** action an in-scope resource request ** + private async actionResourceRequest(req: ResourceRequest): Promise { + debug('********* action request *************') + debug(req) - let retReq= { - method: req.method, - body: req.body, - query: req.query, - resourceType: resType, - resourceId: resId, - apiMethod: apiMethod - } + // check for registered resource providers + if (!this.resProvider) { + return { statusCode: 501, message: `No Provider` } + } - if(this.resourceTypes.includes(resType) && this.resProvider[resType]) { - return retReq - } - else { - debug('Invalid resource type or no provider for this type!') - return undefined - } + if ( + !this.resourceTypes.includes(req.resourceType) || + !this.resProvider[req.resourceType] + ) { + return { statusCode: 501, message: `No Provider` } } - // ** action an in-scope resource request ** - private async actionResourceRequest (req:ResourceRequest):Promise { - debug('********* action request *************') - debug(req) - - // check for registered resource providers - if(!this.resProvider) { - return {statusCode: 501, message: `No Provider`} - } - - if(!this.resourceTypes.includes(req.resourceType) || !this.resProvider[req.resourceType]) { - return {statusCode: 501, message: `No Provider`} - } + // check for API method request + if (req.apiMethod && API_METHODS.includes(req.apiMethod)) { + debug(`API Method (${req.apiMethod})`) + req = this.transformApiRequest(req) + } - // check for API method request - if(req.apiMethod && API_METHODS.includes(req.apiMethod)) { - debug(`API Method (${req.apiMethod})`) - req= this.transformApiRequest(req) - } + return await this.execResourceRequest(req) + } - return await this.execResourceRequest(req) + // ** transform API request to ResourceRequest ** + private transformApiRequest(req: ResourceRequest): ResourceRequest { + if (req.apiMethod?.indexOf('delete') !== -1) { + req.method = 'DELETE' } - - // ** transform API request to ResourceRequest ** - private transformApiRequest(req: ResourceRequest):ResourceRequest { - if(req.apiMethod?.indexOf('delete')!==-1) { - req.method= 'DELETE' - } - if(req.apiMethod?.indexOf('set')!==-1) { - if(!req.body.id) { - req.method= 'POST' - } - else { - req.resourceId= req.body.id - } - req.body= { value: buildResource(req.resourceType, req.body) ?? {} } - } - return req + if (req.apiMethod?.indexOf('set') !== -1) { + if (!req.body.id) { + req.method = 'POST' + } else { + req.resourceId = req.body.id + } + req.body = { value: buildResource(req.resourceType, req.body) ?? {} } } + return req + } - // ** action an in-scope resource request ** - private async execResourceRequest (req:ResourceRequest):Promise { - debug('********* execute request *************') - debug(req) - if(req.method==='GET') { - let retVal: any - if(!req.resourceId) { - retVal= await this.resProvider[req.resourceType]?.listResources(req.resourceType, req.query) - return (retVal) ? - retVal : - {statusCode: 404, message: `Error retrieving resources!` } - } - if(!validate.uuid(req.resourceId)) { - return {statusCode: 406, message: `Invalid resource id provided (${req.resourceId})` } - } - retVal= await this.resProvider[req.resourceType]?.getResource(req.resourceType, req.resourceId) - return (retVal) ? - retVal : - {statusCode: 404, message: `Resource not found (${req.resourceId})!` } + // ** action an in-scope resource request ** + private async execResourceRequest(req: ResourceRequest): Promise { + debug('********* execute request *************') + debug(req) + if (req.method === 'GET') { + let retVal: any + if (!req.resourceId) { + retVal = await this.resProvider[req.resourceType]?.listResources( + req.resourceType, + req.query + ) + return retVal + ? retVal + : { statusCode: 404, message: `Error retrieving resources!` } + } + if (!validate.uuid(req.resourceId)) { + return { + statusCode: 406, + message: `Invalid resource id provided (${req.resourceId})` } + } + retVal = await this.resProvider[req.resourceType]?.getResource( + req.resourceType, + req.resourceId + ) + return retVal + ? retVal + : { + statusCode: 404, + message: `Resource not found (${req.resourceId})!` + } + } - if(req.method==='DELETE' || req.method==='PUT') { - if(!req.resourceId) { - return {statusCode: 406, value: `No resource id provided!` } - } - if(!validate.uuid(req.resourceId)) { - return {statusCode: 406, message: `Invalid resource id provided (${req.resourceId})!` } - } - if( - req.method==='DELETE' || - (req.method==='PUT' && typeof req.body.value!=='undefined' && req.body.value==null) - ) { - let retVal= await this.resProvider[req.resourceType]?.deleteResource(req.resourceType, req.resourceId) - if(retVal){ - this.sendDelta( - this.resProvider[req.resourceType]?.pluginId as string, - req.resourceType, req.resourceId, - null - ) - return {statusCode: 200, message: `Resource (${req.resourceId}) deleted.`} - } - else { - return {statusCode: 400, message: `Error deleting resource (${req.resourceId})!` } - } - } - + if (req.method === 'DELETE' || req.method === 'PUT') { + if (!req.resourceId) { + return { statusCode: 406, value: `No resource id provided!` } + } + if (!validate.uuid(req.resourceId)) { + return { + statusCode: 406, + message: `Invalid resource id provided (${req.resourceId})!` } - - if(req.method==='POST' || req.method==='PUT') { - // check for supplied value - if( typeof req.body.value==='undefined' || req.body.value==null) { - return {statusCode: 406, message: `No resource data supplied!`} - } - // validate supplied request data - if(!validate.resource(req.resourceType, req.body.value)) { - return {statusCode: 406, message: `Invalid resource data supplied!`} - } - if(req.method==='POST') { - let id= UUID_PREFIX + uuidv4() - let retVal= await this.resProvider[req.resourceType]?.setResource(req.resourceType, id, req.body.value) - if(retVal){ - this.sendDelta( - this.resProvider[req.resourceType]?.pluginId as string, - req.resourceType, - id, - req.body.value - ) - return {statusCode: 200, message: `Resource (${id}) saved.`} - } - else { - return {statusCode: 400, message: `Error saving resource (${id})!` } - } - } - if(req.method==='PUT') { - if(!req.resourceId) { - return {statusCode: 406, message: `No resource id provided!` } - } - let retVal= await this.resProvider[req.resourceType]?.setResource(req.resourceType, req.resourceId, req.body.value) - if(retVal){ - this.sendDelta( - this.resProvider[req.resourceType]?.pluginId as string, - req.resourceType, - req.resourceId, - req.body.value - ) - return {statusCode: 200, message: `Resource (${req.resourceId}) updated.`} - } - else { - return {statusCode: 400, message: `Error updating resource (${req.resourceId})!` } - } - } + } + if ( + req.method === 'DELETE' || + (req.method === 'PUT' && + typeof req.body.value !== 'undefined' && + req.body.value == null) + ) { + const retVal = await this.resProvider[req.resourceType]?.deleteResource( + req.resourceType, + req.resourceId + ) + if (retVal) { + this.sendDelta( + this.resProvider[req.resourceType]?.pluginId as string, + req.resourceType, + req.resourceId, + null + ) + return { + statusCode: 200, + message: `Resource (${req.resourceId}) deleted.` + } + } else { + return { + statusCode: 400, + message: `Error deleting resource (${req.resourceId})!` + } } + } } - // ** send delta message with resource PUT, POST, DELETE action result - private sendDelta(providerId:string, type:string, id:string, value:any):void { - debug(`** Sending Delta: resources.${type}.${id}`) - this.server.handleMessage(providerId, { - updates: [ - { - values: [ - { - path: `resources.${type}.${id}`, - value: value - } - ] - } - ] - }) + if (req.method === 'POST' || req.method === 'PUT') { + // check for supplied value + if (typeof req.body.value === 'undefined' || req.body.value == null) { + return { statusCode: 406, message: `No resource data supplied!` } + } + // validate supplied request data + if (!validate.resource(req.resourceType, req.body.value)) { + return { statusCode: 406, message: `Invalid resource data supplied!` } + } + if (req.method === 'POST') { + const id = UUID_PREFIX + uuidv4() + const retVal = await this.resProvider[req.resourceType]?.setResource( + req.resourceType, + id, + req.body.value + ) + if (retVal) { + this.sendDelta( + this.resProvider[req.resourceType]?.pluginId as string, + req.resourceType, + id, + req.body.value + ) + return { statusCode: 200, message: `Resource (${id}) saved.` } + } else { + return { statusCode: 400, message: `Error saving resource (${id})!` } + } + } + if (req.method === 'PUT') { + if (!req.resourceId) { + return { statusCode: 406, message: `No resource id provided!` } + } + const retVal = await this.resProvider[req.resourceType]?.setResource( + req.resourceType, + req.resourceId, + req.body.value + ) + if (retVal) { + this.sendDelta( + this.resProvider[req.resourceType]?.pluginId as string, + req.resourceType, + req.resourceId, + req.body.value + ) + return { + statusCode: 200, + message: `Resource (${req.resourceId}) updated.` + } + } else { + return { + statusCode: 400, + message: `Error updating resource (${req.resourceId})!` + } + } + } } + } + // ** send delta message with resource PUT, POST, DELETE action result + private sendDelta( + providerId: string, + type: string, + id: string, + value: any + ): void { + debug(`** Sending Delta: resources.${type}.${id}`) + this.server.handleMessage(providerId, { + updates: [ + { + values: [ + { + path: `resources.${type}.${id}`, + value + } + ] + } + ] + }) + } } diff --git a/src/api/resources/resources.ts b/src/api/resources/resources.ts index e76cce323..1857c23cd 100644 --- a/src/api/resources/resources.ts +++ b/src/api/resources/resources.ts @@ -2,177 +2,215 @@ import { getDistance, isValidCoordinate } from 'geolib' import ngeohash from 'ngeohash' // ** build resource item ** -export const buildResource= (resType:string, data:any):any=> { - if(resType==='routes') { return buildRoute(data) } - if(resType==='waypoints') { return buildWaypoint(data) } - if(resType==='notes') { return buildNote(data) } - if(resType==='regions') { return buildRegion(data) } +export const buildResource = (resType: string, data: any): any => { + if (resType === 'routes') { + return buildRoute(data) + } + if (resType === 'waypoints') { + return buildWaypoint(data) + } + if (resType === 'notes') { + return buildNote(data) + } + if (resType === 'regions') { + return buildRegion(data) + } } // ** build route -const buildRoute= (rData:any):any=> { - let rte:any= { - feature: { - type: 'Feature', - geometry:{ - type: 'LineString', - coordinates :[] - }, - properties:{} - } - } - if(typeof rData.name !== 'undefined') { - rte.name= rData.name - rte.feature.properties.name= rData.name - } - if(typeof rData.description !== 'undefined') { - rte.description= rData.description - rte.feature.properties.description= rData.description - } - if(typeof rData.attributes !== 'undefined') { - Object.assign(rte.feature.properties, rData.attributes) - } +const buildRoute = (rData: any): any => { + const rte: any = { + feature: { + type: 'Feature', + geometry: { + type: 'LineString', + coordinates: [] + }, + properties: {} + } + } + if (typeof rData.name !== 'undefined') { + rte.name = rData.name + rte.feature.properties.name = rData.name + } + if (typeof rData.description !== 'undefined') { + rte.description = rData.description + rte.feature.properties.description = rData.description + } + if (typeof rData.attributes !== 'undefined') { + Object.assign(rte.feature.properties, rData.attributes) + } - if(typeof rData.points === 'undefined') { return null } - if(!Array.isArray(rData.points)) { return null } - let isValid:boolean= true; - rData.points.forEach( (p:any)=> { - if(!isValidCoordinate(p)) { isValid= false } - }) - if(!isValid) { return null } - rData.points.forEach( (p:any)=> { - rte.feature.geometry.coordinates.push([p.longitude, p.latitude]) - }) + if (typeof rData.points === 'undefined') { + return null + } + if (!Array.isArray(rData.points)) { + return null + } + let isValid: boolean = true + rData.points.forEach((p: any) => { + if (!isValidCoordinate(p)) { + isValid = false + } + }) + if (!isValid) { + return null + } + rte.feature.geometry.coordinates = rData.points.map((p: any) => { + return [p.longitude, p.latitude] + }) - rte.distance= 0 - for(let i=0; i { - let wpt:any= { - position: { - latitude: 0, - longitude: 0 - }, - feature: { - type: 'Feature', - geometry:{ - type: 'Point', - coordinates :[] - }, - properties:{} - } - } - if(typeof rData.name !== 'undefined') { - wpt.feature.properties.name= rData.name - } - if(typeof rData.description !== 'undefined') { - wpt.feature.properties.description= rData.description - } - if(typeof rData.attributes !== 'undefined') { - Object.assign(wpt.feature.properties, rData.attributes) - } +const buildWaypoint = (rData: any): any => { + const wpt: any = { + position: { + latitude: 0, + longitude: 0 + }, + feature: { + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [] + }, + properties: {} + } + } + if (typeof rData.name !== 'undefined') { + wpt.feature.properties.name = rData.name + } + if (typeof rData.description !== 'undefined') { + wpt.feature.properties.description = rData.description + } + if (typeof rData.attributes !== 'undefined') { + Object.assign(wpt.feature.properties, rData.attributes) + } + + if (typeof rData.position === 'undefined') { + return null + } + if (!isValidCoordinate(rData.position)) { + return null + } - if(typeof rData.position === 'undefined') { return null } - if(!isValidCoordinate(rData.position)) { return null } - - wpt.position= rData.position - wpt.feature.geometry.coordinates= [rData.position.longitude, rData.position.latitude] + wpt.position = rData.position + wpt.feature.geometry.coordinates = [ + rData.position.longitude, + rData.position.latitude + ] - return wpt + return wpt } // ** build note -const buildNote= (rData:any):any=> { - let note:any= {} - if(typeof rData.title !== 'undefined') { - note.title= rData.title - note.feature.properties.title= rData.title - } - if(typeof rData.description !== 'undefined') { - note.description= rData.description - note.feature.properties.description= rData.description - } - if(typeof rData.position === 'undefined' - && typeof rData.region === 'undefined' - && typeof rData.geohash === 'undefined') { return null } +const buildNote = (rData: any): any => { + const note: any = {} + if (typeof rData.title !== 'undefined') { + note.title = rData.title + note.feature.properties.title = rData.title + } + if (typeof rData.description !== 'undefined') { + note.description = rData.description + note.feature.properties.description = rData.description + } + if ( + typeof rData.position === 'undefined' && + typeof rData.region === 'undefined' && + typeof rData.geohash === 'undefined' + ) { + return null + } - if(typeof rData.position !== 'undefined') { - if(!isValidCoordinate(rData.position)) { return null } - note.position= rData.position - } - if(typeof rData.region !== 'undefined') { - note.region= rData.region - } - if(typeof rData.geohash !== 'undefined') { - note.geohash= rData.geohash - } - if(typeof rData.url !== 'undefined') { - note.url= rData.url - } - if(typeof rData.mimeType !== 'undefined') { - note.mimeType= rData.mimeType - } - - return note + if (typeof rData.position !== 'undefined') { + if (!isValidCoordinate(rData.position)) { + return null + } + note.position = rData.position + } + if (typeof rData.region !== 'undefined') { + note.region = rData.region + } + if (typeof rData.geohash !== 'undefined') { + note.geohash = rData.geohash + } + if (typeof rData.url !== 'undefined') { + note.url = rData.url + } + if (typeof rData.mimeType !== 'undefined') { + note.mimeType = rData.mimeType + } + + return note } // ** build region -const buildRegion= (rData:any):any=> { - let reg:any= { - feature: { - type: 'Feature', - geometry:{ - type: 'Polygon', - coordinates :[] - }, - properties:{} - } - } - let coords:Array<[number,number]>= [] +const buildRegion = (rData: any): any => { + const reg: any = { + feature: { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [] + }, + properties: {} + } + } + let coords: Array<[number, number]> = [] - if(typeof rData.name !== 'undefined') { - reg.feature.properties.name= rData.name - } - if(typeof rData.description !== 'undefined') { - reg.feature.properties.description= rData.description - } - if(typeof rData.attributes !== 'undefined') { - Object.assign(reg.feature.properties, rData.attributes) - } + if (typeof rData.name !== 'undefined') { + reg.feature.properties.name = rData.name + } + if (typeof rData.description !== 'undefined') { + reg.feature.properties.description = rData.description + } + if (typeof rData.attributes !== 'undefined') { + Object.assign(reg.feature.properties, rData.attributes) + } - if(typeof rData.points=== 'undefined' && rData.geohash=== 'undefined') { return null } - if(typeof rData.geohash!== 'undefined') { - reg.geohash= rData.geohash - - let bounds= ngeohash.decode_bbox(rData.geohash) - coords= [ - [bounds[1], bounds[0]], - [bounds[3], bounds[0]], - [bounds[3], bounds[2]], - [bounds[1], bounds[2]], - [bounds[1], bounds[0]], - ] - reg.feature.geometry.coordinates.push(coords) - } - if(typeof rData.points!== 'undefined' && coords.length===0 ) { - if(!Array.isArray(rData.points)) { return null } - let isValid:boolean= true; - rData.points.forEach( (p:any)=> { - if(!isValidCoordinate(p)) { isValid= false } - }) - if(!isValid) { return null } - rData.points.forEach( (p:any)=> { - coords.push([p.longitude, p.latitude]) - }) - reg.feature.geometry.coordinates.push(coords) + if (typeof rData.points === 'undefined' && rData.geohash === 'undefined') { + return null + } + if (typeof rData.geohash !== 'undefined') { + reg.geohash = rData.geohash + + const bounds = ngeohash.decode_bbox(rData.geohash) + coords = [ + [bounds[1], bounds[0]], + [bounds[3], bounds[0]], + [bounds[3], bounds[2]], + [bounds[1], bounds[2]], + [bounds[1], bounds[0]] + ] + reg.feature.geometry.coordinates.push(coords) + } + if (typeof rData.points !== 'undefined' && coords.length === 0) { + if (!Array.isArray(rData.points)) { + return null + } + let isValid: boolean = true + rData.points.forEach((p: any) => { + if (!isValidCoordinate(p)) { + isValid = false + } + }) + if (!isValid) { + return null } - - return reg + coords = rData.points.map((p: any) => { + return [p.longitude, p.latitude] + }) + reg.feature.geometry.coordinates.push(coords) + } + + return reg } diff --git a/src/api/resources/validate.ts b/src/api/resources/validate.ts index 840d944b2..0d8d6cffe 100644 --- a/src/api/resources/validate.ts +++ b/src/api/resources/validate.ts @@ -1,97 +1,124 @@ -import geoJSON from 'geojson-validation'; +import geoJSON from 'geojson-validation' import { isValidCoordinate } from 'geolib' -export const validate= { - resource: (type:string, value:any):boolean=> { - if(!type) { return false } - switch(type) { - case 'routes': - return validateRoute(value); - break - case 'waypoints': - return validateWaypoint(value) - break - case 'notes': - return validateNote(value) - break; - case 'regions': - return validateRegion(value) - break - default: - return true - } - }, - - // ** returns true if id is a valid Signal K UUID ** - uuid: (id:string): boolean=> { - let uuid= RegExp("^urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$") - return uuid.test(id) +export const validate = { + resource: (type: string, value: any): boolean => { + if (!type) { + return false + } + switch (type) { + case 'routes': + return validateRoute(value) + break + case 'waypoints': + return validateWaypoint(value) + break + case 'notes': + return validateNote(value) + break + case 'regions': + return validateRegion(value) + break + default: + return true } + }, + + // ** returns true if id is a valid Signal K UUID ** + uuid: (id: string): boolean => { + const uuid = RegExp( + '^urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$' + ) + return uuid.test(id) + } } // ** validate route data -const validateRoute= (r:any):boolean=> { - if(r.start) { - let l= r.start.split('/') - if(!validate.uuid(l[l.length-1])) { return false } +const validateRoute = (r: any): boolean => { + if (r.start) { + const l = r.start.split('/') + if (!validate.uuid(l[l.length - 1])) { + return false } - if(r.end) { - let l= r.end.split('/') - if(!validate.uuid(l[l.length-1])) { return false } + } + if (r.end) { + const l = r.end.split('/') + if (!validate.uuid(l[l.length - 1])) { + return false } - try { - if(!r.feature || !geoJSON.valid(r.feature)) { - return false - } - if(r.feature.geometry.type!=='LineString') { return false } + } + try { + if (!r.feature || !geoJSON.valid(r.feature)) { + return false } - catch(err) { return false } - return true + if (r.feature.geometry.type !== 'LineString') { + return false + } + } catch (err) { + return false + } + return true } // ** validate waypoint data -const validateWaypoint= (r:any):boolean=> { - if(typeof r.position === 'undefined') { return false } - if(!isValidCoordinate(r.position)) { - return false +const validateWaypoint = (r: any): boolean => { + if (typeof r.position === 'undefined') { + return false + } + if (!isValidCoordinate(r.position)) { + return false + } + try { + if (!r.feature || !geoJSON.valid(r.feature)) { + return false } - try { - if(!r.feature || !geoJSON.valid(r.feature)) { - return false - } - if(r.feature.geometry.type!=='Point') { return false } + if (r.feature.geometry.type !== 'Point') { + return false } - catch(e) { return false } - return true + } catch (e) { + return false + } + return true } // ** validate note data -const validateNote= (r:any):boolean=> { - if(!r.region && !r.position && !r.geohash ) { return false } - if(typeof r.position!== 'undefined') { - if(!isValidCoordinate(r.position)) { - return false - } +const validateNote = (r: any): boolean => { + if (!r.region && !r.position && !r.geohash) { + return false + } + if (typeof r.position !== 'undefined') { + if (!isValidCoordinate(r.position)) { + return false } - if(r.region) { - let l= r.region.split('/') - if(!validate.uuid(l[l.length-1])) { return false } + } + if (r.region) { + const l = r.region.split('/') + if (!validate.uuid(l[l.length - 1])) { + return false } - return true + } + return true } // ** validate region data -const validateRegion= (r:any):boolean=> { - if(!r.geohash && !r.feature) { return false } - if(r.feature ) { - try { - if(!geoJSON.valid(r.feature)) { return false } - if(r.feature.geometry.type!=='Polygon' && r.feature.geometry.type!=='MultiPolygon') { - return false - } - } - catch(e) { return false } +const validateRegion = (r: any): boolean => { + if (!r.geohash && !r.feature) { + return false + } + if (r.feature) { + try { + if (!geoJSON.valid(r.feature)) { + return false + } + if ( + r.feature.geometry.type !== 'Polygon' && + r.feature.geometry.type !== 'MultiPolygon' + ) { + return false + } + } catch (e) { + return false } - return true + } + return true } - diff --git a/src/put.js b/src/put.js index c77bb3177..e6e14a965 100644 --- a/src/put.js +++ b/src/put.js @@ -31,10 +31,10 @@ module.exports = { app.put(apiPathPrefix + '*', function(req, res, next) { // ** ignore resources paths ** - if(req.path.split('/')[4]==='resources') { + if (req.path.split('/')[4] === 'resources') { next() return - } + } let path = String(req.path).replace(apiPathPrefix, '') const value = req.body From 446e0bac870bbef4d8a828293332e6ee7847c7c3 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 21 Nov 2021 15:59:33 +1030 Subject: [PATCH 066/410] chore: return value descriptions to show a Promise --- RESOURCE_PROVIDER_PLUGINS.md | 48 +++++++++++++++++----------------- src/api/resources/resources.ts | 15 +++++++---- 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index 2d20f1689..4e2c7312d 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -179,7 +179,7 @@ query= { It is the responsibility of the resource provider plugin to filter the resources returned as per the supplied query parameters. -`listResources()` should return a JSON object listing resources by id. +`listResources()` returns a Promise containing a JSON object listing resources by id. _Example: List all routes._ ```javascript @@ -187,34 +187,34 @@ GET /signalk/v1/api/resources/routes listResources('routes', {}) -returns { +returns Promise<{ "resource_id1": { ... }, "resource_id2": { ... }, ... "resource_idn": { ... } -} +}> ``` _Example: List waypoints within the bounded area._ -```javascript +```typescript GET /signalk/v1/api/resources/waypoints?bbox=5.4,25.7,6.9,31.2 listResources('waypoints', {bbox: '5.4,25.7,6.9,31.2'}) -returns { +returns Promise<{ "resource_id1": { ... }, "resource_id2": { ... } -} +}> ``` ### __Retrieve a specific resource:__ `GET` requests for a specific resource will be dispatched to the `getResource` method passing the resource type and id as parameters. -`getResource()` should returns a JSON object containing the resource data. +`getResource()` returns a Promise containing a JSON object with the resource data. _Example: Retrieve route._ -```javascript +```typescript GET /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a getResource( @@ -224,14 +224,14 @@ getResource( ) ``` -_Returns the result:_ -```json -{ +_Returns a Promise containing the resource data:_ +```typescript +Promise<{ "name": "Name of the route", "description": "Description of the route", "distance": 18345, "feature": { ... } -} +}> ``` A request for a resource attribute will pass the attibute path as an array in the query object with the key `resAttrib`. @@ -246,19 +246,19 @@ getResource( { resAttrib: ['feature','geometry'] } ) ``` -_Returns the value of `geometry` attribute of the waypoint._ -```json -{ +_Returns a Promise containing the value of `geometry` attribute of the waypoint._ +```typescript +Promise<{ "type": "Point", "coordinates": [70.4,6.45] -} +}> ``` ### __Saving Resources:__ `PUT` requests to a path containing the resource id are used to store data associated with the resource id. These will be dispatched to the `setResource` method passing the resource type, id and data as parameters. -`setResource() ` returns `true` on success and `null` on failure. +`setResource() ` returns Promise on success and Promise on failure. _Example: Update / add waypoint with the supplied id._ ```javascript @@ -266,34 +266,34 @@ PUT /signalk/v1/api/resources/waypoints/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25- setResource('waypoints', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) -returns true | null +returns Promise ``` `POST` requests to a resource path that do not contina the resource id will be dispatched to the `setResource` method passing the resource type, an id (generated by the server) and resource data as parameters. -`setResource() ` returns `true` on success and `null` on failure. +`setResource()` returns `true` on success and `null` on failure. _Example: New route record._ -```javascript +```typescript POST /signalk/v1/api/resources/routes {} setResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) -returns true | null +returns Promise ``` ### __Deleting Resources:__ `DELETE` requests to a path containing the resource id will be dispatched to the `deleteResource` method passing the resource type and id as parameters. -`deleteResource()` returns `true` on success, `null` on failure. +`deleteResource()` returns `true` on success and `null` on failure. _Example: Delete region with supplied id._ -```javascript +```typescript DELETE /signalk/v1/api/resources/regions/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a deleteResource('regions', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a') -returns true | null +returns Promise ``` diff --git a/src/api/resources/resources.ts b/src/api/resources/resources.ts index 1857c23cd..3b79bc271 100644 --- a/src/api/resources/resources.ts +++ b/src/api/resources/resources.ts @@ -1,7 +1,7 @@ import { getDistance, isValidCoordinate } from 'geolib' import ngeohash from 'ngeohash' -// ** build resource item ** + export const buildResource = (resType: string, data: any): any => { if (resType === 'routes') { return buildRoute(data) @@ -17,7 +17,6 @@ export const buildResource = (resType: string, data: any): any => { } } -// ** build route const buildRoute = (rData: any): any => { const rte: any = { feature: { @@ -70,7 +69,7 @@ const buildRoute = (rData: any): any => { return rte } -// ** build waypoint + const buildWaypoint = (rData: any): any => { const wpt: any = { position: { @@ -112,7 +111,7 @@ const buildWaypoint = (rData: any): any => { return wpt } -// ** build note + const buildNote = (rData: any): any => { const note: any = {} if (typeof rData.title !== 'undefined') { @@ -153,7 +152,7 @@ const buildNote = (rData: any): any => { return note } -// ** build region + const buildRegion = (rData: any): any => { const reg: any = { feature: { @@ -206,6 +205,12 @@ const buildRegion = (rData: any): any => { if (!isValid) { return null } + if ( + rData.points[0].latitude !== rData.points[rData.points.length-1].latitude && + rData.points[0].longitude !== rData.points[rData.points.length-1].longitude + ) { + rData.points.push( rData.points[0]) + } coords = rData.points.map((p: any) => { return [p.longitude, p.latitude] }) From 3d70fd1f25c72fcfd694180967de04f5c8581117 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 21 Nov 2021 16:44:07 +1030 Subject: [PATCH 067/410] specify SignalKResourceType --- src/api/resources/index.ts | 44 ++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 7afc84dc6..d73919678 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -14,8 +14,10 @@ interface ResourceRequest { apiMethod?: string | null } +type SignalKResourceType= 'routes' | 'waypoints' |'notes' |'regions' |'charts' + interface ResourceProvider { - types: string[] + types: SignalKResourceType[] methods: ResourceProviderMethods } @@ -49,8 +51,8 @@ export class Resources { resProvider: { [key: string]: ResourceProviderMethods | null } = {} server: any - // ** in-scope resource types ** - private resourceTypes: string[] = [ + // in-scope resource types + private resourceTypes: SignalKResourceType[] = [ 'routes', 'waypoints', 'notes', @@ -62,7 +64,7 @@ export class Resources { this.start(app) } - // ** register resource provider ** + // register plugin with supplied id as resource provider register(pluginId: string, provider: ResourceProvider) { debug(`** Registering provider(s)....${provider?.types}`) if (!provider) { @@ -80,22 +82,22 @@ export class Resources { debug(this.resProvider) } - // ** un-register resource provider for the supplied types ** + // un-register plugin with supplied id as resource provider unRegister(pluginId: string) { if (!pluginId) { return } debug(`** Un-registering ${pluginId} resource provider(s)....`) - for (const i in this.resProvider) { - if (this.resProvider[i]?.pluginId === pluginId) { - debug(`** Un-registering ${i}....`) - delete this.resProvider[i] + for (const resourceType in this.resProvider) { + if (this.resProvider[resourceType]?.pluginId === pluginId) { + debug(`** Un-registering ${resourceType}....`) + delete this.resProvider[resourceType] } } debug(JSON.stringify(this.resProvider)) } - // ** return resource with supplied type and id ** + // Return resource with supplied type and id getResource(type: string, id: string) { debug(`** getResource(${type}, ${id})`) return this.actionResourceRequest({ @@ -107,17 +109,17 @@ export class Resources { }) } - // ** initialise resourcesApi ** + // initialise resourcesApi private start(app: any) { debug(`** Initialise ${SIGNALK_API_PATH}/resources path handler **`) this.server = app this.initResourceRoutes() } - // ** initialise handler for in-scope resource types ** + // initialise handler for in-scope resource types private initResourceRoutes() { + // list all serviced paths under resources this.server.get(`${SIGNALK_API_PATH}/resources`, (req: any, res: any) => { - // list all serviced paths under resources res.json(this.getResourcePaths()) }) this.server.use( @@ -141,7 +143,7 @@ export class Resources { ) } - // ** return all paths serviced under SIGNALK_API_PATH/resources ** + // return all paths serviced under SIGNALK_API_PATH/resources private getResourcePaths(): { [key: string]: any } { const resPaths: { [key: string]: any } = {} Object.entries(this.resProvider).forEach((p: any) => { @@ -165,7 +167,7 @@ export class Resources { return resPaths } - // ** parse api path request and return ResourceRequest object ** + // parse api path request and return ResourceRequest object private parseResourceRequest(req: any): ResourceRequest | undefined { debug('** req.originalUrl:', req.originalUrl) debug('** req.method:', req.method) @@ -215,7 +217,7 @@ export class Resources { } } - // ** action an in-scope resource request ** + // action an in-scope resource request private async actionResourceRequest(req: ResourceRequest): Promise { debug('********* action request *************') debug(req) @@ -241,7 +243,7 @@ export class Resources { return await this.execResourceRequest(req) } - // ** transform API request to ResourceRequest ** + // transform API request to ResourceRequest private transformApiRequest(req: ResourceRequest): ResourceRequest { if (req.apiMethod?.indexOf('delete') !== -1) { req.method = 'DELETE' @@ -257,7 +259,7 @@ export class Resources { return req } - // ** action an in-scope resource request ** + // action an in-scope resource request private async execResourceRequest(req: ResourceRequest): Promise { debug('********* execute request *************') debug(req) @@ -331,14 +333,14 @@ export class Resources { } if (req.method === 'POST' || req.method === 'PUT') { - // check for supplied value if (typeof req.body.value === 'undefined' || req.body.value == null) { return { statusCode: 406, message: `No resource data supplied!` } } - // validate supplied request data + if (!validate.resource(req.resourceType, req.body.value)) { return { statusCode: 406, message: `Invalid resource data supplied!` } } + if (req.method === 'POST') { const id = UUID_PREFIX + uuidv4() const retVal = await this.resProvider[req.resourceType]?.setResource( @@ -388,7 +390,7 @@ export class Resources { } } - // ** send delta message with resource PUT, POST, DELETE action result + // Send delta message. Used by resource PUT, POST, DELETE actions private sendDelta( providerId: string, type: string, From 8d2cb846a7e58536790c9c013496d578e083618e Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 21 Nov 2021 17:45:53 +1030 Subject: [PATCH 068/410] move interfaces to server-api --- src/api/resources/index.ts | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index d73919678..ec92cb81b 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -3,20 +3,9 @@ import { v4 as uuidv4 } from 'uuid' import { buildResource } from './resources' import { validate } from './validate' -const debug = Debug('signalk:resources') - -interface ResourceRequest { - method: 'GET' | 'PUT' | 'POST' | 'DELETE' - body: any - query: { [key: string]: any } - resourceType: string - resourceId: string - apiMethod?: string | null -} - -type SignalKResourceType= 'routes' | 'waypoints' |'notes' |'regions' |'charts' +/*export type SignalKResourceType= 'routes' | 'waypoints' |'notes' |'regions' |'charts' -interface ResourceProvider { +export interface ResourceProvider { types: SignalKResourceType[] methods: ResourceProviderMethods } @@ -31,6 +20,19 @@ interface ResourceProviderMethods { value: { [key: string]: any } ) => Promise deleteResource: (type: string, id: string) => Promise +}*/ + +import { SignalKResourceType, ResourceProvider, ResourceProviderMethods } from '@signalk/server-api' + +const debug = Debug('signalk:resources') + +interface ResourceRequest { + method: 'GET' | 'PUT' | 'POST' | 'DELETE' + body: any + query: { [key: string]: any } + resourceType: string + resourceId: string + apiMethod?: string | null } const SIGNALK_API_PATH = `/signalk/v1/api` @@ -48,8 +50,8 @@ const API_METHODS = [ ] export class Resources { - resProvider: { [key: string]: ResourceProviderMethods | null } = {} - server: any + private resProvider: { [key: string]: ResourceProviderMethods | null } = {} + private server: any // in-scope resource types private resourceTypes: SignalKResourceType[] = [ @@ -200,7 +202,7 @@ export class Resources { } } - const retReq = { + const retReq:any = { method: req.method, body: req.body, query: req.query, @@ -228,7 +230,7 @@ export class Resources { } if ( - !this.resourceTypes.includes(req.resourceType) || + !this.resourceTypes.includes(req.resourceType as SignalKResourceType) || !this.resProvider[req.resourceType] ) { return { statusCode: 501, message: `No Provider` } From f14e9749543eeb13498e22cb15533ff3e65c97da Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 22 Nov 2021 10:29:39 +1030 Subject: [PATCH 069/410] cleanup express route handling --- src/api/resources/index.ts | 139 ++++++++++++++++++++----------------- 1 file changed, 74 insertions(+), 65 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index ec92cb81b..8c980fa6b 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -3,25 +3,6 @@ import { v4 as uuidv4 } from 'uuid' import { buildResource } from './resources' import { validate } from './validate' -/*export type SignalKResourceType= 'routes' | 'waypoints' |'notes' |'regions' |'charts' - -export interface ResourceProvider { - types: SignalKResourceType[] - methods: ResourceProviderMethods -} - -interface ResourceProviderMethods { - pluginId: string - listResources: (type: string, query: { [key: string]: any }) => Promise - getResource: (type: string, id: string) => Promise - setResource: ( - type: string, - id: string, - value: { [key: string]: any } - ) => Promise - deleteResource: (type: string, id: string) => Promise -}*/ - import { SignalKResourceType, ResourceProvider, ResourceProviderMethods } from '@signalk/server-api' const debug = Debug('signalk:resources') @@ -30,7 +11,7 @@ interface ResourceRequest { method: 'GET' | 'PUT' | 'POST' | 'DELETE' body: any query: { [key: string]: any } - resourceType: string + resourceType: SignalKResourceType resourceId: string apiMethod?: string | null } @@ -66,7 +47,6 @@ export class Resources { this.start(app) } - // register plugin with supplied id as resource provider register(pluginId: string, provider: ResourceProvider) { debug(`** Registering provider(s)....${provider?.types}`) if (!provider) { @@ -84,7 +64,6 @@ export class Resources { debug(this.resProvider) } - // un-register plugin with supplied id as resource provider unRegister(pluginId: string) { if (!pluginId) { return @@ -99,8 +78,7 @@ export class Resources { debug(JSON.stringify(this.resProvider)) } - // Return resource with supplied type and id - getResource(type: string, id: string) { + getResource(type: SignalKResourceType, id: string) { debug(`** getResource(${type}, ${id})`) return this.actionResourceRequest({ method: 'GET', @@ -111,21 +89,60 @@ export class Resources { }) } - // initialise resourcesApi private start(app: any) { debug(`** Initialise ${SIGNALK_API_PATH}/resources path handler **`) this.server = app this.initResourceRoutes() } - // initialise handler for in-scope resource types private initResourceRoutes() { // list all serviced paths under resources this.server.get(`${SIGNALK_API_PATH}/resources`, (req: any, res: any) => { res.json(this.getResourcePaths()) }) + + this.server.get( + `${SIGNALK_API_PATH}/resources/:resourceType/:resourceId/*`, + async (req: any, res: any, next: any) => { + const result = this.parseResourceRequest(req) + if (result) { + const ar = await this.actionResourceRequest(result) + if (typeof ar.statusCode !== 'undefined') { + debug(`${JSON.stringify(ar)}`) + res.status = ar.statusCode + res.send(ar.message) + } else { + res.json(ar) + } + } else { + debug('** No provider found... calling next()...') + next() + } + } + ) + this.server.use( - `${SIGNALK_API_PATH}/resources/*`, + `${SIGNALK_API_PATH}/resources/:resourceType/:resourceId`, + async (req: any, res: any, next: any) => { + const result = this.parseResourceRequest(req) + if (result) { + const ar = await this.actionResourceRequest(result) + if (typeof ar.statusCode !== 'undefined') { + debug(`${JSON.stringify(ar)}`) + res.status = ar.statusCode + res.send(ar.message) + } else { + res.json(ar) + } + } else { + debug('** No provider found... calling next()...') + next() + } + } + ) + + this.server.use( + `${SIGNALK_API_PATH}/resources/:resourceType`, async (req: any, res: any, next: any) => { const result = this.parseResourceRequest(req) if (result) { @@ -145,7 +162,6 @@ export class Resources { ) } - // return all paths serviced under SIGNALK_API_PATH/resources private getResourcePaths(): { [key: string]: any } { const resPaths: { [key: string]: any } = {} Object.entries(this.resProvider).forEach((p: any) => { @@ -169,57 +185,53 @@ export class Resources { return resPaths } - // parse api path request and return ResourceRequest object private parseResourceRequest(req: any): ResourceRequest | undefined { - debug('** req.originalUrl:', req.originalUrl) + debug('********* parse request *************') debug('** req.method:', req.method) debug('** req.body:', req.body) debug('** req.query:', req.query) debug('** req.params:', req.params) - const p = req.params[0].split('/') - let resType = typeof req.params[0] !== 'undefined' ? p[0] : '' - const resId = p.length > 1 ? p[1] : '' - const resAttrib = p.length > 2 ? p.slice(2) : [] - req.query.resAttrib = resAttrib - debug('** resType:', resType) - debug('** resId:', resId) - debug('** resAttrib:', resAttrib) - debug('** req.params + attribs:', req.query) - - const apiMethod = API_METHODS.includes(resType) ? resType : null - if (apiMethod) { - if (apiMethod.toLowerCase().indexOf('waypoint') !== -1) { - resType = 'waypoints' + + const resReq:any = { + method: req.method, + body: req.body, + query: req.query, + resourceType: req.params.resourceType ?? null, + resourceId: req.params.resourceId ?? null, + apiMethod: API_METHODS.includes(req.params.resourceType) ? req.params.resourceType : null + } + + if (resReq.apiMethod) { + if (resReq.apiMethod.toLowerCase().indexOf('waypoint') !== -1) { + resReq.resourceType = 'waypoints' } - if (apiMethod.toLowerCase().indexOf('route') !== -1) { - resType = 'routes' + if (resReq.apiMethod.toLowerCase().indexOf('route') !== -1) { + resReq.resourceType = 'routes' } - if (apiMethod.toLowerCase().indexOf('note') !== -1) { - resType = 'notes' + if (resReq.apiMethod.toLowerCase().indexOf('note') !== -1) { + resReq.resourceType = 'notes' } - if (apiMethod.toLowerCase().indexOf('region') !== -1) { - resType = 'regions' + if (resReq.apiMethod.toLowerCase().indexOf('region') !== -1) { + resReq.resourceType = 'regions' } + } else { + const resAttrib = req.params['0'] ? req.params['0'].split('/') : [] + req.query.attrib = resAttrib } - const retReq:any = { - method: req.method, - body: req.body, - query: req.query, - resourceType: resType, - resourceId: resId, - apiMethod - } + debug('** resReq:', resReq) - if (this.resourceTypes.includes(resType) && this.resProvider[resType]) { - return retReq + if ( + this.resourceTypes.includes(resReq.resourceType) && + this.resProvider[resReq.resourceType] + ) { + return resReq } else { debug('Invalid resource type or no provider for this type!') return undefined } } - // action an in-scope resource request private async actionResourceRequest(req: ResourceRequest): Promise { debug('********* action request *************') debug(req) @@ -230,7 +242,7 @@ export class Resources { } if ( - !this.resourceTypes.includes(req.resourceType as SignalKResourceType) || + !this.resourceTypes.includes(req.resourceType) || !this.resProvider[req.resourceType] ) { return { statusCode: 501, message: `No Provider` } @@ -245,7 +257,6 @@ export class Resources { return await this.execResourceRequest(req) } - // transform API request to ResourceRequest private transformApiRequest(req: ResourceRequest): ResourceRequest { if (req.apiMethod?.indexOf('delete') !== -1) { req.method = 'DELETE' @@ -261,7 +272,6 @@ export class Resources { return req } - // action an in-scope resource request private async execResourceRequest(req: ResourceRequest): Promise { debug('********* execute request *************') debug(req) @@ -392,7 +402,6 @@ export class Resources { } } - // Send delta message. Used by resource PUT, POST, DELETE actions private sendDelta( providerId: string, type: string, From 091e9c162caeac957bddc25251054e0e35fc1aec Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 22 Nov 2021 10:33:00 +1030 Subject: [PATCH 070/410] add ResourceProvider types to server-api --- packages/server-api/src/index.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages/server-api/src/index.ts b/packages/server-api/src/index.ts index 7b9489495..2ccfc0bae 100644 --- a/packages/server-api/src/index.ts +++ b/packages/server-api/src/index.ts @@ -3,6 +3,26 @@ import { PropertyValues, PropertyValuesCallback } from './propertyvalues' export { PropertyValue, PropertyValues, PropertyValuesCallback } from './propertyvalues' + +export type SignalKResourceType= 'routes' | 'waypoints' |'notes' |'regions' |'charts' + +export interface ResourceProviderMethods { + pluginId: string + listResources: (type: string, query: { [key: string]: any }) => Promise + getResource: (type: string, id: string) => Promise + setResource: ( + type: string, + id: string, + value: { [key: string]: any } + ) => Promise + deleteResource: (type: string, id: string) => Promise +} + +export interface ResourceProvider { + types: SignalKResourceType[] + methods: ResourceProviderMethods +} + type Unsubscribe = () => {} export interface PropertyValuesEmitter { emitPropertyValue: (name: string, value: any) => void @@ -54,4 +74,5 @@ export interface Plugin { registerWithRouter?: (router: IRouter) => void signalKApiRoutes?: (router: IRouter) => IRouter enabledByDefault?: boolean + resourceProvider: ResourceProvider } From 76fa9285615a0b386717e8a221c794a99c50ad31 Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 21 Nov 2021 10:21:25 +0200 Subject: [PATCH 071/410] refactor: use Express types --- src/api/resources/index.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 8c980fa6b..d277e7fe1 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -4,6 +4,7 @@ import { buildResource } from './resources' import { validate } from './validate' import { SignalKResourceType, ResourceProvider, ResourceProviderMethods } from '@signalk/server-api' +import { Application, Handler, NextFunction, Request, Response } from 'express' const debug = Debug('signalk:resources') @@ -30,9 +31,13 @@ const API_METHODS = [ 'deleteRegion' ] +// FIXME use types from https://github.com/SignalK/signalk-server/pull/1358 +interface ResourceApplication extends Application { + handleMessage: any +} export class Resources { private resProvider: { [key: string]: ResourceProviderMethods | null } = {} - private server: any + private server: ResourceApplication // in-scope resource types private resourceTypes: SignalKResourceType[] = [ @@ -43,7 +48,8 @@ export class Resources { 'charts' ] - constructor(app: any) { + constructor(app: ResourceApplication) { + this.server = app this.start(app) } @@ -97,7 +103,7 @@ export class Resources { private initResourceRoutes() { // list all serviced paths under resources - this.server.get(`${SIGNALK_API_PATH}/resources`, (req: any, res: any) => { + this.server.get(`${SIGNALK_API_PATH}/resources`, (req: Request, res: Response) => { res.json(this.getResourcePaths()) }) From 875e1559898f46e3430504867704c963a09e3b4d Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 22 Nov 2021 15:16:48 +1030 Subject: [PATCH 072/410] fix type --- src/api/resources/resources.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/api/resources/resources.ts b/src/api/resources/resources.ts index 3b79bc271..756610774 100644 --- a/src/api/resources/resources.ts +++ b/src/api/resources/resources.ts @@ -1,8 +1,9 @@ +import { SignalKResourceType } from '@signalk/server-api' import { getDistance, isValidCoordinate } from 'geolib' import ngeohash from 'ngeohash' -export const buildResource = (resType: string, data: any): any => { +export const buildResource = (resType: SignalKResourceType, data: any): any => { if (resType === 'routes') { return buildRoute(data) } From 41f47bafca1b26ec17ba2bddd6a07ee20bf5cfd1 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 22 Nov 2021 16:18:02 +1030 Subject: [PATCH 073/410] chore: lint --- src/api/resources/resources.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/api/resources/resources.ts b/src/api/resources/resources.ts index 756610774..6a45f736c 100644 --- a/src/api/resources/resources.ts +++ b/src/api/resources/resources.ts @@ -2,7 +2,6 @@ import { SignalKResourceType } from '@signalk/server-api' import { getDistance, isValidCoordinate } from 'geolib' import ngeohash from 'ngeohash' - export const buildResource = (resType: SignalKResourceType, data: any): any => { if (resType === 'routes') { return buildRoute(data) @@ -70,7 +69,6 @@ const buildRoute = (rData: any): any => { return rte } - const buildWaypoint = (rData: any): any => { const wpt: any = { position: { @@ -112,7 +110,6 @@ const buildWaypoint = (rData: any): any => { return wpt } - const buildNote = (rData: any): any => { const note: any = {} if (typeof rData.title !== 'undefined') { @@ -153,7 +150,6 @@ const buildNote = (rData: any): any => { return note } - const buildRegion = (rData: any): any => { const reg: any = { feature: { @@ -207,10 +203,12 @@ const buildRegion = (rData: any): any => { return null } if ( - rData.points[0].latitude !== rData.points[rData.points.length-1].latitude && - rData.points[0].longitude !== rData.points[rData.points.length-1].longitude + rData.points[0].latitude !== + rData.points[rData.points.length - 1].latitude && + rData.points[0].longitude !== + rData.points[rData.points.length - 1].longitude ) { - rData.points.push( rData.points[0]) + rData.points.push(rData.points[0]) } coords = rData.points.map((p: any) => { return [p.longitude, p.latitude] From d7467526aad244eb2ab400451ead18308a92d0d8 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 22 Nov 2021 17:04:43 +1030 Subject: [PATCH 074/410] chore: update return type --- SERVERPLUGINS.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/SERVERPLUGINS.md b/SERVERPLUGINS.md index 8a8e6613c..8fbcb6737 100644 --- a/SERVERPLUGINS.md +++ b/SERVERPLUGINS.md @@ -704,6 +704,9 @@ app.registerDeltaInputHandler((delta, next) => { Retrieve resource data for the supplied resource type and id. +This method invokes the `registered Resource Provider` for the supplied `resource_type` and returns a __resovled Promise__ containing the route resource data if successful or +a __rejected Promise__ containing an Error object if unsuccessful. + data for the full path of the directory where the plugin can persist its internal data, like data files.If a plugin wants to act as a resource provider, it will need to register its provider methods during startup using this function. @@ -711,9 +714,7 @@ Retrieve resource data for the supplied resource type and id. ```javascript let myRoute= app.resourcesApi.getResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a'); ``` -Will return the route resource data or `null` if a route with the supplied id cannot be found. - -_Example:_ +_Returns resolved Promise containing:_ ```json { "name": "Name of the route", From 73ee9a5827c2534d3bc47e00fe235c31dcc9b3bc Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 22 Nov 2021 17:05:39 +1030 Subject: [PATCH 075/410] Use Express routing params for processing requests --- src/api/resources/index.ts | 541 ++++++++++++++++++------------------- 1 file changed, 264 insertions(+), 277 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index d277e7fe1..f0e1f204f 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -3,7 +3,11 @@ import { v4 as uuidv4 } from 'uuid' import { buildResource } from './resources' import { validate } from './validate' -import { SignalKResourceType, ResourceProvider, ResourceProviderMethods } from '@signalk/server-api' +import { + ResourceProvider, + ResourceProviderMethods, + SignalKResourceType +} from '@signalk/server-api' import { Application, Handler, NextFunction, Request, Response } from 'express' const debug = Debug('signalk:resources') @@ -33,7 +37,7 @@ const API_METHODS = [ // FIXME use types from https://github.com/SignalK/signalk-server/pull/1358 interface ResourceApplication extends Application { - handleMessage: any + handleMessage: (id: string, data: any) => void } export class Resources { private resProvider: { [key: string]: ResourceProviderMethods | null } = {} @@ -84,15 +88,12 @@ export class Resources { debug(JSON.stringify(this.resProvider)) } - getResource(type: SignalKResourceType, id: string) { - debug(`** getResource(${type}, ${id})`) - return this.actionResourceRequest({ - method: 'GET', - body: {}, - query: {}, - resourceType: type, - resourceId: id - }) + getResource(resType: SignalKResourceType, resId: string) { + debug(`** getResource(${resType}, ${resId})`) + if (!this.checkForProvider(resType)) { + return Promise.reject(new Error(`No provider for ${resType}`)) + } + return this.resProvider[resType]?.getResource(resType, resId) } private start(app: any) { @@ -103,66 +104,265 @@ export class Resources { private initResourceRoutes() { // list all serviced paths under resources - this.server.get(`${SIGNALK_API_PATH}/resources`, (req: Request, res: Response) => { - res.json(this.getResourcePaths()) - }) + this.server.get( + `${SIGNALK_API_PATH}/resources`, + (req: Request, res: Response) => { + res.json(this.getResourcePaths()) + } + ) + // facilitate retrieval of a specific resource this.server.get( - `${SIGNALK_API_PATH}/resources/:resourceType/:resourceId/*`, - async (req: any, res: any, next: any) => { - const result = this.parseResourceRequest(req) - if (result) { - const ar = await this.actionResourceRequest(result) - if (typeof ar.statusCode !== 'undefined') { - debug(`${JSON.stringify(ar)}`) - res.status = ar.statusCode - res.send(ar.message) - } else { - res.json(ar) - } + `${SIGNALK_API_PATH}/resources/:resourceType/:resourceId`, + async (req: Request, res: Response, next: NextFunction) => { + debug(`** GET ${SIGNALK_API_PATH}/resources/:resourceType/:resourceId`) + if ( + !this.checkForProvider(req.params.resourceType as SignalKResourceType) + ) { + debug('** No provider found... calling next()...') + next() + return + } + if (!validate.uuid(req.params.resourceId)) { + res + .status(406) + .send(`Invalid resource id provided (${req.params.resourceId})`) + return + } + const retVal = await this.resProvider[ + req.params.resourceType + ]?.getResource(req.params.resourceType, req.params.resourceId) + if (retVal) { + res.json(retVal) } else { + res.status(404).send(`Resource not found! (${req.params.resourceId})`) + } + } + ) + + // facilitate retrieval of a collection of resource entries + this.server.get( + `${SIGNALK_API_PATH}/resources/:resourceType`, + async (req: Request, res: Response, next: NextFunction) => { + debug(`** GET ${SIGNALK_API_PATH}/resources/:resourceType`) + if ( + !this.checkForProvider(req.params.resourceType as SignalKResourceType) + ) { + debug('** No provider found... calling next()...') + next() + return + } + const retVal = await this.resProvider[ + req.params.resourceType + ]?.listResources(req.params.resourceType, req.query) + if (retVal) { + res.json(retVal) + } else { + res.status(404).send(`Error retrieving resources!`) + } + } + ) + + // facilitate creation of new resource entry of supplied type + this.server.post( + `${SIGNALK_API_PATH}/resources/:resourceType`, + async (req: Request, res: Response, next: NextFunction) => { + debug(`** POST ${SIGNALK_API_PATH}/resources/:resourceType`) + if ( + !this.checkForProvider(req.params.resourceType as SignalKResourceType) + ) { debug('** No provider found... calling next()...') next() + return + } + if (!validate.resource(req.params.resourceType, req.body.value)) { + res.status(406).send(`Invalid resource data supplied!`) + return + } + const id = UUID_PREFIX + uuidv4() + const retVal = await this.resProvider[ + req.params.resourceType + ]?.setResource(req.params.resourceType, id, req.body.value) + if (retVal) { + this.server.handleMessage( + this.resProvider[req.params.resourceType]?.pluginId as string, + this.buildDeltaMsg( + req.params.resourceType as SignalKResourceType, + id, + req.body.value + ) + ) + res + .status(200) + .send(`New ${req.params.resourceType} resource (${id}) saved.`) + } else { + res + .status(404) + .send(`Error saving ${req.params.resourceType} resource (${id})!`) } } ) - this.server.use( + // facilitate creation / update of resource entry at supplied id + this.server.put( `${SIGNALK_API_PATH}/resources/:resourceType/:resourceId`, - async (req: any, res: any, next: any) => { - const result = this.parseResourceRequest(req) - if (result) { - const ar = await this.actionResourceRequest(result) - if (typeof ar.statusCode !== 'undefined') { - debug(`${JSON.stringify(ar)}`) - res.status = ar.statusCode - res.send(ar.message) - } else { - res.json(ar) - } + async (req: Request, res: Response, next: NextFunction) => { + debug(`** PUT ${SIGNALK_API_PATH}/resources/:resourceType/:resourceId`) + if ( + !this.checkForProvider(req.params.resourceType as SignalKResourceType) + ) { + debug('** No provider found... calling next()...') + next() + return + } + if (!validate.resource(req.params.resourceType, req.body.value)) { + res.status(406).send(`Invalid resource data supplied!`) + return + } + const retVal = await this.resProvider[ + req.params.resourceType + ]?.setResource( + req.params.resourceType, + req.params.resourceId, + req.body.value + ) + if (retVal) { + this.server.handleMessage( + this.resProvider[req.params.resourceType]?.pluginId as string, + this.buildDeltaMsg( + req.params.resourceType as SignalKResourceType, + req.params.resourceId, + req.body.value + ) + ) + res + .status(200) + .send( + `${req.params.resourceType} resource (${req.params.resourceId}) saved.` + ) } else { + res + .status(404) + .send( + `Error saving ${req.params.resourceType} resource (${req.params.resourceId})!` + ) + } + } + ) + + // facilitate deletion of specific of resource entry at supplied id + this.server.delete( + `${SIGNALK_API_PATH}/resources/:resourceType/:resourceId`, + async (req: Request, res: Response, next: NextFunction) => { + debug( + `** DELETE ${SIGNALK_API_PATH}/resources/:resourceType/:resourceId` + ) + if ( + !this.checkForProvider(req.params.resourceType as SignalKResourceType) + ) { debug('** No provider found... calling next()...') next() + return + } + if (!validate.uuid(req.params.resourceId)) { + res + .status(406) + .send(`Invalid resource id provided (${req.params.resourceId})`) + return + } + const retVal = await this.resProvider[ + req.params.resourceType + ]?.deleteResource(req.params.resourceType, req.params.resourceId) + if (retVal) { + this.server.handleMessage( + this.resProvider[req.params.resourceType]?.pluginId as string, + this.buildDeltaMsg( + req.params.resourceType as SignalKResourceType, + req.params.resourceId, + null + ) + ) + res.status(200).send(`Resource (${req.params.resourceId}) deleted.`) + } else { + res + .status(400) + .send(`Error deleting resource (${req.params.resourceId})!`) } } ) - this.server.use( - `${SIGNALK_API_PATH}/resources/:resourceType`, - async (req: any, res: any, next: any) => { - const result = this.parseResourceRequest(req) - if (result) { - const ar = await this.actionResourceRequest(result) - if (typeof ar.statusCode !== 'undefined') { - debug(`${JSON.stringify(ar)}`) - res.status = ar.statusCode - res.send(ar.message) + // facilitate API requests + this.server.put( + `${SIGNALK_API_PATH}/resources/:apiFunction`, + async (req: Request, res: Response, next: NextFunction) => { + debug(`** PUT ${SIGNALK_API_PATH}/resources/:apiFunction`) + + // check for valid API method request + if (!API_METHODS.includes(req.params.apiFunction)) { + res.status(501).send(`Invalid API method ${req.params.apiFunction}!`) + return + } + let resType: SignalKResourceType = 'waypoints' + if (req.params.apiFunction.toLowerCase().indexOf('waypoint') !== -1) { + resType = 'waypoints' + } + if (req.params.apiFunction.toLowerCase().indexOf('route') !== -1) { + resType = 'routes' + } + if (req.params.apiFunction.toLowerCase().indexOf('note') !== -1) { + resType = 'notes' + } + if (req.params.apiFunction.toLowerCase().indexOf('region') !== -1) { + resType = 'regions' + } + if (!this.checkForProvider(resType)) { + res.status(501).send(`No provider for ${resType}!`) + return + } + let resId: string = '' + let resValue: any = null + + if (req.params.apiFunction.toLowerCase().indexOf('set') !== -1) { + resValue = buildResource(resType, req.body) + if (!resValue) { + res.status(406).send(`Invalid resource data supplied!`) + return + } + if (!req.body.id) { + resId = UUID_PREFIX + uuidv4() } else { - res.json(ar) + if (!validate.uuid(req.body.id)) { + res.status(406).send(`Invalid resource id supplied!`) + return + } + resId = req.body.id + } + } + if (req.params.apiFunction.toLowerCase().indexOf('delete') !== -1) { + resValue = null + if (!req.body.id) { + res.status(406).send(`No resource id supplied!`) + return + } + if (!validate.uuid(req.body.id)) { + res.status(406).send(`Invalid resource id supplied!`) + return } + resId = req.body.id + } + const retVal = await this.resProvider[resType]?.setResource( + resType, + resId, + resValue + ) + if (retVal) { + this.server.handleMessage( + this.resProvider[resType]?.pluginId as string, + this.buildDeltaMsg(resType, resId, resValue) + ) + res.status(200).send(`SUCCESS: ${req.params.apiFunction} complete.`) } else { - debug('** No provider found... calling next()...') - next() + res.status(404).send(`ERROR: ${req.params.apiFunction} incomplete!`) } } ) @@ -191,241 +391,28 @@ export class Resources { return resPaths } - private parseResourceRequest(req: any): ResourceRequest | undefined { - debug('********* parse request *************') - debug('** req.method:', req.method) - debug('** req.body:', req.body) - debug('** req.query:', req.query) - debug('** req.params:', req.params) - - const resReq:any = { - method: req.method, - body: req.body, - query: req.query, - resourceType: req.params.resourceType ?? null, - resourceId: req.params.resourceId ?? null, - apiMethod: API_METHODS.includes(req.params.resourceType) ? req.params.resourceType : null - } - - if (resReq.apiMethod) { - if (resReq.apiMethod.toLowerCase().indexOf('waypoint') !== -1) { - resReq.resourceType = 'waypoints' - } - if (resReq.apiMethod.toLowerCase().indexOf('route') !== -1) { - resReq.resourceType = 'routes' - } - if (resReq.apiMethod.toLowerCase().indexOf('note') !== -1) { - resReq.resourceType = 'notes' - } - if (resReq.apiMethod.toLowerCase().indexOf('region') !== -1) { - resReq.resourceType = 'regions' - } - } else { - const resAttrib = req.params['0'] ? req.params['0'].split('/') : [] - req.query.attrib = resAttrib - } - - debug('** resReq:', resReq) - - if ( - this.resourceTypes.includes(resReq.resourceType) && - this.resProvider[resReq.resourceType] - ) { - return resReq - } else { - debug('Invalid resource type or no provider for this type!') - return undefined - } - } - - private async actionResourceRequest(req: ResourceRequest): Promise { - debug('********* action request *************') - debug(req) - - // check for registered resource providers - if (!this.resProvider) { - return { statusCode: 501, message: `No Provider` } - } - - if ( - !this.resourceTypes.includes(req.resourceType) || - !this.resProvider[req.resourceType] - ) { - return { statusCode: 501, message: `No Provider` } - } - - // check for API method request - if (req.apiMethod && API_METHODS.includes(req.apiMethod)) { - debug(`API Method (${req.apiMethod})`) - req = this.transformApiRequest(req) - } - - return await this.execResourceRequest(req) - } - - private transformApiRequest(req: ResourceRequest): ResourceRequest { - if (req.apiMethod?.indexOf('delete') !== -1) { - req.method = 'DELETE' - } - if (req.apiMethod?.indexOf('set') !== -1) { - if (!req.body.id) { - req.method = 'POST' - } else { - req.resourceId = req.body.id - } - req.body = { value: buildResource(req.resourceType, req.body) ?? {} } - } - return req - } - - private async execResourceRequest(req: ResourceRequest): Promise { - debug('********* execute request *************') - debug(req) - if (req.method === 'GET') { - let retVal: any - if (!req.resourceId) { - retVal = await this.resProvider[req.resourceType]?.listResources( - req.resourceType, - req.query - ) - return retVal - ? retVal - : { statusCode: 404, message: `Error retrieving resources!` } - } - if (!validate.uuid(req.resourceId)) { - return { - statusCode: 406, - message: `Invalid resource id provided (${req.resourceId})` - } - } - retVal = await this.resProvider[req.resourceType]?.getResource( - req.resourceType, - req.resourceId - ) - return retVal - ? retVal - : { - statusCode: 404, - message: `Resource not found (${req.resourceId})!` - } - } - - if (req.method === 'DELETE' || req.method === 'PUT') { - if (!req.resourceId) { - return { statusCode: 406, value: `No resource id provided!` } - } - if (!validate.uuid(req.resourceId)) { - return { - statusCode: 406, - message: `Invalid resource id provided (${req.resourceId})!` - } - } - if ( - req.method === 'DELETE' || - (req.method === 'PUT' && - typeof req.body.value !== 'undefined' && - req.body.value == null) - ) { - const retVal = await this.resProvider[req.resourceType]?.deleteResource( - req.resourceType, - req.resourceId - ) - if (retVal) { - this.sendDelta( - this.resProvider[req.resourceType]?.pluginId as string, - req.resourceType, - req.resourceId, - null - ) - return { - statusCode: 200, - message: `Resource (${req.resourceId}) deleted.` - } - } else { - return { - statusCode: 400, - message: `Error deleting resource (${req.resourceId})!` - } - } - } - } - - if (req.method === 'POST' || req.method === 'PUT') { - if (typeof req.body.value === 'undefined' || req.body.value == null) { - return { statusCode: 406, message: `No resource data supplied!` } - } - - if (!validate.resource(req.resourceType, req.body.value)) { - return { statusCode: 406, message: `Invalid resource data supplied!` } - } - - if (req.method === 'POST') { - const id = UUID_PREFIX + uuidv4() - const retVal = await this.resProvider[req.resourceType]?.setResource( - req.resourceType, - id, - req.body.value - ) - if (retVal) { - this.sendDelta( - this.resProvider[req.resourceType]?.pluginId as string, - req.resourceType, - id, - req.body.value - ) - return { statusCode: 200, message: `Resource (${id}) saved.` } - } else { - return { statusCode: 400, message: `Error saving resource (${id})!` } - } - } - if (req.method === 'PUT') { - if (!req.resourceId) { - return { statusCode: 406, message: `No resource id provided!` } - } - const retVal = await this.resProvider[req.resourceType]?.setResource( - req.resourceType, - req.resourceId, - req.body.value - ) - if (retVal) { - this.sendDelta( - this.resProvider[req.resourceType]?.pluginId as string, - req.resourceType, - req.resourceId, - req.body.value - ) - return { - statusCode: 200, - message: `Resource (${req.resourceId}) updated.` - } - } else { - return { - statusCode: 400, - message: `Error updating resource (${req.resourceId})!` - } - } - } - } + private checkForProvider(resType: SignalKResourceType): boolean { + return this.resourceTypes.includes(resType) && this.resProvider[resType] + ? true + : false } - private sendDelta( - providerId: string, - type: string, - id: string, - value: any - ): void { - debug(`** Sending Delta: resources.${type}.${id}`) - this.server.handleMessage(providerId, { + private buildDeltaMsg( + resType: SignalKResourceType, + resid: string, + resValue: any + ): any { + return { updates: [ { values: [ { - path: `resources.${type}.${id}`, - value + path: `resources.${resType}.${resid}`, + value: resValue } ] } ] - }) + } } } From b3d59226c88484ba15f5cd3224ebe99add7cbb74 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 24 Nov 2021 09:13:43 +1030 Subject: [PATCH 076/410] throw on error --- src/api/resources/index.ts | 94 ++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 49 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index f0e1f204f..d8c65b4cb 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -12,15 +12,6 @@ import { Application, Handler, NextFunction, Request, Response } from 'express' const debug = Debug('signalk:resources') -interface ResourceRequest { - method: 'GET' | 'PUT' | 'POST' | 'DELETE' - body: any - query: { [key: string]: any } - resourceType: SignalKResourceType - resourceId: string - apiMethod?: string | null -} - const SIGNALK_API_PATH = `/signalk/v1/api` const UUID_PREFIX = 'urn:mrn:signalk:uuid:' @@ -35,10 +26,10 @@ const API_METHODS = [ 'deleteRegion' ] -// FIXME use types from https://github.com/SignalK/signalk-server/pull/1358 interface ResourceApplication extends Application { handleMessage: (id: string, data: any) => void } + export class Resources { private resProvider: { [key: string]: ResourceProviderMethods | null } = {} private server: ResourceApplication @@ -129,14 +120,15 @@ export class Resources { .send(`Invalid resource id provided (${req.params.resourceId})`) return } - const retVal = await this.resProvider[ - req.params.resourceType - ]?.getResource(req.params.resourceType, req.params.resourceId) - if (retVal) { - res.json(retVal) - } else { + try { + const retVal = await this.resProvider[ + req.params.resourceType + ]?.getResource(req.params.resourceType, req.params.resourceId) + res.json(retVal) + } catch (err) { res.status(404).send(`Resource not found! (${req.params.resourceId})`) } + } ) @@ -152,12 +144,12 @@ export class Resources { next() return } - const retVal = await this.resProvider[ - req.params.resourceType - ]?.listResources(req.params.resourceType, req.query) - if (retVal) { - res.json(retVal) - } else { + try { + const retVal = await this.resProvider[ + req.params.resourceType + ]?.listResources(req.params.resourceType, req.query) + res.json(retVal) + } catch (err) { res.status(404).send(`Error retrieving resources!`) } } @@ -180,10 +172,11 @@ export class Resources { return } const id = UUID_PREFIX + uuidv4() - const retVal = await this.resProvider[ - req.params.resourceType - ]?.setResource(req.params.resourceType, id, req.body.value) - if (retVal) { + try { + const retVal = await this.resProvider[ + req.params.resourceType + ]?.setResource(req.params.resourceType, id, req.body.value) + this.server.handleMessage( this.resProvider[req.params.resourceType]?.pluginId as string, this.buildDeltaMsg( @@ -195,11 +188,11 @@ export class Resources { res .status(200) .send(`New ${req.params.resourceType} resource (${id}) saved.`) - } else { + } catch (err) { res .status(404) .send(`Error saving ${req.params.resourceType} resource (${id})!`) - } + } } ) @@ -219,14 +212,15 @@ export class Resources { res.status(406).send(`Invalid resource data supplied!`) return } - const retVal = await this.resProvider[ - req.params.resourceType - ]?.setResource( - req.params.resourceType, - req.params.resourceId, - req.body.value - ) - if (retVal) { + try { + const retVal = await this.resProvider[ + req.params.resourceType + ]?.setResource( + req.params.resourceType, + req.params.resourceId, + req.body.value + ) + this.server.handleMessage( this.resProvider[req.params.resourceType]?.pluginId as string, this.buildDeltaMsg( @@ -240,7 +234,7 @@ export class Resources { .send( `${req.params.resourceType} resource (${req.params.resourceId}) saved.` ) - } else { + } catch (err) { res .status(404) .send( @@ -270,10 +264,11 @@ export class Resources { .send(`Invalid resource id provided (${req.params.resourceId})`) return } - const retVal = await this.resProvider[ - req.params.resourceType - ]?.deleteResource(req.params.resourceType, req.params.resourceId) - if (retVal) { + try { + const retVal = await this.resProvider[ + req.params.resourceType + ]?.deleteResource(req.params.resourceType, req.params.resourceId) + this.server.handleMessage( this.resProvider[req.params.resourceType]?.pluginId as string, this.buildDeltaMsg( @@ -283,7 +278,7 @@ export class Resources { ) ) res.status(200).send(`Resource (${req.params.resourceId}) deleted.`) - } else { + } catch (err) { res .status(400) .send(`Error deleting resource (${req.params.resourceId})!`) @@ -350,18 +345,19 @@ export class Resources { } resId = req.body.id } - const retVal = await this.resProvider[resType]?.setResource( - resType, - resId, - resValue - ) - if (retVal) { + + try { + const retVal = await this.resProvider[resType]?.setResource( + resType, + resId, + resValue + ) this.server.handleMessage( this.resProvider[resType]?.pluginId as string, this.buildDeltaMsg(resType, resId, resValue) ) res.status(200).send(`SUCCESS: ${req.params.apiFunction} complete.`) - } else { + } catch (err) { res.status(404).send(`ERROR: ${req.params.apiFunction} incomplete!`) } } From 9b0bddafc2761a6d71d8ca35691c5633c6e4fb32 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 24 Nov 2021 09:34:21 +1030 Subject: [PATCH 077/410] PUT/POST payloads directly in `body`..not `value:` --- src/api/resources/index.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index d8c65b4cb..fd4bbf484 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -167,7 +167,7 @@ export class Resources { next() return } - if (!validate.resource(req.params.resourceType, req.body.value)) { + if (!validate.resource(req.params.resourceType, req.body)) { res.status(406).send(`Invalid resource data supplied!`) return } @@ -175,14 +175,14 @@ export class Resources { try { const retVal = await this.resProvider[ req.params.resourceType - ]?.setResource(req.params.resourceType, id, req.body.value) + ]?.setResource(req.params.resourceType, id, req.body) this.server.handleMessage( this.resProvider[req.params.resourceType]?.pluginId as string, this.buildDeltaMsg( req.params.resourceType as SignalKResourceType, id, - req.body.value + req.body ) ) res @@ -208,7 +208,7 @@ export class Resources { next() return } - if (!validate.resource(req.params.resourceType, req.body.value)) { + if (!validate.resource(req.params.resourceType, req.body)) { res.status(406).send(`Invalid resource data supplied!`) return } @@ -218,7 +218,7 @@ export class Resources { ]?.setResource( req.params.resourceType, req.params.resourceId, - req.body.value + req.body ) this.server.handleMessage( @@ -226,7 +226,7 @@ export class Resources { this.buildDeltaMsg( req.params.resourceType as SignalKResourceType, req.params.resourceId, - req.body.value + req.body ) ) res From 559426f92298d56bf3fbbf1453ee1518dbc5fcea Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 24 Nov 2021 10:50:23 +1030 Subject: [PATCH 078/410] remove resourceId validity check on GET --- src/api/resources/index.ts | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index fd4bbf484..09a40f422 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -114,12 +114,6 @@ export class Resources { next() return } - if (!validate.uuid(req.params.resourceId)) { - res - .status(406) - .send(`Invalid resource id provided (${req.params.resourceId})`) - return - } try { const retVal = await this.resProvider[ req.params.resourceType @@ -208,6 +202,14 @@ export class Resources { next() return } + if (req.params.resourceType !== 'charts') { + if(!validate.uuid(req.params.resourceId)) { + res + .status(406) + .send(`Invalid resource id provided (${req.params.resourceId})`) + return + } + } if (!validate.resource(req.params.resourceType, req.body)) { res.status(406).send(`Invalid resource data supplied!`) return @@ -258,12 +260,7 @@ export class Resources { next() return } - if (!validate.uuid(req.params.resourceId)) { - res - .status(406) - .send(`Invalid resource id provided (${req.params.resourceId})`) - return - } + try { const retVal = await this.resProvider[ req.params.resourceType From e5fab438ec104a614e1bef6dde1c4be43adfadfd Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 24 Nov 2021 15:01:01 +1030 Subject: [PATCH 079/410] chore: Updated documentation --- RESOURCE_PROVIDER_PLUGINS.md | 493 +++++++++++++++++++++++------------ SERVERPLUGINS.md | 39 +-- 2 files changed, 340 insertions(+), 192 deletions(-) diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index 4e2c7312d..0b493fb05 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -1,39 +1,65 @@ # Resource Provider plugins +_This document should be read in conjunction with [SERVERPLUGINS.md](./SERVERPLUGINS.md) as it contains additional information regarding the development of plugins that facilitate the storage and retrieval of resource data._ + +--- + ## Overview -This document should be read in conjunction with [SERVERPLUGINS.md](./SERVERPLUGINS.md) as it contains additional information regarding the development of plugins that facilitate the storage and retrieval of resource data (e.g. routes, waypoints, notes, regions and charts). +The SignalK specification defines the path `/signalk/v1/api/resources` for accessing resources to aid in navigation and operation of the vessel. -Resource storage and retrieval is de-coupled from core server function to provide the flexibility to implement the appropriate resource storage solution for specific Signal K implementations. +It also defines the schema for the following __Common__ resource types: +- routes +- waypoints +- notes +- regions +- charts -The Signal K Node server will pass requests made to the following paths to registered resource providers: -- `/signalk/v1/api/resources` -- `/signalk/v1/api/resources/routes` -- `/signalk/v1/api/resources/waypoints` -- `/signalk/v1/api/resources/notes` -- `/signalk/v1/api/resources/regions` -- `/signalk/v1/api/resources/charts` +each of with its own path under the root `resources` path _(e.g. `/signalk/v1/api/resources/routes`)_. -Resource providers will receive request data via a `ResourceProvider` interface which they implement. It is the responsibility of the resource provider to persist resource data in storage (PUT, POST), retrieve the requested resources (GET) and remove resource entries (DELETE). +It should also be noted that the `/signalk/v1/api/resources` path can also host other types of resource data which can be grouped within a __Custom__ path name _(e.g. `/signalk/v1/api/resources/fishingZones`)_. -Resource data passed to the resource provider plugin has been validated by the server and can be considered ready for storage. +The SignalK server does not natively provide the ability to store or retrieve resource data for either __Common__ and __Custom__ resource types. +This functionality needs to be provided by one or more server plugins that handle the data for specific resource types. +These plugins are called __Resource Providers__. -## Resource Providers +This de-coupling of resource request handling and storage / retrieval provides great flexibility to ensure that an appropriate resource storage solution can be configured for your SignalK implementation. -A `resource provider plugin` is responsible for the storage and retrieval of resource data. +It is important to understand that the SignalK server handles requests for __Custom__ resource types differently to requests for __Common__ types, please ensure you refer to the relevant section of this document. -It should implement the necessary functions to: -- Persist each resource with its associated id -- Retrieve an individual resource with the supplied id -- Retrieve a list of resources that match the supplied qery criteria. +--- -Data is passed to and from the plugin via the methods defined in the __resourceProvider__ interface which the plugin must implement. +## Common Resource Type Provider: -_Definition: `resourceProvider` interface._ -```javascript -resourceProvider: { - types: [], +As detailed earlier in this document, the __Common__ resource types are: +`routes`, `waypoints`, `notes`, `regions` & `charts`. + +For the `/signalk/v1/api/resources` path and the __Common__ resource type sub-paths, the Signal K server will pre-process the request before passing on the request details on to the registered resource provider. + +The SignalK server performs the following tasks when pre-processing a request: +- Checks for a registered provider for the resource type +- Checks the validity of the supplied resource id +- For requests to store data, the submitted resource data is validated. + +Only when all pre-processing tasks have completed successfully will the request be passed on to the resource provider plugin. + +Resource providers for __Common__ resource types need to implement the `ResourceProvider` interface which registers with the SignalK server the: +- Resource types provided for by the plugin +- Methods to use when requests are passed to the plugin. It is these methods that implement the saving / deletion of resource data to storage and the retrieval of requested resources. + + +### Resource Provider Interface + +--- +The `ResourceProvider` interface defines the contract between the the Resource Provider plugin and the SignalK server and has the following definition _(which it and other related types can be imported from `@signalk/server-api`)_: + +```typescript +import { SignalKResourceType } from '@signalk/server-api' +// SignalKResourceType= 'routes' | 'waypoints' |'notes' |'regions' |'charts' + +interface ResourceProvider: { + types: SignalKResourceType[], methods: { listResources: (type:string, query: {[key:string]:any})=> Promise getResource: (type:string, id:string)=> Promise @@ -42,41 +68,189 @@ resourceProvider: { } } ``` +where: + +- `types`: An array containing a list of __Common__ resource types provided for by the plugin +- `methods`: An object containing the methods resource requests are passed to by the SignalK server. The plugin __MUST__ implement each method, even if that operation is not supported by the plugin! + +#### __Method Details:__ + +--- +__`listResources(type, query)`__: This method is called when a request is made for resource entries of a specific resource type that match a specifiec criteria. + +_Note: It is the responsibility of the resource provider plugin to filter the resources returned as per the supplied query parameters._ + +`type:` Array of __Common__ resource types provied for by the plugin _e.g. ['routes','waypoints']_ + +`query:` Object contining `key | value` pairs repesenting the parameters by which to filter the returned entries _e.g. {distance,'50000}_ + +`returns:` +- Resolved Promise containing a list of resource entries on completion. +- Rejected Promise containing an Error if incomplete or not implemented. + + +_Example resource request:_ +``` +GET /signalk/v1/api/resources/waypoints?bbox=5.4,25.7,6.9,31.2&distance=30000 +``` +_ResourceProvider method invocation:_ + +```javascript +listResources( + 'waypoints', + { + bbox: '5.4,25.7,6.9,31.2', + distance: 30000 + } +); +``` + +--- +__`getResource(type, id)`__: This method is called when a request is made for a specific resource entry of the supplied resource type and id. + +`type:` String containing one of the __Common__ resource types _e.g. 'waypoints'_ + +`id:` String containing the target resource entry id _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ + +`returns:` +- Resolved Promise containing the resource entry on completion. +- Rejected Promise containing an Error if incomplete or not implemented. + +_Example resource request:_ +``` +GET /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99 +``` +_ResourceProvider method invocation:_ -This interface is used by the server to direct requests to the plugin. +```javascript +getResource( + 'routes', + 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99' +); +``` + +--- +__`setResource(type, id, value)`__: This method is called when a request is made to save / update a resource entry of the specified resource type, with the supplied id and data. + +`type:` Array of __Common__ resource types provied for by the plugin _e.g. ['routes','waypoints']_ + +`id:` String containing the target resource entry id _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ + +`value:` Resource data to be stored. + +`returns:` +- Resolved Promise containing a list of resource entries on completion. +- Rejected Promise containing an Error if incomplete or not implemented. + +_Example PUT resource request:_ +``` +PUT /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99 {resource_data} +``` +_ResourceProvider method invocation:_ + +```javascript +setResource( + 'routes', + 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99', + {} +); +``` + +_Example POST resource request:_ +``` +POST /signalk/v1/api/resources/routes {resource_data} +``` +_ResourceProvider method invocation:_ -It contains the following attributes: -- `types`: An array containing the names of the resource types the plugin is a provider for. Names of the resource types are: `routes, waypoints, notes, regions, charts`. +```javascript +setResource( + 'routes', + '', + {} +); +``` + +--- +__`deleteResource(type, id)`__: This method is called when a request is made to remove the specific resource entry of the supplied resource type and id. + +`type:` String containing one of the __Common__ resource types _e.g. 'waypoints'_ + +`id:` String containing the target resource entry id _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ + +`returns:` +- Resolved Promise on completion. +- Rejected Promise containing an Error if incomplete or not implemented. + +_Example resource request:_ +``` +DELETE /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99 +``` +_ResourceProvider method invocation:_ + +```javascript +deleteResource( + 'routes', + 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99' +); +``` -- `methods`: The methods to which the server dispatches requests. The plugin will implement these methods to perform the necessary save or retrieval operation. Each method returns a promise containing either resource data or `null` if an error is encountered. +--- + +### Example: -_Example: Plugin acting as resource provider for routes & waypoints._ +_Defintion for Resource Provider plugin providing for the retrieval routes & waypoints._ ```javascript +// SignalK server plugin module.exports = function (app) { + let plugin= { id: 'mypluginid', name: 'My Resource Providerplugin', + // ResourceProvider interface resourceProvider: { types: ['routes','waypoints'], methods: { listResources: (type, params)=> { - return Promise.resolve() { ... }; + return new Promise( (resolve, reject) => { + // fetch resource entries from storage + .... + if(ok) { // success + resolve({ + 'id1': { ... }, + 'id2': { ... }, + }); + } else { // error + reject(new Error('Error encountered!') + } + } }, - getResource: (type:string, id:string)=> { - return Promise.resolve() { ... }; - } , - setResource: (type:string, id:string, value:any)=> { - return Promise.resolve() { ... }; + getResource: (type, id)=> { + // fetch resource entries from storage + .... + if(ok) { // success + return Promise.resolve({ + ... + }); + } else { // error + reject(new Error('Error encountered!') + } }, - deleteResource: (type:string, id:string)=> { - return Promise.resolve() { ... }; ; + setResource: (type, id, value)=> { + // not implemented + return Promise.reject(new Error('NOT IMPLEMENTED!')); + }, + deleteResource: (type, id)=> { + // not implemented + return Promise.reject(new Error('NOT IMPLEMENTED!')); } } }, + start: (options)=> { ... app.resourceApi.register(this.id, this.resourceProvider); }, + stop: ()=> { app.resourceApi.unRegister(this.id); ... @@ -85,35 +259,37 @@ module.exports = function (app) { } ``` + +### Registering the Resource Provider: --- -## Plugin Startup - Registering the Resource Provider: +For the plugin to be registered by the SignalK server, it needs to call the server's `resourcesApi.register()` function during plugin startup. + +The server `resourcesApi.register()` function has the following signature: -To register your plugin as a resource provider the server's `resourcesApi.register()` function should be called within the plugin `start()` function passing the `resourceProvider` interface. +```typescript +app.resourcesApi.register(pluginId: string, resourceProvider: ResourceProvider) +``` +where: +- `pluginId`: is the plugin's id +- `resourceProvider`: is a reference to the plugins ResourceProvider interface. -This registers the resource types and the methods with the server so they are called when requests to resource paths are made. +_Note: A resource type can only have one registered plugin, so if more than one installed plugin attempts to register as a provider for the same resource type, only the first one is registered and it's implemented methods will service requests._ _Example:_ ```javascript module.exports = function (app) { + let plugin= { id: 'mypluginid', name: 'My Resource Providerplugin', resourceProvider: { types: ['routes','waypoints'], methods: { - listResources: (type, params)=> { - return Promise.resolve() { ... }; - }, - getResource: (type:string, id:string)=> { - return Promise.resolve() { ... }; - } , - setResource: (type:string, id:string, value:any)=> { - return Promise.resolve() { ... }; - }, - deleteResource: (type:string, id:string)=> { - return Promise.resolve() { ... }; ; - } + listResources: (type, params)=> { ... }, + getResource: (type:string, id:string)=> { ... } , + setResource: (type:string, id:string, value:any)=> { ... }, + deleteResource: (type:string, id:string)=> { ... } } } } @@ -124,15 +300,25 @@ module.exports = function (app) { } } ``` + +### Un-registering the Resource Provider: --- -## Plugin Stop - Un-registering the Resource Provider: +When a resource provider plugin is disabled, it should un-register as a provider to ensure resource requests are no longer directed to it by calling the SignalK server's `resourcesApi.unRegister()` function during shutdown. + +The server `resourcesApi.unRegister()` function has the following signature: + +```typescript +app.resourcesApi.unRegister(pluginId: string) +``` +where: +- `pluginId`: is the plugin's id -When a resource provider plugin is disabled it should un-register as a provider so resource requests are not directed to it. This is done by calling the server's `resourcesApi.unRegister()` function passing the `plugin.id` within the plugin's `stop()` function. _Example:_ ```javascript module.exports = function (app) { + let plugin= { id: 'mypluginid', name: 'My Resource Providerplugin', @@ -150,150 +336,111 @@ module.exports = function (app) { ``` --- -## Operation: - -The Server will dispatch requests made to: -- `/signalk/v1/api/resources/` - -OR -- the `resources API` endpoints +## Custom Resource Type Provider: -to the plugin's `resourceProvider.methods` for each of the resource types listed in `resourceProvider.types`. +Custom resource types are collections of resource entries residing within a sub-path with a user defined name under `/signalk/v1/api/resources`. -Each method defined in `resourceProvider.methods` must have a signature as specified in the __resourceProvider interface__. Each method returns a `Promise` containing the resultant resource data or `null` if an error occurrred or the operation is incomplete. +_Example:_ +``` +/signalk/v1/api/resources/fishingZones +``` +_Note: A custom resource path name __CANNOT__ be one of the __Common__ resource types i.e. `routes`, `waypoints`, `notes`, `regions` or `charts`.__ -### __List Resources:__ +Unlike the __Common Resource Type Providers__: +- The plugin __DOES NOT__ implement the `ResourceProvider` interface +- Requests to __Custom__ resource type sub-paths and any submitted data __WILL NOT__ be pre-processed or validated by the Signal K server +- The plugin __WILL__ need to implement a route handler for the necessary path(s) +- The plugin __WILL__ need to implement any necessary data validation. -`GET` requests that are not for a specific resource will be dispatched to the `listResources` method passing the resource type and any query data as parameters. -Query parameters are passed as an object conatining `key | value` pairs. +### Router Path Handlers +--- -_Example: GET /signalk/v1/api/resources/waypoints?bbox=5.4,25.7,6.9,31.2&distance=30000_ -```javascript -query= { - bbox: '5.4,25.7,6.9,31.2', - distance: 30000 -} -``` +To set up a router path handler, you use the reference to the SignalK server `app` passed to the plugin to register the paths for which requests will be passed to the plugin. -It is the responsibility of the resource provider plugin to filter the resources returned as per the supplied query parameters. +This should be done during plugin startup within the plugin `start()` function. -`listResources()` returns a Promise containing a JSON object listing resources by id. +_Note: To ensure that your resource path is listed when a request to `/signalk/v1/api/resources` is made ensure you use the full path as outlined in the example below._ -_Example: List all routes._ +_Example:_ ```javascript -GET /signalk/v1/api/resources/routes -listResources('routes', {}) - -returns Promise<{ - "resource_id1": { ... }, - "resource_id2": { ... }, - ... - "resource_idn": { ... } -}> -``` - -_Example: List waypoints within the bounded area._ -```typescript -GET /signalk/v1/api/resources/waypoints?bbox=5.4,25.7,6.9,31.2 +module.exports = function (app) { -listResources('waypoints', {bbox: '5.4,25.7,6.9,31.2'}) + let plugin= { + id: 'mypluginid', + name: 'My Resource Providerplugin', + start: (options) => { + // setup router path handlers + initPathHandlers(app); + ... + } + } + + function initPathHandlers(app) { + app.get( + `/signalk/v1/api/resources/myResType`, + (request, response)=> { + // retrieve resource(s) + let result= getMyResources(); + response.status(200).json(result); + } + ); + app.post( + `/signalk/v1/api/resources/myResType`, + (request, response)=> { + // create new resource + ... + } + ); + router.put( + `/signalk/v1/api/resources/myResType/:id`, + (request, response)=> { + // create / update resource with supplied id + ... + } + ); + router.delete( + `/signalk/v1/api/resources/myResType/:id`, + (request, response)=> { + // delete the resource with supplied id + ... + } + ); + } -returns Promise<{ - "resource_id1": { ... }, - "resource_id2": { ... } -}> ``` -### __Retrieve a specific resource:__ +Once registered requests to `/signalk/v1/api/resources/myResType` will be passed to the respective handler based on the request type _(i.e. GET, PUT, POST, DELETE)_ and the request details are available in the `request` parameter. -`GET` requests for a specific resource will be dispatched to the `getResource` method passing the resource type and id as parameters. +For more information regarding the `request` and `response` parameters see the [Express Routing](https://expressjs.com/en/guide/routing.html) documentation. -`getResource()` returns a Promise containing a JSON object with the resource data. +### Data Validation +--- -_Example: Retrieve route._ -```typescript -GET /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a +When providing for resource data to be persisted to storage (PUT, POST) it is recommended that data validation implemented within the `app.put()` and `app.post()` route handlers and that the appropriate `status` is returned. -getResource( - 'routes', - 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', - {} -) -``` - -_Returns a Promise containing the resource data:_ -```typescript -Promise<{ - "name": "Name of the route", - "description": "Description of the route", - "distance": 18345, - "feature": { ... } -}> -``` - -A request for a resource attribute will pass the attibute path as an array in the query object with the key `resAttrib`. - -_Example: Get waypoint geometry._ +_Example:_ ```javascript -GET /signalk/v1/api/resources/waypoints/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a/feature/geometry - -getResource( - 'waypoints', - 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', - { resAttrib: ['feature','geometry'] } +app.post( + `/signalk/v1/api/resources/myResType`, + (request, response)=> { + // validate submitted data + let ok= validate(request.body); + if (ok) { //valid data + if (saveResource(request.body)) { + response.status(200).send('OK'); + } else { + response.status(404).send('ERROR svaing resource!'); + } + } else { + response.status(406).send('ERROR: Invalid data!'); + } + } ) -``` -_Returns a Promise containing the value of `geometry` attribute of the waypoint._ -```typescript -Promise<{ - "type": "Point", - "coordinates": [70.4,6.45] -}> -``` - -### __Saving Resources:__ - -`PUT` requests to a path containing the resource id are used to store data associated with the resource id. These will be dispatched to the `setResource` method passing the resource type, id and data as parameters. -`setResource() ` returns Promise on success and Promise on failure. - -_Example: Update / add waypoint with the supplied id._ -```javascript -PUT /signalk/v1/api/resources/waypoints/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a {} - -setResource('waypoints', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) - -returns Promise ``` +--- -`POST` requests to a resource path that do not contina the resource id will be dispatched to the `setResource` method passing the resource type, an id (generated by the server) and resource data as parameters. - -`setResource()` returns `true` on success and `null` on failure. - -_Example: New route record._ -```typescript -POST /signalk/v1/api/resources/routes {} - -setResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) - -returns Promise -``` - -### __Deleting Resources:__ - -`DELETE` requests to a path containing the resource id will be dispatched to the `deleteResource` method passing the resource type and id as parameters. - -`deleteResource()` returns `true` on success and `null` on failure. - -_Example: Delete region with supplied id._ -```typescript -DELETE /signalk/v1/api/resources/regions/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a - -deleteResource('regions', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a') - -returns Promise -``` diff --git a/SERVERPLUGINS.md b/SERVERPLUGINS.md index 8fbcb6737..bad366eb2 100644 --- a/SERVERPLUGINS.md +++ b/SERVERPLUGINS.md @@ -22,9 +22,9 @@ The plugin module must export a single `function(app)` that must return an objec ## Getting Started with Plugin Development -To get started with SignalK plugin development, you can follow the following guide. +To get started with SignalK plugin development, you can follow this guide. -_Note: For plugins acting as a provider for one or more of the resource types listed in the Signal K specification (e.g. routes, waypoints, notes, regions or charts) refer to __[RESOURCE_PROVIDER_PLUGINS.md](./RESOURCE_PROVIDER_PLUGINS.md)__ for details. +_Note: For plugins acting as a provider for one or more of the SignalK resource types listed in the specification (`routes`, `waypoints`, `notes`, `regions` or `charts`) please refer to __[RESOURCE_PROVIDER_PLUGINS.md](./RESOURCE_PROVIDER_PLUGINS.md)__ for additional details._ ### Project setup @@ -702,29 +702,30 @@ app.registerDeltaInputHandler((delta, next) => { ### `app.resourcesApi.getResource(resource_type, resource_id)` -Retrieve resource data for the supplied resource type and id. +Retrieve resource data for the supplied SignalK resource type and resource id. -This method invokes the `registered Resource Provider` for the supplied `resource_type` and returns a __resovled Promise__ containing the route resource data if successful or -a __rejected Promise__ containing an Error object if unsuccessful. - - data for the full path of the directory where the plugin can persist its internal data, like data files.If a plugin wants to act as a resource provider, it will need to register its provider methods during startup using this function. +_Valid resource types are `routes`, `waypoints`, `notes`, `regions` & `charts`._ +This method invokes the `registered Resource Provider` for the supplied `resource_type` and returns a `resovled` __Promise__ containing the resource data if successful or +a `rejected` __Promise__ containing an __Error__ object if unsuccessful. +_Example:_ ```javascript -let myRoute= app.resourcesApi.getResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a'); -``` -_Returns resolved Promise containing:_ -```json -{ - "name": "Name of the route", - "description": "Description of the route", - "distance": 18345, - "feature": { ... } -} +let resource= app.resourcesApi.getResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a'); +resource.then ( (data)=> { + // route data + console.log(data); + ... +}).catch (error) { + // handle error + console.log(error.message); + ... +} ``` + ### `app.resourcesApi.register(pluginId, resourceProvider)` If a plugin wants to act as a resource provider, it will need to register its provider methods during startup using this function. @@ -742,7 +743,7 @@ module.exports = function (app) { methods: { ... } } start: function(options) { - ... + // do plugin start up app.resourcesApi.register(this.id, this.resourceProvider); } ... @@ -769,7 +770,7 @@ module.exports = function (app) { ... stop: function(options) { app.resourcesApi.unRegister(this.id); - ... + // do plugin shutdown } } } From ac12f21a70dd435db261e48b4887e5dc7b942a58 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 24 Nov 2021 17:22:29 +1030 Subject: [PATCH 080/410] add chartId test & require alignment with spec. --- src/api/resources/index.ts | 50 +++++++++++++++++++++++------------ src/api/resources/validate.ts | 28 ++++++++++++++++---- 2 files changed, 56 insertions(+), 22 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 09a40f422..ea888053c 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -118,11 +118,10 @@ export class Resources { const retVal = await this.resProvider[ req.params.resourceType ]?.getResource(req.params.resourceType, req.params.resourceId) - res.json(retVal) + res.json(retVal) } catch (err) { res.status(404).send(`Resource not found! (${req.params.resourceId})`) } - } ) @@ -142,7 +141,7 @@ export class Resources { const retVal = await this.resProvider[ req.params.resourceType ]?.listResources(req.params.resourceType, req.query) - res.json(retVal) + res.json(retVal) } catch (err) { res.status(404).send(`Error retrieving resources!`) } @@ -165,12 +164,23 @@ export class Resources { res.status(406).send(`Invalid resource data supplied!`) return } - const id = UUID_PREFIX + uuidv4() + if (!validate.resource(req.params.resourceType, req.body)) { + res.status(406).send(`Invalid resource data supplied!`) + return + } + + let id: string + if (req.params.resourceType === 'charts') { + id = req.body.identifier + } else { + id = UUID_PREFIX + uuidv4() + } + try { const retVal = await this.resProvider[ req.params.resourceType ]?.setResource(req.params.resourceType, id, req.body) - + this.server.handleMessage( this.resProvider[req.params.resourceType]?.pluginId as string, this.buildDeltaMsg( @@ -186,7 +196,7 @@ export class Resources { res .status(404) .send(`Error saving ${req.params.resourceType} resource (${id})!`) - } + } } ) @@ -202,14 +212,20 @@ export class Resources { next() return } - if (req.params.resourceType !== 'charts') { - if(!validate.uuid(req.params.resourceId)) { - res - .status(406) - .send(`Invalid resource id provided (${req.params.resourceId})`) - return - } + + let isValidId: boolean + if (req.params.resourceType === 'charts') { + isValidId = validate.chartId(req.params.resourceId) + } else { + isValidId = validate.uuid(req.params.resourceId) + } + if (isValidId) { + res + .status(406) + .send(`Invalid resource id provided (${req.params.resourceId})`) + return } + if (!validate.resource(req.params.resourceType, req.body)) { res.status(406).send(`Invalid resource data supplied!`) return @@ -222,7 +238,7 @@ export class Resources { req.params.resourceId, req.body ) - + this.server.handleMessage( this.resProvider[req.params.resourceType]?.pluginId as string, this.buildDeltaMsg( @@ -236,7 +252,7 @@ export class Resources { .send( `${req.params.resourceType} resource (${req.params.resourceId}) saved.` ) - } catch (err) { + } catch (err) { res .status(404) .send( @@ -260,12 +276,12 @@ export class Resources { next() return } - + try { const retVal = await this.resProvider[ req.params.resourceType ]?.deleteResource(req.params.resourceType, req.params.resourceId) - + this.server.handleMessage( this.resProvider[req.params.resourceType]?.pluginId as string, this.buildDeltaMsg( diff --git a/src/api/resources/validate.ts b/src/api/resources/validate.ts index 0d8d6cffe..16eb3c9a8 100644 --- a/src/api/resources/validate.ts +++ b/src/api/resources/validate.ts @@ -19,21 +19,29 @@ export const validate = { case 'regions': return validateRegion(value) break + case 'charts': + return validateChart(value) + break default: return true } }, - // ** returns true if id is a valid Signal K UUID ** + // returns true if id is a valid Signal K UUID uuid: (id: string): boolean => { const uuid = RegExp( '^urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$' ) return uuid.test(id) + }, + + // returns true if id is a valid Signal K Chart resource id + chartId: (id: string): boolean => { + const uuid = RegExp('(^[A-Za-z0-9_-]{8,}$)') + return uuid.test(id) } } -// ** validate route data const validateRoute = (r: any): boolean => { if (r.start) { const l = r.start.split('/') @@ -60,7 +68,6 @@ const validateRoute = (r: any): boolean => { return true } -// ** validate waypoint data const validateWaypoint = (r: any): boolean => { if (typeof r.position === 'undefined') { return false @@ -81,7 +88,7 @@ const validateWaypoint = (r: any): boolean => { return true } -// ** validate note data +// validate note data const validateNote = (r: any): boolean => { if (!r.region && !r.position && !r.geohash) { return false @@ -100,7 +107,6 @@ const validateNote = (r: any): boolean => { return true } -// ** validate region data const validateRegion = (r: any): boolean => { if (!r.geohash && !r.feature) { return false @@ -122,3 +128,15 @@ const validateRegion = (r: any): boolean => { } return true } + +const validateChart = (r: any): boolean => { + if (!r.name || !r.identifier || !r.chartFormat) { + return false + } + + if (!r.tilemapUrl && !r.chartUrl) { + return false + } + + return true +} From 39d49ebc4c8c7ca73301fc026372ed1a31d1e0a6 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 24 Nov 2021 17:44:05 +1030 Subject: [PATCH 081/410] add charts API methods --- src/api/resources/openApi.json | 474 ++++++++++++++++++++++++++++++++- 1 file changed, 473 insertions(+), 1 deletion(-) diff --git a/src/api/resources/openApi.json b/src/api/resources/openApi.json index 8eddcac32..80350d21f 100644 --- a/src/api/resources/openApi.json +++ b/src/api/resources/openApi.json @@ -787,7 +787,7 @@ "/resources/regions/": { "post": { "tags": ["resources/regions"], - "summary": "Add a new Regkion", + "summary": "Add a new Region", "requestBody": { "description": "Region details", "required": true, @@ -1056,6 +1056,308 @@ }, + "/resources/charts/": { + "post": { + "tags": ["resources/charts"], + "summary": "Add a new Chart", + "requestBody": { + "description": "Chart details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Chart resource", + "required": ["feature"], + "properties": { + "name": { + "description": "Chart common name", + "example":"NZ615 Marlborough Sounds", + "type": "string" + }, + "identifier": { + "type": "string", + "description": "Chart number", + "example":"NZ615" + }, + "description": { + "type": "string", + "description": "A description of the chart" + }, + "tilemapUrl": { + "type": "string", + "description": "A url to the tilemap of the chart for use in TMS chartplotting apps", + "example":"http://{server}:8080/mapcache/NZ615" + }, + "region": { + "type": "string", + "description": "Region related to note. A pointer to a region UUID. Alternative to geohash" + }, + "geohash": { + "description": "Position related to chart. Alternative to region", + "type": "string" + }, + "chartUrl": { + "type": "string", + "description": "A url to the chart file's storage location", + "example":"file:///home/pi/freeboard/mapcache/NZ615" + }, + "scale": { + "type": "integer", + "description": "The scale of the chart, the larger number from 1:200000" + }, + "chartLayers": { + "type": "array", + "description": "If the chart format is WMS, the layers enabled for the chart.", + "items": { + "type": "string", + "description": "Identifier for the layer." + } + }, + "bounds": { + "type": "array", + "description": "The bounds of the chart. An array containing the position of the upper left corner, and the lower right corner. Useful when the chart isn't inherently geo-referenced.", + "items": { + "description": "Position of a corner of the chart", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + }, + "altitude": { + "type": "number", + "format": "float" + } + } + } + }, + "chartFormat": { + "type": "string", + "description": "The format of the chart", + "enum": [ + "gif", + "geotiff", + "kap", + "png", + "jpg", + "kml", + "wkt", + "topojson", + "geojson", + "gpx", + "tms", + "wms", + "S-57", + "S-63", + "svg", + "other" + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + + "/resources/charts/{id}": { + "parameters": { + "name": "id", + "in": "path", + "description": "Chart id", + "required": true, + "schema": { + "type": "string", + "pattern": "(^[A-Za-z0-9_-]{8,}$)" + } + }, + + "get": { + "tags": ["resources/charts"], + "summary": "Retrieve Chart with supplied id", + "responses": { + "200": { + "description": "List of resources identified by their UUID", + "content": { + "application/json": { + "schema": { + "description": "List of Signal K resources", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + } + }, + + "put": { + "tags": ["resources/charts"], + "summary": "Add / update a new Chart with supplied id", + "requestBody": { + "description": "Chart details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Chart resource", + "required": ["feature"], + "properties": { + "name": { + "description": "Chart common name", + "example":"NZ615 Marlborough Sounds", + "type": "string" + }, + "identifier": { + "type": "string", + "description": "Chart number", + "example":"NZ615" + }, + "description": { + "type": "string", + "description": "A description of the chart" + }, + "tilemapUrl": { + "type": "string", + "description": "A url to the tilemap of the chart for use in TMS chartplotting apps", + "example":"http://{server}:8080/mapcache/NZ615" + }, + "region": { + "type": "string", + "description": "Region related to note. A pointer to a region UUID. Alternative to geohash" + }, + "geohash": { + "description": "Position related to chart. Alternative to region", + "type": "string" + }, + "chartUrl": { + "type": "string", + "description": "A url to the chart file's storage location", + "example":"file:///home/pi/freeboard/mapcache/NZ615" + }, + "scale": { + "type": "integer", + "description": "The scale of the chart, the larger number from 1:200000" + }, + "chartLayers": { + "type": "array", + "description": "If the chart format is WMS, the layers enabled for the chart.", + "items": { + "type": "string", + "description": "Identifier for the layer." + } + }, + "bounds": { + "type": "array", + "description": "The bounds of the chart. An array containing the position of the upper left corner, and the lower right corner. Useful when the chart isn't inherently geo-referenced.", + "items": { + "description": "Position of a corner of the chart", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + }, + "altitude": { + "type": "number", + "format": "float" + } + } + } + }, + "chartFormat": { + "type": "string", + "description": "The format of the chart", + "enum": [ + "gif", + "geotiff", + "kap", + "png", + "jpg", + "kml", + "wkt", + "topojson", + "geojson", + "gpx", + "tms", + "wms", + "S-57", + "S-63", + "svg", + "other" + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + }, + + "delete": { + "tags": ["resources/charts"], + "summary": "Remove Chart with supplied id", + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + + }, + "/resources/setWaypoint": { "put": { "tags": ["resources/api"], @@ -1514,6 +1816,176 @@ } } } + }, + + "/resources/setChart": { + "put": { + "tags": ["resources/api"], + "summary": "Add / update a Chart", + "requestBody": { + "description": "Chart attributes", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Chart resource", + "required": ["identifier","tilemapUrl","chartUrl","chartFormat"], + "properties": { + "name": { + "description": "Chart common name", + "example":"NZ615 Marlborough Sounds", + "type": "string" + }, + "identifier": { + "type": "string", + "description": "Chart number", + "example":"NZ615" + }, + "description": { + "type": "string", + "description": "A description of the chart" + }, + "oneOf": [ + { + "tilemapUrl": { + "type": "string", + "description": "A url to the tilemap of the chart for use in TMS chartplotting apps", + "example":"http://{server}:8080/mapcache/NZ615" + } + }, + { + "chartUrl": { + "type": "string", + "description": "A url to the chart file's storage location", + "example":"file:///home/pi/freeboard/mapcache/NZ615" + } + } + ], + "region": { + "type": "string", + "description": "Region related to note. A pointer to a region UUID. Alternative to geohash" + }, + "geohash": { + "description": "Position related to chart. Alternative to region", + "type": "string" + }, + "scale": { + "type": "integer", + "description": "The scale of the chart, the larger number from 1:200000" + }, + "chartLayers": { + "type": "array", + "description": "If the chart format is WMS, the layers enabled for the chart.", + "items": { + "type": "string", + "description": "Identifier for the layer." + } + }, + "bounds": { + "type": "array", + "description": "The bounds of the chart. An array containing the position of the upper left corner, and the lower right corner. Useful when the chart isn't inherently geo-referenced.", + "items": { + "description": "Position of a corner of the chart", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + }, + "altitude": { + "type": "number", + "format": "float" + } + } + } + }, + "chartFormat": { + "type": "string", + "description": "The format of the chart", + "enum": [ + "gif", + "geotiff", + "kap", + "png", + "jpg", + "kml", + "wkt", + "topojson", + "geojson", + "gpx", + "tms", + "wms", + "S-57", + "S-63", + "svg", + "other" + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + + "/resources/deleteChart": { + "put": { + "tags": ["resources/api"], + "summary": "Remove a Chart", + "requestBody": { + "description": "Chart identifier", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["id"], + "properties": { + "id": { + "type": "string", + "pattern": "(^[A-Za-z0-9_-]{8,}$)", + "description": "Chart identifier" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } } } From 81907fe36d9f5a92da1b620ea180166cf925a618 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Thu, 25 Nov 2021 13:21:23 +1030 Subject: [PATCH 082/410] allow registering custom resource types --- src/api/resources/index.ts | 95 ++++++++++++++++++++------------------ 1 file changed, 50 insertions(+), 45 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index ea888053c..a3e32cb19 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -34,8 +34,7 @@ export class Resources { private resProvider: { [key: string]: ResourceProviderMethods | null } = {} private server: ResourceApplication - // in-scope resource types - private resourceTypes: SignalKResourceType[] = [ + private signalkResTypes: SignalKResourceType[] = [ 'routes', 'waypoints', 'notes', @@ -160,13 +159,12 @@ export class Resources { next() return } - if (!validate.resource(req.params.resourceType, req.body)) { - res.status(406).send(`Invalid resource data supplied!`) - return - } - if (!validate.resource(req.params.resourceType, req.body)) { - res.status(406).send(`Invalid resource data supplied!`) - return + + if (this.signalkResTypes.includes(req.params.resourceType as SignalKResourceType)) { + if (!validate.resource(req.params.resourceType, req.body)) { + res.status(406).send(`Invalid resource data supplied!`) + return + } } let id: string @@ -213,23 +211,26 @@ export class Resources { return } - let isValidId: boolean - if (req.params.resourceType === 'charts') { - isValidId = validate.chartId(req.params.resourceId) - } else { - isValidId = validate.uuid(req.params.resourceId) - } - if (isValidId) { - res - .status(406) - .send(`Invalid resource id provided (${req.params.resourceId})`) - return - } + if (this.signalkResTypes.includes(req.params.resourceType as SignalKResourceType)) { + let isValidId: boolean + if (req.params.resourceType === 'charts') { + isValidId = validate.chartId(req.params.resourceId) + } else { + isValidId = validate.uuid(req.params.resourceId) + } + if (isValidId) { + res + .status(406) + .send(`Invalid resource id provided (${req.params.resourceId})`) + return + } - if (!validate.resource(req.params.resourceType, req.body)) { - res.status(406).send(`Invalid resource data supplied!`) - return + if (!validate.resource(req.params.resourceType, req.body)) { + res.status(406).send(`Invalid resource data supplied!`) + return + } } + try { const retVal = await this.resProvider[ req.params.resourceType @@ -379,31 +380,35 @@ export class Resources { private getResourcePaths(): { [key: string]: any } { const resPaths: { [key: string]: any } = {} - Object.entries(this.resProvider).forEach((p: any) => { - if (p[1]) { - resPaths[p[0]] = `Path containing ${p[0]}, each named with a UUID` - } - }) - // check for other plugins servicing paths under ./resources - this.server._router.stack.forEach((i: any) => { - if (i.route && i.route.path && typeof i.route.path === 'string') { - if (i.route.path.indexOf(`${SIGNALK_API_PATH}/resources`) !== -1) { - const r = i.route.path.split('/') - if (r.length > 5 && !(r[5] in resPaths)) { - resPaths[ - r[5] - ] = `Path containing ${r[5]} resources (provided by plug-in)` - } - } - } - }) + for( let i in this.resProvider) { + resPaths[i] = `Path containing ${i.slice(-1)==='s' ? i.slice(0, i.length-1) : i} resources (provided by ${this.resProvider[i]?.pluginId})` + } return resPaths } private checkForProvider(resType: SignalKResourceType): boolean { - return this.resourceTypes.includes(resType) && this.resProvider[resType] - ? true - : false + debug(`** checkForProvider(${resType})`) + debug(this.resProvider[resType]) + + if(this.resProvider[resType]) { + if( + !this.resProvider[resType]?.listResources || + !this.resProvider[resType]?.getResource || + !this.resProvider[resType]?.setResource || + !this.resProvider[resType]?.deleteResource || + typeof this.resProvider[resType]?.listResources !== 'function' || + typeof this.resProvider[resType]?.getResource !== 'function' || + typeof this.resProvider[resType]?.setResource !== 'function' || + typeof this.resProvider[resType]?.deleteResource !== 'function' ) + { + return false + } else { + return true + } + } + else { + return false + } } private buildDeltaMsg( From cbb2bac4d8b7851144e1719fbe76855159da07dd Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Thu, 25 Nov 2021 14:16:04 +1030 Subject: [PATCH 083/410] chore: update docs --- RESOURCE_PROVIDER_PLUGINS.md | 270 +++++++++++------------------------ 1 file changed, 81 insertions(+), 189 deletions(-) diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index 0b493fb05..eb31cd98f 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -26,27 +26,32 @@ These plugins are called __Resource Providers__. This de-coupling of resource request handling and storage / retrieval provides great flexibility to ensure that an appropriate resource storage solution can be configured for your SignalK implementation. -It is important to understand that the SignalK server handles requests for __Custom__ resource types differently to requests for __Common__ types, please ensure you refer to the relevant section of this document. +SignalK server handles requests for both __Common__ and __Custom__ resource types in a similar manner, the only difference being that it does not perform any validation on __Custom__ resource data, so a plugin can act a s a provider for both types. --- +## Server Operation: -## Common Resource Type Provider: +The Signal K server handles all requests to `/signalk/v1/api/resources` and all sub-paths, before passing on the request to the registered resource provider plugin. -As detailed earlier in this document, the __Common__ resource types are: -`routes`, `waypoints`, `notes`, `regions` & `charts`. +The following operations are performed by the server when a request is received: +- Checks for a registered provider for the resource type +- Checks that ResourceProvider methods are defined +- For __Common__ resource types, checks the validity of the: + - Resource id + - Submitted resource data. -For the `/signalk/v1/api/resources` path and the __Common__ resource type sub-paths, the Signal K server will pre-process the request before passing on the request details on to the registered resource provider. +Upon successful completion of these operations the request will then be passed to the registered resource provider plugin. -The SignalK server performs the following tasks when pre-processing a request: -- Checks for a registered provider for the resource type -- Checks the validity of the supplied resource id -- For requests to store data, the submitted resource data is validated. +--- +## Resource Provider plugin: -Only when all pre-processing tasks have completed successfully will the request be passed on to the resource provider plugin. +For a plugin to be considered a Resource Provider it needs to implement the `ResourceProvider` interface. -Resource providers for __Common__ resource types need to implement the `ResourceProvider` interface which registers with the SignalK server the: +By implementing this interface the plugin is able to register with the SignalK server the: - Resource types provided for by the plugin -- Methods to use when requests are passed to the plugin. It is these methods that implement the saving / deletion of resource data to storage and the retrieval of requested resources. +- Methods to used to action requests. + +It is these methods that perform the retrival, saving and deletion of resources from storage. ### Resource Provider Interface @@ -55,11 +60,8 @@ Resource providers for __Common__ resource types need to implement the `Resource The `ResourceProvider` interface defines the contract between the the Resource Provider plugin and the SignalK server and has the following definition _(which it and other related types can be imported from `@signalk/server-api`)_: ```typescript -import { SignalKResourceType } from '@signalk/server-api' -// SignalKResourceType= 'routes' | 'waypoints' |'notes' |'regions' |'charts' - interface ResourceProvider: { - types: SignalKResourceType[], + types: string[], methods: { listResources: (type:string, query: {[key:string]:any})=> Promise getResource: (type:string, id:string)=> Promise @@ -70,7 +72,7 @@ interface ResourceProvider: { ``` where: -- `types`: An array containing a list of __Common__ resource types provided for by the plugin +- `types`: An array containing a list of resource types provided for by the plugin. These can be a mixture of both __Common__ and __Custom__ resource types. - `methods`: An object containing the methods resource requests are passed to by the SignalK server. The plugin __MUST__ implement each method, even if that operation is not supported by the plugin! #### __Method Details:__ @@ -80,9 +82,9 @@ __`listResources(type, query)`__: This method is called when a request is made f _Note: It is the responsibility of the resource provider plugin to filter the resources returned as per the supplied query parameters._ -`type:` Array of __Common__ resource types provied for by the plugin _e.g. ['routes','waypoints']_ +`type:` String containing the type of resource to retrieve. -`query:` Object contining `key | value` pairs repesenting the parameters by which to filter the returned entries _e.g. {distance,'50000}_ +`query:` Object contining `key | value` pairs repesenting the parameters by which to filter the returned entries. _e.g. {distance,'50000}_ `returns:` - Resolved Promise containing a list of resource entries on completion. @@ -108,9 +110,9 @@ listResources( --- __`getResource(type, id)`__: This method is called when a request is made for a specific resource entry of the supplied resource type and id. -`type:` String containing one of the __Common__ resource types _e.g. 'waypoints'_ +`type:` String containing the type of resource to retrieve. -`id:` String containing the target resource entry id _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ +`id:` String containing the target resource entry id. _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ `returns:` - Resolved Promise containing the resource entry on completion. @@ -132,9 +134,9 @@ getResource( --- __`setResource(type, id, value)`__: This method is called when a request is made to save / update a resource entry of the specified resource type, with the supplied id and data. -`type:` Array of __Common__ resource types provied for by the plugin _e.g. ['routes','waypoints']_ +`type:` String containing the type of resource to store. -`id:` String containing the target resource entry id _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ +`id:` String containing the target resource entry id. _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ `value:` Resource data to be stored. @@ -173,9 +175,9 @@ setResource( --- __`deleteResource(type, id)`__: This method is called when a request is made to remove the specific resource entry of the supplied resource type and id. -`type:` String containing one of the __Common__ resource types _e.g. 'waypoints'_ +`type:` String containing the type of resource to delete. -`id:` String containing the target resource entry id _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ +`id:` String containing the target resource entry id. _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ `returns:` - Resolved Promise on completion. @@ -194,76 +196,10 @@ deleteResource( ); ``` +### Registering a Resource Provider: --- -### Example: - -_Defintion for Resource Provider plugin providing for the retrieval routes & waypoints._ -```javascript -// SignalK server plugin -module.exports = function (app) { - - let plugin= { - id: 'mypluginid', - name: 'My Resource Providerplugin', - // ResourceProvider interface - resourceProvider: { - types: ['routes','waypoints'], - methods: { - listResources: (type, params)=> { - return new Promise( (resolve, reject) => { - // fetch resource entries from storage - .... - if(ok) { // success - resolve({ - 'id1': { ... }, - 'id2': { ... }, - }); - } else { // error - reject(new Error('Error encountered!') - } - } - }, - getResource: (type, id)=> { - // fetch resource entries from storage - .... - if(ok) { // success - return Promise.resolve({ - ... - }); - } else { // error - reject(new Error('Error encountered!') - } - }, - setResource: (type, id, value)=> { - // not implemented - return Promise.reject(new Error('NOT IMPLEMENTED!')); - }, - deleteResource: (type, id)=> { - // not implemented - return Promise.reject(new Error('NOT IMPLEMENTED!')); - } - } - }, - - start: (options)=> { - ... - app.resourceApi.register(this.id, this.resourceProvider); - }, - - stop: ()=> { - app.resourceApi.unRegister(this.id); - ... - } - } -} -``` - - -### Registering the Resource Provider: ---- - -For the plugin to be registered by the SignalK server, it needs to call the server's `resourcesApi.register()` function during plugin startup. +To register the resource provider plugin with the SignalK server, the server's `resourcesApi.register()` function should be called during plugin startup. The server `resourcesApi.register()` function has the following signature: @@ -274,7 +210,7 @@ where: - `pluginId`: is the plugin's id - `resourceProvider`: is a reference to the plugins ResourceProvider interface. -_Note: A resource type can only have one registered plugin, so if more than one installed plugin attempts to register as a provider for the same resource type, only the first one is registered and it's implemented methods will service requests._ +_Note: A resource type can only have one registered plugin, so if more than one plugin attempts to register as a provider for the same resource type, the first plugin to call the `register()` function will be registered by the server for the resource types defined in the ResourceProvider interface!_ _Example:_ ```javascript @@ -304,7 +240,7 @@ module.exports = function (app) { ### Un-registering the Resource Provider: --- -When a resource provider plugin is disabled, it should un-register as a provider to ensure resource requests are no longer directed to it by calling the SignalK server's `resourcesApi.unRegister()` function during shutdown. +When a resource provider plugin is disabled, it should un-register itself to ensure resource requests are no longer directed to it by calling the SignalK server. This should be done by calling the server's `resourcesApi.unRegister()` function during shutdown. The server `resourcesApi.unRegister()` function has the following signature: @@ -334,113 +270,69 @@ module.exports = function (app) { } } ``` ---- -## Custom Resource Type Provider: - -Custom resource types are collections of resource entries residing within a sub-path with a user defined name under `/signalk/v1/api/resources`. - -_Example:_ -``` -/signalk/v1/api/resources/fishingZones -``` - -_Note: A custom resource path name __CANNOT__ be one of the __Common__ resource types i.e. `routes`, `waypoints`, `notes`, `regions` or `charts`.__ - -Unlike the __Common Resource Type Providers__: -- The plugin __DOES NOT__ implement the `ResourceProvider` interface -- Requests to __Custom__ resource type sub-paths and any submitted data __WILL NOT__ be pre-processed or validated by the Signal K server -- The plugin __WILL__ need to implement a route handler for the necessary path(s) -- The plugin __WILL__ need to implement any necessary data validation. - - -### Router Path Handlers ---- - -To set up a router path handler, you use the reference to the SignalK server `app` passed to the plugin to register the paths for which requests will be passed to the plugin. +--- -This should be done during plugin startup within the plugin `start()` function. +### __Example:__ -_Note: To ensure that your resource path is listed when a request to `/signalk/v1/api/resources` is made ensure you use the full path as outlined in the example below._ +Resource Provider plugin providing for the retrieval of routes & waypoints. -_Example:_ ```javascript - +// SignalK server plugin module.exports = function (app) { let plugin= { id: 'mypluginid', name: 'My Resource Providerplugin', - start: (options) => { - // setup router path handlers - initPathHandlers(app); - ... - } - } - - function initPathHandlers(app) { - app.get( - `/signalk/v1/api/resources/myResType`, - (request, response)=> { - // retrieve resource(s) - let result= getMyResources(); - response.status(200).json(result); - } - ); - app.post( - `/signalk/v1/api/resources/myResType`, - (request, response)=> { - // create new resource - ... - } - ); - router.put( - `/signalk/v1/api/resources/myResType/:id`, - (request, response)=> { - // create / update resource with supplied id - ... - } - ); - router.delete( - `/signalk/v1/api/resources/myResType/:id`, - (request, response)=> { - // delete the resource with supplied id - ... + // ResourceProvider interface + resourceProvider: { + types: ['routes','waypoints'], + methods: { + listResources: (type, params)=> { + return new Promise( (resolve, reject) => { + // fetch resource entries from storage + .... + if(ok) { // success + resolve({ + 'id1': { ... }, + 'id2': { ... }, + }); + } else { // error + reject(new Error('Error encountered!') + } + } + }, + getResource: (type, id)=> { + // fetch resource entries from storage + .... + if(ok) { // success + return Promise.resolve({ + ... + }); + } else { // error + reject(new Error('Error encountered!') + } + }, + setResource: (type, id, value)=> { + // not implemented + return Promise.reject(new Error('NOT IMPLEMENTED!')); + }, + deleteResource: (type, id)=> { + // not implemented + return Promise.reject(new Error('NOT IMPLEMENTED!')); + } } - ); - } - -``` - -Once registered requests to `/signalk/v1/api/resources/myResType` will be passed to the respective handler based on the request type _(i.e. GET, PUT, POST, DELETE)_ and the request details are available in the `request` parameter. - -For more information regarding the `request` and `response` parameters see the [Express Routing](https://expressjs.com/en/guide/routing.html) documentation. - -### Data Validation ---- + }, -When providing for resource data to be persisted to storage (PUT, POST) it is recommended that data validation implemented within the `app.put()` and `app.post()` route handlers and that the appropriate `status` is returned. + start: (options)=> { + ... + app.resourceApi.register(this.id, this.resourceProvider); + }, -_Example:_ -```javascript -app.post( - `/signalk/v1/api/resources/myResType`, - (request, response)=> { - // validate submitted data - let ok= validate(request.body); - if (ok) { //valid data - if (saveResource(request.body)) { - response.status(200).send('OK'); - } else { - response.status(404).send('ERROR svaing resource!'); - } - } else { - response.status(406).send('ERROR: Invalid data!'); + stop: ()=> { + app.resourceApi.unRegister(this.id); + ... } } -) - +} ``` ---- - - From 115686c2945b69287cbe0c6d4a3217a893a5117d Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Thu, 25 Nov 2021 14:34:17 +1030 Subject: [PATCH 084/410] chore: lint --- src/api/resources/index.ts | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index a3e32cb19..80b313962 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -160,7 +160,11 @@ export class Resources { return } - if (this.signalkResTypes.includes(req.params.resourceType as SignalKResourceType)) { + if ( + this.signalkResTypes.includes( + req.params.resourceType as SignalKResourceType + ) + ) { if (!validate.resource(req.params.resourceType, req.body)) { res.status(406).send(`Invalid resource data supplied!`) return @@ -172,7 +176,7 @@ export class Resources { id = req.body.identifier } else { id = UUID_PREFIX + uuidv4() - } + } try { const retVal = await this.resProvider[ @@ -211,7 +215,11 @@ export class Resources { return } - if (this.signalkResTypes.includes(req.params.resourceType as SignalKResourceType)) { + if ( + this.signalkResTypes.includes( + req.params.resourceType as SignalKResourceType + ) + ) { let isValidId: boolean if (req.params.resourceType === 'charts') { isValidId = validate.chartId(req.params.resourceId) @@ -380,8 +388,10 @@ export class Resources { private getResourcePaths(): { [key: string]: any } { const resPaths: { [key: string]: any } = {} - for( let i in this.resProvider) { - resPaths[i] = `Path containing ${i.slice(-1)==='s' ? i.slice(0, i.length-1) : i} resources (provided by ${this.resProvider[i]?.pluginId})` + for (const i in this.resProvider) { + resPaths[i] = `Path containing ${ + i.slice(-1) === 's' ? i.slice(0, i.length - 1) : i + } resources (provided by ${this.resProvider[i]?.pluginId})` } return resPaths } @@ -390,8 +400,8 @@ export class Resources { debug(`** checkForProvider(${resType})`) debug(this.resProvider[resType]) - if(this.resProvider[resType]) { - if( + if (this.resProvider[resType]) { + if ( !this.resProvider[resType]?.listResources || !this.resProvider[resType]?.getResource || !this.resProvider[resType]?.setResource || @@ -399,14 +409,13 @@ export class Resources { typeof this.resProvider[resType]?.listResources !== 'function' || typeof this.resProvider[resType]?.getResource !== 'function' || typeof this.resProvider[resType]?.setResource !== 'function' || - typeof this.resProvider[resType]?.deleteResource !== 'function' ) - { + typeof this.resProvider[resType]?.deleteResource !== 'function' + ) { return false } else { return true } - } - else { + } else { return false } } From f96e52682de177e992a1d22a727749e9ab244435 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 27 Nov 2021 16:06:13 +1030 Subject: [PATCH 085/410] update types --- packages/server-api/src/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/server-api/src/index.ts b/packages/server-api/src/index.ts index 2ccfc0bae..3f3b45925 100644 --- a/packages/server-api/src/index.ts +++ b/packages/server-api/src/index.ts @@ -5,9 +5,10 @@ export { PropertyValue, PropertyValues, PropertyValuesCallback } from './propert export type SignalKResourceType= 'routes' | 'waypoints' |'notes' |'regions' |'charts' +export type ResourceTypes= SignalKResourceType[] | string[] export interface ResourceProviderMethods { - pluginId: string + pluginId?: string listResources: (type: string, query: { [key: string]: any }) => Promise getResource: (type: string, id: string) => Promise setResource: ( @@ -19,7 +20,7 @@ export interface ResourceProviderMethods { } export interface ResourceProvider { - types: SignalKResourceType[] + types: ResourceTypes methods: ResourceProviderMethods } From 5ac7eea14450aeb7847a86265537936d4c887a09 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 27 Nov 2021 16:08:14 +1030 Subject: [PATCH 086/410] align response formatting forGET ./resources/ --- src/api/resources/index.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 80b313962..52a23aaa6 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -389,9 +389,12 @@ export class Resources { private getResourcePaths(): { [key: string]: any } { const resPaths: { [key: string]: any } = {} for (const i in this.resProvider) { - resPaths[i] = `Path containing ${ - i.slice(-1) === 's' ? i.slice(0, i.length - 1) : i - } resources (provided by ${this.resProvider[i]?.pluginId})` + resPaths[i] = { + description: `Path containing ${ + i.slice(-1) === 's' ? i.slice(0, i.length - 1) : i + } resources`, + $source: this.resProvider[i]?.pluginId + } } return resPaths } From 43176eb7189ba7192c415a088c23a95443e5faa2 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 1 Dec 2021 10:15:08 +1030 Subject: [PATCH 087/410] add resourceApi to index.ts after rebase --- src/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/index.ts b/src/index.ts index 6286c2be9..2645d1814 100644 --- a/src/index.ts +++ b/src/index.ts @@ -51,6 +51,8 @@ import { startSecurity } from './security.js' +import { Resources } from './api/resources' + // tslint:disable-next-line: no-var-requires const { StreamBundle } = require('./streambundle') @@ -81,6 +83,8 @@ class Server { require('./serverroutes')(app, saveSecurityConfig, getSecurityConfig) require('./put').start(app) + app.resourcesApi = new Resources(app) + app.signalk = new FullSignalK(app.selfId, app.selfType) app.propertyValues = new PropertyValues() From ba2996456a963d91d5e302f66d2a30f66cb5fbb0 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Fri, 3 Dec 2021 15:09:37 +1030 Subject: [PATCH 088/410] add charts API methods --- src/api/resources/index.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 52a23aaa6..fac1ff00a 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -1,14 +1,14 @@ import Debug from 'debug' +import { Application, NextFunction, Request, Response } from 'express' import { v4 as uuidv4 } from 'uuid' -import { buildResource } from './resources' -import { validate } from './validate' - import { ResourceProvider, ResourceProviderMethods, SignalKResourceType } from '@signalk/server-api' -import { Application, Handler, NextFunction, Request, Response } from 'express' + +import { buildResource } from './resources' +import { validate } from './validate' const debug = Debug('signalk:resources') @@ -23,7 +23,9 @@ const API_METHODS = [ 'setNote', 'deleteNote', 'setRegion', - 'deleteRegion' + 'deleteRegion', + 'setChart', + 'deleteChart' ] interface ResourceApplication extends Application { @@ -332,6 +334,9 @@ export class Resources { if (req.params.apiFunction.toLowerCase().indexOf('region') !== -1) { resType = 'regions' } + if (req.params.apiFunction.toLowerCase().indexOf('charts') !== -1) { + resType = 'charts' + } if (!this.checkForProvider(resType)) { res.status(501).send(`No provider for ${resType}!`) return From 85457157596c46cfde088c3667e79567075bbb55 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 5 Dec 2021 15:52:33 +1030 Subject: [PATCH 089/410] add securityStrategy test --- src/api/resources/index.ts | 201 ++++++++++++++++++++++++------------- 1 file changed, 134 insertions(+), 67 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index fac1ff00a..ab3d658f3 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -1,11 +1,11 @@ -import Debug from 'debug' -import { Application, NextFunction, Request, Response } from 'express' -import { v4 as uuidv4 } from 'uuid' import { ResourceProvider, ResourceProviderMethods, SignalKResourceType } from '@signalk/server-api' +import Debug from 'debug' +import { Application, NextFunction, Request, Response } from 'express' +import { v4 as uuidv4 } from 'uuid' import { buildResource } from './resources' import { validate } from './validate' @@ -15,21 +15,16 @@ const debug = Debug('signalk:resources') const SIGNALK_API_PATH = `/signalk/v1/api` const UUID_PREFIX = 'urn:mrn:signalk:uuid:' -const API_METHODS = [ - 'setWaypoint', - 'deleteWaypoint', - 'setRoute', - 'deleteRoute', - 'setNote', - 'deleteNote', - 'setRegion', - 'deleteRegion', - 'setChart', - 'deleteChart' -] - interface ResourceApplication extends Application { handleMessage: (id: string, data: any) => void + securityStrategy: { + shouldAllowPut: ( + req: any, + context: string, + source: any, + path: string + ) => boolean + } } export class Resources { @@ -94,6 +89,15 @@ export class Resources { this.initResourceRoutes() } + private updateAllowed(): boolean { + return this.server.securityStrategy.shouldAllowPut( + this.server, + 'vessels.self', + null, + 'resources' + ) + } + private initResourceRoutes() { // list all serviced paths under resources this.server.get( @@ -154,6 +158,7 @@ export class Resources { `${SIGNALK_API_PATH}/resources/:resourceType`, async (req: Request, res: Response, next: NextFunction) => { debug(`** POST ${SIGNALK_API_PATH}/resources/:resourceType`) + if ( !this.checkForProvider(req.params.resourceType as SignalKResourceType) ) { @@ -162,6 +167,10 @@ export class Resources { return } + if (!this.updateAllowed()) { + res.status(403) + return + } if ( this.signalkResTypes.includes( req.params.resourceType as SignalKResourceType @@ -217,6 +226,10 @@ export class Resources { return } + if (!this.updateAllowed()) { + res.status(403) + return + } if ( this.signalkResTypes.includes( req.params.resourceType as SignalKResourceType @@ -288,6 +301,10 @@ export class Resources { return } + if (!this.updateAllowed()) { + res.status(403) + return + } try { const retVal = await this.resProvider[ req.params.resourceType @@ -312,85 +329,135 @@ export class Resources { // facilitate API requests this.server.put( - `${SIGNALK_API_PATH}/resources/:apiFunction`, - async (req: Request, res: Response, next: NextFunction) => { - debug(`** PUT ${SIGNALK_API_PATH}/resources/:apiFunction`) + `${SIGNALK_API_PATH}/resources/set/:resourceType`, + async (req: Request, res: Response) => { + debug(`** PUT ${SIGNALK_API_PATH}/resources/set/:resourceType`) - // check for valid API method request - if (!API_METHODS.includes(req.params.apiFunction)) { - res.status(501).send(`Invalid API method ${req.params.apiFunction}!`) + if (!this.updateAllowed()) { + res.status(403) return } - let resType: SignalKResourceType = 'waypoints' - if (req.params.apiFunction.toLowerCase().indexOf('waypoint') !== -1) { - resType = 'waypoints' - } - if (req.params.apiFunction.toLowerCase().indexOf('route') !== -1) { - resType = 'routes' + + const apiData = this.processApiRequest(req) + + if (!this.checkForProvider(apiData.type)) { + res.status(501).send(`No provider for ${apiData.type}!`) + return } - if (req.params.apiFunction.toLowerCase().indexOf('note') !== -1) { - resType = 'notes' + if (!apiData.value) { + res.status(406).send(`Invalid resource data supplied!`) + return } - if (req.params.apiFunction.toLowerCase().indexOf('region') !== -1) { - resType = 'regions' + if (apiData.type === 'charts') { + if (!validate.chartId(apiData.id)) { + res.status(406).send(`Invalid chart resource id supplied!`) + return + } + } else { + if (!validate.uuid(apiData.id)) { + res.status(406).send(`Invalid resource id supplied!`) + return + } } - if (req.params.apiFunction.toLowerCase().indexOf('charts') !== -1) { - resType = 'charts' + + try { + const retVal = await this.resProvider[apiData.type]?.setResource( + apiData.type, + apiData.id, + apiData.value + ) + this.server.handleMessage( + this.resProvider[apiData.type]?.pluginId as string, + this.buildDeltaMsg(apiData.type, apiData.id, apiData.value) + ) + res.status(200).send(`SUCCESS: ${req.params.resourceType} complete.`) + } catch (err) { + res.status(404).send(`ERROR: ${req.params.resourceType} incomplete!`) } - if (!this.checkForProvider(resType)) { - res.status(501).send(`No provider for ${resType}!`) + } + ) + this.server.put( + `${SIGNALK_API_PATH}/resources/set/:resourceType/:resourceId`, + async (req: Request, res: Response) => { + debug( + `** PUT ${SIGNALK_API_PATH}/resources/set/:resourceType/:resourceId` + ) + + if (!this.updateAllowed()) { + res.status(403) return } - let resId: string = '' - let resValue: any = null - if (req.params.apiFunction.toLowerCase().indexOf('set') !== -1) { - resValue = buildResource(resType, req.body) - if (!resValue) { - res.status(406).send(`Invalid resource data supplied!`) - return - } - if (!req.body.id) { - resId = UUID_PREFIX + uuidv4() - } else { - if (!validate.uuid(req.body.id)) { - res.status(406).send(`Invalid resource id supplied!`) - return - } - resId = req.body.id - } + const apiData = this.processApiRequest(req) + + if (!this.checkForProvider(apiData.type)) { + res.status(501).send(`No provider for ${apiData.type}!`) + return + } + if (!apiData.value) { + res.status(406).send(`Invalid resource data supplied!`) + return } - if (req.params.apiFunction.toLowerCase().indexOf('delete') !== -1) { - resValue = null - if (!req.body.id) { - res.status(406).send(`No resource id supplied!`) + if (apiData.type === 'charts') { + if (!validate.chartId(apiData.id)) { + res.status(406).send(`Invalid chart resource id supplied!`) return } - if (!validate.uuid(req.body.id)) { + } else { + if (!validate.uuid(apiData.id)) { res.status(406).send(`Invalid resource id supplied!`) return } - resId = req.body.id } try { - const retVal = await this.resProvider[resType]?.setResource( - resType, - resId, - resValue + const retVal = await this.resProvider[apiData.type]?.setResource( + apiData.type, + apiData.id, + apiData.value ) this.server.handleMessage( - this.resProvider[resType]?.pluginId as string, - this.buildDeltaMsg(resType, resId, resValue) + this.resProvider[apiData.type]?.pluginId as string, + this.buildDeltaMsg(apiData.type, apiData.id, apiData.value) ) - res.status(200).send(`SUCCESS: ${req.params.apiFunction} complete.`) + res.status(200).send(`SUCCESS: ${req.params.resourceType} complete.`) } catch (err) { - res.status(404).send(`ERROR: ${req.params.apiFunction} incomplete!`) + res.status(404).send(`ERROR: ${req.params.resourceType} incomplete!`) } } ) } + private processApiRequest(req: Request) { + let resType: SignalKResourceType = 'waypoints' + if (req.params.resourceType.toLowerCase() === 'waypoint') { + resType = 'waypoints' + } + if (req.params.resourceType.toLowerCase() === 'route') { + resType = 'routes' + } + if (req.params.resourceType.toLowerCase() === 'note') { + resType = 'notes' + } + if (req.params.resourceType.toLowerCase() === 'region') { + resType = 'regions' + } + if (req.params.resourceType.toLowerCase() === 'charts') { + resType = 'charts' + } + + const resId: string = req.params.resourceId + ? req.params.resourceId + : UUID_PREFIX + uuidv4() + const resValue: any = buildResource(resType, req.body) + + return { + type: resType, + id: resId, + value: resValue + } + } + private getResourcePaths(): { [key: string]: any } { const resPaths: { [key: string]: any } = {} for (const i in this.resProvider) { From fbade98080ec579ae3f41a5124c4ad7acb50a74c Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 5 Dec 2021 17:39:56 +1030 Subject: [PATCH 090/410] chore: update docs --- RESOURCE_PROVIDER_PLUGINS.md | 2 +- WORKING_WITH_RESOURCES_API.md | 316 +++++++++++++++++++++++++++++++++ src/api/resources/openApi.json | 222 +---------------------- 3 files changed, 323 insertions(+), 217 deletions(-) create mode 100644 WORKING_WITH_RESOURCES_API.md diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index eb31cd98f..5cd47f6fa 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -15,7 +15,7 @@ It also defines the schema for the following __Common__ resource types: - regions - charts -each of with its own path under the root `resources` path _(e.g. `/signalk/v1/api/resources/routes`)_. +each with its own path under the root `resources` path _(e.g. `/signalk/v1/api/resources/routes`)_. It should also be noted that the `/signalk/v1/api/resources` path can also host other types of resource data which can be grouped within a __Custom__ path name _(e.g. `/signalk/v1/api/resources/fishingZones`)_. diff --git a/WORKING_WITH_RESOURCES_API.md b/WORKING_WITH_RESOURCES_API.md new file mode 100644 index 000000000..3034b0474 --- /dev/null +++ b/WORKING_WITH_RESOURCES_API.md @@ -0,0 +1,316 @@ +# Working with the Resources API + + +## Overview + +The SignalK specification defines a number of resources (routes, waypoints, notes, regions & charts) each with its path under the root `resources` path _(e.g. `/signalk/v1/api/resources/routes`)_. + +The SignalK server handles requests to these resource paths to enable the retrieval, creation, updating and deletion of resources. + +--- +## Operation: + +For resources to be stored and retrieved, the Signal K server requires that a [Resource Provider plugin](RESOURCE_PROVIDER_PLUGINS.md) be installed and registered to manage each of the resource types your implementation requires. _You can find plugins in the `App Store` section of the server admin UI._ + +Client applications can then use HTTP requests to resource paths to store and retrieve resource entries. _Note: the ability to store resource entries is controlled by the server security settings so client applications may need to authenticate for write / delete operations to complete successfully._ + + +### Retrieving Resources +--- + +Resource entries are retrived by submitting an HTTP `GET` request to the relevant path. + +_Example:_ +```typescript +HTTP GET 'http://hostname:3000/signalk/v1/api/resources/routes' +``` +to return a list of available routes OR +```typescript +HTTP GET 'http://hostname:3000/signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:94052456-65fa-48ce-a85d-41b78a9d2111' +``` +to retrieve a specific resource entry. + +When retrieving a list of entries these can be filtered based on certain criteria such as: + +- being within a bounded area +- distance from vessel +- total entries returned. + +This is done by supplying query string key | value pairs in the request. + +_Example 1: Retrieve waypoints within 50km of the vessel_ +```typescript +HTTP GET 'http://hostname:3000/signalk/v1/api/resources/waypoints?distance=50000' +``` +_Note: the distance supplied is in meters_. + +_Example 2: Retrieve the first 20 waypoints within 90km of the vessel_ +```typescript +HTTP GET 'http://hostname:3000/signalk/v1/api/resources/waypoints?distance=90000&limit=20' +``` +_Note: the distance supplied is in meters_. + +_Example 3: Retrieve waypoints within a bounded area._ +```typescript +HTTP GET 'http://hostname:3000/signalk/v1/api/resources/waypoints?bbox=-135.5,38,-134,38.5' +``` +_Note: the bounded area is supplied as bottom left & top right corner coordinates in the form swLongitude,swLatitude,neLongitude,neLatitude_. + + +### Deleting Resources +--- + +Resource entries are deleted by submitting an HTTP `DELETE` request to a path containing the id of the resource to delete. + +_Example:_ +```typescript +HTTP DELETE 'http://hostname:3000/signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:94052456-65fa-48ce-a85d-41b78a9d2111' +``` + +In this example the route with the supplied id is deleted from storage. + +### Creating / updating Resources +--- + +Resource entries are created and updated by submitting an HTTP `PUT` request that contains the resource data to the relevant API path depending on the type of resource to create / update. + +Each resource type has a specific set of attributes that are required to be supplied before the resource entry can be created. + +Generally a resource will be created by submitting the resource attributes and the server will generate a resource id for the entry. You can assign a specific id for a newly created resource by using an API path containing the id you wish to assign to the resource. _Note: if a resource of the same type with the supplied id already exists, it will be overwritten with the submitted data!_ + +___Note: When submitting data to create or update a resource entry, the submitted resource data is validated against the Signal K schema for that resource type. If the submitted data is deemed to be invalid then the operation is aborted.___ + +___Additionally when supplying an id to assign to or identify the resource on which to perform the operation, the id must be valid for the type of resource as defined in the Signal K schema.___ + +--- +#### __Routes:__ + +To create / update a route entry the body of the PUT request must contain data in the following format: +```javascript +{ + name: 'route name', + description: 'description of the route', + attributes: { + attribute1: 'attribute1 value', + attribute2: 258, + ... + }, + points: [ + {latitude: -38.567,longitude: 135.9467}, + {latitude: -38.967,longitude: 135.2467}, + {latitude: -39.367,longitude: 134.7467}, + {latitude: -39.567,longitude: 134.4467} + ] +} +``` +where: +- name: is text detailing the name of the route +- description (optional): is text describing the route +- attributes (optional): object containing key | value pairs of attributes associated with the route +- points: is an array of route points (latitude and longitude) + + +_Example: Create new route entry (with server generated id)_ +```typescript +HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/route' { + name: 'route name', + description: 'description of the route', + attributes: { + distance: 6580 + }, + points: [ + {latitude: -38.567,longitude: 135.9467}, + {latitude: -38.967,longitude: 135.2467}, + {latitude: -39.367,longitude: 134.7467}, + {latitude: -39.567,longitude: 134.4467} + ] +} +``` + +_Example: Create new route entry (with supplied id)_ +```typescript +HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/route/urn:mrn:signalk:uuid:94052456-65fa-48ce-a85d-41b78a9d2111' { + name: 'route name', + description: 'description of the route', + attributes: { + distance: 6580 + }, + points: [ + {latitude: -38.567,longitude: 135.9467}, + {latitude: -38.967,longitude: 135.2467}, + {latitude: -39.367,longitude: 134.7467}, + {latitude: -39.567,longitude: 134.4467} + ] +} +``` + +--- +#### __Waypoints:__ + +To create / update a waypoint entry the body of the PUT request must contain data in the following format: +```javascript +{ + name: 'waypoint name', + description: 'description of the waypoint', + attributes: { + attribute1: 'attribute1 value', + attribute2: 258, + ... + }, + position: { + latitude: -38.567, + longitude: 135.9467 + } +} +``` +where: +- name: is text detailing the name of the waypoint +- description (optional): is text describing the waypoint +- attributes (optional): object containing key | value pairs of attributes associated with the waypoint +- position: the latitude and longitude of the waypoint + + +_Example: Create new waypoint entry (with server generated id)_ +```typescript +HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/waypoint' { + name: 'waypoint #1', + position: { + latitude: -38.567, + longitude: 135.9467 + } +} +``` + +_Example: Create new waypoint entry (with supplied id)_ +```typescript +HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/waypoint/urn:mrn:signalk:uuid:94052456-65fa-48ce-a85d-41b78a9d2111' { + name: 'waypoint #1', + position: { + latitude: -38.567, + longitude: 135.9467 + } +} +``` + +--- +#### __Regions:__ + +To create / update a region entry the body of the PUT request must contain data in the following format: +```javascript +{ + name: 'region name', + description: 'description of the region', + attributes: { + attribute1: 'attribute1 value', + attribute2: 258, + ... + }, + points: [ + {latitude: -38.567,longitude: 135.9467}, + {latitude: -38.967,longitude: 135.2467}, + {latitude: -39.367,longitude: 134.7467}, + {latitude: -39.567,longitude: 134.4467}, + {latitude: -38.567,longitude: 135.9467} + ], + geohash: 'gbsuv' +} +``` +where: +- name: is text detailing the name of the region +- description (optional): is text describing the region +- attributes (optional): object containing key | value pairs of attributes associated with the region + +and either: +- points: is an array of points (latitude and longitude) defining a polygon. _Note: first and last point in the array must be the same!_ + +OR +- geohash: a value defining a bounded area _e.g. 'gbsuv'_ + + +_Example: Create new region entry (with server generated id)_ +```typescript +HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/region' { + name: 'region name', + description: 'description of the region', + points: [ + {latitude: -38.567,longitude: 135.9467}, + {latitude: -38.967,longitude: 135.2467}, + {latitude: -39.367,longitude: 134.7467}, + {latitude: -39.567,longitude: 134.4467}, + {latitude: -38.567,longitude: 135.9467} + ] +} +``` + +_Example: Create new region entry (with supplied id)_ +```typescript +HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/region/urn:mrn:signalk:uuid:94052456-65fa-48ce-a85d-41b78a9d2111' { + name: 'region name', + description: 'description of the region', + geohash: 'gbsuv' +} +``` + +--- +#### __Notes:__ + +To create / update a note entry the body of the PUT request must contain data in the following format: +```javascript +{ + title: 'note title text', + description: 'description of the note', + attributes: { + attribute1: 'attribute1 value', + attribute2: 258, + ... + }, + url: 'link to note content', + mimeType: 'text/plain, text/html, etc.', + position: { + latitude: -38.567, + longitude: 135.9467 + }, + geohash: 'gbsuv', + region: '/resources/regions/urn:mrn:signalk:uuid:35052456-65fa-48ce-a85d-41b78a9d2a61' +} +``` +where: +- name: is text detailing the name of the note +- description (optional): is text describing the note +- attributes (optional): object containing key | value pairs of attributes associated with the note +- url (optional): link to the note contents +- mimeType (optional): the mime type of the note contents + +and either: +- position: the latitude and longitude associated with the note + +OR +- geohash: a value defining a bounded area associated with the note _e.g. 'gbsuv'_ + +OR +- region: text containing a reference to a region resource associated with the note _e.g. '/resources/regions/urn:mrn:signalk:uuid:35052456-65fa-48ce-a85d-41b78a9d2a61'_ + + +_Example: Create new note entry (with server generated id)_ +```typescript +HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/note' { + title: 'note title', + description: 'text containing brief description', + url: 'http:notehost.com/notes/mynote.html', + mimeType: 'text/plain', + position: { + latitude: -38.567, + longitude: 135.9467 + } +} +``` + +_Example: Create new note entry (with supplied id)_ +```typescript +HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/note/urn:mrn:signalk:uuid:94052456-65fa-48ce-a85d-41b78a9d2111' { + title: 'note title', + description: 'text containing brief description', + region: '/resources/regions/urn:mrn:signalk:uuid:35052456-65fa-48ce-a85d-41b78a9d2a61' +} +``` + diff --git a/src/api/resources/openApi.json b/src/api/resources/openApi.json index 80350d21f..c28f8dce2 100644 --- a/src/api/resources/openApi.json +++ b/src/api/resources/openApi.json @@ -1358,7 +1358,7 @@ }, - "/resources/setWaypoint": { + "/resources/set/waypoint/{id}": { "put": { "tags": ["resources/api"], "summary": "Add / update a Waypoint", @@ -1371,11 +1371,6 @@ "type": "object", "required": ["position"], "properties": { - "id": { - "type": "string", - "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", - "description": "Waypoint identifier" - }, "name": { "type": "string", "description": "Waypoint name" @@ -1429,45 +1424,7 @@ } }, - "/resources/deleteWaypoint": { - "put": { - "tags": ["resources/api"], - "summary": "Remove a Waypoint", - "requestBody": { - "description": "Waypoint identifier", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["id"], - "properties": { - "id": { - "type": "string", - "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", - "description": "Waypoint identifier" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - } - } - } - }, - - "/resources/setRoute": { + "/resources/set/route/{id}": { "put": { "tags": ["resources/api"], "summary": "Add / update a Route", @@ -1480,11 +1437,6 @@ "type": "object", "required": ["position"], "properties": { - "id": { - "type": "string", - "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", - "description": "Route identifier" - }, "name": { "type": "string", "description": "Route name" @@ -1541,45 +1493,7 @@ } }, - "/resources/deleteRoute": { - "put": { - "tags": ["resources/api"], - "summary": "Remove a Route", - "requestBody": { - "description": "Route identifier", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["id"], - "properties": { - "id": { - "type": "string", - "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", - "description": "Route identifier" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - } - } - } - }, - - "/resources/setNote": { + "/resources/set/note/{id}": { "put": { "tags": ["resources/api"], "summary": "Add / update a Note", @@ -1592,11 +1506,6 @@ "type": "object", "required": ["position"], "properties": { - "id": { - "type": "string", - "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", - "description": "Note identifier" - }, "title": { "type": "string", "description": "Note's common name" @@ -1664,45 +1573,7 @@ } }, - "/resources/deleteNote": { - "put": { - "tags": ["resources/api"], - "summary": "Remove a Note", - "requestBody": { - "description": "Note identifier", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["id"], - "properties": { - "id": { - "type": "string", - "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", - "description": "Note identifier" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - } - } - } - }, - - "/resources/setRegion": { + "/resources/set/region/{id}": { "put": { "tags": ["resources/api"], "summary": "Add / update a Region", @@ -1715,11 +1586,6 @@ "type": "object", "required": ["position"], "properties": { - "id": { - "type": "string", - "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", - "description": "Region identifier" - }, "name": { "type": "string", "description": "Region name" @@ -1780,45 +1646,7 @@ } }, - "/resources/deleteRegion": { - "put": { - "tags": ["resources/api"], - "summary": "Remove a Region", - "requestBody": { - "description": "Region identifier", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["id"], - "properties": { - "id": { - "type": "string", - "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", - "description": "Region identifier" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - } - } - } - }, - - "/resources/setChart": { + "/resources/set/chart/{id}": { "put": { "tags": ["resources/api"], "summary": "Add / update a Chart", @@ -1864,7 +1692,7 @@ ], "region": { "type": "string", - "description": "Region related to note. A pointer to a region UUID. Alternative to geohash" + "description": "Region related to chart. A pointer to a region UUID. Alternative to geohash" }, "geohash": { "description": "Position related to chart. Alternative to region", @@ -1948,44 +1776,6 @@ } } } - }, - - "/resources/deleteChart": { - "put": { - "tags": ["resources/api"], - "summary": "Remove a Chart", - "requestBody": { - "description": "Chart identifier", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["id"], - "properties": { - "id": { - "type": "string", - "pattern": "(^[A-Za-z0-9_-]{8,}$)", - "description": "Chart identifier" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - } - } - } } } From bf62346e2e4c615b0aa925be8db5de8b3a04e884 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 6 Dec 2021 11:05:41 +1030 Subject: [PATCH 091/410] chore: update docs --- WORKING_WITH_RESOURCES_API.md | 34 +++++-------- src/api/resources/openApi.json | 91 ++++++++++------------------------ 2 files changed, 39 insertions(+), 86 deletions(-) diff --git a/WORKING_WITH_RESOURCES_API.md b/WORKING_WITH_RESOURCES_API.md index 3034b0474..54019dd61 100644 --- a/WORKING_WITH_RESOURCES_API.md +++ b/WORKING_WITH_RESOURCES_API.md @@ -91,8 +91,6 @@ To create / update a route entry the body of the PUT request must contain data i name: 'route name', description: 'description of the route', attributes: { - attribute1: 'attribute1 value', - attribute2: 258, ... }, points: [ @@ -153,8 +151,6 @@ To create / update a waypoint entry the body of the PUT request must contain dat name: 'waypoint name', description: 'description of the waypoint', attributes: { - attribute1: 'attribute1 value', - attribute2: 258, ... }, position: { @@ -201,8 +197,6 @@ To create / update a region entry the body of the PUT request must contain data name: 'region name', description: 'description of the region', attributes: { - attribute1: 'attribute1 value', - attribute2: 258, ... }, points: [ @@ -211,20 +205,14 @@ To create / update a region entry the body of the PUT request must contain data {latitude: -39.367,longitude: 134.7467}, {latitude: -39.567,longitude: 134.4467}, {latitude: -38.567,longitude: 135.9467} - ], - geohash: 'gbsuv' + ] } ``` where: - name: is text detailing the name of the region - description (optional): is text describing the region - attributes (optional): object containing key | value pairs of attributes associated with the region - -and either: -- points: is an array of points (latitude and longitude) defining a polygon. _Note: first and last point in the array must be the same!_ - -OR -- geohash: a value defining a bounded area _e.g. 'gbsuv'_ +- points: is an array of points (latitude and longitude) defining an area. _Example: Create new region entry (with server generated id)_ @@ -247,7 +235,13 @@ _Example: Create new region entry (with supplied id)_ HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/region/urn:mrn:signalk:uuid:94052456-65fa-48ce-a85d-41b78a9d2111' { name: 'region name', description: 'description of the region', - geohash: 'gbsuv' + points: [ + {latitude: -38.567,longitude: 135.9467}, + {latitude: -38.967,longitude: 135.2467}, + {latitude: -39.367,longitude: 134.7467}, + {latitude: -39.567,longitude: 134.4467}, + {latitude: -38.567,longitude: 135.9467} + ] } ``` @@ -270,8 +264,7 @@ To create / update a note entry the body of the PUT request must contain data in latitude: -38.567, longitude: 135.9467 }, - geohash: 'gbsuv', - region: '/resources/regions/urn:mrn:signalk:uuid:35052456-65fa-48ce-a85d-41b78a9d2a61' + href: 'reference to resource entry' } ``` where: @@ -285,10 +278,7 @@ and either: - position: the latitude and longitude associated with the note OR -- geohash: a value defining a bounded area associated with the note _e.g. 'gbsuv'_ - -OR -- region: text containing a reference to a region resource associated with the note _e.g. '/resources/regions/urn:mrn:signalk:uuid:35052456-65fa-48ce-a85d-41b78a9d2a61'_ +- href: text containing a reference to a resource associated with the note _e.g. '/resources/regions/urn:mrn:signalk:uuid:35052456-65fa-48ce-a85d-41b78a9d2a61'_ _Example: Create new note entry (with server generated id)_ @@ -310,7 +300,7 @@ _Example: Create new note entry (with supplied id)_ HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/note/urn:mrn:signalk:uuid:94052456-65fa-48ce-a85d-41b78a9d2111' { title: 'note title', description: 'text containing brief description', - region: '/resources/regions/urn:mrn:signalk:uuid:35052456-65fa-48ce-a85d-41b78a9d2a61' + href: '/resources/regions/urn:mrn:signalk:uuid:35052456-65fa-48ce-a85d-41b78a9d2a61' } ``` diff --git a/src/api/resources/openApi.json b/src/api/resources/openApi.json index c28f8dce2..6d6a08918 100644 --- a/src/api/resources/openApi.json +++ b/src/api/resources/openApi.json @@ -105,7 +105,7 @@ }, "/resources/routes/": { - "post": { + "put": { "tags": ["resources/routes"], "summary": "Add a new Route", "requestBody": { @@ -339,7 +339,7 @@ }, "/resources/waypoints/": { - "post": { + "put": { "tags": ["resources/waypoints"], "summary": "Add a new Waypoint", "requestBody": { @@ -573,7 +573,7 @@ }, "/resources/notes/": { - "post": { + "put": { "tags": ["resources/notes"], "summary": "Add a new Note", "requestBody": { @@ -785,7 +785,7 @@ }, "/resources/regions/": { - "post": { + "put": { "tags": ["resources/regions"], "summary": "Add a new Region", "requestBody": { @@ -1057,7 +1057,7 @@ }, "/resources/charts/": { - "post": { + "put": { "tags": ["resources/charts"], "summary": "Add a new Chart", "requestBody": { @@ -1504,7 +1504,7 @@ "application/json": { "schema": { "type": "object", - "required": ["position"], + "required": ["title"], "properties": { "title": { "type": "string", @@ -1534,10 +1534,6 @@ } } }, - "geohash": { - "type": "string", - "description": "Position related to note. Alternative to region or position" - }, "region": { "type": "string", "description": "Region related to note. A pointer to a region UUID. Alternative to position or geohash", @@ -1601,10 +1597,6 @@ "type": "string" } }, - "geohash": { - "type": "string", - "description": "Area related to region. Alternative to points." - }, "points": { "description": "Region boundary points", "type": "array", @@ -1658,51 +1650,32 @@ "schema": { "type": "object", "description": "Signal K Chart resource", - "required": ["identifier","tilemapUrl","chartUrl","chartFormat"], + "required": ["identifier", "tilemapUrl"], "properties": { - "name": { - "description": "Chart common name", - "example":"NZ615 Marlborough Sounds", - "type": "string" - }, "identifier": { "type": "string", "description": "Chart number", "example":"NZ615" }, + "name": { + "description": "Chart common name", + "example":"NZ615 Marlborough Sounds", + "type": "string" + }, "description": { "type": "string", "description": "A description of the chart" }, - "oneOf": [ - { - "tilemapUrl": { - "type": "string", - "description": "A url to the tilemap of the chart for use in TMS chartplotting apps", - "example":"http://{server}:8080/mapcache/NZ615" - } - }, - { - "chartUrl": { - "type": "string", - "description": "A url to the chart file's storage location", - "example":"file:///home/pi/freeboard/mapcache/NZ615" - } - } - ], - "region": { + "tilemapUrl": { "type": "string", - "description": "Region related to chart. A pointer to a region UUID. Alternative to geohash" - }, - "geohash": { - "description": "Position related to chart. Alternative to region", - "type": "string" + "description": "A url to the tilemap of the chart for use in TMS chartplotting apps", + "example":"http://{server}:8080/mapcache/NZ615" }, "scale": { "type": "integer", "description": "The scale of the chart, the larger number from 1:200000" }, - "chartLayers": { + "layers": { "type": "array", "description": "If the chart format is WMS, the layers enabled for the chart.", "items": { @@ -1712,7 +1685,7 @@ }, "bounds": { "type": "array", - "description": "The bounds of the chart. An array containing the position of the upper left corner, and the lower right corner. Useful when the chart isn't inherently geo-referenced.", + "description": "The bounding rectangle of the chart defined by the position of the lower left corner, and the upper right corner.", "items": { "description": "Position of a corner of the chart", "type": "object", @@ -1728,34 +1701,24 @@ "longitude": { "type": "number", "format": "float" - }, - "altitude": { - "type": "number", - "format": "float" } } } }, - "chartFormat": { + "format": { "type": "string", "description": "The format of the chart", "enum": [ - "gif", - "geotiff", - "kap", "png", - "jpg", - "kml", - "wkt", - "topojson", - "geojson", - "gpx", - "tms", - "wms", - "S-57", - "S-63", - "svg", - "other" + "jpg" + ] + }, + "serverType": { + "type": "string", + "description": "Chart server type", + "enum": [ + "tilelayer", + "WMS" ] } } From 7449207b7ba57548fa9ed0916196685bd4d7874f Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 6 Dec 2021 11:06:17 +1030 Subject: [PATCH 092/410] fix chart identifier --- src/api/resources/index.ts | 10 ++- src/api/resources/resources.ts | 141 ++++++++++++++++++++++----------- 2 files changed, 101 insertions(+), 50 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index ab3d658f3..1babba166 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -154,7 +154,7 @@ export class Resources { ) // facilitate creation of new resource entry of supplied type - this.server.post( + this.server.put( `${SIGNALK_API_PATH}/resources/:resourceType`, async (req: Request, res: Response, next: NextFunction) => { debug(`** POST ${SIGNALK_API_PATH}/resources/:resourceType`) @@ -429,6 +429,7 @@ export class Resources { } private processApiRequest(req: Request) { + let resType: SignalKResourceType = 'waypoints' if (req.params.resourceType.toLowerCase() === 'waypoint') { resType = 'waypoints' @@ -446,10 +447,13 @@ export class Resources { resType = 'charts' } + const resValue: any = buildResource(resType, req.body) + const resId: string = req.params.resourceId ? req.params.resourceId - : UUID_PREFIX + uuidv4() - const resValue: any = buildResource(resType, req.body) + : resType = 'charts' + ? resValue.identifier + : UUID_PREFIX + uuidv4() return { type: resType, diff --git a/src/api/resources/resources.ts b/src/api/resources/resources.ts index 6a45f736c..badcdaf6e 100644 --- a/src/api/resources/resources.ts +++ b/src/api/resources/resources.ts @@ -1,6 +1,5 @@ import { SignalKResourceType } from '@signalk/server-api' -import { getDistance, isValidCoordinate } from 'geolib' -import ngeohash from 'ngeohash' +import { getDistance, getLatitude, isValidCoordinate } from 'geolib' export const buildResource = (resType: SignalKResourceType, data: any): any => { if (resType === 'routes') { @@ -15,6 +14,10 @@ export const buildResource = (resType: SignalKResourceType, data: any): any => { if (resType === 'regions') { return buildRegion(data) } + if (resType === 'charts') { + return buildChart(data) + } + return null } const buildRoute = (rData: any): any => { @@ -122,8 +125,7 @@ const buildNote = (rData: any): any => { } if ( typeof rData.position === 'undefined' && - typeof rData.region === 'undefined' && - typeof rData.geohash === 'undefined' + typeof rData.href === 'undefined' ) { return null } @@ -134,11 +136,8 @@ const buildNote = (rData: any): any => { } note.position = rData.position } - if (typeof rData.region !== 'undefined') { - note.region = rData.region - } - if (typeof rData.geohash !== 'undefined') { - note.geohash = rData.geohash + if (typeof rData.href !== 'undefined') { + note.region = rData.href } if (typeof rData.url !== 'undefined') { note.url = rData.url @@ -173,48 +172,96 @@ const buildRegion = (rData: any): any => { Object.assign(reg.feature.properties, rData.attributes) } - if (typeof rData.points === 'undefined' && rData.geohash === 'undefined') { + if (typeof rData.points !== 'undefined') { return null } - if (typeof rData.geohash !== 'undefined') { - reg.geohash = rData.geohash - - const bounds = ngeohash.decode_bbox(rData.geohash) - coords = [ - [bounds[1], bounds[0]], - [bounds[3], bounds[0]], - [bounds[3], bounds[2]], - [bounds[1], bounds[2]], - [bounds[1], bounds[0]] - ] - reg.feature.geometry.coordinates.push(coords) + if (!Array.isArray(rData.points)) { + return null } - if (typeof rData.points !== 'undefined' && coords.length === 0) { - if (!Array.isArray(rData.points)) { - return null - } - let isValid: boolean = true - rData.points.forEach((p: any) => { - if (!isValidCoordinate(p)) { - isValid = false - } - }) - if (!isValid) { - return null - } - if ( - rData.points[0].latitude !== - rData.points[rData.points.length - 1].latitude && - rData.points[0].longitude !== - rData.points[rData.points.length - 1].longitude - ) { - rData.points.push(rData.points[0]) + let isValid: boolean = true + rData.points.forEach((p: any) => { + if (!isValidCoordinate(p)) { + isValid = false } - coords = rData.points.map((p: any) => { - return [p.longitude, p.latitude] - }) - reg.feature.geometry.coordinates.push(coords) + }) + if (!isValid) { + return null } - + if ( + rData.points[0].latitude !== + rData.points[rData.points.length - 1].latitude && + rData.points[0].longitude !== + rData.points[rData.points.length - 1].longitude + ) { + rData.points.push(rData.points[0]) + } + coords = rData.points.map((p: any) => { + return [p.longitude, p.latitude] + }) + reg.feature.geometry.coordinates.push(coords) + return reg } + +const buildChart = (rData: any): any => { + const chart: any = { + identifier: '', + name: '', + description: '', + minzoom: 1, + maxzoom: 28, + type: 'tilelayer', + format: 'png', + tilemapUrl: '', + chartLayers: [], + scale: 250000, + bounds: [-180,-90,180,90] + } + + if (typeof rData.identifier === 'undefined') { + return null + } else { + chart.identifier = rData.identifier + } + if (typeof rData.url === 'undefined') { + return null + } else { + chart.tilemapUrl = rData.url + } + if (typeof rData.name !== 'undefined') { + chart.name = rData.name + } else { + chart.name = rData.identifier + } + if (typeof rData.description !== 'undefined') { + chart.description = rData.description + } + if (typeof rData.minZoom === 'number') { + chart.minzoom = rData.minZoom + } + if (typeof rData.maxZoom === 'number') { + chart.maxzoom = rData.maxZoom + } + if (typeof rData.serverType !== 'undefined') { + chart.type = rData.serverType + } + if (typeof rData.format !== 'undefined') { + chart.format = rData.format + } + if (typeof rData.layers !== 'undefined' && Array.isArray(rData.layers)) { + chart.chartLayers = rData.layers + } + if (typeof rData.scale === 'number') { + chart.scale = rData.scale + } + if (typeof rData.bounds !== 'undefined' && Array.isArray(rData.bounds)) { + chart.bounds = [ + rData.bounds[0].longitude, + rData.bounds[0].latitude, + rData.bounds[1].longitude, + rData.bounds[1].latitude + ] + } + + return chart +} From 0f209a38d0ac843bedc71255fb9ef33b911b7d03 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 8 Dec 2021 09:00:15 +1030 Subject: [PATCH 093/410] chore: fix lint errors --- src/api/resources/index.ts | 17 ++++++++--------- src/api/resources/resources.ts | 10 +++++----- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 1babba166..c11f7f9b6 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -429,7 +429,6 @@ export class Resources { } private processApiRequest(req: Request) { - let resType: SignalKResourceType = 'waypoints' if (req.params.resourceType.toLowerCase() === 'waypoint') { resType = 'waypoints' @@ -451,9 +450,7 @@ export class Resources { const resId: string = req.params.resourceId ? req.params.resourceId - : resType = 'charts' - ? resValue.identifier - : UUID_PREFIX + uuidv4() + : (resType = 'charts' ? resValue.identifier : UUID_PREFIX + uuidv4()) return { type: resType, @@ -465,11 +462,13 @@ export class Resources { private getResourcePaths(): { [key: string]: any } { const resPaths: { [key: string]: any } = {} for (const i in this.resProvider) { - resPaths[i] = { - description: `Path containing ${ - i.slice(-1) === 's' ? i.slice(0, i.length - 1) : i - } resources`, - $source: this.resProvider[i]?.pluginId + if (this.resProvider.hasOwnProperty(i)) { + resPaths[i] = { + description: `Path containing ${ + i.slice(-1) === 's' ? i.slice(0, i.length - 1) : i + } resources`, + $source: this.resProvider[i]?.pluginId + } } } return resPaths diff --git a/src/api/resources/resources.ts b/src/api/resources/resources.ts index badcdaf6e..36ee48b21 100644 --- a/src/api/resources/resources.ts +++ b/src/api/resources/resources.ts @@ -199,7 +199,7 @@ const buildRegion = (rData: any): any => { return [p.longitude, p.latitude] }) reg.feature.geometry.coordinates.push(coords) - + return reg } @@ -215,7 +215,7 @@ const buildChart = (rData: any): any => { tilemapUrl: '', chartLayers: [], scale: 250000, - bounds: [-180,-90,180,90] + bounds: [-180, -90, 180, 90] } if (typeof rData.identifier === 'undefined') { @@ -256,12 +256,12 @@ const buildChart = (rData: any): any => { } if (typeof rData.bounds !== 'undefined' && Array.isArray(rData.bounds)) { chart.bounds = [ - rData.bounds[0].longitude, + rData.bounds[0].longitude, rData.bounds[0].latitude, - rData.bounds[1].longitude, + rData.bounds[1].longitude, rData.bounds[1].latitude ] } - + return chart } From bc6781f8fd445b0a7007eb714d1a25bcc00dc4f0 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 4 Dec 2021 17:12:14 +1030 Subject: [PATCH 094/410] init courseApi --- src/api/course/index.ts | 562 ++++++++++++++++++++++++++++++++++++ src/api/course/openApi.json | 413 ++++++++++++++++++++++++++ src/index.ts | 2 + 3 files changed, 977 insertions(+) create mode 100644 src/api/course/index.ts create mode 100644 src/api/course/openApi.json diff --git a/src/api/course/index.ts b/src/api/course/index.ts new file mode 100644 index 000000000..a8d42a1a8 --- /dev/null +++ b/src/api/course/index.ts @@ -0,0 +1,562 @@ +import { + ResourceProvider, + ResourceProviderMethods, + SignalKResourceType +} from '@signalk/server-api' +import Debug from 'debug' +import { Application, Request, Response } from 'express' +import { v4 as uuidv4 } from 'uuid' + +const debug = Debug('signalk:courseApi') + +const SIGNALK_API_PATH: string = `/signalk/v1/api` +const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' + +const API_METHODS: string[] = [] + +interface CourseApplication extends Application { + handleMessage: (id: string, data: any) => void + getSelfPath: (path: string) => any + resourcesApi: { + getResource: (resourceType: string, resourceId: string) => any + } +} + +interface DestinationBase { + href?: string + arrivalCircle?: number +} +interface Destination extends DestinationBase { + position?: { + latitude: number + longitude: number + altitude?: number + } + type?: string +} + +interface ActiveRoute extends DestinationBase { + pointIndex?: number + reverse?: boolean +} + +interface Position { + latitude: number + longitude: number + altitude?: number +} + +interface CourseInfo { + activeRoute: { + href: string | null + startTime: string | null + pointIndex: number + reverse: boolean + } + nextPoint: { + href: string | null + type: string | null + position: Position | null + arrivalCircle: number + } + previousPoint: { + href: string | null + type: string | null + position: Position | null + } +} + +export class CourseApi { + private server: CourseApplication + + private courseInfo: CourseInfo = { + activeRoute: { + href: null, + startTime: null, + pointIndex: 0, + reverse: false + }, + nextPoint: { + href: null, + type: null, + position: null, + arrivalCircle: 0 + }, + previousPoint: { + href: null, + type: null, + position: null + } + } + + constructor(app: CourseApplication) { + this.server = app + this.start(app) + } + + private start(app: any) { + debug(`** Initialise ${COURSE_API_PATH} path handler **`) + this.server = app + this.initResourceRoutes() + } + + private initResourceRoutes() { + // return current course information + this.server.get( + `${COURSE_API_PATH}`, + async (req: Request, res: Response) => { + debug(`** GET ${COURSE_API_PATH}`) + res.json(this.courseInfo) + } + ) + + // restart / arrivalCircle + this.server.put( + `${COURSE_API_PATH}/:action`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/:action`) + if (req.params.restart) { + // set previousPoint to vessel position + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.emitCourseInfo() + res.status(200).send(`Course restarted.`) + } else { + res.status(406).send(`Vessel position unavailable!`) + } + } + if (req.params.arrivalCircle) { + if (this.setArrivalCircle(req.params.arrivalCircle)) { + this.emitCourseInfo() + res.status(200).send(`Destination set successfully.`) + } else { + res.status(406).send(`Invalid Data`) + } + } + } + ) + + // set destination + this.server.put( + `${COURSE_API_PATH}/destination`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/destination`) + + if (!req.body.value) { + res.status(406).send(`Invalid Data`) + return + } + const result = await this.setDestination(req.body.value) + if (result) { + this.emitCourseInfo() + res.status(200).send(`Destination set successfully.`) + } else { + this.clearDestination() + this.emitCourseInfo() + res.status(406).send(`Invalid Data`) + } + } + ) + + // clear destination + this.server.delete( + `${COURSE_API_PATH}/destination`, + async (req: Request, res: Response) => { + debug(`** DELETE ${COURSE_API_PATH}/destination`) + this.clearDestination() + this.emitCourseInfo() + res.status(200).send(`Destination cleared.`) + } + ) + + // set / clear activeRoute + this.server.put( + `${COURSE_API_PATH}/activeRoute`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/activeRoute`) + + const result = await this.activateRoute(req.body.value) + if (result) { + this.emitCourseInfo() + res.status(200).send(`Active route set.`) + } else { + this.clearDestination() + this.emitCourseInfo() + res.status(406).send(`Invalid Data`) + } + } + ) + this.server.delete( + `${COURSE_API_PATH}/activeRoute`, + async (req: Request, res: Response) => { + debug(`** DELETE ${COURSE_API_PATH}/activeRoute`) + + this.clearDestination() + this.emitCourseInfo() + res.status(200).send(`Active route cleared.`) + } + ) + + this.server.put( + `${COURSE_API_PATH}/activeRoute/:action`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/activeRoute`) + + if (!this.courseInfo.activeRoute.href) { + res.status(406).send(`Invalid Data`) + return + } + + const rte = await this.getRoute(this.courseInfo.activeRoute.href) + if (!rte) { + res.status(406).send(`Invalid Data`) + return + } + + if (req.params.nextPoint) { + if ( + typeof req.body.value === 'number' && + (req.body.value === 1 || req.body.value === -1) + ) { + this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( + this.courseInfo.activeRoute.pointIndex + req.body.value, + rte + ) + } else { + res.status(406).send(`Invalid Data`) + } + } + if (req.params.pointIndex) { + if (typeof req.body.value === 'number') { + this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( + req.body.value, + rte + ) + } else { + res.status(406).send(`Invalid Data`) + } + } + + // set nextPoint + this.courseInfo.nextPoint.position = this.getRoutePoint( + rte, + this.courseInfo.activeRoute.pointIndex + ) + this.courseInfo.nextPoint.type = `RoutePoint` + this.courseInfo.nextPoint.href = null + + // set previousPoint + if (this.courseInfo.activeRoute.pointIndex === 0) { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } else { + this.courseInfo.previousPoint.position = this.getRoutePoint( + rte, + this.courseInfo.activeRoute.pointIndex - 1 + ) + this.courseInfo.previousPoint.type = `RoutePoint` + } + this.courseInfo.previousPoint.href = null + + res.status(200).send(`OK`) + } + ) + } + + private async activateRoute(route: ActiveRoute): Promise { + let rte: any + + if (route.href) { + rte = await this.getRoute(route.href) + if (!rte) { + return false + } + if (!Array.isArray(rte.feature?.geometry?.coordinates)) { + return false + } + } else { + return false + } + + // set activeroute + this.courseInfo.activeRoute.href = route.href + + if (typeof route.arrivalCircle === 'number') { + this.setArrivalCircle(route.arrivalCircle) + } + + this.courseInfo.activeRoute.reverse = + typeof route.reverse === 'boolean' ? route.reverse : false + + this.courseInfo.activeRoute.pointIndex = + typeof route.pointIndex === 'number' + ? this.parsePointIndex(route.pointIndex, rte) + : 0 + + this.courseInfo.activeRoute.startTime = new Date().toISOString() + + // set nextPoint + this.courseInfo.nextPoint.position = this.getRoutePoint( + rte, + this.courseInfo.activeRoute.pointIndex + ) + this.courseInfo.nextPoint.type = `RoutePoint` + this.courseInfo.nextPoint.href = null + + // set previousPoint + if (this.courseInfo.activeRoute.pointIndex === 0) { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } else { + this.courseInfo.previousPoint.position = this.getRoutePoint( + rte, + this.courseInfo.activeRoute.pointIndex - 1 + ) + this.courseInfo.previousPoint.type = `RoutePoint` + } + this.courseInfo.previousPoint.href = null + + return true + } + + private async setDestination(dest: Destination): Promise { + // set nextPoint + if (typeof dest.arrivalCircle === 'number') { + this.setArrivalCircle(dest.arrivalCircle) + } + + this.courseInfo.nextPoint.type = + typeof dest.type !== 'undefined' ? dest.type : null + + if (dest.href) { + this.courseInfo.nextPoint.href = dest.href + const href = this.parseHref(dest.href) + if (href) { + try { + const r = await this.server.resourcesApi.getResource( + href.type, + href.id + ) + if (r.position && typeof r.position.value?.latitude !== 'undefined') { + this.courseInfo.nextPoint.position = r.position.value + } + } catch (err) { + return false + } + } + } else if (dest.position) { + this.courseInfo.nextPoint.href = null + if (typeof dest.position.latitude !== 'undefined') { + this.courseInfo.nextPoint.position = dest.position + } else { + return false + } + } else { + return false + } + + // set previousPoint + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + this.courseInfo.previousPoint.href = null + + return true + } + + private clearDestination() { + this.courseInfo.activeRoute.href = null + this.courseInfo.activeRoute.startTime = null + this.courseInfo.activeRoute.pointIndex = 0 + this.courseInfo.activeRoute.reverse = false + this.courseInfo.nextPoint.href = null + this.courseInfo.nextPoint.type = null + this.courseInfo.nextPoint.position = null + this.courseInfo.previousPoint.href = null + this.courseInfo.previousPoint.type = null + this.courseInfo.previousPoint.position = null + } + + private setArrivalCircle(value: any): boolean { + if (typeof value === 'number' && value >= 0) { + this.courseInfo.nextPoint.arrivalCircle = value + return true + } else { + return false + } + } + + private parsePointIndex(index: number, rte: any): number { + if (!rte) { + return 0 + } + if (!rte.feature?.geometry?.coordinates) { + return 0 + } + if (!Array.isArray(rte.feature?.geometry?.coordinates)) { + return 0 + } + if (index < 0) { + return 0 + } + if (index > rte.feature?.geometry?.coordinates.length - 1) { + return rte.feature?.geometry?.coordinates.length + } + return index + } + + private parseHref(href: string): { type: string; id: string } | undefined { + if (href.length === 0) { + return undefined + } + if (href[0] === '/') { + href = href.slice(1) + } + const ref: string[] = href.split('/') + if (ref.length < 3) { + return undefined + } + if (ref[0] !== 'resources') { + return undefined + } + return { + type: ref[1], + id: ref[2] + } + } + + private getRoutePoint(rte: any, index: number) { + const pos = this.courseInfo.activeRoute.reverse + ? rte.feature.geometry.coordinates[ + rte.feature.geometry.coordinates.length - (index + 1) + ] + : rte.feature.geometry.coordinates[index] + return { + latitude: pos[1], + longitude: pos[0], + altitude: pos.length == 3 ? pos[2] : 0 + } + } + + private async getRoute(href: string): Promise { + const h = this.parseHref(href) + if (h) { + try { + return await this.server.resourcesApi.getResource(h.type, h.id) + } catch (err) { + return undefined + } + } else { + return undefined + } + } + + private buildDeltaMsg(): any { + let values: Array<{path:string, value:any}> = [] + let root = [ + 'navigation.courseGreatCircle', + 'navigation.courseRhumbline' + ] + + values.push({ + path: `${root[0]}.activeRoute.href`, + value: this.courseInfo.activeRoute.href + }) + values.push({ + path: `${root[1]}.activeRoute.href`, + value: this.courseInfo.activeRoute.href + }) + values.push({ + path: `${root[0]}.activeRoute.startTime`, + value: this.courseInfo.activeRoute.startTime + }) + values.push({ + path: `${root[1]}.activeRoute.startTime`, + value: this.courseInfo.activeRoute.startTime + }) + values.push({ + path: `${root[0]}.nextPoint.href`, + value: this.courseInfo.nextPoint.href + }) + values.push({ + path: `${root[1]}.nextPoint.href`, + value: this.courseInfo.nextPoint.href + }) + values.push({ + path: `${root[0]}.nextPoint.position`, + value: this.courseInfo.nextPoint.position + }) + values.push({ + path: `${root[1]}.nextPoint.position`, + value: this.courseInfo.nextPoint.position + }) + values.push({ + path: `${root[0]}.nextPoint.type`, + value: this.courseInfo.nextPoint.type + }) + values.push({ + path: `${root[1]}.nextPoint.type`, + value: this.courseInfo.nextPoint.type + }) + values.push({ + path: `${root[0]}.nextPoint.arrivalCircle`, + value: this.courseInfo.nextPoint.arrivalCircle + }) + values.push({ + path: `${root[1]}.nextPoint.arrivalCircle`, + value: this.courseInfo.nextPoint.arrivalCircle + }) + values.push({ + path: `${root[0]}.previousPoint.href`, + value: this.courseInfo.previousPoint.href + }) + values.push({ + path: `${root[1]}.previousPoint.href`, + value: this.courseInfo.previousPoint.href + }) + values.push({ + path: `${root[0]}.previousPoint.position`, + value: this.courseInfo.previousPoint.position + }) + values.push({ + path: `${root[1]}.previousPoint.position`, + value: this.courseInfo.previousPoint.position + }) + values.push({ + path: `${root[0]}.previousPoint.type`, + value: this.courseInfo.previousPoint.type + }) + values.push({ + path: `${root[1]}.previousPoint.type`, + value: this.courseInfo.previousPoint.type + }) + + return { + updates: [ + { + values: values + } + ] + } + } + + private emitCourseInfo() { + this.server.handleMessage('courseApi', this.buildDeltaMsg()) + } +} diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json new file mode 100644 index 000000000..04653db5e --- /dev/null +++ b/src/api/course/openApi.json @@ -0,0 +1,413 @@ +{ + "openapi": "3.0.2", + "info": { + "version": "1.0.0", + "title": "Signal K Course API" + }, + + "paths": { + + "/vessels/self/navigation/course/": { + "get": { + "tags": ["course"], + "summary": "Get course information", + "responses": { + "default": { + "description": "Course data", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "activeRoute": { + "type": "object", + "properties": { + "href": { + "type": "string", + "example": "/resources/routes/urn:mrn:signalk:uuid:0d95e282-3e1f-4521-8c30-8288addbdb69" + }, + "startTime": { + "type": "string", + "example": "2021-10-23T05:17:20.065Z" + }, + "pointIndex": { + "type": "number", + "format": "int64", + "example": 2 + }, + "reverse": { + "type": "boolean", + "default": false + } + } + }, + "nextPoint": { + "type": "object", + "properties": { + "href": { + "type": "string", + "example": "/resources/waypoints/urn:mrn:signalk:uuid:0d95e282-3e1f-4521-8c30-8288addbdbab" + }, + "type": { + "type": "string", + "example": "RoutePoint" + }, + "position": { + "type": "object", + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + }, + "example": {"latitude":-29.5,"longitude":137.5} + } + } + }, + "previousPoint": { + "type": "object", + "properties": { + "href": { + "type": "string", + "example": null + }, + "type": { + "type": "string", + "example": "Location" + }, + "position": { + "type": "object", + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + }, + "example": {"longitude":29.821001582434413,"latitude":70.7014589462524} + } + } + } + } + } + } + } + } + } + } + }, + + "/vessels/self/navigation/course/restart": { + "put": { + "tags": ["course"], + "summary": "Restart course calculations", + "description": "Sets previousPoint value to current vessel location", + "requestBody": { + "description": "Restart payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + } + }, + + "/vessels/self/navigation/course/destination": { + "put": { + "tags": ["course/destination"], + "summary": "Set destination", + "description": "Set destination path from supplied details", + "requestBody": { + "description": "Destination details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "properties": { + "position": { + "type": "object", + "description": "Destination position", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + }, + "example": {"latitude":-29.5,"longitude":137.5} + }, + "href": { + "type": "string", + "description": "A reference (URL) to an object (under /resources) this point is related to", + "example": "/resources/waypoints/urn:mrn:signalk:uuid:3dd34dcc-36bf-4d61-ba80-233799b25672" + }, + "type": { + "type": "string", + "description": "Type of point", + "example": "POI" + }, + "arrivalCircle": { + "type": "number", + "format": "float", + "example": 500 + } + } + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + }, + "delete": { + "tags": ["course/destination"], + "summary": "Clear destination", + "description": "Sets activeRoute, nextPoint & previousPoint values to null", + "requestBody": { + "description": "Delete payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + } + }, + + "/vessels/self/navigation/course/arrivalCircle": { + "put": { + "tags": ["course"], + "summary": "Set arrival circle radius (m)", + "description": "Sets an arrival circle radius (in meters)", + "requestBody": { + "description": "Arrival circle payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "number", + "format": "float", + "example": 500 + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + } + }, + + "/vessels/self/navigation/course/activeRoute": { + "put": { + "tags": ["course/activeRoute"], + "summary": "Set active route", + "description": "Sets activeRoute path and sets nextPoint to first point in the route", + "requestBody": { + "description": "Active route details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "properties": { + "href": { + "type": "string", + "description": "Path to route resource", + "example": "/resources/routes/urn:mrn:signalk:uuid:3dd34dcc-36bf-4d61-ba80-233799b25672" + }, + "pointIndex": { + "type": "number", + "format": "int64", + "description": "Zero based index of route point to use as destination", + "default": 0, + "minimum": 0, + "example": 2 + }, + "reverse": { + "type": "boolean", + "default": false, + "description": "If true performs operations on route points in reverse order", + "example": 2 + }, + "arrivalCircle": { + "type": "number", + "format": "float", + "example": 500 + } + } + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + }, + "delete": { + "tags": ["course/activeRoute"], + "summary": "Clear active route", + "description": "Sets activeRoute, nextPoint & previousPoint values to null", + "requestBody": { + "description": "Delete payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + } + }, + + "/vessels/self/navigation/course/activeRoute/nextPoint": { + "put": { + "tags": ["course/activeRoute"], + "summary": "Increment / decrement point in route as destination", + "description": "Increment / decrement point in the route as destination", + "requestBody": { + "description": "Increment / decrement", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "number", + "description": "increment (1) / decrement (-1) index of point in route to use as destination", + "enum": [-1,1], + "example": -1 + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + } + }, + + "/vessels/self/navigation/course/activeRoute/pointIndex": { + "put": { + "tags": ["course/activeRoute"], + "summary": "Set point in route as destination", + "description": "Sets the specified point in the route as destination", + "requestBody": { + "description": "Next point index", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "number", + "description": "Index of point in route to use as destination", + "minimum": 0, + "example": 2 + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + } + } + + } + +} + + + + + \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 2645d1814..51ed7a03b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -51,6 +51,7 @@ import { startSecurity } from './security.js' +import { CourseApi } from './api/course' import { Resources } from './api/resources' // tslint:disable-next-line: no-var-requires @@ -84,6 +85,7 @@ class Server { require('./put').start(app) app.resourcesApi = new Resources(app) + const courseApi = new CourseApi(app) app.signalk = new FullSignalK(app.selfId, app.selfType) From a1f8238903791a76ad13479626a49bd73b6ebe3c Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 5 Dec 2021 10:26:47 +1030 Subject: [PATCH 095/410] update detlas --- src/api/course/index.ts | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index a8d42a1a8..7d186618c 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,23 +1,15 @@ -import { - ResourceProvider, - ResourceProviderMethods, - SignalKResourceType -} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' -import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' - -const API_METHODS: string[] = [] interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any + registerPutHandler: (context:string, path:string, cb:any) => any resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -35,7 +27,6 @@ interface Destination extends DestinationBase { } type?: string } - interface ActiveRoute extends DestinationBase { pointIndex?: number reverse?: boolean @@ -111,12 +102,26 @@ export class CourseApi { } ) + // + if(this.server.registerPutHandler) { + debug('** Registering PUT Action Handler(s) **') + this.server.registerPutHandler( + 'vessels.self', + 'navigation.course.*', + this.handleCourseApiPut + ); + } + // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { + //test for active destination + if (!this.courseInfo.nextPoint.position) { + return + } // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -270,6 +275,12 @@ export class CourseApi { ) } + private handleCourseApiPut(context:string, path:string, value:any, cb:any) { + + debug('** PUT handler **') + return undefined + } + private async activateRoute(route: ActiveRoute): Promise { let rte: any @@ -474,6 +485,11 @@ export class CourseApi { 'navigation.courseRhumbline' ] + values.push({ + path: `navigation.course`, + value: this.courseInfo + }) + values.push({ path: `${root[0]}.activeRoute.href`, value: this.courseInfo.activeRoute.href From f88c134cd5e22f150cebcb948e726c14306d8e93 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 4 Dec 2021 17:12:14 +1030 Subject: [PATCH 096/410] init courseApi --- src/api/course/index.ts | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 7d186618c..d3d42fed7 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,15 +1,26 @@ + +import { + ResourceProvider, + ResourceProviderMethods, + SignalKResourceType +} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' +import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' + +const API_METHODS: string[] = [] + interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - registerPutHandler: (context:string, path:string, cb:any) => any + resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -102,26 +113,12 @@ export class CourseApi { } ) - // - if(this.server.registerPutHandler) { - debug('** Registering PUT Action Handler(s) **') - this.server.registerPutHandler( - 'vessels.self', - 'navigation.course.*', - this.handleCourseApiPut - ); - } - // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { - //test for active destination - if (!this.courseInfo.nextPoint.position) { - return - } // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -275,12 +272,6 @@ export class CourseApi { ) } - private handleCourseApiPut(context:string, path:string, value:any, cb:any) { - - debug('** PUT handler **') - return undefined - } - private async activateRoute(route: ActiveRoute): Promise { let rte: any From df45346f6384b9dd23b66215a842ac3c9b89e52d Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 5 Dec 2021 10:26:47 +1030 Subject: [PATCH 097/410] update detlas --- src/api/course/index.ts | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index d3d42fed7..7d186618c 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,26 +1,15 @@ - -import { - ResourceProvider, - ResourceProviderMethods, - SignalKResourceType -} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' -import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' - -const API_METHODS: string[] = [] - interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - + registerPutHandler: (context:string, path:string, cb:any) => any resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -113,12 +102,26 @@ export class CourseApi { } ) + // + if(this.server.registerPutHandler) { + debug('** Registering PUT Action Handler(s) **') + this.server.registerPutHandler( + 'vessels.self', + 'navigation.course.*', + this.handleCourseApiPut + ); + } + // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { + //test for active destination + if (!this.courseInfo.nextPoint.position) { + return + } // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -272,6 +275,12 @@ export class CourseApi { ) } + private handleCourseApiPut(context:string, path:string, value:any, cb:any) { + + debug('** PUT handler **') + return undefined + } + private async activateRoute(route: ActiveRoute): Promise { let rte: any From bb31a6a8144bdfb843ab2aa44625307539ca57f8 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 6 Dec 2021 08:39:56 +1030 Subject: [PATCH 098/410] enable put processing --- src/api/course/index.ts | 136 ++++++++++++++++++++++++---------------- src/put.js | 7 +++ 2 files changed, 89 insertions(+), 54 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 7d186618c..1566ecdf3 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -9,7 +9,14 @@ const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/cou interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - registerPutHandler: (context:string, path:string, cb:any) => any + securityStrategy: { + shouldAllowPut: ( + req: any, + context: string, + source: any, + path: string + ) => boolean + } resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -92,6 +99,15 @@ export class CourseApi { this.initResourceRoutes() } + private updateAllowed(): boolean { + return this.server.securityStrategy.shouldAllowPut( + this.server, + 'vessels.self', + null, + 'resources' + ) + } + private initResourceRoutes() { // return current course information this.server.get( @@ -102,43 +118,43 @@ export class CourseApi { } ) - // - if(this.server.registerPutHandler) { - debug('** Registering PUT Action Handler(s) **') - this.server.registerPutHandler( - 'vessels.self', - 'navigation.course.*', - this.handleCourseApiPut - ); - } + this.server.put( + `${COURSE_API_PATH}/restart`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/restart`) + if (!this.updateAllowed()) { + res.status(403) + return + } + if (!this.courseInfo.nextPoint.position) { + res.status(406).send(`No active destination!`) + return + } + // set previousPoint to vessel position + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.emitCourseInfo() + res.status(200) + } else { + res.status(406).send(`Vessel position unavailable!`) + } + } + ) - // restart / arrivalCircle this.server.put( - `${COURSE_API_PATH}/:action`, + `${COURSE_API_PATH}/arrivalCircle`, async (req: Request, res: Response) => { - debug(`** PUT ${COURSE_API_PATH}/:action`) - if (req.params.restart) { - //test for active destination - if (!this.courseInfo.nextPoint.position) { - return - } - // set previousPoint to vessel position - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.emitCourseInfo() - res.status(200).send(`Course restarted.`) - } else { - res.status(406).send(`Vessel position unavailable!`) - } + debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) + if (!this.updateAllowed()) { + res.status(403) + return } - if (req.params.arrivalCircle) { - if (this.setArrivalCircle(req.params.arrivalCircle)) { - this.emitCourseInfo() - res.status(200).send(`Destination set successfully.`) - } else { - res.status(406).send(`Invalid Data`) - } + if (this.setArrivalCircle(req.body.value)) { + this.emitCourseInfo() + res.status(200) + } else { + res.status(406).send(`Invalid Data`) } } ) @@ -148,7 +164,10 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) - + if (!this.updateAllowed()) { + res.status(403) + return + } if (!req.body.value) { res.status(406).send(`Invalid Data`) return @@ -156,7 +175,7 @@ export class CourseApi { const result = await this.setDestination(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send(`Destination set successfully.`) + res.status(200) } else { this.clearDestination() this.emitCourseInfo() @@ -170,22 +189,29 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) + if (!this.updateAllowed()) { + res.status(403) + return + } this.clearDestination() this.emitCourseInfo() - res.status(200).send(`Destination cleared.`) + res.status(200) } ) - // set / clear activeRoute + // set activeRoute this.server.put( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) - + if (!this.updateAllowed()) { + res.status(403) + return + } const result = await this.activateRoute(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send(`Active route set.`) + res.status(200) } else { this.clearDestination() this.emitCourseInfo() @@ -193,14 +219,19 @@ export class CourseApi { } } ) + + // clear activeRoute /destination this.server.delete( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/activeRoute`) - + if (!this.updateAllowed()) { + res.status(403) + return + } this.clearDestination() this.emitCourseInfo() - res.status(200).send(`Active route cleared.`) + res.status(200) } ) @@ -208,19 +239,22 @@ export class CourseApi { `${COURSE_API_PATH}/activeRoute/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) - + if (!this.updateAllowed()) { + res.status(403) + return + } + // fetch route data if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return } - const rte = await this.getRoute(this.courseInfo.activeRoute.href) if (!rte) { res.status(406).send(`Invalid Data`) return } - if (req.params.nextPoint) { + if (req.params.action === 'nextPoint') { if ( typeof req.body.value === 'number' && (req.body.value === 1 || req.body.value === -1) @@ -233,7 +267,7 @@ export class CourseApi { res.status(406).send(`Invalid Data`) } } - if (req.params.pointIndex) { + if (req.params.action === 'pointIndex') { if (typeof req.body.value === 'number') { this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( req.body.value, @@ -244,7 +278,7 @@ export class CourseApi { } } - // set nextPoint + // set new destination this.courseInfo.nextPoint.position = this.getRoutePoint( rte, this.courseInfo.activeRoute.pointIndex @@ -270,17 +304,11 @@ export class CourseApi { } this.courseInfo.previousPoint.href = null - res.status(200).send(`OK`) + res.status(200) } ) } - private handleCourseApiPut(context:string, path:string, value:any, cb:any) { - - debug('** PUT handler **') - return undefined - } - private async activateRoute(route: ActiveRoute): Promise { let rte: any diff --git a/src/put.js b/src/put.js index e6e14a965..c7cf42616 100644 --- a/src/put.js +++ b/src/put.js @@ -35,6 +35,13 @@ module.exports = { next() return } + + // ** ignore course paths ** + if (req.path.indexOf('/navigation/course') !== -1) { + next() + return + } + let path = String(req.path).replace(apiPathPrefix, '') const value = req.body From b21bb43aae27e284cbc2cdc3a2c2a91caacaa089 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 6 Dec 2021 13:34:31 +1030 Subject: [PATCH 099/410] chore: lint --- src/api/course/index.ts | 211 ++++++++++++++++++++---------------- src/api/course/openApi.json | 72 +----------- 2 files changed, 120 insertions(+), 163 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 1566ecdf3..6cd3b80fd 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -11,7 +11,7 @@ interface CourseApplication extends Application { getSelfPath: (path: string) => any securityStrategy: { shouldAllowPut: ( - req: any, + req: Application, context: string, source: any, path: string @@ -104,7 +104,7 @@ export class CourseApi { this.server, 'vessels.self', null, - 'resources' + 'navigation.course' ) } @@ -131,12 +131,14 @@ export class CourseApi { return } // set previousPoint to vessel position - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.emitCourseInfo() - res.status(200) - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.emitCourseInfo() + res.status(200) + } + } catch (err) { res.status(406).send(`Vessel position unavailable!`) } } @@ -150,7 +152,8 @@ export class CourseApi { res.status(403) return } - if (this.setArrivalCircle(req.body.value)) { + if (this.isValidArrivalCircle(req.body.value)) { + this.courseInfo.nextPoint.arrivalCircle = req.body.value this.emitCourseInfo() res.status(200) } else { @@ -243,7 +246,7 @@ export class CourseApi { res.status(403) return } - // fetch route data + // fetch active route data if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return @@ -281,24 +284,30 @@ export class CourseApi { // set new destination this.courseInfo.nextPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex + this.courseInfo.activeRoute.pointIndex, + this.courseInfo.activeRoute.reverse ) this.courseInfo.nextPoint.type = `RoutePoint` this.courseInfo.nextPoint.href = null // set previousPoint if (this.courseInfo.activeRoute.pointIndex === 0) { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } catch (err) { return false } } else { this.courseInfo.previousPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex - 1 + this.courseInfo.activeRoute.pointIndex - 1, + this.courseInfo.activeRoute.reverse ) this.courseInfo.previousPoint.type = `RoutePoint` } @@ -324,81 +333,97 @@ export class CourseApi { return false } + const newDest: any = {} + Object.assign(newDest, this.courseInfo) + // set activeroute - this.courseInfo.activeRoute.href = route.href + newDest.activeRoute.href = route.href - if (typeof route.arrivalCircle === 'number') { - this.setArrivalCircle(route.arrivalCircle) + if (this.isValidArrivalCircle(route.arrivalCircle as number)) { + newDest.nextPoint.arrivalCircle = route.arrivalCircle } - this.courseInfo.activeRoute.reverse = - typeof route.reverse === 'boolean' ? route.reverse : false + newDest.activeRoute.startTime = new Date().toISOString() - this.courseInfo.activeRoute.pointIndex = - typeof route.pointIndex === 'number' - ? this.parsePointIndex(route.pointIndex, rte) - : 0 + if (typeof route.reverse === 'boolean') { + newDest.activeRoute.reverse = route.reverse + } - this.courseInfo.activeRoute.startTime = new Date().toISOString() + newDest.activeRoute.pointIndex = this.parsePointIndex( + route.pointIndex as number, + rte + ) // set nextPoint - this.courseInfo.nextPoint.position = this.getRoutePoint( + newDest.nextPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex + newDest.activeRoute.pointIndex, + newDest.activeRoute.reverse ) - this.courseInfo.nextPoint.type = `RoutePoint` - this.courseInfo.nextPoint.href = null + newDest.nextPoint.type = `RoutePoint` + newDest.nextPoint.href = null // set previousPoint - if (this.courseInfo.activeRoute.pointIndex === 0) { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + if (newDest.activeRoute.pointIndex === 0) { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } catch (err) { return false } } else { - this.courseInfo.previousPoint.position = this.getRoutePoint( + newDest.previousPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex - 1 + newDest.activeRoute.pointIndex - 1, + newDest.activeRoute.reverse ) - this.courseInfo.previousPoint.type = `RoutePoint` + newDest.previousPoint.type = `RoutePoint` } - this.courseInfo.previousPoint.href = null + newDest.previousPoint.href = null + this.courseInfo = newDest return true } - private async setDestination(dest: Destination): Promise { + private async setDestination(dest: any): Promise { + const newDest: any = {} + Object.assign(newDest, this.courseInfo) + // set nextPoint - if (typeof dest.arrivalCircle === 'number') { - this.setArrivalCircle(dest.arrivalCircle) + if (this.isValidArrivalCircle(dest.arrivalCircle)) { + newDest.nextPoint.arrivalCircle = dest.arrivalCircle } - this.courseInfo.nextPoint.type = - typeof dest.type !== 'undefined' ? dest.type : null + newDest.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - this.courseInfo.nextPoint.href = dest.href + newDest.href = dest.href const href = this.parseHref(dest.href) if (href) { + // fetch waypoint resource details try { const r = await this.server.resourcesApi.getResource( href.type, href.id ) - if (r.position && typeof r.position.value?.latitude !== 'undefined') { - this.courseInfo.nextPoint.position = r.position.value + if (r.position && typeof r.position?.latitude !== 'undefined') { + newDest.nextPoint.position = r.position + } else { + return false } } catch (err) { return false } } } else if (dest.position) { - this.courseInfo.nextPoint.href = null + newDest.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { - this.courseInfo.nextPoint.position = dest.position + newDest.nextPoint.position = dest.position } else { return false } @@ -407,15 +432,20 @@ export class CourseApi { } // set previousPoint - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + newDest.previousPoint.position = position.value + newDest.previousPoint.type = `VesselPosition` + } else { + return false + } + newDest.previousPoint.href = null + } catch (err) { return false } - this.courseInfo.previousPoint.href = null + this.courseInfo = newDest return true } @@ -432,17 +462,12 @@ export class CourseApi { this.courseInfo.previousPoint.position = null } - private setArrivalCircle(value: any): boolean { - if (typeof value === 'number' && value >= 0) { - this.courseInfo.nextPoint.arrivalCircle = value - return true - } else { - return false - } + private isValidArrivalCircle(value: number): boolean { + return typeof value === 'number' && value >= 0 } private parsePointIndex(index: number, rte: any): number { - if (!rte) { + if (typeof index !== 'number' || !rte) { return 0 } if (!rte.feature?.geometry?.coordinates) { @@ -461,13 +486,11 @@ export class CourseApi { } private parseHref(href: string): { type: string; id: string } | undefined { - if (href.length === 0) { + if (!href) { return undefined } - if (href[0] === '/') { - href = href.slice(1) - } - const ref: string[] = href.split('/') + + const ref: string[] = href.split('/').slice(-3) if (ref.length < 3) { return undefined } @@ -480,8 +503,8 @@ export class CourseApi { } } - private getRoutePoint(rte: any, index: number) { - const pos = this.courseInfo.activeRoute.reverse + private getRoutePoint(rte: any, index: number, reverse: boolean) { + const pos = reverse ? rte.feature.geometry.coordinates[ rte.feature.geometry.coordinates.length - (index + 1) ] @@ -489,7 +512,7 @@ export class CourseApi { return { latitude: pos[1], longitude: pos[0], - altitude: pos.length == 3 ? pos[2] : 0 + altitude: pos.length === 3 ? pos[2] : 0 } } @@ -507,8 +530,8 @@ export class CourseApi { } private buildDeltaMsg(): any { - let values: Array<{path:string, value:any}> = [] - let root = [ + const values: Array<{ path: string; value: any }> = [] + const navPath = [ 'navigation.courseGreatCircle', 'navigation.courseRhumbline' ] @@ -519,82 +542,82 @@ export class CourseApi { }) values.push({ - path: `${root[0]}.activeRoute.href`, + path: `${navPath[0]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${root[1]}.activeRoute.href`, + path: `${navPath[1]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${root[0]}.activeRoute.startTime`, + path: `${navPath[0]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${root[1]}.activeRoute.startTime`, + path: `${navPath[1]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${root[0]}.nextPoint.href`, + path: `${navPath[0]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${root[1]}.nextPoint.href`, + path: `${navPath[1]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${root[0]}.nextPoint.position`, + path: `${navPath[0]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${root[1]}.nextPoint.position`, + path: `${navPath[1]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${root[0]}.nextPoint.type`, + path: `${navPath[0]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${root[1]}.nextPoint.type`, + path: `${navPath[1]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${root[0]}.nextPoint.arrivalCircle`, + path: `${navPath[0]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${root[1]}.nextPoint.arrivalCircle`, + path: `${navPath[1]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${root[0]}.previousPoint.href`, + path: `${navPath[0]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${root[1]}.previousPoint.href`, + path: `${navPath[1]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${root[0]}.previousPoint.position`, + path: `${navPath[0]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${root[1]}.previousPoint.position`, + path: `${navPath[1]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${root[0]}.previousPoint.type`, + path: `${navPath[0]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) values.push({ - path: `${root[1]}.previousPoint.type`, + path: `${navPath[1]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) return { updates: [ { - values: values + values } ] } diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index 04653db5e..b162d1314 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -108,29 +108,7 @@ "put": { "tags": ["course"], "summary": "Restart course calculations", - "description": "Sets previousPoint value to current vessel location", - "requestBody": { - "description": "Restart payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } - } - } - } - } - } + "description": "Sets previousPoint value to current vessel location" } }, @@ -200,29 +178,7 @@ "delete": { "tags": ["course/destination"], "summary": "Clear destination", - "description": "Sets activeRoute, nextPoint & previousPoint values to null", - "requestBody": { - "description": "Delete payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } - } - } - } - } - } + "description": "Sets activeRoute, nextPoint & previousPoint values to null" } }, @@ -313,29 +269,7 @@ "delete": { "tags": ["course/activeRoute"], "summary": "Clear active route", - "description": "Sets activeRoute, nextPoint & previousPoint values to null", - "requestBody": { - "description": "Delete payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } - } - } - } - } - } + "description": "Sets activeRoute, nextPoint & previousPoint values to null" } }, From 228826a8b01bc73326d6a2fa4eb9e7eade288e15 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 8 Dec 2021 09:27:06 +1030 Subject: [PATCH 100/410] add 30sec delta interval --- src/api/course/index.ts | 100 ++++++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 45 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 6cd3b80fd..2aa57e8a6 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -6,6 +6,8 @@ const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const DELTA_INTERVAL: number = 30000 + interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any @@ -97,6 +99,11 @@ export class CourseApi { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app this.initResourceRoutes() + setInterval( ()=> { + if(this.courseInfo.nextPoint.position) { + this.emitCourseInfo() + } + }, DELTA_INTERVAL) } private updateAllowed(): boolean { @@ -123,7 +130,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/restart`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (!this.courseInfo.nextPoint.position) { @@ -136,7 +143,7 @@ export class CourseApi { if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } } catch (err) { res.status(406).send(`Vessel position unavailable!`) @@ -149,13 +156,13 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (this.isValidArrivalCircle(req.body.value)) { this.courseInfo.nextPoint.arrivalCircle = req.body.value this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { res.status(406).send(`Invalid Data`) } @@ -168,7 +175,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (!req.body.value) { @@ -178,7 +185,7 @@ export class CourseApi { const result = await this.setDestination(req.body.value) if (result) { this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { this.clearDestination() this.emitCourseInfo() @@ -193,12 +200,12 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } this.clearDestination() this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } ) @@ -208,13 +215,13 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } const result = await this.activateRoute(req.body.value) if (result) { this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { this.clearDestination() this.emitCourseInfo() @@ -234,7 +241,7 @@ export class CourseApi { } this.clearDestination() this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } ) @@ -243,7 +250,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } // fetch active route data @@ -268,6 +275,7 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) + return } } if (req.params.action === 'pointIndex') { @@ -278,6 +286,7 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) + return } } @@ -298,9 +307,11 @@ export class CourseApi { this.courseInfo.previousPoint.position = position.value this.courseInfo.previousPoint.type = `VesselPosition` } else { + res.status(406).send(`Invalid Data`) return false } } catch (err) { + res.status(406).send(`Invalid Data`) return false } } else { @@ -312,8 +323,7 @@ export class CourseApi { this.courseInfo.previousPoint.type = `RoutePoint` } this.courseInfo.previousPoint.href = null - - res.status(200) + res.status(200).send('OK') } ) } @@ -333,38 +343,38 @@ export class CourseApi { return false } - const newDest: any = {} - Object.assign(newDest, this.courseInfo) + const newCourse: any = {} + Object.assign(newCourse, this.courseInfo) // set activeroute - newDest.activeRoute.href = route.href + newCourse.activeRoute.href = route.href if (this.isValidArrivalCircle(route.arrivalCircle as number)) { - newDest.nextPoint.arrivalCircle = route.arrivalCircle + newCourse.nextPoint.arrivalCircle = route.arrivalCircle } - newDest.activeRoute.startTime = new Date().toISOString() + newCourse.activeRoute.startTime = new Date().toISOString() if (typeof route.reverse === 'boolean') { - newDest.activeRoute.reverse = route.reverse + newCourse.activeRoute.reverse = route.reverse } - newDest.activeRoute.pointIndex = this.parsePointIndex( + newCourse.activeRoute.pointIndex = this.parsePointIndex( route.pointIndex as number, rte ) // set nextPoint - newDest.nextPoint.position = this.getRoutePoint( + newCourse.nextPoint.position = this.getRoutePoint( rte, - newDest.activeRoute.pointIndex, - newDest.activeRoute.reverse + newCourse.activeRoute.pointIndex, + newCourse.activeRoute.reverse ) - newDest.nextPoint.type = `RoutePoint` - newDest.nextPoint.href = null + newCourse.nextPoint.type = `RoutePoint` + newCourse.nextPoint.href = null // set previousPoint - if (newDest.activeRoute.pointIndex === 0) { + if (newCourse.activeRoute.pointIndex === 0) { try { const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -377,32 +387,32 @@ export class CourseApi { return false } } else { - newDest.previousPoint.position = this.getRoutePoint( + newCourse.previousPoint.position = this.getRoutePoint( rte, - newDest.activeRoute.pointIndex - 1, - newDest.activeRoute.reverse + newCourse.activeRoute.pointIndex - 1, + newCourse.activeRoute.reverse ) - newDest.previousPoint.type = `RoutePoint` + newCourse.previousPoint.type = `RoutePoint` } - newDest.previousPoint.href = null + newCourse.previousPoint.href = null - this.courseInfo = newDest + this.courseInfo = newCourse return true } private async setDestination(dest: any): Promise { - const newDest: any = {} - Object.assign(newDest, this.courseInfo) + const newCourse: any = {} + Object.assign(newCourse, this.courseInfo) // set nextPoint if (this.isValidArrivalCircle(dest.arrivalCircle)) { - newDest.nextPoint.arrivalCircle = dest.arrivalCircle + newCourse.nextPoint.arrivalCircle = dest.arrivalCircle } - newDest.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null + newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - newDest.href = dest.href + newCourse.href = dest.href const href = this.parseHref(dest.href) if (href) { // fetch waypoint resource details @@ -412,7 +422,7 @@ export class CourseApi { href.id ) if (r.position && typeof r.position?.latitude !== 'undefined') { - newDest.nextPoint.position = r.position + newCourse.nextPoint.position = r.position } else { return false } @@ -421,9 +431,9 @@ export class CourseApi { } } } else if (dest.position) { - newDest.nextPoint.href = null + newCourse.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { - newDest.nextPoint.position = dest.position + newCourse.nextPoint.position = dest.position } else { return false } @@ -435,17 +445,17 @@ export class CourseApi { try { const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { - newDest.previousPoint.position = position.value - newDest.previousPoint.type = `VesselPosition` + newCourse.previousPoint.position = position.value + newCourse.previousPoint.type = `VesselPosition` } else { return false } - newDest.previousPoint.href = null + newCourse.previousPoint.href = null } catch (err) { return false } - this.courseInfo = newDest + this.courseInfo = newCourse return true } From 11091eeb38007ee0377980383a7512846c9bddd5 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 12 Dec 2021 14:29:38 +1030 Subject: [PATCH 101/410] chore: lint --- src/api/course/index.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 2aa57e8a6..1e719297a 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -6,7 +6,7 @@ const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const DELTA_INTERVAL: number = 30000 +const DELTA_INTERVAL: number = 30000 interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void @@ -99,8 +99,8 @@ export class CourseApi { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app this.initResourceRoutes() - setInterval( ()=> { - if(this.courseInfo.nextPoint.position) { + setInterval(() => { + if (this.courseInfo.nextPoint.position) { this.emitCourseInfo() } }, DELTA_INTERVAL) @@ -409,7 +409,8 @@ export class CourseApi { newCourse.nextPoint.arrivalCircle = dest.arrivalCircle } - newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null + newCourse.nextPoint.type = + typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { newCourse.href = dest.href From 554768238db7dc6d4b505ded5212fd226ba4bfaa Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 12 Dec 2021 23:04:09 +0200 Subject: [PATCH 102/410] refactor: tweak types Reorganise typings to reuse some of the existings types. --- src/api/resources/index.ts | 16 +++++----------- src/app.ts | 12 +++++++++--- src/index.ts | 6 +++--- src/types.ts | 6 ++++++ 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index c11f7f9b6..c88c281eb 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -6,6 +6,7 @@ import { import Debug from 'debug' import { Application, NextFunction, Request, Response } from 'express' import { v4 as uuidv4 } from 'uuid' +import { WithSecurityStrategy, WithSignalK } from '../../app' import { buildResource } from './resources' import { validate } from './validate' @@ -15,17 +16,10 @@ const debug = Debug('signalk:resources') const SIGNALK_API_PATH = `/signalk/v1/api` const UUID_PREFIX = 'urn:mrn:signalk:uuid:' -interface ResourceApplication extends Application { - handleMessage: (id: string, data: any) => void - securityStrategy: { - shouldAllowPut: ( - req: any, - context: string, - source: any, - path: string - ) => boolean - } -} +interface ResourceApplication + extends Application, + WithSignalK, + WithSecurityStrategy {} export class Resources { private resProvider: { [key: string]: ResourceProviderMethods | null } = {} diff --git a/src/app.ts b/src/app.ts index 633e3dc34..f00e821f3 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,6 +1,8 @@ import { FullSignalK } from '@signalk/signalk-schema' +import { EventEmitter } from 'events' import { Config } from './config/config' import DeltaCache from './deltacache' +import { SecurityStrategy } from './types' export interface ServerApp { started: boolean @@ -15,17 +17,21 @@ export interface ServerApp { clients: number } -export interface SignalKMessageHub { - emit: any - on: any +export interface WithSignalK { signalk: FullSignalK handleMessage: (id: string, data: any) => void } +export interface SignalKMessageHub extends EventEmitter, WithSignalK {} + export interface WithConfig { config: Config } +export interface WithSecurityStrategy { + securityStrategy: SecurityStrategy +} + export interface SelfIdentity { selfType: string selfId: string diff --git a/src/index.ts b/src/index.ts index 2645d1814..c8104d1cc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -261,7 +261,7 @@ class Server { const eventDebugs: { [key: string]: Debug.Debugger } = {} const emit = app.emit - app.emit = (eventName: string) => { + app.emit = (eventName: string, ...args: any[]) => { if (eventName !== 'serverlog') { let eventDebug = eventDebugs[eventName] if (!eventDebug) { @@ -270,10 +270,10 @@ class Server { ) } if (eventDebug.enabled) { - eventDebug([...arguments].slice(1)) + eventDebug([...args].slice(1)) } } - emit.apply(app, arguments) + return emit.apply(app, [eventName, ...args]) } this.app.intervals = [] diff --git a/src/types.ts b/src/types.ts index 4e6f0c5b0..3ab30be25 100644 --- a/src/types.ts +++ b/src/types.ts @@ -14,6 +14,12 @@ export interface SecurityStrategy { allowReadOnly: () => boolean shouldFilterDeltas: () => boolean filterReadDelta: (user: any, delta: any) => any + shouldAllowPut: ( + req: any, + context: string, + source: any, + path: string + ) => boolean } export interface Bus { From c4421479b1c05f78072ede495ababbdb804eca4d Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 13 Dec 2021 09:34:57 +1030 Subject: [PATCH 103/410] move provider.methods check to `register()` --- src/api/resources/index.ts | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index c88c281eb..1ad9e6426 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -48,8 +48,22 @@ export class Resources { } provider.types.forEach((i: string) => { if (!this.resProvider[i]) { - provider.methods.pluginId = pluginId - this.resProvider[i] = provider.methods + if ( + !provider.methods.listResources || + !provider.methods.getResource || + !provider.methods.setResource || + !provider.methods.deleteResource || + typeof provider.methods.listResources !== 'function' || + typeof provider.methods.getResource !== 'function' || + typeof provider.methods.setResource !== 'function' || + typeof provider.methods.deleteResource !== 'function' + ) { + console.error(`Error: Could not register Resource Provider for ${i.toUpperCase()} due to missing provider methods!`) + return + } else { + provider.methods.pluginId = pluginId + this.resProvider[i] = provider.methods + } } }) debug(this.resProvider) @@ -471,25 +485,7 @@ export class Resources { private checkForProvider(resType: SignalKResourceType): boolean { debug(`** checkForProvider(${resType})`) debug(this.resProvider[resType]) - - if (this.resProvider[resType]) { - if ( - !this.resProvider[resType]?.listResources || - !this.resProvider[resType]?.getResource || - !this.resProvider[resType]?.setResource || - !this.resProvider[resType]?.deleteResource || - typeof this.resProvider[resType]?.listResources !== 'function' || - typeof this.resProvider[resType]?.getResource !== 'function' || - typeof this.resProvider[resType]?.setResource !== 'function' || - typeof this.resProvider[resType]?.deleteResource !== 'function' - ) { - return false - } else { - return true - } - } else { - return false - } + return (this.resProvider[resType]) ? true : false } private buildDeltaMsg( From 5ddc49a48a8e07fd2e584e533a3e5d74187a4bf5 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 4 Dec 2021 17:12:14 +1030 Subject: [PATCH 104/410] init courseApi --- src/api/course/index.ts | 562 ++++++++++++++++++++++++++++++++++++ src/api/course/openApi.json | 413 ++++++++++++++++++++++++++ src/index.ts | 2 + 3 files changed, 977 insertions(+) create mode 100644 src/api/course/index.ts create mode 100644 src/api/course/openApi.json diff --git a/src/api/course/index.ts b/src/api/course/index.ts new file mode 100644 index 000000000..a8d42a1a8 --- /dev/null +++ b/src/api/course/index.ts @@ -0,0 +1,562 @@ +import { + ResourceProvider, + ResourceProviderMethods, + SignalKResourceType +} from '@signalk/server-api' +import Debug from 'debug' +import { Application, Request, Response } from 'express' +import { v4 as uuidv4 } from 'uuid' + +const debug = Debug('signalk:courseApi') + +const SIGNALK_API_PATH: string = `/signalk/v1/api` +const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' + +const API_METHODS: string[] = [] + +interface CourseApplication extends Application { + handleMessage: (id: string, data: any) => void + getSelfPath: (path: string) => any + resourcesApi: { + getResource: (resourceType: string, resourceId: string) => any + } +} + +interface DestinationBase { + href?: string + arrivalCircle?: number +} +interface Destination extends DestinationBase { + position?: { + latitude: number + longitude: number + altitude?: number + } + type?: string +} + +interface ActiveRoute extends DestinationBase { + pointIndex?: number + reverse?: boolean +} + +interface Position { + latitude: number + longitude: number + altitude?: number +} + +interface CourseInfo { + activeRoute: { + href: string | null + startTime: string | null + pointIndex: number + reverse: boolean + } + nextPoint: { + href: string | null + type: string | null + position: Position | null + arrivalCircle: number + } + previousPoint: { + href: string | null + type: string | null + position: Position | null + } +} + +export class CourseApi { + private server: CourseApplication + + private courseInfo: CourseInfo = { + activeRoute: { + href: null, + startTime: null, + pointIndex: 0, + reverse: false + }, + nextPoint: { + href: null, + type: null, + position: null, + arrivalCircle: 0 + }, + previousPoint: { + href: null, + type: null, + position: null + } + } + + constructor(app: CourseApplication) { + this.server = app + this.start(app) + } + + private start(app: any) { + debug(`** Initialise ${COURSE_API_PATH} path handler **`) + this.server = app + this.initResourceRoutes() + } + + private initResourceRoutes() { + // return current course information + this.server.get( + `${COURSE_API_PATH}`, + async (req: Request, res: Response) => { + debug(`** GET ${COURSE_API_PATH}`) + res.json(this.courseInfo) + } + ) + + // restart / arrivalCircle + this.server.put( + `${COURSE_API_PATH}/:action`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/:action`) + if (req.params.restart) { + // set previousPoint to vessel position + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.emitCourseInfo() + res.status(200).send(`Course restarted.`) + } else { + res.status(406).send(`Vessel position unavailable!`) + } + } + if (req.params.arrivalCircle) { + if (this.setArrivalCircle(req.params.arrivalCircle)) { + this.emitCourseInfo() + res.status(200).send(`Destination set successfully.`) + } else { + res.status(406).send(`Invalid Data`) + } + } + } + ) + + // set destination + this.server.put( + `${COURSE_API_PATH}/destination`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/destination`) + + if (!req.body.value) { + res.status(406).send(`Invalid Data`) + return + } + const result = await this.setDestination(req.body.value) + if (result) { + this.emitCourseInfo() + res.status(200).send(`Destination set successfully.`) + } else { + this.clearDestination() + this.emitCourseInfo() + res.status(406).send(`Invalid Data`) + } + } + ) + + // clear destination + this.server.delete( + `${COURSE_API_PATH}/destination`, + async (req: Request, res: Response) => { + debug(`** DELETE ${COURSE_API_PATH}/destination`) + this.clearDestination() + this.emitCourseInfo() + res.status(200).send(`Destination cleared.`) + } + ) + + // set / clear activeRoute + this.server.put( + `${COURSE_API_PATH}/activeRoute`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/activeRoute`) + + const result = await this.activateRoute(req.body.value) + if (result) { + this.emitCourseInfo() + res.status(200).send(`Active route set.`) + } else { + this.clearDestination() + this.emitCourseInfo() + res.status(406).send(`Invalid Data`) + } + } + ) + this.server.delete( + `${COURSE_API_PATH}/activeRoute`, + async (req: Request, res: Response) => { + debug(`** DELETE ${COURSE_API_PATH}/activeRoute`) + + this.clearDestination() + this.emitCourseInfo() + res.status(200).send(`Active route cleared.`) + } + ) + + this.server.put( + `${COURSE_API_PATH}/activeRoute/:action`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/activeRoute`) + + if (!this.courseInfo.activeRoute.href) { + res.status(406).send(`Invalid Data`) + return + } + + const rte = await this.getRoute(this.courseInfo.activeRoute.href) + if (!rte) { + res.status(406).send(`Invalid Data`) + return + } + + if (req.params.nextPoint) { + if ( + typeof req.body.value === 'number' && + (req.body.value === 1 || req.body.value === -1) + ) { + this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( + this.courseInfo.activeRoute.pointIndex + req.body.value, + rte + ) + } else { + res.status(406).send(`Invalid Data`) + } + } + if (req.params.pointIndex) { + if (typeof req.body.value === 'number') { + this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( + req.body.value, + rte + ) + } else { + res.status(406).send(`Invalid Data`) + } + } + + // set nextPoint + this.courseInfo.nextPoint.position = this.getRoutePoint( + rte, + this.courseInfo.activeRoute.pointIndex + ) + this.courseInfo.nextPoint.type = `RoutePoint` + this.courseInfo.nextPoint.href = null + + // set previousPoint + if (this.courseInfo.activeRoute.pointIndex === 0) { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } else { + this.courseInfo.previousPoint.position = this.getRoutePoint( + rte, + this.courseInfo.activeRoute.pointIndex - 1 + ) + this.courseInfo.previousPoint.type = `RoutePoint` + } + this.courseInfo.previousPoint.href = null + + res.status(200).send(`OK`) + } + ) + } + + private async activateRoute(route: ActiveRoute): Promise { + let rte: any + + if (route.href) { + rte = await this.getRoute(route.href) + if (!rte) { + return false + } + if (!Array.isArray(rte.feature?.geometry?.coordinates)) { + return false + } + } else { + return false + } + + // set activeroute + this.courseInfo.activeRoute.href = route.href + + if (typeof route.arrivalCircle === 'number') { + this.setArrivalCircle(route.arrivalCircle) + } + + this.courseInfo.activeRoute.reverse = + typeof route.reverse === 'boolean' ? route.reverse : false + + this.courseInfo.activeRoute.pointIndex = + typeof route.pointIndex === 'number' + ? this.parsePointIndex(route.pointIndex, rte) + : 0 + + this.courseInfo.activeRoute.startTime = new Date().toISOString() + + // set nextPoint + this.courseInfo.nextPoint.position = this.getRoutePoint( + rte, + this.courseInfo.activeRoute.pointIndex + ) + this.courseInfo.nextPoint.type = `RoutePoint` + this.courseInfo.nextPoint.href = null + + // set previousPoint + if (this.courseInfo.activeRoute.pointIndex === 0) { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } else { + this.courseInfo.previousPoint.position = this.getRoutePoint( + rte, + this.courseInfo.activeRoute.pointIndex - 1 + ) + this.courseInfo.previousPoint.type = `RoutePoint` + } + this.courseInfo.previousPoint.href = null + + return true + } + + private async setDestination(dest: Destination): Promise { + // set nextPoint + if (typeof dest.arrivalCircle === 'number') { + this.setArrivalCircle(dest.arrivalCircle) + } + + this.courseInfo.nextPoint.type = + typeof dest.type !== 'undefined' ? dest.type : null + + if (dest.href) { + this.courseInfo.nextPoint.href = dest.href + const href = this.parseHref(dest.href) + if (href) { + try { + const r = await this.server.resourcesApi.getResource( + href.type, + href.id + ) + if (r.position && typeof r.position.value?.latitude !== 'undefined') { + this.courseInfo.nextPoint.position = r.position.value + } + } catch (err) { + return false + } + } + } else if (dest.position) { + this.courseInfo.nextPoint.href = null + if (typeof dest.position.latitude !== 'undefined') { + this.courseInfo.nextPoint.position = dest.position + } else { + return false + } + } else { + return false + } + + // set previousPoint + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + this.courseInfo.previousPoint.href = null + + return true + } + + private clearDestination() { + this.courseInfo.activeRoute.href = null + this.courseInfo.activeRoute.startTime = null + this.courseInfo.activeRoute.pointIndex = 0 + this.courseInfo.activeRoute.reverse = false + this.courseInfo.nextPoint.href = null + this.courseInfo.nextPoint.type = null + this.courseInfo.nextPoint.position = null + this.courseInfo.previousPoint.href = null + this.courseInfo.previousPoint.type = null + this.courseInfo.previousPoint.position = null + } + + private setArrivalCircle(value: any): boolean { + if (typeof value === 'number' && value >= 0) { + this.courseInfo.nextPoint.arrivalCircle = value + return true + } else { + return false + } + } + + private parsePointIndex(index: number, rte: any): number { + if (!rte) { + return 0 + } + if (!rte.feature?.geometry?.coordinates) { + return 0 + } + if (!Array.isArray(rte.feature?.geometry?.coordinates)) { + return 0 + } + if (index < 0) { + return 0 + } + if (index > rte.feature?.geometry?.coordinates.length - 1) { + return rte.feature?.geometry?.coordinates.length + } + return index + } + + private parseHref(href: string): { type: string; id: string } | undefined { + if (href.length === 0) { + return undefined + } + if (href[0] === '/') { + href = href.slice(1) + } + const ref: string[] = href.split('/') + if (ref.length < 3) { + return undefined + } + if (ref[0] !== 'resources') { + return undefined + } + return { + type: ref[1], + id: ref[2] + } + } + + private getRoutePoint(rte: any, index: number) { + const pos = this.courseInfo.activeRoute.reverse + ? rte.feature.geometry.coordinates[ + rte.feature.geometry.coordinates.length - (index + 1) + ] + : rte.feature.geometry.coordinates[index] + return { + latitude: pos[1], + longitude: pos[0], + altitude: pos.length == 3 ? pos[2] : 0 + } + } + + private async getRoute(href: string): Promise { + const h = this.parseHref(href) + if (h) { + try { + return await this.server.resourcesApi.getResource(h.type, h.id) + } catch (err) { + return undefined + } + } else { + return undefined + } + } + + private buildDeltaMsg(): any { + let values: Array<{path:string, value:any}> = [] + let root = [ + 'navigation.courseGreatCircle', + 'navigation.courseRhumbline' + ] + + values.push({ + path: `${root[0]}.activeRoute.href`, + value: this.courseInfo.activeRoute.href + }) + values.push({ + path: `${root[1]}.activeRoute.href`, + value: this.courseInfo.activeRoute.href + }) + values.push({ + path: `${root[0]}.activeRoute.startTime`, + value: this.courseInfo.activeRoute.startTime + }) + values.push({ + path: `${root[1]}.activeRoute.startTime`, + value: this.courseInfo.activeRoute.startTime + }) + values.push({ + path: `${root[0]}.nextPoint.href`, + value: this.courseInfo.nextPoint.href + }) + values.push({ + path: `${root[1]}.nextPoint.href`, + value: this.courseInfo.nextPoint.href + }) + values.push({ + path: `${root[0]}.nextPoint.position`, + value: this.courseInfo.nextPoint.position + }) + values.push({ + path: `${root[1]}.nextPoint.position`, + value: this.courseInfo.nextPoint.position + }) + values.push({ + path: `${root[0]}.nextPoint.type`, + value: this.courseInfo.nextPoint.type + }) + values.push({ + path: `${root[1]}.nextPoint.type`, + value: this.courseInfo.nextPoint.type + }) + values.push({ + path: `${root[0]}.nextPoint.arrivalCircle`, + value: this.courseInfo.nextPoint.arrivalCircle + }) + values.push({ + path: `${root[1]}.nextPoint.arrivalCircle`, + value: this.courseInfo.nextPoint.arrivalCircle + }) + values.push({ + path: `${root[0]}.previousPoint.href`, + value: this.courseInfo.previousPoint.href + }) + values.push({ + path: `${root[1]}.previousPoint.href`, + value: this.courseInfo.previousPoint.href + }) + values.push({ + path: `${root[0]}.previousPoint.position`, + value: this.courseInfo.previousPoint.position + }) + values.push({ + path: `${root[1]}.previousPoint.position`, + value: this.courseInfo.previousPoint.position + }) + values.push({ + path: `${root[0]}.previousPoint.type`, + value: this.courseInfo.previousPoint.type + }) + values.push({ + path: `${root[1]}.previousPoint.type`, + value: this.courseInfo.previousPoint.type + }) + + return { + updates: [ + { + values: values + } + ] + } + } + + private emitCourseInfo() { + this.server.handleMessage('courseApi', this.buildDeltaMsg()) + } +} diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json new file mode 100644 index 000000000..04653db5e --- /dev/null +++ b/src/api/course/openApi.json @@ -0,0 +1,413 @@ +{ + "openapi": "3.0.2", + "info": { + "version": "1.0.0", + "title": "Signal K Course API" + }, + + "paths": { + + "/vessels/self/navigation/course/": { + "get": { + "tags": ["course"], + "summary": "Get course information", + "responses": { + "default": { + "description": "Course data", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "activeRoute": { + "type": "object", + "properties": { + "href": { + "type": "string", + "example": "/resources/routes/urn:mrn:signalk:uuid:0d95e282-3e1f-4521-8c30-8288addbdb69" + }, + "startTime": { + "type": "string", + "example": "2021-10-23T05:17:20.065Z" + }, + "pointIndex": { + "type": "number", + "format": "int64", + "example": 2 + }, + "reverse": { + "type": "boolean", + "default": false + } + } + }, + "nextPoint": { + "type": "object", + "properties": { + "href": { + "type": "string", + "example": "/resources/waypoints/urn:mrn:signalk:uuid:0d95e282-3e1f-4521-8c30-8288addbdbab" + }, + "type": { + "type": "string", + "example": "RoutePoint" + }, + "position": { + "type": "object", + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + }, + "example": {"latitude":-29.5,"longitude":137.5} + } + } + }, + "previousPoint": { + "type": "object", + "properties": { + "href": { + "type": "string", + "example": null + }, + "type": { + "type": "string", + "example": "Location" + }, + "position": { + "type": "object", + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + }, + "example": {"longitude":29.821001582434413,"latitude":70.7014589462524} + } + } + } + } + } + } + } + } + } + } + }, + + "/vessels/self/navigation/course/restart": { + "put": { + "tags": ["course"], + "summary": "Restart course calculations", + "description": "Sets previousPoint value to current vessel location", + "requestBody": { + "description": "Restart payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + } + }, + + "/vessels/self/navigation/course/destination": { + "put": { + "tags": ["course/destination"], + "summary": "Set destination", + "description": "Set destination path from supplied details", + "requestBody": { + "description": "Destination details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "properties": { + "position": { + "type": "object", + "description": "Destination position", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + }, + "example": {"latitude":-29.5,"longitude":137.5} + }, + "href": { + "type": "string", + "description": "A reference (URL) to an object (under /resources) this point is related to", + "example": "/resources/waypoints/urn:mrn:signalk:uuid:3dd34dcc-36bf-4d61-ba80-233799b25672" + }, + "type": { + "type": "string", + "description": "Type of point", + "example": "POI" + }, + "arrivalCircle": { + "type": "number", + "format": "float", + "example": 500 + } + } + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + }, + "delete": { + "tags": ["course/destination"], + "summary": "Clear destination", + "description": "Sets activeRoute, nextPoint & previousPoint values to null", + "requestBody": { + "description": "Delete payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + } + }, + + "/vessels/self/navigation/course/arrivalCircle": { + "put": { + "tags": ["course"], + "summary": "Set arrival circle radius (m)", + "description": "Sets an arrival circle radius (in meters)", + "requestBody": { + "description": "Arrival circle payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "number", + "format": "float", + "example": 500 + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + } + }, + + "/vessels/self/navigation/course/activeRoute": { + "put": { + "tags": ["course/activeRoute"], + "summary": "Set active route", + "description": "Sets activeRoute path and sets nextPoint to first point in the route", + "requestBody": { + "description": "Active route details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "properties": { + "href": { + "type": "string", + "description": "Path to route resource", + "example": "/resources/routes/urn:mrn:signalk:uuid:3dd34dcc-36bf-4d61-ba80-233799b25672" + }, + "pointIndex": { + "type": "number", + "format": "int64", + "description": "Zero based index of route point to use as destination", + "default": 0, + "minimum": 0, + "example": 2 + }, + "reverse": { + "type": "boolean", + "default": false, + "description": "If true performs operations on route points in reverse order", + "example": 2 + }, + "arrivalCircle": { + "type": "number", + "format": "float", + "example": 500 + } + } + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + }, + "delete": { + "tags": ["course/activeRoute"], + "summary": "Clear active route", + "description": "Sets activeRoute, nextPoint & previousPoint values to null", + "requestBody": { + "description": "Delete payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + } + }, + + "/vessels/self/navigation/course/activeRoute/nextPoint": { + "put": { + "tags": ["course/activeRoute"], + "summary": "Increment / decrement point in route as destination", + "description": "Increment / decrement point in the route as destination", + "requestBody": { + "description": "Increment / decrement", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "number", + "description": "increment (1) / decrement (-1) index of point in route to use as destination", + "enum": [-1,1], + "example": -1 + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + } + }, + + "/vessels/self/navigation/course/activeRoute/pointIndex": { + "put": { + "tags": ["course/activeRoute"], + "summary": "Set point in route as destination", + "description": "Sets the specified point in the route as destination", + "requestBody": { + "description": "Next point index", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "number", + "description": "Index of point in route to use as destination", + "minimum": 0, + "example": 2 + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + } + } + + } + +} + + + + + \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index c8104d1cc..c4709bed8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -51,6 +51,7 @@ import { startSecurity } from './security.js' +import { CourseApi } from './api/course' import { Resources } from './api/resources' // tslint:disable-next-line: no-var-requires @@ -84,6 +85,7 @@ class Server { require('./put').start(app) app.resourcesApi = new Resources(app) + const courseApi = new CourseApi(app) app.signalk = new FullSignalK(app.selfId, app.selfType) From adb0a5d765c85ab132f2f8461e5bbe3d000d0d5d Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 5 Dec 2021 10:26:47 +1030 Subject: [PATCH 105/410] update detlas --- src/api/course/index.ts | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index a8d42a1a8..7d186618c 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,23 +1,15 @@ -import { - ResourceProvider, - ResourceProviderMethods, - SignalKResourceType -} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' -import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' - -const API_METHODS: string[] = [] interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any + registerPutHandler: (context:string, path:string, cb:any) => any resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -35,7 +27,6 @@ interface Destination extends DestinationBase { } type?: string } - interface ActiveRoute extends DestinationBase { pointIndex?: number reverse?: boolean @@ -111,12 +102,26 @@ export class CourseApi { } ) + // + if(this.server.registerPutHandler) { + debug('** Registering PUT Action Handler(s) **') + this.server.registerPutHandler( + 'vessels.self', + 'navigation.course.*', + this.handleCourseApiPut + ); + } + // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { + //test for active destination + if (!this.courseInfo.nextPoint.position) { + return + } // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -270,6 +275,12 @@ export class CourseApi { ) } + private handleCourseApiPut(context:string, path:string, value:any, cb:any) { + + debug('** PUT handler **') + return undefined + } + private async activateRoute(route: ActiveRoute): Promise { let rte: any @@ -474,6 +485,11 @@ export class CourseApi { 'navigation.courseRhumbline' ] + values.push({ + path: `navigation.course`, + value: this.courseInfo + }) + values.push({ path: `${root[0]}.activeRoute.href`, value: this.courseInfo.activeRoute.href From 49a52d423b0158d21fb0bc7b1b14219369eebb67 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 4 Dec 2021 17:12:14 +1030 Subject: [PATCH 106/410] init courseApi --- src/api/course/index.ts | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 7d186618c..d3d42fed7 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,15 +1,26 @@ + +import { + ResourceProvider, + ResourceProviderMethods, + SignalKResourceType +} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' +import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' + +const API_METHODS: string[] = [] + interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - registerPutHandler: (context:string, path:string, cb:any) => any + resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -102,26 +113,12 @@ export class CourseApi { } ) - // - if(this.server.registerPutHandler) { - debug('** Registering PUT Action Handler(s) **') - this.server.registerPutHandler( - 'vessels.self', - 'navigation.course.*', - this.handleCourseApiPut - ); - } - // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { - //test for active destination - if (!this.courseInfo.nextPoint.position) { - return - } // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -275,12 +272,6 @@ export class CourseApi { ) } - private handleCourseApiPut(context:string, path:string, value:any, cb:any) { - - debug('** PUT handler **') - return undefined - } - private async activateRoute(route: ActiveRoute): Promise { let rte: any From 8deeb49d338337fbf0b5fcfbb969eb7a237aabb0 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 5 Dec 2021 10:26:47 +1030 Subject: [PATCH 107/410] update detlas --- src/api/course/index.ts | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index d3d42fed7..7d186618c 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,26 +1,15 @@ - -import { - ResourceProvider, - ResourceProviderMethods, - SignalKResourceType -} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' -import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' - -const API_METHODS: string[] = [] - interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - + registerPutHandler: (context:string, path:string, cb:any) => any resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -113,12 +102,26 @@ export class CourseApi { } ) + // + if(this.server.registerPutHandler) { + debug('** Registering PUT Action Handler(s) **') + this.server.registerPutHandler( + 'vessels.self', + 'navigation.course.*', + this.handleCourseApiPut + ); + } + // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { + //test for active destination + if (!this.courseInfo.nextPoint.position) { + return + } // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -272,6 +275,12 @@ export class CourseApi { ) } + private handleCourseApiPut(context:string, path:string, value:any, cb:any) { + + debug('** PUT handler **') + return undefined + } + private async activateRoute(route: ActiveRoute): Promise { let rte: any From de98d1d9943866aed2c99ecf551abf0554305e1e Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 6 Dec 2021 08:39:56 +1030 Subject: [PATCH 108/410] enable put processing --- src/api/course/index.ts | 136 ++++++++++++++++++++++++---------------- src/put.js | 7 +++ 2 files changed, 89 insertions(+), 54 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 7d186618c..1566ecdf3 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -9,7 +9,14 @@ const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/cou interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - registerPutHandler: (context:string, path:string, cb:any) => any + securityStrategy: { + shouldAllowPut: ( + req: any, + context: string, + source: any, + path: string + ) => boolean + } resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -92,6 +99,15 @@ export class CourseApi { this.initResourceRoutes() } + private updateAllowed(): boolean { + return this.server.securityStrategy.shouldAllowPut( + this.server, + 'vessels.self', + null, + 'resources' + ) + } + private initResourceRoutes() { // return current course information this.server.get( @@ -102,43 +118,43 @@ export class CourseApi { } ) - // - if(this.server.registerPutHandler) { - debug('** Registering PUT Action Handler(s) **') - this.server.registerPutHandler( - 'vessels.self', - 'navigation.course.*', - this.handleCourseApiPut - ); - } + this.server.put( + `${COURSE_API_PATH}/restart`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/restart`) + if (!this.updateAllowed()) { + res.status(403) + return + } + if (!this.courseInfo.nextPoint.position) { + res.status(406).send(`No active destination!`) + return + } + // set previousPoint to vessel position + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.emitCourseInfo() + res.status(200) + } else { + res.status(406).send(`Vessel position unavailable!`) + } + } + ) - // restart / arrivalCircle this.server.put( - `${COURSE_API_PATH}/:action`, + `${COURSE_API_PATH}/arrivalCircle`, async (req: Request, res: Response) => { - debug(`** PUT ${COURSE_API_PATH}/:action`) - if (req.params.restart) { - //test for active destination - if (!this.courseInfo.nextPoint.position) { - return - } - // set previousPoint to vessel position - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.emitCourseInfo() - res.status(200).send(`Course restarted.`) - } else { - res.status(406).send(`Vessel position unavailable!`) - } + debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) + if (!this.updateAllowed()) { + res.status(403) + return } - if (req.params.arrivalCircle) { - if (this.setArrivalCircle(req.params.arrivalCircle)) { - this.emitCourseInfo() - res.status(200).send(`Destination set successfully.`) - } else { - res.status(406).send(`Invalid Data`) - } + if (this.setArrivalCircle(req.body.value)) { + this.emitCourseInfo() + res.status(200) + } else { + res.status(406).send(`Invalid Data`) } } ) @@ -148,7 +164,10 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) - + if (!this.updateAllowed()) { + res.status(403) + return + } if (!req.body.value) { res.status(406).send(`Invalid Data`) return @@ -156,7 +175,7 @@ export class CourseApi { const result = await this.setDestination(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send(`Destination set successfully.`) + res.status(200) } else { this.clearDestination() this.emitCourseInfo() @@ -170,22 +189,29 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) + if (!this.updateAllowed()) { + res.status(403) + return + } this.clearDestination() this.emitCourseInfo() - res.status(200).send(`Destination cleared.`) + res.status(200) } ) - // set / clear activeRoute + // set activeRoute this.server.put( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) - + if (!this.updateAllowed()) { + res.status(403) + return + } const result = await this.activateRoute(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send(`Active route set.`) + res.status(200) } else { this.clearDestination() this.emitCourseInfo() @@ -193,14 +219,19 @@ export class CourseApi { } } ) + + // clear activeRoute /destination this.server.delete( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/activeRoute`) - + if (!this.updateAllowed()) { + res.status(403) + return + } this.clearDestination() this.emitCourseInfo() - res.status(200).send(`Active route cleared.`) + res.status(200) } ) @@ -208,19 +239,22 @@ export class CourseApi { `${COURSE_API_PATH}/activeRoute/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) - + if (!this.updateAllowed()) { + res.status(403) + return + } + // fetch route data if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return } - const rte = await this.getRoute(this.courseInfo.activeRoute.href) if (!rte) { res.status(406).send(`Invalid Data`) return } - if (req.params.nextPoint) { + if (req.params.action === 'nextPoint') { if ( typeof req.body.value === 'number' && (req.body.value === 1 || req.body.value === -1) @@ -233,7 +267,7 @@ export class CourseApi { res.status(406).send(`Invalid Data`) } } - if (req.params.pointIndex) { + if (req.params.action === 'pointIndex') { if (typeof req.body.value === 'number') { this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( req.body.value, @@ -244,7 +278,7 @@ export class CourseApi { } } - // set nextPoint + // set new destination this.courseInfo.nextPoint.position = this.getRoutePoint( rte, this.courseInfo.activeRoute.pointIndex @@ -270,17 +304,11 @@ export class CourseApi { } this.courseInfo.previousPoint.href = null - res.status(200).send(`OK`) + res.status(200) } ) } - private handleCourseApiPut(context:string, path:string, value:any, cb:any) { - - debug('** PUT handler **') - return undefined - } - private async activateRoute(route: ActiveRoute): Promise { let rte: any diff --git a/src/put.js b/src/put.js index e6e14a965..c7cf42616 100644 --- a/src/put.js +++ b/src/put.js @@ -35,6 +35,13 @@ module.exports = { next() return } + + // ** ignore course paths ** + if (req.path.indexOf('/navigation/course') !== -1) { + next() + return + } + let path = String(req.path).replace(apiPathPrefix, '') const value = req.body From c40a54c19b2a1bb3bee9a812a40ab6ae5a7c99da Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 6 Dec 2021 13:34:31 +1030 Subject: [PATCH 109/410] chore: lint --- src/api/course/index.ts | 211 ++++++++++++++++++++---------------- src/api/course/openApi.json | 72 +----------- 2 files changed, 120 insertions(+), 163 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 1566ecdf3..6cd3b80fd 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -11,7 +11,7 @@ interface CourseApplication extends Application { getSelfPath: (path: string) => any securityStrategy: { shouldAllowPut: ( - req: any, + req: Application, context: string, source: any, path: string @@ -104,7 +104,7 @@ export class CourseApi { this.server, 'vessels.self', null, - 'resources' + 'navigation.course' ) } @@ -131,12 +131,14 @@ export class CourseApi { return } // set previousPoint to vessel position - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.emitCourseInfo() - res.status(200) - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.emitCourseInfo() + res.status(200) + } + } catch (err) { res.status(406).send(`Vessel position unavailable!`) } } @@ -150,7 +152,8 @@ export class CourseApi { res.status(403) return } - if (this.setArrivalCircle(req.body.value)) { + if (this.isValidArrivalCircle(req.body.value)) { + this.courseInfo.nextPoint.arrivalCircle = req.body.value this.emitCourseInfo() res.status(200) } else { @@ -243,7 +246,7 @@ export class CourseApi { res.status(403) return } - // fetch route data + // fetch active route data if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return @@ -281,24 +284,30 @@ export class CourseApi { // set new destination this.courseInfo.nextPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex + this.courseInfo.activeRoute.pointIndex, + this.courseInfo.activeRoute.reverse ) this.courseInfo.nextPoint.type = `RoutePoint` this.courseInfo.nextPoint.href = null // set previousPoint if (this.courseInfo.activeRoute.pointIndex === 0) { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } catch (err) { return false } } else { this.courseInfo.previousPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex - 1 + this.courseInfo.activeRoute.pointIndex - 1, + this.courseInfo.activeRoute.reverse ) this.courseInfo.previousPoint.type = `RoutePoint` } @@ -324,81 +333,97 @@ export class CourseApi { return false } + const newDest: any = {} + Object.assign(newDest, this.courseInfo) + // set activeroute - this.courseInfo.activeRoute.href = route.href + newDest.activeRoute.href = route.href - if (typeof route.arrivalCircle === 'number') { - this.setArrivalCircle(route.arrivalCircle) + if (this.isValidArrivalCircle(route.arrivalCircle as number)) { + newDest.nextPoint.arrivalCircle = route.arrivalCircle } - this.courseInfo.activeRoute.reverse = - typeof route.reverse === 'boolean' ? route.reverse : false + newDest.activeRoute.startTime = new Date().toISOString() - this.courseInfo.activeRoute.pointIndex = - typeof route.pointIndex === 'number' - ? this.parsePointIndex(route.pointIndex, rte) - : 0 + if (typeof route.reverse === 'boolean') { + newDest.activeRoute.reverse = route.reverse + } - this.courseInfo.activeRoute.startTime = new Date().toISOString() + newDest.activeRoute.pointIndex = this.parsePointIndex( + route.pointIndex as number, + rte + ) // set nextPoint - this.courseInfo.nextPoint.position = this.getRoutePoint( + newDest.nextPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex + newDest.activeRoute.pointIndex, + newDest.activeRoute.reverse ) - this.courseInfo.nextPoint.type = `RoutePoint` - this.courseInfo.nextPoint.href = null + newDest.nextPoint.type = `RoutePoint` + newDest.nextPoint.href = null // set previousPoint - if (this.courseInfo.activeRoute.pointIndex === 0) { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + if (newDest.activeRoute.pointIndex === 0) { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } catch (err) { return false } } else { - this.courseInfo.previousPoint.position = this.getRoutePoint( + newDest.previousPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex - 1 + newDest.activeRoute.pointIndex - 1, + newDest.activeRoute.reverse ) - this.courseInfo.previousPoint.type = `RoutePoint` + newDest.previousPoint.type = `RoutePoint` } - this.courseInfo.previousPoint.href = null + newDest.previousPoint.href = null + this.courseInfo = newDest return true } - private async setDestination(dest: Destination): Promise { + private async setDestination(dest: any): Promise { + const newDest: any = {} + Object.assign(newDest, this.courseInfo) + // set nextPoint - if (typeof dest.arrivalCircle === 'number') { - this.setArrivalCircle(dest.arrivalCircle) + if (this.isValidArrivalCircle(dest.arrivalCircle)) { + newDest.nextPoint.arrivalCircle = dest.arrivalCircle } - this.courseInfo.nextPoint.type = - typeof dest.type !== 'undefined' ? dest.type : null + newDest.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - this.courseInfo.nextPoint.href = dest.href + newDest.href = dest.href const href = this.parseHref(dest.href) if (href) { + // fetch waypoint resource details try { const r = await this.server.resourcesApi.getResource( href.type, href.id ) - if (r.position && typeof r.position.value?.latitude !== 'undefined') { - this.courseInfo.nextPoint.position = r.position.value + if (r.position && typeof r.position?.latitude !== 'undefined') { + newDest.nextPoint.position = r.position + } else { + return false } } catch (err) { return false } } } else if (dest.position) { - this.courseInfo.nextPoint.href = null + newDest.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { - this.courseInfo.nextPoint.position = dest.position + newDest.nextPoint.position = dest.position } else { return false } @@ -407,15 +432,20 @@ export class CourseApi { } // set previousPoint - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + newDest.previousPoint.position = position.value + newDest.previousPoint.type = `VesselPosition` + } else { + return false + } + newDest.previousPoint.href = null + } catch (err) { return false } - this.courseInfo.previousPoint.href = null + this.courseInfo = newDest return true } @@ -432,17 +462,12 @@ export class CourseApi { this.courseInfo.previousPoint.position = null } - private setArrivalCircle(value: any): boolean { - if (typeof value === 'number' && value >= 0) { - this.courseInfo.nextPoint.arrivalCircle = value - return true - } else { - return false - } + private isValidArrivalCircle(value: number): boolean { + return typeof value === 'number' && value >= 0 } private parsePointIndex(index: number, rte: any): number { - if (!rte) { + if (typeof index !== 'number' || !rte) { return 0 } if (!rte.feature?.geometry?.coordinates) { @@ -461,13 +486,11 @@ export class CourseApi { } private parseHref(href: string): { type: string; id: string } | undefined { - if (href.length === 0) { + if (!href) { return undefined } - if (href[0] === '/') { - href = href.slice(1) - } - const ref: string[] = href.split('/') + + const ref: string[] = href.split('/').slice(-3) if (ref.length < 3) { return undefined } @@ -480,8 +503,8 @@ export class CourseApi { } } - private getRoutePoint(rte: any, index: number) { - const pos = this.courseInfo.activeRoute.reverse + private getRoutePoint(rte: any, index: number, reverse: boolean) { + const pos = reverse ? rte.feature.geometry.coordinates[ rte.feature.geometry.coordinates.length - (index + 1) ] @@ -489,7 +512,7 @@ export class CourseApi { return { latitude: pos[1], longitude: pos[0], - altitude: pos.length == 3 ? pos[2] : 0 + altitude: pos.length === 3 ? pos[2] : 0 } } @@ -507,8 +530,8 @@ export class CourseApi { } private buildDeltaMsg(): any { - let values: Array<{path:string, value:any}> = [] - let root = [ + const values: Array<{ path: string; value: any }> = [] + const navPath = [ 'navigation.courseGreatCircle', 'navigation.courseRhumbline' ] @@ -519,82 +542,82 @@ export class CourseApi { }) values.push({ - path: `${root[0]}.activeRoute.href`, + path: `${navPath[0]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${root[1]}.activeRoute.href`, + path: `${navPath[1]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${root[0]}.activeRoute.startTime`, + path: `${navPath[0]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${root[1]}.activeRoute.startTime`, + path: `${navPath[1]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${root[0]}.nextPoint.href`, + path: `${navPath[0]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${root[1]}.nextPoint.href`, + path: `${navPath[1]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${root[0]}.nextPoint.position`, + path: `${navPath[0]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${root[1]}.nextPoint.position`, + path: `${navPath[1]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${root[0]}.nextPoint.type`, + path: `${navPath[0]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${root[1]}.nextPoint.type`, + path: `${navPath[1]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${root[0]}.nextPoint.arrivalCircle`, + path: `${navPath[0]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${root[1]}.nextPoint.arrivalCircle`, + path: `${navPath[1]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${root[0]}.previousPoint.href`, + path: `${navPath[0]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${root[1]}.previousPoint.href`, + path: `${navPath[1]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${root[0]}.previousPoint.position`, + path: `${navPath[0]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${root[1]}.previousPoint.position`, + path: `${navPath[1]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${root[0]}.previousPoint.type`, + path: `${navPath[0]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) values.push({ - path: `${root[1]}.previousPoint.type`, + path: `${navPath[1]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) return { updates: [ { - values: values + values } ] } diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index 04653db5e..b162d1314 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -108,29 +108,7 @@ "put": { "tags": ["course"], "summary": "Restart course calculations", - "description": "Sets previousPoint value to current vessel location", - "requestBody": { - "description": "Restart payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } - } - } - } - } - } + "description": "Sets previousPoint value to current vessel location" } }, @@ -200,29 +178,7 @@ "delete": { "tags": ["course/destination"], "summary": "Clear destination", - "description": "Sets activeRoute, nextPoint & previousPoint values to null", - "requestBody": { - "description": "Delete payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } - } - } - } - } - } + "description": "Sets activeRoute, nextPoint & previousPoint values to null" } }, @@ -313,29 +269,7 @@ "delete": { "tags": ["course/activeRoute"], "summary": "Clear active route", - "description": "Sets activeRoute, nextPoint & previousPoint values to null", - "requestBody": { - "description": "Delete payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } - } - } - } - } - } + "description": "Sets activeRoute, nextPoint & previousPoint values to null" } }, From eeeea29dd23f30092bbb09a84dc384bfe3eb3244 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 8 Dec 2021 09:27:06 +1030 Subject: [PATCH 110/410] add 30sec delta interval --- src/api/course/index.ts | 100 ++++++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 45 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 6cd3b80fd..2aa57e8a6 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -6,6 +6,8 @@ const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const DELTA_INTERVAL: number = 30000 + interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any @@ -97,6 +99,11 @@ export class CourseApi { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app this.initResourceRoutes() + setInterval( ()=> { + if(this.courseInfo.nextPoint.position) { + this.emitCourseInfo() + } + }, DELTA_INTERVAL) } private updateAllowed(): boolean { @@ -123,7 +130,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/restart`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (!this.courseInfo.nextPoint.position) { @@ -136,7 +143,7 @@ export class CourseApi { if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } } catch (err) { res.status(406).send(`Vessel position unavailable!`) @@ -149,13 +156,13 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (this.isValidArrivalCircle(req.body.value)) { this.courseInfo.nextPoint.arrivalCircle = req.body.value this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { res.status(406).send(`Invalid Data`) } @@ -168,7 +175,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (!req.body.value) { @@ -178,7 +185,7 @@ export class CourseApi { const result = await this.setDestination(req.body.value) if (result) { this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { this.clearDestination() this.emitCourseInfo() @@ -193,12 +200,12 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } this.clearDestination() this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } ) @@ -208,13 +215,13 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } const result = await this.activateRoute(req.body.value) if (result) { this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { this.clearDestination() this.emitCourseInfo() @@ -234,7 +241,7 @@ export class CourseApi { } this.clearDestination() this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } ) @@ -243,7 +250,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } // fetch active route data @@ -268,6 +275,7 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) + return } } if (req.params.action === 'pointIndex') { @@ -278,6 +286,7 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) + return } } @@ -298,9 +307,11 @@ export class CourseApi { this.courseInfo.previousPoint.position = position.value this.courseInfo.previousPoint.type = `VesselPosition` } else { + res.status(406).send(`Invalid Data`) return false } } catch (err) { + res.status(406).send(`Invalid Data`) return false } } else { @@ -312,8 +323,7 @@ export class CourseApi { this.courseInfo.previousPoint.type = `RoutePoint` } this.courseInfo.previousPoint.href = null - - res.status(200) + res.status(200).send('OK') } ) } @@ -333,38 +343,38 @@ export class CourseApi { return false } - const newDest: any = {} - Object.assign(newDest, this.courseInfo) + const newCourse: any = {} + Object.assign(newCourse, this.courseInfo) // set activeroute - newDest.activeRoute.href = route.href + newCourse.activeRoute.href = route.href if (this.isValidArrivalCircle(route.arrivalCircle as number)) { - newDest.nextPoint.arrivalCircle = route.arrivalCircle + newCourse.nextPoint.arrivalCircle = route.arrivalCircle } - newDest.activeRoute.startTime = new Date().toISOString() + newCourse.activeRoute.startTime = new Date().toISOString() if (typeof route.reverse === 'boolean') { - newDest.activeRoute.reverse = route.reverse + newCourse.activeRoute.reverse = route.reverse } - newDest.activeRoute.pointIndex = this.parsePointIndex( + newCourse.activeRoute.pointIndex = this.parsePointIndex( route.pointIndex as number, rte ) // set nextPoint - newDest.nextPoint.position = this.getRoutePoint( + newCourse.nextPoint.position = this.getRoutePoint( rte, - newDest.activeRoute.pointIndex, - newDest.activeRoute.reverse + newCourse.activeRoute.pointIndex, + newCourse.activeRoute.reverse ) - newDest.nextPoint.type = `RoutePoint` - newDest.nextPoint.href = null + newCourse.nextPoint.type = `RoutePoint` + newCourse.nextPoint.href = null // set previousPoint - if (newDest.activeRoute.pointIndex === 0) { + if (newCourse.activeRoute.pointIndex === 0) { try { const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -377,32 +387,32 @@ export class CourseApi { return false } } else { - newDest.previousPoint.position = this.getRoutePoint( + newCourse.previousPoint.position = this.getRoutePoint( rte, - newDest.activeRoute.pointIndex - 1, - newDest.activeRoute.reverse + newCourse.activeRoute.pointIndex - 1, + newCourse.activeRoute.reverse ) - newDest.previousPoint.type = `RoutePoint` + newCourse.previousPoint.type = `RoutePoint` } - newDest.previousPoint.href = null + newCourse.previousPoint.href = null - this.courseInfo = newDest + this.courseInfo = newCourse return true } private async setDestination(dest: any): Promise { - const newDest: any = {} - Object.assign(newDest, this.courseInfo) + const newCourse: any = {} + Object.assign(newCourse, this.courseInfo) // set nextPoint if (this.isValidArrivalCircle(dest.arrivalCircle)) { - newDest.nextPoint.arrivalCircle = dest.arrivalCircle + newCourse.nextPoint.arrivalCircle = dest.arrivalCircle } - newDest.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null + newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - newDest.href = dest.href + newCourse.href = dest.href const href = this.parseHref(dest.href) if (href) { // fetch waypoint resource details @@ -412,7 +422,7 @@ export class CourseApi { href.id ) if (r.position && typeof r.position?.latitude !== 'undefined') { - newDest.nextPoint.position = r.position + newCourse.nextPoint.position = r.position } else { return false } @@ -421,9 +431,9 @@ export class CourseApi { } } } else if (dest.position) { - newDest.nextPoint.href = null + newCourse.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { - newDest.nextPoint.position = dest.position + newCourse.nextPoint.position = dest.position } else { return false } @@ -435,17 +445,17 @@ export class CourseApi { try { const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { - newDest.previousPoint.position = position.value - newDest.previousPoint.type = `VesselPosition` + newCourse.previousPoint.position = position.value + newCourse.previousPoint.type = `VesselPosition` } else { return false } - newDest.previousPoint.href = null + newCourse.previousPoint.href = null } catch (err) { return false } - this.courseInfo = newDest + this.courseInfo = newCourse return true } From d001e760f7a39dc4016c8b9ef9345edfda82232b Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 12 Dec 2021 14:29:38 +1030 Subject: [PATCH 111/410] chore: lint --- src/api/course/index.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 2aa57e8a6..1e719297a 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -6,7 +6,7 @@ const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const DELTA_INTERVAL: number = 30000 +const DELTA_INTERVAL: number = 30000 interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void @@ -99,8 +99,8 @@ export class CourseApi { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app this.initResourceRoutes() - setInterval( ()=> { - if(this.courseInfo.nextPoint.position) { + setInterval(() => { + if (this.courseInfo.nextPoint.position) { this.emitCourseInfo() } }, DELTA_INTERVAL) @@ -409,7 +409,8 @@ export class CourseApi { newCourse.nextPoint.arrivalCircle = dest.arrivalCircle } - newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null + newCourse.nextPoint.type = + typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { newCourse.href = dest.href From f95a9948ba0268ce7c22d5d04eda6510ba527fb1 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 18 Dec 2021 16:23:13 +1030 Subject: [PATCH 112/410] Use POST for paths that DO NOT specify an id --- WORKING_WITH_RESOURCES_API.md | 36 ++- src/api/resources/index.ts | 8 +- src/api/resources/openApi.json | 395 ++++++++++++++++++++++++++++++++- 3 files changed, 418 insertions(+), 21 deletions(-) diff --git a/WORKING_WITH_RESOURCES_API.md b/WORKING_WITH_RESOURCES_API.md index 54019dd61..f972ea2c4 100644 --- a/WORKING_WITH_RESOURCES_API.md +++ b/WORKING_WITH_RESOURCES_API.md @@ -72,11 +72,25 @@ In this example the route with the supplied id is deleted from storage. ### Creating / updating Resources --- -Resource entries are created and updated by submitting an HTTP `PUT` request that contains the resource data to the relevant API path depending on the type of resource to create / update. +Resource entries are created by submitting an HTTP `POST` request to the relevant API path that does NOT include a resource `id`. In this instance the resource is created with an `id` that is generated by the server. -Each resource type has a specific set of attributes that are required to be supplied before the resource entry can be created. +___Note: Each `POST` will generate a new `id` so if the resource data remains the same duplicate resources will be created.___ -Generally a resource will be created by submitting the resource attributes and the server will generate a resource id for the entry. You can assign a specific id for a newly created resource by using an API path containing the id you wish to assign to the resource. _Note: if a resource of the same type with the supplied id already exists, it will be overwritten with the submitted data!_ +_Example: Create a new route._ +```typescript +HTTP POST 'http://hostname:3000/signalk/v1/api/resources/set/route' {..} +``` + +Resource entries are updated by submitting an HTTP `PUT` request to path that includes a resource `id`. + +_Example: Update a route entry._ +```typescript +HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/route/urn:mrn:signalk:uuid:94052456-65fa-48ce-a85d-41b78a9d2111' {..} +``` + +The body of both `POST` and `PUT` requests should contain valid resource data for the specific resource type in the API path. + +Each resource type has a specific set of attributes that are required to be supplied before the resource entry can be created or updated. ___Note: When submitting data to create or update a resource entry, the submitted resource data is validated against the Signal K schema for that resource type. If the submitted data is deemed to be invalid then the operation is aborted.___ @@ -85,7 +99,7 @@ ___Additionally when supplying an id to assign to or identify the resource on wh --- #### __Routes:__ -To create / update a route entry the body of the PUT request must contain data in the following format: +To create / update a route entry the body of the request must contain data in the following format: ```javascript { name: 'route name', @@ -110,7 +124,7 @@ where: _Example: Create new route entry (with server generated id)_ ```typescript -HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/route' { +HTTP POST 'http://hostname:3000/signalk/v1/api/resources/set/route' { name: 'route name', description: 'description of the route', attributes: { @@ -145,7 +159,7 @@ HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/route/urn:mrn:signal --- #### __Waypoints:__ -To create / update a waypoint entry the body of the PUT request must contain data in the following format: +To create / update a waypoint entry the body of the request must contain data in the following format: ```javascript { name: 'waypoint name', @@ -168,7 +182,7 @@ where: _Example: Create new waypoint entry (with server generated id)_ ```typescript -HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/waypoint' { +HTTP POST 'http://hostname:3000/signalk/v1/api/resources/set/waypoint' { name: 'waypoint #1', position: { latitude: -38.567, @@ -191,7 +205,7 @@ HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/waypoint/urn:mrn:sig --- #### __Regions:__ -To create / update a region entry the body of the PUT request must contain data in the following format: +To create / update a region entry the body of the request must contain data in the following format: ```javascript { name: 'region name', @@ -217,7 +231,7 @@ where: _Example: Create new region entry (with server generated id)_ ```typescript -HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/region' { +HTTP POST 'http://hostname:3000/signalk/v1/api/resources/set/region' { name: 'region name', description: 'description of the region', points: [ @@ -248,7 +262,7 @@ HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/region/urn:mrn:signa --- #### __Notes:__ -To create / update a note entry the body of the PUT request must contain data in the following format: +To create / update a note entry the body of the request must contain data in the following format: ```javascript { title: 'note title text', @@ -283,7 +297,7 @@ OR _Example: Create new note entry (with server generated id)_ ```typescript -HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/note' { +HTTP POST 'http://hostname:3000/signalk/v1/api/resources/set/note' { title: 'note title', description: 'text containing brief description', url: 'http:notehost.com/notes/mynote.html', diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 1ad9e6426..8091a79a1 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -162,7 +162,7 @@ export class Resources { ) // facilitate creation of new resource entry of supplied type - this.server.put( + this.server.post( `${SIGNALK_API_PATH}/resources/:resourceType`, async (req: Request, res: Response, next: NextFunction) => { debug(`** POST ${SIGNALK_API_PATH}/resources/:resourceType`) @@ -336,7 +336,7 @@ export class Resources { ) // facilitate API requests - this.server.put( + this.server.post( `${SIGNALK_API_PATH}/resources/set/:resourceType`, async (req: Request, res: Response) => { debug(`** PUT ${SIGNALK_API_PATH}/resources/set/:resourceType`) @@ -369,7 +369,7 @@ export class Resources { } try { - const retVal = await this.resProvider[apiData.type]?.setResource( + await this.resProvider[apiData.type]?.setResource( apiData.type, apiData.id, apiData.value @@ -419,7 +419,7 @@ export class Resources { } try { - const retVal = await this.resProvider[apiData.type]?.setResource( + await this.resProvider[apiData.type]?.setResource( apiData.type, apiData.id, apiData.value diff --git a/src/api/resources/openApi.json b/src/api/resources/openApi.json index 6d6a08918..7bc1abc8a 100644 --- a/src/api/resources/openApi.json +++ b/src/api/resources/openApi.json @@ -105,7 +105,7 @@ }, "/resources/routes/": { - "put": { + "post": { "tags": ["resources/routes"], "summary": "Add a new Route", "requestBody": { @@ -339,7 +339,7 @@ }, "/resources/waypoints/": { - "put": { + "post": { "tags": ["resources/waypoints"], "summary": "Add a new Waypoint", "requestBody": { @@ -573,7 +573,7 @@ }, "/resources/notes/": { - "put": { + "post": { "tags": ["resources/notes"], "summary": "Add a new Note", "requestBody": { @@ -785,7 +785,7 @@ }, "/resources/regions/": { - "put": { + "post": { "tags": ["resources/regions"], "summary": "Add a new Region", "requestBody": { @@ -1057,7 +1057,7 @@ }, "/resources/charts/": { - "put": { + "post": { "tags": ["resources/charts"], "summary": "Add a new Chart", "requestBody": { @@ -1358,6 +1358,72 @@ }, + "/resources/set/waypoint": { + "post": { + "tags": ["resources/api"], + "summary": "Add / update a Waypoint", + "requestBody": { + "description": "Waypoint attributes", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["position"], + "properties": { + "name": { + "type": "string", + "description": "Waypoint name" + }, + "description": { + "type": "string", + "description": "Textual description of the waypoint" + }, + "attributes": { + "type": "object", + "description": "Additional attributes as name:value pairs.", + "additionalProperties": { + "type": "string" + } + }, + "position": { + "description": "The waypoint position", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/resources/set/waypoint/{id}": { "put": { "tags": ["resources/api"], @@ -1424,6 +1490,75 @@ } }, + "/resources/set/route": { + "post": { + "tags": ["resources/api"], + "summary": "Add / update a Route", + "requestBody": { + "description": "Note attributes", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["position"], + "properties": { + "name": { + "type": "string", + "description": "Route name" + }, + "description": { + "type": "string", + "description": "Textual description of the route" + }, + "attributes": { + "type": "object", + "description": "Additional attributes as name:value pairs.", + "additionalProperties": { + "type": "string" + } + }, + "points": { + "description": "Route points", + "type": "array", + "items": { + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + } + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/resources/set/route/{id}": { "put": { "tags": ["resources/api"], @@ -1493,6 +1628,82 @@ } }, + "/resources/set/note": { + "post": { + "tags": ["resources/api"], + "summary": "Add / update a Note", + "requestBody": { + "description": "Note attributes", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["title"], + "properties": { + "title": { + "type": "string", + "description": "Note's common name" + }, + "description": { + "type": "string", + "description": " Textual description of the note" + }, + "oneOf":[ + { + "position": { + "description": "Position related to note. Alternative to region or geohash", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + } + }, + "region": { + "type": "string", + "description": "Region related to note. A pointer to a region UUID. Alternative to position or geohash", + "example": "/resources/regions/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a" + } + } + ], + "mimeType": { + "type": "string", + "description": "MIME type of the note" + }, + "url": { + "type": "string", + "description": "Location of the note contents" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/resources/set/note/{id}": { "put": { "tags": ["resources/api"], @@ -1569,6 +1780,75 @@ } }, + "/resources/set/region": { + "post": { + "tags": ["resources/api"], + "summary": "Add / update a Region", + "requestBody": { + "description": "Region attributes", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["position"], + "properties": { + "name": { + "type": "string", + "description": "Region name" + }, + "description": { + "type": "string", + "description": "Textual description of region" + }, + "attributes": { + "type": "object", + "description": "Additional attributes as name:value pairs.", + "additionalProperties": { + "type": "string" + } + }, + "points": { + "description": "Region boundary points", + "type": "array", + "items": { + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + } + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/resources/set/region/{id}": { "put": { "tags": ["resources/api"], @@ -1638,6 +1918,109 @@ } }, + "/resources/set/chart": { + "post": { + "tags": ["resources/api"], + "summary": "Add / update a Chart", + "requestBody": { + "description": "Chart attributes", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Chart resource", + "required": ["identifier", "tilemapUrl"], + "properties": { + "identifier": { + "type": "string", + "description": "Chart number", + "example":"NZ615" + }, + "name": { + "description": "Chart common name", + "example":"NZ615 Marlborough Sounds", + "type": "string" + }, + "description": { + "type": "string", + "description": "A description of the chart" + }, + "tilemapUrl": { + "type": "string", + "description": "A url to the tilemap of the chart for use in TMS chartplotting apps", + "example":"http://{server}:8080/mapcache/NZ615" + }, + "scale": { + "type": "integer", + "description": "The scale of the chart, the larger number from 1:200000" + }, + "layers": { + "type": "array", + "description": "If the chart format is WMS, the layers enabled for the chart.", + "items": { + "type": "string", + "description": "Identifier for the layer." + } + }, + "bounds": { + "type": "array", + "description": "The bounding rectangle of the chart defined by the position of the lower left corner, and the upper right corner.", + "items": { + "description": "Position of a corner of the chart", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + } + } + }, + "format": { + "type": "string", + "description": "The format of the chart", + "enum": [ + "png", + "jpg" + ] + }, + "serverType": { + "type": "string", + "description": "Chart server type", + "enum": [ + "tilelayer", + "WMS" + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/resources/set/chart/{id}": { "put": { "tags": ["resources/api"], @@ -1739,7 +2122,7 @@ } } } - } + } } From ba4bb11c505e1d1cd7cb8d0ded51a38bf961c8af Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 4 Dec 2021 17:12:14 +1030 Subject: [PATCH 113/410] init courseApi --- src/api/course/index.ts | 562 ++++++++++++++++++++++++++++++++++++ src/api/course/openApi.json | 413 ++++++++++++++++++++++++++ src/index.ts | 2 + 3 files changed, 977 insertions(+) create mode 100644 src/api/course/index.ts create mode 100644 src/api/course/openApi.json diff --git a/src/api/course/index.ts b/src/api/course/index.ts new file mode 100644 index 000000000..a8d42a1a8 --- /dev/null +++ b/src/api/course/index.ts @@ -0,0 +1,562 @@ +import { + ResourceProvider, + ResourceProviderMethods, + SignalKResourceType +} from '@signalk/server-api' +import Debug from 'debug' +import { Application, Request, Response } from 'express' +import { v4 as uuidv4 } from 'uuid' + +const debug = Debug('signalk:courseApi') + +const SIGNALK_API_PATH: string = `/signalk/v1/api` +const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' + +const API_METHODS: string[] = [] + +interface CourseApplication extends Application { + handleMessage: (id: string, data: any) => void + getSelfPath: (path: string) => any + resourcesApi: { + getResource: (resourceType: string, resourceId: string) => any + } +} + +interface DestinationBase { + href?: string + arrivalCircle?: number +} +interface Destination extends DestinationBase { + position?: { + latitude: number + longitude: number + altitude?: number + } + type?: string +} + +interface ActiveRoute extends DestinationBase { + pointIndex?: number + reverse?: boolean +} + +interface Position { + latitude: number + longitude: number + altitude?: number +} + +interface CourseInfo { + activeRoute: { + href: string | null + startTime: string | null + pointIndex: number + reverse: boolean + } + nextPoint: { + href: string | null + type: string | null + position: Position | null + arrivalCircle: number + } + previousPoint: { + href: string | null + type: string | null + position: Position | null + } +} + +export class CourseApi { + private server: CourseApplication + + private courseInfo: CourseInfo = { + activeRoute: { + href: null, + startTime: null, + pointIndex: 0, + reverse: false + }, + nextPoint: { + href: null, + type: null, + position: null, + arrivalCircle: 0 + }, + previousPoint: { + href: null, + type: null, + position: null + } + } + + constructor(app: CourseApplication) { + this.server = app + this.start(app) + } + + private start(app: any) { + debug(`** Initialise ${COURSE_API_PATH} path handler **`) + this.server = app + this.initResourceRoutes() + } + + private initResourceRoutes() { + // return current course information + this.server.get( + `${COURSE_API_PATH}`, + async (req: Request, res: Response) => { + debug(`** GET ${COURSE_API_PATH}`) + res.json(this.courseInfo) + } + ) + + // restart / arrivalCircle + this.server.put( + `${COURSE_API_PATH}/:action`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/:action`) + if (req.params.restart) { + // set previousPoint to vessel position + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.emitCourseInfo() + res.status(200).send(`Course restarted.`) + } else { + res.status(406).send(`Vessel position unavailable!`) + } + } + if (req.params.arrivalCircle) { + if (this.setArrivalCircle(req.params.arrivalCircle)) { + this.emitCourseInfo() + res.status(200).send(`Destination set successfully.`) + } else { + res.status(406).send(`Invalid Data`) + } + } + } + ) + + // set destination + this.server.put( + `${COURSE_API_PATH}/destination`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/destination`) + + if (!req.body.value) { + res.status(406).send(`Invalid Data`) + return + } + const result = await this.setDestination(req.body.value) + if (result) { + this.emitCourseInfo() + res.status(200).send(`Destination set successfully.`) + } else { + this.clearDestination() + this.emitCourseInfo() + res.status(406).send(`Invalid Data`) + } + } + ) + + // clear destination + this.server.delete( + `${COURSE_API_PATH}/destination`, + async (req: Request, res: Response) => { + debug(`** DELETE ${COURSE_API_PATH}/destination`) + this.clearDestination() + this.emitCourseInfo() + res.status(200).send(`Destination cleared.`) + } + ) + + // set / clear activeRoute + this.server.put( + `${COURSE_API_PATH}/activeRoute`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/activeRoute`) + + const result = await this.activateRoute(req.body.value) + if (result) { + this.emitCourseInfo() + res.status(200).send(`Active route set.`) + } else { + this.clearDestination() + this.emitCourseInfo() + res.status(406).send(`Invalid Data`) + } + } + ) + this.server.delete( + `${COURSE_API_PATH}/activeRoute`, + async (req: Request, res: Response) => { + debug(`** DELETE ${COURSE_API_PATH}/activeRoute`) + + this.clearDestination() + this.emitCourseInfo() + res.status(200).send(`Active route cleared.`) + } + ) + + this.server.put( + `${COURSE_API_PATH}/activeRoute/:action`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/activeRoute`) + + if (!this.courseInfo.activeRoute.href) { + res.status(406).send(`Invalid Data`) + return + } + + const rte = await this.getRoute(this.courseInfo.activeRoute.href) + if (!rte) { + res.status(406).send(`Invalid Data`) + return + } + + if (req.params.nextPoint) { + if ( + typeof req.body.value === 'number' && + (req.body.value === 1 || req.body.value === -1) + ) { + this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( + this.courseInfo.activeRoute.pointIndex + req.body.value, + rte + ) + } else { + res.status(406).send(`Invalid Data`) + } + } + if (req.params.pointIndex) { + if (typeof req.body.value === 'number') { + this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( + req.body.value, + rte + ) + } else { + res.status(406).send(`Invalid Data`) + } + } + + // set nextPoint + this.courseInfo.nextPoint.position = this.getRoutePoint( + rte, + this.courseInfo.activeRoute.pointIndex + ) + this.courseInfo.nextPoint.type = `RoutePoint` + this.courseInfo.nextPoint.href = null + + // set previousPoint + if (this.courseInfo.activeRoute.pointIndex === 0) { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } else { + this.courseInfo.previousPoint.position = this.getRoutePoint( + rte, + this.courseInfo.activeRoute.pointIndex - 1 + ) + this.courseInfo.previousPoint.type = `RoutePoint` + } + this.courseInfo.previousPoint.href = null + + res.status(200).send(`OK`) + } + ) + } + + private async activateRoute(route: ActiveRoute): Promise { + let rte: any + + if (route.href) { + rte = await this.getRoute(route.href) + if (!rte) { + return false + } + if (!Array.isArray(rte.feature?.geometry?.coordinates)) { + return false + } + } else { + return false + } + + // set activeroute + this.courseInfo.activeRoute.href = route.href + + if (typeof route.arrivalCircle === 'number') { + this.setArrivalCircle(route.arrivalCircle) + } + + this.courseInfo.activeRoute.reverse = + typeof route.reverse === 'boolean' ? route.reverse : false + + this.courseInfo.activeRoute.pointIndex = + typeof route.pointIndex === 'number' + ? this.parsePointIndex(route.pointIndex, rte) + : 0 + + this.courseInfo.activeRoute.startTime = new Date().toISOString() + + // set nextPoint + this.courseInfo.nextPoint.position = this.getRoutePoint( + rte, + this.courseInfo.activeRoute.pointIndex + ) + this.courseInfo.nextPoint.type = `RoutePoint` + this.courseInfo.nextPoint.href = null + + // set previousPoint + if (this.courseInfo.activeRoute.pointIndex === 0) { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } else { + this.courseInfo.previousPoint.position = this.getRoutePoint( + rte, + this.courseInfo.activeRoute.pointIndex - 1 + ) + this.courseInfo.previousPoint.type = `RoutePoint` + } + this.courseInfo.previousPoint.href = null + + return true + } + + private async setDestination(dest: Destination): Promise { + // set nextPoint + if (typeof dest.arrivalCircle === 'number') { + this.setArrivalCircle(dest.arrivalCircle) + } + + this.courseInfo.nextPoint.type = + typeof dest.type !== 'undefined' ? dest.type : null + + if (dest.href) { + this.courseInfo.nextPoint.href = dest.href + const href = this.parseHref(dest.href) + if (href) { + try { + const r = await this.server.resourcesApi.getResource( + href.type, + href.id + ) + if (r.position && typeof r.position.value?.latitude !== 'undefined') { + this.courseInfo.nextPoint.position = r.position.value + } + } catch (err) { + return false + } + } + } else if (dest.position) { + this.courseInfo.nextPoint.href = null + if (typeof dest.position.latitude !== 'undefined') { + this.courseInfo.nextPoint.position = dest.position + } else { + return false + } + } else { + return false + } + + // set previousPoint + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + this.courseInfo.previousPoint.href = null + + return true + } + + private clearDestination() { + this.courseInfo.activeRoute.href = null + this.courseInfo.activeRoute.startTime = null + this.courseInfo.activeRoute.pointIndex = 0 + this.courseInfo.activeRoute.reverse = false + this.courseInfo.nextPoint.href = null + this.courseInfo.nextPoint.type = null + this.courseInfo.nextPoint.position = null + this.courseInfo.previousPoint.href = null + this.courseInfo.previousPoint.type = null + this.courseInfo.previousPoint.position = null + } + + private setArrivalCircle(value: any): boolean { + if (typeof value === 'number' && value >= 0) { + this.courseInfo.nextPoint.arrivalCircle = value + return true + } else { + return false + } + } + + private parsePointIndex(index: number, rte: any): number { + if (!rte) { + return 0 + } + if (!rte.feature?.geometry?.coordinates) { + return 0 + } + if (!Array.isArray(rte.feature?.geometry?.coordinates)) { + return 0 + } + if (index < 0) { + return 0 + } + if (index > rte.feature?.geometry?.coordinates.length - 1) { + return rte.feature?.geometry?.coordinates.length + } + return index + } + + private parseHref(href: string): { type: string; id: string } | undefined { + if (href.length === 0) { + return undefined + } + if (href[0] === '/') { + href = href.slice(1) + } + const ref: string[] = href.split('/') + if (ref.length < 3) { + return undefined + } + if (ref[0] !== 'resources') { + return undefined + } + return { + type: ref[1], + id: ref[2] + } + } + + private getRoutePoint(rte: any, index: number) { + const pos = this.courseInfo.activeRoute.reverse + ? rte.feature.geometry.coordinates[ + rte.feature.geometry.coordinates.length - (index + 1) + ] + : rte.feature.geometry.coordinates[index] + return { + latitude: pos[1], + longitude: pos[0], + altitude: pos.length == 3 ? pos[2] : 0 + } + } + + private async getRoute(href: string): Promise { + const h = this.parseHref(href) + if (h) { + try { + return await this.server.resourcesApi.getResource(h.type, h.id) + } catch (err) { + return undefined + } + } else { + return undefined + } + } + + private buildDeltaMsg(): any { + let values: Array<{path:string, value:any}> = [] + let root = [ + 'navigation.courseGreatCircle', + 'navigation.courseRhumbline' + ] + + values.push({ + path: `${root[0]}.activeRoute.href`, + value: this.courseInfo.activeRoute.href + }) + values.push({ + path: `${root[1]}.activeRoute.href`, + value: this.courseInfo.activeRoute.href + }) + values.push({ + path: `${root[0]}.activeRoute.startTime`, + value: this.courseInfo.activeRoute.startTime + }) + values.push({ + path: `${root[1]}.activeRoute.startTime`, + value: this.courseInfo.activeRoute.startTime + }) + values.push({ + path: `${root[0]}.nextPoint.href`, + value: this.courseInfo.nextPoint.href + }) + values.push({ + path: `${root[1]}.nextPoint.href`, + value: this.courseInfo.nextPoint.href + }) + values.push({ + path: `${root[0]}.nextPoint.position`, + value: this.courseInfo.nextPoint.position + }) + values.push({ + path: `${root[1]}.nextPoint.position`, + value: this.courseInfo.nextPoint.position + }) + values.push({ + path: `${root[0]}.nextPoint.type`, + value: this.courseInfo.nextPoint.type + }) + values.push({ + path: `${root[1]}.nextPoint.type`, + value: this.courseInfo.nextPoint.type + }) + values.push({ + path: `${root[0]}.nextPoint.arrivalCircle`, + value: this.courseInfo.nextPoint.arrivalCircle + }) + values.push({ + path: `${root[1]}.nextPoint.arrivalCircle`, + value: this.courseInfo.nextPoint.arrivalCircle + }) + values.push({ + path: `${root[0]}.previousPoint.href`, + value: this.courseInfo.previousPoint.href + }) + values.push({ + path: `${root[1]}.previousPoint.href`, + value: this.courseInfo.previousPoint.href + }) + values.push({ + path: `${root[0]}.previousPoint.position`, + value: this.courseInfo.previousPoint.position + }) + values.push({ + path: `${root[1]}.previousPoint.position`, + value: this.courseInfo.previousPoint.position + }) + values.push({ + path: `${root[0]}.previousPoint.type`, + value: this.courseInfo.previousPoint.type + }) + values.push({ + path: `${root[1]}.previousPoint.type`, + value: this.courseInfo.previousPoint.type + }) + + return { + updates: [ + { + values: values + } + ] + } + } + + private emitCourseInfo() { + this.server.handleMessage('courseApi', this.buildDeltaMsg()) + } +} diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json new file mode 100644 index 000000000..04653db5e --- /dev/null +++ b/src/api/course/openApi.json @@ -0,0 +1,413 @@ +{ + "openapi": "3.0.2", + "info": { + "version": "1.0.0", + "title": "Signal K Course API" + }, + + "paths": { + + "/vessels/self/navigation/course/": { + "get": { + "tags": ["course"], + "summary": "Get course information", + "responses": { + "default": { + "description": "Course data", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "activeRoute": { + "type": "object", + "properties": { + "href": { + "type": "string", + "example": "/resources/routes/urn:mrn:signalk:uuid:0d95e282-3e1f-4521-8c30-8288addbdb69" + }, + "startTime": { + "type": "string", + "example": "2021-10-23T05:17:20.065Z" + }, + "pointIndex": { + "type": "number", + "format": "int64", + "example": 2 + }, + "reverse": { + "type": "boolean", + "default": false + } + } + }, + "nextPoint": { + "type": "object", + "properties": { + "href": { + "type": "string", + "example": "/resources/waypoints/urn:mrn:signalk:uuid:0d95e282-3e1f-4521-8c30-8288addbdbab" + }, + "type": { + "type": "string", + "example": "RoutePoint" + }, + "position": { + "type": "object", + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + }, + "example": {"latitude":-29.5,"longitude":137.5} + } + } + }, + "previousPoint": { + "type": "object", + "properties": { + "href": { + "type": "string", + "example": null + }, + "type": { + "type": "string", + "example": "Location" + }, + "position": { + "type": "object", + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + }, + "example": {"longitude":29.821001582434413,"latitude":70.7014589462524} + } + } + } + } + } + } + } + } + } + } + }, + + "/vessels/self/navigation/course/restart": { + "put": { + "tags": ["course"], + "summary": "Restart course calculations", + "description": "Sets previousPoint value to current vessel location", + "requestBody": { + "description": "Restart payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + } + }, + + "/vessels/self/navigation/course/destination": { + "put": { + "tags": ["course/destination"], + "summary": "Set destination", + "description": "Set destination path from supplied details", + "requestBody": { + "description": "Destination details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "properties": { + "position": { + "type": "object", + "description": "Destination position", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + }, + "example": {"latitude":-29.5,"longitude":137.5} + }, + "href": { + "type": "string", + "description": "A reference (URL) to an object (under /resources) this point is related to", + "example": "/resources/waypoints/urn:mrn:signalk:uuid:3dd34dcc-36bf-4d61-ba80-233799b25672" + }, + "type": { + "type": "string", + "description": "Type of point", + "example": "POI" + }, + "arrivalCircle": { + "type": "number", + "format": "float", + "example": 500 + } + } + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + }, + "delete": { + "tags": ["course/destination"], + "summary": "Clear destination", + "description": "Sets activeRoute, nextPoint & previousPoint values to null", + "requestBody": { + "description": "Delete payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + } + }, + + "/vessels/self/navigation/course/arrivalCircle": { + "put": { + "tags": ["course"], + "summary": "Set arrival circle radius (m)", + "description": "Sets an arrival circle radius (in meters)", + "requestBody": { + "description": "Arrival circle payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "number", + "format": "float", + "example": 500 + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + } + }, + + "/vessels/self/navigation/course/activeRoute": { + "put": { + "tags": ["course/activeRoute"], + "summary": "Set active route", + "description": "Sets activeRoute path and sets nextPoint to first point in the route", + "requestBody": { + "description": "Active route details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "properties": { + "href": { + "type": "string", + "description": "Path to route resource", + "example": "/resources/routes/urn:mrn:signalk:uuid:3dd34dcc-36bf-4d61-ba80-233799b25672" + }, + "pointIndex": { + "type": "number", + "format": "int64", + "description": "Zero based index of route point to use as destination", + "default": 0, + "minimum": 0, + "example": 2 + }, + "reverse": { + "type": "boolean", + "default": false, + "description": "If true performs operations on route points in reverse order", + "example": 2 + }, + "arrivalCircle": { + "type": "number", + "format": "float", + "example": 500 + } + } + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + }, + "delete": { + "tags": ["course/activeRoute"], + "summary": "Clear active route", + "description": "Sets activeRoute, nextPoint & previousPoint values to null", + "requestBody": { + "description": "Delete payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + } + }, + + "/vessels/self/navigation/course/activeRoute/nextPoint": { + "put": { + "tags": ["course/activeRoute"], + "summary": "Increment / decrement point in route as destination", + "description": "Increment / decrement point in the route as destination", + "requestBody": { + "description": "Increment / decrement", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "number", + "description": "increment (1) / decrement (-1) index of point in route to use as destination", + "enum": [-1,1], + "example": -1 + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + } + }, + + "/vessels/self/navigation/course/activeRoute/pointIndex": { + "put": { + "tags": ["course/activeRoute"], + "summary": "Set point in route as destination", + "description": "Sets the specified point in the route as destination", + "requestBody": { + "description": "Next point index", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "number", + "description": "Index of point in route to use as destination", + "minimum": 0, + "example": 2 + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + } + } + + } + +} + + + + + \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index c8104d1cc..c4709bed8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -51,6 +51,7 @@ import { startSecurity } from './security.js' +import { CourseApi } from './api/course' import { Resources } from './api/resources' // tslint:disable-next-line: no-var-requires @@ -84,6 +85,7 @@ class Server { require('./put').start(app) app.resourcesApi = new Resources(app) + const courseApi = new CourseApi(app) app.signalk = new FullSignalK(app.selfId, app.selfType) From a7d78bed224ca09df4e47de46cf978853d3d6fea Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 5 Dec 2021 10:26:47 +1030 Subject: [PATCH 114/410] update detlas --- src/api/course/index.ts | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index a8d42a1a8..7d186618c 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,23 +1,15 @@ -import { - ResourceProvider, - ResourceProviderMethods, - SignalKResourceType -} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' -import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' - -const API_METHODS: string[] = [] interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any + registerPutHandler: (context:string, path:string, cb:any) => any resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -35,7 +27,6 @@ interface Destination extends DestinationBase { } type?: string } - interface ActiveRoute extends DestinationBase { pointIndex?: number reverse?: boolean @@ -111,12 +102,26 @@ export class CourseApi { } ) + // + if(this.server.registerPutHandler) { + debug('** Registering PUT Action Handler(s) **') + this.server.registerPutHandler( + 'vessels.self', + 'navigation.course.*', + this.handleCourseApiPut + ); + } + // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { + //test for active destination + if (!this.courseInfo.nextPoint.position) { + return + } // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -270,6 +275,12 @@ export class CourseApi { ) } + private handleCourseApiPut(context:string, path:string, value:any, cb:any) { + + debug('** PUT handler **') + return undefined + } + private async activateRoute(route: ActiveRoute): Promise { let rte: any @@ -474,6 +485,11 @@ export class CourseApi { 'navigation.courseRhumbline' ] + values.push({ + path: `navigation.course`, + value: this.courseInfo + }) + values.push({ path: `${root[0]}.activeRoute.href`, value: this.courseInfo.activeRoute.href From 8313c65bc1d20ed27508881fab553eaf374f0df0 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 4 Dec 2021 17:12:14 +1030 Subject: [PATCH 115/410] init courseApi --- src/api/course/index.ts | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 7d186618c..d3d42fed7 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,15 +1,26 @@ + +import { + ResourceProvider, + ResourceProviderMethods, + SignalKResourceType +} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' +import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' + +const API_METHODS: string[] = [] + interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - registerPutHandler: (context:string, path:string, cb:any) => any + resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -102,26 +113,12 @@ export class CourseApi { } ) - // - if(this.server.registerPutHandler) { - debug('** Registering PUT Action Handler(s) **') - this.server.registerPutHandler( - 'vessels.self', - 'navigation.course.*', - this.handleCourseApiPut - ); - } - // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { - //test for active destination - if (!this.courseInfo.nextPoint.position) { - return - } // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -275,12 +272,6 @@ export class CourseApi { ) } - private handleCourseApiPut(context:string, path:string, value:any, cb:any) { - - debug('** PUT handler **') - return undefined - } - private async activateRoute(route: ActiveRoute): Promise { let rte: any From 9a90a5825a3d5ac878527c697c1744249c2da56a Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 5 Dec 2021 10:26:47 +1030 Subject: [PATCH 116/410] update detlas --- src/api/course/index.ts | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index d3d42fed7..7d186618c 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,26 +1,15 @@ - -import { - ResourceProvider, - ResourceProviderMethods, - SignalKResourceType -} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' -import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' - -const API_METHODS: string[] = [] - interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - + registerPutHandler: (context:string, path:string, cb:any) => any resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -113,12 +102,26 @@ export class CourseApi { } ) + // + if(this.server.registerPutHandler) { + debug('** Registering PUT Action Handler(s) **') + this.server.registerPutHandler( + 'vessels.self', + 'navigation.course.*', + this.handleCourseApiPut + ); + } + // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { + //test for active destination + if (!this.courseInfo.nextPoint.position) { + return + } // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -272,6 +275,12 @@ export class CourseApi { ) } + private handleCourseApiPut(context:string, path:string, value:any, cb:any) { + + debug('** PUT handler **') + return undefined + } + private async activateRoute(route: ActiveRoute): Promise { let rte: any From 0db86b15367eeb3cf75b37f64bdaecaa080c20d8 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 6 Dec 2021 08:39:56 +1030 Subject: [PATCH 117/410] enable put processing --- src/api/course/index.ts | 136 ++++++++++++++++++++++++---------------- src/put.js | 7 +++ 2 files changed, 89 insertions(+), 54 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 7d186618c..1566ecdf3 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -9,7 +9,14 @@ const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/cou interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - registerPutHandler: (context:string, path:string, cb:any) => any + securityStrategy: { + shouldAllowPut: ( + req: any, + context: string, + source: any, + path: string + ) => boolean + } resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -92,6 +99,15 @@ export class CourseApi { this.initResourceRoutes() } + private updateAllowed(): boolean { + return this.server.securityStrategy.shouldAllowPut( + this.server, + 'vessels.self', + null, + 'resources' + ) + } + private initResourceRoutes() { // return current course information this.server.get( @@ -102,43 +118,43 @@ export class CourseApi { } ) - // - if(this.server.registerPutHandler) { - debug('** Registering PUT Action Handler(s) **') - this.server.registerPutHandler( - 'vessels.self', - 'navigation.course.*', - this.handleCourseApiPut - ); - } + this.server.put( + `${COURSE_API_PATH}/restart`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/restart`) + if (!this.updateAllowed()) { + res.status(403) + return + } + if (!this.courseInfo.nextPoint.position) { + res.status(406).send(`No active destination!`) + return + } + // set previousPoint to vessel position + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.emitCourseInfo() + res.status(200) + } else { + res.status(406).send(`Vessel position unavailable!`) + } + } + ) - // restart / arrivalCircle this.server.put( - `${COURSE_API_PATH}/:action`, + `${COURSE_API_PATH}/arrivalCircle`, async (req: Request, res: Response) => { - debug(`** PUT ${COURSE_API_PATH}/:action`) - if (req.params.restart) { - //test for active destination - if (!this.courseInfo.nextPoint.position) { - return - } - // set previousPoint to vessel position - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.emitCourseInfo() - res.status(200).send(`Course restarted.`) - } else { - res.status(406).send(`Vessel position unavailable!`) - } + debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) + if (!this.updateAllowed()) { + res.status(403) + return } - if (req.params.arrivalCircle) { - if (this.setArrivalCircle(req.params.arrivalCircle)) { - this.emitCourseInfo() - res.status(200).send(`Destination set successfully.`) - } else { - res.status(406).send(`Invalid Data`) - } + if (this.setArrivalCircle(req.body.value)) { + this.emitCourseInfo() + res.status(200) + } else { + res.status(406).send(`Invalid Data`) } } ) @@ -148,7 +164,10 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) - + if (!this.updateAllowed()) { + res.status(403) + return + } if (!req.body.value) { res.status(406).send(`Invalid Data`) return @@ -156,7 +175,7 @@ export class CourseApi { const result = await this.setDestination(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send(`Destination set successfully.`) + res.status(200) } else { this.clearDestination() this.emitCourseInfo() @@ -170,22 +189,29 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) + if (!this.updateAllowed()) { + res.status(403) + return + } this.clearDestination() this.emitCourseInfo() - res.status(200).send(`Destination cleared.`) + res.status(200) } ) - // set / clear activeRoute + // set activeRoute this.server.put( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) - + if (!this.updateAllowed()) { + res.status(403) + return + } const result = await this.activateRoute(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send(`Active route set.`) + res.status(200) } else { this.clearDestination() this.emitCourseInfo() @@ -193,14 +219,19 @@ export class CourseApi { } } ) + + // clear activeRoute /destination this.server.delete( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/activeRoute`) - + if (!this.updateAllowed()) { + res.status(403) + return + } this.clearDestination() this.emitCourseInfo() - res.status(200).send(`Active route cleared.`) + res.status(200) } ) @@ -208,19 +239,22 @@ export class CourseApi { `${COURSE_API_PATH}/activeRoute/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) - + if (!this.updateAllowed()) { + res.status(403) + return + } + // fetch route data if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return } - const rte = await this.getRoute(this.courseInfo.activeRoute.href) if (!rte) { res.status(406).send(`Invalid Data`) return } - if (req.params.nextPoint) { + if (req.params.action === 'nextPoint') { if ( typeof req.body.value === 'number' && (req.body.value === 1 || req.body.value === -1) @@ -233,7 +267,7 @@ export class CourseApi { res.status(406).send(`Invalid Data`) } } - if (req.params.pointIndex) { + if (req.params.action === 'pointIndex') { if (typeof req.body.value === 'number') { this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( req.body.value, @@ -244,7 +278,7 @@ export class CourseApi { } } - // set nextPoint + // set new destination this.courseInfo.nextPoint.position = this.getRoutePoint( rte, this.courseInfo.activeRoute.pointIndex @@ -270,17 +304,11 @@ export class CourseApi { } this.courseInfo.previousPoint.href = null - res.status(200).send(`OK`) + res.status(200) } ) } - private handleCourseApiPut(context:string, path:string, value:any, cb:any) { - - debug('** PUT handler **') - return undefined - } - private async activateRoute(route: ActiveRoute): Promise { let rte: any diff --git a/src/put.js b/src/put.js index e6e14a965..c7cf42616 100644 --- a/src/put.js +++ b/src/put.js @@ -35,6 +35,13 @@ module.exports = { next() return } + + // ** ignore course paths ** + if (req.path.indexOf('/navigation/course') !== -1) { + next() + return + } + let path = String(req.path).replace(apiPathPrefix, '') const value = req.body From 510172e56856f38990d467ded2bf4c2a437f0b0c Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 6 Dec 2021 13:34:31 +1030 Subject: [PATCH 118/410] chore: lint --- src/api/course/index.ts | 211 ++++++++++++++++++++---------------- src/api/course/openApi.json | 72 +----------- 2 files changed, 120 insertions(+), 163 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 1566ecdf3..6cd3b80fd 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -11,7 +11,7 @@ interface CourseApplication extends Application { getSelfPath: (path: string) => any securityStrategy: { shouldAllowPut: ( - req: any, + req: Application, context: string, source: any, path: string @@ -104,7 +104,7 @@ export class CourseApi { this.server, 'vessels.self', null, - 'resources' + 'navigation.course' ) } @@ -131,12 +131,14 @@ export class CourseApi { return } // set previousPoint to vessel position - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.emitCourseInfo() - res.status(200) - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.emitCourseInfo() + res.status(200) + } + } catch (err) { res.status(406).send(`Vessel position unavailable!`) } } @@ -150,7 +152,8 @@ export class CourseApi { res.status(403) return } - if (this.setArrivalCircle(req.body.value)) { + if (this.isValidArrivalCircle(req.body.value)) { + this.courseInfo.nextPoint.arrivalCircle = req.body.value this.emitCourseInfo() res.status(200) } else { @@ -243,7 +246,7 @@ export class CourseApi { res.status(403) return } - // fetch route data + // fetch active route data if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return @@ -281,24 +284,30 @@ export class CourseApi { // set new destination this.courseInfo.nextPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex + this.courseInfo.activeRoute.pointIndex, + this.courseInfo.activeRoute.reverse ) this.courseInfo.nextPoint.type = `RoutePoint` this.courseInfo.nextPoint.href = null // set previousPoint if (this.courseInfo.activeRoute.pointIndex === 0) { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } catch (err) { return false } } else { this.courseInfo.previousPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex - 1 + this.courseInfo.activeRoute.pointIndex - 1, + this.courseInfo.activeRoute.reverse ) this.courseInfo.previousPoint.type = `RoutePoint` } @@ -324,81 +333,97 @@ export class CourseApi { return false } + const newDest: any = {} + Object.assign(newDest, this.courseInfo) + // set activeroute - this.courseInfo.activeRoute.href = route.href + newDest.activeRoute.href = route.href - if (typeof route.arrivalCircle === 'number') { - this.setArrivalCircle(route.arrivalCircle) + if (this.isValidArrivalCircle(route.arrivalCircle as number)) { + newDest.nextPoint.arrivalCircle = route.arrivalCircle } - this.courseInfo.activeRoute.reverse = - typeof route.reverse === 'boolean' ? route.reverse : false + newDest.activeRoute.startTime = new Date().toISOString() - this.courseInfo.activeRoute.pointIndex = - typeof route.pointIndex === 'number' - ? this.parsePointIndex(route.pointIndex, rte) - : 0 + if (typeof route.reverse === 'boolean') { + newDest.activeRoute.reverse = route.reverse + } - this.courseInfo.activeRoute.startTime = new Date().toISOString() + newDest.activeRoute.pointIndex = this.parsePointIndex( + route.pointIndex as number, + rte + ) // set nextPoint - this.courseInfo.nextPoint.position = this.getRoutePoint( + newDest.nextPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex + newDest.activeRoute.pointIndex, + newDest.activeRoute.reverse ) - this.courseInfo.nextPoint.type = `RoutePoint` - this.courseInfo.nextPoint.href = null + newDest.nextPoint.type = `RoutePoint` + newDest.nextPoint.href = null // set previousPoint - if (this.courseInfo.activeRoute.pointIndex === 0) { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + if (newDest.activeRoute.pointIndex === 0) { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } catch (err) { return false } } else { - this.courseInfo.previousPoint.position = this.getRoutePoint( + newDest.previousPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex - 1 + newDest.activeRoute.pointIndex - 1, + newDest.activeRoute.reverse ) - this.courseInfo.previousPoint.type = `RoutePoint` + newDest.previousPoint.type = `RoutePoint` } - this.courseInfo.previousPoint.href = null + newDest.previousPoint.href = null + this.courseInfo = newDest return true } - private async setDestination(dest: Destination): Promise { + private async setDestination(dest: any): Promise { + const newDest: any = {} + Object.assign(newDest, this.courseInfo) + // set nextPoint - if (typeof dest.arrivalCircle === 'number') { - this.setArrivalCircle(dest.arrivalCircle) + if (this.isValidArrivalCircle(dest.arrivalCircle)) { + newDest.nextPoint.arrivalCircle = dest.arrivalCircle } - this.courseInfo.nextPoint.type = - typeof dest.type !== 'undefined' ? dest.type : null + newDest.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - this.courseInfo.nextPoint.href = dest.href + newDest.href = dest.href const href = this.parseHref(dest.href) if (href) { + // fetch waypoint resource details try { const r = await this.server.resourcesApi.getResource( href.type, href.id ) - if (r.position && typeof r.position.value?.latitude !== 'undefined') { - this.courseInfo.nextPoint.position = r.position.value + if (r.position && typeof r.position?.latitude !== 'undefined') { + newDest.nextPoint.position = r.position + } else { + return false } } catch (err) { return false } } } else if (dest.position) { - this.courseInfo.nextPoint.href = null + newDest.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { - this.courseInfo.nextPoint.position = dest.position + newDest.nextPoint.position = dest.position } else { return false } @@ -407,15 +432,20 @@ export class CourseApi { } // set previousPoint - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + newDest.previousPoint.position = position.value + newDest.previousPoint.type = `VesselPosition` + } else { + return false + } + newDest.previousPoint.href = null + } catch (err) { return false } - this.courseInfo.previousPoint.href = null + this.courseInfo = newDest return true } @@ -432,17 +462,12 @@ export class CourseApi { this.courseInfo.previousPoint.position = null } - private setArrivalCircle(value: any): boolean { - if (typeof value === 'number' && value >= 0) { - this.courseInfo.nextPoint.arrivalCircle = value - return true - } else { - return false - } + private isValidArrivalCircle(value: number): boolean { + return typeof value === 'number' && value >= 0 } private parsePointIndex(index: number, rte: any): number { - if (!rte) { + if (typeof index !== 'number' || !rte) { return 0 } if (!rte.feature?.geometry?.coordinates) { @@ -461,13 +486,11 @@ export class CourseApi { } private parseHref(href: string): { type: string; id: string } | undefined { - if (href.length === 0) { + if (!href) { return undefined } - if (href[0] === '/') { - href = href.slice(1) - } - const ref: string[] = href.split('/') + + const ref: string[] = href.split('/').slice(-3) if (ref.length < 3) { return undefined } @@ -480,8 +503,8 @@ export class CourseApi { } } - private getRoutePoint(rte: any, index: number) { - const pos = this.courseInfo.activeRoute.reverse + private getRoutePoint(rte: any, index: number, reverse: boolean) { + const pos = reverse ? rte.feature.geometry.coordinates[ rte.feature.geometry.coordinates.length - (index + 1) ] @@ -489,7 +512,7 @@ export class CourseApi { return { latitude: pos[1], longitude: pos[0], - altitude: pos.length == 3 ? pos[2] : 0 + altitude: pos.length === 3 ? pos[2] : 0 } } @@ -507,8 +530,8 @@ export class CourseApi { } private buildDeltaMsg(): any { - let values: Array<{path:string, value:any}> = [] - let root = [ + const values: Array<{ path: string; value: any }> = [] + const navPath = [ 'navigation.courseGreatCircle', 'navigation.courseRhumbline' ] @@ -519,82 +542,82 @@ export class CourseApi { }) values.push({ - path: `${root[0]}.activeRoute.href`, + path: `${navPath[0]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${root[1]}.activeRoute.href`, + path: `${navPath[1]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${root[0]}.activeRoute.startTime`, + path: `${navPath[0]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${root[1]}.activeRoute.startTime`, + path: `${navPath[1]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${root[0]}.nextPoint.href`, + path: `${navPath[0]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${root[1]}.nextPoint.href`, + path: `${navPath[1]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${root[0]}.nextPoint.position`, + path: `${navPath[0]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${root[1]}.nextPoint.position`, + path: `${navPath[1]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${root[0]}.nextPoint.type`, + path: `${navPath[0]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${root[1]}.nextPoint.type`, + path: `${navPath[1]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${root[0]}.nextPoint.arrivalCircle`, + path: `${navPath[0]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${root[1]}.nextPoint.arrivalCircle`, + path: `${navPath[1]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${root[0]}.previousPoint.href`, + path: `${navPath[0]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${root[1]}.previousPoint.href`, + path: `${navPath[1]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${root[0]}.previousPoint.position`, + path: `${navPath[0]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${root[1]}.previousPoint.position`, + path: `${navPath[1]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${root[0]}.previousPoint.type`, + path: `${navPath[0]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) values.push({ - path: `${root[1]}.previousPoint.type`, + path: `${navPath[1]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) return { updates: [ { - values: values + values } ] } diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index 04653db5e..b162d1314 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -108,29 +108,7 @@ "put": { "tags": ["course"], "summary": "Restart course calculations", - "description": "Sets previousPoint value to current vessel location", - "requestBody": { - "description": "Restart payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } - } - } - } - } - } + "description": "Sets previousPoint value to current vessel location" } }, @@ -200,29 +178,7 @@ "delete": { "tags": ["course/destination"], "summary": "Clear destination", - "description": "Sets activeRoute, nextPoint & previousPoint values to null", - "requestBody": { - "description": "Delete payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } - } - } - } - } - } + "description": "Sets activeRoute, nextPoint & previousPoint values to null" } }, @@ -313,29 +269,7 @@ "delete": { "tags": ["course/activeRoute"], "summary": "Clear active route", - "description": "Sets activeRoute, nextPoint & previousPoint values to null", - "requestBody": { - "description": "Delete payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } - } - } - } - } - } + "description": "Sets activeRoute, nextPoint & previousPoint values to null" } }, From 31f5db4d637d623011502f3fe70f24ea87c827f3 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 8 Dec 2021 09:27:06 +1030 Subject: [PATCH 119/410] add 30sec delta interval --- src/api/course/index.ts | 100 ++++++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 45 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 6cd3b80fd..2aa57e8a6 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -6,6 +6,8 @@ const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const DELTA_INTERVAL: number = 30000 + interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any @@ -97,6 +99,11 @@ export class CourseApi { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app this.initResourceRoutes() + setInterval( ()=> { + if(this.courseInfo.nextPoint.position) { + this.emitCourseInfo() + } + }, DELTA_INTERVAL) } private updateAllowed(): boolean { @@ -123,7 +130,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/restart`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (!this.courseInfo.nextPoint.position) { @@ -136,7 +143,7 @@ export class CourseApi { if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } } catch (err) { res.status(406).send(`Vessel position unavailable!`) @@ -149,13 +156,13 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (this.isValidArrivalCircle(req.body.value)) { this.courseInfo.nextPoint.arrivalCircle = req.body.value this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { res.status(406).send(`Invalid Data`) } @@ -168,7 +175,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (!req.body.value) { @@ -178,7 +185,7 @@ export class CourseApi { const result = await this.setDestination(req.body.value) if (result) { this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { this.clearDestination() this.emitCourseInfo() @@ -193,12 +200,12 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } this.clearDestination() this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } ) @@ -208,13 +215,13 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } const result = await this.activateRoute(req.body.value) if (result) { this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { this.clearDestination() this.emitCourseInfo() @@ -234,7 +241,7 @@ export class CourseApi { } this.clearDestination() this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } ) @@ -243,7 +250,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } // fetch active route data @@ -268,6 +275,7 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) + return } } if (req.params.action === 'pointIndex') { @@ -278,6 +286,7 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) + return } } @@ -298,9 +307,11 @@ export class CourseApi { this.courseInfo.previousPoint.position = position.value this.courseInfo.previousPoint.type = `VesselPosition` } else { + res.status(406).send(`Invalid Data`) return false } } catch (err) { + res.status(406).send(`Invalid Data`) return false } } else { @@ -312,8 +323,7 @@ export class CourseApi { this.courseInfo.previousPoint.type = `RoutePoint` } this.courseInfo.previousPoint.href = null - - res.status(200) + res.status(200).send('OK') } ) } @@ -333,38 +343,38 @@ export class CourseApi { return false } - const newDest: any = {} - Object.assign(newDest, this.courseInfo) + const newCourse: any = {} + Object.assign(newCourse, this.courseInfo) // set activeroute - newDest.activeRoute.href = route.href + newCourse.activeRoute.href = route.href if (this.isValidArrivalCircle(route.arrivalCircle as number)) { - newDest.nextPoint.arrivalCircle = route.arrivalCircle + newCourse.nextPoint.arrivalCircle = route.arrivalCircle } - newDest.activeRoute.startTime = new Date().toISOString() + newCourse.activeRoute.startTime = new Date().toISOString() if (typeof route.reverse === 'boolean') { - newDest.activeRoute.reverse = route.reverse + newCourse.activeRoute.reverse = route.reverse } - newDest.activeRoute.pointIndex = this.parsePointIndex( + newCourse.activeRoute.pointIndex = this.parsePointIndex( route.pointIndex as number, rte ) // set nextPoint - newDest.nextPoint.position = this.getRoutePoint( + newCourse.nextPoint.position = this.getRoutePoint( rte, - newDest.activeRoute.pointIndex, - newDest.activeRoute.reverse + newCourse.activeRoute.pointIndex, + newCourse.activeRoute.reverse ) - newDest.nextPoint.type = `RoutePoint` - newDest.nextPoint.href = null + newCourse.nextPoint.type = `RoutePoint` + newCourse.nextPoint.href = null // set previousPoint - if (newDest.activeRoute.pointIndex === 0) { + if (newCourse.activeRoute.pointIndex === 0) { try { const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -377,32 +387,32 @@ export class CourseApi { return false } } else { - newDest.previousPoint.position = this.getRoutePoint( + newCourse.previousPoint.position = this.getRoutePoint( rte, - newDest.activeRoute.pointIndex - 1, - newDest.activeRoute.reverse + newCourse.activeRoute.pointIndex - 1, + newCourse.activeRoute.reverse ) - newDest.previousPoint.type = `RoutePoint` + newCourse.previousPoint.type = `RoutePoint` } - newDest.previousPoint.href = null + newCourse.previousPoint.href = null - this.courseInfo = newDest + this.courseInfo = newCourse return true } private async setDestination(dest: any): Promise { - const newDest: any = {} - Object.assign(newDest, this.courseInfo) + const newCourse: any = {} + Object.assign(newCourse, this.courseInfo) // set nextPoint if (this.isValidArrivalCircle(dest.arrivalCircle)) { - newDest.nextPoint.arrivalCircle = dest.arrivalCircle + newCourse.nextPoint.arrivalCircle = dest.arrivalCircle } - newDest.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null + newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - newDest.href = dest.href + newCourse.href = dest.href const href = this.parseHref(dest.href) if (href) { // fetch waypoint resource details @@ -412,7 +422,7 @@ export class CourseApi { href.id ) if (r.position && typeof r.position?.latitude !== 'undefined') { - newDest.nextPoint.position = r.position + newCourse.nextPoint.position = r.position } else { return false } @@ -421,9 +431,9 @@ export class CourseApi { } } } else if (dest.position) { - newDest.nextPoint.href = null + newCourse.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { - newDest.nextPoint.position = dest.position + newCourse.nextPoint.position = dest.position } else { return false } @@ -435,17 +445,17 @@ export class CourseApi { try { const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { - newDest.previousPoint.position = position.value - newDest.previousPoint.type = `VesselPosition` + newCourse.previousPoint.position = position.value + newCourse.previousPoint.type = `VesselPosition` } else { return false } - newDest.previousPoint.href = null + newCourse.previousPoint.href = null } catch (err) { return false } - this.courseInfo = newDest + this.courseInfo = newCourse return true } From 70eec182aebbb7970effd66412615fc1d87c3753 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 12 Dec 2021 14:29:38 +1030 Subject: [PATCH 120/410] chore: lint --- src/api/course/index.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 2aa57e8a6..1e719297a 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -6,7 +6,7 @@ const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const DELTA_INTERVAL: number = 30000 +const DELTA_INTERVAL: number = 30000 interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void @@ -99,8 +99,8 @@ export class CourseApi { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app this.initResourceRoutes() - setInterval( ()=> { - if(this.courseInfo.nextPoint.position) { + setInterval(() => { + if (this.courseInfo.nextPoint.position) { this.emitCourseInfo() } }, DELTA_INTERVAL) @@ -409,7 +409,8 @@ export class CourseApi { newCourse.nextPoint.arrivalCircle = dest.arrivalCircle } - newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null + newCourse.nextPoint.type = + typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { newCourse.href = dest.href From a18d150cf5b10922ead49c0b0a0dc9dbd51bf330 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 4 Dec 2021 17:12:14 +1030 Subject: [PATCH 121/410] init courseApi --- src/api/course/index.ts | 318 +++++++++++++----------------------- src/api/course/openApi.json | 72 +++++++- 2 files changed, 187 insertions(+), 203 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 1e719297a..6e8e4074d 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,24 +1,23 @@ +import { + ResourceProvider, + ResourceProviderMethods, + SignalKResourceType +} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' +import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' -const DELTA_INTERVAL: number = 30000 +const API_METHODS: string[] = [] interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - securityStrategy: { - shouldAllowPut: ( - req: Application, - context: string, - source: any, - path: string - ) => boolean - } resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -99,20 +98,6 @@ export class CourseApi { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app this.initResourceRoutes() - setInterval(() => { - if (this.courseInfo.nextPoint.position) { - this.emitCourseInfo() - } - }, DELTA_INTERVAL) - } - - private updateAllowed(): boolean { - return this.server.securityStrategy.shouldAllowPut( - this.server, - 'vessels.self', - null, - 'navigation.course' - ) } private initResourceRoutes() { @@ -125,46 +110,29 @@ export class CourseApi { } ) + // restart / arrivalCircle this.server.put( - `${COURSE_API_PATH}/restart`, + `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { - debug(`** PUT ${COURSE_API_PATH}/restart`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } - if (!this.courseInfo.nextPoint.position) { - res.status(406).send(`No active destination!`) - return - } - // set previousPoint to vessel position - try { + debug(`** PUT ${COURSE_API_PATH}/:action`) + if (req.params.restart) { + // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).send(`Course restarted.`) + } else { + res.status(406).send(`Vessel position unavailable!`) } - } catch (err) { - res.status(406).send(`Vessel position unavailable!`) - } - } - ) - - this.server.put( - `${COURSE_API_PATH}/arrivalCircle`, - async (req: Request, res: Response) => { - debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return } - if (this.isValidArrivalCircle(req.body.value)) { - this.courseInfo.nextPoint.arrivalCircle = req.body.value - this.emitCourseInfo() - res.status(200).send('OK') - } else { - res.status(406).send(`Invalid Data`) + if (req.params.arrivalCircle) { + if (this.setArrivalCircle(req.params.arrivalCircle)) { + this.emitCourseInfo() + res.status(200).send(`Destination set successfully.`) + } else { + res.status(406).send(`Invalid Data`) + } } } ) @@ -174,10 +142,6 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } if (!req.body.value) { res.status(406).send(`Invalid Data`) return @@ -185,7 +149,7 @@ export class CourseApi { const result = await this.setDestination(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).send(`Destination set successfully.`) } else { this.clearDestination() this.emitCourseInfo() @@ -199,29 +163,22 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } this.clearDestination() this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).send(`Destination cleared.`) } ) - // set activeRoute + // set / clear activeRoute this.server.put( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } + const result = await this.activateRoute(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).send(`Active route set.`) } else { this.clearDestination() this.emitCourseInfo() @@ -229,19 +186,14 @@ export class CourseApi { } } ) - - // clear activeRoute /destination this.server.delete( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/activeRoute`) - if (!this.updateAllowed()) { - res.status(403) - return - } + this.clearDestination() this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).send(`Active route cleared.`) } ) @@ -249,11 +201,6 @@ export class CourseApi { `${COURSE_API_PATH}/activeRoute/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } - // fetch active route data if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return @@ -264,7 +211,7 @@ export class CourseApi { return } - if (req.params.action === 'nextPoint') { + if (req.params.nextPoint) { if ( typeof req.body.value === 'number' && (req.body.value === 1 || req.body.value === -1) @@ -275,10 +222,9 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) - return } } - if (req.params.action === 'pointIndex') { + if (req.params.pointIndex) { if (typeof req.body.value === 'number') { this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( req.body.value, @@ -286,44 +232,36 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) - return } } - // set new destination + // set nextPoint this.courseInfo.nextPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex, - this.courseInfo.activeRoute.reverse + this.courseInfo.activeRoute.pointIndex ) this.courseInfo.nextPoint.type = `RoutePoint` this.courseInfo.nextPoint.href = null // set previousPoint if (this.courseInfo.activeRoute.pointIndex === 0) { - try { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { - res.status(406).send(`Invalid Data`) - return false - } - } catch (err) { - res.status(406).send(`Invalid Data`) + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { return false } } else { this.courseInfo.previousPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex - 1, - this.courseInfo.activeRoute.reverse + this.courseInfo.activeRoute.pointIndex - 1 ) this.courseInfo.previousPoint.type = `RoutePoint` } this.courseInfo.previousPoint.href = null - res.status(200).send('OK') + + res.status(200).send(`OK`) } ) } @@ -343,98 +281,81 @@ export class CourseApi { return false } - const newCourse: any = {} - Object.assign(newCourse, this.courseInfo) - // set activeroute - newCourse.activeRoute.href = route.href + this.courseInfo.activeRoute.href = route.href - if (this.isValidArrivalCircle(route.arrivalCircle as number)) { - newCourse.nextPoint.arrivalCircle = route.arrivalCircle + if (typeof route.arrivalCircle === 'number') { + this.setArrivalCircle(route.arrivalCircle) } - newCourse.activeRoute.startTime = new Date().toISOString() + this.courseInfo.activeRoute.reverse = + typeof route.reverse === 'boolean' ? route.reverse : false - if (typeof route.reverse === 'boolean') { - newCourse.activeRoute.reverse = route.reverse - } + this.courseInfo.activeRoute.pointIndex = + typeof route.pointIndex === 'number' + ? this.parsePointIndex(route.pointIndex, rte) + : 0 - newCourse.activeRoute.pointIndex = this.parsePointIndex( - route.pointIndex as number, - rte - ) + this.courseInfo.activeRoute.startTime = new Date().toISOString() // set nextPoint - newCourse.nextPoint.position = this.getRoutePoint( + this.courseInfo.nextPoint.position = this.getRoutePoint( rte, - newCourse.activeRoute.pointIndex, - newCourse.activeRoute.reverse + this.courseInfo.activeRoute.pointIndex ) - newCourse.nextPoint.type = `RoutePoint` - newCourse.nextPoint.href = null + this.courseInfo.nextPoint.type = `RoutePoint` + this.courseInfo.nextPoint.href = null // set previousPoint - if (newCourse.activeRoute.pointIndex === 0) { - try { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { - return false - } - } catch (err) { + if (this.courseInfo.activeRoute.pointIndex === 0) { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { return false } } else { - newCourse.previousPoint.position = this.getRoutePoint( + this.courseInfo.previousPoint.position = this.getRoutePoint( rte, - newCourse.activeRoute.pointIndex - 1, - newCourse.activeRoute.reverse + this.courseInfo.activeRoute.pointIndex - 1 ) - newCourse.previousPoint.type = `RoutePoint` + this.courseInfo.previousPoint.type = `RoutePoint` } - newCourse.previousPoint.href = null + this.courseInfo.previousPoint.href = null - this.courseInfo = newCourse return true } - private async setDestination(dest: any): Promise { - const newCourse: any = {} - Object.assign(newCourse, this.courseInfo) - + private async setDestination(dest: Destination): Promise { // set nextPoint - if (this.isValidArrivalCircle(dest.arrivalCircle)) { - newCourse.nextPoint.arrivalCircle = dest.arrivalCircle + if (typeof dest.arrivalCircle === 'number') { + this.setArrivalCircle(dest.arrivalCircle) } - newCourse.nextPoint.type = + this.courseInfo.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - newCourse.href = dest.href + this.courseInfo.nextPoint.href = dest.href const href = this.parseHref(dest.href) if (href) { - // fetch waypoint resource details try { const r = await this.server.resourcesApi.getResource( href.type, href.id ) - if (r.position && typeof r.position?.latitude !== 'undefined') { - newCourse.nextPoint.position = r.position - } else { - return false + if (r.position && typeof r.position.value?.latitude !== 'undefined') { + this.courseInfo.nextPoint.position = r.position.value } } catch (err) { return false } } } else if (dest.position) { - newCourse.nextPoint.href = null + this.courseInfo.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { - newCourse.nextPoint.position = dest.position + this.courseInfo.nextPoint.position = dest.position } else { return false } @@ -443,20 +364,15 @@ export class CourseApi { } // set previousPoint - try { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - newCourse.previousPoint.position = position.value - newCourse.previousPoint.type = `VesselPosition` - } else { - return false - } - newCourse.previousPoint.href = null - } catch (err) { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { return false } + this.courseInfo.previousPoint.href = null - this.courseInfo = newCourse return true } @@ -473,12 +389,17 @@ export class CourseApi { this.courseInfo.previousPoint.position = null } - private isValidArrivalCircle(value: number): boolean { - return typeof value === 'number' && value >= 0 + private setArrivalCircle(value: any): boolean { + if (typeof value === 'number' && value >= 0) { + this.courseInfo.nextPoint.arrivalCircle = value + return true + } else { + return false + } } private parsePointIndex(index: number, rte: any): number { - if (typeof index !== 'number' || !rte) { + if (!rte) { return 0 } if (!rte.feature?.geometry?.coordinates) { @@ -497,11 +418,13 @@ export class CourseApi { } private parseHref(href: string): { type: string; id: string } | undefined { - if (!href) { + if (href.length === 0) { return undefined } - - const ref: string[] = href.split('/').slice(-3) + if (href[0] === '/') { + href = href.slice(1) + } + const ref: string[] = href.split('/') if (ref.length < 3) { return undefined } @@ -514,8 +437,8 @@ export class CourseApi { } } - private getRoutePoint(rte: any, index: number, reverse: boolean) { - const pos = reverse + private getRoutePoint(rte: any, index: number) { + const pos = this.courseInfo.activeRoute.reverse ? rte.feature.geometry.coordinates[ rte.feature.geometry.coordinates.length - (index + 1) ] @@ -523,7 +446,7 @@ export class CourseApi { return { latitude: pos[1], longitude: pos[0], - altitude: pos.length === 3 ? pos[2] : 0 + altitude: pos.length == 3 ? pos[2] : 0 } } @@ -541,94 +464,89 @@ export class CourseApi { } private buildDeltaMsg(): any { - const values: Array<{ path: string; value: any }> = [] - const navPath = [ + let values: Array<{path:string, value:any}> = [] + let root = [ 'navigation.courseGreatCircle', 'navigation.courseRhumbline' ] values.push({ - path: `navigation.course`, - value: this.courseInfo - }) - - values.push({ - path: `${navPath[0]}.activeRoute.href`, + path: `${root[0]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${navPath[1]}.activeRoute.href`, + path: `${root[1]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${navPath[0]}.activeRoute.startTime`, + path: `${root[0]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${navPath[1]}.activeRoute.startTime`, + path: `${root[1]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${navPath[0]}.nextPoint.href`, + path: `${root[0]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${navPath[1]}.nextPoint.href`, + path: `${root[1]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${navPath[0]}.nextPoint.position`, + path: `${root[0]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${navPath[1]}.nextPoint.position`, + path: `${root[1]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${navPath[0]}.nextPoint.type`, + path: `${root[0]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${navPath[1]}.nextPoint.type`, + path: `${root[1]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${navPath[0]}.nextPoint.arrivalCircle`, + path: `${root[0]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${navPath[1]}.nextPoint.arrivalCircle`, + path: `${root[1]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${navPath[0]}.previousPoint.href`, + path: `${root[0]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${navPath[1]}.previousPoint.href`, + path: `${root[1]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${navPath[0]}.previousPoint.position`, + path: `${root[0]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${navPath[1]}.previousPoint.position`, + path: `${root[1]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${navPath[0]}.previousPoint.type`, + path: `${root[0]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) values.push({ - path: `${navPath[1]}.previousPoint.type`, + path: `${root[1]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) return { updates: [ { - values + values: values } ] } diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index b162d1314..04653db5e 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -108,7 +108,29 @@ "put": { "tags": ["course"], "summary": "Restart course calculations", - "description": "Sets previousPoint value to current vessel location" + "description": "Sets previousPoint value to current vessel location", + "requestBody": { + "description": "Restart payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } } }, @@ -178,7 +200,29 @@ "delete": { "tags": ["course/destination"], "summary": "Clear destination", - "description": "Sets activeRoute, nextPoint & previousPoint values to null" + "description": "Sets activeRoute, nextPoint & previousPoint values to null", + "requestBody": { + "description": "Delete payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } } }, @@ -269,7 +313,29 @@ "delete": { "tags": ["course/activeRoute"], "summary": "Clear active route", - "description": "Sets activeRoute, nextPoint & previousPoint values to null" + "description": "Sets activeRoute, nextPoint & previousPoint values to null", + "requestBody": { + "description": "Delete payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } } }, From 98a8305f4dd59b1c35989d82d68542a08366291c Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 5 Dec 2021 10:26:47 +1030 Subject: [PATCH 122/410] update detlas --- src/api/course/index.ts | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 6e8e4074d..7510ca1e7 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,23 +1,15 @@ -import { - ResourceProvider, - ResourceProviderMethods, - SignalKResourceType -} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' -import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' - -const API_METHODS: string[] = [] interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any + registerPutHandler: (context:string, path:string, cb:any) => any resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -110,12 +102,26 @@ export class CourseApi { } ) + // + if(this.server.registerPutHandler) { + debug('** Registering PUT Action Handler(s) **') + this.server.registerPutHandler( + 'vessels.self', + 'navigation.course.*', + this.handleCourseApiPut + ); + } + // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { + //test for active destination + if (!this.courseInfo.nextPoint.position) { + return + } // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -266,6 +272,12 @@ export class CourseApi { ) } + private handleCourseApiPut(context:string, path:string, value:any, cb:any) { + + debug('** PUT handler **') + return undefined + } + private async activateRoute(route: ActiveRoute): Promise { let rte: any @@ -470,6 +482,11 @@ export class CourseApi { 'navigation.courseRhumbline' ] + values.push({ + path: `navigation.course`, + value: this.courseInfo + }) + values.push({ path: `${root[0]}.activeRoute.href`, value: this.courseInfo.activeRoute.href From f97c84635f0d0529ee9427cbd41ef6f487d72ded Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 4 Dec 2021 17:12:14 +1030 Subject: [PATCH 123/410] init courseApi --- src/api/course/index.ts | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 7510ca1e7..af32aceee 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,15 +1,26 @@ + +import { + ResourceProvider, + ResourceProviderMethods, + SignalKResourceType +} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' +import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' + +const API_METHODS: string[] = [] + interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - registerPutHandler: (context:string, path:string, cb:any) => any + resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -102,26 +113,12 @@ export class CourseApi { } ) - // - if(this.server.registerPutHandler) { - debug('** Registering PUT Action Handler(s) **') - this.server.registerPutHandler( - 'vessels.self', - 'navigation.course.*', - this.handleCourseApiPut - ); - } - // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { - //test for active destination - if (!this.courseInfo.nextPoint.position) { - return - } // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -272,12 +269,6 @@ export class CourseApi { ) } - private handleCourseApiPut(context:string, path:string, value:any, cb:any) { - - debug('** PUT handler **') - return undefined - } - private async activateRoute(route: ActiveRoute): Promise { let rte: any From 621e001d3c77e82d816c9e010f57043d022579c9 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 5 Dec 2021 10:26:47 +1030 Subject: [PATCH 124/410] update detlas --- src/api/course/index.ts | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index af32aceee..7510ca1e7 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,26 +1,15 @@ - -import { - ResourceProvider, - ResourceProviderMethods, - SignalKResourceType -} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' -import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' - -const API_METHODS: string[] = [] - interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - + registerPutHandler: (context:string, path:string, cb:any) => any resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -113,12 +102,26 @@ export class CourseApi { } ) + // + if(this.server.registerPutHandler) { + debug('** Registering PUT Action Handler(s) **') + this.server.registerPutHandler( + 'vessels.self', + 'navigation.course.*', + this.handleCourseApiPut + ); + } + // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { + //test for active destination + if (!this.courseInfo.nextPoint.position) { + return + } // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -269,6 +272,12 @@ export class CourseApi { ) } + private handleCourseApiPut(context:string, path:string, value:any, cb:any) { + + debug('** PUT handler **') + return undefined + } + private async activateRoute(route: ActiveRoute): Promise { let rte: any From 10772060ff80ffdbd97c3c2858aa9ef5772e469f Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 6 Dec 2021 08:39:56 +1030 Subject: [PATCH 125/410] enable put processing --- src/api/course/index.ts | 133 +++++++++++++++++++++++++--------------- 1 file changed, 82 insertions(+), 51 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 7510ca1e7..1566ecdf3 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -9,7 +9,14 @@ const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/cou interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - registerPutHandler: (context:string, path:string, cb:any) => any + securityStrategy: { + shouldAllowPut: ( + req: any, + context: string, + source: any, + path: string + ) => boolean + } resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -92,6 +99,15 @@ export class CourseApi { this.initResourceRoutes() } + private updateAllowed(): boolean { + return this.server.securityStrategy.shouldAllowPut( + this.server, + 'vessels.self', + null, + 'resources' + ) + } + private initResourceRoutes() { // return current course information this.server.get( @@ -102,43 +118,43 @@ export class CourseApi { } ) - // - if(this.server.registerPutHandler) { - debug('** Registering PUT Action Handler(s) **') - this.server.registerPutHandler( - 'vessels.self', - 'navigation.course.*', - this.handleCourseApiPut - ); - } + this.server.put( + `${COURSE_API_PATH}/restart`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/restart`) + if (!this.updateAllowed()) { + res.status(403) + return + } + if (!this.courseInfo.nextPoint.position) { + res.status(406).send(`No active destination!`) + return + } + // set previousPoint to vessel position + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.emitCourseInfo() + res.status(200) + } else { + res.status(406).send(`Vessel position unavailable!`) + } + } + ) - // restart / arrivalCircle this.server.put( - `${COURSE_API_PATH}/:action`, + `${COURSE_API_PATH}/arrivalCircle`, async (req: Request, res: Response) => { - debug(`** PUT ${COURSE_API_PATH}/:action`) - if (req.params.restart) { - //test for active destination - if (!this.courseInfo.nextPoint.position) { - return - } - // set previousPoint to vessel position - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.emitCourseInfo() - res.status(200).send(`Course restarted.`) - } else { - res.status(406).send(`Vessel position unavailable!`) - } + debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) + if (!this.updateAllowed()) { + res.status(403) + return } - if (req.params.arrivalCircle) { - if (this.setArrivalCircle(req.params.arrivalCircle)) { - this.emitCourseInfo() - res.status(200).send(`Destination set successfully.`) - } else { - res.status(406).send(`Invalid Data`) - } + if (this.setArrivalCircle(req.body.value)) { + this.emitCourseInfo() + res.status(200) + } else { + res.status(406).send(`Invalid Data`) } } ) @@ -148,6 +164,10 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) + if (!this.updateAllowed()) { + res.status(403) + return + } if (!req.body.value) { res.status(406).send(`Invalid Data`) return @@ -155,7 +175,7 @@ export class CourseApi { const result = await this.setDestination(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send(`Destination set successfully.`) + res.status(200) } else { this.clearDestination() this.emitCourseInfo() @@ -169,22 +189,29 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) + if (!this.updateAllowed()) { + res.status(403) + return + } this.clearDestination() this.emitCourseInfo() - res.status(200).send(`Destination cleared.`) + res.status(200) } ) - // set / clear activeRoute + // set activeRoute this.server.put( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) - + if (!this.updateAllowed()) { + res.status(403) + return + } const result = await this.activateRoute(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send(`Active route set.`) + res.status(200) } else { this.clearDestination() this.emitCourseInfo() @@ -192,14 +219,19 @@ export class CourseApi { } } ) + + // clear activeRoute /destination this.server.delete( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/activeRoute`) - + if (!this.updateAllowed()) { + res.status(403) + return + } this.clearDestination() this.emitCourseInfo() - res.status(200).send(`Active route cleared.`) + res.status(200) } ) @@ -207,6 +239,11 @@ export class CourseApi { `${COURSE_API_PATH}/activeRoute/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) + if (!this.updateAllowed()) { + res.status(403) + return + } + // fetch route data if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return @@ -217,7 +254,7 @@ export class CourseApi { return } - if (req.params.nextPoint) { + if (req.params.action === 'nextPoint') { if ( typeof req.body.value === 'number' && (req.body.value === 1 || req.body.value === -1) @@ -230,7 +267,7 @@ export class CourseApi { res.status(406).send(`Invalid Data`) } } - if (req.params.pointIndex) { + if (req.params.action === 'pointIndex') { if (typeof req.body.value === 'number') { this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( req.body.value, @@ -241,7 +278,7 @@ export class CourseApi { } } - // set nextPoint + // set new destination this.courseInfo.nextPoint.position = this.getRoutePoint( rte, this.courseInfo.activeRoute.pointIndex @@ -267,17 +304,11 @@ export class CourseApi { } this.courseInfo.previousPoint.href = null - res.status(200).send(`OK`) + res.status(200) } ) } - private handleCourseApiPut(context:string, path:string, value:any, cb:any) { - - debug('** PUT handler **') - return undefined - } - private async activateRoute(route: ActiveRoute): Promise { let rte: any From f9e9a982d8ff6747837e9334a66dda1d680a01b3 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 6 Dec 2021 13:34:31 +1030 Subject: [PATCH 126/410] chore: lint --- src/api/course/index.ts | 211 ++++++++++++++++++++---------------- src/api/course/openApi.json | 72 +----------- 2 files changed, 120 insertions(+), 163 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 1566ecdf3..6cd3b80fd 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -11,7 +11,7 @@ interface CourseApplication extends Application { getSelfPath: (path: string) => any securityStrategy: { shouldAllowPut: ( - req: any, + req: Application, context: string, source: any, path: string @@ -104,7 +104,7 @@ export class CourseApi { this.server, 'vessels.self', null, - 'resources' + 'navigation.course' ) } @@ -131,12 +131,14 @@ export class CourseApi { return } // set previousPoint to vessel position - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.emitCourseInfo() - res.status(200) - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.emitCourseInfo() + res.status(200) + } + } catch (err) { res.status(406).send(`Vessel position unavailable!`) } } @@ -150,7 +152,8 @@ export class CourseApi { res.status(403) return } - if (this.setArrivalCircle(req.body.value)) { + if (this.isValidArrivalCircle(req.body.value)) { + this.courseInfo.nextPoint.arrivalCircle = req.body.value this.emitCourseInfo() res.status(200) } else { @@ -243,7 +246,7 @@ export class CourseApi { res.status(403) return } - // fetch route data + // fetch active route data if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return @@ -281,24 +284,30 @@ export class CourseApi { // set new destination this.courseInfo.nextPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex + this.courseInfo.activeRoute.pointIndex, + this.courseInfo.activeRoute.reverse ) this.courseInfo.nextPoint.type = `RoutePoint` this.courseInfo.nextPoint.href = null // set previousPoint if (this.courseInfo.activeRoute.pointIndex === 0) { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } catch (err) { return false } } else { this.courseInfo.previousPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex - 1 + this.courseInfo.activeRoute.pointIndex - 1, + this.courseInfo.activeRoute.reverse ) this.courseInfo.previousPoint.type = `RoutePoint` } @@ -324,81 +333,97 @@ export class CourseApi { return false } + const newDest: any = {} + Object.assign(newDest, this.courseInfo) + // set activeroute - this.courseInfo.activeRoute.href = route.href + newDest.activeRoute.href = route.href - if (typeof route.arrivalCircle === 'number') { - this.setArrivalCircle(route.arrivalCircle) + if (this.isValidArrivalCircle(route.arrivalCircle as number)) { + newDest.nextPoint.arrivalCircle = route.arrivalCircle } - this.courseInfo.activeRoute.reverse = - typeof route.reverse === 'boolean' ? route.reverse : false + newDest.activeRoute.startTime = new Date().toISOString() - this.courseInfo.activeRoute.pointIndex = - typeof route.pointIndex === 'number' - ? this.parsePointIndex(route.pointIndex, rte) - : 0 + if (typeof route.reverse === 'boolean') { + newDest.activeRoute.reverse = route.reverse + } - this.courseInfo.activeRoute.startTime = new Date().toISOString() + newDest.activeRoute.pointIndex = this.parsePointIndex( + route.pointIndex as number, + rte + ) // set nextPoint - this.courseInfo.nextPoint.position = this.getRoutePoint( + newDest.nextPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex + newDest.activeRoute.pointIndex, + newDest.activeRoute.reverse ) - this.courseInfo.nextPoint.type = `RoutePoint` - this.courseInfo.nextPoint.href = null + newDest.nextPoint.type = `RoutePoint` + newDest.nextPoint.href = null // set previousPoint - if (this.courseInfo.activeRoute.pointIndex === 0) { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + if (newDest.activeRoute.pointIndex === 0) { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } catch (err) { return false } } else { - this.courseInfo.previousPoint.position = this.getRoutePoint( + newDest.previousPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex - 1 + newDest.activeRoute.pointIndex - 1, + newDest.activeRoute.reverse ) - this.courseInfo.previousPoint.type = `RoutePoint` + newDest.previousPoint.type = `RoutePoint` } - this.courseInfo.previousPoint.href = null + newDest.previousPoint.href = null + this.courseInfo = newDest return true } - private async setDestination(dest: Destination): Promise { + private async setDestination(dest: any): Promise { + const newDest: any = {} + Object.assign(newDest, this.courseInfo) + // set nextPoint - if (typeof dest.arrivalCircle === 'number') { - this.setArrivalCircle(dest.arrivalCircle) + if (this.isValidArrivalCircle(dest.arrivalCircle)) { + newDest.nextPoint.arrivalCircle = dest.arrivalCircle } - this.courseInfo.nextPoint.type = - typeof dest.type !== 'undefined' ? dest.type : null + newDest.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - this.courseInfo.nextPoint.href = dest.href + newDest.href = dest.href const href = this.parseHref(dest.href) if (href) { + // fetch waypoint resource details try { const r = await this.server.resourcesApi.getResource( href.type, href.id ) - if (r.position && typeof r.position.value?.latitude !== 'undefined') { - this.courseInfo.nextPoint.position = r.position.value + if (r.position && typeof r.position?.latitude !== 'undefined') { + newDest.nextPoint.position = r.position + } else { + return false } } catch (err) { return false } } } else if (dest.position) { - this.courseInfo.nextPoint.href = null + newDest.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { - this.courseInfo.nextPoint.position = dest.position + newDest.nextPoint.position = dest.position } else { return false } @@ -407,15 +432,20 @@ export class CourseApi { } // set previousPoint - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + newDest.previousPoint.position = position.value + newDest.previousPoint.type = `VesselPosition` + } else { + return false + } + newDest.previousPoint.href = null + } catch (err) { return false } - this.courseInfo.previousPoint.href = null + this.courseInfo = newDest return true } @@ -432,17 +462,12 @@ export class CourseApi { this.courseInfo.previousPoint.position = null } - private setArrivalCircle(value: any): boolean { - if (typeof value === 'number' && value >= 0) { - this.courseInfo.nextPoint.arrivalCircle = value - return true - } else { - return false - } + private isValidArrivalCircle(value: number): boolean { + return typeof value === 'number' && value >= 0 } private parsePointIndex(index: number, rte: any): number { - if (!rte) { + if (typeof index !== 'number' || !rte) { return 0 } if (!rte.feature?.geometry?.coordinates) { @@ -461,13 +486,11 @@ export class CourseApi { } private parseHref(href: string): { type: string; id: string } | undefined { - if (href.length === 0) { + if (!href) { return undefined } - if (href[0] === '/') { - href = href.slice(1) - } - const ref: string[] = href.split('/') + + const ref: string[] = href.split('/').slice(-3) if (ref.length < 3) { return undefined } @@ -480,8 +503,8 @@ export class CourseApi { } } - private getRoutePoint(rte: any, index: number) { - const pos = this.courseInfo.activeRoute.reverse + private getRoutePoint(rte: any, index: number, reverse: boolean) { + const pos = reverse ? rte.feature.geometry.coordinates[ rte.feature.geometry.coordinates.length - (index + 1) ] @@ -489,7 +512,7 @@ export class CourseApi { return { latitude: pos[1], longitude: pos[0], - altitude: pos.length == 3 ? pos[2] : 0 + altitude: pos.length === 3 ? pos[2] : 0 } } @@ -507,8 +530,8 @@ export class CourseApi { } private buildDeltaMsg(): any { - let values: Array<{path:string, value:any}> = [] - let root = [ + const values: Array<{ path: string; value: any }> = [] + const navPath = [ 'navigation.courseGreatCircle', 'navigation.courseRhumbline' ] @@ -519,82 +542,82 @@ export class CourseApi { }) values.push({ - path: `${root[0]}.activeRoute.href`, + path: `${navPath[0]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${root[1]}.activeRoute.href`, + path: `${navPath[1]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${root[0]}.activeRoute.startTime`, + path: `${navPath[0]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${root[1]}.activeRoute.startTime`, + path: `${navPath[1]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${root[0]}.nextPoint.href`, + path: `${navPath[0]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${root[1]}.nextPoint.href`, + path: `${navPath[1]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${root[0]}.nextPoint.position`, + path: `${navPath[0]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${root[1]}.nextPoint.position`, + path: `${navPath[1]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${root[0]}.nextPoint.type`, + path: `${navPath[0]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${root[1]}.nextPoint.type`, + path: `${navPath[1]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${root[0]}.nextPoint.arrivalCircle`, + path: `${navPath[0]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${root[1]}.nextPoint.arrivalCircle`, + path: `${navPath[1]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${root[0]}.previousPoint.href`, + path: `${navPath[0]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${root[1]}.previousPoint.href`, + path: `${navPath[1]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${root[0]}.previousPoint.position`, + path: `${navPath[0]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${root[1]}.previousPoint.position`, + path: `${navPath[1]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${root[0]}.previousPoint.type`, + path: `${navPath[0]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) values.push({ - path: `${root[1]}.previousPoint.type`, + path: `${navPath[1]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) return { updates: [ { - values: values + values } ] } diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index 04653db5e..b162d1314 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -108,29 +108,7 @@ "put": { "tags": ["course"], "summary": "Restart course calculations", - "description": "Sets previousPoint value to current vessel location", - "requestBody": { - "description": "Restart payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } - } - } - } - } - } + "description": "Sets previousPoint value to current vessel location" } }, @@ -200,29 +178,7 @@ "delete": { "tags": ["course/destination"], "summary": "Clear destination", - "description": "Sets activeRoute, nextPoint & previousPoint values to null", - "requestBody": { - "description": "Delete payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } - } - } - } - } - } + "description": "Sets activeRoute, nextPoint & previousPoint values to null" } }, @@ -313,29 +269,7 @@ "delete": { "tags": ["course/activeRoute"], "summary": "Clear active route", - "description": "Sets activeRoute, nextPoint & previousPoint values to null", - "requestBody": { - "description": "Delete payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } - } - } - } - } - } + "description": "Sets activeRoute, nextPoint & previousPoint values to null" } }, From 0b844c9ff39c535ab57d1f085a59a56c185d622d Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 8 Dec 2021 09:27:06 +1030 Subject: [PATCH 127/410] add 30sec delta interval --- src/api/course/index.ts | 100 ++++++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 45 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 6cd3b80fd..2aa57e8a6 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -6,6 +6,8 @@ const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const DELTA_INTERVAL: number = 30000 + interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any @@ -97,6 +99,11 @@ export class CourseApi { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app this.initResourceRoutes() + setInterval( ()=> { + if(this.courseInfo.nextPoint.position) { + this.emitCourseInfo() + } + }, DELTA_INTERVAL) } private updateAllowed(): boolean { @@ -123,7 +130,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/restart`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (!this.courseInfo.nextPoint.position) { @@ -136,7 +143,7 @@ export class CourseApi { if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } } catch (err) { res.status(406).send(`Vessel position unavailable!`) @@ -149,13 +156,13 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (this.isValidArrivalCircle(req.body.value)) { this.courseInfo.nextPoint.arrivalCircle = req.body.value this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { res.status(406).send(`Invalid Data`) } @@ -168,7 +175,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (!req.body.value) { @@ -178,7 +185,7 @@ export class CourseApi { const result = await this.setDestination(req.body.value) if (result) { this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { this.clearDestination() this.emitCourseInfo() @@ -193,12 +200,12 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } this.clearDestination() this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } ) @@ -208,13 +215,13 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } const result = await this.activateRoute(req.body.value) if (result) { this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { this.clearDestination() this.emitCourseInfo() @@ -234,7 +241,7 @@ export class CourseApi { } this.clearDestination() this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } ) @@ -243,7 +250,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } // fetch active route data @@ -268,6 +275,7 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) + return } } if (req.params.action === 'pointIndex') { @@ -278,6 +286,7 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) + return } } @@ -298,9 +307,11 @@ export class CourseApi { this.courseInfo.previousPoint.position = position.value this.courseInfo.previousPoint.type = `VesselPosition` } else { + res.status(406).send(`Invalid Data`) return false } } catch (err) { + res.status(406).send(`Invalid Data`) return false } } else { @@ -312,8 +323,7 @@ export class CourseApi { this.courseInfo.previousPoint.type = `RoutePoint` } this.courseInfo.previousPoint.href = null - - res.status(200) + res.status(200).send('OK') } ) } @@ -333,38 +343,38 @@ export class CourseApi { return false } - const newDest: any = {} - Object.assign(newDest, this.courseInfo) + const newCourse: any = {} + Object.assign(newCourse, this.courseInfo) // set activeroute - newDest.activeRoute.href = route.href + newCourse.activeRoute.href = route.href if (this.isValidArrivalCircle(route.arrivalCircle as number)) { - newDest.nextPoint.arrivalCircle = route.arrivalCircle + newCourse.nextPoint.arrivalCircle = route.arrivalCircle } - newDest.activeRoute.startTime = new Date().toISOString() + newCourse.activeRoute.startTime = new Date().toISOString() if (typeof route.reverse === 'boolean') { - newDest.activeRoute.reverse = route.reverse + newCourse.activeRoute.reverse = route.reverse } - newDest.activeRoute.pointIndex = this.parsePointIndex( + newCourse.activeRoute.pointIndex = this.parsePointIndex( route.pointIndex as number, rte ) // set nextPoint - newDest.nextPoint.position = this.getRoutePoint( + newCourse.nextPoint.position = this.getRoutePoint( rte, - newDest.activeRoute.pointIndex, - newDest.activeRoute.reverse + newCourse.activeRoute.pointIndex, + newCourse.activeRoute.reverse ) - newDest.nextPoint.type = `RoutePoint` - newDest.nextPoint.href = null + newCourse.nextPoint.type = `RoutePoint` + newCourse.nextPoint.href = null // set previousPoint - if (newDest.activeRoute.pointIndex === 0) { + if (newCourse.activeRoute.pointIndex === 0) { try { const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -377,32 +387,32 @@ export class CourseApi { return false } } else { - newDest.previousPoint.position = this.getRoutePoint( + newCourse.previousPoint.position = this.getRoutePoint( rte, - newDest.activeRoute.pointIndex - 1, - newDest.activeRoute.reverse + newCourse.activeRoute.pointIndex - 1, + newCourse.activeRoute.reverse ) - newDest.previousPoint.type = `RoutePoint` + newCourse.previousPoint.type = `RoutePoint` } - newDest.previousPoint.href = null + newCourse.previousPoint.href = null - this.courseInfo = newDest + this.courseInfo = newCourse return true } private async setDestination(dest: any): Promise { - const newDest: any = {} - Object.assign(newDest, this.courseInfo) + const newCourse: any = {} + Object.assign(newCourse, this.courseInfo) // set nextPoint if (this.isValidArrivalCircle(dest.arrivalCircle)) { - newDest.nextPoint.arrivalCircle = dest.arrivalCircle + newCourse.nextPoint.arrivalCircle = dest.arrivalCircle } - newDest.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null + newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - newDest.href = dest.href + newCourse.href = dest.href const href = this.parseHref(dest.href) if (href) { // fetch waypoint resource details @@ -412,7 +422,7 @@ export class CourseApi { href.id ) if (r.position && typeof r.position?.latitude !== 'undefined') { - newDest.nextPoint.position = r.position + newCourse.nextPoint.position = r.position } else { return false } @@ -421,9 +431,9 @@ export class CourseApi { } } } else if (dest.position) { - newDest.nextPoint.href = null + newCourse.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { - newDest.nextPoint.position = dest.position + newCourse.nextPoint.position = dest.position } else { return false } @@ -435,17 +445,17 @@ export class CourseApi { try { const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { - newDest.previousPoint.position = position.value - newDest.previousPoint.type = `VesselPosition` + newCourse.previousPoint.position = position.value + newCourse.previousPoint.type = `VesselPosition` } else { return false } - newDest.previousPoint.href = null + newCourse.previousPoint.href = null } catch (err) { return false } - this.courseInfo = newDest + this.courseInfo = newCourse return true } From d46e03ca52e19022347d303fe0e86696cf7cc045 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 12 Dec 2021 14:29:38 +1030 Subject: [PATCH 128/410] chore: lint --- src/api/course/index.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 2aa57e8a6..1e719297a 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -6,7 +6,7 @@ const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const DELTA_INTERVAL: number = 30000 +const DELTA_INTERVAL: number = 30000 interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void @@ -99,8 +99,8 @@ export class CourseApi { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app this.initResourceRoutes() - setInterval( ()=> { - if(this.courseInfo.nextPoint.position) { + setInterval(() => { + if (this.courseInfo.nextPoint.position) { this.emitCourseInfo() } }, DELTA_INTERVAL) @@ -409,7 +409,8 @@ export class CourseApi { newCourse.nextPoint.arrivalCircle = dest.arrivalCircle } - newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null + newCourse.nextPoint.type = + typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { newCourse.href = dest.href From 2d5aa1fa7ef725a1b734789d6fde3faacc0541f4 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 20 Dec 2021 10:56:22 +1030 Subject: [PATCH 129/410] fix api request id validation --- src/api/resources/index.ts | 44 ++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 8091a79a1..91243ca81 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -249,7 +249,7 @@ export class Resources { } else { isValidId = validate.uuid(req.params.resourceId) } - if (isValidId) { + if (!isValidId) { res .status(406) .send(`Invalid resource id provided (${req.params.resourceId})`) @@ -339,14 +339,15 @@ export class Resources { this.server.post( `${SIGNALK_API_PATH}/resources/set/:resourceType`, async (req: Request, res: Response) => { - debug(`** PUT ${SIGNALK_API_PATH}/resources/set/:resourceType`) + debug(`** POST ${SIGNALK_API_PATH}/resources/set/:resourceType`) if (!this.updateAllowed()) { res.status(403) return } - const apiData = this.processApiRequest(req) + let apiData = this.processApiRequest(req) + debug(apiData) if (!this.checkForProvider(apiData.type)) { res.status(501).send(`No provider for ${apiData.type}!`) @@ -378,9 +379,9 @@ export class Resources { this.resProvider[apiData.type]?.pluginId as string, this.buildDeltaMsg(apiData.type, apiData.id, apiData.value) ) - res.status(200).send(`SUCCESS: ${req.params.resourceType} complete.`) + res.status(200).send(`SUCCESS: New ${req.params.resourceType} resource created.`) } catch (err) { - res.status(404).send(`ERROR: ${req.params.resourceType} incomplete!`) + res.status(404).send(`ERROR: Could not create ${req.params.resourceType} resource!`) } } ) @@ -428,43 +429,44 @@ export class Resources { this.resProvider[apiData.type]?.pluginId as string, this.buildDeltaMsg(apiData.type, apiData.id, apiData.value) ) - res.status(200).send(`SUCCESS: ${req.params.resourceType} complete.`) + res.status(200).send(`SUCCESS: ${req.params.resourceType} resource updated.`) } catch (err) { - res.status(404).send(`ERROR: ${req.params.resourceType} incomplete!`) + res.status(404).send(`ERROR: ${req.params.resourceType} resource could not be updated!`) } } ) } private processApiRequest(req: Request) { - let resType: SignalKResourceType = 'waypoints' + let apiReq: any = { + type: undefined, + id: undefined, + value: undefined + } + if (req.params.resourceType.toLowerCase() === 'waypoint') { - resType = 'waypoints' + apiReq.type = 'waypoints' } if (req.params.resourceType.toLowerCase() === 'route') { - resType = 'routes' + apiReq.type = 'routes' } if (req.params.resourceType.toLowerCase() === 'note') { - resType = 'notes' + apiReq.type = 'notes' } if (req.params.resourceType.toLowerCase() === 'region') { - resType = 'regions' + apiReq.type = 'regions' } if (req.params.resourceType.toLowerCase() === 'charts') { - resType = 'charts' + apiReq.type = 'charts' } - const resValue: any = buildResource(resType, req.body) + apiReq.value = buildResource(apiReq.type, req.body) - const resId: string = req.params.resourceId + apiReq.id = req.params.resourceId ? req.params.resourceId - : (resType = 'charts' ? resValue.identifier : UUID_PREFIX + uuidv4()) + : (apiReq.type === 'charts' ? apiReq.value.identifier : UUID_PREFIX + uuidv4()) - return { - type: resType, - id: resId, - value: resValue - } + return apiReq } private getResourcePaths(): { [key: string]: any } { From 895d1a0f4ec7976fe03f1766402a0834feb726a6 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 4 Dec 2021 17:12:14 +1030 Subject: [PATCH 130/410] init courseApi --- src/api/course/index.ts | 562 ++++++++++++++++++++++++++++++++++++ src/api/course/openApi.json | 413 ++++++++++++++++++++++++++ src/index.ts | 2 + 3 files changed, 977 insertions(+) create mode 100644 src/api/course/index.ts create mode 100644 src/api/course/openApi.json diff --git a/src/api/course/index.ts b/src/api/course/index.ts new file mode 100644 index 000000000..a8d42a1a8 --- /dev/null +++ b/src/api/course/index.ts @@ -0,0 +1,562 @@ +import { + ResourceProvider, + ResourceProviderMethods, + SignalKResourceType +} from '@signalk/server-api' +import Debug from 'debug' +import { Application, Request, Response } from 'express' +import { v4 as uuidv4 } from 'uuid' + +const debug = Debug('signalk:courseApi') + +const SIGNALK_API_PATH: string = `/signalk/v1/api` +const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' + +const API_METHODS: string[] = [] + +interface CourseApplication extends Application { + handleMessage: (id: string, data: any) => void + getSelfPath: (path: string) => any + resourcesApi: { + getResource: (resourceType: string, resourceId: string) => any + } +} + +interface DestinationBase { + href?: string + arrivalCircle?: number +} +interface Destination extends DestinationBase { + position?: { + latitude: number + longitude: number + altitude?: number + } + type?: string +} + +interface ActiveRoute extends DestinationBase { + pointIndex?: number + reverse?: boolean +} + +interface Position { + latitude: number + longitude: number + altitude?: number +} + +interface CourseInfo { + activeRoute: { + href: string | null + startTime: string | null + pointIndex: number + reverse: boolean + } + nextPoint: { + href: string | null + type: string | null + position: Position | null + arrivalCircle: number + } + previousPoint: { + href: string | null + type: string | null + position: Position | null + } +} + +export class CourseApi { + private server: CourseApplication + + private courseInfo: CourseInfo = { + activeRoute: { + href: null, + startTime: null, + pointIndex: 0, + reverse: false + }, + nextPoint: { + href: null, + type: null, + position: null, + arrivalCircle: 0 + }, + previousPoint: { + href: null, + type: null, + position: null + } + } + + constructor(app: CourseApplication) { + this.server = app + this.start(app) + } + + private start(app: any) { + debug(`** Initialise ${COURSE_API_PATH} path handler **`) + this.server = app + this.initResourceRoutes() + } + + private initResourceRoutes() { + // return current course information + this.server.get( + `${COURSE_API_PATH}`, + async (req: Request, res: Response) => { + debug(`** GET ${COURSE_API_PATH}`) + res.json(this.courseInfo) + } + ) + + // restart / arrivalCircle + this.server.put( + `${COURSE_API_PATH}/:action`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/:action`) + if (req.params.restart) { + // set previousPoint to vessel position + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.emitCourseInfo() + res.status(200).send(`Course restarted.`) + } else { + res.status(406).send(`Vessel position unavailable!`) + } + } + if (req.params.arrivalCircle) { + if (this.setArrivalCircle(req.params.arrivalCircle)) { + this.emitCourseInfo() + res.status(200).send(`Destination set successfully.`) + } else { + res.status(406).send(`Invalid Data`) + } + } + } + ) + + // set destination + this.server.put( + `${COURSE_API_PATH}/destination`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/destination`) + + if (!req.body.value) { + res.status(406).send(`Invalid Data`) + return + } + const result = await this.setDestination(req.body.value) + if (result) { + this.emitCourseInfo() + res.status(200).send(`Destination set successfully.`) + } else { + this.clearDestination() + this.emitCourseInfo() + res.status(406).send(`Invalid Data`) + } + } + ) + + // clear destination + this.server.delete( + `${COURSE_API_PATH}/destination`, + async (req: Request, res: Response) => { + debug(`** DELETE ${COURSE_API_PATH}/destination`) + this.clearDestination() + this.emitCourseInfo() + res.status(200).send(`Destination cleared.`) + } + ) + + // set / clear activeRoute + this.server.put( + `${COURSE_API_PATH}/activeRoute`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/activeRoute`) + + const result = await this.activateRoute(req.body.value) + if (result) { + this.emitCourseInfo() + res.status(200).send(`Active route set.`) + } else { + this.clearDestination() + this.emitCourseInfo() + res.status(406).send(`Invalid Data`) + } + } + ) + this.server.delete( + `${COURSE_API_PATH}/activeRoute`, + async (req: Request, res: Response) => { + debug(`** DELETE ${COURSE_API_PATH}/activeRoute`) + + this.clearDestination() + this.emitCourseInfo() + res.status(200).send(`Active route cleared.`) + } + ) + + this.server.put( + `${COURSE_API_PATH}/activeRoute/:action`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/activeRoute`) + + if (!this.courseInfo.activeRoute.href) { + res.status(406).send(`Invalid Data`) + return + } + + const rte = await this.getRoute(this.courseInfo.activeRoute.href) + if (!rte) { + res.status(406).send(`Invalid Data`) + return + } + + if (req.params.nextPoint) { + if ( + typeof req.body.value === 'number' && + (req.body.value === 1 || req.body.value === -1) + ) { + this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( + this.courseInfo.activeRoute.pointIndex + req.body.value, + rte + ) + } else { + res.status(406).send(`Invalid Data`) + } + } + if (req.params.pointIndex) { + if (typeof req.body.value === 'number') { + this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( + req.body.value, + rte + ) + } else { + res.status(406).send(`Invalid Data`) + } + } + + // set nextPoint + this.courseInfo.nextPoint.position = this.getRoutePoint( + rte, + this.courseInfo.activeRoute.pointIndex + ) + this.courseInfo.nextPoint.type = `RoutePoint` + this.courseInfo.nextPoint.href = null + + // set previousPoint + if (this.courseInfo.activeRoute.pointIndex === 0) { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } else { + this.courseInfo.previousPoint.position = this.getRoutePoint( + rte, + this.courseInfo.activeRoute.pointIndex - 1 + ) + this.courseInfo.previousPoint.type = `RoutePoint` + } + this.courseInfo.previousPoint.href = null + + res.status(200).send(`OK`) + } + ) + } + + private async activateRoute(route: ActiveRoute): Promise { + let rte: any + + if (route.href) { + rte = await this.getRoute(route.href) + if (!rte) { + return false + } + if (!Array.isArray(rte.feature?.geometry?.coordinates)) { + return false + } + } else { + return false + } + + // set activeroute + this.courseInfo.activeRoute.href = route.href + + if (typeof route.arrivalCircle === 'number') { + this.setArrivalCircle(route.arrivalCircle) + } + + this.courseInfo.activeRoute.reverse = + typeof route.reverse === 'boolean' ? route.reverse : false + + this.courseInfo.activeRoute.pointIndex = + typeof route.pointIndex === 'number' + ? this.parsePointIndex(route.pointIndex, rte) + : 0 + + this.courseInfo.activeRoute.startTime = new Date().toISOString() + + // set nextPoint + this.courseInfo.nextPoint.position = this.getRoutePoint( + rte, + this.courseInfo.activeRoute.pointIndex + ) + this.courseInfo.nextPoint.type = `RoutePoint` + this.courseInfo.nextPoint.href = null + + // set previousPoint + if (this.courseInfo.activeRoute.pointIndex === 0) { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } else { + this.courseInfo.previousPoint.position = this.getRoutePoint( + rte, + this.courseInfo.activeRoute.pointIndex - 1 + ) + this.courseInfo.previousPoint.type = `RoutePoint` + } + this.courseInfo.previousPoint.href = null + + return true + } + + private async setDestination(dest: Destination): Promise { + // set nextPoint + if (typeof dest.arrivalCircle === 'number') { + this.setArrivalCircle(dest.arrivalCircle) + } + + this.courseInfo.nextPoint.type = + typeof dest.type !== 'undefined' ? dest.type : null + + if (dest.href) { + this.courseInfo.nextPoint.href = dest.href + const href = this.parseHref(dest.href) + if (href) { + try { + const r = await this.server.resourcesApi.getResource( + href.type, + href.id + ) + if (r.position && typeof r.position.value?.latitude !== 'undefined') { + this.courseInfo.nextPoint.position = r.position.value + } + } catch (err) { + return false + } + } + } else if (dest.position) { + this.courseInfo.nextPoint.href = null + if (typeof dest.position.latitude !== 'undefined') { + this.courseInfo.nextPoint.position = dest.position + } else { + return false + } + } else { + return false + } + + // set previousPoint + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + this.courseInfo.previousPoint.href = null + + return true + } + + private clearDestination() { + this.courseInfo.activeRoute.href = null + this.courseInfo.activeRoute.startTime = null + this.courseInfo.activeRoute.pointIndex = 0 + this.courseInfo.activeRoute.reverse = false + this.courseInfo.nextPoint.href = null + this.courseInfo.nextPoint.type = null + this.courseInfo.nextPoint.position = null + this.courseInfo.previousPoint.href = null + this.courseInfo.previousPoint.type = null + this.courseInfo.previousPoint.position = null + } + + private setArrivalCircle(value: any): boolean { + if (typeof value === 'number' && value >= 0) { + this.courseInfo.nextPoint.arrivalCircle = value + return true + } else { + return false + } + } + + private parsePointIndex(index: number, rte: any): number { + if (!rte) { + return 0 + } + if (!rte.feature?.geometry?.coordinates) { + return 0 + } + if (!Array.isArray(rte.feature?.geometry?.coordinates)) { + return 0 + } + if (index < 0) { + return 0 + } + if (index > rte.feature?.geometry?.coordinates.length - 1) { + return rte.feature?.geometry?.coordinates.length + } + return index + } + + private parseHref(href: string): { type: string; id: string } | undefined { + if (href.length === 0) { + return undefined + } + if (href[0] === '/') { + href = href.slice(1) + } + const ref: string[] = href.split('/') + if (ref.length < 3) { + return undefined + } + if (ref[0] !== 'resources') { + return undefined + } + return { + type: ref[1], + id: ref[2] + } + } + + private getRoutePoint(rte: any, index: number) { + const pos = this.courseInfo.activeRoute.reverse + ? rte.feature.geometry.coordinates[ + rte.feature.geometry.coordinates.length - (index + 1) + ] + : rte.feature.geometry.coordinates[index] + return { + latitude: pos[1], + longitude: pos[0], + altitude: pos.length == 3 ? pos[2] : 0 + } + } + + private async getRoute(href: string): Promise { + const h = this.parseHref(href) + if (h) { + try { + return await this.server.resourcesApi.getResource(h.type, h.id) + } catch (err) { + return undefined + } + } else { + return undefined + } + } + + private buildDeltaMsg(): any { + let values: Array<{path:string, value:any}> = [] + let root = [ + 'navigation.courseGreatCircle', + 'navigation.courseRhumbline' + ] + + values.push({ + path: `${root[0]}.activeRoute.href`, + value: this.courseInfo.activeRoute.href + }) + values.push({ + path: `${root[1]}.activeRoute.href`, + value: this.courseInfo.activeRoute.href + }) + values.push({ + path: `${root[0]}.activeRoute.startTime`, + value: this.courseInfo.activeRoute.startTime + }) + values.push({ + path: `${root[1]}.activeRoute.startTime`, + value: this.courseInfo.activeRoute.startTime + }) + values.push({ + path: `${root[0]}.nextPoint.href`, + value: this.courseInfo.nextPoint.href + }) + values.push({ + path: `${root[1]}.nextPoint.href`, + value: this.courseInfo.nextPoint.href + }) + values.push({ + path: `${root[0]}.nextPoint.position`, + value: this.courseInfo.nextPoint.position + }) + values.push({ + path: `${root[1]}.nextPoint.position`, + value: this.courseInfo.nextPoint.position + }) + values.push({ + path: `${root[0]}.nextPoint.type`, + value: this.courseInfo.nextPoint.type + }) + values.push({ + path: `${root[1]}.nextPoint.type`, + value: this.courseInfo.nextPoint.type + }) + values.push({ + path: `${root[0]}.nextPoint.arrivalCircle`, + value: this.courseInfo.nextPoint.arrivalCircle + }) + values.push({ + path: `${root[1]}.nextPoint.arrivalCircle`, + value: this.courseInfo.nextPoint.arrivalCircle + }) + values.push({ + path: `${root[0]}.previousPoint.href`, + value: this.courseInfo.previousPoint.href + }) + values.push({ + path: `${root[1]}.previousPoint.href`, + value: this.courseInfo.previousPoint.href + }) + values.push({ + path: `${root[0]}.previousPoint.position`, + value: this.courseInfo.previousPoint.position + }) + values.push({ + path: `${root[1]}.previousPoint.position`, + value: this.courseInfo.previousPoint.position + }) + values.push({ + path: `${root[0]}.previousPoint.type`, + value: this.courseInfo.previousPoint.type + }) + values.push({ + path: `${root[1]}.previousPoint.type`, + value: this.courseInfo.previousPoint.type + }) + + return { + updates: [ + { + values: values + } + ] + } + } + + private emitCourseInfo() { + this.server.handleMessage('courseApi', this.buildDeltaMsg()) + } +} diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json new file mode 100644 index 000000000..04653db5e --- /dev/null +++ b/src/api/course/openApi.json @@ -0,0 +1,413 @@ +{ + "openapi": "3.0.2", + "info": { + "version": "1.0.0", + "title": "Signal K Course API" + }, + + "paths": { + + "/vessels/self/navigation/course/": { + "get": { + "tags": ["course"], + "summary": "Get course information", + "responses": { + "default": { + "description": "Course data", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "activeRoute": { + "type": "object", + "properties": { + "href": { + "type": "string", + "example": "/resources/routes/urn:mrn:signalk:uuid:0d95e282-3e1f-4521-8c30-8288addbdb69" + }, + "startTime": { + "type": "string", + "example": "2021-10-23T05:17:20.065Z" + }, + "pointIndex": { + "type": "number", + "format": "int64", + "example": 2 + }, + "reverse": { + "type": "boolean", + "default": false + } + } + }, + "nextPoint": { + "type": "object", + "properties": { + "href": { + "type": "string", + "example": "/resources/waypoints/urn:mrn:signalk:uuid:0d95e282-3e1f-4521-8c30-8288addbdbab" + }, + "type": { + "type": "string", + "example": "RoutePoint" + }, + "position": { + "type": "object", + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + }, + "example": {"latitude":-29.5,"longitude":137.5} + } + } + }, + "previousPoint": { + "type": "object", + "properties": { + "href": { + "type": "string", + "example": null + }, + "type": { + "type": "string", + "example": "Location" + }, + "position": { + "type": "object", + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + }, + "example": {"longitude":29.821001582434413,"latitude":70.7014589462524} + } + } + } + } + } + } + } + } + } + } + }, + + "/vessels/self/navigation/course/restart": { + "put": { + "tags": ["course"], + "summary": "Restart course calculations", + "description": "Sets previousPoint value to current vessel location", + "requestBody": { + "description": "Restart payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + } + }, + + "/vessels/self/navigation/course/destination": { + "put": { + "tags": ["course/destination"], + "summary": "Set destination", + "description": "Set destination path from supplied details", + "requestBody": { + "description": "Destination details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "properties": { + "position": { + "type": "object", + "description": "Destination position", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + }, + "example": {"latitude":-29.5,"longitude":137.5} + }, + "href": { + "type": "string", + "description": "A reference (URL) to an object (under /resources) this point is related to", + "example": "/resources/waypoints/urn:mrn:signalk:uuid:3dd34dcc-36bf-4d61-ba80-233799b25672" + }, + "type": { + "type": "string", + "description": "Type of point", + "example": "POI" + }, + "arrivalCircle": { + "type": "number", + "format": "float", + "example": 500 + } + } + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + }, + "delete": { + "tags": ["course/destination"], + "summary": "Clear destination", + "description": "Sets activeRoute, nextPoint & previousPoint values to null", + "requestBody": { + "description": "Delete payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + } + }, + + "/vessels/self/navigation/course/arrivalCircle": { + "put": { + "tags": ["course"], + "summary": "Set arrival circle radius (m)", + "description": "Sets an arrival circle radius (in meters)", + "requestBody": { + "description": "Arrival circle payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "number", + "format": "float", + "example": 500 + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + } + }, + + "/vessels/self/navigation/course/activeRoute": { + "put": { + "tags": ["course/activeRoute"], + "summary": "Set active route", + "description": "Sets activeRoute path and sets nextPoint to first point in the route", + "requestBody": { + "description": "Active route details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "properties": { + "href": { + "type": "string", + "description": "Path to route resource", + "example": "/resources/routes/urn:mrn:signalk:uuid:3dd34dcc-36bf-4d61-ba80-233799b25672" + }, + "pointIndex": { + "type": "number", + "format": "int64", + "description": "Zero based index of route point to use as destination", + "default": 0, + "minimum": 0, + "example": 2 + }, + "reverse": { + "type": "boolean", + "default": false, + "description": "If true performs operations on route points in reverse order", + "example": 2 + }, + "arrivalCircle": { + "type": "number", + "format": "float", + "example": 500 + } + } + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + }, + "delete": { + "tags": ["course/activeRoute"], + "summary": "Clear active route", + "description": "Sets activeRoute, nextPoint & previousPoint values to null", + "requestBody": { + "description": "Delete payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + } + }, + + "/vessels/self/navigation/course/activeRoute/nextPoint": { + "put": { + "tags": ["course/activeRoute"], + "summary": "Increment / decrement point in route as destination", + "description": "Increment / decrement point in the route as destination", + "requestBody": { + "description": "Increment / decrement", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "number", + "description": "increment (1) / decrement (-1) index of point in route to use as destination", + "enum": [-1,1], + "example": -1 + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + } + }, + + "/vessels/self/navigation/course/activeRoute/pointIndex": { + "put": { + "tags": ["course/activeRoute"], + "summary": "Set point in route as destination", + "description": "Sets the specified point in the route as destination", + "requestBody": { + "description": "Next point index", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "number", + "description": "Index of point in route to use as destination", + "minimum": 0, + "example": 2 + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + } + } + + } + +} + + + + + \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index c8104d1cc..c4709bed8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -51,6 +51,7 @@ import { startSecurity } from './security.js' +import { CourseApi } from './api/course' import { Resources } from './api/resources' // tslint:disable-next-line: no-var-requires @@ -84,6 +85,7 @@ class Server { require('./put').start(app) app.resourcesApi = new Resources(app) + const courseApi = new CourseApi(app) app.signalk = new FullSignalK(app.selfId, app.selfType) From c41df69a097137485c83713288defb2ad6aad958 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 5 Dec 2021 10:26:47 +1030 Subject: [PATCH 131/410] update detlas --- src/api/course/index.ts | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index a8d42a1a8..7d186618c 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,23 +1,15 @@ -import { - ResourceProvider, - ResourceProviderMethods, - SignalKResourceType -} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' -import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' - -const API_METHODS: string[] = [] interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any + registerPutHandler: (context:string, path:string, cb:any) => any resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -35,7 +27,6 @@ interface Destination extends DestinationBase { } type?: string } - interface ActiveRoute extends DestinationBase { pointIndex?: number reverse?: boolean @@ -111,12 +102,26 @@ export class CourseApi { } ) + // + if(this.server.registerPutHandler) { + debug('** Registering PUT Action Handler(s) **') + this.server.registerPutHandler( + 'vessels.self', + 'navigation.course.*', + this.handleCourseApiPut + ); + } + // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { + //test for active destination + if (!this.courseInfo.nextPoint.position) { + return + } // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -270,6 +275,12 @@ export class CourseApi { ) } + private handleCourseApiPut(context:string, path:string, value:any, cb:any) { + + debug('** PUT handler **') + return undefined + } + private async activateRoute(route: ActiveRoute): Promise { let rte: any @@ -474,6 +485,11 @@ export class CourseApi { 'navigation.courseRhumbline' ] + values.push({ + path: `navigation.course`, + value: this.courseInfo + }) + values.push({ path: `${root[0]}.activeRoute.href`, value: this.courseInfo.activeRoute.href From 916fa95587aa0cdbdfa08ff6cce3d0ffc8b45371 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 4 Dec 2021 17:12:14 +1030 Subject: [PATCH 132/410] init courseApi --- src/api/course/index.ts | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 7d186618c..d3d42fed7 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,15 +1,26 @@ + +import { + ResourceProvider, + ResourceProviderMethods, + SignalKResourceType +} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' +import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' + +const API_METHODS: string[] = [] + interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - registerPutHandler: (context:string, path:string, cb:any) => any + resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -102,26 +113,12 @@ export class CourseApi { } ) - // - if(this.server.registerPutHandler) { - debug('** Registering PUT Action Handler(s) **') - this.server.registerPutHandler( - 'vessels.self', - 'navigation.course.*', - this.handleCourseApiPut - ); - } - // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { - //test for active destination - if (!this.courseInfo.nextPoint.position) { - return - } // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -275,12 +272,6 @@ export class CourseApi { ) } - private handleCourseApiPut(context:string, path:string, value:any, cb:any) { - - debug('** PUT handler **') - return undefined - } - private async activateRoute(route: ActiveRoute): Promise { let rte: any From 574628fa900e7be7e392e5ce21078f37270b973b Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 5 Dec 2021 10:26:47 +1030 Subject: [PATCH 133/410] update detlas --- src/api/course/index.ts | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index d3d42fed7..7d186618c 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,26 +1,15 @@ - -import { - ResourceProvider, - ResourceProviderMethods, - SignalKResourceType -} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' -import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' - -const API_METHODS: string[] = [] - interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - + registerPutHandler: (context:string, path:string, cb:any) => any resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -113,12 +102,26 @@ export class CourseApi { } ) + // + if(this.server.registerPutHandler) { + debug('** Registering PUT Action Handler(s) **') + this.server.registerPutHandler( + 'vessels.self', + 'navigation.course.*', + this.handleCourseApiPut + ); + } + // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { + //test for active destination + if (!this.courseInfo.nextPoint.position) { + return + } // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -272,6 +275,12 @@ export class CourseApi { ) } + private handleCourseApiPut(context:string, path:string, value:any, cb:any) { + + debug('** PUT handler **') + return undefined + } + private async activateRoute(route: ActiveRoute): Promise { let rte: any From cb23cccf195b7de980a30357a91bbf79ca0d427e Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 6 Dec 2021 08:39:56 +1030 Subject: [PATCH 134/410] enable put processing --- src/api/course/index.ts | 136 ++++++++++++++++++++++++---------------- src/put.js | 7 +++ 2 files changed, 89 insertions(+), 54 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 7d186618c..1566ecdf3 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -9,7 +9,14 @@ const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/cou interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - registerPutHandler: (context:string, path:string, cb:any) => any + securityStrategy: { + shouldAllowPut: ( + req: any, + context: string, + source: any, + path: string + ) => boolean + } resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -92,6 +99,15 @@ export class CourseApi { this.initResourceRoutes() } + private updateAllowed(): boolean { + return this.server.securityStrategy.shouldAllowPut( + this.server, + 'vessels.self', + null, + 'resources' + ) + } + private initResourceRoutes() { // return current course information this.server.get( @@ -102,43 +118,43 @@ export class CourseApi { } ) - // - if(this.server.registerPutHandler) { - debug('** Registering PUT Action Handler(s) **') - this.server.registerPutHandler( - 'vessels.self', - 'navigation.course.*', - this.handleCourseApiPut - ); - } + this.server.put( + `${COURSE_API_PATH}/restart`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/restart`) + if (!this.updateAllowed()) { + res.status(403) + return + } + if (!this.courseInfo.nextPoint.position) { + res.status(406).send(`No active destination!`) + return + } + // set previousPoint to vessel position + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.emitCourseInfo() + res.status(200) + } else { + res.status(406).send(`Vessel position unavailable!`) + } + } + ) - // restart / arrivalCircle this.server.put( - `${COURSE_API_PATH}/:action`, + `${COURSE_API_PATH}/arrivalCircle`, async (req: Request, res: Response) => { - debug(`** PUT ${COURSE_API_PATH}/:action`) - if (req.params.restart) { - //test for active destination - if (!this.courseInfo.nextPoint.position) { - return - } - // set previousPoint to vessel position - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.emitCourseInfo() - res.status(200).send(`Course restarted.`) - } else { - res.status(406).send(`Vessel position unavailable!`) - } + debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) + if (!this.updateAllowed()) { + res.status(403) + return } - if (req.params.arrivalCircle) { - if (this.setArrivalCircle(req.params.arrivalCircle)) { - this.emitCourseInfo() - res.status(200).send(`Destination set successfully.`) - } else { - res.status(406).send(`Invalid Data`) - } + if (this.setArrivalCircle(req.body.value)) { + this.emitCourseInfo() + res.status(200) + } else { + res.status(406).send(`Invalid Data`) } } ) @@ -148,7 +164,10 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) - + if (!this.updateAllowed()) { + res.status(403) + return + } if (!req.body.value) { res.status(406).send(`Invalid Data`) return @@ -156,7 +175,7 @@ export class CourseApi { const result = await this.setDestination(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send(`Destination set successfully.`) + res.status(200) } else { this.clearDestination() this.emitCourseInfo() @@ -170,22 +189,29 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) + if (!this.updateAllowed()) { + res.status(403) + return + } this.clearDestination() this.emitCourseInfo() - res.status(200).send(`Destination cleared.`) + res.status(200) } ) - // set / clear activeRoute + // set activeRoute this.server.put( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) - + if (!this.updateAllowed()) { + res.status(403) + return + } const result = await this.activateRoute(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send(`Active route set.`) + res.status(200) } else { this.clearDestination() this.emitCourseInfo() @@ -193,14 +219,19 @@ export class CourseApi { } } ) + + // clear activeRoute /destination this.server.delete( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/activeRoute`) - + if (!this.updateAllowed()) { + res.status(403) + return + } this.clearDestination() this.emitCourseInfo() - res.status(200).send(`Active route cleared.`) + res.status(200) } ) @@ -208,19 +239,22 @@ export class CourseApi { `${COURSE_API_PATH}/activeRoute/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) - + if (!this.updateAllowed()) { + res.status(403) + return + } + // fetch route data if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return } - const rte = await this.getRoute(this.courseInfo.activeRoute.href) if (!rte) { res.status(406).send(`Invalid Data`) return } - if (req.params.nextPoint) { + if (req.params.action === 'nextPoint') { if ( typeof req.body.value === 'number' && (req.body.value === 1 || req.body.value === -1) @@ -233,7 +267,7 @@ export class CourseApi { res.status(406).send(`Invalid Data`) } } - if (req.params.pointIndex) { + if (req.params.action === 'pointIndex') { if (typeof req.body.value === 'number') { this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( req.body.value, @@ -244,7 +278,7 @@ export class CourseApi { } } - // set nextPoint + // set new destination this.courseInfo.nextPoint.position = this.getRoutePoint( rte, this.courseInfo.activeRoute.pointIndex @@ -270,17 +304,11 @@ export class CourseApi { } this.courseInfo.previousPoint.href = null - res.status(200).send(`OK`) + res.status(200) } ) } - private handleCourseApiPut(context:string, path:string, value:any, cb:any) { - - debug('** PUT handler **') - return undefined - } - private async activateRoute(route: ActiveRoute): Promise { let rte: any diff --git a/src/put.js b/src/put.js index e6e14a965..c7cf42616 100644 --- a/src/put.js +++ b/src/put.js @@ -35,6 +35,13 @@ module.exports = { next() return } + + // ** ignore course paths ** + if (req.path.indexOf('/navigation/course') !== -1) { + next() + return + } + let path = String(req.path).replace(apiPathPrefix, '') const value = req.body From 2020c2011c5edf78c72882755fcc53df7ace58b4 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 6 Dec 2021 13:34:31 +1030 Subject: [PATCH 135/410] chore: lint --- src/api/course/index.ts | 211 ++++++++++++++++++++---------------- src/api/course/openApi.json | 72 +----------- 2 files changed, 120 insertions(+), 163 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 1566ecdf3..6cd3b80fd 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -11,7 +11,7 @@ interface CourseApplication extends Application { getSelfPath: (path: string) => any securityStrategy: { shouldAllowPut: ( - req: any, + req: Application, context: string, source: any, path: string @@ -104,7 +104,7 @@ export class CourseApi { this.server, 'vessels.self', null, - 'resources' + 'navigation.course' ) } @@ -131,12 +131,14 @@ export class CourseApi { return } // set previousPoint to vessel position - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.emitCourseInfo() - res.status(200) - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.emitCourseInfo() + res.status(200) + } + } catch (err) { res.status(406).send(`Vessel position unavailable!`) } } @@ -150,7 +152,8 @@ export class CourseApi { res.status(403) return } - if (this.setArrivalCircle(req.body.value)) { + if (this.isValidArrivalCircle(req.body.value)) { + this.courseInfo.nextPoint.arrivalCircle = req.body.value this.emitCourseInfo() res.status(200) } else { @@ -243,7 +246,7 @@ export class CourseApi { res.status(403) return } - // fetch route data + // fetch active route data if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return @@ -281,24 +284,30 @@ export class CourseApi { // set new destination this.courseInfo.nextPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex + this.courseInfo.activeRoute.pointIndex, + this.courseInfo.activeRoute.reverse ) this.courseInfo.nextPoint.type = `RoutePoint` this.courseInfo.nextPoint.href = null // set previousPoint if (this.courseInfo.activeRoute.pointIndex === 0) { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } catch (err) { return false } } else { this.courseInfo.previousPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex - 1 + this.courseInfo.activeRoute.pointIndex - 1, + this.courseInfo.activeRoute.reverse ) this.courseInfo.previousPoint.type = `RoutePoint` } @@ -324,81 +333,97 @@ export class CourseApi { return false } + const newDest: any = {} + Object.assign(newDest, this.courseInfo) + // set activeroute - this.courseInfo.activeRoute.href = route.href + newDest.activeRoute.href = route.href - if (typeof route.arrivalCircle === 'number') { - this.setArrivalCircle(route.arrivalCircle) + if (this.isValidArrivalCircle(route.arrivalCircle as number)) { + newDest.nextPoint.arrivalCircle = route.arrivalCircle } - this.courseInfo.activeRoute.reverse = - typeof route.reverse === 'boolean' ? route.reverse : false + newDest.activeRoute.startTime = new Date().toISOString() - this.courseInfo.activeRoute.pointIndex = - typeof route.pointIndex === 'number' - ? this.parsePointIndex(route.pointIndex, rte) - : 0 + if (typeof route.reverse === 'boolean') { + newDest.activeRoute.reverse = route.reverse + } - this.courseInfo.activeRoute.startTime = new Date().toISOString() + newDest.activeRoute.pointIndex = this.parsePointIndex( + route.pointIndex as number, + rte + ) // set nextPoint - this.courseInfo.nextPoint.position = this.getRoutePoint( + newDest.nextPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex + newDest.activeRoute.pointIndex, + newDest.activeRoute.reverse ) - this.courseInfo.nextPoint.type = `RoutePoint` - this.courseInfo.nextPoint.href = null + newDest.nextPoint.type = `RoutePoint` + newDest.nextPoint.href = null // set previousPoint - if (this.courseInfo.activeRoute.pointIndex === 0) { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + if (newDest.activeRoute.pointIndex === 0) { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } catch (err) { return false } } else { - this.courseInfo.previousPoint.position = this.getRoutePoint( + newDest.previousPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex - 1 + newDest.activeRoute.pointIndex - 1, + newDest.activeRoute.reverse ) - this.courseInfo.previousPoint.type = `RoutePoint` + newDest.previousPoint.type = `RoutePoint` } - this.courseInfo.previousPoint.href = null + newDest.previousPoint.href = null + this.courseInfo = newDest return true } - private async setDestination(dest: Destination): Promise { + private async setDestination(dest: any): Promise { + const newDest: any = {} + Object.assign(newDest, this.courseInfo) + // set nextPoint - if (typeof dest.arrivalCircle === 'number') { - this.setArrivalCircle(dest.arrivalCircle) + if (this.isValidArrivalCircle(dest.arrivalCircle)) { + newDest.nextPoint.arrivalCircle = dest.arrivalCircle } - this.courseInfo.nextPoint.type = - typeof dest.type !== 'undefined' ? dest.type : null + newDest.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - this.courseInfo.nextPoint.href = dest.href + newDest.href = dest.href const href = this.parseHref(dest.href) if (href) { + // fetch waypoint resource details try { const r = await this.server.resourcesApi.getResource( href.type, href.id ) - if (r.position && typeof r.position.value?.latitude !== 'undefined') { - this.courseInfo.nextPoint.position = r.position.value + if (r.position && typeof r.position?.latitude !== 'undefined') { + newDest.nextPoint.position = r.position + } else { + return false } } catch (err) { return false } } } else if (dest.position) { - this.courseInfo.nextPoint.href = null + newDest.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { - this.courseInfo.nextPoint.position = dest.position + newDest.nextPoint.position = dest.position } else { return false } @@ -407,15 +432,20 @@ export class CourseApi { } // set previousPoint - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + newDest.previousPoint.position = position.value + newDest.previousPoint.type = `VesselPosition` + } else { + return false + } + newDest.previousPoint.href = null + } catch (err) { return false } - this.courseInfo.previousPoint.href = null + this.courseInfo = newDest return true } @@ -432,17 +462,12 @@ export class CourseApi { this.courseInfo.previousPoint.position = null } - private setArrivalCircle(value: any): boolean { - if (typeof value === 'number' && value >= 0) { - this.courseInfo.nextPoint.arrivalCircle = value - return true - } else { - return false - } + private isValidArrivalCircle(value: number): boolean { + return typeof value === 'number' && value >= 0 } private parsePointIndex(index: number, rte: any): number { - if (!rte) { + if (typeof index !== 'number' || !rte) { return 0 } if (!rte.feature?.geometry?.coordinates) { @@ -461,13 +486,11 @@ export class CourseApi { } private parseHref(href: string): { type: string; id: string } | undefined { - if (href.length === 0) { + if (!href) { return undefined } - if (href[0] === '/') { - href = href.slice(1) - } - const ref: string[] = href.split('/') + + const ref: string[] = href.split('/').slice(-3) if (ref.length < 3) { return undefined } @@ -480,8 +503,8 @@ export class CourseApi { } } - private getRoutePoint(rte: any, index: number) { - const pos = this.courseInfo.activeRoute.reverse + private getRoutePoint(rte: any, index: number, reverse: boolean) { + const pos = reverse ? rte.feature.geometry.coordinates[ rte.feature.geometry.coordinates.length - (index + 1) ] @@ -489,7 +512,7 @@ export class CourseApi { return { latitude: pos[1], longitude: pos[0], - altitude: pos.length == 3 ? pos[2] : 0 + altitude: pos.length === 3 ? pos[2] : 0 } } @@ -507,8 +530,8 @@ export class CourseApi { } private buildDeltaMsg(): any { - let values: Array<{path:string, value:any}> = [] - let root = [ + const values: Array<{ path: string; value: any }> = [] + const navPath = [ 'navigation.courseGreatCircle', 'navigation.courseRhumbline' ] @@ -519,82 +542,82 @@ export class CourseApi { }) values.push({ - path: `${root[0]}.activeRoute.href`, + path: `${navPath[0]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${root[1]}.activeRoute.href`, + path: `${navPath[1]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${root[0]}.activeRoute.startTime`, + path: `${navPath[0]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${root[1]}.activeRoute.startTime`, + path: `${navPath[1]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${root[0]}.nextPoint.href`, + path: `${navPath[0]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${root[1]}.nextPoint.href`, + path: `${navPath[1]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${root[0]}.nextPoint.position`, + path: `${navPath[0]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${root[1]}.nextPoint.position`, + path: `${navPath[1]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${root[0]}.nextPoint.type`, + path: `${navPath[0]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${root[1]}.nextPoint.type`, + path: `${navPath[1]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${root[0]}.nextPoint.arrivalCircle`, + path: `${navPath[0]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${root[1]}.nextPoint.arrivalCircle`, + path: `${navPath[1]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${root[0]}.previousPoint.href`, + path: `${navPath[0]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${root[1]}.previousPoint.href`, + path: `${navPath[1]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${root[0]}.previousPoint.position`, + path: `${navPath[0]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${root[1]}.previousPoint.position`, + path: `${navPath[1]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${root[0]}.previousPoint.type`, + path: `${navPath[0]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) values.push({ - path: `${root[1]}.previousPoint.type`, + path: `${navPath[1]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) return { updates: [ { - values: values + values } ] } diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index 04653db5e..b162d1314 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -108,29 +108,7 @@ "put": { "tags": ["course"], "summary": "Restart course calculations", - "description": "Sets previousPoint value to current vessel location", - "requestBody": { - "description": "Restart payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } - } - } - } - } - } + "description": "Sets previousPoint value to current vessel location" } }, @@ -200,29 +178,7 @@ "delete": { "tags": ["course/destination"], "summary": "Clear destination", - "description": "Sets activeRoute, nextPoint & previousPoint values to null", - "requestBody": { - "description": "Delete payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } - } - } - } - } - } + "description": "Sets activeRoute, nextPoint & previousPoint values to null" } }, @@ -313,29 +269,7 @@ "delete": { "tags": ["course/activeRoute"], "summary": "Clear active route", - "description": "Sets activeRoute, nextPoint & previousPoint values to null", - "requestBody": { - "description": "Delete payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } - } - } - } - } - } + "description": "Sets activeRoute, nextPoint & previousPoint values to null" } }, From 5dbbf6e6a9498b9b57517a3ae7d431faa30e3c1d Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 8 Dec 2021 09:27:06 +1030 Subject: [PATCH 136/410] add 30sec delta interval --- src/api/course/index.ts | 100 ++++++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 45 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 6cd3b80fd..2aa57e8a6 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -6,6 +6,8 @@ const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const DELTA_INTERVAL: number = 30000 + interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any @@ -97,6 +99,11 @@ export class CourseApi { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app this.initResourceRoutes() + setInterval( ()=> { + if(this.courseInfo.nextPoint.position) { + this.emitCourseInfo() + } + }, DELTA_INTERVAL) } private updateAllowed(): boolean { @@ -123,7 +130,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/restart`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (!this.courseInfo.nextPoint.position) { @@ -136,7 +143,7 @@ export class CourseApi { if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } } catch (err) { res.status(406).send(`Vessel position unavailable!`) @@ -149,13 +156,13 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (this.isValidArrivalCircle(req.body.value)) { this.courseInfo.nextPoint.arrivalCircle = req.body.value this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { res.status(406).send(`Invalid Data`) } @@ -168,7 +175,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (!req.body.value) { @@ -178,7 +185,7 @@ export class CourseApi { const result = await this.setDestination(req.body.value) if (result) { this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { this.clearDestination() this.emitCourseInfo() @@ -193,12 +200,12 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } this.clearDestination() this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } ) @@ -208,13 +215,13 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } const result = await this.activateRoute(req.body.value) if (result) { this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { this.clearDestination() this.emitCourseInfo() @@ -234,7 +241,7 @@ export class CourseApi { } this.clearDestination() this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } ) @@ -243,7 +250,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } // fetch active route data @@ -268,6 +275,7 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) + return } } if (req.params.action === 'pointIndex') { @@ -278,6 +286,7 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) + return } } @@ -298,9 +307,11 @@ export class CourseApi { this.courseInfo.previousPoint.position = position.value this.courseInfo.previousPoint.type = `VesselPosition` } else { + res.status(406).send(`Invalid Data`) return false } } catch (err) { + res.status(406).send(`Invalid Data`) return false } } else { @@ -312,8 +323,7 @@ export class CourseApi { this.courseInfo.previousPoint.type = `RoutePoint` } this.courseInfo.previousPoint.href = null - - res.status(200) + res.status(200).send('OK') } ) } @@ -333,38 +343,38 @@ export class CourseApi { return false } - const newDest: any = {} - Object.assign(newDest, this.courseInfo) + const newCourse: any = {} + Object.assign(newCourse, this.courseInfo) // set activeroute - newDest.activeRoute.href = route.href + newCourse.activeRoute.href = route.href if (this.isValidArrivalCircle(route.arrivalCircle as number)) { - newDest.nextPoint.arrivalCircle = route.arrivalCircle + newCourse.nextPoint.arrivalCircle = route.arrivalCircle } - newDest.activeRoute.startTime = new Date().toISOString() + newCourse.activeRoute.startTime = new Date().toISOString() if (typeof route.reverse === 'boolean') { - newDest.activeRoute.reverse = route.reverse + newCourse.activeRoute.reverse = route.reverse } - newDest.activeRoute.pointIndex = this.parsePointIndex( + newCourse.activeRoute.pointIndex = this.parsePointIndex( route.pointIndex as number, rte ) // set nextPoint - newDest.nextPoint.position = this.getRoutePoint( + newCourse.nextPoint.position = this.getRoutePoint( rte, - newDest.activeRoute.pointIndex, - newDest.activeRoute.reverse + newCourse.activeRoute.pointIndex, + newCourse.activeRoute.reverse ) - newDest.nextPoint.type = `RoutePoint` - newDest.nextPoint.href = null + newCourse.nextPoint.type = `RoutePoint` + newCourse.nextPoint.href = null // set previousPoint - if (newDest.activeRoute.pointIndex === 0) { + if (newCourse.activeRoute.pointIndex === 0) { try { const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -377,32 +387,32 @@ export class CourseApi { return false } } else { - newDest.previousPoint.position = this.getRoutePoint( + newCourse.previousPoint.position = this.getRoutePoint( rte, - newDest.activeRoute.pointIndex - 1, - newDest.activeRoute.reverse + newCourse.activeRoute.pointIndex - 1, + newCourse.activeRoute.reverse ) - newDest.previousPoint.type = `RoutePoint` + newCourse.previousPoint.type = `RoutePoint` } - newDest.previousPoint.href = null + newCourse.previousPoint.href = null - this.courseInfo = newDest + this.courseInfo = newCourse return true } private async setDestination(dest: any): Promise { - const newDest: any = {} - Object.assign(newDest, this.courseInfo) + const newCourse: any = {} + Object.assign(newCourse, this.courseInfo) // set nextPoint if (this.isValidArrivalCircle(dest.arrivalCircle)) { - newDest.nextPoint.arrivalCircle = dest.arrivalCircle + newCourse.nextPoint.arrivalCircle = dest.arrivalCircle } - newDest.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null + newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - newDest.href = dest.href + newCourse.href = dest.href const href = this.parseHref(dest.href) if (href) { // fetch waypoint resource details @@ -412,7 +422,7 @@ export class CourseApi { href.id ) if (r.position && typeof r.position?.latitude !== 'undefined') { - newDest.nextPoint.position = r.position + newCourse.nextPoint.position = r.position } else { return false } @@ -421,9 +431,9 @@ export class CourseApi { } } } else if (dest.position) { - newDest.nextPoint.href = null + newCourse.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { - newDest.nextPoint.position = dest.position + newCourse.nextPoint.position = dest.position } else { return false } @@ -435,17 +445,17 @@ export class CourseApi { try { const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { - newDest.previousPoint.position = position.value - newDest.previousPoint.type = `VesselPosition` + newCourse.previousPoint.position = position.value + newCourse.previousPoint.type = `VesselPosition` } else { return false } - newDest.previousPoint.href = null + newCourse.previousPoint.href = null } catch (err) { return false } - this.courseInfo = newDest + this.courseInfo = newCourse return true } From 2b755c2b0cf5e38a2d73854baa871a766549f6ee Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 12 Dec 2021 14:29:38 +1030 Subject: [PATCH 137/410] chore: lint --- src/api/course/index.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 2aa57e8a6..1e719297a 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -6,7 +6,7 @@ const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const DELTA_INTERVAL: number = 30000 +const DELTA_INTERVAL: number = 30000 interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void @@ -99,8 +99,8 @@ export class CourseApi { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app this.initResourceRoutes() - setInterval( ()=> { - if(this.courseInfo.nextPoint.position) { + setInterval(() => { + if (this.courseInfo.nextPoint.position) { this.emitCourseInfo() } }, DELTA_INTERVAL) @@ -409,7 +409,8 @@ export class CourseApi { newCourse.nextPoint.arrivalCircle = dest.arrivalCircle } - newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null + newCourse.nextPoint.type = + typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { newCourse.href = dest.href From 33f634bc6e9e7b7d4428ee455c8ab1a0fa720b89 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 4 Dec 2021 17:12:14 +1030 Subject: [PATCH 138/410] init courseApi --- src/api/course/index.ts | 318 +++++++++++++----------------------- src/api/course/openApi.json | 72 +++++++- 2 files changed, 187 insertions(+), 203 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 1e719297a..6e8e4074d 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,24 +1,23 @@ +import { + ResourceProvider, + ResourceProviderMethods, + SignalKResourceType +} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' +import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' -const DELTA_INTERVAL: number = 30000 +const API_METHODS: string[] = [] interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - securityStrategy: { - shouldAllowPut: ( - req: Application, - context: string, - source: any, - path: string - ) => boolean - } resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -99,20 +98,6 @@ export class CourseApi { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app this.initResourceRoutes() - setInterval(() => { - if (this.courseInfo.nextPoint.position) { - this.emitCourseInfo() - } - }, DELTA_INTERVAL) - } - - private updateAllowed(): boolean { - return this.server.securityStrategy.shouldAllowPut( - this.server, - 'vessels.self', - null, - 'navigation.course' - ) } private initResourceRoutes() { @@ -125,46 +110,29 @@ export class CourseApi { } ) + // restart / arrivalCircle this.server.put( - `${COURSE_API_PATH}/restart`, + `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { - debug(`** PUT ${COURSE_API_PATH}/restart`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } - if (!this.courseInfo.nextPoint.position) { - res.status(406).send(`No active destination!`) - return - } - // set previousPoint to vessel position - try { + debug(`** PUT ${COURSE_API_PATH}/:action`) + if (req.params.restart) { + // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).send(`Course restarted.`) + } else { + res.status(406).send(`Vessel position unavailable!`) } - } catch (err) { - res.status(406).send(`Vessel position unavailable!`) - } - } - ) - - this.server.put( - `${COURSE_API_PATH}/arrivalCircle`, - async (req: Request, res: Response) => { - debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return } - if (this.isValidArrivalCircle(req.body.value)) { - this.courseInfo.nextPoint.arrivalCircle = req.body.value - this.emitCourseInfo() - res.status(200).send('OK') - } else { - res.status(406).send(`Invalid Data`) + if (req.params.arrivalCircle) { + if (this.setArrivalCircle(req.params.arrivalCircle)) { + this.emitCourseInfo() + res.status(200).send(`Destination set successfully.`) + } else { + res.status(406).send(`Invalid Data`) + } } } ) @@ -174,10 +142,6 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } if (!req.body.value) { res.status(406).send(`Invalid Data`) return @@ -185,7 +149,7 @@ export class CourseApi { const result = await this.setDestination(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).send(`Destination set successfully.`) } else { this.clearDestination() this.emitCourseInfo() @@ -199,29 +163,22 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } this.clearDestination() this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).send(`Destination cleared.`) } ) - // set activeRoute + // set / clear activeRoute this.server.put( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } + const result = await this.activateRoute(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).send(`Active route set.`) } else { this.clearDestination() this.emitCourseInfo() @@ -229,19 +186,14 @@ export class CourseApi { } } ) - - // clear activeRoute /destination this.server.delete( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/activeRoute`) - if (!this.updateAllowed()) { - res.status(403) - return - } + this.clearDestination() this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).send(`Active route cleared.`) } ) @@ -249,11 +201,6 @@ export class CourseApi { `${COURSE_API_PATH}/activeRoute/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } - // fetch active route data if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return @@ -264,7 +211,7 @@ export class CourseApi { return } - if (req.params.action === 'nextPoint') { + if (req.params.nextPoint) { if ( typeof req.body.value === 'number' && (req.body.value === 1 || req.body.value === -1) @@ -275,10 +222,9 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) - return } } - if (req.params.action === 'pointIndex') { + if (req.params.pointIndex) { if (typeof req.body.value === 'number') { this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( req.body.value, @@ -286,44 +232,36 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) - return } } - // set new destination + // set nextPoint this.courseInfo.nextPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex, - this.courseInfo.activeRoute.reverse + this.courseInfo.activeRoute.pointIndex ) this.courseInfo.nextPoint.type = `RoutePoint` this.courseInfo.nextPoint.href = null // set previousPoint if (this.courseInfo.activeRoute.pointIndex === 0) { - try { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { - res.status(406).send(`Invalid Data`) - return false - } - } catch (err) { - res.status(406).send(`Invalid Data`) + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { return false } } else { this.courseInfo.previousPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex - 1, - this.courseInfo.activeRoute.reverse + this.courseInfo.activeRoute.pointIndex - 1 ) this.courseInfo.previousPoint.type = `RoutePoint` } this.courseInfo.previousPoint.href = null - res.status(200).send('OK') + + res.status(200).send(`OK`) } ) } @@ -343,98 +281,81 @@ export class CourseApi { return false } - const newCourse: any = {} - Object.assign(newCourse, this.courseInfo) - // set activeroute - newCourse.activeRoute.href = route.href + this.courseInfo.activeRoute.href = route.href - if (this.isValidArrivalCircle(route.arrivalCircle as number)) { - newCourse.nextPoint.arrivalCircle = route.arrivalCircle + if (typeof route.arrivalCircle === 'number') { + this.setArrivalCircle(route.arrivalCircle) } - newCourse.activeRoute.startTime = new Date().toISOString() + this.courseInfo.activeRoute.reverse = + typeof route.reverse === 'boolean' ? route.reverse : false - if (typeof route.reverse === 'boolean') { - newCourse.activeRoute.reverse = route.reverse - } + this.courseInfo.activeRoute.pointIndex = + typeof route.pointIndex === 'number' + ? this.parsePointIndex(route.pointIndex, rte) + : 0 - newCourse.activeRoute.pointIndex = this.parsePointIndex( - route.pointIndex as number, - rte - ) + this.courseInfo.activeRoute.startTime = new Date().toISOString() // set nextPoint - newCourse.nextPoint.position = this.getRoutePoint( + this.courseInfo.nextPoint.position = this.getRoutePoint( rte, - newCourse.activeRoute.pointIndex, - newCourse.activeRoute.reverse + this.courseInfo.activeRoute.pointIndex ) - newCourse.nextPoint.type = `RoutePoint` - newCourse.nextPoint.href = null + this.courseInfo.nextPoint.type = `RoutePoint` + this.courseInfo.nextPoint.href = null // set previousPoint - if (newCourse.activeRoute.pointIndex === 0) { - try { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { - return false - } - } catch (err) { + if (this.courseInfo.activeRoute.pointIndex === 0) { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { return false } } else { - newCourse.previousPoint.position = this.getRoutePoint( + this.courseInfo.previousPoint.position = this.getRoutePoint( rte, - newCourse.activeRoute.pointIndex - 1, - newCourse.activeRoute.reverse + this.courseInfo.activeRoute.pointIndex - 1 ) - newCourse.previousPoint.type = `RoutePoint` + this.courseInfo.previousPoint.type = `RoutePoint` } - newCourse.previousPoint.href = null + this.courseInfo.previousPoint.href = null - this.courseInfo = newCourse return true } - private async setDestination(dest: any): Promise { - const newCourse: any = {} - Object.assign(newCourse, this.courseInfo) - + private async setDestination(dest: Destination): Promise { // set nextPoint - if (this.isValidArrivalCircle(dest.arrivalCircle)) { - newCourse.nextPoint.arrivalCircle = dest.arrivalCircle + if (typeof dest.arrivalCircle === 'number') { + this.setArrivalCircle(dest.arrivalCircle) } - newCourse.nextPoint.type = + this.courseInfo.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - newCourse.href = dest.href + this.courseInfo.nextPoint.href = dest.href const href = this.parseHref(dest.href) if (href) { - // fetch waypoint resource details try { const r = await this.server.resourcesApi.getResource( href.type, href.id ) - if (r.position && typeof r.position?.latitude !== 'undefined') { - newCourse.nextPoint.position = r.position - } else { - return false + if (r.position && typeof r.position.value?.latitude !== 'undefined') { + this.courseInfo.nextPoint.position = r.position.value } } catch (err) { return false } } } else if (dest.position) { - newCourse.nextPoint.href = null + this.courseInfo.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { - newCourse.nextPoint.position = dest.position + this.courseInfo.nextPoint.position = dest.position } else { return false } @@ -443,20 +364,15 @@ export class CourseApi { } // set previousPoint - try { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - newCourse.previousPoint.position = position.value - newCourse.previousPoint.type = `VesselPosition` - } else { - return false - } - newCourse.previousPoint.href = null - } catch (err) { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { return false } + this.courseInfo.previousPoint.href = null - this.courseInfo = newCourse return true } @@ -473,12 +389,17 @@ export class CourseApi { this.courseInfo.previousPoint.position = null } - private isValidArrivalCircle(value: number): boolean { - return typeof value === 'number' && value >= 0 + private setArrivalCircle(value: any): boolean { + if (typeof value === 'number' && value >= 0) { + this.courseInfo.nextPoint.arrivalCircle = value + return true + } else { + return false + } } private parsePointIndex(index: number, rte: any): number { - if (typeof index !== 'number' || !rte) { + if (!rte) { return 0 } if (!rte.feature?.geometry?.coordinates) { @@ -497,11 +418,13 @@ export class CourseApi { } private parseHref(href: string): { type: string; id: string } | undefined { - if (!href) { + if (href.length === 0) { return undefined } - - const ref: string[] = href.split('/').slice(-3) + if (href[0] === '/') { + href = href.slice(1) + } + const ref: string[] = href.split('/') if (ref.length < 3) { return undefined } @@ -514,8 +437,8 @@ export class CourseApi { } } - private getRoutePoint(rte: any, index: number, reverse: boolean) { - const pos = reverse + private getRoutePoint(rte: any, index: number) { + const pos = this.courseInfo.activeRoute.reverse ? rte.feature.geometry.coordinates[ rte.feature.geometry.coordinates.length - (index + 1) ] @@ -523,7 +446,7 @@ export class CourseApi { return { latitude: pos[1], longitude: pos[0], - altitude: pos.length === 3 ? pos[2] : 0 + altitude: pos.length == 3 ? pos[2] : 0 } } @@ -541,94 +464,89 @@ export class CourseApi { } private buildDeltaMsg(): any { - const values: Array<{ path: string; value: any }> = [] - const navPath = [ + let values: Array<{path:string, value:any}> = [] + let root = [ 'navigation.courseGreatCircle', 'navigation.courseRhumbline' ] values.push({ - path: `navigation.course`, - value: this.courseInfo - }) - - values.push({ - path: `${navPath[0]}.activeRoute.href`, + path: `${root[0]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${navPath[1]}.activeRoute.href`, + path: `${root[1]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${navPath[0]}.activeRoute.startTime`, + path: `${root[0]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${navPath[1]}.activeRoute.startTime`, + path: `${root[1]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${navPath[0]}.nextPoint.href`, + path: `${root[0]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${navPath[1]}.nextPoint.href`, + path: `${root[1]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${navPath[0]}.nextPoint.position`, + path: `${root[0]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${navPath[1]}.nextPoint.position`, + path: `${root[1]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${navPath[0]}.nextPoint.type`, + path: `${root[0]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${navPath[1]}.nextPoint.type`, + path: `${root[1]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${navPath[0]}.nextPoint.arrivalCircle`, + path: `${root[0]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${navPath[1]}.nextPoint.arrivalCircle`, + path: `${root[1]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${navPath[0]}.previousPoint.href`, + path: `${root[0]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${navPath[1]}.previousPoint.href`, + path: `${root[1]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${navPath[0]}.previousPoint.position`, + path: `${root[0]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${navPath[1]}.previousPoint.position`, + path: `${root[1]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${navPath[0]}.previousPoint.type`, + path: `${root[0]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) values.push({ - path: `${navPath[1]}.previousPoint.type`, + path: `${root[1]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) return { updates: [ { - values + values: values } ] } diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index b162d1314..04653db5e 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -108,7 +108,29 @@ "put": { "tags": ["course"], "summary": "Restart course calculations", - "description": "Sets previousPoint value to current vessel location" + "description": "Sets previousPoint value to current vessel location", + "requestBody": { + "description": "Restart payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } } }, @@ -178,7 +200,29 @@ "delete": { "tags": ["course/destination"], "summary": "Clear destination", - "description": "Sets activeRoute, nextPoint & previousPoint values to null" + "description": "Sets activeRoute, nextPoint & previousPoint values to null", + "requestBody": { + "description": "Delete payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } } }, @@ -269,7 +313,29 @@ "delete": { "tags": ["course/activeRoute"], "summary": "Clear active route", - "description": "Sets activeRoute, nextPoint & previousPoint values to null" + "description": "Sets activeRoute, nextPoint & previousPoint values to null", + "requestBody": { + "description": "Delete payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } } }, From aba08176c4c74c00b75f0391a279fada405d1921 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 5 Dec 2021 10:26:47 +1030 Subject: [PATCH 139/410] update detlas --- src/api/course/index.ts | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 6e8e4074d..7510ca1e7 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,23 +1,15 @@ -import { - ResourceProvider, - ResourceProviderMethods, - SignalKResourceType -} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' -import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' - -const API_METHODS: string[] = [] interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any + registerPutHandler: (context:string, path:string, cb:any) => any resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -110,12 +102,26 @@ export class CourseApi { } ) + // + if(this.server.registerPutHandler) { + debug('** Registering PUT Action Handler(s) **') + this.server.registerPutHandler( + 'vessels.self', + 'navigation.course.*', + this.handleCourseApiPut + ); + } + // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { + //test for active destination + if (!this.courseInfo.nextPoint.position) { + return + } // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -266,6 +272,12 @@ export class CourseApi { ) } + private handleCourseApiPut(context:string, path:string, value:any, cb:any) { + + debug('** PUT handler **') + return undefined + } + private async activateRoute(route: ActiveRoute): Promise { let rte: any @@ -470,6 +482,11 @@ export class CourseApi { 'navigation.courseRhumbline' ] + values.push({ + path: `navigation.course`, + value: this.courseInfo + }) + values.push({ path: `${root[0]}.activeRoute.href`, value: this.courseInfo.activeRoute.href From fc8dd4668e6c08906aaccd12d17d9abe0ae1e48f Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 4 Dec 2021 17:12:14 +1030 Subject: [PATCH 140/410] init courseApi --- src/api/course/index.ts | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 7510ca1e7..af32aceee 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,15 +1,26 @@ + +import { + ResourceProvider, + ResourceProviderMethods, + SignalKResourceType +} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' +import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' + +const API_METHODS: string[] = [] + interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - registerPutHandler: (context:string, path:string, cb:any) => any + resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -102,26 +113,12 @@ export class CourseApi { } ) - // - if(this.server.registerPutHandler) { - debug('** Registering PUT Action Handler(s) **') - this.server.registerPutHandler( - 'vessels.self', - 'navigation.course.*', - this.handleCourseApiPut - ); - } - // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { - //test for active destination - if (!this.courseInfo.nextPoint.position) { - return - } // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -272,12 +269,6 @@ export class CourseApi { ) } - private handleCourseApiPut(context:string, path:string, value:any, cb:any) { - - debug('** PUT handler **') - return undefined - } - private async activateRoute(route: ActiveRoute): Promise { let rte: any From 3b44abab44e980506d3c84b7371a1f8b8825bd0e Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 5 Dec 2021 10:26:47 +1030 Subject: [PATCH 141/410] update detlas --- src/api/course/index.ts | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index af32aceee..7510ca1e7 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,26 +1,15 @@ - -import { - ResourceProvider, - ResourceProviderMethods, - SignalKResourceType -} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' -import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' - -const API_METHODS: string[] = [] - interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - + registerPutHandler: (context:string, path:string, cb:any) => any resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -113,12 +102,26 @@ export class CourseApi { } ) + // + if(this.server.registerPutHandler) { + debug('** Registering PUT Action Handler(s) **') + this.server.registerPutHandler( + 'vessels.self', + 'navigation.course.*', + this.handleCourseApiPut + ); + } + // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { + //test for active destination + if (!this.courseInfo.nextPoint.position) { + return + } // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -269,6 +272,12 @@ export class CourseApi { ) } + private handleCourseApiPut(context:string, path:string, value:any, cb:any) { + + debug('** PUT handler **') + return undefined + } + private async activateRoute(route: ActiveRoute): Promise { let rte: any From ccbfa6019583fb7a2c96d4bae7936749f6939ce9 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 6 Dec 2021 08:39:56 +1030 Subject: [PATCH 142/410] enable put processing --- src/api/course/index.ts | 133 +++++++++++++++++++++++++--------------- 1 file changed, 82 insertions(+), 51 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 7510ca1e7..1566ecdf3 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -9,7 +9,14 @@ const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/cou interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - registerPutHandler: (context:string, path:string, cb:any) => any + securityStrategy: { + shouldAllowPut: ( + req: any, + context: string, + source: any, + path: string + ) => boolean + } resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -92,6 +99,15 @@ export class CourseApi { this.initResourceRoutes() } + private updateAllowed(): boolean { + return this.server.securityStrategy.shouldAllowPut( + this.server, + 'vessels.self', + null, + 'resources' + ) + } + private initResourceRoutes() { // return current course information this.server.get( @@ -102,43 +118,43 @@ export class CourseApi { } ) - // - if(this.server.registerPutHandler) { - debug('** Registering PUT Action Handler(s) **') - this.server.registerPutHandler( - 'vessels.self', - 'navigation.course.*', - this.handleCourseApiPut - ); - } + this.server.put( + `${COURSE_API_PATH}/restart`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/restart`) + if (!this.updateAllowed()) { + res.status(403) + return + } + if (!this.courseInfo.nextPoint.position) { + res.status(406).send(`No active destination!`) + return + } + // set previousPoint to vessel position + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.emitCourseInfo() + res.status(200) + } else { + res.status(406).send(`Vessel position unavailable!`) + } + } + ) - // restart / arrivalCircle this.server.put( - `${COURSE_API_PATH}/:action`, + `${COURSE_API_PATH}/arrivalCircle`, async (req: Request, res: Response) => { - debug(`** PUT ${COURSE_API_PATH}/:action`) - if (req.params.restart) { - //test for active destination - if (!this.courseInfo.nextPoint.position) { - return - } - // set previousPoint to vessel position - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.emitCourseInfo() - res.status(200).send(`Course restarted.`) - } else { - res.status(406).send(`Vessel position unavailable!`) - } + debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) + if (!this.updateAllowed()) { + res.status(403) + return } - if (req.params.arrivalCircle) { - if (this.setArrivalCircle(req.params.arrivalCircle)) { - this.emitCourseInfo() - res.status(200).send(`Destination set successfully.`) - } else { - res.status(406).send(`Invalid Data`) - } + if (this.setArrivalCircle(req.body.value)) { + this.emitCourseInfo() + res.status(200) + } else { + res.status(406).send(`Invalid Data`) } } ) @@ -148,6 +164,10 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) + if (!this.updateAllowed()) { + res.status(403) + return + } if (!req.body.value) { res.status(406).send(`Invalid Data`) return @@ -155,7 +175,7 @@ export class CourseApi { const result = await this.setDestination(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send(`Destination set successfully.`) + res.status(200) } else { this.clearDestination() this.emitCourseInfo() @@ -169,22 +189,29 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) + if (!this.updateAllowed()) { + res.status(403) + return + } this.clearDestination() this.emitCourseInfo() - res.status(200).send(`Destination cleared.`) + res.status(200) } ) - // set / clear activeRoute + // set activeRoute this.server.put( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) - + if (!this.updateAllowed()) { + res.status(403) + return + } const result = await this.activateRoute(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send(`Active route set.`) + res.status(200) } else { this.clearDestination() this.emitCourseInfo() @@ -192,14 +219,19 @@ export class CourseApi { } } ) + + // clear activeRoute /destination this.server.delete( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/activeRoute`) - + if (!this.updateAllowed()) { + res.status(403) + return + } this.clearDestination() this.emitCourseInfo() - res.status(200).send(`Active route cleared.`) + res.status(200) } ) @@ -207,6 +239,11 @@ export class CourseApi { `${COURSE_API_PATH}/activeRoute/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) + if (!this.updateAllowed()) { + res.status(403) + return + } + // fetch route data if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return @@ -217,7 +254,7 @@ export class CourseApi { return } - if (req.params.nextPoint) { + if (req.params.action === 'nextPoint') { if ( typeof req.body.value === 'number' && (req.body.value === 1 || req.body.value === -1) @@ -230,7 +267,7 @@ export class CourseApi { res.status(406).send(`Invalid Data`) } } - if (req.params.pointIndex) { + if (req.params.action === 'pointIndex') { if (typeof req.body.value === 'number') { this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( req.body.value, @@ -241,7 +278,7 @@ export class CourseApi { } } - // set nextPoint + // set new destination this.courseInfo.nextPoint.position = this.getRoutePoint( rte, this.courseInfo.activeRoute.pointIndex @@ -267,17 +304,11 @@ export class CourseApi { } this.courseInfo.previousPoint.href = null - res.status(200).send(`OK`) + res.status(200) } ) } - private handleCourseApiPut(context:string, path:string, value:any, cb:any) { - - debug('** PUT handler **') - return undefined - } - private async activateRoute(route: ActiveRoute): Promise { let rte: any From a5193213554303602522a0c34e47096d989edd1c Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 6 Dec 2021 13:34:31 +1030 Subject: [PATCH 143/410] chore: lint --- src/api/course/index.ts | 211 ++++++++++++++++++++---------------- src/api/course/openApi.json | 72 +----------- 2 files changed, 120 insertions(+), 163 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 1566ecdf3..6cd3b80fd 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -11,7 +11,7 @@ interface CourseApplication extends Application { getSelfPath: (path: string) => any securityStrategy: { shouldAllowPut: ( - req: any, + req: Application, context: string, source: any, path: string @@ -104,7 +104,7 @@ export class CourseApi { this.server, 'vessels.self', null, - 'resources' + 'navigation.course' ) } @@ -131,12 +131,14 @@ export class CourseApi { return } // set previousPoint to vessel position - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.emitCourseInfo() - res.status(200) - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.emitCourseInfo() + res.status(200) + } + } catch (err) { res.status(406).send(`Vessel position unavailable!`) } } @@ -150,7 +152,8 @@ export class CourseApi { res.status(403) return } - if (this.setArrivalCircle(req.body.value)) { + if (this.isValidArrivalCircle(req.body.value)) { + this.courseInfo.nextPoint.arrivalCircle = req.body.value this.emitCourseInfo() res.status(200) } else { @@ -243,7 +246,7 @@ export class CourseApi { res.status(403) return } - // fetch route data + // fetch active route data if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return @@ -281,24 +284,30 @@ export class CourseApi { // set new destination this.courseInfo.nextPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex + this.courseInfo.activeRoute.pointIndex, + this.courseInfo.activeRoute.reverse ) this.courseInfo.nextPoint.type = `RoutePoint` this.courseInfo.nextPoint.href = null // set previousPoint if (this.courseInfo.activeRoute.pointIndex === 0) { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } catch (err) { return false } } else { this.courseInfo.previousPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex - 1 + this.courseInfo.activeRoute.pointIndex - 1, + this.courseInfo.activeRoute.reverse ) this.courseInfo.previousPoint.type = `RoutePoint` } @@ -324,81 +333,97 @@ export class CourseApi { return false } + const newDest: any = {} + Object.assign(newDest, this.courseInfo) + // set activeroute - this.courseInfo.activeRoute.href = route.href + newDest.activeRoute.href = route.href - if (typeof route.arrivalCircle === 'number') { - this.setArrivalCircle(route.arrivalCircle) + if (this.isValidArrivalCircle(route.arrivalCircle as number)) { + newDest.nextPoint.arrivalCircle = route.arrivalCircle } - this.courseInfo.activeRoute.reverse = - typeof route.reverse === 'boolean' ? route.reverse : false + newDest.activeRoute.startTime = new Date().toISOString() - this.courseInfo.activeRoute.pointIndex = - typeof route.pointIndex === 'number' - ? this.parsePointIndex(route.pointIndex, rte) - : 0 + if (typeof route.reverse === 'boolean') { + newDest.activeRoute.reverse = route.reverse + } - this.courseInfo.activeRoute.startTime = new Date().toISOString() + newDest.activeRoute.pointIndex = this.parsePointIndex( + route.pointIndex as number, + rte + ) // set nextPoint - this.courseInfo.nextPoint.position = this.getRoutePoint( + newDest.nextPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex + newDest.activeRoute.pointIndex, + newDest.activeRoute.reverse ) - this.courseInfo.nextPoint.type = `RoutePoint` - this.courseInfo.nextPoint.href = null + newDest.nextPoint.type = `RoutePoint` + newDest.nextPoint.href = null // set previousPoint - if (this.courseInfo.activeRoute.pointIndex === 0) { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + if (newDest.activeRoute.pointIndex === 0) { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } catch (err) { return false } } else { - this.courseInfo.previousPoint.position = this.getRoutePoint( + newDest.previousPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex - 1 + newDest.activeRoute.pointIndex - 1, + newDest.activeRoute.reverse ) - this.courseInfo.previousPoint.type = `RoutePoint` + newDest.previousPoint.type = `RoutePoint` } - this.courseInfo.previousPoint.href = null + newDest.previousPoint.href = null + this.courseInfo = newDest return true } - private async setDestination(dest: Destination): Promise { + private async setDestination(dest: any): Promise { + const newDest: any = {} + Object.assign(newDest, this.courseInfo) + // set nextPoint - if (typeof dest.arrivalCircle === 'number') { - this.setArrivalCircle(dest.arrivalCircle) + if (this.isValidArrivalCircle(dest.arrivalCircle)) { + newDest.nextPoint.arrivalCircle = dest.arrivalCircle } - this.courseInfo.nextPoint.type = - typeof dest.type !== 'undefined' ? dest.type : null + newDest.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - this.courseInfo.nextPoint.href = dest.href + newDest.href = dest.href const href = this.parseHref(dest.href) if (href) { + // fetch waypoint resource details try { const r = await this.server.resourcesApi.getResource( href.type, href.id ) - if (r.position && typeof r.position.value?.latitude !== 'undefined') { - this.courseInfo.nextPoint.position = r.position.value + if (r.position && typeof r.position?.latitude !== 'undefined') { + newDest.nextPoint.position = r.position + } else { + return false } } catch (err) { return false } } } else if (dest.position) { - this.courseInfo.nextPoint.href = null + newDest.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { - this.courseInfo.nextPoint.position = dest.position + newDest.nextPoint.position = dest.position } else { return false } @@ -407,15 +432,20 @@ export class CourseApi { } // set previousPoint - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + newDest.previousPoint.position = position.value + newDest.previousPoint.type = `VesselPosition` + } else { + return false + } + newDest.previousPoint.href = null + } catch (err) { return false } - this.courseInfo.previousPoint.href = null + this.courseInfo = newDest return true } @@ -432,17 +462,12 @@ export class CourseApi { this.courseInfo.previousPoint.position = null } - private setArrivalCircle(value: any): boolean { - if (typeof value === 'number' && value >= 0) { - this.courseInfo.nextPoint.arrivalCircle = value - return true - } else { - return false - } + private isValidArrivalCircle(value: number): boolean { + return typeof value === 'number' && value >= 0 } private parsePointIndex(index: number, rte: any): number { - if (!rte) { + if (typeof index !== 'number' || !rte) { return 0 } if (!rte.feature?.geometry?.coordinates) { @@ -461,13 +486,11 @@ export class CourseApi { } private parseHref(href: string): { type: string; id: string } | undefined { - if (href.length === 0) { + if (!href) { return undefined } - if (href[0] === '/') { - href = href.slice(1) - } - const ref: string[] = href.split('/') + + const ref: string[] = href.split('/').slice(-3) if (ref.length < 3) { return undefined } @@ -480,8 +503,8 @@ export class CourseApi { } } - private getRoutePoint(rte: any, index: number) { - const pos = this.courseInfo.activeRoute.reverse + private getRoutePoint(rte: any, index: number, reverse: boolean) { + const pos = reverse ? rte.feature.geometry.coordinates[ rte.feature.geometry.coordinates.length - (index + 1) ] @@ -489,7 +512,7 @@ export class CourseApi { return { latitude: pos[1], longitude: pos[0], - altitude: pos.length == 3 ? pos[2] : 0 + altitude: pos.length === 3 ? pos[2] : 0 } } @@ -507,8 +530,8 @@ export class CourseApi { } private buildDeltaMsg(): any { - let values: Array<{path:string, value:any}> = [] - let root = [ + const values: Array<{ path: string; value: any }> = [] + const navPath = [ 'navigation.courseGreatCircle', 'navigation.courseRhumbline' ] @@ -519,82 +542,82 @@ export class CourseApi { }) values.push({ - path: `${root[0]}.activeRoute.href`, + path: `${navPath[0]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${root[1]}.activeRoute.href`, + path: `${navPath[1]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${root[0]}.activeRoute.startTime`, + path: `${navPath[0]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${root[1]}.activeRoute.startTime`, + path: `${navPath[1]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${root[0]}.nextPoint.href`, + path: `${navPath[0]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${root[1]}.nextPoint.href`, + path: `${navPath[1]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${root[0]}.nextPoint.position`, + path: `${navPath[0]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${root[1]}.nextPoint.position`, + path: `${navPath[1]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${root[0]}.nextPoint.type`, + path: `${navPath[0]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${root[1]}.nextPoint.type`, + path: `${navPath[1]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${root[0]}.nextPoint.arrivalCircle`, + path: `${navPath[0]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${root[1]}.nextPoint.arrivalCircle`, + path: `${navPath[1]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${root[0]}.previousPoint.href`, + path: `${navPath[0]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${root[1]}.previousPoint.href`, + path: `${navPath[1]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${root[0]}.previousPoint.position`, + path: `${navPath[0]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${root[1]}.previousPoint.position`, + path: `${navPath[1]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${root[0]}.previousPoint.type`, + path: `${navPath[0]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) values.push({ - path: `${root[1]}.previousPoint.type`, + path: `${navPath[1]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) return { updates: [ { - values: values + values } ] } diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index 04653db5e..b162d1314 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -108,29 +108,7 @@ "put": { "tags": ["course"], "summary": "Restart course calculations", - "description": "Sets previousPoint value to current vessel location", - "requestBody": { - "description": "Restart payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } - } - } - } - } - } + "description": "Sets previousPoint value to current vessel location" } }, @@ -200,29 +178,7 @@ "delete": { "tags": ["course/destination"], "summary": "Clear destination", - "description": "Sets activeRoute, nextPoint & previousPoint values to null", - "requestBody": { - "description": "Delete payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } - } - } - } - } - } + "description": "Sets activeRoute, nextPoint & previousPoint values to null" } }, @@ -313,29 +269,7 @@ "delete": { "tags": ["course/activeRoute"], "summary": "Clear active route", - "description": "Sets activeRoute, nextPoint & previousPoint values to null", - "requestBody": { - "description": "Delete payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } - } - } - } - } - } + "description": "Sets activeRoute, nextPoint & previousPoint values to null" } }, From 860cc655d67397e4715c5f1f06f4b64f1c956e3b Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 8 Dec 2021 09:27:06 +1030 Subject: [PATCH 144/410] add 30sec delta interval --- src/api/course/index.ts | 100 ++++++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 45 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 6cd3b80fd..2aa57e8a6 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -6,6 +6,8 @@ const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const DELTA_INTERVAL: number = 30000 + interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any @@ -97,6 +99,11 @@ export class CourseApi { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app this.initResourceRoutes() + setInterval( ()=> { + if(this.courseInfo.nextPoint.position) { + this.emitCourseInfo() + } + }, DELTA_INTERVAL) } private updateAllowed(): boolean { @@ -123,7 +130,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/restart`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (!this.courseInfo.nextPoint.position) { @@ -136,7 +143,7 @@ export class CourseApi { if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } } catch (err) { res.status(406).send(`Vessel position unavailable!`) @@ -149,13 +156,13 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (this.isValidArrivalCircle(req.body.value)) { this.courseInfo.nextPoint.arrivalCircle = req.body.value this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { res.status(406).send(`Invalid Data`) } @@ -168,7 +175,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (!req.body.value) { @@ -178,7 +185,7 @@ export class CourseApi { const result = await this.setDestination(req.body.value) if (result) { this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { this.clearDestination() this.emitCourseInfo() @@ -193,12 +200,12 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } this.clearDestination() this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } ) @@ -208,13 +215,13 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } const result = await this.activateRoute(req.body.value) if (result) { this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { this.clearDestination() this.emitCourseInfo() @@ -234,7 +241,7 @@ export class CourseApi { } this.clearDestination() this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } ) @@ -243,7 +250,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } // fetch active route data @@ -268,6 +275,7 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) + return } } if (req.params.action === 'pointIndex') { @@ -278,6 +286,7 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) + return } } @@ -298,9 +307,11 @@ export class CourseApi { this.courseInfo.previousPoint.position = position.value this.courseInfo.previousPoint.type = `VesselPosition` } else { + res.status(406).send(`Invalid Data`) return false } } catch (err) { + res.status(406).send(`Invalid Data`) return false } } else { @@ -312,8 +323,7 @@ export class CourseApi { this.courseInfo.previousPoint.type = `RoutePoint` } this.courseInfo.previousPoint.href = null - - res.status(200) + res.status(200).send('OK') } ) } @@ -333,38 +343,38 @@ export class CourseApi { return false } - const newDest: any = {} - Object.assign(newDest, this.courseInfo) + const newCourse: any = {} + Object.assign(newCourse, this.courseInfo) // set activeroute - newDest.activeRoute.href = route.href + newCourse.activeRoute.href = route.href if (this.isValidArrivalCircle(route.arrivalCircle as number)) { - newDest.nextPoint.arrivalCircle = route.arrivalCircle + newCourse.nextPoint.arrivalCircle = route.arrivalCircle } - newDest.activeRoute.startTime = new Date().toISOString() + newCourse.activeRoute.startTime = new Date().toISOString() if (typeof route.reverse === 'boolean') { - newDest.activeRoute.reverse = route.reverse + newCourse.activeRoute.reverse = route.reverse } - newDest.activeRoute.pointIndex = this.parsePointIndex( + newCourse.activeRoute.pointIndex = this.parsePointIndex( route.pointIndex as number, rte ) // set nextPoint - newDest.nextPoint.position = this.getRoutePoint( + newCourse.nextPoint.position = this.getRoutePoint( rte, - newDest.activeRoute.pointIndex, - newDest.activeRoute.reverse + newCourse.activeRoute.pointIndex, + newCourse.activeRoute.reverse ) - newDest.nextPoint.type = `RoutePoint` - newDest.nextPoint.href = null + newCourse.nextPoint.type = `RoutePoint` + newCourse.nextPoint.href = null // set previousPoint - if (newDest.activeRoute.pointIndex === 0) { + if (newCourse.activeRoute.pointIndex === 0) { try { const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -377,32 +387,32 @@ export class CourseApi { return false } } else { - newDest.previousPoint.position = this.getRoutePoint( + newCourse.previousPoint.position = this.getRoutePoint( rte, - newDest.activeRoute.pointIndex - 1, - newDest.activeRoute.reverse + newCourse.activeRoute.pointIndex - 1, + newCourse.activeRoute.reverse ) - newDest.previousPoint.type = `RoutePoint` + newCourse.previousPoint.type = `RoutePoint` } - newDest.previousPoint.href = null + newCourse.previousPoint.href = null - this.courseInfo = newDest + this.courseInfo = newCourse return true } private async setDestination(dest: any): Promise { - const newDest: any = {} - Object.assign(newDest, this.courseInfo) + const newCourse: any = {} + Object.assign(newCourse, this.courseInfo) // set nextPoint if (this.isValidArrivalCircle(dest.arrivalCircle)) { - newDest.nextPoint.arrivalCircle = dest.arrivalCircle + newCourse.nextPoint.arrivalCircle = dest.arrivalCircle } - newDest.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null + newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - newDest.href = dest.href + newCourse.href = dest.href const href = this.parseHref(dest.href) if (href) { // fetch waypoint resource details @@ -412,7 +422,7 @@ export class CourseApi { href.id ) if (r.position && typeof r.position?.latitude !== 'undefined') { - newDest.nextPoint.position = r.position + newCourse.nextPoint.position = r.position } else { return false } @@ -421,9 +431,9 @@ export class CourseApi { } } } else if (dest.position) { - newDest.nextPoint.href = null + newCourse.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { - newDest.nextPoint.position = dest.position + newCourse.nextPoint.position = dest.position } else { return false } @@ -435,17 +445,17 @@ export class CourseApi { try { const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { - newDest.previousPoint.position = position.value - newDest.previousPoint.type = `VesselPosition` + newCourse.previousPoint.position = position.value + newCourse.previousPoint.type = `VesselPosition` } else { return false } - newDest.previousPoint.href = null + newCourse.previousPoint.href = null } catch (err) { return false } - this.courseInfo = newDest + this.courseInfo = newCourse return true } From ebeb36be3fa7bf439d3121f0de3ed04ade858ec6 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 12 Dec 2021 14:29:38 +1030 Subject: [PATCH 145/410] chore: lint --- src/api/course/index.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 2aa57e8a6..1e719297a 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -6,7 +6,7 @@ const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const DELTA_INTERVAL: number = 30000 +const DELTA_INTERVAL: number = 30000 interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void @@ -99,8 +99,8 @@ export class CourseApi { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app this.initResourceRoutes() - setInterval( ()=> { - if(this.courseInfo.nextPoint.position) { + setInterval(() => { + if (this.courseInfo.nextPoint.position) { this.emitCourseInfo() } }, DELTA_INTERVAL) @@ -409,7 +409,8 @@ export class CourseApi { newCourse.nextPoint.arrivalCircle = dest.arrivalCircle } - newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null + newCourse.nextPoint.type = + typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { newCourse.href = dest.href From 3335f9fc381835cd707d0bae367ae1a00485c50e Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 4 Dec 2021 17:12:14 +1030 Subject: [PATCH 146/410] init courseApi --- src/api/course/index.ts | 318 +++++++++++++----------------------- src/api/course/openApi.json | 72 +++++++- 2 files changed, 187 insertions(+), 203 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 1e719297a..6e8e4074d 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,24 +1,23 @@ +import { + ResourceProvider, + ResourceProviderMethods, + SignalKResourceType +} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' +import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' -const DELTA_INTERVAL: number = 30000 +const API_METHODS: string[] = [] interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - securityStrategy: { - shouldAllowPut: ( - req: Application, - context: string, - source: any, - path: string - ) => boolean - } resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -99,20 +98,6 @@ export class CourseApi { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app this.initResourceRoutes() - setInterval(() => { - if (this.courseInfo.nextPoint.position) { - this.emitCourseInfo() - } - }, DELTA_INTERVAL) - } - - private updateAllowed(): boolean { - return this.server.securityStrategy.shouldAllowPut( - this.server, - 'vessels.self', - null, - 'navigation.course' - ) } private initResourceRoutes() { @@ -125,46 +110,29 @@ export class CourseApi { } ) + // restart / arrivalCircle this.server.put( - `${COURSE_API_PATH}/restart`, + `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { - debug(`** PUT ${COURSE_API_PATH}/restart`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } - if (!this.courseInfo.nextPoint.position) { - res.status(406).send(`No active destination!`) - return - } - // set previousPoint to vessel position - try { + debug(`** PUT ${COURSE_API_PATH}/:action`) + if (req.params.restart) { + // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).send(`Course restarted.`) + } else { + res.status(406).send(`Vessel position unavailable!`) } - } catch (err) { - res.status(406).send(`Vessel position unavailable!`) - } - } - ) - - this.server.put( - `${COURSE_API_PATH}/arrivalCircle`, - async (req: Request, res: Response) => { - debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return } - if (this.isValidArrivalCircle(req.body.value)) { - this.courseInfo.nextPoint.arrivalCircle = req.body.value - this.emitCourseInfo() - res.status(200).send('OK') - } else { - res.status(406).send(`Invalid Data`) + if (req.params.arrivalCircle) { + if (this.setArrivalCircle(req.params.arrivalCircle)) { + this.emitCourseInfo() + res.status(200).send(`Destination set successfully.`) + } else { + res.status(406).send(`Invalid Data`) + } } } ) @@ -174,10 +142,6 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } if (!req.body.value) { res.status(406).send(`Invalid Data`) return @@ -185,7 +149,7 @@ export class CourseApi { const result = await this.setDestination(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).send(`Destination set successfully.`) } else { this.clearDestination() this.emitCourseInfo() @@ -199,29 +163,22 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } this.clearDestination() this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).send(`Destination cleared.`) } ) - // set activeRoute + // set / clear activeRoute this.server.put( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } + const result = await this.activateRoute(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).send(`Active route set.`) } else { this.clearDestination() this.emitCourseInfo() @@ -229,19 +186,14 @@ export class CourseApi { } } ) - - // clear activeRoute /destination this.server.delete( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/activeRoute`) - if (!this.updateAllowed()) { - res.status(403) - return - } + this.clearDestination() this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).send(`Active route cleared.`) } ) @@ -249,11 +201,6 @@ export class CourseApi { `${COURSE_API_PATH}/activeRoute/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } - // fetch active route data if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return @@ -264,7 +211,7 @@ export class CourseApi { return } - if (req.params.action === 'nextPoint') { + if (req.params.nextPoint) { if ( typeof req.body.value === 'number' && (req.body.value === 1 || req.body.value === -1) @@ -275,10 +222,9 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) - return } } - if (req.params.action === 'pointIndex') { + if (req.params.pointIndex) { if (typeof req.body.value === 'number') { this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( req.body.value, @@ -286,44 +232,36 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) - return } } - // set new destination + // set nextPoint this.courseInfo.nextPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex, - this.courseInfo.activeRoute.reverse + this.courseInfo.activeRoute.pointIndex ) this.courseInfo.nextPoint.type = `RoutePoint` this.courseInfo.nextPoint.href = null // set previousPoint if (this.courseInfo.activeRoute.pointIndex === 0) { - try { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { - res.status(406).send(`Invalid Data`) - return false - } - } catch (err) { - res.status(406).send(`Invalid Data`) + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { return false } } else { this.courseInfo.previousPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex - 1, - this.courseInfo.activeRoute.reverse + this.courseInfo.activeRoute.pointIndex - 1 ) this.courseInfo.previousPoint.type = `RoutePoint` } this.courseInfo.previousPoint.href = null - res.status(200).send('OK') + + res.status(200).send(`OK`) } ) } @@ -343,98 +281,81 @@ export class CourseApi { return false } - const newCourse: any = {} - Object.assign(newCourse, this.courseInfo) - // set activeroute - newCourse.activeRoute.href = route.href + this.courseInfo.activeRoute.href = route.href - if (this.isValidArrivalCircle(route.arrivalCircle as number)) { - newCourse.nextPoint.arrivalCircle = route.arrivalCircle + if (typeof route.arrivalCircle === 'number') { + this.setArrivalCircle(route.arrivalCircle) } - newCourse.activeRoute.startTime = new Date().toISOString() + this.courseInfo.activeRoute.reverse = + typeof route.reverse === 'boolean' ? route.reverse : false - if (typeof route.reverse === 'boolean') { - newCourse.activeRoute.reverse = route.reverse - } + this.courseInfo.activeRoute.pointIndex = + typeof route.pointIndex === 'number' + ? this.parsePointIndex(route.pointIndex, rte) + : 0 - newCourse.activeRoute.pointIndex = this.parsePointIndex( - route.pointIndex as number, - rte - ) + this.courseInfo.activeRoute.startTime = new Date().toISOString() // set nextPoint - newCourse.nextPoint.position = this.getRoutePoint( + this.courseInfo.nextPoint.position = this.getRoutePoint( rte, - newCourse.activeRoute.pointIndex, - newCourse.activeRoute.reverse + this.courseInfo.activeRoute.pointIndex ) - newCourse.nextPoint.type = `RoutePoint` - newCourse.nextPoint.href = null + this.courseInfo.nextPoint.type = `RoutePoint` + this.courseInfo.nextPoint.href = null // set previousPoint - if (newCourse.activeRoute.pointIndex === 0) { - try { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { - return false - } - } catch (err) { + if (this.courseInfo.activeRoute.pointIndex === 0) { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { return false } } else { - newCourse.previousPoint.position = this.getRoutePoint( + this.courseInfo.previousPoint.position = this.getRoutePoint( rte, - newCourse.activeRoute.pointIndex - 1, - newCourse.activeRoute.reverse + this.courseInfo.activeRoute.pointIndex - 1 ) - newCourse.previousPoint.type = `RoutePoint` + this.courseInfo.previousPoint.type = `RoutePoint` } - newCourse.previousPoint.href = null + this.courseInfo.previousPoint.href = null - this.courseInfo = newCourse return true } - private async setDestination(dest: any): Promise { - const newCourse: any = {} - Object.assign(newCourse, this.courseInfo) - + private async setDestination(dest: Destination): Promise { // set nextPoint - if (this.isValidArrivalCircle(dest.arrivalCircle)) { - newCourse.nextPoint.arrivalCircle = dest.arrivalCircle + if (typeof dest.arrivalCircle === 'number') { + this.setArrivalCircle(dest.arrivalCircle) } - newCourse.nextPoint.type = + this.courseInfo.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - newCourse.href = dest.href + this.courseInfo.nextPoint.href = dest.href const href = this.parseHref(dest.href) if (href) { - // fetch waypoint resource details try { const r = await this.server.resourcesApi.getResource( href.type, href.id ) - if (r.position && typeof r.position?.latitude !== 'undefined') { - newCourse.nextPoint.position = r.position - } else { - return false + if (r.position && typeof r.position.value?.latitude !== 'undefined') { + this.courseInfo.nextPoint.position = r.position.value } } catch (err) { return false } } } else if (dest.position) { - newCourse.nextPoint.href = null + this.courseInfo.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { - newCourse.nextPoint.position = dest.position + this.courseInfo.nextPoint.position = dest.position } else { return false } @@ -443,20 +364,15 @@ export class CourseApi { } // set previousPoint - try { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - newCourse.previousPoint.position = position.value - newCourse.previousPoint.type = `VesselPosition` - } else { - return false - } - newCourse.previousPoint.href = null - } catch (err) { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { return false } + this.courseInfo.previousPoint.href = null - this.courseInfo = newCourse return true } @@ -473,12 +389,17 @@ export class CourseApi { this.courseInfo.previousPoint.position = null } - private isValidArrivalCircle(value: number): boolean { - return typeof value === 'number' && value >= 0 + private setArrivalCircle(value: any): boolean { + if (typeof value === 'number' && value >= 0) { + this.courseInfo.nextPoint.arrivalCircle = value + return true + } else { + return false + } } private parsePointIndex(index: number, rte: any): number { - if (typeof index !== 'number' || !rte) { + if (!rte) { return 0 } if (!rte.feature?.geometry?.coordinates) { @@ -497,11 +418,13 @@ export class CourseApi { } private parseHref(href: string): { type: string; id: string } | undefined { - if (!href) { + if (href.length === 0) { return undefined } - - const ref: string[] = href.split('/').slice(-3) + if (href[0] === '/') { + href = href.slice(1) + } + const ref: string[] = href.split('/') if (ref.length < 3) { return undefined } @@ -514,8 +437,8 @@ export class CourseApi { } } - private getRoutePoint(rte: any, index: number, reverse: boolean) { - const pos = reverse + private getRoutePoint(rte: any, index: number) { + const pos = this.courseInfo.activeRoute.reverse ? rte.feature.geometry.coordinates[ rte.feature.geometry.coordinates.length - (index + 1) ] @@ -523,7 +446,7 @@ export class CourseApi { return { latitude: pos[1], longitude: pos[0], - altitude: pos.length === 3 ? pos[2] : 0 + altitude: pos.length == 3 ? pos[2] : 0 } } @@ -541,94 +464,89 @@ export class CourseApi { } private buildDeltaMsg(): any { - const values: Array<{ path: string; value: any }> = [] - const navPath = [ + let values: Array<{path:string, value:any}> = [] + let root = [ 'navigation.courseGreatCircle', 'navigation.courseRhumbline' ] values.push({ - path: `navigation.course`, - value: this.courseInfo - }) - - values.push({ - path: `${navPath[0]}.activeRoute.href`, + path: `${root[0]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${navPath[1]}.activeRoute.href`, + path: `${root[1]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${navPath[0]}.activeRoute.startTime`, + path: `${root[0]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${navPath[1]}.activeRoute.startTime`, + path: `${root[1]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${navPath[0]}.nextPoint.href`, + path: `${root[0]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${navPath[1]}.nextPoint.href`, + path: `${root[1]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${navPath[0]}.nextPoint.position`, + path: `${root[0]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${navPath[1]}.nextPoint.position`, + path: `${root[1]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${navPath[0]}.nextPoint.type`, + path: `${root[0]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${navPath[1]}.nextPoint.type`, + path: `${root[1]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${navPath[0]}.nextPoint.arrivalCircle`, + path: `${root[0]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${navPath[1]}.nextPoint.arrivalCircle`, + path: `${root[1]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${navPath[0]}.previousPoint.href`, + path: `${root[0]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${navPath[1]}.previousPoint.href`, + path: `${root[1]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${navPath[0]}.previousPoint.position`, + path: `${root[0]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${navPath[1]}.previousPoint.position`, + path: `${root[1]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${navPath[0]}.previousPoint.type`, + path: `${root[0]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) values.push({ - path: `${navPath[1]}.previousPoint.type`, + path: `${root[1]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) return { updates: [ { - values + values: values } ] } diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index b162d1314..04653db5e 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -108,7 +108,29 @@ "put": { "tags": ["course"], "summary": "Restart course calculations", - "description": "Sets previousPoint value to current vessel location" + "description": "Sets previousPoint value to current vessel location", + "requestBody": { + "description": "Restart payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } } }, @@ -178,7 +200,29 @@ "delete": { "tags": ["course/destination"], "summary": "Clear destination", - "description": "Sets activeRoute, nextPoint & previousPoint values to null" + "description": "Sets activeRoute, nextPoint & previousPoint values to null", + "requestBody": { + "description": "Delete payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } } }, @@ -269,7 +313,29 @@ "delete": { "tags": ["course/activeRoute"], "summary": "Clear active route", - "description": "Sets activeRoute, nextPoint & previousPoint values to null" + "description": "Sets activeRoute, nextPoint & previousPoint values to null", + "requestBody": { + "description": "Delete payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } } }, From c38a16ba953beabf1b468ca5f9eae41d31266dfd Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 5 Dec 2021 10:26:47 +1030 Subject: [PATCH 147/410] update detlas --- src/api/course/index.ts | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 6e8e4074d..7510ca1e7 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,23 +1,15 @@ -import { - ResourceProvider, - ResourceProviderMethods, - SignalKResourceType -} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' -import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' - -const API_METHODS: string[] = [] interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any + registerPutHandler: (context:string, path:string, cb:any) => any resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -110,12 +102,26 @@ export class CourseApi { } ) + // + if(this.server.registerPutHandler) { + debug('** Registering PUT Action Handler(s) **') + this.server.registerPutHandler( + 'vessels.self', + 'navigation.course.*', + this.handleCourseApiPut + ); + } + // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { + //test for active destination + if (!this.courseInfo.nextPoint.position) { + return + } // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -266,6 +272,12 @@ export class CourseApi { ) } + private handleCourseApiPut(context:string, path:string, value:any, cb:any) { + + debug('** PUT handler **') + return undefined + } + private async activateRoute(route: ActiveRoute): Promise { let rte: any @@ -470,6 +482,11 @@ export class CourseApi { 'navigation.courseRhumbline' ] + values.push({ + path: `navigation.course`, + value: this.courseInfo + }) + values.push({ path: `${root[0]}.activeRoute.href`, value: this.courseInfo.activeRoute.href From 9996d04e633a85be5e732ef6f5bd7f9258b70f05 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 4 Dec 2021 17:12:14 +1030 Subject: [PATCH 148/410] init courseApi --- src/api/course/index.ts | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 7510ca1e7..af32aceee 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,15 +1,26 @@ + +import { + ResourceProvider, + ResourceProviderMethods, + SignalKResourceType +} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' +import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' + +const API_METHODS: string[] = [] + interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - registerPutHandler: (context:string, path:string, cb:any) => any + resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -102,26 +113,12 @@ export class CourseApi { } ) - // - if(this.server.registerPutHandler) { - debug('** Registering PUT Action Handler(s) **') - this.server.registerPutHandler( - 'vessels.self', - 'navigation.course.*', - this.handleCourseApiPut - ); - } - // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { - //test for active destination - if (!this.courseInfo.nextPoint.position) { - return - } // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -272,12 +269,6 @@ export class CourseApi { ) } - private handleCourseApiPut(context:string, path:string, value:any, cb:any) { - - debug('** PUT handler **') - return undefined - } - private async activateRoute(route: ActiveRoute): Promise { let rte: any From a3d8d95c57951ffb4a86682f4ba53a45fb18ebf5 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 5 Dec 2021 10:26:47 +1030 Subject: [PATCH 149/410] update detlas --- src/api/course/index.ts | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index af32aceee..7510ca1e7 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,26 +1,15 @@ - -import { - ResourceProvider, - ResourceProviderMethods, - SignalKResourceType -} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' -import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' - -const API_METHODS: string[] = [] - interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - + registerPutHandler: (context:string, path:string, cb:any) => any resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -113,12 +102,26 @@ export class CourseApi { } ) + // + if(this.server.registerPutHandler) { + debug('** Registering PUT Action Handler(s) **') + this.server.registerPutHandler( + 'vessels.self', + 'navigation.course.*', + this.handleCourseApiPut + ); + } + // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { + //test for active destination + if (!this.courseInfo.nextPoint.position) { + return + } // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -269,6 +272,12 @@ export class CourseApi { ) } + private handleCourseApiPut(context:string, path:string, value:any, cb:any) { + + debug('** PUT handler **') + return undefined + } + private async activateRoute(route: ActiveRoute): Promise { let rte: any From c03871c72b8b44dfe7f47fd5019386cb6621b8a3 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 6 Dec 2021 08:39:56 +1030 Subject: [PATCH 150/410] enable put processing --- src/api/course/index.ts | 133 +++++++++++++++++++++++++--------------- 1 file changed, 82 insertions(+), 51 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 7510ca1e7..1566ecdf3 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -9,7 +9,14 @@ const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/cou interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - registerPutHandler: (context:string, path:string, cb:any) => any + securityStrategy: { + shouldAllowPut: ( + req: any, + context: string, + source: any, + path: string + ) => boolean + } resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -92,6 +99,15 @@ export class CourseApi { this.initResourceRoutes() } + private updateAllowed(): boolean { + return this.server.securityStrategy.shouldAllowPut( + this.server, + 'vessels.self', + null, + 'resources' + ) + } + private initResourceRoutes() { // return current course information this.server.get( @@ -102,43 +118,43 @@ export class CourseApi { } ) - // - if(this.server.registerPutHandler) { - debug('** Registering PUT Action Handler(s) **') - this.server.registerPutHandler( - 'vessels.self', - 'navigation.course.*', - this.handleCourseApiPut - ); - } + this.server.put( + `${COURSE_API_PATH}/restart`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/restart`) + if (!this.updateAllowed()) { + res.status(403) + return + } + if (!this.courseInfo.nextPoint.position) { + res.status(406).send(`No active destination!`) + return + } + // set previousPoint to vessel position + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.emitCourseInfo() + res.status(200) + } else { + res.status(406).send(`Vessel position unavailable!`) + } + } + ) - // restart / arrivalCircle this.server.put( - `${COURSE_API_PATH}/:action`, + `${COURSE_API_PATH}/arrivalCircle`, async (req: Request, res: Response) => { - debug(`** PUT ${COURSE_API_PATH}/:action`) - if (req.params.restart) { - //test for active destination - if (!this.courseInfo.nextPoint.position) { - return - } - // set previousPoint to vessel position - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.emitCourseInfo() - res.status(200).send(`Course restarted.`) - } else { - res.status(406).send(`Vessel position unavailable!`) - } + debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) + if (!this.updateAllowed()) { + res.status(403) + return } - if (req.params.arrivalCircle) { - if (this.setArrivalCircle(req.params.arrivalCircle)) { - this.emitCourseInfo() - res.status(200).send(`Destination set successfully.`) - } else { - res.status(406).send(`Invalid Data`) - } + if (this.setArrivalCircle(req.body.value)) { + this.emitCourseInfo() + res.status(200) + } else { + res.status(406).send(`Invalid Data`) } } ) @@ -148,6 +164,10 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) + if (!this.updateAllowed()) { + res.status(403) + return + } if (!req.body.value) { res.status(406).send(`Invalid Data`) return @@ -155,7 +175,7 @@ export class CourseApi { const result = await this.setDestination(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send(`Destination set successfully.`) + res.status(200) } else { this.clearDestination() this.emitCourseInfo() @@ -169,22 +189,29 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) + if (!this.updateAllowed()) { + res.status(403) + return + } this.clearDestination() this.emitCourseInfo() - res.status(200).send(`Destination cleared.`) + res.status(200) } ) - // set / clear activeRoute + // set activeRoute this.server.put( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) - + if (!this.updateAllowed()) { + res.status(403) + return + } const result = await this.activateRoute(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send(`Active route set.`) + res.status(200) } else { this.clearDestination() this.emitCourseInfo() @@ -192,14 +219,19 @@ export class CourseApi { } } ) + + // clear activeRoute /destination this.server.delete( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/activeRoute`) - + if (!this.updateAllowed()) { + res.status(403) + return + } this.clearDestination() this.emitCourseInfo() - res.status(200).send(`Active route cleared.`) + res.status(200) } ) @@ -207,6 +239,11 @@ export class CourseApi { `${COURSE_API_PATH}/activeRoute/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) + if (!this.updateAllowed()) { + res.status(403) + return + } + // fetch route data if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return @@ -217,7 +254,7 @@ export class CourseApi { return } - if (req.params.nextPoint) { + if (req.params.action === 'nextPoint') { if ( typeof req.body.value === 'number' && (req.body.value === 1 || req.body.value === -1) @@ -230,7 +267,7 @@ export class CourseApi { res.status(406).send(`Invalid Data`) } } - if (req.params.pointIndex) { + if (req.params.action === 'pointIndex') { if (typeof req.body.value === 'number') { this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( req.body.value, @@ -241,7 +278,7 @@ export class CourseApi { } } - // set nextPoint + // set new destination this.courseInfo.nextPoint.position = this.getRoutePoint( rte, this.courseInfo.activeRoute.pointIndex @@ -267,17 +304,11 @@ export class CourseApi { } this.courseInfo.previousPoint.href = null - res.status(200).send(`OK`) + res.status(200) } ) } - private handleCourseApiPut(context:string, path:string, value:any, cb:any) { - - debug('** PUT handler **') - return undefined - } - private async activateRoute(route: ActiveRoute): Promise { let rte: any From fdc944724f10b485cabe40f33eac14f835748005 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 6 Dec 2021 13:34:31 +1030 Subject: [PATCH 151/410] chore: lint --- src/api/course/index.ts | 211 ++++++++++++++++++++---------------- src/api/course/openApi.json | 72 +----------- 2 files changed, 120 insertions(+), 163 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 1566ecdf3..6cd3b80fd 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -11,7 +11,7 @@ interface CourseApplication extends Application { getSelfPath: (path: string) => any securityStrategy: { shouldAllowPut: ( - req: any, + req: Application, context: string, source: any, path: string @@ -104,7 +104,7 @@ export class CourseApi { this.server, 'vessels.self', null, - 'resources' + 'navigation.course' ) } @@ -131,12 +131,14 @@ export class CourseApi { return } // set previousPoint to vessel position - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.emitCourseInfo() - res.status(200) - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.emitCourseInfo() + res.status(200) + } + } catch (err) { res.status(406).send(`Vessel position unavailable!`) } } @@ -150,7 +152,8 @@ export class CourseApi { res.status(403) return } - if (this.setArrivalCircle(req.body.value)) { + if (this.isValidArrivalCircle(req.body.value)) { + this.courseInfo.nextPoint.arrivalCircle = req.body.value this.emitCourseInfo() res.status(200) } else { @@ -243,7 +246,7 @@ export class CourseApi { res.status(403) return } - // fetch route data + // fetch active route data if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return @@ -281,24 +284,30 @@ export class CourseApi { // set new destination this.courseInfo.nextPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex + this.courseInfo.activeRoute.pointIndex, + this.courseInfo.activeRoute.reverse ) this.courseInfo.nextPoint.type = `RoutePoint` this.courseInfo.nextPoint.href = null // set previousPoint if (this.courseInfo.activeRoute.pointIndex === 0) { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } catch (err) { return false } } else { this.courseInfo.previousPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex - 1 + this.courseInfo.activeRoute.pointIndex - 1, + this.courseInfo.activeRoute.reverse ) this.courseInfo.previousPoint.type = `RoutePoint` } @@ -324,81 +333,97 @@ export class CourseApi { return false } + const newDest: any = {} + Object.assign(newDest, this.courseInfo) + // set activeroute - this.courseInfo.activeRoute.href = route.href + newDest.activeRoute.href = route.href - if (typeof route.arrivalCircle === 'number') { - this.setArrivalCircle(route.arrivalCircle) + if (this.isValidArrivalCircle(route.arrivalCircle as number)) { + newDest.nextPoint.arrivalCircle = route.arrivalCircle } - this.courseInfo.activeRoute.reverse = - typeof route.reverse === 'boolean' ? route.reverse : false + newDest.activeRoute.startTime = new Date().toISOString() - this.courseInfo.activeRoute.pointIndex = - typeof route.pointIndex === 'number' - ? this.parsePointIndex(route.pointIndex, rte) - : 0 + if (typeof route.reverse === 'boolean') { + newDest.activeRoute.reverse = route.reverse + } - this.courseInfo.activeRoute.startTime = new Date().toISOString() + newDest.activeRoute.pointIndex = this.parsePointIndex( + route.pointIndex as number, + rte + ) // set nextPoint - this.courseInfo.nextPoint.position = this.getRoutePoint( + newDest.nextPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex + newDest.activeRoute.pointIndex, + newDest.activeRoute.reverse ) - this.courseInfo.nextPoint.type = `RoutePoint` - this.courseInfo.nextPoint.href = null + newDest.nextPoint.type = `RoutePoint` + newDest.nextPoint.href = null // set previousPoint - if (this.courseInfo.activeRoute.pointIndex === 0) { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + if (newDest.activeRoute.pointIndex === 0) { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } catch (err) { return false } } else { - this.courseInfo.previousPoint.position = this.getRoutePoint( + newDest.previousPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex - 1 + newDest.activeRoute.pointIndex - 1, + newDest.activeRoute.reverse ) - this.courseInfo.previousPoint.type = `RoutePoint` + newDest.previousPoint.type = `RoutePoint` } - this.courseInfo.previousPoint.href = null + newDest.previousPoint.href = null + this.courseInfo = newDest return true } - private async setDestination(dest: Destination): Promise { + private async setDestination(dest: any): Promise { + const newDest: any = {} + Object.assign(newDest, this.courseInfo) + // set nextPoint - if (typeof dest.arrivalCircle === 'number') { - this.setArrivalCircle(dest.arrivalCircle) + if (this.isValidArrivalCircle(dest.arrivalCircle)) { + newDest.nextPoint.arrivalCircle = dest.arrivalCircle } - this.courseInfo.nextPoint.type = - typeof dest.type !== 'undefined' ? dest.type : null + newDest.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - this.courseInfo.nextPoint.href = dest.href + newDest.href = dest.href const href = this.parseHref(dest.href) if (href) { + // fetch waypoint resource details try { const r = await this.server.resourcesApi.getResource( href.type, href.id ) - if (r.position && typeof r.position.value?.latitude !== 'undefined') { - this.courseInfo.nextPoint.position = r.position.value + if (r.position && typeof r.position?.latitude !== 'undefined') { + newDest.nextPoint.position = r.position + } else { + return false } } catch (err) { return false } } } else if (dest.position) { - this.courseInfo.nextPoint.href = null + newDest.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { - this.courseInfo.nextPoint.position = dest.position + newDest.nextPoint.position = dest.position } else { return false } @@ -407,15 +432,20 @@ export class CourseApi { } // set previousPoint - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + newDest.previousPoint.position = position.value + newDest.previousPoint.type = `VesselPosition` + } else { + return false + } + newDest.previousPoint.href = null + } catch (err) { return false } - this.courseInfo.previousPoint.href = null + this.courseInfo = newDest return true } @@ -432,17 +462,12 @@ export class CourseApi { this.courseInfo.previousPoint.position = null } - private setArrivalCircle(value: any): boolean { - if (typeof value === 'number' && value >= 0) { - this.courseInfo.nextPoint.arrivalCircle = value - return true - } else { - return false - } + private isValidArrivalCircle(value: number): boolean { + return typeof value === 'number' && value >= 0 } private parsePointIndex(index: number, rte: any): number { - if (!rte) { + if (typeof index !== 'number' || !rte) { return 0 } if (!rte.feature?.geometry?.coordinates) { @@ -461,13 +486,11 @@ export class CourseApi { } private parseHref(href: string): { type: string; id: string } | undefined { - if (href.length === 0) { + if (!href) { return undefined } - if (href[0] === '/') { - href = href.slice(1) - } - const ref: string[] = href.split('/') + + const ref: string[] = href.split('/').slice(-3) if (ref.length < 3) { return undefined } @@ -480,8 +503,8 @@ export class CourseApi { } } - private getRoutePoint(rte: any, index: number) { - const pos = this.courseInfo.activeRoute.reverse + private getRoutePoint(rte: any, index: number, reverse: boolean) { + const pos = reverse ? rte.feature.geometry.coordinates[ rte.feature.geometry.coordinates.length - (index + 1) ] @@ -489,7 +512,7 @@ export class CourseApi { return { latitude: pos[1], longitude: pos[0], - altitude: pos.length == 3 ? pos[2] : 0 + altitude: pos.length === 3 ? pos[2] : 0 } } @@ -507,8 +530,8 @@ export class CourseApi { } private buildDeltaMsg(): any { - let values: Array<{path:string, value:any}> = [] - let root = [ + const values: Array<{ path: string; value: any }> = [] + const navPath = [ 'navigation.courseGreatCircle', 'navigation.courseRhumbline' ] @@ -519,82 +542,82 @@ export class CourseApi { }) values.push({ - path: `${root[0]}.activeRoute.href`, + path: `${navPath[0]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${root[1]}.activeRoute.href`, + path: `${navPath[1]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${root[0]}.activeRoute.startTime`, + path: `${navPath[0]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${root[1]}.activeRoute.startTime`, + path: `${navPath[1]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${root[0]}.nextPoint.href`, + path: `${navPath[0]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${root[1]}.nextPoint.href`, + path: `${navPath[1]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${root[0]}.nextPoint.position`, + path: `${navPath[0]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${root[1]}.nextPoint.position`, + path: `${navPath[1]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${root[0]}.nextPoint.type`, + path: `${navPath[0]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${root[1]}.nextPoint.type`, + path: `${navPath[1]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${root[0]}.nextPoint.arrivalCircle`, + path: `${navPath[0]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${root[1]}.nextPoint.arrivalCircle`, + path: `${navPath[1]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${root[0]}.previousPoint.href`, + path: `${navPath[0]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${root[1]}.previousPoint.href`, + path: `${navPath[1]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${root[0]}.previousPoint.position`, + path: `${navPath[0]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${root[1]}.previousPoint.position`, + path: `${navPath[1]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${root[0]}.previousPoint.type`, + path: `${navPath[0]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) values.push({ - path: `${root[1]}.previousPoint.type`, + path: `${navPath[1]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) return { updates: [ { - values: values + values } ] } diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index 04653db5e..b162d1314 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -108,29 +108,7 @@ "put": { "tags": ["course"], "summary": "Restart course calculations", - "description": "Sets previousPoint value to current vessel location", - "requestBody": { - "description": "Restart payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } - } - } - } - } - } + "description": "Sets previousPoint value to current vessel location" } }, @@ -200,29 +178,7 @@ "delete": { "tags": ["course/destination"], "summary": "Clear destination", - "description": "Sets activeRoute, nextPoint & previousPoint values to null", - "requestBody": { - "description": "Delete payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } - } - } - } - } - } + "description": "Sets activeRoute, nextPoint & previousPoint values to null" } }, @@ -313,29 +269,7 @@ "delete": { "tags": ["course/activeRoute"], "summary": "Clear active route", - "description": "Sets activeRoute, nextPoint & previousPoint values to null", - "requestBody": { - "description": "Delete payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } - } - } - } - } - } + "description": "Sets activeRoute, nextPoint & previousPoint values to null" } }, From 6e25a1ced96f0b199ab8d77342600fd8b8628a99 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 8 Dec 2021 09:27:06 +1030 Subject: [PATCH 152/410] add 30sec delta interval --- src/api/course/index.ts | 100 ++++++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 45 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 6cd3b80fd..2aa57e8a6 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -6,6 +6,8 @@ const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const DELTA_INTERVAL: number = 30000 + interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any @@ -97,6 +99,11 @@ export class CourseApi { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app this.initResourceRoutes() + setInterval( ()=> { + if(this.courseInfo.nextPoint.position) { + this.emitCourseInfo() + } + }, DELTA_INTERVAL) } private updateAllowed(): boolean { @@ -123,7 +130,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/restart`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (!this.courseInfo.nextPoint.position) { @@ -136,7 +143,7 @@ export class CourseApi { if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } } catch (err) { res.status(406).send(`Vessel position unavailable!`) @@ -149,13 +156,13 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (this.isValidArrivalCircle(req.body.value)) { this.courseInfo.nextPoint.arrivalCircle = req.body.value this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { res.status(406).send(`Invalid Data`) } @@ -168,7 +175,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (!req.body.value) { @@ -178,7 +185,7 @@ export class CourseApi { const result = await this.setDestination(req.body.value) if (result) { this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { this.clearDestination() this.emitCourseInfo() @@ -193,12 +200,12 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } this.clearDestination() this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } ) @@ -208,13 +215,13 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } const result = await this.activateRoute(req.body.value) if (result) { this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { this.clearDestination() this.emitCourseInfo() @@ -234,7 +241,7 @@ export class CourseApi { } this.clearDestination() this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } ) @@ -243,7 +250,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } // fetch active route data @@ -268,6 +275,7 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) + return } } if (req.params.action === 'pointIndex') { @@ -278,6 +286,7 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) + return } } @@ -298,9 +307,11 @@ export class CourseApi { this.courseInfo.previousPoint.position = position.value this.courseInfo.previousPoint.type = `VesselPosition` } else { + res.status(406).send(`Invalid Data`) return false } } catch (err) { + res.status(406).send(`Invalid Data`) return false } } else { @@ -312,8 +323,7 @@ export class CourseApi { this.courseInfo.previousPoint.type = `RoutePoint` } this.courseInfo.previousPoint.href = null - - res.status(200) + res.status(200).send('OK') } ) } @@ -333,38 +343,38 @@ export class CourseApi { return false } - const newDest: any = {} - Object.assign(newDest, this.courseInfo) + const newCourse: any = {} + Object.assign(newCourse, this.courseInfo) // set activeroute - newDest.activeRoute.href = route.href + newCourse.activeRoute.href = route.href if (this.isValidArrivalCircle(route.arrivalCircle as number)) { - newDest.nextPoint.arrivalCircle = route.arrivalCircle + newCourse.nextPoint.arrivalCircle = route.arrivalCircle } - newDest.activeRoute.startTime = new Date().toISOString() + newCourse.activeRoute.startTime = new Date().toISOString() if (typeof route.reverse === 'boolean') { - newDest.activeRoute.reverse = route.reverse + newCourse.activeRoute.reverse = route.reverse } - newDest.activeRoute.pointIndex = this.parsePointIndex( + newCourse.activeRoute.pointIndex = this.parsePointIndex( route.pointIndex as number, rte ) // set nextPoint - newDest.nextPoint.position = this.getRoutePoint( + newCourse.nextPoint.position = this.getRoutePoint( rte, - newDest.activeRoute.pointIndex, - newDest.activeRoute.reverse + newCourse.activeRoute.pointIndex, + newCourse.activeRoute.reverse ) - newDest.nextPoint.type = `RoutePoint` - newDest.nextPoint.href = null + newCourse.nextPoint.type = `RoutePoint` + newCourse.nextPoint.href = null // set previousPoint - if (newDest.activeRoute.pointIndex === 0) { + if (newCourse.activeRoute.pointIndex === 0) { try { const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -377,32 +387,32 @@ export class CourseApi { return false } } else { - newDest.previousPoint.position = this.getRoutePoint( + newCourse.previousPoint.position = this.getRoutePoint( rte, - newDest.activeRoute.pointIndex - 1, - newDest.activeRoute.reverse + newCourse.activeRoute.pointIndex - 1, + newCourse.activeRoute.reverse ) - newDest.previousPoint.type = `RoutePoint` + newCourse.previousPoint.type = `RoutePoint` } - newDest.previousPoint.href = null + newCourse.previousPoint.href = null - this.courseInfo = newDest + this.courseInfo = newCourse return true } private async setDestination(dest: any): Promise { - const newDest: any = {} - Object.assign(newDest, this.courseInfo) + const newCourse: any = {} + Object.assign(newCourse, this.courseInfo) // set nextPoint if (this.isValidArrivalCircle(dest.arrivalCircle)) { - newDest.nextPoint.arrivalCircle = dest.arrivalCircle + newCourse.nextPoint.arrivalCircle = dest.arrivalCircle } - newDest.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null + newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - newDest.href = dest.href + newCourse.href = dest.href const href = this.parseHref(dest.href) if (href) { // fetch waypoint resource details @@ -412,7 +422,7 @@ export class CourseApi { href.id ) if (r.position && typeof r.position?.latitude !== 'undefined') { - newDest.nextPoint.position = r.position + newCourse.nextPoint.position = r.position } else { return false } @@ -421,9 +431,9 @@ export class CourseApi { } } } else if (dest.position) { - newDest.nextPoint.href = null + newCourse.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { - newDest.nextPoint.position = dest.position + newCourse.nextPoint.position = dest.position } else { return false } @@ -435,17 +445,17 @@ export class CourseApi { try { const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { - newDest.previousPoint.position = position.value - newDest.previousPoint.type = `VesselPosition` + newCourse.previousPoint.position = position.value + newCourse.previousPoint.type = `VesselPosition` } else { return false } - newDest.previousPoint.href = null + newCourse.previousPoint.href = null } catch (err) { return false } - this.courseInfo = newDest + this.courseInfo = newCourse return true } From fc59506ff85f881185df1403f76eb810acd013d6 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 12 Dec 2021 14:29:38 +1030 Subject: [PATCH 153/410] chore: lint --- src/api/course/index.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 2aa57e8a6..1e719297a 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -6,7 +6,7 @@ const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const DELTA_INTERVAL: number = 30000 +const DELTA_INTERVAL: number = 30000 interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void @@ -99,8 +99,8 @@ export class CourseApi { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app this.initResourceRoutes() - setInterval( ()=> { - if(this.courseInfo.nextPoint.position) { + setInterval(() => { + if (this.courseInfo.nextPoint.position) { this.emitCourseInfo() } }, DELTA_INTERVAL) @@ -409,7 +409,8 @@ export class CourseApi { newCourse.nextPoint.arrivalCircle = dest.arrivalCircle } - newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null + newCourse.nextPoint.type = + typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { newCourse.href = dest.href From a597725a0456acdb781fe6032038f8b1dc6cbcbb Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 4 Dec 2021 17:12:14 +1030 Subject: [PATCH 154/410] init courseApi --- src/api/course/index.ts | 318 +++++++++++++----------------------- src/api/course/openApi.json | 72 +++++++- 2 files changed, 187 insertions(+), 203 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 1e719297a..6e8e4074d 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,24 +1,23 @@ +import { + ResourceProvider, + ResourceProviderMethods, + SignalKResourceType +} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' +import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' -const DELTA_INTERVAL: number = 30000 +const API_METHODS: string[] = [] interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - securityStrategy: { - shouldAllowPut: ( - req: Application, - context: string, - source: any, - path: string - ) => boolean - } resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -99,20 +98,6 @@ export class CourseApi { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app this.initResourceRoutes() - setInterval(() => { - if (this.courseInfo.nextPoint.position) { - this.emitCourseInfo() - } - }, DELTA_INTERVAL) - } - - private updateAllowed(): boolean { - return this.server.securityStrategy.shouldAllowPut( - this.server, - 'vessels.self', - null, - 'navigation.course' - ) } private initResourceRoutes() { @@ -125,46 +110,29 @@ export class CourseApi { } ) + // restart / arrivalCircle this.server.put( - `${COURSE_API_PATH}/restart`, + `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { - debug(`** PUT ${COURSE_API_PATH}/restart`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } - if (!this.courseInfo.nextPoint.position) { - res.status(406).send(`No active destination!`) - return - } - // set previousPoint to vessel position - try { + debug(`** PUT ${COURSE_API_PATH}/:action`) + if (req.params.restart) { + // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).send(`Course restarted.`) + } else { + res.status(406).send(`Vessel position unavailable!`) } - } catch (err) { - res.status(406).send(`Vessel position unavailable!`) - } - } - ) - - this.server.put( - `${COURSE_API_PATH}/arrivalCircle`, - async (req: Request, res: Response) => { - debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return } - if (this.isValidArrivalCircle(req.body.value)) { - this.courseInfo.nextPoint.arrivalCircle = req.body.value - this.emitCourseInfo() - res.status(200).send('OK') - } else { - res.status(406).send(`Invalid Data`) + if (req.params.arrivalCircle) { + if (this.setArrivalCircle(req.params.arrivalCircle)) { + this.emitCourseInfo() + res.status(200).send(`Destination set successfully.`) + } else { + res.status(406).send(`Invalid Data`) + } } } ) @@ -174,10 +142,6 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } if (!req.body.value) { res.status(406).send(`Invalid Data`) return @@ -185,7 +149,7 @@ export class CourseApi { const result = await this.setDestination(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).send(`Destination set successfully.`) } else { this.clearDestination() this.emitCourseInfo() @@ -199,29 +163,22 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } this.clearDestination() this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).send(`Destination cleared.`) } ) - // set activeRoute + // set / clear activeRoute this.server.put( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } + const result = await this.activateRoute(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).send(`Active route set.`) } else { this.clearDestination() this.emitCourseInfo() @@ -229,19 +186,14 @@ export class CourseApi { } } ) - - // clear activeRoute /destination this.server.delete( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/activeRoute`) - if (!this.updateAllowed()) { - res.status(403) - return - } + this.clearDestination() this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).send(`Active route cleared.`) } ) @@ -249,11 +201,6 @@ export class CourseApi { `${COURSE_API_PATH}/activeRoute/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } - // fetch active route data if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return @@ -264,7 +211,7 @@ export class CourseApi { return } - if (req.params.action === 'nextPoint') { + if (req.params.nextPoint) { if ( typeof req.body.value === 'number' && (req.body.value === 1 || req.body.value === -1) @@ -275,10 +222,9 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) - return } } - if (req.params.action === 'pointIndex') { + if (req.params.pointIndex) { if (typeof req.body.value === 'number') { this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( req.body.value, @@ -286,44 +232,36 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) - return } } - // set new destination + // set nextPoint this.courseInfo.nextPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex, - this.courseInfo.activeRoute.reverse + this.courseInfo.activeRoute.pointIndex ) this.courseInfo.nextPoint.type = `RoutePoint` this.courseInfo.nextPoint.href = null // set previousPoint if (this.courseInfo.activeRoute.pointIndex === 0) { - try { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { - res.status(406).send(`Invalid Data`) - return false - } - } catch (err) { - res.status(406).send(`Invalid Data`) + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { return false } } else { this.courseInfo.previousPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex - 1, - this.courseInfo.activeRoute.reverse + this.courseInfo.activeRoute.pointIndex - 1 ) this.courseInfo.previousPoint.type = `RoutePoint` } this.courseInfo.previousPoint.href = null - res.status(200).send('OK') + + res.status(200).send(`OK`) } ) } @@ -343,98 +281,81 @@ export class CourseApi { return false } - const newCourse: any = {} - Object.assign(newCourse, this.courseInfo) - // set activeroute - newCourse.activeRoute.href = route.href + this.courseInfo.activeRoute.href = route.href - if (this.isValidArrivalCircle(route.arrivalCircle as number)) { - newCourse.nextPoint.arrivalCircle = route.arrivalCircle + if (typeof route.arrivalCircle === 'number') { + this.setArrivalCircle(route.arrivalCircle) } - newCourse.activeRoute.startTime = new Date().toISOString() + this.courseInfo.activeRoute.reverse = + typeof route.reverse === 'boolean' ? route.reverse : false - if (typeof route.reverse === 'boolean') { - newCourse.activeRoute.reverse = route.reverse - } + this.courseInfo.activeRoute.pointIndex = + typeof route.pointIndex === 'number' + ? this.parsePointIndex(route.pointIndex, rte) + : 0 - newCourse.activeRoute.pointIndex = this.parsePointIndex( - route.pointIndex as number, - rte - ) + this.courseInfo.activeRoute.startTime = new Date().toISOString() // set nextPoint - newCourse.nextPoint.position = this.getRoutePoint( + this.courseInfo.nextPoint.position = this.getRoutePoint( rte, - newCourse.activeRoute.pointIndex, - newCourse.activeRoute.reverse + this.courseInfo.activeRoute.pointIndex ) - newCourse.nextPoint.type = `RoutePoint` - newCourse.nextPoint.href = null + this.courseInfo.nextPoint.type = `RoutePoint` + this.courseInfo.nextPoint.href = null // set previousPoint - if (newCourse.activeRoute.pointIndex === 0) { - try { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { - return false - } - } catch (err) { + if (this.courseInfo.activeRoute.pointIndex === 0) { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { return false } } else { - newCourse.previousPoint.position = this.getRoutePoint( + this.courseInfo.previousPoint.position = this.getRoutePoint( rte, - newCourse.activeRoute.pointIndex - 1, - newCourse.activeRoute.reverse + this.courseInfo.activeRoute.pointIndex - 1 ) - newCourse.previousPoint.type = `RoutePoint` + this.courseInfo.previousPoint.type = `RoutePoint` } - newCourse.previousPoint.href = null + this.courseInfo.previousPoint.href = null - this.courseInfo = newCourse return true } - private async setDestination(dest: any): Promise { - const newCourse: any = {} - Object.assign(newCourse, this.courseInfo) - + private async setDestination(dest: Destination): Promise { // set nextPoint - if (this.isValidArrivalCircle(dest.arrivalCircle)) { - newCourse.nextPoint.arrivalCircle = dest.arrivalCircle + if (typeof dest.arrivalCircle === 'number') { + this.setArrivalCircle(dest.arrivalCircle) } - newCourse.nextPoint.type = + this.courseInfo.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - newCourse.href = dest.href + this.courseInfo.nextPoint.href = dest.href const href = this.parseHref(dest.href) if (href) { - // fetch waypoint resource details try { const r = await this.server.resourcesApi.getResource( href.type, href.id ) - if (r.position && typeof r.position?.latitude !== 'undefined') { - newCourse.nextPoint.position = r.position - } else { - return false + if (r.position && typeof r.position.value?.latitude !== 'undefined') { + this.courseInfo.nextPoint.position = r.position.value } } catch (err) { return false } } } else if (dest.position) { - newCourse.nextPoint.href = null + this.courseInfo.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { - newCourse.nextPoint.position = dest.position + this.courseInfo.nextPoint.position = dest.position } else { return false } @@ -443,20 +364,15 @@ export class CourseApi { } // set previousPoint - try { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - newCourse.previousPoint.position = position.value - newCourse.previousPoint.type = `VesselPosition` - } else { - return false - } - newCourse.previousPoint.href = null - } catch (err) { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { return false } + this.courseInfo.previousPoint.href = null - this.courseInfo = newCourse return true } @@ -473,12 +389,17 @@ export class CourseApi { this.courseInfo.previousPoint.position = null } - private isValidArrivalCircle(value: number): boolean { - return typeof value === 'number' && value >= 0 + private setArrivalCircle(value: any): boolean { + if (typeof value === 'number' && value >= 0) { + this.courseInfo.nextPoint.arrivalCircle = value + return true + } else { + return false + } } private parsePointIndex(index: number, rte: any): number { - if (typeof index !== 'number' || !rte) { + if (!rte) { return 0 } if (!rte.feature?.geometry?.coordinates) { @@ -497,11 +418,13 @@ export class CourseApi { } private parseHref(href: string): { type: string; id: string } | undefined { - if (!href) { + if (href.length === 0) { return undefined } - - const ref: string[] = href.split('/').slice(-3) + if (href[0] === '/') { + href = href.slice(1) + } + const ref: string[] = href.split('/') if (ref.length < 3) { return undefined } @@ -514,8 +437,8 @@ export class CourseApi { } } - private getRoutePoint(rte: any, index: number, reverse: boolean) { - const pos = reverse + private getRoutePoint(rte: any, index: number) { + const pos = this.courseInfo.activeRoute.reverse ? rte.feature.geometry.coordinates[ rte.feature.geometry.coordinates.length - (index + 1) ] @@ -523,7 +446,7 @@ export class CourseApi { return { latitude: pos[1], longitude: pos[0], - altitude: pos.length === 3 ? pos[2] : 0 + altitude: pos.length == 3 ? pos[2] : 0 } } @@ -541,94 +464,89 @@ export class CourseApi { } private buildDeltaMsg(): any { - const values: Array<{ path: string; value: any }> = [] - const navPath = [ + let values: Array<{path:string, value:any}> = [] + let root = [ 'navigation.courseGreatCircle', 'navigation.courseRhumbline' ] values.push({ - path: `navigation.course`, - value: this.courseInfo - }) - - values.push({ - path: `${navPath[0]}.activeRoute.href`, + path: `${root[0]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${navPath[1]}.activeRoute.href`, + path: `${root[1]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${navPath[0]}.activeRoute.startTime`, + path: `${root[0]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${navPath[1]}.activeRoute.startTime`, + path: `${root[1]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${navPath[0]}.nextPoint.href`, + path: `${root[0]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${navPath[1]}.nextPoint.href`, + path: `${root[1]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${navPath[0]}.nextPoint.position`, + path: `${root[0]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${navPath[1]}.nextPoint.position`, + path: `${root[1]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${navPath[0]}.nextPoint.type`, + path: `${root[0]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${navPath[1]}.nextPoint.type`, + path: `${root[1]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${navPath[0]}.nextPoint.arrivalCircle`, + path: `${root[0]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${navPath[1]}.nextPoint.arrivalCircle`, + path: `${root[1]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${navPath[0]}.previousPoint.href`, + path: `${root[0]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${navPath[1]}.previousPoint.href`, + path: `${root[1]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${navPath[0]}.previousPoint.position`, + path: `${root[0]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${navPath[1]}.previousPoint.position`, + path: `${root[1]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${navPath[0]}.previousPoint.type`, + path: `${root[0]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) values.push({ - path: `${navPath[1]}.previousPoint.type`, + path: `${root[1]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) return { updates: [ { - values + values: values } ] } diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index b162d1314..04653db5e 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -108,7 +108,29 @@ "put": { "tags": ["course"], "summary": "Restart course calculations", - "description": "Sets previousPoint value to current vessel location" + "description": "Sets previousPoint value to current vessel location", + "requestBody": { + "description": "Restart payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } } }, @@ -178,7 +200,29 @@ "delete": { "tags": ["course/destination"], "summary": "Clear destination", - "description": "Sets activeRoute, nextPoint & previousPoint values to null" + "description": "Sets activeRoute, nextPoint & previousPoint values to null", + "requestBody": { + "description": "Delete payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } } }, @@ -269,7 +313,29 @@ "delete": { "tags": ["course/activeRoute"], "summary": "Clear active route", - "description": "Sets activeRoute, nextPoint & previousPoint values to null" + "description": "Sets activeRoute, nextPoint & previousPoint values to null", + "requestBody": { + "description": "Delete payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } } }, From c3cb429e253ff5f540f4023c4290273ca51c37de Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 5 Dec 2021 10:26:47 +1030 Subject: [PATCH 155/410] update detlas --- src/api/course/index.ts | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 6e8e4074d..7510ca1e7 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,23 +1,15 @@ -import { - ResourceProvider, - ResourceProviderMethods, - SignalKResourceType -} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' -import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' - -const API_METHODS: string[] = [] interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any + registerPutHandler: (context:string, path:string, cb:any) => any resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -110,12 +102,26 @@ export class CourseApi { } ) + // + if(this.server.registerPutHandler) { + debug('** Registering PUT Action Handler(s) **') + this.server.registerPutHandler( + 'vessels.self', + 'navigation.course.*', + this.handleCourseApiPut + ); + } + // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { + //test for active destination + if (!this.courseInfo.nextPoint.position) { + return + } // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -266,6 +272,12 @@ export class CourseApi { ) } + private handleCourseApiPut(context:string, path:string, value:any, cb:any) { + + debug('** PUT handler **') + return undefined + } + private async activateRoute(route: ActiveRoute): Promise { let rte: any @@ -470,6 +482,11 @@ export class CourseApi { 'navigation.courseRhumbline' ] + values.push({ + path: `navigation.course`, + value: this.courseInfo + }) + values.push({ path: `${root[0]}.activeRoute.href`, value: this.courseInfo.activeRoute.href From 57ef52df936bcc0c256e55c5f501298192ad58fb Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 4 Dec 2021 17:12:14 +1030 Subject: [PATCH 156/410] init courseApi --- src/api/course/index.ts | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 7510ca1e7..af32aceee 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,15 +1,26 @@ + +import { + ResourceProvider, + ResourceProviderMethods, + SignalKResourceType +} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' +import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' + +const API_METHODS: string[] = [] + interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - registerPutHandler: (context:string, path:string, cb:any) => any + resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -102,26 +113,12 @@ export class CourseApi { } ) - // - if(this.server.registerPutHandler) { - debug('** Registering PUT Action Handler(s) **') - this.server.registerPutHandler( - 'vessels.self', - 'navigation.course.*', - this.handleCourseApiPut - ); - } - // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { - //test for active destination - if (!this.courseInfo.nextPoint.position) { - return - } // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -272,12 +269,6 @@ export class CourseApi { ) } - private handleCourseApiPut(context:string, path:string, value:any, cb:any) { - - debug('** PUT handler **') - return undefined - } - private async activateRoute(route: ActiveRoute): Promise { let rte: any From 3334a47f3bd2761aae633608f66b691542ec5419 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 5 Dec 2021 10:26:47 +1030 Subject: [PATCH 157/410] update detlas --- src/api/course/index.ts | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index af32aceee..7510ca1e7 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,26 +1,15 @@ - -import { - ResourceProvider, - ResourceProviderMethods, - SignalKResourceType -} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' -import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' - -const API_METHODS: string[] = [] - interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - + registerPutHandler: (context:string, path:string, cb:any) => any resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -113,12 +102,26 @@ export class CourseApi { } ) + // + if(this.server.registerPutHandler) { + debug('** Registering PUT Action Handler(s) **') + this.server.registerPutHandler( + 'vessels.self', + 'navigation.course.*', + this.handleCourseApiPut + ); + } + // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { + //test for active destination + if (!this.courseInfo.nextPoint.position) { + return + } // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -269,6 +272,12 @@ export class CourseApi { ) } + private handleCourseApiPut(context:string, path:string, value:any, cb:any) { + + debug('** PUT handler **') + return undefined + } + private async activateRoute(route: ActiveRoute): Promise { let rte: any From 0cb9b7b8d32c97c362aedcb94ea704cf4ba61d96 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 6 Dec 2021 08:39:56 +1030 Subject: [PATCH 158/410] enable put processing --- src/api/course/index.ts | 133 +++++++++++++++++++++++++--------------- 1 file changed, 82 insertions(+), 51 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 7510ca1e7..1566ecdf3 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -9,7 +9,14 @@ const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/cou interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - registerPutHandler: (context:string, path:string, cb:any) => any + securityStrategy: { + shouldAllowPut: ( + req: any, + context: string, + source: any, + path: string + ) => boolean + } resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -92,6 +99,15 @@ export class CourseApi { this.initResourceRoutes() } + private updateAllowed(): boolean { + return this.server.securityStrategy.shouldAllowPut( + this.server, + 'vessels.self', + null, + 'resources' + ) + } + private initResourceRoutes() { // return current course information this.server.get( @@ -102,43 +118,43 @@ export class CourseApi { } ) - // - if(this.server.registerPutHandler) { - debug('** Registering PUT Action Handler(s) **') - this.server.registerPutHandler( - 'vessels.self', - 'navigation.course.*', - this.handleCourseApiPut - ); - } + this.server.put( + `${COURSE_API_PATH}/restart`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/restart`) + if (!this.updateAllowed()) { + res.status(403) + return + } + if (!this.courseInfo.nextPoint.position) { + res.status(406).send(`No active destination!`) + return + } + // set previousPoint to vessel position + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.emitCourseInfo() + res.status(200) + } else { + res.status(406).send(`Vessel position unavailable!`) + } + } + ) - // restart / arrivalCircle this.server.put( - `${COURSE_API_PATH}/:action`, + `${COURSE_API_PATH}/arrivalCircle`, async (req: Request, res: Response) => { - debug(`** PUT ${COURSE_API_PATH}/:action`) - if (req.params.restart) { - //test for active destination - if (!this.courseInfo.nextPoint.position) { - return - } - // set previousPoint to vessel position - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.emitCourseInfo() - res.status(200).send(`Course restarted.`) - } else { - res.status(406).send(`Vessel position unavailable!`) - } + debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) + if (!this.updateAllowed()) { + res.status(403) + return } - if (req.params.arrivalCircle) { - if (this.setArrivalCircle(req.params.arrivalCircle)) { - this.emitCourseInfo() - res.status(200).send(`Destination set successfully.`) - } else { - res.status(406).send(`Invalid Data`) - } + if (this.setArrivalCircle(req.body.value)) { + this.emitCourseInfo() + res.status(200) + } else { + res.status(406).send(`Invalid Data`) } } ) @@ -148,6 +164,10 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) + if (!this.updateAllowed()) { + res.status(403) + return + } if (!req.body.value) { res.status(406).send(`Invalid Data`) return @@ -155,7 +175,7 @@ export class CourseApi { const result = await this.setDestination(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send(`Destination set successfully.`) + res.status(200) } else { this.clearDestination() this.emitCourseInfo() @@ -169,22 +189,29 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) + if (!this.updateAllowed()) { + res.status(403) + return + } this.clearDestination() this.emitCourseInfo() - res.status(200).send(`Destination cleared.`) + res.status(200) } ) - // set / clear activeRoute + // set activeRoute this.server.put( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) - + if (!this.updateAllowed()) { + res.status(403) + return + } const result = await this.activateRoute(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send(`Active route set.`) + res.status(200) } else { this.clearDestination() this.emitCourseInfo() @@ -192,14 +219,19 @@ export class CourseApi { } } ) + + // clear activeRoute /destination this.server.delete( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/activeRoute`) - + if (!this.updateAllowed()) { + res.status(403) + return + } this.clearDestination() this.emitCourseInfo() - res.status(200).send(`Active route cleared.`) + res.status(200) } ) @@ -207,6 +239,11 @@ export class CourseApi { `${COURSE_API_PATH}/activeRoute/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) + if (!this.updateAllowed()) { + res.status(403) + return + } + // fetch route data if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return @@ -217,7 +254,7 @@ export class CourseApi { return } - if (req.params.nextPoint) { + if (req.params.action === 'nextPoint') { if ( typeof req.body.value === 'number' && (req.body.value === 1 || req.body.value === -1) @@ -230,7 +267,7 @@ export class CourseApi { res.status(406).send(`Invalid Data`) } } - if (req.params.pointIndex) { + if (req.params.action === 'pointIndex') { if (typeof req.body.value === 'number') { this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( req.body.value, @@ -241,7 +278,7 @@ export class CourseApi { } } - // set nextPoint + // set new destination this.courseInfo.nextPoint.position = this.getRoutePoint( rte, this.courseInfo.activeRoute.pointIndex @@ -267,17 +304,11 @@ export class CourseApi { } this.courseInfo.previousPoint.href = null - res.status(200).send(`OK`) + res.status(200) } ) } - private handleCourseApiPut(context:string, path:string, value:any, cb:any) { - - debug('** PUT handler **') - return undefined - } - private async activateRoute(route: ActiveRoute): Promise { let rte: any From 6f981ef2b34b692995bddfbb89467e877b11ca95 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 6 Dec 2021 13:34:31 +1030 Subject: [PATCH 159/410] chore: lint --- src/api/course/index.ts | 211 ++++++++++++++++++++---------------- src/api/course/openApi.json | 72 +----------- 2 files changed, 120 insertions(+), 163 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 1566ecdf3..6cd3b80fd 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -11,7 +11,7 @@ interface CourseApplication extends Application { getSelfPath: (path: string) => any securityStrategy: { shouldAllowPut: ( - req: any, + req: Application, context: string, source: any, path: string @@ -104,7 +104,7 @@ export class CourseApi { this.server, 'vessels.self', null, - 'resources' + 'navigation.course' ) } @@ -131,12 +131,14 @@ export class CourseApi { return } // set previousPoint to vessel position - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.emitCourseInfo() - res.status(200) - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.emitCourseInfo() + res.status(200) + } + } catch (err) { res.status(406).send(`Vessel position unavailable!`) } } @@ -150,7 +152,8 @@ export class CourseApi { res.status(403) return } - if (this.setArrivalCircle(req.body.value)) { + if (this.isValidArrivalCircle(req.body.value)) { + this.courseInfo.nextPoint.arrivalCircle = req.body.value this.emitCourseInfo() res.status(200) } else { @@ -243,7 +246,7 @@ export class CourseApi { res.status(403) return } - // fetch route data + // fetch active route data if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return @@ -281,24 +284,30 @@ export class CourseApi { // set new destination this.courseInfo.nextPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex + this.courseInfo.activeRoute.pointIndex, + this.courseInfo.activeRoute.reverse ) this.courseInfo.nextPoint.type = `RoutePoint` this.courseInfo.nextPoint.href = null // set previousPoint if (this.courseInfo.activeRoute.pointIndex === 0) { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } catch (err) { return false } } else { this.courseInfo.previousPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex - 1 + this.courseInfo.activeRoute.pointIndex - 1, + this.courseInfo.activeRoute.reverse ) this.courseInfo.previousPoint.type = `RoutePoint` } @@ -324,81 +333,97 @@ export class CourseApi { return false } + const newDest: any = {} + Object.assign(newDest, this.courseInfo) + // set activeroute - this.courseInfo.activeRoute.href = route.href + newDest.activeRoute.href = route.href - if (typeof route.arrivalCircle === 'number') { - this.setArrivalCircle(route.arrivalCircle) + if (this.isValidArrivalCircle(route.arrivalCircle as number)) { + newDest.nextPoint.arrivalCircle = route.arrivalCircle } - this.courseInfo.activeRoute.reverse = - typeof route.reverse === 'boolean' ? route.reverse : false + newDest.activeRoute.startTime = new Date().toISOString() - this.courseInfo.activeRoute.pointIndex = - typeof route.pointIndex === 'number' - ? this.parsePointIndex(route.pointIndex, rte) - : 0 + if (typeof route.reverse === 'boolean') { + newDest.activeRoute.reverse = route.reverse + } - this.courseInfo.activeRoute.startTime = new Date().toISOString() + newDest.activeRoute.pointIndex = this.parsePointIndex( + route.pointIndex as number, + rte + ) // set nextPoint - this.courseInfo.nextPoint.position = this.getRoutePoint( + newDest.nextPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex + newDest.activeRoute.pointIndex, + newDest.activeRoute.reverse ) - this.courseInfo.nextPoint.type = `RoutePoint` - this.courseInfo.nextPoint.href = null + newDest.nextPoint.type = `RoutePoint` + newDest.nextPoint.href = null // set previousPoint - if (this.courseInfo.activeRoute.pointIndex === 0) { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + if (newDest.activeRoute.pointIndex === 0) { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } catch (err) { return false } } else { - this.courseInfo.previousPoint.position = this.getRoutePoint( + newDest.previousPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex - 1 + newDest.activeRoute.pointIndex - 1, + newDest.activeRoute.reverse ) - this.courseInfo.previousPoint.type = `RoutePoint` + newDest.previousPoint.type = `RoutePoint` } - this.courseInfo.previousPoint.href = null + newDest.previousPoint.href = null + this.courseInfo = newDest return true } - private async setDestination(dest: Destination): Promise { + private async setDestination(dest: any): Promise { + const newDest: any = {} + Object.assign(newDest, this.courseInfo) + // set nextPoint - if (typeof dest.arrivalCircle === 'number') { - this.setArrivalCircle(dest.arrivalCircle) + if (this.isValidArrivalCircle(dest.arrivalCircle)) { + newDest.nextPoint.arrivalCircle = dest.arrivalCircle } - this.courseInfo.nextPoint.type = - typeof dest.type !== 'undefined' ? dest.type : null + newDest.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - this.courseInfo.nextPoint.href = dest.href + newDest.href = dest.href const href = this.parseHref(dest.href) if (href) { + // fetch waypoint resource details try { const r = await this.server.resourcesApi.getResource( href.type, href.id ) - if (r.position && typeof r.position.value?.latitude !== 'undefined') { - this.courseInfo.nextPoint.position = r.position.value + if (r.position && typeof r.position?.latitude !== 'undefined') { + newDest.nextPoint.position = r.position + } else { + return false } } catch (err) { return false } } } else if (dest.position) { - this.courseInfo.nextPoint.href = null + newDest.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { - this.courseInfo.nextPoint.position = dest.position + newDest.nextPoint.position = dest.position } else { return false } @@ -407,15 +432,20 @@ export class CourseApi { } // set previousPoint - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + newDest.previousPoint.position = position.value + newDest.previousPoint.type = `VesselPosition` + } else { + return false + } + newDest.previousPoint.href = null + } catch (err) { return false } - this.courseInfo.previousPoint.href = null + this.courseInfo = newDest return true } @@ -432,17 +462,12 @@ export class CourseApi { this.courseInfo.previousPoint.position = null } - private setArrivalCircle(value: any): boolean { - if (typeof value === 'number' && value >= 0) { - this.courseInfo.nextPoint.arrivalCircle = value - return true - } else { - return false - } + private isValidArrivalCircle(value: number): boolean { + return typeof value === 'number' && value >= 0 } private parsePointIndex(index: number, rte: any): number { - if (!rte) { + if (typeof index !== 'number' || !rte) { return 0 } if (!rte.feature?.geometry?.coordinates) { @@ -461,13 +486,11 @@ export class CourseApi { } private parseHref(href: string): { type: string; id: string } | undefined { - if (href.length === 0) { + if (!href) { return undefined } - if (href[0] === '/') { - href = href.slice(1) - } - const ref: string[] = href.split('/') + + const ref: string[] = href.split('/').slice(-3) if (ref.length < 3) { return undefined } @@ -480,8 +503,8 @@ export class CourseApi { } } - private getRoutePoint(rte: any, index: number) { - const pos = this.courseInfo.activeRoute.reverse + private getRoutePoint(rte: any, index: number, reverse: boolean) { + const pos = reverse ? rte.feature.geometry.coordinates[ rte.feature.geometry.coordinates.length - (index + 1) ] @@ -489,7 +512,7 @@ export class CourseApi { return { latitude: pos[1], longitude: pos[0], - altitude: pos.length == 3 ? pos[2] : 0 + altitude: pos.length === 3 ? pos[2] : 0 } } @@ -507,8 +530,8 @@ export class CourseApi { } private buildDeltaMsg(): any { - let values: Array<{path:string, value:any}> = [] - let root = [ + const values: Array<{ path: string; value: any }> = [] + const navPath = [ 'navigation.courseGreatCircle', 'navigation.courseRhumbline' ] @@ -519,82 +542,82 @@ export class CourseApi { }) values.push({ - path: `${root[0]}.activeRoute.href`, + path: `${navPath[0]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${root[1]}.activeRoute.href`, + path: `${navPath[1]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${root[0]}.activeRoute.startTime`, + path: `${navPath[0]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${root[1]}.activeRoute.startTime`, + path: `${navPath[1]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${root[0]}.nextPoint.href`, + path: `${navPath[0]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${root[1]}.nextPoint.href`, + path: `${navPath[1]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${root[0]}.nextPoint.position`, + path: `${navPath[0]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${root[1]}.nextPoint.position`, + path: `${navPath[1]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${root[0]}.nextPoint.type`, + path: `${navPath[0]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${root[1]}.nextPoint.type`, + path: `${navPath[1]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${root[0]}.nextPoint.arrivalCircle`, + path: `${navPath[0]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${root[1]}.nextPoint.arrivalCircle`, + path: `${navPath[1]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${root[0]}.previousPoint.href`, + path: `${navPath[0]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${root[1]}.previousPoint.href`, + path: `${navPath[1]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${root[0]}.previousPoint.position`, + path: `${navPath[0]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${root[1]}.previousPoint.position`, + path: `${navPath[1]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${root[0]}.previousPoint.type`, + path: `${navPath[0]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) values.push({ - path: `${root[1]}.previousPoint.type`, + path: `${navPath[1]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) return { updates: [ { - values: values + values } ] } diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index 04653db5e..b162d1314 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -108,29 +108,7 @@ "put": { "tags": ["course"], "summary": "Restart course calculations", - "description": "Sets previousPoint value to current vessel location", - "requestBody": { - "description": "Restart payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } - } - } - } - } - } + "description": "Sets previousPoint value to current vessel location" } }, @@ -200,29 +178,7 @@ "delete": { "tags": ["course/destination"], "summary": "Clear destination", - "description": "Sets activeRoute, nextPoint & previousPoint values to null", - "requestBody": { - "description": "Delete payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } - } - } - } - } - } + "description": "Sets activeRoute, nextPoint & previousPoint values to null" } }, @@ -313,29 +269,7 @@ "delete": { "tags": ["course/activeRoute"], "summary": "Clear active route", - "description": "Sets activeRoute, nextPoint & previousPoint values to null", - "requestBody": { - "description": "Delete payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } - } - } - } - } - } + "description": "Sets activeRoute, nextPoint & previousPoint values to null" } }, From 01aaac5eb0790711f636009e4fe9ba56110009fb Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 8 Dec 2021 09:27:06 +1030 Subject: [PATCH 160/410] add 30sec delta interval --- src/api/course/index.ts | 100 ++++++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 45 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 6cd3b80fd..2aa57e8a6 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -6,6 +6,8 @@ const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const DELTA_INTERVAL: number = 30000 + interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any @@ -97,6 +99,11 @@ export class CourseApi { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app this.initResourceRoutes() + setInterval( ()=> { + if(this.courseInfo.nextPoint.position) { + this.emitCourseInfo() + } + }, DELTA_INTERVAL) } private updateAllowed(): boolean { @@ -123,7 +130,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/restart`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (!this.courseInfo.nextPoint.position) { @@ -136,7 +143,7 @@ export class CourseApi { if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } } catch (err) { res.status(406).send(`Vessel position unavailable!`) @@ -149,13 +156,13 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (this.isValidArrivalCircle(req.body.value)) { this.courseInfo.nextPoint.arrivalCircle = req.body.value this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { res.status(406).send(`Invalid Data`) } @@ -168,7 +175,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (!req.body.value) { @@ -178,7 +185,7 @@ export class CourseApi { const result = await this.setDestination(req.body.value) if (result) { this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { this.clearDestination() this.emitCourseInfo() @@ -193,12 +200,12 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } this.clearDestination() this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } ) @@ -208,13 +215,13 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } const result = await this.activateRoute(req.body.value) if (result) { this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { this.clearDestination() this.emitCourseInfo() @@ -234,7 +241,7 @@ export class CourseApi { } this.clearDestination() this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } ) @@ -243,7 +250,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } // fetch active route data @@ -268,6 +275,7 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) + return } } if (req.params.action === 'pointIndex') { @@ -278,6 +286,7 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) + return } } @@ -298,9 +307,11 @@ export class CourseApi { this.courseInfo.previousPoint.position = position.value this.courseInfo.previousPoint.type = `VesselPosition` } else { + res.status(406).send(`Invalid Data`) return false } } catch (err) { + res.status(406).send(`Invalid Data`) return false } } else { @@ -312,8 +323,7 @@ export class CourseApi { this.courseInfo.previousPoint.type = `RoutePoint` } this.courseInfo.previousPoint.href = null - - res.status(200) + res.status(200).send('OK') } ) } @@ -333,38 +343,38 @@ export class CourseApi { return false } - const newDest: any = {} - Object.assign(newDest, this.courseInfo) + const newCourse: any = {} + Object.assign(newCourse, this.courseInfo) // set activeroute - newDest.activeRoute.href = route.href + newCourse.activeRoute.href = route.href if (this.isValidArrivalCircle(route.arrivalCircle as number)) { - newDest.nextPoint.arrivalCircle = route.arrivalCircle + newCourse.nextPoint.arrivalCircle = route.arrivalCircle } - newDest.activeRoute.startTime = new Date().toISOString() + newCourse.activeRoute.startTime = new Date().toISOString() if (typeof route.reverse === 'boolean') { - newDest.activeRoute.reverse = route.reverse + newCourse.activeRoute.reverse = route.reverse } - newDest.activeRoute.pointIndex = this.parsePointIndex( + newCourse.activeRoute.pointIndex = this.parsePointIndex( route.pointIndex as number, rte ) // set nextPoint - newDest.nextPoint.position = this.getRoutePoint( + newCourse.nextPoint.position = this.getRoutePoint( rte, - newDest.activeRoute.pointIndex, - newDest.activeRoute.reverse + newCourse.activeRoute.pointIndex, + newCourse.activeRoute.reverse ) - newDest.nextPoint.type = `RoutePoint` - newDest.nextPoint.href = null + newCourse.nextPoint.type = `RoutePoint` + newCourse.nextPoint.href = null // set previousPoint - if (newDest.activeRoute.pointIndex === 0) { + if (newCourse.activeRoute.pointIndex === 0) { try { const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -377,32 +387,32 @@ export class CourseApi { return false } } else { - newDest.previousPoint.position = this.getRoutePoint( + newCourse.previousPoint.position = this.getRoutePoint( rte, - newDest.activeRoute.pointIndex - 1, - newDest.activeRoute.reverse + newCourse.activeRoute.pointIndex - 1, + newCourse.activeRoute.reverse ) - newDest.previousPoint.type = `RoutePoint` + newCourse.previousPoint.type = `RoutePoint` } - newDest.previousPoint.href = null + newCourse.previousPoint.href = null - this.courseInfo = newDest + this.courseInfo = newCourse return true } private async setDestination(dest: any): Promise { - const newDest: any = {} - Object.assign(newDest, this.courseInfo) + const newCourse: any = {} + Object.assign(newCourse, this.courseInfo) // set nextPoint if (this.isValidArrivalCircle(dest.arrivalCircle)) { - newDest.nextPoint.arrivalCircle = dest.arrivalCircle + newCourse.nextPoint.arrivalCircle = dest.arrivalCircle } - newDest.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null + newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - newDest.href = dest.href + newCourse.href = dest.href const href = this.parseHref(dest.href) if (href) { // fetch waypoint resource details @@ -412,7 +422,7 @@ export class CourseApi { href.id ) if (r.position && typeof r.position?.latitude !== 'undefined') { - newDest.nextPoint.position = r.position + newCourse.nextPoint.position = r.position } else { return false } @@ -421,9 +431,9 @@ export class CourseApi { } } } else if (dest.position) { - newDest.nextPoint.href = null + newCourse.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { - newDest.nextPoint.position = dest.position + newCourse.nextPoint.position = dest.position } else { return false } @@ -435,17 +445,17 @@ export class CourseApi { try { const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { - newDest.previousPoint.position = position.value - newDest.previousPoint.type = `VesselPosition` + newCourse.previousPoint.position = position.value + newCourse.previousPoint.type = `VesselPosition` } else { return false } - newDest.previousPoint.href = null + newCourse.previousPoint.href = null } catch (err) { return false } - this.courseInfo = newDest + this.courseInfo = newCourse return true } From 5dec80facb3e567788557b6b50f6929a9e02941d Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 12 Dec 2021 14:29:38 +1030 Subject: [PATCH 161/410] chore: lint --- src/api/course/index.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 2aa57e8a6..1e719297a 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -6,7 +6,7 @@ const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const DELTA_INTERVAL: number = 30000 +const DELTA_INTERVAL: number = 30000 interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void @@ -99,8 +99,8 @@ export class CourseApi { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app this.initResourceRoutes() - setInterval( ()=> { - if(this.courseInfo.nextPoint.position) { + setInterval(() => { + if (this.courseInfo.nextPoint.position) { this.emitCourseInfo() } }, DELTA_INTERVAL) @@ -409,7 +409,8 @@ export class CourseApi { newCourse.nextPoint.arrivalCircle = dest.arrivalCircle } - newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null + newCourse.nextPoint.type = + typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { newCourse.href = dest.href From 8f885c635fa7422c5fb6717c22a7749e3b3ccfb9 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Fri, 31 Dec 2021 17:06:01 +1030 Subject: [PATCH 162/410] persist courseInfo to settings file --- src/api/course/index.ts | 58 ++++++++++++++++++++++++++++++++--------- src/api/store.ts | 42 +++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 13 deletions(-) create mode 100644 src/api/store.ts diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 1e719297a..dc26e61f4 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,5 +1,8 @@ import Debug from 'debug' import { Application, Request, Response } from 'express' +import path from 'path' +import { WithConfig, WithSecurityStrategy, WithSignalK } from '../../app' +import { Store } from '../store' const debug = Debug('signalk:courseApi') @@ -8,17 +11,15 @@ const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/cou const DELTA_INTERVAL: number = 30000 +interface CourseApplication + extends Application, + WithConfig, + WithSignalK, + WithSecurityStrategy {} + interface CourseApplication extends Application { - handleMessage: (id: string, data: any) => void + // handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - securityStrategy: { - shouldAllowPut: ( - req: Application, - context: string, - source: any, - path: string - ) => boolean - } resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -90,22 +91,48 @@ export class CourseApi { } } + private store: Store + constructor(app: CourseApplication) { this.server = app - this.start(app) + this.store = new Store(path.join(app.config.configPath, 'api/course')) + this.start(app).catch(error => { + console.log(error) + }) } - private start(app: any) { + private async start(app: any) { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app this.initResourceRoutes() + + try { + const storeData = await this.store.read() + this.courseInfo = this.validateCourseInfo(storeData) + } catch (error) { + debug('** No persisted course data (using default) **') + this.store.write(this.courseInfo).catch(error => { + console.log(error) + }) + } + debug(this.courseInfo) + setInterval(() => { if (this.courseInfo.nextPoint.position) { - this.emitCourseInfo() + this.emitCourseInfo(true) } }, DELTA_INTERVAL) } + private validateCourseInfo(info: CourseInfo) { + if (info.activeRoute && info.nextPoint && info.previousPoint) { + return info + } else { + debug(`** Error: Loaded course data is invalid!! (using default) **`) + return this.courseInfo + } + } + private updateAllowed(): boolean { return this.server.securityStrategy.shouldAllowPut( this.server, @@ -634,7 +661,12 @@ export class CourseApi { } } - private emitCourseInfo() { + private emitCourseInfo(noSave: boolean = false) { this.server.handleMessage('courseApi', this.buildDeltaMsg()) + if (!noSave) { + this.store.write(this.courseInfo).catch(error => { + console.log(error) + }) + } } } diff --git a/src/api/store.ts b/src/api/store.ts new file mode 100644 index 000000000..5e979d13b --- /dev/null +++ b/src/api/store.ts @@ -0,0 +1,42 @@ +import { constants } from 'fs' +import { access, mkdir, readFile, writeFile } from 'fs/promises' +import path from 'path' + +export class Store { + private filePath: string = '' + private fileName: string = '' + + constructor(filePath: string, fileName: string = 'settings.json') { + this.filePath = filePath + this.fileName = fileName + this.init().catch(error => { + console.log(error) + }) + } + + async read(): Promise { + const data = await readFile(path.join(this.filePath, this.fileName), 'utf8') + return JSON.parse(data) + } + + write(data: any): Promise { + return writeFile( + path.join(this.filePath, this.fileName), + JSON.stringify(data) + ) + } + + private async init() { + try { + /* tslint:disable:no-bitwise */ + await access(this.filePath, constants.R_OK | constants.W_OK) + /* tslint:enable:no-bitwise */ + } catch (error) { + try { + await mkdir(this.filePath, { recursive: true }) + } catch (error) { + console.log(`Error: Unable to create ${this.filePath}`) + } + } + } +} From c8c40bdbdf673f243a74933f9e034f69b902686c Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 1 Jan 2022 16:16:59 +1030 Subject: [PATCH 163/410] add getVesselPosition --- src/api/course/index.ts | 26 ++++++++++++++++++-------- src/api/course/openApi.json | 24 ++---------------------- src/put.js | 6 +++--- 3 files changed, 23 insertions(+), 33 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index dc26e61f4..933973ece 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -2,6 +2,7 @@ import Debug from 'debug' import { Application, Request, Response } from 'express' import path from 'path' import { WithConfig, WithSecurityStrategy, WithSignalK } from '../../app' +import _ from 'lodash' import { Store } from '../store' const debug = Debug('signalk:courseApi') @@ -18,8 +19,6 @@ interface CourseApplication WithSecurityStrategy {} interface CourseApplication extends Application { - // handleMessage: (id: string, data: any) => void - getSelfPath: (path: string) => any resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -93,6 +92,10 @@ export class CourseApi { private store: Store + private getVesselPosition() { + return _.get( (this.server.signalk as any).self, 'navigation.position') + } + constructor(app: CourseApplication) { this.server = app this.store = new Store(path.join(app.config.configPath, 'api/course')) @@ -116,6 +119,7 @@ export class CourseApi { }) } debug(this.courseInfo) + this.emitCourseInfo(true) setInterval(() => { if (this.courseInfo.nextPoint.position) { @@ -166,7 +170,7 @@ export class CourseApi { } // set previousPoint to vessel position try { - const position: any = this.server.getSelfPath('navigation.position') + const position: any = this.getVesselPosition() if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.emitCourseInfo() @@ -206,6 +210,7 @@ export class CourseApi { return } if (!req.body.value) { + debug(`** Error: req.body.value is null || undefined!`) res.status(406).send(`Invalid Data`) return } @@ -329,7 +334,7 @@ export class CourseApi { // set previousPoint if (this.courseInfo.activeRoute.pointIndex === 0) { try { - const position: any = this.server.getSelfPath('navigation.position') + const position: any = this.getVesselPosition() if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.courseInfo.previousPoint.type = `VesselPosition` @@ -403,7 +408,7 @@ export class CourseApi { // set previousPoint if (newCourse.activeRoute.pointIndex === 0) { try { - const position: any = this.server.getSelfPath('navigation.position') + const position: any = this.getVesselPosition() if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.courseInfo.previousPoint.type = `VesselPosition` @@ -440,7 +445,6 @@ export class CourseApi { typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - newCourse.href = dest.href const href = this.parseHref(dest.href) if (href) { // fetch waypoint resource details @@ -451,6 +455,8 @@ export class CourseApi { ) if (r.position && typeof r.position?.latitude !== 'undefined') { newCourse.nextPoint.position = r.position + newCourse.nextPoint.href = dest.href + newCourse.nextPoint.type = 'Waypoint' } else { return false } @@ -460,9 +466,11 @@ export class CourseApi { } } else if (dest.position) { newCourse.nextPoint.href = null + newCourse.nextPoint.type = 'Location' if (typeof dest.position.latitude !== 'undefined') { newCourse.nextPoint.position = dest.position } else { + debug(`** Error: position.latitude is undefined!`) return false } } else { @@ -471,15 +479,17 @@ export class CourseApi { // set previousPoint try { - const position: any = this.server.getSelfPath('navigation.position') + const position: any = this.getVesselPosition() if (position && position.value) { newCourse.previousPoint.position = position.value newCourse.previousPoint.type = `VesselPosition` + newCourse.previousPoint.href = null } else { + debug(`** Error: navigation.position.value is undefined! (${position})`) return false } - newCourse.previousPoint.href = null } catch (err) { + debug(`** Error: unable to retrieve navigation.position! (${err})`) return false } diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index b162d1314..7a87e4768 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -164,10 +164,6 @@ "example": 500 } } - }, - "source": { - "type": "string", - "example": "freeboard-sk" } } } @@ -200,11 +196,7 @@ "type": "number", "format": "float", "example": 500 - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } + } } } } @@ -255,11 +247,7 @@ "example": 500 } } - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } + } } } } @@ -292,10 +280,6 @@ "description": "increment (1) / decrement (-1) index of point in route to use as destination", "enum": [-1,1], "example": -1 - }, - "source": { - "type": "string", - "example": "freeboard-sk" } } } @@ -324,10 +308,6 @@ "description": "Index of point in route to use as destination", "minimum": 0, "example": 2 - }, - "source": { - "type": "string", - "example": "freeboard-sk" } } } diff --git a/src/put.js b/src/put.js index c7cf42616..fa745590b 100644 --- a/src/put.js +++ b/src/put.js @@ -30,14 +30,14 @@ module.exports = { app.deRegisterActionHandler = deRegisterActionHandler app.put(apiPathPrefix + '*', function(req, res, next) { - // ** ignore resources paths ** + // ** ignore resources API paths ** if (req.path.split('/')[4] === 'resources') { next() return } - // ** ignore course paths ** - if (req.path.indexOf('/navigation/course') !== -1) { + // ** ignore course API paths ** + if (req.path.indexOf('/navigation/course/') !== -1) { next() return } From 43bb1aa4a234d73489c20350b1889dc88775c5ca Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 1 Jan 2022 16:36:11 +1030 Subject: [PATCH 164/410] align openApi.json --- src/api/course/index.ts | 8 +-- src/api/course/openApi.json | 114 ++++++++++++++++-------------------- 2 files changed, 55 insertions(+), 67 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 933973ece..97c01007a 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -209,12 +209,12 @@ export class CourseApi { res.status(403).send('Unauthorised') return } - if (!req.body.value) { - debug(`** Error: req.body.value is null || undefined!`) + if (!req.body) { + debug(`** Error: req.body is null || undefined!`) res.status(406).send(`Invalid Data`) return } - const result = await this.setDestination(req.body.value) + const result = await this.setDestination(req.body) if (result) { this.emitCourseInfo() res.status(200).send('OK') @@ -250,7 +250,7 @@ export class CourseApi { res.status(403).send('Unauthorised') return } - const result = await this.activateRoute(req.body.value) + const result = await this.activateRoute(req.body) if (result) { this.emitCourseInfo() res.status(200).send('OK') diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index 7a87e4768..ef0d480c4 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -124,46 +124,40 @@ "application/json": { "schema": { "type": "object", - "required": ["value"], "properties": { - "value": { + "position": { "type": "object", + "description": "Destination position", + "required": [ + "latitude", + "longitude" + ], "properties": { - "position": { - "type": "object", - "description": "Destination position", - "required": [ - "latitude", - "longitude" - ], - "properties": { - "latitude": { - "type": "number", - "format": "float" - }, - "longitude": { - "type": "number", - "format": "float" - } - }, - "example": {"latitude":-29.5,"longitude":137.5} - }, - "href": { - "type": "string", - "description": "A reference (URL) to an object (under /resources) this point is related to", - "example": "/resources/waypoints/urn:mrn:signalk:uuid:3dd34dcc-36bf-4d61-ba80-233799b25672" - }, - "type": { - "type": "string", - "description": "Type of point", - "example": "POI" + "latitude": { + "type": "number", + "format": "float" }, - "arrivalCircle": { + "longitude": { "type": "number", - "format": "float", - "example": 500 + "format": "float" } - } + }, + "example": {"latitude":-29.5,"longitude":137.5} + }, + "href": { + "type": "string", + "description": "A reference (URL) to an object (under /resources) this point is related to", + "example": "/resources/waypoints/urn:mrn:signalk:uuid:3dd34dcc-36bf-4d61-ba80-233799b25672" + }, + "type": { + "type": "string", + "description": "Type of point", + "example": "POI" + }, + "arrivalCircle": { + "type": "number", + "format": "float", + "example": 500 } } } @@ -217,36 +211,30 @@ "application/json": { "schema": { "type": "object", - "required": ["value"], "properties": { - "value": { - "type": "object", - "properties": { - "href": { - "type": "string", - "description": "Path to route resource", - "example": "/resources/routes/urn:mrn:signalk:uuid:3dd34dcc-36bf-4d61-ba80-233799b25672" - }, - "pointIndex": { - "type": "number", - "format": "int64", - "description": "Zero based index of route point to use as destination", - "default": 0, - "minimum": 0, - "example": 2 - }, - "reverse": { - "type": "boolean", - "default": false, - "description": "If true performs operations on route points in reverse order", - "example": 2 - }, - "arrivalCircle": { - "type": "number", - "format": "float", - "example": 500 - } - } + "href": { + "type": "string", + "description": "Path to route resource", + "example": "/resources/routes/urn:mrn:signalk:uuid:3dd34dcc-36bf-4d61-ba80-233799b25672" + }, + "pointIndex": { + "type": "number", + "format": "int64", + "description": "Zero based index of route point to use as destination", + "default": 0, + "minimum": 0, + "example": 2 + }, + "reverse": { + "type": "boolean", + "default": false, + "description": "If true performs operations on route points in reverse order", + "example": 2 + }, + "arrivalCircle": { + "type": "number", + "format": "float", + "example": 500 } } } From e66fcac599e6b6ddc20bb69f06edef6a0be291e6 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 2 Jan 2022 14:33:38 +1030 Subject: [PATCH 165/410] move `store.ts` to serverstate` folder --- src/api/course/index.ts | 4 +++- src/{api => serverstate}/store.ts | 0 2 files changed, 3 insertions(+), 1 deletion(-) rename src/{api => serverstate}/store.ts (100%) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 97c01007a..e5fb43ccc 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -3,7 +3,7 @@ import { Application, Request, Response } from 'express' import path from 'path' import { WithConfig, WithSecurityStrategy, WithSignalK } from '../../app' import _ from 'lodash' -import { Store } from '../store' +import { Store } from '../../serverstate/store' const debug = Debug('signalk:courseApi') @@ -175,6 +175,8 @@ export class CourseApi { this.courseInfo.previousPoint.position = position.value this.emitCourseInfo() res.status(200).send('OK') + } else { + res.status(406).send(`Vessel position unavailable!`) } } catch (err) { res.status(406).send(`Vessel position unavailable!`) diff --git a/src/api/store.ts b/src/serverstate/store.ts similarity index 100% rename from src/api/store.ts rename to src/serverstate/store.ts From 12c54df8be912ea6c2ec119a6d045fa4e2cb7f42 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 3 Jan 2022 13:10:10 +1030 Subject: [PATCH 166/410] change saved couorse path to serverstate --- src/api/course/index.ts | 5 +---- src/serverstate/store.ts | 9 +++++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index e5fb43ccc..19f1d6eff 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -98,7 +98,7 @@ export class CourseApi { constructor(app: CourseApplication) { this.server = app - this.store = new Store(path.join(app.config.configPath, 'api/course')) + this.store = new Store(path.join(app.config.configPath, 'serverstate/course')) this.start(app).catch(error => { console.log(error) }) @@ -114,9 +114,6 @@ export class CourseApi { this.courseInfo = this.validateCourseInfo(storeData) } catch (error) { debug('** No persisted course data (using default) **') - this.store.write(this.courseInfo).catch(error => { - console.log(error) - }) } debug(this.courseInfo) this.emitCourseInfo(true) diff --git a/src/serverstate/store.ts b/src/serverstate/store.ts index 5e979d13b..6839806ea 100644 --- a/src/serverstate/store.ts +++ b/src/serverstate/store.ts @@ -10,13 +10,18 @@ export class Store { this.filePath = filePath this.fileName = fileName this.init().catch(error => { + console.log(`Could not initialise ${path.join(this.filePath, this.fileName)}`) console.log(error) }) } async read(): Promise { - const data = await readFile(path.join(this.filePath, this.fileName), 'utf8') - return JSON.parse(data) + try { + const data = await readFile(path.join(this.filePath, this.fileName), 'utf8') + return JSON.parse(data) + } catch (error) { + throw(error) + } } write(data: any): Promise { From 0559e658eb69713adbeebd3eb8e01928a1bf7c5a Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 8 Jan 2022 13:13:40 +1030 Subject: [PATCH 167/410] regresstion testing fixes --- src/api/course/index.ts | 134 +++++++++++++++++++++-------- src/api/course/openApi.json | 13 +++ src/api/resources/index.ts | 165 +++++++++++++++++++++++++----------- src/api/responses.ts | 31 +++++++ src/serverstate/store.ts | 11 ++- 5 files changed, 265 insertions(+), 89 deletions(-) create mode 100644 src/api/responses.ts diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 19f1d6eff..ae033a5b3 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,9 +1,10 @@ import Debug from 'debug' import { Application, Request, Response } from 'express' +import _ from 'lodash' import path from 'path' import { WithConfig, WithSecurityStrategy, WithSignalK } from '../../app' -import _ from 'lodash' import { Store } from '../../serverstate/store' +import { Responses } from '../responses' const debug = Debug('signalk:courseApi') @@ -52,6 +53,7 @@ interface CourseInfo { href: string | null startTime: string | null pointIndex: number + pointTotal: number reverse: boolean } nextPoint: { @@ -75,6 +77,7 @@ export class CourseApi { href: null, startTime: null, pointIndex: 0, + pointTotal: 0, reverse: false }, nextPoint: { @@ -92,22 +95,24 @@ export class CourseApi { private store: Store - private getVesselPosition() { - return _.get( (this.server.signalk as any).self, 'navigation.position') - } - constructor(app: CourseApplication) { this.server = app - this.store = new Store(path.join(app.config.configPath, 'serverstate/course')) + this.store = new Store( + path.join(app.config.configPath, 'serverstate/course') + ) this.start(app).catch(error => { console.log(error) }) } + private getVesselPosition() { + return _.get((this.server.signalk as any).self, 'navigation.position') + } + private async start(app: any) { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app - this.initResourceRoutes() + this.initCourseRoutes() try { const storeData = await this.store.read() @@ -143,7 +148,7 @@ export class CourseApi { ) } - private initResourceRoutes() { + private initCourseRoutes() { // return current course information this.server.get( `${COURSE_API_PATH}`, @@ -158,11 +163,15 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/restart`) if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') + res.status(403).json(Responses.unauthorised) return } if (!this.courseInfo.nextPoint.position) { - res.status(406).send(`No active destination!`) + res.status(406).json({ + state: 'FAILED', + statusCode: 406, + message: `No active destination!` + }) return } // set previousPoint to vessel position @@ -171,12 +180,20 @@ export class CourseApi { if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).json(Responses.ok) } else { - res.status(406).send(`Vessel position unavailable!`) + res.status(406).json({ + state: 'FAILED', + statusCode: 406, + message: `Vessel position unavailable!` + }) } } catch (err) { - res.status(406).send(`Vessel position unavailable!`) + res.status(406).json({ + state: 'FAILED', + statusCode: 406, + message: `Vessel position unavailable!` + }) } } ) @@ -186,15 +203,15 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') + res.status(403).json(Responses.unauthorised) return } if (this.isValidArrivalCircle(req.body.value)) { this.courseInfo.nextPoint.arrivalCircle = req.body.value this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).json(Responses.ok) } else { - res.status(406).send(`Invalid Data`) + res.status(406).json(Responses.invalid) } } ) @@ -205,22 +222,22 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') + res.status(403).json(Responses.unauthorised) return } if (!req.body) { debug(`** Error: req.body is null || undefined!`) - res.status(406).send(`Invalid Data`) + res.status(406).json(Responses.invalid) return } const result = await this.setDestination(req.body) if (result) { this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).json(Responses.ok) } else { this.clearDestination() this.emitCourseInfo() - res.status(406).send(`Invalid Data`) + res.status(406).json(Responses.invalid) } } ) @@ -231,12 +248,12 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') + res.status(403).json(Responses.unauthorised) return } this.clearDestination() this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).json(Responses.ok) } ) @@ -246,17 +263,17 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') + res.status(403).json(Responses.unauthorised) return } const result = await this.activateRoute(req.body) if (result) { this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).json(Responses.ok) } else { this.clearDestination() this.emitCourseInfo() - res.status(406).send(`Invalid Data`) + res.status(406).json(Responses.invalid) } } ) @@ -267,31 +284,31 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/activeRoute`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).json(Responses.unauthorised) return } this.clearDestination() this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).json(Responses.ok) } ) this.server.put( `${COURSE_API_PATH}/activeRoute/:action`, async (req: Request, res: Response) => { - debug(`** PUT ${COURSE_API_PATH}/activeRoute`) + debug(`** PUT ${COURSE_API_PATH}/activeRoute/${req.params.action}`) if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') + res.status(403).json(Responses.unauthorised) return } // fetch active route data if (!this.courseInfo.activeRoute.href) { - res.status(406).send(`Invalid Data`) + res.status(406).json(Responses.invalid) return } const rte = await this.getRoute(this.courseInfo.activeRoute.href) if (!rte) { - res.status(406).send(`Invalid Data`) + res.status(406).json(Responses.invalid) return } @@ -305,10 +322,11 @@ export class CourseApi { rte ) } else { - res.status(406).send(`Invalid Data`) + res.status(406).json(Responses.invalid) return } } + if (req.params.action === 'pointIndex') { if (typeof req.body.value === 'number') { this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( @@ -316,11 +334,33 @@ export class CourseApi { rte ) } else { - res.status(406).send(`Invalid Data`) + res.status(406).json(Responses.invalid) return } } + if (req.params.action === 'refresh') { + this.courseInfo.activeRoute.pointTotal = + rte.feature.geometry.coordinates.length + let idx: number = -1 + for (let i = 0; i < rte.feature.geometry.coordinates.length; i++) { + if ( + rte.feature.geometry.coordinates[i][0] === + this.courseInfo.nextPoint.position?.longitude && + rte.feature.geometry.coordinates[i][1] === + this.courseInfo.nextPoint.position?.latitude + ) { + idx = i + } + } + if (idx !== -1) { + this.courseInfo.activeRoute.pointIndex = idx + } + this.emitCourseInfo() + res.status(200).json(Responses.ok) + return + } + // set new destination this.courseInfo.nextPoint.position = this.getRoutePoint( rte, @@ -338,11 +378,12 @@ export class CourseApi { this.courseInfo.previousPoint.position = position.value this.courseInfo.previousPoint.type = `VesselPosition` } else { - res.status(406).send(`Invalid Data`) + res.status(406).json(Responses.invalid) return false } } catch (err) { - res.status(406).send(`Invalid Data`) + console.log(`** Error: unable to retrieve vessel position!`) + res.status(406).json(Responses.invalid) return false } } else { @@ -354,7 +395,8 @@ export class CourseApi { this.courseInfo.previousPoint.type = `RoutePoint` } this.courseInfo.previousPoint.href = null - res.status(200).send('OK') + this.emitCourseInfo() + res.status(200).json(Responses.ok) } ) } @@ -365,9 +407,11 @@ export class CourseApi { if (route.href) { rte = await this.getRoute(route.href) if (!rte) { + console.log(`** Could not retrieve route information for ${route.href}`) return false } if (!Array.isArray(rte.feature?.geometry?.coordinates)) { + debug(`** Invalid route coordinate data! (${route.href})`) return false } } else { @@ -394,6 +438,7 @@ export class CourseApi { route.pointIndex as number, rte ) + newCourse.activeRoute.pointTotal = rte.feature.geometry.coordinates.length // set nextPoint newCourse.nextPoint.position = this.getRoutePoint( @@ -412,6 +457,7 @@ export class CourseApi { this.courseInfo.previousPoint.position = position.value this.courseInfo.previousPoint.type = `VesselPosition` } else { + console.log(`** Error: unable to retrieve vessel position!`) return false } } catch (err) { @@ -457,9 +503,13 @@ export class CourseApi { newCourse.nextPoint.href = dest.href newCourse.nextPoint.type = 'Waypoint' } else { + debug(`** Invalid waypoint coordinate data! (${dest.href})`) return false } } catch (err) { + console.log( + `** Could not retrieve waypoint information for ${dest.href}` + ) return false } } @@ -476,6 +526,13 @@ export class CourseApi { return false } + // clear activeRoute values + newCourse.activeRoute.href = null + newCourse.activeRoute.startTime = null + newCourse.activeRoute.pointindex = 0 + newCourse.activeRoute.pointTotal = 0 + newCourse.activeRoute.reverse = false + // set previousPoint try { const position: any = this.getVesselPosition() @@ -488,7 +545,7 @@ export class CourseApi { return false } } catch (err) { - debug(`** Error: unable to retrieve navigation.position! (${err})`) + console.log(`** Error: unable to retrieve vessel position!`) return false } @@ -500,6 +557,7 @@ export class CourseApi { this.courseInfo.activeRoute.href = null this.courseInfo.activeRoute.startTime = null this.courseInfo.activeRoute.pointIndex = 0 + this.courseInfo.activeRoute.pointTotal = 0 this.courseInfo.activeRoute.reverse = false this.courseInfo.nextPoint.href = null this.courseInfo.nextPoint.type = null @@ -569,9 +627,11 @@ export class CourseApi { try { return await this.server.resourcesApi.getResource(h.type, h.id) } catch (err) { + debug(`** Unable to fetch resource: ${h.type}, ${h.id}`) return undefined } } else { + debug(`** Unable to parse href: ${href}`) return undefined } } diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index ef0d480c4..65822af15 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -35,6 +35,11 @@ "format": "int64", "example": 2 }, + "pointTotal": { + "type": "number", + "format": "int64", + "example": 9 + }, "reverse": { "type": "boolean", "default": false @@ -303,6 +308,14 @@ } } } + }, + + "/vessels/self/navigation/course/activeRoute/refresh": { + "put": { + "tags": ["course/activeRoute"], + "summary": "Refresh route details.", + "description": "Use after active route is modified to refresh pointIndex and pointsTotal values." + } } } diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 91243ca81..c47ff5e61 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -8,6 +8,7 @@ import { Application, NextFunction, Request, Response } from 'express' import { v4 as uuidv4 } from 'uuid' import { WithSecurityStrategy, WithSignalK } from '../../app' +import { Responses } from '../responses' import { buildResource } from './resources' import { validate } from './validate' @@ -58,7 +59,9 @@ export class Resources { typeof provider.methods.setResource !== 'function' || typeof provider.methods.deleteResource !== 'function' ) { - console.error(`Error: Could not register Resource Provider for ${i.toUpperCase()} due to missing provider methods!`) + console.error( + `Error: Could not register Resource Provider for ${i.toUpperCase()} due to missing provider methods!` + ) return } else { provider.methods.pluginId = pluginId @@ -133,7 +136,11 @@ export class Resources { ]?.getResource(req.params.resourceType, req.params.resourceId) res.json(retVal) } catch (err) { - res.status(404).send(`Resource not found! (${req.params.resourceId})`) + res.status(404).json({ + state: 'FAILED', + statusCode: 404, + message: `Resource not found! (${req.params.resourceId})` + }) } } ) @@ -156,7 +163,11 @@ export class Resources { ]?.listResources(req.params.resourceType, req.query) res.json(retVal) } catch (err) { - res.status(404).send(`Error retrieving resources!`) + res.status(404).json({ + state: 'FAILED', + statusCode: 404, + message: `Error retrieving resources!` + }) } } ) @@ -176,7 +187,7 @@ export class Resources { } if (!this.updateAllowed()) { - res.status(403) + res.status(403).json(Responses.unauthorised) return } if ( @@ -185,7 +196,7 @@ export class Resources { ) ) { if (!validate.resource(req.params.resourceType, req.body)) { - res.status(406).send(`Invalid resource data supplied!`) + res.status(406).json(Responses.invalid) return } } @@ -210,13 +221,17 @@ export class Resources { req.body ) ) - res - .status(200) - .send(`New ${req.params.resourceType} resource (${id}) saved.`) + res.status(200).json({ + state: 'COMPLETED', + statusCode: 200, + message: `New ${req.params.resourceType} resource (${id}) saved.` + }) } catch (err) { - res - .status(404) - .send(`Error saving ${req.params.resourceType} resource (${id})!`) + res.status(404).json({ + state: 'FAILED', + statusCode: 404, + message: `Error saving ${req.params.resourceType} resource (${id})!` + }) } } ) @@ -235,7 +250,7 @@ export class Resources { } if (!this.updateAllowed()) { - res.status(403) + res.status(403).json(Responses.unauthorised) return } if ( @@ -250,14 +265,18 @@ export class Resources { isValidId = validate.uuid(req.params.resourceId) } if (!isValidId) { - res - .status(406) - .send(`Invalid resource id provided (${req.params.resourceId})`) + res.status(406).json({ + state: 'FAILED', + statusCode: 406, + message: `Invalid resource id provided (${req.params.resourceId})` + }) return } + debug('req.body') + debug(req.body) if (!validate.resource(req.params.resourceType, req.body)) { - res.status(406).send(`Invalid resource data supplied!`) + res.status(406).json(Responses.invalid) return } } @@ -279,17 +298,17 @@ export class Resources { req.body ) ) - res - .status(200) - .send( - `${req.params.resourceType} resource (${req.params.resourceId}) saved.` - ) + res.status(200).json({ + state: 'COMPLETED', + statusCode: 200, + message: `${req.params.resourceType} resource (${req.params.resourceId}) saved.` + }) } catch (err) { - res - .status(404) - .send( - `Error saving ${req.params.resourceType} resource (${req.params.resourceId})!` - ) + res.status(404).json({ + state: 'FAILED', + statusCode: 404, + message: `Error saving ${req.params.resourceType} resource (${req.params.resourceId})!` + }) } } ) @@ -310,7 +329,7 @@ export class Resources { } if (!this.updateAllowed()) { - res.status(403) + res.status(403).json(Responses.unauthorised) return } try { @@ -326,11 +345,17 @@ export class Resources { null ) ) - res.status(200).send(`Resource (${req.params.resourceId}) deleted.`) + res.status(200).json({ + state: 'COMPLETED', + statusCode: 200, + message: `Resource (${req.params.resourceId}) deleted.` + }) } catch (err) { - res - .status(400) - .send(`Error deleting resource (${req.params.resourceId})!`) + res.status(400).json({ + state: 'FAILED', + statusCode: 400, + message: `Error deleting resource (${req.params.resourceId})!` + }) } } ) @@ -342,29 +367,41 @@ export class Resources { debug(`** POST ${SIGNALK_API_PATH}/resources/set/:resourceType`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).json(Responses.unauthorised) return } - let apiData = this.processApiRequest(req) + const apiData = this.processApiRequest(req) debug(apiData) if (!this.checkForProvider(apiData.type)) { - res.status(501).send(`No provider for ${apiData.type}!`) + res.status(501).json({ + state: 'FAILED', + statusCode: 501, + message: `No provider for ${apiData.type}!` + }) return } if (!apiData.value) { - res.status(406).send(`Invalid resource data supplied!`) + res.status(406).json(Responses.invalid) return } if (apiData.type === 'charts') { if (!validate.chartId(apiData.id)) { - res.status(406).send(`Invalid chart resource id supplied!`) + res.status(406).json({ + state: 'FAILED', + statusCode: 406, + message: `Invalid chart resource id supplied!` + }) return } } else { if (!validate.uuid(apiData.id)) { - res.status(406).send(`Invalid resource id supplied!`) + res.status(406).json({ + state: 'FAILED', + statusCode: 406, + message: `Invalid resource id supplied!` + }) return } } @@ -379,9 +416,17 @@ export class Resources { this.resProvider[apiData.type]?.pluginId as string, this.buildDeltaMsg(apiData.type, apiData.id, apiData.value) ) - res.status(200).send(`SUCCESS: New ${req.params.resourceType} resource created.`) + res.status(200).json({ + state: 'COMPLETED', + statusCode: 200, + message: `SUCCESS: New ${req.params.resourceType} resource created.` + }) } catch (err) { - res.status(404).send(`ERROR: Could not create ${req.params.resourceType} resource!`) + res.status(404).json({ + state: 'FAILED', + statusCode: 404, + message: `ERROR: Could not create ${req.params.resourceType} resource!` + }) } } ) @@ -393,28 +438,40 @@ export class Resources { ) if (!this.updateAllowed()) { - res.status(403) + res.status(403).json(Responses.unauthorised) return } const apiData = this.processApiRequest(req) if (!this.checkForProvider(apiData.type)) { - res.status(501).send(`No provider for ${apiData.type}!`) + res.status(501).json({ + state: 'FAILED', + statusCode: 501, + message: `No provider for ${apiData.type}!` + }) return } if (!apiData.value) { - res.status(406).send(`Invalid resource data supplied!`) + res.status(406).json(Responses.invalid) return } if (apiData.type === 'charts') { if (!validate.chartId(apiData.id)) { - res.status(406).send(`Invalid chart resource id supplied!`) + res.status(406).json({ + state: 'FAILED', + statusCode: 406, + message: `Invalid chart resource id supplied!` + }) return } } else { if (!validate.uuid(apiData.id)) { - res.status(406).send(`Invalid resource id supplied!`) + res.status(406).json({ + state: 'FAILED', + statusCode: 406, + message: `Invalid resource id supplied!` + }) return } } @@ -429,16 +486,24 @@ export class Resources { this.resProvider[apiData.type]?.pluginId as string, this.buildDeltaMsg(apiData.type, apiData.id, apiData.value) ) - res.status(200).send(`SUCCESS: ${req.params.resourceType} resource updated.`) + res.status(200).json({ + state: 'COMPLETED', + statusCode: 200, + message: `SUCCESS: ${req.params.resourceType} resource updated.` + }) } catch (err) { - res.status(404).send(`ERROR: ${req.params.resourceType} resource could not be updated!`) + res.status(404).json({ + state: 'FAILED', + statusCode: 404, + message: `ERROR: ${req.params.resourceType} resource could not be updated!` + }) } } ) } private processApiRequest(req: Request) { - let apiReq: any = { + const apiReq: any = { type: undefined, id: undefined, value: undefined @@ -464,7 +529,9 @@ export class Resources { apiReq.id = req.params.resourceId ? req.params.resourceId - : (apiReq.type === 'charts' ? apiReq.value.identifier : UUID_PREFIX + uuidv4()) + : apiReq.type === 'charts' + ? apiReq.value.identifier + : UUID_PREFIX + uuidv4() return apiReq } @@ -487,7 +554,7 @@ export class Resources { private checkForProvider(resType: SignalKResourceType): boolean { debug(`** checkForProvider(${resType})`) debug(this.resProvider[resType]) - return (this.resProvider[resType]) ? true : false + return this.resProvider[resType] ? true : false } private buildDeltaMsg( diff --git a/src/api/responses.ts b/src/api/responses.ts new file mode 100644 index 000000000..82c009c27 --- /dev/null +++ b/src/api/responses.ts @@ -0,0 +1,31 @@ +export interface ApiResponse { + state: 'FAILED' | 'COMPLETED' | 'PENDING' + statusCode: number + message: string + requestId?: string + href?: string + token?: string +} + +export const Responses = { + ok: { + state: 'COMPLETED', + statusCode: 200, + message: 'OK' + }, + invalid: { + state: 'FAILED', + statusCode: 406, + message: `Invalid Data supplied.` + }, + unauthorised: { + state: 'FAILED', + statusCode: 403, + message: 'Unauthorised' + }, + notFound: { + state: 'FAILED', + statusCode: 404, + message: 'Resource not found.' + } +} diff --git a/src/serverstate/store.ts b/src/serverstate/store.ts index 6839806ea..99a2d74de 100644 --- a/src/serverstate/store.ts +++ b/src/serverstate/store.ts @@ -10,17 +10,22 @@ export class Store { this.filePath = filePath this.fileName = fileName this.init().catch(error => { - console.log(`Could not initialise ${path.join(this.filePath, this.fileName)}`) + console.log( + `Could not initialise ${path.join(this.filePath, this.fileName)}` + ) console.log(error) }) } async read(): Promise { try { - const data = await readFile(path.join(this.filePath, this.fileName), 'utf8') + const data = await readFile( + path.join(this.filePath, this.fileName), + 'utf8' + ) return JSON.parse(data) } catch (error) { - throw(error) + throw error } } From 7e4ffa2a71c1572b3042807c0fb4cb029316fcf0 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 8 Jan 2022 15:43:20 +1030 Subject: [PATCH 168/410] add activeRouote/reverse path --- src/api/course/index.ts | 18 ++++++++++++++++++ src/api/course/openApi.json | 27 +++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index ae033a5b3..c16da8a10 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -338,6 +338,16 @@ export class CourseApi { return } } + // reverse direction from current point + if (req.params.action === 'reverse') { + if (typeof req.body.pointIndex === 'number') { + this.courseInfo.activeRoute.pointIndex = req.body.pointIndex + } else { + this.courseInfo.activeRoute.pointIndex = this.calcReversedIndex() + } + this.courseInfo.activeRoute.reverse = !this.courseInfo.activeRoute + .reverse + } if (req.params.action === 'refresh') { this.courseInfo.activeRoute.pointTotal = @@ -401,6 +411,14 @@ export class CourseApi { ) } + private calcReversedIndex(): number { + return ( + this.courseInfo.activeRoute.pointTotal - + 1 - + this.courseInfo.activeRoute.pointIndex + ) + } + private async activateRoute(route: ActiveRoute): Promise { let rte: any diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index 65822af15..9efca70ff 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -316,6 +316,33 @@ "summary": "Refresh route details.", "description": "Use after active route is modified to refresh pointIndex and pointsTotal values." } + }, + + "/vessels/self/navigation/course/activeRoute/reverse": { + "put": { + "tags": ["course/activeRoute"], + "summary": "Reverse direction of route", + "description": "Reverse direction of route from current point or from supplied pointIndex.", + "requestBody": { + "description": "Reverse route", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "pointIndex": { + "type": "number", + "description": "Index of point in route to use as destination", + "minimum": 0, + "example": 2 + } + } + } + } + } + } + } } } From 909ab039ed261bd4c5b03544e9c3d342ac73cfa2 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 9 Jan 2022 18:06:25 +1030 Subject: [PATCH 169/410] chore: update OpenApi responses --- src/api/course/index.ts | 2 +- src/api/course/openApi.json | 278 +++++++++++++++- src/api/resources/openApi.json | 575 +++++++++++++++++++++++++++------ 3 files changed, 750 insertions(+), 105 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index c16da8a10..5c67e319c 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -603,7 +603,7 @@ export class CourseApi { return 0 } if (index > rte.feature?.geometry?.coordinates.length - 1) { - return rte.feature?.geometry?.coordinates.length + return rte.feature?.geometry?.coordinates.length -1 } return index } diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index 9efca70ff..6fde0b6cd 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -113,7 +113,34 @@ "put": { "tags": ["course"], "summary": "Restart course calculations", - "description": "Sets previousPoint value to current vessel location" + "description": "Sets previousPoint value to current vessel location", + "responses": { + "default": { + "description": "Default response format", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } + } + } + } + } + } } }, @@ -168,12 +195,66 @@ } } } + }, + "responses": { + "default": { + "description": "Default response format", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } + } + } + } + } } }, "delete": { "tags": ["course/destination"], "summary": "Clear destination", - "description": "Sets activeRoute, nextPoint & previousPoint values to null" + "description": "Sets activeRoute, nextPoint & previousPoint values to null", + "responses": { + "default": { + "description": "Default response format", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } + } + } + } + } + } } }, @@ -200,6 +281,33 @@ } } } + }, + "responses": { + "default": { + "description": "Default response format", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } + } + } + } + } } } }, @@ -245,12 +353,66 @@ } } } + }, + "responses": { + "default": { + "description": "Default response format", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } + } + } + } + } } }, "delete": { "tags": ["course/activeRoute"], "summary": "Clear active route", - "description": "Sets activeRoute, nextPoint & previousPoint values to null" + "description": "Sets activeRoute, nextPoint & previousPoint values to null", + "responses": { + "default": { + "description": "Default response format", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } + } + } + } + } + } } }, @@ -278,6 +440,33 @@ } } } + }, + "responses": { + "default": { + "description": "Default response format", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } + } + } + } + } } } }, @@ -306,6 +495,33 @@ } } } + }, + "responses": { + "default": { + "description": "Default response format", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } + } + } + } + } } } }, @@ -314,7 +530,34 @@ "put": { "tags": ["course/activeRoute"], "summary": "Refresh route details.", - "description": "Use after active route is modified to refresh pointIndex and pointsTotal values." + "description": "Use after active route is modified to refresh pointIndex and pointsTotal values.", + "responses": { + "default": { + "description": "Default response format", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } + } + } + } + } + } } }, @@ -341,6 +584,33 @@ } } } + }, + "responses": { + "default": { + "description": "Default response format", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } + } + } + } + } } } } diff --git a/src/api/resources/openApi.json b/src/api/resources/openApi.json index 7bc1abc8a..e70e60d87 100644 --- a/src/api/resources/openApi.json +++ b/src/api/resources/openApi.json @@ -182,12 +182,27 @@ } }, "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -306,12 +321,27 @@ } }, "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -323,12 +353,27 @@ "tags": ["resources/routes"], "summary": "Remove Route with supplied id", "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -416,12 +461,27 @@ } }, "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -540,12 +600,27 @@ } }, "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -557,12 +632,27 @@ "tags": ["resources/waypoints"], "summary": "Remove Waypoint with supplied id", "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -639,12 +729,27 @@ } }, "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -752,12 +857,27 @@ } }, "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -769,12 +889,27 @@ "tags": ["resources/notes"], "summary": "Remove Note with supplied id", "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -881,12 +1016,27 @@ } }, "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -1024,12 +1174,27 @@ } }, "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -1041,12 +1206,27 @@ "tags": ["resources/regions"], "summary": "Remove Region with supplied id", "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -1168,12 +1348,27 @@ } }, "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -1326,12 +1521,27 @@ } }, "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -1343,12 +1553,27 @@ "tags": ["resources/charts"], "summary": "Remove Chart with supplied id", "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -1410,12 +1635,27 @@ } }, "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -1476,12 +1716,27 @@ } }, "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -1545,12 +1800,27 @@ } }, "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -1614,12 +1884,27 @@ } }, "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -1690,12 +1975,27 @@ } }, "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -1766,12 +2066,27 @@ } }, "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -1835,12 +2150,27 @@ } }, "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -1904,12 +2234,27 @@ } }, "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -2007,12 +2352,27 @@ } }, "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -2110,12 +2470,27 @@ } }, "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } From fe575bb76bf0967d522152e402916c00e584dca0 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 9 Jan 2022 18:08:43 +1030 Subject: [PATCH 170/410] regression testing fixes 2 --- src/api/course/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 5c67e319c..ec16f5e26 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -547,7 +547,7 @@ export class CourseApi { // clear activeRoute values newCourse.activeRoute.href = null newCourse.activeRoute.startTime = null - newCourse.activeRoute.pointindex = 0 + newCourse.activeRoute.pointIndex = 0 newCourse.activeRoute.pointTotal = 0 newCourse.activeRoute.reverse = false From 0f437e586eaaf080b861325758249da683311fd1 Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 9 Jan 2022 20:14:07 +0200 Subject: [PATCH 171/410] style: linter fix --- src/api/course/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index ec16f5e26..911c36e7e 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -603,7 +603,7 @@ export class CourseApi { return 0 } if (index > rte.feature?.geometry?.coordinates.length - 1) { - return rte.feature?.geometry?.coordinates.length -1 + return rte.feature?.geometry?.coordinates.length - 1 } return index } From efb523e8db3ebfd7260d09e0c8027c152fd8016a Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 9 Jan 2022 20:15:21 +0200 Subject: [PATCH 172/410] style: no-inferrable-types --- src/api/course/index.ts | 10 +++++----- src/api/resources/resources.ts | 4 ++-- src/deltaPriority.ts | 2 +- src/serverstate/store.ts | 6 +++--- tslint.js | 3 ++- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 911c36e7e..06509085c 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -8,10 +8,10 @@ import { Responses } from '../responses' const debug = Debug('signalk:courseApi') -const SIGNALK_API_PATH: string = `/signalk/v1/api` -const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const SIGNALK_API_PATH = `/signalk/v1/api` +const COURSE_API_PATH = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const DELTA_INTERVAL: number = 30000 +const DELTA_INTERVAL = 30000 interface CourseApplication extends Application, @@ -352,7 +352,7 @@ export class CourseApi { if (req.params.action === 'refresh') { this.courseInfo.activeRoute.pointTotal = rte.feature.geometry.coordinates.length - let idx: number = -1 + let idx = -1 for (let i = 0; i < rte.feature.geometry.coordinates.length; i++) { if ( rte.feature.geometry.coordinates[i][0] === @@ -748,7 +748,7 @@ export class CourseApi { } } - private emitCourseInfo(noSave: boolean = false) { + private emitCourseInfo(noSave = false) { this.server.handleMessage('courseApi', this.buildDeltaMsg()) if (!noSave) { this.store.write(this.courseInfo).catch(error => { diff --git a/src/api/resources/resources.ts b/src/api/resources/resources.ts index 36ee48b21..c87dc5241 100644 --- a/src/api/resources/resources.ts +++ b/src/api/resources/resources.ts @@ -49,7 +49,7 @@ const buildRoute = (rData: any): any => { if (!Array.isArray(rData.points)) { return null } - let isValid: boolean = true + let isValid = true rData.points.forEach((p: any) => { if (!isValidCoordinate(p)) { isValid = false @@ -178,7 +178,7 @@ const buildRegion = (rData: any): any => { if (!Array.isArray(rData.points)) { return null } - let isValid: boolean = true + let isValid = true rData.points.forEach((p: any) => { if (!isValidCoordinate(p)) { isValid = false diff --git a/src/deltaPriority.ts b/src/deltaPriority.ts index fab06f4cd..1625edb31 100644 --- a/src/deltaPriority.ts +++ b/src/deltaPriority.ts @@ -63,7 +63,7 @@ export type ToPreferredDelta = ( export const getToPreferredDelta = ( sourcePrioritiesData: SourcePrioritiesData, - unknownSourceTimeout: number = 10000 + unknownSourceTimeout = 10000 ): ToPreferredDelta => { if (!sourcePrioritiesData) { debug('No priorities data') diff --git a/src/serverstate/store.ts b/src/serverstate/store.ts index 99a2d74de..d6db82f7b 100644 --- a/src/serverstate/store.ts +++ b/src/serverstate/store.ts @@ -3,10 +3,10 @@ import { access, mkdir, readFile, writeFile } from 'fs/promises' import path from 'path' export class Store { - private filePath: string = '' - private fileName: string = '' + private filePath = '' + private fileName = '' - constructor(filePath: string, fileName: string = 'settings.json') { + constructor(filePath: string, fileName = 'settings.json') { this.filePath = filePath this.fileName = fileName this.init().catch(error => { diff --git a/tslint.js b/tslint.js index 36884a0e2..140f322bd 100644 --- a/tslint.js +++ b/tslint.js @@ -3,7 +3,8 @@ const TSRULES = { 'member-access': [true, 'no-public'], 'interface-name': false, 'max-classes-per-file': false, - 'no-any': false + 'no-any': false, + 'no-inferrable-types': true } const UNIVERSAL_RULES = { From 572c0a28b3d3627c0f5256225e6e82b6a3b4be59 Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 9 Jan 2022 20:20:24 +0200 Subject: [PATCH 173/410] fix: double definition of CourseApplication --- src/api/course/index.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 06509085c..ba20fd2d9 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -17,9 +17,7 @@ interface CourseApplication extends Application, WithConfig, WithSignalK, - WithSecurityStrategy {} - -interface CourseApplication extends Application { + WithSecurityStrategy { resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } From ba955b173919d9129d213e40f2947bcd0e60aed4 Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 9 Jan 2022 20:25:18 +0200 Subject: [PATCH 174/410] chore: use global Position type --- src/api/course/index.ts | 13 ++----------- src/types.ts | 1 + 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index ba20fd2d9..387e5a476 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -4,6 +4,7 @@ import _ from 'lodash' import path from 'path' import { WithConfig, WithSecurityStrategy, WithSignalK } from '../../app' import { Store } from '../../serverstate/store' +import { Position } from '../../types' import { Responses } from '../responses' const debug = Debug('signalk:courseApi') @@ -28,11 +29,7 @@ interface DestinationBase { arrivalCircle?: number } interface Destination extends DestinationBase { - position?: { - latitude: number - longitude: number - altitude?: number - } + position?: Position type?: string } interface ActiveRoute extends DestinationBase { @@ -40,12 +37,6 @@ interface ActiveRoute extends DestinationBase { reverse?: boolean } -interface Position { - latitude: number - longitude: number - altitude?: number -} - interface CourseInfo { activeRoute: { href: string | null diff --git a/src/types.ts b/src/types.ts index 3ab30be25..4beff7645 100644 --- a/src/types.ts +++ b/src/types.ts @@ -87,4 +87,5 @@ export type Value = object | number | string | null export interface Position { latitude: number longitude: number + altitude?: number } From 656bdc03ab9bc5974277a8bab82d1be0777126ce Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 9 Jan 2022 20:50:14 +0200 Subject: [PATCH 175/410] refactor: prefer object spread over Object.assign Allows typechecking. --- src/api/course/index.ts | 8 +++----- tslint.js | 3 ++- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 387e5a476..6ec3b8c57 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -425,14 +425,13 @@ export class CourseApi { return false } - const newCourse: any = {} - Object.assign(newCourse, this.courseInfo) + const newCourse: CourseInfo = {...this.courseInfo} // set activeroute newCourse.activeRoute.href = route.href if (this.isValidArrivalCircle(route.arrivalCircle as number)) { - newCourse.nextPoint.arrivalCircle = route.arrivalCircle + newCourse.nextPoint.arrivalCircle = route.arrivalCircle as number } newCourse.activeRoute.startTime = new Date().toISOString() @@ -485,8 +484,7 @@ export class CourseApi { } private async setDestination(dest: any): Promise { - const newCourse: any = {} - Object.assign(newCourse, this.courseInfo) + const newCourse: CourseInfo = {...this.courseInfo} // set nextPoint if (this.isValidArrivalCircle(dest.arrivalCircle)) { diff --git a/tslint.js b/tslint.js index 140f322bd..dc1a72635 100644 --- a/tslint.js +++ b/tslint.js @@ -4,7 +4,8 @@ const TSRULES = { 'interface-name': false, 'max-classes-per-file': false, 'no-any': false, - 'no-inferrable-types': true + 'no-inferrable-types': true, + 'prefer-object-spread': true } const UNIVERSAL_RULES = { From 91a7abb77cbe433f6e6f131a16dfb734d57b63b4 Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 9 Jan 2022 21:08:42 +0200 Subject: [PATCH 176/410] fix: SecurityStrategy.shouldAllowPut must have Request shouldAllowPut first Parameter must be the request that is being checked for access. --- src/api/resources/index.ts | 14 +++++++------- src/types.ts | 3 ++- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index c47ff5e61..df2a90edd 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -100,9 +100,9 @@ export class Resources { this.initResourceRoutes() } - private updateAllowed(): boolean { + private updateAllowed(req: Request): boolean { return this.server.securityStrategy.shouldAllowPut( - this.server, + req, 'vessels.self', null, 'resources' @@ -186,7 +186,7 @@ export class Resources { return } - if (!this.updateAllowed()) { + if (!this.updateAllowed(req)) { res.status(403).json(Responses.unauthorised) return } @@ -249,7 +249,7 @@ export class Resources { return } - if (!this.updateAllowed()) { + if (!this.updateAllowed(req)) { res.status(403).json(Responses.unauthorised) return } @@ -328,7 +328,7 @@ export class Resources { return } - if (!this.updateAllowed()) { + if (!this.updateAllowed(req)) { res.status(403).json(Responses.unauthorised) return } @@ -366,7 +366,7 @@ export class Resources { async (req: Request, res: Response) => { debug(`** POST ${SIGNALK_API_PATH}/resources/set/:resourceType`) - if (!this.updateAllowed()) { + if (!this.updateAllowed(req)) { res.status(403).json(Responses.unauthorised) return } @@ -437,7 +437,7 @@ export class Resources { `** PUT ${SIGNALK_API_PATH}/resources/set/:resourceType/:resourceId` ) - if (!this.updateAllowed()) { + if (!this.updateAllowed(req)) { res.status(403).json(Responses.unauthorised) return } diff --git a/src/types.ts b/src/types.ts index 4beff7645..f87246fad 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,5 @@ import { FullSignalK } from '@signalk/signalk-schema' +import { Request } from 'express' import SubscriptionManager from './subscriptionmanager' export interface HelloMessage { @@ -15,7 +16,7 @@ export interface SecurityStrategy { shouldFilterDeltas: () => boolean filterReadDelta: (user: any, delta: any) => any shouldAllowPut: ( - req: any, + req: Request, context: string, source: any, path: string From a3c376de7e27e395632981ee1ed313274dec5c90 Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 9 Jan 2022 21:11:00 +0200 Subject: [PATCH 177/410] fix: updateAllowed(request) is required --- src/api/course/index.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 6ec3b8c57..89bec1890 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -128,9 +128,9 @@ export class CourseApi { } } - private updateAllowed(): boolean { + private updateAllowed(request: Request): boolean { return this.server.securityStrategy.shouldAllowPut( - this.server, + request, 'vessels.self', null, 'navigation.course' @@ -151,7 +151,7 @@ export class CourseApi { `${COURSE_API_PATH}/restart`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/restart`) - if (!this.updateAllowed()) { + if (!this.updateAllowed(req)) { res.status(403).json(Responses.unauthorised) return } @@ -191,7 +191,7 @@ export class CourseApi { `${COURSE_API_PATH}/arrivalCircle`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) - if (!this.updateAllowed()) { + if (!this.updateAllowed(req)) { res.status(403).json(Responses.unauthorised) return } @@ -210,7 +210,7 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) - if (!this.updateAllowed()) { + if (!this.updateAllowed(req)) { res.status(403).json(Responses.unauthorised) return } @@ -236,7 +236,7 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) - if (!this.updateAllowed()) { + if (!this.updateAllowed(req)) { res.status(403).json(Responses.unauthorised) return } @@ -251,7 +251,7 @@ export class CourseApi { `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) - if (!this.updateAllowed()) { + if (!this.updateAllowed(req)) { res.status(403).json(Responses.unauthorised) return } @@ -272,7 +272,7 @@ export class CourseApi { `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/activeRoute`) - if (!this.updateAllowed()) { + if (!this.updateAllowed(req)) { res.status(403).json(Responses.unauthorised) return } @@ -286,7 +286,7 @@ export class CourseApi { `${COURSE_API_PATH}/activeRoute/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute/${req.params.action}`) - if (!this.updateAllowed()) { + if (!this.updateAllowed(req)) { res.status(403).json(Responses.unauthorised) return } From afebb58c88c52b0750efdd39d5cb058277e5b81a Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 9 Jan 2022 21:11:31 +0200 Subject: [PATCH 178/410] chore: this.server is initialised in constructor --- src/api/course/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 89bec1890..6b82adea1 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -100,7 +100,6 @@ export class CourseApi { private async start(app: any) { debug(`** Initialise ${COURSE_API_PATH} path handler **`) - this.server = app this.initCourseRoutes() try { From ea24705903d90c304e37861ff05d10b72593f358 Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 9 Jan 2022 21:24:57 +0200 Subject: [PATCH 179/410] refactor: use Destination type --- src/api/course/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 6b82adea1..ddde02ad7 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -482,12 +482,12 @@ export class CourseApi { return true } - private async setDestination(dest: any): Promise { + private async setDestination(dest: Destination): Promise { const newCourse: CourseInfo = {...this.courseInfo} // set nextPoint if (this.isValidArrivalCircle(dest.arrivalCircle)) { - newCourse.nextPoint.arrivalCircle = dest.arrivalCircle + newCourse.nextPoint.arrivalCircle = dest.arrivalCircle as number } newCourse.nextPoint.type = @@ -571,7 +571,7 @@ export class CourseApi { this.courseInfo.previousPoint.position = null } - private isValidArrivalCircle(value: number): boolean { + private isValidArrivalCircle(value: number | undefined): boolean { return typeof value === 'number' && value >= 0 } From 3f2a7095e05cd585db6e036ee43c6913ac8ed46a Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 10 Jan 2022 16:43:37 +1030 Subject: [PATCH 180/410] review updates 1 --- src/api/course/index.ts | 29 +++++++++--- src/api/resources/types.ts | 85 +++++++++++++++++++++++++++++++++++ src/api/resources/validate.ts | 11 ++--- 3 files changed, 115 insertions(+), 10 deletions(-) create mode 100644 src/api/resources/types.ts diff --git a/src/api/course/index.ts b/src/api/course/index.ts index ddde02ad7..75127d78a 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -5,6 +5,7 @@ import path from 'path' import { WithConfig, WithSecurityStrategy, WithSignalK } from '../../app' import { Store } from '../../serverstate/store' import { Position } from '../../types' +import { Route } from '../resources/types' import { Responses } from '../responses' const debug = Debug('signalk:courseApi') @@ -424,7 +425,7 @@ export class CourseApi { return false } - const newCourse: CourseInfo = {...this.courseInfo} + const newCourse: CourseInfo = { ...this.courseInfo } // set activeroute newCourse.activeRoute.href = route.href @@ -483,7 +484,7 @@ export class CourseApi { } private async setDestination(dest: Destination): Promise { - const newCourse: CourseInfo = {...this.courseInfo} + const newCourse: CourseInfo = { ...this.courseInfo } // set nextPoint if (this.isValidArrivalCircle(dest.arrivalCircle)) { @@ -502,7 +503,10 @@ export class CourseApi { href.type, href.id ) - if (r.position && typeof r.position?.latitude !== 'undefined') { + if ( + typeof r.position?.latitude !== 'undefined' && + typeof r.position?.longitude !== 'undefined' + ) { newCourse.nextPoint.position = r.position newCourse.nextPoint.href = dest.href newCourse.nextPoint.type = 'Waypoint' @@ -516,6 +520,9 @@ export class CourseApi { ) return false } + } else { + debug(`** Invalid href! (${dest.href})`) + return false } } else if (dest.position) { newCourse.nextPoint.href = null @@ -625,7 +632,7 @@ export class CourseApi { } } - private async getRoute(href: string): Promise { + private async getRoute(href: string): Promise { const h = this.parseHref(href) if (h) { try { @@ -647,9 +654,21 @@ export class CourseApi { 'navigation.courseRhumbline' ] + let course = null + if (this.courseInfo.activeRoute.href) { + course = this.courseInfo + } else if (this.courseInfo.nextPoint.position) { + course = { + nextPoint: this.courseInfo.nextPoint, + previousPoint: this.courseInfo.previousPoint + } + } + + debug(course) + values.push({ path: `navigation.course`, - value: this.courseInfo + value: course }) values.push({ diff --git a/src/api/resources/types.ts b/src/api/resources/types.ts new file mode 100644 index 000000000..b995b0adb --- /dev/null +++ b/src/api/resources/types.ts @@ -0,0 +1,85 @@ +import { Position } from '../../types' + +export interface Route { + name?: string + description?: string + distance?: number + start?: string + end?: string + feature: { + type: 'Feature' + geometry: { + type: 'LineString' + coordinates: GeoJsonLinestring + } + properties?: object + id?: string + } +} + +export interface Waypoint { + position?: Position + feature: { + type: 'Feature' + geometry: { + type: 'Point' + coords: GeoJsonPoint + } + properties?: object + id?: string + } +} + +export interface Note { + title?: string + description?: string + region?: string + position?: Position + geohash?: string + mimeType?: string + url?: string +} + +export interface Region { + geohash?: string + feature: Polygon | MultiPolygon +} + +export interface Chart { + name: string + identifier: string + description?: string + tilemapUrl?: string + chartUrl?: string + geohash?: string + region?: string + scale?: number + chartLayers?: string[] + bounds?: [[number, number], [number, number]] + chartFormat: string +} + +type GeoJsonPoint = [number, number, number?] +type GeoJsonLinestring = GeoJsonPoint[] +type GeoJsonPolygon = GeoJsonLinestring[] +type GeoJsonMultiPolygon = GeoJsonPolygon[] + +interface Polygon { + type: 'Feature' + geometry: { + type: 'Polygon' + coords: GeoJsonPolygon + } + properties?: object + id?: string +} + +interface MultiPolygon { + type: 'Feature' + geometry: { + type: 'MultiPolygon' + coords: GeoJsonMultiPolygon + } + properties?: object + id?: string +} diff --git a/src/api/resources/validate.ts b/src/api/resources/validate.ts index 16eb3c9a8..305c885be 100644 --- a/src/api/resources/validate.ts +++ b/src/api/resources/validate.ts @@ -1,5 +1,6 @@ import geoJSON from 'geojson-validation' import { isValidCoordinate } from 'geolib' +import { Chart, Note, Region, Route, Waypoint } from '../resources/types' export const validate = { resource: (type: string, value: any): boolean => { @@ -42,7 +43,7 @@ export const validate = { } } -const validateRoute = (r: any): boolean => { +const validateRoute = (r: Route): boolean => { if (r.start) { const l = r.start.split('/') if (!validate.uuid(l[l.length - 1])) { @@ -68,7 +69,7 @@ const validateRoute = (r: any): boolean => { return true } -const validateWaypoint = (r: any): boolean => { +const validateWaypoint = (r: Waypoint): boolean => { if (typeof r.position === 'undefined') { return false } @@ -89,7 +90,7 @@ const validateWaypoint = (r: any): boolean => { } // validate note data -const validateNote = (r: any): boolean => { +const validateNote = (r: Note): boolean => { if (!r.region && !r.position && !r.geohash) { return false } @@ -107,7 +108,7 @@ const validateNote = (r: any): boolean => { return true } -const validateRegion = (r: any): boolean => { +const validateRegion = (r: Region): boolean => { if (!r.geohash && !r.feature) { return false } @@ -129,7 +130,7 @@ const validateRegion = (r: any): boolean => { return true } -const validateChart = (r: any): boolean => { +const validateChart = (r: Chart): boolean => { if (!r.name || !r.identifier || !r.chartFormat) { return false } From 870b13122cc7215177e5478f9230422a9f292c08 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Tue, 11 Jan 2022 09:14:40 +1030 Subject: [PATCH 181/410] removed periodic delta transmission --- src/api/course/index.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 75127d78a..10d7bc746 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -13,8 +13,6 @@ const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH = `/signalk/v1/api` const COURSE_API_PATH = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const DELTA_INTERVAL = 30000 - interface CourseApplication extends Application, WithConfig, @@ -111,12 +109,6 @@ export class CourseApi { } debug(this.courseInfo) this.emitCourseInfo(true) - - setInterval(() => { - if (this.courseInfo.nextPoint.position) { - this.emitCourseInfo(true) - } - }, DELTA_INTERVAL) } private validateCourseInfo(info: CourseInfo) { From 32bea6394fdfad0481bc2b3f1bfad59b7f3ccaf1 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 12 Jan 2022 16:58:52 +1030 Subject: [PATCH 182/410] update types --- packages/server-api/src/index.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/server-api/src/index.ts b/packages/server-api/src/index.ts index 3f3b45925..cc27ed596 100644 --- a/packages/server-api/src/index.ts +++ b/packages/server-api/src/index.ts @@ -7,6 +7,17 @@ export { PropertyValue, PropertyValues, PropertyValuesCallback } from './propert export type SignalKResourceType= 'routes' | 'waypoints' |'notes' |'regions' |'charts' export type ResourceTypes= SignalKResourceType[] | string[] +export interface ResourcesApi { + register: (pluginId: string, provider: ResourceProvider) => void; + unRegister: (pluginId: string) => void; + getResource: (resType: SignalKResourceType, resId: string) => any; +} + +export interface ResourceProvider { + types: ResourceTypes + methods: ResourceProviderMethods +} + export interface ResourceProviderMethods { pluginId?: string listResources: (type: string, query: { [key: string]: any }) => Promise @@ -19,10 +30,6 @@ export interface ResourceProviderMethods { deleteResource: (type: string, id: string) => Promise } -export interface ResourceProvider { - types: ResourceTypes - methods: ResourceProviderMethods -} type Unsubscribe = () => {} export interface PropertyValuesEmitter { @@ -75,5 +82,5 @@ export interface Plugin { registerWithRouter?: (router: IRouter) => void signalKApiRoutes?: (router: IRouter) => IRouter enabledByDefault?: boolean - resourceProvider: ResourceProvider + resourceProvider?: ResourceProvider } From c84fdfb9fb353854c204a3d48bbf53267f0ccd2d Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Fri, 14 Jan 2022 23:17:08 +0200 Subject: [PATCH 183/410] chore: version 1.41.0-beta.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 616a59ad1..c31296292 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "signalk-server", - "version": "1.40.0", + "version": "1.41.0-beta.1", "description": "An implementation of a [Signal K](http://signalk.org) server for boats.", "main": "index.js", "scripts": { From 50711269011253e5336e3a28c1e1448d333837fd Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Fri, 14 Jan 2022 23:35:40 +0200 Subject: [PATCH 184/410] chore: convert ports to ts --- src/config/config.ts | 2 ++ src/index.ts | 11 ++++----- src/ports.js | 56 -------------------------------------------- src/ports.ts | 53 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 63 deletions(-) delete mode 100644 src/ports.js create mode 100644 src/ports.ts diff --git a/src/config/config.ts b/src/config/config.ts index e107744f1..10b22e848 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -58,6 +58,8 @@ export interface Config { hostname?: string pruneContextsMinutes?: number mdns?: boolean + sslport?: number + port?: number } defaults: object } diff --git a/src/index.ts b/src/index.ts index 6286c2be9..36aca80f5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,27 +23,24 @@ import Debug from 'debug' import express from 'express' import _ from 'lodash' const debug = Debug('signalk-server') +import { PropertyValues } from '@signalk/server-api' import { FullSignalK, getSourceId } from '@signalk/signalk-schema' +import { Request, Response } from 'express' import http from 'http' import https from 'https' import path from 'path' -import ports from './ports' -import SubscriptionManager from './subscriptionmanager' -const getPrimaryPort = ports.getPrimaryPort -const getSecondaryPort = ports.getSecondaryPort -const getExternalPort = ports.getExternalPort -import { PropertyValues } from '@signalk/server-api' -import { Request, Response } from 'express' import { SelfIdentity, ServerApp, SignalKMessageHub, WithConfig } from './app' import { Config, ConfigApp } from './config/config' import DeltaCache from './deltacache' import DeltaChain from './deltachain' import { getToPreferredDelta, ToPreferredDelta } from './deltaPriority' import { checkForNewServerVersion } from './modules' +import SubscriptionManager from './subscriptionmanager' import { Delta } from './types' import { load, sendBaseDeltas } from './config/config' import { incDeltaStatistics, startDeltaStatistics } from './deltastats' +import { getExternalPort, getPrimaryPort, getSecondaryPort } from './ports' import { getCertificateOptions, getSecurityConfig, diff --git a/src/ports.js b/src/ports.js deleted file mode 100644 index b1f6fec80..000000000 --- a/src/ports.js +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2017 Teppo Kurki - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. -*/ - -const SD_LISTEN_FDS_START = 3 - -const getSslPort = app => - Number(process.env.SSLPORT) || app.config.settings.sslport || 3443 - -const getHttpPort = app => - Number(process.env.PORT) || app.config.settings.port || 3000 - -module.exports = { - getPrimaryPort: function(app) { - if (process.env.LISTEN_FDS > 0) { - return { - fd: SD_LISTEN_FDS_START - } - } - return app.config.settings.ssl ? getSslPort(app) : getHttpPort(app) - }, - - getSecondaryPort: function(app) { - if (process.env.LISTEN_FDS > 0) { - if (process.env.LISTEN_FDS !== 2) { - return false - } - return { - fd: SD_LISTEN_FDS_START + 1 - } - } - return app.config.settings.ssl ? getHttpPort(app) : -7777 - }, - - getExternalPort: function(app) { - if (process.env.EXTERNALPORT > 0) { - return Number(process.env.EXTERNALPORT) - } - return app.config.settings.ssl ? getSslPort(app) : getHttpPort(app) - }, - - getSslPort, - getHttpPort -} diff --git a/src/ports.ts b/src/ports.ts new file mode 100644 index 000000000..cadc7a5ba --- /dev/null +++ b/src/ports.ts @@ -0,0 +1,53 @@ +/* + * Copyright 2017 Teppo Kurki + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +import { WithConfig } from './app' + +const SD_LISTEN_FDS_START = 3 + +export const getSslPort = (app: WithConfig) => + Number(process.env?.SSLPORT) || app.config.settings.sslport || 3443 + +export const getHttpPort = (app: WithConfig) => + Number(process.env?.PORT) || app.config.settings.port || 3000 + +export function getPrimaryPort(app: WithConfig) { + if (Number(process.env.LISTEN_FDS) > 0) { + return { + fd: SD_LISTEN_FDS_START + } + } + return app.config.settings.ssl ? getSslPort(app) : getHttpPort(app) +} + +export function getSecondaryPort(app: WithConfig): any { + if (Number(process.env.LISTEN_FDS) > 0) { + if (Number(process.env.LISTEN_FDS) !== 2) { + return false + } + return { + fd: SD_LISTEN_FDS_START + 1 + } + } + return app.config.settings.ssl ? getHttpPort(app) : -7777 +} + +export function getExternalPort(app: WithConfig) { + if (Number(process.env?.EXTERNALPORT) > 0) { + return Number(process.env?.EXTERNALPORT) + } + return app.config.settings.ssl ? getSslPort(app) : getHttpPort(app) +} From 8ad9c16c2dbc0e0ceffea867a436bc298107944c Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Fri, 14 Jan 2022 23:49:10 +0200 Subject: [PATCH 185/410] chore: convert deltachain & deltastats to ts --- src/deltachain.js | 38 ---------------------- src/deltachain.ts | 45 ++++++++++++++++++++++++++ src/deltastats.js | 68 --------------------------------------- src/deltastats.ts | 66 +++++++++++++++++++++++++++++++++++++ src/index.ts | 5 +-- src/interfaces/plugins.ts | 5 ++- 6 files changed, 116 insertions(+), 111 deletions(-) delete mode 100644 src/deltachain.js create mode 100644 src/deltachain.ts delete mode 100644 src/deltastats.js create mode 100644 src/deltastats.ts diff --git a/src/deltachain.js b/src/deltachain.js deleted file mode 100644 index 59e7fd54a..000000000 --- a/src/deltachain.js +++ /dev/null @@ -1,38 +0,0 @@ -function DeltaChain(dispatchMessage) { - const chain = [] - let next = [] - - this.process = function(msg) { - return doProcess(0, msg) - } - - function doProcess(index, msg) { - if (index >= chain.length) { - dispatchMessage(msg) - return - } - chain[index](msg, next[index]) - } - - this.register = function(handler) { - chain.push(handler) - updateNexts() - return () => { - const handlerIndex = chain.indexOf(handler) - if (handlerIndex >= 0) { - chain.splice(handlerIndex, 1) - updateNexts() - } - } - } - - function updateNexts() { - next = chain.map((chainElement, index) => { - return msg => { - doProcess(index + 1, msg) - } - }) - } -} - -module.exports = DeltaChain diff --git a/src/deltachain.ts b/src/deltachain.ts new file mode 100644 index 000000000..cad8eb5ae --- /dev/null +++ b/src/deltachain.ts @@ -0,0 +1,45 @@ +export type DeltaInputHandler = ( + delta: object, + next: (delta: object) => void +) => void + +export default class DeltaChain { + chain: any + next: any + constructor(private dispatchMessage: any) { + this.chain = [] + this.next = [] + } + + process(msg: any) { + return this.doProcess(0, msg) + } + + doProcess(index: number, msg: any) { + if (index >= this.chain.length) { + this.dispatchMessage(msg) + return + } + this.chain[index](msg, this.next[index]) + } + + register(handler: DeltaInputHandler) { + this.chain.push(handler) + this.updateNexts() + return () => { + const handlerIndex = this.chain.indexOf(handler) + if (handlerIndex >= 0) { + this.chain.splice(handlerIndex, 1) + this.updateNexts() + } + } + } + + updateNexts() { + this.next = this.chain.map((chainElement: any, index: number) => { + return (msg: any) => { + this.doProcess(index + 1, msg) + } + }) + } +} diff --git a/src/deltastats.js b/src/deltastats.js deleted file mode 100644 index 56c1d45a2..000000000 --- a/src/deltastats.js +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2017 Teppo Kurki, Scott Bender - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. -*/ - -const { isUndefined, values } = require('lodash') - -module.exports = { - startDeltaStatistics: function(app) { - app.deltaCount = 0 - app.lastIntervalDeltaCount = 0 - app.providerStatistics = {} - - return setInterval(() => { - updateProviderPeriodStats(app) - app.emit('serverevent', { - type: 'SERVERSTATISTICS', - from: 'signalk-server', - data: { - deltaRate: (app.deltaCount - app.lastIntervalDeltaCount) / 5, - numberOfAvailablePaths: app.streambundle.getAvailablePaths().length, - wsClients: app.interfaces.ws ? app.interfaces.ws.numClients() : 0, - providerStatistics: app.providerStatistics, - uptime: process.uptime() - } - }) - app.lastIntervalDeltaCount = app.deltaCount - }, 5 * 1000) - }, - - incDeltaStatistics: function(app, providerId) { - app.deltaCount++ - - const stats = - app.providerStatistics[providerId] || - (app.providerStatistics[providerId] = { - deltaCount: 0 - }) - stats.deltaCount++ - } -} - -function updateProviderPeriodStats(app) { - app.providers.forEach(provider => { - if (isUndefined(app.providerStatistics[provider.id])) { - app.providerStatistics[provider.id] = { - deltaCount: 0, - deltaRate: 0 - } - } - }) - - values(app.providerStatistics).forEach(stats => { - stats.deltaRate = (stats.deltaCount - stats.lastIntervalDeltaCount) / 5 - stats.lastIntervalDeltaCount = stats.deltaCount - }) -} diff --git a/src/deltastats.ts b/src/deltastats.ts new file mode 100644 index 000000000..94fed689f --- /dev/null +++ b/src/deltastats.ts @@ -0,0 +1,66 @@ +/* + * Copyright 2017 Teppo Kurki, Scott Bender + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +import { isUndefined, values } from 'lodash' + +export function startDeltaStatistics(app: any) { + app.deltaCount = 0 + app.lastIntervalDeltaCount = 0 + app.providerStatistics = {} + + return setInterval(() => { + updateProviderPeriodStats(app) + app.emit('serverevent', { + type: 'SERVERSTATISTICS', + from: 'signalk-server', + data: { + deltaRate: (app.deltaCount - app.lastIntervalDeltaCount) / 5, + numberOfAvailablePaths: app.streambundle.getAvailablePaths().length, + wsClients: app.interfaces.ws ? app.interfaces.ws.numClients() : 0, + providerStatistics: app.providerStatistics, + uptime: process.uptime() + } + }) + app.lastIntervalDeltaCount = app.deltaCount + }, 5 * 1000) +} + +export function incDeltaStatistics(app: any, providerId: any) { + app.deltaCount++ + + const stats = + app.providerStatistics[providerId] || + (app.providerStatistics[providerId] = { + deltaCount: 0 + }) + stats.deltaCount++ +} + +function updateProviderPeriodStats(app: any) { + app.providers.forEach((provider: any) => { + if (isUndefined(app.providerStatistics[provider.id])) { + app.providerStatistics[provider.id] = { + deltaCount: 0, + deltaRate: 0 + } + } + }) + + values(app.providerStatistics).forEach((stats: any) => { + stats.deltaRate = (stats.deltaCount - stats.lastIntervalDeltaCount) / 5 + stats.lastIntervalDeltaCount = stats.deltaCount + }) +} diff --git a/src/index.ts b/src/index.ts index 36aca80f5..14b376967 100644 --- a/src/index.ts +++ b/src/index.ts @@ -32,7 +32,7 @@ import path from 'path' import { SelfIdentity, ServerApp, SignalKMessageHub, WithConfig } from './app' import { Config, ConfigApp } from './config/config' import DeltaCache from './deltacache' -import DeltaChain from './deltachain' +import DeltaChain, { DeltaInputHandler } from './deltachain' import { getToPreferredDelta, ToPreferredDelta } from './deltaPriority' import { checkForNewServerVersion } from './modules' import SubscriptionManager from './subscriptionmanager' @@ -83,7 +83,8 @@ class Server { app.propertyValues = new PropertyValues() const deltachain = new DeltaChain(app.signalk.addDelta.bind(app.signalk)) - app.registerDeltaInputHandler = deltachain.register + app.registerDeltaInputHandler = (handler: DeltaInputHandler) => + deltachain.register(handler) app.providerStatus = {} diff --git a/src/interfaces/plugins.ts b/src/interfaces/plugins.ts index a29e48b64..6eb59d250 100644 --- a/src/interfaces/plugins.ts +++ b/src/interfaces/plugins.ts @@ -28,6 +28,7 @@ import fs from 'fs' import _ from 'lodash' import path from 'path' import { SERVERROUTESPREFIX } from '../constants' +import { DeltaInputHandler } from '../deltachain' import { listAllSerialPorts, Ports } from '../serialports' // tslint:disable-next-line:no-var-requires @@ -90,9 +91,7 @@ export interface ServerAPI extends PluginServerApp { queryRequest: (requestId: string) => Promise error: (msg: string) => void debug: (msg: string) => void - registerDeltaInputHandler: ( - handler: (delta: object, next: (delta: object) => void) => void - ) => void + registerDeltaInputHandler: (handler: DeltaInputHandler) => void setProviderStatus: (msg: string) => void handleMessage: (id: string, msg: any) => void setProviderError: (msg: string) => void From 27875a925e17cd120cc8e216b3c9f36af32dcc55 Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sat, 15 Jan 2022 09:11:08 +0200 Subject: [PATCH 186/410] chore: convert security.js to ts --- package.json | 1 + src/{dummysecurity.js => dummysecurity.ts} | 69 +++++++---- src/{security.js => security.ts} | 131 +++++++++++++-------- src/types.ts | 8 +- 4 files changed, 130 insertions(+), 79 deletions(-) rename src/{dummysecurity.js => dummysecurity.ts} (54%) rename src/{security.js => security.ts} (63%) diff --git a/package.json b/package.json index c31296292..eb2fdb77c 100644 --- a/package.json +++ b/package.json @@ -136,6 +136,7 @@ "@types/lodash": "^4.14.139", "@types/mocha": "^8.2.0", "@types/node-fetch": "^2.5.3", + "@types/pem": "^1.9.6", "@types/semver": "^7.1.0", "@types/serialport": "^8.0.1", "@types/split": "^1.0.0", diff --git a/src/dummysecurity.js b/src/dummysecurity.ts similarity index 54% rename from src/dummysecurity.js rename to src/dummysecurity.ts index 68a9039ac..def94bc0c 100644 --- a/src/dummysecurity.js +++ b/src/dummysecurity.ts @@ -14,23 +14,23 @@ * limitations under the License. */ - /* tslint:disable */ - -module.exports = function(app, config) { +/* tslint:disable */ + +export default function() { return { getConfiguration: () => { return {} }, - allowRestart: req => { + allowRestart: (_req: any) => { return false }, - allowConfigure: req => { + allowConfigure: (_req: any) => { return false }, - getLoginStatus: req => { + getLoginStatus: (_req: any) => { return { status: 'notLoggedIn', readOnlyAccess: false, @@ -38,45 +38,66 @@ module.exports = function(app, config) { } }, - getConfig: config => { - return config + getConfig: (_config: any) => { + return _config }, - setConfig: (config, newConfig) => {}, + setConfig: (_config: any, _newConfig: any) => {}, - getUsers: config => { + getUsers: (_config: any) => { return [] }, - updateUser: (config, username, updates, callback) => {}, + updateUser: ( + _config: any, + _username: any, + _updates: any, + _callback: any + ) => {}, - addUser: (config, user, callback) => {}, + addUser: (_config: any, _user: any, _callback: any) => {}, - setPassword: (config, username, password, callback) => {}, + setPassword: ( + _config: any, + _username: any, + _password: any, + _callback: any + ) => {}, - deleteUser: (config, username, callback) => {}, + deleteUser: (_config: any, _username: any, _callback: any) => {}, - shouldAllowWrite: function(req, delta) { + shouldAllowWrite: function(_req: any, _delta: any) { return true }, - shouldAllowPut: function(req, context, source, path) { + shouldAllowPut: function( + _req: any, + _context: any, + _source: any, + _path: any + ) { return true }, - filterReadDelta: (user, delta) => { + filterReadDelta: (_user: any, delta: any) => { return delta }, - verifyWS: spark => {}, + verifyWS: (_spark: any) => {}, - authorizeWS: req => {}, + authorizeWS: (_req: any) => {}, anyACLs: () => { return false }, - checkACL: (id, context, path, source, operation) => { + checkACL: ( + _id: any, + _context: any, + _path: any, + _source: any, + _operation: any + ) => { return true }, @@ -97,7 +118,7 @@ module.exports = function(app, config) { addAdminWriteMiddleware: () => {}, addWriteMiddleware: () => {}, - + allowReadOnly: () => { return true }, @@ -108,6 +129,10 @@ module.exports = function(app, config) { return 'never' }, - validateConfiguration: (configuration) => {} + validateConfiguration: (_configuration: any) => {}, + + configFromArguments: false, + securityConfig: undefined, + requestAccess: () => undefined } } diff --git a/src/security.js b/src/security.ts similarity index 63% rename from src/security.js rename to src/security.ts index dc5417e49..97d55fb6f 100644 --- a/src/security.js +++ b/src/security.ts @@ -14,22 +14,49 @@ * limitations under the License. */ -const fs = require('fs') -const path = require('path') -const Mode = require('stat-mode') -const pem = require('pem') -const debug = require('debug')('signalk-server:security') -const _ = require('lodash') -const dummysecurity = require('./dummysecurity') - -class InvalidTokenError extends Error { - constructor(...args) { +import Debug from 'debug' +import { + chmodSync, + existsSync, + readFileSync, + Stats, + statSync, + writeFile, + writeFileSync +} from 'fs' +import _ from 'lodash' +import path from 'path' +import pem from 'pem' +import { Mode } from 'stat-mode' +import { WithConfig } from './app' +import dummysecurity from './dummysecurity' +const debug = Debug('signalk-server:security') + +export interface WithSecurityStrategy { + securityStrategy: SecurityStrategy +} + +export interface SecurityStrategy { + isDummy: () => boolean + allowReadOnly: () => boolean + shouldFilterDeltas: () => boolean + filterReadDelta: (user: any, delta: any) => any + configFromArguments: boolean + securityConfig: any + requestAccess: (config: any, request: any, ip: any, updateCb: any) => any +} + +export class InvalidTokenError extends Error { + constructor(...args: any[]) { super(...args) Error.captureStackTrace(this, InvalidTokenError) } } -function startSecurity(app, securityConfig) { +export function startSecurity( + app: WithSecurityStrategy & WithConfig, + securityConfig: any +) { let securityStrategyModuleName = process.env.SECURITYSTRATEGY || _.get(app, 'config.settings.security.strategy') @@ -58,15 +85,15 @@ function startSecurity(app, securityConfig) { } } -function getSecurityConfig(app, forceRead = false) { +export function getSecurityConfig( + app: WithConfig & WithSecurityStrategy, + forceRead = false +) { if (!forceRead && app.securityStrategy.configFromArguments) { return app.securityStrategy.securityConfig } else { try { - const optionsAsString = fs.readFileSync( - pathForSecurityConfig(app), - 'utf8' - ) + const optionsAsString = readFileSync(pathForSecurityConfig(app), 'utf8') return JSON.parse(optionsAsString) } catch (e) { console.error('Could not parse security config') @@ -76,11 +103,15 @@ function getSecurityConfig(app, forceRead = false) { } } -function pathForSecurityConfig(app) { +export function pathForSecurityConfig(app: WithConfig) { return path.join(app.config.configPath, 'security.json') } -function saveSecurityConfig(app, data, callback) { +export function saveSecurityConfig( + app: WithSecurityStrategy & WithConfig, + data: any, + callback: any +) { if (app.securityStrategy.configFromArguments) { app.securityStrategy.securityConfig = data if (callback) { @@ -89,9 +120,9 @@ function saveSecurityConfig(app, data, callback) { } else { const config = JSON.parse(JSON.stringify(data)) const configPath = pathForSecurityConfig(app) - fs.writeFile(configPath, JSON.stringify(data, null, 2), err => { + writeFile(configPath, JSON.stringify(data, null, 2), err => { if (!err) { - fs.chmodSync(configPath, '600') + chmodSync(configPath, '600') } if (callback) { callback(err) @@ -100,10 +131,10 @@ function saveSecurityConfig(app, data, callback) { } } -function getCertificateOptions(app, cb) { +export function getCertificateOptions(app: WithConfig, cb: any) { let certLocation - if (!app.config.configPath || fs.existsSync('./settings/ssl-cert.pem')) { + if (!app.config.configPath || existsSync('./settings/ssl-cert.pem')) { certLocation = './settings' } else { certLocation = app.config.configPath @@ -113,8 +144,8 @@ function getCertificateOptions(app, cb) { const keyFile = path.join(certLocation, 'ssl-key.pem') const chainFile = path.join(certLocation, 'ssl-chain.pem') - if (fs.existsSync(certFile) && fs.existsSync(keyFile)) { - if (!hasStrictPermissions(fs.statSync(keyFile))) { + if (existsSync(certFile) && existsSync(keyFile)) { + if (!hasStrictPermissions(statSync(keyFile))) { cb( new Error( `${keyFile} must be accessible only by the user that is running the server, refusing to start` @@ -122,7 +153,7 @@ function getCertificateOptions(app, cb) { ) return } - if (!hasStrictPermissions(fs.statSync(certFile))) { + if (!hasStrictPermissions(statSync(certFile))) { cb( new Error( `${certFile} must be accessible only by the user that is running the server, refusing to start` @@ -131,15 +162,15 @@ function getCertificateOptions(app, cb) { return } let ca - if (fs.existsSync(chainFile)) { + if (existsSync(chainFile)) { debug('Found ssl-chain.pem') ca = getCAChainArray(chainFile) debug(JSON.stringify(ca, null, 2)) } debug(`Using certificate ssl-key.pem and ssl-cert.pem in ${certLocation}`) cb(null, { - key: fs.readFileSync(keyFile), - cert: fs.readFileSync(certFile), + key: readFileSync(keyFile), + cert: readFileSync(certFile), ca }) } else { @@ -147,7 +178,7 @@ function getCertificateOptions(app, cb) { } } -function hasStrictPermissions(stat) { +function hasStrictPermissions(stat: Stats) { if (process.platform === 'win32') { return true } else { @@ -155,10 +186,9 @@ function hasStrictPermissions(stat) { } } -function getCAChainArray(filename) { - let chainCert = [] - return fs - .readFileSync(filename, 'utf8') +export function getCAChainArray(filename: string) { + let chainCert = new Array() + return readFileSync(filename, 'utf8') .split('\n') .reduce((ca, line) => { chainCert.push(line) @@ -167,10 +197,15 @@ function getCAChainArray(filename) { chainCert = [] } return ca - }, []) + }, new Array()) } -function createCertificateOptions(app, certFile, keyFile, cb) { +export function createCertificateOptions( + app: WithConfig, + certFile: string, + keyFile: string, + cb: any +) { const location = app.config.configPath ? app.config.configPath : './settings' debug(`Creating certificate files in ${location}`) pem.createCertificate( @@ -178,15 +213,15 @@ function createCertificateOptions(app, certFile, keyFile, cb) { days: 360, selfSigned: true }, - function(err, keys) { + (err: any, keys: any) => { if (err) { console.error('Could not create SSL certificate:' + err.message) throw err } else { - fs.writeFileSync(keyFile, keys.serviceKey) - fs.chmodSync(keyFile, '600') - fs.writeFileSync(certFile, keys.certificate) - fs.chmodSync(certFile, '600') + writeFileSync(keyFile, keys.serviceKey) + chmodSync(keyFile, '600') + writeFileSync(certFile, keys.certificate) + chmodSync(certFile, '600') cb(null, { key: keys.serviceKey, cert: keys.certificate @@ -196,16 +231,12 @@ function createCertificateOptions(app, certFile, keyFile, cb) { ) } -function requestAccess(app, request, ip, updateCb) { +export function requestAccess( + app: WithSecurityStrategy & WithConfig, + request: any, + ip: any, + updateCb: any +) { const config = getSecurityConfig(app) return app.securityStrategy.requestAccess(config, request, ip, updateCb) } - -module.exports = { - startSecurity, - getCertificateOptions, - getSecurityConfig, - saveSecurityConfig, - requestAccess, - InvalidTokenError -} diff --git a/src/types.ts b/src/types.ts index 4e6f0c5b0..d9dbf8f87 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,5 @@ import { FullSignalK } from '@signalk/signalk-schema' +import { SecurityStrategy } from './security' import SubscriptionManager from './subscriptionmanager' export interface HelloMessage { @@ -9,13 +10,6 @@ export interface HelloMessage { timestamp: Date } -export interface SecurityStrategy { - isDummy: () => boolean - allowReadOnly: () => boolean - shouldFilterDeltas: () => boolean - filterReadDelta: (user: any, delta: any) => any -} - export interface Bus { onValue: (callback: (value: any) => any) => () => void push: (v: any) => void From 3852d9a065c149ccaeb3d8eb35a2d29f0c18e64c Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sat, 15 Jan 2022 10:05:58 +0200 Subject: [PATCH 187/410] 1.41.0-beta.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index eb2fdb77c..dd3910f37 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "signalk-server", - "version": "1.41.0-beta.1", + "version": "1.41.0-beta.2", "description": "An implementation of a [Signal K](http://signalk.org) server for boats.", "main": "index.js", "scripts": { From f9bb471e6647051f259cc3c99f40511935cf34d7 Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sat, 15 Jan 2022 22:09:29 +0200 Subject: [PATCH 188/410] chore: use npm run watch, not dev --- packages/server-admin-ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server-admin-ui/package.json b/packages/server-admin-ui/package.json index c187501f7..ef5b35529 100644 --- a/packages/server-admin-ui/package.json +++ b/packages/server-admin-ui/package.json @@ -55,7 +55,7 @@ }, "scripts": { "prepublishOnly": "npm run clean && npm run build", - "dev": "webpack --watch --mode development", + "watch": "webpack --watch --mode development", "build": "webpack --mode=production", "format": "prettier --write src/", "clean": "rimraf ./public", From 605cc90591867e5d76a276b123ff04e775a4f235 Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sat, 15 Jan 2022 22:11:24 +0200 Subject: [PATCH 189/410] fix: event emitting on app With rest arguments and parameters there is no need for using the js magical arguments object, that was broken after the conversion to ts. --- src/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index 14b376967..a7de1893d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -254,8 +254,8 @@ class Server { const app = this.app const eventDebugs: { [key: string]: Debug.Debugger } = {} - const emit = app.emit - app.emit = (eventName: string) => { + const expressAppEmit = app.emit.bind(app) + app.emit = (eventName: string, ...args: any[]) => { if (eventName !== 'serverlog') { let eventDebug = eventDebugs[eventName] if (!eventDebug) { @@ -264,10 +264,10 @@ class Server { ) } if (eventDebug.enabled) { - eventDebug([...arguments].slice(1)) + eventDebug([...args].slice(1)) } } - emit.apply(app, arguments) + expressAppEmit(eventName, ...args) } this.app.intervals = [] From eb874e3ccc4eb8efa55e44b92c13165210d9ddbb Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sat, 15 Jan 2022 22:16:54 +0200 Subject: [PATCH 190/410] 1.41.0-beta.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index dd3910f37..d67a744dd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "signalk-server", - "version": "1.41.0-beta.2", + "version": "1.41.0-beta.3", "description": "An implementation of a [Signal K](http://signalk.org) server for boats.", "main": "index.js", "scripts": { From 694a264f83c30213e652b9e42d0cecc95f20a4aa Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 16 Jan 2022 17:09:32 +0200 Subject: [PATCH 191/410] chore: drop aws-sdk from streams' dependencies aws-sdk is not included in dependencies because of the persistent deprecation warnings caused by its transitive dependencies. This feature is not in wide use, especially not in signalk-server where people encounter the scary looking deprecation warnings. Known to work with ^2.413.0 --- packages/streams/s3.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/streams/s3.js b/packages/streams/s3.js index 202cdf3e4..f19ac1b3c 100644 --- a/packages/streams/s3.js +++ b/packages/streams/s3.js @@ -15,7 +15,15 @@ */ var Transform = require('stream').Transform -const AWS = require('aws-sdk') +/* + aws-sdk is not included in dependencies because of the + persistent deprecation warnings caused by its transitive + dependencies. This feature is not in wide use, especially + not in signalk-server where people encounter the scary looking + deprecation warnings. + Known to work with ^2.413.0 +*/ +const AWS = require('aws-sdk') const debug = require('debug')('signalk:streams:s3-provider') function S3Provider ({ bucket, prefix }) { From 205625fbbfa4e8df32855c93ad15e1ef82bf30ed Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 16 Jan 2022 17:14:54 +0200 Subject: [PATCH 192/410] chore: @signalk/streams@2.0.0 Not strictly backwards compatible, so bumping major. --- package.json | 2 +- packages/streams/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index d67a744dd..23be31219 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "@signalk/server-admin-ui": "1.39.1", "@signalk/server-api": "1.39.x", "@signalk/signalk-schema": "1.5.1", - "@signalk/streams": "1.19.x", + "@signalk/streams": "2.x", "@types/debug": "^4.1.5", "baconjs": "^1.0.1", "bcryptjs": "^2.4.3", diff --git a/packages/streams/package.json b/packages/streams/package.json index bb81db625..01c6cd428 100644 --- a/packages/streams/package.json +++ b/packages/streams/package.json @@ -1,6 +1,6 @@ { "name": "@signalk/streams", - "version": "1.19.0", + "version": "2.0.0", "description": "Utilities for handling streams of Signal K data", "main": "index.js", "scripts": { From 293557f81e19907f85c480b0eb0965bba7539fcb Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 16 Jan 2022 17:31:05 +0200 Subject: [PATCH 193/410] chore: debug ^4.3.3 4.2.0 is deprecated. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 23be31219..6d54d0176 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,7 @@ "cookie": "^0.4.0", "cookie-parser": "^1.4.3", "cors": "^2.5.2", - "debug": "4.2.0", + "debug": "^4.3.3", "deep-get-set": "^1.1.0", "dev-null-stream": "0.0.1", "dnssd2": "1.0.0", From 4e207a573e2130ee169c736a66c5bb9049733029 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 19 Jan 2022 09:52:56 +1030 Subject: [PATCH 194/410] add test for ApiRequst path --- src/api/course/index.ts | 5 ++-- src/api/index.ts | 43 ++++++++++++++++++++++++++++++++++ src/api/resources/index.ts | 4 ++-- src/api/resources/resources.ts | 2 +- src/api/responses.ts | 31 ------------------------ src/put.js | 11 ++++----- 6 files changed, 53 insertions(+), 43 deletions(-) create mode 100644 src/api/index.ts delete mode 100644 src/api/responses.ts diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 10d7bc746..739bc7b5d 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -3,10 +3,11 @@ import { Application, Request, Response } from 'express' import _ from 'lodash' import path from 'path' import { WithConfig, WithSecurityStrategy, WithSignalK } from '../../app' -import { Store } from '../../serverstate/store' import { Position } from '../../types' + +import { Store } from '../../serverstate/store' import { Route } from '../resources/types' -import { Responses } from '../responses' +import { Responses } from '../' const debug = Debug('signalk:courseApi') diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 000000000..02232b2cc --- /dev/null +++ b/src/api/index.ts @@ -0,0 +1,43 @@ +export interface ApiResponse { + state: 'FAILED' | 'COMPLETED' | 'PENDING' + statusCode: number + message: string + requestId?: string + href?: string + token?: string +} + +export const Responses = { + ok: { + state: 'COMPLETED', + statusCode: 200, + message: 'OK' + }, + invalid: { + state: 'FAILED', + statusCode: 406, + message: `Invalid Data supplied.` + }, + unauthorised: { + state: 'FAILED', + statusCode: 403, + message: 'Unauthorised' + }, + notFound: { + state: 'FAILED', + statusCode: 404, + message: 'Resource not found.' + } +} + +// returns true if target path is an API request +export function isApiRequest(path:string): boolean { + if ( + path.split('/')[4] === 'resources' || // resources API + path.indexOf('/navigation/course/') !== -1 // course API + ) { + return true + } else { + return false + } +} \ No newline at end of file diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index df2a90edd..5d4b2d3cd 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -8,11 +8,11 @@ import { Application, NextFunction, Request, Response } from 'express' import { v4 as uuidv4 } from 'uuid' import { WithSecurityStrategy, WithSignalK } from '../../app' -import { Responses } from '../responses' +import { Responses } from '../' import { buildResource } from './resources' import { validate } from './validate' -const debug = Debug('signalk:resources') +const debug = Debug('signalk:resourcesApi') const SIGNALK_API_PATH = `/signalk/v1/api` const UUID_PREFIX = 'urn:mrn:signalk:uuid:' diff --git a/src/api/resources/resources.ts b/src/api/resources/resources.ts index c87dc5241..709d964f3 100644 --- a/src/api/resources/resources.ts +++ b/src/api/resources/resources.ts @@ -1,5 +1,5 @@ import { SignalKResourceType } from '@signalk/server-api' -import { getDistance, getLatitude, isValidCoordinate } from 'geolib' +import { getDistance, isValidCoordinate } from 'geolib' export const buildResource = (resType: SignalKResourceType, data: any): any => { if (resType === 'routes') { diff --git a/src/api/responses.ts b/src/api/responses.ts deleted file mode 100644 index 82c009c27..000000000 --- a/src/api/responses.ts +++ /dev/null @@ -1,31 +0,0 @@ -export interface ApiResponse { - state: 'FAILED' | 'COMPLETED' | 'PENDING' - statusCode: number - message: string - requestId?: string - href?: string - token?: string -} - -export const Responses = { - ok: { - state: 'COMPLETED', - statusCode: 200, - message: 'OK' - }, - invalid: { - state: 'FAILED', - statusCode: 406, - message: `Invalid Data supplied.` - }, - unauthorised: { - state: 'FAILED', - statusCode: 403, - message: 'Unauthorised' - }, - notFound: { - state: 'FAILED', - statusCode: 404, - message: 'Resource not found.' - } -} diff --git a/src/put.js b/src/put.js index fa745590b..d603bc941 100644 --- a/src/put.js +++ b/src/put.js @@ -4,6 +4,8 @@ const { v4: uuidv4 } = require('uuid') const { createRequest, updateRequest } = require('./requestResponse') const skConfig = require('./config/config') +const {isApiRequest} = require('./api') + const pathPrefix = '/signalk' const versionPrefix = '/v1' const apiPathPrefix = pathPrefix + versionPrefix + '/api/' @@ -30,14 +32,9 @@ module.exports = { app.deRegisterActionHandler = deRegisterActionHandler app.put(apiPathPrefix + '*', function(req, res, next) { - // ** ignore resources API paths ** - if (req.path.split('/')[4] === 'resources') { - next() - return - } - // ** ignore course API paths ** - if (req.path.indexOf('/navigation/course/') !== -1) { + // check for resources API, course API, etc request + if (isApiRequest(req.path)) { next() return } From c71cfdf45535099dd8f0d2987e40f9d929ecca9c Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Thu, 20 Jan 2022 21:44:42 +0200 Subject: [PATCH 195/410] fix: debug output of event payload --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index a7de1893d..501e583b2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -264,7 +264,7 @@ class Server { ) } if (eventDebug.enabled) { - eventDebug([...args].slice(1)) + eventDebug(args) } } expressAppEmit(eventName, ...args) From b1e351d7b5e35798c15b1b298cacad040cdf0757 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Fri, 21 Jan 2022 13:34:27 +1030 Subject: [PATCH 196/410] chore: update docs --- RESOURCE_PROVIDER_PLUGINS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index 5cd47f6fa..fc8efa00b 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -2,6 +2,8 @@ _This document should be read in conjunction with [SERVERPLUGINS.md](./SERVERPLUGINS.md) as it contains additional information regarding the development of plugins that facilitate the storage and retrieval of resource data._ +To see an example of a resource provider plugin see [resources-provider-plugin](https://github.com/SignalK/resources-provider-plugin/) + --- ## Overview From c93e0bc1ebfb017f23e94205d660588a8b969534 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 22 Jan 2022 10:04:39 +1030 Subject: [PATCH 197/410] POST, PUT & DELETE response.message = resource id --- src/api/resources/index.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 5d4b2d3cd..5d7757bd4 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -224,7 +224,7 @@ export class Resources { res.status(200).json({ state: 'COMPLETED', statusCode: 200, - message: `New ${req.params.resourceType} resource (${id}) saved.` + message: id }) } catch (err) { res.status(404).json({ @@ -301,7 +301,7 @@ export class Resources { res.status(200).json({ state: 'COMPLETED', statusCode: 200, - message: `${req.params.resourceType} resource (${req.params.resourceId}) saved.` + message: req.params.resourceId }) } catch (err) { res.status(404).json({ @@ -348,7 +348,7 @@ export class Resources { res.status(200).json({ state: 'COMPLETED', statusCode: 200, - message: `Resource (${req.params.resourceId}) deleted.` + message: req.params.resourceId }) } catch (err) { res.status(400).json({ @@ -419,7 +419,7 @@ export class Resources { res.status(200).json({ state: 'COMPLETED', statusCode: 200, - message: `SUCCESS: New ${req.params.resourceType} resource created.` + message: apiData.id }) } catch (err) { res.status(404).json({ @@ -489,13 +489,13 @@ export class Resources { res.status(200).json({ state: 'COMPLETED', statusCode: 200, - message: `SUCCESS: ${req.params.resourceType} resource updated.` + message: apiData.id }) } catch (err) { res.status(404).json({ state: 'FAILED', statusCode: 404, - message: `ERROR: ${req.params.resourceType} resource could not be updated!` + message: `ERROR: ${req.params.resourceType}/${apiData.id} could not be updated!` }) } } From 7acf3de4f8fae6994a40892570c9d9b0f0950f52 Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sat, 22 Jan 2022 19:39:52 +0200 Subject: [PATCH 198/410] feature: use debug wrapper The internal structure of debug modules changed in 4.3.0 https://github.com/debug-js/debug/pull/740 so debug no longer maintains an internal list of created debug instances. This means that we can no longer get a list of known debug instances for the Server Log admin UI page. This changes the server code so that all code that uses the added createDebug call get their debug key included in the list. I think the change also means that the settings in Server Log no longer take effect on older debug instances that are transitive dependencies. --- SERVERPLUGINS.md | 2 ++ src/categories.ts | 4 ++-- src/config/config.ts | 10 +++++----- src/debug.ts | 12 ++++++++++++ src/deltaPriority.ts | 4 ++-- src/deltacache.ts | 4 ++-- src/discovery.js | 3 ++- src/index.ts | 25 ++++++++++++------------- src/interfaces/applicationData.js | 3 ++- src/interfaces/appstore.js | 3 ++- src/interfaces/logfiles.js | 3 ++- src/interfaces/nmea-tcp.js | 4 +++- src/interfaces/plugins.ts | 9 ++++----- src/interfaces/rest.js | 3 ++- src/interfaces/tcp.ts | 5 ++--- src/interfaces/webapps.js | 3 ++- src/interfaces/ws.js | 7 +++---- src/mdns.js | 3 ++- src/modules.ts | 8 +++----- src/put.js | 3 ++- src/requestResponse.js | 3 ++- src/security.ts | 4 ++-- src/serverroutes.js | 5 +++-- src/subscriptionmanager.ts | 4 ++-- src/tokensecurity.js | 3 ++- 25 files changed, 79 insertions(+), 58 deletions(-) create mode 100644 src/debug.ts diff --git a/SERVERPLUGINS.md b/SERVERPLUGINS.md index 72e8f7634..8e433b271 100644 --- a/SERVERPLUGINS.md +++ b/SERVERPLUGINS.md @@ -567,6 +567,8 @@ Log debug messages. This is the debug method from the [debug module](https://www `app.debug()` can take any type and will serialize it before outputting. +*Do not use `debug` directly*. Using the debug function provided by the server makes sure that the plugin taps into the server's debug logging system, including the helper switches in Admin UI's Server Log page. + ### `app.savePluginOptions(options, callback)` Save changes to the plugin's options. diff --git a/src/categories.ts b/src/categories.ts index e0cc84bef..cb9ef3022 100644 --- a/src/categories.ts +++ b/src/categories.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import Debug from 'debug' -const debug = Debug('signalk:categories') +import { createDebug } from './debug' +const debug = createDebug('signalk:categories') // tslint:disable-next-line:no-var-requires const { getKeywords } = require('./modules') diff --git a/src/config/config.ts b/src/config/config.ts index 10b22e848..28243c5db 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -15,15 +15,15 @@ */ 'use strict' -import Debug from 'debug' -import path from 'path' -import { SelfIdentity, SignalKMessageHub, WithConfig } from '../app' -import DeltaEditor from '../deltaeditor' -const debug = Debug('signalk-server:config') import fs from 'fs' import _ from 'lodash' +import path from 'path' import semver from 'semver' import { v4 as uuidv4 } from 'uuid' +import { SelfIdentity, SignalKMessageHub, WithConfig } from '../app' +import { createDebug } from '../debug' +import DeltaEditor from '../deltaeditor' +const debug = createDebug('signalk-server:config') let disableWriteSettings = false diff --git a/src/debug.ts b/src/debug.ts new file mode 100644 index 000000000..6d2f69212 --- /dev/null +++ b/src/debug.ts @@ -0,0 +1,12 @@ +import coreDebug from 'debug' + +const knownDebugs = new Set() + +export function createDebug(debugName: string) { + knownDebugs.add(debugName) + return coreDebug(debugName) +} + +export function listKnownDebugs() { + return Array.from(knownDebugs) +} diff --git a/src/deltaPriority.ts b/src/deltaPriority.ts index fab06f4cd..38086bc63 100644 --- a/src/deltaPriority.ts +++ b/src/deltaPriority.ts @@ -1,5 +1,5 @@ -import Debug from 'debug' -const debug = Debug('signalk-server:sourcepriorities') +import { createDebug } from './debug' +const debug = createDebug('signalk-server:sourcepriorities') type Brand = K & { __brand: T } diff --git a/src/deltacache.ts b/src/deltacache.ts index 7af8eacfc..be1b92f05 100644 --- a/src/deltacache.ts +++ b/src/deltacache.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import Debug from 'debug' -const debug = Debug('signalk-server:deltacache') +import { createDebug } from './debug' +const debug = createDebug('signalk-server:deltacache') import { FullSignalK, getSourceId } from '@signalk/signalk-schema' import _, { isUndefined } from 'lodash' import { toDelta } from './streambundle' diff --git a/src/discovery.js b/src/discovery.js index 8a8fd6c4b..ab2589a87 100644 --- a/src/discovery.js +++ b/src/discovery.js @@ -14,7 +14,8 @@ * limitations under the License. */ -const debug = require('debug')('signalk-server:discovery') +import { createDebug } from './debug' +const debug = createDebug('signalk-server:discovery') const canboatjs = require('@canboat/canboatjs') const dgram = require('dgram') const mdns = require('mdns-js') diff --git a/src/index.ts b/src/index.ts index 501e583b2..5984e77bd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,27 +19,22 @@ if (typeof [].includes !== 'function') { process.exit(-1) } -import Debug from 'debug' -import express from 'express' -import _ from 'lodash' -const debug = Debug('signalk-server') import { PropertyValues } from '@signalk/server-api' import { FullSignalK, getSourceId } from '@signalk/signalk-schema' -import { Request, Response } from 'express' +import { Debugger } from 'debug' +import express, { Request, Response } from 'express' import http from 'http' import https from 'https' +import _ from 'lodash' import path from 'path' import { SelfIdentity, ServerApp, SignalKMessageHub, WithConfig } from './app' -import { Config, ConfigApp } from './config/config' +import { ConfigApp, load, sendBaseDeltas } from './config/config' +import { createDebug } from './debug' import DeltaCache from './deltacache' import DeltaChain, { DeltaInputHandler } from './deltachain' import { getToPreferredDelta, ToPreferredDelta } from './deltaPriority' -import { checkForNewServerVersion } from './modules' -import SubscriptionManager from './subscriptionmanager' -import { Delta } from './types' - -import { load, sendBaseDeltas } from './config/config' import { incDeltaStatistics, startDeltaStatistics } from './deltastats' +import { checkForNewServerVersion } from './modules' import { getExternalPort, getPrimaryPort, getSecondaryPort } from './ports' import { getCertificateOptions, @@ -47,6 +42,10 @@ import { saveSecurityConfig, startSecurity } from './security.js' +import SubscriptionManager from './subscriptionmanager' +import { Delta } from './types' +const debug = createDebug('signalk-server') + // tslint:disable-next-line: no-var-requires const { StreamBundle } = require('./streambundle') @@ -253,13 +252,13 @@ class Server { const self = this const app = this.app - const eventDebugs: { [key: string]: Debug.Debugger } = {} + const eventDebugs: { [key: string]: Debugger } = {} const expressAppEmit = app.emit.bind(app) app.emit = (eventName: string, ...args: any[]) => { if (eventName !== 'serverlog') { let eventDebug = eventDebugs[eventName] if (!eventDebug) { - eventDebugs[eventName] = eventDebug = Debug( + eventDebugs[eventName] = eventDebug = createDebug( `signalk-server:events:${eventName}` ) } diff --git a/src/interfaces/applicationData.js b/src/interfaces/applicationData.js index 41d7c2b61..8a75d090d 100644 --- a/src/interfaces/applicationData.js +++ b/src/interfaces/applicationData.js @@ -15,7 +15,8 @@ */ const _ = require('lodash') -const debug = require('debug')('signalk-server:interfaces:applicationData') +import { createDebug } from '../debug' +const debug = createDebug('signalk-server:interfaces:applicationData') const fs = require('fs') const path = require('path') const jsonpatch = require('json-patch') diff --git a/src/interfaces/appstore.js b/src/interfaces/appstore.js index dbfb00fbd..d3051bb26 100644 --- a/src/interfaces/appstore.js +++ b/src/interfaces/appstore.js @@ -14,7 +14,8 @@ * limitations under the License. */ -const debug = require('debug')('signalk:interfaces:appstore') +import { createDebug } from '../debug' +const debug = createDebug('signalk:interfaces:appstore') const _ = require('lodash') const compareVersions = require('compare-versions') const { installModule, removeModule } = require('../modules') diff --git a/src/interfaces/logfiles.js b/src/interfaces/logfiles.js index a937db61c..8029a4289 100644 --- a/src/interfaces/logfiles.js +++ b/src/interfaces/logfiles.js @@ -14,7 +14,8 @@ * limitations under the License. */ -const debug = require('debug')('signalk:interfaces:logfiles') +import { createDebug } from '../debug' +const debug = createDebug('signalk:interfaces:logfiles') const moment = require('moment') const fs = require('fs') const path = require('path') diff --git a/src/interfaces/nmea-tcp.js b/src/interfaces/nmea-tcp.js index 391729590..03f736fa6 100644 --- a/src/interfaces/nmea-tcp.js +++ b/src/interfaces/nmea-tcp.js @@ -15,6 +15,9 @@ const _ = require('lodash') +import { createDebug } from '../debug' +const debug = createDebug('signalk-server:interfaces:tcp:nmea0183') + module.exports = function(app) { 'use strict' const net = require('net') @@ -24,7 +27,6 @@ module.exports = function(app) { const port = process.env.NMEA0183PORT || 10110 const api = {} - const debug = require('debug')('signalk-server:interfaces:tcp:nmea0183') api.start = function() { debug('Starting tcp interface') diff --git a/src/interfaces/plugins.ts b/src/interfaces/plugins.ts index 6eb59d250..786f142ce 100644 --- a/src/interfaces/plugins.ts +++ b/src/interfaces/plugins.ts @@ -13,9 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import Debug from 'debug' -import { Request, Response } from 'express' -const debug = Debug('signalk:interfaces:plugins') import { PluginServerApp, PropertyValues, @@ -23,13 +20,15 @@ import { } from '@signalk/server-api' // @ts-ignore import { getLogger } from '@signalk/streams/logging' -import express from 'express' +import express, { Request, Response } from 'express' import fs from 'fs' import _ from 'lodash' import path from 'path' import { SERVERROUTESPREFIX } from '../constants' +import { createDebug } from '../debug' import { DeltaInputHandler } from '../deltachain' import { listAllSerialPorts, Ports } from '../serialports' +const debug = createDebug('signalk:interfaces:plugins') // tslint:disable-next-line:no-var-requires const modulesWithKeyword = require('../modules').modulesWithKeyword @@ -511,7 +510,7 @@ module.exports = (theApp: any) => { console.error(msg.stack) } }, - debug: require('debug')(packageName), + debug: createDebug(packageName), registerDeltaInputHandler: (handler: any) => { onStopHandlers[plugin.id].push(app.registerDeltaInputHandler(handler)) }, diff --git a/src/interfaces/rest.js b/src/interfaces/rest.js index 9f65126bf..72bea7075 100644 --- a/src/interfaces/rest.js +++ b/src/interfaces/rest.js @@ -14,7 +14,8 @@ * limitations under the License. */ -const debug = require('debug')('signalk-server:interfaces:rest') +const { createDebug } = require('../debug') +const debug = createDebug('signalk-server:interfaces:rest') const express = require('express') const { getMetadata, getUnits } = require('@signalk/signalk-schema') const ports = require('../ports') diff --git a/src/interfaces/tcp.ts b/src/interfaces/tcp.ts index 7721dee17..0b8e609c6 100644 --- a/src/interfaces/tcp.ts +++ b/src/interfaces/tcp.ts @@ -13,12 +13,11 @@ * limitations under the License. */ -import Debug from 'debug' -import { values } from 'lodash' import { createServer, Server, Socket } from 'net' import split from 'split' -const debug = Debug('signalk-server:interfaces:tcp:signalk') +import { createDebug } from '../debug' import { Interface, SignalKServer, Unsubscribes } from '../types' +const debug = createDebug('signalk-server:interfaces:tcp:signalk') interface SocketWithId extends Socket { id?: number diff --git a/src/interfaces/webapps.js b/src/interfaces/webapps.js index 232b77e91..8d2676833 100644 --- a/src/interfaces/webapps.js +++ b/src/interfaces/webapps.js @@ -14,7 +14,8 @@ * limitations under the License. */ -const debug = require('debug')('signalk:interfaces:webapps') +import { createDebug } from '../debug' +const debug = createDebug('signalk:interfaces:webapps') const fs = require('fs') const path = require('path') const express = require('express') diff --git a/src/interfaces/ws.js b/src/interfaces/ws.js index 65a40b695..f3bf4f5fe 100644 --- a/src/interfaces/ws.js +++ b/src/interfaces/ws.js @@ -26,10 +26,9 @@ const { } = require('../requestResponse') const { putPath } = require('../put') const skConfig = require('../config/config') -const debug = require('debug')('signalk-server:interfaces:ws') -const debugConnection = require('debug')( - 'signalk-server:interfaces:ws:connections' -) +import { createDebug } from '../debug' +const debug = createDebug('signalk-server:interfaces:ws') +const debugConnection = createDebug('signalk-server:interfaces:ws:connections') const Primus = require('primus') const supportedQuerySubscribeValues = ['self', 'all'] diff --git a/src/mdns.js b/src/mdns.js index ee7a6f100..cb3f0bc67 100644 --- a/src/mdns.js +++ b/src/mdns.js @@ -17,7 +17,8 @@ 'use strict' const _ = require('lodash') -const debug = require('debug')('signalk-server:mdns') +import { createDebug } from './debug' +const debug = createDebug('signalk-server:mdns') const dnssd = require('dnssd2') const ports = require('./ports') diff --git a/src/modules.ts b/src/modules.ts index b2bea5f11..5727fe1e4 100644 --- a/src/modules.ts +++ b/src/modules.ts @@ -15,16 +15,14 @@ */ import { spawn } from 'child_process' -import Debug from 'debug' import fs from 'fs' -import fetch from 'node-fetch' -import { Response } from 'node-fetch' -const debug = Debug('signalk:modules') import _ from 'lodash' +import fetch, { Response } from 'node-fetch' import path from 'path' import semver, { SemVer } from 'semver' -import { WithConfig } from './app' import { Config } from './config/config' +import { createDebug } from './debug' +const debug = createDebug('signalk:modules') interface ModuleData { module: string diff --git a/src/put.js b/src/put.js index 8db74fac8..eb6566215 100644 --- a/src/put.js +++ b/src/put.js @@ -1,5 +1,6 @@ const _ = require('lodash') -const debug = require('debug')('signalk-server:put') +import { createDebug } from './debug' +const debug = createDebug('signalk-server:put') const { v4: uuidv4 } = require('uuid') const { createRequest, updateRequest } = require('./requestResponse') const skConfig = require('./config/config') diff --git a/src/requestResponse.js b/src/requestResponse.js index 52230f8f8..bb4703dd1 100644 --- a/src/requestResponse.js +++ b/src/requestResponse.js @@ -1,5 +1,6 @@ const { v4: uuidv4 } = require('uuid') -const debug = require('debug')('signalk-server:requestResponse') +import { createDebug } from './debug' +const debug = createDebug('signalk-server:requestResponse') const _ = require('lodash') const requests = {} diff --git a/src/security.ts b/src/security.ts index 97d55fb6f..ccffd8692 100644 --- a/src/security.ts +++ b/src/security.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import Debug from 'debug' import { chmodSync, existsSync, @@ -29,8 +28,9 @@ import path from 'path' import pem from 'pem' import { Mode } from 'stat-mode' import { WithConfig } from './app' +import { createDebug } from './debug' import dummysecurity from './dummysecurity' -const debug = Debug('signalk-server:security') +const debug = createDebug('signalk-server:security') export interface WithSecurityStrategy { securityStrategy: SecurityStrategy diff --git a/src/serverroutes.js b/src/serverroutes.js index eb5e65a96..75a1d48f9 100644 --- a/src/serverroutes.js +++ b/src/serverroutes.js @@ -18,7 +18,8 @@ const fs = require('fs') const os = require('os') const readdir = require('util').promisify(fs.readdir) const page = require('./page') -const debug = require('debug')('signalk-server:serverroutes') +import { createDebug, listKnownDebugs } from './debug' +const debug = createDebug('signalk-server:serverroutes') const path = require('path') const _ = require('lodash') const skConfig = require('./config/config') @@ -760,7 +761,7 @@ module.exports = function(app, saveSecurityConfig, getSecurityConfig) { }) app.get(`${SERVERROUTESPREFIX}/debugKeys`, (req, res) => { - res.json(_.uniq(require('debug').instances.map(i => i.namespace))) + res.json(listKnownDebugs()) }) app.post(`${SERVERROUTESPREFIX}/rememberDebug`, (req, res) => { diff --git a/src/subscriptionmanager.ts b/src/subscriptionmanager.ts index 32089c1c3..766fd7cbf 100644 --- a/src/subscriptionmanager.ts +++ b/src/subscriptionmanager.ts @@ -15,13 +15,13 @@ */ import Bacon from 'baconjs' -import Debug from 'debug' import { isPointWithinRadius } from 'geolib' import _, { forOwn, get, isString } from 'lodash' -const debug = Debug('signalk-server:subscriptionmanager') +import { createDebug } from './debug' import DeltaCache from './deltacache' import { toDelta } from './streambundle' import { ContextMatcher, Position, Unsubscribes, WithContext } from './types' +const debug = createDebug('signalk-server:subscriptionmanager') interface BusesMap { [key: string]: any diff --git a/src/tokensecurity.js b/src/tokensecurity.js index f79872651..24d701652 100644 --- a/src/tokensecurity.js +++ b/src/tokensecurity.js @@ -14,7 +14,8 @@ * limitations under the License. */ -const debug = require('debug')('signalk-server:tokensecurity') +import { createDebug } from './debug' +const debug = createDebug('signalk-server:tokensecurity') const util = require('util') const jwt = require('jsonwebtoken') const _ = require('lodash') From 55fe8aa553684f83bf429da37992461793a5f5f2 Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sat, 22 Jan 2022 23:02:55 +0200 Subject: [PATCH 199/410] feature: use debug wrapper in pipedprovider & streams Modify streams to use createDebug passed in via options. Keep backwards compatibility for usage outside the SK server. --- packages/streams/canboatjs.js | 4 +++- packages/streams/execute.js | 3 ++- packages/streams/gpsd.js | 7 ++++--- packages/streams/keys-filter.js | 5 +++-- packages/streams/logging.js | 3 ++- packages/streams/mdns-ws.js | 12 +++++++----- packages/streams/n2kAnalyzer.js | 3 +-- packages/streams/nmea0183-signalk.js | 3 +-- packages/streams/pigpio-seatalk.js | 9 ++++----- packages/streams/s3.js | 3 +-- packages/streams/serialport.js | 15 +++++++++------ packages/streams/simple.js | 10 +++++++--- packages/streams/tcp.js | 25 ++++++++++++------------- packages/streams/udp.js | 4 ++-- src/index.ts | 1 - src/pipedproviders.js | 6 +++++- 16 files changed, 63 insertions(+), 50 deletions(-) diff --git a/packages/streams/canboatjs.js b/packages/streams/canboatjs.js index 3ca7caf98..33209ef91 100644 --- a/packages/streams/canboatjs.js +++ b/packages/streams/canboatjs.js @@ -16,7 +16,6 @@ const Transform = require('stream').Transform const FromPgn = require('@canboat/canboatjs').FromPgn -const debug = require('debug')('signalk:streams:canboatjs') const _ = require('lodash') function CanboatJs (options) { @@ -25,6 +24,9 @@ function CanboatJs (options) { }) this.fromPgn = new FromPgn(options) + const createDebug = options.createDebug || require('debug') + const debug = createDebug('signalk:streams:nmea0183-signalk') + this.fromPgn.on('warning', (pgn, warning) => { debug(`[warning] ${pgn.pgn} ${warning}`) diff --git a/packages/streams/execute.js b/packages/streams/execute.js index f6b83ead1..782c083f4 100644 --- a/packages/streams/execute.js +++ b/packages/streams/execute.js @@ -42,7 +42,8 @@ const { pgnToActisenseSerialFormat } = require('@canboat/canboatjs') function Execute (options) { Transform.call(this, {}) this.options = options - this.debug = options.debug || require('debug')('signalk:streams:execute') + const createDebug = options.createDebug || require('debug') + this.debug = options.debug || createDebug('signalk:streams:execute') } require('util').inherits(Execute, Transform) diff --git a/packages/streams/gpsd.js b/packages/streams/gpsd.js index 51122706d..973b01771 100644 --- a/packages/streams/gpsd.js +++ b/packages/streams/gpsd.js @@ -32,7 +32,6 @@ const Transform = require('stream').Transform const gpsd = require('node-gpsd') -const debug = require('debug')('signalk:streams:gpsd') function Gpsd (options) { Transform.call(this, { @@ -45,12 +44,14 @@ function Gpsd (options) { function setProviderStatus(msg) { options.app.setProviderStatus(options.providerId, msg) } - + + const createDebug = options.createDebug || require('debug') + this.listener = new gpsd.Listener({ port, hostname, logger: { - info: debug, + info: createDebug('signalk:streams:gpsd'), warn: console.warn, error: (msg) => { options.app.setProviderError(options.providerId, `${hostname}:${port}: ` + msg) diff --git a/packages/streams/keys-filter.js b/packages/streams/keys-filter.js index 27e9c1b64..10dfe7286 100644 --- a/packages/streams/keys-filter.js +++ b/packages/streams/keys-filter.js @@ -1,13 +1,14 @@ 'use strict' const Transform = require('stream').Transform -const debug = require('debug')('signalk:streams:keys-filter') function ToSignalK (options) { Transform.call(this, { objectMode: true }) + const createDebug = options.createDebug || require('debug') + this.debug = createDebug('signalk:streams:keys-filter') this.exclude = options.excludeMatchingPaths } @@ -25,7 +26,7 @@ ToSignalK.prototype._transform = function (chunk, encoding, done) { delta = JSON.parse(chunk) string = true } catch (e) { - debug(`Error parsing chunk: ${e.message}`) + this.debug(`Error parsing chunk: ${e.message}`) } } diff --git a/packages/streams/logging.js b/packages/streams/logging.js index aae29574f..08aa159d7 100644 --- a/packages/streams/logging.js +++ b/packages/streams/logging.js @@ -16,7 +16,7 @@ const { FileTimestampStream } = require('file-timestamp-stream') const path = require('path') -const debug = require('debug')('signalk:streams:logging') +let debug = require('debug')('signalk:streams:logging') const fs = require('fs') const filenamePattern = /skserver\-raw\_\d\d\d\d\-\d\d\-\d\dT\d\d\.log/ @@ -35,6 +35,7 @@ class FileTimestampStreamWithDelete extends FileTimestampStream { this.filesToKeep = filesToKeep this.fullLogDir = fullLogDir this.prevFilename = undefined + debug = (options.createDebug || require('debug'))('signalk:streams:logging') } // This method of base class is called when new file name is contemplated diff --git a/packages/streams/mdns-ws.js b/packages/streams/mdns-ws.js index 01bf56538..f3cf436fc 100644 --- a/packages/streams/mdns-ws.js +++ b/packages/streams/mdns-ws.js @@ -18,8 +18,6 @@ const Transform = require('stream').Transform const SignalK = require('@signalk/client') -const debug = require('debug')('signalk:streams:mdns-ws') -const dataDebug = require('debug')('signalk:streams:mdns-ws-data') const WebSocket = require('ws') @@ -33,6 +31,10 @@ function MdnsWs (options) { this.remoteServers = {} this.remoteServers[this.selfHost + ':' + this.selfPort] = {} const deltaStreamBehaviour = options.subscription ? 'none' : 'all' + + const createDebug = options.createDebug || require('debug') + this.debug = createDebug('signalk:streams:mdns-ws') + this.dataDebug = createDebug('signalk:streams:mdns-ws-data') debug(`deltaStreamBehaviour:${deltaStreamBehaviour}`) this.handleContext = () => { } @@ -92,7 +94,7 @@ MdnsWs.prototype.connect = function (client) { setProviderStatus(that, that.options.providerId, `ws connection connected to ${client.options.hostname}:${client.options.port}`) if (this.options.selfHandling === 'useRemoteSelf') { client.API().then(api => api.get('/self')).then(selfFromServer => { - debug(`Mapping context ${selfFromServer} to self (empty context)`) + that.debug(`Mapping context ${selfFromServer} to self (empty context)`) this.handleContext = (delta) => { if (delta.context === selfFromServer) { delete delta.context @@ -115,7 +117,7 @@ MdnsWs.prototype.connect = function (client) { parsed = [ parsed ] } parsed.forEach((sub, idx) => { - debug('sending subscription %j', sub) + that.debug('sending subscription %j', sub) client.subscribe(sub, String(idx)) }) } @@ -127,7 +129,7 @@ MdnsWs.prototype.connect = function (client) { client.on('delta', (data) => { if (data && data.updates) { that.handleContext(data) - if (dataDebug.enabled) { dataDebug(JSON.stringify(data)) } + if (that.dataDebug.enabled) { that.dataDebug(JSON.stringify(data)) } data.updates.forEach(function (update) { update['$source'] = `${that.options.providerId}.${client.options.hostname}:${client.options.port}` }) diff --git a/packages/streams/n2kAnalyzer.js b/packages/streams/n2kAnalyzer.js index a0441e563..e9d104b5d 100644 --- a/packages/streams/n2kAnalyzer.js +++ b/packages/streams/n2kAnalyzer.js @@ -15,7 +15,6 @@ */ const Transform = require('stream').Transform -const debug = require('debug')('signalk:streams:n2k-analyzer') function N2KAnalyzer (options) { Transform.call(this, { @@ -68,7 +67,7 @@ N2KAnalyzer.prototype.pipe = function (pipeTo) { } N2KAnalyzer.prototype.end = function () { - debug('end, killing child analyzer process') + console.log('end, killing child analyzer process') this.analyzerProcess.kill() this.pipeTo.end() } diff --git a/packages/streams/nmea0183-signalk.js b/packages/streams/nmea0183-signalk.js index 05f9b4b33..2c48ed28f 100644 --- a/packages/streams/nmea0183-signalk.js +++ b/packages/streams/nmea0183-signalk.js @@ -32,7 +32,6 @@ const Transform = require('stream').Transform const Parser = require('@signalk/nmea0183-signalk') const utils = require('@signalk/nmea0183-utilities') -const debug = require('debug')('signalk:streams:nmea0183-signalk') const n2kToDelta = require('@signalk/n2k-signalk').toDelta const FromPgn = require('@canboat/canboatjs').FromPgn @@ -108,7 +107,7 @@ Nmea0183ToSignalK.prototype._transform = function (chunk, encoding, done) { } } } catch (e) { - debug(`[error] ${e.message}`) + console.error(e) } done() diff --git a/packages/streams/pigpio-seatalk.js b/packages/streams/pigpio-seatalk.js index 283a4fdb0..88bebcb0d 100644 --- a/packages/streams/pigpio-seatalk.js +++ b/packages/streams/pigpio-seatalk.js @@ -15,14 +15,13 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see . - * + * * 2020-06-24 Original Python code from @Thomas-GeDaD https://github.com/Thomas-GeDaD/Seatalk1-Raspi-reader * and finetuned by @MatsA * */ const Execute = require('./execute') -const debug = require('debug')('signalk:streams:pigpio-seatalk') const cmd = ` import pigpio, time, signal, sys @@ -79,13 +78,13 @@ if __name__ == "__main__": print ("exit") ` -function PigpioSeatalk (options) { - Execute.call(this, {debug}) +function PigpioSeatalk(options) { + const createDebug = options.createDebug || require('debug') + Execute.call(this, { debug: createDebug('signalk:streams:pigpio-seatalk') }) this.options = options this.options.command = `python -u -c '${cmd}' ${options.gpio} ${options.gpioInvert} ` } require('util').inherits(PigpioSeatalk, Execute) - module.exports = PigpioSeatalk diff --git a/packages/streams/s3.js b/packages/streams/s3.js index f19ac1b3c..2cf10aea0 100644 --- a/packages/streams/s3.js +++ b/packages/streams/s3.js @@ -23,8 +23,7 @@ var Transform = require('stream').Transform deprecation warnings. Known to work with ^2.413.0 */ -const AWS = require('aws-sdk') -const debug = require('debug')('signalk:streams:s3-provider') +const AWS = require('aws-sdk') function S3Provider ({ bucket, prefix }) { Transform.call(this, { diff --git a/packages/streams/serialport.js b/packages/streams/serialport.js index bb2ae56f3..f9584c29f 100644 --- a/packages/streams/serialport.js +++ b/packages/streams/serialport.js @@ -62,7 +62,6 @@ const child_process = require('child_process') const shellescape = require('any-shell-escape') const SerialPort = require('serialport') const isArray = require('lodash').isArray -const debug = require('debug')('signalk:streams:serialport') const isBuffer = require('lodash').isBuffer function SerialStream (options) { @@ -79,11 +78,16 @@ function SerialStream (options) { this.maxPendingWrites = options.maxPendingWrites || 5 this.start() this.isFirstError = true + + const createDebug = options.createDebug || require('debug') + this.debug = createDebug('signalk:streams:serialport') } require('util').inherits(SerialStream, Transform) SerialStream.prototype.start = function () { + const that = this + if (this.serial !== null) { this.serial.unpipe(this) this.serial.removeAllListeners() @@ -125,7 +129,7 @@ SerialStream.prototype.start = function () { if (this.isFirstError) { console.log(x.message) } - debug(x.message) + this.debug(x.message) this.isFirstError = false this.scheduleReconnect() }.bind(this) @@ -141,7 +145,6 @@ SerialStream.prototype.start = function () { }.bind(this) ) - const that = this let pendingWrites = 0 const stdOutEvent = this.options.toStdout if (stdOutEvent) { @@ -152,10 +155,10 @@ SerialStream.prototype.start = function () { that.options.app.on(event, d => { if (pendingWrites > that.maxPendingWrites) { - debug('Buffer overflow, not writing:' + d) + that.debug('Buffer overflow, not writing:' + d) return } - debug('Writing:' + d) + that.debug('Writing:' + d) if ( isBuffer(d) ) { that.serial.write(d) } else { @@ -182,7 +185,7 @@ SerialStream.prototype.scheduleReconnect = function () { const msg = `Not connected (retry delay ${( this.reconnectDelay / 1000 ).toFixed(0)} s)` - debug(msg) + this.debug(msg) this.options.app.setProviderStatus(this.options.providerId, msg) setTimeout(this.start.bind(this), this.reconnectDelay) } diff --git a/packages/streams/simple.js b/packages/streams/simple.js index b9530ce62..1bdb51b74 100644 --- a/packages/streams/simple.js +++ b/packages/streams/simple.js @@ -1,7 +1,6 @@ const Transform = require('stream').Transform const Writable = require('stream').Writable const _ = require('lodash') -const debug = require('debug')('signalk:simple') const N2kAnalyzer = require('./n2kAnalyzer') const FromJson = require('./from_json') const MultiplexedLog = require('./multiplexedlog') @@ -27,9 +26,14 @@ const pigpioSeatalk = require('./pigpio-seatalk') function Simple (options) { Transform.call(this, { objectMode: true }) - const { emitPropertyValue, onPropertyValues } = options + const { emitPropertyValue, onPropertyValues, createDebug } = options options = { ...options } - options.subOptions = { ...options.subOptions, emitPropertyValue, onPropertyValues } + options.subOptions = { + ...options.subOptions, + emitPropertyValue, + onPropertyValues, + createDebug + } options.subOptions.providerId = options.providerId const dataType = options.subOptions.dataType || options.type diff --git a/packages/streams/tcp.js b/packages/streams/tcp.js index 42a80802b..21934848e 100644 --- a/packages/streams/tcp.js +++ b/packages/streams/tcp.js @@ -32,23 +32,22 @@ const net = require('net') const Transform = require('stream').Transform const isArray = require('lodash').isArray -const debug = require('debug')('signalk-server:streams:tcp') -const debugData = require('debug')('signalk-server:streams:tcp-data') - function TcpStream (options) { Transform.call(this, options) this.options = options this.noDataReceivedTimeout = Number.parseInt((this.options.noDataReceivedTimeout + '').trim()) * 1000 + this.debug = (options.createDebug || require('debug'))('signalk:streams:tcp') + this.debugData = (options.createDebug || require('debug'))('signalk:streams:tcp-data') } require('util').inherits(TcpStream, Transform) TcpStream.prototype.pipe = function (pipeTo) { + const that = this if ( this.options.outEvent ) { - const that = this that.options.app.on(that.options.outEvent, function (d) { if ( that.tcpStream ) { - debug('sending %s', d) + that.debug('sending %s', d) that.tcpStream.write(d) } }) @@ -61,7 +60,7 @@ TcpStream.prototype.pipe = function (pipeTo) { that.options.app.on(stdEvent, function (d) { if (that.tcpStream) { that.tcpStream.write(d + '\r\n') - debug('event %s sending %s', stdEvent, d) + that. debug('event %s sending %s', stdEvent, d) } }) }) @@ -72,19 +71,19 @@ TcpStream.prototype.pipe = function (pipeTo) { })({ maxDelay: 5 * 1000 }, tcpStream => { if (!isNaN(this.noDataReceivedTimeout)) { tcpStream.setTimeout(this.noDataReceivedTimeout) - debug( + that.debug( `Setting socket idle timeout ${this.options.host}:${this.options.port} ${this.noDataReceivedTimeout}` ) tcpStream.on('timeout', () => { - debug( + that.debug( `Idle timeout, closing socket ${this.options.host}:${this.options.port}` ) tcpStream.end() }) } tcpStream.on('data', data => { - if (debugData.enabled) { - debugData(data.toString()) + if (that.debugData.enabled) { + that.debugData(data.toString()) } this.write(data) }) @@ -93,18 +92,18 @@ TcpStream.prototype.pipe = function (pipeTo) { this.tcpStream = con const msg = `Connected to ${this.options.host} ${this.options.port}` this.options.app.setProviderStatus(this.options.providerId, msg) - debug(msg) + that.debug(msg) }) .on('reconnect', (n, delay) => { const msg = `Reconnect ${this.options.host} ${ this.options.port } retry ${n} delay ${delay}` this.options.app.setProviderError(this.options.providerId, msg) - debug(msg) + that.debug(msg) }) .on('disconnect', err => { delete this.tcpStream - debug(`Disconnected ${this.options.host} ${this.options.port}`) + that.debug(`Disconnected ${this.options.host} ${this.options.port}`) }) .on('error', err => { this.options.app.setProviderError(this.options.providerId, err.message) diff --git a/packages/streams/udp.js b/packages/streams/udp.js index e33939f91..f8fc26026 100644 --- a/packages/streams/udp.js +++ b/packages/streams/udp.js @@ -34,13 +34,13 @@ */ const Transform = require('stream').Transform -const debug = require('debug')('signalk:streams:udp') function Udp (options) { Transform.call(this, { objectMode: false }) this.options = options + this.debug = (options.createDebug || require('debug'))('signalk:streams:udp') } require('util').inherits(Udp, Transform) @@ -52,7 +52,7 @@ Udp.prototype.pipe = function (pipeTo) { const socket = require('dgram').createSocket('udp4') const self = this socket.on('message', function (message, remote) { - debug(message.toString()) + self.debug(message.toString()) self.push(message) }) socket.bind(this.options.port) diff --git a/src/index.ts b/src/index.ts index 5984e77bd..816df5389 100644 --- a/src/index.ts +++ b/src/index.ts @@ -46,7 +46,6 @@ import SubscriptionManager from './subscriptionmanager' import { Delta } from './types' const debug = createDebug('signalk-server') - // tslint:disable-next-line: no-var-requires const { StreamBundle } = require('./streambundle') diff --git a/src/pipedproviders.js b/src/pipedproviders.js index 5974c40ae..51f3ea45e 100644 --- a/src/pipedproviders.js +++ b/src/pipedproviders.js @@ -14,6 +14,7 @@ * limitations under the License. */ +import { createDebug } from './debug' const deep = require('deep-get-set') const DevNull = require('dev-null-stream') const _ = require('lodash') @@ -84,7 +85,10 @@ module.exports = function(app) { const efectiveElementType = elementConfig.type.startsWith('providers/') ? elementConfig.type.replace('providers/', '@signalk/streams/') : elementConfig.type - return new (require(efectiveElementType))(elementConfig.options) + return new (require(efectiveElementType))({ + ...elementConfig.options, + createDebug + }) } function startProviders() { From 0af55d12c96e1fdccb146e0b9f1281f281e6e0b2 Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 23 Jan 2022 18:46:46 +0200 Subject: [PATCH 200/410] chore: @signalk/streams@2.0.1 --- packages/streams/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/streams/package.json b/packages/streams/package.json index 01c6cd428..27c1096db 100644 --- a/packages/streams/package.json +++ b/packages/streams/package.json @@ -1,6 +1,6 @@ { "name": "@signalk/streams", - "version": "2.0.0", + "version": "2.0.1", "description": "Utilities for handling streams of Signal K data", "main": "index.js", "scripts": { From 8866f3d2c9ef748360b09dd77e2ac077e290634b Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 23 Jan 2022 18:48:58 +0200 Subject: [PATCH 201/410] 1.41.0-beta.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6d54d0176..72b553c6e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "signalk-server", - "version": "1.41.0-beta.3", + "version": "1.41.0-beta.4", "description": "An implementation of a [Signal K](http://signalk.org) server for boats.", "main": "index.js", "scripts": { From 9aa5e558ee64dfba8da8c8f0a55866d29cb33410 Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 23 Jan 2022 19:00:34 +0200 Subject: [PATCH 202/410] chore: remove aws-sdk dependency This was supposed to be included in 694a264f83c30213e652b9e42d0cecc95f20a4aa. --- packages/streams/package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/streams/package.json b/packages/streams/package.json index 27c1096db..a2f34f27d 100644 --- a/packages/streams/package.json +++ b/packages/streams/package.json @@ -28,7 +28,6 @@ "@signalk/nmea0183-utilities": "^0.8.0", "@signalk/signalk-schema": "^1.5.0", "any-shell-escape": "^0.1.1", - "aws-sdk": "^2.413.0", "file-timestamp-stream": "^2.1.2", "lodash": "^4.17.4", "moment": "^2.24.0", @@ -38,5 +37,8 @@ }, "optionalDependencies": { "serialport": "^9.0.1" + }, + "devDependencies": { + "prettier": "2.5.1" } } From 9fa5d5ec5687bcae46c19a2496b7cec48832c72a Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 23 Jan 2022 19:04:47 +0200 Subject: [PATCH 203/410] chore: add prettier to @signalk/streams --- packages/streams/.prettierrc.json | 4 ++++ packages/streams/package.json | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 packages/streams/.prettierrc.json diff --git a/packages/streams/.prettierrc.json b/packages/streams/.prettierrc.json new file mode 100644 index 000000000..00fbdb185 --- /dev/null +++ b/packages/streams/.prettierrc.json @@ -0,0 +1,4 @@ +{ + "semi": false, + "singleQuote": true +} \ No newline at end of file diff --git a/packages/streams/package.json b/packages/streams/package.json index a2f34f27d..3fdeec456 100644 --- a/packages/streams/package.json +++ b/packages/streams/package.json @@ -4,7 +4,8 @@ "description": "Utilities for handling streams of Signal K data", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "prettier --check .", + "format": "prettier --write ." }, "repository": { "type": "git", From f800fc9c62b249dc9c7f67252339408b0d8fa304 Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 23 Jan 2022 19:08:24 +0200 Subject: [PATCH 204/410] style: format @signalk/streams --- packages/streams/.prettierrc.json | 2 +- packages/streams/README.md | 12 +- packages/streams/autodetect.js | 10 +- packages/streams/canboatjs.js | 15 +-- packages/streams/execute.js | 14 ++- packages/streams/filestream.js | 4 +- packages/streams/folderstream.js | 43 ++++--- packages/streams/from_json.js | 4 +- packages/streams/gpsd.js | 21 ++-- packages/streams/keys-filter.js | 12 +- packages/streams/liner.js | 4 +- packages/streams/log.js | 6 +- packages/streams/logging.js | 57 +++++----- packages/streams/mdns-ws.js | 90 +++++++++------ packages/streams/n2k-signalk.js | 149 +++++++++++++++---------- packages/streams/n2kAnalyzer.js | 8 +- packages/streams/nmea0183-signalk.js | 18 +-- packages/streams/nullprovider.js | 4 +- packages/streams/replacer.js | 6 +- packages/streams/s3.js | 23 ++-- packages/streams/serialport.js | 12 +- packages/streams/simple.js | 144 +++++++++++++----------- packages/streams/splitting-liner.js | 4 +- packages/streams/tcp.js | 47 ++++---- packages/streams/tcpserver.js | 4 +- packages/streams/timestamp-throttle.js | 6 +- packages/streams/udp.js | 4 +- 27 files changed, 399 insertions(+), 324 deletions(-) diff --git a/packages/streams/.prettierrc.json b/packages/streams/.prettierrc.json index 00fbdb185..b2095be81 100644 --- a/packages/streams/.prettierrc.json +++ b/packages/streams/.prettierrc.json @@ -1,4 +1,4 @@ { "semi": false, "singleQuote": true -} \ No newline at end of file +} diff --git a/packages/streams/README.md b/packages/streams/README.md index 40798a9df..c3f65643f 100644 --- a/packages/streams/README.md +++ b/packages/streams/README.md @@ -6,9 +6,9 @@ The code is not compiled and is not more effective, but allows using the streams ## Development -* Install dev packages with `npm i`. -* Edit files with `/src`. -* `npm link` -* `cd ../../` -* `npm link @signalk/streams` -* Restart signalk `npm start` +- Install dev packages with `npm i`. +- Edit files with `/src`. +- `npm link` +- `cd ../../` +- `npm link @signalk/streams` +- Restart signalk `npm start` diff --git a/packages/streams/autodetect.js b/packages/streams/autodetect.js index 8780352c4..3c002417e 100644 --- a/packages/streams/autodetect.js +++ b/packages/streams/autodetect.js @@ -45,12 +45,12 @@ A => actisense-serial format N2K data 1471172400153;A;2016-07-16T12:00:00.000Z,2,130306,105,255,8,00,d1,03,c9,23,fa,ff,ff */ -function DeMultiplexer (options) { +function DeMultiplexer(options) { Writable.call(this) this.toTimestamped = new ToTimestamped(this, options) this.timestampThrottle = new TimestampThrottle({ - getMilliseconds: msg => msg.timestamp + getMilliseconds: (msg) => msg.timestamp, }) this.splitter = new Splitter(this, options) this.options = options @@ -66,7 +66,7 @@ DeMultiplexer.prototype.write = function (chunk, encoding, callback) { return this.toTimestamped.write(chunk, encoding, callback) } -function Splitter (deMultiplexer, options) { +function Splitter(deMultiplexer, options) { Transform.call(this, { objectMode: true }) this.demuxEmitData = function (msg) { deMultiplexer.emit('data', msg) @@ -91,7 +91,7 @@ Splitter.prototype._transform = function (msg, encoding, _done) { let done = _done try { switch (msg.discriminator) { - case 'A': { + case 'A': { msg.fromFile = true const result = this.fromActisenseSerial.write(msg, encoding) if (!result) { @@ -130,7 +130,7 @@ Splitter.prototype.pipe = function (target) { return Transform.prototype.pipe.call(this, target) } -function ToTimestamped (deMultiplexer, options) { +function ToTimestamped(deMultiplexer, options) { Transform.call(this, { objectMode: true }) this.deMultiplexer = deMultiplexer this.options = options diff --git a/packages/streams/canboatjs.js b/packages/streams/canboatjs.js index 33209ef91..f5d806f4d 100644 --- a/packages/streams/canboatjs.js +++ b/packages/streams/canboatjs.js @@ -18,21 +18,22 @@ const Transform = require('stream').Transform const FromPgn = require('@canboat/canboatjs').FromPgn const _ = require('lodash') -function CanboatJs (options) { +function CanboatJs(options) { Transform.call(this, { - objectMode: true + objectMode: true, }) this.fromPgn = new FromPgn(options) - const createDebug = options.createDebug || require('debug') + const createDebug = options.createDebug || require('debug') const debug = createDebug('signalk:streams:nmea0183-signalk') - this.fromPgn.on('warning', (pgn, warning) => { debug(`[warning] ${pgn.pgn} ${warning}`) }) - this.fromPgn.on('error', (pgn, err) => {console.log(err)} ) + this.fromPgn.on('error', (pgn, err) => { + console.log(err) + }) this.app = options.app } @@ -42,14 +43,14 @@ require('util').inherits(CanboatJs, Transform) CanboatJs.prototype._transform = function (chunk, encoding, done) { if (_.isObject(chunk) && chunk.fromFile) { const pgnData = this.fromPgn.parse(chunk.data) - if ( pgnData ) { + if (pgnData) { pgnData.timestamp = new Date(Number(chunk.timestamp)).toISOString() this.push(pgnData) this.app.emit('N2KAnalyzerOut', pgnData) } } else { const pgnData = this.fromPgn.parse(chunk) - if ( pgnData ) { + if (pgnData) { this.push(pgnData) this.app.emit('N2KAnalyzerOut', pgnData) } diff --git a/packages/streams/execute.js b/packages/streams/execute.js index 782c083f4..1fa1d118a 100644 --- a/packages/streams/execute.js +++ b/packages/streams/execute.js @@ -39,10 +39,10 @@ const Transform = require('stream').Transform const { pgnToActisenseSerialFormat } = require('@canboat/canboatjs') -function Execute (options) { +function Execute(options) { Transform.call(this, {}) this.options = options - const createDebug = options.createDebug || require('debug') + const createDebug = options.createDebug || require('debug') this.debug = options.debug || createDebug('signalk:streams:execute') } @@ -53,7 +53,7 @@ Execute.prototype._transform = function (chunk, encoding, done) { this.analyzerProcess.stdin.write(chunk.toString()) done() } -function start (command, that) { +function start(command, that) { that.debug(`starting |${command}|`) if (process.platform === 'win32') { that.childProcess = require('child_process').spawn('cmd', ['/c', command]) @@ -76,7 +76,7 @@ function start (command, that) { that.push(data) }) - that.childProcess.on('close', code => { + that.childProcess.on('close', (code) => { const msg = `|${command}| exited with ${code}` // that.options.app.setProviderError(that.options.providerId, msg) console.error(msg) @@ -107,7 +107,9 @@ Execute.prototype.pipe = function (pipeTo) { start(this.options.command, this) const stdOutEvent = this.options.toChildProcess || 'toChildProcess' - this.debug('Using event ' + stdOutEvent + " for output to child process's stdin") + this.debug( + 'Using event ' + stdOutEvent + " for output to child process's stdin" + ) const that = this that.options.app.on(stdOutEvent, function (d) { try { @@ -118,7 +120,7 @@ Execute.prototype.pipe = function (pipeTo) { }) if (stdOutEvent === 'nmea2000out') { - that.options.app.on('nmea2000JsonOut', pgn => { + that.options.app.on('nmea2000JsonOut', (pgn) => { that.childProcess.stdin.write(pgnToActisenseSerialFormat(pgn) + '\r\n') }) that.options.app.emit('nmea2000OutAvailable') diff --git a/packages/streams/filestream.js b/packages/streams/filestream.js index 9f382874c..a57e368ab 100644 --- a/packages/streams/filestream.js +++ b/packages/streams/filestream.js @@ -37,7 +37,7 @@ const path = require('path') const PassThrough = require('stream').PassThrough const fs = require('fs') -function EndIgnoringPassThrough () { +function EndIgnoringPassThrough() { PassThrough.call(this) } @@ -72,7 +72,7 @@ FileStream.prototype.startStream = function () { } this.filestream = require('fs').createReadStream(filename) - this.filestream.on('error', err => { + this.filestream.on('error', (err) => { console.error(err.message) this.keepRunning = false }) diff --git a/packages/streams/folderstream.js b/packages/streams/folderstream.js index f62c6540c..9bc515b36 100644 --- a/packages/streams/folderstream.js +++ b/packages/streams/folderstream.js @@ -1,39 +1,36 @@ -const Transform = require("stream").Transform; -const fs = require("fs"); +const Transform = require('stream').Transform +const fs = require('fs') function FolderStreamProvider(folder) { Transform.call(this, { objectMode: false, - }); - this.folder = folder; - this.fileIndex = 0; + }) + this.folder = folder + this.fileIndex = 0 } -require("util").inherits(FolderStreamProvider, Transform); +require('util').inherits(FolderStreamProvider, Transform) -FolderStreamProvider.prototype.pipe = function(pipeTo) { - const files = fs.readdirSync(this.folder); - pipeNextFile.bind(this)(); +FolderStreamProvider.prototype.pipe = function (pipeTo) { + const files = fs.readdirSync(this.folder) + pipeNextFile.bind(this)() function pipeNextFile() { const fileStream = fs.createReadStream( - this.folder + "/" + files[this.fileIndex] - ); - fileStream.pipe( - pipeTo, - { end: false } - ); - fileStream.on("end", () => { - this.fileIndex++; + this.folder + '/' + files[this.fileIndex] + ) + fileStream.pipe(pipeTo, { end: false }) + fileStream.on('end', () => { + this.fileIndex++ if (this.fileIndex === files.length) { - pipeTo.end(); + pipeTo.end() } else { - pipeNextFile.bind(this)(); + pipeNextFile.bind(this)() } - }); + }) } - return pipeTo; -}; + return pipeTo +} -module.exports = FolderStreamProvider; +module.exports = FolderStreamProvider diff --git a/packages/streams/from_json.js b/packages/streams/from_json.js index 15ec80227..24c582c14 100644 --- a/packages/streams/from_json.js +++ b/packages/streams/from_json.js @@ -27,9 +27,9 @@ const Transform = require('stream').Transform -function FromJson () { +function FromJson() { Transform.call(this, { - objectMode: true + objectMode: true, }) } diff --git a/packages/streams/gpsd.js b/packages/streams/gpsd.js index 973b01771..b26e41bd4 100644 --- a/packages/streams/gpsd.js +++ b/packages/streams/gpsd.js @@ -33,11 +33,11 @@ const Transform = require('stream').Transform const gpsd = require('node-gpsd') -function Gpsd (options) { +function Gpsd(options) { Transform.call(this, { - objectMode: true + objectMode: true, }) - + const port = options.port || 2947 const hostname = options.hostname || options.host || 'localhost' @@ -45,7 +45,7 @@ function Gpsd (options) { options.app.setProviderStatus(options.providerId, msg) } - const createDebug = options.createDebug || require('debug') + const createDebug = options.createDebug || require('debug') this.listener = new gpsd.Listener({ port, @@ -54,14 +54,17 @@ function Gpsd (options) { info: createDebug('signalk:streams:gpsd'), warn: console.warn, error: (msg) => { - options.app.setProviderError(options.providerId, `${hostname}:${port}: ` + msg) - } + options.app.setProviderError( + options.providerId, + `${hostname}:${port}: ` + msg + ) + }, }, - parse: false + parse: false, }) setProviderStatus(`Connecting to ${hostname}:${port}`) - + this.listener.connect(function () { setProviderStatus(`Connected to ${hostname}:${port}`) }) @@ -73,7 +76,7 @@ function Gpsd (options) { this.listener.watch({ class: 'WATCH', - nmea: true + nmea: true, }) } diff --git a/packages/streams/keys-filter.js b/packages/streams/keys-filter.js index 10dfe7286..bffc7d1c8 100644 --- a/packages/streams/keys-filter.js +++ b/packages/streams/keys-filter.js @@ -2,12 +2,12 @@ const Transform = require('stream').Transform -function ToSignalK (options) { +function ToSignalK(options) { Transform.call(this, { - objectMode: true + objectMode: true, }) - const createDebug = options.createDebug || require('debug') + const createDebug = options.createDebug || require('debug') this.debug = createDebug('signalk:streams:keys-filter') this.exclude = options.excludeMatchingPaths } @@ -32,11 +32,11 @@ ToSignalK.prototype._transform = function (chunk, encoding, done) { if (Array.isArray(delta.updates)) { const updates = [] - delta.updates.forEach(update => { + delta.updates.forEach((update) => { if (Array.isArray(update.values)) { const values = [] - update.values.forEach(value => { + update.values.forEach((value) => { if (this.exclude.includes(value.path) !== true) { values.push(value) } @@ -44,7 +44,7 @@ ToSignalK.prototype._transform = function (chunk, encoding, done) { if (values.length > 0) { const upd = { - values + values, } if (update.hasOwnProperty('$source')) { diff --git a/packages/streams/liner.js b/packages/streams/liner.js index 6750f5a7f..8ad669950 100644 --- a/packages/streams/liner.js +++ b/packages/streams/liner.js @@ -28,9 +28,9 @@ const Transform = require('stream').Transform require('util').inherits(Liner, Transform) -function Liner (options) { +function Liner(options) { Transform.call(this, { - objectMode: true + objectMode: true, }) this.doPush = this.push.bind(this) this.lineSeparator = options.lineSeparator || '\n' diff --git a/packages/streams/log.js b/packages/streams/log.js index 14b6adcda..48d2cb16c 100644 --- a/packages/streams/log.js +++ b/packages/streams/log.js @@ -32,9 +32,9 @@ const Transform = require('stream').Transform const getLogger = require('./logging').getLogger -function Log (options) { +function Log(options) { Transform.call(this, { - objectMode: true + objectMode: true, }) this.logger = getLogger(options.app, options.discriminator, options.logdir) @@ -48,7 +48,7 @@ Log.prototype._transform = function (msg, encoding, done) { done() } -function pad (num) { +function pad(num) { return (num > 9 ? '' : '0') + num } diff --git a/packages/streams/logging.js b/packages/streams/logging.js index 08aa159d7..39a65b88b 100644 --- a/packages/streams/logging.js +++ b/packages/streams/logging.js @@ -25,11 +25,11 @@ const loggers = {} module.exports = { getLogger, getFullLogDir, - listLogFiles + listLogFiles, } class FileTimestampStreamWithDelete extends FileTimestampStream { - constructor(app, fullLogDir, filesToKeep, options){ + constructor(app, fullLogDir, filesToKeep, options) { super(options) this.app = app this.filesToKeep = filesToKeep @@ -41,42 +41,42 @@ class FileTimestampStreamWithDelete extends FileTimestampStream { // This method of base class is called when new file name is contemplated // So let's override it to check how many files are there and delete the oldest ones newFilename() { - if (this.prevFilename !== this.currentFilename){ // Only do that after new file created + if (this.prevFilename !== this.currentFilename) { + // Only do that after new file created this.prevFilename = this.currentFilename this.deleteOldFiles() } return super.newFilename() } - deleteOldFiles(){ + deleteOldFiles() { debug(`Checking for old log files`) listLogFiles(this.app, (err, files) => { if (err) { - console.error(err); - }else{ + console.error(err) + } else { if (files.length > this.filesToKeep) { - const sortedFiles = files.sort(); - const numToDelete = files.length - this.filesToKeep; + const sortedFiles = files.sort() + const numToDelete = files.length - this.filesToKeep debug(`Will delete ${numToDelete} files`) - for(let i = 0; i < numToDelete; i++){ + for (let i = 0; i < numToDelete; i++) { const fileName = path.join(this.fullLogDir, sortedFiles[i]) debug(`Deleting ${fileName}`) fs.unlink(fileName, (err) => { - if (err){ + if (err) { console.error(err) - } - else { + } else { debug(`${fileName} was deleted`) } - }); + }) } } } - }); + }) } } -function getLogger (app, discriminator = '', logdir) { +function getLogger(app, discriminator = '', logdir) { const fullLogdir = getFullLogDir(app, logdir) if (!loggers[fullLogdir]) { @@ -85,26 +85,28 @@ function getLogger (app, discriminator = '', logdir) { debug(`logging to ${fileName}`) let fileTimestampStream - if (app.config.settings.keepMostRecentLogsOnly){ // Delete old logs + if (app.config.settings.keepMostRecentLogsOnly) { + // Delete old logs fileTimestampStream = new FileTimestampStreamWithDelete( - app, fullLogdir, app.config.settings.logCountToKeep, - { path: fileName } - ) - }else{ // Don't delete any logs - fileTimestampStream = new FileTimestampStream( + app, + fullLogdir, + app.config.settings.logCountToKeep, { path: fileName } ) + } else { + // Don't delete any logs + fileTimestampStream = new FileTimestampStream({ path: fileName }) } loggers[fullLogdir] = fileTimestampStream } const logger = loggers[fullLogdir] - logger.on('error', err => { + logger.on('error', (err) => { console.error(`Error opening data logging file: ${err.message}`) }) - return msg => { + return (msg) => { try { logger.write( Date.now() + @@ -120,7 +122,7 @@ function getLogger (app, discriminator = '', logdir) { } } -function getFullLogDir (app, logdir) { +function getFullLogDir(app, logdir) { if (!logdir) { logdir = app.config.settings.loggingDirectory || app.config.configPath } @@ -129,10 +131,13 @@ function getFullLogDir (app, logdir) { : path.join(app.config.configPath, logdir) } -function listLogFiles (app, cb) { +function listLogFiles(app, cb) { fs.readdir(getFullLogDir(app), (err, files) => { if (!err) { - cb(undefined, files.filter(filename => filename.match(filenamePattern))) + cb( + undefined, + files.filter((filename) => filename.match(filenamePattern)) + ) } else { cb(err) } diff --git a/packages/streams/mdns-ws.js b/packages/streams/mdns-ws.js index f3cf436fc..7025c2245 100644 --- a/packages/streams/mdns-ws.js +++ b/packages/streams/mdns-ws.js @@ -18,12 +18,11 @@ const Transform = require('stream').Transform const SignalK = require('@signalk/client') - const WebSocket = require('ws') -function MdnsWs (options) { +function MdnsWs(options) { Transform.call(this, { - objectMode: true + objectMode: true, }) this.options = options this.selfHost = options.app.config.getExternalHostname() + '.' @@ -32,12 +31,12 @@ function MdnsWs (options) { this.remoteServers[this.selfHost + ':' + this.selfPort] = {} const deltaStreamBehaviour = options.subscription ? 'none' : 'all' - const createDebug = options.createDebug || require('debug') + const createDebug = options.createDebug || require('debug') this.debug = createDebug('signalk:streams:mdns-ws') this.dataDebug = createDebug('signalk:streams:mdns-ws-data') debug(`deltaStreamBehaviour:${deltaStreamBehaviour}`) - this.handleContext = () => { } + this.handleContext = () => {} if (options.selfHandling === 'manualSelf') { if (options.remoteSelf) { debug(`Using manual remote self ${options.remoteSelf}`) @@ -47,12 +46,14 @@ function MdnsWs (options) { } } } else { - console.error('Manual self handling speficied but no remoteSelf configured') + console.error( + 'Manual self handling speficied but no remoteSelf configured' + ) } } if (options.ignoreServers) { - options.ignoreServers.forEach(s => { + options.ignoreServers.forEach((s) => { this.remoteServers[s] = {} }) } @@ -64,18 +65,21 @@ function MdnsWs (options) { reconnect: true, autoConnect: false, deltaStreamBehaviour, - rejectUnauthorized: !(options.selfsignedcert === true) + rejectUnauthorized: !(options.selfsignedcert === true), }) this.connect(this.signalkClient) } else { - this.options.app.setProviderError(this.options.providerId, 'This connection is deprecated and must be deleted') + this.options.app.setProviderError( + this.options.providerId, + 'This connection is deprecated and must be deleted' + ) return } } require('util').inherits(MdnsWs, Transform) -function setProviderStatus (that, providerId, message, isError) { +function setProviderStatus(that, providerId, message, isError) { if (!isError) { that.options.app.setProviderStatus(providerId, message) console.log(message) @@ -91,30 +95,46 @@ MdnsWs.prototype.connect = function (client) { client .connect() .then(() => { - setProviderStatus(that, that.options.providerId, `ws connection connected to ${client.options.hostname}:${client.options.port}`) + setProviderStatus( + that, + that.options.providerId, + `ws connection connected to ${client.options.hostname}:${client.options.port}` + ) if (this.options.selfHandling === 'useRemoteSelf') { - client.API().then(api => api.get('/self')).then(selfFromServer => { - that.debug(`Mapping context ${selfFromServer} to self (empty context)`) - this.handleContext = (delta) => { - if (delta.context === selfFromServer) { - delete delta.context + client + .API() + .then((api) => api.get('/self')) + .then((selfFromServer) => { + that.debug( + `Mapping context ${selfFromServer} to self (empty context)` + ) + this.handleContext = (delta) => { + if (delta.context === selfFromServer) { + delete delta.context + } } - } - }).catch(err => { - console.error('Error retrieving self from remote server') - console.error(err) - }) + }) + .catch((err) => { + console.error('Error retrieving self from remote server') + console.error(err) + }) } - that.remoteServers[client.options.hostname + ':' + client.options.port] = client - if ( that.options.subscription ) { - let parsed + that.remoteServers[client.options.hostname + ':' + client.options.port] = + client + if (that.options.subscription) { + let parsed try { parsed = JSON.parse(that.options.subscription) - } catch ( ex ) { - setProviderStatus(that, that.options.providerId, `unable to parse subscription json: ${that.options.subscription}: ${ex.message}`, true) + } catch (ex) { + setProviderStatus( + that, + that.options.providerId, + `unable to parse subscription json: ${that.options.subscription}: ${ex.message}`, + true + ) } - if ( !Array.isArray(parsed) ) { - parsed = [ parsed ] + if (!Array.isArray(parsed)) { + parsed = [parsed] } parsed.forEach((sub, idx) => { that.debug('sending subscription %j', sub) @@ -122,19 +142,23 @@ MdnsWs.prototype.connect = function (client) { }) } }) - .catch(err => { + .catch((err) => { setProviderStatus(that, that.options.providerId, err.message, true) }) - + client.on('delta', (data) => { if (data && data.updates) { that.handleContext(data) - if (that.dataDebug.enabled) { that.dataDebug(JSON.stringify(data)) } + if (that.dataDebug.enabled) { + that.dataDebug(JSON.stringify(data)) + } data.updates.forEach(function (update) { - update['$source'] = `${that.options.providerId}.${client.options.hostname}:${client.options.port}` + update[ + '$source' + ] = `${that.options.providerId}.${client.options.hostname}:${client.options.port}` }) } - + that.push(data) }) } diff --git a/packages/streams/n2k-signalk.js b/packages/streams/n2k-signalk.js index 908718ad4..6a209df8e 100644 --- a/packages/streams/n2k-signalk.js +++ b/packages/streams/n2k-signalk.js @@ -20,22 +20,22 @@ const N2kMapper = require('@signalk/n2k-signalk').N2kMapper require('util').inherits(ToSignalK, Transform) -function ToSignalK (options) { +function ToSignalK(options) { Transform.call(this, { - objectMode: true + objectMode: true, }) const n2kOutEvent = 'nmea2000JsonOut' this.sourceMeta = {} this.notifications = {} this.options = options this.app = options.app - if ( options.filters && options.filtersEnabled ) { - this.filters = options.filters.filter(f => { + if (options.filters && options.filtersEnabled) { + this.filters = options.filters.filter((f) => { return (f.source && f.source.length) || (f.pgn && f.pgn.length) }) } - this.n2kMapper = new N2kMapper({...options, sendMetaData: true}) + this.n2kMapper = new N2kMapper({ ...options, sendMetaData: true }) this.n2kMapper.on('n2kOut', (pgn) => this.app.emit('nmea2000JsonOut', pgn)) @@ -43,7 +43,7 @@ function ToSignalK (options) { const existing = this.sourceMeta[n2k.src] || {} this.sourceMeta[n2k.src] = { ...existing, - ...meta + ...meta, } const delta = { context: this.app.selfContext, @@ -54,21 +54,24 @@ function ToSignalK (options) { label: this.options.providerId, type: 'NMEA2000', pgn: Number(n2k.pgn), - src: n2k.src.toString() + src: n2k.src.toString(), }, timestamp: n2k.timestamp.substring(0, 10) + 'T' + n2k.timestamp.substring(11, n2k.timestamp.length), - values: [] - } - ] + values: [], + }, + ], } - this.app.deltaCache.setSourceDelta(`${this.options.providerId}.${n2k.src}`, delta) + this.app.deltaCache.setSourceDelta( + `${this.options.providerId}.${n2k.src}`, + delta + ) }) this.n2kMapper.on('n2kSourceMetadataTimeout', (pgn, src) => { - if ( pgn == 60928 ) { + if (pgn == 60928) { console.warn(`n2k-signalk: unable to detect can name for src ${src}`) this.sourceMeta[src].unknowCanName = true } @@ -76,86 +79,110 @@ function ToSignalK (options) { this.n2kMapper.on('n2kSourceChanged', (src, from, to) => { console.warn(`n2k-signalk: address ${src} changed from ${from} ${to}`) - if ( this.sourceMeta[src] ) { + if (this.sourceMeta[src]) { delete this.sourceMeta[src] } }) - if ( this.app.isNmea2000OutAvailable ) { + if (this.app.isNmea2000OutAvailable) { this.n2kMapper.n2kOutIsAvailable(this.app, n2kOutEvent) } else { this.app.on('nmea2000OutAvailable', () => - this.n2kMapper.n2kOutIsAvailable(this.app, n2kOutEvent)) + this.n2kMapper.n2kOutIsAvailable(this.app, n2kOutEvent) + ) } } -ToSignalK.prototype.isFiltered = function(source) { - return this.filters && this.filters.find(filter => { - const sFilter = this.options.useCanName ? source.canName : source.src - return (!filter.source || filter.source.length === 0 || filter.source == sFilter) && (!filter.pgn || filter.pgn.length === 0 || filter.pgn == source.pgn) - }) +ToSignalK.prototype.isFiltered = function (source) { + return ( + this.filters && + this.filters.find((filter) => { + const sFilter = this.options.useCanName ? source.canName : source.src + return ( + (!filter.source || + filter.source.length === 0 || + filter.source == sFilter) && + (!filter.pgn || filter.pgn.length === 0 || filter.pgn == source.pgn) + ) + }) + ) } ToSignalK.prototype._transform = function (chunk, encoding, done) { try { const delta = this.n2kMapper.toDelta(chunk) - + const src = Number(chunk.src) - if ( !this.sourceMeta[src] ) { + if (!this.sourceMeta[src]) { this.sourceMeta[src] = {} - } + } - if (delta && delta.updates[0].values.length > 0 && !this.isFiltered(delta.updates[0].source) ) { - if ( !this.options.useCanName ) { + if ( + delta && + delta.updates[0].values.length > 0 && + !this.isFiltered(delta.updates[0].source) + ) { + if (!this.options.useCanName) { delete delta.updates[0].source.canName } const canName = delta.updates[0].source.canName - - if ( this.options.useCanName && !canName && !this.sourceMeta[src].unknowCanName ) { + + if ( + this.options.useCanName && + !canName && + !this.sourceMeta[src].unknowCanName + ) { done() return } - delta.updates.forEach(update => { - update.values.forEach(kv => { - if ( kv.path && kv.path.startsWith('notifications.') ) { - if ( kv.value.state === 'normal' && this.notifications[kv.path] && this.notifications[kv.path][src]) { - clearInterval(this.notifications[kv.path][src].interval) - delete this.notifications[kv.path][src] - } else if ( kv.value.state !== 'normal' ) { - if ( !this.notifications[kv.path] ) { - this.notifications[kv.path] = {} - } - if ( !this.notifications[kv.path][src] ) { - const interval = setInterval(() => { - if (Date.now() - this.notifications[kv.path][src].lastTime > 10000) { - const copy = JSON.parse(JSON.stringify(kv)) - copy.value.state = 'normal' - const normalDelta = { - context: delta.context, - updates: [ - { - source: update.source, - values: [ copy ] - } - ] - } - delete this.notifications[kv.path][src] - clearInterval(interval) - this.app.handleMessage(this.options.providerId, normalDelta) + delta.updates.forEach((update) => { + update.values.forEach((kv) => { + if (kv.path && kv.path.startsWith('notifications.')) { + if ( + kv.value.state === 'normal' && + this.notifications[kv.path] && + this.notifications[kv.path][src] + ) { + clearInterval(this.notifications[kv.path][src].interval) + delete this.notifications[kv.path][src] + } else if (kv.value.state !== 'normal') { + if (!this.notifications[kv.path]) { + this.notifications[kv.path] = {} + } + if (!this.notifications[kv.path][src]) { + const interval = setInterval(() => { + if ( + Date.now() - this.notifications[kv.path][src].lastTime > + 10000 + ) { + const copy = JSON.parse(JSON.stringify(kv)) + copy.value.state = 'normal' + const normalDelta = { + context: delta.context, + updates: [ + { + source: update.source, + values: [copy], + }, + ], } - }, 5000) - this.notifications[kv.path][src] = { - lastTime: Date.now(), - interval: interval + delete this.notifications[kv.path][src] + clearInterval(interval) + this.app.handleMessage(this.options.providerId, normalDelta) } - } else { - this.notifications[kv.path][src].lastTime = Date.now() + }, 5000) + this.notifications[kv.path][src] = { + lastTime: Date.now(), + interval: interval, } + } else { + this.notifications[kv.path][src].lastTime = Date.now() } } - }) + } + }) }) this.push(delta) } diff --git a/packages/streams/n2kAnalyzer.js b/packages/streams/n2kAnalyzer.js index e9d104b5d..14326237c 100644 --- a/packages/streams/n2kAnalyzer.js +++ b/packages/streams/n2kAnalyzer.js @@ -16,19 +16,19 @@ const Transform = require('stream').Transform -function N2KAnalyzer (options) { +function N2KAnalyzer(options) { Transform.call(this, { - objectMode: true + objectMode: true, }) if (process.platform === 'win32') { this.analyzerProcess = require('child_process').spawn('cmd', [ '/c', - 'analyzer -json -si' + 'analyzer -json -si', ]) } else { this.analyzerProcess = require('child_process').spawn('sh', [ '-c', - 'analyzer -json -si' + 'analyzer -json -si', ]) } this.analyzerProcess.stderr.on('data', function (data) { diff --git a/packages/streams/nmea0183-signalk.js b/packages/streams/nmea0183-signalk.js index 2c48ed28f..f425e064f 100644 --- a/packages/streams/nmea0183-signalk.js +++ b/packages/streams/nmea0183-signalk.js @@ -35,9 +35,9 @@ const utils = require('@signalk/nmea0183-utilities') const n2kToDelta = require('@signalk/n2k-signalk').toDelta const FromPgn = require('@canboat/canboatjs').FromPgn -function Nmea0183ToSignalK (options) { +function Nmea0183ToSignalK(options) { Transform.call(this, { - objectMode: true + objectMode: true, }) this.parser = new Parser(options) @@ -49,7 +49,7 @@ function Nmea0183ToSignalK (options) { // Prepare a list of events to send for each sentence received this.sentenceEvents = options.suppress0183event ? [] : ['nmea0183'] - this.appendChecksum = options.appendChecksum; + this.appendChecksum = options.appendChecksum if (options.sentenceEvent) { if (Array.isArray(options.sentenceEvent)) { @@ -78,19 +78,19 @@ Nmea0183ToSignalK.prototype._transform = function (chunk, encoding, done) { try { if (sentence !== undefined) { if (this.appendChecksum) { - sentence = utils.appendChecksum(sentence); + sentence = utils.appendChecksum(sentence) } // Send 'sentences' event to the app for each sentence - this.sentenceEvents.forEach(eventName => { + this.sentenceEvents.forEach((eventName) => { this.app.emit(eventName, sentence) this.app.signalk.emit(eventName, sentence) }) let delta = null - if ( this.n2kParser.isN2KOver0183(sentence) ) { + if (this.n2kParser.isN2KOver0183(sentence)) { const pgn = this.n2kParser.parseN2KOver0183(sentence) - if ( pgn ) { - delta = n2kToDelta(pgn, this.state, {sendMetaData: true}) + if (pgn) { + delta = n2kToDelta(pgn, this.state, { sendMetaData: true }) } } else { delta = this.parser.parse(sentence) @@ -98,7 +98,7 @@ Nmea0183ToSignalK.prototype._transform = function (chunk, encoding, done) { if (delta !== null) { if (timestamp !== null) { - delta.updates.forEach(update => { + delta.updates.forEach((update) => { update.timestamp = timestamp }) } diff --git a/packages/streams/nullprovider.js b/packages/streams/nullprovider.js index 0fc702285..e73c364a2 100644 --- a/packages/streams/nullprovider.js +++ b/packages/streams/nullprovider.js @@ -20,9 +20,9 @@ const Transform = require('stream').Transform -function NullProvider (options) { +function NullProvider(options) { Transform.call(this, { - objectMode: true + objectMode: true, }) } diff --git a/packages/streams/replacer.js b/packages/streams/replacer.js index 6ee141791..8dcae5d09 100644 --- a/packages/streams/replacer.js +++ b/packages/streams/replacer.js @@ -14,21 +14,19 @@ * limitations under the License. */ - const Transform = require('stream').Transform require('util').inherits(Replacer, Transform) -function Replacer (options) { +function Replacer(options) { Transform.call(this, { - objectMode: true + objectMode: true, }) this.doPush = this.push.bind(this) this.regexp = new RegExp(options.regexp, 'gu') this.template = options.template } - Replacer.prototype._transform = function (chunk, encoding, done) { this.doPush(chunk.toString().replace(this.regexp, this.template)) done() diff --git a/packages/streams/s3.js b/packages/streams/s3.js index 2cf10aea0..ef76270b2 100644 --- a/packages/streams/s3.js +++ b/packages/streams/s3.js @@ -25,9 +25,9 @@ var Transform = require('stream').Transform */ const AWS = require('aws-sdk') -function S3Provider ({ bucket, prefix }) { +function S3Provider({ bucket, prefix }) { Transform.call(this, { - objectMode: false + objectMode: false, }) this.Bucket = bucket this.Prefix = prefix @@ -41,12 +41,12 @@ S3Provider.prototype.pipe = function (pipeTo) { const s3 = new AWS.S3() const params = { Bucket: this.Bucket, - Prefix: this.Prefix + Prefix: this.Prefix, } console.log('listobjects') s3.listObjects(params) .promise() - .then(data => { + .then((data) => { // console.log(data) const jobs = data.Contents.map( (item, i) => @@ -55,18 +55,15 @@ S3Provider.prototype.pipe = function (pipeTo) { console.log('Starting key ' + item.Key) const objectParams = { Bucket: params.Bucket, - Key: item.Key + Key: item.Key, } const request = s3.getObject(objectParams) - request.on('error', err => { + request.on('error', (err) => { console.log(err) }) const stream = request.createReadStream() stream.on('end', resolve) - stream.pipe( - pipeTo, - { end: i === data.Contents.length-1 } - ) + stream.pipe(pipeTo, { end: i === data.Contents.length - 1 }) }) } ) @@ -74,14 +71,14 @@ S3Provider.prototype.pipe = function (pipeTo) { let i = 0 function startNext() { if (i < jobs.length) { - jobs[i++]().then(startNext); + jobs[i++]().then(startNext) } else { doEnd() } } - startNext(); + startNext() }) - .catch(error => { + .catch((error) => { console.error(error) }) return pipeTo diff --git a/packages/streams/serialport.js b/packages/streams/serialport.js index f9584c29f..1c80b92ef 100644 --- a/packages/streams/serialport.js +++ b/packages/streams/serialport.js @@ -64,7 +64,7 @@ const SerialPort = require('serialport') const isArray = require('lodash').isArray const isBuffer = require('lodash').isBuffer -function SerialStream (options) { +function SerialStream(options) { if (!(this instanceof SerialStream)) { return new SerialStream(options) } @@ -105,7 +105,7 @@ SerialStream.prototype.start = function () { } this.serial = new SerialPort(this.options.device, { - baudRate: this.options.baudrate + baudRate: this.options.baudrate, }) this.serial.on( @@ -148,22 +148,22 @@ SerialStream.prototype.start = function () { let pendingWrites = 0 const stdOutEvent = this.options.toStdout if (stdOutEvent) { - (isArray(stdOutEvent) ? stdOutEvent : [stdOutEvent]).forEach(event => { + ;(isArray(stdOutEvent) ? stdOutEvent : [stdOutEvent]).forEach((event) => { const onDrain = () => { pendingWrites-- } - that.options.app.on(event, d => { + that.options.app.on(event, (d) => { if (pendingWrites > that.maxPendingWrites) { that.debug('Buffer overflow, not writing:' + d) return } that.debug('Writing:' + d) - if ( isBuffer(d) ) { + if (isBuffer(d)) { that.serial.write(d) } else { that.serial.write(d + '\r\n') - } + } pendingWrites++ that.serial.drain(onDrain) }) diff --git a/packages/streams/simple.js b/packages/streams/simple.js index 1bdb51b74..fedd7c518 100644 --- a/packages/streams/simple.js +++ b/packages/streams/simple.js @@ -23,7 +23,7 @@ const Ydwg02 = require('@canboat/canboatjs').Ydwg02 const gpsd = require('./gpsd') const pigpioSeatalk = require('./pigpio-seatalk') -function Simple (options) { +function Simple(options) { Transform.call(this, { objectMode: true }) const { emitPropertyValue, onPropertyValues, createDebug } = options @@ -32,7 +32,7 @@ function Simple (options) { ...options.subOptions, emitPropertyValue, onPropertyValues, - createDebug + createDebug, } options.subOptions.providerId = options.providerId @@ -61,14 +61,18 @@ function Simple (options) { options.subOptions.type === 'canbus-canboatjs' ) { mappingType = 'NMEA2000JS' - } else if (options.subOptions.type === 'ikonvert-canboatjs' || - options.subOptions.type === 'navlink2-tcp-canboatjs' ) { + } else if ( + options.subOptions.type === 'ikonvert-canboatjs' || + options.subOptions.type === 'navlink2-tcp-canboatjs' + ) { mappingType = 'NMEA2000IK' - } else if (options.subOptions.type === 'ydwg02-canboatjs' || - options.subOptions.type === 'ydwg02-udp-canboatjs' || - options.subOptions.type === 'ydwg02-usb-canboatjs') { + } else if ( + options.subOptions.type === 'ydwg02-canboatjs' || + options.subOptions.type === 'ydwg02-udp-canboatjs' || + options.subOptions.type === 'ydwg02-usb-canboatjs' + ) { mappingType = 'NMEA2000YD' - } + } } const pipeline = [].concat( @@ -103,8 +107,8 @@ const getLogger = (app, logging, discriminator) => ? [ new Log({ app: app, - discriminator - }) + discriminator, + }), ] : [] @@ -115,73 +119,77 @@ const discriminatorByDataType = { NMEA2000: 'A', NMEA0183: 'N', SignalK: 'I', - Seatalk: 'N' + Seatalk: 'N', } const dataTypeMapping = { - SignalK: options => + SignalK: (options) => options.subOptions.type !== 'wss' && options.subOptions.type !== 'ws' ? [new FromJson(options.subOptions)] : [], - Seatalk: options => [new nmea0183_signalk({...options.subOptions, validateChecksum: false})], - NMEA0183: options => { + Seatalk: (options) => [ + new nmea0183_signalk({ ...options.subOptions, validateChecksum: false }), + ], + NMEA0183: (options) => { const result = [new nmea0183_signalk(options.subOptions)] if (options.type === 'FileStream') { result.unshift( new Throttle({ - rate: options.subOptions.throttleRate || 1000 + rate: options.subOptions.throttleRate || 1000, }) ) } return result }, - NMEA2000: options => { + NMEA2000: (options) => { const result = [new N2kAnalyzer(options.subOptions)] if (options.type === 'FileStream') { result.push(new TimestampThrottle()) } return result.concat([new N2kToSignalK(options.subOptions)]) }, - NMEA2000JS: options => { + NMEA2000JS: (options) => { const result = [new CanboatJs(options.subOptions)] if (options.type === 'FileStream') { result.push(new TimestampThrottle()) } return result.concat([new N2kToSignalK(options.subOptions)]) }, - NMEA2000IK: options => { + NMEA2000IK: (options) => { const result = [new CanboatJs(options.subOptions)] if (options.type === 'FileStream') { result.push( new TimestampThrottle({ - getMilliseconds: msg => { + getMilliseconds: (msg) => { return msg.timer * 1000 - } + }, }) ) } // else { let subOptions - if ( options.subOptions.type === 'navlink2-tcp-canboatjs' ) - { - subOptions = {...options.subOptions, tcp: true} - } - else - { + if (options.subOptions.type === 'navlink2-tcp-canboatjs') { + subOptions = { ...options.subOptions, tcp: true } + } else { subOptions = options.subOptions } result.unshift(new iKonvert(subOptions)) } return result.concat([new N2kToSignalK(options.subOptions)]) }, - NMEA2000YD: options => { - const result = [new Ydwg02(options.subOptions, options.subOptions.type === 'ydwg02-usb-canboatjs' ? 'usb' : 'network')] + NMEA2000YD: (options) => { + const result = [ + new Ydwg02( + options.subOptions, + options.subOptions.type === 'ydwg02-usb-canboatjs' ? 'usb' : 'network' + ), + ] if (options.type === 'FileStream') { result.push(new TimestampThrottle()) } return result.concat([new N2kToSignalK(options.subOptions)]) }, - Multiplexed: options => [new MultiplexedLog(options.subOptions)] + Multiplexed: (options) => [new MultiplexedLog(options.subOptions)], } const pipeStartByType = { @@ -190,28 +198,28 @@ const pipeStartByType = { Execute: executeInput, FileStream: fileInput, SignalK: signalKInput, - Seatalk: seatalkInput + Seatalk: seatalkInput, } -function nmea2000input (subOptions, logging) { +function nmea2000input(subOptions, logging) { if (subOptions.type === 'ngt-1-canboatjs') { const actisenseSerial = require('./actisense-serial') - if ( ! actisenseSerial ) { + if (!actisenseSerial) { throw new Error('unable to load actisense serial') } return [ new actisenseSerial({ ...subOptions, outEvent: 'nmea2000out', - plainText: logging - }) + plainText: logging, + }), ] } else if (subOptions.type === 'canbus-canboatjs') { return [ new require('./canbus')({ ...subOptions, canDevice: subOptions.interface, - }) + }), ] } else if (subOptions.type === 'ikonvert-canboatjs') { const serialport = require('./serialport') @@ -219,22 +227,28 @@ function nmea2000input (subOptions, logging) { new serialport({ ...subOptions, baudrate: 230400, - toStdout: 'ikonvertOut' - }) + toStdout: 'ikonvertOut', + }), ] } else if (subOptions.type === 'ydwg02-canboatjs') { - return [new Tcp({ - ...subOptions, - outEvent: 'ydwg02-out' - }), new Liner(subOptions)] + return [ + new Tcp({ + ...subOptions, + outEvent: 'ydwg02-out', + }), + new Liner(subOptions), + ] } else if (subOptions.type === 'ydwg02-udp-canboatjs') { return [new Udp(subOptions), new Liner(subOptions)] } else if (subOptions.type === 'navlink2-tcp-canboatjs') { - return [new Tcp({ - ...subOptions, - outEvent: 'navlink2-out' - }), new Liner(subOptions)] - } else if (subOptions.type === 'navlink2-udp-canboatjs' ) { + return [ + new Tcp({ + ...subOptions, + outEvent: 'navlink2-out', + }), + new Liner(subOptions), + ] + } else if (subOptions.type === 'navlink2-udp-canboatjs') { return [new Udp(subOptions), new Liner(subOptions)] } else if (subOptions.type === 'ydwg02-usb-canboatjs') { const serialport = require('./serialport') @@ -242,8 +256,8 @@ function nmea2000input (subOptions, logging) { new serialport({ ...subOptions, baudrate: 38400, - toStdout: 'ydwg02-out' - }) + toStdout: 'ydwg02-out', + }), ] } else { let command @@ -264,14 +278,14 @@ function nmea2000input (subOptions, logging) { command: command, toChildProcess: toChildProcess, app: subOptions.app, - providerId: subOptions.providerId + providerId: subOptions.providerId, }), - new Liner(subOptions) + new Liner(subOptions), ] } } -function nmea0183input (subOptions) { +function nmea0183input(subOptions) { let pipePart if (subOptions.type === 'tcp') { pipePart = [new Tcp(subOptions), new Liner(subOptions)] @@ -288,19 +302,23 @@ function nmea0183input (subOptions) { if (pipePart) { if (subOptions.removeNulls) { - pipePart.push(new Replacer({ - regexp: '\u0000', - template: '' - })) + pipePart.push( + new Replacer({ + regexp: '\u0000', + template: '', + }) + ) } if (subOptions.ignoredSentences) { console.log(subOptions.ignoredSentences) - subOptions.ignoredSentences.forEach(sentence => { + subOptions.ignoredSentences.forEach((sentence) => { if (sentence.length > 0) { - pipePart.push(new Replacer({ - regexp: `^...${sentence}.*`, - template: '' - })) + pipePart.push( + new Replacer({ + regexp: `^...${sentence}.*`, + template: '', + }) + ) } }) } @@ -310,17 +328,17 @@ function nmea0183input (subOptions) { } } -function executeInput (subOptions) { +function executeInput(subOptions) { return [new execute(subOptions), new Liner(subOptions)] } -function fileInput (subOptions) { +function fileInput(subOptions) { const result = [new FileStream(subOptions)] result.push(new Liner(subOptions)) return result } -function signalKInput (subOptions) { +function signalKInput(subOptions) { if (subOptions.type === 'ws' || subOptions.type === 'wss') { const mdns_ws = require('./mdns-ws') return [new mdns_ws(subOptions)] diff --git a/packages/streams/splitting-liner.js b/packages/streams/splitting-liner.js index f05a61922..4100dae8e 100644 --- a/packages/streams/splitting-liner.js +++ b/packages/streams/splitting-liner.js @@ -28,9 +28,9 @@ const Transform = require('stream').Transform require('util').inherits(SplittingLiner, Transform) -function SplittingLiner (options) { +function SplittingLiner(options) { Transform.call(this, { - objectMode: true + objectMode: true, }) this.doPush = this.push.bind(this) this.lineSeparator = options.lineSeparator || '\n' diff --git a/packages/streams/tcp.js b/packages/streams/tcp.js index 21934848e..a865df1ac 100644 --- a/packages/streams/tcp.js +++ b/packages/streams/tcp.js @@ -32,21 +32,24 @@ const net = require('net') const Transform = require('stream').Transform const isArray = require('lodash').isArray -function TcpStream (options) { +function TcpStream(options) { Transform.call(this, options) this.options = options - this.noDataReceivedTimeout = Number.parseInt((this.options.noDataReceivedTimeout + '').trim()) * 1000 + this.noDataReceivedTimeout = + Number.parseInt((this.options.noDataReceivedTimeout + '').trim()) * 1000 this.debug = (options.createDebug || require('debug'))('signalk:streams:tcp') - this.debugData = (options.createDebug || require('debug'))('signalk:streams:tcp-data') + this.debugData = (options.createDebug || require('debug'))( + 'signalk:streams:tcp-data' + ) } require('util').inherits(TcpStream, Transform) TcpStream.prototype.pipe = function (pipeTo) { const that = this - if ( this.options.outEvent ) { + if (this.options.outEvent) { that.options.app.on(that.options.outEvent, function (d) { - if ( that.tcpStream ) { + if (that.tcpStream) { that.debug('sending %s', d) that.tcpStream.write(d) } @@ -55,20 +58,22 @@ TcpStream.prototype.pipe = function (pipeTo) { const stdOutEvent = this.options.toStdout if (stdOutEvent) { - const that = this; //semicolon required here - (isArray(stdOutEvent) ? stdOutEvent : [stdOutEvent]).forEach(stdEvent => { - that.options.app.on(stdEvent, function (d) { - if (that.tcpStream) { - that.tcpStream.write(d + '\r\n') - that. debug('event %s sending %s', stdEvent, d) - } - }) - }) + const that = this //semicolon required here + ;(isArray(stdOutEvent) ? stdOutEvent : [stdOutEvent]).forEach( + (stdEvent) => { + that.options.app.on(stdEvent, function (d) { + if (that.tcpStream) { + that.tcpStream.write(d + '\r\n') + that.debug('event %s sending %s', stdEvent, d) + } + }) + } + ) } const re = require('reconnect-core')(function () { return net.connect.apply(null, arguments) - })({ maxDelay: 5 * 1000 }, tcpStream => { + })({ maxDelay: 5 * 1000 }, (tcpStream) => { if (!isNaN(this.noDataReceivedTimeout)) { tcpStream.setTimeout(this.noDataReceivedTimeout) that.debug( @@ -81,31 +86,29 @@ TcpStream.prototype.pipe = function (pipeTo) { tcpStream.end() }) } - tcpStream.on('data', data => { + tcpStream.on('data', (data) => { if (that.debugData.enabled) { that.debugData(data.toString()) } this.write(data) }) }) - .on('connect', con => { + .on('connect', (con) => { this.tcpStream = con const msg = `Connected to ${this.options.host} ${this.options.port}` this.options.app.setProviderStatus(this.options.providerId, msg) that.debug(msg) }) .on('reconnect', (n, delay) => { - const msg = `Reconnect ${this.options.host} ${ - this.options.port - } retry ${n} delay ${delay}` + const msg = `Reconnect ${this.options.host} ${this.options.port} retry ${n} delay ${delay}` this.options.app.setProviderError(this.options.providerId, msg) that.debug(msg) }) - .on('disconnect', err => { + .on('disconnect', (err) => { delete this.tcpStream that.debug(`Disconnected ${this.options.host} ${this.options.port}`) }) - .on('error', err => { + .on('error', (err) => { this.options.app.setProviderError(this.options.providerId, err.message) console.error('TcpProvider:' + err.message) }) diff --git a/packages/streams/tcpserver.js b/packages/streams/tcpserver.js index 4916ebfb7..c48e749c4 100644 --- a/packages/streams/tcpserver.js +++ b/packages/streams/tcpserver.js @@ -21,7 +21,7 @@ const Transform = require('stream').Transform -function TcpServer (options) { +function TcpServer(options) { Transform.call(this) this.options = options } @@ -29,7 +29,7 @@ function TcpServer (options) { require('util').inherits(TcpServer, Transform) TcpServer.prototype.pipe = function (pipeTo) { - this.options.app.on('tcpserver0183data', d => this.write(d)) + this.options.app.on('tcpserver0183data', (d) => this.write(d)) Transform.prototype.pipe.call(this, pipeTo) } diff --git a/packages/streams/timestamp-throttle.js b/packages/streams/timestamp-throttle.js index ce39bd6a4..3ee8d8e4c 100644 --- a/packages/streams/timestamp-throttle.js +++ b/packages/streams/timestamp-throttle.js @@ -23,9 +23,9 @@ so that throughput rate is real time. Aimed at canboat analyzer output rate control */ -function TimestampThrottle (options) { +function TimestampThrottle(options) { Transform.call(this, { - objectMode: true + objectMode: true, }) this.lastMsgMillis = new Date().getTime() this.getMilliseconds = @@ -55,7 +55,7 @@ TimestampThrottle.prototype._transform = function (msg, encoding, done) { } } -function getMilliseconds (msg) { +function getMilliseconds(msg) { // 2014-08-15-16:00:00.083 return moment(msg.timestamp, 'YYYY-MM-DD-HH:mm:ss.SSS').valueOf() } diff --git a/packages/streams/udp.js b/packages/streams/udp.js index f8fc26026..72ead3ddd 100644 --- a/packages/streams/udp.js +++ b/packages/streams/udp.js @@ -35,9 +35,9 @@ const Transform = require('stream').Transform -function Udp (options) { +function Udp(options) { Transform.call(this, { - objectMode: false + objectMode: false, }) this.options = options this.debug = (options.createDebug || require('debug'))('signalk:streams:udp') From 038b09db7d6f1f1d2353fc28542676446f00959b Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 23 Jan 2022 22:18:38 +0200 Subject: [PATCH 205/410] chore: use npm workspace --- package.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/package.json b/package.json index 72b553c6e..bf16fe4d1 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,12 @@ "engines": { "node": ">=10" }, + "workspaces":[ + "packages/server-admin-ui-dependencies", + "packages/server-admin-ui", + "packages/streams", + "packages/server-api" + ], "dependencies": { "@signalk/server-admin-ui": "1.39.1", "@signalk/server-api": "1.39.x", From ee52722cd390ccd9db259aa1efd5cc9045107b6a Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 23 Jan 2022 22:20:16 +0200 Subject: [PATCH 206/410] fix: add nmea0183 and n2k explicitly Use in interfaces/playground, I have no idea how this has worked so far. By hoisting all the deps to the server's node_modules? Better to depend on these explicitly. --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index bf16fe4d1..9b7ece064 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,8 @@ "packages/server-api" ], "dependencies": { + "@signalk/n2k-signalk": "^2.0.0", + "@signalk/nmea0183-signalk": "^3.0.0", "@signalk/server-admin-ui": "1.39.1", "@signalk/server-api": "1.39.x", "@signalk/signalk-schema": "1.5.1", From 684c1bc83aeebd3691c790782b24686849917238 Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 23 Jan 2022 22:42:35 +0200 Subject: [PATCH 207/410] fix: use ~ instead of node_modules With npm workspaces bootstrap gets installed under server root, not here, but apparently you can reference it via tilde operator. --- packages/server-admin-ui/scss/style.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server-admin-ui/scss/style.scss b/packages/server-admin-ui/scss/style.scss index 571b6ea7f..d1f706553 100644 --- a/packages/server-admin-ui/scss/style.scss +++ b/packages/server-admin-ui/scss/style.scss @@ -10,7 +10,7 @@ @import "bootstrap-variables"; // Import Bootstrap source files -@import "node_modules/bootstrap/scss/bootstrap"; +@import "~bootstrap/scss/bootstrap"; // Override core variables @import "core-variables"; From bff5eebb0059e697c937e5959742639f51bcc1a8 Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 23 Jan 2022 22:43:45 +0200 Subject: [PATCH 208/410] chore: add CI builds for packages --- .github/workflows/test.yml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2d6c343cc..bc11eab4b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,7 +9,7 @@ jobs: strategy: matrix: - node-version: [10.x, 16.x] + node-version: [16.x] steps: - uses: actions/checkout@v2 @@ -18,6 +18,22 @@ jobs: with: node-version: ${{ matrix.node-version }} - run: npm install + + - name: streams + working-directory: ./packages/streams + run: | + npm test + + - name: server-api + working-directory: ./packages/server-api + run: | + npm run build + + - name: server-admin-ui + working-directory: ./packages/server-admin-ui + run: | + npm run build + - run: npm test env: CI: true \ No newline at end of file From d84e90bbee7c190b705c6e8a2bc815762cf0c6b8 Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 23 Jan 2022 22:48:21 +0200 Subject: [PATCH 209/410] style: ci to check formatting in streams and admin ui --- .github/workflows/test.yml | 3 ++- packages/server-admin-ui/package.json | 1 + packages/streams/package.json | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bc11eab4b..8490309cf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: - name: streams working-directory: ./packages/streams run: | - npm test + npm run ci - name: server-api working-directory: ./packages/server-api @@ -32,6 +32,7 @@ jobs: - name: server-admin-ui working-directory: ./packages/server-admin-ui run: | + npm run ci npm run build - run: npm test diff --git a/packages/server-admin-ui/package.json b/packages/server-admin-ui/package.json index ef5b35529..647b46c52 100644 --- a/packages/server-admin-ui/package.json +++ b/packages/server-admin-ui/package.json @@ -58,6 +58,7 @@ "watch": "webpack --watch --mode development", "build": "webpack --mode=production", "format": "prettier --write src/", + "ci": "prettier --check src/", "clean": "rimraf ./public", "bundle-analyzer": "webpack-bundle-analyzer --port 4200 public/stats.json" } diff --git a/packages/streams/package.json b/packages/streams/package.json index 3fdeec456..c9df896aa 100644 --- a/packages/streams/package.json +++ b/packages/streams/package.json @@ -4,7 +4,7 @@ "description": "Utilities for handling streams of Signal K data", "main": "index.js", "scripts": { - "test": "prettier --check .", + "ci": "prettier --check .", "format": "prettier --write ." }, "repository": { From cfcad1dc219934c369e4686b70e41469ce951299 Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Mon, 24 Jan 2022 21:33:32 +0200 Subject: [PATCH 210/410] chore: @signalk/streams@2.0.2 --- packages/streams/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/streams/package.json b/packages/streams/package.json index c9df896aa..9596ae501 100644 --- a/packages/streams/package.json +++ b/packages/streams/package.json @@ -1,6 +1,6 @@ { "name": "@signalk/streams", - "version": "2.0.1", + "version": "2.0.2", "description": "Utilities for handling streams of Signal K data", "main": "index.js", "scripts": { From 157958a09b23b6fd89da1c55b03237d3670dbe58 Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Mon, 24 Jan 2022 21:46:40 +0200 Subject: [PATCH 211/410] 1.41.0 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 9b7ece064..c1167f44f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "signalk-server", - "version": "1.41.0-beta.4", + "version": "1.41.0", "description": "An implementation of a [Signal K](http://signalk.org) server for boats.", "main": "index.js", "scripts": { @@ -67,7 +67,7 @@ "engines": { "node": ">=10" }, - "workspaces":[ + "workspaces": [ "packages/server-admin-ui-dependencies", "packages/server-admin-ui", "packages/streams", From 7aeb9c2fe0af93a24d7241975f7e7edb3d98ff0f Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Mon, 24 Jan 2022 22:19:14 +0200 Subject: [PATCH 212/410] chore: add build-docker branch to debug docker builds --- .github/workflows/build-docker.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 9d1d96cd9..fa7c316fb 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -5,6 +5,7 @@ on: branches: - master - latest + - "build-docker" tags: - '*' From 991220022384fa90777085b191bf4f90e33d8db6 Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Tue, 25 Jan 2022 19:57:14 +0200 Subject: [PATCH 213/410] fix: docker build (#1377) Now that we are using npm workspaces npm install does not install the packages from npmjs.org, but relies on linking. Any packages that need explicit building need to be built. I did not check if adding a postinstall script would have worked, but explicit building seems to fix things. This commit adds also express as a explicit devDependency. The build step needs this explicitly. --- Dockerfile | 5 +++++ packages/server-api/package.json | 2 ++ 2 files changed, 7 insertions(+) diff --git a/Dockerfile b/Dockerfile index 0422ce694..96c493872 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,6 +12,11 @@ RUN mkdir -p /home/node/signalk WORKDIR /home/node/signalk COPY --chown=node:node . . + +WORKDIR /home/node/signalk/packages/server-api +RUN npm install && npm run build + +WORKDIR /home/node/signalk RUN npm install RUN npm run build RUN mkdir -p /home/node/.signalk diff --git a/packages/server-api/package.json b/packages/server-api/package.json index 0526be60b..89970ba90 100644 --- a/packages/server-api/package.json +++ b/packages/server-api/package.json @@ -18,7 +18,9 @@ "author": "teppo.kurki@iki.fi", "license": "Apache-2.0", "devDependencies": { + "express": "^4.10.4", "@types/chai": "^4.2.15", + "@types/express": "^4.17.1", "@types/mocha": "^8.2.0", "chai": "^4.3.0", "mocha": "^8.3.0", From bcc80cafbbd2fb838d8c8854ff63d5190adc003e Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Tue, 25 Jan 2022 20:55:05 +0200 Subject: [PATCH 214/410] fix: nmea0183-signalk logging (#1378) Use debug instead of console.error changed in 55fe8aa553684f83bf429da37992461793a5f5f2 otherwise people get too verbose logging under normal conditions with some unrecognised sentences. --- packages/streams/nmea0183-signalk.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/streams/nmea0183-signalk.js b/packages/streams/nmea0183-signalk.js index f425e064f..1afcc5db8 100644 --- a/packages/streams/nmea0183-signalk.js +++ b/packages/streams/nmea0183-signalk.js @@ -39,6 +39,8 @@ function Nmea0183ToSignalK(options) { Transform.call(this, { objectMode: true, }) + this.debug = (options.createDebug || require('debug'))('signalk:streams:nmea0183-signalk') + this.parser = new Parser(options) this.n2kParser = new FromPgn(options) @@ -107,7 +109,7 @@ Nmea0183ToSignalK.prototype._transform = function (chunk, encoding, done) { } } } catch (e) { - console.error(e) + this.debug(e) } done() From c2c3e70e90d2d319856187c6f7d45628930ffcd9 Mon Sep 17 00:00:00 2001 From: MatsA Date: Wed, 26 Jan 2022 09:51:03 +0100 Subject: [PATCH 215/410] doc: update RPi instructions for Node.js 16 / 1.41 (#1379) --- raspberry_pi_installation.md | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/raspberry_pi_installation.md b/raspberry_pi_installation.md index 1663a3fca..0f14b7aed 100644 --- a/raspberry_pi_installation.md +++ b/raspberry_pi_installation.md @@ -1,12 +1,12 @@ # Getting Started -If you already have a Raspberry Pi up and running go direct to [Installing Signal K.](https://github.com/SignalK/signalk-server/blob/master/raspberry_pi_installation.md#installing-signal-k) +If you are updating the Signal K server, especially moving from a version <= 1.40.0, [please check here.](https://github.com/SignalK/signalk-server/blob/master/raspberry_pi_installation.md#update-signal-k-node-server) -Instructions to install the operating system, Raspberry Pi OS, [is found here.](https://www.raspberrypi.org/documentation/computers/getting-started.html#setting-up-your-raspberry-pi) +Instructions to install the operating system, Raspberry Pi OS, [is found here.](https://www.raspberrypi.org/documentation/computers/getting-started.html#setting-up-your-raspberry-pi) Please use Buster OS! Bullseye, is not fully tested. -If you are familiar with a "headless install", with Raspberry Pi OS Lite, it's also possible since the GUI for Signal K is browser based. +If you are familiar with a "headless install" using Raspberry Pi OS Lite it's also possible since the GUI for Signal K is browser based. -After everything has been configured you should be presented with the RPi Desktop up and running, just waiting for you to install Signal K. +After everything has been configured, using the GUI install, you should be presented with the RPi Desktop up and running, just waiting for you to install Signal K. # Installing Signal K @@ -24,12 +24,19 @@ Raspbian, the Linux distribution for RPi, is based on Debian, which has a powerf Now, install node and npm - $ sudo apt install nodejs npm + $ curl -sL https://deb.nodesource.com/setup_16.x | sudo -E bash - + $ sudo apt-get install -y nodejs We want to make sure that we're using the latest version of npm: $ sudo npm install -g npm@latest +Use the following command to check the install + + node -v && npm -v + +which will report, something like, `v16.13.2, 8.3.1` which are the versions of "node" and "npm" + Finally we need to install a Bonjour (mDNS) service for Linux called Avahi, which allows Apps and other network devices to Discover the Signal K server. To do this we will use "apt" again ... $ sudo apt install libnss-mdns avahi-utils libavahi-compat-libdnssd-dev @@ -284,9 +291,10 @@ Click on that row and You will open next window Click on update and the installation will start -If below version 1.8.0 use this command instead +**Please note !** - $ sudo npm install -g signalk-server +Starting with Signal K server version 1.41.0 the recommended Node.js version is 16. [16 is the active LTS](https://nodejs.org/en/about/releases/) (Long Term Support) version in Jan 2022, with End of Life set at 2024-04-30. Node 10 is past its end of life and won't receive any (security) updates. +So if you are updating from a Signal K version <= V 1.40.0 [check out the Wiki](https://github.com/SignalK/signalk-server/wiki/Updating-to-Node.js-16) on how to. ![server_during_update](https://user-images.githubusercontent.com/16189982/51401178-71a9e400-1b4a-11e9-86b9-1148442ba59c.png) From 3cd86a4a1f4d9620d67ec0f00ca5c9808e7fd77f Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Thu, 27 Jan 2022 12:03:42 +1030 Subject: [PATCH 216/410] fix type definitions --- src/api/resources/types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/resources/types.ts b/src/api/resources/types.ts index b995b0adb..2a6b0d1d7 100644 --- a/src/api/resources/types.ts +++ b/src/api/resources/types.ts @@ -68,7 +68,7 @@ interface Polygon { type: 'Feature' geometry: { type: 'Polygon' - coords: GeoJsonPolygon + coordinates: GeoJsonPolygon } properties?: object id?: string @@ -78,7 +78,7 @@ interface MultiPolygon { type: 'Feature' geometry: { type: 'MultiPolygon' - coords: GeoJsonMultiPolygon + coordinates: GeoJsonMultiPolygon } properties?: object id?: string From f9f5c14e07f7f62bce53eb7b12bd664a0f482e2b Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Thu, 27 Jan 2022 12:04:20 +1030 Subject: [PATCH 217/410] update debug use --- src/api/course/index.ts | 6 ++++-- src/api/resources/index.ts | 9 ++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 739bc7b5d..7958bfb78 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,4 +1,8 @@ import Debug from 'debug' +const debug = Debug('signalk:courseApi') +// import { createDebug } from './debug' +// const debug = createDebug('signalk:courseApi') + import { Application, Request, Response } from 'express' import _ from 'lodash' import path from 'path' @@ -9,8 +13,6 @@ import { Store } from '../../serverstate/store' import { Route } from '../resources/types' import { Responses } from '../' -const debug = Debug('signalk:courseApi') - const SIGNALK_API_PATH = `/signalk/v1/api` const COURSE_API_PATH = `${SIGNALK_API_PATH}/vessels/self/navigation/course` diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 5d7757bd4..464c949f0 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -1,9 +1,14 @@ +import Debug from 'debug' +const debug = Debug('signalk:resourcesApi') +// import { createDebug } from './debug' +// const debug = createDebug('signalk:resourcesApi') + import { ResourceProvider, ResourceProviderMethods, SignalKResourceType } from '@signalk/server-api' -import Debug from 'debug' + import { Application, NextFunction, Request, Response } from 'express' import { v4 as uuidv4 } from 'uuid' import { WithSecurityStrategy, WithSignalK } from '../../app' @@ -12,8 +17,6 @@ import { Responses } from '../' import { buildResource } from './resources' import { validate } from './validate' -const debug = Debug('signalk:resourcesApi') - const SIGNALK_API_PATH = `/signalk/v1/api` const UUID_PREFIX = 'urn:mrn:signalk:uuid:' From ee0d8ce7efd9b5fed697654830b879bdd46f9273 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 7 Nov 2021 15:28:54 +1030 Subject: [PATCH 218/410] Add Signal K standard resource path handling --- package.json | 1 + src/@types/geojson-validation.d.ts | 1 + src/api/resources/index.ts | 265 +++++++++++++++++++++++++++++ src/api/resources/validate.ts | 106 ++++++++++++ src/put.js | 5 + 5 files changed, 378 insertions(+) create mode 100644 src/@types/geojson-validation.d.ts create mode 100644 src/api/resources/index.ts create mode 100644 src/api/resources/validate.ts diff --git a/package.json b/package.json index c1167f44f..efd7ba360 100644 --- a/package.json +++ b/package.json @@ -104,6 +104,7 @@ "figlet": "^1.2.0", "file-timestamp-stream": "^2.1.2", "flatmap": "0.0.3", + "geojson-validation": "^1.0.2", "geolib": "3.2.2", "get-installed-path": "^4.0.8", "inquirer": "^7.0.0", diff --git a/src/@types/geojson-validation.d.ts b/src/@types/geojson-validation.d.ts new file mode 100644 index 000000000..a2e92c5c9 --- /dev/null +++ b/src/@types/geojson-validation.d.ts @@ -0,0 +1 @@ +declare module 'geojson-validation' diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts new file mode 100644 index 000000000..838f5b962 --- /dev/null +++ b/src/api/resources/index.ts @@ -0,0 +1,265 @@ +import Debug from 'debug' +import { v4 as uuidv4 } from 'uuid' +import { validate } from './validate' + +const debug = Debug('signalk:resources') + +interface ResourceRequest { + method: 'GET' | 'PUT' | 'POST' | 'DELETE' + body: any + query: {[key:string]: any} + resourceType: string + resourceId: string +} + +interface ResourceProvider { + listResources: (type:string, query: {[key:string]:any})=> Promise + getResource: (type:string, id:string)=> Promise + setResource: (type:string, id:string, value:{[key:string]:any})=> Promise + deleteResource: (type:string, id:string)=> Promise +} + +const SIGNALK_API_PATH= `/signalk/v1/api` +const UUID_PREFIX= 'urn:mrn:signalk:uuid:' + +export class Resources { + + // ** in-scope resource types ** + private resourceTypes:Array= [ + 'routes', + 'waypoints', + 'notes', + 'regions', + 'charts' + ] + + resProvider: {[key:string]: any}= {} + server: any + + public start(app:any) { + debug(`** Initialise ${SIGNALK_API_PATH}/resources path handler **`) + this.server= app + this.initResourceRoutes() + } + + public checkForProviders(rescan:boolean= false) { + if(rescan || Object.keys(this.resProvider).length==0) { + debug('** Checking for providers....') + this.resProvider= {} + this.resourceTypes.forEach( (rt:string)=> { + this.resProvider[rt]= this.getResourceProviderFor(rt) + }) + debug(this.resProvider) + } + } + + public getResource(type:string, id:string) { + debug(`** getResource(${type}, ${id})`) + this.checkForProviders() + return this.actionResourceRequest({ + method: 'GET', + body: {}, + query: {}, + resourceType: type, + resourceId: id + }) + } + + + // ** initialise handler for in-scope resource types ** + private initResourceRoutes() { + this.server.get(`${SIGNALK_API_PATH}/resources`, (req:any, res:any) => { + this.checkForProviders() + // list all serviced paths under resources + res.json(this.getResourcePaths()) + }) + this.server.use(`${SIGNALK_API_PATH}/resources/*`, async (req:any, res:any, next: any) => { + let result= this.parseResourceRequest(req) + if(result) { + let ar= await this.actionResourceRequest(result) + if(typeof ar.statusCode!== 'undefined'){ + debug(`${JSON.stringify(ar)}`) + res.status= ar.statusCode + res.send(ar.message) + } + else { + res.json(ar) + } + } + else { + debug('** No provider found... calling next()...') + next() + } + }) + } + + // ** return all paths serviced under ./resources *8 + private getResourcePaths(): {[key:string]:any} { + let resPaths:{[key:string]:any}= {} + Object.entries(this.resProvider).forEach( (p:any)=> { + if(p[1]) { resPaths[p[0]]= `Path containing ${p[0]}, each named with a UUID` } + }) + // check for other plugins servicing paths under ./resources + this.server._router.stack.forEach((i:any)=> { + if(i.route && i.route.path && typeof i.route.path==='string') { + if(i.route.path.indexOf(`${SIGNALK_API_PATH}/resources`)!=-1) { + let r= i.route.path.split('/') + if( r.length>5 && !(r[5] in resPaths) ) { + resPaths[r[5]]= `Path containing ${r[5]} resources (provided by plug-in)` + } + } + } + }) + return resPaths + } + + // ** parse api path request and return ResourceRequest object ** + private parseResourceRequest(req:any): ResourceRequest | undefined { + debug('** req.originalUrl:', req.originalUrl) + debug('** req.method:', req.method) + debug('** req.body:', req.body) + debug('** req.query:', req.query) + debug('** req.params:', req.params) + let p= req.params[0].split('/') + let resType= (typeof req.params[0]!=='undefined') ? p[0] : '' + let resId= p.length>1 ? p[1] : '' + debug('** resType:', resType) + debug('** resId:', resId) + + this.checkForProviders() + if(this.resourceTypes.includes(resType) && this.resProvider[resType]) { + return { + method: req.method, + body: req.body, + query: req.query, + resourceType: resType, + resourceId: resId + } + } + else { + debug('Invalid resource type or no provider for this type!') + return undefined + } + } + + // ** action an in-scope resource request ** + private async actionResourceRequest (req:ResourceRequest):Promise { + debug('********* action request *************') + debug(req) + + // check for registered resource providers + if(!this.resProvider) { + return {statusCode: 501, message: `No Provider`} + } + if(!this.resourceTypes.includes(req.resourceType) || !this.resProvider[req.resourceType]) { + return {statusCode: 501, message: `No Provider`} + } + + if(req.method==='GET') { + let retVal: any + if(!req.resourceId) { + retVal= await this.resProvider[req.resourceType].listResources(req.resourceType, req.query) + return (retVal) ? + retVal : + {statusCode: 404, message: `Error retrieving resources!` } + } + if(!validate.uuid(req.resourceId)) { + return {statusCode: 406, message: `Invalid resource id provided (${req.resourceId})` } + } + retVal= await this.resProvider[req.resourceType].getResource(req.resourceType, req.resourceId) + return (retVal) ? + retVal : + {statusCode: 404, message: `Resource not found (${req.resourceId})!` } + } + + if(req.method==='DELETE' || req.method==='PUT') { + if(!req.resourceId) { + return {statusCode: 406, value: `No resource id provided!` } + } + if(!validate.uuid(req.resourceId)) { + return {statusCode: 406, message: `Invalid resource id provided (${req.resourceId})!` } + } + if( + req.method==='DELETE' || + (req.method==='PUT' && typeof req.body.value!=='undefined' && req.body.value==null) + ) { + let retVal= await this.resProvider[req.resourceType].deleteResource(req.resourceType, req.resourceId) + if(retVal){ + this.sendDelta(req.resourceType, req.resourceId, null) + return {statusCode: 200, message: `Resource (${req.resourceId}) deleted.`} + } + else { + return {statusCode: 400, message: `Error deleting resource (${req.resourceId})!` } + } + } + + } + + if(req.method==='POST' || req.method==='PUT') { + // check for supplied value + if( typeof req.body.value==='undefined' || req.body.value==null) { + return {statusCode: 406, message: `No resource data supplied!`} + } + // validate supplied request data + if(!validate.resource(req.resourceType, req.body.value)) { + return {statusCode: 406, message: `Invalid resource data supplied!`} + } + if(req.method==='POST') { + let id= UUID_PREFIX + uuidv4() + let retVal= await this.resProvider[req.resourceType].setResource(req.resourceType, id, req.body.value) + if(retVal){ + this.sendDelta(req.resourceType, id, req.body.value) + return {statusCode: 200, message: `Resource (${id}) saved.`} + } + else { + return {statusCode: 400, message: `Error saving resource (${id})!` } + } + } + if(req.method==='PUT') { + if(!req.resourceId) { + return {statusCode: 406, message: `No resource id provided!` } + } + let retVal= await this.resProvider[req.resourceType].setResource(req.resourceType, req.resourceId, req.body.value) + if(retVal){ + this.sendDelta(req.resourceType, req.resourceId, req.body.value) + return {statusCode: 200, message: `Resource (${req.resourceId}) updated.`} + } + else { + return {statusCode: 400, message: `Error updating resource (${req.resourceId})!` } + } + } + } + } + + private sendDelta(type:string, id:string, value:any):void { + debug(`** Sending Delta: resources.${type}.${id}`) + this.server.handleMessage('signalk-resources', { + updates: [ + { + values: [ + { + path: `resources.${type}.${id}`, + value: value + } + ] + } + ] + }) + } + + // ** get reference to installed resource provider (plug-in). returns null if none found + private getResourceProviderFor(resType:string): ResourceProvider | null { + if(!this.server.plugins) { return null} + let pSource: ResourceProvider | null= null + this.server.plugins.forEach((plugin:any)=> { + if(typeof plugin.resourceProvider !== 'undefined') { + pSource= plugin.resourceProvider.types.includes(resType) ? + plugin.resourceProvider.methods : + null + } + }) + debug(`** Checking for ${resType} provider.... ${pSource ? 'Found' : 'Not found'}`) + return pSource + } + +} diff --git a/src/api/resources/validate.ts b/src/api/resources/validate.ts new file mode 100644 index 000000000..91d8b28f9 --- /dev/null +++ b/src/api/resources/validate.ts @@ -0,0 +1,106 @@ +//import { GeoHash, GeoBounds } from './geo'; +import geoJSON from 'geojson-validation'; + +export const validate= { + resource: (type:string, value:any):boolean=> { + if(!type) { return false } + switch(type) { + case 'routes': + return validateRoute(value); + break + case 'waypoints': + return validateWaypoint(value) + break + case 'notes': + return validateNote(value) + break; + case 'regions': + return validateRegion(value) + break + default: + return true + } + }, + + // ** returns true if id is a valid Signal K UUID ** + uuid: (id:string): boolean=> { + let uuid= RegExp("^urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$") + return uuid.test(id) + } +} + +// ** validate route data +const validateRoute= (r:any):boolean=> { + //if(typeof r.name === 'undefined') { return false } + //if(typeof r.description === 'undefined') { return false } + if(typeof r.distance === 'undefined' || typeof r.distance !=='number') { return false } + if(r.start) { + let l= r.start.split('/') + if(!validate.uuid(l[l.length-1])) { return false } + } + if(r.end) { + let l= r.end.split('/') + if(!validate.uuid(l[l.length-1])) { return false } + } + try { + if(!r.feature || !geoJSON.valid(r.feature)) { + return false + } + if(r.feature.geometry.type!=='LineString') { return false } + } + catch(err) { return false } + return true +} + +// ** validate waypoint data +const validateWaypoint= (r:any):boolean=> { + if(typeof r.position === 'undefined') { return false } + if(typeof r.position.latitude === 'undefined' || typeof r.position.longitude === 'undefined') { + return false + } + if(typeof r.position.latitude !== 'number' || typeof r.position.longitude !== 'number') { + return false + } + try { + if(!r.feature || !geoJSON.valid(r.feature)) { + return false + } + if(r.feature.geometry.type!=='Point') { return false } + } + catch(e) { return false } + return true +} + +// ** validate note data +const validateNote= (r:any):boolean=> { + if(!r.region && !r.position && !r.geohash ) { return false } + if(typeof r.position!== 'undefined') { + if(typeof r.position.latitude === 'undefined' || typeof r.position.longitude === 'undefined') { + return false + } + if(typeof r.position.latitude !== 'number' || typeof r.position.longitude !== 'number') { + return false + } + } + if(r.region) { + let l= r.region.split('/') + if(!validate.uuid(l[l.length-1])) { return false } + } + return true +} + +// ** validate region data +const validateRegion= (r:any):boolean=> { + if(!r.geohash && !r.feature) { return false } + if(r.feature ) { + try { + if(!geoJSON.valid(r.feature)) { return false } + if(r.feature.geometry.type!=='Polygon' && r.feature.geometry.type!=='MultiPolygon') { + return false + } + } + catch(e) { return false } + } + return true +} + diff --git a/src/put.js b/src/put.js index eb6566215..7a89d4ad1 100644 --- a/src/put.js +++ b/src/put.js @@ -31,6 +31,11 @@ module.exports = { app.deRegisterActionHandler = deRegisterActionHandler app.put(apiPathPrefix + '*', function(req, res, next) { + // ** ignore resources paths ** + if(req.path.split('/')[4]==='resources') { + next() + return + } let path = String(req.path).replace(apiPathPrefix, '') const value = req.body From 9dd084b123ee2688e0fc49cf421d30a3d3904d8f Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 7 Nov 2021 17:27:25 +1030 Subject: [PATCH 219/410] add OpenApi definition file --- src/api/resources/openApi.json | 830 +++++++++++++++++++++++++++++++++ 1 file changed, 830 insertions(+) create mode 100644 src/api/resources/openApi.json diff --git a/src/api/resources/openApi.json b/src/api/resources/openApi.json new file mode 100644 index 000000000..c14f53d5a --- /dev/null +++ b/src/api/resources/openApi.json @@ -0,0 +1,830 @@ +{ + "openapi": "3.0.2", + "info": { + "version": "1.0.0", + "title": "Signal K Resources API" + }, + + "paths": { + + "/resources": { + "get": { + "tags": ["resources"], + "summary": "List available resource types" + + } + }, + + "/resources/{resourceClass}": { + "get": { + "tags": ["resources"], + "summary": "Retrieve resources", + "parameters": [ + { + "name": "resourceClass", + "in": "path", + "description": "resource class", + "required": true, + "schema": { + "type": "string", + "enum": ["routes", "waypoints", "notes", "regions", "charts"], + "example": "waypoints" + } + }, + { + "name": "limit", + "in": "query", + "description": "Maximum number of records to return", + "schema": { + "type": "integer", + "format": "int32", + "minimum": 1, + "example": 100 + } + }, + { + "in": "query", + "name": "radius", + "description": "Limit results to resources that fall within a square area, centered around the vessel's position, the edges of which are the sepecified distance in meters from the vessel.", + "schema": { + "type": "integer", + "format": "int32", + "minimum": 100, + "example": 2000 + } + }, + { + "in": "query", + "name": "geohash", + "description": "limit results to resources that fall within an area sepecified by the geohash.", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "bbox", + "description": "Limit results to resources that fall within the bounded area defined as lower left and upper right x,y coordinates x1,y1,x2,y2", + "style": "form", + "explode": false, + "schema": { + "type": "array", + "minItems": 4, + "maxItems": 4, + "items": { + "type": "number", + "format": "float", + "example": [135.5,-25.2,138.1,-28.0] + } + } + } + ], + "responses": { + "200": { + "description": "List of resources identified by their UUID", + "content": { + "application/json": { + "schema": { + "description": "List of Signal K resources", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + } + } + }, + + "/resources/routes/": { + "post": { + "tags": ["resources/routes"], + "summary": "Add a new Route", + "requestBody": { + "description": "Route details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Route resource", + "required": ["feature"], + "properties": { + "name": { + "type": "string", + "description": "Route's common name" + }, + "description": { + "type": "string", + "description": "A description of the route" + }, + "distance": { + "description": "Total distance from start to end", + "type": "number" + }, + "start": { + "description": "The waypoint UUID at the start of the route", + "type": "string", + "pattern": "/resources/waypoints/urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$" + }, + "end": { + "description": "The waypoint UUID at the end of the route", + "type": "string", + "pattern": "/resources//waypoints/urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$" + }, + "feature": { + "type": "object", + "title": "Feature", + "description": "A GeoJSON feature object which describes a route", + "properties": { + "geometry": { + "type": { + "type": "string", + "enum": ["LineString"] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } + } + } + }, + "properties": { + "description": "Additional feature properties", + "type": "object", + "additionalProperties": true + }, + "id": { + "type": "string" + } + } + } + } + + } + } + } + } + } + }, + + "/resources/routes/{id}": { + "parameters": { + "name": "id", + "in": "path", + "description": "route id", + "required": true, + "schema": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$" + } + }, + + "get": { + "tags": ["resources/routes"], + "summary": "Retrieve route with supplied id" + }, + + "put": { + "tags": ["resources/routes"], + "summary": "Add / update a new Route with supplied id", + "requestBody": { + "description": "Route details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Route resource", + "required": ["feature"], + "properties": { + "name": { + "type": "string", + "description": "Route's common name" + }, + "description": { + "type": "string", + "description": "A description of the route" + }, + "distance": { + "description": "Total distance from start to end", + "type": "number" + }, + "start": { + "description": "The waypoint UUID at the start of the route", + "type": "string", + "pattern": "/resources/waypoints/urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$" + }, + "end": { + "description": "The waypoint UUID at the end of the route", + "type": "string", + "pattern": "/resources/waypoints/urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$" + }, + "feature": { + "type": "object", + "title": "Feature", + "description": "A GeoJSON feature object which describes a route", + "properties": { + "geometry": { + "type": { + "type": "string", + "enum": ["LineString"] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } + } + } + }, + "properties": { + "description": "Additional feature properties", + "type": "object", + "additionalProperties": true + }, + "id": { + "type": "string" + } + } + } + } + + } + } + } + } + }, + + "delete": { + "tags": ["resources/routes"], + "summary": "Remove Route with supplied id" + } + + }, + + "/resources/waypoints/": { + "post": { + "tags": ["resources/waypoints"], + "summary": "Add a new Waypoint", + "requestBody": { + "description": "Waypoint details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Waypoint resource", + "required": ["feature"], + "properties": { + "position": { + "description": "The waypoint position", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + }, + "altitude": { + "type": "number", + "format": "float" + } + } + }, + "feature": { + "type": "object", + "title": "Feature", + "description": "A GeoJSON feature object which describes a waypoint", + "properties": { + "geometry": { + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["Point"] + } + }, + "coordinates": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } + } + }, + "properties": { + "description": "Additional feature properties", + "type": "object", + "additionalProperties": true + }, + "id": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + + "/resources/waypoints/{id}": { + "parameters": { + "name": "id", + "in": "path", + "description": "waypoint id", + "required": true, + "schema": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$" + } + }, + + "get": { + "tags": ["resources/waypoints"], + "summary": "Retrieve waypoint with supplied id" + }, + + "put": { + "tags": ["resources/waypoints"], + "summary": "Add / update a new Waypoint with supplied id", + "requestBody": { + "description": "Waypoint details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Waypoint resource", + "required": ["feature"], + "properties": { + "position": { + "description": "The waypoint position", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + }, + "altitude": { + "type": "number", + "format": "float" + } + } + }, + "feature": { + "type": "object", + "title": "Feature", + "description": "A GeoJSON feature object which describes a waypoint", + "properties": { + "geometry": { + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["Point"] + } + }, + "coordinates": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } + } + }, + "properties": { + "description": "Additional feature properties", + "type": "object", + "additionalProperties": true + }, + "id": { + "type": "string" + } + } + } + } + } + } + } + } + }, + + "delete": { + "tags": ["resources/waypoints"], + "summary": "Remove Waypoint with supplied id" + } + + }, + + "/resources/notes/": { + "post": { + "tags": ["resources/notes"], + "summary": "Add a new Note", + "requestBody": { + "description": "Note details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Note resource", + "required": ["feature"], + "properties": { + "title": { + "type": "string", + "description": "Common Note name" + }, + "description": { + "type": "string", + "description": "A description of the note" + }, + "position": { + "description": "Position related to note. Alternative to region or geohash.", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + }, + "altitude": { + "type": "number", + "format": "float" + } + } + }, + "region": { + "description": "Pointer / path to Region related to note (e.g. /resources/routes/{uuid}. Alternative to position or geohash", + "type": "string", + "pattern": "/resources/regions/urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$" + }, + "geohash": { + "description": "Area related to note. Alternative to region or position", + "type": "string" + }, + "mimeType": { + "description": "MIME type of the note", + "type": "string" + }, + "url": { + "description": "Location of the note content", + "type": "string" + } + } + } + } + } + } + } + }, + + "/resources/notes/{id}": { + "parameters": { + "name": "id", + "in": "path", + "description": "note id", + "required": true, + "schema": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$" + } + }, + + "get": { + "tags": ["resources/notes"], + "summary": "Retrieve Note with supplied id" + }, + + "put": { + "tags": ["resources/notes"], + "summary": "Add / update a new Note with supplied id", + "requestBody": { + "description": "Note details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Note resource", + "required": ["feature"], + "properties": { + "title": { + "type": "string", + "description": "Common Note name" + }, + "description": { + "type": "string", + "description": "A description of the note" + }, + "position": { + "description": "Position related to note. Alternative to region or geohash.", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + }, + "altitude": { + "type": "number", + "format": "float" + } + } + }, + "region": { + "description": "Pointer / path to Region related to note (e.g. /resources/routes/{uuid}. Alternative to position or geohash", + "type": "string", + "pattern": "/resources/regions/urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$" + }, + "geohash": { + "description": "Area related to note. Alternative to region or position", + "type": "string" + }, + "mimeType": { + "description": "MIME type of the note", + "type": "string" + }, + "url": { + "description": "Location of the note content", + "type": "string" + } + } + } + } + } + } + }, + + "delete": { + "tags": ["resources/notes"], + "summary": "Remove Note with supplied id" + } + + }, + + "/resources/regions/": { + "post": { + "tags": ["resources/regions"], + "summary": "Add a new Regkion", + "requestBody": { + "description": "Region details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Region resource", + "required": ["feature"], + "properties": { + "geohash": { + "description": "geohash of the approximate boundary of this region", + "type": "string" + }, + "feature": { + "type": "object", + "title": "Feature", + "description": "A Geo JSON feature object which describes the regions boundary", + "properties": { + "geometry": { + "oneOf": [ + { + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["Polygon"] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } + } + } + } + } + }, + { + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["MultiPolygon"] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } + } + } + } + } + } + } + ] + }, + "properties": { + "description": "Additional feature properties", + "type": "object", + "additionalProperties": true + }, + "id": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + + "/resources/regions/{id}": { + "parameters": { + "name": "id", + "in": "path", + "description": "region id", + "required": true, + "schema": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$" + } + }, + + "get": { + "tags": ["resources/regions"], + "summary": "Retrieve Region with supplied id" + }, + + "put": { + "tags": ["resources/regions"], + "summary": "Add / update a new Region with supplied id", + "requestBody": { + "description": "Region details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Region resource", + "required": ["feature"], + "properties": { + "geohash": { + "description": "geohash of the approximate boundary of this region", + "type": "string" + }, + "feature": { + "type": "object", + "title": "Feature", + "description": "A Geo JSON feature object which describes the regions boundary", + "properties": { + "geometry": { + "oneOf": [ + { + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["Polygon"] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } + } + } + } + } + }, + { + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["MultiPolygon"] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } + } + } + } + } + } + } + ] + }, + "properties": { + "description": "Additional feature properties", + "type": "object", + "additionalProperties": true + }, + "id": { + "type": "string" + } + } + } + } + } + } + } + } + }, + + "delete": { + "tags": ["resources/regions"], + "summary": "Remove Region with supplied id" + } + + } + + } + +} + \ No newline at end of file From 20970e01c55683af7c85888cb14e692ee3de26ca Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 8 Nov 2021 09:04:45 +1030 Subject: [PATCH 220/410] chore: fix lint errors --- src/api/resources/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 838f5b962..ae0495e08 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -43,7 +43,7 @@ export class Resources { } public checkForProviders(rescan:boolean= false) { - if(rescan || Object.keys(this.resProvider).length==0) { + if(rescan || Object.keys(this.resProvider).length===0) { debug('** Checking for providers....') this.resProvider= {} this.resourceTypes.forEach( (rt:string)=> { @@ -102,7 +102,7 @@ export class Resources { // check for other plugins servicing paths under ./resources this.server._router.stack.forEach((i:any)=> { if(i.route && i.route.path && typeof i.route.path==='string') { - if(i.route.path.indexOf(`${SIGNALK_API_PATH}/resources`)!=-1) { + if(i.route.path.indexOf(`${SIGNALK_API_PATH}/resources`)!==-1) { let r= i.route.path.split('/') if( r.length>5 && !(r[5] in resPaths) ) { resPaths[r[5]]= `Path containing ${r[5]} resources (provided by plug-in)` From a74cc7fe8a4f30e67db0c8362c1d1917a8c27dd2 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Tue, 9 Nov 2021 15:13:44 +1030 Subject: [PATCH 221/410] addressed comments re parameters --- src/api/resources/openApi.json | 664 ++++++++++++++++++++++++++------- 1 file changed, 527 insertions(+), 137 deletions(-) diff --git a/src/api/resources/openApi.json b/src/api/resources/openApi.json index c14f53d5a..e23a100f3 100644 --- a/src/api/resources/openApi.json +++ b/src/api/resources/openApi.json @@ -10,8 +10,22 @@ "/resources": { "get": { "tags": ["resources"], - "summary": "List available resource types" - + "summary": "List available resource types", + "responses": { + "default": { + "description": "List of available resource types", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + } } }, @@ -43,8 +57,8 @@ } }, { + "name": "distance", "in": "query", - "name": "radius", "description": "Limit results to resources that fall within a square area, centered around the vessel's position, the edges of which are the sepecified distance in meters from the vessel.", "schema": { "type": "integer", @@ -53,18 +67,10 @@ "example": 2000 } }, - { - "in": "query", - "name": "geohash", - "description": "limit results to resources that fall within an area sepecified by the geohash.", - "schema": { - "type": "string" - } - }, { "in": "query", "name": "bbox", - "description": "Limit results to resources that fall within the bounded area defined as lower left and upper right x,y coordinates x1,y1,x2,y2", + "description": "Limit results to resources that fall within the bounded area defined as lower left (south west) and upper right (north east) coordinates [swlon,swlat,nelon,nelat]", "style": "form", "explode": false, "schema": { @@ -140,18 +146,21 @@ "description": "A GeoJSON feature object which describes a route", "properties": { "geometry": { - "type": { - "type": "string", - "enum": ["LineString"] - }, - "coordinates": { - "type": "array", - "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["LineString"] + }, + "coordinates": { "type": "array", - "maxItems": 3, - "minItems": 2, "items": { - "type": "number" + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } } } } @@ -171,7 +180,8 @@ } } } - } + }, + "responses": {} } }, @@ -189,7 +199,8 @@ "get": { "tags": ["resources/routes"], - "summary": "Retrieve route with supplied id" + "summary": "Retrieve route with supplied id", + "responses": {} }, "put": { @@ -233,18 +244,21 @@ "description": "A GeoJSON feature object which describes a route", "properties": { "geometry": { - "type": { - "type": "string", - "enum": ["LineString"] - }, - "coordinates": { - "type": "array", - "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["LineString"] + }, + "coordinates": { "type": "array", - "maxItems": 3, - "minItems": 2, "items": { - "type": "number" + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } } } } @@ -264,12 +278,14 @@ } } } - } + }, + "responses": {} }, "delete": { "tags": ["resources/routes"], - "summary": "Remove Route with supplied id" + "summary": "Remove Route with supplied id", + "responses": {} } }, @@ -317,19 +333,22 @@ "properties": { "geometry": { "type": "object", - "description": "GeoJSon geometry", "properties": { - "type": { - "type": "string", - "enum": ["Point"] - } - }, - "coordinates": { - "type": "array", - "maxItems": 3, - "minItems": 2, - "items": { - "type": "number" + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["Point"] + } + }, + "coordinates": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } } } }, @@ -347,7 +366,8 @@ } } } - } + }, + "responses": {} } }, @@ -365,7 +385,8 @@ "get": { "tags": ["resources/waypoints"], - "summary": "Retrieve waypoint with supplied id" + "summary": "Retrieve waypoint with supplied id", + "responses": {} }, "put": { @@ -410,19 +431,22 @@ "properties": { "geometry": { "type": "object", - "description": "GeoJSon geometry", "properties": { - "type": { - "type": "string", - "enum": ["Point"] - } - }, - "coordinates": { - "type": "array", - "maxItems": 3, - "minItems": 2, - "items": { - "type": "number" + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["Point"] + } + }, + "coordinates": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } } } }, @@ -440,12 +464,14 @@ } } } - } + }, + "responses": {} }, "delete": { "tags": ["resources/waypoints"], - "summary": "Remove Waypoint with supplied id" + "summary": "Remove Waypoint with supplied id", + "responses": {} } }, @@ -533,7 +559,8 @@ "get": { "tags": ["resources/notes"], - "summary": "Retrieve Note with supplied id" + "summary": "Retrieve Note with supplied id", + "responses": {} }, "put": { @@ -600,12 +627,14 @@ } } } - } + }, + "responses": {} }, "delete": { "tags": ["resources/notes"], - "summary": "Remove Note with supplied id" + "summary": "Remove Note with supplied id", + "responses": {} } }, @@ -634,59 +663,62 @@ "description": "A Geo JSON feature object which describes the regions boundary", "properties": { "geometry": { - "oneOf": [ - { - "type": "object", - "description": "GeoJSon geometry", - "properties": { - "type": { - "type": "string", - "enum": ["Polygon"] - }, - "coordinates": { - "type": "array", - "items": { + "type": "object", + "properties": { + "oneOf": [ + { + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["Polygon"] + }, + "coordinates": { "type": "array", "items": { "type": "array", - "maxItems": 3, - "minItems": 2, "items": { - "type": "number" + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } } } } } - } - }, - { - "type": "object", - "description": "GeoJSon geometry", - "properties": { - "type": { - "type": "string", - "enum": ["MultiPolygon"] - }, - "coordinates": { - "type": "array", - "items": { + }, + { + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["MultiPolygon"] + }, + "coordinates": { "type": "array", "items": { "type": "array", "items": { "type": "array", - "maxItems": 3, - "minItems": 2, "items": { - "type": "number" + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } } } } } } } - } - ] + ] + } }, "properties": { "description": "Additional feature properties", @@ -702,7 +734,8 @@ } } } - } + }, + "responses": {} } }, @@ -720,7 +753,8 @@ "get": { "tags": ["resources/regions"], - "summary": "Retrieve Region with supplied id" + "summary": "Retrieve Region with supplied id", + "responses": {} }, "put": { @@ -746,59 +780,62 @@ "description": "A Geo JSON feature object which describes the regions boundary", "properties": { "geometry": { - "oneOf": [ - { - "type": "object", - "description": "GeoJSon geometry", - "properties": { - "type": { - "type": "string", - "enum": ["Polygon"] - }, - "coordinates": { - "type": "array", - "items": { + "type": "object", + "properties": { + "oneOf": [ + { + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["Polygon"] + }, + "coordinates": { "type": "array", "items": { "type": "array", - "maxItems": 3, - "minItems": 2, "items": { - "type": "number" + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } } } } } - } - }, - { - "type": "object", - "description": "GeoJSon geometry", - "properties": { - "type": { - "type": "string", - "enum": ["MultiPolygon"] - }, - "coordinates": { - "type": "array", - "items": { + }, + { + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["MultiPolygon"] + }, + "coordinates": { "type": "array", "items": { "type": "array", "items": { "type": "array", - "maxItems": 3, - "minItems": 2, "items": { - "type": "number" + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } } } } } } } - } - ] + ] + } }, "properties": { "description": "Additional feature properties", @@ -814,14 +851,367 @@ } } } - } + }, + "responses": {} }, "delete": { "tags": ["resources/regions"], - "summary": "Remove Region with supplied id" + "summary": "Remove Region with supplied id", + "responses": {} + } + + }, + + "/resources/setWaypoint": { + "put": { + "tags": ["resources/api"], + "summary": "Add / update a Waypoint", + "requestBody": { + "description": "Waypoint attributes", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["position"], + "properties": { + "id": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", + "description": "Waypoint identifier" + }, + "name": { + "type": "string", + "description": "Waypoint name" + }, + "description": { + "type": "string", + "description": "Textual description of the waypoint" + }, + "position": { + "description": "The waypoint position", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + } + } + } + } + } + } + }, + "responses": {} + } + }, + + "/resources/deleteWaypoint": { + "put": { + "tags": ["resources/api"], + "summary": "Remove a Waypoint", + "requestBody": { + "description": "Waypoint identifier", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["id"], + "properties": { + "id": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", + "description": "Waypoint identifier" + } + } + } + } + } + }, + "responses": {} + } + }, + + "/resources/setRoute": { + "put": { + "tags": ["resources/api"], + "summary": "Add / update a Route", + "requestBody": { + "description": "Note attributes", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["position"], + "properties": { + "id": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", + "description": "Route identifier" + }, + "name": { + "type": "string", + "description": "Route name" + }, + "description": { + "type": "string", + "description": "Textual description of the route" + }, + "points": { + "description": "Route points", + "type": "array", + "items": { + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + } + } + } + } + } + } + } + }, + "responses": {} + } + }, + + "/resources/deleteRoute": { + "put": { + "tags": ["resources/api"], + "summary": "Remove a Route", + "requestBody": { + "description": "Route identifier", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["id"], + "properties": { + "id": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", + "description": "Route identifier" + } + } + } + } + } + }, + "responses": {} } + }, + "/resources/setNote": { + "put": { + "tags": ["resources/api"], + "summary": "Add / update a Note", + "requestBody": { + "description": "Note attributes", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["position"], + "properties": { + "id": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", + "description": "Note identifier" + }, + "title": { + "type": "string", + "description": "Note's common name" + }, + "description": { + "type": "string", + "description": " Textual description of the note" + }, + "oneOf":[ + { + "position": { + "description": "Position related to note. Alternative to region or geohash", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + } + }, + "geohash": { + "type": "string", + "description": "Position related to note. Alternative to region or position" + }, + "region": { + "type": "string", + "description": "Region related to note. A pointer to a region UUID. Alternative to position or geohash", + "example": "/resources/regions/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a" + } + } + ], + "mimeType": { + "type": "string", + "description": "MIME type of the note" + }, + "url": { + "type": "string", + "description": "Location of the note contents" + } + } + } + } + } + }, + "responses": {} + } + }, + + "/resources/deleteNote": { + "put": { + "tags": ["resources/api"], + "summary": "Remove a Note", + "requestBody": { + "description": "Note identifier", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["id"], + "properties": { + "id": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", + "description": "Note identifier" + } + } + } + } + } + }, + "responses": {} + } + }, + + "/resources/setRegion": { + "put": { + "tags": ["resources/api"], + "summary": "Add / update a Region", + "requestBody": { + "description": "Region attributes", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["position"], + "properties": { + "id": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", + "description": "Region identifier" + }, + "name": { + "type": "string", + "description": "Region name" + }, + "description": { + "type": "string", + "description": "Textual description of region" + }, + "geohash": { + "type": "string", + "description": "Area related to region. Alternative to points." + }, + "points": { + "description": "Region boundary points", + "type": "array", + "items": { + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + } + } + } + } + } + } + } + }, + "responses": {} + } + }, + + "/resources/deleteRegion": { + "put": { + "tags": ["resources/api"], + "summary": "Remove a Region", + "requestBody": { + "description": "Region identifier", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["id"], + "properties": { + "id": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", + "description": "Region identifier" + } + } + } + } + } + }, + "responses": {} + } } } From dafecf65d8e66a46f008de42f5c1c84e325d92c3 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Tue, 9 Nov 2021 15:28:15 +1030 Subject: [PATCH 222/410] add API definitions --- src/api/resources/openApi.json | 350 ++++++++++++++++++++++++++++++--- 1 file changed, 326 insertions(+), 24 deletions(-) diff --git a/src/api/resources/openApi.json b/src/api/resources/openApi.json index e23a100f3..d7a7f6e35 100644 --- a/src/api/resources/openApi.json +++ b/src/api/resources/openApi.json @@ -12,7 +12,7 @@ "tags": ["resources"], "summary": "List available resource types", "responses": { - "default": { + "200": { "description": "List of available resource types", "content": { "application/json": { @@ -181,7 +181,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -200,7 +211,22 @@ "get": { "tags": ["resources/routes"], "summary": "Retrieve route with supplied id", - "responses": {} + "responses": { + "200": { + "description": "List of resources identified by their UUID", + "content": { + "application/json": { + "schema": { + "description": "List of Signal K resources", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + } }, "put": { @@ -279,13 +305,35 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } }, "delete": { "tags": ["resources/routes"], "summary": "Remove Route with supplied id", - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -367,7 +415,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -386,7 +445,22 @@ "get": { "tags": ["resources/waypoints"], "summary": "Retrieve waypoint with supplied id", - "responses": {} + "responses": { + "200": { + "description": "List of resources identified by their UUID", + "content": { + "application/json": { + "schema": { + "description": "List of Signal K resources", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + } }, "put": { @@ -465,13 +539,35 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } }, "delete": { "tags": ["resources/waypoints"], "summary": "Remove Waypoint with supplied id", - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -541,6 +637,18 @@ } } } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } } } }, @@ -560,7 +668,22 @@ "get": { "tags": ["resources/notes"], "summary": "Retrieve Note with supplied id", - "responses": {} + "responses": { + "200": { + "description": "List of resources identified by their UUID", + "content": { + "application/json": { + "schema": { + "description": "List of Signal K resources", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + } }, "put": { @@ -628,13 +751,35 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } }, "delete": { "tags": ["resources/notes"], "summary": "Remove Note with supplied id", - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -735,7 +880,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -754,7 +910,22 @@ "get": { "tags": ["resources/regions"], "summary": "Retrieve Region with supplied id", - "responses": {} + "responses": { + "200": { + "description": "List of resources identified by their UUID", + "content": { + "application/json": { + "schema": { + "description": "List of Signal K resources", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + } }, "put": { @@ -852,13 +1023,35 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } }, "delete": { "tags": ["resources/regions"], "summary": "Remove Region with supplied id", - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -889,6 +1082,13 @@ "type": "string", "description": "Textual description of the waypoint" }, + "attributes": { + "type": "object", + "description": "Additional attributes as name:value pairs.", + "additionalProperties": { + "type": "string" + } + }, "position": { "description": "The waypoint position", "type": "object", @@ -912,7 +1112,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -939,7 +1150,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -969,6 +1191,13 @@ "type": "string", "description": "Textual description of the route" }, + "attributes": { + "type": "object", + "description": "Additional attributes as name:value pairs.", + "additionalProperties": { + "type": "string" + } + }, "points": { "description": "Route points", "type": "array", @@ -995,7 +1224,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -1022,7 +1262,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -1096,7 +1347,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -1123,7 +1385,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -1153,6 +1426,13 @@ "type": "string", "description": "Textual description of region" }, + "attributes": { + "type": "object", + "description": "Additional attributes as name:value pairs.", + "additionalProperties": { + "type": "string" + } + }, "geohash": { "type": "string", "description": "Area related to region. Alternative to points." @@ -1183,7 +1463,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -1210,7 +1501,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } } From 96b925dbb86fba7161895a091fd2258b8f894114 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Tue, 9 Nov 2021 15:29:13 +1030 Subject: [PATCH 223/410] add geohash library --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index efd7ba360..9955e043e 100644 --- a/package.json +++ b/package.json @@ -118,6 +118,7 @@ "morgan": "^1.5.0", "ms": "^2.1.2", "ncp": "^2.0.0", + "ngeohash": "^0.6.3", "node-fetch": "^2.6.0", "pem": "^1.14.3", "primus": "^7.0.0", @@ -144,6 +145,7 @@ "@types/express": "^4.17.1", "@types/lodash": "^4.14.139", "@types/mocha": "^8.2.0", + "@types/ngeohash": "^0.6.4", "@types/node-fetch": "^2.5.3", "@types/pem": "^1.9.6", "@types/semver": "^7.1.0", From 755f22fed24623b134987a0c45020cd8a86af2eb Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Tue, 9 Nov 2021 15:30:30 +1030 Subject: [PATCH 224/410] add API endpoint processing --- src/api/resources/index.ts | 81 ++++++++++++++-- src/api/resources/resources.ts | 167 +++++++++++++++++++++++++++++++++ src/api/resources/validate.ts | 1 - 3 files changed, 239 insertions(+), 10 deletions(-) create mode 100644 src/api/resources/resources.ts diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index ae0495e08..d84cd29a8 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -1,6 +1,7 @@ import Debug from 'debug' import { v4 as uuidv4 } from 'uuid' import { validate } from './validate' +import { buildResource } from './resources' const debug = Debug('signalk:resources') @@ -9,7 +10,8 @@ interface ResourceRequest { body: any query: {[key:string]: any} resourceType: string - resourceId: string + resourceId: string, + apiMethod?: string | null } interface ResourceProvider { @@ -22,6 +24,17 @@ interface ResourceProvider { const SIGNALK_API_PATH= `/signalk/v1/api` const UUID_PREFIX= 'urn:mrn:signalk:uuid:' +const API_METHODS= [ + 'setWaypoint', + 'deleteWaypoint', + 'setRoute', + 'deleteRoute', + 'setNote', + 'deleteNote', + 'setRegion', + 'deleteRegion' +] + export class Resources { // ** in-scope resource types ** @@ -114,7 +127,7 @@ export class Resources { } // ** parse api path request and return ResourceRequest object ** - private parseResourceRequest(req:any): ResourceRequest | undefined { + private parseResourceRequest(req:any):ResourceRequest | undefined { debug('** req.originalUrl:', req.originalUrl) debug('** req.method:', req.method) debug('** req.body:', req.body) @@ -125,16 +138,35 @@ export class Resources { let resId= p.length>1 ? p[1] : '' debug('** resType:', resType) debug('** resId:', resId) + + let apiMethod= (API_METHODS.includes(resType)) ? resType : null + if(apiMethod) { + if(apiMethod.toLowerCase().indexOf('waypoint')!==-1) { + resType= 'waypoints' + } + if(apiMethod.toLowerCase().indexOf('route')!==-1) { + resType= 'routes' + } + if(apiMethod.toLowerCase().indexOf('note')!==-1) { + resType= 'notes' + } + if(apiMethod.toLowerCase().indexOf('region')!==-1) { + resType= 'regions' + } + } this.checkForProviders() + let retReq= { + method: req.method, + body: req.body, + query: req.query, + resourceType: resType, + resourceId: resId, + apiMethod: apiMethod + } + if(this.resourceTypes.includes(resType) && this.resProvider[resType]) { - return { - method: req.method, - body: req.body, - query: req.query, - resourceType: resType, - resourceId: resId - } + return retReq } else { debug('Invalid resource type or no provider for this type!') @@ -151,10 +183,41 @@ export class Resources { if(!this.resProvider) { return {statusCode: 501, message: `No Provider`} } + if(!this.resourceTypes.includes(req.resourceType) || !this.resProvider[req.resourceType]) { return {statusCode: 501, message: `No Provider`} } + // check for API method request + if(req.apiMethod && API_METHODS.includes(req.apiMethod)) { + debug(`API Method (${req.apiMethod})`) + req= this.transformApiRequest(req) + } + + return await this.execResourceRequest(req) + } + + // ** transform API request to ResourceRequest ** + private transformApiRequest(req: ResourceRequest):ResourceRequest { + if(req.apiMethod?.indexOf('delete')!==-1) { + req.method= 'DELETE' + } + if(req.apiMethod?.indexOf('set')!==-1) { + if(!req.body.value?.id) { + req.method= 'POST' + } + else { + req.resourceId= req.body.value.id + } + req.body.value= buildResource(req.resourceType, req.body.value) ?? {} + } + console.log(req) + return req + } + + // ** action an in-scope resource request ** + private async execResourceRequest (req:ResourceRequest):Promise { + if(req.method==='GET') { let retVal: any if(!req.resourceId) { diff --git a/src/api/resources/resources.ts b/src/api/resources/resources.ts new file mode 100644 index 000000000..590a6591b --- /dev/null +++ b/src/api/resources/resources.ts @@ -0,0 +1,167 @@ +import { getDistance } from 'geolib' +import ngeohash from 'ngeohash' +import geolib from 'geolib' + +// ** build resource item ** +export const buildResource= (resType:string, data:any):any=> { + console.log(`** resType: ${resType}, data: ${JSON.stringify(data)}`) + if(resType==='routes') { return buildRoute(data) } + if(resType==='waypoints') { return buildWaypoint(data) } + if(resType==='notes') { return buildNote(data) } + if(resType==='regions') { return buildRegion(data) } +} + +// ** build route +const buildRoute= (rData:any):any=> { + let rte:any= { + feature: { + type: 'Feature', + geometry:{ + type: 'LineString', + coordinates :[] + }, + properties:{} + } + } + if(typeof rData.name !== 'undefined') { + rte.name= rData.name + rte.feature.properties.name= rData.name + } + if(typeof rData.description !== 'undefined') { + rte.description= rData.description + rte.feature.properties.description= rData.description + } + if(typeof rData.points === 'undefined') { return null } + if(!Array.isArray(rData.points)) { return null } + let isValid:boolean= true; + rData.points.forEach( (p:any)=> { + if(!geolib.isValidCoordinate(p)) { isValid= false } + }) + if(!isValid) { return null } + rData.points.forEach( (p:any)=> { + rte.feature.geometry.coordinates.push([p.longitude, p.latitude]) + }) + rte.distance= 0 + for(let i=0; i { + let wpt:any= { + position: { + latitude: 0, + longitude: 0 + }, + feature: { + type: 'Feature', + geometry:{ + type: 'Point', + coordinates :[] + }, + properties:{} + } + } + if(typeof rData.name !== 'undefined') { + wpt.feature.properties.name= rData.name + } + if(typeof rData.description !== 'undefined') { + wpt.feature.properties.description= rData.description + } + if(typeof rData.position === 'undefined') { return null } + if(!geolib.isValidCoordinate(rData.position)) { return null } + + wpt.position= rData.position + wpt.feature.geometry.coordinates= [rData.position.longitude, rData.position.latitude] + + return wpt +} + +// ** build note +const buildNote= (rData:any):any=> { + let note:any= {} + if(typeof rData.title !== 'undefined') { + note.title= rData.title + note.feature.properties.title= rData.title + } + if(typeof rData.description !== 'undefined') { + note.description= rData.description + note.feature.properties.description= rData.description + } + if(typeof rData.position === 'undefined' + && typeof rData.region === 'undefined' + && typeof rData.geohash === 'undefined') { return null } + + if(typeof rData.position !== 'undefined') { + if(!geolib.isValidCoordinate(rData.position)) { return null } + note.position= rData.position + } + if(typeof rData.region !== 'undefined') { + note.region= rData.region + } + if(typeof rData.geohash !== 'undefined') { + note.geohash= rData.geohash + } + if(typeof rData.url !== 'undefined') { + note.url= rData.url + } + if(typeof rData.mimeType !== 'undefined') { + note.mimeType= rData.mimeType + } + + return note +} + +// ** build region +const buildRegion= (rData:any):any=> { + let reg:any= { + feature: { + type: 'Feature', + geometry:{ + type: 'Polygon', + coordinates :[] + }, + properties:{} + } + } + let coords:Array<[number,number]>= [] + + if(typeof rData.name !== 'undefined') { + reg.feature.properties.name= rData.name + } + if(typeof rData.description !== 'undefined') { + reg.feature.properties.description= rData.description + } + if(typeof rData.points=== 'undefined' && rData.geohash=== 'undefined') { return null } + if(typeof rData.geohash!== 'undefined') { + reg.geohash= rData.geohash + + let bounds= ngeohash.decode_bbox(rData.geohash) + coords= [ + [bounds[1], bounds[0]], + [bounds[3], bounds[0]], + [bounds[3], bounds[2]], + [bounds[1], bounds[2]], + [bounds[1], bounds[0]], + ] + reg.feature.geometry.coordinates.push(coords) + } + if(typeof rData.points!== 'undefined' && coords.length===0 ) { + if(!Array.isArray(rData.points)) { return null } + let isValid:boolean= true; + rData.points.forEach( (p:any)=> { + if(!geolib.isValidCoordinate(p)) { isValid= false } + }) + if(!isValid) { return null } + rData.points.forEach( (p:any)=> { + coords.push([p.longitude, p.latitude]) + }) + reg.feature.geometry.coordinates.push(coords) + } + + return reg +} diff --git a/src/api/resources/validate.ts b/src/api/resources/validate.ts index 91d8b28f9..341b7465f 100644 --- a/src/api/resources/validate.ts +++ b/src/api/resources/validate.ts @@ -1,4 +1,3 @@ -//import { GeoHash, GeoBounds } from './geo'; import geoJSON from 'geojson-validation'; export const validate= { From eaafd7b20a1c35593d8cd15656f3b1f839ea41ed Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Tue, 9 Nov 2021 16:44:49 +1030 Subject: [PATCH 225/410] align with openapi definitions --- src/api/resources/index.ts | 10 +++++----- src/api/resources/resources.ts | 35 ++++++++++++++++++++++------------ src/api/resources/validate.ts | 14 +++----------- 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index d84cd29a8..8d453cbc9 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -203,21 +203,21 @@ export class Resources { req.method= 'DELETE' } if(req.apiMethod?.indexOf('set')!==-1) { - if(!req.body.value?.id) { + if(!req.body.id) { req.method= 'POST' } else { - req.resourceId= req.body.value.id + req.resourceId= req.body.id } - req.body.value= buildResource(req.resourceType, req.body.value) ?? {} + req.body= { value: buildResource(req.resourceType, req.body) ?? {} } } - console.log(req) return req } // ** action an in-scope resource request ** private async execResourceRequest (req:ResourceRequest):Promise { - + debug('********* execute request *************') + debug(req) if(req.method==='GET') { let retVal: any if(!req.resourceId) { diff --git a/src/api/resources/resources.ts b/src/api/resources/resources.ts index 590a6591b..e76cce323 100644 --- a/src/api/resources/resources.ts +++ b/src/api/resources/resources.ts @@ -1,10 +1,8 @@ -import { getDistance } from 'geolib' +import { getDistance, isValidCoordinate } from 'geolib' import ngeohash from 'ngeohash' -import geolib from 'geolib' // ** build resource item ** export const buildResource= (resType:string, data:any):any=> { - console.log(`** resType: ${resType}, data: ${JSON.stringify(data)}`) if(resType==='routes') { return buildRoute(data) } if(resType==='waypoints') { return buildWaypoint(data) } if(resType==='notes') { return buildNote(data) } @@ -12,7 +10,7 @@ export const buildResource= (resType:string, data:any):any=> { } // ** build route -const buildRoute= (rData:any):any=> { +const buildRoute= (rData:any):any=> { let rte:any= { feature: { type: 'Feature', @@ -31,22 +29,27 @@ const buildRoute= (rData:any):any=> { rte.description= rData.description rte.feature.properties.description= rData.description } + if(typeof rData.attributes !== 'undefined') { + Object.assign(rte.feature.properties, rData.attributes) + } + if(typeof rData.points === 'undefined') { return null } if(!Array.isArray(rData.points)) { return null } let isValid:boolean= true; rData.points.forEach( (p:any)=> { - if(!geolib.isValidCoordinate(p)) { isValid= false } + if(!isValidCoordinate(p)) { isValid= false } }) if(!isValid) { return null } rData.points.forEach( (p:any)=> { rte.feature.geometry.coordinates.push([p.longitude, p.latitude]) }) + rte.distance= 0 - for(let i=0; i { if(typeof rData.description !== 'undefined') { wpt.feature.properties.description= rData.description } + if(typeof rData.attributes !== 'undefined') { + Object.assign(wpt.feature.properties, rData.attributes) + } + if(typeof rData.position === 'undefined') { return null } - if(!geolib.isValidCoordinate(rData.position)) { return null } + if(!isValidCoordinate(rData.position)) { return null } wpt.position= rData.position wpt.feature.geometry.coordinates= [rData.position.longitude, rData.position.latitude] @@ -97,7 +104,7 @@ const buildNote= (rData:any):any=> { && typeof rData.geohash === 'undefined') { return null } if(typeof rData.position !== 'undefined') { - if(!geolib.isValidCoordinate(rData.position)) { return null } + if(!isValidCoordinate(rData.position)) { return null } note.position= rData.position } if(typeof rData.region !== 'undefined') { @@ -136,6 +143,10 @@ const buildRegion= (rData:any):any=> { if(typeof rData.description !== 'undefined') { reg.feature.properties.description= rData.description } + if(typeof rData.attributes !== 'undefined') { + Object.assign(reg.feature.properties, rData.attributes) + } + if(typeof rData.points=== 'undefined' && rData.geohash=== 'undefined') { return null } if(typeof rData.geohash!== 'undefined') { reg.geohash= rData.geohash @@ -154,7 +165,7 @@ const buildRegion= (rData:any):any=> { if(!Array.isArray(rData.points)) { return null } let isValid:boolean= true; rData.points.forEach( (p:any)=> { - if(!geolib.isValidCoordinate(p)) { isValid= false } + if(!isValidCoordinate(p)) { isValid= false } }) if(!isValid) { return null } rData.points.forEach( (p:any)=> { diff --git a/src/api/resources/validate.ts b/src/api/resources/validate.ts index 341b7465f..840d944b2 100644 --- a/src/api/resources/validate.ts +++ b/src/api/resources/validate.ts @@ -1,4 +1,5 @@ import geoJSON from 'geojson-validation'; +import { isValidCoordinate } from 'geolib' export const validate= { resource: (type:string, value:any):boolean=> { @@ -30,9 +31,6 @@ export const validate= { // ** validate route data const validateRoute= (r:any):boolean=> { - //if(typeof r.name === 'undefined') { return false } - //if(typeof r.description === 'undefined') { return false } - if(typeof r.distance === 'undefined' || typeof r.distance !=='number') { return false } if(r.start) { let l= r.start.split('/') if(!validate.uuid(l[l.length-1])) { return false } @@ -54,12 +52,9 @@ const validateRoute= (r:any):boolean=> { // ** validate waypoint data const validateWaypoint= (r:any):boolean=> { if(typeof r.position === 'undefined') { return false } - if(typeof r.position.latitude === 'undefined' || typeof r.position.longitude === 'undefined') { + if(!isValidCoordinate(r.position)) { return false } - if(typeof r.position.latitude !== 'number' || typeof r.position.longitude !== 'number') { - return false - } try { if(!r.feature || !geoJSON.valid(r.feature)) { return false @@ -74,12 +69,9 @@ const validateWaypoint= (r:any):boolean=> { const validateNote= (r:any):boolean=> { if(!r.region && !r.position && !r.geohash ) { return false } if(typeof r.position!== 'undefined') { - if(typeof r.position.latitude === 'undefined' || typeof r.position.longitude === 'undefined') { + if(!isValidCoordinate(r.position)) { return false } - if(typeof r.position.latitude !== 'number' || typeof r.position.longitude !== 'number') { - return false - } } if(r.region) { let l= r.region.split('/') From 3b1d2280aa9edd5330223608560456ade5ce879e Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Thu, 11 Nov 2021 16:51:47 +1030 Subject: [PATCH 226/410] Added Resource_Provider documentation --- RESOURCE_PROVIDER_PLUGINS.md | 192 +++++++++++++++++++++++++++++++++++ SERVERPLUGINS.md | 2 + 2 files changed, 194 insertions(+) create mode 100644 RESOURCE_PROVIDER_PLUGINS.md diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md new file mode 100644 index 000000000..64ac8276a --- /dev/null +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -0,0 +1,192 @@ +# Resource Provider plugins + +## Overview + +This document should be read in conjunction with [SERVERPLUGINS.md](./SERVERPLUGINS.md) as it contains additional information regarding the development of plugins that facilitate the storage and retrieval of resource data (e.g. routes, waypoints, notes, regions and charts). + +The Signal K Node server will handle all requests to the following paths: + +`/signalk/v1/api/resources` +`/signalk/v1/api/resources/routes` +`/signalk/v1/api/resources/waypoints` +`/signalk/v1/api/resources/notes` +`/signalk/v1/api/resources/regions` +`/signalk/v1/api/resources/charts` + +This means all requests (GET, PUT, POST and DELETE) are captured and any supplied data is validated prior to being dispatched for processing. + +The server itself has no built-in functionality to save or retrieve resource data from storage, this is the responsibility of a `resource provider plugin`. + +If there are no registered providers for the resource type for which the request is made, then no action is taken. + +This architecture de-couples the resource storage from core server function to provide flexibility to implement the appropriate storage solution for the various resource types for your Signal K implementation. + +## Resource Providers + +A `resource provider plugin` is responsible for the storage and retrieval of resource data. +This allows the method for persisting resource data to be tailored to the Signal K implementation e.g. the local file system, a database service, cloud service, etc. + +It is similar to any other Server Plugin except that it implements the __resourceProvider__ interface. + +```JAVASCRIPT +resourceProvider: { + types: [], + methods: { + listResources: (type:string, query: {[key:string]:any})=> Promise + getResource: (type:string, id:string)=> Promise + setResource: (type:string, id:string, value:{[key:string]:any})=> Promise + deleteResource: (type:string, id:string)=> Promise + } +} +``` + +This interface exposes the following information to the server enabling it to direct requests to the plugin: +- `types`: The resource types for which requests should be directed to the plugin. These can be one or all of `routes, waypoints, notes, regions, charts`. +- `methods`: The methods to which the server dispatches requests. The plugin will implement these methods to perform the necessary save or retrieval operation. + +_Example: Plugin acting as resource provider for routes & waypoints._ +```JAVASCRIPT +let plugin= { + id: 'mypluginid', + name: 'My Resource Providerplugin', + start: (options, restart)=> { ... }, + stop: ()=> { ... }, + resourceProvider: { + types: ['routes','waypoints'], + methods: { + listResources: (type, params)=> { + return Promise.resolve() { ... }; + }, + getResource: (type:string, id:string)=> { + return Promise.resolve() { ... }; + } , + setResource: (type:string, id:string, value:any)=> { + return Promise.resolve() { ... }; + }, + deleteResource: (type:string, id:string)=> { + return Promise.resolve() { ... }; ; + } + } + } +} +``` + +### Methods: + +The Server will dispatch requests to `/signalk/v1/api/resources/` to the methods defined in the resourceProvider interface. + +Each method must have a signature as defined in the interface and return a `Promise` containing the resource data or `null` if the operation was unsuccessful. + +--- +`GET` requests that are not for a specific resource will be dispatched to the `listResources` method passing the resource type and query data as parameters. + +Returns: Object listing resources by id. + +_Example: List all routes._ +```JAVASCRIPT +GET /signalk/v1/api/resources/routes + +listResources('routes', {}) + +returns { + "resource_id1": { ... }, + "resource_id2": { ... }, + ... + "resource_idn": { ... } +} +``` + +_Example: List routes within the bounded area._ +```JAVASCRIPT +GET /signalk/v1/api/resources/routes?bbox=5.4,25.7,6.9,31.2 + +listResources('routes', {bbox: '5.4,25.7,6.9,31.2'}) + +returns { + "resource_id1": { ... }, + "resource_id2": { ... } +} +``` + +`GET` requests for a specific resource will be dispatched to the `getResource` method passing the resource type and id as parameters. + +Returns: Object containing resourcesdata. + +_Example: Retrieve route._ +```JAVASCRIPT +GET /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a + +getResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a') + +returns { + "name": "route name", + ... + "feature": { ... } +} +``` +--- + +`PUT` requests for a specific resource will be dispatched to the `setResource` method passing the resource type, id and resource data as parameters. + +Returns: `true` on success, `null` on failure. + +_Example: Update route data._ +```JAVASCRIPT +PUT /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a {resource data} + +setResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) +``` + +`POST` requests will be dispatched to the `setResource` method passing the resource type, a generated id and resource data as parameters. + +Returns: `true` on success, `null` on failure. + +_Example: New route._ +```JAVASCRIPT +POST /signalk/v1/api/resources/routes/ {resource data} + +setResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) +``` +--- + +`DELETE` requests for a specific resource will be dispatched to the `deleteResource` method passing the resource type and id as parameters. + +Returns: `true` on success, `null` on failure. + +_Example: Delete route._ +```JAVASCRIPT +DELETE /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a {resource data} + +deleteResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a') +``` +--- + +### Plugin Startup: + +If your plugin provides the ability for the user to choose which resource types are handled, then the plugin will need to notify the server that the `types` attribute of the `resourceProvider` interface has been modified. + +The server exposes `resourcesApi` which has the following method: +```JAVASCRIPT +checkForProviders(rescan:boolean) +``` +which can be called within the plugin `start()` function with `rescan= true`. + +This will cause the server to `rescan` for resource providers and register the new list of resource types being handled. + +_Example:_ +```JAVASCRIPT +module.exports = function (app) { + let plugin= { + id: 'mypluginid', + name: 'My Resource Providerplugin', + start: (options, restart)=> { + ... + setTimeout( ()=> { app.resourcesApi.checkForProviders(true) }, 1000) + ... + }, + stop: ()=> { ... }, + ... + } +} +``` + diff --git a/SERVERPLUGINS.md b/SERVERPLUGINS.md index 8e433b271..d623d6939 100644 --- a/SERVERPLUGINS.md +++ b/SERVERPLUGINS.md @@ -24,6 +24,8 @@ The plugin module must export a single `function(app)` that must return an objec To get started with SignalK plugin development, you can follow the following guide. +_Note: For plugins acting as a `provider` for one or more of the resource types listed in the Signal K specification (e.g. routes, waypoints, notes, regions or charts) there are additional requirementss which are detailed in __[RESOURCE_PROVIDER_PLUGINS.md](./RESOURCE_PROVIDER_PLUGINS.md)__._ + ### Project setup First, create a new directory and initialize a new module: From 25430a44c39b1503a98ddc740fe6a8a2043c1a1d Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 14 Nov 2021 19:32:38 +1030 Subject: [PATCH 227/410] Add register / unregister --- RESOURCE_PROVIDER_PLUGINS.md | 258 ++++++++++++++++++++++------------- SERVERPLUGINS.md | 37 ++++- src/api/resources/index.ts | 28 +++- 3 files changed, 221 insertions(+), 102 deletions(-) diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index 64ac8276a..4b80ba853 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -4,31 +4,34 @@ This document should be read in conjunction with [SERVERPLUGINS.md](./SERVERPLUGINS.md) as it contains additional information regarding the development of plugins that facilitate the storage and retrieval of resource data (e.g. routes, waypoints, notes, regions and charts). -The Signal K Node server will handle all requests to the following paths: +Resource storage and retrieval is de-coupled from core server function to provide the flexibility to implement the appropriate resource storage solution for specific Signal K implementations. -`/signalk/v1/api/resources` -`/signalk/v1/api/resources/routes` -`/signalk/v1/api/resources/waypoints` -`/signalk/v1/api/resources/notes` -`/signalk/v1/api/resources/regions` -`/signalk/v1/api/resources/charts` +The Signal K Node server will pass requests made to the following paths to registered resource providers: +- `/signalk/v1/api/resources` +- `/signalk/v1/api/resources/routes` +- `/signalk/v1/api/resources/waypoints` +- `/signalk/v1/api/resources/notes` +- `/signalk/v1/api/resources/regions` +- `/signalk/v1/api/resources/charts` -This means all requests (GET, PUT, POST and DELETE) are captured and any supplied data is validated prior to being dispatched for processing. +Resource providers will receive request data via a `ResourceProvider` interface which they implement. It is the responsibility of the resource provider to persist resource data in storage (PUT, POST), retrieve the requested resources (GET) and remove resource entries (DELETE). -The server itself has no built-in functionality to save or retrieve resource data from storage, this is the responsibility of a `resource provider plugin`. +Resource data passed to the resource provider plugin has been validated by the server and can be considered ready for storage. -If there are no registered providers for the resource type for which the request is made, then no action is taken. - -This architecture de-couples the resource storage from core server function to provide flexibility to implement the appropriate storage solution for the various resource types for your Signal K implementation. ## Resource Providers A `resource provider plugin` is responsible for the storage and retrieval of resource data. -This allows the method for persisting resource data to be tailored to the Signal K implementation e.g. the local file system, a database service, cloud service, etc. -It is similar to any other Server Plugin except that it implements the __resourceProvider__ interface. +It should implement the necessary functions to: +- Persist each resource with its associated id +- Retrieve an individual resource with the supplied id +- Retrieve a list of resources that match the supplied qery criteria. + +Data is passed to and from the plugin via the methods defined in the __resourceProvider__ interface which the plugin must implement. -```JAVASCRIPT +_Definition: `resourceProvider` interface._ +```javascript resourceProvider: { types: [], methods: { @@ -40,50 +43,130 @@ resourceProvider: { } ``` -This interface exposes the following information to the server enabling it to direct requests to the plugin: -- `types`: The resource types for which requests should be directed to the plugin. These can be one or all of `routes, waypoints, notes, regions, charts`. -- `methods`: The methods to which the server dispatches requests. The plugin will implement these methods to perform the necessary save or retrieval operation. +This interface is used by the server to direct requests to the plugin. + +It contains the following attributes: +- `types`: An array containing the names of the resource types the plugin is a provider for. Names of the resource types are: `routes, waypoints, notes, regions, charts`. + +- `methods`: The methods to which the server dispatches requests. The plugin will implement these methods to perform the necessary save or retrieval operation. Each method returns a promise containing either resource data or `null` if an error is encountered. _Example: Plugin acting as resource provider for routes & waypoints._ -```JAVASCRIPT -let plugin= { +```javascript +module.exports = function (app) { + let plugin= { + id: 'mypluginid', + name: 'My Resource Providerplugin', + resourceProvider: { + types: ['routes','waypoints'], + methods: { + listResources: (type, params)=> { + return Promise.resolve() { ... }; + }, + getResource: (type:string, id:string)=> { + return Promise.resolve() { ... }; + } , + setResource: (type:string, id:string, value:any)=> { + return Promise.resolve() { ... }; + }, + deleteResource: (type:string, id:string)=> { + return Promise.resolve() { ... }; ; + } + } + }, + start: (options, restart)=> { + ... + app.resourceApi.register(this.resourceProvider); + }, + stop: ()=> { + app.resourceApi.unRegister(this.resourceProvider.types); + ... + } + } +} +``` + +--- + +### Plugin Startup - Registering the Resource Provider: + +To register your plugin as a resource provider the server's `resourcesApi.register()` function should be called within the plugin `start()` function passing the `resourceProvider` interface. + +This registers the resource types and the methods with the server so they are called when requests to resource paths are made. + +_Example:_ +```javascript +module.exports = function (app) { + let plugin= { id: 'mypluginid', name: 'My Resource Providerplugin', - start: (options, restart)=> { ... }, - stop: ()=> { ... }, resourceProvider: { - types: ['routes','waypoints'], - methods: { - listResources: (type, params)=> { - return Promise.resolve() { ... }; - }, - getResource: (type:string, id:string)=> { - return Promise.resolve() { ... }; - } , - setResource: (type:string, id:string, value:any)=> { - return Promise.resolve() { ... }; - }, - deleteResource: (type:string, id:string)=> { - return Promise.resolve() { ... }; ; - } + types: ['routes','waypoints'], + methods: { + listResources: (type, params)=> { + return Promise.resolve() { ... }; + }, + getResource: (type:string, id:string)=> { + return Promise.resolve() { ... }; + } , + setResource: (type:string, id:string, value:any)=> { + return Promise.resolve() { ... }; + }, + deleteResource: (type:string, id:string)=> { + return Promise.resolve() { ... }; ; } + } } + } + + plugin.start = function(options) { + ... + app.resourcesApi.register(plugin.resourceProvider); + } } ``` +--- -### Methods: +### Plugin Stop - Un-registering the Resource Provider: -The Server will dispatch requests to `/signalk/v1/api/resources/` to the methods defined in the resourceProvider interface. +When a resource provider plugin is disabled / stopped it should un-register as a provider so resource requests are not directed to it. This is done by calling the server's `resourcesApi.unRegister()` function passing `resourceProvider.types` within the plugin's `stop()` function. -Each method must have a signature as defined in the interface and return a `Promise` containing the resource data or `null` if the operation was unsuccessful. +_Example:_ +```javascript +module.exports = function (app) { + let plugin= { + id: 'mypluginid', + name: 'My Resource Providerplugin', + resourceProvider: { + types: ['routes','waypoints'], + methods: { ... } + } + } + plugin.stop = function(options) { + ... + app.resourcesApi.unRegister(plugin.resourceProvider.types); + } +} +``` --- -`GET` requests that are not for a specific resource will be dispatched to the `listResources` method passing the resource type and query data as parameters. -Returns: Object listing resources by id. +### Operation: + +The Server will dispatch requests made to `/signalk/v1/api/resources/` to the plugin's `resourceProvider.methods` for each resource type listed in `resourceProvider.types`. + +Each method defined in the plugin must have a signature as specified in the interface. Each method returns a `Promise` containing the resource data or `null` if an error occurrred. + + +### __List Resources:__ + +`GET` requests that are not for a specific resource will be dispatched to the `listResources` method passing the resource type and any query data as parameters. + +It is the responsibility of the resource provider plugin to filter the resources returned as per the supplied query parameters. + +`listResources()` should return a JSON object listing resources by id. _Example: List all routes._ -```JAVASCRIPT +```javascript GET /signalk/v1/api/resources/routes listResources('routes', {}) @@ -96,11 +179,11 @@ returns { } ``` -_Example: List routes within the bounded area._ -```JAVASCRIPT -GET /signalk/v1/api/resources/routes?bbox=5.4,25.7,6.9,31.2 +_Example: List waypoints within the bounded area._ +```javascript +GET /signalk/v1/api/resources/waypoints?bbox=5.4,25.7,6.9,31.2 -listResources('routes', {bbox: '5.4,25.7,6.9,31.2'}) +listResources('waypoints', {bbox: '5.4,25.7,6.9,31.2'}) returns { "resource_id1": { ... }, @@ -108,85 +191,66 @@ returns { } ``` +### __Get specific resource:__ + `GET` requests for a specific resource will be dispatched to the `getResource` method passing the resource type and id as parameters. -Returns: Object containing resourcesdata. +`getResource()` should returns a JSON object containing the resource data. _Example: Retrieve route._ -```JAVASCRIPT +```javascript GET /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a getResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a') returns { - "name": "route name", - ... + "name": "Name of the route", + "description": "Description of the route", + "distance": 18345, "feature": { ... } } ``` ---- -`PUT` requests for a specific resource will be dispatched to the `setResource` method passing the resource type, id and resource data as parameters. +### __Saving Resources:__ -Returns: `true` on success, `null` on failure. +`PUT` requests to a path containing the resource id are used to store data associated with the resource id. These will be dispatched to the `setResource` method passing the resource type, id and data as parameters. -_Example: Update route data._ -```JAVASCRIPT -PUT /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a {resource data} +`setResource() ` returns `true` on success and `null` on failure. -setResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) -``` - -`POST` requests will be dispatched to the `setResource` method passing the resource type, a generated id and resource data as parameters. - -Returns: `true` on success, `null` on failure. +_Example: Update / add waypoint with the supplied id._ +```javascript +PUT /signalk/v1/api/resources/waypoints/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a {} -_Example: New route._ -```JAVASCRIPT -POST /signalk/v1/api/resources/routes/ {resource data} +setResource('waypoints', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) -setResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) +returns true | null ``` ---- -`DELETE` requests for a specific resource will be dispatched to the `deleteResource` method passing the resource type and id as parameters. +`POST` requests to a resource path that do not contina the resource id will be dispatched to the `setResource` method passing the resource type, an id (generated by the server) and resource data as parameters. -Returns: `true` on success, `null` on failure. +`setResource() ` returns `true` on success and `null` on failure. -_Example: Delete route._ -```JAVASCRIPT -DELETE /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a {resource data} +_Example: New route record._ +```javascript +POST /signalk/v1/api/resources/routes {} + +setResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) -deleteResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a') +returns true | null ``` ---- -### Plugin Startup: +### __Deleting Resources:__ -If your plugin provides the ability for the user to choose which resource types are handled, then the plugin will need to notify the server that the `types` attribute of the `resourceProvider` interface has been modified. +`DELETE` requests to a path containing the resource id will be dispatched to the `deleteResource` method passing the resource type and id as parameters. -The server exposes `resourcesApi` which has the following method: -```JAVASCRIPT -checkForProviders(rescan:boolean) -``` -which can be called within the plugin `start()` function with `rescan= true`. +`deleteResource()` returns `true` on success, `null` on failure. -This will cause the server to `rescan` for resource providers and register the new list of resource types being handled. +_Example: Delete region with supplied id._ +```javascript +DELETE /signalk/v1/api/resources/regions/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a -_Example:_ -```JAVASCRIPT -module.exports = function (app) { - let plugin= { - id: 'mypluginid', - name: 'My Resource Providerplugin', - start: (options, restart)=> { - ... - setTimeout( ()=> { app.resourcesApi.checkForProviders(true) }, 1000) - ... - }, - stop: ()=> { ... }, - ... - } -} +deleteResource('regions', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a') + +returns true | null ``` diff --git a/SERVERPLUGINS.md b/SERVERPLUGINS.md index d623d6939..ade261027 100644 --- a/SERVERPLUGINS.md +++ b/SERVERPLUGINS.md @@ -24,7 +24,7 @@ The plugin module must export a single `function(app)` that must return an objec To get started with SignalK plugin development, you can follow the following guide. -_Note: For plugins acting as a `provider` for one or more of the resource types listed in the Signal K specification (e.g. routes, waypoints, notes, regions or charts) there are additional requirementss which are detailed in __[RESOURCE_PROVIDER_PLUGINS.md](./RESOURCE_PROVIDER_PLUGINS.md)__._ +_Note: For plugins acting as a provider for one or more of the resource types listed in the Signal K specification (e.g. routes, waypoints, notes, regions or charts) refer to __[RESOURCE_PROVIDER_PLUGINS.md](./RESOURCE_PROVIDER_PLUGINS.md)__ for details. ### Project setup @@ -702,6 +702,41 @@ app.registerDeltaInputHandler((delta, next) => { }) ``` +### `app.resourcesApi.register(provider)` + +If a plugin wants to act as a resource provider, it will need to register its provider methods during startup using this function. + +See [`RESOURCE_PROVIDER_PLUGINS.md`](./RESOURCE_PROVIDER_PLUGINS.md) for details. + + +```javascript +plugin.start = function(options) { + ... + // plugin_provider is the plugin's `ResourceProvider` interface. + app.resourcesApi.register(plugin_provider); +} + +``` + + + +### `app.resourcesApi.unRegister(resource_types)` + +When a resource provider plugin is disabled it will need to un-register its provider methods for the resource types it manages. This should be done in the plugin's `stop()` function. + +See [`RESOURCE_PROVIDER_PLUGINS.md`](./RESOURCE_PROVIDER_PLUGINS.md) for details. + + +```javascript +plugin.stop = function(options) { + // resource_types example: ['routes',waypoints'] + app.resourcesApi.unRegister(resource_types); + ... +} + +``` + + ### `app.setPluginStatus(msg)` Set the current status of the plugin. The `msg` should be a short message describing the current status of the plugin and will be displayed in the plugin configuration UI and the Dashboard. diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 8d453cbc9..4375f2301 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -55,9 +55,32 @@ export class Resources { this.initResourceRoutes() } + public register(provider:any) { + debug(`** Registering provider(s)....${provider?.types}`) + if(!provider ) { return } + if(provider.types && !Array.isArray(provider.types)) { return } + provider.types.forEach( (i:string)=>{ + if(!this.resProvider[i]) { + this.resProvider[i]= provider.methods + } + }) + debug(this.resProvider) + } + + public unRegister(resourceTypes:string[]) { + debug(`** Un-registering provider(s)....${resourceTypes}`) + if(!Array.isArray(resourceTypes)) { return } + resourceTypes.forEach( (i:string)=>{ + if(this.resProvider[i]) { + delete this.resProvider[i] + } + }) + debug(JSON.stringify(this.resProvider)) + } + public checkForProviders(rescan:boolean= false) { if(rescan || Object.keys(this.resProvider).length===0) { - debug('** Checking for providers....') + debug(`** Checking for providers....(rescan=${rescan})`) this.resProvider= {} this.resourceTypes.forEach( (rt:string)=> { this.resProvider[rt]= this.getResourceProviderFor(rt) @@ -68,7 +91,6 @@ export class Resources { public getResource(type:string, id:string) { debug(`** getResource(${type}, ${id})`) - this.checkForProviders() return this.actionResourceRequest({ method: 'GET', body: {}, @@ -82,7 +104,6 @@ export class Resources { // ** initialise handler for in-scope resource types ** private initResourceRoutes() { this.server.get(`${SIGNALK_API_PATH}/resources`, (req:any, res:any) => { - this.checkForProviders() // list all serviced paths under resources res.json(this.getResourcePaths()) }) @@ -155,7 +176,6 @@ export class Resources { } } - this.checkForProviders() let retReq= { method: req.method, body: req.body, From 6f3cd49037912f6fbf211b2f9025f09e07cf5763 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 15 Nov 2021 10:10:21 +1030 Subject: [PATCH 228/410] add constructor --- src/api/resources/index.ts | 67 +++++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 22 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 4375f2301..fba887dc8 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -15,6 +15,11 @@ interface ResourceRequest { } interface ResourceProvider { + types: Array + methods: ResourceProviderMethods +} + +interface ResourceProviderMethods { listResources: (type:string, query: {[key:string]:any})=> Promise getResource: (type:string, id:string)=> Promise setResource: (type:string, id:string, value:{[key:string]:any})=> Promise @@ -46,16 +51,22 @@ export class Resources { 'charts' ] - resProvider: {[key:string]: any}= {} + resProvider: {[key:string]: ResourceProviderMethods | null}= {} server: any - public start(app:any) { + constructor(app:any) { + this.start(app) + } + + // ** initialise resourcesApi ** + private start(app:any) { debug(`** Initialise ${SIGNALK_API_PATH}/resources path handler **`) this.server= app this.initResourceRoutes() } - public register(provider:any) { + // ** register resource provider ** + public register(provider:ResourceProvider) { debug(`** Registering provider(s)....${provider?.types}`) if(!provider ) { return } if(provider.types && !Array.isArray(provider.types)) { return } @@ -67,6 +78,7 @@ export class Resources { debug(this.resProvider) } + // ** un-register resource provider for the supplied types ** public unRegister(resourceTypes:string[]) { debug(`** Un-registering provider(s)....${resourceTypes}`) if(!Array.isArray(resourceTypes)) { return } @@ -76,19 +88,15 @@ export class Resources { } }) debug(JSON.stringify(this.resProvider)) - } - public checkForProviders(rescan:boolean= false) { - if(rescan || Object.keys(this.resProvider).length===0) { - debug(`** Checking for providers....(rescan=${rescan})`) - this.resProvider= {} - this.resourceTypes.forEach( (rt:string)=> { - this.resProvider[rt]= this.getResourceProviderFor(rt) - }) - debug(this.resProvider) - } + /** scan plugins in case there is more than one plugin that can service + * a particular resource type. **/ + debug('** RESCANNING **') + this.checkForProviders() + debug(JSON.stringify(this.resProvider)) } + // ** return resource with supplied type and id ** public getResource(type:string, id:string) { debug(`** getResource(${type}, ${id})`) return this.actionResourceRequest({ @@ -100,6 +108,20 @@ export class Resources { }) } + /** Scan plugins for resource providers and register them + * rescan= false: only add providers for types where no provider is registered + * rescan= true: clear providers for all types prior to commencing scan. + **/ + private checkForProviders(rescan:boolean= false) { + if(rescan) { this.resProvider= {} } + debug(`** Checking for providers....(rescan=${rescan})`) + this.resProvider= {} + this.resourceTypes.forEach( (rt:string)=> { + this.resProvider[rt]= this.getResourceProviderFor(rt) + }) + debug(this.resProvider) + + } // ** initialise handler for in-scope resource types ** private initResourceRoutes() { @@ -127,7 +149,7 @@ export class Resources { }) } - // ** return all paths serviced under ./resources *8 + // ** return all paths serviced under SIGNALK_API_PATH/resources ** private getResourcePaths(): {[key:string]:any} { let resPaths:{[key:string]:any}= {} Object.entries(this.resProvider).forEach( (p:any)=> { @@ -241,7 +263,7 @@ export class Resources { if(req.method==='GET') { let retVal: any if(!req.resourceId) { - retVal= await this.resProvider[req.resourceType].listResources(req.resourceType, req.query) + retVal= await this.resProvider[req.resourceType]?.listResources(req.resourceType, req.query) return (retVal) ? retVal : {statusCode: 404, message: `Error retrieving resources!` } @@ -249,7 +271,7 @@ export class Resources { if(!validate.uuid(req.resourceId)) { return {statusCode: 406, message: `Invalid resource id provided (${req.resourceId})` } } - retVal= await this.resProvider[req.resourceType].getResource(req.resourceType, req.resourceId) + retVal= await this.resProvider[req.resourceType]?.getResource(req.resourceType, req.resourceId) return (retVal) ? retVal : {statusCode: 404, message: `Resource not found (${req.resourceId})!` } @@ -266,7 +288,7 @@ export class Resources { req.method==='DELETE' || (req.method==='PUT' && typeof req.body.value!=='undefined' && req.body.value==null) ) { - let retVal= await this.resProvider[req.resourceType].deleteResource(req.resourceType, req.resourceId) + let retVal= await this.resProvider[req.resourceType]?.deleteResource(req.resourceType, req.resourceId) if(retVal){ this.sendDelta(req.resourceType, req.resourceId, null) return {statusCode: 200, message: `Resource (${req.resourceId}) deleted.`} @@ -289,7 +311,7 @@ export class Resources { } if(req.method==='POST') { let id= UUID_PREFIX + uuidv4() - let retVal= await this.resProvider[req.resourceType].setResource(req.resourceType, id, req.body.value) + let retVal= await this.resProvider[req.resourceType]?.setResource(req.resourceType, id, req.body.value) if(retVal){ this.sendDelta(req.resourceType, id, req.body.value) return {statusCode: 200, message: `Resource (${id}) saved.`} @@ -302,7 +324,7 @@ export class Resources { if(!req.resourceId) { return {statusCode: 406, message: `No resource id provided!` } } - let retVal= await this.resProvider[req.resourceType].setResource(req.resourceType, req.resourceId, req.body.value) + let retVal= await this.resProvider[req.resourceType]?.setResource(req.resourceType, req.resourceId, req.body.value) if(retVal){ this.sendDelta(req.resourceType, req.resourceId, req.body.value) return {statusCode: 200, message: `Resource (${req.resourceId}) updated.`} @@ -314,6 +336,7 @@ export class Resources { } } + // ** send delta message with resource PUT, POST, DELETE action result private sendDelta(type:string, id:string, value:any):void { debug(`** Sending Delta: resources.${type}.${id}`) this.server.handleMessage('signalk-resources', { @@ -330,10 +353,10 @@ export class Resources { }) } - // ** get reference to installed resource provider (plug-in). returns null if none found - private getResourceProviderFor(resType:string): ResourceProvider | null { + // ** Get provider methods for supplied resource type. Returns null if none found ** + private getResourceProviderFor(resType:string): ResourceProviderMethods | null { if(!this.server.plugins) { return null} - let pSource: ResourceProvider | null= null + let pSource: ResourceProviderMethods | null= null this.server.plugins.forEach((plugin:any)=> { if(typeof plugin.resourceProvider !== 'undefined') { pSource= plugin.resourceProvider.types.includes(resType) ? From d05dd7d2bf8739ab0272ed0b5045b7ded7c017aa Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 15 Nov 2021 10:10:47 +1030 Subject: [PATCH 229/410] add getResource function --- SERVERPLUGINS.md | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/SERVERPLUGINS.md b/SERVERPLUGINS.md index ade261027..a724cc2a7 100644 --- a/SERVERPLUGINS.md +++ b/SERVERPLUGINS.md @@ -702,6 +702,30 @@ app.registerDeltaInputHandler((delta, next) => { }) ``` +### `app.resourcesApi.getResource(resource_type, resource_id)` + +Retrieve resource data for the supplied resource type and id. + + data for the full path of the directory where the plugin can persist its internal data, like data files.If a plugin wants to act as a resource provider, it will need to register its provider methods during startup using this function. + + + +```javascript +let myRoute= app.resourcesApi.getResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a'); +``` +Will return the route resource data or `null` if a route with the supplied id cannot be found. + +_Example:_ +```json +{ + "name": "Name of the route", + "description": "Description of the route", + "distance": 18345, + "feature": { ... } +} + +``` + ### `app.resourcesApi.register(provider)` If a plugin wants to act as a resource provider, it will need to register its provider methods during startup using this function. @@ -718,8 +742,6 @@ plugin.start = function(options) { ``` - - ### `app.resourcesApi.unRegister(resource_types)` When a resource provider plugin is disabled it will need to un-register its provider methods for the resource types it manages. This should be done in the plugin's `stop()` function. From f35ca632b54662dc635a59d1ebe2053aa6788514 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 15 Nov 2021 10:31:19 +1030 Subject: [PATCH 230/410] chore: fix formatting --- src/api/resources/index.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index fba887dc8..05c05761b 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -89,8 +89,7 @@ export class Resources { }) debug(JSON.stringify(this.resProvider)) - /** scan plugins in case there is more than one plugin that can service - * a particular resource type. **/ + //** scan plugins in case there is more than one plugin that can service a particular resource type. ** debug('** RESCANNING **') this.checkForProviders() debug(JSON.stringify(this.resProvider)) @@ -108,10 +107,9 @@ export class Resources { }) } - /** Scan plugins for resource providers and register them - * rescan= false: only add providers for types where no provider is registered - * rescan= true: clear providers for all types prior to commencing scan. - **/ + // Scan plugins for resource providers and register them + // rescan= false: only add providers for types where no provider is registered + // rescan= true: clear providers for all types prior to commencing scan. private checkForProviders(rescan:boolean= false) { if(rescan) { this.resProvider= {} } debug(`** Checking for providers....(rescan=${rescan})`) From 3cdf6e5bb6511f057a2383e04f17f88e73edc13b Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 15 Nov 2021 10:41:56 +1030 Subject: [PATCH 231/410] OpenApi descriptions --- src/api/resources/openApi.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/api/resources/openApi.json b/src/api/resources/openApi.json index d7a7f6e35..8eddcac32 100644 --- a/src/api/resources/openApi.json +++ b/src/api/resources/openApi.json @@ -29,15 +29,15 @@ } }, - "/resources/{resourceClass}": { + "/resources/{resourceType}": { "get": { "tags": ["resources"], "summary": "Retrieve resources", "parameters": [ { - "name": "resourceClass", + "name": "resourceType", "in": "path", - "description": "resource class", + "description": "Type of resources to retrieve. Valid values are: routes, waypoints, notes, regions, charts", "required": true, "schema": { "type": "string", @@ -68,8 +68,8 @@ } }, { - "in": "query", "name": "bbox", + "in": "query", "description": "Limit results to resources that fall within the bounded area defined as lower left (south west) and upper right (north east) coordinates [swlon,swlat,nelon,nelat]", "style": "form", "explode": false, From 55cc8b7b5dc40445d47a6b190649316732fd4e99 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 20 Nov 2021 13:46:56 +1030 Subject: [PATCH 232/410] add pluginId to register() function --- RESOURCE_PROVIDER_PLUGINS.md | 4 ++-- SERVERPLUGINS.md | 22 +++++++++++++++------- src/api/resources/index.ts | 4 +++- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index 4b80ba853..24331b387 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -75,7 +75,7 @@ module.exports = function (app) { }, start: (options, restart)=> { ... - app.resourceApi.register(this.resourceProvider); + app.resourceApi.register(this.id, this.resourceProvider); }, stop: ()=> { app.resourceApi.unRegister(this.resourceProvider.types); @@ -120,7 +120,7 @@ module.exports = function (app) { plugin.start = function(options) { ... - app.resourcesApi.register(plugin.resourceProvider); + app.resourcesApi.register(plugin.id, plugin.resourceProvider); } } ``` diff --git a/SERVERPLUGINS.md b/SERVERPLUGINS.md index a724cc2a7..d9214f8ee 100644 --- a/SERVERPLUGINS.md +++ b/SERVERPLUGINS.md @@ -726,7 +726,7 @@ _Example:_ ``` -### `app.resourcesApi.register(provider)` +### `app.resourcesApi.register(pluginId, provider)` If a plugin wants to act as a resource provider, it will need to register its provider methods during startup using this function. @@ -734,12 +734,20 @@ See [`RESOURCE_PROVIDER_PLUGINS.md`](./RESOURCE_PROVIDER_PLUGINS.md) for details ```javascript -plugin.start = function(options) { - ... - // plugin_provider is the plugin's `ResourceProvider` interface. - app.resourcesApi.register(plugin_provider); -} - +module.exports = function (app) { + let plugin= { + id: 'mypluginid', + name: 'My Resource Providerplugin', + resourceProvider: { + types: ['routes','waypoints'], + methods: { ... } + } + start: function(options) { + ... + app.resourcesApi.register(this.id, this.resourceProvider); + } + ... + } ``` ### `app.resourcesApi.unRegister(resource_types)` diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 05c05761b..24568de8e 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -20,6 +20,7 @@ interface ResourceProvider { } interface ResourceProviderMethods { + pluginId: string listResources: (type:string, query: {[key:string]:any})=> Promise getResource: (type:string, id:string)=> Promise setResource: (type:string, id:string, value:{[key:string]:any})=> Promise @@ -66,12 +67,13 @@ export class Resources { } // ** register resource provider ** - public register(provider:ResourceProvider) { + public register(pluginId:string, provider:ResourceProvider) { debug(`** Registering provider(s)....${provider?.types}`) if(!provider ) { return } if(provider.types && !Array.isArray(provider.types)) { return } provider.types.forEach( (i:string)=>{ if(!this.resProvider[i]) { + provider.methods.pluginId= pluginId this.resProvider[i]= provider.methods } }) From f926ad5c7d449a1c20a9247c21847b4097169e7a Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 20 Nov 2021 14:17:32 +1030 Subject: [PATCH 233/410] add pluginId to unRegister function --- RESOURCE_PROVIDER_PLUGINS.md | 10 ++++----- SERVERPLUGINS.md | 24 +++++++++++++++------- src/api/resources/index.ts | 40 +++--------------------------------- 3 files changed, 25 insertions(+), 49 deletions(-) diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index 24331b387..e90b45d08 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -73,12 +73,12 @@ module.exports = function (app) { } } }, - start: (options, restart)=> { + start: (options)=> { ... app.resourceApi.register(this.id, this.resourceProvider); }, stop: ()=> { - app.resourceApi.unRegister(this.resourceProvider.types); + app.resourceApi.unRegister(this.id, this.resourceProvider.types); ... } } @@ -89,7 +89,7 @@ module.exports = function (app) { ### Plugin Startup - Registering the Resource Provider: -To register your plugin as a resource provider the server's `resourcesApi.register()` function should be called within the plugin `start()` function passing the `resourceProvider` interface. +To register your plugin as a resource provider the server's `resourcesApi.register()` function should be called within the plugin `start()` function passing the `resourceProvider` interface. This registers the resource types and the methods with the server so they are called when requests to resource paths are made. @@ -128,7 +128,7 @@ module.exports = function (app) { ### Plugin Stop - Un-registering the Resource Provider: -When a resource provider plugin is disabled / stopped it should un-register as a provider so resource requests are not directed to it. This is done by calling the server's `resourcesApi.unRegister()` function passing `resourceProvider.types` within the plugin's `stop()` function. +When a resource provider plugin is disabled it should un-register as a provider so resource requests are not directed to it. This is done by calling the server's `resourcesApi.unRegister()` function passing `resourceProvider.types` within the plugin's `stop()` function. _Example:_ ```javascript @@ -144,7 +144,7 @@ module.exports = function (app) { plugin.stop = function(options) { ... - app.resourcesApi.unRegister(plugin.resourceProvider.types); + app.resourcesApi.unRegister(plugin.id, plugin.resourceProvider.types); } } ``` diff --git a/SERVERPLUGINS.md b/SERVERPLUGINS.md index d9214f8ee..2a671e6ad 100644 --- a/SERVERPLUGINS.md +++ b/SERVERPLUGINS.md @@ -748,22 +748,32 @@ module.exports = function (app) { } ... } +} ``` -### `app.resourcesApi.unRegister(resource_types)` +### `app.resourcesApi.unRegister(pluginId, resource_types)` -When a resource provider plugin is disabled it will need to un-register its provider methods for the resource types it manages. This should be done in the plugin's `stop()` function. +When a resource provider plugin is disabled it will need to un-register its provider methods for all of the resource types it manages. This should be done in the plugin's `stop()` function. See [`RESOURCE_PROVIDER_PLUGINS.md`](./RESOURCE_PROVIDER_PLUGINS.md) for details. ```javascript -plugin.stop = function(options) { - // resource_types example: ['routes',waypoints'] - app.resourcesApi.unRegister(resource_types); - ... +module.exports = function (app) { + let plugin= { + id: 'mypluginid', + name: 'My Resource Providerplugin', + resourceProvider: { + types: ['routes','waypoints'], + methods: { ... } + } + ... + stop: function(options) { + app.resourcesApi.unRegister(this.id, this.resourceProvider.types); + ... + } + } } - ``` diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 24568de8e..ae2bb68c0 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -81,20 +81,15 @@ export class Resources { } // ** un-register resource provider for the supplied types ** - public unRegister(resourceTypes:string[]) { - debug(`** Un-registering provider(s)....${resourceTypes}`) + public unRegister(pluginId:string, resourceTypes:string[]) { + debug(`** Un-registering ${pluginId} provider(s)....${resourceTypes}`) if(!Array.isArray(resourceTypes)) { return } resourceTypes.forEach( (i:string)=>{ - if(this.resProvider[i]) { + if(this.resProvider[i] && this.resProvider[i]?.pluginId===pluginId) { delete this.resProvider[i] } }) debug(JSON.stringify(this.resProvider)) - - //** scan plugins in case there is more than one plugin that can service a particular resource type. ** - debug('** RESCANNING **') - this.checkForProviders() - debug(JSON.stringify(this.resProvider)) } // ** return resource with supplied type and id ** @@ -109,20 +104,6 @@ export class Resources { }) } - // Scan plugins for resource providers and register them - // rescan= false: only add providers for types where no provider is registered - // rescan= true: clear providers for all types prior to commencing scan. - private checkForProviders(rescan:boolean= false) { - if(rescan) { this.resProvider= {} } - debug(`** Checking for providers....(rescan=${rescan})`) - this.resProvider= {} - this.resourceTypes.forEach( (rt:string)=> { - this.resProvider[rt]= this.getResourceProviderFor(rt) - }) - debug(this.resProvider) - - } - // ** initialise handler for in-scope resource types ** private initResourceRoutes() { this.server.get(`${SIGNALK_API_PATH}/resources`, (req:any, res:any) => { @@ -353,19 +334,4 @@ export class Resources { }) } - // ** Get provider methods for supplied resource type. Returns null if none found ** - private getResourceProviderFor(resType:string): ResourceProviderMethods | null { - if(!this.server.plugins) { return null} - let pSource: ResourceProviderMethods | null= null - this.server.plugins.forEach((plugin:any)=> { - if(typeof plugin.resourceProvider !== 'undefined') { - pSource= plugin.resourceProvider.types.includes(resType) ? - plugin.resourceProvider.methods : - null - } - }) - debug(`** Checking for ${resType} provider.... ${pSource ? 'Found' : 'Not found'}`) - return pSource - } - } From 07059c5297dc8442a5eb2ba101d8ec751f4c7433 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 20 Nov 2021 14:25:51 +1030 Subject: [PATCH 234/410] set plugin id as delta source --- src/api/resources/index.ts | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index ae2bb68c0..3238001f7 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -271,7 +271,11 @@ export class Resources { ) { let retVal= await this.resProvider[req.resourceType]?.deleteResource(req.resourceType, req.resourceId) if(retVal){ - this.sendDelta(req.resourceType, req.resourceId, null) + this.sendDelta( + this.resProvider[req.resourceType]?.pluginId as string, + req.resourceType, req.resourceId, + null + ) return {statusCode: 200, message: `Resource (${req.resourceId}) deleted.`} } else { @@ -294,7 +298,12 @@ export class Resources { let id= UUID_PREFIX + uuidv4() let retVal= await this.resProvider[req.resourceType]?.setResource(req.resourceType, id, req.body.value) if(retVal){ - this.sendDelta(req.resourceType, id, req.body.value) + this.sendDelta( + this.resProvider[req.resourceType]?.pluginId as string, + req.resourceType, + id, + req.body.value + ) return {statusCode: 200, message: `Resource (${id}) saved.`} } else { @@ -307,7 +316,12 @@ export class Resources { } let retVal= await this.resProvider[req.resourceType]?.setResource(req.resourceType, req.resourceId, req.body.value) if(retVal){ - this.sendDelta(req.resourceType, req.resourceId, req.body.value) + this.sendDelta( + this.resProvider[req.resourceType]?.pluginId as string, + req.resourceType, + req.resourceId, + req.body.value + ) return {statusCode: 200, message: `Resource (${req.resourceId}) updated.`} } else { @@ -318,9 +332,9 @@ export class Resources { } // ** send delta message with resource PUT, POST, DELETE action result - private sendDelta(type:string, id:string, value:any):void { + private sendDelta(providerId:string, type:string, id:string, value:any):void { debug(`** Sending Delta: resources.${type}.${id}`) - this.server.handleMessage('signalk-resources', { + this.server.handleMessage(providerId, { updates: [ { values: [ From e006b4f3aa7f5fe4d97a0c3f4d8caf39db3401b8 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 20 Nov 2021 14:59:03 +1030 Subject: [PATCH 235/410] unregister only requires plugin id --- src/api/resources/index.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 3238001f7..9ce3cc88b 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -81,14 +81,15 @@ export class Resources { } // ** un-register resource provider for the supplied types ** - public unRegister(pluginId:string, resourceTypes:string[]) { - debug(`** Un-registering ${pluginId} provider(s)....${resourceTypes}`) - if(!Array.isArray(resourceTypes)) { return } - resourceTypes.forEach( (i:string)=>{ - if(this.resProvider[i] && this.resProvider[i]?.pluginId===pluginId) { + public unRegister(pluginId:string) { + if(!pluginId) { return } + debug(`** Un-registering ${pluginId} resource provider(s)....`) + for( let i in this.resProvider ) { + if(this.resProvider[i]?.pluginId===pluginId) { + debug(`** Un-registering ${i}....`) delete this.resProvider[i] } - }) + } debug(JSON.stringify(this.resProvider)) } From 2867ca7c5dd9f359fda25c5dad61e905361bf46e Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 20 Nov 2021 14:59:16 +1030 Subject: [PATCH 236/410] update docs --- RESOURCE_PROVIDER_PLUGINS.md | 26 ++++++++++++++++---------- SERVERPLUGINS.md | 6 +++--- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index e90b45d08..a5b73fdf8 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -78,7 +78,7 @@ module.exports = function (app) { app.resourceApi.register(this.id, this.resourceProvider); }, stop: ()=> { - app.resourceApi.unRegister(this.id, this.resourceProvider.types); + app.resourceApi.unRegister(this.id); ... } } @@ -87,7 +87,7 @@ module.exports = function (app) { --- -### Plugin Startup - Registering the Resource Provider: +## Plugin Startup - Registering the Resource Provider: To register your plugin as a resource provider the server's `resourcesApi.register()` function should be called within the plugin `start()` function passing the `resourceProvider` interface. @@ -126,9 +126,9 @@ module.exports = function (app) { ``` --- -### Plugin Stop - Un-registering the Resource Provider: +## Plugin Stop - Un-registering the Resource Provider: -When a resource provider plugin is disabled it should un-register as a provider so resource requests are not directed to it. This is done by calling the server's `resourcesApi.unRegister()` function passing `resourceProvider.types` within the plugin's `stop()` function. +When a resource provider plugin is disabled it should un-register as a provider so resource requests are not directed to it. This is done by calling the server's `resourcesApi.unRegister()` function passing the `plugin.id` within the plugin's `stop()` function. _Example:_ ```javascript @@ -137,24 +137,30 @@ module.exports = function (app) { id: 'mypluginid', name: 'My Resource Providerplugin', resourceProvider: { - types: ['routes','waypoints'], + types: [ ... ], methods: { ... } } } plugin.stop = function(options) { + app.resourcesApi.unRegister(plugin.id); ... - app.resourcesApi.unRegister(plugin.id, plugin.resourceProvider.types); } } ``` --- -### Operation: +## Operation: + +The Server will dispatch requests made to: +- `/signalk/v1/api/resources/` + +OR +- the `resources API` endpoints -The Server will dispatch requests made to `/signalk/v1/api/resources/` to the plugin's `resourceProvider.methods` for each resource type listed in `resourceProvider.types`. +to the plugin's `resourceProvider.methods` for each of the resource types listed in `resourceProvider.types`. -Each method defined in the plugin must have a signature as specified in the interface. Each method returns a `Promise` containing the resource data or `null` if an error occurrred. +Each method defined in `resourceProvider.methods` must have a signature as specified in the __resourceProvider interface__. Each method returns a `Promise` containing the resultant resource data or `null` if an error occurrred or the operation is incomplete. ### __List Resources:__ @@ -191,7 +197,7 @@ returns { } ``` -### __Get specific resource:__ +### __Retrieve a specific resource:__ `GET` requests for a specific resource will be dispatched to the `getResource` method passing the resource type and id as parameters. diff --git a/SERVERPLUGINS.md b/SERVERPLUGINS.md index 2a671e6ad..75bbeb535 100644 --- a/SERVERPLUGINS.md +++ b/SERVERPLUGINS.md @@ -726,7 +726,7 @@ _Example:_ ``` -### `app.resourcesApi.register(pluginId, provider)` +### `app.resourcesApi.register(pluginId, resourceProvider)` If a plugin wants to act as a resource provider, it will need to register its provider methods during startup using this function. @@ -751,7 +751,7 @@ module.exports = function (app) { } ``` -### `app.resourcesApi.unRegister(pluginId, resource_types)` +### `app.resourcesApi.unRegister(pluginId)` When a resource provider plugin is disabled it will need to un-register its provider methods for all of the resource types it manages. This should be done in the plugin's `stop()` function. @@ -769,7 +769,7 @@ module.exports = function (app) { } ... stop: function(options) { - app.resourcesApi.unRegister(this.id, this.resourceProvider.types); + app.resourcesApi.unRegister(this.id); ... } } From cb9d5f990312fb0074127c34776ce171a1450062 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 21 Nov 2021 11:26:43 +1030 Subject: [PATCH 237/410] add resource attribute req to query object --- src/api/resources/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 9ce3cc88b..9aaf0b86d 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -161,8 +161,12 @@ export class Resources { let p= req.params[0].split('/') let resType= (typeof req.params[0]!=='undefined') ? p[0] : '' let resId= p.length>1 ? p[1] : '' + let resAttrib= p.length>2 ? p.slice(2) : [] + req.query.resAttrib= resAttrib debug('** resType:', resType) debug('** resId:', resId) + debug('** resAttrib:', resAttrib) + debug('** req.params + attribs:', req.query) let apiMethod= (API_METHODS.includes(resType)) ? resType : null if(apiMethod) { From dfd8be1d5294695716be2e0043c543f4c92586d1 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 21 Nov 2021 11:45:14 +1030 Subject: [PATCH 238/410] chore: update docs with query object examples. --- RESOURCE_PROVIDER_PLUGINS.md | 41 ++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index a5b73fdf8..2d20f1689 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -167,6 +167,16 @@ Each method defined in `resourceProvider.methods` must have a signature as speci `GET` requests that are not for a specific resource will be dispatched to the `listResources` method passing the resource type and any query data as parameters. +Query parameters are passed as an object conatining `key | value` pairs. + +_Example: GET /signalk/v1/api/resources/waypoints?bbox=5.4,25.7,6.9,31.2&distance=30000_ +```javascript +query= { + bbox: '5.4,25.7,6.9,31.2', + distance: 30000 +} +``` + It is the responsibility of the resource provider plugin to filter the resources returned as per the supplied query parameters. `listResources()` should return a JSON object listing resources by id. @@ -207,9 +217,16 @@ _Example: Retrieve route._ ```javascript GET /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a -getResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a') +getResource( + 'routes', + 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', + {} +) +``` -returns { +_Returns the result:_ +```json +{ "name": "Name of the route", "description": "Description of the route", "distance": 18345, @@ -217,6 +234,26 @@ returns { } ``` +A request for a resource attribute will pass the attibute path as an array in the query object with the key `resAttrib`. + +_Example: Get waypoint geometry._ +```javascript +GET /signalk/v1/api/resources/waypoints/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a/feature/geometry + +getResource( + 'waypoints', + 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', + { resAttrib: ['feature','geometry'] } +) +``` +_Returns the value of `geometry` attribute of the waypoint._ +```json +{ + "type": "Point", + "coordinates": [70.4,6.45] +} +``` + ### __Saving Resources:__ `PUT` requests to a path containing the resource id are used to store data associated with the resource id. These will be dispatched to the `setResource` method passing the resource type, id and data as parameters. From bdf213d23ee6a84171a1781948f5fff97572fa4c Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 21 Nov 2021 15:40:58 +1030 Subject: [PATCH 239/410] chore: linted --- src/api/resources/index.ts | 662 ++++++++++++++++++--------------- src/api/resources/resources.ts | 344 +++++++++-------- src/api/resources/validate.ts | 173 +++++---- src/put.js | 4 +- 4 files changed, 652 insertions(+), 531 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 9aaf0b86d..7afc84dc6 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -1,356 +1,412 @@ import Debug from 'debug' import { v4 as uuidv4 } from 'uuid' -import { validate } from './validate' import { buildResource } from './resources' +import { validate } from './validate' const debug = Debug('signalk:resources') interface ResourceRequest { - method: 'GET' | 'PUT' | 'POST' | 'DELETE' - body: any - query: {[key:string]: any} - resourceType: string - resourceId: string, - apiMethod?: string | null + method: 'GET' | 'PUT' | 'POST' | 'DELETE' + body: any + query: { [key: string]: any } + resourceType: string + resourceId: string + apiMethod?: string | null } interface ResourceProvider { - types: Array - methods: ResourceProviderMethods + types: string[] + methods: ResourceProviderMethods } interface ResourceProviderMethods { - pluginId: string - listResources: (type:string, query: {[key:string]:any})=> Promise - getResource: (type:string, id:string)=> Promise - setResource: (type:string, id:string, value:{[key:string]:any})=> Promise - deleteResource: (type:string, id:string)=> Promise + pluginId: string + listResources: (type: string, query: { [key: string]: any }) => Promise + getResource: (type: string, id: string) => Promise + setResource: ( + type: string, + id: string, + value: { [key: string]: any } + ) => Promise + deleteResource: (type: string, id: string) => Promise } -const SIGNALK_API_PATH= `/signalk/v1/api` -const UUID_PREFIX= 'urn:mrn:signalk:uuid:' +const SIGNALK_API_PATH = `/signalk/v1/api` +const UUID_PREFIX = 'urn:mrn:signalk:uuid:' -const API_METHODS= [ - 'setWaypoint', - 'deleteWaypoint', - 'setRoute', - 'deleteRoute', - 'setNote', - 'deleteNote', - 'setRegion', - 'deleteRegion' +const API_METHODS = [ + 'setWaypoint', + 'deleteWaypoint', + 'setRoute', + 'deleteRoute', + 'setNote', + 'deleteNote', + 'setRegion', + 'deleteRegion' ] export class Resources { + resProvider: { [key: string]: ResourceProviderMethods | null } = {} + server: any - // ** in-scope resource types ** - private resourceTypes:Array= [ - 'routes', - 'waypoints', - 'notes', - 'regions', - 'charts' - ] + // ** in-scope resource types ** + private resourceTypes: string[] = [ + 'routes', + 'waypoints', + 'notes', + 'regions', + 'charts' + ] - resProvider: {[key:string]: ResourceProviderMethods | null}= {} - server: any + constructor(app: any) { + this.start(app) + } - constructor(app:any) { - this.start(app) + // ** register resource provider ** + register(pluginId: string, provider: ResourceProvider) { + debug(`** Registering provider(s)....${provider?.types}`) + if (!provider) { + return } - - // ** initialise resourcesApi ** - private start(app:any) { - debug(`** Initialise ${SIGNALK_API_PATH}/resources path handler **`) - this.server= app - this.initResourceRoutes() + if (provider.types && !Array.isArray(provider.types)) { + return } + provider.types.forEach((i: string) => { + if (!this.resProvider[i]) { + provider.methods.pluginId = pluginId + this.resProvider[i] = provider.methods + } + }) + debug(this.resProvider) + } - // ** register resource provider ** - public register(pluginId:string, provider:ResourceProvider) { - debug(`** Registering provider(s)....${provider?.types}`) - if(!provider ) { return } - if(provider.types && !Array.isArray(provider.types)) { return } - provider.types.forEach( (i:string)=>{ - if(!this.resProvider[i]) { - provider.methods.pluginId= pluginId - this.resProvider[i]= provider.methods - } - }) - debug(this.resProvider) + // ** un-register resource provider for the supplied types ** + unRegister(pluginId: string) { + if (!pluginId) { + return + } + debug(`** Un-registering ${pluginId} resource provider(s)....`) + for (const i in this.resProvider) { + if (this.resProvider[i]?.pluginId === pluginId) { + debug(`** Un-registering ${i}....`) + delete this.resProvider[i] + } } + debug(JSON.stringify(this.resProvider)) + } - // ** un-register resource provider for the supplied types ** - public unRegister(pluginId:string) { - if(!pluginId) { return } - debug(`** Un-registering ${pluginId} resource provider(s)....`) - for( let i in this.resProvider ) { - if(this.resProvider[i]?.pluginId===pluginId) { - debug(`** Un-registering ${i}....`) - delete this.resProvider[i] - } + // ** return resource with supplied type and id ** + getResource(type: string, id: string) { + debug(`** getResource(${type}, ${id})`) + return this.actionResourceRequest({ + method: 'GET', + body: {}, + query: {}, + resourceType: type, + resourceId: id + }) + } + + // ** initialise resourcesApi ** + private start(app: any) { + debug(`** Initialise ${SIGNALK_API_PATH}/resources path handler **`) + this.server = app + this.initResourceRoutes() + } + + // ** initialise handler for in-scope resource types ** + private initResourceRoutes() { + this.server.get(`${SIGNALK_API_PATH}/resources`, (req: any, res: any) => { + // list all serviced paths under resources + res.json(this.getResourcePaths()) + }) + this.server.use( + `${SIGNALK_API_PATH}/resources/*`, + async (req: any, res: any, next: any) => { + const result = this.parseResourceRequest(req) + if (result) { + const ar = await this.actionResourceRequest(result) + if (typeof ar.statusCode !== 'undefined') { + debug(`${JSON.stringify(ar)}`) + res.status = ar.statusCode + res.send(ar.message) + } else { + res.json(ar) + } + } else { + debug('** No provider found... calling next()...') + next() } - debug(JSON.stringify(this.resProvider)) - } + } + ) + } + + // ** return all paths serviced under SIGNALK_API_PATH/resources ** + private getResourcePaths(): { [key: string]: any } { + const resPaths: { [key: string]: any } = {} + Object.entries(this.resProvider).forEach((p: any) => { + if (p[1]) { + resPaths[p[0]] = `Path containing ${p[0]}, each named with a UUID` + } + }) + // check for other plugins servicing paths under ./resources + this.server._router.stack.forEach((i: any) => { + if (i.route && i.route.path && typeof i.route.path === 'string') { + if (i.route.path.indexOf(`${SIGNALK_API_PATH}/resources`) !== -1) { + const r = i.route.path.split('/') + if (r.length > 5 && !(r[5] in resPaths)) { + resPaths[ + r[5] + ] = `Path containing ${r[5]} resources (provided by plug-in)` + } + } + } + }) + return resPaths + } + + // ** parse api path request and return ResourceRequest object ** + private parseResourceRequest(req: any): ResourceRequest | undefined { + debug('** req.originalUrl:', req.originalUrl) + debug('** req.method:', req.method) + debug('** req.body:', req.body) + debug('** req.query:', req.query) + debug('** req.params:', req.params) + const p = req.params[0].split('/') + let resType = typeof req.params[0] !== 'undefined' ? p[0] : '' + const resId = p.length > 1 ? p[1] : '' + const resAttrib = p.length > 2 ? p.slice(2) : [] + req.query.resAttrib = resAttrib + debug('** resType:', resType) + debug('** resId:', resId) + debug('** resAttrib:', resAttrib) + debug('** req.params + attribs:', req.query) - // ** return resource with supplied type and id ** - public getResource(type:string, id:string) { - debug(`** getResource(${type}, ${id})`) - return this.actionResourceRequest({ - method: 'GET', - body: {}, - query: {}, - resourceType: type, - resourceId: id - }) + const apiMethod = API_METHODS.includes(resType) ? resType : null + if (apiMethod) { + if (apiMethod.toLowerCase().indexOf('waypoint') !== -1) { + resType = 'waypoints' + } + if (apiMethod.toLowerCase().indexOf('route') !== -1) { + resType = 'routes' + } + if (apiMethod.toLowerCase().indexOf('note') !== -1) { + resType = 'notes' + } + if (apiMethod.toLowerCase().indexOf('region') !== -1) { + resType = 'regions' + } } - // ** initialise handler for in-scope resource types ** - private initResourceRoutes() { - this.server.get(`${SIGNALK_API_PATH}/resources`, (req:any, res:any) => { - // list all serviced paths under resources - res.json(this.getResourcePaths()) - }) - this.server.use(`${SIGNALK_API_PATH}/resources/*`, async (req:any, res:any, next: any) => { - let result= this.parseResourceRequest(req) - if(result) { - let ar= await this.actionResourceRequest(result) - if(typeof ar.statusCode!== 'undefined'){ - debug(`${JSON.stringify(ar)}`) - res.status= ar.statusCode - res.send(ar.message) - } - else { - res.json(ar) - } - } - else { - debug('** No provider found... calling next()...') - next() - } - }) + const retReq = { + method: req.method, + body: req.body, + query: req.query, + resourceType: resType, + resourceId: resId, + apiMethod } - // ** return all paths serviced under SIGNALK_API_PATH/resources ** - private getResourcePaths(): {[key:string]:any} { - let resPaths:{[key:string]:any}= {} - Object.entries(this.resProvider).forEach( (p:any)=> { - if(p[1]) { resPaths[p[0]]= `Path containing ${p[0]}, each named with a UUID` } - }) - // check for other plugins servicing paths under ./resources - this.server._router.stack.forEach((i:any)=> { - if(i.route && i.route.path && typeof i.route.path==='string') { - if(i.route.path.indexOf(`${SIGNALK_API_PATH}/resources`)!==-1) { - let r= i.route.path.split('/') - if( r.length>5 && !(r[5] in resPaths) ) { - resPaths[r[5]]= `Path containing ${r[5]} resources (provided by plug-in)` - } - } - } - }) - return resPaths + if (this.resourceTypes.includes(resType) && this.resProvider[resType]) { + return retReq + } else { + debug('Invalid resource type or no provider for this type!') + return undefined } + } - // ** parse api path request and return ResourceRequest object ** - private parseResourceRequest(req:any):ResourceRequest | undefined { - debug('** req.originalUrl:', req.originalUrl) - debug('** req.method:', req.method) - debug('** req.body:', req.body) - debug('** req.query:', req.query) - debug('** req.params:', req.params) - let p= req.params[0].split('/') - let resType= (typeof req.params[0]!=='undefined') ? p[0] : '' - let resId= p.length>1 ? p[1] : '' - let resAttrib= p.length>2 ? p.slice(2) : [] - req.query.resAttrib= resAttrib - debug('** resType:', resType) - debug('** resId:', resId) - debug('** resAttrib:', resAttrib) - debug('** req.params + attribs:', req.query) - - let apiMethod= (API_METHODS.includes(resType)) ? resType : null - if(apiMethod) { - if(apiMethod.toLowerCase().indexOf('waypoint')!==-1) { - resType= 'waypoints' - } - if(apiMethod.toLowerCase().indexOf('route')!==-1) { - resType= 'routes' - } - if(apiMethod.toLowerCase().indexOf('note')!==-1) { - resType= 'notes' - } - if(apiMethod.toLowerCase().indexOf('region')!==-1) { - resType= 'regions' - } - } + // ** action an in-scope resource request ** + private async actionResourceRequest(req: ResourceRequest): Promise { + debug('********* action request *************') + debug(req) - let retReq= { - method: req.method, - body: req.body, - query: req.query, - resourceType: resType, - resourceId: resId, - apiMethod: apiMethod - } + // check for registered resource providers + if (!this.resProvider) { + return { statusCode: 501, message: `No Provider` } + } - if(this.resourceTypes.includes(resType) && this.resProvider[resType]) { - return retReq - } - else { - debug('Invalid resource type or no provider for this type!') - return undefined - } + if ( + !this.resourceTypes.includes(req.resourceType) || + !this.resProvider[req.resourceType] + ) { + return { statusCode: 501, message: `No Provider` } } - // ** action an in-scope resource request ** - private async actionResourceRequest (req:ResourceRequest):Promise { - debug('********* action request *************') - debug(req) - - // check for registered resource providers - if(!this.resProvider) { - return {statusCode: 501, message: `No Provider`} - } - - if(!this.resourceTypes.includes(req.resourceType) || !this.resProvider[req.resourceType]) { - return {statusCode: 501, message: `No Provider`} - } + // check for API method request + if (req.apiMethod && API_METHODS.includes(req.apiMethod)) { + debug(`API Method (${req.apiMethod})`) + req = this.transformApiRequest(req) + } - // check for API method request - if(req.apiMethod && API_METHODS.includes(req.apiMethod)) { - debug(`API Method (${req.apiMethod})`) - req= this.transformApiRequest(req) - } + return await this.execResourceRequest(req) + } - return await this.execResourceRequest(req) + // ** transform API request to ResourceRequest ** + private transformApiRequest(req: ResourceRequest): ResourceRequest { + if (req.apiMethod?.indexOf('delete') !== -1) { + req.method = 'DELETE' } - - // ** transform API request to ResourceRequest ** - private transformApiRequest(req: ResourceRequest):ResourceRequest { - if(req.apiMethod?.indexOf('delete')!==-1) { - req.method= 'DELETE' - } - if(req.apiMethod?.indexOf('set')!==-1) { - if(!req.body.id) { - req.method= 'POST' - } - else { - req.resourceId= req.body.id - } - req.body= { value: buildResource(req.resourceType, req.body) ?? {} } - } - return req + if (req.apiMethod?.indexOf('set') !== -1) { + if (!req.body.id) { + req.method = 'POST' + } else { + req.resourceId = req.body.id + } + req.body = { value: buildResource(req.resourceType, req.body) ?? {} } } + return req + } - // ** action an in-scope resource request ** - private async execResourceRequest (req:ResourceRequest):Promise { - debug('********* execute request *************') - debug(req) - if(req.method==='GET') { - let retVal: any - if(!req.resourceId) { - retVal= await this.resProvider[req.resourceType]?.listResources(req.resourceType, req.query) - return (retVal) ? - retVal : - {statusCode: 404, message: `Error retrieving resources!` } - } - if(!validate.uuid(req.resourceId)) { - return {statusCode: 406, message: `Invalid resource id provided (${req.resourceId})` } - } - retVal= await this.resProvider[req.resourceType]?.getResource(req.resourceType, req.resourceId) - return (retVal) ? - retVal : - {statusCode: 404, message: `Resource not found (${req.resourceId})!` } + // ** action an in-scope resource request ** + private async execResourceRequest(req: ResourceRequest): Promise { + debug('********* execute request *************') + debug(req) + if (req.method === 'GET') { + let retVal: any + if (!req.resourceId) { + retVal = await this.resProvider[req.resourceType]?.listResources( + req.resourceType, + req.query + ) + return retVal + ? retVal + : { statusCode: 404, message: `Error retrieving resources!` } + } + if (!validate.uuid(req.resourceId)) { + return { + statusCode: 406, + message: `Invalid resource id provided (${req.resourceId})` } + } + retVal = await this.resProvider[req.resourceType]?.getResource( + req.resourceType, + req.resourceId + ) + return retVal + ? retVal + : { + statusCode: 404, + message: `Resource not found (${req.resourceId})!` + } + } - if(req.method==='DELETE' || req.method==='PUT') { - if(!req.resourceId) { - return {statusCode: 406, value: `No resource id provided!` } - } - if(!validate.uuid(req.resourceId)) { - return {statusCode: 406, message: `Invalid resource id provided (${req.resourceId})!` } - } - if( - req.method==='DELETE' || - (req.method==='PUT' && typeof req.body.value!=='undefined' && req.body.value==null) - ) { - let retVal= await this.resProvider[req.resourceType]?.deleteResource(req.resourceType, req.resourceId) - if(retVal){ - this.sendDelta( - this.resProvider[req.resourceType]?.pluginId as string, - req.resourceType, req.resourceId, - null - ) - return {statusCode: 200, message: `Resource (${req.resourceId}) deleted.`} - } - else { - return {statusCode: 400, message: `Error deleting resource (${req.resourceId})!` } - } - } - + if (req.method === 'DELETE' || req.method === 'PUT') { + if (!req.resourceId) { + return { statusCode: 406, value: `No resource id provided!` } + } + if (!validate.uuid(req.resourceId)) { + return { + statusCode: 406, + message: `Invalid resource id provided (${req.resourceId})!` } - - if(req.method==='POST' || req.method==='PUT') { - // check for supplied value - if( typeof req.body.value==='undefined' || req.body.value==null) { - return {statusCode: 406, message: `No resource data supplied!`} - } - // validate supplied request data - if(!validate.resource(req.resourceType, req.body.value)) { - return {statusCode: 406, message: `Invalid resource data supplied!`} - } - if(req.method==='POST') { - let id= UUID_PREFIX + uuidv4() - let retVal= await this.resProvider[req.resourceType]?.setResource(req.resourceType, id, req.body.value) - if(retVal){ - this.sendDelta( - this.resProvider[req.resourceType]?.pluginId as string, - req.resourceType, - id, - req.body.value - ) - return {statusCode: 200, message: `Resource (${id}) saved.`} - } - else { - return {statusCode: 400, message: `Error saving resource (${id})!` } - } - } - if(req.method==='PUT') { - if(!req.resourceId) { - return {statusCode: 406, message: `No resource id provided!` } - } - let retVal= await this.resProvider[req.resourceType]?.setResource(req.resourceType, req.resourceId, req.body.value) - if(retVal){ - this.sendDelta( - this.resProvider[req.resourceType]?.pluginId as string, - req.resourceType, - req.resourceId, - req.body.value - ) - return {statusCode: 200, message: `Resource (${req.resourceId}) updated.`} - } - else { - return {statusCode: 400, message: `Error updating resource (${req.resourceId})!` } - } - } + } + if ( + req.method === 'DELETE' || + (req.method === 'PUT' && + typeof req.body.value !== 'undefined' && + req.body.value == null) + ) { + const retVal = await this.resProvider[req.resourceType]?.deleteResource( + req.resourceType, + req.resourceId + ) + if (retVal) { + this.sendDelta( + this.resProvider[req.resourceType]?.pluginId as string, + req.resourceType, + req.resourceId, + null + ) + return { + statusCode: 200, + message: `Resource (${req.resourceId}) deleted.` + } + } else { + return { + statusCode: 400, + message: `Error deleting resource (${req.resourceId})!` + } } + } } - // ** send delta message with resource PUT, POST, DELETE action result - private sendDelta(providerId:string, type:string, id:string, value:any):void { - debug(`** Sending Delta: resources.${type}.${id}`) - this.server.handleMessage(providerId, { - updates: [ - { - values: [ - { - path: `resources.${type}.${id}`, - value: value - } - ] - } - ] - }) + if (req.method === 'POST' || req.method === 'PUT') { + // check for supplied value + if (typeof req.body.value === 'undefined' || req.body.value == null) { + return { statusCode: 406, message: `No resource data supplied!` } + } + // validate supplied request data + if (!validate.resource(req.resourceType, req.body.value)) { + return { statusCode: 406, message: `Invalid resource data supplied!` } + } + if (req.method === 'POST') { + const id = UUID_PREFIX + uuidv4() + const retVal = await this.resProvider[req.resourceType]?.setResource( + req.resourceType, + id, + req.body.value + ) + if (retVal) { + this.sendDelta( + this.resProvider[req.resourceType]?.pluginId as string, + req.resourceType, + id, + req.body.value + ) + return { statusCode: 200, message: `Resource (${id}) saved.` } + } else { + return { statusCode: 400, message: `Error saving resource (${id})!` } + } + } + if (req.method === 'PUT') { + if (!req.resourceId) { + return { statusCode: 406, message: `No resource id provided!` } + } + const retVal = await this.resProvider[req.resourceType]?.setResource( + req.resourceType, + req.resourceId, + req.body.value + ) + if (retVal) { + this.sendDelta( + this.resProvider[req.resourceType]?.pluginId as string, + req.resourceType, + req.resourceId, + req.body.value + ) + return { + statusCode: 200, + message: `Resource (${req.resourceId}) updated.` + } + } else { + return { + statusCode: 400, + message: `Error updating resource (${req.resourceId})!` + } + } + } } + } + // ** send delta message with resource PUT, POST, DELETE action result + private sendDelta( + providerId: string, + type: string, + id: string, + value: any + ): void { + debug(`** Sending Delta: resources.${type}.${id}`) + this.server.handleMessage(providerId, { + updates: [ + { + values: [ + { + path: `resources.${type}.${id}`, + value + } + ] + } + ] + }) + } } diff --git a/src/api/resources/resources.ts b/src/api/resources/resources.ts index e76cce323..1857c23cd 100644 --- a/src/api/resources/resources.ts +++ b/src/api/resources/resources.ts @@ -2,177 +2,215 @@ import { getDistance, isValidCoordinate } from 'geolib' import ngeohash from 'ngeohash' // ** build resource item ** -export const buildResource= (resType:string, data:any):any=> { - if(resType==='routes') { return buildRoute(data) } - if(resType==='waypoints') { return buildWaypoint(data) } - if(resType==='notes') { return buildNote(data) } - if(resType==='regions') { return buildRegion(data) } +export const buildResource = (resType: string, data: any): any => { + if (resType === 'routes') { + return buildRoute(data) + } + if (resType === 'waypoints') { + return buildWaypoint(data) + } + if (resType === 'notes') { + return buildNote(data) + } + if (resType === 'regions') { + return buildRegion(data) + } } // ** build route -const buildRoute= (rData:any):any=> { - let rte:any= { - feature: { - type: 'Feature', - geometry:{ - type: 'LineString', - coordinates :[] - }, - properties:{} - } - } - if(typeof rData.name !== 'undefined') { - rte.name= rData.name - rte.feature.properties.name= rData.name - } - if(typeof rData.description !== 'undefined') { - rte.description= rData.description - rte.feature.properties.description= rData.description - } - if(typeof rData.attributes !== 'undefined') { - Object.assign(rte.feature.properties, rData.attributes) - } +const buildRoute = (rData: any): any => { + const rte: any = { + feature: { + type: 'Feature', + geometry: { + type: 'LineString', + coordinates: [] + }, + properties: {} + } + } + if (typeof rData.name !== 'undefined') { + rte.name = rData.name + rte.feature.properties.name = rData.name + } + if (typeof rData.description !== 'undefined') { + rte.description = rData.description + rte.feature.properties.description = rData.description + } + if (typeof rData.attributes !== 'undefined') { + Object.assign(rte.feature.properties, rData.attributes) + } - if(typeof rData.points === 'undefined') { return null } - if(!Array.isArray(rData.points)) { return null } - let isValid:boolean= true; - rData.points.forEach( (p:any)=> { - if(!isValidCoordinate(p)) { isValid= false } - }) - if(!isValid) { return null } - rData.points.forEach( (p:any)=> { - rte.feature.geometry.coordinates.push([p.longitude, p.latitude]) - }) + if (typeof rData.points === 'undefined') { + return null + } + if (!Array.isArray(rData.points)) { + return null + } + let isValid: boolean = true + rData.points.forEach((p: any) => { + if (!isValidCoordinate(p)) { + isValid = false + } + }) + if (!isValid) { + return null + } + rte.feature.geometry.coordinates = rData.points.map((p: any) => { + return [p.longitude, p.latitude] + }) - rte.distance= 0 - for(let i=0; i { - let wpt:any= { - position: { - latitude: 0, - longitude: 0 - }, - feature: { - type: 'Feature', - geometry:{ - type: 'Point', - coordinates :[] - }, - properties:{} - } - } - if(typeof rData.name !== 'undefined') { - wpt.feature.properties.name= rData.name - } - if(typeof rData.description !== 'undefined') { - wpt.feature.properties.description= rData.description - } - if(typeof rData.attributes !== 'undefined') { - Object.assign(wpt.feature.properties, rData.attributes) - } +const buildWaypoint = (rData: any): any => { + const wpt: any = { + position: { + latitude: 0, + longitude: 0 + }, + feature: { + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [] + }, + properties: {} + } + } + if (typeof rData.name !== 'undefined') { + wpt.feature.properties.name = rData.name + } + if (typeof rData.description !== 'undefined') { + wpt.feature.properties.description = rData.description + } + if (typeof rData.attributes !== 'undefined') { + Object.assign(wpt.feature.properties, rData.attributes) + } + + if (typeof rData.position === 'undefined') { + return null + } + if (!isValidCoordinate(rData.position)) { + return null + } - if(typeof rData.position === 'undefined') { return null } - if(!isValidCoordinate(rData.position)) { return null } - - wpt.position= rData.position - wpt.feature.geometry.coordinates= [rData.position.longitude, rData.position.latitude] + wpt.position = rData.position + wpt.feature.geometry.coordinates = [ + rData.position.longitude, + rData.position.latitude + ] - return wpt + return wpt } // ** build note -const buildNote= (rData:any):any=> { - let note:any= {} - if(typeof rData.title !== 'undefined') { - note.title= rData.title - note.feature.properties.title= rData.title - } - if(typeof rData.description !== 'undefined') { - note.description= rData.description - note.feature.properties.description= rData.description - } - if(typeof rData.position === 'undefined' - && typeof rData.region === 'undefined' - && typeof rData.geohash === 'undefined') { return null } +const buildNote = (rData: any): any => { + const note: any = {} + if (typeof rData.title !== 'undefined') { + note.title = rData.title + note.feature.properties.title = rData.title + } + if (typeof rData.description !== 'undefined') { + note.description = rData.description + note.feature.properties.description = rData.description + } + if ( + typeof rData.position === 'undefined' && + typeof rData.region === 'undefined' && + typeof rData.geohash === 'undefined' + ) { + return null + } - if(typeof rData.position !== 'undefined') { - if(!isValidCoordinate(rData.position)) { return null } - note.position= rData.position - } - if(typeof rData.region !== 'undefined') { - note.region= rData.region - } - if(typeof rData.geohash !== 'undefined') { - note.geohash= rData.geohash - } - if(typeof rData.url !== 'undefined') { - note.url= rData.url - } - if(typeof rData.mimeType !== 'undefined') { - note.mimeType= rData.mimeType - } - - return note + if (typeof rData.position !== 'undefined') { + if (!isValidCoordinate(rData.position)) { + return null + } + note.position = rData.position + } + if (typeof rData.region !== 'undefined') { + note.region = rData.region + } + if (typeof rData.geohash !== 'undefined') { + note.geohash = rData.geohash + } + if (typeof rData.url !== 'undefined') { + note.url = rData.url + } + if (typeof rData.mimeType !== 'undefined') { + note.mimeType = rData.mimeType + } + + return note } // ** build region -const buildRegion= (rData:any):any=> { - let reg:any= { - feature: { - type: 'Feature', - geometry:{ - type: 'Polygon', - coordinates :[] - }, - properties:{} - } - } - let coords:Array<[number,number]>= [] +const buildRegion = (rData: any): any => { + const reg: any = { + feature: { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [] + }, + properties: {} + } + } + let coords: Array<[number, number]> = [] - if(typeof rData.name !== 'undefined') { - reg.feature.properties.name= rData.name - } - if(typeof rData.description !== 'undefined') { - reg.feature.properties.description= rData.description - } - if(typeof rData.attributes !== 'undefined') { - Object.assign(reg.feature.properties, rData.attributes) - } + if (typeof rData.name !== 'undefined') { + reg.feature.properties.name = rData.name + } + if (typeof rData.description !== 'undefined') { + reg.feature.properties.description = rData.description + } + if (typeof rData.attributes !== 'undefined') { + Object.assign(reg.feature.properties, rData.attributes) + } - if(typeof rData.points=== 'undefined' && rData.geohash=== 'undefined') { return null } - if(typeof rData.geohash!== 'undefined') { - reg.geohash= rData.geohash - - let bounds= ngeohash.decode_bbox(rData.geohash) - coords= [ - [bounds[1], bounds[0]], - [bounds[3], bounds[0]], - [bounds[3], bounds[2]], - [bounds[1], bounds[2]], - [bounds[1], bounds[0]], - ] - reg.feature.geometry.coordinates.push(coords) - } - if(typeof rData.points!== 'undefined' && coords.length===0 ) { - if(!Array.isArray(rData.points)) { return null } - let isValid:boolean= true; - rData.points.forEach( (p:any)=> { - if(!isValidCoordinate(p)) { isValid= false } - }) - if(!isValid) { return null } - rData.points.forEach( (p:any)=> { - coords.push([p.longitude, p.latitude]) - }) - reg.feature.geometry.coordinates.push(coords) + if (typeof rData.points === 'undefined' && rData.geohash === 'undefined') { + return null + } + if (typeof rData.geohash !== 'undefined') { + reg.geohash = rData.geohash + + const bounds = ngeohash.decode_bbox(rData.geohash) + coords = [ + [bounds[1], bounds[0]], + [bounds[3], bounds[0]], + [bounds[3], bounds[2]], + [bounds[1], bounds[2]], + [bounds[1], bounds[0]] + ] + reg.feature.geometry.coordinates.push(coords) + } + if (typeof rData.points !== 'undefined' && coords.length === 0) { + if (!Array.isArray(rData.points)) { + return null + } + let isValid: boolean = true + rData.points.forEach((p: any) => { + if (!isValidCoordinate(p)) { + isValid = false + } + }) + if (!isValid) { + return null } - - return reg + coords = rData.points.map((p: any) => { + return [p.longitude, p.latitude] + }) + reg.feature.geometry.coordinates.push(coords) + } + + return reg } diff --git a/src/api/resources/validate.ts b/src/api/resources/validate.ts index 840d944b2..0d8d6cffe 100644 --- a/src/api/resources/validate.ts +++ b/src/api/resources/validate.ts @@ -1,97 +1,124 @@ -import geoJSON from 'geojson-validation'; +import geoJSON from 'geojson-validation' import { isValidCoordinate } from 'geolib' -export const validate= { - resource: (type:string, value:any):boolean=> { - if(!type) { return false } - switch(type) { - case 'routes': - return validateRoute(value); - break - case 'waypoints': - return validateWaypoint(value) - break - case 'notes': - return validateNote(value) - break; - case 'regions': - return validateRegion(value) - break - default: - return true - } - }, - - // ** returns true if id is a valid Signal K UUID ** - uuid: (id:string): boolean=> { - let uuid= RegExp("^urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$") - return uuid.test(id) +export const validate = { + resource: (type: string, value: any): boolean => { + if (!type) { + return false + } + switch (type) { + case 'routes': + return validateRoute(value) + break + case 'waypoints': + return validateWaypoint(value) + break + case 'notes': + return validateNote(value) + break + case 'regions': + return validateRegion(value) + break + default: + return true } + }, + + // ** returns true if id is a valid Signal K UUID ** + uuid: (id: string): boolean => { + const uuid = RegExp( + '^urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$' + ) + return uuid.test(id) + } } // ** validate route data -const validateRoute= (r:any):boolean=> { - if(r.start) { - let l= r.start.split('/') - if(!validate.uuid(l[l.length-1])) { return false } +const validateRoute = (r: any): boolean => { + if (r.start) { + const l = r.start.split('/') + if (!validate.uuid(l[l.length - 1])) { + return false } - if(r.end) { - let l= r.end.split('/') - if(!validate.uuid(l[l.length-1])) { return false } + } + if (r.end) { + const l = r.end.split('/') + if (!validate.uuid(l[l.length - 1])) { + return false } - try { - if(!r.feature || !geoJSON.valid(r.feature)) { - return false - } - if(r.feature.geometry.type!=='LineString') { return false } + } + try { + if (!r.feature || !geoJSON.valid(r.feature)) { + return false } - catch(err) { return false } - return true + if (r.feature.geometry.type !== 'LineString') { + return false + } + } catch (err) { + return false + } + return true } // ** validate waypoint data -const validateWaypoint= (r:any):boolean=> { - if(typeof r.position === 'undefined') { return false } - if(!isValidCoordinate(r.position)) { - return false +const validateWaypoint = (r: any): boolean => { + if (typeof r.position === 'undefined') { + return false + } + if (!isValidCoordinate(r.position)) { + return false + } + try { + if (!r.feature || !geoJSON.valid(r.feature)) { + return false } - try { - if(!r.feature || !geoJSON.valid(r.feature)) { - return false - } - if(r.feature.geometry.type!=='Point') { return false } + if (r.feature.geometry.type !== 'Point') { + return false } - catch(e) { return false } - return true + } catch (e) { + return false + } + return true } // ** validate note data -const validateNote= (r:any):boolean=> { - if(!r.region && !r.position && !r.geohash ) { return false } - if(typeof r.position!== 'undefined') { - if(!isValidCoordinate(r.position)) { - return false - } +const validateNote = (r: any): boolean => { + if (!r.region && !r.position && !r.geohash) { + return false + } + if (typeof r.position !== 'undefined') { + if (!isValidCoordinate(r.position)) { + return false } - if(r.region) { - let l= r.region.split('/') - if(!validate.uuid(l[l.length-1])) { return false } + } + if (r.region) { + const l = r.region.split('/') + if (!validate.uuid(l[l.length - 1])) { + return false } - return true + } + return true } // ** validate region data -const validateRegion= (r:any):boolean=> { - if(!r.geohash && !r.feature) { return false } - if(r.feature ) { - try { - if(!geoJSON.valid(r.feature)) { return false } - if(r.feature.geometry.type!=='Polygon' && r.feature.geometry.type!=='MultiPolygon') { - return false - } - } - catch(e) { return false } +const validateRegion = (r: any): boolean => { + if (!r.geohash && !r.feature) { + return false + } + if (r.feature) { + try { + if (!geoJSON.valid(r.feature)) { + return false + } + if ( + r.feature.geometry.type !== 'Polygon' && + r.feature.geometry.type !== 'MultiPolygon' + ) { + return false + } + } catch (e) { + return false } - return true + } + return true } - diff --git a/src/put.js b/src/put.js index 7a89d4ad1..f3f50f078 100644 --- a/src/put.js +++ b/src/put.js @@ -32,10 +32,10 @@ module.exports = { app.put(apiPathPrefix + '*', function(req, res, next) { // ** ignore resources paths ** - if(req.path.split('/')[4]==='resources') { + if (req.path.split('/')[4] === 'resources') { next() return - } + } let path = String(req.path).replace(apiPathPrefix, '') const value = req.body From fd4a38af6fe5f3b5bc56eba43b5ff81e8b1b4486 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 21 Nov 2021 15:59:33 +1030 Subject: [PATCH 240/410] chore: return value descriptions to show a Promise --- RESOURCE_PROVIDER_PLUGINS.md | 48 +++++++++++++++++----------------- src/api/resources/resources.ts | 15 +++++++---- 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index 2d20f1689..4e2c7312d 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -179,7 +179,7 @@ query= { It is the responsibility of the resource provider plugin to filter the resources returned as per the supplied query parameters. -`listResources()` should return a JSON object listing resources by id. +`listResources()` returns a Promise containing a JSON object listing resources by id. _Example: List all routes._ ```javascript @@ -187,34 +187,34 @@ GET /signalk/v1/api/resources/routes listResources('routes', {}) -returns { +returns Promise<{ "resource_id1": { ... }, "resource_id2": { ... }, ... "resource_idn": { ... } -} +}> ``` _Example: List waypoints within the bounded area._ -```javascript +```typescript GET /signalk/v1/api/resources/waypoints?bbox=5.4,25.7,6.9,31.2 listResources('waypoints', {bbox: '5.4,25.7,6.9,31.2'}) -returns { +returns Promise<{ "resource_id1": { ... }, "resource_id2": { ... } -} +}> ``` ### __Retrieve a specific resource:__ `GET` requests for a specific resource will be dispatched to the `getResource` method passing the resource type and id as parameters. -`getResource()` should returns a JSON object containing the resource data. +`getResource()` returns a Promise containing a JSON object with the resource data. _Example: Retrieve route._ -```javascript +```typescript GET /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a getResource( @@ -224,14 +224,14 @@ getResource( ) ``` -_Returns the result:_ -```json -{ +_Returns a Promise containing the resource data:_ +```typescript +Promise<{ "name": "Name of the route", "description": "Description of the route", "distance": 18345, "feature": { ... } -} +}> ``` A request for a resource attribute will pass the attibute path as an array in the query object with the key `resAttrib`. @@ -246,19 +246,19 @@ getResource( { resAttrib: ['feature','geometry'] } ) ``` -_Returns the value of `geometry` attribute of the waypoint._ -```json -{ +_Returns a Promise containing the value of `geometry` attribute of the waypoint._ +```typescript +Promise<{ "type": "Point", "coordinates": [70.4,6.45] -} +}> ``` ### __Saving Resources:__ `PUT` requests to a path containing the resource id are used to store data associated with the resource id. These will be dispatched to the `setResource` method passing the resource type, id and data as parameters. -`setResource() ` returns `true` on success and `null` on failure. +`setResource() ` returns Promise on success and Promise on failure. _Example: Update / add waypoint with the supplied id._ ```javascript @@ -266,34 +266,34 @@ PUT /signalk/v1/api/resources/waypoints/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25- setResource('waypoints', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) -returns true | null +returns Promise ``` `POST` requests to a resource path that do not contina the resource id will be dispatched to the `setResource` method passing the resource type, an id (generated by the server) and resource data as parameters. -`setResource() ` returns `true` on success and `null` on failure. +`setResource()` returns `true` on success and `null` on failure. _Example: New route record._ -```javascript +```typescript POST /signalk/v1/api/resources/routes {} setResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) -returns true | null +returns Promise ``` ### __Deleting Resources:__ `DELETE` requests to a path containing the resource id will be dispatched to the `deleteResource` method passing the resource type and id as parameters. -`deleteResource()` returns `true` on success, `null` on failure. +`deleteResource()` returns `true` on success and `null` on failure. _Example: Delete region with supplied id._ -```javascript +```typescript DELETE /signalk/v1/api/resources/regions/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a deleteResource('regions', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a') -returns true | null +returns Promise ``` diff --git a/src/api/resources/resources.ts b/src/api/resources/resources.ts index 1857c23cd..3b79bc271 100644 --- a/src/api/resources/resources.ts +++ b/src/api/resources/resources.ts @@ -1,7 +1,7 @@ import { getDistance, isValidCoordinate } from 'geolib' import ngeohash from 'ngeohash' -// ** build resource item ** + export const buildResource = (resType: string, data: any): any => { if (resType === 'routes') { return buildRoute(data) @@ -17,7 +17,6 @@ export const buildResource = (resType: string, data: any): any => { } } -// ** build route const buildRoute = (rData: any): any => { const rte: any = { feature: { @@ -70,7 +69,7 @@ const buildRoute = (rData: any): any => { return rte } -// ** build waypoint + const buildWaypoint = (rData: any): any => { const wpt: any = { position: { @@ -112,7 +111,7 @@ const buildWaypoint = (rData: any): any => { return wpt } -// ** build note + const buildNote = (rData: any): any => { const note: any = {} if (typeof rData.title !== 'undefined') { @@ -153,7 +152,7 @@ const buildNote = (rData: any): any => { return note } -// ** build region + const buildRegion = (rData: any): any => { const reg: any = { feature: { @@ -206,6 +205,12 @@ const buildRegion = (rData: any): any => { if (!isValid) { return null } + if ( + rData.points[0].latitude !== rData.points[rData.points.length-1].latitude && + rData.points[0].longitude !== rData.points[rData.points.length-1].longitude + ) { + rData.points.push( rData.points[0]) + } coords = rData.points.map((p: any) => { return [p.longitude, p.latitude] }) From 75ea43aab9dd48117d02b3033ad2a87f38d7bffa Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 21 Nov 2021 16:44:07 +1030 Subject: [PATCH 241/410] specify SignalKResourceType --- src/api/resources/index.ts | 44 ++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 7afc84dc6..d73919678 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -14,8 +14,10 @@ interface ResourceRequest { apiMethod?: string | null } +type SignalKResourceType= 'routes' | 'waypoints' |'notes' |'regions' |'charts' + interface ResourceProvider { - types: string[] + types: SignalKResourceType[] methods: ResourceProviderMethods } @@ -49,8 +51,8 @@ export class Resources { resProvider: { [key: string]: ResourceProviderMethods | null } = {} server: any - // ** in-scope resource types ** - private resourceTypes: string[] = [ + // in-scope resource types + private resourceTypes: SignalKResourceType[] = [ 'routes', 'waypoints', 'notes', @@ -62,7 +64,7 @@ export class Resources { this.start(app) } - // ** register resource provider ** + // register plugin with supplied id as resource provider register(pluginId: string, provider: ResourceProvider) { debug(`** Registering provider(s)....${provider?.types}`) if (!provider) { @@ -80,22 +82,22 @@ export class Resources { debug(this.resProvider) } - // ** un-register resource provider for the supplied types ** + // un-register plugin with supplied id as resource provider unRegister(pluginId: string) { if (!pluginId) { return } debug(`** Un-registering ${pluginId} resource provider(s)....`) - for (const i in this.resProvider) { - if (this.resProvider[i]?.pluginId === pluginId) { - debug(`** Un-registering ${i}....`) - delete this.resProvider[i] + for (const resourceType in this.resProvider) { + if (this.resProvider[resourceType]?.pluginId === pluginId) { + debug(`** Un-registering ${resourceType}....`) + delete this.resProvider[resourceType] } } debug(JSON.stringify(this.resProvider)) } - // ** return resource with supplied type and id ** + // Return resource with supplied type and id getResource(type: string, id: string) { debug(`** getResource(${type}, ${id})`) return this.actionResourceRequest({ @@ -107,17 +109,17 @@ export class Resources { }) } - // ** initialise resourcesApi ** + // initialise resourcesApi private start(app: any) { debug(`** Initialise ${SIGNALK_API_PATH}/resources path handler **`) this.server = app this.initResourceRoutes() } - // ** initialise handler for in-scope resource types ** + // initialise handler for in-scope resource types private initResourceRoutes() { + // list all serviced paths under resources this.server.get(`${SIGNALK_API_PATH}/resources`, (req: any, res: any) => { - // list all serviced paths under resources res.json(this.getResourcePaths()) }) this.server.use( @@ -141,7 +143,7 @@ export class Resources { ) } - // ** return all paths serviced under SIGNALK_API_PATH/resources ** + // return all paths serviced under SIGNALK_API_PATH/resources private getResourcePaths(): { [key: string]: any } { const resPaths: { [key: string]: any } = {} Object.entries(this.resProvider).forEach((p: any) => { @@ -165,7 +167,7 @@ export class Resources { return resPaths } - // ** parse api path request and return ResourceRequest object ** + // parse api path request and return ResourceRequest object private parseResourceRequest(req: any): ResourceRequest | undefined { debug('** req.originalUrl:', req.originalUrl) debug('** req.method:', req.method) @@ -215,7 +217,7 @@ export class Resources { } } - // ** action an in-scope resource request ** + // action an in-scope resource request private async actionResourceRequest(req: ResourceRequest): Promise { debug('********* action request *************') debug(req) @@ -241,7 +243,7 @@ export class Resources { return await this.execResourceRequest(req) } - // ** transform API request to ResourceRequest ** + // transform API request to ResourceRequest private transformApiRequest(req: ResourceRequest): ResourceRequest { if (req.apiMethod?.indexOf('delete') !== -1) { req.method = 'DELETE' @@ -257,7 +259,7 @@ export class Resources { return req } - // ** action an in-scope resource request ** + // action an in-scope resource request private async execResourceRequest(req: ResourceRequest): Promise { debug('********* execute request *************') debug(req) @@ -331,14 +333,14 @@ export class Resources { } if (req.method === 'POST' || req.method === 'PUT') { - // check for supplied value if (typeof req.body.value === 'undefined' || req.body.value == null) { return { statusCode: 406, message: `No resource data supplied!` } } - // validate supplied request data + if (!validate.resource(req.resourceType, req.body.value)) { return { statusCode: 406, message: `Invalid resource data supplied!` } } + if (req.method === 'POST') { const id = UUID_PREFIX + uuidv4() const retVal = await this.resProvider[req.resourceType]?.setResource( @@ -388,7 +390,7 @@ export class Resources { } } - // ** send delta message with resource PUT, POST, DELETE action result + // Send delta message. Used by resource PUT, POST, DELETE actions private sendDelta( providerId: string, type: string, From efebc394688a7113a74187476fbfb0e09add7904 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 21 Nov 2021 17:45:53 +1030 Subject: [PATCH 242/410] move interfaces to server-api --- src/api/resources/index.ts | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index d73919678..ec92cb81b 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -3,20 +3,9 @@ import { v4 as uuidv4 } from 'uuid' import { buildResource } from './resources' import { validate } from './validate' -const debug = Debug('signalk:resources') - -interface ResourceRequest { - method: 'GET' | 'PUT' | 'POST' | 'DELETE' - body: any - query: { [key: string]: any } - resourceType: string - resourceId: string - apiMethod?: string | null -} - -type SignalKResourceType= 'routes' | 'waypoints' |'notes' |'regions' |'charts' +/*export type SignalKResourceType= 'routes' | 'waypoints' |'notes' |'regions' |'charts' -interface ResourceProvider { +export interface ResourceProvider { types: SignalKResourceType[] methods: ResourceProviderMethods } @@ -31,6 +20,19 @@ interface ResourceProviderMethods { value: { [key: string]: any } ) => Promise deleteResource: (type: string, id: string) => Promise +}*/ + +import { SignalKResourceType, ResourceProvider, ResourceProviderMethods } from '@signalk/server-api' + +const debug = Debug('signalk:resources') + +interface ResourceRequest { + method: 'GET' | 'PUT' | 'POST' | 'DELETE' + body: any + query: { [key: string]: any } + resourceType: string + resourceId: string + apiMethod?: string | null } const SIGNALK_API_PATH = `/signalk/v1/api` @@ -48,8 +50,8 @@ const API_METHODS = [ ] export class Resources { - resProvider: { [key: string]: ResourceProviderMethods | null } = {} - server: any + private resProvider: { [key: string]: ResourceProviderMethods | null } = {} + private server: any // in-scope resource types private resourceTypes: SignalKResourceType[] = [ @@ -200,7 +202,7 @@ export class Resources { } } - const retReq = { + const retReq:any = { method: req.method, body: req.body, query: req.query, @@ -228,7 +230,7 @@ export class Resources { } if ( - !this.resourceTypes.includes(req.resourceType) || + !this.resourceTypes.includes(req.resourceType as SignalKResourceType) || !this.resProvider[req.resourceType] ) { return { statusCode: 501, message: `No Provider` } From 07735a7d7a72749c8d77606b0c2e08b743baac94 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 22 Nov 2021 10:29:39 +1030 Subject: [PATCH 243/410] cleanup express route handling --- src/api/resources/index.ts | 139 ++++++++++++++++++++----------------- 1 file changed, 74 insertions(+), 65 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index ec92cb81b..8c980fa6b 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -3,25 +3,6 @@ import { v4 as uuidv4 } from 'uuid' import { buildResource } from './resources' import { validate } from './validate' -/*export type SignalKResourceType= 'routes' | 'waypoints' |'notes' |'regions' |'charts' - -export interface ResourceProvider { - types: SignalKResourceType[] - methods: ResourceProviderMethods -} - -interface ResourceProviderMethods { - pluginId: string - listResources: (type: string, query: { [key: string]: any }) => Promise - getResource: (type: string, id: string) => Promise - setResource: ( - type: string, - id: string, - value: { [key: string]: any } - ) => Promise - deleteResource: (type: string, id: string) => Promise -}*/ - import { SignalKResourceType, ResourceProvider, ResourceProviderMethods } from '@signalk/server-api' const debug = Debug('signalk:resources') @@ -30,7 +11,7 @@ interface ResourceRequest { method: 'GET' | 'PUT' | 'POST' | 'DELETE' body: any query: { [key: string]: any } - resourceType: string + resourceType: SignalKResourceType resourceId: string apiMethod?: string | null } @@ -66,7 +47,6 @@ export class Resources { this.start(app) } - // register plugin with supplied id as resource provider register(pluginId: string, provider: ResourceProvider) { debug(`** Registering provider(s)....${provider?.types}`) if (!provider) { @@ -84,7 +64,6 @@ export class Resources { debug(this.resProvider) } - // un-register plugin with supplied id as resource provider unRegister(pluginId: string) { if (!pluginId) { return @@ -99,8 +78,7 @@ export class Resources { debug(JSON.stringify(this.resProvider)) } - // Return resource with supplied type and id - getResource(type: string, id: string) { + getResource(type: SignalKResourceType, id: string) { debug(`** getResource(${type}, ${id})`) return this.actionResourceRequest({ method: 'GET', @@ -111,21 +89,60 @@ export class Resources { }) } - // initialise resourcesApi private start(app: any) { debug(`** Initialise ${SIGNALK_API_PATH}/resources path handler **`) this.server = app this.initResourceRoutes() } - // initialise handler for in-scope resource types private initResourceRoutes() { // list all serviced paths under resources this.server.get(`${SIGNALK_API_PATH}/resources`, (req: any, res: any) => { res.json(this.getResourcePaths()) }) + + this.server.get( + `${SIGNALK_API_PATH}/resources/:resourceType/:resourceId/*`, + async (req: any, res: any, next: any) => { + const result = this.parseResourceRequest(req) + if (result) { + const ar = await this.actionResourceRequest(result) + if (typeof ar.statusCode !== 'undefined') { + debug(`${JSON.stringify(ar)}`) + res.status = ar.statusCode + res.send(ar.message) + } else { + res.json(ar) + } + } else { + debug('** No provider found... calling next()...') + next() + } + } + ) + this.server.use( - `${SIGNALK_API_PATH}/resources/*`, + `${SIGNALK_API_PATH}/resources/:resourceType/:resourceId`, + async (req: any, res: any, next: any) => { + const result = this.parseResourceRequest(req) + if (result) { + const ar = await this.actionResourceRequest(result) + if (typeof ar.statusCode !== 'undefined') { + debug(`${JSON.stringify(ar)}`) + res.status = ar.statusCode + res.send(ar.message) + } else { + res.json(ar) + } + } else { + debug('** No provider found... calling next()...') + next() + } + } + ) + + this.server.use( + `${SIGNALK_API_PATH}/resources/:resourceType`, async (req: any, res: any, next: any) => { const result = this.parseResourceRequest(req) if (result) { @@ -145,7 +162,6 @@ export class Resources { ) } - // return all paths serviced under SIGNALK_API_PATH/resources private getResourcePaths(): { [key: string]: any } { const resPaths: { [key: string]: any } = {} Object.entries(this.resProvider).forEach((p: any) => { @@ -169,57 +185,53 @@ export class Resources { return resPaths } - // parse api path request and return ResourceRequest object private parseResourceRequest(req: any): ResourceRequest | undefined { - debug('** req.originalUrl:', req.originalUrl) + debug('********* parse request *************') debug('** req.method:', req.method) debug('** req.body:', req.body) debug('** req.query:', req.query) debug('** req.params:', req.params) - const p = req.params[0].split('/') - let resType = typeof req.params[0] !== 'undefined' ? p[0] : '' - const resId = p.length > 1 ? p[1] : '' - const resAttrib = p.length > 2 ? p.slice(2) : [] - req.query.resAttrib = resAttrib - debug('** resType:', resType) - debug('** resId:', resId) - debug('** resAttrib:', resAttrib) - debug('** req.params + attribs:', req.query) - - const apiMethod = API_METHODS.includes(resType) ? resType : null - if (apiMethod) { - if (apiMethod.toLowerCase().indexOf('waypoint') !== -1) { - resType = 'waypoints' + + const resReq:any = { + method: req.method, + body: req.body, + query: req.query, + resourceType: req.params.resourceType ?? null, + resourceId: req.params.resourceId ?? null, + apiMethod: API_METHODS.includes(req.params.resourceType) ? req.params.resourceType : null + } + + if (resReq.apiMethod) { + if (resReq.apiMethod.toLowerCase().indexOf('waypoint') !== -1) { + resReq.resourceType = 'waypoints' } - if (apiMethod.toLowerCase().indexOf('route') !== -1) { - resType = 'routes' + if (resReq.apiMethod.toLowerCase().indexOf('route') !== -1) { + resReq.resourceType = 'routes' } - if (apiMethod.toLowerCase().indexOf('note') !== -1) { - resType = 'notes' + if (resReq.apiMethod.toLowerCase().indexOf('note') !== -1) { + resReq.resourceType = 'notes' } - if (apiMethod.toLowerCase().indexOf('region') !== -1) { - resType = 'regions' + if (resReq.apiMethod.toLowerCase().indexOf('region') !== -1) { + resReq.resourceType = 'regions' } + } else { + const resAttrib = req.params['0'] ? req.params['0'].split('/') : [] + req.query.attrib = resAttrib } - const retReq:any = { - method: req.method, - body: req.body, - query: req.query, - resourceType: resType, - resourceId: resId, - apiMethod - } + debug('** resReq:', resReq) - if (this.resourceTypes.includes(resType) && this.resProvider[resType]) { - return retReq + if ( + this.resourceTypes.includes(resReq.resourceType) && + this.resProvider[resReq.resourceType] + ) { + return resReq } else { debug('Invalid resource type or no provider for this type!') return undefined } } - // action an in-scope resource request private async actionResourceRequest(req: ResourceRequest): Promise { debug('********* action request *************') debug(req) @@ -230,7 +242,7 @@ export class Resources { } if ( - !this.resourceTypes.includes(req.resourceType as SignalKResourceType) || + !this.resourceTypes.includes(req.resourceType) || !this.resProvider[req.resourceType] ) { return { statusCode: 501, message: `No Provider` } @@ -245,7 +257,6 @@ export class Resources { return await this.execResourceRequest(req) } - // transform API request to ResourceRequest private transformApiRequest(req: ResourceRequest): ResourceRequest { if (req.apiMethod?.indexOf('delete') !== -1) { req.method = 'DELETE' @@ -261,7 +272,6 @@ export class Resources { return req } - // action an in-scope resource request private async execResourceRequest(req: ResourceRequest): Promise { debug('********* execute request *************') debug(req) @@ -392,7 +402,6 @@ export class Resources { } } - // Send delta message. Used by resource PUT, POST, DELETE actions private sendDelta( providerId: string, type: string, From 3bb78a09f875c7d45fd2f592af495b7bfec74340 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 22 Nov 2021 10:33:00 +1030 Subject: [PATCH 244/410] add ResourceProvider types to server-api --- packages/server-api/src/index.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages/server-api/src/index.ts b/packages/server-api/src/index.ts index 7b9489495..2ccfc0bae 100644 --- a/packages/server-api/src/index.ts +++ b/packages/server-api/src/index.ts @@ -3,6 +3,26 @@ import { PropertyValues, PropertyValuesCallback } from './propertyvalues' export { PropertyValue, PropertyValues, PropertyValuesCallback } from './propertyvalues' + +export type SignalKResourceType= 'routes' | 'waypoints' |'notes' |'regions' |'charts' + +export interface ResourceProviderMethods { + pluginId: string + listResources: (type: string, query: { [key: string]: any }) => Promise + getResource: (type: string, id: string) => Promise + setResource: ( + type: string, + id: string, + value: { [key: string]: any } + ) => Promise + deleteResource: (type: string, id: string) => Promise +} + +export interface ResourceProvider { + types: SignalKResourceType[] + methods: ResourceProviderMethods +} + type Unsubscribe = () => {} export interface PropertyValuesEmitter { emitPropertyValue: (name: string, value: any) => void @@ -54,4 +74,5 @@ export interface Plugin { registerWithRouter?: (router: IRouter) => void signalKApiRoutes?: (router: IRouter) => IRouter enabledByDefault?: boolean + resourceProvider: ResourceProvider } From bf6da860d7db8004d1df8625834b9dce3a6ef2cb Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 21 Nov 2021 10:21:25 +0200 Subject: [PATCH 245/410] refactor: use Express types --- src/api/resources/index.ts | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 8c980fa6b..922fbbac7 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -4,6 +4,7 @@ import { buildResource } from './resources' import { validate } from './validate' import { SignalKResourceType, ResourceProvider, ResourceProviderMethods } from '@signalk/server-api' +import { Application, Handler, NextFunction, Request, Response } from 'express' const debug = Debug('signalk:resources') @@ -30,9 +31,13 @@ const API_METHODS = [ 'deleteRegion' ] +// FIXME use types from https://github.com/SignalK/signalk-server/pull/1358 +interface ResourceApplication extends Application { + handleMessage: any +} export class Resources { private resProvider: { [key: string]: ResourceProviderMethods | null } = {} - private server: any + private server: ResourceApplication // in-scope resource types private resourceTypes: SignalKResourceType[] = [ @@ -43,7 +48,8 @@ export class Resources { 'charts' ] - constructor(app: any) { + constructor(app: ResourceApplication) { + this.server = app this.start(app) } @@ -97,7 +103,7 @@ export class Resources { private initResourceRoutes() { // list all serviced paths under resources - this.server.get(`${SIGNALK_API_PATH}/resources`, (req: any, res: any) => { + this.server.get(`${SIGNALK_API_PATH}/resources`, (req: Request, res: Response) => { res.json(this.getResourcePaths()) }) @@ -122,6 +128,7 @@ export class Resources { ) this.server.use( +<<<<<<< HEAD `${SIGNALK_API_PATH}/resources/:resourceType/:resourceId`, async (req: any, res: any, next: any) => { const result = this.parseResourceRequest(req) @@ -144,6 +151,10 @@ export class Resources { this.server.use( `${SIGNALK_API_PATH}/resources/:resourceType`, async (req: any, res: any, next: any) => { +======= + `${SIGNALK_API_PATH}/resources/*`, + async (req: Request, res: Response, next: NextFunction) => { +>>>>>>> refactor: use Express types const result = this.parseResourceRequest(req) if (result) { const ar = await this.actionResourceRequest(result) @@ -185,8 +196,14 @@ export class Resources { return resPaths } +<<<<<<< HEAD private parseResourceRequest(req: any): ResourceRequest | undefined { debug('********* parse request *************') +======= + // parse api path request and return ResourceRequest object + private parseResourceRequest(req: Request): ResourceRequest | undefined { + debug('** req.originalUrl:', req.originalUrl) +>>>>>>> refactor: use Express types debug('** req.method:', req.method) debug('** req.body:', req.body) debug('** req.query:', req.query) From 130f94afcbe4039c31d63871738e326274c2af9b Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 22 Nov 2021 15:16:48 +1030 Subject: [PATCH 246/410] fix type --- src/api/resources/resources.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/api/resources/resources.ts b/src/api/resources/resources.ts index 3b79bc271..756610774 100644 --- a/src/api/resources/resources.ts +++ b/src/api/resources/resources.ts @@ -1,8 +1,9 @@ +import { SignalKResourceType } from '@signalk/server-api' import { getDistance, isValidCoordinate } from 'geolib' import ngeohash from 'ngeohash' -export const buildResource = (resType: string, data: any): any => { +export const buildResource = (resType: SignalKResourceType, data: any): any => { if (resType === 'routes') { return buildRoute(data) } From 7bf17a0f2c87a3eb5ad682bb6400c5c51fdb26c6 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 22 Nov 2021 16:18:02 +1030 Subject: [PATCH 247/410] chore: lint --- src/api/resources/resources.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/api/resources/resources.ts b/src/api/resources/resources.ts index 756610774..6a45f736c 100644 --- a/src/api/resources/resources.ts +++ b/src/api/resources/resources.ts @@ -2,7 +2,6 @@ import { SignalKResourceType } from '@signalk/server-api' import { getDistance, isValidCoordinate } from 'geolib' import ngeohash from 'ngeohash' - export const buildResource = (resType: SignalKResourceType, data: any): any => { if (resType === 'routes') { return buildRoute(data) @@ -70,7 +69,6 @@ const buildRoute = (rData: any): any => { return rte } - const buildWaypoint = (rData: any): any => { const wpt: any = { position: { @@ -112,7 +110,6 @@ const buildWaypoint = (rData: any): any => { return wpt } - const buildNote = (rData: any): any => { const note: any = {} if (typeof rData.title !== 'undefined') { @@ -153,7 +150,6 @@ const buildNote = (rData: any): any => { return note } - const buildRegion = (rData: any): any => { const reg: any = { feature: { @@ -207,10 +203,12 @@ const buildRegion = (rData: any): any => { return null } if ( - rData.points[0].latitude !== rData.points[rData.points.length-1].latitude && - rData.points[0].longitude !== rData.points[rData.points.length-1].longitude + rData.points[0].latitude !== + rData.points[rData.points.length - 1].latitude && + rData.points[0].longitude !== + rData.points[rData.points.length - 1].longitude ) { - rData.points.push( rData.points[0]) + rData.points.push(rData.points[0]) } coords = rData.points.map((p: any) => { return [p.longitude, p.latitude] From 8ffa64656c8b915655c5c439fd081cb0da327f23 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 22 Nov 2021 17:04:43 +1030 Subject: [PATCH 248/410] chore: update return type --- SERVERPLUGINS.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/SERVERPLUGINS.md b/SERVERPLUGINS.md index 75bbeb535..dbf791ae2 100644 --- a/SERVERPLUGINS.md +++ b/SERVERPLUGINS.md @@ -706,6 +706,9 @@ app.registerDeltaInputHandler((delta, next) => { Retrieve resource data for the supplied resource type and id. +This method invokes the `registered Resource Provider` for the supplied `resource_type` and returns a __resovled Promise__ containing the route resource data if successful or +a __rejected Promise__ containing an Error object if unsuccessful. + data for the full path of the directory where the plugin can persist its internal data, like data files.If a plugin wants to act as a resource provider, it will need to register its provider methods during startup using this function. @@ -713,9 +716,7 @@ Retrieve resource data for the supplied resource type and id. ```javascript let myRoute= app.resourcesApi.getResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a'); ``` -Will return the route resource data or `null` if a route with the supplied id cannot be found. - -_Example:_ +_Returns resolved Promise containing:_ ```json { "name": "Name of the route", From e039a9260dee4af082ac114aa49734808da7370b Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 22 Nov 2021 17:05:39 +1030 Subject: [PATCH 249/410] Use Express routing params for processing requests --- src/api/resources/index.ts | 494 ++++++++++++++++++++----------------- 1 file changed, 273 insertions(+), 221 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 922fbbac7..5bbe18077 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -3,7 +3,11 @@ import { v4 as uuidv4 } from 'uuid' import { buildResource } from './resources' import { validate } from './validate' -import { SignalKResourceType, ResourceProvider, ResourceProviderMethods } from '@signalk/server-api' +import { + ResourceProvider, + ResourceProviderMethods, + SignalKResourceType +} from '@signalk/server-api' import { Application, Handler, NextFunction, Request, Response } from 'express' const debug = Debug('signalk:resources') @@ -33,7 +37,7 @@ const API_METHODS = [ // FIXME use types from https://github.com/SignalK/signalk-server/pull/1358 interface ResourceApplication extends Application { - handleMessage: any + handleMessage: (id: string, data: any) => void } export class Resources { private resProvider: { [key: string]: ResourceProviderMethods | null } = {} @@ -84,15 +88,12 @@ export class Resources { debug(JSON.stringify(this.resProvider)) } - getResource(type: SignalKResourceType, id: string) { - debug(`** getResource(${type}, ${id})`) - return this.actionResourceRequest({ - method: 'GET', - body: {}, - query: {}, - resourceType: type, - resourceId: id - }) + getResource(resType: SignalKResourceType, resId: string) { + debug(`** getResource(${resType}, ${resId})`) + if (!this.checkForProvider(resType)) { + return Promise.reject(new Error(`No provider for ${resType}`)) + } + return this.resProvider[resType]?.getResource(resType, resId) } private start(app: any) { @@ -103,51 +104,199 @@ export class Resources { private initResourceRoutes() { // list all serviced paths under resources - this.server.get(`${SIGNALK_API_PATH}/resources`, (req: Request, res: Response) => { - res.json(this.getResourcePaths()) - }) + this.server.get( + `${SIGNALK_API_PATH}/resources`, + (req: Request, res: Response) => { + res.json(this.getResourcePaths()) + } + ) + // facilitate retrieval of a specific resource this.server.get( - `${SIGNALK_API_PATH}/resources/:resourceType/:resourceId/*`, - async (req: any, res: any, next: any) => { - const result = this.parseResourceRequest(req) - if (result) { - const ar = await this.actionResourceRequest(result) - if (typeof ar.statusCode !== 'undefined') { - debug(`${JSON.stringify(ar)}`) - res.status = ar.statusCode - res.send(ar.message) - } else { - res.json(ar) - } + `${SIGNALK_API_PATH}/resources/:resourceType/:resourceId`, + async (req: Request, res: Response, next: NextFunction) => { + debug(`** GET ${SIGNALK_API_PATH}/resources/:resourceType/:resourceId`) + if ( + !this.checkForProvider(req.params.resourceType as SignalKResourceType) + ) { + debug('** No provider found... calling next()...') + next() + return + } + if (!validate.uuid(req.params.resourceId)) { + res + .status(406) + .send(`Invalid resource id provided (${req.params.resourceId})`) + return + } + const retVal = await this.resProvider[ + req.params.resourceType + ]?.getResource(req.params.resourceType, req.params.resourceId) + if (retVal) { + res.json(retVal) } else { + res.status(404).send(`Resource not found! (${req.params.resourceId})`) + } + } + ) + + // facilitate retrieval of a collection of resource entries + this.server.get( + `${SIGNALK_API_PATH}/resources/:resourceType`, + async (req: Request, res: Response, next: NextFunction) => { + debug(`** GET ${SIGNALK_API_PATH}/resources/:resourceType`) + if ( + !this.checkForProvider(req.params.resourceType as SignalKResourceType) + ) { debug('** No provider found... calling next()...') next() + return + } + const retVal = await this.resProvider[ + req.params.resourceType + ]?.listResources(req.params.resourceType, req.query) + if (retVal) { + res.json(retVal) + } else { + res.status(404).send(`Error retrieving resources!`) } } ) + // facilitate creation of new resource entry of supplied type + this.server.post( + `${SIGNALK_API_PATH}/resources/:resourceType`, + async (req: Request, res: Response, next: NextFunction) => { + debug(`** POST ${SIGNALK_API_PATH}/resources/:resourceType`) + if ( + !this.checkForProvider(req.params.resourceType as SignalKResourceType) + ) { + debug('** No provider found... calling next()...') + next() + return + } + if (!validate.resource(req.params.resourceType, req.body.value)) { + res.status(406).send(`Invalid resource data supplied!`) + return + } + const id = UUID_PREFIX + uuidv4() + const retVal = await this.resProvider[ + req.params.resourceType + ]?.setResource(req.params.resourceType, id, req.body.value) + if (retVal) { + this.server.handleMessage( + this.resProvider[req.params.resourceType]?.pluginId as string, + this.buildDeltaMsg( + req.params.resourceType as SignalKResourceType, + id, + req.body.value + ) + ) + res + .status(200) + .send(`New ${req.params.resourceType} resource (${id}) saved.`) + } else { + res + .status(404) + .send(`Error saving ${req.params.resourceType} resource (${id})!`) + } + } + ) + +<<<<<<< HEAD this.server.use( <<<<<<< HEAD +======= + // facilitate creation / update of resource entry at supplied id + this.server.put( +>>>>>>> Use Express routing params for processing requests `${SIGNALK_API_PATH}/resources/:resourceType/:resourceId`, - async (req: any, res: any, next: any) => { - const result = this.parseResourceRequest(req) - if (result) { - const ar = await this.actionResourceRequest(result) - if (typeof ar.statusCode !== 'undefined') { - debug(`${JSON.stringify(ar)}`) - res.status = ar.statusCode - res.send(ar.message) - } else { - res.json(ar) - } + async (req: Request, res: Response, next: NextFunction) => { + debug(`** PUT ${SIGNALK_API_PATH}/resources/:resourceType/:resourceId`) + if ( + !this.checkForProvider(req.params.resourceType as SignalKResourceType) + ) { + debug('** No provider found... calling next()...') + next() + return + } + if (!validate.resource(req.params.resourceType, req.body.value)) { + res.status(406).send(`Invalid resource data supplied!`) + return + } + const retVal = await this.resProvider[ + req.params.resourceType + ]?.setResource( + req.params.resourceType, + req.params.resourceId, + req.body.value + ) + if (retVal) { + this.server.handleMessage( + this.resProvider[req.params.resourceType]?.pluginId as string, + this.buildDeltaMsg( + req.params.resourceType as SignalKResourceType, + req.params.resourceId, + req.body.value + ) + ) + res + .status(200) + .send( + `${req.params.resourceType} resource (${req.params.resourceId}) saved.` + ) } else { + res + .status(404) + .send( + `Error saving ${req.params.resourceType} resource (${req.params.resourceId})!` + ) + } + } + ) + + // facilitate deletion of specific of resource entry at supplied id + this.server.delete( + `${SIGNALK_API_PATH}/resources/:resourceType/:resourceId`, + async (req: Request, res: Response, next: NextFunction) => { + debug( + `** DELETE ${SIGNALK_API_PATH}/resources/:resourceType/:resourceId` + ) + if ( + !this.checkForProvider(req.params.resourceType as SignalKResourceType) + ) { debug('** No provider found... calling next()...') next() + return + } + if (!validate.uuid(req.params.resourceId)) { + res + .status(406) + .send(`Invalid resource id provided (${req.params.resourceId})`) + return + } + const retVal = await this.resProvider[ + req.params.resourceType + ]?.deleteResource(req.params.resourceType, req.params.resourceId) + if (retVal) { + this.server.handleMessage( + this.resProvider[req.params.resourceType]?.pluginId as string, + this.buildDeltaMsg( + req.params.resourceType as SignalKResourceType, + req.params.resourceId, + null + ) + ) + res.status(200).send(`Resource (${req.params.resourceId}) deleted.`) + } else { + res + .status(400) + .send(`Error deleting resource (${req.params.resourceId})!`) } } ) +<<<<<<< HEAD this.server.use( `${SIGNALK_API_PATH}/resources/:resourceType`, async (req: any, res: any, next: any) => { @@ -162,12 +311,80 @@ export class Resources { debug(`${JSON.stringify(ar)}`) res.status = ar.statusCode res.send(ar.message) +======= + // facilitate API requests + this.server.put( + `${SIGNALK_API_PATH}/resources/:apiFunction`, + async (req: Request, res: Response, next: NextFunction) => { + debug(`** PUT ${SIGNALK_API_PATH}/resources/:apiFunction`) + + // check for valid API method request + if (!API_METHODS.includes(req.params.apiFunction)) { + res.status(501).send(`Invalid API method ${req.params.apiFunction}!`) + return + } + let resType: SignalKResourceType = 'waypoints' + if (req.params.apiFunction.toLowerCase().indexOf('waypoint') !== -1) { + resType = 'waypoints' + } + if (req.params.apiFunction.toLowerCase().indexOf('route') !== -1) { + resType = 'routes' + } + if (req.params.apiFunction.toLowerCase().indexOf('note') !== -1) { + resType = 'notes' + } + if (req.params.apiFunction.toLowerCase().indexOf('region') !== -1) { + resType = 'regions' + } + if (!this.checkForProvider(resType)) { + res.status(501).send(`No provider for ${resType}!`) + return + } + let resId: string = '' + let resValue: any = null + + if (req.params.apiFunction.toLowerCase().indexOf('set') !== -1) { + resValue = buildResource(resType, req.body) + if (!resValue) { + res.status(406).send(`Invalid resource data supplied!`) + return + } + if (!req.body.id) { + resId = UUID_PREFIX + uuidv4() +>>>>>>> Use Express routing params for processing requests } else { - res.json(ar) + if (!validate.uuid(req.body.id)) { + res.status(406).send(`Invalid resource id supplied!`) + return + } + resId = req.body.id + } + } + if (req.params.apiFunction.toLowerCase().indexOf('delete') !== -1) { + resValue = null + if (!req.body.id) { + res.status(406).send(`No resource id supplied!`) + return } + if (!validate.uuid(req.body.id)) { + res.status(406).send(`Invalid resource id supplied!`) + return + } + resId = req.body.id + } + const retVal = await this.resProvider[resType]?.setResource( + resType, + resId, + resValue + ) + if (retVal) { + this.server.handleMessage( + this.resProvider[resType]?.pluginId as string, + this.buildDeltaMsg(resType, resId, resValue) + ) + res.status(200).send(`SUCCESS: ${req.params.apiFunction} complete.`) } else { - debug('** No provider found... calling next()...') - next() + res.status(404).send(`ERROR: ${req.params.apiFunction} incomplete!`) } } ) @@ -196,6 +413,7 @@ export class Resources { return resPaths } +<<<<<<< HEAD <<<<<<< HEAD private parseResourceRequest(req: any): ResourceRequest | undefined { debug('********* parse request *************') @@ -247,196 +465,30 @@ export class Resources { debug('Invalid resource type or no provider for this type!') return undefined } +======= + private checkForProvider(resType: SignalKResourceType): boolean { + return this.resourceTypes.includes(resType) && this.resProvider[resType] + ? true + : false +>>>>>>> Use Express routing params for processing requests } - private async actionResourceRequest(req: ResourceRequest): Promise { - debug('********* action request *************') - debug(req) - - // check for registered resource providers - if (!this.resProvider) { - return { statusCode: 501, message: `No Provider` } - } - - if ( - !this.resourceTypes.includes(req.resourceType) || - !this.resProvider[req.resourceType] - ) { - return { statusCode: 501, message: `No Provider` } - } - - // check for API method request - if (req.apiMethod && API_METHODS.includes(req.apiMethod)) { - debug(`API Method (${req.apiMethod})`) - req = this.transformApiRequest(req) - } - - return await this.execResourceRequest(req) - } - - private transformApiRequest(req: ResourceRequest): ResourceRequest { - if (req.apiMethod?.indexOf('delete') !== -1) { - req.method = 'DELETE' - } - if (req.apiMethod?.indexOf('set') !== -1) { - if (!req.body.id) { - req.method = 'POST' - } else { - req.resourceId = req.body.id - } - req.body = { value: buildResource(req.resourceType, req.body) ?? {} } - } - return req - } - - private async execResourceRequest(req: ResourceRequest): Promise { - debug('********* execute request *************') - debug(req) - if (req.method === 'GET') { - let retVal: any - if (!req.resourceId) { - retVal = await this.resProvider[req.resourceType]?.listResources( - req.resourceType, - req.query - ) - return retVal - ? retVal - : { statusCode: 404, message: `Error retrieving resources!` } - } - if (!validate.uuid(req.resourceId)) { - return { - statusCode: 406, - message: `Invalid resource id provided (${req.resourceId})` - } - } - retVal = await this.resProvider[req.resourceType]?.getResource( - req.resourceType, - req.resourceId - ) - return retVal - ? retVal - : { - statusCode: 404, - message: `Resource not found (${req.resourceId})!` - } - } - - if (req.method === 'DELETE' || req.method === 'PUT') { - if (!req.resourceId) { - return { statusCode: 406, value: `No resource id provided!` } - } - if (!validate.uuid(req.resourceId)) { - return { - statusCode: 406, - message: `Invalid resource id provided (${req.resourceId})!` - } - } - if ( - req.method === 'DELETE' || - (req.method === 'PUT' && - typeof req.body.value !== 'undefined' && - req.body.value == null) - ) { - const retVal = await this.resProvider[req.resourceType]?.deleteResource( - req.resourceType, - req.resourceId - ) - if (retVal) { - this.sendDelta( - this.resProvider[req.resourceType]?.pluginId as string, - req.resourceType, - req.resourceId, - null - ) - return { - statusCode: 200, - message: `Resource (${req.resourceId}) deleted.` - } - } else { - return { - statusCode: 400, - message: `Error deleting resource (${req.resourceId})!` - } - } - } - } - - if (req.method === 'POST' || req.method === 'PUT') { - if (typeof req.body.value === 'undefined' || req.body.value == null) { - return { statusCode: 406, message: `No resource data supplied!` } - } - - if (!validate.resource(req.resourceType, req.body.value)) { - return { statusCode: 406, message: `Invalid resource data supplied!` } - } - - if (req.method === 'POST') { - const id = UUID_PREFIX + uuidv4() - const retVal = await this.resProvider[req.resourceType]?.setResource( - req.resourceType, - id, - req.body.value - ) - if (retVal) { - this.sendDelta( - this.resProvider[req.resourceType]?.pluginId as string, - req.resourceType, - id, - req.body.value - ) - return { statusCode: 200, message: `Resource (${id}) saved.` } - } else { - return { statusCode: 400, message: `Error saving resource (${id})!` } - } - } - if (req.method === 'PUT') { - if (!req.resourceId) { - return { statusCode: 406, message: `No resource id provided!` } - } - const retVal = await this.resProvider[req.resourceType]?.setResource( - req.resourceType, - req.resourceId, - req.body.value - ) - if (retVal) { - this.sendDelta( - this.resProvider[req.resourceType]?.pluginId as string, - req.resourceType, - req.resourceId, - req.body.value - ) - return { - statusCode: 200, - message: `Resource (${req.resourceId}) updated.` - } - } else { - return { - statusCode: 400, - message: `Error updating resource (${req.resourceId})!` - } - } - } - } - } - - private sendDelta( - providerId: string, - type: string, - id: string, - value: any - ): void { - debug(`** Sending Delta: resources.${type}.${id}`) - this.server.handleMessage(providerId, { + private buildDeltaMsg( + resType: SignalKResourceType, + resid: string, + resValue: any + ): any { + return { updates: [ { values: [ { - path: `resources.${type}.${id}`, - value + path: `resources.${resType}.${resid}`, + value: resValue } ] } ] - }) + } } } From f892617c7ef8dbc721d398834dfa7415e8d6aa6b Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 24 Nov 2021 09:13:43 +1030 Subject: [PATCH 250/410] throw on error --- src/api/resources/index.ts | 94 ++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 49 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 5bbe18077..6c81e5d0a 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -12,15 +12,6 @@ import { Application, Handler, NextFunction, Request, Response } from 'express' const debug = Debug('signalk:resources') -interface ResourceRequest { - method: 'GET' | 'PUT' | 'POST' | 'DELETE' - body: any - query: { [key: string]: any } - resourceType: SignalKResourceType - resourceId: string - apiMethod?: string | null -} - const SIGNALK_API_PATH = `/signalk/v1/api` const UUID_PREFIX = 'urn:mrn:signalk:uuid:' @@ -35,10 +26,10 @@ const API_METHODS = [ 'deleteRegion' ] -// FIXME use types from https://github.com/SignalK/signalk-server/pull/1358 interface ResourceApplication extends Application { handleMessage: (id: string, data: any) => void } + export class Resources { private resProvider: { [key: string]: ResourceProviderMethods | null } = {} private server: ResourceApplication @@ -129,14 +120,15 @@ export class Resources { .send(`Invalid resource id provided (${req.params.resourceId})`) return } - const retVal = await this.resProvider[ - req.params.resourceType - ]?.getResource(req.params.resourceType, req.params.resourceId) - if (retVal) { - res.json(retVal) - } else { + try { + const retVal = await this.resProvider[ + req.params.resourceType + ]?.getResource(req.params.resourceType, req.params.resourceId) + res.json(retVal) + } catch (err) { res.status(404).send(`Resource not found! (${req.params.resourceId})`) } + } ) @@ -152,12 +144,12 @@ export class Resources { next() return } - const retVal = await this.resProvider[ - req.params.resourceType - ]?.listResources(req.params.resourceType, req.query) - if (retVal) { - res.json(retVal) - } else { + try { + const retVal = await this.resProvider[ + req.params.resourceType + ]?.listResources(req.params.resourceType, req.query) + res.json(retVal) + } catch (err) { res.status(404).send(`Error retrieving resources!`) } } @@ -180,10 +172,11 @@ export class Resources { return } const id = UUID_PREFIX + uuidv4() - const retVal = await this.resProvider[ - req.params.resourceType - ]?.setResource(req.params.resourceType, id, req.body.value) - if (retVal) { + try { + const retVal = await this.resProvider[ + req.params.resourceType + ]?.setResource(req.params.resourceType, id, req.body.value) + this.server.handleMessage( this.resProvider[req.params.resourceType]?.pluginId as string, this.buildDeltaMsg( @@ -195,11 +188,11 @@ export class Resources { res .status(200) .send(`New ${req.params.resourceType} resource (${id}) saved.`) - } else { + } catch (err) { res .status(404) .send(`Error saving ${req.params.resourceType} resource (${id})!`) - } + } } ) @@ -224,14 +217,15 @@ export class Resources { res.status(406).send(`Invalid resource data supplied!`) return } - const retVal = await this.resProvider[ - req.params.resourceType - ]?.setResource( - req.params.resourceType, - req.params.resourceId, - req.body.value - ) - if (retVal) { + try { + const retVal = await this.resProvider[ + req.params.resourceType + ]?.setResource( + req.params.resourceType, + req.params.resourceId, + req.body.value + ) + this.server.handleMessage( this.resProvider[req.params.resourceType]?.pluginId as string, this.buildDeltaMsg( @@ -245,7 +239,7 @@ export class Resources { .send( `${req.params.resourceType} resource (${req.params.resourceId}) saved.` ) - } else { + } catch (err) { res .status(404) .send( @@ -275,10 +269,11 @@ export class Resources { .send(`Invalid resource id provided (${req.params.resourceId})`) return } - const retVal = await this.resProvider[ - req.params.resourceType - ]?.deleteResource(req.params.resourceType, req.params.resourceId) - if (retVal) { + try { + const retVal = await this.resProvider[ + req.params.resourceType + ]?.deleteResource(req.params.resourceType, req.params.resourceId) + this.server.handleMessage( this.resProvider[req.params.resourceType]?.pluginId as string, this.buildDeltaMsg( @@ -288,7 +283,7 @@ export class Resources { ) ) res.status(200).send(`Resource (${req.params.resourceId}) deleted.`) - } else { + } catch (err) { res .status(400) .send(`Error deleting resource (${req.params.resourceId})!`) @@ -372,18 +367,19 @@ export class Resources { } resId = req.body.id } - const retVal = await this.resProvider[resType]?.setResource( - resType, - resId, - resValue - ) - if (retVal) { + + try { + const retVal = await this.resProvider[resType]?.setResource( + resType, + resId, + resValue + ) this.server.handleMessage( this.resProvider[resType]?.pluginId as string, this.buildDeltaMsg(resType, resId, resValue) ) res.status(200).send(`SUCCESS: ${req.params.apiFunction} complete.`) - } else { + } catch (err) { res.status(404).send(`ERROR: ${req.params.apiFunction} incomplete!`) } } From 273fc65a579b6322d838e9edb2a6b1b5bd4beed5 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 24 Nov 2021 09:34:21 +1030 Subject: [PATCH 251/410] PUT/POST payloads directly in `body`..not `value:` --- src/api/resources/index.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 6c81e5d0a..760d903b1 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -167,7 +167,7 @@ export class Resources { next() return } - if (!validate.resource(req.params.resourceType, req.body.value)) { + if (!validate.resource(req.params.resourceType, req.body)) { res.status(406).send(`Invalid resource data supplied!`) return } @@ -175,14 +175,14 @@ export class Resources { try { const retVal = await this.resProvider[ req.params.resourceType - ]?.setResource(req.params.resourceType, id, req.body.value) + ]?.setResource(req.params.resourceType, id, req.body) this.server.handleMessage( this.resProvider[req.params.resourceType]?.pluginId as string, this.buildDeltaMsg( req.params.resourceType as SignalKResourceType, id, - req.body.value + req.body ) ) res @@ -213,7 +213,7 @@ export class Resources { next() return } - if (!validate.resource(req.params.resourceType, req.body.value)) { + if (!validate.resource(req.params.resourceType, req.body)) { res.status(406).send(`Invalid resource data supplied!`) return } @@ -223,7 +223,7 @@ export class Resources { ]?.setResource( req.params.resourceType, req.params.resourceId, - req.body.value + req.body ) this.server.handleMessage( @@ -231,7 +231,7 @@ export class Resources { this.buildDeltaMsg( req.params.resourceType as SignalKResourceType, req.params.resourceId, - req.body.value + req.body ) ) res From 2464cf333131eef81d454b802bff7c4061088735 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 24 Nov 2021 10:50:23 +1030 Subject: [PATCH 252/410] remove resourceId validity check on GET --- src/api/resources/index.ts | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 760d903b1..af21d575b 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -114,12 +114,6 @@ export class Resources { next() return } - if (!validate.uuid(req.params.resourceId)) { - res - .status(406) - .send(`Invalid resource id provided (${req.params.resourceId})`) - return - } try { const retVal = await this.resProvider[ req.params.resourceType @@ -213,6 +207,14 @@ export class Resources { next() return } + if (req.params.resourceType !== 'charts') { + if(!validate.uuid(req.params.resourceId)) { + res + .status(406) + .send(`Invalid resource id provided (${req.params.resourceId})`) + return + } + } if (!validate.resource(req.params.resourceType, req.body)) { res.status(406).send(`Invalid resource data supplied!`) return @@ -263,12 +265,7 @@ export class Resources { next() return } - if (!validate.uuid(req.params.resourceId)) { - res - .status(406) - .send(`Invalid resource id provided (${req.params.resourceId})`) - return - } + try { const retVal = await this.resProvider[ req.params.resourceType From f94625c465ab7efbbf24a062ed21fc8ca3bed400 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 24 Nov 2021 15:01:01 +1030 Subject: [PATCH 253/410] chore: Updated documentation --- RESOURCE_PROVIDER_PLUGINS.md | 493 +++++++++++++++++++++++------------ SERVERPLUGINS.md | 39 +-- 2 files changed, 340 insertions(+), 192 deletions(-) diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index 4e2c7312d..0b493fb05 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -1,39 +1,65 @@ # Resource Provider plugins +_This document should be read in conjunction with [SERVERPLUGINS.md](./SERVERPLUGINS.md) as it contains additional information regarding the development of plugins that facilitate the storage and retrieval of resource data._ + +--- + ## Overview -This document should be read in conjunction with [SERVERPLUGINS.md](./SERVERPLUGINS.md) as it contains additional information regarding the development of plugins that facilitate the storage and retrieval of resource data (e.g. routes, waypoints, notes, regions and charts). +The SignalK specification defines the path `/signalk/v1/api/resources` for accessing resources to aid in navigation and operation of the vessel. -Resource storage and retrieval is de-coupled from core server function to provide the flexibility to implement the appropriate resource storage solution for specific Signal K implementations. +It also defines the schema for the following __Common__ resource types: +- routes +- waypoints +- notes +- regions +- charts -The Signal K Node server will pass requests made to the following paths to registered resource providers: -- `/signalk/v1/api/resources` -- `/signalk/v1/api/resources/routes` -- `/signalk/v1/api/resources/waypoints` -- `/signalk/v1/api/resources/notes` -- `/signalk/v1/api/resources/regions` -- `/signalk/v1/api/resources/charts` +each of with its own path under the root `resources` path _(e.g. `/signalk/v1/api/resources/routes`)_. -Resource providers will receive request data via a `ResourceProvider` interface which they implement. It is the responsibility of the resource provider to persist resource data in storage (PUT, POST), retrieve the requested resources (GET) and remove resource entries (DELETE). +It should also be noted that the `/signalk/v1/api/resources` path can also host other types of resource data which can be grouped within a __Custom__ path name _(e.g. `/signalk/v1/api/resources/fishingZones`)_. -Resource data passed to the resource provider plugin has been validated by the server and can be considered ready for storage. +The SignalK server does not natively provide the ability to store or retrieve resource data for either __Common__ and __Custom__ resource types. +This functionality needs to be provided by one or more server plugins that handle the data for specific resource types. +These plugins are called __Resource Providers__. -## Resource Providers +This de-coupling of resource request handling and storage / retrieval provides great flexibility to ensure that an appropriate resource storage solution can be configured for your SignalK implementation. -A `resource provider plugin` is responsible for the storage and retrieval of resource data. +It is important to understand that the SignalK server handles requests for __Custom__ resource types differently to requests for __Common__ types, please ensure you refer to the relevant section of this document. -It should implement the necessary functions to: -- Persist each resource with its associated id -- Retrieve an individual resource with the supplied id -- Retrieve a list of resources that match the supplied qery criteria. +--- -Data is passed to and from the plugin via the methods defined in the __resourceProvider__ interface which the plugin must implement. +## Common Resource Type Provider: -_Definition: `resourceProvider` interface._ -```javascript -resourceProvider: { - types: [], +As detailed earlier in this document, the __Common__ resource types are: +`routes`, `waypoints`, `notes`, `regions` & `charts`. + +For the `/signalk/v1/api/resources` path and the __Common__ resource type sub-paths, the Signal K server will pre-process the request before passing on the request details on to the registered resource provider. + +The SignalK server performs the following tasks when pre-processing a request: +- Checks for a registered provider for the resource type +- Checks the validity of the supplied resource id +- For requests to store data, the submitted resource data is validated. + +Only when all pre-processing tasks have completed successfully will the request be passed on to the resource provider plugin. + +Resource providers for __Common__ resource types need to implement the `ResourceProvider` interface which registers with the SignalK server the: +- Resource types provided for by the plugin +- Methods to use when requests are passed to the plugin. It is these methods that implement the saving / deletion of resource data to storage and the retrieval of requested resources. + + +### Resource Provider Interface + +--- +The `ResourceProvider` interface defines the contract between the the Resource Provider plugin and the SignalK server and has the following definition _(which it and other related types can be imported from `@signalk/server-api`)_: + +```typescript +import { SignalKResourceType } from '@signalk/server-api' +// SignalKResourceType= 'routes' | 'waypoints' |'notes' |'regions' |'charts' + +interface ResourceProvider: { + types: SignalKResourceType[], methods: { listResources: (type:string, query: {[key:string]:any})=> Promise getResource: (type:string, id:string)=> Promise @@ -42,41 +68,189 @@ resourceProvider: { } } ``` +where: + +- `types`: An array containing a list of __Common__ resource types provided for by the plugin +- `methods`: An object containing the methods resource requests are passed to by the SignalK server. The plugin __MUST__ implement each method, even if that operation is not supported by the plugin! + +#### __Method Details:__ + +--- +__`listResources(type, query)`__: This method is called when a request is made for resource entries of a specific resource type that match a specifiec criteria. + +_Note: It is the responsibility of the resource provider plugin to filter the resources returned as per the supplied query parameters._ + +`type:` Array of __Common__ resource types provied for by the plugin _e.g. ['routes','waypoints']_ + +`query:` Object contining `key | value` pairs repesenting the parameters by which to filter the returned entries _e.g. {distance,'50000}_ + +`returns:` +- Resolved Promise containing a list of resource entries on completion. +- Rejected Promise containing an Error if incomplete or not implemented. + + +_Example resource request:_ +``` +GET /signalk/v1/api/resources/waypoints?bbox=5.4,25.7,6.9,31.2&distance=30000 +``` +_ResourceProvider method invocation:_ + +```javascript +listResources( + 'waypoints', + { + bbox: '5.4,25.7,6.9,31.2', + distance: 30000 + } +); +``` + +--- +__`getResource(type, id)`__: This method is called when a request is made for a specific resource entry of the supplied resource type and id. + +`type:` String containing one of the __Common__ resource types _e.g. 'waypoints'_ + +`id:` String containing the target resource entry id _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ + +`returns:` +- Resolved Promise containing the resource entry on completion. +- Rejected Promise containing an Error if incomplete or not implemented. + +_Example resource request:_ +``` +GET /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99 +``` +_ResourceProvider method invocation:_ -This interface is used by the server to direct requests to the plugin. +```javascript +getResource( + 'routes', + 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99' +); +``` + +--- +__`setResource(type, id, value)`__: This method is called when a request is made to save / update a resource entry of the specified resource type, with the supplied id and data. + +`type:` Array of __Common__ resource types provied for by the plugin _e.g. ['routes','waypoints']_ + +`id:` String containing the target resource entry id _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ + +`value:` Resource data to be stored. + +`returns:` +- Resolved Promise containing a list of resource entries on completion. +- Rejected Promise containing an Error if incomplete or not implemented. + +_Example PUT resource request:_ +``` +PUT /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99 {resource_data} +``` +_ResourceProvider method invocation:_ + +```javascript +setResource( + 'routes', + 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99', + {} +); +``` + +_Example POST resource request:_ +``` +POST /signalk/v1/api/resources/routes {resource_data} +``` +_ResourceProvider method invocation:_ -It contains the following attributes: -- `types`: An array containing the names of the resource types the plugin is a provider for. Names of the resource types are: `routes, waypoints, notes, regions, charts`. +```javascript +setResource( + 'routes', + '', + {} +); +``` + +--- +__`deleteResource(type, id)`__: This method is called when a request is made to remove the specific resource entry of the supplied resource type and id. + +`type:` String containing one of the __Common__ resource types _e.g. 'waypoints'_ + +`id:` String containing the target resource entry id _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ + +`returns:` +- Resolved Promise on completion. +- Rejected Promise containing an Error if incomplete or not implemented. + +_Example resource request:_ +``` +DELETE /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99 +``` +_ResourceProvider method invocation:_ + +```javascript +deleteResource( + 'routes', + 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99' +); +``` -- `methods`: The methods to which the server dispatches requests. The plugin will implement these methods to perform the necessary save or retrieval operation. Each method returns a promise containing either resource data or `null` if an error is encountered. +--- + +### Example: -_Example: Plugin acting as resource provider for routes & waypoints._ +_Defintion for Resource Provider plugin providing for the retrieval routes & waypoints._ ```javascript +// SignalK server plugin module.exports = function (app) { + let plugin= { id: 'mypluginid', name: 'My Resource Providerplugin', + // ResourceProvider interface resourceProvider: { types: ['routes','waypoints'], methods: { listResources: (type, params)=> { - return Promise.resolve() { ... }; + return new Promise( (resolve, reject) => { + // fetch resource entries from storage + .... + if(ok) { // success + resolve({ + 'id1': { ... }, + 'id2': { ... }, + }); + } else { // error + reject(new Error('Error encountered!') + } + } }, - getResource: (type:string, id:string)=> { - return Promise.resolve() { ... }; - } , - setResource: (type:string, id:string, value:any)=> { - return Promise.resolve() { ... }; + getResource: (type, id)=> { + // fetch resource entries from storage + .... + if(ok) { // success + return Promise.resolve({ + ... + }); + } else { // error + reject(new Error('Error encountered!') + } }, - deleteResource: (type:string, id:string)=> { - return Promise.resolve() { ... }; ; + setResource: (type, id, value)=> { + // not implemented + return Promise.reject(new Error('NOT IMPLEMENTED!')); + }, + deleteResource: (type, id)=> { + // not implemented + return Promise.reject(new Error('NOT IMPLEMENTED!')); } } }, + start: (options)=> { ... app.resourceApi.register(this.id, this.resourceProvider); }, + stop: ()=> { app.resourceApi.unRegister(this.id); ... @@ -85,35 +259,37 @@ module.exports = function (app) { } ``` + +### Registering the Resource Provider: --- -## Plugin Startup - Registering the Resource Provider: +For the plugin to be registered by the SignalK server, it needs to call the server's `resourcesApi.register()` function during plugin startup. + +The server `resourcesApi.register()` function has the following signature: -To register your plugin as a resource provider the server's `resourcesApi.register()` function should be called within the plugin `start()` function passing the `resourceProvider` interface. +```typescript +app.resourcesApi.register(pluginId: string, resourceProvider: ResourceProvider) +``` +where: +- `pluginId`: is the plugin's id +- `resourceProvider`: is a reference to the plugins ResourceProvider interface. -This registers the resource types and the methods with the server so they are called when requests to resource paths are made. +_Note: A resource type can only have one registered plugin, so if more than one installed plugin attempts to register as a provider for the same resource type, only the first one is registered and it's implemented methods will service requests._ _Example:_ ```javascript module.exports = function (app) { + let plugin= { id: 'mypluginid', name: 'My Resource Providerplugin', resourceProvider: { types: ['routes','waypoints'], methods: { - listResources: (type, params)=> { - return Promise.resolve() { ... }; - }, - getResource: (type:string, id:string)=> { - return Promise.resolve() { ... }; - } , - setResource: (type:string, id:string, value:any)=> { - return Promise.resolve() { ... }; - }, - deleteResource: (type:string, id:string)=> { - return Promise.resolve() { ... }; ; - } + listResources: (type, params)=> { ... }, + getResource: (type:string, id:string)=> { ... } , + setResource: (type:string, id:string, value:any)=> { ... }, + deleteResource: (type:string, id:string)=> { ... } } } } @@ -124,15 +300,25 @@ module.exports = function (app) { } } ``` + +### Un-registering the Resource Provider: --- -## Plugin Stop - Un-registering the Resource Provider: +When a resource provider plugin is disabled, it should un-register as a provider to ensure resource requests are no longer directed to it by calling the SignalK server's `resourcesApi.unRegister()` function during shutdown. + +The server `resourcesApi.unRegister()` function has the following signature: + +```typescript +app.resourcesApi.unRegister(pluginId: string) +``` +where: +- `pluginId`: is the plugin's id -When a resource provider plugin is disabled it should un-register as a provider so resource requests are not directed to it. This is done by calling the server's `resourcesApi.unRegister()` function passing the `plugin.id` within the plugin's `stop()` function. _Example:_ ```javascript module.exports = function (app) { + let plugin= { id: 'mypluginid', name: 'My Resource Providerplugin', @@ -150,150 +336,111 @@ module.exports = function (app) { ``` --- -## Operation: - -The Server will dispatch requests made to: -- `/signalk/v1/api/resources/` - -OR -- the `resources API` endpoints +## Custom Resource Type Provider: -to the plugin's `resourceProvider.methods` for each of the resource types listed in `resourceProvider.types`. +Custom resource types are collections of resource entries residing within a sub-path with a user defined name under `/signalk/v1/api/resources`. -Each method defined in `resourceProvider.methods` must have a signature as specified in the __resourceProvider interface__. Each method returns a `Promise` containing the resultant resource data or `null` if an error occurrred or the operation is incomplete. +_Example:_ +``` +/signalk/v1/api/resources/fishingZones +``` +_Note: A custom resource path name __CANNOT__ be one of the __Common__ resource types i.e. `routes`, `waypoints`, `notes`, `regions` or `charts`.__ -### __List Resources:__ +Unlike the __Common Resource Type Providers__: +- The plugin __DOES NOT__ implement the `ResourceProvider` interface +- Requests to __Custom__ resource type sub-paths and any submitted data __WILL NOT__ be pre-processed or validated by the Signal K server +- The plugin __WILL__ need to implement a route handler for the necessary path(s) +- The plugin __WILL__ need to implement any necessary data validation. -`GET` requests that are not for a specific resource will be dispatched to the `listResources` method passing the resource type and any query data as parameters. -Query parameters are passed as an object conatining `key | value` pairs. +### Router Path Handlers +--- -_Example: GET /signalk/v1/api/resources/waypoints?bbox=5.4,25.7,6.9,31.2&distance=30000_ -```javascript -query= { - bbox: '5.4,25.7,6.9,31.2', - distance: 30000 -} -``` +To set up a router path handler, you use the reference to the SignalK server `app` passed to the plugin to register the paths for which requests will be passed to the plugin. -It is the responsibility of the resource provider plugin to filter the resources returned as per the supplied query parameters. +This should be done during plugin startup within the plugin `start()` function. -`listResources()` returns a Promise containing a JSON object listing resources by id. +_Note: To ensure that your resource path is listed when a request to `/signalk/v1/api/resources` is made ensure you use the full path as outlined in the example below._ -_Example: List all routes._ +_Example:_ ```javascript -GET /signalk/v1/api/resources/routes -listResources('routes', {}) - -returns Promise<{ - "resource_id1": { ... }, - "resource_id2": { ... }, - ... - "resource_idn": { ... } -}> -``` - -_Example: List waypoints within the bounded area._ -```typescript -GET /signalk/v1/api/resources/waypoints?bbox=5.4,25.7,6.9,31.2 +module.exports = function (app) { -listResources('waypoints', {bbox: '5.4,25.7,6.9,31.2'}) + let plugin= { + id: 'mypluginid', + name: 'My Resource Providerplugin', + start: (options) => { + // setup router path handlers + initPathHandlers(app); + ... + } + } + + function initPathHandlers(app) { + app.get( + `/signalk/v1/api/resources/myResType`, + (request, response)=> { + // retrieve resource(s) + let result= getMyResources(); + response.status(200).json(result); + } + ); + app.post( + `/signalk/v1/api/resources/myResType`, + (request, response)=> { + // create new resource + ... + } + ); + router.put( + `/signalk/v1/api/resources/myResType/:id`, + (request, response)=> { + // create / update resource with supplied id + ... + } + ); + router.delete( + `/signalk/v1/api/resources/myResType/:id`, + (request, response)=> { + // delete the resource with supplied id + ... + } + ); + } -returns Promise<{ - "resource_id1": { ... }, - "resource_id2": { ... } -}> ``` -### __Retrieve a specific resource:__ +Once registered requests to `/signalk/v1/api/resources/myResType` will be passed to the respective handler based on the request type _(i.e. GET, PUT, POST, DELETE)_ and the request details are available in the `request` parameter. -`GET` requests for a specific resource will be dispatched to the `getResource` method passing the resource type and id as parameters. +For more information regarding the `request` and `response` parameters see the [Express Routing](https://expressjs.com/en/guide/routing.html) documentation. -`getResource()` returns a Promise containing a JSON object with the resource data. +### Data Validation +--- -_Example: Retrieve route._ -```typescript -GET /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a +When providing for resource data to be persisted to storage (PUT, POST) it is recommended that data validation implemented within the `app.put()` and `app.post()` route handlers and that the appropriate `status` is returned. -getResource( - 'routes', - 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', - {} -) -``` - -_Returns a Promise containing the resource data:_ -```typescript -Promise<{ - "name": "Name of the route", - "description": "Description of the route", - "distance": 18345, - "feature": { ... } -}> -``` - -A request for a resource attribute will pass the attibute path as an array in the query object with the key `resAttrib`. - -_Example: Get waypoint geometry._ +_Example:_ ```javascript -GET /signalk/v1/api/resources/waypoints/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a/feature/geometry - -getResource( - 'waypoints', - 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', - { resAttrib: ['feature','geometry'] } +app.post( + `/signalk/v1/api/resources/myResType`, + (request, response)=> { + // validate submitted data + let ok= validate(request.body); + if (ok) { //valid data + if (saveResource(request.body)) { + response.status(200).send('OK'); + } else { + response.status(404).send('ERROR svaing resource!'); + } + } else { + response.status(406).send('ERROR: Invalid data!'); + } + } ) -``` -_Returns a Promise containing the value of `geometry` attribute of the waypoint._ -```typescript -Promise<{ - "type": "Point", - "coordinates": [70.4,6.45] -}> -``` - -### __Saving Resources:__ - -`PUT` requests to a path containing the resource id are used to store data associated with the resource id. These will be dispatched to the `setResource` method passing the resource type, id and data as parameters. -`setResource() ` returns Promise on success and Promise on failure. - -_Example: Update / add waypoint with the supplied id._ -```javascript -PUT /signalk/v1/api/resources/waypoints/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a {} - -setResource('waypoints', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) - -returns Promise ``` +--- -`POST` requests to a resource path that do not contina the resource id will be dispatched to the `setResource` method passing the resource type, an id (generated by the server) and resource data as parameters. - -`setResource()` returns `true` on success and `null` on failure. - -_Example: New route record._ -```typescript -POST /signalk/v1/api/resources/routes {} - -setResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) - -returns Promise -``` - -### __Deleting Resources:__ - -`DELETE` requests to a path containing the resource id will be dispatched to the `deleteResource` method passing the resource type and id as parameters. - -`deleteResource()` returns `true` on success and `null` on failure. - -_Example: Delete region with supplied id._ -```typescript -DELETE /signalk/v1/api/resources/regions/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a - -deleteResource('regions', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a') - -returns Promise -``` diff --git a/SERVERPLUGINS.md b/SERVERPLUGINS.md index dbf791ae2..1d8a53797 100644 --- a/SERVERPLUGINS.md +++ b/SERVERPLUGINS.md @@ -22,9 +22,9 @@ The plugin module must export a single `function(app)` that must return an objec ## Getting Started with Plugin Development -To get started with SignalK plugin development, you can follow the following guide. +To get started with SignalK plugin development, you can follow this guide. -_Note: For plugins acting as a provider for one or more of the resource types listed in the Signal K specification (e.g. routes, waypoints, notes, regions or charts) refer to __[RESOURCE_PROVIDER_PLUGINS.md](./RESOURCE_PROVIDER_PLUGINS.md)__ for details. +_Note: For plugins acting as a provider for one or more of the SignalK resource types listed in the specification (`routes`, `waypoints`, `notes`, `regions` or `charts`) please refer to __[RESOURCE_PROVIDER_PLUGINS.md](./RESOURCE_PROVIDER_PLUGINS.md)__ for additional details._ ### Project setup @@ -704,29 +704,30 @@ app.registerDeltaInputHandler((delta, next) => { ### `app.resourcesApi.getResource(resource_type, resource_id)` -Retrieve resource data for the supplied resource type and id. +Retrieve resource data for the supplied SignalK resource type and resource id. -This method invokes the `registered Resource Provider` for the supplied `resource_type` and returns a __resovled Promise__ containing the route resource data if successful or -a __rejected Promise__ containing an Error object if unsuccessful. - - data for the full path of the directory where the plugin can persist its internal data, like data files.If a plugin wants to act as a resource provider, it will need to register its provider methods during startup using this function. +_Valid resource types are `routes`, `waypoints`, `notes`, `regions` & `charts`._ +This method invokes the `registered Resource Provider` for the supplied `resource_type` and returns a `resovled` __Promise__ containing the resource data if successful or +a `rejected` __Promise__ containing an __Error__ object if unsuccessful. +_Example:_ ```javascript -let myRoute= app.resourcesApi.getResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a'); -``` -_Returns resolved Promise containing:_ -```json -{ - "name": "Name of the route", - "description": "Description of the route", - "distance": 18345, - "feature": { ... } -} +let resource= app.resourcesApi.getResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a'); +resource.then ( (data)=> { + // route data + console.log(data); + ... +}).catch (error) { + // handle error + console.log(error.message); + ... +} ``` + ### `app.resourcesApi.register(pluginId, resourceProvider)` If a plugin wants to act as a resource provider, it will need to register its provider methods during startup using this function. @@ -744,7 +745,7 @@ module.exports = function (app) { methods: { ... } } start: function(options) { - ... + // do plugin start up app.resourcesApi.register(this.id, this.resourceProvider); } ... @@ -771,7 +772,7 @@ module.exports = function (app) { ... stop: function(options) { app.resourcesApi.unRegister(this.id); - ... + // do plugin shutdown } } } From 25b3145b48ef30346aacaed636ffb18273d1df5e Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 24 Nov 2021 17:22:29 +1030 Subject: [PATCH 254/410] add chartId test & require alignment with spec. --- src/api/resources/index.ts | 50 +++++++++++++++++++++++------------ src/api/resources/validate.ts | 28 ++++++++++++++++---- 2 files changed, 56 insertions(+), 22 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index af21d575b..418e94fac 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -118,11 +118,10 @@ export class Resources { const retVal = await this.resProvider[ req.params.resourceType ]?.getResource(req.params.resourceType, req.params.resourceId) - res.json(retVal) + res.json(retVal) } catch (err) { res.status(404).send(`Resource not found! (${req.params.resourceId})`) } - } ) @@ -142,7 +141,7 @@ export class Resources { const retVal = await this.resProvider[ req.params.resourceType ]?.listResources(req.params.resourceType, req.query) - res.json(retVal) + res.json(retVal) } catch (err) { res.status(404).send(`Error retrieving resources!`) } @@ -165,12 +164,23 @@ export class Resources { res.status(406).send(`Invalid resource data supplied!`) return } - const id = UUID_PREFIX + uuidv4() + if (!validate.resource(req.params.resourceType, req.body)) { + res.status(406).send(`Invalid resource data supplied!`) + return + } + + let id: string + if (req.params.resourceType === 'charts') { + id = req.body.identifier + } else { + id = UUID_PREFIX + uuidv4() + } + try { const retVal = await this.resProvider[ req.params.resourceType ]?.setResource(req.params.resourceType, id, req.body) - + this.server.handleMessage( this.resProvider[req.params.resourceType]?.pluginId as string, this.buildDeltaMsg( @@ -186,7 +196,7 @@ export class Resources { res .status(404) .send(`Error saving ${req.params.resourceType} resource (${id})!`) - } + } } ) @@ -207,14 +217,20 @@ export class Resources { next() return } - if (req.params.resourceType !== 'charts') { - if(!validate.uuid(req.params.resourceId)) { - res - .status(406) - .send(`Invalid resource id provided (${req.params.resourceId})`) - return - } + + let isValidId: boolean + if (req.params.resourceType === 'charts') { + isValidId = validate.chartId(req.params.resourceId) + } else { + isValidId = validate.uuid(req.params.resourceId) + } + if (isValidId) { + res + .status(406) + .send(`Invalid resource id provided (${req.params.resourceId})`) + return } + if (!validate.resource(req.params.resourceType, req.body)) { res.status(406).send(`Invalid resource data supplied!`) return @@ -227,7 +243,7 @@ export class Resources { req.params.resourceId, req.body ) - + this.server.handleMessage( this.resProvider[req.params.resourceType]?.pluginId as string, this.buildDeltaMsg( @@ -241,7 +257,7 @@ export class Resources { .send( `${req.params.resourceType} resource (${req.params.resourceId}) saved.` ) - } catch (err) { + } catch (err) { res .status(404) .send( @@ -265,12 +281,12 @@ export class Resources { next() return } - + try { const retVal = await this.resProvider[ req.params.resourceType ]?.deleteResource(req.params.resourceType, req.params.resourceId) - + this.server.handleMessage( this.resProvider[req.params.resourceType]?.pluginId as string, this.buildDeltaMsg( diff --git a/src/api/resources/validate.ts b/src/api/resources/validate.ts index 0d8d6cffe..16eb3c9a8 100644 --- a/src/api/resources/validate.ts +++ b/src/api/resources/validate.ts @@ -19,21 +19,29 @@ export const validate = { case 'regions': return validateRegion(value) break + case 'charts': + return validateChart(value) + break default: return true } }, - // ** returns true if id is a valid Signal K UUID ** + // returns true if id is a valid Signal K UUID uuid: (id: string): boolean => { const uuid = RegExp( '^urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$' ) return uuid.test(id) + }, + + // returns true if id is a valid Signal K Chart resource id + chartId: (id: string): boolean => { + const uuid = RegExp('(^[A-Za-z0-9_-]{8,}$)') + return uuid.test(id) } } -// ** validate route data const validateRoute = (r: any): boolean => { if (r.start) { const l = r.start.split('/') @@ -60,7 +68,6 @@ const validateRoute = (r: any): boolean => { return true } -// ** validate waypoint data const validateWaypoint = (r: any): boolean => { if (typeof r.position === 'undefined') { return false @@ -81,7 +88,7 @@ const validateWaypoint = (r: any): boolean => { return true } -// ** validate note data +// validate note data const validateNote = (r: any): boolean => { if (!r.region && !r.position && !r.geohash) { return false @@ -100,7 +107,6 @@ const validateNote = (r: any): boolean => { return true } -// ** validate region data const validateRegion = (r: any): boolean => { if (!r.geohash && !r.feature) { return false @@ -122,3 +128,15 @@ const validateRegion = (r: any): boolean => { } return true } + +const validateChart = (r: any): boolean => { + if (!r.name || !r.identifier || !r.chartFormat) { + return false + } + + if (!r.tilemapUrl && !r.chartUrl) { + return false + } + + return true +} From 5f92793afb442db1976ccf47e3c2b92c580bfb10 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 24 Nov 2021 17:44:05 +1030 Subject: [PATCH 255/410] add charts API methods --- src/api/resources/openApi.json | 474 ++++++++++++++++++++++++++++++++- 1 file changed, 473 insertions(+), 1 deletion(-) diff --git a/src/api/resources/openApi.json b/src/api/resources/openApi.json index 8eddcac32..80350d21f 100644 --- a/src/api/resources/openApi.json +++ b/src/api/resources/openApi.json @@ -787,7 +787,7 @@ "/resources/regions/": { "post": { "tags": ["resources/regions"], - "summary": "Add a new Regkion", + "summary": "Add a new Region", "requestBody": { "description": "Region details", "required": true, @@ -1056,6 +1056,308 @@ }, + "/resources/charts/": { + "post": { + "tags": ["resources/charts"], + "summary": "Add a new Chart", + "requestBody": { + "description": "Chart details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Chart resource", + "required": ["feature"], + "properties": { + "name": { + "description": "Chart common name", + "example":"NZ615 Marlborough Sounds", + "type": "string" + }, + "identifier": { + "type": "string", + "description": "Chart number", + "example":"NZ615" + }, + "description": { + "type": "string", + "description": "A description of the chart" + }, + "tilemapUrl": { + "type": "string", + "description": "A url to the tilemap of the chart for use in TMS chartplotting apps", + "example":"http://{server}:8080/mapcache/NZ615" + }, + "region": { + "type": "string", + "description": "Region related to note. A pointer to a region UUID. Alternative to geohash" + }, + "geohash": { + "description": "Position related to chart. Alternative to region", + "type": "string" + }, + "chartUrl": { + "type": "string", + "description": "A url to the chart file's storage location", + "example":"file:///home/pi/freeboard/mapcache/NZ615" + }, + "scale": { + "type": "integer", + "description": "The scale of the chart, the larger number from 1:200000" + }, + "chartLayers": { + "type": "array", + "description": "If the chart format is WMS, the layers enabled for the chart.", + "items": { + "type": "string", + "description": "Identifier for the layer." + } + }, + "bounds": { + "type": "array", + "description": "The bounds of the chart. An array containing the position of the upper left corner, and the lower right corner. Useful when the chart isn't inherently geo-referenced.", + "items": { + "description": "Position of a corner of the chart", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + }, + "altitude": { + "type": "number", + "format": "float" + } + } + } + }, + "chartFormat": { + "type": "string", + "description": "The format of the chart", + "enum": [ + "gif", + "geotiff", + "kap", + "png", + "jpg", + "kml", + "wkt", + "topojson", + "geojson", + "gpx", + "tms", + "wms", + "S-57", + "S-63", + "svg", + "other" + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + + "/resources/charts/{id}": { + "parameters": { + "name": "id", + "in": "path", + "description": "Chart id", + "required": true, + "schema": { + "type": "string", + "pattern": "(^[A-Za-z0-9_-]{8,}$)" + } + }, + + "get": { + "tags": ["resources/charts"], + "summary": "Retrieve Chart with supplied id", + "responses": { + "200": { + "description": "List of resources identified by their UUID", + "content": { + "application/json": { + "schema": { + "description": "List of Signal K resources", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + } + }, + + "put": { + "tags": ["resources/charts"], + "summary": "Add / update a new Chart with supplied id", + "requestBody": { + "description": "Chart details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Chart resource", + "required": ["feature"], + "properties": { + "name": { + "description": "Chart common name", + "example":"NZ615 Marlborough Sounds", + "type": "string" + }, + "identifier": { + "type": "string", + "description": "Chart number", + "example":"NZ615" + }, + "description": { + "type": "string", + "description": "A description of the chart" + }, + "tilemapUrl": { + "type": "string", + "description": "A url to the tilemap of the chart for use in TMS chartplotting apps", + "example":"http://{server}:8080/mapcache/NZ615" + }, + "region": { + "type": "string", + "description": "Region related to note. A pointer to a region UUID. Alternative to geohash" + }, + "geohash": { + "description": "Position related to chart. Alternative to region", + "type": "string" + }, + "chartUrl": { + "type": "string", + "description": "A url to the chart file's storage location", + "example":"file:///home/pi/freeboard/mapcache/NZ615" + }, + "scale": { + "type": "integer", + "description": "The scale of the chart, the larger number from 1:200000" + }, + "chartLayers": { + "type": "array", + "description": "If the chart format is WMS, the layers enabled for the chart.", + "items": { + "type": "string", + "description": "Identifier for the layer." + } + }, + "bounds": { + "type": "array", + "description": "The bounds of the chart. An array containing the position of the upper left corner, and the lower right corner. Useful when the chart isn't inherently geo-referenced.", + "items": { + "description": "Position of a corner of the chart", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + }, + "altitude": { + "type": "number", + "format": "float" + } + } + } + }, + "chartFormat": { + "type": "string", + "description": "The format of the chart", + "enum": [ + "gif", + "geotiff", + "kap", + "png", + "jpg", + "kml", + "wkt", + "topojson", + "geojson", + "gpx", + "tms", + "wms", + "S-57", + "S-63", + "svg", + "other" + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + }, + + "delete": { + "tags": ["resources/charts"], + "summary": "Remove Chart with supplied id", + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + + }, + "/resources/setWaypoint": { "put": { "tags": ["resources/api"], @@ -1514,6 +1816,176 @@ } } } + }, + + "/resources/setChart": { + "put": { + "tags": ["resources/api"], + "summary": "Add / update a Chart", + "requestBody": { + "description": "Chart attributes", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Chart resource", + "required": ["identifier","tilemapUrl","chartUrl","chartFormat"], + "properties": { + "name": { + "description": "Chart common name", + "example":"NZ615 Marlborough Sounds", + "type": "string" + }, + "identifier": { + "type": "string", + "description": "Chart number", + "example":"NZ615" + }, + "description": { + "type": "string", + "description": "A description of the chart" + }, + "oneOf": [ + { + "tilemapUrl": { + "type": "string", + "description": "A url to the tilemap of the chart for use in TMS chartplotting apps", + "example":"http://{server}:8080/mapcache/NZ615" + } + }, + { + "chartUrl": { + "type": "string", + "description": "A url to the chart file's storage location", + "example":"file:///home/pi/freeboard/mapcache/NZ615" + } + } + ], + "region": { + "type": "string", + "description": "Region related to note. A pointer to a region UUID. Alternative to geohash" + }, + "geohash": { + "description": "Position related to chart. Alternative to region", + "type": "string" + }, + "scale": { + "type": "integer", + "description": "The scale of the chart, the larger number from 1:200000" + }, + "chartLayers": { + "type": "array", + "description": "If the chart format is WMS, the layers enabled for the chart.", + "items": { + "type": "string", + "description": "Identifier for the layer." + } + }, + "bounds": { + "type": "array", + "description": "The bounds of the chart. An array containing the position of the upper left corner, and the lower right corner. Useful when the chart isn't inherently geo-referenced.", + "items": { + "description": "Position of a corner of the chart", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + }, + "altitude": { + "type": "number", + "format": "float" + } + } + } + }, + "chartFormat": { + "type": "string", + "description": "The format of the chart", + "enum": [ + "gif", + "geotiff", + "kap", + "png", + "jpg", + "kml", + "wkt", + "topojson", + "geojson", + "gpx", + "tms", + "wms", + "S-57", + "S-63", + "svg", + "other" + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + + "/resources/deleteChart": { + "put": { + "tags": ["resources/api"], + "summary": "Remove a Chart", + "requestBody": { + "description": "Chart identifier", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["id"], + "properties": { + "id": { + "type": "string", + "pattern": "(^[A-Za-z0-9_-]{8,}$)", + "description": "Chart identifier" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } } } From 05a3160788894e8e3897d60275e64c5300a8dfb7 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Thu, 25 Nov 2021 13:21:23 +1030 Subject: [PATCH 256/410] allow registering custom resource types --- src/api/resources/index.ts | 95 +++++++++++++++++++++----------------- 1 file changed, 53 insertions(+), 42 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 418e94fac..393a5dfc1 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -34,8 +34,7 @@ export class Resources { private resProvider: { [key: string]: ResourceProviderMethods | null } = {} private server: ResourceApplication - // in-scope resource types - private resourceTypes: SignalKResourceType[] = [ + private signalkResTypes: SignalKResourceType[] = [ 'routes', 'waypoints', 'notes', @@ -160,13 +159,12 @@ export class Resources { next() return } - if (!validate.resource(req.params.resourceType, req.body)) { - res.status(406).send(`Invalid resource data supplied!`) - return - } - if (!validate.resource(req.params.resourceType, req.body)) { - res.status(406).send(`Invalid resource data supplied!`) - return + + if (this.signalkResTypes.includes(req.params.resourceType as SignalKResourceType)) { + if (!validate.resource(req.params.resourceType, req.body)) { + res.status(406).send(`Invalid resource data supplied!`) + return + } } let id: string @@ -218,23 +216,26 @@ export class Resources { return } - let isValidId: boolean - if (req.params.resourceType === 'charts') { - isValidId = validate.chartId(req.params.resourceId) - } else { - isValidId = validate.uuid(req.params.resourceId) - } - if (isValidId) { - res - .status(406) - .send(`Invalid resource id provided (${req.params.resourceId})`) - return - } + if (this.signalkResTypes.includes(req.params.resourceType as SignalKResourceType)) { + let isValidId: boolean + if (req.params.resourceType === 'charts') { + isValidId = validate.chartId(req.params.resourceId) + } else { + isValidId = validate.uuid(req.params.resourceId) + } + if (isValidId) { + res + .status(406) + .send(`Invalid resource id provided (${req.params.resourceId})`) + return + } - if (!validate.resource(req.params.resourceType, req.body)) { - res.status(406).send(`Invalid resource data supplied!`) - return + if (!validate.resource(req.params.resourceType, req.body)) { + res.status(406).send(`Invalid resource data supplied!`) + return + } } + try { const retVal = await this.resProvider[ req.params.resourceType @@ -401,24 +402,9 @@ export class Resources { private getResourcePaths(): { [key: string]: any } { const resPaths: { [key: string]: any } = {} - Object.entries(this.resProvider).forEach((p: any) => { - if (p[1]) { - resPaths[p[0]] = `Path containing ${p[0]}, each named with a UUID` - } - }) - // check for other plugins servicing paths under ./resources - this.server._router.stack.forEach((i: any) => { - if (i.route && i.route.path && typeof i.route.path === 'string') { - if (i.route.path.indexOf(`${SIGNALK_API_PATH}/resources`) !== -1) { - const r = i.route.path.split('/') - if (r.length > 5 && !(r[5] in resPaths)) { - resPaths[ - r[5] - ] = `Path containing ${r[5]} resources (provided by plug-in)` - } - } - } - }) + for( let i in this.resProvider) { + resPaths[i] = `Path containing ${i.slice(-1)==='s' ? i.slice(0, i.length-1) : i} resources (provided by ${this.resProvider[i]?.pluginId})` + } return resPaths } @@ -476,10 +462,35 @@ export class Resources { } ======= private checkForProvider(resType: SignalKResourceType): boolean { +<<<<<<< HEAD return this.resourceTypes.includes(resType) && this.resProvider[resType] ? true : false >>>>>>> Use Express routing params for processing requests +======= + debug(`** checkForProvider(${resType})`) + debug(this.resProvider[resType]) + + if(this.resProvider[resType]) { + if( + !this.resProvider[resType]?.listResources || + !this.resProvider[resType]?.getResource || + !this.resProvider[resType]?.setResource || + !this.resProvider[resType]?.deleteResource || + typeof this.resProvider[resType]?.listResources !== 'function' || + typeof this.resProvider[resType]?.getResource !== 'function' || + typeof this.resProvider[resType]?.setResource !== 'function' || + typeof this.resProvider[resType]?.deleteResource !== 'function' ) + { + return false + } else { + return true + } + } + else { + return false + } +>>>>>>> allow registering custom resource types } private buildDeltaMsg( From 66db0350bab881df83c8f74b81dc3a9ab93a7df4 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Thu, 25 Nov 2021 14:16:04 +1030 Subject: [PATCH 257/410] chore: update docs --- RESOURCE_PROVIDER_PLUGINS.md | 270 +++++++++++------------------------ 1 file changed, 81 insertions(+), 189 deletions(-) diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index 0b493fb05..eb31cd98f 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -26,27 +26,32 @@ These plugins are called __Resource Providers__. This de-coupling of resource request handling and storage / retrieval provides great flexibility to ensure that an appropriate resource storage solution can be configured for your SignalK implementation. -It is important to understand that the SignalK server handles requests for __Custom__ resource types differently to requests for __Common__ types, please ensure you refer to the relevant section of this document. +SignalK server handles requests for both __Common__ and __Custom__ resource types in a similar manner, the only difference being that it does not perform any validation on __Custom__ resource data, so a plugin can act a s a provider for both types. --- +## Server Operation: -## Common Resource Type Provider: +The Signal K server handles all requests to `/signalk/v1/api/resources` and all sub-paths, before passing on the request to the registered resource provider plugin. -As detailed earlier in this document, the __Common__ resource types are: -`routes`, `waypoints`, `notes`, `regions` & `charts`. +The following operations are performed by the server when a request is received: +- Checks for a registered provider for the resource type +- Checks that ResourceProvider methods are defined +- For __Common__ resource types, checks the validity of the: + - Resource id + - Submitted resource data. -For the `/signalk/v1/api/resources` path and the __Common__ resource type sub-paths, the Signal K server will pre-process the request before passing on the request details on to the registered resource provider. +Upon successful completion of these operations the request will then be passed to the registered resource provider plugin. -The SignalK server performs the following tasks when pre-processing a request: -- Checks for a registered provider for the resource type -- Checks the validity of the supplied resource id -- For requests to store data, the submitted resource data is validated. +--- +## Resource Provider plugin: -Only when all pre-processing tasks have completed successfully will the request be passed on to the resource provider plugin. +For a plugin to be considered a Resource Provider it needs to implement the `ResourceProvider` interface. -Resource providers for __Common__ resource types need to implement the `ResourceProvider` interface which registers with the SignalK server the: +By implementing this interface the plugin is able to register with the SignalK server the: - Resource types provided for by the plugin -- Methods to use when requests are passed to the plugin. It is these methods that implement the saving / deletion of resource data to storage and the retrieval of requested resources. +- Methods to used to action requests. + +It is these methods that perform the retrival, saving and deletion of resources from storage. ### Resource Provider Interface @@ -55,11 +60,8 @@ Resource providers for __Common__ resource types need to implement the `Resource The `ResourceProvider` interface defines the contract between the the Resource Provider plugin and the SignalK server and has the following definition _(which it and other related types can be imported from `@signalk/server-api`)_: ```typescript -import { SignalKResourceType } from '@signalk/server-api' -// SignalKResourceType= 'routes' | 'waypoints' |'notes' |'regions' |'charts' - interface ResourceProvider: { - types: SignalKResourceType[], + types: string[], methods: { listResources: (type:string, query: {[key:string]:any})=> Promise getResource: (type:string, id:string)=> Promise @@ -70,7 +72,7 @@ interface ResourceProvider: { ``` where: -- `types`: An array containing a list of __Common__ resource types provided for by the plugin +- `types`: An array containing a list of resource types provided for by the plugin. These can be a mixture of both __Common__ and __Custom__ resource types. - `methods`: An object containing the methods resource requests are passed to by the SignalK server. The plugin __MUST__ implement each method, even if that operation is not supported by the plugin! #### __Method Details:__ @@ -80,9 +82,9 @@ __`listResources(type, query)`__: This method is called when a request is made f _Note: It is the responsibility of the resource provider plugin to filter the resources returned as per the supplied query parameters._ -`type:` Array of __Common__ resource types provied for by the plugin _e.g. ['routes','waypoints']_ +`type:` String containing the type of resource to retrieve. -`query:` Object contining `key | value` pairs repesenting the parameters by which to filter the returned entries _e.g. {distance,'50000}_ +`query:` Object contining `key | value` pairs repesenting the parameters by which to filter the returned entries. _e.g. {distance,'50000}_ `returns:` - Resolved Promise containing a list of resource entries on completion. @@ -108,9 +110,9 @@ listResources( --- __`getResource(type, id)`__: This method is called when a request is made for a specific resource entry of the supplied resource type and id. -`type:` String containing one of the __Common__ resource types _e.g. 'waypoints'_ +`type:` String containing the type of resource to retrieve. -`id:` String containing the target resource entry id _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ +`id:` String containing the target resource entry id. _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ `returns:` - Resolved Promise containing the resource entry on completion. @@ -132,9 +134,9 @@ getResource( --- __`setResource(type, id, value)`__: This method is called when a request is made to save / update a resource entry of the specified resource type, with the supplied id and data. -`type:` Array of __Common__ resource types provied for by the plugin _e.g. ['routes','waypoints']_ +`type:` String containing the type of resource to store. -`id:` String containing the target resource entry id _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ +`id:` String containing the target resource entry id. _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ `value:` Resource data to be stored. @@ -173,9 +175,9 @@ setResource( --- __`deleteResource(type, id)`__: This method is called when a request is made to remove the specific resource entry of the supplied resource type and id. -`type:` String containing one of the __Common__ resource types _e.g. 'waypoints'_ +`type:` String containing the type of resource to delete. -`id:` String containing the target resource entry id _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ +`id:` String containing the target resource entry id. _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ `returns:` - Resolved Promise on completion. @@ -194,76 +196,10 @@ deleteResource( ); ``` +### Registering a Resource Provider: --- -### Example: - -_Defintion for Resource Provider plugin providing for the retrieval routes & waypoints._ -```javascript -// SignalK server plugin -module.exports = function (app) { - - let plugin= { - id: 'mypluginid', - name: 'My Resource Providerplugin', - // ResourceProvider interface - resourceProvider: { - types: ['routes','waypoints'], - methods: { - listResources: (type, params)=> { - return new Promise( (resolve, reject) => { - // fetch resource entries from storage - .... - if(ok) { // success - resolve({ - 'id1': { ... }, - 'id2': { ... }, - }); - } else { // error - reject(new Error('Error encountered!') - } - } - }, - getResource: (type, id)=> { - // fetch resource entries from storage - .... - if(ok) { // success - return Promise.resolve({ - ... - }); - } else { // error - reject(new Error('Error encountered!') - } - }, - setResource: (type, id, value)=> { - // not implemented - return Promise.reject(new Error('NOT IMPLEMENTED!')); - }, - deleteResource: (type, id)=> { - // not implemented - return Promise.reject(new Error('NOT IMPLEMENTED!')); - } - } - }, - - start: (options)=> { - ... - app.resourceApi.register(this.id, this.resourceProvider); - }, - - stop: ()=> { - app.resourceApi.unRegister(this.id); - ... - } - } -} -``` - - -### Registering the Resource Provider: ---- - -For the plugin to be registered by the SignalK server, it needs to call the server's `resourcesApi.register()` function during plugin startup. +To register the resource provider plugin with the SignalK server, the server's `resourcesApi.register()` function should be called during plugin startup. The server `resourcesApi.register()` function has the following signature: @@ -274,7 +210,7 @@ where: - `pluginId`: is the plugin's id - `resourceProvider`: is a reference to the plugins ResourceProvider interface. -_Note: A resource type can only have one registered plugin, so if more than one installed plugin attempts to register as a provider for the same resource type, only the first one is registered and it's implemented methods will service requests._ +_Note: A resource type can only have one registered plugin, so if more than one plugin attempts to register as a provider for the same resource type, the first plugin to call the `register()` function will be registered by the server for the resource types defined in the ResourceProvider interface!_ _Example:_ ```javascript @@ -304,7 +240,7 @@ module.exports = function (app) { ### Un-registering the Resource Provider: --- -When a resource provider plugin is disabled, it should un-register as a provider to ensure resource requests are no longer directed to it by calling the SignalK server's `resourcesApi.unRegister()` function during shutdown. +When a resource provider plugin is disabled, it should un-register itself to ensure resource requests are no longer directed to it by calling the SignalK server. This should be done by calling the server's `resourcesApi.unRegister()` function during shutdown. The server `resourcesApi.unRegister()` function has the following signature: @@ -334,113 +270,69 @@ module.exports = function (app) { } } ``` ---- -## Custom Resource Type Provider: - -Custom resource types are collections of resource entries residing within a sub-path with a user defined name under `/signalk/v1/api/resources`. - -_Example:_ -``` -/signalk/v1/api/resources/fishingZones -``` - -_Note: A custom resource path name __CANNOT__ be one of the __Common__ resource types i.e. `routes`, `waypoints`, `notes`, `regions` or `charts`.__ - -Unlike the __Common Resource Type Providers__: -- The plugin __DOES NOT__ implement the `ResourceProvider` interface -- Requests to __Custom__ resource type sub-paths and any submitted data __WILL NOT__ be pre-processed or validated by the Signal K server -- The plugin __WILL__ need to implement a route handler for the necessary path(s) -- The plugin __WILL__ need to implement any necessary data validation. - - -### Router Path Handlers ---- - -To set up a router path handler, you use the reference to the SignalK server `app` passed to the plugin to register the paths for which requests will be passed to the plugin. +--- -This should be done during plugin startup within the plugin `start()` function. +### __Example:__ -_Note: To ensure that your resource path is listed when a request to `/signalk/v1/api/resources` is made ensure you use the full path as outlined in the example below._ +Resource Provider plugin providing for the retrieval of routes & waypoints. -_Example:_ ```javascript - +// SignalK server plugin module.exports = function (app) { let plugin= { id: 'mypluginid', name: 'My Resource Providerplugin', - start: (options) => { - // setup router path handlers - initPathHandlers(app); - ... - } - } - - function initPathHandlers(app) { - app.get( - `/signalk/v1/api/resources/myResType`, - (request, response)=> { - // retrieve resource(s) - let result= getMyResources(); - response.status(200).json(result); - } - ); - app.post( - `/signalk/v1/api/resources/myResType`, - (request, response)=> { - // create new resource - ... - } - ); - router.put( - `/signalk/v1/api/resources/myResType/:id`, - (request, response)=> { - // create / update resource with supplied id - ... - } - ); - router.delete( - `/signalk/v1/api/resources/myResType/:id`, - (request, response)=> { - // delete the resource with supplied id - ... + // ResourceProvider interface + resourceProvider: { + types: ['routes','waypoints'], + methods: { + listResources: (type, params)=> { + return new Promise( (resolve, reject) => { + // fetch resource entries from storage + .... + if(ok) { // success + resolve({ + 'id1': { ... }, + 'id2': { ... }, + }); + } else { // error + reject(new Error('Error encountered!') + } + } + }, + getResource: (type, id)=> { + // fetch resource entries from storage + .... + if(ok) { // success + return Promise.resolve({ + ... + }); + } else { // error + reject(new Error('Error encountered!') + } + }, + setResource: (type, id, value)=> { + // not implemented + return Promise.reject(new Error('NOT IMPLEMENTED!')); + }, + deleteResource: (type, id)=> { + // not implemented + return Promise.reject(new Error('NOT IMPLEMENTED!')); + } } - ); - } - -``` - -Once registered requests to `/signalk/v1/api/resources/myResType` will be passed to the respective handler based on the request type _(i.e. GET, PUT, POST, DELETE)_ and the request details are available in the `request` parameter. - -For more information regarding the `request` and `response` parameters see the [Express Routing](https://expressjs.com/en/guide/routing.html) documentation. - -### Data Validation ---- + }, -When providing for resource data to be persisted to storage (PUT, POST) it is recommended that data validation implemented within the `app.put()` and `app.post()` route handlers and that the appropriate `status` is returned. + start: (options)=> { + ... + app.resourceApi.register(this.id, this.resourceProvider); + }, -_Example:_ -```javascript -app.post( - `/signalk/v1/api/resources/myResType`, - (request, response)=> { - // validate submitted data - let ok= validate(request.body); - if (ok) { //valid data - if (saveResource(request.body)) { - response.status(200).send('OK'); - } else { - response.status(404).send('ERROR svaing resource!'); - } - } else { - response.status(406).send('ERROR: Invalid data!'); + stop: ()=> { + app.resourceApi.unRegister(this.id); + ... } } -) - +} ``` ---- - - From e7f3ee265290fbc7c765a197dccaa7a139981533 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Thu, 25 Nov 2021 14:34:17 +1030 Subject: [PATCH 258/410] chore: lint --- src/api/resources/index.ts | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 393a5dfc1..76276cfeb 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -160,7 +160,11 @@ export class Resources { return } - if (this.signalkResTypes.includes(req.params.resourceType as SignalKResourceType)) { + if ( + this.signalkResTypes.includes( + req.params.resourceType as SignalKResourceType + ) + ) { if (!validate.resource(req.params.resourceType, req.body)) { res.status(406).send(`Invalid resource data supplied!`) return @@ -172,7 +176,7 @@ export class Resources { id = req.body.identifier } else { id = UUID_PREFIX + uuidv4() - } + } try { const retVal = await this.resProvider[ @@ -216,7 +220,11 @@ export class Resources { return } - if (this.signalkResTypes.includes(req.params.resourceType as SignalKResourceType)) { + if ( + this.signalkResTypes.includes( + req.params.resourceType as SignalKResourceType + ) + ) { let isValidId: boolean if (req.params.resourceType === 'charts') { isValidId = validate.chartId(req.params.resourceId) @@ -402,8 +410,10 @@ export class Resources { private getResourcePaths(): { [key: string]: any } { const resPaths: { [key: string]: any } = {} - for( let i in this.resProvider) { - resPaths[i] = `Path containing ${i.slice(-1)==='s' ? i.slice(0, i.length-1) : i} resources (provided by ${this.resProvider[i]?.pluginId})` + for (const i in this.resProvider) { + resPaths[i] = `Path containing ${ + i.slice(-1) === 's' ? i.slice(0, i.length - 1) : i + } resources (provided by ${this.resProvider[i]?.pluginId})` } return resPaths } @@ -471,8 +481,8 @@ export class Resources { debug(`** checkForProvider(${resType})`) debug(this.resProvider[resType]) - if(this.resProvider[resType]) { - if( + if (this.resProvider[resType]) { + if ( !this.resProvider[resType]?.listResources || !this.resProvider[resType]?.getResource || !this.resProvider[resType]?.setResource || @@ -480,14 +490,13 @@ export class Resources { typeof this.resProvider[resType]?.listResources !== 'function' || typeof this.resProvider[resType]?.getResource !== 'function' || typeof this.resProvider[resType]?.setResource !== 'function' || - typeof this.resProvider[resType]?.deleteResource !== 'function' ) - { + typeof this.resProvider[resType]?.deleteResource !== 'function' + ) { return false } else { return true } - } - else { + } else { return false } >>>>>>> allow registering custom resource types From 19ef69696c9129c0d11ded5bf2018d1fe8fc4a71 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 27 Nov 2021 16:06:13 +1030 Subject: [PATCH 259/410] update types --- packages/server-api/src/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/server-api/src/index.ts b/packages/server-api/src/index.ts index 2ccfc0bae..3f3b45925 100644 --- a/packages/server-api/src/index.ts +++ b/packages/server-api/src/index.ts @@ -5,9 +5,10 @@ export { PropertyValue, PropertyValues, PropertyValuesCallback } from './propert export type SignalKResourceType= 'routes' | 'waypoints' |'notes' |'regions' |'charts' +export type ResourceTypes= SignalKResourceType[] | string[] export interface ResourceProviderMethods { - pluginId: string + pluginId?: string listResources: (type: string, query: { [key: string]: any }) => Promise getResource: (type: string, id: string) => Promise setResource: ( @@ -19,7 +20,7 @@ export interface ResourceProviderMethods { } export interface ResourceProvider { - types: SignalKResourceType[] + types: ResourceTypes methods: ResourceProviderMethods } From 087620d500620c166f81df07e3033d0c1365f1d9 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 27 Nov 2021 16:08:14 +1030 Subject: [PATCH 260/410] align response formatting forGET ./resources/ --- src/api/resources/index.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 76276cfeb..47e3b625a 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -411,9 +411,12 @@ export class Resources { private getResourcePaths(): { [key: string]: any } { const resPaths: { [key: string]: any } = {} for (const i in this.resProvider) { - resPaths[i] = `Path containing ${ - i.slice(-1) === 's' ? i.slice(0, i.length - 1) : i - } resources (provided by ${this.resProvider[i]?.pluginId})` + resPaths[i] = { + description: `Path containing ${ + i.slice(-1) === 's' ? i.slice(0, i.length - 1) : i + } resources`, + $source: this.resProvider[i]?.pluginId + } } return resPaths } From 27ec94edfc09738eedcf4e2898f510671565d058 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 7 Nov 2021 15:28:54 +1030 Subject: [PATCH 261/410] Add Signal K standard resource path handling --- src/api/resources/index.ts | 265 ++++++++++++++++++++++++++++++++++ src/api/resources/validate.ts | 109 ++++++++++++++ src/put.js | 7 + 3 files changed, 381 insertions(+) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 47e3b625a..39515f783 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -1,5 +1,6 @@ import Debug from 'debug' import { v4 as uuidv4 } from 'uuid' +<<<<<<< HEAD import { buildResource } from './resources' import { validate } from './validate' @@ -523,4 +524,268 @@ export class Resources { ] } } +======= +import { validate } from './validate' + +const debug = Debug('signalk:resources') + +interface ResourceRequest { + method: 'GET' | 'PUT' | 'POST' | 'DELETE' + body: any + query: {[key:string]: any} + resourceType: string + resourceId: string +} + +interface ResourceProvider { + listResources: (type:string, query: {[key:string]:any})=> Promise + getResource: (type:string, id:string)=> Promise + setResource: (type:string, id:string, value:{[key:string]:any})=> Promise + deleteResource: (type:string, id:string)=> Promise +} + +const SIGNALK_API_PATH= `/signalk/v1/api` +const UUID_PREFIX= 'urn:mrn:signalk:uuid:' + +export class Resources { + + // ** in-scope resource types ** + private resourceTypes:Array= [ + 'routes', + 'waypoints', + 'notes', + 'regions', + 'charts' + ] + + resProvider: {[key:string]: any}= {} + server: any + + public start(app:any) { + debug(`** Initialise ${SIGNALK_API_PATH}/resources path handler **`) + this.server= app + this.initResourceRoutes() + } + + public checkForProviders(rescan:boolean= false) { + if(rescan || Object.keys(this.resProvider).length==0) { + debug('** Checking for providers....') + this.resProvider= {} + this.resourceTypes.forEach( (rt:string)=> { + this.resProvider[rt]= this.getResourceProviderFor(rt) + }) + debug(this.resProvider) + } + } + + public getResource(type:string, id:string) { + debug(`** getResource(${type}, ${id})`) + this.checkForProviders() + return this.actionResourceRequest({ + method: 'GET', + body: {}, + query: {}, + resourceType: type, + resourceId: id + }) + } + + + // ** initialise handler for in-scope resource types ** + private initResourceRoutes() { + this.server.get(`${SIGNALK_API_PATH}/resources`, (req:any, res:any) => { + this.checkForProviders() + // list all serviced paths under resources + res.json(this.getResourcePaths()) + }) + this.server.use(`${SIGNALK_API_PATH}/resources/*`, async (req:any, res:any, next: any) => { + let result= this.parseResourceRequest(req) + if(result) { + let ar= await this.actionResourceRequest(result) + if(typeof ar.statusCode!== 'undefined'){ + debug(`${JSON.stringify(ar)}`) + res.status= ar.statusCode + res.send(ar.message) + } + else { + res.json(ar) + } + } + else { + debug('** No provider found... calling next()...') + next() + } + }) + } + + // ** return all paths serviced under ./resources *8 + private getResourcePaths(): {[key:string]:any} { + let resPaths:{[key:string]:any}= {} + Object.entries(this.resProvider).forEach( (p:any)=> { + if(p[1]) { resPaths[p[0]]= `Path containing ${p[0]}, each named with a UUID` } + }) + // check for other plugins servicing paths under ./resources + this.server._router.stack.forEach((i:any)=> { + if(i.route && i.route.path && typeof i.route.path==='string') { + if(i.route.path.indexOf(`${SIGNALK_API_PATH}/resources`)!=-1) { + let r= i.route.path.split('/') + if( r.length>5 && !(r[5] in resPaths) ) { + resPaths[r[5]]= `Path containing ${r[5]} resources (provided by plug-in)` + } + } + } + }) + return resPaths + } + + // ** parse api path request and return ResourceRequest object ** + private parseResourceRequest(req:any): ResourceRequest | undefined { + debug('** req.originalUrl:', req.originalUrl) + debug('** req.method:', req.method) + debug('** req.body:', req.body) + debug('** req.query:', req.query) + debug('** req.params:', req.params) + let p= req.params[0].split('/') + let resType= (typeof req.params[0]!=='undefined') ? p[0] : '' + let resId= p.length>1 ? p[1] : '' + debug('** resType:', resType) + debug('** resId:', resId) + + this.checkForProviders() + if(this.resourceTypes.includes(resType) && this.resProvider[resType]) { + return { + method: req.method, + body: req.body, + query: req.query, + resourceType: resType, + resourceId: resId + } + } + else { + debug('Invalid resource type or no provider for this type!') + return undefined + } + } + + // ** action an in-scope resource request ** + private async actionResourceRequest (req:ResourceRequest):Promise { + debug('********* action request *************') + debug(req) + + // check for registered resource providers + if(!this.resProvider) { + return {statusCode: 501, message: `No Provider`} + } + if(!this.resourceTypes.includes(req.resourceType) || !this.resProvider[req.resourceType]) { + return {statusCode: 501, message: `No Provider`} + } + + if(req.method==='GET') { + let retVal: any + if(!req.resourceId) { + retVal= await this.resProvider[req.resourceType].listResources(req.resourceType, req.query) + return (retVal) ? + retVal : + {statusCode: 404, message: `Error retrieving resources!` } + } + if(!validate.uuid(req.resourceId)) { + return {statusCode: 406, message: `Invalid resource id provided (${req.resourceId})` } + } + retVal= await this.resProvider[req.resourceType].getResource(req.resourceType, req.resourceId) + return (retVal) ? + retVal : + {statusCode: 404, message: `Resource not found (${req.resourceId})!` } + } + + if(req.method==='DELETE' || req.method==='PUT') { + if(!req.resourceId) { + return {statusCode: 406, value: `No resource id provided!` } + } + if(!validate.uuid(req.resourceId)) { + return {statusCode: 406, message: `Invalid resource id provided (${req.resourceId})!` } + } + if( + req.method==='DELETE' || + (req.method==='PUT' && typeof req.body.value!=='undefined' && req.body.value==null) + ) { + let retVal= await this.resProvider[req.resourceType].deleteResource(req.resourceType, req.resourceId) + if(retVal){ + this.sendDelta(req.resourceType, req.resourceId, null) + return {statusCode: 200, message: `Resource (${req.resourceId}) deleted.`} + } + else { + return {statusCode: 400, message: `Error deleting resource (${req.resourceId})!` } + } + } + + } + + if(req.method==='POST' || req.method==='PUT') { + // check for supplied value + if( typeof req.body.value==='undefined' || req.body.value==null) { + return {statusCode: 406, message: `No resource data supplied!`} + } + // validate supplied request data + if(!validate.resource(req.resourceType, req.body.value)) { + return {statusCode: 406, message: `Invalid resource data supplied!`} + } + if(req.method==='POST') { + let id= UUID_PREFIX + uuidv4() + let retVal= await this.resProvider[req.resourceType].setResource(req.resourceType, id, req.body.value) + if(retVal){ + this.sendDelta(req.resourceType, id, req.body.value) + return {statusCode: 200, message: `Resource (${id}) saved.`} + } + else { + return {statusCode: 400, message: `Error saving resource (${id})!` } + } + } + if(req.method==='PUT') { + if(!req.resourceId) { + return {statusCode: 406, message: `No resource id provided!` } + } + let retVal= await this.resProvider[req.resourceType].setResource(req.resourceType, req.resourceId, req.body.value) + if(retVal){ + this.sendDelta(req.resourceType, req.resourceId, req.body.value) + return {statusCode: 200, message: `Resource (${req.resourceId}) updated.`} + } + else { + return {statusCode: 400, message: `Error updating resource (${req.resourceId})!` } + } + } + } + } + + private sendDelta(type:string, id:string, value:any):void { + debug(`** Sending Delta: resources.${type}.${id}`) + this.server.handleMessage('signalk-resources', { + updates: [ + { + values: [ + { + path: `resources.${type}.${id}`, + value: value + } + ] + } + ] + }) + } + + // ** get reference to installed resource provider (plug-in). returns null if none found + private getResourceProviderFor(resType:string): ResourceProvider | null { + if(!this.server.plugins) { return null} + let pSource: ResourceProvider | null= null + this.server.plugins.forEach((plugin:any)=> { + if(typeof plugin.resourceProvider !== 'undefined') { + pSource= plugin.resourceProvider.types.includes(resType) ? + plugin.resourceProvider.methods : + null + } + }) + debug(`** Checking for ${resType} provider.... ${pSource ? 'Found' : 'Not found'}`) + return pSource + } + +>>>>>>> Add Signal K standard resource path handling } diff --git a/src/api/resources/validate.ts b/src/api/resources/validate.ts index 16eb3c9a8..a19a7caba 100644 --- a/src/api/resources/validate.ts +++ b/src/api/resources/validate.ts @@ -1,3 +1,4 @@ +<<<<<<< HEAD import geoJSON from 'geojson-validation' import { isValidCoordinate } from 'geolib' @@ -140,3 +141,111 @@ const validateChart = (r: any): boolean => { return true } +======= +//import { GeoHash, GeoBounds } from './geo'; +import geoJSON from 'geojson-validation'; + +export const validate= { + resource: (type:string, value:any):boolean=> { + if(!type) { return false } + switch(type) { + case 'routes': + return validateRoute(value); + break + case 'waypoints': + return validateWaypoint(value) + break + case 'notes': + return validateNote(value) + break; + case 'regions': + return validateRegion(value) + break + default: + return true + } + }, + + // ** returns true if id is a valid Signal K UUID ** + uuid: (id:string): boolean=> { + let uuid= RegExp("^urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$") + return uuid.test(id) + } +} + +// ** validate route data +const validateRoute= (r:any):boolean=> { + //if(typeof r.name === 'undefined') { return false } + //if(typeof r.description === 'undefined') { return false } + if(typeof r.distance === 'undefined' || typeof r.distance !=='number') { return false } + if(r.start) { + let l= r.start.split('/') + if(!validate.uuid(l[l.length-1])) { return false } + } + if(r.end) { + let l= r.end.split('/') + if(!validate.uuid(l[l.length-1])) { return false } + } + try { + if(!r.feature || !geoJSON.valid(r.feature)) { + return false + } + if(r.feature.geometry.type!=='LineString') { return false } + } + catch(err) { return false } + return true +} + +// ** validate waypoint data +const validateWaypoint= (r:any):boolean=> { + if(typeof r.position === 'undefined') { return false } + if(typeof r.position.latitude === 'undefined' || typeof r.position.longitude === 'undefined') { + return false + } + if(typeof r.position.latitude !== 'number' || typeof r.position.longitude !== 'number') { + return false + } + try { + if(!r.feature || !geoJSON.valid(r.feature)) { + return false + } + if(r.feature.geometry.type!=='Point') { return false } + } + catch(e) { return false } + return true +} + +// ** validate note data +const validateNote= (r:any):boolean=> { + if(!r.region && !r.position && !r.geohash ) { return false } + if(typeof r.position!== 'undefined') { + if(typeof r.position.latitude === 'undefined' || typeof r.position.longitude === 'undefined') { + return false + } + if(typeof r.position.latitude !== 'number' || typeof r.position.longitude !== 'number') { + return false + } + } + if(r.region) { + let l= r.region.split('/') + if(!validate.uuid(l[l.length-1])) { return false } + } + return true +} + +// ** validate region data +const validateRegion= (r:any):boolean=> { + if(!r.geohash && !r.feature) { return false } + if(r.feature ) { + try { + if(!geoJSON.valid(r.feature)) { return false } + if(r.feature.geometry.type!=='Polygon' && r.feature.geometry.type!=='MultiPolygon') { + return false + } + } + catch(e) { return false } + } + return true +} + +>>>>>>> Add Signal K standard resource path handling diff --git a/src/put.js b/src/put.js index f3f50f078..607d7bfaa 100644 --- a/src/put.js +++ b/src/put.js @@ -32,10 +32,17 @@ module.exports = { app.put(apiPathPrefix + '*', function(req, res, next) { // ** ignore resources paths ** +<<<<<<< HEAD if (req.path.split('/')[4] === 'resources') { next() return } +======= + if(req.path.split('/')[4]==='resources') { + next() + return + } +>>>>>>> Add Signal K standard resource path handling let path = String(req.path).replace(apiPathPrefix, '') const value = req.body From 1d2808266c48dcc8d9861a6033e39f07a4218a07 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 7 Nov 2021 17:27:25 +1030 Subject: [PATCH 262/410] add OpenApi definition file --- src/api/resources/openApi.json | 258 +++++++++++++++++++++++++++++++++ 1 file changed, 258 insertions(+) diff --git a/src/api/resources/openApi.json b/src/api/resources/openApi.json index 80350d21f..e85c6b33d 100644 --- a/src/api/resources/openApi.json +++ b/src/api/resources/openApi.json @@ -10,6 +10,7 @@ "/resources": { "get": { "tags": ["resources"], +<<<<<<< HEAD "summary": "List available resource types", "responses": { "200": { @@ -30,14 +31,28 @@ }, "/resources/{resourceType}": { +======= + "summary": "List available resource types" + + } + }, + + "/resources/{resourceClass}": { +>>>>>>> add OpenApi definition file "get": { "tags": ["resources"], "summary": "Retrieve resources", "parameters": [ { +<<<<<<< HEAD "name": "resourceType", "in": "path", "description": "Type of resources to retrieve. Valid values are: routes, waypoints, notes, regions, charts", +======= + "name": "resourceClass", + "in": "path", + "description": "resource class", +>>>>>>> add OpenApi definition file "required": true, "schema": { "type": "string", @@ -57,8 +72,13 @@ } }, { +<<<<<<< HEAD "name": "distance", "in": "query", +======= + "in": "query", + "name": "radius", +>>>>>>> add OpenApi definition file "description": "Limit results to resources that fall within a square area, centered around the vessel's position, the edges of which are the sepecified distance in meters from the vessel.", "schema": { "type": "integer", @@ -68,9 +88,23 @@ } }, { +<<<<<<< HEAD "name": "bbox", "in": "query", "description": "Limit results to resources that fall within the bounded area defined as lower left (south west) and upper right (north east) coordinates [swlon,swlat,nelon,nelat]", +======= + "in": "query", + "name": "geohash", + "description": "limit results to resources that fall within an area sepecified by the geohash.", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "bbox", + "description": "Limit results to resources that fall within the bounded area defined as lower left and upper right x,y coordinates x1,y1,x2,y2", +>>>>>>> add OpenApi definition file "style": "form", "explode": false, "schema": { @@ -146,6 +180,7 @@ "description": "A GeoJSON feature object which describes a route", "properties": { "geometry": { +<<<<<<< HEAD "type": "object", "properties": { "type": { @@ -161,6 +196,20 @@ "items": { "type": "number" } +======= + "type": { + "type": "string", + "enum": ["LineString"] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" +>>>>>>> add OpenApi definition file } } } @@ -180,6 +229,7 @@ } } } +<<<<<<< HEAD }, "responses": { "200": { @@ -192,6 +242,8 @@ } } } +======= +>>>>>>> add OpenApi definition file } } }, @@ -210,6 +262,7 @@ "get": { "tags": ["resources/routes"], +<<<<<<< HEAD "summary": "Retrieve route with supplied id", "responses": { "200": { @@ -227,6 +280,9 @@ } } } +======= + "summary": "Retrieve route with supplied id" +>>>>>>> add OpenApi definition file }, "put": { @@ -270,6 +326,7 @@ "description": "A GeoJSON feature object which describes a route", "properties": { "geometry": { +<<<<<<< HEAD "type": "object", "properties": { "type": { @@ -285,6 +342,20 @@ "items": { "type": "number" } +======= + "type": { + "type": "string", + "enum": ["LineString"] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" +>>>>>>> add OpenApi definition file } } } @@ -304,6 +375,7 @@ } } } +<<<<<<< HEAD }, "responses": { "200": { @@ -316,11 +388,14 @@ } } } +======= +>>>>>>> add OpenApi definition file } }, "delete": { "tags": ["resources/routes"], +<<<<<<< HEAD "summary": "Remove Route with supplied id", "responses": { "200": { @@ -334,6 +409,9 @@ } } } +======= + "summary": "Remove Route with supplied id" +>>>>>>> add OpenApi definition file } }, @@ -381,6 +459,7 @@ "properties": { "geometry": { "type": "object", +<<<<<<< HEAD "properties": { "type": "object", "description": "GeoJSon geometry", @@ -397,6 +476,21 @@ "items": { "type": "number" } +======= + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["Point"] + } + }, + "coordinates": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" +>>>>>>> add OpenApi definition file } } }, @@ -414,6 +508,7 @@ } } } +<<<<<<< HEAD }, "responses": { "200": { @@ -426,6 +521,8 @@ } } } +======= +>>>>>>> add OpenApi definition file } } }, @@ -444,6 +541,7 @@ "get": { "tags": ["resources/waypoints"], +<<<<<<< HEAD "summary": "Retrieve waypoint with supplied id", "responses": { "200": { @@ -461,6 +559,9 @@ } } } +======= + "summary": "Retrieve waypoint with supplied id" +>>>>>>> add OpenApi definition file }, "put": { @@ -505,6 +606,7 @@ "properties": { "geometry": { "type": "object", +<<<<<<< HEAD "properties": { "type": "object", "description": "GeoJSon geometry", @@ -521,6 +623,21 @@ "items": { "type": "number" } +======= + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["Point"] + } + }, + "coordinates": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" +>>>>>>> add OpenApi definition file } } }, @@ -538,6 +655,7 @@ } } } +<<<<<<< HEAD }, "responses": { "200": { @@ -550,11 +668,14 @@ } } } +======= +>>>>>>> add OpenApi definition file } }, "delete": { "tags": ["resources/waypoints"], +<<<<<<< HEAD "summary": "Remove Waypoint with supplied id", "responses": { "200": { @@ -568,6 +689,9 @@ } } } +======= + "summary": "Remove Waypoint with supplied id" +>>>>>>> add OpenApi definition file } }, @@ -637,6 +761,7 @@ } } } +<<<<<<< HEAD }, "responses": { "200": { @@ -649,6 +774,8 @@ } } } +======= +>>>>>>> add OpenApi definition file } } }, @@ -667,6 +794,7 @@ "get": { "tags": ["resources/notes"], +<<<<<<< HEAD "summary": "Retrieve Note with supplied id", "responses": { "200": { @@ -684,6 +812,9 @@ } } } +======= + "summary": "Retrieve Note with supplied id" +>>>>>>> add OpenApi definition file }, "put": { @@ -750,6 +881,7 @@ } } } +<<<<<<< HEAD }, "responses": { "200": { @@ -762,11 +894,14 @@ } } } +======= +>>>>>>> add OpenApi definition file } }, "delete": { "tags": ["resources/notes"], +<<<<<<< HEAD "summary": "Remove Note with supplied id", "responses": { "200": { @@ -780,6 +915,9 @@ } } } +======= + "summary": "Remove Note with supplied id" +>>>>>>> add OpenApi definition file } }, @@ -787,7 +925,11 @@ "/resources/regions/": { "post": { "tags": ["resources/regions"], +<<<<<<< HEAD "summary": "Add a new Region", +======= + "summary": "Add a new Regkion", +>>>>>>> add OpenApi definition file "requestBody": { "description": "Region details", "required": true, @@ -808,6 +950,7 @@ "description": "A Geo JSON feature object which describes the regions boundary", "properties": { "geometry": { +<<<<<<< HEAD "type": "object", "properties": { "oneOf": [ @@ -830,10 +973,32 @@ "items": { "type": "number" } +======= + "oneOf": [ + { + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["Polygon"] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" +>>>>>>> add OpenApi definition file } } } } +<<<<<<< HEAD }, { "type": "object", @@ -844,11 +1009,27 @@ "enum": ["MultiPolygon"] }, "coordinates": { +======= + } + }, + { + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["MultiPolygon"] + }, + "coordinates": { + "type": "array", + "items": { +>>>>>>> add OpenApi definition file "type": "array", "items": { "type": "array", "items": { "type": "array", +<<<<<<< HEAD "items": { "type": "array", "maxItems": 3, @@ -856,14 +1037,25 @@ "items": { "type": "number" } +======= + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" +>>>>>>> add OpenApi definition file } } } } } } +<<<<<<< HEAD ] } +======= + } + ] +>>>>>>> add OpenApi definition file }, "properties": { "description": "Additional feature properties", @@ -879,6 +1071,7 @@ } } } +<<<<<<< HEAD }, "responses": { "200": { @@ -891,6 +1084,8 @@ } } } +======= +>>>>>>> add OpenApi definition file } } }, @@ -909,6 +1104,7 @@ "get": { "tags": ["resources/regions"], +<<<<<<< HEAD "summary": "Retrieve Region with supplied id", "responses": { "200": { @@ -926,6 +1122,9 @@ } } } +======= + "summary": "Retrieve Region with supplied id" +>>>>>>> add OpenApi definition file }, "put": { @@ -951,6 +1150,7 @@ "description": "A Geo JSON feature object which describes the regions boundary", "properties": { "geometry": { +<<<<<<< HEAD "type": "object", "properties": { "oneOf": [ @@ -973,10 +1173,32 @@ "items": { "type": "number" } +======= + "oneOf": [ + { + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["Polygon"] + }, + "coordinates": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" +>>>>>>> add OpenApi definition file } } } } +<<<<<<< HEAD }, { "type": "object", @@ -987,11 +1209,27 @@ "enum": ["MultiPolygon"] }, "coordinates": { +======= + } + }, + { + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["MultiPolygon"] + }, + "coordinates": { + "type": "array", + "items": { +>>>>>>> add OpenApi definition file "type": "array", "items": { "type": "array", "items": { "type": "array", +<<<<<<< HEAD "items": { "type": "array", "maxItems": 3, @@ -999,14 +1237,25 @@ "items": { "type": "number" } +======= + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" +>>>>>>> add OpenApi definition file } } } } } } +<<<<<<< HEAD ] } +======= + } + ] +>>>>>>> add OpenApi definition file }, "properties": { "description": "Additional feature properties", @@ -1022,6 +1271,7 @@ } } } +<<<<<<< HEAD }, "responses": { "200": { @@ -1034,11 +1284,14 @@ } } } +======= +>>>>>>> add OpenApi definition file } }, "delete": { "tags": ["resources/regions"], +<<<<<<< HEAD "summary": "Remove Region with supplied id", "responses": { "200": { @@ -1986,6 +2239,11 @@ } } } +======= + "summary": "Remove Region with supplied id" + } + +>>>>>>> add OpenApi definition file } } From c2b3bbf03fc6b823736a7ee2867b16605af052a2 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 8 Nov 2021 09:04:45 +1030 Subject: [PATCH 263/410] chore: fix lint errors --- src/api/resources/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 39515f783..0ac8f4beb 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -568,7 +568,7 @@ export class Resources { } public checkForProviders(rescan:boolean= false) { - if(rescan || Object.keys(this.resProvider).length==0) { + if(rescan || Object.keys(this.resProvider).length===0) { debug('** Checking for providers....') this.resProvider= {} this.resourceTypes.forEach( (rt:string)=> { @@ -627,7 +627,7 @@ export class Resources { // check for other plugins servicing paths under ./resources this.server._router.stack.forEach((i:any)=> { if(i.route && i.route.path && typeof i.route.path==='string') { - if(i.route.path.indexOf(`${SIGNALK_API_PATH}/resources`)!=-1) { + if(i.route.path.indexOf(`${SIGNALK_API_PATH}/resources`)!==-1) { let r= i.route.path.split('/') if( r.length>5 && !(r[5] in resPaths) ) { resPaths[r[5]]= `Path containing ${r[5]} resources (provided by plug-in)` From 9dbd1c0ce42e62cb75d29619145f05cbff37f8ec Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Tue, 9 Nov 2021 15:13:44 +1030 Subject: [PATCH 264/410] addressed comments re parameters --- src/api/resources/openApi.json | 461 +++++++++++++++++++++++++++++++-- 1 file changed, 445 insertions(+), 16 deletions(-) diff --git a/src/api/resources/openApi.json b/src/api/resources/openApi.json index e85c6b33d..be34c412f 100644 --- a/src/api/resources/openApi.json +++ b/src/api/resources/openApi.json @@ -10,10 +10,16 @@ "/resources": { "get": { "tags": ["resources"], +<<<<<<< HEAD <<<<<<< HEAD "summary": "List available resource types", "responses": { "200": { +======= + "summary": "List available resource types", + "responses": { + "default": { +>>>>>>> addressed comments re parameters "description": "List of available resource types", "content": { "application/json": { @@ -27,6 +33,7 @@ } } } +<<<<<<< HEAD } }, @@ -34,6 +41,8 @@ ======= "summary": "List available resource types" +======= +>>>>>>> addressed comments re parameters } }, @@ -72,6 +81,7 @@ } }, { +<<<<<<< HEAD <<<<<<< HEAD "name": "distance", "in": "query", @@ -79,6 +89,10 @@ "in": "query", "name": "radius", >>>>>>> add OpenApi definition file +======= + "name": "distance", + "in": "query", +>>>>>>> addressed comments re parameters "description": "Limit results to resources that fall within a square area, centered around the vessel's position, the edges of which are the sepecified distance in meters from the vessel.", "schema": { "type": "integer", @@ -93,18 +107,14 @@ "in": "query", "description": "Limit results to resources that fall within the bounded area defined as lower left (south west) and upper right (north east) coordinates [swlon,swlat,nelon,nelat]", ======= - "in": "query", - "name": "geohash", - "description": "limit results to resources that fall within an area sepecified by the geohash.", - "schema": { - "type": "string" - } - }, - { "in": "query", "name": "bbox", +<<<<<<< HEAD "description": "Limit results to resources that fall within the bounded area defined as lower left and upper right x,y coordinates x1,y1,x2,y2", >>>>>>> add OpenApi definition file +======= + "description": "Limit results to resources that fall within the bounded area defined as lower left (south west) and upper right (north east) coordinates [swlon,swlat,nelon,nelat]", +>>>>>>> addressed comments re parameters "style": "form", "explode": false, "schema": { @@ -181,6 +191,9 @@ "properties": { "geometry": { <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> addressed comments re parameters "type": "object", "properties": { "type": { @@ -188,6 +201,7 @@ "enum": ["LineString"] }, "coordinates": { +<<<<<<< HEAD "type": "array", "items": { "type": "array", @@ -204,12 +218,21 @@ "coordinates": { "type": "array", "items": { +======= +>>>>>>> addressed comments re parameters "type": "array", - "maxItems": 3, - "minItems": 2, "items": { +<<<<<<< HEAD "type": "number" >>>>>>> add OpenApi definition file +======= + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } +>>>>>>> addressed comments re parameters } } } @@ -229,6 +252,7 @@ } } } +<<<<<<< HEAD <<<<<<< HEAD }, "responses": { @@ -245,6 +269,10 @@ ======= >>>>>>> add OpenApi definition file } +======= + }, + "responses": {} +>>>>>>> addressed comments re parameters } }, @@ -262,6 +290,7 @@ "get": { "tags": ["resources/routes"], +<<<<<<< HEAD <<<<<<< HEAD "summary": "Retrieve route with supplied id", "responses": { @@ -283,6 +312,10 @@ ======= "summary": "Retrieve route with supplied id" >>>>>>> add OpenApi definition file +======= + "summary": "Retrieve route with supplied id", + "responses": {} +>>>>>>> addressed comments re parameters }, "put": { @@ -327,6 +360,9 @@ "properties": { "geometry": { <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> addressed comments re parameters "type": "object", "properties": { "type": { @@ -334,6 +370,7 @@ "enum": ["LineString"] }, "coordinates": { +<<<<<<< HEAD "type": "array", "items": { "type": "array", @@ -350,12 +387,21 @@ "coordinates": { "type": "array", "items": { +======= +>>>>>>> addressed comments re parameters "type": "array", - "maxItems": 3, - "minItems": 2, "items": { +<<<<<<< HEAD "type": "number" >>>>>>> add OpenApi definition file +======= + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } +>>>>>>> addressed comments re parameters } } } @@ -375,6 +421,7 @@ } } } +<<<<<<< HEAD <<<<<<< HEAD }, "responses": { @@ -391,10 +438,15 @@ ======= >>>>>>> add OpenApi definition file } +======= + }, + "responses": {} +>>>>>>> addressed comments re parameters }, "delete": { "tags": ["resources/routes"], +<<<<<<< HEAD <<<<<<< HEAD "summary": "Remove Route with supplied id", "responses": { @@ -412,6 +464,10 @@ ======= "summary": "Remove Route with supplied id" >>>>>>> add OpenApi definition file +======= + "summary": "Remove Route with supplied id", + "responses": {} +>>>>>>> addressed comments re parameters } }, @@ -459,6 +515,7 @@ "properties": { "geometry": { "type": "object", +<<<<<<< HEAD <<<<<<< HEAD "properties": { "type": "object", @@ -491,6 +548,24 @@ "items": { "type": "number" >>>>>>> add OpenApi definition file +======= + "properties": { + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["Point"] + } + }, + "coordinates": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } +>>>>>>> addressed comments re parameters } } }, @@ -508,6 +583,7 @@ } } } +<<<<<<< HEAD <<<<<<< HEAD }, "responses": { @@ -524,6 +600,10 @@ ======= >>>>>>> add OpenApi definition file } +======= + }, + "responses": {} +>>>>>>> addressed comments re parameters } }, @@ -541,6 +621,7 @@ "get": { "tags": ["resources/waypoints"], +<<<<<<< HEAD <<<<<<< HEAD "summary": "Retrieve waypoint with supplied id", "responses": { @@ -562,6 +643,10 @@ ======= "summary": "Retrieve waypoint with supplied id" >>>>>>> add OpenApi definition file +======= + "summary": "Retrieve waypoint with supplied id", + "responses": {} +>>>>>>> addressed comments re parameters }, "put": { @@ -606,6 +691,7 @@ "properties": { "geometry": { "type": "object", +<<<<<<< HEAD <<<<<<< HEAD "properties": { "type": "object", @@ -638,6 +724,24 @@ "items": { "type": "number" >>>>>>> add OpenApi definition file +======= + "properties": { + "type": "object", + "description": "GeoJSon geometry", + "properties": { + "type": { + "type": "string", + "enum": ["Point"] + } + }, + "coordinates": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } +>>>>>>> addressed comments re parameters } } }, @@ -655,6 +759,7 @@ } } } +<<<<<<< HEAD <<<<<<< HEAD }, "responses": { @@ -671,10 +776,15 @@ ======= >>>>>>> add OpenApi definition file } +======= + }, + "responses": {} +>>>>>>> addressed comments re parameters }, "delete": { "tags": ["resources/waypoints"], +<<<<<<< HEAD <<<<<<< HEAD "summary": "Remove Waypoint with supplied id", "responses": { @@ -692,6 +802,10 @@ ======= "summary": "Remove Waypoint with supplied id" >>>>>>> add OpenApi definition file +======= + "summary": "Remove Waypoint with supplied id", + "responses": {} +>>>>>>> addressed comments re parameters } }, @@ -794,6 +908,7 @@ "get": { "tags": ["resources/notes"], +<<<<<<< HEAD <<<<<<< HEAD "summary": "Retrieve Note with supplied id", "responses": { @@ -815,6 +930,10 @@ ======= "summary": "Retrieve Note with supplied id" >>>>>>> add OpenApi definition file +======= + "summary": "Retrieve Note with supplied id", + "responses": {} +>>>>>>> addressed comments re parameters }, "put": { @@ -881,6 +1000,7 @@ } } } +<<<<<<< HEAD <<<<<<< HEAD }, "responses": { @@ -897,10 +1017,15 @@ ======= >>>>>>> add OpenApi definition file } +======= + }, + "responses": {} +>>>>>>> addressed comments re parameters }, "delete": { "tags": ["resources/notes"], +<<<<<<< HEAD <<<<<<< HEAD "summary": "Remove Note with supplied id", "responses": { @@ -918,6 +1043,10 @@ ======= "summary": "Remove Note with supplied id" >>>>>>> add OpenApi definition file +======= + "summary": "Remove Note with supplied id", + "responses": {} +>>>>>>> addressed comments re parameters } }, @@ -951,6 +1080,9 @@ "properties": { "geometry": { <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> addressed comments re parameters "type": "object", "properties": { "oneOf": [ @@ -963,6 +1095,7 @@ "enum": ["Polygon"] }, "coordinates": { +<<<<<<< HEAD "type": "array", "items": { "type": "array", @@ -986,19 +1119,31 @@ "coordinates": { "type": "array", "items": { +======= +>>>>>>> addressed comments re parameters "type": "array", "items": { "type": "array", - "maxItems": 3, - "minItems": 2, "items": { +<<<<<<< HEAD "type": "number" >>>>>>> add OpenApi definition file +======= + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } +>>>>>>> addressed comments re parameters } } } } <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> addressed comments re parameters }, { "type": "object", @@ -1009,6 +1154,7 @@ "enum": ["MultiPolygon"] }, "coordinates": { +<<<<<<< HEAD ======= } }, @@ -1024,11 +1170,14 @@ "type": "array", "items": { >>>>>>> add OpenApi definition file +======= +>>>>>>> addressed comments re parameters "type": "array", "items": { "type": "array", "items": { "type": "array", +<<<<<<< HEAD <<<<<<< HEAD "items": { "type": "array", @@ -1043,12 +1192,22 @@ "items": { "type": "number" >>>>>>> add OpenApi definition file +======= + "items": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } +>>>>>>> addressed comments re parameters } } } } } } +<<<<<<< HEAD <<<<<<< HEAD ] } @@ -1056,6 +1215,10 @@ } ] >>>>>>> add OpenApi definition file +======= + ] + } +>>>>>>> addressed comments re parameters }, "properties": { "description": "Additional feature properties", @@ -1071,6 +1234,7 @@ } } } +<<<<<<< HEAD <<<<<<< HEAD }, "responses": { @@ -1087,6 +1251,10 @@ ======= >>>>>>> add OpenApi definition file } +======= + }, + "responses": {} +>>>>>>> addressed comments re parameters } }, @@ -1104,6 +1272,7 @@ "get": { "tags": ["resources/regions"], +<<<<<<< HEAD <<<<<<< HEAD "summary": "Retrieve Region with supplied id", "responses": { @@ -1125,6 +1294,10 @@ ======= "summary": "Retrieve Region with supplied id" >>>>>>> add OpenApi definition file +======= + "summary": "Retrieve Region with supplied id", + "responses": {} +>>>>>>> addressed comments re parameters }, "put": { @@ -1151,6 +1324,9 @@ "properties": { "geometry": { <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> addressed comments re parameters "type": "object", "properties": { "oneOf": [ @@ -1163,6 +1339,7 @@ "enum": ["Polygon"] }, "coordinates": { +<<<<<<< HEAD "type": "array", "items": { "type": "array", @@ -1186,19 +1363,31 @@ "coordinates": { "type": "array", "items": { +======= +>>>>>>> addressed comments re parameters "type": "array", "items": { "type": "array", - "maxItems": 3, - "minItems": 2, "items": { +<<<<<<< HEAD "type": "number" >>>>>>> add OpenApi definition file +======= + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } +>>>>>>> addressed comments re parameters } } } } <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> addressed comments re parameters }, { "type": "object", @@ -1209,6 +1398,7 @@ "enum": ["MultiPolygon"] }, "coordinates": { +<<<<<<< HEAD ======= } }, @@ -1224,11 +1414,14 @@ "type": "array", "items": { >>>>>>> add OpenApi definition file +======= +>>>>>>> addressed comments re parameters "type": "array", "items": { "type": "array", "items": { "type": "array", +<<<<<<< HEAD <<<<<<< HEAD "items": { "type": "array", @@ -1243,12 +1436,22 @@ "items": { "type": "number" >>>>>>> add OpenApi definition file +======= + "items": { + "type": "array", + "maxItems": 3, + "minItems": 2, + "items": { + "type": "number" + } +>>>>>>> addressed comments re parameters } } } } } } +<<<<<<< HEAD <<<<<<< HEAD ] } @@ -1256,6 +1459,10 @@ } ] >>>>>>> add OpenApi definition file +======= + ] + } +>>>>>>> addressed comments re parameters }, "properties": { "description": "Additional feature properties", @@ -1271,6 +1478,7 @@ } } } +<<<<<<< HEAD <<<<<<< HEAD }, "responses": { @@ -1287,10 +1495,15 @@ ======= >>>>>>> add OpenApi definition file } +======= + }, + "responses": {} +>>>>>>> addressed comments re parameters }, "delete": { "tags": ["resources/regions"], +<<<<<<< HEAD <<<<<<< HEAD "summary": "Remove Region with supplied id", "responses": { @@ -1607,6 +1820,10 @@ } } } +======= + "summary": "Remove Region with supplied id", + "responses": {} +>>>>>>> addressed comments re parameters } }, @@ -1637,6 +1854,7 @@ "type": "string", "description": "Textual description of the waypoint" }, +<<<<<<< HEAD "attributes": { "type": "object", "description": "Additional attributes as name:value pairs.", @@ -1644,6 +1862,8 @@ "type": "string" } }, +======= +>>>>>>> addressed comments re parameters "position": { "description": "The waypoint position", "type": "object", @@ -1667,6 +1887,7 @@ } } }, +<<<<<<< HEAD "responses": { "200": { "description": "OK", @@ -1679,6 +1900,9 @@ } } } +======= + "responses": {} +>>>>>>> addressed comments re parameters } }, @@ -1705,6 +1929,7 @@ } } }, +<<<<<<< HEAD "responses": { "200": { "description": "OK", @@ -1717,6 +1942,9 @@ } } } +======= + "responses": {} +>>>>>>> addressed comments re parameters } }, @@ -1746,6 +1974,7 @@ "type": "string", "description": "Textual description of the route" }, +<<<<<<< HEAD "attributes": { "type": "object", "description": "Additional attributes as name:value pairs.", @@ -1753,6 +1982,8 @@ "type": "string" } }, +======= +>>>>>>> addressed comments re parameters "points": { "description": "Route points", "type": "array", @@ -1779,6 +2010,7 @@ } } }, +<<<<<<< HEAD "responses": { "200": { "description": "OK", @@ -1791,6 +2023,9 @@ } } } +======= + "responses": {} +>>>>>>> addressed comments re parameters } }, @@ -1817,6 +2052,7 @@ } } }, +<<<<<<< HEAD "responses": { "200": { "description": "OK", @@ -2241,9 +2477,202 @@ } ======= "summary": "Remove Region with supplied id" +======= + "responses": {} +>>>>>>> addressed comments re parameters } + }, +<<<<<<< HEAD >>>>>>> add OpenApi definition file +======= + "/resources/setNote": { + "put": { + "tags": ["resources/api"], + "summary": "Add / update a Note", + "requestBody": { + "description": "Note attributes", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["position"], + "properties": { + "id": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", + "description": "Note identifier" + }, + "title": { + "type": "string", + "description": "Note's common name" + }, + "description": { + "type": "string", + "description": " Textual description of the note" + }, + "oneOf":[ + { + "position": { + "description": "Position related to note. Alternative to region or geohash", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + } + }, + "geohash": { + "type": "string", + "description": "Position related to note. Alternative to region or position" + }, + "region": { + "type": "string", + "description": "Region related to note. A pointer to a region UUID. Alternative to position or geohash", + "example": "/resources/regions/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a" + } + } + ], + "mimeType": { + "type": "string", + "description": "MIME type of the note" + }, + "url": { + "type": "string", + "description": "Location of the note contents" + } + } + } + } + } + }, + "responses": {} + } + }, + + "/resources/deleteNote": { + "put": { + "tags": ["resources/api"], + "summary": "Remove a Note", + "requestBody": { + "description": "Note identifier", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["id"], + "properties": { + "id": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", + "description": "Note identifier" + } + } + } + } + } + }, + "responses": {} + } + }, + + "/resources/setRegion": { + "put": { + "tags": ["resources/api"], + "summary": "Add / update a Region", + "requestBody": { + "description": "Region attributes", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["position"], + "properties": { + "id": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", + "description": "Region identifier" + }, + "name": { + "type": "string", + "description": "Region name" + }, + "description": { + "type": "string", + "description": "Textual description of region" + }, + "geohash": { + "type": "string", + "description": "Area related to region. Alternative to points." + }, + "points": { + "description": "Region boundary points", + "type": "array", + "items": { + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + } + } + } + } + } + } + } + }, + "responses": {} + } + }, + + "/resources/deleteRegion": { + "put": { + "tags": ["resources/api"], + "summary": "Remove a Region", + "requestBody": { + "description": "Region identifier", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["id"], + "properties": { + "id": { + "type": "string", + "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", + "description": "Region identifier" + } + } + } + } + } + }, + "responses": {} + } +>>>>>>> addressed comments re parameters } } From f9aabb7a19f0e76a98b8234364eb68473cfef3cc Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Tue, 9 Nov 2021 15:28:15 +1030 Subject: [PATCH 265/410] add API definitions --- src/api/resources/openApi.json | 340 ++++++++++++++++++++++++++++++++- 1 file changed, 336 insertions(+), 4 deletions(-) diff --git a/src/api/resources/openApi.json b/src/api/resources/openApi.json index be34c412f..81b9c9913 100644 --- a/src/api/resources/openApi.json +++ b/src/api/resources/openApi.json @@ -18,8 +18,12 @@ ======= "summary": "List available resource types", "responses": { +<<<<<<< HEAD "default": { >>>>>>> addressed comments re parameters +======= + "200": { +>>>>>>> add API definitions "description": "List of available resource types", "content": { "application/json": { @@ -271,8 +275,23 @@ } ======= }, +<<<<<<< HEAD "responses": {} >>>>>>> addressed comments re parameters +======= + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } +>>>>>>> add API definitions } }, @@ -314,8 +333,27 @@ >>>>>>> add OpenApi definition file ======= "summary": "Retrieve route with supplied id", +<<<<<<< HEAD "responses": {} >>>>>>> addressed comments re parameters +======= + "responses": { + "200": { + "description": "List of resources identified by their UUID", + "content": { + "application/json": { + "schema": { + "description": "List of Signal K resources", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + } +>>>>>>> add API definitions }, "put": { @@ -440,8 +478,23 @@ } ======= }, +<<<<<<< HEAD "responses": {} >>>>>>> addressed comments re parameters +======= + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } +>>>>>>> add API definitions }, "delete": { @@ -466,8 +519,23 @@ >>>>>>> add OpenApi definition file ======= "summary": "Remove Route with supplied id", +<<<<<<< HEAD "responses": {} >>>>>>> addressed comments re parameters +======= + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } +>>>>>>> add API definitions } }, @@ -602,8 +670,23 @@ } ======= }, +<<<<<<< HEAD "responses": {} >>>>>>> addressed comments re parameters +======= + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } +>>>>>>> add API definitions } }, @@ -645,8 +728,27 @@ >>>>>>> add OpenApi definition file ======= "summary": "Retrieve waypoint with supplied id", +<<<<<<< HEAD "responses": {} >>>>>>> addressed comments re parameters +======= + "responses": { + "200": { + "description": "List of resources identified by their UUID", + "content": { + "application/json": { + "schema": { + "description": "List of Signal K resources", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + } +>>>>>>> add API definitions }, "put": { @@ -778,8 +880,23 @@ } ======= }, +<<<<<<< HEAD "responses": {} >>>>>>> addressed comments re parameters +======= + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } +>>>>>>> add API definitions }, "delete": { @@ -804,8 +921,23 @@ >>>>>>> add OpenApi definition file ======= "summary": "Remove Waypoint with supplied id", +<<<<<<< HEAD "responses": {} >>>>>>> addressed comments re parameters +======= + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } +>>>>>>> add API definitions } }, @@ -876,6 +1008,9 @@ } } <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> add API definitions }, "responses": { "200": { @@ -888,8 +1023,11 @@ } } } +<<<<<<< HEAD ======= >>>>>>> add OpenApi definition file +======= +>>>>>>> add API definitions } } }, @@ -932,8 +1070,27 @@ >>>>>>> add OpenApi definition file ======= "summary": "Retrieve Note with supplied id", +<<<<<<< HEAD "responses": {} >>>>>>> addressed comments re parameters +======= + "responses": { + "200": { + "description": "List of resources identified by their UUID", + "content": { + "application/json": { + "schema": { + "description": "List of Signal K resources", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + } +>>>>>>> add API definitions }, "put": { @@ -1019,8 +1176,23 @@ } ======= }, +<<<<<<< HEAD "responses": {} >>>>>>> addressed comments re parameters +======= + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } +>>>>>>> add API definitions }, "delete": { @@ -1045,8 +1217,23 @@ >>>>>>> add OpenApi definition file ======= "summary": "Remove Note with supplied id", +<<<<<<< HEAD "responses": {} >>>>>>> addressed comments re parameters +======= + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } +>>>>>>> add API definitions } }, @@ -1253,8 +1440,23 @@ } ======= }, +<<<<<<< HEAD "responses": {} >>>>>>> addressed comments re parameters +======= + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } +>>>>>>> add API definitions } }, @@ -1291,6 +1493,7 @@ } } } +<<<<<<< HEAD ======= "summary": "Retrieve Region with supplied id" >>>>>>> add OpenApi definition file @@ -1298,6 +1501,8 @@ "summary": "Retrieve Region with supplied id", "responses": {} >>>>>>> addressed comments re parameters +======= +>>>>>>> add API definitions }, "put": { @@ -1497,8 +1702,23 @@ } ======= }, +<<<<<<< HEAD "responses": {} >>>>>>> addressed comments re parameters +======= + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } +>>>>>>> add API definitions }, "delete": { @@ -1550,6 +1770,7 @@ "type": "string", "description": "A description of the chart" }, +<<<<<<< HEAD "tilemapUrl": { "type": "string", "description": "A url to the tilemap of the chart for use in TMS chartplotting apps", @@ -1603,6 +1824,30 @@ "type": "number", "format": "float" } +======= + "attributes": { + "type": "object", + "description": "Additional attributes as name:value pairs.", + "additionalProperties": { + "type": "string" + } + }, + "position": { + "description": "The waypoint position", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" +>>>>>>> add API definitions } } }, @@ -1678,8 +1923,26 @@ } } } +<<<<<<< HEAD } }, +======= + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, +>>>>>>> add API definitions "put": { "tags": ["resources/charts"], @@ -1708,6 +1971,7 @@ "type": "string", "description": "A description of the chart" }, +<<<<<<< HEAD "tilemapUrl": { "type": "string", "description": "A url to the tilemap of the chart for use in TMS chartplotting apps", @@ -1739,6 +2003,17 @@ } }, "bounds": { +======= + "attributes": { + "type": "object", + "description": "Additional attributes as name:value pairs.", + "additionalProperties": { + "type": "string" + } + }, + "points": { + "description": "Route points", +>>>>>>> add API definitions "type": "array", "description": "The bounds of the chart. An array containing the position of the upper left corner, and the lower right corner. Useful when the chart isn't inherently geo-referenced.", "items": { @@ -1803,6 +2078,7 @@ } } } +<<<<<<< HEAD }, "delete": { @@ -1824,6 +2100,8 @@ "summary": "Remove Region with supplied id", "responses": {} >>>>>>> addressed comments re parameters +======= +>>>>>>> add API definitions } }, @@ -2266,6 +2544,7 @@ } } } +<<<<<<< HEAD } }, @@ -2480,6 +2759,8 @@ ======= "responses": {} >>>>>>> addressed comments re parameters +======= +>>>>>>> add API definitions } }, @@ -2556,7 +2837,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -2583,7 +2875,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -2613,6 +2916,13 @@ "type": "string", "description": "Textual description of region" }, + "attributes": { + "type": "object", + "description": "Additional attributes as name:value pairs.", + "additionalProperties": { + "type": "string" + } + }, "geohash": { "type": "string", "description": "Area related to region. Alternative to points." @@ -2643,7 +2953,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } }, @@ -2670,7 +2991,18 @@ } } }, - "responses": {} + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } } >>>>>>> addressed comments re parameters } From b1478cf5f387520c606741cde2a5a78a9adf8dce Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Tue, 9 Nov 2021 15:30:30 +1030 Subject: [PATCH 266/410] add API endpoint processing --- src/api/resources/index.ts | 81 ++++++++++++++-- src/api/resources/resources.ts | 169 +++++++++++++++++++++++++++++++++ src/api/resources/validate.ts | 3 + 3 files changed, 244 insertions(+), 9 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 0ac8f4beb..cc0e90438 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -526,6 +526,7 @@ export class Resources { } ======= import { validate } from './validate' +import { buildResource } from './resources' const debug = Debug('signalk:resources') @@ -534,7 +535,8 @@ interface ResourceRequest { body: any query: {[key:string]: any} resourceType: string - resourceId: string + resourceId: string, + apiMethod?: string | null } interface ResourceProvider { @@ -547,6 +549,17 @@ interface ResourceProvider { const SIGNALK_API_PATH= `/signalk/v1/api` const UUID_PREFIX= 'urn:mrn:signalk:uuid:' +const API_METHODS= [ + 'setWaypoint', + 'deleteWaypoint', + 'setRoute', + 'deleteRoute', + 'setNote', + 'deleteNote', + 'setRegion', + 'deleteRegion' +] + export class Resources { // ** in-scope resource types ** @@ -639,7 +652,7 @@ export class Resources { } // ** parse api path request and return ResourceRequest object ** - private parseResourceRequest(req:any): ResourceRequest | undefined { + private parseResourceRequest(req:any):ResourceRequest | undefined { debug('** req.originalUrl:', req.originalUrl) debug('** req.method:', req.method) debug('** req.body:', req.body) @@ -650,16 +663,35 @@ export class Resources { let resId= p.length>1 ? p[1] : '' debug('** resType:', resType) debug('** resId:', resId) + + let apiMethod= (API_METHODS.includes(resType)) ? resType : null + if(apiMethod) { + if(apiMethod.toLowerCase().indexOf('waypoint')!==-1) { + resType= 'waypoints' + } + if(apiMethod.toLowerCase().indexOf('route')!==-1) { + resType= 'routes' + } + if(apiMethod.toLowerCase().indexOf('note')!==-1) { + resType= 'notes' + } + if(apiMethod.toLowerCase().indexOf('region')!==-1) { + resType= 'regions' + } + } this.checkForProviders() + let retReq= { + method: req.method, + body: req.body, + query: req.query, + resourceType: resType, + resourceId: resId, + apiMethod: apiMethod + } + if(this.resourceTypes.includes(resType) && this.resProvider[resType]) { - return { - method: req.method, - body: req.body, - query: req.query, - resourceType: resType, - resourceId: resId - } + return retReq } else { debug('Invalid resource type or no provider for this type!') @@ -676,10 +708,41 @@ export class Resources { if(!this.resProvider) { return {statusCode: 501, message: `No Provider`} } + if(!this.resourceTypes.includes(req.resourceType) || !this.resProvider[req.resourceType]) { return {statusCode: 501, message: `No Provider`} } + // check for API method request + if(req.apiMethod && API_METHODS.includes(req.apiMethod)) { + debug(`API Method (${req.apiMethod})`) + req= this.transformApiRequest(req) + } + + return await this.execResourceRequest(req) + } + + // ** transform API request to ResourceRequest ** + private transformApiRequest(req: ResourceRequest):ResourceRequest { + if(req.apiMethod?.indexOf('delete')!==-1) { + req.method= 'DELETE' + } + if(req.apiMethod?.indexOf('set')!==-1) { + if(!req.body.value?.id) { + req.method= 'POST' + } + else { + req.resourceId= req.body.value.id + } + req.body.value= buildResource(req.resourceType, req.body.value) ?? {} + } + console.log(req) + return req + } + + // ** action an in-scope resource request ** + private async execResourceRequest (req:ResourceRequest):Promise { + if(req.method==='GET') { let retVal: any if(!req.resourceId) { diff --git a/src/api/resources/resources.ts b/src/api/resources/resources.ts index 6a45f736c..cf5133c9c 100644 --- a/src/api/resources/resources.ts +++ b/src/api/resources/resources.ts @@ -1,3 +1,4 @@ +<<<<<<< HEAD import { SignalKResourceType } from '@signalk/server-api' import { getDistance, isValidCoordinate } from 'geolib' import ngeohash from 'ngeohash' @@ -217,4 +218,172 @@ const buildRegion = (rData: any): any => { } return reg +======= +import { getDistance } from 'geolib' +import ngeohash from 'ngeohash' +import geolib from 'geolib' + +// ** build resource item ** +export const buildResource= (resType:string, data:any):any=> { + console.log(`** resType: ${resType}, data: ${JSON.stringify(data)}`) + if(resType==='routes') { return buildRoute(data) } + if(resType==='waypoints') { return buildWaypoint(data) } + if(resType==='notes') { return buildNote(data) } + if(resType==='regions') { return buildRegion(data) } +} + +// ** build route +const buildRoute= (rData:any):any=> { + let rte:any= { + feature: { + type: 'Feature', + geometry:{ + type: 'LineString', + coordinates :[] + }, + properties:{} + } + } + if(typeof rData.name !== 'undefined') { + rte.name= rData.name + rte.feature.properties.name= rData.name + } + if(typeof rData.description !== 'undefined') { + rte.description= rData.description + rte.feature.properties.description= rData.description + } + if(typeof rData.points === 'undefined') { return null } + if(!Array.isArray(rData.points)) { return null } + let isValid:boolean= true; + rData.points.forEach( (p:any)=> { + if(!geolib.isValidCoordinate(p)) { isValid= false } + }) + if(!isValid) { return null } + rData.points.forEach( (p:any)=> { + rte.feature.geometry.coordinates.push([p.longitude, p.latitude]) + }) + rte.distance= 0 + for(let i=0; i { + let wpt:any= { + position: { + latitude: 0, + longitude: 0 + }, + feature: { + type: 'Feature', + geometry:{ + type: 'Point', + coordinates :[] + }, + properties:{} + } + } + if(typeof rData.name !== 'undefined') { + wpt.feature.properties.name= rData.name + } + if(typeof rData.description !== 'undefined') { + wpt.feature.properties.description= rData.description + } + if(typeof rData.position === 'undefined') { return null } + if(!geolib.isValidCoordinate(rData.position)) { return null } + + wpt.position= rData.position + wpt.feature.geometry.coordinates= [rData.position.longitude, rData.position.latitude] + + return wpt +} + +// ** build note +const buildNote= (rData:any):any=> { + let note:any= {} + if(typeof rData.title !== 'undefined') { + note.title= rData.title + note.feature.properties.title= rData.title + } + if(typeof rData.description !== 'undefined') { + note.description= rData.description + note.feature.properties.description= rData.description + } + if(typeof rData.position === 'undefined' + && typeof rData.region === 'undefined' + && typeof rData.geohash === 'undefined') { return null } + + if(typeof rData.position !== 'undefined') { + if(!geolib.isValidCoordinate(rData.position)) { return null } + note.position= rData.position + } + if(typeof rData.region !== 'undefined') { + note.region= rData.region + } + if(typeof rData.geohash !== 'undefined') { + note.geohash= rData.geohash + } + if(typeof rData.url !== 'undefined') { + note.url= rData.url + } + if(typeof rData.mimeType !== 'undefined') { + note.mimeType= rData.mimeType + } + + return note +} + +// ** build region +const buildRegion= (rData:any):any=> { + let reg:any= { + feature: { + type: 'Feature', + geometry:{ + type: 'Polygon', + coordinates :[] + }, + properties:{} + } + } + let coords:Array<[number,number]>= [] + + if(typeof rData.name !== 'undefined') { + reg.feature.properties.name= rData.name + } + if(typeof rData.description !== 'undefined') { + reg.feature.properties.description= rData.description + } + if(typeof rData.points=== 'undefined' && rData.geohash=== 'undefined') { return null } + if(typeof rData.geohash!== 'undefined') { + reg.geohash= rData.geohash + + let bounds= ngeohash.decode_bbox(rData.geohash) + coords= [ + [bounds[1], bounds[0]], + [bounds[3], bounds[0]], + [bounds[3], bounds[2]], + [bounds[1], bounds[2]], + [bounds[1], bounds[0]], + ] + reg.feature.geometry.coordinates.push(coords) + } + if(typeof rData.points!== 'undefined' && coords.length===0 ) { + if(!Array.isArray(rData.points)) { return null } + let isValid:boolean= true; + rData.points.forEach( (p:any)=> { + if(!geolib.isValidCoordinate(p)) { isValid= false } + }) + if(!isValid) { return null } + rData.points.forEach( (p:any)=> { + coords.push([p.longitude, p.latitude]) + }) + reg.feature.geometry.coordinates.push(coords) + } + + return reg +>>>>>>> add API endpoint processing } diff --git a/src/api/resources/validate.ts b/src/api/resources/validate.ts index a19a7caba..862a14a42 100644 --- a/src/api/resources/validate.ts +++ b/src/api/resources/validate.ts @@ -1,4 +1,5 @@ <<<<<<< HEAD +<<<<<<< HEAD import geoJSON from 'geojson-validation' import { isValidCoordinate } from 'geolib' @@ -143,6 +144,8 @@ const validateChart = (r: any): boolean => { } ======= //import { GeoHash, GeoBounds } from './geo'; +======= +>>>>>>> add API endpoint processing import geoJSON from 'geojson-validation'; export const validate= { From 16ebf2ebec4992ec88076456ed3cbd8c7cdba315 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Tue, 9 Nov 2021 16:44:49 +1030 Subject: [PATCH 267/410] align with openapi definitions --- src/api/resources/index.ts | 10 ++++----- src/api/resources/resources.ts | 37 ++++++++++++++++++++++++---------- src/api/resources/validate.ts | 14 +++---------- 3 files changed, 34 insertions(+), 27 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index cc0e90438..0e32cfd01 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -728,21 +728,21 @@ export class Resources { req.method= 'DELETE' } if(req.apiMethod?.indexOf('set')!==-1) { - if(!req.body.value?.id) { + if(!req.body.id) { req.method= 'POST' } else { - req.resourceId= req.body.value.id + req.resourceId= req.body.id } - req.body.value= buildResource(req.resourceType, req.body.value) ?? {} + req.body= { value: buildResource(req.resourceType, req.body) ?? {} } } - console.log(req) return req } // ** action an in-scope resource request ** private async execResourceRequest (req:ResourceRequest):Promise { - + debug('********* execute request *************') + debug(req) if(req.method==='GET') { let retVal: any if(!req.resourceId) { diff --git a/src/api/resources/resources.ts b/src/api/resources/resources.ts index cf5133c9c..77b48e156 100644 --- a/src/api/resources/resources.ts +++ b/src/api/resources/resources.ts @@ -1,4 +1,5 @@ <<<<<<< HEAD +<<<<<<< HEAD import { SignalKResourceType } from '@signalk/server-api' import { getDistance, isValidCoordinate } from 'geolib' import ngeohash from 'ngeohash' @@ -220,12 +221,13 @@ const buildRegion = (rData: any): any => { return reg ======= import { getDistance } from 'geolib' +======= +import { getDistance, isValidCoordinate } from 'geolib' +>>>>>>> align with openapi definitions import ngeohash from 'ngeohash' -import geolib from 'geolib' // ** build resource item ** export const buildResource= (resType:string, data:any):any=> { - console.log(`** resType: ${resType}, data: ${JSON.stringify(data)}`) if(resType==='routes') { return buildRoute(data) } if(resType==='waypoints') { return buildWaypoint(data) } if(resType==='notes') { return buildNote(data) } @@ -233,7 +235,7 @@ export const buildResource= (resType:string, data:any):any=> { } // ** build route -const buildRoute= (rData:any):any=> { +const buildRoute= (rData:any):any=> { let rte:any= { feature: { type: 'Feature', @@ -252,22 +254,27 @@ const buildRoute= (rData:any):any=> { rte.description= rData.description rte.feature.properties.description= rData.description } + if(typeof rData.attributes !== 'undefined') { + Object.assign(rte.feature.properties, rData.attributes) + } + if(typeof rData.points === 'undefined') { return null } if(!Array.isArray(rData.points)) { return null } let isValid:boolean= true; rData.points.forEach( (p:any)=> { - if(!geolib.isValidCoordinate(p)) { isValid= false } + if(!isValidCoordinate(p)) { isValid= false } }) if(!isValid) { return null } rData.points.forEach( (p:any)=> { rte.feature.geometry.coordinates.push([p.longitude, p.latitude]) }) + rte.distance= 0 - for(let i=0; i { if(typeof rData.description !== 'undefined') { wpt.feature.properties.description= rData.description } + if(typeof rData.attributes !== 'undefined') { + Object.assign(wpt.feature.properties, rData.attributes) + } + if(typeof rData.position === 'undefined') { return null } - if(!geolib.isValidCoordinate(rData.position)) { return null } + if(!isValidCoordinate(rData.position)) { return null } wpt.position= rData.position wpt.feature.geometry.coordinates= [rData.position.longitude, rData.position.latitude] @@ -318,7 +329,7 @@ const buildNote= (rData:any):any=> { && typeof rData.geohash === 'undefined') { return null } if(typeof rData.position !== 'undefined') { - if(!geolib.isValidCoordinate(rData.position)) { return null } + if(!isValidCoordinate(rData.position)) { return null } note.position= rData.position } if(typeof rData.region !== 'undefined') { @@ -357,6 +368,10 @@ const buildRegion= (rData:any):any=> { if(typeof rData.description !== 'undefined') { reg.feature.properties.description= rData.description } + if(typeof rData.attributes !== 'undefined') { + Object.assign(reg.feature.properties, rData.attributes) + } + if(typeof rData.points=== 'undefined' && rData.geohash=== 'undefined') { return null } if(typeof rData.geohash!== 'undefined') { reg.geohash= rData.geohash @@ -375,7 +390,7 @@ const buildRegion= (rData:any):any=> { if(!Array.isArray(rData.points)) { return null } let isValid:boolean= true; rData.points.forEach( (p:any)=> { - if(!geolib.isValidCoordinate(p)) { isValid= false } + if(!isValidCoordinate(p)) { isValid= false } }) if(!isValid) { return null } rData.points.forEach( (p:any)=> { diff --git a/src/api/resources/validate.ts b/src/api/resources/validate.ts index 862a14a42..59560d03c 100644 --- a/src/api/resources/validate.ts +++ b/src/api/resources/validate.ts @@ -147,6 +147,7 @@ const validateChart = (r: any): boolean => { ======= >>>>>>> add API endpoint processing import geoJSON from 'geojson-validation'; +import { isValidCoordinate } from 'geolib' export const validate= { resource: (type:string, value:any):boolean=> { @@ -178,9 +179,6 @@ export const validate= { // ** validate route data const validateRoute= (r:any):boolean=> { - //if(typeof r.name === 'undefined') { return false } - //if(typeof r.description === 'undefined') { return false } - if(typeof r.distance === 'undefined' || typeof r.distance !=='number') { return false } if(r.start) { let l= r.start.split('/') if(!validate.uuid(l[l.length-1])) { return false } @@ -202,12 +200,9 @@ const validateRoute= (r:any):boolean=> { // ** validate waypoint data const validateWaypoint= (r:any):boolean=> { if(typeof r.position === 'undefined') { return false } - if(typeof r.position.latitude === 'undefined' || typeof r.position.longitude === 'undefined') { + if(!isValidCoordinate(r.position)) { return false } - if(typeof r.position.latitude !== 'number' || typeof r.position.longitude !== 'number') { - return false - } try { if(!r.feature || !geoJSON.valid(r.feature)) { return false @@ -222,12 +217,9 @@ const validateWaypoint= (r:any):boolean=> { const validateNote= (r:any):boolean=> { if(!r.region && !r.position && !r.geohash ) { return false } if(typeof r.position!== 'undefined') { - if(typeof r.position.latitude === 'undefined' || typeof r.position.longitude === 'undefined') { + if(!isValidCoordinate(r.position)) { return false } - if(typeof r.position.latitude !== 'number' || typeof r.position.longitude !== 'number') { - return false - } } if(r.region) { let l= r.region.split('/') From 44b7ba1f1d061e01668eaa95738312fa435e8d82 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Thu, 11 Nov 2021 16:51:47 +1030 Subject: [PATCH 268/410] Added Resource_Provider documentation --- RESOURCE_PROVIDER_PLUGINS.md | 187 +++++++++++++++++++++++++++++++++++ SERVERPLUGINS.md | 2 + 2 files changed, 189 insertions(+) diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index eb31cd98f..31a50026a 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -1,5 +1,6 @@ # Resource Provider plugins +<<<<<<< HEAD _This document should be read in conjunction with [SERVERPLUGINS.md](./SERVERPLUGINS.md) as it contains additional information regarding the development of plugins that facilitate the storage and retrieval of resource data._ --- @@ -62,6 +63,39 @@ The `ResourceProvider` interface defines the contract between the the Resource P ```typescript interface ResourceProvider: { types: string[], +======= +## Overview + +This document should be read in conjunction with [SERVERPLUGINS.md](./SERVERPLUGINS.md) as it contains additional information regarding the development of plugins that facilitate the storage and retrieval of resource data (e.g. routes, waypoints, notes, regions and charts). + +The Signal K Node server will handle all requests to the following paths: + +`/signalk/v1/api/resources` +`/signalk/v1/api/resources/routes` +`/signalk/v1/api/resources/waypoints` +`/signalk/v1/api/resources/notes` +`/signalk/v1/api/resources/regions` +`/signalk/v1/api/resources/charts` + +This means all requests (GET, PUT, POST and DELETE) are captured and any supplied data is validated prior to being dispatched for processing. + +The server itself has no built-in functionality to save or retrieve resource data from storage, this is the responsibility of a `resource provider plugin`. + +If there are no registered providers for the resource type for which the request is made, then no action is taken. + +This architecture de-couples the resource storage from core server function to provide flexibility to implement the appropriate storage solution for the various resource types for your Signal K implementation. + +## Resource Providers + +A `resource provider plugin` is responsible for the storage and retrieval of resource data. +This allows the method for persisting resource data to be tailored to the Signal K implementation e.g. the local file system, a database service, cloud service, etc. + +It is similar to any other Server Plugin except that it implements the __resourceProvider__ interface. + +```JAVASCRIPT +resourceProvider: { + types: [], +>>>>>>> Added Resource_Provider documentation methods: { listResources: (type:string, query: {[key:string]:any})=> Promise getResource: (type:string, id:string)=> Promise @@ -70,6 +104,7 @@ interface ResourceProvider: { } } ``` +<<<<<<< HEAD where: - `types`: An array containing a list of resource types provided for by the plugin. These can be a mixture of both __Common__ and __Custom__ resource types. @@ -267,10 +302,160 @@ module.exports = function (app) { plugin.stop = function(options) { app.resourcesApi.unRegister(plugin.id); ... +======= + +This interface exposes the following information to the server enabling it to direct requests to the plugin: +- `types`: The resource types for which requests should be directed to the plugin. These can be one or all of `routes, waypoints, notes, regions, charts`. +- `methods`: The methods to which the server dispatches requests. The plugin will implement these methods to perform the necessary save or retrieval operation. + +_Example: Plugin acting as resource provider for routes & waypoints._ +```JAVASCRIPT +let plugin= { + id: 'mypluginid', + name: 'My Resource Providerplugin', + start: (options, restart)=> { ... }, + stop: ()=> { ... }, + resourceProvider: { + types: ['routes','waypoints'], + methods: { + listResources: (type, params)=> { + return Promise.resolve() { ... }; + }, + getResource: (type:string, id:string)=> { + return Promise.resolve() { ... }; + } , + setResource: (type:string, id:string, value:any)=> { + return Promise.resolve() { ... }; + }, + deleteResource: (type:string, id:string)=> { + return Promise.resolve() { ... }; ; + } + } + } +} +``` + +### Methods: + +The Server will dispatch requests to `/signalk/v1/api/resources/` to the methods defined in the resourceProvider interface. + +Each method must have a signature as defined in the interface and return a `Promise` containing the resource data or `null` if the operation was unsuccessful. + +--- +`GET` requests that are not for a specific resource will be dispatched to the `listResources` method passing the resource type and query data as parameters. + +Returns: Object listing resources by id. + +_Example: List all routes._ +```JAVASCRIPT +GET /signalk/v1/api/resources/routes + +listResources('routes', {}) + +returns { + "resource_id1": { ... }, + "resource_id2": { ... }, + ... + "resource_idn": { ... } +} +``` + +_Example: List routes within the bounded area._ +```JAVASCRIPT +GET /signalk/v1/api/resources/routes?bbox=5.4,25.7,6.9,31.2 + +listResources('routes', {bbox: '5.4,25.7,6.9,31.2'}) + +returns { + "resource_id1": { ... }, + "resource_id2": { ... } +} +``` + +`GET` requests for a specific resource will be dispatched to the `getResource` method passing the resource type and id as parameters. + +Returns: Object containing resourcesdata. + +_Example: Retrieve route._ +```JAVASCRIPT +GET /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a + +getResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a') + +returns { + "name": "route name", + ... + "feature": { ... } +} +``` +--- + +`PUT` requests for a specific resource will be dispatched to the `setResource` method passing the resource type, id and resource data as parameters. + +Returns: `true` on success, `null` on failure. + +_Example: Update route data._ +```JAVASCRIPT +PUT /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a {resource data} + +setResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) +``` + +`POST` requests will be dispatched to the `setResource` method passing the resource type, a generated id and resource data as parameters. + +Returns: `true` on success, `null` on failure. + +_Example: New route._ +```JAVASCRIPT +POST /signalk/v1/api/resources/routes/ {resource data} + +setResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) +``` +--- + +`DELETE` requests for a specific resource will be dispatched to the `deleteResource` method passing the resource type and id as parameters. + +Returns: `true` on success, `null` on failure. + +_Example: Delete route._ +```JAVASCRIPT +DELETE /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a {resource data} + +deleteResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a') +``` +--- + +### Plugin Startup: + +If your plugin provides the ability for the user to choose which resource types are handled, then the plugin will need to notify the server that the `types` attribute of the `resourceProvider` interface has been modified. + +The server exposes `resourcesApi` which has the following method: +```JAVASCRIPT +checkForProviders(rescan:boolean) +``` +which can be called within the plugin `start()` function with `rescan= true`. + +This will cause the server to `rescan` for resource providers and register the new list of resource types being handled. + +_Example:_ +```JAVASCRIPT +module.exports = function (app) { + let plugin= { + id: 'mypluginid', + name: 'My Resource Providerplugin', + start: (options, restart)=> { + ... + setTimeout( ()=> { app.resourcesApi.checkForProviders(true) }, 1000) + ... + }, + stop: ()=> { ... }, + ... +>>>>>>> Added Resource_Provider documentation } } ``` +<<<<<<< HEAD --- ### __Example:__ @@ -336,3 +521,5 @@ module.exports = function (app) { } } ``` +======= +>>>>>>> Added Resource_Provider documentation diff --git a/SERVERPLUGINS.md b/SERVERPLUGINS.md index 1d8a53797..e904d7d59 100644 --- a/SERVERPLUGINS.md +++ b/SERVERPLUGINS.md @@ -26,6 +26,8 @@ To get started with SignalK plugin development, you can follow this guide. _Note: For plugins acting as a provider for one or more of the SignalK resource types listed in the specification (`routes`, `waypoints`, `notes`, `regions` or `charts`) please refer to __[RESOURCE_PROVIDER_PLUGINS.md](./RESOURCE_PROVIDER_PLUGINS.md)__ for additional details._ +_Note: For plugins acting as a `provider` for one or more of the resource types listed in the Signal K specification (e.g. routes, waypoints, notes, regions or charts) there are additional requirementss which are detailed in __[RESOURCE_PROVIDER_PLUGINS.md](./RESOURCE_PROVIDER_PLUGINS.md)__._ + ### Project setup First, create a new directory and initialize a new module: From e53b7348890660c2c7a0bd6c18687f49cd04280e Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 14 Nov 2021 19:32:38 +1030 Subject: [PATCH 269/410] Add register / unregister --- RESOURCE_PROVIDER_PLUGINS.md | 246 +++++++++++++++++++++++------------ SERVERPLUGINS.md | 35 ++++- src/api/resources/index.ts | 28 +++- 3 files changed, 222 insertions(+), 87 deletions(-) diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index 31a50026a..adacb9fa7 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -68,31 +68,34 @@ interface ResourceProvider: { This document should be read in conjunction with [SERVERPLUGINS.md](./SERVERPLUGINS.md) as it contains additional information regarding the development of plugins that facilitate the storage and retrieval of resource data (e.g. routes, waypoints, notes, regions and charts). -The Signal K Node server will handle all requests to the following paths: +Resource storage and retrieval is de-coupled from core server function to provide the flexibility to implement the appropriate resource storage solution for specific Signal K implementations. -`/signalk/v1/api/resources` -`/signalk/v1/api/resources/routes` -`/signalk/v1/api/resources/waypoints` -`/signalk/v1/api/resources/notes` -`/signalk/v1/api/resources/regions` -`/signalk/v1/api/resources/charts` +The Signal K Node server will pass requests made to the following paths to registered resource providers: +- `/signalk/v1/api/resources` +- `/signalk/v1/api/resources/routes` +- `/signalk/v1/api/resources/waypoints` +- `/signalk/v1/api/resources/notes` +- `/signalk/v1/api/resources/regions` +- `/signalk/v1/api/resources/charts` -This means all requests (GET, PUT, POST and DELETE) are captured and any supplied data is validated prior to being dispatched for processing. +Resource providers will receive request data via a `ResourceProvider` interface which they implement. It is the responsibility of the resource provider to persist resource data in storage (PUT, POST), retrieve the requested resources (GET) and remove resource entries (DELETE). -The server itself has no built-in functionality to save or retrieve resource data from storage, this is the responsibility of a `resource provider plugin`. +Resource data passed to the resource provider plugin has been validated by the server and can be considered ready for storage. -If there are no registered providers for the resource type for which the request is made, then no action is taken. - -This architecture de-couples the resource storage from core server function to provide flexibility to implement the appropriate storage solution for the various resource types for your Signal K implementation. ## Resource Providers A `resource provider plugin` is responsible for the storage and retrieval of resource data. -This allows the method for persisting resource data to be tailored to the Signal K implementation e.g. the local file system, a database service, cloud service, etc. -It is similar to any other Server Plugin except that it implements the __resourceProvider__ interface. +It should implement the necessary functions to: +- Persist each resource with its associated id +- Retrieve an individual resource with the supplied id +- Retrieve a list of resources that match the supplied qery criteria. -```JAVASCRIPT +Data is passed to and from the plugin via the methods defined in the __resourceProvider__ interface which the plugin must implement. + +_Definition: `resourceProvider` interface._ +```javascript resourceProvider: { types: [], >>>>>>> Added Resource_Provider documentation @@ -304,50 +307,130 @@ module.exports = function (app) { ... ======= -This interface exposes the following information to the server enabling it to direct requests to the plugin: -- `types`: The resource types for which requests should be directed to the plugin. These can be one or all of `routes, waypoints, notes, regions, charts`. -- `methods`: The methods to which the server dispatches requests. The plugin will implement these methods to perform the necessary save or retrieval operation. +This interface is used by the server to direct requests to the plugin. + +It contains the following attributes: +- `types`: An array containing the names of the resource types the plugin is a provider for. Names of the resource types are: `routes, waypoints, notes, regions, charts`. + +- `methods`: The methods to which the server dispatches requests. The plugin will implement these methods to perform the necessary save or retrieval operation. Each method returns a promise containing either resource data or `null` if an error is encountered. _Example: Plugin acting as resource provider for routes & waypoints._ -```JAVASCRIPT -let plugin= { +```javascript +module.exports = function (app) { + let plugin= { id: 'mypluginid', name: 'My Resource Providerplugin', - start: (options, restart)=> { ... }, - stop: ()=> { ... }, resourceProvider: { - types: ['routes','waypoints'], - methods: { - listResources: (type, params)=> { - return Promise.resolve() { ... }; - }, - getResource: (type:string, id:string)=> { - return Promise.resolve() { ... }; - } , - setResource: (type:string, id:string, value:any)=> { - return Promise.resolve() { ... }; - }, - deleteResource: (type:string, id:string)=> { - return Promise.resolve() { ... }; ; - } + types: ['routes','waypoints'], + methods: { + listResources: (type, params)=> { + return Promise.resolve() { ... }; + }, + getResource: (type:string, id:string)=> { + return Promise.resolve() { ... }; + } , + setResource: (type:string, id:string, value:any)=> { + return Promise.resolve() { ... }; + }, + deleteResource: (type:string, id:string)=> { + return Promise.resolve() { ... }; ; + } + } + }, + start: (options, restart)=> { + ... + app.resourceApi.register(this.resourceProvider); + }, + stop: ()=> { + app.resourceApi.unRegister(this.resourceProvider.types); + ... + } + } +} +``` + +--- + +### Plugin Startup - Registering the Resource Provider: + +To register your plugin as a resource provider the server's `resourcesApi.register()` function should be called within the plugin `start()` function passing the `resourceProvider` interface. + +This registers the resource types and the methods with the server so they are called when requests to resource paths are made. + +_Example:_ +```javascript +module.exports = function (app) { + let plugin= { + id: 'mypluginid', + name: 'My Resource Providerplugin', + resourceProvider: { + types: ['routes','waypoints'], + methods: { + listResources: (type, params)=> { + return Promise.resolve() { ... }; + }, + getResource: (type:string, id:string)=> { + return Promise.resolve() { ... }; + } , + setResource: (type:string, id:string, value:any)=> { + return Promise.resolve() { ... }; + }, + deleteResource: (type:string, id:string)=> { + return Promise.resolve() { ... }; ; } + } } + } + + plugin.start = function(options) { + ... + app.resourcesApi.register(plugin.resourceProvider); + } } ``` +--- -### Methods: +### Plugin Stop - Un-registering the Resource Provider: -The Server will dispatch requests to `/signalk/v1/api/resources/` to the methods defined in the resourceProvider interface. +When a resource provider plugin is disabled / stopped it should un-register as a provider so resource requests are not directed to it. This is done by calling the server's `resourcesApi.unRegister()` function passing `resourceProvider.types` within the plugin's `stop()` function. -Each method must have a signature as defined in the interface and return a `Promise` containing the resource data or `null` if the operation was unsuccessful. +_Example:_ +```javascript +module.exports = function (app) { + let plugin= { + id: 'mypluginid', + name: 'My Resource Providerplugin', + resourceProvider: { + types: ['routes','waypoints'], + methods: { ... } + } + } + plugin.stop = function(options) { + ... + app.resourcesApi.unRegister(plugin.resourceProvider.types); + } +} +``` --- -`GET` requests that are not for a specific resource will be dispatched to the `listResources` method passing the resource type and query data as parameters. -Returns: Object listing resources by id. +### Operation: + +The Server will dispatch requests made to `/signalk/v1/api/resources/` to the plugin's `resourceProvider.methods` for each resource type listed in `resourceProvider.types`. + +Each method defined in the plugin must have a signature as specified in the interface. Each method returns a `Promise` containing the resource data or `null` if an error occurrred. + + +### __List Resources:__ + +`GET` requests that are not for a specific resource will be dispatched to the `listResources` method passing the resource type and any query data as parameters. + +It is the responsibility of the resource provider plugin to filter the resources returned as per the supplied query parameters. + +`listResources()` should return a JSON object listing resources by id. _Example: List all routes._ -```JAVASCRIPT +```javascript GET /signalk/v1/api/resources/routes listResources('routes', {}) @@ -360,11 +443,11 @@ returns { } ``` -_Example: List routes within the bounded area._ -```JAVASCRIPT -GET /signalk/v1/api/resources/routes?bbox=5.4,25.7,6.9,31.2 +_Example: List waypoints within the bounded area._ +```javascript +GET /signalk/v1/api/resources/waypoints?bbox=5.4,25.7,6.9,31.2 -listResources('routes', {bbox: '5.4,25.7,6.9,31.2'}) +listResources('waypoints', {bbox: '5.4,25.7,6.9,31.2'}) returns { "resource_id1": { ... }, @@ -372,71 +455,65 @@ returns { } ``` +### __Get specific resource:__ + `GET` requests for a specific resource will be dispatched to the `getResource` method passing the resource type and id as parameters. -Returns: Object containing resourcesdata. +`getResource()` should returns a JSON object containing the resource data. _Example: Retrieve route._ -```JAVASCRIPT +```javascript GET /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a getResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a') returns { - "name": "route name", - ... + "name": "Name of the route", + "description": "Description of the route", + "distance": 18345, "feature": { ... } } ``` ---- -`PUT` requests for a specific resource will be dispatched to the `setResource` method passing the resource type, id and resource data as parameters. +### __Saving Resources:__ -Returns: `true` on success, `null` on failure. +`PUT` requests to a path containing the resource id are used to store data associated with the resource id. These will be dispatched to the `setResource` method passing the resource type, id and data as parameters. -_Example: Update route data._ -```JAVASCRIPT -PUT /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a {resource data} +`setResource() ` returns `true` on success and `null` on failure. -setResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) -``` - -`POST` requests will be dispatched to the `setResource` method passing the resource type, a generated id and resource data as parameters. - -Returns: `true` on success, `null` on failure. +_Example: Update / add waypoint with the supplied id._ +```javascript +PUT /signalk/v1/api/resources/waypoints/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a {} -_Example: New route._ -```JAVASCRIPT -POST /signalk/v1/api/resources/routes/ {resource data} +setResource('waypoints', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) -setResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) +returns true | null ``` ---- -`DELETE` requests for a specific resource will be dispatched to the `deleteResource` method passing the resource type and id as parameters. +`POST` requests to a resource path that do not contina the resource id will be dispatched to the `setResource` method passing the resource type, an id (generated by the server) and resource data as parameters. -Returns: `true` on success, `null` on failure. +`setResource() ` returns `true` on success and `null` on failure. -_Example: Delete route._ -```JAVASCRIPT -DELETE /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a {resource data} +_Example: New route record._ +```javascript +POST /signalk/v1/api/resources/routes {} -deleteResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a') +setResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) + +returns true | null ``` ---- -### Plugin Startup: +### __Deleting Resources:__ -If your plugin provides the ability for the user to choose which resource types are handled, then the plugin will need to notify the server that the `types` attribute of the `resourceProvider` interface has been modified. +`DELETE` requests to a path containing the resource id will be dispatched to the `deleteResource` method passing the resource type and id as parameters. -The server exposes `resourcesApi` which has the following method: -```JAVASCRIPT -checkForProviders(rescan:boolean) -``` -which can be called within the plugin `start()` function with `rescan= true`. +`deleteResource()` returns `true` on success, `null` on failure. -This will cause the server to `rescan` for resource providers and register the new list of resource types being handled. +_Example: Delete region with supplied id._ +```javascript +DELETE /signalk/v1/api/resources/regions/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a +<<<<<<< HEAD _Example:_ ```JAVASCRIPT module.exports = function (app) { @@ -453,6 +530,11 @@ module.exports = function (app) { >>>>>>> Added Resource_Provider documentation } } +======= +deleteResource('regions', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a') + +returns true | null +>>>>>>> Add register / unregister ``` <<<<<<< HEAD diff --git a/SERVERPLUGINS.md b/SERVERPLUGINS.md index e904d7d59..6c0bb286c 100644 --- a/SERVERPLUGINS.md +++ b/SERVERPLUGINS.md @@ -26,7 +26,7 @@ To get started with SignalK plugin development, you can follow this guide. _Note: For plugins acting as a provider for one or more of the SignalK resource types listed in the specification (`routes`, `waypoints`, `notes`, `regions` or `charts`) please refer to __[RESOURCE_PROVIDER_PLUGINS.md](./RESOURCE_PROVIDER_PLUGINS.md)__ for additional details._ -_Note: For plugins acting as a `provider` for one or more of the resource types listed in the Signal K specification (e.g. routes, waypoints, notes, regions or charts) there are additional requirementss which are detailed in __[RESOURCE_PROVIDER_PLUGINS.md](./RESOURCE_PROVIDER_PLUGINS.md)__._ +_Note: For plugins acting as a provider for one or more of the resource types listed in the Signal K specification (e.g. routes, waypoints, notes, regions or charts) refer to __[RESOURCE_PROVIDER_PLUGINS.md](./RESOURCE_PROVIDER_PLUGINS.md)__ for details. ### Project setup @@ -704,6 +704,7 @@ app.registerDeltaInputHandler((delta, next) => { }) ``` +<<<<<<< HEAD ### `app.resourcesApi.getResource(resource_type, resource_id)` Retrieve resource data for the supplied SignalK resource type and resource id. @@ -758,11 +759,35 @@ module.exports = function (app) { ### `app.resourcesApi.unRegister(pluginId)` When a resource provider plugin is disabled it will need to un-register its provider methods for all of the resource types it manages. This should be done in the plugin's `stop()` function. +======= +### `app.resourcesApi.register(provider)` + +If a plugin wants to act as a resource provider, it will need to register its provider methods during startup using this function. See [`RESOURCE_PROVIDER_PLUGINS.md`](./RESOURCE_PROVIDER_PLUGINS.md) for details. ```javascript +plugin.start = function(options) { + ... + // plugin_provider is the plugin's `ResourceProvider` interface. + app.resourcesApi.register(plugin_provider); +} + +``` + + + +### `app.resourcesApi.unRegister(resource_types)` + +When a resource provider plugin is disabled it will need to un-register its provider methods for the resource types it manages. This should be done in the plugin's `stop()` function. +>>>>>>> Add register / unregister + +See [`RESOURCE_PROVIDER_PLUGINS.md`](./RESOURCE_PROVIDER_PLUGINS.md) for details. + + +```javascript +<<<<<<< HEAD module.exports = function (app) { let plugin= { id: 'mypluginid', @@ -778,6 +803,14 @@ module.exports = function (app) { } } } +======= +plugin.stop = function(options) { + // resource_types example: ['routes',waypoints'] + app.resourcesApi.unRegister(resource_types); + ... +} + +>>>>>>> Add register / unregister ``` diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 0e32cfd01..8a87e0b1b 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -580,9 +580,32 @@ export class Resources { this.initResourceRoutes() } + public register(provider:any) { + debug(`** Registering provider(s)....${provider?.types}`) + if(!provider ) { return } + if(provider.types && !Array.isArray(provider.types)) { return } + provider.types.forEach( (i:string)=>{ + if(!this.resProvider[i]) { + this.resProvider[i]= provider.methods + } + }) + debug(this.resProvider) + } + + public unRegister(resourceTypes:string[]) { + debug(`** Un-registering provider(s)....${resourceTypes}`) + if(!Array.isArray(resourceTypes)) { return } + resourceTypes.forEach( (i:string)=>{ + if(this.resProvider[i]) { + delete this.resProvider[i] + } + }) + debug(JSON.stringify(this.resProvider)) + } + public checkForProviders(rescan:boolean= false) { if(rescan || Object.keys(this.resProvider).length===0) { - debug('** Checking for providers....') + debug(`** Checking for providers....(rescan=${rescan})`) this.resProvider= {} this.resourceTypes.forEach( (rt:string)=> { this.resProvider[rt]= this.getResourceProviderFor(rt) @@ -593,7 +616,6 @@ export class Resources { public getResource(type:string, id:string) { debug(`** getResource(${type}, ${id})`) - this.checkForProviders() return this.actionResourceRequest({ method: 'GET', body: {}, @@ -607,7 +629,6 @@ export class Resources { // ** initialise handler for in-scope resource types ** private initResourceRoutes() { this.server.get(`${SIGNALK_API_PATH}/resources`, (req:any, res:any) => { - this.checkForProviders() // list all serviced paths under resources res.json(this.getResourcePaths()) }) @@ -680,7 +701,6 @@ export class Resources { } } - this.checkForProviders() let retReq= { method: req.method, body: req.body, From 41e2616052ba6c1e6f22217477b5d17e35da0747 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 15 Nov 2021 10:10:21 +1030 Subject: [PATCH 270/410] add constructor --- src/api/resources/index.ts | 67 +++++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 22 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 8a87e0b1b..81bc75211 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -540,6 +540,11 @@ interface ResourceRequest { } interface ResourceProvider { + types: Array + methods: ResourceProviderMethods +} + +interface ResourceProviderMethods { listResources: (type:string, query: {[key:string]:any})=> Promise getResource: (type:string, id:string)=> Promise setResource: (type:string, id:string, value:{[key:string]:any})=> Promise @@ -571,16 +576,22 @@ export class Resources { 'charts' ] - resProvider: {[key:string]: any}= {} + resProvider: {[key:string]: ResourceProviderMethods | null}= {} server: any - public start(app:any) { + constructor(app:any) { + this.start(app) + } + + // ** initialise resourcesApi ** + private start(app:any) { debug(`** Initialise ${SIGNALK_API_PATH}/resources path handler **`) this.server= app this.initResourceRoutes() } - public register(provider:any) { + // ** register resource provider ** + public register(provider:ResourceProvider) { debug(`** Registering provider(s)....${provider?.types}`) if(!provider ) { return } if(provider.types && !Array.isArray(provider.types)) { return } @@ -592,6 +603,7 @@ export class Resources { debug(this.resProvider) } + // ** un-register resource provider for the supplied types ** public unRegister(resourceTypes:string[]) { debug(`** Un-registering provider(s)....${resourceTypes}`) if(!Array.isArray(resourceTypes)) { return } @@ -601,19 +613,15 @@ export class Resources { } }) debug(JSON.stringify(this.resProvider)) - } - public checkForProviders(rescan:boolean= false) { - if(rescan || Object.keys(this.resProvider).length===0) { - debug(`** Checking for providers....(rescan=${rescan})`) - this.resProvider= {} - this.resourceTypes.forEach( (rt:string)=> { - this.resProvider[rt]= this.getResourceProviderFor(rt) - }) - debug(this.resProvider) - } + /** scan plugins in case there is more than one plugin that can service + * a particular resource type. **/ + debug('** RESCANNING **') + this.checkForProviders() + debug(JSON.stringify(this.resProvider)) } + // ** return resource with supplied type and id ** public getResource(type:string, id:string) { debug(`** getResource(${type}, ${id})`) return this.actionResourceRequest({ @@ -625,6 +633,20 @@ export class Resources { }) } + /** Scan plugins for resource providers and register them + * rescan= false: only add providers for types where no provider is registered + * rescan= true: clear providers for all types prior to commencing scan. + **/ + private checkForProviders(rescan:boolean= false) { + if(rescan) { this.resProvider= {} } + debug(`** Checking for providers....(rescan=${rescan})`) + this.resProvider= {} + this.resourceTypes.forEach( (rt:string)=> { + this.resProvider[rt]= this.getResourceProviderFor(rt) + }) + debug(this.resProvider) + + } // ** initialise handler for in-scope resource types ** private initResourceRoutes() { @@ -652,7 +674,7 @@ export class Resources { }) } - // ** return all paths serviced under ./resources *8 + // ** return all paths serviced under SIGNALK_API_PATH/resources ** private getResourcePaths(): {[key:string]:any} { let resPaths:{[key:string]:any}= {} Object.entries(this.resProvider).forEach( (p:any)=> { @@ -766,7 +788,7 @@ export class Resources { if(req.method==='GET') { let retVal: any if(!req.resourceId) { - retVal= await this.resProvider[req.resourceType].listResources(req.resourceType, req.query) + retVal= await this.resProvider[req.resourceType]?.listResources(req.resourceType, req.query) return (retVal) ? retVal : {statusCode: 404, message: `Error retrieving resources!` } @@ -774,7 +796,7 @@ export class Resources { if(!validate.uuid(req.resourceId)) { return {statusCode: 406, message: `Invalid resource id provided (${req.resourceId})` } } - retVal= await this.resProvider[req.resourceType].getResource(req.resourceType, req.resourceId) + retVal= await this.resProvider[req.resourceType]?.getResource(req.resourceType, req.resourceId) return (retVal) ? retVal : {statusCode: 404, message: `Resource not found (${req.resourceId})!` } @@ -791,7 +813,7 @@ export class Resources { req.method==='DELETE' || (req.method==='PUT' && typeof req.body.value!=='undefined' && req.body.value==null) ) { - let retVal= await this.resProvider[req.resourceType].deleteResource(req.resourceType, req.resourceId) + let retVal= await this.resProvider[req.resourceType]?.deleteResource(req.resourceType, req.resourceId) if(retVal){ this.sendDelta(req.resourceType, req.resourceId, null) return {statusCode: 200, message: `Resource (${req.resourceId}) deleted.`} @@ -814,7 +836,7 @@ export class Resources { } if(req.method==='POST') { let id= UUID_PREFIX + uuidv4() - let retVal= await this.resProvider[req.resourceType].setResource(req.resourceType, id, req.body.value) + let retVal= await this.resProvider[req.resourceType]?.setResource(req.resourceType, id, req.body.value) if(retVal){ this.sendDelta(req.resourceType, id, req.body.value) return {statusCode: 200, message: `Resource (${id}) saved.`} @@ -827,7 +849,7 @@ export class Resources { if(!req.resourceId) { return {statusCode: 406, message: `No resource id provided!` } } - let retVal= await this.resProvider[req.resourceType].setResource(req.resourceType, req.resourceId, req.body.value) + let retVal= await this.resProvider[req.resourceType]?.setResource(req.resourceType, req.resourceId, req.body.value) if(retVal){ this.sendDelta(req.resourceType, req.resourceId, req.body.value) return {statusCode: 200, message: `Resource (${req.resourceId}) updated.`} @@ -839,6 +861,7 @@ export class Resources { } } + // ** send delta message with resource PUT, POST, DELETE action result private sendDelta(type:string, id:string, value:any):void { debug(`** Sending Delta: resources.${type}.${id}`) this.server.handleMessage('signalk-resources', { @@ -855,10 +878,10 @@ export class Resources { }) } - // ** get reference to installed resource provider (plug-in). returns null if none found - private getResourceProviderFor(resType:string): ResourceProvider | null { + // ** Get provider methods for supplied resource type. Returns null if none found ** + private getResourceProviderFor(resType:string): ResourceProviderMethods | null { if(!this.server.plugins) { return null} - let pSource: ResourceProvider | null= null + let pSource: ResourceProviderMethods | null= null this.server.plugins.forEach((plugin:any)=> { if(typeof plugin.resourceProvider !== 'undefined') { pSource= plugin.resourceProvider.types.includes(resType) ? From 5a3acb8a85a3e699660bfd19a89fb57801088fe5 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 15 Nov 2021 10:10:47 +1030 Subject: [PATCH 271/410] add getResource function --- SERVERPLUGINS.md | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/SERVERPLUGINS.md b/SERVERPLUGINS.md index 6c0bb286c..6835483bc 100644 --- a/SERVERPLUGINS.md +++ b/SERVERPLUGINS.md @@ -704,6 +704,7 @@ app.registerDeltaInputHandler((delta, next) => { }) ``` +<<<<<<< HEAD <<<<<<< HEAD ### `app.resourcesApi.getResource(resource_type, resource_id)` @@ -760,6 +761,32 @@ module.exports = function (app) { When a resource provider plugin is disabled it will need to un-register its provider methods for all of the resource types it manages. This should be done in the plugin's `stop()` function. ======= +======= +### `app.resourcesApi.getResource(resource_type, resource_id)` + +Retrieve resource data for the supplied resource type and id. + + data for the full path of the directory where the plugin can persist its internal data, like data files.If a plugin wants to act as a resource provider, it will need to register its provider methods during startup using this function. + + + +```javascript +let myRoute= app.resourcesApi.getResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a'); +``` +Will return the route resource data or `null` if a route with the supplied id cannot be found. + +_Example:_ +```json +{ + "name": "Name of the route", + "description": "Description of the route", + "distance": 18345, + "feature": { ... } +} + +``` + +>>>>>>> add getResource function ### `app.resourcesApi.register(provider)` If a plugin wants to act as a resource provider, it will need to register its provider methods during startup using this function. @@ -776,8 +803,6 @@ plugin.start = function(options) { ``` - - ### `app.resourcesApi.unRegister(resource_types)` When a resource provider plugin is disabled it will need to un-register its provider methods for the resource types it manages. This should be done in the plugin's `stop()` function. From 694f8fab93ef0c089271ae011acf80f332ada9ce Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 15 Nov 2021 10:31:19 +1030 Subject: [PATCH 272/410] chore: fix formatting --- src/api/resources/index.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 81bc75211..12651884f 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -614,8 +614,7 @@ export class Resources { }) debug(JSON.stringify(this.resProvider)) - /** scan plugins in case there is more than one plugin that can service - * a particular resource type. **/ + //** scan plugins in case there is more than one plugin that can service a particular resource type. ** debug('** RESCANNING **') this.checkForProviders() debug(JSON.stringify(this.resProvider)) @@ -633,10 +632,9 @@ export class Resources { }) } - /** Scan plugins for resource providers and register them - * rescan= false: only add providers for types where no provider is registered - * rescan= true: clear providers for all types prior to commencing scan. - **/ + // Scan plugins for resource providers and register them + // rescan= false: only add providers for types where no provider is registered + // rescan= true: clear providers for all types prior to commencing scan. private checkForProviders(rescan:boolean= false) { if(rescan) { this.resProvider= {} } debug(`** Checking for providers....(rescan=${rescan})`) From 523fd50367df54aba0e57791bbc4667add58e369 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 15 Nov 2021 10:41:56 +1030 Subject: [PATCH 273/410] OpenApi descriptions --- src/api/resources/openApi.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/api/resources/openApi.json b/src/api/resources/openApi.json index 81b9c9913..126ae0f94 100644 --- a/src/api/resources/openApi.json +++ b/src/api/resources/openApi.json @@ -50,13 +50,18 @@ } }, +<<<<<<< HEAD "/resources/{resourceClass}": { >>>>>>> add OpenApi definition file +======= + "/resources/{resourceType}": { +>>>>>>> OpenApi descriptions "get": { "tags": ["resources"], "summary": "Retrieve resources", "parameters": [ { +<<<<<<< HEAD <<<<<<< HEAD "name": "resourceType", "in": "path", @@ -66,6 +71,11 @@ "in": "path", "description": "resource class", >>>>>>> add OpenApi definition file +======= + "name": "resourceType", + "in": "path", + "description": "Type of resources to retrieve. Valid values are: routes, waypoints, notes, regions, charts", +>>>>>>> OpenApi descriptions "required": true, "schema": { "type": "string", @@ -106,6 +116,7 @@ } }, { +<<<<<<< HEAD <<<<<<< HEAD "name": "bbox", "in": "query", @@ -117,6 +128,10 @@ "description": "Limit results to resources that fall within the bounded area defined as lower left and upper right x,y coordinates x1,y1,x2,y2", >>>>>>> add OpenApi definition file ======= +======= + "name": "bbox", + "in": "query", +>>>>>>> OpenApi descriptions "description": "Limit results to resources that fall within the bounded area defined as lower left (south west) and upper right (north east) coordinates [swlon,swlat,nelon,nelat]", >>>>>>> addressed comments re parameters "style": "form", From 6bbaf2deaa11108caad523cce7fbde9f0964144d Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 20 Nov 2021 13:46:56 +1030 Subject: [PATCH 274/410] add pluginId to register() function --- RESOURCE_PROVIDER_PLUGINS.md | 4 ++-- SERVERPLUGINS.md | 24 ++++++++++++++++++------ src/api/resources/index.ts | 4 +++- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index adacb9fa7..fe25a61f6 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -339,7 +339,7 @@ module.exports = function (app) { }, start: (options, restart)=> { ... - app.resourceApi.register(this.resourceProvider); + app.resourceApi.register(this.id, this.resourceProvider); }, stop: ()=> { app.resourceApi.unRegister(this.resourceProvider.types); @@ -384,7 +384,7 @@ module.exports = function (app) { plugin.start = function(options) { ... - app.resourcesApi.register(plugin.resourceProvider); + app.resourcesApi.register(plugin.id, plugin.resourceProvider); } } ``` diff --git a/SERVERPLUGINS.md b/SERVERPLUGINS.md index 6835483bc..1d58211ed 100644 --- a/SERVERPLUGINS.md +++ b/SERVERPLUGINS.md @@ -786,8 +786,12 @@ _Example:_ ``` +<<<<<<< HEAD >>>>>>> add getResource function ### `app.resourcesApi.register(provider)` +======= +### `app.resourcesApi.register(pluginId, provider)` +>>>>>>> add pluginId to register() function If a plugin wants to act as a resource provider, it will need to register its provider methods during startup using this function. @@ -795,12 +799,20 @@ See [`RESOURCE_PROVIDER_PLUGINS.md`](./RESOURCE_PROVIDER_PLUGINS.md) for details ```javascript -plugin.start = function(options) { - ... - // plugin_provider is the plugin's `ResourceProvider` interface. - app.resourcesApi.register(plugin_provider); -} - +module.exports = function (app) { + let plugin= { + id: 'mypluginid', + name: 'My Resource Providerplugin', + resourceProvider: { + types: ['routes','waypoints'], + methods: { ... } + } + start: function(options) { + ... + app.resourcesApi.register(this.id, this.resourceProvider); + } + ... + } ``` ### `app.resourcesApi.unRegister(resource_types)` diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 12651884f..0aed89900 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -545,6 +545,7 @@ interface ResourceProvider { } interface ResourceProviderMethods { + pluginId: string listResources: (type:string, query: {[key:string]:any})=> Promise getResource: (type:string, id:string)=> Promise setResource: (type:string, id:string, value:{[key:string]:any})=> Promise @@ -591,12 +592,13 @@ export class Resources { } // ** register resource provider ** - public register(provider:ResourceProvider) { + public register(pluginId:string, provider:ResourceProvider) { debug(`** Registering provider(s)....${provider?.types}`) if(!provider ) { return } if(provider.types && !Array.isArray(provider.types)) { return } provider.types.forEach( (i:string)=>{ if(!this.resProvider[i]) { + provider.methods.pluginId= pluginId this.resProvider[i]= provider.methods } }) From 99bdce7ccae53ff32af2b0bf50343de0d7853b45 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 20 Nov 2021 14:17:32 +1030 Subject: [PATCH 275/410] add pluginId to unRegister function --- RESOURCE_PROVIDER_PLUGINS.md | 10 +++++----- SERVERPLUGINS.md | 18 +++++++++++++++++- src/api/resources/index.ts | 28 ++++++---------------------- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index fe25a61f6..ecdad62d5 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -337,12 +337,12 @@ module.exports = function (app) { } } }, - start: (options, restart)=> { + start: (options)=> { ... app.resourceApi.register(this.id, this.resourceProvider); }, stop: ()=> { - app.resourceApi.unRegister(this.resourceProvider.types); + app.resourceApi.unRegister(this.id, this.resourceProvider.types); ... } } @@ -353,7 +353,7 @@ module.exports = function (app) { ### Plugin Startup - Registering the Resource Provider: -To register your plugin as a resource provider the server's `resourcesApi.register()` function should be called within the plugin `start()` function passing the `resourceProvider` interface. +To register your plugin as a resource provider the server's `resourcesApi.register()` function should be called within the plugin `start()` function passing the `resourceProvider` interface. This registers the resource types and the methods with the server so they are called when requests to resource paths are made. @@ -392,7 +392,7 @@ module.exports = function (app) { ### Plugin Stop - Un-registering the Resource Provider: -When a resource provider plugin is disabled / stopped it should un-register as a provider so resource requests are not directed to it. This is done by calling the server's `resourcesApi.unRegister()` function passing `resourceProvider.types` within the plugin's `stop()` function. +When a resource provider plugin is disabled it should un-register as a provider so resource requests are not directed to it. This is done by calling the server's `resourcesApi.unRegister()` function passing `resourceProvider.types` within the plugin's `stop()` function. _Example:_ ```javascript @@ -408,7 +408,7 @@ module.exports = function (app) { plugin.stop = function(options) { ... - app.resourcesApi.unRegister(plugin.resourceProvider.types); + app.resourcesApi.unRegister(plugin.id, plugin.resourceProvider.types); } } ``` diff --git a/SERVERPLUGINS.md b/SERVERPLUGINS.md index 1d58211ed..11a1612ae 100644 --- a/SERVERPLUGINS.md +++ b/SERVERPLUGINS.md @@ -813,18 +813,26 @@ module.exports = function (app) { } ... } +} ``` -### `app.resourcesApi.unRegister(resource_types)` +### `app.resourcesApi.unRegister(pluginId, resource_types)` +<<<<<<< HEAD When a resource provider plugin is disabled it will need to un-register its provider methods for the resource types it manages. This should be done in the plugin's `stop()` function. >>>>>>> Add register / unregister +======= +When a resource provider plugin is disabled it will need to un-register its provider methods for all of the resource types it manages. This should be done in the plugin's `stop()` function. +>>>>>>> add pluginId to unRegister function See [`RESOURCE_PROVIDER_PLUGINS.md`](./RESOURCE_PROVIDER_PLUGINS.md) for details. ```javascript <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> add pluginId to unRegister function module.exports = function (app) { let plugin= { id: 'mypluginid', @@ -835,6 +843,7 @@ module.exports = function (app) { } ... stop: function(options) { +<<<<<<< HEAD app.resourcesApi.unRegister(this.id); // do plugin shutdown } @@ -848,6 +857,13 @@ plugin.stop = function(options) { } >>>>>>> Add register / unregister +======= + app.resourcesApi.unRegister(this.id, this.resourceProvider.types); + ... + } + } +} +>>>>>>> add pluginId to unRegister function ``` diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 0aed89900..285c02b9d 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -606,20 +606,15 @@ export class Resources { } // ** un-register resource provider for the supplied types ** - public unRegister(resourceTypes:string[]) { - debug(`** Un-registering provider(s)....${resourceTypes}`) + public unRegister(pluginId:string, resourceTypes:string[]) { + debug(`** Un-registering ${pluginId} provider(s)....${resourceTypes}`) if(!Array.isArray(resourceTypes)) { return } resourceTypes.forEach( (i:string)=>{ - if(this.resProvider[i]) { + if(this.resProvider[i] && this.resProvider[i]?.pluginId===pluginId) { delete this.resProvider[i] } }) debug(JSON.stringify(this.resProvider)) - - //** scan plugins in case there is more than one plugin that can service a particular resource type. ** - debug('** RESCANNING **') - this.checkForProviders() - debug(JSON.stringify(this.resProvider)) } // ** return resource with supplied type and id ** @@ -634,20 +629,6 @@ export class Resources { }) } - // Scan plugins for resource providers and register them - // rescan= false: only add providers for types where no provider is registered - // rescan= true: clear providers for all types prior to commencing scan. - private checkForProviders(rescan:boolean= false) { - if(rescan) { this.resProvider= {} } - debug(`** Checking for providers....(rescan=${rescan})`) - this.resProvider= {} - this.resourceTypes.forEach( (rt:string)=> { - this.resProvider[rt]= this.getResourceProviderFor(rt) - }) - debug(this.resProvider) - - } - // ** initialise handler for in-scope resource types ** private initResourceRoutes() { this.server.get(`${SIGNALK_API_PATH}/resources`, (req:any, res:any) => { @@ -878,6 +859,7 @@ export class Resources { }) } +<<<<<<< HEAD // ** Get provider methods for supplied resource type. Returns null if none found ** private getResourceProviderFor(resType:string): ResourceProviderMethods | null { if(!this.server.plugins) { return null} @@ -894,4 +876,6 @@ export class Resources { } >>>>>>> Add Signal K standard resource path handling +======= +>>>>>>> add pluginId to unRegister function } From 0bae09fbcb07f7307246b96f152db6e2a3e9c3f3 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 20 Nov 2021 14:25:51 +1030 Subject: [PATCH 276/410] set plugin id as delta source --- src/api/resources/index.ts | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 285c02b9d..3a54d87ef 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -796,7 +796,11 @@ export class Resources { ) { let retVal= await this.resProvider[req.resourceType]?.deleteResource(req.resourceType, req.resourceId) if(retVal){ - this.sendDelta(req.resourceType, req.resourceId, null) + this.sendDelta( + this.resProvider[req.resourceType]?.pluginId as string, + req.resourceType, req.resourceId, + null + ) return {statusCode: 200, message: `Resource (${req.resourceId}) deleted.`} } else { @@ -819,7 +823,12 @@ export class Resources { let id= UUID_PREFIX + uuidv4() let retVal= await this.resProvider[req.resourceType]?.setResource(req.resourceType, id, req.body.value) if(retVal){ - this.sendDelta(req.resourceType, id, req.body.value) + this.sendDelta( + this.resProvider[req.resourceType]?.pluginId as string, + req.resourceType, + id, + req.body.value + ) return {statusCode: 200, message: `Resource (${id}) saved.`} } else { @@ -832,7 +841,12 @@ export class Resources { } let retVal= await this.resProvider[req.resourceType]?.setResource(req.resourceType, req.resourceId, req.body.value) if(retVal){ - this.sendDelta(req.resourceType, req.resourceId, req.body.value) + this.sendDelta( + this.resProvider[req.resourceType]?.pluginId as string, + req.resourceType, + req.resourceId, + req.body.value + ) return {statusCode: 200, message: `Resource (${req.resourceId}) updated.`} } else { @@ -843,9 +857,9 @@ export class Resources { } // ** send delta message with resource PUT, POST, DELETE action result - private sendDelta(type:string, id:string, value:any):void { + private sendDelta(providerId:string, type:string, id:string, value:any):void { debug(`** Sending Delta: resources.${type}.${id}`) - this.server.handleMessage('signalk-resources', { + this.server.handleMessage(providerId, { updates: [ { values: [ From 3c33925df8c38222a018c338b1929ca35130dfb2 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 20 Nov 2021 14:59:03 +1030 Subject: [PATCH 277/410] unregister only requires plugin id --- src/api/resources/index.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 3a54d87ef..a806294f5 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -606,14 +606,15 @@ export class Resources { } // ** un-register resource provider for the supplied types ** - public unRegister(pluginId:string, resourceTypes:string[]) { - debug(`** Un-registering ${pluginId} provider(s)....${resourceTypes}`) - if(!Array.isArray(resourceTypes)) { return } - resourceTypes.forEach( (i:string)=>{ - if(this.resProvider[i] && this.resProvider[i]?.pluginId===pluginId) { + public unRegister(pluginId:string) { + if(!pluginId) { return } + debug(`** Un-registering ${pluginId} resource provider(s)....`) + for( let i in this.resProvider ) { + if(this.resProvider[i]?.pluginId===pluginId) { + debug(`** Un-registering ${i}....`) delete this.resProvider[i] } - }) + } debug(JSON.stringify(this.resProvider)) } From 9969eb195fe1e652d9a2c4f9d8805afc38650f5c Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 20 Nov 2021 14:59:16 +1030 Subject: [PATCH 278/410] update docs --- RESOURCE_PROVIDER_PLUGINS.md | 26 ++++++++++++++++---------- SERVERPLUGINS.md | 10 +++++++++- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index ecdad62d5..84b9b91c6 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -342,7 +342,7 @@ module.exports = function (app) { app.resourceApi.register(this.id, this.resourceProvider); }, stop: ()=> { - app.resourceApi.unRegister(this.id, this.resourceProvider.types); + app.resourceApi.unRegister(this.id); ... } } @@ -351,7 +351,7 @@ module.exports = function (app) { --- -### Plugin Startup - Registering the Resource Provider: +## Plugin Startup - Registering the Resource Provider: To register your plugin as a resource provider the server's `resourcesApi.register()` function should be called within the plugin `start()` function passing the `resourceProvider` interface. @@ -390,9 +390,9 @@ module.exports = function (app) { ``` --- -### Plugin Stop - Un-registering the Resource Provider: +## Plugin Stop - Un-registering the Resource Provider: -When a resource provider plugin is disabled it should un-register as a provider so resource requests are not directed to it. This is done by calling the server's `resourcesApi.unRegister()` function passing `resourceProvider.types` within the plugin's `stop()` function. +When a resource provider plugin is disabled it should un-register as a provider so resource requests are not directed to it. This is done by calling the server's `resourcesApi.unRegister()` function passing the `plugin.id` within the plugin's `stop()` function. _Example:_ ```javascript @@ -401,24 +401,30 @@ module.exports = function (app) { id: 'mypluginid', name: 'My Resource Providerplugin', resourceProvider: { - types: ['routes','waypoints'], + types: [ ... ], methods: { ... } } } plugin.stop = function(options) { + app.resourcesApi.unRegister(plugin.id); ... - app.resourcesApi.unRegister(plugin.id, plugin.resourceProvider.types); } } ``` --- -### Operation: +## Operation: + +The Server will dispatch requests made to: +- `/signalk/v1/api/resources/` + +OR +- the `resources API` endpoints -The Server will dispatch requests made to `/signalk/v1/api/resources/` to the plugin's `resourceProvider.methods` for each resource type listed in `resourceProvider.types`. +to the plugin's `resourceProvider.methods` for each of the resource types listed in `resourceProvider.types`. -Each method defined in the plugin must have a signature as specified in the interface. Each method returns a `Promise` containing the resource data or `null` if an error occurrred. +Each method defined in `resourceProvider.methods` must have a signature as specified in the __resourceProvider interface__. Each method returns a `Promise` containing the resultant resource data or `null` if an error occurrred or the operation is incomplete. ### __List Resources:__ @@ -455,7 +461,7 @@ returns { } ``` -### __Get specific resource:__ +### __Retrieve a specific resource:__ `GET` requests for a specific resource will be dispatched to the `getResource` method passing the resource type and id as parameters. diff --git a/SERVERPLUGINS.md b/SERVERPLUGINS.md index 11a1612ae..27c551352 100644 --- a/SERVERPLUGINS.md +++ b/SERVERPLUGINS.md @@ -786,12 +786,16 @@ _Example:_ ``` +<<<<<<< HEAD <<<<<<< HEAD >>>>>>> add getResource function ### `app.resourcesApi.register(provider)` ======= ### `app.resourcesApi.register(pluginId, provider)` >>>>>>> add pluginId to register() function +======= +### `app.resourcesApi.register(pluginId, resourceProvider)` +>>>>>>> update docs If a plugin wants to act as a resource provider, it will need to register its provider methods during startup using this function. @@ -816,7 +820,7 @@ module.exports = function (app) { } ``` -### `app.resourcesApi.unRegister(pluginId, resource_types)` +### `app.resourcesApi.unRegister(pluginId)` <<<<<<< HEAD When a resource provider plugin is disabled it will need to un-register its provider methods for the resource types it manages. This should be done in the plugin's `stop()` function. @@ -843,6 +847,7 @@ module.exports = function (app) { } ... stop: function(options) { +<<<<<<< HEAD <<<<<<< HEAD app.resourcesApi.unRegister(this.id); // do plugin shutdown @@ -859,6 +864,9 @@ plugin.stop = function(options) { >>>>>>> Add register / unregister ======= app.resourcesApi.unRegister(this.id, this.resourceProvider.types); +======= + app.resourcesApi.unRegister(this.id); +>>>>>>> update docs ... } } From eb835d2b9576abc6be91d97c78240f7743a52ead Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 21 Nov 2021 11:26:43 +1030 Subject: [PATCH 279/410] add resource attribute req to query object --- src/api/resources/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index a806294f5..51b5452aa 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -686,8 +686,12 @@ export class Resources { let p= req.params[0].split('/') let resType= (typeof req.params[0]!=='undefined') ? p[0] : '' let resId= p.length>1 ? p[1] : '' + let resAttrib= p.length>2 ? p.slice(2) : [] + req.query.resAttrib= resAttrib debug('** resType:', resType) debug('** resId:', resId) + debug('** resAttrib:', resAttrib) + debug('** req.params + attribs:', req.query) let apiMethod= (API_METHODS.includes(resType)) ? resType : null if(apiMethod) { From 774560e13621d5be0ced7145542472a8fc914945 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 21 Nov 2021 11:45:14 +1030 Subject: [PATCH 280/410] chore: update docs with query object examples. --- RESOURCE_PROVIDER_PLUGINS.md | 41 ++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index 84b9b91c6..2bab44358 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -431,6 +431,16 @@ Each method defined in `resourceProvider.methods` must have a signature as speci `GET` requests that are not for a specific resource will be dispatched to the `listResources` method passing the resource type and any query data as parameters. +Query parameters are passed as an object conatining `key | value` pairs. + +_Example: GET /signalk/v1/api/resources/waypoints?bbox=5.4,25.7,6.9,31.2&distance=30000_ +```javascript +query= { + bbox: '5.4,25.7,6.9,31.2', + distance: 30000 +} +``` + It is the responsibility of the resource provider plugin to filter the resources returned as per the supplied query parameters. `listResources()` should return a JSON object listing resources by id. @@ -471,9 +481,16 @@ _Example: Retrieve route._ ```javascript GET /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a -getResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a') +getResource( + 'routes', + 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', + {} +) +``` -returns { +_Returns the result:_ +```json +{ "name": "Name of the route", "description": "Description of the route", "distance": 18345, @@ -481,6 +498,26 @@ returns { } ``` +A request for a resource attribute will pass the attibute path as an array in the query object with the key `resAttrib`. + +_Example: Get waypoint geometry._ +```javascript +GET /signalk/v1/api/resources/waypoints/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a/feature/geometry + +getResource( + 'waypoints', + 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', + { resAttrib: ['feature','geometry'] } +) +``` +_Returns the value of `geometry` attribute of the waypoint._ +```json +{ + "type": "Point", + "coordinates": [70.4,6.45] +} +``` + ### __Saving Resources:__ `PUT` requests to a path containing the resource id are used to store data associated with the resource id. These will be dispatched to the `setResource` method passing the resource type, id and data as parameters. From 984a200302bb4f1ad4dfcb2ec669c1d1913654b6 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 21 Nov 2021 15:40:58 +1030 Subject: [PATCH 281/410] chore: linted --- src/api/resources/index.ts | 669 ++++++++++++++++++--------------- src/api/resources/resources.ts | 333 +++++++++------- src/api/resources/validate.ts | 183 +++++---- src/put.js | 7 + 4 files changed, 670 insertions(+), 522 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 51b5452aa..876f377ae 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -1,6 +1,7 @@ import Debug from 'debug' import { v4 as uuidv4 } from 'uuid' <<<<<<< HEAD +<<<<<<< HEAD import { buildResource } from './resources' import { validate } from './validate' @@ -526,358 +527,397 @@ export class Resources { } ======= import { validate } from './validate' +======= +>>>>>>> chore: linted import { buildResource } from './resources' +import { validate } from './validate' const debug = Debug('signalk:resources') interface ResourceRequest { - method: 'GET' | 'PUT' | 'POST' | 'DELETE' - body: any - query: {[key:string]: any} - resourceType: string - resourceId: string, - apiMethod?: string | null + method: 'GET' | 'PUT' | 'POST' | 'DELETE' + body: any + query: { [key: string]: any } + resourceType: string + resourceId: string + apiMethod?: string | null } interface ResourceProvider { - types: Array - methods: ResourceProviderMethods + types: string[] + methods: ResourceProviderMethods } interface ResourceProviderMethods { - pluginId: string - listResources: (type:string, query: {[key:string]:any})=> Promise - getResource: (type:string, id:string)=> Promise - setResource: (type:string, id:string, value:{[key:string]:any})=> Promise - deleteResource: (type:string, id:string)=> Promise + pluginId: string + listResources: (type: string, query: { [key: string]: any }) => Promise + getResource: (type: string, id: string) => Promise + setResource: ( + type: string, + id: string, + value: { [key: string]: any } + ) => Promise + deleteResource: (type: string, id: string) => Promise } -const SIGNALK_API_PATH= `/signalk/v1/api` -const UUID_PREFIX= 'urn:mrn:signalk:uuid:' - -const API_METHODS= [ - 'setWaypoint', - 'deleteWaypoint', - 'setRoute', - 'deleteRoute', - 'setNote', - 'deleteNote', - 'setRegion', - 'deleteRegion' +const SIGNALK_API_PATH = `/signalk/v1/api` +const UUID_PREFIX = 'urn:mrn:signalk:uuid:' + +const API_METHODS = [ + 'setWaypoint', + 'deleteWaypoint', + 'setRoute', + 'deleteRoute', + 'setNote', + 'deleteNote', + 'setRegion', + 'deleteRegion' ] export class Resources { + resProvider: { [key: string]: ResourceProviderMethods | null } = {} + server: any - // ** in-scope resource types ** - private resourceTypes:Array= [ - 'routes', - 'waypoints', - 'notes', - 'regions', - 'charts' - ] + // ** in-scope resource types ** + private resourceTypes: string[] = [ + 'routes', + 'waypoints', + 'notes', + 'regions', + 'charts' + ] - resProvider: {[key:string]: ResourceProviderMethods | null}= {} - server: any + constructor(app: any) { + this.start(app) + } - constructor(app:any) { - this.start(app) + // ** register resource provider ** + register(pluginId: string, provider: ResourceProvider) { + debug(`** Registering provider(s)....${provider?.types}`) + if (!provider) { + return } - - // ** initialise resourcesApi ** - private start(app:any) { - debug(`** Initialise ${SIGNALK_API_PATH}/resources path handler **`) - this.server= app - this.initResourceRoutes() + if (provider.types && !Array.isArray(provider.types)) { + return } + provider.types.forEach((i: string) => { + if (!this.resProvider[i]) { + provider.methods.pluginId = pluginId + this.resProvider[i] = provider.methods + } + }) + debug(this.resProvider) + } - // ** register resource provider ** - public register(pluginId:string, provider:ResourceProvider) { - debug(`** Registering provider(s)....${provider?.types}`) - if(!provider ) { return } - if(provider.types && !Array.isArray(provider.types)) { return } - provider.types.forEach( (i:string)=>{ - if(!this.resProvider[i]) { - provider.methods.pluginId= pluginId - this.resProvider[i]= provider.methods - } - }) - debug(this.resProvider) + // ** un-register resource provider for the supplied types ** + unRegister(pluginId: string) { + if (!pluginId) { + return + } + debug(`** Un-registering ${pluginId} resource provider(s)....`) + for (const i in this.resProvider) { + if (this.resProvider[i]?.pluginId === pluginId) { + debug(`** Un-registering ${i}....`) + delete this.resProvider[i] + } } + debug(JSON.stringify(this.resProvider)) + } - // ** un-register resource provider for the supplied types ** - public unRegister(pluginId:string) { - if(!pluginId) { return } - debug(`** Un-registering ${pluginId} resource provider(s)....`) - for( let i in this.resProvider ) { - if(this.resProvider[i]?.pluginId===pluginId) { - debug(`** Un-registering ${i}....`) - delete this.resProvider[i] - } + // ** return resource with supplied type and id ** + getResource(type: string, id: string) { + debug(`** getResource(${type}, ${id})`) + return this.actionResourceRequest({ + method: 'GET', + body: {}, + query: {}, + resourceType: type, + resourceId: id + }) + } + + // ** initialise resourcesApi ** + private start(app: any) { + debug(`** Initialise ${SIGNALK_API_PATH}/resources path handler **`) + this.server = app + this.initResourceRoutes() + } + + // ** initialise handler for in-scope resource types ** + private initResourceRoutes() { + this.server.get(`${SIGNALK_API_PATH}/resources`, (req: any, res: any) => { + // list all serviced paths under resources + res.json(this.getResourcePaths()) + }) + this.server.use( + `${SIGNALK_API_PATH}/resources/*`, + async (req: any, res: any, next: any) => { + const result = this.parseResourceRequest(req) + if (result) { + const ar = await this.actionResourceRequest(result) + if (typeof ar.statusCode !== 'undefined') { + debug(`${JSON.stringify(ar)}`) + res.status = ar.statusCode + res.send(ar.message) + } else { + res.json(ar) + } + } else { + debug('** No provider found... calling next()...') + next() } - debug(JSON.stringify(this.resProvider)) - } + } + ) + } - // ** return resource with supplied type and id ** - public getResource(type:string, id:string) { - debug(`** getResource(${type}, ${id})`) - return this.actionResourceRequest({ - method: 'GET', - body: {}, - query: {}, - resourceType: type, - resourceId: id - }) + // ** return all paths serviced under SIGNALK_API_PATH/resources ** + private getResourcePaths(): { [key: string]: any } { + const resPaths: { [key: string]: any } = {} + Object.entries(this.resProvider).forEach((p: any) => { + if (p[1]) { + resPaths[p[0]] = `Path containing ${p[0]}, each named with a UUID` + } + }) + // check for other plugins servicing paths under ./resources + this.server._router.stack.forEach((i: any) => { + if (i.route && i.route.path && typeof i.route.path === 'string') { + if (i.route.path.indexOf(`${SIGNALK_API_PATH}/resources`) !== -1) { + const r = i.route.path.split('/') + if (r.length > 5 && !(r[5] in resPaths)) { + resPaths[ + r[5] + ] = `Path containing ${r[5]} resources (provided by plug-in)` + } + } + } + }) + return resPaths + } + + // ** parse api path request and return ResourceRequest object ** + private parseResourceRequest(req: any): ResourceRequest | undefined { + debug('** req.originalUrl:', req.originalUrl) + debug('** req.method:', req.method) + debug('** req.body:', req.body) + debug('** req.query:', req.query) + debug('** req.params:', req.params) + const p = req.params[0].split('/') + let resType = typeof req.params[0] !== 'undefined' ? p[0] : '' + const resId = p.length > 1 ? p[1] : '' + const resAttrib = p.length > 2 ? p.slice(2) : [] + req.query.resAttrib = resAttrib + debug('** resType:', resType) + debug('** resId:', resId) + debug('** resAttrib:', resAttrib) + debug('** req.params + attribs:', req.query) + + const apiMethod = API_METHODS.includes(resType) ? resType : null + if (apiMethod) { + if (apiMethod.toLowerCase().indexOf('waypoint') !== -1) { + resType = 'waypoints' + } + if (apiMethod.toLowerCase().indexOf('route') !== -1) { + resType = 'routes' + } + if (apiMethod.toLowerCase().indexOf('note') !== -1) { + resType = 'notes' + } + if (apiMethod.toLowerCase().indexOf('region') !== -1) { + resType = 'regions' + } } - // ** initialise handler for in-scope resource types ** - private initResourceRoutes() { - this.server.get(`${SIGNALK_API_PATH}/resources`, (req:any, res:any) => { - // list all serviced paths under resources - res.json(this.getResourcePaths()) - }) - this.server.use(`${SIGNALK_API_PATH}/resources/*`, async (req:any, res:any, next: any) => { - let result= this.parseResourceRequest(req) - if(result) { - let ar= await this.actionResourceRequest(result) - if(typeof ar.statusCode!== 'undefined'){ - debug(`${JSON.stringify(ar)}`) - res.status= ar.statusCode - res.send(ar.message) - } - else { - res.json(ar) - } - } - else { - debug('** No provider found... calling next()...') - next() - } - }) + const retReq = { + method: req.method, + body: req.body, + query: req.query, + resourceType: resType, + resourceId: resId, + apiMethod } - // ** return all paths serviced under SIGNALK_API_PATH/resources ** - private getResourcePaths(): {[key:string]:any} { - let resPaths:{[key:string]:any}= {} - Object.entries(this.resProvider).forEach( (p:any)=> { - if(p[1]) { resPaths[p[0]]= `Path containing ${p[0]}, each named with a UUID` } - }) - // check for other plugins servicing paths under ./resources - this.server._router.stack.forEach((i:any)=> { - if(i.route && i.route.path && typeof i.route.path==='string') { - if(i.route.path.indexOf(`${SIGNALK_API_PATH}/resources`)!==-1) { - let r= i.route.path.split('/') - if( r.length>5 && !(r[5] in resPaths) ) { - resPaths[r[5]]= `Path containing ${r[5]} resources (provided by plug-in)` - } - } - } - }) - return resPaths + if (this.resourceTypes.includes(resType) && this.resProvider[resType]) { + return retReq + } else { + debug('Invalid resource type or no provider for this type!') + return undefined } + } - // ** parse api path request and return ResourceRequest object ** - private parseResourceRequest(req:any):ResourceRequest | undefined { - debug('** req.originalUrl:', req.originalUrl) - debug('** req.method:', req.method) - debug('** req.body:', req.body) - debug('** req.query:', req.query) - debug('** req.params:', req.params) - let p= req.params[0].split('/') - let resType= (typeof req.params[0]!=='undefined') ? p[0] : '' - let resId= p.length>1 ? p[1] : '' - let resAttrib= p.length>2 ? p.slice(2) : [] - req.query.resAttrib= resAttrib - debug('** resType:', resType) - debug('** resId:', resId) - debug('** resAttrib:', resAttrib) - debug('** req.params + attribs:', req.query) - - let apiMethod= (API_METHODS.includes(resType)) ? resType : null - if(apiMethod) { - if(apiMethod.toLowerCase().indexOf('waypoint')!==-1) { - resType= 'waypoints' - } - if(apiMethod.toLowerCase().indexOf('route')!==-1) { - resType= 'routes' - } - if(apiMethod.toLowerCase().indexOf('note')!==-1) { - resType= 'notes' - } - if(apiMethod.toLowerCase().indexOf('region')!==-1) { - resType= 'regions' - } - } + // ** action an in-scope resource request ** + private async actionResourceRequest(req: ResourceRequest): Promise { + debug('********* action request *************') + debug(req) - let retReq= { - method: req.method, - body: req.body, - query: req.query, - resourceType: resType, - resourceId: resId, - apiMethod: apiMethod - } + // check for registered resource providers + if (!this.resProvider) { + return { statusCode: 501, message: `No Provider` } + } - if(this.resourceTypes.includes(resType) && this.resProvider[resType]) { - return retReq - } - else { - debug('Invalid resource type or no provider for this type!') - return undefined - } + if ( + !this.resourceTypes.includes(req.resourceType) || + !this.resProvider[req.resourceType] + ) { + return { statusCode: 501, message: `No Provider` } } - // ** action an in-scope resource request ** - private async actionResourceRequest (req:ResourceRequest):Promise { - debug('********* action request *************') - debug(req) - - // check for registered resource providers - if(!this.resProvider) { - return {statusCode: 501, message: `No Provider`} - } - - if(!this.resourceTypes.includes(req.resourceType) || !this.resProvider[req.resourceType]) { - return {statusCode: 501, message: `No Provider`} - } + // check for API method request + if (req.apiMethod && API_METHODS.includes(req.apiMethod)) { + debug(`API Method (${req.apiMethod})`) + req = this.transformApiRequest(req) + } - // check for API method request - if(req.apiMethod && API_METHODS.includes(req.apiMethod)) { - debug(`API Method (${req.apiMethod})`) - req= this.transformApiRequest(req) - } + return await this.execResourceRequest(req) + } - return await this.execResourceRequest(req) + // ** transform API request to ResourceRequest ** + private transformApiRequest(req: ResourceRequest): ResourceRequest { + if (req.apiMethod?.indexOf('delete') !== -1) { + req.method = 'DELETE' } - - // ** transform API request to ResourceRequest ** - private transformApiRequest(req: ResourceRequest):ResourceRequest { - if(req.apiMethod?.indexOf('delete')!==-1) { - req.method= 'DELETE' - } - if(req.apiMethod?.indexOf('set')!==-1) { - if(!req.body.id) { - req.method= 'POST' - } - else { - req.resourceId= req.body.id - } - req.body= { value: buildResource(req.resourceType, req.body) ?? {} } - } - return req + if (req.apiMethod?.indexOf('set') !== -1) { + if (!req.body.id) { + req.method = 'POST' + } else { + req.resourceId = req.body.id + } + req.body = { value: buildResource(req.resourceType, req.body) ?? {} } } + return req + } - // ** action an in-scope resource request ** - private async execResourceRequest (req:ResourceRequest):Promise { - debug('********* execute request *************') - debug(req) - if(req.method==='GET') { - let retVal: any - if(!req.resourceId) { - retVal= await this.resProvider[req.resourceType]?.listResources(req.resourceType, req.query) - return (retVal) ? - retVal : - {statusCode: 404, message: `Error retrieving resources!` } - } - if(!validate.uuid(req.resourceId)) { - return {statusCode: 406, message: `Invalid resource id provided (${req.resourceId})` } - } - retVal= await this.resProvider[req.resourceType]?.getResource(req.resourceType, req.resourceId) - return (retVal) ? - retVal : - {statusCode: 404, message: `Resource not found (${req.resourceId})!` } + // ** action an in-scope resource request ** + private async execResourceRequest(req: ResourceRequest): Promise { + debug('********* execute request *************') + debug(req) + if (req.method === 'GET') { + let retVal: any + if (!req.resourceId) { + retVal = await this.resProvider[req.resourceType]?.listResources( + req.resourceType, + req.query + ) + return retVal + ? retVal + : { statusCode: 404, message: `Error retrieving resources!` } + } + if (!validate.uuid(req.resourceId)) { + return { + statusCode: 406, + message: `Invalid resource id provided (${req.resourceId})` } + } + retVal = await this.resProvider[req.resourceType]?.getResource( + req.resourceType, + req.resourceId + ) + return retVal + ? retVal + : { + statusCode: 404, + message: `Resource not found (${req.resourceId})!` + } + } - if(req.method==='DELETE' || req.method==='PUT') { - if(!req.resourceId) { - return {statusCode: 406, value: `No resource id provided!` } - } - if(!validate.uuid(req.resourceId)) { - return {statusCode: 406, message: `Invalid resource id provided (${req.resourceId})!` } - } - if( - req.method==='DELETE' || - (req.method==='PUT' && typeof req.body.value!=='undefined' && req.body.value==null) - ) { - let retVal= await this.resProvider[req.resourceType]?.deleteResource(req.resourceType, req.resourceId) - if(retVal){ - this.sendDelta( - this.resProvider[req.resourceType]?.pluginId as string, - req.resourceType, req.resourceId, - null - ) - return {statusCode: 200, message: `Resource (${req.resourceId}) deleted.`} - } - else { - return {statusCode: 400, message: `Error deleting resource (${req.resourceId})!` } - } - } - + if (req.method === 'DELETE' || req.method === 'PUT') { + if (!req.resourceId) { + return { statusCode: 406, value: `No resource id provided!` } + } + if (!validate.uuid(req.resourceId)) { + return { + statusCode: 406, + message: `Invalid resource id provided (${req.resourceId})!` } - - if(req.method==='POST' || req.method==='PUT') { - // check for supplied value - if( typeof req.body.value==='undefined' || req.body.value==null) { - return {statusCode: 406, message: `No resource data supplied!`} - } - // validate supplied request data - if(!validate.resource(req.resourceType, req.body.value)) { - return {statusCode: 406, message: `Invalid resource data supplied!`} - } - if(req.method==='POST') { - let id= UUID_PREFIX + uuidv4() - let retVal= await this.resProvider[req.resourceType]?.setResource(req.resourceType, id, req.body.value) - if(retVal){ - this.sendDelta( - this.resProvider[req.resourceType]?.pluginId as string, - req.resourceType, - id, - req.body.value - ) - return {statusCode: 200, message: `Resource (${id}) saved.`} - } - else { - return {statusCode: 400, message: `Error saving resource (${id})!` } - } - } - if(req.method==='PUT') { - if(!req.resourceId) { - return {statusCode: 406, message: `No resource id provided!` } - } - let retVal= await this.resProvider[req.resourceType]?.setResource(req.resourceType, req.resourceId, req.body.value) - if(retVal){ - this.sendDelta( - this.resProvider[req.resourceType]?.pluginId as string, - req.resourceType, - req.resourceId, - req.body.value - ) - return {statusCode: 200, message: `Resource (${req.resourceId}) updated.`} - } - else { - return {statusCode: 400, message: `Error updating resource (${req.resourceId})!` } - } - } + } + if ( + req.method === 'DELETE' || + (req.method === 'PUT' && + typeof req.body.value !== 'undefined' && + req.body.value == null) + ) { + const retVal = await this.resProvider[req.resourceType]?.deleteResource( + req.resourceType, + req.resourceId + ) + if (retVal) { + this.sendDelta( + this.resProvider[req.resourceType]?.pluginId as string, + req.resourceType, + req.resourceId, + null + ) + return { + statusCode: 200, + message: `Resource (${req.resourceId}) deleted.` + } + } else { + return { + statusCode: 400, + message: `Error deleting resource (${req.resourceId})!` + } } + } } - // ** send delta message with resource PUT, POST, DELETE action result - private sendDelta(providerId:string, type:string, id:string, value:any):void { - debug(`** Sending Delta: resources.${type}.${id}`) - this.server.handleMessage(providerId, { - updates: [ - { - values: [ - { - path: `resources.${type}.${id}`, - value: value - } - ] - } - ] - }) + if (req.method === 'POST' || req.method === 'PUT') { + // check for supplied value + if (typeof req.body.value === 'undefined' || req.body.value == null) { + return { statusCode: 406, message: `No resource data supplied!` } + } + // validate supplied request data + if (!validate.resource(req.resourceType, req.body.value)) { + return { statusCode: 406, message: `Invalid resource data supplied!` } + } + if (req.method === 'POST') { + const id = UUID_PREFIX + uuidv4() + const retVal = await this.resProvider[req.resourceType]?.setResource( + req.resourceType, + id, + req.body.value + ) + if (retVal) { + this.sendDelta( + this.resProvider[req.resourceType]?.pluginId as string, + req.resourceType, + id, + req.body.value + ) + return { statusCode: 200, message: `Resource (${id}) saved.` } + } else { + return { statusCode: 400, message: `Error saving resource (${id})!` } + } + } + if (req.method === 'PUT') { + if (!req.resourceId) { + return { statusCode: 406, message: `No resource id provided!` } + } + const retVal = await this.resProvider[req.resourceType]?.setResource( + req.resourceType, + req.resourceId, + req.body.value + ) + if (retVal) { + this.sendDelta( + this.resProvider[req.resourceType]?.pluginId as string, + req.resourceType, + req.resourceId, + req.body.value + ) + return { + statusCode: 200, + message: `Resource (${req.resourceId}) updated.` + } + } else { + return { + statusCode: 400, + message: `Error updating resource (${req.resourceId})!` + } + } + } } + } +<<<<<<< HEAD <<<<<<< HEAD // ** Get provider methods for supplied resource type. Returns null if none found ** private getResourceProviderFor(resType:string): ResourceProviderMethods | null { @@ -897,4 +937,27 @@ export class Resources { >>>>>>> Add Signal K standard resource path handling ======= >>>>>>> add pluginId to unRegister function +======= + // ** send delta message with resource PUT, POST, DELETE action result + private sendDelta( + providerId: string, + type: string, + id: string, + value: any + ): void { + debug(`** Sending Delta: resources.${type}.${id}`) + this.server.handleMessage(providerId, { + updates: [ + { + values: [ + { + path: `resources.${type}.${id}`, + value + } + ] + } + ] + }) + } +>>>>>>> chore: linted } diff --git a/src/api/resources/resources.ts b/src/api/resources/resources.ts index 77b48e156..ba049990e 100644 --- a/src/api/resources/resources.ts +++ b/src/api/resources/resources.ts @@ -227,178 +227,221 @@ import { getDistance, isValidCoordinate } from 'geolib' import ngeohash from 'ngeohash' // ** build resource item ** -export const buildResource= (resType:string, data:any):any=> { - if(resType==='routes') { return buildRoute(data) } - if(resType==='waypoints') { return buildWaypoint(data) } - if(resType==='notes') { return buildNote(data) } - if(resType==='regions') { return buildRegion(data) } +export const buildResource = (resType: string, data: any): any => { + if (resType === 'routes') { + return buildRoute(data) + } + if (resType === 'waypoints') { + return buildWaypoint(data) + } + if (resType === 'notes') { + return buildNote(data) + } + if (resType === 'regions') { + return buildRegion(data) + } } // ** build route -const buildRoute= (rData:any):any=> { - let rte:any= { - feature: { - type: 'Feature', - geometry:{ - type: 'LineString', - coordinates :[] - }, - properties:{} - } - } - if(typeof rData.name !== 'undefined') { - rte.name= rData.name - rte.feature.properties.name= rData.name - } - if(typeof rData.description !== 'undefined') { - rte.description= rData.description - rte.feature.properties.description= rData.description - } - if(typeof rData.attributes !== 'undefined') { - Object.assign(rte.feature.properties, rData.attributes) +const buildRoute = (rData: any): any => { + const rte: any = { + feature: { + type: 'Feature', + geometry: { + type: 'LineString', + coordinates: [] + }, + properties: {} } + } + if (typeof rData.name !== 'undefined') { + rte.name = rData.name + rte.feature.properties.name = rData.name + } + if (typeof rData.description !== 'undefined') { + rte.description = rData.description + rte.feature.properties.description = rData.description + } + if (typeof rData.attributes !== 'undefined') { + Object.assign(rte.feature.properties, rData.attributes) + } - if(typeof rData.points === 'undefined') { return null } - if(!Array.isArray(rData.points)) { return null } - let isValid:boolean= true; - rData.points.forEach( (p:any)=> { - if(!isValidCoordinate(p)) { isValid= false } - }) - if(!isValid) { return null } - rData.points.forEach( (p:any)=> { - rte.feature.geometry.coordinates.push([p.longitude, p.latitude]) - }) + if (typeof rData.points === 'undefined') { + return null + } + if (!Array.isArray(rData.points)) { + return null + } + let isValid: boolean = true + rData.points.forEach((p: any) => { + if (!isValidCoordinate(p)) { + isValid = false + } + }) + if (!isValid) { + return null + } + rte.feature.geometry.coordinates = rData.points.map((p: any) => { + return [p.longitude, p.latitude] + }) - rte.distance= 0 - for(let i=0; i { - let wpt:any= { - position: { - latitude: 0, - longitude: 0 - }, - feature: { - type: 'Feature', - geometry:{ - type: 'Point', - coordinates :[] - }, - properties:{} - } - } - if(typeof rData.name !== 'undefined') { - wpt.feature.properties.name= rData.name - } - if(typeof rData.description !== 'undefined') { - wpt.feature.properties.description= rData.description - } - if(typeof rData.attributes !== 'undefined') { - Object.assign(wpt.feature.properties, rData.attributes) +const buildWaypoint = (rData: any): any => { + const wpt: any = { + position: { + latitude: 0, + longitude: 0 + }, + feature: { + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [] + }, + properties: {} } + } + if (typeof rData.name !== 'undefined') { + wpt.feature.properties.name = rData.name + } + if (typeof rData.description !== 'undefined') { + wpt.feature.properties.description = rData.description + } + if (typeof rData.attributes !== 'undefined') { + Object.assign(wpt.feature.properties, rData.attributes) + } - if(typeof rData.position === 'undefined') { return null } - if(!isValidCoordinate(rData.position)) { return null } - - wpt.position= rData.position - wpt.feature.geometry.coordinates= [rData.position.longitude, rData.position.latitude] + if (typeof rData.position === 'undefined') { + return null + } + if (!isValidCoordinate(rData.position)) { + return null + } + + wpt.position = rData.position + wpt.feature.geometry.coordinates = [ + rData.position.longitude, + rData.position.latitude + ] - return wpt + return wpt } // ** build note -const buildNote= (rData:any):any=> { - let note:any= {} - if(typeof rData.title !== 'undefined') { - note.title= rData.title - note.feature.properties.title= rData.title - } - if(typeof rData.description !== 'undefined') { - note.description= rData.description - note.feature.properties.description= rData.description - } - if(typeof rData.position === 'undefined' - && typeof rData.region === 'undefined' - && typeof rData.geohash === 'undefined') { return null } +const buildNote = (rData: any): any => { + const note: any = {} + if (typeof rData.title !== 'undefined') { + note.title = rData.title + note.feature.properties.title = rData.title + } + if (typeof rData.description !== 'undefined') { + note.description = rData.description + note.feature.properties.description = rData.description + } + if ( + typeof rData.position === 'undefined' && + typeof rData.region === 'undefined' && + typeof rData.geohash === 'undefined' + ) { + return null + } - if(typeof rData.position !== 'undefined') { - if(!isValidCoordinate(rData.position)) { return null } - note.position= rData.position - } - if(typeof rData.region !== 'undefined') { - note.region= rData.region - } - if(typeof rData.geohash !== 'undefined') { - note.geohash= rData.geohash - } - if(typeof rData.url !== 'undefined') { - note.url= rData.url - } - if(typeof rData.mimeType !== 'undefined') { - note.mimeType= rData.mimeType + if (typeof rData.position !== 'undefined') { + if (!isValidCoordinate(rData.position)) { + return null } - - return note + note.position = rData.position + } + if (typeof rData.region !== 'undefined') { + note.region = rData.region + } + if (typeof rData.geohash !== 'undefined') { + note.geohash = rData.geohash + } + if (typeof rData.url !== 'undefined') { + note.url = rData.url + } + if (typeof rData.mimeType !== 'undefined') { + note.mimeType = rData.mimeType + } + + return note } // ** build region -const buildRegion= (rData:any):any=> { - let reg:any= { - feature: { - type: 'Feature', - geometry:{ - type: 'Polygon', - coordinates :[] - }, - properties:{} - } +const buildRegion = (rData: any): any => { + const reg: any = { + feature: { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [] + }, + properties: {} } - let coords:Array<[number,number]>= [] + } + let coords: Array<[number, number]> = [] - if(typeof rData.name !== 'undefined') { - reg.feature.properties.name= rData.name - } - if(typeof rData.description !== 'undefined') { - reg.feature.properties.description= rData.description - } - if(typeof rData.attributes !== 'undefined') { - Object.assign(reg.feature.properties, rData.attributes) - } + if (typeof rData.name !== 'undefined') { + reg.feature.properties.name = rData.name + } + if (typeof rData.description !== 'undefined') { + reg.feature.properties.description = rData.description + } + if (typeof rData.attributes !== 'undefined') { + Object.assign(reg.feature.properties, rData.attributes) + } - if(typeof rData.points=== 'undefined' && rData.geohash=== 'undefined') { return null } - if(typeof rData.geohash!== 'undefined') { - reg.geohash= rData.geohash - - let bounds= ngeohash.decode_bbox(rData.geohash) - coords= [ - [bounds[1], bounds[0]], - [bounds[3], bounds[0]], - [bounds[3], bounds[2]], - [bounds[1], bounds[2]], - [bounds[1], bounds[0]], - ] - reg.feature.geometry.coordinates.push(coords) + if (typeof rData.points === 'undefined' && rData.geohash === 'undefined') { + return null + } + if (typeof rData.geohash !== 'undefined') { + reg.geohash = rData.geohash + + const bounds = ngeohash.decode_bbox(rData.geohash) + coords = [ + [bounds[1], bounds[0]], + [bounds[3], bounds[0]], + [bounds[3], bounds[2]], + [bounds[1], bounds[2]], + [bounds[1], bounds[0]] + ] + reg.feature.geometry.coordinates.push(coords) + } + if (typeof rData.points !== 'undefined' && coords.length === 0) { + if (!Array.isArray(rData.points)) { + return null } - if(typeof rData.points!== 'undefined' && coords.length===0 ) { - if(!Array.isArray(rData.points)) { return null } - let isValid:boolean= true; - rData.points.forEach( (p:any)=> { - if(!isValidCoordinate(p)) { isValid= false } - }) - if(!isValid) { return null } - rData.points.forEach( (p:any)=> { - coords.push([p.longitude, p.latitude]) - }) - reg.feature.geometry.coordinates.push(coords) + let isValid: boolean = true + rData.points.forEach((p: any) => { + if (!isValidCoordinate(p)) { + isValid = false + } + }) + if (!isValid) { + return null } +<<<<<<< HEAD return reg >>>>>>> add API endpoint processing +======= + coords = rData.points.map((p: any) => { + return [p.longitude, p.latitude] + }) + reg.feature.geometry.coordinates.push(coords) + } + + return reg +>>>>>>> chore: linted } diff --git a/src/api/resources/validate.ts b/src/api/resources/validate.ts index 59560d03c..5a39f139d 100644 --- a/src/api/resources/validate.ts +++ b/src/api/resources/validate.ts @@ -1,5 +1,6 @@ <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD import geoJSON from 'geojson-validation' import { isValidCoordinate } from 'geolib' @@ -147,100 +148,134 @@ const validateChart = (r: any): boolean => { ======= >>>>>>> add API endpoint processing import geoJSON from 'geojson-validation'; +======= +import geoJSON from 'geojson-validation' +>>>>>>> chore: linted import { isValidCoordinate } from 'geolib' -export const validate= { - resource: (type:string, value:any):boolean=> { - if(!type) { return false } - switch(type) { - case 'routes': - return validateRoute(value); - break - case 'waypoints': - return validateWaypoint(value) - break - case 'notes': - return validateNote(value) - break; - case 'regions': - return validateRegion(value) - break - default: - return true - } - }, - - // ** returns true if id is a valid Signal K UUID ** - uuid: (id:string): boolean=> { - let uuid= RegExp("^urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$") - return uuid.test(id) +export const validate = { + resource: (type: string, value: any): boolean => { + if (!type) { + return false } + switch (type) { + case 'routes': + return validateRoute(value) + break + case 'waypoints': + return validateWaypoint(value) + break + case 'notes': + return validateNote(value) + break + case 'regions': + return validateRegion(value) + break + default: + return true + } + }, + + // ** returns true if id is a valid Signal K UUID ** + uuid: (id: string): boolean => { + const uuid = RegExp( + '^urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$' + ) + return uuid.test(id) + } } // ** validate route data -const validateRoute= (r:any):boolean=> { - if(r.start) { - let l= r.start.split('/') - if(!validate.uuid(l[l.length-1])) { return false } +const validateRoute = (r: any): boolean => { + if (r.start) { + const l = r.start.split('/') + if (!validate.uuid(l[l.length - 1])) { + return false } - if(r.end) { - let l= r.end.split('/') - if(!validate.uuid(l[l.length-1])) { return false } + } + if (r.end) { + const l = r.end.split('/') + if (!validate.uuid(l[l.length - 1])) { + return false } - try { - if(!r.feature || !geoJSON.valid(r.feature)) { - return false - } - if(r.feature.geometry.type!=='LineString') { return false } + } + try { + if (!r.feature || !geoJSON.valid(r.feature)) { + return false } - catch(err) { return false } - return true + if (r.feature.geometry.type !== 'LineString') { + return false + } + } catch (err) { + return false + } + return true } // ** validate waypoint data -const validateWaypoint= (r:any):boolean=> { - if(typeof r.position === 'undefined') { return false } - if(!isValidCoordinate(r.position)) { - return false +const validateWaypoint = (r: any): boolean => { + if (typeof r.position === 'undefined') { + return false + } + if (!isValidCoordinate(r.position)) { + return false + } + try { + if (!r.feature || !geoJSON.valid(r.feature)) { + return false } - try { - if(!r.feature || !geoJSON.valid(r.feature)) { - return false - } - if(r.feature.geometry.type!=='Point') { return false } + if (r.feature.geometry.type !== 'Point') { + return false } - catch(e) { return false } - return true + } catch (e) { + return false + } + return true } // ** validate note data -const validateNote= (r:any):boolean=> { - if(!r.region && !r.position && !r.geohash ) { return false } - if(typeof r.position!== 'undefined') { - if(!isValidCoordinate(r.position)) { - return false - } - } - if(r.region) { - let l= r.region.split('/') - if(!validate.uuid(l[l.length-1])) { return false } - } - return true +const validateNote = (r: any): boolean => { + if (!r.region && !r.position && !r.geohash) { + return false + } + if (typeof r.position !== 'undefined') { + if (!isValidCoordinate(r.position)) { + return false + } + } + if (r.region) { + const l = r.region.split('/') + if (!validate.uuid(l[l.length - 1])) { + return false + } + } + return true } // ** validate region data -const validateRegion= (r:any):boolean=> { - if(!r.geohash && !r.feature) { return false } - if(r.feature ) { - try { - if(!geoJSON.valid(r.feature)) { return false } - if(r.feature.geometry.type!=='Polygon' && r.feature.geometry.type!=='MultiPolygon') { - return false - } - } - catch(e) { return false } - } - return true +const validateRegion = (r: any): boolean => { + if (!r.geohash && !r.feature) { + return false + } + if (r.feature) { + try { + if (!geoJSON.valid(r.feature)) { + return false + } + if ( + r.feature.geometry.type !== 'Polygon' && + r.feature.geometry.type !== 'MultiPolygon' + ) { + return false + } + } catch (e) { + return false + } + } + return true } +<<<<<<< HEAD >>>>>>> Add Signal K standard resource path handling +======= +>>>>>>> chore: linted diff --git a/src/put.js b/src/put.js index 607d7bfaa..d875c4f85 100644 --- a/src/put.js +++ b/src/put.js @@ -32,6 +32,7 @@ module.exports = { app.put(apiPathPrefix + '*', function(req, res, next) { // ** ignore resources paths ** +<<<<<<< HEAD <<<<<<< HEAD if (req.path.split('/')[4] === 'resources') { next() @@ -43,6 +44,12 @@ module.exports = { return } >>>>>>> Add Signal K standard resource path handling +======= + if (req.path.split('/')[4] === 'resources') { + next() + return + } +>>>>>>> chore: linted let path = String(req.path).replace(apiPathPrefix, '') const value = req.body From da9693f57a171df93a5ddc2b8d7007ff0b1d8745 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 21 Nov 2021 15:59:33 +1030 Subject: [PATCH 282/410] chore: return value descriptions to show a Promise --- RESOURCE_PROVIDER_PLUGINS.md | 50 ++++++++++++++++++---------------- src/api/resources/resources.ts | 20 +++++++++++--- 2 files changed, 43 insertions(+), 27 deletions(-) diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index 2bab44358..d1ae13b26 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -443,7 +443,7 @@ query= { It is the responsibility of the resource provider plugin to filter the resources returned as per the supplied query parameters. -`listResources()` should return a JSON object listing resources by id. +`listResources()` returns a Promise containing a JSON object listing resources by id. _Example: List all routes._ ```javascript @@ -451,34 +451,34 @@ GET /signalk/v1/api/resources/routes listResources('routes', {}) -returns { +returns Promise<{ "resource_id1": { ... }, "resource_id2": { ... }, ... "resource_idn": { ... } -} +}> ``` _Example: List waypoints within the bounded area._ -```javascript +```typescript GET /signalk/v1/api/resources/waypoints?bbox=5.4,25.7,6.9,31.2 listResources('waypoints', {bbox: '5.4,25.7,6.9,31.2'}) -returns { +returns Promise<{ "resource_id1": { ... }, "resource_id2": { ... } -} +}> ``` ### __Retrieve a specific resource:__ `GET` requests for a specific resource will be dispatched to the `getResource` method passing the resource type and id as parameters. -`getResource()` should returns a JSON object containing the resource data. +`getResource()` returns a Promise containing a JSON object with the resource data. _Example: Retrieve route._ -```javascript +```typescript GET /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a getResource( @@ -488,14 +488,14 @@ getResource( ) ``` -_Returns the result:_ -```json -{ +_Returns a Promise containing the resource data:_ +```typescript +Promise<{ "name": "Name of the route", "description": "Description of the route", "distance": 18345, "feature": { ... } -} +}> ``` A request for a resource attribute will pass the attibute path as an array in the query object with the key `resAttrib`. @@ -510,19 +510,19 @@ getResource( { resAttrib: ['feature','geometry'] } ) ``` -_Returns the value of `geometry` attribute of the waypoint._ -```json -{ +_Returns a Promise containing the value of `geometry` attribute of the waypoint._ +```typescript +Promise<{ "type": "Point", "coordinates": [70.4,6.45] -} +}> ``` ### __Saving Resources:__ `PUT` requests to a path containing the resource id are used to store data associated with the resource id. These will be dispatched to the `setResource` method passing the resource type, id and data as parameters. -`setResource() ` returns `true` on success and `null` on failure. +`setResource() ` returns Promise on success and Promise on failure. _Example: Update / add waypoint with the supplied id._ ```javascript @@ -530,30 +530,30 @@ PUT /signalk/v1/api/resources/waypoints/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25- setResource('waypoints', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) -returns true | null +returns Promise ``` `POST` requests to a resource path that do not contina the resource id will be dispatched to the `setResource` method passing the resource type, an id (generated by the server) and resource data as parameters. -`setResource() ` returns `true` on success and `null` on failure. +`setResource()` returns `true` on success and `null` on failure. _Example: New route record._ -```javascript +```typescript POST /signalk/v1/api/resources/routes {} setResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) -returns true | null +returns Promise ``` ### __Deleting Resources:__ `DELETE` requests to a path containing the resource id will be dispatched to the `deleteResource` method passing the resource type and id as parameters. -`deleteResource()` returns `true` on success, `null` on failure. +`deleteResource()` returns `true` on success and `null` on failure. _Example: Delete region with supplied id._ -```javascript +```typescript DELETE /signalk/v1/api/resources/regions/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a <<<<<<< HEAD @@ -576,8 +576,12 @@ module.exports = function (app) { ======= deleteResource('regions', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a') +<<<<<<< HEAD returns true | null >>>>>>> Add register / unregister +======= +returns Promise +>>>>>>> chore: return value descriptions to show a Promise ``` <<<<<<< HEAD diff --git a/src/api/resources/resources.ts b/src/api/resources/resources.ts index ba049990e..7d8712670 100644 --- a/src/api/resources/resources.ts +++ b/src/api/resources/resources.ts @@ -4,6 +4,7 @@ import { SignalKResourceType } from '@signalk/server-api' import { getDistance, isValidCoordinate } from 'geolib' import ngeohash from 'ngeohash' +<<<<<<< HEAD export const buildResource = (resType: SignalKResourceType, data: any): any => { if (resType === 'routes') { return buildRoute(data) @@ -227,6 +228,9 @@ import { getDistance, isValidCoordinate } from 'geolib' import ngeohash from 'ngeohash' // ** build resource item ** +======= + +>>>>>>> chore: return value descriptions to show a Promise export const buildResource = (resType: string, data: any): any => { if (resType === 'routes') { return buildRoute(data) @@ -242,7 +246,6 @@ export const buildResource = (resType: string, data: any): any => { } } -// ** build route const buildRoute = (rData: any): any => { const rte: any = { feature: { @@ -295,7 +298,7 @@ const buildRoute = (rData: any): any => { return rte } -// ** build waypoint + const buildWaypoint = (rData: any): any => { const wpt: any = { position: { @@ -337,7 +340,7 @@ const buildWaypoint = (rData: any): any => { return wpt } -// ** build note + const buildNote = (rData: any): any => { const note: any = {} if (typeof rData.title !== 'undefined') { @@ -378,7 +381,7 @@ const buildNote = (rData: any): any => { return note } -// ** build region + const buildRegion = (rData: any): any => { const reg: any = { feature: { @@ -431,11 +434,20 @@ const buildRegion = (rData: any): any => { if (!isValid) { return null } +<<<<<<< HEAD <<<<<<< HEAD return reg >>>>>>> add API endpoint processing ======= +======= + if ( + rData.points[0].latitude !== rData.points[rData.points.length-1].latitude && + rData.points[0].longitude !== rData.points[rData.points.length-1].longitude + ) { + rData.points.push( rData.points[0]) + } +>>>>>>> chore: return value descriptions to show a Promise coords = rData.points.map((p: any) => { return [p.longitude, p.latitude] }) From 8b43064c19b3ae93183c4fd09460535c5251f374 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 21 Nov 2021 16:44:07 +1030 Subject: [PATCH 283/410] specify SignalKResourceType --- src/api/resources/index.ts | 46 +++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 876f377ae..699d37445 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -543,8 +543,10 @@ interface ResourceRequest { apiMethod?: string | null } +type SignalKResourceType= 'routes' | 'waypoints' |'notes' |'regions' |'charts' + interface ResourceProvider { - types: string[] + types: SignalKResourceType[] methods: ResourceProviderMethods } @@ -578,8 +580,8 @@ export class Resources { resProvider: { [key: string]: ResourceProviderMethods | null } = {} server: any - // ** in-scope resource types ** - private resourceTypes: string[] = [ + // in-scope resource types + private resourceTypes: SignalKResourceType[] = [ 'routes', 'waypoints', 'notes', @@ -591,7 +593,7 @@ export class Resources { this.start(app) } - // ** register resource provider ** + // register plugin with supplied id as resource provider register(pluginId: string, provider: ResourceProvider) { debug(`** Registering provider(s)....${provider?.types}`) if (!provider) { @@ -609,22 +611,22 @@ export class Resources { debug(this.resProvider) } - // ** un-register resource provider for the supplied types ** + // un-register plugin with supplied id as resource provider unRegister(pluginId: string) { if (!pluginId) { return } debug(`** Un-registering ${pluginId} resource provider(s)....`) - for (const i in this.resProvider) { - if (this.resProvider[i]?.pluginId === pluginId) { - debug(`** Un-registering ${i}....`) - delete this.resProvider[i] + for (const resourceType in this.resProvider) { + if (this.resProvider[resourceType]?.pluginId === pluginId) { + debug(`** Un-registering ${resourceType}....`) + delete this.resProvider[resourceType] } } debug(JSON.stringify(this.resProvider)) } - // ** return resource with supplied type and id ** + // Return resource with supplied type and id getResource(type: string, id: string) { debug(`** getResource(${type}, ${id})`) return this.actionResourceRequest({ @@ -636,17 +638,17 @@ export class Resources { }) } - // ** initialise resourcesApi ** + // initialise resourcesApi private start(app: any) { debug(`** Initialise ${SIGNALK_API_PATH}/resources path handler **`) this.server = app this.initResourceRoutes() } - // ** initialise handler for in-scope resource types ** + // initialise handler for in-scope resource types private initResourceRoutes() { + // list all serviced paths under resources this.server.get(`${SIGNALK_API_PATH}/resources`, (req: any, res: any) => { - // list all serviced paths under resources res.json(this.getResourcePaths()) }) this.server.use( @@ -670,7 +672,7 @@ export class Resources { ) } - // ** return all paths serviced under SIGNALK_API_PATH/resources ** + // return all paths serviced under SIGNALK_API_PATH/resources private getResourcePaths(): { [key: string]: any } { const resPaths: { [key: string]: any } = {} Object.entries(this.resProvider).forEach((p: any) => { @@ -694,7 +696,7 @@ export class Resources { return resPaths } - // ** parse api path request and return ResourceRequest object ** + // parse api path request and return ResourceRequest object private parseResourceRequest(req: any): ResourceRequest | undefined { debug('** req.originalUrl:', req.originalUrl) debug('** req.method:', req.method) @@ -744,7 +746,7 @@ export class Resources { } } - // ** action an in-scope resource request ** + // action an in-scope resource request private async actionResourceRequest(req: ResourceRequest): Promise { debug('********* action request *************') debug(req) @@ -770,7 +772,7 @@ export class Resources { return await this.execResourceRequest(req) } - // ** transform API request to ResourceRequest ** + // transform API request to ResourceRequest private transformApiRequest(req: ResourceRequest): ResourceRequest { if (req.apiMethod?.indexOf('delete') !== -1) { req.method = 'DELETE' @@ -786,7 +788,7 @@ export class Resources { return req } - // ** action an in-scope resource request ** + // action an in-scope resource request private async execResourceRequest(req: ResourceRequest): Promise { debug('********* execute request *************') debug(req) @@ -860,14 +862,14 @@ export class Resources { } if (req.method === 'POST' || req.method === 'PUT') { - // check for supplied value if (typeof req.body.value === 'undefined' || req.body.value == null) { return { statusCode: 406, message: `No resource data supplied!` } } - // validate supplied request data + if (!validate.resource(req.resourceType, req.body.value)) { return { statusCode: 406, message: `Invalid resource data supplied!` } } + if (req.method === 'POST') { const id = UUID_PREFIX + uuidv4() const retVal = await this.resProvider[req.resourceType]?.setResource( @@ -917,6 +919,7 @@ export class Resources { } } +<<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD // ** Get provider methods for supplied resource type. Returns null if none found ** @@ -939,6 +942,9 @@ export class Resources { >>>>>>> add pluginId to unRegister function ======= // ** send delta message with resource PUT, POST, DELETE action result +======= + // Send delta message. Used by resource PUT, POST, DELETE actions +>>>>>>> specify SignalKResourceType private sendDelta( providerId: string, type: string, From 94ee9067217745e512d307677049ab8b6a0c4bf6 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 21 Nov 2021 17:45:53 +1030 Subject: [PATCH 284/410] move interfaces to server-api --- src/api/resources/index.ts | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 699d37445..27230fe69 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -532,20 +532,9 @@ import { validate } from './validate' import { buildResource } from './resources' import { validate } from './validate' -const debug = Debug('signalk:resources') - -interface ResourceRequest { - method: 'GET' | 'PUT' | 'POST' | 'DELETE' - body: any - query: { [key: string]: any } - resourceType: string - resourceId: string - apiMethod?: string | null -} - -type SignalKResourceType= 'routes' | 'waypoints' |'notes' |'regions' |'charts' +/*export type SignalKResourceType= 'routes' | 'waypoints' |'notes' |'regions' |'charts' -interface ResourceProvider { +export interface ResourceProvider { types: SignalKResourceType[] methods: ResourceProviderMethods } @@ -560,6 +549,19 @@ interface ResourceProviderMethods { value: { [key: string]: any } ) => Promise deleteResource: (type: string, id: string) => Promise +}*/ + +import { SignalKResourceType, ResourceProvider, ResourceProviderMethods } from '@signalk/server-api' + +const debug = Debug('signalk:resources') + +interface ResourceRequest { + method: 'GET' | 'PUT' | 'POST' | 'DELETE' + body: any + query: { [key: string]: any } + resourceType: string + resourceId: string + apiMethod?: string | null } const SIGNALK_API_PATH = `/signalk/v1/api` @@ -577,8 +579,8 @@ const API_METHODS = [ ] export class Resources { - resProvider: { [key: string]: ResourceProviderMethods | null } = {} - server: any + private resProvider: { [key: string]: ResourceProviderMethods | null } = {} + private server: any // in-scope resource types private resourceTypes: SignalKResourceType[] = [ @@ -729,7 +731,7 @@ export class Resources { } } - const retReq = { + const retReq:any = { method: req.method, body: req.body, query: req.query, @@ -757,7 +759,7 @@ export class Resources { } if ( - !this.resourceTypes.includes(req.resourceType) || + !this.resourceTypes.includes(req.resourceType as SignalKResourceType) || !this.resProvider[req.resourceType] ) { return { statusCode: 501, message: `No Provider` } From 16e2e6879ba4899c4b411c8e91475a0d6390535a Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 22 Nov 2021 10:29:39 +1030 Subject: [PATCH 285/410] cleanup express route handling --- src/api/resources/index.ts | 143 ++++++++++++++++++++----------------- 1 file changed, 78 insertions(+), 65 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 27230fe69..507016161 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -532,25 +532,6 @@ import { validate } from './validate' import { buildResource } from './resources' import { validate } from './validate' -/*export type SignalKResourceType= 'routes' | 'waypoints' |'notes' |'regions' |'charts' - -export interface ResourceProvider { - types: SignalKResourceType[] - methods: ResourceProviderMethods -} - -interface ResourceProviderMethods { - pluginId: string - listResources: (type: string, query: { [key: string]: any }) => Promise - getResource: (type: string, id: string) => Promise - setResource: ( - type: string, - id: string, - value: { [key: string]: any } - ) => Promise - deleteResource: (type: string, id: string) => Promise -}*/ - import { SignalKResourceType, ResourceProvider, ResourceProviderMethods } from '@signalk/server-api' const debug = Debug('signalk:resources') @@ -559,7 +540,7 @@ interface ResourceRequest { method: 'GET' | 'PUT' | 'POST' | 'DELETE' body: any query: { [key: string]: any } - resourceType: string + resourceType: SignalKResourceType resourceId: string apiMethod?: string | null } @@ -595,7 +576,6 @@ export class Resources { this.start(app) } - // register plugin with supplied id as resource provider register(pluginId: string, provider: ResourceProvider) { debug(`** Registering provider(s)....${provider?.types}`) if (!provider) { @@ -613,7 +593,6 @@ export class Resources { debug(this.resProvider) } - // un-register plugin with supplied id as resource provider unRegister(pluginId: string) { if (!pluginId) { return @@ -628,8 +607,7 @@ export class Resources { debug(JSON.stringify(this.resProvider)) } - // Return resource with supplied type and id - getResource(type: string, id: string) { + getResource(type: SignalKResourceType, id: string) { debug(`** getResource(${type}, ${id})`) return this.actionResourceRequest({ method: 'GET', @@ -640,21 +618,60 @@ export class Resources { }) } - // initialise resourcesApi private start(app: any) { debug(`** Initialise ${SIGNALK_API_PATH}/resources path handler **`) this.server = app this.initResourceRoutes() } - // initialise handler for in-scope resource types private initResourceRoutes() { // list all serviced paths under resources this.server.get(`${SIGNALK_API_PATH}/resources`, (req: any, res: any) => { res.json(this.getResourcePaths()) }) + + this.server.get( + `${SIGNALK_API_PATH}/resources/:resourceType/:resourceId/*`, + async (req: any, res: any, next: any) => { + const result = this.parseResourceRequest(req) + if (result) { + const ar = await this.actionResourceRequest(result) + if (typeof ar.statusCode !== 'undefined') { + debug(`${JSON.stringify(ar)}`) + res.status = ar.statusCode + res.send(ar.message) + } else { + res.json(ar) + } + } else { + debug('** No provider found... calling next()...') + next() + } + } + ) + this.server.use( - `${SIGNALK_API_PATH}/resources/*`, + `${SIGNALK_API_PATH}/resources/:resourceType/:resourceId`, + async (req: any, res: any, next: any) => { + const result = this.parseResourceRequest(req) + if (result) { + const ar = await this.actionResourceRequest(result) + if (typeof ar.statusCode !== 'undefined') { + debug(`${JSON.stringify(ar)}`) + res.status = ar.statusCode + res.send(ar.message) + } else { + res.json(ar) + } + } else { + debug('** No provider found... calling next()...') + next() + } + } + ) + + this.server.use( + `${SIGNALK_API_PATH}/resources/:resourceType`, async (req: any, res: any, next: any) => { const result = this.parseResourceRequest(req) if (result) { @@ -674,7 +691,6 @@ export class Resources { ) } - // return all paths serviced under SIGNALK_API_PATH/resources private getResourcePaths(): { [key: string]: any } { const resPaths: { [key: string]: any } = {} Object.entries(this.resProvider).forEach((p: any) => { @@ -698,57 +714,53 @@ export class Resources { return resPaths } - // parse api path request and return ResourceRequest object private parseResourceRequest(req: any): ResourceRequest | undefined { - debug('** req.originalUrl:', req.originalUrl) + debug('********* parse request *************') debug('** req.method:', req.method) debug('** req.body:', req.body) debug('** req.query:', req.query) debug('** req.params:', req.params) - const p = req.params[0].split('/') - let resType = typeof req.params[0] !== 'undefined' ? p[0] : '' - const resId = p.length > 1 ? p[1] : '' - const resAttrib = p.length > 2 ? p.slice(2) : [] - req.query.resAttrib = resAttrib - debug('** resType:', resType) - debug('** resId:', resId) - debug('** resAttrib:', resAttrib) - debug('** req.params + attribs:', req.query) - - const apiMethod = API_METHODS.includes(resType) ? resType : null - if (apiMethod) { - if (apiMethod.toLowerCase().indexOf('waypoint') !== -1) { - resType = 'waypoints' - } - if (apiMethod.toLowerCase().indexOf('route') !== -1) { - resType = 'routes' - } - if (apiMethod.toLowerCase().indexOf('note') !== -1) { - resType = 'notes' - } - if (apiMethod.toLowerCase().indexOf('region') !== -1) { - resType = 'regions' - } - } - const retReq:any = { + const resReq:any = { method: req.method, body: req.body, query: req.query, - resourceType: resType, - resourceId: resId, - apiMethod + resourceType: req.params.resourceType ?? null, + resourceId: req.params.resourceId ?? null, + apiMethod: API_METHODS.includes(req.params.resourceType) ? req.params.resourceType : null + } + + if (resReq.apiMethod) { + if (resReq.apiMethod.toLowerCase().indexOf('waypoint') !== -1) { + resReq.resourceType = 'waypoints' + } + if (resReq.apiMethod.toLowerCase().indexOf('route') !== -1) { + resReq.resourceType = 'routes' + } + if (resReq.apiMethod.toLowerCase().indexOf('note') !== -1) { + resReq.resourceType = 'notes' + } + if (resReq.apiMethod.toLowerCase().indexOf('region') !== -1) { + resReq.resourceType = 'regions' + } + } else { + const resAttrib = req.params['0'] ? req.params['0'].split('/') : [] + req.query.attrib = resAttrib } - if (this.resourceTypes.includes(resType) && this.resProvider[resType]) { - return retReq + debug('** resReq:', resReq) + + if ( + this.resourceTypes.includes(resReq.resourceType) && + this.resProvider[resReq.resourceType] + ) { + return resReq } else { debug('Invalid resource type or no provider for this type!') return undefined } } - // action an in-scope resource request private async actionResourceRequest(req: ResourceRequest): Promise { debug('********* action request *************') debug(req) @@ -759,7 +771,7 @@ export class Resources { } if ( - !this.resourceTypes.includes(req.resourceType as SignalKResourceType) || + !this.resourceTypes.includes(req.resourceType) || !this.resProvider[req.resourceType] ) { return { statusCode: 501, message: `No Provider` } @@ -774,7 +786,6 @@ export class Resources { return await this.execResourceRequest(req) } - // transform API request to ResourceRequest private transformApiRequest(req: ResourceRequest): ResourceRequest { if (req.apiMethod?.indexOf('delete') !== -1) { req.method = 'DELETE' @@ -790,7 +801,6 @@ export class Resources { return req } - // action an in-scope resource request private async execResourceRequest(req: ResourceRequest): Promise { debug('********* execute request *************') debug(req) @@ -923,6 +933,7 @@ export class Resources { <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD // ** Get provider methods for supplied resource type. Returns null if none found ** private getResourceProviderFor(resType:string): ResourceProviderMethods | null { @@ -947,6 +958,8 @@ export class Resources { ======= // Send delta message. Used by resource PUT, POST, DELETE actions >>>>>>> specify SignalKResourceType +======= +>>>>>>> cleanup express route handling private sendDelta( providerId: string, type: string, From 142f2bc2a4719bb4a195fda5ac939fa7dd911c94 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 22 Nov 2021 10:33:00 +1030 Subject: [PATCH 286/410] add ResourceProvider types to server-api --- packages/server-api/src/index.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/server-api/src/index.ts b/packages/server-api/src/index.ts index 3f3b45925..f6b1808dc 100644 --- a/packages/server-api/src/index.ts +++ b/packages/server-api/src/index.ts @@ -5,10 +5,16 @@ export { PropertyValue, PropertyValues, PropertyValuesCallback } from './propert export type SignalKResourceType= 'routes' | 'waypoints' |'notes' |'regions' |'charts' +<<<<<<< HEAD export type ResourceTypes= SignalKResourceType[] | string[] export interface ResourceProviderMethods { pluginId?: string +======= + +export interface ResourceProviderMethods { + pluginId: string +>>>>>>> add ResourceProvider types to server-api listResources: (type: string, query: { [key: string]: any }) => Promise getResource: (type: string, id: string) => Promise setResource: ( @@ -20,7 +26,11 @@ export interface ResourceProviderMethods { } export interface ResourceProvider { +<<<<<<< HEAD types: ResourceTypes +======= + types: SignalKResourceType[] +>>>>>>> add ResourceProvider types to server-api methods: ResourceProviderMethods } From 55ae9e303f689a1c4fd008da8a20a63c143dac1f Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 21 Nov 2021 10:21:25 +0200 Subject: [PATCH 287/410] refactor: use Express types --- src/api/resources/index.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 507016161..8e9edc1e4 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -533,6 +533,7 @@ import { buildResource } from './resources' import { validate } from './validate' import { SignalKResourceType, ResourceProvider, ResourceProviderMethods } from '@signalk/server-api' +import { Application, Handler, NextFunction, Request, Response } from 'express' const debug = Debug('signalk:resources') @@ -559,9 +560,13 @@ const API_METHODS = [ 'deleteRegion' ] +// FIXME use types from https://github.com/SignalK/signalk-server/pull/1358 +interface ResourceApplication extends Application { + handleMessage: any +} export class Resources { private resProvider: { [key: string]: ResourceProviderMethods | null } = {} - private server: any + private server: ResourceApplication // in-scope resource types private resourceTypes: SignalKResourceType[] = [ @@ -572,7 +577,8 @@ export class Resources { 'charts' ] - constructor(app: any) { + constructor(app: ResourceApplication) { + this.server = app this.start(app) } @@ -626,7 +632,7 @@ export class Resources { private initResourceRoutes() { // list all serviced paths under resources - this.server.get(`${SIGNALK_API_PATH}/resources`, (req: any, res: any) => { + this.server.get(`${SIGNALK_API_PATH}/resources`, (req: Request, res: Response) => { res.json(this.getResourcePaths()) }) From 273cbc4f9dad151884d1d09c555cbb7cb21fb288 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 22 Nov 2021 15:16:48 +1030 Subject: [PATCH 288/410] fix type --- src/api/resources/resources.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/api/resources/resources.ts b/src/api/resources/resources.ts index 7d8712670..b2ade50db 100644 --- a/src/api/resources/resources.ts +++ b/src/api/resources/resources.ts @@ -1,5 +1,8 @@ <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> fix type import { SignalKResourceType } from '@signalk/server-api' import { getDistance, isValidCoordinate } from 'geolib' import ngeohash from 'ngeohash' @@ -230,8 +233,12 @@ import ngeohash from 'ngeohash' // ** build resource item ** ======= +<<<<<<< HEAD >>>>>>> chore: return value descriptions to show a Promise export const buildResource = (resType: string, data: any): any => { +======= +export const buildResource = (resType: SignalKResourceType, data: any): any => { +>>>>>>> fix type if (resType === 'routes') { return buildRoute(data) } From a359bf4408f6b5e18d2395710dab77ca4783c95b Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 22 Nov 2021 16:18:02 +1030 Subject: [PATCH 289/410] chore: lint --- src/api/resources/resources.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/api/resources/resources.ts b/src/api/resources/resources.ts index b2ade50db..71efc2106 100644 --- a/src/api/resources/resources.ts +++ b/src/api/resources/resources.ts @@ -7,6 +7,7 @@ import { SignalKResourceType } from '@signalk/server-api' import { getDistance, isValidCoordinate } from 'geolib' import ngeohash from 'ngeohash' +<<<<<<< HEAD <<<<<<< HEAD export const buildResource = (resType: SignalKResourceType, data: any): any => { if (resType === 'routes') { @@ -237,6 +238,8 @@ import ngeohash from 'ngeohash' >>>>>>> chore: return value descriptions to show a Promise export const buildResource = (resType: string, data: any): any => { ======= +======= +>>>>>>> chore: lint export const buildResource = (resType: SignalKResourceType, data: any): any => { >>>>>>> fix type if (resType === 'routes') { @@ -305,7 +308,6 @@ const buildRoute = (rData: any): any => { return rte } - const buildWaypoint = (rData: any): any => { const wpt: any = { position: { @@ -347,7 +349,6 @@ const buildWaypoint = (rData: any): any => { return wpt } - const buildNote = (rData: any): any => { const note: any = {} if (typeof rData.title !== 'undefined') { @@ -388,7 +389,6 @@ const buildNote = (rData: any): any => { return note } - const buildRegion = (rData: any): any => { const reg: any = { feature: { @@ -449,10 +449,12 @@ const buildRegion = (rData: any): any => { ======= ======= if ( - rData.points[0].latitude !== rData.points[rData.points.length-1].latitude && - rData.points[0].longitude !== rData.points[rData.points.length-1].longitude + rData.points[0].latitude !== + rData.points[rData.points.length - 1].latitude && + rData.points[0].longitude !== + rData.points[rData.points.length - 1].longitude ) { - rData.points.push( rData.points[0]) + rData.points.push(rData.points[0]) } >>>>>>> chore: return value descriptions to show a Promise coords = rData.points.map((p: any) => { From 7a7e406ca99ae5184896bd6b58b770add22f9a02 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 22 Nov 2021 17:04:43 +1030 Subject: [PATCH 290/410] chore: update return type --- SERVERPLUGINS.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/SERVERPLUGINS.md b/SERVERPLUGINS.md index 27c551352..e6d8f098d 100644 --- a/SERVERPLUGINS.md +++ b/SERVERPLUGINS.md @@ -766,6 +766,9 @@ When a resource provider plugin is disabled it will need to un-register its prov Retrieve resource data for the supplied resource type and id. +This method invokes the `registered Resource Provider` for the supplied `resource_type` and returns a __resovled Promise__ containing the route resource data if successful or +a __rejected Promise__ containing an Error object if unsuccessful. + data for the full path of the directory where the plugin can persist its internal data, like data files.If a plugin wants to act as a resource provider, it will need to register its provider methods during startup using this function. @@ -773,9 +776,7 @@ Retrieve resource data for the supplied resource type and id. ```javascript let myRoute= app.resourcesApi.getResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a'); ``` -Will return the route resource data or `null` if a route with the supplied id cannot be found. - -_Example:_ +_Returns resolved Promise containing:_ ```json { "name": "Name of the route", From fbadba522990a4f4908bd76eac5e8adb2a6cf49d Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 22 Nov 2021 17:05:39 +1030 Subject: [PATCH 291/410] Use Express routing params for processing requests --- src/api/resources/index.ts | 536 ++++++++++++++++++------------------- 1 file changed, 267 insertions(+), 269 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 8e9edc1e4..f74329a20 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -532,7 +532,11 @@ import { validate } from './validate' import { buildResource } from './resources' import { validate } from './validate' -import { SignalKResourceType, ResourceProvider, ResourceProviderMethods } from '@signalk/server-api' +import { + ResourceProvider, + ResourceProviderMethods, + SignalKResourceType +} from '@signalk/server-api' import { Application, Handler, NextFunction, Request, Response } from 'express' const debug = Debug('signalk:resources') @@ -562,7 +566,7 @@ const API_METHODS = [ // FIXME use types from https://github.com/SignalK/signalk-server/pull/1358 interface ResourceApplication extends Application { - handleMessage: any + handleMessage: (id: string, data: any) => void } export class Resources { private resProvider: { [key: string]: ResourceProviderMethods | null } = {} @@ -613,15 +617,12 @@ export class Resources { debug(JSON.stringify(this.resProvider)) } - getResource(type: SignalKResourceType, id: string) { - debug(`** getResource(${type}, ${id})`) - return this.actionResourceRequest({ - method: 'GET', - body: {}, - query: {}, - resourceType: type, - resourceId: id - }) + getResource(resType: SignalKResourceType, resId: string) { + debug(`** getResource(${resType}, ${resId})`) + if (!this.checkForProvider(resType)) { + return Promise.reject(new Error(`No provider for ${resType}`)) + } + return this.resProvider[resType]?.getResource(resType, resId) } private start(app: any) { @@ -632,66 +633,265 @@ export class Resources { private initResourceRoutes() { // list all serviced paths under resources - this.server.get(`${SIGNALK_API_PATH}/resources`, (req: Request, res: Response) => { - res.json(this.getResourcePaths()) - }) + this.server.get( + `${SIGNALK_API_PATH}/resources`, + (req: Request, res: Response) => { + res.json(this.getResourcePaths()) + } + ) + // facilitate retrieval of a specific resource this.server.get( - `${SIGNALK_API_PATH}/resources/:resourceType/:resourceId/*`, - async (req: any, res: any, next: any) => { - const result = this.parseResourceRequest(req) - if (result) { - const ar = await this.actionResourceRequest(result) - if (typeof ar.statusCode !== 'undefined') { - debug(`${JSON.stringify(ar)}`) - res.status = ar.statusCode - res.send(ar.message) - } else { - res.json(ar) - } + `${SIGNALK_API_PATH}/resources/:resourceType/:resourceId`, + async (req: Request, res: Response, next: NextFunction) => { + debug(`** GET ${SIGNALK_API_PATH}/resources/:resourceType/:resourceId`) + if ( + !this.checkForProvider(req.params.resourceType as SignalKResourceType) + ) { + debug('** No provider found... calling next()...') + next() + return + } + if (!validate.uuid(req.params.resourceId)) { + res + .status(406) + .send(`Invalid resource id provided (${req.params.resourceId})`) + return + } + const retVal = await this.resProvider[ + req.params.resourceType + ]?.getResource(req.params.resourceType, req.params.resourceId) + if (retVal) { + res.json(retVal) } else { + res.status(404).send(`Resource not found! (${req.params.resourceId})`) + } + } + ) + + // facilitate retrieval of a collection of resource entries + this.server.get( + `${SIGNALK_API_PATH}/resources/:resourceType`, + async (req: Request, res: Response, next: NextFunction) => { + debug(`** GET ${SIGNALK_API_PATH}/resources/:resourceType`) + if ( + !this.checkForProvider(req.params.resourceType as SignalKResourceType) + ) { debug('** No provider found... calling next()...') next() + return + } + const retVal = await this.resProvider[ + req.params.resourceType + ]?.listResources(req.params.resourceType, req.query) + if (retVal) { + res.json(retVal) + } else { + res.status(404).send(`Error retrieving resources!`) } } ) - this.server.use( + // facilitate creation of new resource entry of supplied type + this.server.post( + `${SIGNALK_API_PATH}/resources/:resourceType`, + async (req: Request, res: Response, next: NextFunction) => { + debug(`** POST ${SIGNALK_API_PATH}/resources/:resourceType`) + if ( + !this.checkForProvider(req.params.resourceType as SignalKResourceType) + ) { + debug('** No provider found... calling next()...') + next() + return + } + if (!validate.resource(req.params.resourceType, req.body.value)) { + res.status(406).send(`Invalid resource data supplied!`) + return + } + const id = UUID_PREFIX + uuidv4() + const retVal = await this.resProvider[ + req.params.resourceType + ]?.setResource(req.params.resourceType, id, req.body.value) + if (retVal) { + this.server.handleMessage( + this.resProvider[req.params.resourceType]?.pluginId as string, + this.buildDeltaMsg( + req.params.resourceType as SignalKResourceType, + id, + req.body.value + ) + ) + res + .status(200) + .send(`New ${req.params.resourceType} resource (${id}) saved.`) + } else { + res + .status(404) + .send(`Error saving ${req.params.resourceType} resource (${id})!`) + } + } + ) + + // facilitate creation / update of resource entry at supplied id + this.server.put( `${SIGNALK_API_PATH}/resources/:resourceType/:resourceId`, - async (req: any, res: any, next: any) => { - const result = this.parseResourceRequest(req) - if (result) { - const ar = await this.actionResourceRequest(result) - if (typeof ar.statusCode !== 'undefined') { - debug(`${JSON.stringify(ar)}`) - res.status = ar.statusCode - res.send(ar.message) - } else { - res.json(ar) - } + async (req: Request, res: Response, next: NextFunction) => { + debug(`** PUT ${SIGNALK_API_PATH}/resources/:resourceType/:resourceId`) + if ( + !this.checkForProvider(req.params.resourceType as SignalKResourceType) + ) { + debug('** No provider found... calling next()...') + next() + return + } + if (!validate.resource(req.params.resourceType, req.body.value)) { + res.status(406).send(`Invalid resource data supplied!`) + return + } + const retVal = await this.resProvider[ + req.params.resourceType + ]?.setResource( + req.params.resourceType, + req.params.resourceId, + req.body.value + ) + if (retVal) { + this.server.handleMessage( + this.resProvider[req.params.resourceType]?.pluginId as string, + this.buildDeltaMsg( + req.params.resourceType as SignalKResourceType, + req.params.resourceId, + req.body.value + ) + ) + res + .status(200) + .send( + `${req.params.resourceType} resource (${req.params.resourceId}) saved.` + ) } else { + res + .status(404) + .send( + `Error saving ${req.params.resourceType} resource (${req.params.resourceId})!` + ) + } + } + ) + + // facilitate deletion of specific of resource entry at supplied id + this.server.delete( + `${SIGNALK_API_PATH}/resources/:resourceType/:resourceId`, + async (req: Request, res: Response, next: NextFunction) => { + debug( + `** DELETE ${SIGNALK_API_PATH}/resources/:resourceType/:resourceId` + ) + if ( + !this.checkForProvider(req.params.resourceType as SignalKResourceType) + ) { debug('** No provider found... calling next()...') next() + return + } + if (!validate.uuid(req.params.resourceId)) { + res + .status(406) + .send(`Invalid resource id provided (${req.params.resourceId})`) + return + } + const retVal = await this.resProvider[ + req.params.resourceType + ]?.deleteResource(req.params.resourceType, req.params.resourceId) + if (retVal) { + this.server.handleMessage( + this.resProvider[req.params.resourceType]?.pluginId as string, + this.buildDeltaMsg( + req.params.resourceType as SignalKResourceType, + req.params.resourceId, + null + ) + ) + res.status(200).send(`Resource (${req.params.resourceId}) deleted.`) + } else { + res + .status(400) + .send(`Error deleting resource (${req.params.resourceId})!`) } } ) - this.server.use( - `${SIGNALK_API_PATH}/resources/:resourceType`, - async (req: any, res: any, next: any) => { - const result = this.parseResourceRequest(req) - if (result) { - const ar = await this.actionResourceRequest(result) - if (typeof ar.statusCode !== 'undefined') { - debug(`${JSON.stringify(ar)}`) - res.status = ar.statusCode - res.send(ar.message) + // facilitate API requests + this.server.put( + `${SIGNALK_API_PATH}/resources/:apiFunction`, + async (req: Request, res: Response, next: NextFunction) => { + debug(`** PUT ${SIGNALK_API_PATH}/resources/:apiFunction`) + + // check for valid API method request + if (!API_METHODS.includes(req.params.apiFunction)) { + res.status(501).send(`Invalid API method ${req.params.apiFunction}!`) + return + } + let resType: SignalKResourceType = 'waypoints' + if (req.params.apiFunction.toLowerCase().indexOf('waypoint') !== -1) { + resType = 'waypoints' + } + if (req.params.apiFunction.toLowerCase().indexOf('route') !== -1) { + resType = 'routes' + } + if (req.params.apiFunction.toLowerCase().indexOf('note') !== -1) { + resType = 'notes' + } + if (req.params.apiFunction.toLowerCase().indexOf('region') !== -1) { + resType = 'regions' + } + if (!this.checkForProvider(resType)) { + res.status(501).send(`No provider for ${resType}!`) + return + } + let resId: string = '' + let resValue: any = null + + if (req.params.apiFunction.toLowerCase().indexOf('set') !== -1) { + resValue = buildResource(resType, req.body) + if (!resValue) { + res.status(406).send(`Invalid resource data supplied!`) + return + } + if (!req.body.id) { + resId = UUID_PREFIX + uuidv4() } else { - res.json(ar) + if (!validate.uuid(req.body.id)) { + res.status(406).send(`Invalid resource id supplied!`) + return + } + resId = req.body.id + } + } + if (req.params.apiFunction.toLowerCase().indexOf('delete') !== -1) { + resValue = null + if (!req.body.id) { + res.status(406).send(`No resource id supplied!`) + return } + if (!validate.uuid(req.body.id)) { + res.status(406).send(`Invalid resource id supplied!`) + return + } + resId = req.body.id + } + const retVal = await this.resProvider[resType]?.setResource( + resType, + resId, + resValue + ) + if (retVal) { + this.server.handleMessage( + this.resProvider[resType]?.pluginId as string, + this.buildDeltaMsg(resType, resId, resValue) + ) + res.status(200).send(`SUCCESS: ${req.params.apiFunction} complete.`) } else { - debug('** No provider found... calling next()...') - next() + res.status(404).send(`ERROR: ${req.params.apiFunction} incomplete!`) } } ) @@ -720,226 +920,16 @@ export class Resources { return resPaths } - private parseResourceRequest(req: any): ResourceRequest | undefined { - debug('********* parse request *************') - debug('** req.method:', req.method) - debug('** req.body:', req.body) - debug('** req.query:', req.query) - debug('** req.params:', req.params) - - const resReq:any = { - method: req.method, - body: req.body, - query: req.query, - resourceType: req.params.resourceType ?? null, - resourceId: req.params.resourceId ?? null, - apiMethod: API_METHODS.includes(req.params.resourceType) ? req.params.resourceType : null - } - - if (resReq.apiMethod) { - if (resReq.apiMethod.toLowerCase().indexOf('waypoint') !== -1) { - resReq.resourceType = 'waypoints' - } - if (resReq.apiMethod.toLowerCase().indexOf('route') !== -1) { - resReq.resourceType = 'routes' - } - if (resReq.apiMethod.toLowerCase().indexOf('note') !== -1) { - resReq.resourceType = 'notes' - } - if (resReq.apiMethod.toLowerCase().indexOf('region') !== -1) { - resReq.resourceType = 'regions' - } - } else { - const resAttrib = req.params['0'] ? req.params['0'].split('/') : [] - req.query.attrib = resAttrib - } - - debug('** resReq:', resReq) - - if ( - this.resourceTypes.includes(resReq.resourceType) && - this.resProvider[resReq.resourceType] - ) { - return resReq - } else { - debug('Invalid resource type or no provider for this type!') - return undefined - } - } - - private async actionResourceRequest(req: ResourceRequest): Promise { - debug('********* action request *************') - debug(req) - - // check for registered resource providers - if (!this.resProvider) { - return { statusCode: 501, message: `No Provider` } - } - - if ( - !this.resourceTypes.includes(req.resourceType) || - !this.resProvider[req.resourceType] - ) { - return { statusCode: 501, message: `No Provider` } - } - - // check for API method request - if (req.apiMethod && API_METHODS.includes(req.apiMethod)) { - debug(`API Method (${req.apiMethod})`) - req = this.transformApiRequest(req) - } - - return await this.execResourceRequest(req) - } - - private transformApiRequest(req: ResourceRequest): ResourceRequest { - if (req.apiMethod?.indexOf('delete') !== -1) { - req.method = 'DELETE' - } - if (req.apiMethod?.indexOf('set') !== -1) { - if (!req.body.id) { - req.method = 'POST' - } else { - req.resourceId = req.body.id - } - req.body = { value: buildResource(req.resourceType, req.body) ?? {} } - } - return req - } - - private async execResourceRequest(req: ResourceRequest): Promise { - debug('********* execute request *************') - debug(req) - if (req.method === 'GET') { - let retVal: any - if (!req.resourceId) { - retVal = await this.resProvider[req.resourceType]?.listResources( - req.resourceType, - req.query - ) - return retVal - ? retVal - : { statusCode: 404, message: `Error retrieving resources!` } - } - if (!validate.uuid(req.resourceId)) { - return { - statusCode: 406, - message: `Invalid resource id provided (${req.resourceId})` - } - } - retVal = await this.resProvider[req.resourceType]?.getResource( - req.resourceType, - req.resourceId - ) - return retVal - ? retVal - : { - statusCode: 404, - message: `Resource not found (${req.resourceId})!` - } - } - - if (req.method === 'DELETE' || req.method === 'PUT') { - if (!req.resourceId) { - return { statusCode: 406, value: `No resource id provided!` } - } - if (!validate.uuid(req.resourceId)) { - return { - statusCode: 406, - message: `Invalid resource id provided (${req.resourceId})!` - } - } - if ( - req.method === 'DELETE' || - (req.method === 'PUT' && - typeof req.body.value !== 'undefined' && - req.body.value == null) - ) { - const retVal = await this.resProvider[req.resourceType]?.deleteResource( - req.resourceType, - req.resourceId - ) - if (retVal) { - this.sendDelta( - this.resProvider[req.resourceType]?.pluginId as string, - req.resourceType, - req.resourceId, - null - ) - return { - statusCode: 200, - message: `Resource (${req.resourceId}) deleted.` - } - } else { - return { - statusCode: 400, - message: `Error deleting resource (${req.resourceId})!` - } - } - } - } - - if (req.method === 'POST' || req.method === 'PUT') { - if (typeof req.body.value === 'undefined' || req.body.value == null) { - return { statusCode: 406, message: `No resource data supplied!` } - } - - if (!validate.resource(req.resourceType, req.body.value)) { - return { statusCode: 406, message: `Invalid resource data supplied!` } - } - - if (req.method === 'POST') { - const id = UUID_PREFIX + uuidv4() - const retVal = await this.resProvider[req.resourceType]?.setResource( - req.resourceType, - id, - req.body.value - ) - if (retVal) { - this.sendDelta( - this.resProvider[req.resourceType]?.pluginId as string, - req.resourceType, - id, - req.body.value - ) - return { statusCode: 200, message: `Resource (${id}) saved.` } - } else { - return { statusCode: 400, message: `Error saving resource (${id})!` } - } - } - if (req.method === 'PUT') { - if (!req.resourceId) { - return { statusCode: 406, message: `No resource id provided!` } - } - const retVal = await this.resProvider[req.resourceType]?.setResource( - req.resourceType, - req.resourceId, - req.body.value - ) - if (retVal) { - this.sendDelta( - this.resProvider[req.resourceType]?.pluginId as string, - req.resourceType, - req.resourceId, - req.body.value - ) - return { - statusCode: 200, - message: `Resource (${req.resourceId}) updated.` - } - } else { - return { - statusCode: 400, - message: `Error updating resource (${req.resourceId})!` - } - } - } - } + private checkForProvider(resType: SignalKResourceType): boolean { + return this.resourceTypes.includes(resType) && this.resProvider[resType] + ? true + : false } <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD // ** Get provider methods for supplied resource type. Returns null if none found ** private getResourceProviderFor(resType:string): ResourceProviderMethods | null { @@ -974,17 +964,25 @@ export class Resources { ): void { debug(`** Sending Delta: resources.${type}.${id}`) this.server.handleMessage(providerId, { +======= + private buildDeltaMsg( + resType: SignalKResourceType, + resid: string, + resValue: any + ): any { + return { +>>>>>>> Use Express routing params for processing requests updates: [ { values: [ { - path: `resources.${type}.${id}`, - value + path: `resources.${resType}.${resid}`, + value: resValue } ] } ] - }) + } } >>>>>>> chore: linted } From fb6b12c98b5d926cd18a42bd66593c9a5ba7455f Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 24 Nov 2021 09:13:43 +1030 Subject: [PATCH 292/410] throw on error --- src/api/resources/index.ts | 94 ++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 49 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index f74329a20..014e8dfde 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -541,15 +541,6 @@ import { Application, Handler, NextFunction, Request, Response } from 'express' const debug = Debug('signalk:resources') -interface ResourceRequest { - method: 'GET' | 'PUT' | 'POST' | 'DELETE' - body: any - query: { [key: string]: any } - resourceType: SignalKResourceType - resourceId: string - apiMethod?: string | null -} - const SIGNALK_API_PATH = `/signalk/v1/api` const UUID_PREFIX = 'urn:mrn:signalk:uuid:' @@ -564,10 +555,10 @@ const API_METHODS = [ 'deleteRegion' ] -// FIXME use types from https://github.com/SignalK/signalk-server/pull/1358 interface ResourceApplication extends Application { handleMessage: (id: string, data: any) => void } + export class Resources { private resProvider: { [key: string]: ResourceProviderMethods | null } = {} private server: ResourceApplication @@ -658,14 +649,15 @@ export class Resources { .send(`Invalid resource id provided (${req.params.resourceId})`) return } - const retVal = await this.resProvider[ - req.params.resourceType - ]?.getResource(req.params.resourceType, req.params.resourceId) - if (retVal) { - res.json(retVal) - } else { + try { + const retVal = await this.resProvider[ + req.params.resourceType + ]?.getResource(req.params.resourceType, req.params.resourceId) + res.json(retVal) + } catch (err) { res.status(404).send(`Resource not found! (${req.params.resourceId})`) } + } ) @@ -681,12 +673,12 @@ export class Resources { next() return } - const retVal = await this.resProvider[ - req.params.resourceType - ]?.listResources(req.params.resourceType, req.query) - if (retVal) { - res.json(retVal) - } else { + try { + const retVal = await this.resProvider[ + req.params.resourceType + ]?.listResources(req.params.resourceType, req.query) + res.json(retVal) + } catch (err) { res.status(404).send(`Error retrieving resources!`) } } @@ -709,10 +701,11 @@ export class Resources { return } const id = UUID_PREFIX + uuidv4() - const retVal = await this.resProvider[ - req.params.resourceType - ]?.setResource(req.params.resourceType, id, req.body.value) - if (retVal) { + try { + const retVal = await this.resProvider[ + req.params.resourceType + ]?.setResource(req.params.resourceType, id, req.body.value) + this.server.handleMessage( this.resProvider[req.params.resourceType]?.pluginId as string, this.buildDeltaMsg( @@ -724,11 +717,11 @@ export class Resources { res .status(200) .send(`New ${req.params.resourceType} resource (${id}) saved.`) - } else { + } catch (err) { res .status(404) .send(`Error saving ${req.params.resourceType} resource (${id})!`) - } + } } ) @@ -748,14 +741,15 @@ export class Resources { res.status(406).send(`Invalid resource data supplied!`) return } - const retVal = await this.resProvider[ - req.params.resourceType - ]?.setResource( - req.params.resourceType, - req.params.resourceId, - req.body.value - ) - if (retVal) { + try { + const retVal = await this.resProvider[ + req.params.resourceType + ]?.setResource( + req.params.resourceType, + req.params.resourceId, + req.body.value + ) + this.server.handleMessage( this.resProvider[req.params.resourceType]?.pluginId as string, this.buildDeltaMsg( @@ -769,7 +763,7 @@ export class Resources { .send( `${req.params.resourceType} resource (${req.params.resourceId}) saved.` ) - } else { + } catch (err) { res .status(404) .send( @@ -799,10 +793,11 @@ export class Resources { .send(`Invalid resource id provided (${req.params.resourceId})`) return } - const retVal = await this.resProvider[ - req.params.resourceType - ]?.deleteResource(req.params.resourceType, req.params.resourceId) - if (retVal) { + try { + const retVal = await this.resProvider[ + req.params.resourceType + ]?.deleteResource(req.params.resourceType, req.params.resourceId) + this.server.handleMessage( this.resProvider[req.params.resourceType]?.pluginId as string, this.buildDeltaMsg( @@ -812,7 +807,7 @@ export class Resources { ) ) res.status(200).send(`Resource (${req.params.resourceId}) deleted.`) - } else { + } catch (err) { res .status(400) .send(`Error deleting resource (${req.params.resourceId})!`) @@ -879,18 +874,19 @@ export class Resources { } resId = req.body.id } - const retVal = await this.resProvider[resType]?.setResource( - resType, - resId, - resValue - ) - if (retVal) { + + try { + const retVal = await this.resProvider[resType]?.setResource( + resType, + resId, + resValue + ) this.server.handleMessage( this.resProvider[resType]?.pluginId as string, this.buildDeltaMsg(resType, resId, resValue) ) res.status(200).send(`SUCCESS: ${req.params.apiFunction} complete.`) - } else { + } catch (err) { res.status(404).send(`ERROR: ${req.params.apiFunction} incomplete!`) } } From 7e71f862b73ab53de919f6c212a3b13224219a09 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 24 Nov 2021 09:34:21 +1030 Subject: [PATCH 293/410] PUT/POST payloads directly in `body`..not `value:` --- src/api/resources/index.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 014e8dfde..f12c46c59 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -696,7 +696,7 @@ export class Resources { next() return } - if (!validate.resource(req.params.resourceType, req.body.value)) { + if (!validate.resource(req.params.resourceType, req.body)) { res.status(406).send(`Invalid resource data supplied!`) return } @@ -704,14 +704,14 @@ export class Resources { try { const retVal = await this.resProvider[ req.params.resourceType - ]?.setResource(req.params.resourceType, id, req.body.value) + ]?.setResource(req.params.resourceType, id, req.body) this.server.handleMessage( this.resProvider[req.params.resourceType]?.pluginId as string, this.buildDeltaMsg( req.params.resourceType as SignalKResourceType, id, - req.body.value + req.body ) ) res @@ -737,7 +737,7 @@ export class Resources { next() return } - if (!validate.resource(req.params.resourceType, req.body.value)) { + if (!validate.resource(req.params.resourceType, req.body)) { res.status(406).send(`Invalid resource data supplied!`) return } @@ -747,7 +747,7 @@ export class Resources { ]?.setResource( req.params.resourceType, req.params.resourceId, - req.body.value + req.body ) this.server.handleMessage( @@ -755,7 +755,7 @@ export class Resources { this.buildDeltaMsg( req.params.resourceType as SignalKResourceType, req.params.resourceId, - req.body.value + req.body ) ) res From 18503d415392e897be65cb9c406ea43bd02c67c0 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 24 Nov 2021 10:50:23 +1030 Subject: [PATCH 294/410] remove resourceId validity check on GET --- src/api/resources/index.ts | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index f12c46c59..e52579e78 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -643,12 +643,6 @@ export class Resources { next() return } - if (!validate.uuid(req.params.resourceId)) { - res - .status(406) - .send(`Invalid resource id provided (${req.params.resourceId})`) - return - } try { const retVal = await this.resProvider[ req.params.resourceType @@ -696,6 +690,14 @@ export class Resources { next() return } + if (req.params.resourceType !== 'charts') { + if(!validate.uuid(req.params.resourceId)) { + res + .status(406) + .send(`Invalid resource id provided (${req.params.resourceId})`) + return + } + } if (!validate.resource(req.params.resourceType, req.body)) { res.status(406).send(`Invalid resource data supplied!`) return @@ -787,12 +789,7 @@ export class Resources { next() return } - if (!validate.uuid(req.params.resourceId)) { - res - .status(406) - .send(`Invalid resource id provided (${req.params.resourceId})`) - return - } + try { const retVal = await this.resProvider[ req.params.resourceType From f18a8e192904a37feee34908d06cfddf0307f7b3 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 24 Nov 2021 15:01:01 +1030 Subject: [PATCH 295/410] chore: Updated documentation --- RESOURCE_PROVIDER_PLUGINS.md | 438 +++++++++++++++++++++++------------ SERVERPLUGINS.md | 45 ++-- 2 files changed, 324 insertions(+), 159 deletions(-) diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index d1ae13b26..f97a913e9 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -1,10 +1,14 @@ # Resource Provider plugins <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> chore: Updated documentation _This document should be read in conjunction with [SERVERPLUGINS.md](./SERVERPLUGINS.md) as it contains additional information regarding the development of plugins that facilitate the storage and retrieval of resource data._ --- +<<<<<<< HEAD ## Overview The SignalK specification defines the path `/signalk/v1/api/resources` for accessing resources to aid in navigation and operation of the vessel. @@ -64,41 +68,72 @@ The `ResourceProvider` interface defines the contract between the the Resource P interface ResourceProvider: { types: string[], ======= +======= +>>>>>>> chore: Updated documentation ## Overview -This document should be read in conjunction with [SERVERPLUGINS.md](./SERVERPLUGINS.md) as it contains additional information regarding the development of plugins that facilitate the storage and retrieval of resource data (e.g. routes, waypoints, notes, regions and charts). +The SignalK specification defines the path `/signalk/v1/api/resources` for accessing resources to aid in navigation and operation of the vessel. -Resource storage and retrieval is de-coupled from core server function to provide the flexibility to implement the appropriate resource storage solution for specific Signal K implementations. +It also defines the schema for the following __Common__ resource types: +- routes +- waypoints +- notes +- regions +- charts -The Signal K Node server will pass requests made to the following paths to registered resource providers: -- `/signalk/v1/api/resources` -- `/signalk/v1/api/resources/routes` -- `/signalk/v1/api/resources/waypoints` -- `/signalk/v1/api/resources/notes` -- `/signalk/v1/api/resources/regions` -- `/signalk/v1/api/resources/charts` +each of with its own path under the root `resources` path _(e.g. `/signalk/v1/api/resources/routes`)_. -Resource providers will receive request data via a `ResourceProvider` interface which they implement. It is the responsibility of the resource provider to persist resource data in storage (PUT, POST), retrieve the requested resources (GET) and remove resource entries (DELETE). +It should also be noted that the `/signalk/v1/api/resources` path can also host other types of resource data which can be grouped within a __Custom__ path name _(e.g. `/signalk/v1/api/resources/fishingZones`)_. -Resource data passed to the resource provider plugin has been validated by the server and can be considered ready for storage. +The SignalK server does not natively provide the ability to store or retrieve resource data for either __Common__ and __Custom__ resource types. +This functionality needs to be provided by one or more server plugins that handle the data for specific resource types. +These plugins are called __Resource Providers__. -## Resource Providers +This de-coupling of resource request handling and storage / retrieval provides great flexibility to ensure that an appropriate resource storage solution can be configured for your SignalK implementation. -A `resource provider plugin` is responsible for the storage and retrieval of resource data. +It is important to understand that the SignalK server handles requests for __Custom__ resource types differently to requests for __Common__ types, please ensure you refer to the relevant section of this document. -It should implement the necessary functions to: -- Persist each resource with its associated id -- Retrieve an individual resource with the supplied id -- Retrieve a list of resources that match the supplied qery criteria. +--- -Data is passed to and from the plugin via the methods defined in the __resourceProvider__ interface which the plugin must implement. +## Common Resource Type Provider: +<<<<<<< HEAD _Definition: `resourceProvider` interface._ ```javascript resourceProvider: { types: [], >>>>>>> Added Resource_Provider documentation +======= +As detailed earlier in this document, the __Common__ resource types are: +`routes`, `waypoints`, `notes`, `regions` & `charts`. + +For the `/signalk/v1/api/resources` path and the __Common__ resource type sub-paths, the Signal K server will pre-process the request before passing on the request details on to the registered resource provider. + +The SignalK server performs the following tasks when pre-processing a request: +- Checks for a registered provider for the resource type +- Checks the validity of the supplied resource id +- For requests to store data, the submitted resource data is validated. + +Only when all pre-processing tasks have completed successfully will the request be passed on to the resource provider plugin. + +Resource providers for __Common__ resource types need to implement the `ResourceProvider` interface which registers with the SignalK server the: +- Resource types provided for by the plugin +- Methods to use when requests are passed to the plugin. It is these methods that implement the saving / deletion of resource data to storage and the retrieval of requested resources. + + +### Resource Provider Interface + +--- +The `ResourceProvider` interface defines the contract between the the Resource Provider plugin and the SignalK server and has the following definition _(which it and other related types can be imported from `@signalk/server-api`)_: + +```typescript +import { SignalKResourceType } from '@signalk/server-api' +// SignalKResourceType= 'routes' | 'waypoints' |'notes' |'regions' |'charts' + +interface ResourceProvider: { + types: SignalKResourceType[], +>>>>>>> chore: Updated documentation methods: { listResources: (type:string, query: {[key:string]:any})=> Promise getResource: (type:string, id:string)=> Promise @@ -108,9 +143,15 @@ resourceProvider: { } ``` <<<<<<< HEAD +<<<<<<< HEAD where: - `types`: An array containing a list of resource types provided for by the plugin. These can be a mixture of both __Common__ and __Custom__ resource types. +======= +where: + +- `types`: An array containing a list of __Common__ resource types provided for by the plugin +>>>>>>> chore: Updated documentation - `methods`: An object containing the methods resource requests are passed to by the SignalK server. The plugin __MUST__ implement each method, even if that operation is not supported by the plugin! #### __Method Details:__ @@ -120,9 +161,15 @@ __`listResources(type, query)`__: This method is called when a request is made f _Note: It is the responsibility of the resource provider plugin to filter the resources returned as per the supplied query parameters._ +<<<<<<< HEAD `type:` String containing the type of resource to retrieve. `query:` Object contining `key | value` pairs repesenting the parameters by which to filter the returned entries. _e.g. {distance,'50000}_ +======= +`type:` Array of __Common__ resource types provied for by the plugin _e.g. ['routes','waypoints']_ + +`query:` Object contining `key | value` pairs repesenting the parameters by which to filter the returned entries _e.g. {distance,'50000}_ +>>>>>>> chore: Updated documentation `returns:` - Resolved Promise containing a list of resource entries on completion. @@ -148,9 +195,15 @@ listResources( --- __`getResource(type, id)`__: This method is called when a request is made for a specific resource entry of the supplied resource type and id. +<<<<<<< HEAD `type:` String containing the type of resource to retrieve. `id:` String containing the target resource entry id. _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ +======= +`type:` String containing one of the __Common__ resource types _e.g. 'waypoints'_ + +`id:` String containing the target resource entry id _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ +>>>>>>> chore: Updated documentation `returns:` - Resolved Promise containing the resource entry on completion. @@ -161,6 +214,7 @@ _Example resource request:_ GET /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99 ``` _ResourceProvider method invocation:_ +<<<<<<< HEAD ```javascript getResource( @@ -306,41 +360,138 @@ module.exports = function (app) { app.resourcesApi.unRegister(plugin.id); ... ======= +======= +>>>>>>> chore: Updated documentation + +```javascript +getResource( + 'routes', + 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99' +); +``` -This interface is used by the server to direct requests to the plugin. +--- +__`setResource(type, id, value)`__: This method is called when a request is made to save / update a resource entry of the specified resource type, with the supplied id and data. + +`type:` Array of __Common__ resource types provied for by the plugin _e.g. ['routes','waypoints']_ -It contains the following attributes: -- `types`: An array containing the names of the resource types the plugin is a provider for. Names of the resource types are: `routes, waypoints, notes, regions, charts`. +`id:` String containing the target resource entry id _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ -- `methods`: The methods to which the server dispatches requests. The plugin will implement these methods to perform the necessary save or retrieval operation. Each method returns a promise containing either resource data or `null` if an error is encountered. +`value:` Resource data to be stored. -_Example: Plugin acting as resource provider for routes & waypoints._ +`returns:` +- Resolved Promise containing a list of resource entries on completion. +- Rejected Promise containing an Error if incomplete or not implemented. + +_Example PUT resource request:_ +``` +PUT /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99 {resource_data} +``` +_ResourceProvider method invocation:_ + +```javascript +setResource( + 'routes', + 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99', + {} +); +``` + +_Example POST resource request:_ +``` +POST /signalk/v1/api/resources/routes {resource_data} +``` +_ResourceProvider method invocation:_ + +```javascript +setResource( + 'routes', + '', + {} +); +``` + +--- +__`deleteResource(type, id)`__: This method is called when a request is made to remove the specific resource entry of the supplied resource type and id. + +`type:` String containing one of the __Common__ resource types _e.g. 'waypoints'_ + +`id:` String containing the target resource entry id _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ + +`returns:` +- Resolved Promise on completion. +- Rejected Promise containing an Error if incomplete or not implemented. + +_Example resource request:_ +``` +DELETE /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99 +``` +_ResourceProvider method invocation:_ + +```javascript +deleteResource( + 'routes', + 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99' +); +``` + +--- + +### Example: + +_Defintion for Resource Provider plugin providing for the retrieval routes & waypoints._ ```javascript +// SignalK server plugin module.exports = function (app) { + let plugin= { id: 'mypluginid', name: 'My Resource Providerplugin', + // ResourceProvider interface resourceProvider: { types: ['routes','waypoints'], methods: { listResources: (type, params)=> { - return Promise.resolve() { ... }; + return new Promise( (resolve, reject) => { + // fetch resource entries from storage + .... + if(ok) { // success + resolve({ + 'id1': { ... }, + 'id2': { ... }, + }); + } else { // error + reject(new Error('Error encountered!') + } + } + }, + getResource: (type, id)=> { + // fetch resource entries from storage + .... + if(ok) { // success + return Promise.resolve({ + ... + }); + } else { // error + reject(new Error('Error encountered!') + } }, - getResource: (type:string, id:string)=> { - return Promise.resolve() { ... }; - } , - setResource: (type:string, id:string, value:any)=> { - return Promise.resolve() { ... }; + setResource: (type, id, value)=> { + // not implemented + return Promise.reject(new Error('NOT IMPLEMENTED!')); }, - deleteResource: (type:string, id:string)=> { - return Promise.resolve() { ... }; ; + deleteResource: (type, id)=> { + // not implemented + return Promise.reject(new Error('NOT IMPLEMENTED!')); } } }, + start: (options)=> { ... app.resourceApi.register(this.id, this.resourceProvider); }, + stop: ()=> { app.resourceApi.unRegister(this.id); ... @@ -349,35 +500,37 @@ module.exports = function (app) { } ``` + +### Registering the Resource Provider: --- -## Plugin Startup - Registering the Resource Provider: +For the plugin to be registered by the SignalK server, it needs to call the server's `resourcesApi.register()` function during plugin startup. -To register your plugin as a resource provider the server's `resourcesApi.register()` function should be called within the plugin `start()` function passing the `resourceProvider` interface. +The server `resourcesApi.register()` function has the following signature: -This registers the resource types and the methods with the server so they are called when requests to resource paths are made. +```typescript +app.resourcesApi.register(pluginId: string, resourceProvider: ResourceProvider) +``` +where: +- `pluginId`: is the plugin's id +- `resourceProvider`: is a reference to the plugins ResourceProvider interface. + +_Note: A resource type can only have one registered plugin, so if more than one installed plugin attempts to register as a provider for the same resource type, only the first one is registered and it's implemented methods will service requests._ _Example:_ ```javascript module.exports = function (app) { + let plugin= { id: 'mypluginid', name: 'My Resource Providerplugin', resourceProvider: { types: ['routes','waypoints'], methods: { - listResources: (type, params)=> { - return Promise.resolve() { ... }; - }, - getResource: (type:string, id:string)=> { - return Promise.resolve() { ... }; - } , - setResource: (type:string, id:string, value:any)=> { - return Promise.resolve() { ... }; - }, - deleteResource: (type:string, id:string)=> { - return Promise.resolve() { ... }; ; - } + listResources: (type, params)=> { ... }, + getResource: (type:string, id:string)=> { ... } , + setResource: (type:string, id:string, value:any)=> { ... }, + deleteResource: (type:string, id:string)=> { ... } } } } @@ -388,15 +541,25 @@ module.exports = function (app) { } } ``` + +### Un-registering the Resource Provider: --- -## Plugin Stop - Un-registering the Resource Provider: +When a resource provider plugin is disabled, it should un-register as a provider to ensure resource requests are no longer directed to it by calling the SignalK server's `resourcesApi.unRegister()` function during shutdown. + +The server `resourcesApi.unRegister()` function has the following signature: + +```typescript +app.resourcesApi.unRegister(pluginId: string) +``` +where: +- `pluginId`: is the plugin's id -When a resource provider plugin is disabled it should un-register as a provider so resource requests are not directed to it. This is done by calling the server's `resourcesApi.unRegister()` function passing the `plugin.id` within the plugin's `stop()` function. _Example:_ ```javascript module.exports = function (app) { + let plugin= { id: 'mypluginid', name: 'My Resource Providerplugin', @@ -414,125 +577,114 @@ module.exports = function (app) { ``` --- -## Operation: +## Custom Resource Type Provider: -The Server will dispatch requests made to: -- `/signalk/v1/api/resources/` +Custom resource types are collections of resource entries residing within a sub-path with a user defined name under `/signalk/v1/api/resources`. -OR -- the `resources API` endpoints +_Example:_ +``` +/signalk/v1/api/resources/fishingZones +``` -to the plugin's `resourceProvider.methods` for each of the resource types listed in `resourceProvider.types`. +_Note: A custom resource path name __CANNOT__ be one of the __Common__ resource types i.e. `routes`, `waypoints`, `notes`, `regions` or `charts`.__ -Each method defined in `resourceProvider.methods` must have a signature as specified in the __resourceProvider interface__. Each method returns a `Promise` containing the resultant resource data or `null` if an error occurrred or the operation is incomplete. +Unlike the __Common Resource Type Providers__: +- The plugin __DOES NOT__ implement the `ResourceProvider` interface +- Requests to __Custom__ resource type sub-paths and any submitted data __WILL NOT__ be pre-processed or validated by the Signal K server +- The plugin __WILL__ need to implement a route handler for the necessary path(s) +- The plugin __WILL__ need to implement any necessary data validation. -### __List Resources:__ +### Router Path Handlers +--- -`GET` requests that are not for a specific resource will be dispatched to the `listResources` method passing the resource type and any query data as parameters. +To set up a router path handler, you use the reference to the SignalK server `app` passed to the plugin to register the paths for which requests will be passed to the plugin. -Query parameters are passed as an object conatining `key | value` pairs. +This should be done during plugin startup within the plugin `start()` function. -_Example: GET /signalk/v1/api/resources/waypoints?bbox=5.4,25.7,6.9,31.2&distance=30000_ -```javascript -query= { - bbox: '5.4,25.7,6.9,31.2', - distance: 30000 -} -``` - -It is the responsibility of the resource provider plugin to filter the resources returned as per the supplied query parameters. +_Note: To ensure that your resource path is listed when a request to `/signalk/v1/api/resources` is made ensure you use the full path as outlined in the example below._ -`listResources()` returns a Promise containing a JSON object listing resources by id. - -_Example: List all routes._ +_Example:_ ```javascript -GET /signalk/v1/api/resources/routes - -listResources('routes', {}) - -returns Promise<{ - "resource_id1": { ... }, - "resource_id2": { ... }, - ... - "resource_idn": { ... } -}> -``` -_Example: List waypoints within the bounded area._ -```typescript -GET /signalk/v1/api/resources/waypoints?bbox=5.4,25.7,6.9,31.2 +module.exports = function (app) { -listResources('waypoints', {bbox: '5.4,25.7,6.9,31.2'}) + let plugin= { + id: 'mypluginid', + name: 'My Resource Providerplugin', + start: (options) => { + // setup router path handlers + initPathHandlers(app); + ... + } + } + + function initPathHandlers(app) { + app.get( + `/signalk/v1/api/resources/myResType`, + (request, response)=> { + // retrieve resource(s) + let result= getMyResources(); + response.status(200).json(result); + } + ); + app.post( + `/signalk/v1/api/resources/myResType`, + (request, response)=> { + // create new resource + ... + } + ); + router.put( + `/signalk/v1/api/resources/myResType/:id`, + (request, response)=> { + // create / update resource with supplied id + ... + } + ); + router.delete( + `/signalk/v1/api/resources/myResType/:id`, + (request, response)=> { + // delete the resource with supplied id + ... + } + ); + } -returns Promise<{ - "resource_id1": { ... }, - "resource_id2": { ... } -}> ``` -### __Retrieve a specific resource:__ - -`GET` requests for a specific resource will be dispatched to the `getResource` method passing the resource type and id as parameters. +Once registered requests to `/signalk/v1/api/resources/myResType` will be passed to the respective handler based on the request type _(i.e. GET, PUT, POST, DELETE)_ and the request details are available in the `request` parameter. -`getResource()` returns a Promise containing a JSON object with the resource data. +For more information regarding the `request` and `response` parameters see the [Express Routing](https://expressjs.com/en/guide/routing.html) documentation. -_Example: Retrieve route._ -```typescript -GET /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a +### Data Validation +--- -getResource( - 'routes', - 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', - {} -) -``` +When providing for resource data to be persisted to storage (PUT, POST) it is recommended that data validation implemented within the `app.put()` and `app.post()` route handlers and that the appropriate `status` is returned. -_Returns a Promise containing the resource data:_ -```typescript -Promise<{ - "name": "Name of the route", - "description": "Description of the route", - "distance": 18345, - "feature": { ... } -}> -``` - -A request for a resource attribute will pass the attibute path as an array in the query object with the key `resAttrib`. - -_Example: Get waypoint geometry._ +_Example:_ ```javascript -GET /signalk/v1/api/resources/waypoints/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a/feature/geometry - -getResource( - 'waypoints', - 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', - { resAttrib: ['feature','geometry'] } +app.post( + `/signalk/v1/api/resources/myResType`, + (request, response)=> { + // validate submitted data + let ok= validate(request.body); + if (ok) { //valid data + if (saveResource(request.body)) { + response.status(200).send('OK'); + } else { + response.status(404).send('ERROR svaing resource!'); + } + } else { + response.status(406).send('ERROR: Invalid data!'); + } + } ) -``` -_Returns a Promise containing the value of `geometry` attribute of the waypoint._ -```typescript -Promise<{ - "type": "Point", - "coordinates": [70.4,6.45] -}> -``` - -### __Saving Resources:__ - -`PUT` requests to a path containing the resource id are used to store data associated with the resource id. These will be dispatched to the `setResource` method passing the resource type, id and data as parameters. -`setResource() ` returns Promise on success and Promise on failure. - -_Example: Update / add waypoint with the supplied id._ -```javascript -PUT /signalk/v1/api/resources/waypoints/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a {} - -setResource('waypoints', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) - -returns Promise ``` +--- +<<<<<<< HEAD `POST` requests to a resource path that do not contina the resource id will be dispatched to the `setResource` method passing the resource type, an id (generated by the server) and resource data as parameters. `setResource()` returns `true` on success and `null` on failure. @@ -583,6 +735,8 @@ returns true | null returns Promise >>>>>>> chore: return value descriptions to show a Promise ``` +======= +>>>>>>> chore: Updated documentation <<<<<<< HEAD --- diff --git a/SERVERPLUGINS.md b/SERVERPLUGINS.md index e6d8f098d..4de5a2bba 100644 --- a/SERVERPLUGINS.md +++ b/SERVERPLUGINS.md @@ -23,10 +23,13 @@ The plugin module must export a single `function(app)` that must return an objec ## Getting Started with Plugin Development To get started with SignalK plugin development, you can follow this guide. +<<<<<<< HEAD _Note: For plugins acting as a provider for one or more of the SignalK resource types listed in the specification (`routes`, `waypoints`, `notes`, `regions` or `charts`) please refer to __[RESOURCE_PROVIDER_PLUGINS.md](./RESOURCE_PROVIDER_PLUGINS.md)__ for additional details._ +======= +>>>>>>> chore: Updated documentation -_Note: For plugins acting as a provider for one or more of the resource types listed in the Signal K specification (e.g. routes, waypoints, notes, regions or charts) refer to __[RESOURCE_PROVIDER_PLUGINS.md](./RESOURCE_PROVIDER_PLUGINS.md)__ for details. +_Note: For plugins acting as a provider for one or more of the SignalK resource types listed in the specification (`routes`, `waypoints`, `notes`, `regions` or `charts`) please refer to __[RESOURCE_PROVIDER_PLUGINS.md](./RESOURCE_PROVIDER_PLUGINS.md)__ for additional details._ ### Project setup @@ -764,29 +767,30 @@ When a resource provider plugin is disabled it will need to un-register its prov ======= ### `app.resourcesApi.getResource(resource_type, resource_id)` -Retrieve resource data for the supplied resource type and id. - -This method invokes the `registered Resource Provider` for the supplied `resource_type` and returns a __resovled Promise__ containing the route resource data if successful or -a __rejected Promise__ containing an Error object if unsuccessful. +Retrieve resource data for the supplied SignalK resource type and resource id. - data for the full path of the directory where the plugin can persist its internal data, like data files.If a plugin wants to act as a resource provider, it will need to register its provider methods during startup using this function. +_Valid resource types are `routes`, `waypoints`, `notes`, `regions` & `charts`._ +This method invokes the `registered Resource Provider` for the supplied `resource_type` and returns a `resovled` __Promise__ containing the resource data if successful or +a `rejected` __Promise__ containing an __Error__ object if unsuccessful. +_Example:_ ```javascript -let myRoute= app.resourcesApi.getResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a'); -``` -_Returns resolved Promise containing:_ -```json -{ - "name": "Name of the route", - "description": "Description of the route", - "distance": 18345, - "feature": { ... } -} +let resource= app.resourcesApi.getResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a'); +resource.then ( (data)=> { + // route data + console.log(data); + ... +}).catch (error) { + // handle error + console.log(error.message); + ... +} ``` +<<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD >>>>>>> add getResource function @@ -795,6 +799,9 @@ _Returns resolved Promise containing:_ ### `app.resourcesApi.register(pluginId, provider)` >>>>>>> add pluginId to register() function ======= +======= + +>>>>>>> chore: Updated documentation ### `app.resourcesApi.register(pluginId, resourceProvider)` >>>>>>> update docs @@ -813,7 +820,7 @@ module.exports = function (app) { methods: { ... } } start: function(options) { - ... + // do plugin start up app.resourcesApi.register(this.id, this.resourceProvider); } ... @@ -867,8 +874,12 @@ plugin.stop = function(options) { app.resourcesApi.unRegister(this.id, this.resourceProvider.types); ======= app.resourcesApi.unRegister(this.id); +<<<<<<< HEAD >>>>>>> update docs ... +======= + // do plugin shutdown +>>>>>>> chore: Updated documentation } } } From 5b7b998ca38e20661f79122ff172c1bfa384850c Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 24 Nov 2021 17:22:29 +1030 Subject: [PATCH 296/410] add chartId test & require alignment with spec. --- src/api/resources/index.ts | 47 +++++++++++++++++++++++++++-------- src/api/resources/validate.ts | 31 +++++++++++++++++++---- 2 files changed, 63 insertions(+), 15 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index e52579e78..6ff411bc9 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -647,11 +647,10 @@ export class Resources { const retVal = await this.resProvider[ req.params.resourceType ]?.getResource(req.params.resourceType, req.params.resourceId) - res.json(retVal) + res.json(retVal) } catch (err) { res.status(404).send(`Resource not found! (${req.params.resourceId})`) } - } ) @@ -671,7 +670,7 @@ export class Resources { const retVal = await this.resProvider[ req.params.resourceType ]?.listResources(req.params.resourceType, req.query) - res.json(retVal) + res.json(retVal) } catch (err) { res.status(404).send(`Error retrieving resources!`) } @@ -702,12 +701,23 @@ export class Resources { res.status(406).send(`Invalid resource data supplied!`) return } - const id = UUID_PREFIX + uuidv4() + if (!validate.resource(req.params.resourceType, req.body)) { + res.status(406).send(`Invalid resource data supplied!`) + return + } + + let id: string + if (req.params.resourceType === 'charts') { + id = req.body.identifier + } else { + id = UUID_PREFIX + uuidv4() + } + try { const retVal = await this.resProvider[ req.params.resourceType ]?.setResource(req.params.resourceType, id, req.body) - + this.server.handleMessage( this.resProvider[req.params.resourceType]?.pluginId as string, this.buildDeltaMsg( @@ -723,7 +733,7 @@ export class Resources { res .status(404) .send(`Error saving ${req.params.resourceType} resource (${id})!`) - } + } } ) @@ -739,6 +749,23 @@ export class Resources { next() return } +<<<<<<< HEAD +======= + + let isValidId: boolean + if (req.params.resourceType === 'charts') { + isValidId = validate.chartId(req.params.resourceId) + } else { + isValidId = validate.uuid(req.params.resourceId) + } + if (isValidId) { + res + .status(406) + .send(`Invalid resource id provided (${req.params.resourceId})`) + return + } + +>>>>>>> add chartId test & require alignment with spec. if (!validate.resource(req.params.resourceType, req.body)) { res.status(406).send(`Invalid resource data supplied!`) return @@ -751,7 +778,7 @@ export class Resources { req.params.resourceId, req.body ) - + this.server.handleMessage( this.resProvider[req.params.resourceType]?.pluginId as string, this.buildDeltaMsg( @@ -765,7 +792,7 @@ export class Resources { .send( `${req.params.resourceType} resource (${req.params.resourceId}) saved.` ) - } catch (err) { + } catch (err) { res .status(404) .send( @@ -789,12 +816,12 @@ export class Resources { next() return } - + try { const retVal = await this.resProvider[ req.params.resourceType ]?.deleteResource(req.params.resourceType, req.params.resourceId) - + this.server.handleMessage( this.resProvider[req.params.resourceType]?.pluginId as string, this.buildDeltaMsg( diff --git a/src/api/resources/validate.ts b/src/api/resources/validate.ts index 5a39f139d..a16096fd7 100644 --- a/src/api/resources/validate.ts +++ b/src/api/resources/validate.ts @@ -25,6 +25,7 @@ export const validate = { case 'charts': return validateChart(value) break +<<<<<<< HEAD default: return true } @@ -171,21 +172,28 @@ export const validate = { case 'regions': return validateRegion(value) break +======= +>>>>>>> add chartId test & require alignment with spec. default: return true } }, - // ** returns true if id is a valid Signal K UUID ** + // returns true if id is a valid Signal K UUID uuid: (id: string): boolean => { const uuid = RegExp( '^urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$' ) return uuid.test(id) + }, + + // returns true if id is a valid Signal K Chart resource id + chartId: (id: string): boolean => { + const uuid = RegExp('(^[A-Za-z0-9_-]{8,}$)') + return uuid.test(id) } } -// ** validate route data const validateRoute = (r: any): boolean => { if (r.start) { const l = r.start.split('/') @@ -212,7 +220,6 @@ const validateRoute = (r: any): boolean => { return true } -// ** validate waypoint data const validateWaypoint = (r: any): boolean => { if (typeof r.position === 'undefined') { return false @@ -233,7 +240,7 @@ const validateWaypoint = (r: any): boolean => { return true } -// ** validate note data +// validate note data const validateNote = (r: any): boolean => { if (!r.region && !r.position && !r.geohash) { return false @@ -252,7 +259,6 @@ const validateNote = (r: any): boolean => { return true } -// ** validate region data const validateRegion = (r: any): boolean => { if (!r.geohash && !r.feature) { return false @@ -275,7 +281,22 @@ const validateRegion = (r: any): boolean => { return true } <<<<<<< HEAD +<<<<<<< HEAD >>>>>>> Add Signal K standard resource path handling ======= >>>>>>> chore: linted +======= + +const validateChart = (r: any): boolean => { + if (!r.name || !r.identifier || !r.chartFormat) { + return false + } + + if (!r.tilemapUrl && !r.chartUrl) { + return false + } + + return true +} +>>>>>>> add chartId test & require alignment with spec. From 2b04dfee58b56ba4beb8b4e7cbf5b62edee3f3ca Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 24 Nov 2021 17:44:05 +1030 Subject: [PATCH 297/410] add charts API methods --- src/api/resources/openApi.json | 482 +++++++++++++++++++++++++++++++++ 1 file changed, 482 insertions(+) diff --git a/src/api/resources/openApi.json b/src/api/resources/openApi.json index 126ae0f94..18262474c 100644 --- a/src/api/resources/openApi.json +++ b/src/api/resources/openApi.json @@ -1256,11 +1256,15 @@ "/resources/regions/": { "post": { "tags": ["resources/regions"], +<<<<<<< HEAD <<<<<<< HEAD "summary": "Add a new Region", ======= "summary": "Add a new Regkion", >>>>>>> add OpenApi definition file +======= + "summary": "Add a new Region", +>>>>>>> add charts API methods "requestBody": { "description": "Region details", "required": true, @@ -1761,6 +1765,311 @@ "post": { "tags": ["resources/charts"], "summary": "Add a new Chart", +<<<<<<< HEAD +======= + "requestBody": { + "description": "Chart details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Chart resource", + "required": ["feature"], + "properties": { + "name": { + "description": "Chart common name", + "example":"NZ615 Marlborough Sounds", + "type": "string" + }, + "identifier": { + "type": "string", + "description": "Chart number", + "example":"NZ615" + }, + "description": { + "type": "string", + "description": "A description of the chart" + }, + "tilemapUrl": { + "type": "string", + "description": "A url to the tilemap of the chart for use in TMS chartplotting apps", + "example":"http://{server}:8080/mapcache/NZ615" + }, + "region": { + "type": "string", + "description": "Region related to note. A pointer to a region UUID. Alternative to geohash" + }, + "geohash": { + "description": "Position related to chart. Alternative to region", + "type": "string" + }, + "chartUrl": { + "type": "string", + "description": "A url to the chart file's storage location", + "example":"file:///home/pi/freeboard/mapcache/NZ615" + }, + "scale": { + "type": "integer", + "description": "The scale of the chart, the larger number from 1:200000" + }, + "chartLayers": { + "type": "array", + "description": "If the chart format is WMS, the layers enabled for the chart.", + "items": { + "type": "string", + "description": "Identifier for the layer." + } + }, + "bounds": { + "type": "array", + "description": "The bounds of the chart. An array containing the position of the upper left corner, and the lower right corner. Useful when the chart isn't inherently geo-referenced.", + "items": { + "description": "Position of a corner of the chart", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + }, + "altitude": { + "type": "number", + "format": "float" + } + } + } + }, + "chartFormat": { + "type": "string", + "description": "The format of the chart", + "enum": [ + "gif", + "geotiff", + "kap", + "png", + "jpg", + "kml", + "wkt", + "topojson", + "geojson", + "gpx", + "tms", + "wms", + "S-57", + "S-63", + "svg", + "other" + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + + "/resources/charts/{id}": { + "parameters": { + "name": "id", + "in": "path", + "description": "Chart id", + "required": true, + "schema": { + "type": "string", + "pattern": "(^[A-Za-z0-9_-]{8,}$)" + } + }, + + "get": { + "tags": ["resources/charts"], + "summary": "Retrieve Chart with supplied id", + "responses": { + "200": { + "description": "List of resources identified by their UUID", + "content": { + "application/json": { + "schema": { + "description": "List of Signal K resources", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + } + }, + + "put": { + "tags": ["resources/charts"], + "summary": "Add / update a new Chart with supplied id", + "requestBody": { + "description": "Chart details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Chart resource", + "required": ["feature"], + "properties": { + "name": { + "description": "Chart common name", + "example":"NZ615 Marlborough Sounds", + "type": "string" + }, + "identifier": { + "type": "string", + "description": "Chart number", + "example":"NZ615" + }, + "description": { + "type": "string", + "description": "A description of the chart" + }, + "tilemapUrl": { + "type": "string", + "description": "A url to the tilemap of the chart for use in TMS chartplotting apps", + "example":"http://{server}:8080/mapcache/NZ615" + }, + "region": { + "type": "string", + "description": "Region related to note. A pointer to a region UUID. Alternative to geohash" + }, + "geohash": { + "description": "Position related to chart. Alternative to region", + "type": "string" + }, + "chartUrl": { + "type": "string", + "description": "A url to the chart file's storage location", + "example":"file:///home/pi/freeboard/mapcache/NZ615" + }, + "scale": { + "type": "integer", + "description": "The scale of the chart, the larger number from 1:200000" + }, + "chartLayers": { + "type": "array", + "description": "If the chart format is WMS, the layers enabled for the chart.", + "items": { + "type": "string", + "description": "Identifier for the layer." + } + }, + "bounds": { + "type": "array", + "description": "The bounds of the chart. An array containing the position of the upper left corner, and the lower right corner. Useful when the chart isn't inherently geo-referenced.", + "items": { + "description": "Position of a corner of the chart", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + }, + "altitude": { + "type": "number", + "format": "float" + } + } + } + }, + "chartFormat": { + "type": "string", + "description": "The format of the chart", + "enum": [ + "gif", + "geotiff", + "kap", + "png", + "jpg", + "kml", + "wkt", + "topojson", + "geojson", + "gpx", + "tms", + "wms", + "S-57", + "S-63", + "svg", + "other" + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + }, + + "delete": { + "tags": ["resources/charts"], + "summary": "Remove Chart with supplied id", + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + + }, + + "/resources/setWaypoint": { + "put": { + "tags": ["resources/api"], + "summary": "Add / update a Waypoint", +>>>>>>> add charts API methods "requestBody": { "description": "Chart details", "required": true, @@ -3019,7 +3328,180 @@ } } } +<<<<<<< HEAD >>>>>>> addressed comments re parameters +======= + }, + + "/resources/setChart": { + "put": { + "tags": ["resources/api"], + "summary": "Add / update a Chart", + "requestBody": { + "description": "Chart attributes", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K Chart resource", + "required": ["identifier","tilemapUrl","chartUrl","chartFormat"], + "properties": { + "name": { + "description": "Chart common name", + "example":"NZ615 Marlborough Sounds", + "type": "string" + }, + "identifier": { + "type": "string", + "description": "Chart number", + "example":"NZ615" + }, + "description": { + "type": "string", + "description": "A description of the chart" + }, + "oneOf": [ + { + "tilemapUrl": { + "type": "string", + "description": "A url to the tilemap of the chart for use in TMS chartplotting apps", + "example":"http://{server}:8080/mapcache/NZ615" + } + }, + { + "chartUrl": { + "type": "string", + "description": "A url to the chart file's storage location", + "example":"file:///home/pi/freeboard/mapcache/NZ615" + } + } + ], + "region": { + "type": "string", + "description": "Region related to note. A pointer to a region UUID. Alternative to geohash" + }, + "geohash": { + "description": "Position related to chart. Alternative to region", + "type": "string" + }, + "scale": { + "type": "integer", + "description": "The scale of the chart, the larger number from 1:200000" + }, + "chartLayers": { + "type": "array", + "description": "If the chart format is WMS, the layers enabled for the chart.", + "items": { + "type": "string", + "description": "Identifier for the layer." + } + }, + "bounds": { + "type": "array", + "description": "The bounds of the chart. An array containing the position of the upper left corner, and the lower right corner. Useful when the chart isn't inherently geo-referenced.", + "items": { + "description": "Position of a corner of the chart", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + }, + "altitude": { + "type": "number", + "format": "float" + } + } + } + }, + "chartFormat": { + "type": "string", + "description": "The format of the chart", + "enum": [ + "gif", + "geotiff", + "kap", + "png", + "jpg", + "kml", + "wkt", + "topojson", + "geojson", + "gpx", + "tms", + "wms", + "S-57", + "S-63", + "svg", + "other" + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + + "/resources/deleteChart": { + "put": { + "tags": ["resources/api"], + "summary": "Remove a Chart", + "requestBody": { + "description": "Chart identifier", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["id"], + "properties": { + "id": { + "type": "string", + "pattern": "(^[A-Za-z0-9_-]{8,}$)", + "description": "Chart identifier" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } +>>>>>>> add charts API methods } } From aaf76fa706690508ca8a6225ed571bf1e44959e6 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Thu, 25 Nov 2021 13:21:23 +1030 Subject: [PATCH 298/410] allow registering custom resource types --- src/api/resources/index.ts | 91 +++++++++++++++++++++++--------------- 1 file changed, 56 insertions(+), 35 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 6ff411bc9..a14c593aa 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -563,8 +563,7 @@ export class Resources { private resProvider: { [key: string]: ResourceProviderMethods | null } = {} private server: ResourceApplication - // in-scope resource types - private resourceTypes: SignalKResourceType[] = [ + private signalkResTypes: SignalKResourceType[] = [ 'routes', 'waypoints', 'notes', @@ -689,6 +688,7 @@ export class Resources { next() return } +<<<<<<< HEAD if (req.params.resourceType !== 'charts') { if(!validate.uuid(req.params.resourceId)) { res @@ -704,6 +704,14 @@ export class Resources { if (!validate.resource(req.params.resourceType, req.body)) { res.status(406).send(`Invalid resource data supplied!`) return +======= + + if (this.signalkResTypes.includes(req.params.resourceType as SignalKResourceType)) { + if (!validate.resource(req.params.resourceType, req.body)) { + res.status(406).send(`Invalid resource data supplied!`) + return + } +>>>>>>> allow registering custom resource types } let id: string @@ -752,24 +760,33 @@ export class Resources { <<<<<<< HEAD ======= - let isValidId: boolean - if (req.params.resourceType === 'charts') { - isValidId = validate.chartId(req.params.resourceId) - } else { - isValidId = validate.uuid(req.params.resourceId) - } - if (isValidId) { - res - .status(406) - .send(`Invalid resource id provided (${req.params.resourceId})`) - return - } + if (this.signalkResTypes.includes(req.params.resourceType as SignalKResourceType)) { + let isValidId: boolean + if (req.params.resourceType === 'charts') { + isValidId = validate.chartId(req.params.resourceId) + } else { + isValidId = validate.uuid(req.params.resourceId) + } + if (isValidId) { + res + .status(406) + .send(`Invalid resource id provided (${req.params.resourceId})`) + return + } +<<<<<<< HEAD >>>>>>> add chartId test & require alignment with spec. if (!validate.resource(req.params.resourceType, req.body)) { res.status(406).send(`Invalid resource data supplied!`) return +======= + if (!validate.resource(req.params.resourceType, req.body)) { + res.status(406).send(`Invalid resource data supplied!`) + return + } +>>>>>>> allow registering custom resource types } + try { const retVal = await this.resProvider[ req.params.resourceType @@ -919,31 +936,35 @@ export class Resources { private getResourcePaths(): { [key: string]: any } { const resPaths: { [key: string]: any } = {} - Object.entries(this.resProvider).forEach((p: any) => { - if (p[1]) { - resPaths[p[0]] = `Path containing ${p[0]}, each named with a UUID` - } - }) - // check for other plugins servicing paths under ./resources - this.server._router.stack.forEach((i: any) => { - if (i.route && i.route.path && typeof i.route.path === 'string') { - if (i.route.path.indexOf(`${SIGNALK_API_PATH}/resources`) !== -1) { - const r = i.route.path.split('/') - if (r.length > 5 && !(r[5] in resPaths)) { - resPaths[ - r[5] - ] = `Path containing ${r[5]} resources (provided by plug-in)` - } - } - } - }) + for( let i in this.resProvider) { + resPaths[i] = `Path containing ${i.slice(-1)==='s' ? i.slice(0, i.length-1) : i} resources (provided by ${this.resProvider[i]?.pluginId})` + } return resPaths } private checkForProvider(resType: SignalKResourceType): boolean { - return this.resourceTypes.includes(resType) && this.resProvider[resType] - ? true - : false + debug(`** checkForProvider(${resType})`) + debug(this.resProvider[resType]) + + if(this.resProvider[resType]) { + if( + !this.resProvider[resType]?.listResources || + !this.resProvider[resType]?.getResource || + !this.resProvider[resType]?.setResource || + !this.resProvider[resType]?.deleteResource || + typeof this.resProvider[resType]?.listResources !== 'function' || + typeof this.resProvider[resType]?.getResource !== 'function' || + typeof this.resProvider[resType]?.setResource !== 'function' || + typeof this.resProvider[resType]?.deleteResource !== 'function' ) + { + return false + } else { + return true + } + } + else { + return false + } } <<<<<<< HEAD From 3d7d6f09f04884b15fbe70c5daa49ec13f16b8a9 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Thu, 25 Nov 2021 14:16:04 +1030 Subject: [PATCH 299/410] chore: update docs --- RESOURCE_PROVIDER_PLUGINS.md | 279 +++++++++++++---------------------- 1 file changed, 101 insertions(+), 178 deletions(-) diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index f97a913e9..883fe1ddf 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -92,12 +92,14 @@ These plugins are called __Resource Providers__. This de-coupling of resource request handling and storage / retrieval provides great flexibility to ensure that an appropriate resource storage solution can be configured for your SignalK implementation. -It is important to understand that the SignalK server handles requests for __Custom__ resource types differently to requests for __Common__ types, please ensure you refer to the relevant section of this document. +SignalK server handles requests for both __Common__ and __Custom__ resource types in a similar manner, the only difference being that it does not perform any validation on __Custom__ resource data, so a plugin can act a s a provider for both types. --- +## Server Operation: -## Common Resource Type Provider: +The Signal K server handles all requests to `/signalk/v1/api/resources` and all sub-paths, before passing on the request to the registered resource provider plugin. +<<<<<<< HEAD <<<<<<< HEAD _Definition: `resourceProvider` interface._ ```javascript @@ -107,19 +109,27 @@ resourceProvider: { ======= As detailed earlier in this document, the __Common__ resource types are: `routes`, `waypoints`, `notes`, `regions` & `charts`. +======= +The following operations are performed by the server when a request is received: +- Checks for a registered provider for the resource type +- Checks that ResourceProvider methods are defined +- For __Common__ resource types, checks the validity of the: + - Resource id + - Submitted resource data. +>>>>>>> chore: update docs -For the `/signalk/v1/api/resources` path and the __Common__ resource type sub-paths, the Signal K server will pre-process the request before passing on the request details on to the registered resource provider. +Upon successful completion of these operations the request will then be passed to the registered resource provider plugin. -The SignalK server performs the following tasks when pre-processing a request: -- Checks for a registered provider for the resource type -- Checks the validity of the supplied resource id -- For requests to store data, the submitted resource data is validated. +--- +## Resource Provider plugin: -Only when all pre-processing tasks have completed successfully will the request be passed on to the resource provider plugin. +For a plugin to be considered a Resource Provider it needs to implement the `ResourceProvider` interface. -Resource providers for __Common__ resource types need to implement the `ResourceProvider` interface which registers with the SignalK server the: +By implementing this interface the plugin is able to register with the SignalK server the: - Resource types provided for by the plugin -- Methods to use when requests are passed to the plugin. It is these methods that implement the saving / deletion of resource data to storage and the retrieval of requested resources. +- Methods to used to action requests. + +It is these methods that perform the retrival, saving and deletion of resources from storage. ### Resource Provider Interface @@ -128,12 +138,13 @@ Resource providers for __Common__ resource types need to implement the `Resource The `ResourceProvider` interface defines the contract between the the Resource Provider plugin and the SignalK server and has the following definition _(which it and other related types can be imported from `@signalk/server-api`)_: ```typescript -import { SignalKResourceType } from '@signalk/server-api' -// SignalKResourceType= 'routes' | 'waypoints' |'notes' |'regions' |'charts' - interface ResourceProvider: { +<<<<<<< HEAD types: SignalKResourceType[], >>>>>>> chore: Updated documentation +======= + types: string[], +>>>>>>> chore: update docs methods: { listResources: (type:string, query: {[key:string]:any})=> Promise getResource: (type:string, id:string)=> Promise @@ -150,8 +161,12 @@ where: ======= where: +<<<<<<< HEAD - `types`: An array containing a list of __Common__ resource types provided for by the plugin >>>>>>> chore: Updated documentation +======= +- `types`: An array containing a list of resource types provided for by the plugin. These can be a mixture of both __Common__ and __Custom__ resource types. +>>>>>>> chore: update docs - `methods`: An object containing the methods resource requests are passed to by the SignalK server. The plugin __MUST__ implement each method, even if that operation is not supported by the plugin! #### __Method Details:__ @@ -161,6 +176,7 @@ __`listResources(type, query)`__: This method is called when a request is made f _Note: It is the responsibility of the resource provider plugin to filter the resources returned as per the supplied query parameters._ +<<<<<<< HEAD <<<<<<< HEAD `type:` String containing the type of resource to retrieve. @@ -170,6 +186,11 @@ _Note: It is the responsibility of the resource provider plugin to filter the re `query:` Object contining `key | value` pairs repesenting the parameters by which to filter the returned entries _e.g. {distance,'50000}_ >>>>>>> chore: Updated documentation +======= +`type:` String containing the type of resource to retrieve. + +`query:` Object contining `key | value` pairs repesenting the parameters by which to filter the returned entries. _e.g. {distance,'50000}_ +>>>>>>> chore: update docs `returns:` - Resolved Promise containing a list of resource entries on completion. @@ -195,6 +216,7 @@ listResources( --- __`getResource(type, id)`__: This method is called when a request is made for a specific resource entry of the supplied resource type and id. +<<<<<<< HEAD <<<<<<< HEAD `type:` String containing the type of resource to retrieve. @@ -204,6 +226,11 @@ __`getResource(type, id)`__: This method is called when a request is made for a `id:` String containing the target resource entry id _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ >>>>>>> chore: Updated documentation +======= +`type:` String containing the type of resource to retrieve. + +`id:` String containing the target resource entry id. _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ +>>>>>>> chore: update docs `returns:` - Resolved Promise containing the resource entry on completion. @@ -373,9 +400,9 @@ getResource( --- __`setResource(type, id, value)`__: This method is called when a request is made to save / update a resource entry of the specified resource type, with the supplied id and data. -`type:` Array of __Common__ resource types provied for by the plugin _e.g. ['routes','waypoints']_ +`type:` String containing the type of resource to store. -`id:` String containing the target resource entry id _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ +`id:` String containing the target resource entry id. _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ `value:` Resource data to be stored. @@ -414,9 +441,9 @@ setResource( --- __`deleteResource(type, id)`__: This method is called when a request is made to remove the specific resource entry of the supplied resource type and id. -`type:` String containing one of the __Common__ resource types _e.g. 'waypoints'_ +`type:` String containing the type of resource to delete. -`id:` String containing the target resource entry id _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ +`id:` String containing the target resource entry id. _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ `returns:` - Resolved Promise on completion. @@ -435,76 +462,10 @@ deleteResource( ); ``` +### Registering a Resource Provider: --- -### Example: - -_Defintion for Resource Provider plugin providing for the retrieval routes & waypoints._ -```javascript -// SignalK server plugin -module.exports = function (app) { - - let plugin= { - id: 'mypluginid', - name: 'My Resource Providerplugin', - // ResourceProvider interface - resourceProvider: { - types: ['routes','waypoints'], - methods: { - listResources: (type, params)=> { - return new Promise( (resolve, reject) => { - // fetch resource entries from storage - .... - if(ok) { // success - resolve({ - 'id1': { ... }, - 'id2': { ... }, - }); - } else { // error - reject(new Error('Error encountered!') - } - } - }, - getResource: (type, id)=> { - // fetch resource entries from storage - .... - if(ok) { // success - return Promise.resolve({ - ... - }); - } else { // error - reject(new Error('Error encountered!') - } - }, - setResource: (type, id, value)=> { - // not implemented - return Promise.reject(new Error('NOT IMPLEMENTED!')); - }, - deleteResource: (type, id)=> { - // not implemented - return Promise.reject(new Error('NOT IMPLEMENTED!')); - } - } - }, - - start: (options)=> { - ... - app.resourceApi.register(this.id, this.resourceProvider); - }, - - stop: ()=> { - app.resourceApi.unRegister(this.id); - ... - } - } -} -``` - - -### Registering the Resource Provider: ---- - -For the plugin to be registered by the SignalK server, it needs to call the server's `resourcesApi.register()` function during plugin startup. +To register the resource provider plugin with the SignalK server, the server's `resourcesApi.register()` function should be called during plugin startup. The server `resourcesApi.register()` function has the following signature: @@ -515,7 +476,7 @@ where: - `pluginId`: is the plugin's id - `resourceProvider`: is a reference to the plugins ResourceProvider interface. -_Note: A resource type can only have one registered plugin, so if more than one installed plugin attempts to register as a provider for the same resource type, only the first one is registered and it's implemented methods will service requests._ +_Note: A resource type can only have one registered plugin, so if more than one plugin attempts to register as a provider for the same resource type, the first plugin to call the `register()` function will be registered by the server for the resource types defined in the ResourceProvider interface!_ _Example:_ ```javascript @@ -545,7 +506,7 @@ module.exports = function (app) { ### Un-registering the Resource Provider: --- -When a resource provider plugin is disabled, it should un-register as a provider to ensure resource requests are no longer directed to it by calling the SignalK server's `resourcesApi.unRegister()` function during shutdown. +When a resource provider plugin is disabled, it should un-register itself to ensure resource requests are no longer directed to it by calling the SignalK server. This should be done by calling the server's `resourcesApi.unRegister()` function during shutdown. The server `resourcesApi.unRegister()` function has the following signature: @@ -575,113 +536,73 @@ module.exports = function (app) { } } ``` ---- - -## Custom Resource Type Provider: - -Custom resource types are collections of resource entries residing within a sub-path with a user defined name under `/signalk/v1/api/resources`. - -_Example:_ -``` -/signalk/v1/api/resources/fishingZones -``` - -_Note: A custom resource path name __CANNOT__ be one of the __Common__ resource types i.e. `routes`, `waypoints`, `notes`, `regions` or `charts`.__ - -Unlike the __Common Resource Type Providers__: -- The plugin __DOES NOT__ implement the `ResourceProvider` interface -- Requests to __Custom__ resource type sub-paths and any submitted data __WILL NOT__ be pre-processed or validated by the Signal K server -- The plugin __WILL__ need to implement a route handler for the necessary path(s) -- The plugin __WILL__ need to implement any necessary data validation. - -### Router Path Handlers ---- - -To set up a router path handler, you use the reference to the SignalK server `app` passed to the plugin to register the paths for which requests will be passed to the plugin. +--- -This should be done during plugin startup within the plugin `start()` function. +### __Example:__ -_Note: To ensure that your resource path is listed when a request to `/signalk/v1/api/resources` is made ensure you use the full path as outlined in the example below._ +Resource Provider plugin providing for the retrieval of routes & waypoints. -_Example:_ ```javascript - +// SignalK server plugin module.exports = function (app) { let plugin= { id: 'mypluginid', name: 'My Resource Providerplugin', - start: (options) => { - // setup router path handlers - initPathHandlers(app); - ... - } - } - - function initPathHandlers(app) { - app.get( - `/signalk/v1/api/resources/myResType`, - (request, response)=> { - // retrieve resource(s) - let result= getMyResources(); - response.status(200).json(result); - } - ); - app.post( - `/signalk/v1/api/resources/myResType`, - (request, response)=> { - // create new resource - ... - } - ); - router.put( - `/signalk/v1/api/resources/myResType/:id`, - (request, response)=> { - // create / update resource with supplied id - ... - } - ); - router.delete( - `/signalk/v1/api/resources/myResType/:id`, - (request, response)=> { - // delete the resource with supplied id - ... + // ResourceProvider interface + resourceProvider: { + types: ['routes','waypoints'], + methods: { + listResources: (type, params)=> { + return new Promise( (resolve, reject) => { + // fetch resource entries from storage + .... + if(ok) { // success + resolve({ + 'id1': { ... }, + 'id2': { ... }, + }); + } else { // error + reject(new Error('Error encountered!') + } + } + }, + getResource: (type, id)=> { + // fetch resource entries from storage + .... + if(ok) { // success + return Promise.resolve({ + ... + }); + } else { // error + reject(new Error('Error encountered!') + } + }, + setResource: (type, id, value)=> { + // not implemented + return Promise.reject(new Error('NOT IMPLEMENTED!')); + }, + deleteResource: (type, id)=> { + // not implemented + return Promise.reject(new Error('NOT IMPLEMENTED!')); + } } - ); - } - -``` - -Once registered requests to `/signalk/v1/api/resources/myResType` will be passed to the respective handler based on the request type _(i.e. GET, PUT, POST, DELETE)_ and the request details are available in the `request` parameter. - -For more information regarding the `request` and `response` parameters see the [Express Routing](https://expressjs.com/en/guide/routing.html) documentation. - -### Data Validation ---- + }, -When providing for resource data to be persisted to storage (PUT, POST) it is recommended that data validation implemented within the `app.put()` and `app.post()` route handlers and that the appropriate `status` is returned. + start: (options)=> { + ... + app.resourceApi.register(this.id, this.resourceProvider); + }, -_Example:_ -```javascript -app.post( - `/signalk/v1/api/resources/myResType`, - (request, response)=> { - // validate submitted data - let ok= validate(request.body); - if (ok) { //valid data - if (saveResource(request.body)) { - response.status(200).send('OK'); - } else { - response.status(404).send('ERROR svaing resource!'); - } - } else { - response.status(406).send('ERROR: Invalid data!'); + stop: ()=> { + app.resourceApi.unRegister(this.id); + ... } } -) - +} ``` +<<<<<<< HEAD --- <<<<<<< HEAD @@ -806,3 +727,5 @@ module.exports = function (app) { ``` ======= >>>>>>> Added Resource_Provider documentation +======= +>>>>>>> chore: update docs From 3844b06df129a4067ec733692d9b702172f7e001 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Thu, 25 Nov 2021 14:34:17 +1030 Subject: [PATCH 300/410] chore: lint --- src/api/resources/index.ts | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index a14c593aa..9577df9d9 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -167,6 +167,7 @@ export class Resources { req.params.resourceType as SignalKResourceType ) ) { +<<<<<<< HEAD if (!validate.resource(req.params.resourceType, req.body)) { res.status(406).send(`Invalid resource data supplied!`) return @@ -707,6 +708,8 @@ export class Resources { ======= if (this.signalkResTypes.includes(req.params.resourceType as SignalKResourceType)) { +======= +>>>>>>> chore: lint if (!validate.resource(req.params.resourceType, req.body)) { res.status(406).send(`Invalid resource data supplied!`) return @@ -719,7 +722,7 @@ export class Resources { id = req.body.identifier } else { id = UUID_PREFIX + uuidv4() - } + } try { const retVal = await this.resProvider[ @@ -760,7 +763,11 @@ export class Resources { <<<<<<< HEAD ======= - if (this.signalkResTypes.includes(req.params.resourceType as SignalKResourceType)) { + if ( + this.signalkResTypes.includes( + req.params.resourceType as SignalKResourceType + ) + ) { let isValidId: boolean if (req.params.resourceType === 'charts') { isValidId = validate.chartId(req.params.resourceId) @@ -936,8 +943,10 @@ export class Resources { private getResourcePaths(): { [key: string]: any } { const resPaths: { [key: string]: any } = {} - for( let i in this.resProvider) { - resPaths[i] = `Path containing ${i.slice(-1)==='s' ? i.slice(0, i.length-1) : i} resources (provided by ${this.resProvider[i]?.pluginId})` + for (const i in this.resProvider) { + resPaths[i] = `Path containing ${ + i.slice(-1) === 's' ? i.slice(0, i.length - 1) : i + } resources (provided by ${this.resProvider[i]?.pluginId})` } return resPaths } @@ -946,8 +955,8 @@ export class Resources { debug(`** checkForProvider(${resType})`) debug(this.resProvider[resType]) - if(this.resProvider[resType]) { - if( + if (this.resProvider[resType]) { + if ( !this.resProvider[resType]?.listResources || !this.resProvider[resType]?.getResource || !this.resProvider[resType]?.setResource || @@ -955,14 +964,13 @@ export class Resources { typeof this.resProvider[resType]?.listResources !== 'function' || typeof this.resProvider[resType]?.getResource !== 'function' || typeof this.resProvider[resType]?.setResource !== 'function' || - typeof this.resProvider[resType]?.deleteResource !== 'function' ) - { + typeof this.resProvider[resType]?.deleteResource !== 'function' + ) { return false } else { return true } - } - else { + } else { return false } } From 4ca91f3ab86c62fdfce477a67df4b004d56d71b5 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 27 Nov 2021 16:06:13 +1030 Subject: [PATCH 301/410] update types --- packages/server-api/src/index.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/server-api/src/index.ts b/packages/server-api/src/index.ts index f6b1808dc..bc5ac6b7a 100644 --- a/packages/server-api/src/index.ts +++ b/packages/server-api/src/index.ts @@ -6,6 +6,7 @@ export { PropertyValue, PropertyValues, PropertyValuesCallback } from './propert export type SignalKResourceType= 'routes' | 'waypoints' |'notes' |'regions' |'charts' <<<<<<< HEAD +<<<<<<< HEAD export type ResourceTypes= SignalKResourceType[] | string[] export interface ResourceProviderMethods { @@ -15,6 +16,12 @@ export interface ResourceProviderMethods { export interface ResourceProviderMethods { pluginId: string >>>>>>> add ResourceProvider types to server-api +======= +export type ResourceTypes= SignalKResourceType[] | string[] + +export interface ResourceProviderMethods { + pluginId?: string +>>>>>>> update types listResources: (type: string, query: { [key: string]: any }) => Promise getResource: (type: string, id: string) => Promise setResource: ( @@ -26,11 +33,15 @@ export interface ResourceProviderMethods { } export interface ResourceProvider { +<<<<<<< HEAD <<<<<<< HEAD types: ResourceTypes ======= types: SignalKResourceType[] >>>>>>> add ResourceProvider types to server-api +======= + types: ResourceTypes +>>>>>>> update types methods: ResourceProviderMethods } From b376130585e8bd1b91fb870e70e052d79e1c032b Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 27 Nov 2021 16:08:14 +1030 Subject: [PATCH 302/410] align response formatting forGET ./resources/ --- src/api/resources/index.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 9577df9d9..83bdbf9bc 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -944,9 +944,12 @@ export class Resources { private getResourcePaths(): { [key: string]: any } { const resPaths: { [key: string]: any } = {} for (const i in this.resProvider) { - resPaths[i] = `Path containing ${ - i.slice(-1) === 's' ? i.slice(0, i.length - 1) : i - } resources (provided by ${this.resProvider[i]?.pluginId})` + resPaths[i] = { + description: `Path containing ${ + i.slice(-1) === 's' ? i.slice(0, i.length - 1) : i + } resources`, + $source: this.resProvider[i]?.pluginId + } } return resPaths } From 3ef8c7322004a3c328e0ba13fc152c73197d6678 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 1 Dec 2021 10:15:08 +1030 Subject: [PATCH 303/410] add resourceApi to index.ts after rebase --- src/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/index.ts b/src/index.ts index 816df5389..6718a7e7a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -46,6 +46,8 @@ import SubscriptionManager from './subscriptionmanager' import { Delta } from './types' const debug = createDebug('signalk-server') +import { Resources } from './api/resources' + // tslint:disable-next-line: no-var-requires const { StreamBundle } = require('./streambundle') @@ -76,6 +78,8 @@ class Server { require('./serverroutes')(app, saveSecurityConfig, getSecurityConfig) require('./put').start(app) + app.resourcesApi = new Resources(app) + app.signalk = new FullSignalK(app.selfId, app.selfType) app.propertyValues = new PropertyValues() From f768c7fa26550cb73c13598cf31f1c9165a1418f Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Fri, 3 Dec 2021 15:09:37 +1030 Subject: [PATCH 304/410] add charts API methods --- src/api/resources/index.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 83bdbf9bc..0a05102f9 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -1,16 +1,22 @@ import Debug from 'debug' +import { Application, NextFunction, Request, Response } from 'express' import { v4 as uuidv4 } from 'uuid' <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD import { buildResource } from './resources' import { validate } from './validate' +======= +>>>>>>> add charts API methods import { ResourceProvider, ResourceProviderMethods, SignalKResourceType } from '@signalk/server-api' -import { Application, Handler, NextFunction, Request, Response } from 'express' + +import { buildResource } from './resources' +import { validate } from './validate' const debug = Debug('signalk:resources') @@ -25,7 +31,9 @@ const API_METHODS = [ 'setNote', 'deleteNote', 'setRegion', - 'deleteRegion' + 'deleteRegion', + 'setChart', + 'deleteChart' ] interface ResourceApplication extends Application { @@ -356,6 +364,9 @@ export class Resources { if (req.params.apiFunction.toLowerCase().indexOf('region') !== -1) { resType = 'regions' } + if (req.params.apiFunction.toLowerCase().indexOf('charts') !== -1) { + resType = 'charts' + } if (!this.checkForProvider(resType)) { res.status(501).send(`No provider for ${resType}!`) return From c00fd8ded086c37a51d2c464bbfaef7b41902c09 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 5 Dec 2021 15:52:33 +1030 Subject: [PATCH 305/410] add securityStrategy test --- src/api/resources/index.ts | 189 +++++++++++++++++++++++++++---------- 1 file changed, 140 insertions(+), 49 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 0a05102f9..9d5244174 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -1,3 +1,4 @@ +<<<<<<< HEAD import Debug from 'debug' import { Application, NextFunction, Request, Response } from 'express' import { v4 as uuidv4 } from 'uuid' @@ -9,11 +10,16 @@ import { validate } from './validate' ======= >>>>>>> add charts API methods +======= +>>>>>>> add securityStrategy test import { ResourceProvider, ResourceProviderMethods, SignalKResourceType } from '@signalk/server-api' +import Debug from 'debug' +import { Application, NextFunction, Request, Response } from 'express' +import { v4 as uuidv4 } from 'uuid' import { buildResource } from './resources' import { validate } from './validate' @@ -23,21 +29,16 @@ const debug = Debug('signalk:resources') const SIGNALK_API_PATH = `/signalk/v1/api` const UUID_PREFIX = 'urn:mrn:signalk:uuid:' -const API_METHODS = [ - 'setWaypoint', - 'deleteWaypoint', - 'setRoute', - 'deleteRoute', - 'setNote', - 'deleteNote', - 'setRegion', - 'deleteRegion', - 'setChart', - 'deleteChart' -] - interface ResourceApplication extends Application { handleMessage: (id: string, data: any) => void + securityStrategy: { + shouldAllowPut: ( + req: any, + context: string, + source: any, + path: string + ) => boolean + } } export class Resources { @@ -102,6 +103,15 @@ export class Resources { this.initResourceRoutes() } + private updateAllowed(): boolean { + return this.server.securityStrategy.shouldAllowPut( + this.server, + 'vessels.self', + null, + 'resources' + ) + } + private initResourceRoutes() { // list all serviced paths under resources this.server.get( @@ -162,6 +172,7 @@ export class Resources { `${SIGNALK_API_PATH}/resources/:resourceType`, async (req: Request, res: Response, next: NextFunction) => { debug(`** POST ${SIGNALK_API_PATH}/resources/:resourceType`) + if ( !this.checkForProvider(req.params.resourceType as SignalKResourceType) ) { @@ -170,6 +181,10 @@ export class Resources { return } + if (!this.updateAllowed()) { + res.status(403) + return + } if ( this.signalkResTypes.includes( req.params.resourceType as SignalKResourceType @@ -231,6 +246,10 @@ export class Resources { return } + if (!this.updateAllowed()) { + res.status(403) + return + } if ( this.signalkResTypes.includes( req.params.resourceType as SignalKResourceType @@ -302,6 +321,10 @@ export class Resources { return } + if (!this.updateAllowed()) { + res.status(403) + return + } try { const retVal = await this.resProvider[ req.params.resourceType @@ -342,38 +365,66 @@ export class Resources { ======= // facilitate API requests this.server.put( - `${SIGNALK_API_PATH}/resources/:apiFunction`, - async (req: Request, res: Response, next: NextFunction) => { - debug(`** PUT ${SIGNALK_API_PATH}/resources/:apiFunction`) + `${SIGNALK_API_PATH}/resources/set/:resourceType`, + async (req: Request, res: Response) => { + debug(`** PUT ${SIGNALK_API_PATH}/resources/set/:resourceType`) - // check for valid API method request - if (!API_METHODS.includes(req.params.apiFunction)) { - res.status(501).send(`Invalid API method ${req.params.apiFunction}!`) + if (!this.updateAllowed()) { + res.status(403) return } - let resType: SignalKResourceType = 'waypoints' - if (req.params.apiFunction.toLowerCase().indexOf('waypoint') !== -1) { - resType = 'waypoints' - } - if (req.params.apiFunction.toLowerCase().indexOf('route') !== -1) { - resType = 'routes' + + const apiData = this.processApiRequest(req) + + if (!this.checkForProvider(apiData.type)) { + res.status(501).send(`No provider for ${apiData.type}!`) + return } - if (req.params.apiFunction.toLowerCase().indexOf('note') !== -1) { - resType = 'notes' + if (!apiData.value) { + res.status(406).send(`Invalid resource data supplied!`) + return } - if (req.params.apiFunction.toLowerCase().indexOf('region') !== -1) { - resType = 'regions' + if (apiData.type === 'charts') { + if (!validate.chartId(apiData.id)) { + res.status(406).send(`Invalid chart resource id supplied!`) + return + } + } else { + if (!validate.uuid(apiData.id)) { + res.status(406).send(`Invalid resource id supplied!`) + return + } } - if (req.params.apiFunction.toLowerCase().indexOf('charts') !== -1) { - resType = 'charts' + + try { + const retVal = await this.resProvider[apiData.type]?.setResource( + apiData.type, + apiData.id, + apiData.value + ) + this.server.handleMessage( + this.resProvider[apiData.type]?.pluginId as string, + this.buildDeltaMsg(apiData.type, apiData.id, apiData.value) + ) + res.status(200).send(`SUCCESS: ${req.params.resourceType} complete.`) + } catch (err) { + res.status(404).send(`ERROR: ${req.params.resourceType} incomplete!`) } - if (!this.checkForProvider(resType)) { - res.status(501).send(`No provider for ${resType}!`) + } + ) + this.server.put( + `${SIGNALK_API_PATH}/resources/set/:resourceType/:resourceId`, + async (req: Request, res: Response) => { + debug( + `** PUT ${SIGNALK_API_PATH}/resources/set/:resourceType/:resourceId` + ) + + if (!this.updateAllowed()) { + res.status(403) return } - let resId: string = '' - let resValue: any = null +<<<<<<< HEAD if (req.params.apiFunction.toLowerCase().indexOf('set') !== -1) { resValue = buildResource(resType, req.body) if (!resValue) { @@ -920,38 +971,78 @@ export class Resources { } resId = req.body.id } +======= + const apiData = this.processApiRequest(req) + + if (!this.checkForProvider(apiData.type)) { + res.status(501).send(`No provider for ${apiData.type}!`) + return } - if (req.params.apiFunction.toLowerCase().indexOf('delete') !== -1) { - resValue = null - if (!req.body.id) { - res.status(406).send(`No resource id supplied!`) + if (!apiData.value) { + res.status(406).send(`Invalid resource data supplied!`) + return +>>>>>>> add securityStrategy test + } + if (apiData.type === 'charts') { + if (!validate.chartId(apiData.id)) { + res.status(406).send(`Invalid chart resource id supplied!`) return } - if (!validate.uuid(req.body.id)) { + } else { + if (!validate.uuid(apiData.id)) { res.status(406).send(`Invalid resource id supplied!`) return } - resId = req.body.id } try { - const retVal = await this.resProvider[resType]?.setResource( - resType, - resId, - resValue + const retVal = await this.resProvider[apiData.type]?.setResource( + apiData.type, + apiData.id, + apiData.value ) this.server.handleMessage( - this.resProvider[resType]?.pluginId as string, - this.buildDeltaMsg(resType, resId, resValue) + this.resProvider[apiData.type]?.pluginId as string, + this.buildDeltaMsg(apiData.type, apiData.id, apiData.value) ) - res.status(200).send(`SUCCESS: ${req.params.apiFunction} complete.`) + res.status(200).send(`SUCCESS: ${req.params.resourceType} complete.`) } catch (err) { - res.status(404).send(`ERROR: ${req.params.apiFunction} incomplete!`) + res.status(404).send(`ERROR: ${req.params.resourceType} incomplete!`) } } ) } + private processApiRequest(req: Request) { + let resType: SignalKResourceType = 'waypoints' + if (req.params.resourceType.toLowerCase() === 'waypoint') { + resType = 'waypoints' + } + if (req.params.resourceType.toLowerCase() === 'route') { + resType = 'routes' + } + if (req.params.resourceType.toLowerCase() === 'note') { + resType = 'notes' + } + if (req.params.resourceType.toLowerCase() === 'region') { + resType = 'regions' + } + if (req.params.resourceType.toLowerCase() === 'charts') { + resType = 'charts' + } + + const resId: string = req.params.resourceId + ? req.params.resourceId + : UUID_PREFIX + uuidv4() + const resValue: any = buildResource(resType, req.body) + + return { + type: resType, + id: resId, + value: resValue + } + } + private getResourcePaths(): { [key: string]: any } { const resPaths: { [key: string]: any } = {} for (const i in this.resProvider) { From 3eaeccd5ef01b0db2bc96dc0263f2fb128a823bf Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 5 Dec 2021 17:39:56 +1030 Subject: [PATCH 306/410] chore: update docs --- RESOURCE_PROVIDER_PLUGINS.md | 2 +- WORKING_WITH_RESOURCES_API.md | 316 +++++++++++++++++++++++++++++++++ src/api/resources/openApi.json | 69 ++----- 3 files changed, 335 insertions(+), 52 deletions(-) create mode 100644 WORKING_WITH_RESOURCES_API.md diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index 883fe1ddf..f563fc9b5 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -20,7 +20,7 @@ It also defines the schema for the following __Common__ resource types: - regions - charts -each of with its own path under the root `resources` path _(e.g. `/signalk/v1/api/resources/routes`)_. +each with its own path under the root `resources` path _(e.g. `/signalk/v1/api/resources/routes`)_. It should also be noted that the `/signalk/v1/api/resources` path can also host other types of resource data which can be grouped within a __Custom__ path name _(e.g. `/signalk/v1/api/resources/fishingZones`)_. diff --git a/WORKING_WITH_RESOURCES_API.md b/WORKING_WITH_RESOURCES_API.md new file mode 100644 index 000000000..3034b0474 --- /dev/null +++ b/WORKING_WITH_RESOURCES_API.md @@ -0,0 +1,316 @@ +# Working with the Resources API + + +## Overview + +The SignalK specification defines a number of resources (routes, waypoints, notes, regions & charts) each with its path under the root `resources` path _(e.g. `/signalk/v1/api/resources/routes`)_. + +The SignalK server handles requests to these resource paths to enable the retrieval, creation, updating and deletion of resources. + +--- +## Operation: + +For resources to be stored and retrieved, the Signal K server requires that a [Resource Provider plugin](RESOURCE_PROVIDER_PLUGINS.md) be installed and registered to manage each of the resource types your implementation requires. _You can find plugins in the `App Store` section of the server admin UI._ + +Client applications can then use HTTP requests to resource paths to store and retrieve resource entries. _Note: the ability to store resource entries is controlled by the server security settings so client applications may need to authenticate for write / delete operations to complete successfully._ + + +### Retrieving Resources +--- + +Resource entries are retrived by submitting an HTTP `GET` request to the relevant path. + +_Example:_ +```typescript +HTTP GET 'http://hostname:3000/signalk/v1/api/resources/routes' +``` +to return a list of available routes OR +```typescript +HTTP GET 'http://hostname:3000/signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:94052456-65fa-48ce-a85d-41b78a9d2111' +``` +to retrieve a specific resource entry. + +When retrieving a list of entries these can be filtered based on certain criteria such as: + +- being within a bounded area +- distance from vessel +- total entries returned. + +This is done by supplying query string key | value pairs in the request. + +_Example 1: Retrieve waypoints within 50km of the vessel_ +```typescript +HTTP GET 'http://hostname:3000/signalk/v1/api/resources/waypoints?distance=50000' +``` +_Note: the distance supplied is in meters_. + +_Example 2: Retrieve the first 20 waypoints within 90km of the vessel_ +```typescript +HTTP GET 'http://hostname:3000/signalk/v1/api/resources/waypoints?distance=90000&limit=20' +``` +_Note: the distance supplied is in meters_. + +_Example 3: Retrieve waypoints within a bounded area._ +```typescript +HTTP GET 'http://hostname:3000/signalk/v1/api/resources/waypoints?bbox=-135.5,38,-134,38.5' +``` +_Note: the bounded area is supplied as bottom left & top right corner coordinates in the form swLongitude,swLatitude,neLongitude,neLatitude_. + + +### Deleting Resources +--- + +Resource entries are deleted by submitting an HTTP `DELETE` request to a path containing the id of the resource to delete. + +_Example:_ +```typescript +HTTP DELETE 'http://hostname:3000/signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:94052456-65fa-48ce-a85d-41b78a9d2111' +``` + +In this example the route with the supplied id is deleted from storage. + +### Creating / updating Resources +--- + +Resource entries are created and updated by submitting an HTTP `PUT` request that contains the resource data to the relevant API path depending on the type of resource to create / update. + +Each resource type has a specific set of attributes that are required to be supplied before the resource entry can be created. + +Generally a resource will be created by submitting the resource attributes and the server will generate a resource id for the entry. You can assign a specific id for a newly created resource by using an API path containing the id you wish to assign to the resource. _Note: if a resource of the same type with the supplied id already exists, it will be overwritten with the submitted data!_ + +___Note: When submitting data to create or update a resource entry, the submitted resource data is validated against the Signal K schema for that resource type. If the submitted data is deemed to be invalid then the operation is aborted.___ + +___Additionally when supplying an id to assign to or identify the resource on which to perform the operation, the id must be valid for the type of resource as defined in the Signal K schema.___ + +--- +#### __Routes:__ + +To create / update a route entry the body of the PUT request must contain data in the following format: +```javascript +{ + name: 'route name', + description: 'description of the route', + attributes: { + attribute1: 'attribute1 value', + attribute2: 258, + ... + }, + points: [ + {latitude: -38.567,longitude: 135.9467}, + {latitude: -38.967,longitude: 135.2467}, + {latitude: -39.367,longitude: 134.7467}, + {latitude: -39.567,longitude: 134.4467} + ] +} +``` +where: +- name: is text detailing the name of the route +- description (optional): is text describing the route +- attributes (optional): object containing key | value pairs of attributes associated with the route +- points: is an array of route points (latitude and longitude) + + +_Example: Create new route entry (with server generated id)_ +```typescript +HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/route' { + name: 'route name', + description: 'description of the route', + attributes: { + distance: 6580 + }, + points: [ + {latitude: -38.567,longitude: 135.9467}, + {latitude: -38.967,longitude: 135.2467}, + {latitude: -39.367,longitude: 134.7467}, + {latitude: -39.567,longitude: 134.4467} + ] +} +``` + +_Example: Create new route entry (with supplied id)_ +```typescript +HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/route/urn:mrn:signalk:uuid:94052456-65fa-48ce-a85d-41b78a9d2111' { + name: 'route name', + description: 'description of the route', + attributes: { + distance: 6580 + }, + points: [ + {latitude: -38.567,longitude: 135.9467}, + {latitude: -38.967,longitude: 135.2467}, + {latitude: -39.367,longitude: 134.7467}, + {latitude: -39.567,longitude: 134.4467} + ] +} +``` + +--- +#### __Waypoints:__ + +To create / update a waypoint entry the body of the PUT request must contain data in the following format: +```javascript +{ + name: 'waypoint name', + description: 'description of the waypoint', + attributes: { + attribute1: 'attribute1 value', + attribute2: 258, + ... + }, + position: { + latitude: -38.567, + longitude: 135.9467 + } +} +``` +where: +- name: is text detailing the name of the waypoint +- description (optional): is text describing the waypoint +- attributes (optional): object containing key | value pairs of attributes associated with the waypoint +- position: the latitude and longitude of the waypoint + + +_Example: Create new waypoint entry (with server generated id)_ +```typescript +HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/waypoint' { + name: 'waypoint #1', + position: { + latitude: -38.567, + longitude: 135.9467 + } +} +``` + +_Example: Create new waypoint entry (with supplied id)_ +```typescript +HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/waypoint/urn:mrn:signalk:uuid:94052456-65fa-48ce-a85d-41b78a9d2111' { + name: 'waypoint #1', + position: { + latitude: -38.567, + longitude: 135.9467 + } +} +``` + +--- +#### __Regions:__ + +To create / update a region entry the body of the PUT request must contain data in the following format: +```javascript +{ + name: 'region name', + description: 'description of the region', + attributes: { + attribute1: 'attribute1 value', + attribute2: 258, + ... + }, + points: [ + {latitude: -38.567,longitude: 135.9467}, + {latitude: -38.967,longitude: 135.2467}, + {latitude: -39.367,longitude: 134.7467}, + {latitude: -39.567,longitude: 134.4467}, + {latitude: -38.567,longitude: 135.9467} + ], + geohash: 'gbsuv' +} +``` +where: +- name: is text detailing the name of the region +- description (optional): is text describing the region +- attributes (optional): object containing key | value pairs of attributes associated with the region + +and either: +- points: is an array of points (latitude and longitude) defining a polygon. _Note: first and last point in the array must be the same!_ + +OR +- geohash: a value defining a bounded area _e.g. 'gbsuv'_ + + +_Example: Create new region entry (with server generated id)_ +```typescript +HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/region' { + name: 'region name', + description: 'description of the region', + points: [ + {latitude: -38.567,longitude: 135.9467}, + {latitude: -38.967,longitude: 135.2467}, + {latitude: -39.367,longitude: 134.7467}, + {latitude: -39.567,longitude: 134.4467}, + {latitude: -38.567,longitude: 135.9467} + ] +} +``` + +_Example: Create new region entry (with supplied id)_ +```typescript +HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/region/urn:mrn:signalk:uuid:94052456-65fa-48ce-a85d-41b78a9d2111' { + name: 'region name', + description: 'description of the region', + geohash: 'gbsuv' +} +``` + +--- +#### __Notes:__ + +To create / update a note entry the body of the PUT request must contain data in the following format: +```javascript +{ + title: 'note title text', + description: 'description of the note', + attributes: { + attribute1: 'attribute1 value', + attribute2: 258, + ... + }, + url: 'link to note content', + mimeType: 'text/plain, text/html, etc.', + position: { + latitude: -38.567, + longitude: 135.9467 + }, + geohash: 'gbsuv', + region: '/resources/regions/urn:mrn:signalk:uuid:35052456-65fa-48ce-a85d-41b78a9d2a61' +} +``` +where: +- name: is text detailing the name of the note +- description (optional): is text describing the note +- attributes (optional): object containing key | value pairs of attributes associated with the note +- url (optional): link to the note contents +- mimeType (optional): the mime type of the note contents + +and either: +- position: the latitude and longitude associated with the note + +OR +- geohash: a value defining a bounded area associated with the note _e.g. 'gbsuv'_ + +OR +- region: text containing a reference to a region resource associated with the note _e.g. '/resources/regions/urn:mrn:signalk:uuid:35052456-65fa-48ce-a85d-41b78a9d2a61'_ + + +_Example: Create new note entry (with server generated id)_ +```typescript +HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/note' { + title: 'note title', + description: 'text containing brief description', + url: 'http:notehost.com/notes/mynote.html', + mimeType: 'text/plain', + position: { + latitude: -38.567, + longitude: 135.9467 + } +} +``` + +_Example: Create new note entry (with supplied id)_ +```typescript +HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/note/urn:mrn:signalk:uuid:94052456-65fa-48ce-a85d-41b78a9d2111' { + title: 'note title', + description: 'text containing brief description', + region: '/resources/regions/urn:mrn:signalk:uuid:35052456-65fa-48ce-a85d-41b78a9d2a61' +} +``` + diff --git a/src/api/resources/openApi.json b/src/api/resources/openApi.json index 18262474c..c44bf16cd 100644 --- a/src/api/resources/openApi.json +++ b/src/api/resources/openApi.json @@ -2065,7 +2065,7 @@ }, - "/resources/setWaypoint": { + "/resources/set/waypoint/{id}": { "put": { "tags": ["resources/api"], "summary": "Add / update a Waypoint", @@ -2217,6 +2217,7 @@ } }, +<<<<<<< HEAD "/resources/charts/{id}": { "parameters": { "name": "id", @@ -2268,6 +2269,9 @@ }, >>>>>>> add API definitions +======= + "/resources/set/route/{id}": { +>>>>>>> chore: update docs "put": { "tags": ["resources/charts"], "summary": "Add / update a new Chart with supplied id", @@ -2430,6 +2434,7 @@ }, +<<<<<<< HEAD "/resources/setWaypoint": { "put": { "tags": ["resources/api"], @@ -3092,6 +3097,9 @@ >>>>>>> add OpenApi definition file ======= "/resources/setNote": { +======= + "/resources/set/note/{id}": { +>>>>>>> chore: update docs "put": { "tags": ["resources/api"], "summary": "Add / update a Note", @@ -3104,11 +3112,6 @@ "type": "object", "required": ["position"], "properties": { - "id": { - "type": "string", - "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", - "description": "Note identifier" - }, "title": { "type": "string", "description": "Note's common name" @@ -3176,45 +3179,7 @@ } }, - "/resources/deleteNote": { - "put": { - "tags": ["resources/api"], - "summary": "Remove a Note", - "requestBody": { - "description": "Note identifier", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["id"], - "properties": { - "id": { - "type": "string", - "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", - "description": "Note identifier" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - } - } - } - }, - - "/resources/setRegion": { + "/resources/set/region/{id}": { "put": { "tags": ["resources/api"], "summary": "Add / update a Region", @@ -3227,11 +3192,6 @@ "type": "object", "required": ["position"], "properties": { - "id": { - "type": "string", - "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", - "description": "Region identifier" - }, "name": { "type": "string", "description": "Region name" @@ -3292,6 +3252,7 @@ } }, +<<<<<<< HEAD "/resources/deleteRegion": { "put": { "tags": ["resources/api"], @@ -3334,6 +3295,9 @@ }, "/resources/setChart": { +======= + "/resources/set/chart/{id}": { +>>>>>>> chore: update docs "put": { "tags": ["resources/api"], "summary": "Add / update a Chart", @@ -3379,7 +3343,7 @@ ], "region": { "type": "string", - "description": "Region related to note. A pointer to a region UUID. Alternative to geohash" + "description": "Region related to chart. A pointer to a region UUID. Alternative to geohash" }, "geohash": { "description": "Position related to chart. Alternative to region", @@ -3463,6 +3427,7 @@ } } } +<<<<<<< HEAD }, "/resources/deleteChart": { @@ -3502,6 +3467,8 @@ } } >>>>>>> add charts API methods +======= +>>>>>>> chore: update docs } } From ccb6dff686acca512cec51cd4c363559ab94e861 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 6 Dec 2021 11:05:41 +1030 Subject: [PATCH 307/410] chore: update docs --- WORKING_WITH_RESOURCES_API.md | 34 +++++-------- src/api/resources/openApi.json | 93 +++++++++++----------------------- 2 files changed, 42 insertions(+), 85 deletions(-) diff --git a/WORKING_WITH_RESOURCES_API.md b/WORKING_WITH_RESOURCES_API.md index 3034b0474..54019dd61 100644 --- a/WORKING_WITH_RESOURCES_API.md +++ b/WORKING_WITH_RESOURCES_API.md @@ -91,8 +91,6 @@ To create / update a route entry the body of the PUT request must contain data i name: 'route name', description: 'description of the route', attributes: { - attribute1: 'attribute1 value', - attribute2: 258, ... }, points: [ @@ -153,8 +151,6 @@ To create / update a waypoint entry the body of the PUT request must contain dat name: 'waypoint name', description: 'description of the waypoint', attributes: { - attribute1: 'attribute1 value', - attribute2: 258, ... }, position: { @@ -201,8 +197,6 @@ To create / update a region entry the body of the PUT request must contain data name: 'region name', description: 'description of the region', attributes: { - attribute1: 'attribute1 value', - attribute2: 258, ... }, points: [ @@ -211,20 +205,14 @@ To create / update a region entry the body of the PUT request must contain data {latitude: -39.367,longitude: 134.7467}, {latitude: -39.567,longitude: 134.4467}, {latitude: -38.567,longitude: 135.9467} - ], - geohash: 'gbsuv' + ] } ``` where: - name: is text detailing the name of the region - description (optional): is text describing the region - attributes (optional): object containing key | value pairs of attributes associated with the region - -and either: -- points: is an array of points (latitude and longitude) defining a polygon. _Note: first and last point in the array must be the same!_ - -OR -- geohash: a value defining a bounded area _e.g. 'gbsuv'_ +- points: is an array of points (latitude and longitude) defining an area. _Example: Create new region entry (with server generated id)_ @@ -247,7 +235,13 @@ _Example: Create new region entry (with supplied id)_ HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/region/urn:mrn:signalk:uuid:94052456-65fa-48ce-a85d-41b78a9d2111' { name: 'region name', description: 'description of the region', - geohash: 'gbsuv' + points: [ + {latitude: -38.567,longitude: 135.9467}, + {latitude: -38.967,longitude: 135.2467}, + {latitude: -39.367,longitude: 134.7467}, + {latitude: -39.567,longitude: 134.4467}, + {latitude: -38.567,longitude: 135.9467} + ] } ``` @@ -270,8 +264,7 @@ To create / update a note entry the body of the PUT request must contain data in latitude: -38.567, longitude: 135.9467 }, - geohash: 'gbsuv', - region: '/resources/regions/urn:mrn:signalk:uuid:35052456-65fa-48ce-a85d-41b78a9d2a61' + href: 'reference to resource entry' } ``` where: @@ -285,10 +278,7 @@ and either: - position: the latitude and longitude associated with the note OR -- geohash: a value defining a bounded area associated with the note _e.g. 'gbsuv'_ - -OR -- region: text containing a reference to a region resource associated with the note _e.g. '/resources/regions/urn:mrn:signalk:uuid:35052456-65fa-48ce-a85d-41b78a9d2a61'_ +- href: text containing a reference to a resource associated with the note _e.g. '/resources/regions/urn:mrn:signalk:uuid:35052456-65fa-48ce-a85d-41b78a9d2a61'_ _Example: Create new note entry (with server generated id)_ @@ -310,7 +300,7 @@ _Example: Create new note entry (with supplied id)_ HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/note/urn:mrn:signalk:uuid:94052456-65fa-48ce-a85d-41b78a9d2111' { title: 'note title', description: 'text containing brief description', - region: '/resources/regions/urn:mrn:signalk:uuid:35052456-65fa-48ce-a85d-41b78a9d2a61' + href: '/resources/regions/urn:mrn:signalk:uuid:35052456-65fa-48ce-a85d-41b78a9d2a61' } ``` diff --git a/src/api/resources/openApi.json b/src/api/resources/openApi.json index c44bf16cd..d8ab778b6 100644 --- a/src/api/resources/openApi.json +++ b/src/api/resources/openApi.json @@ -168,7 +168,7 @@ }, "/resources/routes/": { - "post": { + "put": { "tags": ["resources/routes"], "summary": "Add a new Route", "requestBody": { @@ -556,7 +556,7 @@ }, "/resources/waypoints/": { - "post": { + "put": { "tags": ["resources/waypoints"], "summary": "Add a new Waypoint", "requestBody": { @@ -958,7 +958,7 @@ }, "/resources/notes/": { - "post": { + "put": { "tags": ["resources/notes"], "summary": "Add a new Note", "requestBody": { @@ -1254,7 +1254,7 @@ }, "/resources/regions/": { - "post": { + "put": { "tags": ["resources/regions"], <<<<<<< HEAD <<<<<<< HEAD @@ -1762,7 +1762,7 @@ }, "/resources/charts/": { - "post": { + "put": { "tags": ["resources/charts"], "summary": "Add a new Chart", <<<<<<< HEAD @@ -2282,8 +2282,12 @@ "application/json": { "schema": { "type": "object", +<<<<<<< HEAD "description": "Signal K Chart resource", "required": ["feature"], +======= + "required": ["title"], +>>>>>>> chore: update docs "properties": { "name": { "description": "Chart common name", @@ -3140,10 +3144,6 @@ } } }, - "geohash": { - "type": "string", - "description": "Position related to note. Alternative to region or position" - }, "region": { "type": "string", "description": "Region related to note. A pointer to a region UUID. Alternative to position or geohash", @@ -3207,10 +3207,6 @@ "type": "string" } }, - "geohash": { - "type": "string", - "description": "Area related to region. Alternative to points." - }, "points": { "description": "Region boundary points", "type": "array", @@ -3309,51 +3305,32 @@ "schema": { "type": "object", "description": "Signal K Chart resource", - "required": ["identifier","tilemapUrl","chartUrl","chartFormat"], + "required": ["identifier", "tilemapUrl"], "properties": { - "name": { - "description": "Chart common name", - "example":"NZ615 Marlborough Sounds", - "type": "string" - }, "identifier": { "type": "string", "description": "Chart number", "example":"NZ615" }, + "name": { + "description": "Chart common name", + "example":"NZ615 Marlborough Sounds", + "type": "string" + }, "description": { "type": "string", "description": "A description of the chart" }, - "oneOf": [ - { - "tilemapUrl": { - "type": "string", - "description": "A url to the tilemap of the chart for use in TMS chartplotting apps", - "example":"http://{server}:8080/mapcache/NZ615" - } - }, - { - "chartUrl": { - "type": "string", - "description": "A url to the chart file's storage location", - "example":"file:///home/pi/freeboard/mapcache/NZ615" - } - } - ], - "region": { + "tilemapUrl": { "type": "string", - "description": "Region related to chart. A pointer to a region UUID. Alternative to geohash" - }, - "geohash": { - "description": "Position related to chart. Alternative to region", - "type": "string" + "description": "A url to the tilemap of the chart for use in TMS chartplotting apps", + "example":"http://{server}:8080/mapcache/NZ615" }, "scale": { "type": "integer", "description": "The scale of the chart, the larger number from 1:200000" }, - "chartLayers": { + "layers": { "type": "array", "description": "If the chart format is WMS, the layers enabled for the chart.", "items": { @@ -3363,7 +3340,7 @@ }, "bounds": { "type": "array", - "description": "The bounds of the chart. An array containing the position of the upper left corner, and the lower right corner. Useful when the chart isn't inherently geo-referenced.", + "description": "The bounding rectangle of the chart defined by the position of the lower left corner, and the upper right corner.", "items": { "description": "Position of a corner of the chart", "type": "object", @@ -3379,34 +3356,24 @@ "longitude": { "type": "number", "format": "float" - }, - "altitude": { - "type": "number", - "format": "float" } } } }, - "chartFormat": { + "format": { "type": "string", "description": "The format of the chart", "enum": [ - "gif", - "geotiff", - "kap", "png", - "jpg", - "kml", - "wkt", - "topojson", - "geojson", - "gpx", - "tms", - "wms", - "S-57", - "S-63", - "svg", - "other" + "jpg" + ] + }, + "serverType": { + "type": "string", + "description": "Chart server type", + "enum": [ + "tilelayer", + "WMS" ] } } From b7e7c4d711f313b11c6a92a840df2daad32ac6cf Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 6 Dec 2021 11:06:17 +1030 Subject: [PATCH 308/410] fix chart identifier --- src/api/resources/index.ts | 10 ++- src/api/resources/resources.ts | 141 ++++++++++++++++++++++----------- 2 files changed, 101 insertions(+), 50 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 9d5244174..879482b3c 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -168,7 +168,7 @@ export class Resources { ) // facilitate creation of new resource entry of supplied type - this.server.post( + this.server.put( `${SIGNALK_API_PATH}/resources/:resourceType`, async (req: Request, res: Response, next: NextFunction) => { debug(`** POST ${SIGNALK_API_PATH}/resources/:resourceType`) @@ -1014,6 +1014,7 @@ export class Resources { } private processApiRequest(req: Request) { + let resType: SignalKResourceType = 'waypoints' if (req.params.resourceType.toLowerCase() === 'waypoint') { resType = 'waypoints' @@ -1031,10 +1032,13 @@ export class Resources { resType = 'charts' } + const resValue: any = buildResource(resType, req.body) + const resId: string = req.params.resourceId ? req.params.resourceId - : UUID_PREFIX + uuidv4() - const resValue: any = buildResource(resType, req.body) + : resType = 'charts' + ? resValue.identifier + : UUID_PREFIX + uuidv4() return { type: resType, diff --git a/src/api/resources/resources.ts b/src/api/resources/resources.ts index 71efc2106..4b4cf5b23 100644 --- a/src/api/resources/resources.ts +++ b/src/api/resources/resources.ts @@ -4,8 +4,7 @@ ======= >>>>>>> fix type import { SignalKResourceType } from '@signalk/server-api' -import { getDistance, isValidCoordinate } from 'geolib' -import ngeohash from 'ngeohash' +import { getDistance, getLatitude, isValidCoordinate } from 'geolib' <<<<<<< HEAD <<<<<<< HEAD @@ -22,6 +21,10 @@ export const buildResource = (resType: SignalKResourceType, data: any): any => { if (resType === 'regions') { return buildRegion(data) } + if (resType === 'charts') { + return buildChart(data) + } + return null } const buildRoute = (rData: any): any => { @@ -129,8 +132,7 @@ const buildNote = (rData: any): any => { } if ( typeof rData.position === 'undefined' && - typeof rData.region === 'undefined' && - typeof rData.geohash === 'undefined' + typeof rData.href === 'undefined' ) { return null } @@ -141,11 +143,8 @@ const buildNote = (rData: any): any => { } note.position = rData.position } - if (typeof rData.region !== 'undefined') { - note.region = rData.region - } - if (typeof rData.geohash !== 'undefined') { - note.geohash = rData.geohash + if (typeof rData.href !== 'undefined') { + note.region = rData.href } if (typeof rData.url !== 'undefined') { note.url = rData.url @@ -180,49 +179,34 @@ const buildRegion = (rData: any): any => { Object.assign(reg.feature.properties, rData.attributes) } - if (typeof rData.points === 'undefined' && rData.geohash === 'undefined') { + if (typeof rData.points !== 'undefined') { return null } - if (typeof rData.geohash !== 'undefined') { - reg.geohash = rData.geohash - - const bounds = ngeohash.decode_bbox(rData.geohash) - coords = [ - [bounds[1], bounds[0]], - [bounds[3], bounds[0]], - [bounds[3], bounds[2]], - [bounds[1], bounds[2]], - [bounds[1], bounds[0]] - ] - reg.feature.geometry.coordinates.push(coords) + if (!Array.isArray(rData.points)) { + return null } - if (typeof rData.points !== 'undefined' && coords.length === 0) { - if (!Array.isArray(rData.points)) { - return null - } - let isValid: boolean = true - rData.points.forEach((p: any) => { - if (!isValidCoordinate(p)) { - isValid = false - } - }) - if (!isValid) { - return null - } - if ( - rData.points[0].latitude !== - rData.points[rData.points.length - 1].latitude && - rData.points[0].longitude !== - rData.points[rData.points.length - 1].longitude - ) { - rData.points.push(rData.points[0]) + let isValid: boolean = true + rData.points.forEach((p: any) => { + if (!isValidCoordinate(p)) { + isValid = false } - coords = rData.points.map((p: any) => { - return [p.longitude, p.latitude] - }) - reg.feature.geometry.coordinates.push(coords) + }) + if (!isValid) { + return null } - + if ( + rData.points[0].latitude !== + rData.points[rData.points.length - 1].latitude && + rData.points[0].longitude !== + rData.points[rData.points.length - 1].longitude + ) { + rData.points.push(rData.points[0]) + } + coords = rData.points.map((p: any) => { + return [p.longitude, p.latitude] + }) + reg.feature.geometry.coordinates.push(coords) + return reg ======= import { getDistance } from 'geolib' @@ -466,3 +450,66 @@ const buildRegion = (rData: any): any => { return reg >>>>>>> chore: linted } + +const buildChart = (rData: any): any => { + const chart: any = { + identifier: '', + name: '', + description: '', + minzoom: 1, + maxzoom: 28, + type: 'tilelayer', + format: 'png', + tilemapUrl: '', + chartLayers: [], + scale: 250000, + bounds: [-180,-90,180,90] + } + + if (typeof rData.identifier === 'undefined') { + return null + } else { + chart.identifier = rData.identifier + } + if (typeof rData.url === 'undefined') { + return null + } else { + chart.tilemapUrl = rData.url + } + if (typeof rData.name !== 'undefined') { + chart.name = rData.name + } else { + chart.name = rData.identifier + } + if (typeof rData.description !== 'undefined') { + chart.description = rData.description + } + if (typeof rData.minZoom === 'number') { + chart.minzoom = rData.minZoom + } + if (typeof rData.maxZoom === 'number') { + chart.maxzoom = rData.maxZoom + } + if (typeof rData.serverType !== 'undefined') { + chart.type = rData.serverType + } + if (typeof rData.format !== 'undefined') { + chart.format = rData.format + } + if (typeof rData.layers !== 'undefined' && Array.isArray(rData.layers)) { + chart.chartLayers = rData.layers + } + if (typeof rData.scale === 'number') { + chart.scale = rData.scale + } + if (typeof rData.bounds !== 'undefined' && Array.isArray(rData.bounds)) { + chart.bounds = [ + rData.bounds[0].longitude, + rData.bounds[0].latitude, + rData.bounds[1].longitude, + rData.bounds[1].latitude + ] + } + + return chart +} From 1df55d9dbb03a0ced690d02efd7e15a83a5d9f17 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 8 Dec 2021 09:00:15 +1030 Subject: [PATCH 309/410] chore: fix lint errors --- src/api/resources/index.ts | 17 ++++++++--------- src/api/resources/resources.ts | 10 +++++----- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 879482b3c..a485300c4 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -1014,7 +1014,6 @@ export class Resources { } private processApiRequest(req: Request) { - let resType: SignalKResourceType = 'waypoints' if (req.params.resourceType.toLowerCase() === 'waypoint') { resType = 'waypoints' @@ -1036,9 +1035,7 @@ export class Resources { const resId: string = req.params.resourceId ? req.params.resourceId - : resType = 'charts' - ? resValue.identifier - : UUID_PREFIX + uuidv4() + : (resType = 'charts' ? resValue.identifier : UUID_PREFIX + uuidv4()) return { type: resType, @@ -1050,11 +1047,13 @@ export class Resources { private getResourcePaths(): { [key: string]: any } { const resPaths: { [key: string]: any } = {} for (const i in this.resProvider) { - resPaths[i] = { - description: `Path containing ${ - i.slice(-1) === 's' ? i.slice(0, i.length - 1) : i - } resources`, - $source: this.resProvider[i]?.pluginId + if (this.resProvider.hasOwnProperty(i)) { + resPaths[i] = { + description: `Path containing ${ + i.slice(-1) === 's' ? i.slice(0, i.length - 1) : i + } resources`, + $source: this.resProvider[i]?.pluginId + } } } return resPaths diff --git a/src/api/resources/resources.ts b/src/api/resources/resources.ts index 4b4cf5b23..808371a5e 100644 --- a/src/api/resources/resources.ts +++ b/src/api/resources/resources.ts @@ -206,7 +206,7 @@ const buildRegion = (rData: any): any => { return [p.longitude, p.latitude] }) reg.feature.geometry.coordinates.push(coords) - + return reg ======= import { getDistance } from 'geolib' @@ -463,7 +463,7 @@ const buildChart = (rData: any): any => { tilemapUrl: '', chartLayers: [], scale: 250000, - bounds: [-180,-90,180,90] + bounds: [-180, -90, 180, 90] } if (typeof rData.identifier === 'undefined') { @@ -504,12 +504,12 @@ const buildChart = (rData: any): any => { } if (typeof rData.bounds !== 'undefined' && Array.isArray(rData.bounds)) { chart.bounds = [ - rData.bounds[0].longitude, + rData.bounds[0].longitude, rData.bounds[0].latitude, - rData.bounds[1].longitude, + rData.bounds[1].longitude, rData.bounds[1].latitude ] } - + return chart } From 11deafb6ba570208e8405f556866f073c5eef4e5 Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 12 Dec 2021 23:04:09 +0200 Subject: [PATCH 310/410] refactor: tweak types Reorganise typings to reuse some of the existings types. --- src/api/resources/index.ts | 16 +++++----------- src/app.ts | 12 +++++++++--- src/index.ts | 12 ++++++++++++ src/types.ts | 16 ++++++++++++++++ 4 files changed, 42 insertions(+), 14 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index a485300c4..a935a3bd9 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -20,6 +20,7 @@ import { import Debug from 'debug' import { Application, NextFunction, Request, Response } from 'express' import { v4 as uuidv4 } from 'uuid' +import { WithSecurityStrategy, WithSignalK } from '../../app' import { buildResource } from './resources' import { validate } from './validate' @@ -29,17 +30,10 @@ const debug = Debug('signalk:resources') const SIGNALK_API_PATH = `/signalk/v1/api` const UUID_PREFIX = 'urn:mrn:signalk:uuid:' -interface ResourceApplication extends Application { - handleMessage: (id: string, data: any) => void - securityStrategy: { - shouldAllowPut: ( - req: any, - context: string, - source: any, - path: string - ) => boolean - } -} +interface ResourceApplication + extends Application, + WithSignalK, + WithSecurityStrategy {} export class Resources { private resProvider: { [key: string]: ResourceProviderMethods | null } = {} diff --git a/src/app.ts b/src/app.ts index 633e3dc34..f00e821f3 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,6 +1,8 @@ import { FullSignalK } from '@signalk/signalk-schema' +import { EventEmitter } from 'events' import { Config } from './config/config' import DeltaCache from './deltacache' +import { SecurityStrategy } from './types' export interface ServerApp { started: boolean @@ -15,17 +17,21 @@ export interface ServerApp { clients: number } -export interface SignalKMessageHub { - emit: any - on: any +export interface WithSignalK { signalk: FullSignalK handleMessage: (id: string, data: any) => void } +export interface SignalKMessageHub extends EventEmitter, WithSignalK {} + export interface WithConfig { config: Config } +export interface WithSecurityStrategy { + securityStrategy: SecurityStrategy +} + export interface SelfIdentity { selfType: string selfId: string diff --git a/src/index.ts b/src/index.ts index 6718a7e7a..c639d73e1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -255,8 +255,13 @@ class Server { const self = this const app = this.app +<<<<<<< HEAD const eventDebugs: { [key: string]: Debugger } = {} const expressAppEmit = app.emit.bind(app) +======= + const eventDebugs: { [key: string]: Debug.Debugger } = {} + const emit = app.emit +>>>>>>> refactor: tweak types app.emit = (eventName: string, ...args: any[]) => { if (eventName !== 'serverlog') { let eventDebug = eventDebugs[eventName] @@ -266,10 +271,17 @@ class Server { ) } if (eventDebug.enabled) { +<<<<<<< HEAD eventDebug(args) } } expressAppEmit(eventName, ...args) +======= + eventDebug([...args].slice(1)) + } + } + return emit.apply(app, [eventName, ...args]) +>>>>>>> refactor: tweak types } this.app.intervals = [] diff --git a/src/types.ts b/src/types.ts index d9dbf8f87..cc475b9d9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -10,6 +10,22 @@ export interface HelloMessage { timestamp: Date } +<<<<<<< HEAD +======= +export interface SecurityStrategy { + isDummy: () => boolean + allowReadOnly: () => boolean + shouldFilterDeltas: () => boolean + filterReadDelta: (user: any, delta: any) => any + shouldAllowPut: ( + req: any, + context: string, + source: any, + path: string + ) => boolean +} + +>>>>>>> refactor: tweak types export interface Bus { onValue: (callback: (value: any) => any) => () => void push: (v: any) => void From eb77914faf9c011eed11de4f7c37027ec724189e Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 13 Dec 2021 09:34:57 +1030 Subject: [PATCH 311/410] move provider.methods check to `register()` --- src/api/resources/index.ts | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index a935a3bd9..ebf9eadcc 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -62,8 +62,22 @@ export class Resources { } provider.types.forEach((i: string) => { if (!this.resProvider[i]) { - provider.methods.pluginId = pluginId - this.resProvider[i] = provider.methods + if ( + !provider.methods.listResources || + !provider.methods.getResource || + !provider.methods.setResource || + !provider.methods.deleteResource || + typeof provider.methods.listResources !== 'function' || + typeof provider.methods.getResource !== 'function' || + typeof provider.methods.setResource !== 'function' || + typeof provider.methods.deleteResource !== 'function' + ) { + console.error(`Error: Could not register Resource Provider for ${i.toUpperCase()} due to missing provider methods!`) + return + } else { + provider.methods.pluginId = pluginId + this.resProvider[i] = provider.methods + } } }) debug(this.resProvider) @@ -1056,25 +1070,7 @@ export class Resources { private checkForProvider(resType: SignalKResourceType): boolean { debug(`** checkForProvider(${resType})`) debug(this.resProvider[resType]) - - if (this.resProvider[resType]) { - if ( - !this.resProvider[resType]?.listResources || - !this.resProvider[resType]?.getResource || - !this.resProvider[resType]?.setResource || - !this.resProvider[resType]?.deleteResource || - typeof this.resProvider[resType]?.listResources !== 'function' || - typeof this.resProvider[resType]?.getResource !== 'function' || - typeof this.resProvider[resType]?.setResource !== 'function' || - typeof this.resProvider[resType]?.deleteResource !== 'function' - ) { - return false - } else { - return true - } - } else { - return false - } + return (this.resProvider[resType]) ? true : false } <<<<<<< HEAD From 5d130006653106d2672b77636af97a47e6afbf27 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 18 Dec 2021 16:23:13 +1030 Subject: [PATCH 312/410] Use POST for paths that DO NOT specify an id --- WORKING_WITH_RESOURCES_API.md | 36 ++- src/api/resources/index.ts | 8 +- src/api/resources/openApi.json | 437 ++++++++++++++++++++++++++++++++- 3 files changed, 461 insertions(+), 20 deletions(-) diff --git a/WORKING_WITH_RESOURCES_API.md b/WORKING_WITH_RESOURCES_API.md index 54019dd61..f972ea2c4 100644 --- a/WORKING_WITH_RESOURCES_API.md +++ b/WORKING_WITH_RESOURCES_API.md @@ -72,11 +72,25 @@ In this example the route with the supplied id is deleted from storage. ### Creating / updating Resources --- -Resource entries are created and updated by submitting an HTTP `PUT` request that contains the resource data to the relevant API path depending on the type of resource to create / update. +Resource entries are created by submitting an HTTP `POST` request to the relevant API path that does NOT include a resource `id`. In this instance the resource is created with an `id` that is generated by the server. -Each resource type has a specific set of attributes that are required to be supplied before the resource entry can be created. +___Note: Each `POST` will generate a new `id` so if the resource data remains the same duplicate resources will be created.___ -Generally a resource will be created by submitting the resource attributes and the server will generate a resource id for the entry. You can assign a specific id for a newly created resource by using an API path containing the id you wish to assign to the resource. _Note: if a resource of the same type with the supplied id already exists, it will be overwritten with the submitted data!_ +_Example: Create a new route._ +```typescript +HTTP POST 'http://hostname:3000/signalk/v1/api/resources/set/route' {..} +``` + +Resource entries are updated by submitting an HTTP `PUT` request to path that includes a resource `id`. + +_Example: Update a route entry._ +```typescript +HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/route/urn:mrn:signalk:uuid:94052456-65fa-48ce-a85d-41b78a9d2111' {..} +``` + +The body of both `POST` and `PUT` requests should contain valid resource data for the specific resource type in the API path. + +Each resource type has a specific set of attributes that are required to be supplied before the resource entry can be created or updated. ___Note: When submitting data to create or update a resource entry, the submitted resource data is validated against the Signal K schema for that resource type. If the submitted data is deemed to be invalid then the operation is aborted.___ @@ -85,7 +99,7 @@ ___Additionally when supplying an id to assign to or identify the resource on wh --- #### __Routes:__ -To create / update a route entry the body of the PUT request must contain data in the following format: +To create / update a route entry the body of the request must contain data in the following format: ```javascript { name: 'route name', @@ -110,7 +124,7 @@ where: _Example: Create new route entry (with server generated id)_ ```typescript -HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/route' { +HTTP POST 'http://hostname:3000/signalk/v1/api/resources/set/route' { name: 'route name', description: 'description of the route', attributes: { @@ -145,7 +159,7 @@ HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/route/urn:mrn:signal --- #### __Waypoints:__ -To create / update a waypoint entry the body of the PUT request must contain data in the following format: +To create / update a waypoint entry the body of the request must contain data in the following format: ```javascript { name: 'waypoint name', @@ -168,7 +182,7 @@ where: _Example: Create new waypoint entry (with server generated id)_ ```typescript -HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/waypoint' { +HTTP POST 'http://hostname:3000/signalk/v1/api/resources/set/waypoint' { name: 'waypoint #1', position: { latitude: -38.567, @@ -191,7 +205,7 @@ HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/waypoint/urn:mrn:sig --- #### __Regions:__ -To create / update a region entry the body of the PUT request must contain data in the following format: +To create / update a region entry the body of the request must contain data in the following format: ```javascript { name: 'region name', @@ -217,7 +231,7 @@ where: _Example: Create new region entry (with server generated id)_ ```typescript -HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/region' { +HTTP POST 'http://hostname:3000/signalk/v1/api/resources/set/region' { name: 'region name', description: 'description of the region', points: [ @@ -248,7 +262,7 @@ HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/region/urn:mrn:signa --- #### __Notes:__ -To create / update a note entry the body of the PUT request must contain data in the following format: +To create / update a note entry the body of the request must contain data in the following format: ```javascript { title: 'note title text', @@ -283,7 +297,7 @@ OR _Example: Create new note entry (with server generated id)_ ```typescript -HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/note' { +HTTP POST 'http://hostname:3000/signalk/v1/api/resources/set/note' { title: 'note title', description: 'text containing brief description', url: 'http:notehost.com/notes/mynote.html', diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index ebf9eadcc..84debf2aa 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -176,7 +176,7 @@ export class Resources { ) // facilitate creation of new resource entry of supplied type - this.server.put( + this.server.post( `${SIGNALK_API_PATH}/resources/:resourceType`, async (req: Request, res: Response, next: NextFunction) => { debug(`** POST ${SIGNALK_API_PATH}/resources/:resourceType`) @@ -372,7 +372,7 @@ export class Resources { res.send(ar.message) ======= // facilitate API requests - this.server.put( + this.server.post( `${SIGNALK_API_PATH}/resources/set/:resourceType`, async (req: Request, res: Response) => { debug(`** PUT ${SIGNALK_API_PATH}/resources/set/:resourceType`) @@ -405,7 +405,7 @@ export class Resources { } try { - const retVal = await this.resProvider[apiData.type]?.setResource( + await this.resProvider[apiData.type]?.setResource( apiData.type, apiData.id, apiData.value @@ -1004,7 +1004,7 @@ export class Resources { } try { - const retVal = await this.resProvider[apiData.type]?.setResource( + await this.resProvider[apiData.type]?.setResource( apiData.type, apiData.id, apiData.value diff --git a/src/api/resources/openApi.json b/src/api/resources/openApi.json index d8ab778b6..114c35f38 100644 --- a/src/api/resources/openApi.json +++ b/src/api/resources/openApi.json @@ -168,7 +168,7 @@ }, "/resources/routes/": { - "put": { + "post": { "tags": ["resources/routes"], "summary": "Add a new Route", "requestBody": { @@ -556,7 +556,7 @@ }, "/resources/waypoints/": { - "put": { + "post": { "tags": ["resources/waypoints"], "summary": "Add a new Waypoint", "requestBody": { @@ -958,7 +958,7 @@ }, "/resources/notes/": { - "put": { + "post": { "tags": ["resources/notes"], "summary": "Add a new Note", "requestBody": { @@ -1254,7 +1254,7 @@ }, "/resources/regions/": { - "put": { + "post": { "tags": ["resources/regions"], <<<<<<< HEAD <<<<<<< HEAD @@ -1762,7 +1762,7 @@ }, "/resources/charts/": { - "put": { + "post": { "tags": ["resources/charts"], "summary": "Add a new Chart", <<<<<<< HEAD @@ -2065,6 +2065,72 @@ }, + "/resources/set/waypoint": { + "post": { + "tags": ["resources/api"], + "summary": "Add / update a Waypoint", + "requestBody": { + "description": "Waypoint attributes", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["position"], + "properties": { + "name": { + "type": "string", + "description": "Waypoint name" + }, + "description": { + "type": "string", + "description": "Textual description of the waypoint" + }, + "attributes": { + "type": "object", + "description": "Additional attributes as name:value pairs.", + "additionalProperties": { + "type": "string" + } + }, + "position": { + "description": "The waypoint position", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/resources/set/waypoint/{id}": { "put": { "tags": ["resources/api"], @@ -2217,6 +2283,7 @@ } }, +<<<<<<< HEAD <<<<<<< HEAD "/resources/charts/{id}": { "parameters": { @@ -2243,6 +2310,125 @@ "type": "object", "additionalProperties": { "type": "string" +======= + "/resources/set/route": { + "post": { + "tags": ["resources/api"], + "summary": "Add / update a Route", + "requestBody": { + "description": "Note attributes", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["position"], + "properties": { + "name": { + "type": "string", + "description": "Route name" + }, + "description": { + "type": "string", + "description": "Textual description of the route" + }, + "attributes": { + "type": "object", + "description": "Additional attributes as name:value pairs.", + "additionalProperties": { + "type": "string" + } + }, + "points": { + "description": "Route points", + "type": "array", + "items": { + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + } + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + + "/resources/set/route/{id}": { + "put": { + "tags": ["resources/api"], + "summary": "Add / update a Route", + "requestBody": { + "description": "Note attributes", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["position"], + "properties": { + "name": { + "type": "string", + "description": "Route name" + }, + "description": { + "type": "string", + "description": "Textual description of the route" + }, + "attributes": { + "type": "object", + "description": "Additional attributes as name:value pairs.", + "additionalProperties": { + "type": "string" + } + }, + "points": { + "description": "Route points", + "type": "array", + "items": { + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + } + } +>>>>>>> Use POST for paths that DO NOT specify an id } } } @@ -2269,9 +2455,89 @@ }, >>>>>>> add API definitions +<<<<<<< HEAD ======= "/resources/set/route/{id}": { >>>>>>> chore: update docs +======= + "/resources/set/note": { + "post": { + "tags": ["resources/api"], + "summary": "Add / update a Note", + "requestBody": { + "description": "Note attributes", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["title"], + "properties": { + "title": { + "type": "string", + "description": "Note's common name" + }, + "description": { + "type": "string", + "description": " Textual description of the note" + }, + "oneOf":[ + { + "position": { + "description": "Position related to note. Alternative to region or geohash", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + } + }, + "region": { + "type": "string", + "description": "Region related to note. A pointer to a region UUID. Alternative to position or geohash", + "example": "/resources/regions/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a" + } + } + ], + "mimeType": { + "type": "string", + "description": "MIME type of the note" + }, + "url": { + "type": "string", + "description": "Location of the note contents" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + + "/resources/set/note/{id}": { +>>>>>>> Use POST for paths that DO NOT specify an id "put": { "tags": ["resources/charts"], "summary": "Add / update a new Chart with supplied id", @@ -3179,6 +3445,75 @@ } }, + "/resources/set/region": { + "post": { + "tags": ["resources/api"], + "summary": "Add / update a Region", + "requestBody": { + "description": "Region attributes", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["position"], + "properties": { + "name": { + "type": "string", + "description": "Region name" + }, + "description": { + "type": "string", + "description": "Textual description of region" + }, + "attributes": { + "type": "object", + "description": "Additional attributes as name:value pairs.", + "additionalProperties": { + "type": "string" + } + }, + "points": { + "description": "Region boundary points", + "type": "array", + "items": { + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + } + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/resources/set/region/{id}": { "put": { "tags": ["resources/api"], @@ -3248,6 +3583,7 @@ } }, +<<<<<<< HEAD <<<<<<< HEAD "/resources/deleteRegion": { "put": { @@ -3255,17 +3591,99 @@ "summary": "Remove a Region", "requestBody": { "description": "Region identifier", +======= + "/resources/set/chart": { + "post": { + "tags": ["resources/api"], + "summary": "Add / update a Chart", + "requestBody": { + "description": "Chart attributes", +>>>>>>> Use POST for paths that DO NOT specify an id "required": true, "content": { "application/json": { "schema": { "type": "object", +<<<<<<< HEAD "required": ["id"], "properties": { "id": { "type": "string", "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", "description": "Region identifier" +======= + "description": "Signal K Chart resource", + "required": ["identifier", "tilemapUrl"], + "properties": { + "identifier": { + "type": "string", + "description": "Chart number", + "example":"NZ615" + }, + "name": { + "description": "Chart common name", + "example":"NZ615 Marlborough Sounds", + "type": "string" + }, + "description": { + "type": "string", + "description": "A description of the chart" + }, + "tilemapUrl": { + "type": "string", + "description": "A url to the tilemap of the chart for use in TMS chartplotting apps", + "example":"http://{server}:8080/mapcache/NZ615" + }, + "scale": { + "type": "integer", + "description": "The scale of the chart, the larger number from 1:200000" + }, + "layers": { + "type": "array", + "description": "If the chart format is WMS, the layers enabled for the chart.", + "items": { + "type": "string", + "description": "Identifier for the layer." + } + }, + "bounds": { + "type": "array", + "description": "The bounding rectangle of the chart defined by the position of the lower left corner, and the upper right corner.", + "items": { + "description": "Position of a corner of the chart", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + } + } + }, + "format": { + "type": "string", + "description": "The format of the chart", + "enum": [ + "png", + "jpg" + ] + }, + "serverType": { + "type": "string", + "description": "Chart server type", + "enum": [ + "tilelayer", + "WMS" + ] +>>>>>>> Use POST for paths that DO NOT specify an id } } } @@ -3286,12 +3704,17 @@ } } <<<<<<< HEAD +<<<<<<< HEAD >>>>>>> addressed comments re parameters ======= }, "/resources/setChart": { ======= +======= + }, + +>>>>>>> Use POST for paths that DO NOT specify an id "/resources/set/chart/{id}": { >>>>>>> chore: update docs "put": { @@ -3394,6 +3817,7 @@ } } } +<<<<<<< HEAD <<<<<<< HEAD }, @@ -3437,6 +3861,9 @@ ======= >>>>>>> chore: update docs } +======= + } +>>>>>>> Use POST for paths that DO NOT specify an id } From 46dfa0c3ad60a59c5f36bfe61a873e25b36960c5 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 20 Dec 2021 10:56:22 +1030 Subject: [PATCH 313/410] fix api request id validation --- src/api/resources/index.ts | 44 ++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 84debf2aa..7338c6bd7 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -269,7 +269,7 @@ export class Resources { } else { isValidId = validate.uuid(req.params.resourceId) } - if (isValidId) { + if (!isValidId) { res .status(406) .send(`Invalid resource id provided (${req.params.resourceId})`) @@ -375,14 +375,15 @@ export class Resources { this.server.post( `${SIGNALK_API_PATH}/resources/set/:resourceType`, async (req: Request, res: Response) => { - debug(`** PUT ${SIGNALK_API_PATH}/resources/set/:resourceType`) + debug(`** POST ${SIGNALK_API_PATH}/resources/set/:resourceType`) if (!this.updateAllowed()) { res.status(403) return } - const apiData = this.processApiRequest(req) + let apiData = this.processApiRequest(req) + debug(apiData) if (!this.checkForProvider(apiData.type)) { res.status(501).send(`No provider for ${apiData.type}!`) @@ -414,9 +415,9 @@ export class Resources { this.resProvider[apiData.type]?.pluginId as string, this.buildDeltaMsg(apiData.type, apiData.id, apiData.value) ) - res.status(200).send(`SUCCESS: ${req.params.resourceType} complete.`) + res.status(200).send(`SUCCESS: New ${req.params.resourceType} resource created.`) } catch (err) { - res.status(404).send(`ERROR: ${req.params.resourceType} incomplete!`) + res.status(404).send(`ERROR: Could not create ${req.params.resourceType} resource!`) } } ) @@ -1013,43 +1014,44 @@ export class Resources { this.resProvider[apiData.type]?.pluginId as string, this.buildDeltaMsg(apiData.type, apiData.id, apiData.value) ) - res.status(200).send(`SUCCESS: ${req.params.resourceType} complete.`) + res.status(200).send(`SUCCESS: ${req.params.resourceType} resource updated.`) } catch (err) { - res.status(404).send(`ERROR: ${req.params.resourceType} incomplete!`) + res.status(404).send(`ERROR: ${req.params.resourceType} resource could not be updated!`) } } ) } private processApiRequest(req: Request) { - let resType: SignalKResourceType = 'waypoints' + let apiReq: any = { + type: undefined, + id: undefined, + value: undefined + } + if (req.params.resourceType.toLowerCase() === 'waypoint') { - resType = 'waypoints' + apiReq.type = 'waypoints' } if (req.params.resourceType.toLowerCase() === 'route') { - resType = 'routes' + apiReq.type = 'routes' } if (req.params.resourceType.toLowerCase() === 'note') { - resType = 'notes' + apiReq.type = 'notes' } if (req.params.resourceType.toLowerCase() === 'region') { - resType = 'regions' + apiReq.type = 'regions' } if (req.params.resourceType.toLowerCase() === 'charts') { - resType = 'charts' + apiReq.type = 'charts' } - const resValue: any = buildResource(resType, req.body) + apiReq.value = buildResource(apiReq.type, req.body) - const resId: string = req.params.resourceId + apiReq.id = req.params.resourceId ? req.params.resourceId - : (resType = 'charts' ? resValue.identifier : UUID_PREFIX + uuidv4()) + : (apiReq.type === 'charts' ? apiReq.value.identifier : UUID_PREFIX + uuidv4()) - return { - type: resType, - id: resId, - value: resValue - } + return apiReq } private getResourcePaths(): { [key: string]: any } { From 6081501363468e15e6868b9600195aa8c44e86cd Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 4 Dec 2021 17:12:14 +1030 Subject: [PATCH 314/410] init courseApi --- src/api/course/index.ts | 562 ++++++++++++++++++++++++++++++++++++ src/api/course/openApi.json | 413 ++++++++++++++++++++++++++ src/index.ts | 2 + 3 files changed, 977 insertions(+) create mode 100644 src/api/course/index.ts create mode 100644 src/api/course/openApi.json diff --git a/src/api/course/index.ts b/src/api/course/index.ts new file mode 100644 index 000000000..a8d42a1a8 --- /dev/null +++ b/src/api/course/index.ts @@ -0,0 +1,562 @@ +import { + ResourceProvider, + ResourceProviderMethods, + SignalKResourceType +} from '@signalk/server-api' +import Debug from 'debug' +import { Application, Request, Response } from 'express' +import { v4 as uuidv4 } from 'uuid' + +const debug = Debug('signalk:courseApi') + +const SIGNALK_API_PATH: string = `/signalk/v1/api` +const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' + +const API_METHODS: string[] = [] + +interface CourseApplication extends Application { + handleMessage: (id: string, data: any) => void + getSelfPath: (path: string) => any + resourcesApi: { + getResource: (resourceType: string, resourceId: string) => any + } +} + +interface DestinationBase { + href?: string + arrivalCircle?: number +} +interface Destination extends DestinationBase { + position?: { + latitude: number + longitude: number + altitude?: number + } + type?: string +} + +interface ActiveRoute extends DestinationBase { + pointIndex?: number + reverse?: boolean +} + +interface Position { + latitude: number + longitude: number + altitude?: number +} + +interface CourseInfo { + activeRoute: { + href: string | null + startTime: string | null + pointIndex: number + reverse: boolean + } + nextPoint: { + href: string | null + type: string | null + position: Position | null + arrivalCircle: number + } + previousPoint: { + href: string | null + type: string | null + position: Position | null + } +} + +export class CourseApi { + private server: CourseApplication + + private courseInfo: CourseInfo = { + activeRoute: { + href: null, + startTime: null, + pointIndex: 0, + reverse: false + }, + nextPoint: { + href: null, + type: null, + position: null, + arrivalCircle: 0 + }, + previousPoint: { + href: null, + type: null, + position: null + } + } + + constructor(app: CourseApplication) { + this.server = app + this.start(app) + } + + private start(app: any) { + debug(`** Initialise ${COURSE_API_PATH} path handler **`) + this.server = app + this.initResourceRoutes() + } + + private initResourceRoutes() { + // return current course information + this.server.get( + `${COURSE_API_PATH}`, + async (req: Request, res: Response) => { + debug(`** GET ${COURSE_API_PATH}`) + res.json(this.courseInfo) + } + ) + + // restart / arrivalCircle + this.server.put( + `${COURSE_API_PATH}/:action`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/:action`) + if (req.params.restart) { + // set previousPoint to vessel position + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.emitCourseInfo() + res.status(200).send(`Course restarted.`) + } else { + res.status(406).send(`Vessel position unavailable!`) + } + } + if (req.params.arrivalCircle) { + if (this.setArrivalCircle(req.params.arrivalCircle)) { + this.emitCourseInfo() + res.status(200).send(`Destination set successfully.`) + } else { + res.status(406).send(`Invalid Data`) + } + } + } + ) + + // set destination + this.server.put( + `${COURSE_API_PATH}/destination`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/destination`) + + if (!req.body.value) { + res.status(406).send(`Invalid Data`) + return + } + const result = await this.setDestination(req.body.value) + if (result) { + this.emitCourseInfo() + res.status(200).send(`Destination set successfully.`) + } else { + this.clearDestination() + this.emitCourseInfo() + res.status(406).send(`Invalid Data`) + } + } + ) + + // clear destination + this.server.delete( + `${COURSE_API_PATH}/destination`, + async (req: Request, res: Response) => { + debug(`** DELETE ${COURSE_API_PATH}/destination`) + this.clearDestination() + this.emitCourseInfo() + res.status(200).send(`Destination cleared.`) + } + ) + + // set / clear activeRoute + this.server.put( + `${COURSE_API_PATH}/activeRoute`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/activeRoute`) + + const result = await this.activateRoute(req.body.value) + if (result) { + this.emitCourseInfo() + res.status(200).send(`Active route set.`) + } else { + this.clearDestination() + this.emitCourseInfo() + res.status(406).send(`Invalid Data`) + } + } + ) + this.server.delete( + `${COURSE_API_PATH}/activeRoute`, + async (req: Request, res: Response) => { + debug(`** DELETE ${COURSE_API_PATH}/activeRoute`) + + this.clearDestination() + this.emitCourseInfo() + res.status(200).send(`Active route cleared.`) + } + ) + + this.server.put( + `${COURSE_API_PATH}/activeRoute/:action`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/activeRoute`) + + if (!this.courseInfo.activeRoute.href) { + res.status(406).send(`Invalid Data`) + return + } + + const rte = await this.getRoute(this.courseInfo.activeRoute.href) + if (!rte) { + res.status(406).send(`Invalid Data`) + return + } + + if (req.params.nextPoint) { + if ( + typeof req.body.value === 'number' && + (req.body.value === 1 || req.body.value === -1) + ) { + this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( + this.courseInfo.activeRoute.pointIndex + req.body.value, + rte + ) + } else { + res.status(406).send(`Invalid Data`) + } + } + if (req.params.pointIndex) { + if (typeof req.body.value === 'number') { + this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( + req.body.value, + rte + ) + } else { + res.status(406).send(`Invalid Data`) + } + } + + // set nextPoint + this.courseInfo.nextPoint.position = this.getRoutePoint( + rte, + this.courseInfo.activeRoute.pointIndex + ) + this.courseInfo.nextPoint.type = `RoutePoint` + this.courseInfo.nextPoint.href = null + + // set previousPoint + if (this.courseInfo.activeRoute.pointIndex === 0) { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } else { + this.courseInfo.previousPoint.position = this.getRoutePoint( + rte, + this.courseInfo.activeRoute.pointIndex - 1 + ) + this.courseInfo.previousPoint.type = `RoutePoint` + } + this.courseInfo.previousPoint.href = null + + res.status(200).send(`OK`) + } + ) + } + + private async activateRoute(route: ActiveRoute): Promise { + let rte: any + + if (route.href) { + rte = await this.getRoute(route.href) + if (!rte) { + return false + } + if (!Array.isArray(rte.feature?.geometry?.coordinates)) { + return false + } + } else { + return false + } + + // set activeroute + this.courseInfo.activeRoute.href = route.href + + if (typeof route.arrivalCircle === 'number') { + this.setArrivalCircle(route.arrivalCircle) + } + + this.courseInfo.activeRoute.reverse = + typeof route.reverse === 'boolean' ? route.reverse : false + + this.courseInfo.activeRoute.pointIndex = + typeof route.pointIndex === 'number' + ? this.parsePointIndex(route.pointIndex, rte) + : 0 + + this.courseInfo.activeRoute.startTime = new Date().toISOString() + + // set nextPoint + this.courseInfo.nextPoint.position = this.getRoutePoint( + rte, + this.courseInfo.activeRoute.pointIndex + ) + this.courseInfo.nextPoint.type = `RoutePoint` + this.courseInfo.nextPoint.href = null + + // set previousPoint + if (this.courseInfo.activeRoute.pointIndex === 0) { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } else { + this.courseInfo.previousPoint.position = this.getRoutePoint( + rte, + this.courseInfo.activeRoute.pointIndex - 1 + ) + this.courseInfo.previousPoint.type = `RoutePoint` + } + this.courseInfo.previousPoint.href = null + + return true + } + + private async setDestination(dest: Destination): Promise { + // set nextPoint + if (typeof dest.arrivalCircle === 'number') { + this.setArrivalCircle(dest.arrivalCircle) + } + + this.courseInfo.nextPoint.type = + typeof dest.type !== 'undefined' ? dest.type : null + + if (dest.href) { + this.courseInfo.nextPoint.href = dest.href + const href = this.parseHref(dest.href) + if (href) { + try { + const r = await this.server.resourcesApi.getResource( + href.type, + href.id + ) + if (r.position && typeof r.position.value?.latitude !== 'undefined') { + this.courseInfo.nextPoint.position = r.position.value + } + } catch (err) { + return false + } + } + } else if (dest.position) { + this.courseInfo.nextPoint.href = null + if (typeof dest.position.latitude !== 'undefined') { + this.courseInfo.nextPoint.position = dest.position + } else { + return false + } + } else { + return false + } + + // set previousPoint + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + this.courseInfo.previousPoint.href = null + + return true + } + + private clearDestination() { + this.courseInfo.activeRoute.href = null + this.courseInfo.activeRoute.startTime = null + this.courseInfo.activeRoute.pointIndex = 0 + this.courseInfo.activeRoute.reverse = false + this.courseInfo.nextPoint.href = null + this.courseInfo.nextPoint.type = null + this.courseInfo.nextPoint.position = null + this.courseInfo.previousPoint.href = null + this.courseInfo.previousPoint.type = null + this.courseInfo.previousPoint.position = null + } + + private setArrivalCircle(value: any): boolean { + if (typeof value === 'number' && value >= 0) { + this.courseInfo.nextPoint.arrivalCircle = value + return true + } else { + return false + } + } + + private parsePointIndex(index: number, rte: any): number { + if (!rte) { + return 0 + } + if (!rte.feature?.geometry?.coordinates) { + return 0 + } + if (!Array.isArray(rte.feature?.geometry?.coordinates)) { + return 0 + } + if (index < 0) { + return 0 + } + if (index > rte.feature?.geometry?.coordinates.length - 1) { + return rte.feature?.geometry?.coordinates.length + } + return index + } + + private parseHref(href: string): { type: string; id: string } | undefined { + if (href.length === 0) { + return undefined + } + if (href[0] === '/') { + href = href.slice(1) + } + const ref: string[] = href.split('/') + if (ref.length < 3) { + return undefined + } + if (ref[0] !== 'resources') { + return undefined + } + return { + type: ref[1], + id: ref[2] + } + } + + private getRoutePoint(rte: any, index: number) { + const pos = this.courseInfo.activeRoute.reverse + ? rte.feature.geometry.coordinates[ + rte.feature.geometry.coordinates.length - (index + 1) + ] + : rte.feature.geometry.coordinates[index] + return { + latitude: pos[1], + longitude: pos[0], + altitude: pos.length == 3 ? pos[2] : 0 + } + } + + private async getRoute(href: string): Promise { + const h = this.parseHref(href) + if (h) { + try { + return await this.server.resourcesApi.getResource(h.type, h.id) + } catch (err) { + return undefined + } + } else { + return undefined + } + } + + private buildDeltaMsg(): any { + let values: Array<{path:string, value:any}> = [] + let root = [ + 'navigation.courseGreatCircle', + 'navigation.courseRhumbline' + ] + + values.push({ + path: `${root[0]}.activeRoute.href`, + value: this.courseInfo.activeRoute.href + }) + values.push({ + path: `${root[1]}.activeRoute.href`, + value: this.courseInfo.activeRoute.href + }) + values.push({ + path: `${root[0]}.activeRoute.startTime`, + value: this.courseInfo.activeRoute.startTime + }) + values.push({ + path: `${root[1]}.activeRoute.startTime`, + value: this.courseInfo.activeRoute.startTime + }) + values.push({ + path: `${root[0]}.nextPoint.href`, + value: this.courseInfo.nextPoint.href + }) + values.push({ + path: `${root[1]}.nextPoint.href`, + value: this.courseInfo.nextPoint.href + }) + values.push({ + path: `${root[0]}.nextPoint.position`, + value: this.courseInfo.nextPoint.position + }) + values.push({ + path: `${root[1]}.nextPoint.position`, + value: this.courseInfo.nextPoint.position + }) + values.push({ + path: `${root[0]}.nextPoint.type`, + value: this.courseInfo.nextPoint.type + }) + values.push({ + path: `${root[1]}.nextPoint.type`, + value: this.courseInfo.nextPoint.type + }) + values.push({ + path: `${root[0]}.nextPoint.arrivalCircle`, + value: this.courseInfo.nextPoint.arrivalCircle + }) + values.push({ + path: `${root[1]}.nextPoint.arrivalCircle`, + value: this.courseInfo.nextPoint.arrivalCircle + }) + values.push({ + path: `${root[0]}.previousPoint.href`, + value: this.courseInfo.previousPoint.href + }) + values.push({ + path: `${root[1]}.previousPoint.href`, + value: this.courseInfo.previousPoint.href + }) + values.push({ + path: `${root[0]}.previousPoint.position`, + value: this.courseInfo.previousPoint.position + }) + values.push({ + path: `${root[1]}.previousPoint.position`, + value: this.courseInfo.previousPoint.position + }) + values.push({ + path: `${root[0]}.previousPoint.type`, + value: this.courseInfo.previousPoint.type + }) + values.push({ + path: `${root[1]}.previousPoint.type`, + value: this.courseInfo.previousPoint.type + }) + + return { + updates: [ + { + values: values + } + ] + } + } + + private emitCourseInfo() { + this.server.handleMessage('courseApi', this.buildDeltaMsg()) + } +} diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json new file mode 100644 index 000000000..04653db5e --- /dev/null +++ b/src/api/course/openApi.json @@ -0,0 +1,413 @@ +{ + "openapi": "3.0.2", + "info": { + "version": "1.0.0", + "title": "Signal K Course API" + }, + + "paths": { + + "/vessels/self/navigation/course/": { + "get": { + "tags": ["course"], + "summary": "Get course information", + "responses": { + "default": { + "description": "Course data", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "activeRoute": { + "type": "object", + "properties": { + "href": { + "type": "string", + "example": "/resources/routes/urn:mrn:signalk:uuid:0d95e282-3e1f-4521-8c30-8288addbdb69" + }, + "startTime": { + "type": "string", + "example": "2021-10-23T05:17:20.065Z" + }, + "pointIndex": { + "type": "number", + "format": "int64", + "example": 2 + }, + "reverse": { + "type": "boolean", + "default": false + } + } + }, + "nextPoint": { + "type": "object", + "properties": { + "href": { + "type": "string", + "example": "/resources/waypoints/urn:mrn:signalk:uuid:0d95e282-3e1f-4521-8c30-8288addbdbab" + }, + "type": { + "type": "string", + "example": "RoutePoint" + }, + "position": { + "type": "object", + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + }, + "example": {"latitude":-29.5,"longitude":137.5} + } + } + }, + "previousPoint": { + "type": "object", + "properties": { + "href": { + "type": "string", + "example": null + }, + "type": { + "type": "string", + "example": "Location" + }, + "position": { + "type": "object", + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + }, + "example": {"longitude":29.821001582434413,"latitude":70.7014589462524} + } + } + } + } + } + } + } + } + } + } + }, + + "/vessels/self/navigation/course/restart": { + "put": { + "tags": ["course"], + "summary": "Restart course calculations", + "description": "Sets previousPoint value to current vessel location", + "requestBody": { + "description": "Restart payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + } + }, + + "/vessels/self/navigation/course/destination": { + "put": { + "tags": ["course/destination"], + "summary": "Set destination", + "description": "Set destination path from supplied details", + "requestBody": { + "description": "Destination details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "properties": { + "position": { + "type": "object", + "description": "Destination position", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } + }, + "example": {"latitude":-29.5,"longitude":137.5} + }, + "href": { + "type": "string", + "description": "A reference (URL) to an object (under /resources) this point is related to", + "example": "/resources/waypoints/urn:mrn:signalk:uuid:3dd34dcc-36bf-4d61-ba80-233799b25672" + }, + "type": { + "type": "string", + "description": "Type of point", + "example": "POI" + }, + "arrivalCircle": { + "type": "number", + "format": "float", + "example": 500 + } + } + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + }, + "delete": { + "tags": ["course/destination"], + "summary": "Clear destination", + "description": "Sets activeRoute, nextPoint & previousPoint values to null", + "requestBody": { + "description": "Delete payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + } + }, + + "/vessels/self/navigation/course/arrivalCircle": { + "put": { + "tags": ["course"], + "summary": "Set arrival circle radius (m)", + "description": "Sets an arrival circle radius (in meters)", + "requestBody": { + "description": "Arrival circle payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "number", + "format": "float", + "example": 500 + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + } + }, + + "/vessels/self/navigation/course/activeRoute": { + "put": { + "tags": ["course/activeRoute"], + "summary": "Set active route", + "description": "Sets activeRoute path and sets nextPoint to first point in the route", + "requestBody": { + "description": "Active route details", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "properties": { + "href": { + "type": "string", + "description": "Path to route resource", + "example": "/resources/routes/urn:mrn:signalk:uuid:3dd34dcc-36bf-4d61-ba80-233799b25672" + }, + "pointIndex": { + "type": "number", + "format": "int64", + "description": "Zero based index of route point to use as destination", + "default": 0, + "minimum": 0, + "example": 2 + }, + "reverse": { + "type": "boolean", + "default": false, + "description": "If true performs operations on route points in reverse order", + "example": 2 + }, + "arrivalCircle": { + "type": "number", + "format": "float", + "example": 500 + } + } + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + }, + "delete": { + "tags": ["course/activeRoute"], + "summary": "Clear active route", + "description": "Sets activeRoute, nextPoint & previousPoint values to null", + "requestBody": { + "description": "Delete payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + } + }, + + "/vessels/self/navigation/course/activeRoute/nextPoint": { + "put": { + "tags": ["course/activeRoute"], + "summary": "Increment / decrement point in route as destination", + "description": "Increment / decrement point in the route as destination", + "requestBody": { + "description": "Increment / decrement", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "number", + "description": "increment (1) / decrement (-1) index of point in route to use as destination", + "enum": [-1,1], + "example": -1 + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + } + }, + + "/vessels/self/navigation/course/activeRoute/pointIndex": { + "put": { + "tags": ["course/activeRoute"], + "summary": "Set point in route as destination", + "description": "Sets the specified point in the route as destination", + "requestBody": { + "description": "Next point index", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "number", + "description": "Index of point in route to use as destination", + "minimum": 0, + "example": 2 + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } + } + } + + } + +} + + + + + \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index c639d73e1..fda6204aa 100644 --- a/src/index.ts +++ b/src/index.ts @@ -46,6 +46,7 @@ import SubscriptionManager from './subscriptionmanager' import { Delta } from './types' const debug = createDebug('signalk-server') +import { CourseApi } from './api/course' import { Resources } from './api/resources' // tslint:disable-next-line: no-var-requires @@ -79,6 +80,7 @@ class Server { require('./put').start(app) app.resourcesApi = new Resources(app) + const courseApi = new CourseApi(app) app.signalk = new FullSignalK(app.selfId, app.selfType) From e195d266ee8d5640f2a8e0ac5cf796d360c92e91 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 5 Dec 2021 10:26:47 +1030 Subject: [PATCH 315/410] update detlas --- src/api/course/index.ts | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index a8d42a1a8..7d186618c 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,23 +1,15 @@ -import { - ResourceProvider, - ResourceProviderMethods, - SignalKResourceType -} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' -import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' - -const API_METHODS: string[] = [] interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any + registerPutHandler: (context:string, path:string, cb:any) => any resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -35,7 +27,6 @@ interface Destination extends DestinationBase { } type?: string } - interface ActiveRoute extends DestinationBase { pointIndex?: number reverse?: boolean @@ -111,12 +102,26 @@ export class CourseApi { } ) + // + if(this.server.registerPutHandler) { + debug('** Registering PUT Action Handler(s) **') + this.server.registerPutHandler( + 'vessels.self', + 'navigation.course.*', + this.handleCourseApiPut + ); + } + // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { + //test for active destination + if (!this.courseInfo.nextPoint.position) { + return + } // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -270,6 +275,12 @@ export class CourseApi { ) } + private handleCourseApiPut(context:string, path:string, value:any, cb:any) { + + debug('** PUT handler **') + return undefined + } + private async activateRoute(route: ActiveRoute): Promise { let rte: any @@ -474,6 +485,11 @@ export class CourseApi { 'navigation.courseRhumbline' ] + values.push({ + path: `navigation.course`, + value: this.courseInfo + }) + values.push({ path: `${root[0]}.activeRoute.href`, value: this.courseInfo.activeRoute.href From f0f1dfba28e91987ee2e46308227796010a33c4e Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 4 Dec 2021 17:12:14 +1030 Subject: [PATCH 316/410] init courseApi --- src/api/course/index.ts | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 7d186618c..d3d42fed7 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,15 +1,26 @@ + +import { + ResourceProvider, + ResourceProviderMethods, + SignalKResourceType +} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' +import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' + +const API_METHODS: string[] = [] + interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - registerPutHandler: (context:string, path:string, cb:any) => any + resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -102,26 +113,12 @@ export class CourseApi { } ) - // - if(this.server.registerPutHandler) { - debug('** Registering PUT Action Handler(s) **') - this.server.registerPutHandler( - 'vessels.self', - 'navigation.course.*', - this.handleCourseApiPut - ); - } - // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { - //test for active destination - if (!this.courseInfo.nextPoint.position) { - return - } // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -275,12 +272,6 @@ export class CourseApi { ) } - private handleCourseApiPut(context:string, path:string, value:any, cb:any) { - - debug('** PUT handler **') - return undefined - } - private async activateRoute(route: ActiveRoute): Promise { let rte: any From f51cfb609a36905843a4612207f3098f2d9dcd67 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 5 Dec 2021 10:26:47 +1030 Subject: [PATCH 317/410] update detlas --- src/api/course/index.ts | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index d3d42fed7..7d186618c 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,26 +1,15 @@ - -import { - ResourceProvider, - ResourceProviderMethods, - SignalKResourceType -} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' -import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' - -const API_METHODS: string[] = [] - interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - + registerPutHandler: (context:string, path:string, cb:any) => any resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -113,12 +102,26 @@ export class CourseApi { } ) + // + if(this.server.registerPutHandler) { + debug('** Registering PUT Action Handler(s) **') + this.server.registerPutHandler( + 'vessels.self', + 'navigation.course.*', + this.handleCourseApiPut + ); + } + // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { + //test for active destination + if (!this.courseInfo.nextPoint.position) { + return + } // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -272,6 +275,12 @@ export class CourseApi { ) } + private handleCourseApiPut(context:string, path:string, value:any, cb:any) { + + debug('** PUT handler **') + return undefined + } + private async activateRoute(route: ActiveRoute): Promise { let rte: any From f3ff1f623d1f3a3a799031f74a997b76a2e8e2bb Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 6 Dec 2021 08:39:56 +1030 Subject: [PATCH 318/410] enable put processing --- src/api/course/index.ts | 136 ++++++++++++++++++++++++---------------- src/put.js | 10 +++ 2 files changed, 92 insertions(+), 54 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 7d186618c..1566ecdf3 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -9,7 +9,14 @@ const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/cou interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - registerPutHandler: (context:string, path:string, cb:any) => any + securityStrategy: { + shouldAllowPut: ( + req: any, + context: string, + source: any, + path: string + ) => boolean + } resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -92,6 +99,15 @@ export class CourseApi { this.initResourceRoutes() } + private updateAllowed(): boolean { + return this.server.securityStrategy.shouldAllowPut( + this.server, + 'vessels.self', + null, + 'resources' + ) + } + private initResourceRoutes() { // return current course information this.server.get( @@ -102,43 +118,43 @@ export class CourseApi { } ) - // - if(this.server.registerPutHandler) { - debug('** Registering PUT Action Handler(s) **') - this.server.registerPutHandler( - 'vessels.self', - 'navigation.course.*', - this.handleCourseApiPut - ); - } + this.server.put( + `${COURSE_API_PATH}/restart`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/restart`) + if (!this.updateAllowed()) { + res.status(403) + return + } + if (!this.courseInfo.nextPoint.position) { + res.status(406).send(`No active destination!`) + return + } + // set previousPoint to vessel position + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.emitCourseInfo() + res.status(200) + } else { + res.status(406).send(`Vessel position unavailable!`) + } + } + ) - // restart / arrivalCircle this.server.put( - `${COURSE_API_PATH}/:action`, + `${COURSE_API_PATH}/arrivalCircle`, async (req: Request, res: Response) => { - debug(`** PUT ${COURSE_API_PATH}/:action`) - if (req.params.restart) { - //test for active destination - if (!this.courseInfo.nextPoint.position) { - return - } - // set previousPoint to vessel position - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.emitCourseInfo() - res.status(200).send(`Course restarted.`) - } else { - res.status(406).send(`Vessel position unavailable!`) - } + debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) + if (!this.updateAllowed()) { + res.status(403) + return } - if (req.params.arrivalCircle) { - if (this.setArrivalCircle(req.params.arrivalCircle)) { - this.emitCourseInfo() - res.status(200).send(`Destination set successfully.`) - } else { - res.status(406).send(`Invalid Data`) - } + if (this.setArrivalCircle(req.body.value)) { + this.emitCourseInfo() + res.status(200) + } else { + res.status(406).send(`Invalid Data`) } } ) @@ -148,7 +164,10 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) - + if (!this.updateAllowed()) { + res.status(403) + return + } if (!req.body.value) { res.status(406).send(`Invalid Data`) return @@ -156,7 +175,7 @@ export class CourseApi { const result = await this.setDestination(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send(`Destination set successfully.`) + res.status(200) } else { this.clearDestination() this.emitCourseInfo() @@ -170,22 +189,29 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) + if (!this.updateAllowed()) { + res.status(403) + return + } this.clearDestination() this.emitCourseInfo() - res.status(200).send(`Destination cleared.`) + res.status(200) } ) - // set / clear activeRoute + // set activeRoute this.server.put( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) - + if (!this.updateAllowed()) { + res.status(403) + return + } const result = await this.activateRoute(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send(`Active route set.`) + res.status(200) } else { this.clearDestination() this.emitCourseInfo() @@ -193,14 +219,19 @@ export class CourseApi { } } ) + + // clear activeRoute /destination this.server.delete( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/activeRoute`) - + if (!this.updateAllowed()) { + res.status(403) + return + } this.clearDestination() this.emitCourseInfo() - res.status(200).send(`Active route cleared.`) + res.status(200) } ) @@ -208,19 +239,22 @@ export class CourseApi { `${COURSE_API_PATH}/activeRoute/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) - + if (!this.updateAllowed()) { + res.status(403) + return + } + // fetch route data if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return } - const rte = await this.getRoute(this.courseInfo.activeRoute.href) if (!rte) { res.status(406).send(`Invalid Data`) return } - if (req.params.nextPoint) { + if (req.params.action === 'nextPoint') { if ( typeof req.body.value === 'number' && (req.body.value === 1 || req.body.value === -1) @@ -233,7 +267,7 @@ export class CourseApi { res.status(406).send(`Invalid Data`) } } - if (req.params.pointIndex) { + if (req.params.action === 'pointIndex') { if (typeof req.body.value === 'number') { this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( req.body.value, @@ -244,7 +278,7 @@ export class CourseApi { } } - // set nextPoint + // set new destination this.courseInfo.nextPoint.position = this.getRoutePoint( rte, this.courseInfo.activeRoute.pointIndex @@ -270,17 +304,11 @@ export class CourseApi { } this.courseInfo.previousPoint.href = null - res.status(200).send(`OK`) + res.status(200) } ) } - private handleCourseApiPut(context:string, path:string, value:any, cb:any) { - - debug('** PUT handler **') - return undefined - } - private async activateRoute(route: ActiveRoute): Promise { let rte: any diff --git a/src/put.js b/src/put.js index d875c4f85..cf137b3a2 100644 --- a/src/put.js +++ b/src/put.js @@ -38,6 +38,7 @@ module.exports = { next() return } +<<<<<<< HEAD ======= if(req.path.split('/')[4]==='resources') { next() @@ -50,6 +51,15 @@ module.exports = { return } >>>>>>> chore: linted +======= + + // ** ignore course paths ** + if (req.path.indexOf('/navigation/course') !== -1) { + next() + return + } + +>>>>>>> enable put processing let path = String(req.path).replace(apiPathPrefix, '') const value = req.body From 5557653330427b91e79a3e2c26132ee52a3e044f Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 6 Dec 2021 13:34:31 +1030 Subject: [PATCH 319/410] chore: lint --- src/api/course/index.ts | 211 ++++++++++++++++++++---------------- src/api/course/openApi.json | 72 +----------- 2 files changed, 120 insertions(+), 163 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 1566ecdf3..6cd3b80fd 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -11,7 +11,7 @@ interface CourseApplication extends Application { getSelfPath: (path: string) => any securityStrategy: { shouldAllowPut: ( - req: any, + req: Application, context: string, source: any, path: string @@ -104,7 +104,7 @@ export class CourseApi { this.server, 'vessels.self', null, - 'resources' + 'navigation.course' ) } @@ -131,12 +131,14 @@ export class CourseApi { return } // set previousPoint to vessel position - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.emitCourseInfo() - res.status(200) - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.emitCourseInfo() + res.status(200) + } + } catch (err) { res.status(406).send(`Vessel position unavailable!`) } } @@ -150,7 +152,8 @@ export class CourseApi { res.status(403) return } - if (this.setArrivalCircle(req.body.value)) { + if (this.isValidArrivalCircle(req.body.value)) { + this.courseInfo.nextPoint.arrivalCircle = req.body.value this.emitCourseInfo() res.status(200) } else { @@ -243,7 +246,7 @@ export class CourseApi { res.status(403) return } - // fetch route data + // fetch active route data if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return @@ -281,24 +284,30 @@ export class CourseApi { // set new destination this.courseInfo.nextPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex + this.courseInfo.activeRoute.pointIndex, + this.courseInfo.activeRoute.reverse ) this.courseInfo.nextPoint.type = `RoutePoint` this.courseInfo.nextPoint.href = null // set previousPoint if (this.courseInfo.activeRoute.pointIndex === 0) { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } catch (err) { return false } } else { this.courseInfo.previousPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex - 1 + this.courseInfo.activeRoute.pointIndex - 1, + this.courseInfo.activeRoute.reverse ) this.courseInfo.previousPoint.type = `RoutePoint` } @@ -324,81 +333,97 @@ export class CourseApi { return false } + const newDest: any = {} + Object.assign(newDest, this.courseInfo) + // set activeroute - this.courseInfo.activeRoute.href = route.href + newDest.activeRoute.href = route.href - if (typeof route.arrivalCircle === 'number') { - this.setArrivalCircle(route.arrivalCircle) + if (this.isValidArrivalCircle(route.arrivalCircle as number)) { + newDest.nextPoint.arrivalCircle = route.arrivalCircle } - this.courseInfo.activeRoute.reverse = - typeof route.reverse === 'boolean' ? route.reverse : false + newDest.activeRoute.startTime = new Date().toISOString() - this.courseInfo.activeRoute.pointIndex = - typeof route.pointIndex === 'number' - ? this.parsePointIndex(route.pointIndex, rte) - : 0 + if (typeof route.reverse === 'boolean') { + newDest.activeRoute.reverse = route.reverse + } - this.courseInfo.activeRoute.startTime = new Date().toISOString() + newDest.activeRoute.pointIndex = this.parsePointIndex( + route.pointIndex as number, + rte + ) // set nextPoint - this.courseInfo.nextPoint.position = this.getRoutePoint( + newDest.nextPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex + newDest.activeRoute.pointIndex, + newDest.activeRoute.reverse ) - this.courseInfo.nextPoint.type = `RoutePoint` - this.courseInfo.nextPoint.href = null + newDest.nextPoint.type = `RoutePoint` + newDest.nextPoint.href = null // set previousPoint - if (this.courseInfo.activeRoute.pointIndex === 0) { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + if (newDest.activeRoute.pointIndex === 0) { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } catch (err) { return false } } else { - this.courseInfo.previousPoint.position = this.getRoutePoint( + newDest.previousPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex - 1 + newDest.activeRoute.pointIndex - 1, + newDest.activeRoute.reverse ) - this.courseInfo.previousPoint.type = `RoutePoint` + newDest.previousPoint.type = `RoutePoint` } - this.courseInfo.previousPoint.href = null + newDest.previousPoint.href = null + this.courseInfo = newDest return true } - private async setDestination(dest: Destination): Promise { + private async setDestination(dest: any): Promise { + const newDest: any = {} + Object.assign(newDest, this.courseInfo) + // set nextPoint - if (typeof dest.arrivalCircle === 'number') { - this.setArrivalCircle(dest.arrivalCircle) + if (this.isValidArrivalCircle(dest.arrivalCircle)) { + newDest.nextPoint.arrivalCircle = dest.arrivalCircle } - this.courseInfo.nextPoint.type = - typeof dest.type !== 'undefined' ? dest.type : null + newDest.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - this.courseInfo.nextPoint.href = dest.href + newDest.href = dest.href const href = this.parseHref(dest.href) if (href) { + // fetch waypoint resource details try { const r = await this.server.resourcesApi.getResource( href.type, href.id ) - if (r.position && typeof r.position.value?.latitude !== 'undefined') { - this.courseInfo.nextPoint.position = r.position.value + if (r.position && typeof r.position?.latitude !== 'undefined') { + newDest.nextPoint.position = r.position + } else { + return false } } catch (err) { return false } } } else if (dest.position) { - this.courseInfo.nextPoint.href = null + newDest.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { - this.courseInfo.nextPoint.position = dest.position + newDest.nextPoint.position = dest.position } else { return false } @@ -407,15 +432,20 @@ export class CourseApi { } // set previousPoint - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + newDest.previousPoint.position = position.value + newDest.previousPoint.type = `VesselPosition` + } else { + return false + } + newDest.previousPoint.href = null + } catch (err) { return false } - this.courseInfo.previousPoint.href = null + this.courseInfo = newDest return true } @@ -432,17 +462,12 @@ export class CourseApi { this.courseInfo.previousPoint.position = null } - private setArrivalCircle(value: any): boolean { - if (typeof value === 'number' && value >= 0) { - this.courseInfo.nextPoint.arrivalCircle = value - return true - } else { - return false - } + private isValidArrivalCircle(value: number): boolean { + return typeof value === 'number' && value >= 0 } private parsePointIndex(index: number, rte: any): number { - if (!rte) { + if (typeof index !== 'number' || !rte) { return 0 } if (!rte.feature?.geometry?.coordinates) { @@ -461,13 +486,11 @@ export class CourseApi { } private parseHref(href: string): { type: string; id: string } | undefined { - if (href.length === 0) { + if (!href) { return undefined } - if (href[0] === '/') { - href = href.slice(1) - } - const ref: string[] = href.split('/') + + const ref: string[] = href.split('/').slice(-3) if (ref.length < 3) { return undefined } @@ -480,8 +503,8 @@ export class CourseApi { } } - private getRoutePoint(rte: any, index: number) { - const pos = this.courseInfo.activeRoute.reverse + private getRoutePoint(rte: any, index: number, reverse: boolean) { + const pos = reverse ? rte.feature.geometry.coordinates[ rte.feature.geometry.coordinates.length - (index + 1) ] @@ -489,7 +512,7 @@ export class CourseApi { return { latitude: pos[1], longitude: pos[0], - altitude: pos.length == 3 ? pos[2] : 0 + altitude: pos.length === 3 ? pos[2] : 0 } } @@ -507,8 +530,8 @@ export class CourseApi { } private buildDeltaMsg(): any { - let values: Array<{path:string, value:any}> = [] - let root = [ + const values: Array<{ path: string; value: any }> = [] + const navPath = [ 'navigation.courseGreatCircle', 'navigation.courseRhumbline' ] @@ -519,82 +542,82 @@ export class CourseApi { }) values.push({ - path: `${root[0]}.activeRoute.href`, + path: `${navPath[0]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${root[1]}.activeRoute.href`, + path: `${navPath[1]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${root[0]}.activeRoute.startTime`, + path: `${navPath[0]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${root[1]}.activeRoute.startTime`, + path: `${navPath[1]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${root[0]}.nextPoint.href`, + path: `${navPath[0]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${root[1]}.nextPoint.href`, + path: `${navPath[1]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${root[0]}.nextPoint.position`, + path: `${navPath[0]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${root[1]}.nextPoint.position`, + path: `${navPath[1]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${root[0]}.nextPoint.type`, + path: `${navPath[0]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${root[1]}.nextPoint.type`, + path: `${navPath[1]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${root[0]}.nextPoint.arrivalCircle`, + path: `${navPath[0]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${root[1]}.nextPoint.arrivalCircle`, + path: `${navPath[1]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${root[0]}.previousPoint.href`, + path: `${navPath[0]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${root[1]}.previousPoint.href`, + path: `${navPath[1]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${root[0]}.previousPoint.position`, + path: `${navPath[0]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${root[1]}.previousPoint.position`, + path: `${navPath[1]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${root[0]}.previousPoint.type`, + path: `${navPath[0]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) values.push({ - path: `${root[1]}.previousPoint.type`, + path: `${navPath[1]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) return { updates: [ { - values: values + values } ] } diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index 04653db5e..b162d1314 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -108,29 +108,7 @@ "put": { "tags": ["course"], "summary": "Restart course calculations", - "description": "Sets previousPoint value to current vessel location", - "requestBody": { - "description": "Restart payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } - } - } - } - } - } + "description": "Sets previousPoint value to current vessel location" } }, @@ -200,29 +178,7 @@ "delete": { "tags": ["course/destination"], "summary": "Clear destination", - "description": "Sets activeRoute, nextPoint & previousPoint values to null", - "requestBody": { - "description": "Delete payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } - } - } - } - } - } + "description": "Sets activeRoute, nextPoint & previousPoint values to null" } }, @@ -313,29 +269,7 @@ "delete": { "tags": ["course/activeRoute"], "summary": "Clear active route", - "description": "Sets activeRoute, nextPoint & previousPoint values to null", - "requestBody": { - "description": "Delete payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } - } - } - } - } - } + "description": "Sets activeRoute, nextPoint & previousPoint values to null" } }, From d8c568542202070a3787575fbd00aa3f41823a2d Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 8 Dec 2021 09:27:06 +1030 Subject: [PATCH 320/410] add 30sec delta interval --- src/api/course/index.ts | 100 ++++++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 45 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 6cd3b80fd..2aa57e8a6 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -6,6 +6,8 @@ const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const DELTA_INTERVAL: number = 30000 + interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any @@ -97,6 +99,11 @@ export class CourseApi { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app this.initResourceRoutes() + setInterval( ()=> { + if(this.courseInfo.nextPoint.position) { + this.emitCourseInfo() + } + }, DELTA_INTERVAL) } private updateAllowed(): boolean { @@ -123,7 +130,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/restart`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (!this.courseInfo.nextPoint.position) { @@ -136,7 +143,7 @@ export class CourseApi { if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } } catch (err) { res.status(406).send(`Vessel position unavailable!`) @@ -149,13 +156,13 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (this.isValidArrivalCircle(req.body.value)) { this.courseInfo.nextPoint.arrivalCircle = req.body.value this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { res.status(406).send(`Invalid Data`) } @@ -168,7 +175,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (!req.body.value) { @@ -178,7 +185,7 @@ export class CourseApi { const result = await this.setDestination(req.body.value) if (result) { this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { this.clearDestination() this.emitCourseInfo() @@ -193,12 +200,12 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } this.clearDestination() this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } ) @@ -208,13 +215,13 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } const result = await this.activateRoute(req.body.value) if (result) { this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { this.clearDestination() this.emitCourseInfo() @@ -234,7 +241,7 @@ export class CourseApi { } this.clearDestination() this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } ) @@ -243,7 +250,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } // fetch active route data @@ -268,6 +275,7 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) + return } } if (req.params.action === 'pointIndex') { @@ -278,6 +286,7 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) + return } } @@ -298,9 +307,11 @@ export class CourseApi { this.courseInfo.previousPoint.position = position.value this.courseInfo.previousPoint.type = `VesselPosition` } else { + res.status(406).send(`Invalid Data`) return false } } catch (err) { + res.status(406).send(`Invalid Data`) return false } } else { @@ -312,8 +323,7 @@ export class CourseApi { this.courseInfo.previousPoint.type = `RoutePoint` } this.courseInfo.previousPoint.href = null - - res.status(200) + res.status(200).send('OK') } ) } @@ -333,38 +343,38 @@ export class CourseApi { return false } - const newDest: any = {} - Object.assign(newDest, this.courseInfo) + const newCourse: any = {} + Object.assign(newCourse, this.courseInfo) // set activeroute - newDest.activeRoute.href = route.href + newCourse.activeRoute.href = route.href if (this.isValidArrivalCircle(route.arrivalCircle as number)) { - newDest.nextPoint.arrivalCircle = route.arrivalCircle + newCourse.nextPoint.arrivalCircle = route.arrivalCircle } - newDest.activeRoute.startTime = new Date().toISOString() + newCourse.activeRoute.startTime = new Date().toISOString() if (typeof route.reverse === 'boolean') { - newDest.activeRoute.reverse = route.reverse + newCourse.activeRoute.reverse = route.reverse } - newDest.activeRoute.pointIndex = this.parsePointIndex( + newCourse.activeRoute.pointIndex = this.parsePointIndex( route.pointIndex as number, rte ) // set nextPoint - newDest.nextPoint.position = this.getRoutePoint( + newCourse.nextPoint.position = this.getRoutePoint( rte, - newDest.activeRoute.pointIndex, - newDest.activeRoute.reverse + newCourse.activeRoute.pointIndex, + newCourse.activeRoute.reverse ) - newDest.nextPoint.type = `RoutePoint` - newDest.nextPoint.href = null + newCourse.nextPoint.type = `RoutePoint` + newCourse.nextPoint.href = null // set previousPoint - if (newDest.activeRoute.pointIndex === 0) { + if (newCourse.activeRoute.pointIndex === 0) { try { const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -377,32 +387,32 @@ export class CourseApi { return false } } else { - newDest.previousPoint.position = this.getRoutePoint( + newCourse.previousPoint.position = this.getRoutePoint( rte, - newDest.activeRoute.pointIndex - 1, - newDest.activeRoute.reverse + newCourse.activeRoute.pointIndex - 1, + newCourse.activeRoute.reverse ) - newDest.previousPoint.type = `RoutePoint` + newCourse.previousPoint.type = `RoutePoint` } - newDest.previousPoint.href = null + newCourse.previousPoint.href = null - this.courseInfo = newDest + this.courseInfo = newCourse return true } private async setDestination(dest: any): Promise { - const newDest: any = {} - Object.assign(newDest, this.courseInfo) + const newCourse: any = {} + Object.assign(newCourse, this.courseInfo) // set nextPoint if (this.isValidArrivalCircle(dest.arrivalCircle)) { - newDest.nextPoint.arrivalCircle = dest.arrivalCircle + newCourse.nextPoint.arrivalCircle = dest.arrivalCircle } - newDest.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null + newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - newDest.href = dest.href + newCourse.href = dest.href const href = this.parseHref(dest.href) if (href) { // fetch waypoint resource details @@ -412,7 +422,7 @@ export class CourseApi { href.id ) if (r.position && typeof r.position?.latitude !== 'undefined') { - newDest.nextPoint.position = r.position + newCourse.nextPoint.position = r.position } else { return false } @@ -421,9 +431,9 @@ export class CourseApi { } } } else if (dest.position) { - newDest.nextPoint.href = null + newCourse.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { - newDest.nextPoint.position = dest.position + newCourse.nextPoint.position = dest.position } else { return false } @@ -435,17 +445,17 @@ export class CourseApi { try { const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { - newDest.previousPoint.position = position.value - newDest.previousPoint.type = `VesselPosition` + newCourse.previousPoint.position = position.value + newCourse.previousPoint.type = `VesselPosition` } else { return false } - newDest.previousPoint.href = null + newCourse.previousPoint.href = null } catch (err) { return false } - this.courseInfo = newDest + this.courseInfo = newCourse return true } From 454ae4a04a55263402811bd78a77d845c09a8158 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 12 Dec 2021 14:29:38 +1030 Subject: [PATCH 321/410] chore: lint --- src/api/course/index.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 2aa57e8a6..1e719297a 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -6,7 +6,7 @@ const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const DELTA_INTERVAL: number = 30000 +const DELTA_INTERVAL: number = 30000 interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void @@ -99,8 +99,8 @@ export class CourseApi { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app this.initResourceRoutes() - setInterval( ()=> { - if(this.courseInfo.nextPoint.position) { + setInterval(() => { + if (this.courseInfo.nextPoint.position) { this.emitCourseInfo() } }, DELTA_INTERVAL) @@ -409,7 +409,8 @@ export class CourseApi { newCourse.nextPoint.arrivalCircle = dest.arrivalCircle } - newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null + newCourse.nextPoint.type = + typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { newCourse.href = dest.href From a38fb8b52072471c60ec709508306f585cce76af Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 4 Dec 2021 17:12:14 +1030 Subject: [PATCH 322/410] init courseApi --- src/api/course/index.ts | 318 +++++++++++++----------------------- src/api/course/openApi.json | 72 +++++++- 2 files changed, 187 insertions(+), 203 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 1e719297a..6e8e4074d 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,24 +1,23 @@ +import { + ResourceProvider, + ResourceProviderMethods, + SignalKResourceType +} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' +import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' -const DELTA_INTERVAL: number = 30000 +const API_METHODS: string[] = [] interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - securityStrategy: { - shouldAllowPut: ( - req: Application, - context: string, - source: any, - path: string - ) => boolean - } resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -99,20 +98,6 @@ export class CourseApi { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app this.initResourceRoutes() - setInterval(() => { - if (this.courseInfo.nextPoint.position) { - this.emitCourseInfo() - } - }, DELTA_INTERVAL) - } - - private updateAllowed(): boolean { - return this.server.securityStrategy.shouldAllowPut( - this.server, - 'vessels.self', - null, - 'navigation.course' - ) } private initResourceRoutes() { @@ -125,46 +110,29 @@ export class CourseApi { } ) + // restart / arrivalCircle this.server.put( - `${COURSE_API_PATH}/restart`, + `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { - debug(`** PUT ${COURSE_API_PATH}/restart`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } - if (!this.courseInfo.nextPoint.position) { - res.status(406).send(`No active destination!`) - return - } - // set previousPoint to vessel position - try { + debug(`** PUT ${COURSE_API_PATH}/:action`) + if (req.params.restart) { + // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).send(`Course restarted.`) + } else { + res.status(406).send(`Vessel position unavailable!`) } - } catch (err) { - res.status(406).send(`Vessel position unavailable!`) - } - } - ) - - this.server.put( - `${COURSE_API_PATH}/arrivalCircle`, - async (req: Request, res: Response) => { - debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return } - if (this.isValidArrivalCircle(req.body.value)) { - this.courseInfo.nextPoint.arrivalCircle = req.body.value - this.emitCourseInfo() - res.status(200).send('OK') - } else { - res.status(406).send(`Invalid Data`) + if (req.params.arrivalCircle) { + if (this.setArrivalCircle(req.params.arrivalCircle)) { + this.emitCourseInfo() + res.status(200).send(`Destination set successfully.`) + } else { + res.status(406).send(`Invalid Data`) + } } } ) @@ -174,10 +142,6 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } if (!req.body.value) { res.status(406).send(`Invalid Data`) return @@ -185,7 +149,7 @@ export class CourseApi { const result = await this.setDestination(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).send(`Destination set successfully.`) } else { this.clearDestination() this.emitCourseInfo() @@ -199,29 +163,22 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } this.clearDestination() this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).send(`Destination cleared.`) } ) - // set activeRoute + // set / clear activeRoute this.server.put( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } + const result = await this.activateRoute(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).send(`Active route set.`) } else { this.clearDestination() this.emitCourseInfo() @@ -229,19 +186,14 @@ export class CourseApi { } } ) - - // clear activeRoute /destination this.server.delete( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/activeRoute`) - if (!this.updateAllowed()) { - res.status(403) - return - } + this.clearDestination() this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).send(`Active route cleared.`) } ) @@ -249,11 +201,6 @@ export class CourseApi { `${COURSE_API_PATH}/activeRoute/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } - // fetch active route data if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return @@ -264,7 +211,7 @@ export class CourseApi { return } - if (req.params.action === 'nextPoint') { + if (req.params.nextPoint) { if ( typeof req.body.value === 'number' && (req.body.value === 1 || req.body.value === -1) @@ -275,10 +222,9 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) - return } } - if (req.params.action === 'pointIndex') { + if (req.params.pointIndex) { if (typeof req.body.value === 'number') { this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( req.body.value, @@ -286,44 +232,36 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) - return } } - // set new destination + // set nextPoint this.courseInfo.nextPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex, - this.courseInfo.activeRoute.reverse + this.courseInfo.activeRoute.pointIndex ) this.courseInfo.nextPoint.type = `RoutePoint` this.courseInfo.nextPoint.href = null // set previousPoint if (this.courseInfo.activeRoute.pointIndex === 0) { - try { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { - res.status(406).send(`Invalid Data`) - return false - } - } catch (err) { - res.status(406).send(`Invalid Data`) + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { return false } } else { this.courseInfo.previousPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex - 1, - this.courseInfo.activeRoute.reverse + this.courseInfo.activeRoute.pointIndex - 1 ) this.courseInfo.previousPoint.type = `RoutePoint` } this.courseInfo.previousPoint.href = null - res.status(200).send('OK') + + res.status(200).send(`OK`) } ) } @@ -343,98 +281,81 @@ export class CourseApi { return false } - const newCourse: any = {} - Object.assign(newCourse, this.courseInfo) - // set activeroute - newCourse.activeRoute.href = route.href + this.courseInfo.activeRoute.href = route.href - if (this.isValidArrivalCircle(route.arrivalCircle as number)) { - newCourse.nextPoint.arrivalCircle = route.arrivalCircle + if (typeof route.arrivalCircle === 'number') { + this.setArrivalCircle(route.arrivalCircle) } - newCourse.activeRoute.startTime = new Date().toISOString() + this.courseInfo.activeRoute.reverse = + typeof route.reverse === 'boolean' ? route.reverse : false - if (typeof route.reverse === 'boolean') { - newCourse.activeRoute.reverse = route.reverse - } + this.courseInfo.activeRoute.pointIndex = + typeof route.pointIndex === 'number' + ? this.parsePointIndex(route.pointIndex, rte) + : 0 - newCourse.activeRoute.pointIndex = this.parsePointIndex( - route.pointIndex as number, - rte - ) + this.courseInfo.activeRoute.startTime = new Date().toISOString() // set nextPoint - newCourse.nextPoint.position = this.getRoutePoint( + this.courseInfo.nextPoint.position = this.getRoutePoint( rte, - newCourse.activeRoute.pointIndex, - newCourse.activeRoute.reverse + this.courseInfo.activeRoute.pointIndex ) - newCourse.nextPoint.type = `RoutePoint` - newCourse.nextPoint.href = null + this.courseInfo.nextPoint.type = `RoutePoint` + this.courseInfo.nextPoint.href = null // set previousPoint - if (newCourse.activeRoute.pointIndex === 0) { - try { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { - return false - } - } catch (err) { + if (this.courseInfo.activeRoute.pointIndex === 0) { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { return false } } else { - newCourse.previousPoint.position = this.getRoutePoint( + this.courseInfo.previousPoint.position = this.getRoutePoint( rte, - newCourse.activeRoute.pointIndex - 1, - newCourse.activeRoute.reverse + this.courseInfo.activeRoute.pointIndex - 1 ) - newCourse.previousPoint.type = `RoutePoint` + this.courseInfo.previousPoint.type = `RoutePoint` } - newCourse.previousPoint.href = null + this.courseInfo.previousPoint.href = null - this.courseInfo = newCourse return true } - private async setDestination(dest: any): Promise { - const newCourse: any = {} - Object.assign(newCourse, this.courseInfo) - + private async setDestination(dest: Destination): Promise { // set nextPoint - if (this.isValidArrivalCircle(dest.arrivalCircle)) { - newCourse.nextPoint.arrivalCircle = dest.arrivalCircle + if (typeof dest.arrivalCircle === 'number') { + this.setArrivalCircle(dest.arrivalCircle) } - newCourse.nextPoint.type = + this.courseInfo.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - newCourse.href = dest.href + this.courseInfo.nextPoint.href = dest.href const href = this.parseHref(dest.href) if (href) { - // fetch waypoint resource details try { const r = await this.server.resourcesApi.getResource( href.type, href.id ) - if (r.position && typeof r.position?.latitude !== 'undefined') { - newCourse.nextPoint.position = r.position - } else { - return false + if (r.position && typeof r.position.value?.latitude !== 'undefined') { + this.courseInfo.nextPoint.position = r.position.value } } catch (err) { return false } } } else if (dest.position) { - newCourse.nextPoint.href = null + this.courseInfo.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { - newCourse.nextPoint.position = dest.position + this.courseInfo.nextPoint.position = dest.position } else { return false } @@ -443,20 +364,15 @@ export class CourseApi { } // set previousPoint - try { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - newCourse.previousPoint.position = position.value - newCourse.previousPoint.type = `VesselPosition` - } else { - return false - } - newCourse.previousPoint.href = null - } catch (err) { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { return false } + this.courseInfo.previousPoint.href = null - this.courseInfo = newCourse return true } @@ -473,12 +389,17 @@ export class CourseApi { this.courseInfo.previousPoint.position = null } - private isValidArrivalCircle(value: number): boolean { - return typeof value === 'number' && value >= 0 + private setArrivalCircle(value: any): boolean { + if (typeof value === 'number' && value >= 0) { + this.courseInfo.nextPoint.arrivalCircle = value + return true + } else { + return false + } } private parsePointIndex(index: number, rte: any): number { - if (typeof index !== 'number' || !rte) { + if (!rte) { return 0 } if (!rte.feature?.geometry?.coordinates) { @@ -497,11 +418,13 @@ export class CourseApi { } private parseHref(href: string): { type: string; id: string } | undefined { - if (!href) { + if (href.length === 0) { return undefined } - - const ref: string[] = href.split('/').slice(-3) + if (href[0] === '/') { + href = href.slice(1) + } + const ref: string[] = href.split('/') if (ref.length < 3) { return undefined } @@ -514,8 +437,8 @@ export class CourseApi { } } - private getRoutePoint(rte: any, index: number, reverse: boolean) { - const pos = reverse + private getRoutePoint(rte: any, index: number) { + const pos = this.courseInfo.activeRoute.reverse ? rte.feature.geometry.coordinates[ rte.feature.geometry.coordinates.length - (index + 1) ] @@ -523,7 +446,7 @@ export class CourseApi { return { latitude: pos[1], longitude: pos[0], - altitude: pos.length === 3 ? pos[2] : 0 + altitude: pos.length == 3 ? pos[2] : 0 } } @@ -541,94 +464,89 @@ export class CourseApi { } private buildDeltaMsg(): any { - const values: Array<{ path: string; value: any }> = [] - const navPath = [ + let values: Array<{path:string, value:any}> = [] + let root = [ 'navigation.courseGreatCircle', 'navigation.courseRhumbline' ] values.push({ - path: `navigation.course`, - value: this.courseInfo - }) - - values.push({ - path: `${navPath[0]}.activeRoute.href`, + path: `${root[0]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${navPath[1]}.activeRoute.href`, + path: `${root[1]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${navPath[0]}.activeRoute.startTime`, + path: `${root[0]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${navPath[1]}.activeRoute.startTime`, + path: `${root[1]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${navPath[0]}.nextPoint.href`, + path: `${root[0]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${navPath[1]}.nextPoint.href`, + path: `${root[1]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${navPath[0]}.nextPoint.position`, + path: `${root[0]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${navPath[1]}.nextPoint.position`, + path: `${root[1]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${navPath[0]}.nextPoint.type`, + path: `${root[0]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${navPath[1]}.nextPoint.type`, + path: `${root[1]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${navPath[0]}.nextPoint.arrivalCircle`, + path: `${root[0]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${navPath[1]}.nextPoint.arrivalCircle`, + path: `${root[1]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${navPath[0]}.previousPoint.href`, + path: `${root[0]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${navPath[1]}.previousPoint.href`, + path: `${root[1]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${navPath[0]}.previousPoint.position`, + path: `${root[0]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${navPath[1]}.previousPoint.position`, + path: `${root[1]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${navPath[0]}.previousPoint.type`, + path: `${root[0]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) values.push({ - path: `${navPath[1]}.previousPoint.type`, + path: `${root[1]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) return { updates: [ { - values + values: values } ] } diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index b162d1314..04653db5e 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -108,7 +108,29 @@ "put": { "tags": ["course"], "summary": "Restart course calculations", - "description": "Sets previousPoint value to current vessel location" + "description": "Sets previousPoint value to current vessel location", + "requestBody": { + "description": "Restart payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } } }, @@ -178,7 +200,29 @@ "delete": { "tags": ["course/destination"], "summary": "Clear destination", - "description": "Sets activeRoute, nextPoint & previousPoint values to null" + "description": "Sets activeRoute, nextPoint & previousPoint values to null", + "requestBody": { + "description": "Delete payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } } }, @@ -269,7 +313,29 @@ "delete": { "tags": ["course/activeRoute"], "summary": "Clear active route", - "description": "Sets activeRoute, nextPoint & previousPoint values to null" + "description": "Sets activeRoute, nextPoint & previousPoint values to null", + "requestBody": { + "description": "Delete payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } } }, From 1572e869ae2f3dbca58a44bb22ae1233cc456563 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 5 Dec 2021 10:26:47 +1030 Subject: [PATCH 323/410] update detlas --- src/api/course/index.ts | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 6e8e4074d..7510ca1e7 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,23 +1,15 @@ -import { - ResourceProvider, - ResourceProviderMethods, - SignalKResourceType -} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' -import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' - -const API_METHODS: string[] = [] interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any + registerPutHandler: (context:string, path:string, cb:any) => any resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -110,12 +102,26 @@ export class CourseApi { } ) + // + if(this.server.registerPutHandler) { + debug('** Registering PUT Action Handler(s) **') + this.server.registerPutHandler( + 'vessels.self', + 'navigation.course.*', + this.handleCourseApiPut + ); + } + // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { + //test for active destination + if (!this.courseInfo.nextPoint.position) { + return + } // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -266,6 +272,12 @@ export class CourseApi { ) } + private handleCourseApiPut(context:string, path:string, value:any, cb:any) { + + debug('** PUT handler **') + return undefined + } + private async activateRoute(route: ActiveRoute): Promise { let rte: any @@ -470,6 +482,11 @@ export class CourseApi { 'navigation.courseRhumbline' ] + values.push({ + path: `navigation.course`, + value: this.courseInfo + }) + values.push({ path: `${root[0]}.activeRoute.href`, value: this.courseInfo.activeRoute.href From 3024f2325ab5b363ac5706fc70f1d5af8107236e Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 4 Dec 2021 17:12:14 +1030 Subject: [PATCH 324/410] init courseApi --- src/api/course/index.ts | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 7510ca1e7..af32aceee 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,15 +1,26 @@ + +import { + ResourceProvider, + ResourceProviderMethods, + SignalKResourceType +} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' +import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' + +const API_METHODS: string[] = [] + interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - registerPutHandler: (context:string, path:string, cb:any) => any + resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -102,26 +113,12 @@ export class CourseApi { } ) - // - if(this.server.registerPutHandler) { - debug('** Registering PUT Action Handler(s) **') - this.server.registerPutHandler( - 'vessels.self', - 'navigation.course.*', - this.handleCourseApiPut - ); - } - // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { - //test for active destination - if (!this.courseInfo.nextPoint.position) { - return - } // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -272,12 +269,6 @@ export class CourseApi { ) } - private handleCourseApiPut(context:string, path:string, value:any, cb:any) { - - debug('** PUT handler **') - return undefined - } - private async activateRoute(route: ActiveRoute): Promise { let rte: any From c4296c983a023941442db56abd98a26d153b18ec Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 5 Dec 2021 10:26:47 +1030 Subject: [PATCH 325/410] update detlas --- src/api/course/index.ts | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index af32aceee..7510ca1e7 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,26 +1,15 @@ - -import { - ResourceProvider, - ResourceProviderMethods, - SignalKResourceType -} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' -import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' - -const API_METHODS: string[] = [] - interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - + registerPutHandler: (context:string, path:string, cb:any) => any resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -113,12 +102,26 @@ export class CourseApi { } ) + // + if(this.server.registerPutHandler) { + debug('** Registering PUT Action Handler(s) **') + this.server.registerPutHandler( + 'vessels.self', + 'navigation.course.*', + this.handleCourseApiPut + ); + } + // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { + //test for active destination + if (!this.courseInfo.nextPoint.position) { + return + } // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -269,6 +272,12 @@ export class CourseApi { ) } + private handleCourseApiPut(context:string, path:string, value:any, cb:any) { + + debug('** PUT handler **') + return undefined + } + private async activateRoute(route: ActiveRoute): Promise { let rte: any From 4f2f3ae02fb4f3c4e3e1e723c529574afb2cac5f Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 6 Dec 2021 08:39:56 +1030 Subject: [PATCH 326/410] enable put processing --- src/api/course/index.ts | 133 +++++++++++++++++++++++++--------------- 1 file changed, 82 insertions(+), 51 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 7510ca1e7..1566ecdf3 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -9,7 +9,14 @@ const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/cou interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - registerPutHandler: (context:string, path:string, cb:any) => any + securityStrategy: { + shouldAllowPut: ( + req: any, + context: string, + source: any, + path: string + ) => boolean + } resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -92,6 +99,15 @@ export class CourseApi { this.initResourceRoutes() } + private updateAllowed(): boolean { + return this.server.securityStrategy.shouldAllowPut( + this.server, + 'vessels.self', + null, + 'resources' + ) + } + private initResourceRoutes() { // return current course information this.server.get( @@ -102,43 +118,43 @@ export class CourseApi { } ) - // - if(this.server.registerPutHandler) { - debug('** Registering PUT Action Handler(s) **') - this.server.registerPutHandler( - 'vessels.self', - 'navigation.course.*', - this.handleCourseApiPut - ); - } + this.server.put( + `${COURSE_API_PATH}/restart`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/restart`) + if (!this.updateAllowed()) { + res.status(403) + return + } + if (!this.courseInfo.nextPoint.position) { + res.status(406).send(`No active destination!`) + return + } + // set previousPoint to vessel position + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.emitCourseInfo() + res.status(200) + } else { + res.status(406).send(`Vessel position unavailable!`) + } + } + ) - // restart / arrivalCircle this.server.put( - `${COURSE_API_PATH}/:action`, + `${COURSE_API_PATH}/arrivalCircle`, async (req: Request, res: Response) => { - debug(`** PUT ${COURSE_API_PATH}/:action`) - if (req.params.restart) { - //test for active destination - if (!this.courseInfo.nextPoint.position) { - return - } - // set previousPoint to vessel position - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.emitCourseInfo() - res.status(200).send(`Course restarted.`) - } else { - res.status(406).send(`Vessel position unavailable!`) - } + debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) + if (!this.updateAllowed()) { + res.status(403) + return } - if (req.params.arrivalCircle) { - if (this.setArrivalCircle(req.params.arrivalCircle)) { - this.emitCourseInfo() - res.status(200).send(`Destination set successfully.`) - } else { - res.status(406).send(`Invalid Data`) - } + if (this.setArrivalCircle(req.body.value)) { + this.emitCourseInfo() + res.status(200) + } else { + res.status(406).send(`Invalid Data`) } } ) @@ -148,6 +164,10 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) + if (!this.updateAllowed()) { + res.status(403) + return + } if (!req.body.value) { res.status(406).send(`Invalid Data`) return @@ -155,7 +175,7 @@ export class CourseApi { const result = await this.setDestination(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send(`Destination set successfully.`) + res.status(200) } else { this.clearDestination() this.emitCourseInfo() @@ -169,22 +189,29 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) + if (!this.updateAllowed()) { + res.status(403) + return + } this.clearDestination() this.emitCourseInfo() - res.status(200).send(`Destination cleared.`) + res.status(200) } ) - // set / clear activeRoute + // set activeRoute this.server.put( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) - + if (!this.updateAllowed()) { + res.status(403) + return + } const result = await this.activateRoute(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send(`Active route set.`) + res.status(200) } else { this.clearDestination() this.emitCourseInfo() @@ -192,14 +219,19 @@ export class CourseApi { } } ) + + // clear activeRoute /destination this.server.delete( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/activeRoute`) - + if (!this.updateAllowed()) { + res.status(403) + return + } this.clearDestination() this.emitCourseInfo() - res.status(200).send(`Active route cleared.`) + res.status(200) } ) @@ -207,6 +239,11 @@ export class CourseApi { `${COURSE_API_PATH}/activeRoute/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) + if (!this.updateAllowed()) { + res.status(403) + return + } + // fetch route data if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return @@ -217,7 +254,7 @@ export class CourseApi { return } - if (req.params.nextPoint) { + if (req.params.action === 'nextPoint') { if ( typeof req.body.value === 'number' && (req.body.value === 1 || req.body.value === -1) @@ -230,7 +267,7 @@ export class CourseApi { res.status(406).send(`Invalid Data`) } } - if (req.params.pointIndex) { + if (req.params.action === 'pointIndex') { if (typeof req.body.value === 'number') { this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( req.body.value, @@ -241,7 +278,7 @@ export class CourseApi { } } - // set nextPoint + // set new destination this.courseInfo.nextPoint.position = this.getRoutePoint( rte, this.courseInfo.activeRoute.pointIndex @@ -267,17 +304,11 @@ export class CourseApi { } this.courseInfo.previousPoint.href = null - res.status(200).send(`OK`) + res.status(200) } ) } - private handleCourseApiPut(context:string, path:string, value:any, cb:any) { - - debug('** PUT handler **') - return undefined - } - private async activateRoute(route: ActiveRoute): Promise { let rte: any From fb803caa62a4e2ba9d96b1d1e76f30012eddb031 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 6 Dec 2021 13:34:31 +1030 Subject: [PATCH 327/410] chore: lint --- src/api/course/index.ts | 211 ++++++++++++++++++++---------------- src/api/course/openApi.json | 72 +----------- 2 files changed, 120 insertions(+), 163 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 1566ecdf3..6cd3b80fd 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -11,7 +11,7 @@ interface CourseApplication extends Application { getSelfPath: (path: string) => any securityStrategy: { shouldAllowPut: ( - req: any, + req: Application, context: string, source: any, path: string @@ -104,7 +104,7 @@ export class CourseApi { this.server, 'vessels.self', null, - 'resources' + 'navigation.course' ) } @@ -131,12 +131,14 @@ export class CourseApi { return } // set previousPoint to vessel position - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.emitCourseInfo() - res.status(200) - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.emitCourseInfo() + res.status(200) + } + } catch (err) { res.status(406).send(`Vessel position unavailable!`) } } @@ -150,7 +152,8 @@ export class CourseApi { res.status(403) return } - if (this.setArrivalCircle(req.body.value)) { + if (this.isValidArrivalCircle(req.body.value)) { + this.courseInfo.nextPoint.arrivalCircle = req.body.value this.emitCourseInfo() res.status(200) } else { @@ -243,7 +246,7 @@ export class CourseApi { res.status(403) return } - // fetch route data + // fetch active route data if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return @@ -281,24 +284,30 @@ export class CourseApi { // set new destination this.courseInfo.nextPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex + this.courseInfo.activeRoute.pointIndex, + this.courseInfo.activeRoute.reverse ) this.courseInfo.nextPoint.type = `RoutePoint` this.courseInfo.nextPoint.href = null // set previousPoint if (this.courseInfo.activeRoute.pointIndex === 0) { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } catch (err) { return false } } else { this.courseInfo.previousPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex - 1 + this.courseInfo.activeRoute.pointIndex - 1, + this.courseInfo.activeRoute.reverse ) this.courseInfo.previousPoint.type = `RoutePoint` } @@ -324,81 +333,97 @@ export class CourseApi { return false } + const newDest: any = {} + Object.assign(newDest, this.courseInfo) + // set activeroute - this.courseInfo.activeRoute.href = route.href + newDest.activeRoute.href = route.href - if (typeof route.arrivalCircle === 'number') { - this.setArrivalCircle(route.arrivalCircle) + if (this.isValidArrivalCircle(route.arrivalCircle as number)) { + newDest.nextPoint.arrivalCircle = route.arrivalCircle } - this.courseInfo.activeRoute.reverse = - typeof route.reverse === 'boolean' ? route.reverse : false + newDest.activeRoute.startTime = new Date().toISOString() - this.courseInfo.activeRoute.pointIndex = - typeof route.pointIndex === 'number' - ? this.parsePointIndex(route.pointIndex, rte) - : 0 + if (typeof route.reverse === 'boolean') { + newDest.activeRoute.reverse = route.reverse + } - this.courseInfo.activeRoute.startTime = new Date().toISOString() + newDest.activeRoute.pointIndex = this.parsePointIndex( + route.pointIndex as number, + rte + ) // set nextPoint - this.courseInfo.nextPoint.position = this.getRoutePoint( + newDest.nextPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex + newDest.activeRoute.pointIndex, + newDest.activeRoute.reverse ) - this.courseInfo.nextPoint.type = `RoutePoint` - this.courseInfo.nextPoint.href = null + newDest.nextPoint.type = `RoutePoint` + newDest.nextPoint.href = null // set previousPoint - if (this.courseInfo.activeRoute.pointIndex === 0) { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + if (newDest.activeRoute.pointIndex === 0) { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } catch (err) { return false } } else { - this.courseInfo.previousPoint.position = this.getRoutePoint( + newDest.previousPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex - 1 + newDest.activeRoute.pointIndex - 1, + newDest.activeRoute.reverse ) - this.courseInfo.previousPoint.type = `RoutePoint` + newDest.previousPoint.type = `RoutePoint` } - this.courseInfo.previousPoint.href = null + newDest.previousPoint.href = null + this.courseInfo = newDest return true } - private async setDestination(dest: Destination): Promise { + private async setDestination(dest: any): Promise { + const newDest: any = {} + Object.assign(newDest, this.courseInfo) + // set nextPoint - if (typeof dest.arrivalCircle === 'number') { - this.setArrivalCircle(dest.arrivalCircle) + if (this.isValidArrivalCircle(dest.arrivalCircle)) { + newDest.nextPoint.arrivalCircle = dest.arrivalCircle } - this.courseInfo.nextPoint.type = - typeof dest.type !== 'undefined' ? dest.type : null + newDest.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - this.courseInfo.nextPoint.href = dest.href + newDest.href = dest.href const href = this.parseHref(dest.href) if (href) { + // fetch waypoint resource details try { const r = await this.server.resourcesApi.getResource( href.type, href.id ) - if (r.position && typeof r.position.value?.latitude !== 'undefined') { - this.courseInfo.nextPoint.position = r.position.value + if (r.position && typeof r.position?.latitude !== 'undefined') { + newDest.nextPoint.position = r.position + } else { + return false } } catch (err) { return false } } } else if (dest.position) { - this.courseInfo.nextPoint.href = null + newDest.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { - this.courseInfo.nextPoint.position = dest.position + newDest.nextPoint.position = dest.position } else { return false } @@ -407,15 +432,20 @@ export class CourseApi { } // set previousPoint - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + newDest.previousPoint.position = position.value + newDest.previousPoint.type = `VesselPosition` + } else { + return false + } + newDest.previousPoint.href = null + } catch (err) { return false } - this.courseInfo.previousPoint.href = null + this.courseInfo = newDest return true } @@ -432,17 +462,12 @@ export class CourseApi { this.courseInfo.previousPoint.position = null } - private setArrivalCircle(value: any): boolean { - if (typeof value === 'number' && value >= 0) { - this.courseInfo.nextPoint.arrivalCircle = value - return true - } else { - return false - } + private isValidArrivalCircle(value: number): boolean { + return typeof value === 'number' && value >= 0 } private parsePointIndex(index: number, rte: any): number { - if (!rte) { + if (typeof index !== 'number' || !rte) { return 0 } if (!rte.feature?.geometry?.coordinates) { @@ -461,13 +486,11 @@ export class CourseApi { } private parseHref(href: string): { type: string; id: string } | undefined { - if (href.length === 0) { + if (!href) { return undefined } - if (href[0] === '/') { - href = href.slice(1) - } - const ref: string[] = href.split('/') + + const ref: string[] = href.split('/').slice(-3) if (ref.length < 3) { return undefined } @@ -480,8 +503,8 @@ export class CourseApi { } } - private getRoutePoint(rte: any, index: number) { - const pos = this.courseInfo.activeRoute.reverse + private getRoutePoint(rte: any, index: number, reverse: boolean) { + const pos = reverse ? rte.feature.geometry.coordinates[ rte.feature.geometry.coordinates.length - (index + 1) ] @@ -489,7 +512,7 @@ export class CourseApi { return { latitude: pos[1], longitude: pos[0], - altitude: pos.length == 3 ? pos[2] : 0 + altitude: pos.length === 3 ? pos[2] : 0 } } @@ -507,8 +530,8 @@ export class CourseApi { } private buildDeltaMsg(): any { - let values: Array<{path:string, value:any}> = [] - let root = [ + const values: Array<{ path: string; value: any }> = [] + const navPath = [ 'navigation.courseGreatCircle', 'navigation.courseRhumbline' ] @@ -519,82 +542,82 @@ export class CourseApi { }) values.push({ - path: `${root[0]}.activeRoute.href`, + path: `${navPath[0]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${root[1]}.activeRoute.href`, + path: `${navPath[1]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${root[0]}.activeRoute.startTime`, + path: `${navPath[0]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${root[1]}.activeRoute.startTime`, + path: `${navPath[1]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${root[0]}.nextPoint.href`, + path: `${navPath[0]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${root[1]}.nextPoint.href`, + path: `${navPath[1]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${root[0]}.nextPoint.position`, + path: `${navPath[0]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${root[1]}.nextPoint.position`, + path: `${navPath[1]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${root[0]}.nextPoint.type`, + path: `${navPath[0]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${root[1]}.nextPoint.type`, + path: `${navPath[1]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${root[0]}.nextPoint.arrivalCircle`, + path: `${navPath[0]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${root[1]}.nextPoint.arrivalCircle`, + path: `${navPath[1]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${root[0]}.previousPoint.href`, + path: `${navPath[0]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${root[1]}.previousPoint.href`, + path: `${navPath[1]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${root[0]}.previousPoint.position`, + path: `${navPath[0]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${root[1]}.previousPoint.position`, + path: `${navPath[1]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${root[0]}.previousPoint.type`, + path: `${navPath[0]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) values.push({ - path: `${root[1]}.previousPoint.type`, + path: `${navPath[1]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) return { updates: [ { - values: values + values } ] } diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index 04653db5e..b162d1314 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -108,29 +108,7 @@ "put": { "tags": ["course"], "summary": "Restart course calculations", - "description": "Sets previousPoint value to current vessel location", - "requestBody": { - "description": "Restart payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } - } - } - } - } - } + "description": "Sets previousPoint value to current vessel location" } }, @@ -200,29 +178,7 @@ "delete": { "tags": ["course/destination"], "summary": "Clear destination", - "description": "Sets activeRoute, nextPoint & previousPoint values to null", - "requestBody": { - "description": "Delete payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } - } - } - } - } - } + "description": "Sets activeRoute, nextPoint & previousPoint values to null" } }, @@ -313,29 +269,7 @@ "delete": { "tags": ["course/activeRoute"], "summary": "Clear active route", - "description": "Sets activeRoute, nextPoint & previousPoint values to null", - "requestBody": { - "description": "Delete payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } - } - } - } - } - } + "description": "Sets activeRoute, nextPoint & previousPoint values to null" } }, From d84348e5db62ff0930a69b71dfcced60fed4c46c Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 8 Dec 2021 09:27:06 +1030 Subject: [PATCH 328/410] add 30sec delta interval --- src/api/course/index.ts | 100 ++++++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 45 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 6cd3b80fd..2aa57e8a6 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -6,6 +6,8 @@ const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const DELTA_INTERVAL: number = 30000 + interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any @@ -97,6 +99,11 @@ export class CourseApi { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app this.initResourceRoutes() + setInterval( ()=> { + if(this.courseInfo.nextPoint.position) { + this.emitCourseInfo() + } + }, DELTA_INTERVAL) } private updateAllowed(): boolean { @@ -123,7 +130,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/restart`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (!this.courseInfo.nextPoint.position) { @@ -136,7 +143,7 @@ export class CourseApi { if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } } catch (err) { res.status(406).send(`Vessel position unavailable!`) @@ -149,13 +156,13 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (this.isValidArrivalCircle(req.body.value)) { this.courseInfo.nextPoint.arrivalCircle = req.body.value this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { res.status(406).send(`Invalid Data`) } @@ -168,7 +175,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (!req.body.value) { @@ -178,7 +185,7 @@ export class CourseApi { const result = await this.setDestination(req.body.value) if (result) { this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { this.clearDestination() this.emitCourseInfo() @@ -193,12 +200,12 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } this.clearDestination() this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } ) @@ -208,13 +215,13 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } const result = await this.activateRoute(req.body.value) if (result) { this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { this.clearDestination() this.emitCourseInfo() @@ -234,7 +241,7 @@ export class CourseApi { } this.clearDestination() this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } ) @@ -243,7 +250,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } // fetch active route data @@ -268,6 +275,7 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) + return } } if (req.params.action === 'pointIndex') { @@ -278,6 +286,7 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) + return } } @@ -298,9 +307,11 @@ export class CourseApi { this.courseInfo.previousPoint.position = position.value this.courseInfo.previousPoint.type = `VesselPosition` } else { + res.status(406).send(`Invalid Data`) return false } } catch (err) { + res.status(406).send(`Invalid Data`) return false } } else { @@ -312,8 +323,7 @@ export class CourseApi { this.courseInfo.previousPoint.type = `RoutePoint` } this.courseInfo.previousPoint.href = null - - res.status(200) + res.status(200).send('OK') } ) } @@ -333,38 +343,38 @@ export class CourseApi { return false } - const newDest: any = {} - Object.assign(newDest, this.courseInfo) + const newCourse: any = {} + Object.assign(newCourse, this.courseInfo) // set activeroute - newDest.activeRoute.href = route.href + newCourse.activeRoute.href = route.href if (this.isValidArrivalCircle(route.arrivalCircle as number)) { - newDest.nextPoint.arrivalCircle = route.arrivalCircle + newCourse.nextPoint.arrivalCircle = route.arrivalCircle } - newDest.activeRoute.startTime = new Date().toISOString() + newCourse.activeRoute.startTime = new Date().toISOString() if (typeof route.reverse === 'boolean') { - newDest.activeRoute.reverse = route.reverse + newCourse.activeRoute.reverse = route.reverse } - newDest.activeRoute.pointIndex = this.parsePointIndex( + newCourse.activeRoute.pointIndex = this.parsePointIndex( route.pointIndex as number, rte ) // set nextPoint - newDest.nextPoint.position = this.getRoutePoint( + newCourse.nextPoint.position = this.getRoutePoint( rte, - newDest.activeRoute.pointIndex, - newDest.activeRoute.reverse + newCourse.activeRoute.pointIndex, + newCourse.activeRoute.reverse ) - newDest.nextPoint.type = `RoutePoint` - newDest.nextPoint.href = null + newCourse.nextPoint.type = `RoutePoint` + newCourse.nextPoint.href = null // set previousPoint - if (newDest.activeRoute.pointIndex === 0) { + if (newCourse.activeRoute.pointIndex === 0) { try { const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -377,32 +387,32 @@ export class CourseApi { return false } } else { - newDest.previousPoint.position = this.getRoutePoint( + newCourse.previousPoint.position = this.getRoutePoint( rte, - newDest.activeRoute.pointIndex - 1, - newDest.activeRoute.reverse + newCourse.activeRoute.pointIndex - 1, + newCourse.activeRoute.reverse ) - newDest.previousPoint.type = `RoutePoint` + newCourse.previousPoint.type = `RoutePoint` } - newDest.previousPoint.href = null + newCourse.previousPoint.href = null - this.courseInfo = newDest + this.courseInfo = newCourse return true } private async setDestination(dest: any): Promise { - const newDest: any = {} - Object.assign(newDest, this.courseInfo) + const newCourse: any = {} + Object.assign(newCourse, this.courseInfo) // set nextPoint if (this.isValidArrivalCircle(dest.arrivalCircle)) { - newDest.nextPoint.arrivalCircle = dest.arrivalCircle + newCourse.nextPoint.arrivalCircle = dest.arrivalCircle } - newDest.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null + newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - newDest.href = dest.href + newCourse.href = dest.href const href = this.parseHref(dest.href) if (href) { // fetch waypoint resource details @@ -412,7 +422,7 @@ export class CourseApi { href.id ) if (r.position && typeof r.position?.latitude !== 'undefined') { - newDest.nextPoint.position = r.position + newCourse.nextPoint.position = r.position } else { return false } @@ -421,9 +431,9 @@ export class CourseApi { } } } else if (dest.position) { - newDest.nextPoint.href = null + newCourse.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { - newDest.nextPoint.position = dest.position + newCourse.nextPoint.position = dest.position } else { return false } @@ -435,17 +445,17 @@ export class CourseApi { try { const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { - newDest.previousPoint.position = position.value - newDest.previousPoint.type = `VesselPosition` + newCourse.previousPoint.position = position.value + newCourse.previousPoint.type = `VesselPosition` } else { return false } - newDest.previousPoint.href = null + newCourse.previousPoint.href = null } catch (err) { return false } - this.courseInfo = newDest + this.courseInfo = newCourse return true } From 74336c47cc55bef8c15456125ee8032ef579e7c6 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 12 Dec 2021 14:29:38 +1030 Subject: [PATCH 329/410] chore: lint --- src/api/course/index.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 2aa57e8a6..1e719297a 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -6,7 +6,7 @@ const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const DELTA_INTERVAL: number = 30000 +const DELTA_INTERVAL: number = 30000 interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void @@ -99,8 +99,8 @@ export class CourseApi { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app this.initResourceRoutes() - setInterval( ()=> { - if(this.courseInfo.nextPoint.position) { + setInterval(() => { + if (this.courseInfo.nextPoint.position) { this.emitCourseInfo() } }, DELTA_INTERVAL) @@ -409,7 +409,8 @@ export class CourseApi { newCourse.nextPoint.arrivalCircle = dest.arrivalCircle } - newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null + newCourse.nextPoint.type = + typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { newCourse.href = dest.href From d015ba94131f8c20823f442db450069f0ed204f6 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 4 Dec 2021 17:12:14 +1030 Subject: [PATCH 330/410] init courseApi --- src/api/course/index.ts | 318 +++++++++++++----------------------- src/api/course/openApi.json | 72 +++++++- 2 files changed, 187 insertions(+), 203 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 1e719297a..6e8e4074d 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,24 +1,23 @@ +import { + ResourceProvider, + ResourceProviderMethods, + SignalKResourceType +} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' +import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' -const DELTA_INTERVAL: number = 30000 +const API_METHODS: string[] = [] interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - securityStrategy: { - shouldAllowPut: ( - req: Application, - context: string, - source: any, - path: string - ) => boolean - } resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -99,20 +98,6 @@ export class CourseApi { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app this.initResourceRoutes() - setInterval(() => { - if (this.courseInfo.nextPoint.position) { - this.emitCourseInfo() - } - }, DELTA_INTERVAL) - } - - private updateAllowed(): boolean { - return this.server.securityStrategy.shouldAllowPut( - this.server, - 'vessels.self', - null, - 'navigation.course' - ) } private initResourceRoutes() { @@ -125,46 +110,29 @@ export class CourseApi { } ) + // restart / arrivalCircle this.server.put( - `${COURSE_API_PATH}/restart`, + `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { - debug(`** PUT ${COURSE_API_PATH}/restart`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } - if (!this.courseInfo.nextPoint.position) { - res.status(406).send(`No active destination!`) - return - } - // set previousPoint to vessel position - try { + debug(`** PUT ${COURSE_API_PATH}/:action`) + if (req.params.restart) { + // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).send(`Course restarted.`) + } else { + res.status(406).send(`Vessel position unavailable!`) } - } catch (err) { - res.status(406).send(`Vessel position unavailable!`) - } - } - ) - - this.server.put( - `${COURSE_API_PATH}/arrivalCircle`, - async (req: Request, res: Response) => { - debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return } - if (this.isValidArrivalCircle(req.body.value)) { - this.courseInfo.nextPoint.arrivalCircle = req.body.value - this.emitCourseInfo() - res.status(200).send('OK') - } else { - res.status(406).send(`Invalid Data`) + if (req.params.arrivalCircle) { + if (this.setArrivalCircle(req.params.arrivalCircle)) { + this.emitCourseInfo() + res.status(200).send(`Destination set successfully.`) + } else { + res.status(406).send(`Invalid Data`) + } } } ) @@ -174,10 +142,6 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } if (!req.body.value) { res.status(406).send(`Invalid Data`) return @@ -185,7 +149,7 @@ export class CourseApi { const result = await this.setDestination(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).send(`Destination set successfully.`) } else { this.clearDestination() this.emitCourseInfo() @@ -199,29 +163,22 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } this.clearDestination() this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).send(`Destination cleared.`) } ) - // set activeRoute + // set / clear activeRoute this.server.put( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } + const result = await this.activateRoute(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).send(`Active route set.`) } else { this.clearDestination() this.emitCourseInfo() @@ -229,19 +186,14 @@ export class CourseApi { } } ) - - // clear activeRoute /destination this.server.delete( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/activeRoute`) - if (!this.updateAllowed()) { - res.status(403) - return - } + this.clearDestination() this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).send(`Active route cleared.`) } ) @@ -249,11 +201,6 @@ export class CourseApi { `${COURSE_API_PATH}/activeRoute/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } - // fetch active route data if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return @@ -264,7 +211,7 @@ export class CourseApi { return } - if (req.params.action === 'nextPoint') { + if (req.params.nextPoint) { if ( typeof req.body.value === 'number' && (req.body.value === 1 || req.body.value === -1) @@ -275,10 +222,9 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) - return } } - if (req.params.action === 'pointIndex') { + if (req.params.pointIndex) { if (typeof req.body.value === 'number') { this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( req.body.value, @@ -286,44 +232,36 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) - return } } - // set new destination + // set nextPoint this.courseInfo.nextPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex, - this.courseInfo.activeRoute.reverse + this.courseInfo.activeRoute.pointIndex ) this.courseInfo.nextPoint.type = `RoutePoint` this.courseInfo.nextPoint.href = null // set previousPoint if (this.courseInfo.activeRoute.pointIndex === 0) { - try { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { - res.status(406).send(`Invalid Data`) - return false - } - } catch (err) { - res.status(406).send(`Invalid Data`) + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { return false } } else { this.courseInfo.previousPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex - 1, - this.courseInfo.activeRoute.reverse + this.courseInfo.activeRoute.pointIndex - 1 ) this.courseInfo.previousPoint.type = `RoutePoint` } this.courseInfo.previousPoint.href = null - res.status(200).send('OK') + + res.status(200).send(`OK`) } ) } @@ -343,98 +281,81 @@ export class CourseApi { return false } - const newCourse: any = {} - Object.assign(newCourse, this.courseInfo) - // set activeroute - newCourse.activeRoute.href = route.href + this.courseInfo.activeRoute.href = route.href - if (this.isValidArrivalCircle(route.arrivalCircle as number)) { - newCourse.nextPoint.arrivalCircle = route.arrivalCircle + if (typeof route.arrivalCircle === 'number') { + this.setArrivalCircle(route.arrivalCircle) } - newCourse.activeRoute.startTime = new Date().toISOString() + this.courseInfo.activeRoute.reverse = + typeof route.reverse === 'boolean' ? route.reverse : false - if (typeof route.reverse === 'boolean') { - newCourse.activeRoute.reverse = route.reverse - } + this.courseInfo.activeRoute.pointIndex = + typeof route.pointIndex === 'number' + ? this.parsePointIndex(route.pointIndex, rte) + : 0 - newCourse.activeRoute.pointIndex = this.parsePointIndex( - route.pointIndex as number, - rte - ) + this.courseInfo.activeRoute.startTime = new Date().toISOString() // set nextPoint - newCourse.nextPoint.position = this.getRoutePoint( + this.courseInfo.nextPoint.position = this.getRoutePoint( rte, - newCourse.activeRoute.pointIndex, - newCourse.activeRoute.reverse + this.courseInfo.activeRoute.pointIndex ) - newCourse.nextPoint.type = `RoutePoint` - newCourse.nextPoint.href = null + this.courseInfo.nextPoint.type = `RoutePoint` + this.courseInfo.nextPoint.href = null // set previousPoint - if (newCourse.activeRoute.pointIndex === 0) { - try { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { - return false - } - } catch (err) { + if (this.courseInfo.activeRoute.pointIndex === 0) { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { return false } } else { - newCourse.previousPoint.position = this.getRoutePoint( + this.courseInfo.previousPoint.position = this.getRoutePoint( rte, - newCourse.activeRoute.pointIndex - 1, - newCourse.activeRoute.reverse + this.courseInfo.activeRoute.pointIndex - 1 ) - newCourse.previousPoint.type = `RoutePoint` + this.courseInfo.previousPoint.type = `RoutePoint` } - newCourse.previousPoint.href = null + this.courseInfo.previousPoint.href = null - this.courseInfo = newCourse return true } - private async setDestination(dest: any): Promise { - const newCourse: any = {} - Object.assign(newCourse, this.courseInfo) - + private async setDestination(dest: Destination): Promise { // set nextPoint - if (this.isValidArrivalCircle(dest.arrivalCircle)) { - newCourse.nextPoint.arrivalCircle = dest.arrivalCircle + if (typeof dest.arrivalCircle === 'number') { + this.setArrivalCircle(dest.arrivalCircle) } - newCourse.nextPoint.type = + this.courseInfo.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - newCourse.href = dest.href + this.courseInfo.nextPoint.href = dest.href const href = this.parseHref(dest.href) if (href) { - // fetch waypoint resource details try { const r = await this.server.resourcesApi.getResource( href.type, href.id ) - if (r.position && typeof r.position?.latitude !== 'undefined') { - newCourse.nextPoint.position = r.position - } else { - return false + if (r.position && typeof r.position.value?.latitude !== 'undefined') { + this.courseInfo.nextPoint.position = r.position.value } } catch (err) { return false } } } else if (dest.position) { - newCourse.nextPoint.href = null + this.courseInfo.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { - newCourse.nextPoint.position = dest.position + this.courseInfo.nextPoint.position = dest.position } else { return false } @@ -443,20 +364,15 @@ export class CourseApi { } // set previousPoint - try { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - newCourse.previousPoint.position = position.value - newCourse.previousPoint.type = `VesselPosition` - } else { - return false - } - newCourse.previousPoint.href = null - } catch (err) { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { return false } + this.courseInfo.previousPoint.href = null - this.courseInfo = newCourse return true } @@ -473,12 +389,17 @@ export class CourseApi { this.courseInfo.previousPoint.position = null } - private isValidArrivalCircle(value: number): boolean { - return typeof value === 'number' && value >= 0 + private setArrivalCircle(value: any): boolean { + if (typeof value === 'number' && value >= 0) { + this.courseInfo.nextPoint.arrivalCircle = value + return true + } else { + return false + } } private parsePointIndex(index: number, rte: any): number { - if (typeof index !== 'number' || !rte) { + if (!rte) { return 0 } if (!rte.feature?.geometry?.coordinates) { @@ -497,11 +418,13 @@ export class CourseApi { } private parseHref(href: string): { type: string; id: string } | undefined { - if (!href) { + if (href.length === 0) { return undefined } - - const ref: string[] = href.split('/').slice(-3) + if (href[0] === '/') { + href = href.slice(1) + } + const ref: string[] = href.split('/') if (ref.length < 3) { return undefined } @@ -514,8 +437,8 @@ export class CourseApi { } } - private getRoutePoint(rte: any, index: number, reverse: boolean) { - const pos = reverse + private getRoutePoint(rte: any, index: number) { + const pos = this.courseInfo.activeRoute.reverse ? rte.feature.geometry.coordinates[ rte.feature.geometry.coordinates.length - (index + 1) ] @@ -523,7 +446,7 @@ export class CourseApi { return { latitude: pos[1], longitude: pos[0], - altitude: pos.length === 3 ? pos[2] : 0 + altitude: pos.length == 3 ? pos[2] : 0 } } @@ -541,94 +464,89 @@ export class CourseApi { } private buildDeltaMsg(): any { - const values: Array<{ path: string; value: any }> = [] - const navPath = [ + let values: Array<{path:string, value:any}> = [] + let root = [ 'navigation.courseGreatCircle', 'navigation.courseRhumbline' ] values.push({ - path: `navigation.course`, - value: this.courseInfo - }) - - values.push({ - path: `${navPath[0]}.activeRoute.href`, + path: `${root[0]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${navPath[1]}.activeRoute.href`, + path: `${root[1]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${navPath[0]}.activeRoute.startTime`, + path: `${root[0]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${navPath[1]}.activeRoute.startTime`, + path: `${root[1]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${navPath[0]}.nextPoint.href`, + path: `${root[0]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${navPath[1]}.nextPoint.href`, + path: `${root[1]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${navPath[0]}.nextPoint.position`, + path: `${root[0]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${navPath[1]}.nextPoint.position`, + path: `${root[1]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${navPath[0]}.nextPoint.type`, + path: `${root[0]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${navPath[1]}.nextPoint.type`, + path: `${root[1]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${navPath[0]}.nextPoint.arrivalCircle`, + path: `${root[0]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${navPath[1]}.nextPoint.arrivalCircle`, + path: `${root[1]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${navPath[0]}.previousPoint.href`, + path: `${root[0]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${navPath[1]}.previousPoint.href`, + path: `${root[1]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${navPath[0]}.previousPoint.position`, + path: `${root[0]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${navPath[1]}.previousPoint.position`, + path: `${root[1]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${navPath[0]}.previousPoint.type`, + path: `${root[0]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) values.push({ - path: `${navPath[1]}.previousPoint.type`, + path: `${root[1]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) return { updates: [ { - values + values: values } ] } diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index b162d1314..04653db5e 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -108,7 +108,29 @@ "put": { "tags": ["course"], "summary": "Restart course calculations", - "description": "Sets previousPoint value to current vessel location" + "description": "Sets previousPoint value to current vessel location", + "requestBody": { + "description": "Restart payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } } }, @@ -178,7 +200,29 @@ "delete": { "tags": ["course/destination"], "summary": "Clear destination", - "description": "Sets activeRoute, nextPoint & previousPoint values to null" + "description": "Sets activeRoute, nextPoint & previousPoint values to null", + "requestBody": { + "description": "Delete payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } } }, @@ -269,7 +313,29 @@ "delete": { "tags": ["course/activeRoute"], "summary": "Clear active route", - "description": "Sets activeRoute, nextPoint & previousPoint values to null" + "description": "Sets activeRoute, nextPoint & previousPoint values to null", + "requestBody": { + "description": "Delete payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } } }, From c10258385bff62bca22001380e714c9eb542c112 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 5 Dec 2021 10:26:47 +1030 Subject: [PATCH 331/410] update detlas --- src/api/course/index.ts | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 6e8e4074d..7510ca1e7 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,23 +1,15 @@ -import { - ResourceProvider, - ResourceProviderMethods, - SignalKResourceType -} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' -import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' - -const API_METHODS: string[] = [] interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any + registerPutHandler: (context:string, path:string, cb:any) => any resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -110,12 +102,26 @@ export class CourseApi { } ) + // + if(this.server.registerPutHandler) { + debug('** Registering PUT Action Handler(s) **') + this.server.registerPutHandler( + 'vessels.self', + 'navigation.course.*', + this.handleCourseApiPut + ); + } + // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { + //test for active destination + if (!this.courseInfo.nextPoint.position) { + return + } // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -266,6 +272,12 @@ export class CourseApi { ) } + private handleCourseApiPut(context:string, path:string, value:any, cb:any) { + + debug('** PUT handler **') + return undefined + } + private async activateRoute(route: ActiveRoute): Promise { let rte: any @@ -470,6 +482,11 @@ export class CourseApi { 'navigation.courseRhumbline' ] + values.push({ + path: `navigation.course`, + value: this.courseInfo + }) + values.push({ path: `${root[0]}.activeRoute.href`, value: this.courseInfo.activeRoute.href From 9c6a3485b33e8eb05b6f76bbe8eb0d0867cbfbb1 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 4 Dec 2021 17:12:14 +1030 Subject: [PATCH 332/410] init courseApi --- src/api/course/index.ts | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 7510ca1e7..af32aceee 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,15 +1,26 @@ + +import { + ResourceProvider, + ResourceProviderMethods, + SignalKResourceType +} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' +import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' + +const API_METHODS: string[] = [] + interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - registerPutHandler: (context:string, path:string, cb:any) => any + resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -102,26 +113,12 @@ export class CourseApi { } ) - // - if(this.server.registerPutHandler) { - debug('** Registering PUT Action Handler(s) **') - this.server.registerPutHandler( - 'vessels.self', - 'navigation.course.*', - this.handleCourseApiPut - ); - } - // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { - //test for active destination - if (!this.courseInfo.nextPoint.position) { - return - } // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -272,12 +269,6 @@ export class CourseApi { ) } - private handleCourseApiPut(context:string, path:string, value:any, cb:any) { - - debug('** PUT handler **') - return undefined - } - private async activateRoute(route: ActiveRoute): Promise { let rte: any From 7f077d6b9f57eb2ba147ebe6ba639904fb4e4267 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 5 Dec 2021 10:26:47 +1030 Subject: [PATCH 333/410] update detlas --- src/api/course/index.ts | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index af32aceee..7510ca1e7 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,26 +1,15 @@ - -import { - ResourceProvider, - ResourceProviderMethods, - SignalKResourceType -} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' -import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' - -const API_METHODS: string[] = [] - interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - + registerPutHandler: (context:string, path:string, cb:any) => any resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -113,12 +102,26 @@ export class CourseApi { } ) + // + if(this.server.registerPutHandler) { + debug('** Registering PUT Action Handler(s) **') + this.server.registerPutHandler( + 'vessels.self', + 'navigation.course.*', + this.handleCourseApiPut + ); + } + // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { + //test for active destination + if (!this.courseInfo.nextPoint.position) { + return + } // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -269,6 +272,12 @@ export class CourseApi { ) } + private handleCourseApiPut(context:string, path:string, value:any, cb:any) { + + debug('** PUT handler **') + return undefined + } + private async activateRoute(route: ActiveRoute): Promise { let rte: any From a3ed8b117c130c3daee4da7c6b719c6a0898bfd1 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 6 Dec 2021 08:39:56 +1030 Subject: [PATCH 334/410] enable put processing --- src/api/course/index.ts | 133 +++++++++++++++++++++++++--------------- 1 file changed, 82 insertions(+), 51 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 7510ca1e7..1566ecdf3 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -9,7 +9,14 @@ const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/cou interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - registerPutHandler: (context:string, path:string, cb:any) => any + securityStrategy: { + shouldAllowPut: ( + req: any, + context: string, + source: any, + path: string + ) => boolean + } resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -92,6 +99,15 @@ export class CourseApi { this.initResourceRoutes() } + private updateAllowed(): boolean { + return this.server.securityStrategy.shouldAllowPut( + this.server, + 'vessels.self', + null, + 'resources' + ) + } + private initResourceRoutes() { // return current course information this.server.get( @@ -102,43 +118,43 @@ export class CourseApi { } ) - // - if(this.server.registerPutHandler) { - debug('** Registering PUT Action Handler(s) **') - this.server.registerPutHandler( - 'vessels.self', - 'navigation.course.*', - this.handleCourseApiPut - ); - } + this.server.put( + `${COURSE_API_PATH}/restart`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/restart`) + if (!this.updateAllowed()) { + res.status(403) + return + } + if (!this.courseInfo.nextPoint.position) { + res.status(406).send(`No active destination!`) + return + } + // set previousPoint to vessel position + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.emitCourseInfo() + res.status(200) + } else { + res.status(406).send(`Vessel position unavailable!`) + } + } + ) - // restart / arrivalCircle this.server.put( - `${COURSE_API_PATH}/:action`, + `${COURSE_API_PATH}/arrivalCircle`, async (req: Request, res: Response) => { - debug(`** PUT ${COURSE_API_PATH}/:action`) - if (req.params.restart) { - //test for active destination - if (!this.courseInfo.nextPoint.position) { - return - } - // set previousPoint to vessel position - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.emitCourseInfo() - res.status(200).send(`Course restarted.`) - } else { - res.status(406).send(`Vessel position unavailable!`) - } + debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) + if (!this.updateAllowed()) { + res.status(403) + return } - if (req.params.arrivalCircle) { - if (this.setArrivalCircle(req.params.arrivalCircle)) { - this.emitCourseInfo() - res.status(200).send(`Destination set successfully.`) - } else { - res.status(406).send(`Invalid Data`) - } + if (this.setArrivalCircle(req.body.value)) { + this.emitCourseInfo() + res.status(200) + } else { + res.status(406).send(`Invalid Data`) } } ) @@ -148,6 +164,10 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) + if (!this.updateAllowed()) { + res.status(403) + return + } if (!req.body.value) { res.status(406).send(`Invalid Data`) return @@ -155,7 +175,7 @@ export class CourseApi { const result = await this.setDestination(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send(`Destination set successfully.`) + res.status(200) } else { this.clearDestination() this.emitCourseInfo() @@ -169,22 +189,29 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) + if (!this.updateAllowed()) { + res.status(403) + return + } this.clearDestination() this.emitCourseInfo() - res.status(200).send(`Destination cleared.`) + res.status(200) } ) - // set / clear activeRoute + // set activeRoute this.server.put( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) - + if (!this.updateAllowed()) { + res.status(403) + return + } const result = await this.activateRoute(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send(`Active route set.`) + res.status(200) } else { this.clearDestination() this.emitCourseInfo() @@ -192,14 +219,19 @@ export class CourseApi { } } ) + + // clear activeRoute /destination this.server.delete( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/activeRoute`) - + if (!this.updateAllowed()) { + res.status(403) + return + } this.clearDestination() this.emitCourseInfo() - res.status(200).send(`Active route cleared.`) + res.status(200) } ) @@ -207,6 +239,11 @@ export class CourseApi { `${COURSE_API_PATH}/activeRoute/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) + if (!this.updateAllowed()) { + res.status(403) + return + } + // fetch route data if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return @@ -217,7 +254,7 @@ export class CourseApi { return } - if (req.params.nextPoint) { + if (req.params.action === 'nextPoint') { if ( typeof req.body.value === 'number' && (req.body.value === 1 || req.body.value === -1) @@ -230,7 +267,7 @@ export class CourseApi { res.status(406).send(`Invalid Data`) } } - if (req.params.pointIndex) { + if (req.params.action === 'pointIndex') { if (typeof req.body.value === 'number') { this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( req.body.value, @@ -241,7 +278,7 @@ export class CourseApi { } } - // set nextPoint + // set new destination this.courseInfo.nextPoint.position = this.getRoutePoint( rte, this.courseInfo.activeRoute.pointIndex @@ -267,17 +304,11 @@ export class CourseApi { } this.courseInfo.previousPoint.href = null - res.status(200).send(`OK`) + res.status(200) } ) } - private handleCourseApiPut(context:string, path:string, value:any, cb:any) { - - debug('** PUT handler **') - return undefined - } - private async activateRoute(route: ActiveRoute): Promise { let rte: any From d8a6aae77d8b139640d6ed90d29f0a3387a41687 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 6 Dec 2021 13:34:31 +1030 Subject: [PATCH 335/410] chore: lint --- src/api/course/index.ts | 211 ++++++++++++++++++++---------------- src/api/course/openApi.json | 72 +----------- 2 files changed, 120 insertions(+), 163 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 1566ecdf3..6cd3b80fd 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -11,7 +11,7 @@ interface CourseApplication extends Application { getSelfPath: (path: string) => any securityStrategy: { shouldAllowPut: ( - req: any, + req: Application, context: string, source: any, path: string @@ -104,7 +104,7 @@ export class CourseApi { this.server, 'vessels.self', null, - 'resources' + 'navigation.course' ) } @@ -131,12 +131,14 @@ export class CourseApi { return } // set previousPoint to vessel position - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.emitCourseInfo() - res.status(200) - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.emitCourseInfo() + res.status(200) + } + } catch (err) { res.status(406).send(`Vessel position unavailable!`) } } @@ -150,7 +152,8 @@ export class CourseApi { res.status(403) return } - if (this.setArrivalCircle(req.body.value)) { + if (this.isValidArrivalCircle(req.body.value)) { + this.courseInfo.nextPoint.arrivalCircle = req.body.value this.emitCourseInfo() res.status(200) } else { @@ -243,7 +246,7 @@ export class CourseApi { res.status(403) return } - // fetch route data + // fetch active route data if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return @@ -281,24 +284,30 @@ export class CourseApi { // set new destination this.courseInfo.nextPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex + this.courseInfo.activeRoute.pointIndex, + this.courseInfo.activeRoute.reverse ) this.courseInfo.nextPoint.type = `RoutePoint` this.courseInfo.nextPoint.href = null // set previousPoint if (this.courseInfo.activeRoute.pointIndex === 0) { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } catch (err) { return false } } else { this.courseInfo.previousPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex - 1 + this.courseInfo.activeRoute.pointIndex - 1, + this.courseInfo.activeRoute.reverse ) this.courseInfo.previousPoint.type = `RoutePoint` } @@ -324,81 +333,97 @@ export class CourseApi { return false } + const newDest: any = {} + Object.assign(newDest, this.courseInfo) + // set activeroute - this.courseInfo.activeRoute.href = route.href + newDest.activeRoute.href = route.href - if (typeof route.arrivalCircle === 'number') { - this.setArrivalCircle(route.arrivalCircle) + if (this.isValidArrivalCircle(route.arrivalCircle as number)) { + newDest.nextPoint.arrivalCircle = route.arrivalCircle } - this.courseInfo.activeRoute.reverse = - typeof route.reverse === 'boolean' ? route.reverse : false + newDest.activeRoute.startTime = new Date().toISOString() - this.courseInfo.activeRoute.pointIndex = - typeof route.pointIndex === 'number' - ? this.parsePointIndex(route.pointIndex, rte) - : 0 + if (typeof route.reverse === 'boolean') { + newDest.activeRoute.reverse = route.reverse + } - this.courseInfo.activeRoute.startTime = new Date().toISOString() + newDest.activeRoute.pointIndex = this.parsePointIndex( + route.pointIndex as number, + rte + ) // set nextPoint - this.courseInfo.nextPoint.position = this.getRoutePoint( + newDest.nextPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex + newDest.activeRoute.pointIndex, + newDest.activeRoute.reverse ) - this.courseInfo.nextPoint.type = `RoutePoint` - this.courseInfo.nextPoint.href = null + newDest.nextPoint.type = `RoutePoint` + newDest.nextPoint.href = null // set previousPoint - if (this.courseInfo.activeRoute.pointIndex === 0) { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + if (newDest.activeRoute.pointIndex === 0) { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } catch (err) { return false } } else { - this.courseInfo.previousPoint.position = this.getRoutePoint( + newDest.previousPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex - 1 + newDest.activeRoute.pointIndex - 1, + newDest.activeRoute.reverse ) - this.courseInfo.previousPoint.type = `RoutePoint` + newDest.previousPoint.type = `RoutePoint` } - this.courseInfo.previousPoint.href = null + newDest.previousPoint.href = null + this.courseInfo = newDest return true } - private async setDestination(dest: Destination): Promise { + private async setDestination(dest: any): Promise { + const newDest: any = {} + Object.assign(newDest, this.courseInfo) + // set nextPoint - if (typeof dest.arrivalCircle === 'number') { - this.setArrivalCircle(dest.arrivalCircle) + if (this.isValidArrivalCircle(dest.arrivalCircle)) { + newDest.nextPoint.arrivalCircle = dest.arrivalCircle } - this.courseInfo.nextPoint.type = - typeof dest.type !== 'undefined' ? dest.type : null + newDest.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - this.courseInfo.nextPoint.href = dest.href + newDest.href = dest.href const href = this.parseHref(dest.href) if (href) { + // fetch waypoint resource details try { const r = await this.server.resourcesApi.getResource( href.type, href.id ) - if (r.position && typeof r.position.value?.latitude !== 'undefined') { - this.courseInfo.nextPoint.position = r.position.value + if (r.position && typeof r.position?.latitude !== 'undefined') { + newDest.nextPoint.position = r.position + } else { + return false } } catch (err) { return false } } } else if (dest.position) { - this.courseInfo.nextPoint.href = null + newDest.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { - this.courseInfo.nextPoint.position = dest.position + newDest.nextPoint.position = dest.position } else { return false } @@ -407,15 +432,20 @@ export class CourseApi { } // set previousPoint - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + newDest.previousPoint.position = position.value + newDest.previousPoint.type = `VesselPosition` + } else { + return false + } + newDest.previousPoint.href = null + } catch (err) { return false } - this.courseInfo.previousPoint.href = null + this.courseInfo = newDest return true } @@ -432,17 +462,12 @@ export class CourseApi { this.courseInfo.previousPoint.position = null } - private setArrivalCircle(value: any): boolean { - if (typeof value === 'number' && value >= 0) { - this.courseInfo.nextPoint.arrivalCircle = value - return true - } else { - return false - } + private isValidArrivalCircle(value: number): boolean { + return typeof value === 'number' && value >= 0 } private parsePointIndex(index: number, rte: any): number { - if (!rte) { + if (typeof index !== 'number' || !rte) { return 0 } if (!rte.feature?.geometry?.coordinates) { @@ -461,13 +486,11 @@ export class CourseApi { } private parseHref(href: string): { type: string; id: string } | undefined { - if (href.length === 0) { + if (!href) { return undefined } - if (href[0] === '/') { - href = href.slice(1) - } - const ref: string[] = href.split('/') + + const ref: string[] = href.split('/').slice(-3) if (ref.length < 3) { return undefined } @@ -480,8 +503,8 @@ export class CourseApi { } } - private getRoutePoint(rte: any, index: number) { - const pos = this.courseInfo.activeRoute.reverse + private getRoutePoint(rte: any, index: number, reverse: boolean) { + const pos = reverse ? rte.feature.geometry.coordinates[ rte.feature.geometry.coordinates.length - (index + 1) ] @@ -489,7 +512,7 @@ export class CourseApi { return { latitude: pos[1], longitude: pos[0], - altitude: pos.length == 3 ? pos[2] : 0 + altitude: pos.length === 3 ? pos[2] : 0 } } @@ -507,8 +530,8 @@ export class CourseApi { } private buildDeltaMsg(): any { - let values: Array<{path:string, value:any}> = [] - let root = [ + const values: Array<{ path: string; value: any }> = [] + const navPath = [ 'navigation.courseGreatCircle', 'navigation.courseRhumbline' ] @@ -519,82 +542,82 @@ export class CourseApi { }) values.push({ - path: `${root[0]}.activeRoute.href`, + path: `${navPath[0]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${root[1]}.activeRoute.href`, + path: `${navPath[1]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${root[0]}.activeRoute.startTime`, + path: `${navPath[0]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${root[1]}.activeRoute.startTime`, + path: `${navPath[1]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${root[0]}.nextPoint.href`, + path: `${navPath[0]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${root[1]}.nextPoint.href`, + path: `${navPath[1]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${root[0]}.nextPoint.position`, + path: `${navPath[0]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${root[1]}.nextPoint.position`, + path: `${navPath[1]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${root[0]}.nextPoint.type`, + path: `${navPath[0]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${root[1]}.nextPoint.type`, + path: `${navPath[1]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${root[0]}.nextPoint.arrivalCircle`, + path: `${navPath[0]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${root[1]}.nextPoint.arrivalCircle`, + path: `${navPath[1]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${root[0]}.previousPoint.href`, + path: `${navPath[0]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${root[1]}.previousPoint.href`, + path: `${navPath[1]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${root[0]}.previousPoint.position`, + path: `${navPath[0]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${root[1]}.previousPoint.position`, + path: `${navPath[1]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${root[0]}.previousPoint.type`, + path: `${navPath[0]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) values.push({ - path: `${root[1]}.previousPoint.type`, + path: `${navPath[1]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) return { updates: [ { - values: values + values } ] } diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index 04653db5e..b162d1314 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -108,29 +108,7 @@ "put": { "tags": ["course"], "summary": "Restart course calculations", - "description": "Sets previousPoint value to current vessel location", - "requestBody": { - "description": "Restart payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } - } - } - } - } - } + "description": "Sets previousPoint value to current vessel location" } }, @@ -200,29 +178,7 @@ "delete": { "tags": ["course/destination"], "summary": "Clear destination", - "description": "Sets activeRoute, nextPoint & previousPoint values to null", - "requestBody": { - "description": "Delete payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } - } - } - } - } - } + "description": "Sets activeRoute, nextPoint & previousPoint values to null" } }, @@ -313,29 +269,7 @@ "delete": { "tags": ["course/activeRoute"], "summary": "Clear active route", - "description": "Sets activeRoute, nextPoint & previousPoint values to null", - "requestBody": { - "description": "Delete payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } - } - } - } - } - } + "description": "Sets activeRoute, nextPoint & previousPoint values to null" } }, From 1efd58ba80e80d81402bf4fb2c13faa524aceb45 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 8 Dec 2021 09:27:06 +1030 Subject: [PATCH 336/410] add 30sec delta interval --- src/api/course/index.ts | 100 ++++++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 45 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 6cd3b80fd..2aa57e8a6 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -6,6 +6,8 @@ const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const DELTA_INTERVAL: number = 30000 + interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any @@ -97,6 +99,11 @@ export class CourseApi { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app this.initResourceRoutes() + setInterval( ()=> { + if(this.courseInfo.nextPoint.position) { + this.emitCourseInfo() + } + }, DELTA_INTERVAL) } private updateAllowed(): boolean { @@ -123,7 +130,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/restart`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (!this.courseInfo.nextPoint.position) { @@ -136,7 +143,7 @@ export class CourseApi { if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } } catch (err) { res.status(406).send(`Vessel position unavailable!`) @@ -149,13 +156,13 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (this.isValidArrivalCircle(req.body.value)) { this.courseInfo.nextPoint.arrivalCircle = req.body.value this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { res.status(406).send(`Invalid Data`) } @@ -168,7 +175,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (!req.body.value) { @@ -178,7 +185,7 @@ export class CourseApi { const result = await this.setDestination(req.body.value) if (result) { this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { this.clearDestination() this.emitCourseInfo() @@ -193,12 +200,12 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } this.clearDestination() this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } ) @@ -208,13 +215,13 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } const result = await this.activateRoute(req.body.value) if (result) { this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { this.clearDestination() this.emitCourseInfo() @@ -234,7 +241,7 @@ export class CourseApi { } this.clearDestination() this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } ) @@ -243,7 +250,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } // fetch active route data @@ -268,6 +275,7 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) + return } } if (req.params.action === 'pointIndex') { @@ -278,6 +286,7 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) + return } } @@ -298,9 +307,11 @@ export class CourseApi { this.courseInfo.previousPoint.position = position.value this.courseInfo.previousPoint.type = `VesselPosition` } else { + res.status(406).send(`Invalid Data`) return false } } catch (err) { + res.status(406).send(`Invalid Data`) return false } } else { @@ -312,8 +323,7 @@ export class CourseApi { this.courseInfo.previousPoint.type = `RoutePoint` } this.courseInfo.previousPoint.href = null - - res.status(200) + res.status(200).send('OK') } ) } @@ -333,38 +343,38 @@ export class CourseApi { return false } - const newDest: any = {} - Object.assign(newDest, this.courseInfo) + const newCourse: any = {} + Object.assign(newCourse, this.courseInfo) // set activeroute - newDest.activeRoute.href = route.href + newCourse.activeRoute.href = route.href if (this.isValidArrivalCircle(route.arrivalCircle as number)) { - newDest.nextPoint.arrivalCircle = route.arrivalCircle + newCourse.nextPoint.arrivalCircle = route.arrivalCircle } - newDest.activeRoute.startTime = new Date().toISOString() + newCourse.activeRoute.startTime = new Date().toISOString() if (typeof route.reverse === 'boolean') { - newDest.activeRoute.reverse = route.reverse + newCourse.activeRoute.reverse = route.reverse } - newDest.activeRoute.pointIndex = this.parsePointIndex( + newCourse.activeRoute.pointIndex = this.parsePointIndex( route.pointIndex as number, rte ) // set nextPoint - newDest.nextPoint.position = this.getRoutePoint( + newCourse.nextPoint.position = this.getRoutePoint( rte, - newDest.activeRoute.pointIndex, - newDest.activeRoute.reverse + newCourse.activeRoute.pointIndex, + newCourse.activeRoute.reverse ) - newDest.nextPoint.type = `RoutePoint` - newDest.nextPoint.href = null + newCourse.nextPoint.type = `RoutePoint` + newCourse.nextPoint.href = null // set previousPoint - if (newDest.activeRoute.pointIndex === 0) { + if (newCourse.activeRoute.pointIndex === 0) { try { const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -377,32 +387,32 @@ export class CourseApi { return false } } else { - newDest.previousPoint.position = this.getRoutePoint( + newCourse.previousPoint.position = this.getRoutePoint( rte, - newDest.activeRoute.pointIndex - 1, - newDest.activeRoute.reverse + newCourse.activeRoute.pointIndex - 1, + newCourse.activeRoute.reverse ) - newDest.previousPoint.type = `RoutePoint` + newCourse.previousPoint.type = `RoutePoint` } - newDest.previousPoint.href = null + newCourse.previousPoint.href = null - this.courseInfo = newDest + this.courseInfo = newCourse return true } private async setDestination(dest: any): Promise { - const newDest: any = {} - Object.assign(newDest, this.courseInfo) + const newCourse: any = {} + Object.assign(newCourse, this.courseInfo) // set nextPoint if (this.isValidArrivalCircle(dest.arrivalCircle)) { - newDest.nextPoint.arrivalCircle = dest.arrivalCircle + newCourse.nextPoint.arrivalCircle = dest.arrivalCircle } - newDest.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null + newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - newDest.href = dest.href + newCourse.href = dest.href const href = this.parseHref(dest.href) if (href) { // fetch waypoint resource details @@ -412,7 +422,7 @@ export class CourseApi { href.id ) if (r.position && typeof r.position?.latitude !== 'undefined') { - newDest.nextPoint.position = r.position + newCourse.nextPoint.position = r.position } else { return false } @@ -421,9 +431,9 @@ export class CourseApi { } } } else if (dest.position) { - newDest.nextPoint.href = null + newCourse.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { - newDest.nextPoint.position = dest.position + newCourse.nextPoint.position = dest.position } else { return false } @@ -435,17 +445,17 @@ export class CourseApi { try { const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { - newDest.previousPoint.position = position.value - newDest.previousPoint.type = `VesselPosition` + newCourse.previousPoint.position = position.value + newCourse.previousPoint.type = `VesselPosition` } else { return false } - newDest.previousPoint.href = null + newCourse.previousPoint.href = null } catch (err) { return false } - this.courseInfo = newDest + this.courseInfo = newCourse return true } From 39d961a7e9330128fc22c810a26287fd11a84d8f Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 12 Dec 2021 14:29:38 +1030 Subject: [PATCH 337/410] chore: lint --- src/api/course/index.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 2aa57e8a6..1e719297a 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -6,7 +6,7 @@ const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const DELTA_INTERVAL: number = 30000 +const DELTA_INTERVAL: number = 30000 interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void @@ -99,8 +99,8 @@ export class CourseApi { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app this.initResourceRoutes() - setInterval( ()=> { - if(this.courseInfo.nextPoint.position) { + setInterval(() => { + if (this.courseInfo.nextPoint.position) { this.emitCourseInfo() } }, DELTA_INTERVAL) @@ -409,7 +409,8 @@ export class CourseApi { newCourse.nextPoint.arrivalCircle = dest.arrivalCircle } - newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null + newCourse.nextPoint.type = + typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { newCourse.href = dest.href From 800770fc8ae97bf70badc8bec9073c110ce0124d Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 4 Dec 2021 17:12:14 +1030 Subject: [PATCH 338/410] init courseApi --- src/api/course/index.ts | 318 +++++++++++++----------------------- src/api/course/openApi.json | 72 +++++++- 2 files changed, 187 insertions(+), 203 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 1e719297a..6e8e4074d 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,24 +1,23 @@ +import { + ResourceProvider, + ResourceProviderMethods, + SignalKResourceType +} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' +import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' -const DELTA_INTERVAL: number = 30000 +const API_METHODS: string[] = [] interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - securityStrategy: { - shouldAllowPut: ( - req: Application, - context: string, - source: any, - path: string - ) => boolean - } resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -99,20 +98,6 @@ export class CourseApi { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app this.initResourceRoutes() - setInterval(() => { - if (this.courseInfo.nextPoint.position) { - this.emitCourseInfo() - } - }, DELTA_INTERVAL) - } - - private updateAllowed(): boolean { - return this.server.securityStrategy.shouldAllowPut( - this.server, - 'vessels.self', - null, - 'navigation.course' - ) } private initResourceRoutes() { @@ -125,46 +110,29 @@ export class CourseApi { } ) + // restart / arrivalCircle this.server.put( - `${COURSE_API_PATH}/restart`, + `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { - debug(`** PUT ${COURSE_API_PATH}/restart`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } - if (!this.courseInfo.nextPoint.position) { - res.status(406).send(`No active destination!`) - return - } - // set previousPoint to vessel position - try { + debug(`** PUT ${COURSE_API_PATH}/:action`) + if (req.params.restart) { + // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).send(`Course restarted.`) + } else { + res.status(406).send(`Vessel position unavailable!`) } - } catch (err) { - res.status(406).send(`Vessel position unavailable!`) - } - } - ) - - this.server.put( - `${COURSE_API_PATH}/arrivalCircle`, - async (req: Request, res: Response) => { - debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return } - if (this.isValidArrivalCircle(req.body.value)) { - this.courseInfo.nextPoint.arrivalCircle = req.body.value - this.emitCourseInfo() - res.status(200).send('OK') - } else { - res.status(406).send(`Invalid Data`) + if (req.params.arrivalCircle) { + if (this.setArrivalCircle(req.params.arrivalCircle)) { + this.emitCourseInfo() + res.status(200).send(`Destination set successfully.`) + } else { + res.status(406).send(`Invalid Data`) + } } } ) @@ -174,10 +142,6 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } if (!req.body.value) { res.status(406).send(`Invalid Data`) return @@ -185,7 +149,7 @@ export class CourseApi { const result = await this.setDestination(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).send(`Destination set successfully.`) } else { this.clearDestination() this.emitCourseInfo() @@ -199,29 +163,22 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } this.clearDestination() this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).send(`Destination cleared.`) } ) - // set activeRoute + // set / clear activeRoute this.server.put( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } + const result = await this.activateRoute(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).send(`Active route set.`) } else { this.clearDestination() this.emitCourseInfo() @@ -229,19 +186,14 @@ export class CourseApi { } } ) - - // clear activeRoute /destination this.server.delete( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/activeRoute`) - if (!this.updateAllowed()) { - res.status(403) - return - } + this.clearDestination() this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).send(`Active route cleared.`) } ) @@ -249,11 +201,6 @@ export class CourseApi { `${COURSE_API_PATH}/activeRoute/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } - // fetch active route data if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return @@ -264,7 +211,7 @@ export class CourseApi { return } - if (req.params.action === 'nextPoint') { + if (req.params.nextPoint) { if ( typeof req.body.value === 'number' && (req.body.value === 1 || req.body.value === -1) @@ -275,10 +222,9 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) - return } } - if (req.params.action === 'pointIndex') { + if (req.params.pointIndex) { if (typeof req.body.value === 'number') { this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( req.body.value, @@ -286,44 +232,36 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) - return } } - // set new destination + // set nextPoint this.courseInfo.nextPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex, - this.courseInfo.activeRoute.reverse + this.courseInfo.activeRoute.pointIndex ) this.courseInfo.nextPoint.type = `RoutePoint` this.courseInfo.nextPoint.href = null // set previousPoint if (this.courseInfo.activeRoute.pointIndex === 0) { - try { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { - res.status(406).send(`Invalid Data`) - return false - } - } catch (err) { - res.status(406).send(`Invalid Data`) + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { return false } } else { this.courseInfo.previousPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex - 1, - this.courseInfo.activeRoute.reverse + this.courseInfo.activeRoute.pointIndex - 1 ) this.courseInfo.previousPoint.type = `RoutePoint` } this.courseInfo.previousPoint.href = null - res.status(200).send('OK') + + res.status(200).send(`OK`) } ) } @@ -343,98 +281,81 @@ export class CourseApi { return false } - const newCourse: any = {} - Object.assign(newCourse, this.courseInfo) - // set activeroute - newCourse.activeRoute.href = route.href + this.courseInfo.activeRoute.href = route.href - if (this.isValidArrivalCircle(route.arrivalCircle as number)) { - newCourse.nextPoint.arrivalCircle = route.arrivalCircle + if (typeof route.arrivalCircle === 'number') { + this.setArrivalCircle(route.arrivalCircle) } - newCourse.activeRoute.startTime = new Date().toISOString() + this.courseInfo.activeRoute.reverse = + typeof route.reverse === 'boolean' ? route.reverse : false - if (typeof route.reverse === 'boolean') { - newCourse.activeRoute.reverse = route.reverse - } + this.courseInfo.activeRoute.pointIndex = + typeof route.pointIndex === 'number' + ? this.parsePointIndex(route.pointIndex, rte) + : 0 - newCourse.activeRoute.pointIndex = this.parsePointIndex( - route.pointIndex as number, - rte - ) + this.courseInfo.activeRoute.startTime = new Date().toISOString() // set nextPoint - newCourse.nextPoint.position = this.getRoutePoint( + this.courseInfo.nextPoint.position = this.getRoutePoint( rte, - newCourse.activeRoute.pointIndex, - newCourse.activeRoute.reverse + this.courseInfo.activeRoute.pointIndex ) - newCourse.nextPoint.type = `RoutePoint` - newCourse.nextPoint.href = null + this.courseInfo.nextPoint.type = `RoutePoint` + this.courseInfo.nextPoint.href = null // set previousPoint - if (newCourse.activeRoute.pointIndex === 0) { - try { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { - return false - } - } catch (err) { + if (this.courseInfo.activeRoute.pointIndex === 0) { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { return false } } else { - newCourse.previousPoint.position = this.getRoutePoint( + this.courseInfo.previousPoint.position = this.getRoutePoint( rte, - newCourse.activeRoute.pointIndex - 1, - newCourse.activeRoute.reverse + this.courseInfo.activeRoute.pointIndex - 1 ) - newCourse.previousPoint.type = `RoutePoint` + this.courseInfo.previousPoint.type = `RoutePoint` } - newCourse.previousPoint.href = null + this.courseInfo.previousPoint.href = null - this.courseInfo = newCourse return true } - private async setDestination(dest: any): Promise { - const newCourse: any = {} - Object.assign(newCourse, this.courseInfo) - + private async setDestination(dest: Destination): Promise { // set nextPoint - if (this.isValidArrivalCircle(dest.arrivalCircle)) { - newCourse.nextPoint.arrivalCircle = dest.arrivalCircle + if (typeof dest.arrivalCircle === 'number') { + this.setArrivalCircle(dest.arrivalCircle) } - newCourse.nextPoint.type = + this.courseInfo.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - newCourse.href = dest.href + this.courseInfo.nextPoint.href = dest.href const href = this.parseHref(dest.href) if (href) { - // fetch waypoint resource details try { const r = await this.server.resourcesApi.getResource( href.type, href.id ) - if (r.position && typeof r.position?.latitude !== 'undefined') { - newCourse.nextPoint.position = r.position - } else { - return false + if (r.position && typeof r.position.value?.latitude !== 'undefined') { + this.courseInfo.nextPoint.position = r.position.value } } catch (err) { return false } } } else if (dest.position) { - newCourse.nextPoint.href = null + this.courseInfo.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { - newCourse.nextPoint.position = dest.position + this.courseInfo.nextPoint.position = dest.position } else { return false } @@ -443,20 +364,15 @@ export class CourseApi { } // set previousPoint - try { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - newCourse.previousPoint.position = position.value - newCourse.previousPoint.type = `VesselPosition` - } else { - return false - } - newCourse.previousPoint.href = null - } catch (err) { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { return false } + this.courseInfo.previousPoint.href = null - this.courseInfo = newCourse return true } @@ -473,12 +389,17 @@ export class CourseApi { this.courseInfo.previousPoint.position = null } - private isValidArrivalCircle(value: number): boolean { - return typeof value === 'number' && value >= 0 + private setArrivalCircle(value: any): boolean { + if (typeof value === 'number' && value >= 0) { + this.courseInfo.nextPoint.arrivalCircle = value + return true + } else { + return false + } } private parsePointIndex(index: number, rte: any): number { - if (typeof index !== 'number' || !rte) { + if (!rte) { return 0 } if (!rte.feature?.geometry?.coordinates) { @@ -497,11 +418,13 @@ export class CourseApi { } private parseHref(href: string): { type: string; id: string } | undefined { - if (!href) { + if (href.length === 0) { return undefined } - - const ref: string[] = href.split('/').slice(-3) + if (href[0] === '/') { + href = href.slice(1) + } + const ref: string[] = href.split('/') if (ref.length < 3) { return undefined } @@ -514,8 +437,8 @@ export class CourseApi { } } - private getRoutePoint(rte: any, index: number, reverse: boolean) { - const pos = reverse + private getRoutePoint(rte: any, index: number) { + const pos = this.courseInfo.activeRoute.reverse ? rte.feature.geometry.coordinates[ rte.feature.geometry.coordinates.length - (index + 1) ] @@ -523,7 +446,7 @@ export class CourseApi { return { latitude: pos[1], longitude: pos[0], - altitude: pos.length === 3 ? pos[2] : 0 + altitude: pos.length == 3 ? pos[2] : 0 } } @@ -541,94 +464,89 @@ export class CourseApi { } private buildDeltaMsg(): any { - const values: Array<{ path: string; value: any }> = [] - const navPath = [ + let values: Array<{path:string, value:any}> = [] + let root = [ 'navigation.courseGreatCircle', 'navigation.courseRhumbline' ] values.push({ - path: `navigation.course`, - value: this.courseInfo - }) - - values.push({ - path: `${navPath[0]}.activeRoute.href`, + path: `${root[0]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${navPath[1]}.activeRoute.href`, + path: `${root[1]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${navPath[0]}.activeRoute.startTime`, + path: `${root[0]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${navPath[1]}.activeRoute.startTime`, + path: `${root[1]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${navPath[0]}.nextPoint.href`, + path: `${root[0]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${navPath[1]}.nextPoint.href`, + path: `${root[1]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${navPath[0]}.nextPoint.position`, + path: `${root[0]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${navPath[1]}.nextPoint.position`, + path: `${root[1]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${navPath[0]}.nextPoint.type`, + path: `${root[0]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${navPath[1]}.nextPoint.type`, + path: `${root[1]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${navPath[0]}.nextPoint.arrivalCircle`, + path: `${root[0]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${navPath[1]}.nextPoint.arrivalCircle`, + path: `${root[1]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${navPath[0]}.previousPoint.href`, + path: `${root[0]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${navPath[1]}.previousPoint.href`, + path: `${root[1]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${navPath[0]}.previousPoint.position`, + path: `${root[0]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${navPath[1]}.previousPoint.position`, + path: `${root[1]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${navPath[0]}.previousPoint.type`, + path: `${root[0]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) values.push({ - path: `${navPath[1]}.previousPoint.type`, + path: `${root[1]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) return { updates: [ { - values + values: values } ] } diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index b162d1314..04653db5e 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -108,7 +108,29 @@ "put": { "tags": ["course"], "summary": "Restart course calculations", - "description": "Sets previousPoint value to current vessel location" + "description": "Sets previousPoint value to current vessel location", + "requestBody": { + "description": "Restart payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } } }, @@ -178,7 +200,29 @@ "delete": { "tags": ["course/destination"], "summary": "Clear destination", - "description": "Sets activeRoute, nextPoint & previousPoint values to null" + "description": "Sets activeRoute, nextPoint & previousPoint values to null", + "requestBody": { + "description": "Delete payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } } }, @@ -269,7 +313,29 @@ "delete": { "tags": ["course/activeRoute"], "summary": "Clear active route", - "description": "Sets activeRoute, nextPoint & previousPoint values to null" + "description": "Sets activeRoute, nextPoint & previousPoint values to null", + "requestBody": { + "description": "Delete payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } } }, From 3ca5a8978f707486254ef409281ad7f6bdd6eccf Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 5 Dec 2021 10:26:47 +1030 Subject: [PATCH 339/410] update detlas --- src/api/course/index.ts | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 6e8e4074d..7510ca1e7 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,23 +1,15 @@ -import { - ResourceProvider, - ResourceProviderMethods, - SignalKResourceType -} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' -import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' - -const API_METHODS: string[] = [] interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any + registerPutHandler: (context:string, path:string, cb:any) => any resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -110,12 +102,26 @@ export class CourseApi { } ) + // + if(this.server.registerPutHandler) { + debug('** Registering PUT Action Handler(s) **') + this.server.registerPutHandler( + 'vessels.self', + 'navigation.course.*', + this.handleCourseApiPut + ); + } + // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { + //test for active destination + if (!this.courseInfo.nextPoint.position) { + return + } // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -266,6 +272,12 @@ export class CourseApi { ) } + private handleCourseApiPut(context:string, path:string, value:any, cb:any) { + + debug('** PUT handler **') + return undefined + } + private async activateRoute(route: ActiveRoute): Promise { let rte: any @@ -470,6 +482,11 @@ export class CourseApi { 'navigation.courseRhumbline' ] + values.push({ + path: `navigation.course`, + value: this.courseInfo + }) + values.push({ path: `${root[0]}.activeRoute.href`, value: this.courseInfo.activeRoute.href From bfed30da0a1aa5bab5c2aec8878632d46f2116af Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 4 Dec 2021 17:12:14 +1030 Subject: [PATCH 340/410] init courseApi --- src/api/course/index.ts | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 7510ca1e7..af32aceee 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,15 +1,26 @@ + +import { + ResourceProvider, + ResourceProviderMethods, + SignalKResourceType +} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' +import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' + +const API_METHODS: string[] = [] + interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - registerPutHandler: (context:string, path:string, cb:any) => any + resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -102,26 +113,12 @@ export class CourseApi { } ) - // - if(this.server.registerPutHandler) { - debug('** Registering PUT Action Handler(s) **') - this.server.registerPutHandler( - 'vessels.self', - 'navigation.course.*', - this.handleCourseApiPut - ); - } - // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { - //test for active destination - if (!this.courseInfo.nextPoint.position) { - return - } // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -272,12 +269,6 @@ export class CourseApi { ) } - private handleCourseApiPut(context:string, path:string, value:any, cb:any) { - - debug('** PUT handler **') - return undefined - } - private async activateRoute(route: ActiveRoute): Promise { let rte: any From 86a5bc3d5b4ff6a3a7c4b5e78290b247b77956ac Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 5 Dec 2021 10:26:47 +1030 Subject: [PATCH 341/410] update detlas --- src/api/course/index.ts | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index af32aceee..7510ca1e7 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,26 +1,15 @@ - -import { - ResourceProvider, - ResourceProviderMethods, - SignalKResourceType -} from '@signalk/server-api' import Debug from 'debug' import { Application, Request, Response } from 'express' -import { v4 as uuidv4 } from 'uuid' const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' - -const API_METHODS: string[] = [] - interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - + registerPutHandler: (context:string, path:string, cb:any) => any resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -113,12 +102,26 @@ export class CourseApi { } ) + // + if(this.server.registerPutHandler) { + debug('** Registering PUT Action Handler(s) **') + this.server.registerPutHandler( + 'vessels.self', + 'navigation.course.*', + this.handleCourseApiPut + ); + } + // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { + //test for active destination + if (!this.courseInfo.nextPoint.position) { + return + } // set previousPoint to vessel position const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -269,6 +272,12 @@ export class CourseApi { ) } + private handleCourseApiPut(context:string, path:string, value:any, cb:any) { + + debug('** PUT handler **') + return undefined + } + private async activateRoute(route: ActiveRoute): Promise { let rte: any From 020374a3576845a19e6041145e8bb3a254525aa0 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 6 Dec 2021 08:39:56 +1030 Subject: [PATCH 342/410] enable put processing --- src/api/course/index.ts | 133 +++++++++++++++++++++++++--------------- 1 file changed, 82 insertions(+), 51 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 7510ca1e7..1566ecdf3 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -9,7 +9,14 @@ const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/cou interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any - registerPutHandler: (context:string, path:string, cb:any) => any + securityStrategy: { + shouldAllowPut: ( + req: any, + context: string, + source: any, + path: string + ) => boolean + } resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -92,6 +99,15 @@ export class CourseApi { this.initResourceRoutes() } + private updateAllowed(): boolean { + return this.server.securityStrategy.shouldAllowPut( + this.server, + 'vessels.self', + null, + 'resources' + ) + } + private initResourceRoutes() { // return current course information this.server.get( @@ -102,43 +118,43 @@ export class CourseApi { } ) - // - if(this.server.registerPutHandler) { - debug('** Registering PUT Action Handler(s) **') - this.server.registerPutHandler( - 'vessels.self', - 'navigation.course.*', - this.handleCourseApiPut - ); - } + this.server.put( + `${COURSE_API_PATH}/restart`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/restart`) + if (!this.updateAllowed()) { + res.status(403) + return + } + if (!this.courseInfo.nextPoint.position) { + res.status(406).send(`No active destination!`) + return + } + // set previousPoint to vessel position + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.emitCourseInfo() + res.status(200) + } else { + res.status(406).send(`Vessel position unavailable!`) + } + } + ) - // restart / arrivalCircle this.server.put( - `${COURSE_API_PATH}/:action`, + `${COURSE_API_PATH}/arrivalCircle`, async (req: Request, res: Response) => { - debug(`** PUT ${COURSE_API_PATH}/:action`) - if (req.params.restart) { - //test for active destination - if (!this.courseInfo.nextPoint.position) { - return - } - // set previousPoint to vessel position - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.emitCourseInfo() - res.status(200).send(`Course restarted.`) - } else { - res.status(406).send(`Vessel position unavailable!`) - } + debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) + if (!this.updateAllowed()) { + res.status(403) + return } - if (req.params.arrivalCircle) { - if (this.setArrivalCircle(req.params.arrivalCircle)) { - this.emitCourseInfo() - res.status(200).send(`Destination set successfully.`) - } else { - res.status(406).send(`Invalid Data`) - } + if (this.setArrivalCircle(req.body.value)) { + this.emitCourseInfo() + res.status(200) + } else { + res.status(406).send(`Invalid Data`) } } ) @@ -148,6 +164,10 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) + if (!this.updateAllowed()) { + res.status(403) + return + } if (!req.body.value) { res.status(406).send(`Invalid Data`) return @@ -155,7 +175,7 @@ export class CourseApi { const result = await this.setDestination(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send(`Destination set successfully.`) + res.status(200) } else { this.clearDestination() this.emitCourseInfo() @@ -169,22 +189,29 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) + if (!this.updateAllowed()) { + res.status(403) + return + } this.clearDestination() this.emitCourseInfo() - res.status(200).send(`Destination cleared.`) + res.status(200) } ) - // set / clear activeRoute + // set activeRoute this.server.put( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) - + if (!this.updateAllowed()) { + res.status(403) + return + } const result = await this.activateRoute(req.body.value) if (result) { this.emitCourseInfo() - res.status(200).send(`Active route set.`) + res.status(200) } else { this.clearDestination() this.emitCourseInfo() @@ -192,14 +219,19 @@ export class CourseApi { } } ) + + // clear activeRoute /destination this.server.delete( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/activeRoute`) - + if (!this.updateAllowed()) { + res.status(403) + return + } this.clearDestination() this.emitCourseInfo() - res.status(200).send(`Active route cleared.`) + res.status(200) } ) @@ -207,6 +239,11 @@ export class CourseApi { `${COURSE_API_PATH}/activeRoute/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) + if (!this.updateAllowed()) { + res.status(403) + return + } + // fetch route data if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return @@ -217,7 +254,7 @@ export class CourseApi { return } - if (req.params.nextPoint) { + if (req.params.action === 'nextPoint') { if ( typeof req.body.value === 'number' && (req.body.value === 1 || req.body.value === -1) @@ -230,7 +267,7 @@ export class CourseApi { res.status(406).send(`Invalid Data`) } } - if (req.params.pointIndex) { + if (req.params.action === 'pointIndex') { if (typeof req.body.value === 'number') { this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( req.body.value, @@ -241,7 +278,7 @@ export class CourseApi { } } - // set nextPoint + // set new destination this.courseInfo.nextPoint.position = this.getRoutePoint( rte, this.courseInfo.activeRoute.pointIndex @@ -267,17 +304,11 @@ export class CourseApi { } this.courseInfo.previousPoint.href = null - res.status(200).send(`OK`) + res.status(200) } ) } - private handleCourseApiPut(context:string, path:string, value:any, cb:any) { - - debug('** PUT handler **') - return undefined - } - private async activateRoute(route: ActiveRoute): Promise { let rte: any From e9dee2e104b2166c25923786b4785b6da6700066 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 6 Dec 2021 13:34:31 +1030 Subject: [PATCH 343/410] chore: lint --- src/api/course/index.ts | 211 ++++++++++++++++++++---------------- src/api/course/openApi.json | 72 +----------- 2 files changed, 120 insertions(+), 163 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 1566ecdf3..6cd3b80fd 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -11,7 +11,7 @@ interface CourseApplication extends Application { getSelfPath: (path: string) => any securityStrategy: { shouldAllowPut: ( - req: any, + req: Application, context: string, source: any, path: string @@ -104,7 +104,7 @@ export class CourseApi { this.server, 'vessels.self', null, - 'resources' + 'navigation.course' ) } @@ -131,12 +131,14 @@ export class CourseApi { return } // set previousPoint to vessel position - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.emitCourseInfo() - res.status(200) - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.emitCourseInfo() + res.status(200) + } + } catch (err) { res.status(406).send(`Vessel position unavailable!`) } } @@ -150,7 +152,8 @@ export class CourseApi { res.status(403) return } - if (this.setArrivalCircle(req.body.value)) { + if (this.isValidArrivalCircle(req.body.value)) { + this.courseInfo.nextPoint.arrivalCircle = req.body.value this.emitCourseInfo() res.status(200) } else { @@ -243,7 +246,7 @@ export class CourseApi { res.status(403) return } - // fetch route data + // fetch active route data if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return @@ -281,24 +284,30 @@ export class CourseApi { // set new destination this.courseInfo.nextPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex + this.courseInfo.activeRoute.pointIndex, + this.courseInfo.activeRoute.reverse ) this.courseInfo.nextPoint.type = `RoutePoint` this.courseInfo.nextPoint.href = null // set previousPoint if (this.courseInfo.activeRoute.pointIndex === 0) { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } catch (err) { return false } } else { this.courseInfo.previousPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex - 1 + this.courseInfo.activeRoute.pointIndex - 1, + this.courseInfo.activeRoute.reverse ) this.courseInfo.previousPoint.type = `RoutePoint` } @@ -324,81 +333,97 @@ export class CourseApi { return false } + const newDest: any = {} + Object.assign(newDest, this.courseInfo) + // set activeroute - this.courseInfo.activeRoute.href = route.href + newDest.activeRoute.href = route.href - if (typeof route.arrivalCircle === 'number') { - this.setArrivalCircle(route.arrivalCircle) + if (this.isValidArrivalCircle(route.arrivalCircle as number)) { + newDest.nextPoint.arrivalCircle = route.arrivalCircle } - this.courseInfo.activeRoute.reverse = - typeof route.reverse === 'boolean' ? route.reverse : false + newDest.activeRoute.startTime = new Date().toISOString() - this.courseInfo.activeRoute.pointIndex = - typeof route.pointIndex === 'number' - ? this.parsePointIndex(route.pointIndex, rte) - : 0 + if (typeof route.reverse === 'boolean') { + newDest.activeRoute.reverse = route.reverse + } - this.courseInfo.activeRoute.startTime = new Date().toISOString() + newDest.activeRoute.pointIndex = this.parsePointIndex( + route.pointIndex as number, + rte + ) // set nextPoint - this.courseInfo.nextPoint.position = this.getRoutePoint( + newDest.nextPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex + newDest.activeRoute.pointIndex, + newDest.activeRoute.reverse ) - this.courseInfo.nextPoint.type = `RoutePoint` - this.courseInfo.nextPoint.href = null + newDest.nextPoint.type = `RoutePoint` + newDest.nextPoint.href = null // set previousPoint - if (this.courseInfo.activeRoute.pointIndex === 0) { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + if (newDest.activeRoute.pointIndex === 0) { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } catch (err) { return false } } else { - this.courseInfo.previousPoint.position = this.getRoutePoint( + newDest.previousPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex - 1 + newDest.activeRoute.pointIndex - 1, + newDest.activeRoute.reverse ) - this.courseInfo.previousPoint.type = `RoutePoint` + newDest.previousPoint.type = `RoutePoint` } - this.courseInfo.previousPoint.href = null + newDest.previousPoint.href = null + this.courseInfo = newDest return true } - private async setDestination(dest: Destination): Promise { + private async setDestination(dest: any): Promise { + const newDest: any = {} + Object.assign(newDest, this.courseInfo) + // set nextPoint - if (typeof dest.arrivalCircle === 'number') { - this.setArrivalCircle(dest.arrivalCircle) + if (this.isValidArrivalCircle(dest.arrivalCircle)) { + newDest.nextPoint.arrivalCircle = dest.arrivalCircle } - this.courseInfo.nextPoint.type = - typeof dest.type !== 'undefined' ? dest.type : null + newDest.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - this.courseInfo.nextPoint.href = dest.href + newDest.href = dest.href const href = this.parseHref(dest.href) if (href) { + // fetch waypoint resource details try { const r = await this.server.resourcesApi.getResource( href.type, href.id ) - if (r.position && typeof r.position.value?.latitude !== 'undefined') { - this.courseInfo.nextPoint.position = r.position.value + if (r.position && typeof r.position?.latitude !== 'undefined') { + newDest.nextPoint.position = r.position + } else { + return false } } catch (err) { return false } } } else if (dest.position) { - this.courseInfo.nextPoint.href = null + newDest.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { - this.courseInfo.nextPoint.position = dest.position + newDest.nextPoint.position = dest.position } else { return false } @@ -407,15 +432,20 @@ export class CourseApi { } // set previousPoint - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + newDest.previousPoint.position = position.value + newDest.previousPoint.type = `VesselPosition` + } else { + return false + } + newDest.previousPoint.href = null + } catch (err) { return false } - this.courseInfo.previousPoint.href = null + this.courseInfo = newDest return true } @@ -432,17 +462,12 @@ export class CourseApi { this.courseInfo.previousPoint.position = null } - private setArrivalCircle(value: any): boolean { - if (typeof value === 'number' && value >= 0) { - this.courseInfo.nextPoint.arrivalCircle = value - return true - } else { - return false - } + private isValidArrivalCircle(value: number): boolean { + return typeof value === 'number' && value >= 0 } private parsePointIndex(index: number, rte: any): number { - if (!rte) { + if (typeof index !== 'number' || !rte) { return 0 } if (!rte.feature?.geometry?.coordinates) { @@ -461,13 +486,11 @@ export class CourseApi { } private parseHref(href: string): { type: string; id: string } | undefined { - if (href.length === 0) { + if (!href) { return undefined } - if (href[0] === '/') { - href = href.slice(1) - } - const ref: string[] = href.split('/') + + const ref: string[] = href.split('/').slice(-3) if (ref.length < 3) { return undefined } @@ -480,8 +503,8 @@ export class CourseApi { } } - private getRoutePoint(rte: any, index: number) { - const pos = this.courseInfo.activeRoute.reverse + private getRoutePoint(rte: any, index: number, reverse: boolean) { + const pos = reverse ? rte.feature.geometry.coordinates[ rte.feature.geometry.coordinates.length - (index + 1) ] @@ -489,7 +512,7 @@ export class CourseApi { return { latitude: pos[1], longitude: pos[0], - altitude: pos.length == 3 ? pos[2] : 0 + altitude: pos.length === 3 ? pos[2] : 0 } } @@ -507,8 +530,8 @@ export class CourseApi { } private buildDeltaMsg(): any { - let values: Array<{path:string, value:any}> = [] - let root = [ + const values: Array<{ path: string; value: any }> = [] + const navPath = [ 'navigation.courseGreatCircle', 'navigation.courseRhumbline' ] @@ -519,82 +542,82 @@ export class CourseApi { }) values.push({ - path: `${root[0]}.activeRoute.href`, + path: `${navPath[0]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${root[1]}.activeRoute.href`, + path: `${navPath[1]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${root[0]}.activeRoute.startTime`, + path: `${navPath[0]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${root[1]}.activeRoute.startTime`, + path: `${navPath[1]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${root[0]}.nextPoint.href`, + path: `${navPath[0]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${root[1]}.nextPoint.href`, + path: `${navPath[1]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${root[0]}.nextPoint.position`, + path: `${navPath[0]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${root[1]}.nextPoint.position`, + path: `${navPath[1]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${root[0]}.nextPoint.type`, + path: `${navPath[0]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${root[1]}.nextPoint.type`, + path: `${navPath[1]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${root[0]}.nextPoint.arrivalCircle`, + path: `${navPath[0]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${root[1]}.nextPoint.arrivalCircle`, + path: `${navPath[1]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${root[0]}.previousPoint.href`, + path: `${navPath[0]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${root[1]}.previousPoint.href`, + path: `${navPath[1]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${root[0]}.previousPoint.position`, + path: `${navPath[0]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${root[1]}.previousPoint.position`, + path: `${navPath[1]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${root[0]}.previousPoint.type`, + path: `${navPath[0]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) values.push({ - path: `${root[1]}.previousPoint.type`, + path: `${navPath[1]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) return { updates: [ { - values: values + values } ] } diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index 04653db5e..b162d1314 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -108,29 +108,7 @@ "put": { "tags": ["course"], "summary": "Restart course calculations", - "description": "Sets previousPoint value to current vessel location", - "requestBody": { - "description": "Restart payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } - } - } - } - } - } + "description": "Sets previousPoint value to current vessel location" } }, @@ -200,29 +178,7 @@ "delete": { "tags": ["course/destination"], "summary": "Clear destination", - "description": "Sets activeRoute, nextPoint & previousPoint values to null", - "requestBody": { - "description": "Delete payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } - } - } - } - } - } + "description": "Sets activeRoute, nextPoint & previousPoint values to null" } }, @@ -313,29 +269,7 @@ "delete": { "tags": ["course/activeRoute"], "summary": "Clear active route", - "description": "Sets activeRoute, nextPoint & previousPoint values to null", - "requestBody": { - "description": "Delete payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } - } - } - } - } - } + "description": "Sets activeRoute, nextPoint & previousPoint values to null" } }, From b61624c6ae581882c3721e980f58064b36a02254 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 8 Dec 2021 09:27:06 +1030 Subject: [PATCH 344/410] add 30sec delta interval --- src/api/course/index.ts | 100 ++++++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 45 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 6cd3b80fd..2aa57e8a6 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -6,6 +6,8 @@ const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +const DELTA_INTERVAL: number = 30000 + interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any @@ -97,6 +99,11 @@ export class CourseApi { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app this.initResourceRoutes() + setInterval( ()=> { + if(this.courseInfo.nextPoint.position) { + this.emitCourseInfo() + } + }, DELTA_INTERVAL) } private updateAllowed(): boolean { @@ -123,7 +130,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/restart`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (!this.courseInfo.nextPoint.position) { @@ -136,7 +143,7 @@ export class CourseApi { if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } } catch (err) { res.status(406).send(`Vessel position unavailable!`) @@ -149,13 +156,13 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (this.isValidArrivalCircle(req.body.value)) { this.courseInfo.nextPoint.arrivalCircle = req.body.value this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { res.status(406).send(`Invalid Data`) } @@ -168,7 +175,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (!req.body.value) { @@ -178,7 +185,7 @@ export class CourseApi { const result = await this.setDestination(req.body.value) if (result) { this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { this.clearDestination() this.emitCourseInfo() @@ -193,12 +200,12 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } this.clearDestination() this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } ) @@ -208,13 +215,13 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } const result = await this.activateRoute(req.body.value) if (result) { this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { this.clearDestination() this.emitCourseInfo() @@ -234,7 +241,7 @@ export class CourseApi { } this.clearDestination() this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } ) @@ -243,7 +250,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } // fetch active route data @@ -268,6 +275,7 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) + return } } if (req.params.action === 'pointIndex') { @@ -278,6 +286,7 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) + return } } @@ -298,9 +307,11 @@ export class CourseApi { this.courseInfo.previousPoint.position = position.value this.courseInfo.previousPoint.type = `VesselPosition` } else { + res.status(406).send(`Invalid Data`) return false } } catch (err) { + res.status(406).send(`Invalid Data`) return false } } else { @@ -312,8 +323,7 @@ export class CourseApi { this.courseInfo.previousPoint.type = `RoutePoint` } this.courseInfo.previousPoint.href = null - - res.status(200) + res.status(200).send('OK') } ) } @@ -333,38 +343,38 @@ export class CourseApi { return false } - const newDest: any = {} - Object.assign(newDest, this.courseInfo) + const newCourse: any = {} + Object.assign(newCourse, this.courseInfo) // set activeroute - newDest.activeRoute.href = route.href + newCourse.activeRoute.href = route.href if (this.isValidArrivalCircle(route.arrivalCircle as number)) { - newDest.nextPoint.arrivalCircle = route.arrivalCircle + newCourse.nextPoint.arrivalCircle = route.arrivalCircle } - newDest.activeRoute.startTime = new Date().toISOString() + newCourse.activeRoute.startTime = new Date().toISOString() if (typeof route.reverse === 'boolean') { - newDest.activeRoute.reverse = route.reverse + newCourse.activeRoute.reverse = route.reverse } - newDest.activeRoute.pointIndex = this.parsePointIndex( + newCourse.activeRoute.pointIndex = this.parsePointIndex( route.pointIndex as number, rte ) // set nextPoint - newDest.nextPoint.position = this.getRoutePoint( + newCourse.nextPoint.position = this.getRoutePoint( rte, - newDest.activeRoute.pointIndex, - newDest.activeRoute.reverse + newCourse.activeRoute.pointIndex, + newCourse.activeRoute.reverse ) - newDest.nextPoint.type = `RoutePoint` - newDest.nextPoint.href = null + newCourse.nextPoint.type = `RoutePoint` + newCourse.nextPoint.href = null // set previousPoint - if (newDest.activeRoute.pointIndex === 0) { + if (newCourse.activeRoute.pointIndex === 0) { try { const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -377,32 +387,32 @@ export class CourseApi { return false } } else { - newDest.previousPoint.position = this.getRoutePoint( + newCourse.previousPoint.position = this.getRoutePoint( rte, - newDest.activeRoute.pointIndex - 1, - newDest.activeRoute.reverse + newCourse.activeRoute.pointIndex - 1, + newCourse.activeRoute.reverse ) - newDest.previousPoint.type = `RoutePoint` + newCourse.previousPoint.type = `RoutePoint` } - newDest.previousPoint.href = null + newCourse.previousPoint.href = null - this.courseInfo = newDest + this.courseInfo = newCourse return true } private async setDestination(dest: any): Promise { - const newDest: any = {} - Object.assign(newDest, this.courseInfo) + const newCourse: any = {} + Object.assign(newCourse, this.courseInfo) // set nextPoint if (this.isValidArrivalCircle(dest.arrivalCircle)) { - newDest.nextPoint.arrivalCircle = dest.arrivalCircle + newCourse.nextPoint.arrivalCircle = dest.arrivalCircle } - newDest.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null + newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - newDest.href = dest.href + newCourse.href = dest.href const href = this.parseHref(dest.href) if (href) { // fetch waypoint resource details @@ -412,7 +422,7 @@ export class CourseApi { href.id ) if (r.position && typeof r.position?.latitude !== 'undefined') { - newDest.nextPoint.position = r.position + newCourse.nextPoint.position = r.position } else { return false } @@ -421,9 +431,9 @@ export class CourseApi { } } } else if (dest.position) { - newDest.nextPoint.href = null + newCourse.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { - newDest.nextPoint.position = dest.position + newCourse.nextPoint.position = dest.position } else { return false } @@ -435,17 +445,17 @@ export class CourseApi { try { const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { - newDest.previousPoint.position = position.value - newDest.previousPoint.type = `VesselPosition` + newCourse.previousPoint.position = position.value + newCourse.previousPoint.type = `VesselPosition` } else { return false } - newDest.previousPoint.href = null + newCourse.previousPoint.href = null } catch (err) { return false } - this.courseInfo = newDest + this.courseInfo = newCourse return true } From ea5de64248df89b982a34736ef8a6acd03b59b52 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 12 Dec 2021 14:29:38 +1030 Subject: [PATCH 345/410] chore: lint --- src/api/course/index.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 2aa57e8a6..1e719297a 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -6,7 +6,7 @@ const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -const DELTA_INTERVAL: number = 30000 +const DELTA_INTERVAL: number = 30000 interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void @@ -99,8 +99,8 @@ export class CourseApi { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app this.initResourceRoutes() - setInterval( ()=> { - if(this.courseInfo.nextPoint.position) { + setInterval(() => { + if (this.courseInfo.nextPoint.position) { this.emitCourseInfo() } }, DELTA_INTERVAL) @@ -409,7 +409,8 @@ export class CourseApi { newCourse.nextPoint.arrivalCircle = dest.arrivalCircle } - newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null + newCourse.nextPoint.type = + typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { newCourse.href = dest.href From c7672a00cb26504df820015af366117ed8d0c583 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 4 Dec 2021 17:12:14 +1030 Subject: [PATCH 346/410] init courseApi --- src/api/course/index.ts | 332 ++++++++++++++++++++++++++++++++++++ src/api/course/openApi.json | 78 +++++++++ 2 files changed, 410 insertions(+) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 1e719297a..a71346933 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,16 +1,34 @@ +<<<<<<< HEAD import Debug from 'debug' import { Application, Request, Response } from 'express' +======= +import { + ResourceProvider, + ResourceProviderMethods, + SignalKResourceType +} from '@signalk/server-api' +import Debug from 'debug' +import { Application, Request, Response } from 'express' +import { v4 as uuidv4 } from 'uuid' +>>>>>>> init courseApi const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +<<<<<<< HEAD const DELTA_INTERVAL: number = 30000 +======= +const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' + +const API_METHODS: string[] = [] +>>>>>>> init courseApi interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any +<<<<<<< HEAD securityStrategy: { shouldAllowPut: ( req: Application, @@ -19,6 +37,8 @@ interface CourseApplication extends Application { path: string ) => boolean } +======= +>>>>>>> init courseApi resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -36,6 +56,10 @@ interface Destination extends DestinationBase { } type?: string } +<<<<<<< HEAD +======= + +>>>>>>> init courseApi interface ActiveRoute extends DestinationBase { pointIndex?: number reverse?: boolean @@ -99,6 +123,7 @@ export class CourseApi { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app this.initResourceRoutes() +<<<<<<< HEAD setInterval(() => { if (this.courseInfo.nextPoint.position) { this.emitCourseInfo() @@ -113,6 +138,8 @@ export class CourseApi { null, 'navigation.course' ) +======= +>>>>>>> init courseApi } private initResourceRoutes() { @@ -125,6 +152,7 @@ export class CourseApi { } ) +<<<<<<< HEAD this.server.put( `${COURSE_API_PATH}/restart`, async (req: Request, res: Response) => { @@ -139,10 +167,20 @@ export class CourseApi { } // set previousPoint to vessel position try { +======= + // restart / arrivalCircle + this.server.put( + `${COURSE_API_PATH}/:action`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/:action`) + if (req.params.restart) { + // set previousPoint to vessel position +>>>>>>> init courseApi const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.emitCourseInfo() +<<<<<<< HEAD res.status(200).send('OK') } } catch (err) { @@ -165,6 +203,20 @@ export class CourseApi { res.status(200).send('OK') } else { res.status(406).send(`Invalid Data`) +======= + res.status(200).send(`Course restarted.`) + } else { + res.status(406).send(`Vessel position unavailable!`) + } + } + if (req.params.arrivalCircle) { + if (this.setArrivalCircle(req.params.arrivalCircle)) { + this.emitCourseInfo() + res.status(200).send(`Destination set successfully.`) + } else { + res.status(406).send(`Invalid Data`) + } +>>>>>>> init courseApi } } ) @@ -174,10 +226,14 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) +<<<<<<< HEAD if (!this.updateAllowed()) { res.status(403).send('Unauthorised') return } +======= + +>>>>>>> init courseApi if (!req.body.value) { res.status(406).send(`Invalid Data`) return @@ -185,7 +241,11 @@ export class CourseApi { const result = await this.setDestination(req.body.value) if (result) { this.emitCourseInfo() +<<<<<<< HEAD res.status(200).send('OK') +======= + res.status(200).send(`Destination set successfully.`) +>>>>>>> init courseApi } else { this.clearDestination() this.emitCourseInfo() @@ -199,6 +259,7 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) +<<<<<<< HEAD if (!this.updateAllowed()) { res.status(403).send('Unauthorised') return @@ -210,10 +271,20 @@ export class CourseApi { ) // set activeRoute +======= + this.clearDestination() + this.emitCourseInfo() + res.status(200).send(`Destination cleared.`) + } + ) + + // set / clear activeRoute +>>>>>>> init courseApi this.server.put( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) +<<<<<<< HEAD if (!this.updateAllowed()) { res.status(403).send('Unauthorised') return @@ -222,6 +293,13 @@ export class CourseApi { if (result) { this.emitCourseInfo() res.status(200).send('OK') +======= + + const result = await this.activateRoute(req.body.value) + if (result) { + this.emitCourseInfo() + res.status(200).send(`Active route set.`) +>>>>>>> init courseApi } else { this.clearDestination() this.emitCourseInfo() @@ -229,12 +307,16 @@ export class CourseApi { } } ) +<<<<<<< HEAD // clear activeRoute /destination +======= +>>>>>>> init courseApi this.server.delete( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/activeRoute`) +<<<<<<< HEAD if (!this.updateAllowed()) { res.status(403) return @@ -242,6 +324,12 @@ export class CourseApi { this.clearDestination() this.emitCourseInfo() res.status(200).send('OK') +======= + + this.clearDestination() + this.emitCourseInfo() + res.status(200).send(`Active route cleared.`) +>>>>>>> init courseApi } ) @@ -249,22 +337,34 @@ export class CourseApi { `${COURSE_API_PATH}/activeRoute/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) +<<<<<<< HEAD if (!this.updateAllowed()) { res.status(403).send('Unauthorised') return } // fetch active route data +======= + +>>>>>>> init courseApi if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return } +<<<<<<< HEAD +======= + +>>>>>>> init courseApi const rte = await this.getRoute(this.courseInfo.activeRoute.href) if (!rte) { res.status(406).send(`Invalid Data`) return } +<<<<<<< HEAD if (req.params.action === 'nextPoint') { +======= + if (req.params.nextPoint) { +>>>>>>> init courseApi if ( typeof req.body.value === 'number' && (req.body.value === 1 || req.body.value === -1) @@ -275,10 +375,16 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) +<<<<<<< HEAD return } } if (req.params.action === 'pointIndex') { +======= + } + } + if (req.params.pointIndex) { +>>>>>>> init courseApi if (typeof req.body.value === 'number') { this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( req.body.value, @@ -286,6 +392,7 @@ export class CourseApi { ) } else { res.status(406).send(`Invalid Data`) +<<<<<<< HEAD return } } @@ -295,12 +402,22 @@ export class CourseApi { rte, this.courseInfo.activeRoute.pointIndex, this.courseInfo.activeRoute.reverse +======= + } + } + + // set nextPoint + this.courseInfo.nextPoint.position = this.getRoutePoint( + rte, + this.courseInfo.activeRoute.pointIndex +>>>>>>> init courseApi ) this.courseInfo.nextPoint.type = `RoutePoint` this.courseInfo.nextPoint.href = null // set previousPoint if (this.courseInfo.activeRoute.pointIndex === 0) { +<<<<<<< HEAD try { const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -312,18 +429,34 @@ export class CourseApi { } } catch (err) { res.status(406).send(`Invalid Data`) +======= + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { +>>>>>>> init courseApi return false } } else { this.courseInfo.previousPoint.position = this.getRoutePoint( rte, +<<<<<<< HEAD this.courseInfo.activeRoute.pointIndex - 1, this.courseInfo.activeRoute.reverse +======= + this.courseInfo.activeRoute.pointIndex - 1 +>>>>>>> init courseApi ) this.courseInfo.previousPoint.type = `RoutePoint` } this.courseInfo.previousPoint.href = null +<<<<<<< HEAD res.status(200).send('OK') +======= + + res.status(200).send(`OK`) +>>>>>>> init courseApi } ) } @@ -343,6 +476,7 @@ export class CourseApi { return false } +<<<<<<< HEAD const newCourse: any = {} Object.assign(newCourse, this.courseInfo) @@ -417,24 +551,96 @@ export class CourseApi { const href = this.parseHref(dest.href) if (href) { // fetch waypoint resource details +======= + // set activeroute + this.courseInfo.activeRoute.href = route.href + + if (typeof route.arrivalCircle === 'number') { + this.setArrivalCircle(route.arrivalCircle) + } + + this.courseInfo.activeRoute.reverse = + typeof route.reverse === 'boolean' ? route.reverse : false + + this.courseInfo.activeRoute.pointIndex = + typeof route.pointIndex === 'number' + ? this.parsePointIndex(route.pointIndex, rte) + : 0 + + this.courseInfo.activeRoute.startTime = new Date().toISOString() + + // set nextPoint + this.courseInfo.nextPoint.position = this.getRoutePoint( + rte, + this.courseInfo.activeRoute.pointIndex + ) + this.courseInfo.nextPoint.type = `RoutePoint` + this.courseInfo.nextPoint.href = null + + // set previousPoint + if (this.courseInfo.activeRoute.pointIndex === 0) { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } else { + this.courseInfo.previousPoint.position = this.getRoutePoint( + rte, + this.courseInfo.activeRoute.pointIndex - 1 + ) + this.courseInfo.previousPoint.type = `RoutePoint` + } + this.courseInfo.previousPoint.href = null + + return true + } + + private async setDestination(dest: Destination): Promise { + // set nextPoint + if (typeof dest.arrivalCircle === 'number') { + this.setArrivalCircle(dest.arrivalCircle) + } + + this.courseInfo.nextPoint.type = + typeof dest.type !== 'undefined' ? dest.type : null + + if (dest.href) { + this.courseInfo.nextPoint.href = dest.href + const href = this.parseHref(dest.href) + if (href) { +>>>>>>> init courseApi try { const r = await this.server.resourcesApi.getResource( href.type, href.id ) +<<<<<<< HEAD if (r.position && typeof r.position?.latitude !== 'undefined') { newCourse.nextPoint.position = r.position } else { return false +======= + if (r.position && typeof r.position.value?.latitude !== 'undefined') { + this.courseInfo.nextPoint.position = r.position.value +>>>>>>> init courseApi } } catch (err) { return false } } } else if (dest.position) { +<<<<<<< HEAD newCourse.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { newCourse.nextPoint.position = dest.position +======= + this.courseInfo.nextPoint.href = null + if (typeof dest.position.latitude !== 'undefined') { + this.courseInfo.nextPoint.position = dest.position +>>>>>>> init courseApi } else { return false } @@ -443,6 +649,7 @@ export class CourseApi { } // set previousPoint +<<<<<<< HEAD try { const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -457,6 +664,17 @@ export class CourseApi { } this.courseInfo = newCourse +======= + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + this.courseInfo.previousPoint.href = null + +>>>>>>> init courseApi return true } @@ -473,12 +691,26 @@ export class CourseApi { this.courseInfo.previousPoint.position = null } +<<<<<<< HEAD private isValidArrivalCircle(value: number): boolean { return typeof value === 'number' && value >= 0 } private parsePointIndex(index: number, rte: any): number { if (typeof index !== 'number' || !rte) { +======= + private setArrivalCircle(value: any): boolean { + if (typeof value === 'number' && value >= 0) { + this.courseInfo.nextPoint.arrivalCircle = value + return true + } else { + return false + } + } + + private parsePointIndex(index: number, rte: any): number { + if (!rte) { +>>>>>>> init courseApi return 0 } if (!rte.feature?.geometry?.coordinates) { @@ -497,11 +729,21 @@ export class CourseApi { } private parseHref(href: string): { type: string; id: string } | undefined { +<<<<<<< HEAD if (!href) { return undefined } const ref: string[] = href.split('/').slice(-3) +======= + if (href.length === 0) { + return undefined + } + if (href[0] === '/') { + href = href.slice(1) + } + const ref: string[] = href.split('/') +>>>>>>> init courseApi if (ref.length < 3) { return undefined } @@ -514,8 +756,13 @@ export class CourseApi { } } +<<<<<<< HEAD private getRoutePoint(rte: any, index: number, reverse: boolean) { const pos = reverse +======= + private getRoutePoint(rte: any, index: number) { + const pos = this.courseInfo.activeRoute.reverse +>>>>>>> init courseApi ? rte.feature.geometry.coordinates[ rte.feature.geometry.coordinates.length - (index + 1) ] @@ -523,7 +770,11 @@ export class CourseApi { return { latitude: pos[1], longitude: pos[0], +<<<<<<< HEAD altitude: pos.length === 3 ? pos[2] : 0 +======= + altitude: pos.length == 3 ? pos[2] : 0 +>>>>>>> init courseApi } } @@ -541,13 +792,19 @@ export class CourseApi { } private buildDeltaMsg(): any { +<<<<<<< HEAD const values: Array<{ path: string; value: any }> = [] const navPath = [ +======= + let values: Array<{path:string, value:any}> = [] + let root = [ +>>>>>>> init courseApi 'navigation.courseGreatCircle', 'navigation.courseRhumbline' ] values.push({ +<<<<<<< HEAD path: `navigation.course`, value: this.courseInfo }) @@ -622,13 +879,88 @@ export class CourseApi { }) values.push({ path: `${navPath[1]}.previousPoint.type`, +======= + path: `${root[0]}.activeRoute.href`, + value: this.courseInfo.activeRoute.href + }) + values.push({ + path: `${root[1]}.activeRoute.href`, + value: this.courseInfo.activeRoute.href + }) + values.push({ + path: `${root[0]}.activeRoute.startTime`, + value: this.courseInfo.activeRoute.startTime + }) + values.push({ + path: `${root[1]}.activeRoute.startTime`, + value: this.courseInfo.activeRoute.startTime + }) + values.push({ + path: `${root[0]}.nextPoint.href`, + value: this.courseInfo.nextPoint.href + }) + values.push({ + path: `${root[1]}.nextPoint.href`, + value: this.courseInfo.nextPoint.href + }) + values.push({ + path: `${root[0]}.nextPoint.position`, + value: this.courseInfo.nextPoint.position + }) + values.push({ + path: `${root[1]}.nextPoint.position`, + value: this.courseInfo.nextPoint.position + }) + values.push({ + path: `${root[0]}.nextPoint.type`, + value: this.courseInfo.nextPoint.type + }) + values.push({ + path: `${root[1]}.nextPoint.type`, + value: this.courseInfo.nextPoint.type + }) + values.push({ + path: `${root[0]}.nextPoint.arrivalCircle`, + value: this.courseInfo.nextPoint.arrivalCircle + }) + values.push({ + path: `${root[1]}.nextPoint.arrivalCircle`, + value: this.courseInfo.nextPoint.arrivalCircle + }) + values.push({ + path: `${root[0]}.previousPoint.href`, + value: this.courseInfo.previousPoint.href + }) + values.push({ + path: `${root[1]}.previousPoint.href`, + value: this.courseInfo.previousPoint.href + }) + values.push({ + path: `${root[0]}.previousPoint.position`, + value: this.courseInfo.previousPoint.position + }) + values.push({ + path: `${root[1]}.previousPoint.position`, + value: this.courseInfo.previousPoint.position + }) + values.push({ + path: `${root[0]}.previousPoint.type`, + value: this.courseInfo.previousPoint.type + }) + values.push({ + path: `${root[1]}.previousPoint.type`, +>>>>>>> init courseApi value: this.courseInfo.previousPoint.type }) return { updates: [ { +<<<<<<< HEAD values +======= + values: values +>>>>>>> init courseApi } ] } diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index b162d1314..16b5a8d29 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -108,7 +108,33 @@ "put": { "tags": ["course"], "summary": "Restart course calculations", +<<<<<<< HEAD "description": "Sets previousPoint value to current vessel location" +======= + "description": "Sets previousPoint value to current vessel location", + "requestBody": { + "description": "Restart payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } +>>>>>>> init courseApi } }, @@ -178,7 +204,33 @@ "delete": { "tags": ["course/destination"], "summary": "Clear destination", +<<<<<<< HEAD "description": "Sets activeRoute, nextPoint & previousPoint values to null" +======= + "description": "Sets activeRoute, nextPoint & previousPoint values to null", + "requestBody": { + "description": "Delete payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } +>>>>>>> init courseApi } }, @@ -269,7 +321,33 @@ "delete": { "tags": ["course/activeRoute"], "summary": "Clear active route", +<<<<<<< HEAD "description": "Sets activeRoute, nextPoint & previousPoint values to null" +======= + "description": "Sets activeRoute, nextPoint & previousPoint values to null", + "requestBody": { + "description": "Delete payload", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "object", + "default": null + }, + "source": { + "type": "string", + "example": "freeboard-sk" + } + } + } + } + } + } +>>>>>>> init courseApi } }, From f8dcc1df5fc4e5c3b41cdeb4dcaf8a1620ebbb44 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 5 Dec 2021 10:26:47 +1030 Subject: [PATCH 347/410] update detlas --- src/api/course/index.ts | 44 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index a71346933..46fd69122 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,4 +1,5 @@ <<<<<<< HEAD +<<<<<<< HEAD import Debug from 'debug' import { Application, Request, Response } from 'express' ======= @@ -11,12 +12,17 @@ import Debug from 'debug' import { Application, Request, Response } from 'express' import { v4 as uuidv4 } from 'uuid' >>>>>>> init courseApi +======= +import Debug from 'debug' +import { Application, Request, Response } from 'express' +>>>>>>> update detlas const debug = Debug('signalk:courseApi') const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` <<<<<<< HEAD +<<<<<<< HEAD const DELTA_INTERVAL: number = 30000 ======= @@ -24,10 +30,13 @@ const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' const API_METHODS: string[] = [] >>>>>>> init courseApi +======= +>>>>>>> update detlas interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any +<<<<<<< HEAD <<<<<<< HEAD securityStrategy: { shouldAllowPut: ( @@ -39,6 +48,9 @@ interface CourseApplication extends Application { } ======= >>>>>>> init courseApi +======= + registerPutHandler: (context:string, path:string, cb:any) => any +>>>>>>> update detlas resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -57,9 +69,12 @@ interface Destination extends DestinationBase { type?: string } <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> init courseApi +======= +>>>>>>> update detlas interface ActiveRoute extends DestinationBase { pointIndex?: number reverse?: boolean @@ -152,6 +167,7 @@ export class CourseApi { } ) +<<<<<<< HEAD <<<<<<< HEAD this.server.put( `${COURSE_API_PATH}/restart`, @@ -168,12 +184,28 @@ export class CourseApi { // set previousPoint to vessel position try { ======= +======= + // + if(this.server.registerPutHandler) { + debug('** Registering PUT Action Handler(s) **') + this.server.registerPutHandler( + 'vessels.self', + 'navigation.course.*', + this.handleCourseApiPut + ); + } + +>>>>>>> update detlas // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { + //test for active destination + if (!this.courseInfo.nextPoint.position) { + return + } // set previousPoint to vessel position >>>>>>> init courseApi const position: any = this.server.getSelfPath('navigation.position') @@ -461,6 +493,12 @@ export class CourseApi { ) } + private handleCourseApiPut(context:string, path:string, value:any, cb:any) { + + debug('** PUT handler **') + return undefined + } + private async activateRoute(route: ActiveRoute): Promise { let rte: any @@ -805,11 +843,15 @@ export class CourseApi { values.push({ <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> update detlas path: `navigation.course`, value: this.courseInfo }) values.push({ +<<<<<<< HEAD path: `${navPath[0]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) @@ -880,6 +922,8 @@ export class CourseApi { values.push({ path: `${navPath[1]}.previousPoint.type`, ======= +======= +>>>>>>> update detlas path: `${root[0]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) From 9538cc6e0f3cbf3abde343fb7b1c4d6bba96bf0c Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 4 Dec 2021 17:12:14 +1030 Subject: [PATCH 348/410] init courseApi --- src/api/course/index.ts | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 46fd69122..0df8d2994 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,5 +1,6 @@ <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD import Debug from 'debug' import { Application, Request, Response } from 'express' ======= @@ -16,6 +17,17 @@ import { v4 as uuidv4 } from 'uuid' import Debug from 'debug' import { Application, Request, Response } from 'express' >>>>>>> update detlas +======= + +import { + ResourceProvider, + ResourceProviderMethods, + SignalKResourceType +} from '@signalk/server-api' +import Debug from 'debug' +import { Application, Request, Response } from 'express' +import { v4 as uuidv4 } from 'uuid' +>>>>>>> init courseApi const debug = Debug('signalk:courseApi') @@ -33,10 +45,15 @@ const API_METHODS: string[] = [] ======= >>>>>>> update detlas +const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' + +const API_METHODS: string[] = [] + interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD securityStrategy: { shouldAllowPut: ( @@ -51,6 +68,9 @@ interface CourseApplication extends Application { ======= registerPutHandler: (context:string, path:string, cb:any) => any >>>>>>> update detlas +======= + +>>>>>>> init courseApi resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -167,6 +187,7 @@ export class CourseApi { } ) +<<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD this.server.put( @@ -196,16 +217,14 @@ export class CourseApi { } >>>>>>> update detlas +======= +>>>>>>> init courseApi // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { - //test for active destination - if (!this.courseInfo.nextPoint.position) { - return - } // set previousPoint to vessel position >>>>>>> init courseApi const position: any = this.server.getSelfPath('navigation.position') @@ -493,12 +512,6 @@ export class CourseApi { ) } - private handleCourseApiPut(context:string, path:string, value:any, cb:any) { - - debug('** PUT handler **') - return undefined - } - private async activateRoute(route: ActiveRoute): Promise { let rte: any From 98aa838327105e56d6cf58d25e962ca092966cd5 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 5 Dec 2021 10:26:47 +1030 Subject: [PATCH 349/410] update detlas --- src/api/course/index.ts | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 0df8d2994..f06dd9078 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,6 +1,7 @@ <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD import Debug from 'debug' import { Application, Request, Response } from 'express' ======= @@ -28,6 +29,10 @@ import Debug from 'debug' import { Application, Request, Response } from 'express' import { v4 as uuidv4 } from 'uuid' >>>>>>> init courseApi +======= +import Debug from 'debug' +import { Application, Request, Response } from 'express' +>>>>>>> update detlas const debug = Debug('signalk:courseApi') @@ -45,15 +50,12 @@ const API_METHODS: string[] = [] ======= >>>>>>> update detlas -const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' - -const API_METHODS: string[] = [] - interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD securityStrategy: { shouldAllowPut: ( @@ -71,6 +73,9 @@ interface CourseApplication extends Application { ======= >>>>>>> init courseApi +======= + registerPutHandler: (context:string, path:string, cb:any) => any +>>>>>>> update detlas resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -189,6 +194,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD this.server.put( `${COURSE_API_PATH}/restart`, @@ -206,6 +212,8 @@ export class CourseApi { try { ======= ======= +======= +>>>>>>> update detlas // if(this.server.registerPutHandler) { debug('** Registering PUT Action Handler(s) **') @@ -216,15 +224,22 @@ export class CourseApi { ); } +<<<<<<< HEAD >>>>>>> update detlas ======= >>>>>>> init courseApi +======= +>>>>>>> update detlas // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { + //test for active destination + if (!this.courseInfo.nextPoint.position) { + return + } // set previousPoint to vessel position >>>>>>> init courseApi const position: any = this.server.getSelfPath('navigation.position') @@ -512,6 +527,12 @@ export class CourseApi { ) } + private handleCourseApiPut(context:string, path:string, value:any, cb:any) { + + debug('** PUT handler **') + return undefined + } + private async activateRoute(route: ActiveRoute): Promise { let rte: any From 5d38536dc1ff1e159ea801051bd805d8d9d989c8 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 6 Dec 2021 08:39:56 +1030 Subject: [PATCH 350/410] enable put processing --- src/api/course/index.ts | 134 +++++++++++++++++++++++++++++++++++++--- src/put.js | 6 ++ 2 files changed, 131 insertions(+), 9 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index f06dd9078..a5427bac1 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -56,15 +56,22 @@ interface CourseApplication extends Application { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD securityStrategy: { shouldAllowPut: ( req: Application, +======= + securityStrategy: { + shouldAllowPut: ( + req: any, +>>>>>>> enable put processing context: string, source: any, path: string ) => boolean } +<<<<<<< HEAD ======= >>>>>>> init courseApi ======= @@ -76,6 +83,8 @@ interface CourseApplication extends Application { ======= registerPutHandler: (context:string, path:string, cb:any) => any >>>>>>> update detlas +======= +>>>>>>> enable put processing resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -182,6 +191,15 @@ export class CourseApi { >>>>>>> init courseApi } + private updateAllowed(): boolean { + return this.server.securityStrategy.shouldAllowPut( + this.server, + 'vessels.self', + null, + 'resources' + ) + } + private initResourceRoutes() { // return current course information this.server.get( @@ -196,12 +214,19 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> enable put processing this.server.put( `${COURSE_API_PATH}/restart`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/restart`) if (!this.updateAllowed()) { +<<<<<<< HEAD res.status(403).send('Unauthorised') +======= + res.status(403) +>>>>>>> enable put processing return } if (!this.courseInfo.nextPoint.position) { @@ -209,6 +234,7 @@ export class CourseApi { return } // set previousPoint to vessel position +<<<<<<< HEAD try { ======= ======= @@ -231,9 +257,23 @@ export class CourseApi { ======= >>>>>>> update detlas // restart / arrivalCircle +======= + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.emitCourseInfo() + res.status(200) + } else { + res.status(406).send(`Vessel position unavailable!`) + } + } + ) + +>>>>>>> enable put processing this.server.put( - `${COURSE_API_PATH}/:action`, + `${COURSE_API_PATH}/arrivalCircle`, async (req: Request, res: Response) => { +<<<<<<< HEAD debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { //test for active destination @@ -283,6 +323,18 @@ export class CourseApi { res.status(406).send(`Invalid Data`) } >>>>>>> init courseApi +======= + debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) + if (!this.updateAllowed()) { + res.status(403) + return + } + if (this.setArrivalCircle(req.body.value)) { + this.emitCourseInfo() + res.status(200) + } else { + res.status(406).send(`Invalid Data`) +>>>>>>> enable put processing } } ) @@ -292,6 +344,7 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) +<<<<<<< HEAD <<<<<<< HEAD if (!this.updateAllowed()) { res.status(403).send('Unauthorised') @@ -300,6 +353,12 @@ export class CourseApi { ======= >>>>>>> init courseApi +======= + if (!this.updateAllowed()) { + res.status(403) + return + } +>>>>>>> enable put processing if (!req.body.value) { res.status(406).send(`Invalid Data`) return @@ -307,11 +366,15 @@ export class CourseApi { const result = await this.setDestination(req.body.value) if (result) { this.emitCourseInfo() +<<<<<<< HEAD <<<<<<< HEAD res.status(200).send('OK') ======= res.status(200).send(`Destination set successfully.`) >>>>>>> init courseApi +======= + res.status(200) +>>>>>>> enable put processing } else { this.clearDestination() this.emitCourseInfo() @@ -325,6 +388,7 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) +<<<<<<< HEAD <<<<<<< HEAD if (!this.updateAllowed()) { res.status(403).send('Unauthorised') @@ -338,18 +402,29 @@ export class CourseApi { // set activeRoute ======= +======= + if (!this.updateAllowed()) { + res.status(403) + return + } +>>>>>>> enable put processing this.clearDestination() this.emitCourseInfo() - res.status(200).send(`Destination cleared.`) + res.status(200) } ) +<<<<<<< HEAD // set / clear activeRoute >>>>>>> init courseApi +======= + // set activeRoute +>>>>>>> enable put processing this.server.put( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) +<<<<<<< HEAD <<<<<<< HEAD if (!this.updateAllowed()) { res.status(403).send('Unauthorised') @@ -366,6 +441,16 @@ export class CourseApi { this.emitCourseInfo() res.status(200).send(`Active route set.`) >>>>>>> init courseApi +======= + if (!this.updateAllowed()) { + res.status(403) + return + } + const result = await this.activateRoute(req.body.value) + if (result) { + this.emitCourseInfo() + res.status(200) +>>>>>>> enable put processing } else { this.clearDestination() this.emitCourseInfo() @@ -373,20 +458,29 @@ export class CourseApi { } } ) +<<<<<<< HEAD <<<<<<< HEAD // clear activeRoute /destination ======= >>>>>>> init courseApi +======= + + // clear activeRoute /destination +>>>>>>> enable put processing this.server.delete( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/activeRoute`) <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> enable put processing if (!this.updateAllowed()) { res.status(403) return } +<<<<<<< HEAD this.clearDestination() this.emitCourseInfo() res.status(200).send('OK') @@ -396,6 +490,11 @@ export class CourseApi { this.emitCourseInfo() res.status(200).send(`Active route cleared.`) >>>>>>> init courseApi +======= + this.clearDestination() + this.emitCourseInfo() + res.status(200) +>>>>>>> enable put processing } ) @@ -403,6 +502,7 @@ export class CourseApi { `${COURSE_API_PATH}/activeRoute/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) +<<<<<<< HEAD <<<<<<< HEAD if (!this.updateAllowed()) { res.status(403).send('Unauthorised') @@ -412,25 +512,39 @@ export class CourseApi { ======= >>>>>>> init courseApi +======= + if (!this.updateAllowed()) { + res.status(403) + return + } + // fetch route data +>>>>>>> enable put processing if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return } <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> init courseApi +======= +>>>>>>> enable put processing const rte = await this.getRoute(this.courseInfo.activeRoute.href) if (!rte) { res.status(406).send(`Invalid Data`) return } +<<<<<<< HEAD <<<<<<< HEAD if (req.params.action === 'nextPoint') { ======= if (req.params.nextPoint) { >>>>>>> init courseApi +======= + if (req.params.action === 'nextPoint') { +>>>>>>> enable put processing if ( typeof req.body.value === 'number' && (req.body.value === 1 || req.body.value === -1) @@ -449,8 +563,12 @@ export class CourseApi { ======= } } +<<<<<<< HEAD if (req.params.pointIndex) { >>>>>>> init courseApi +======= + if (req.params.action === 'pointIndex') { +>>>>>>> enable put processing if (typeof req.body.value === 'number') { this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( req.body.value, @@ -472,7 +590,7 @@ export class CourseApi { } } - // set nextPoint + // set new destination this.courseInfo.nextPoint.position = this.getRoutePoint( rte, this.courseInfo.activeRoute.pointIndex @@ -521,18 +639,16 @@ export class CourseApi { res.status(200).send('OK') ======= +<<<<<<< HEAD res.status(200).send(`OK`) >>>>>>> init courseApi +======= + res.status(200) +>>>>>>> enable put processing } ) } - private handleCourseApiPut(context:string, path:string, value:any, cb:any) { - - debug('** PUT handler **') - return undefined - } - private async activateRoute(route: ActiveRoute): Promise { let rte: any diff --git a/src/put.js b/src/put.js index cf137b3a2..5498f51b0 100644 --- a/src/put.js +++ b/src/put.js @@ -39,6 +39,7 @@ module.exports = { return } <<<<<<< HEAD +<<<<<<< HEAD ======= if(req.path.split('/')[4]==='resources') { next() @@ -52,6 +53,8 @@ module.exports = { } >>>>>>> chore: linted ======= +======= +>>>>>>> enable put processing // ** ignore course paths ** if (req.path.indexOf('/navigation/course') !== -1) { @@ -59,6 +62,9 @@ module.exports = { return } +<<<<<<< HEAD +>>>>>>> enable put processing +======= >>>>>>> enable put processing let path = String(req.path).replace(apiPathPrefix, '') From a204cee62e96c6413968a11854b9ea31425599e1 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 6 Dec 2021 13:34:31 +1030 Subject: [PATCH 351/410] chore: lint --- src/api/course/index.ts | 229 ++++++++++++++++++++++++++++-------- src/api/course/openApi.json | 12 ++ 2 files changed, 192 insertions(+), 49 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index a5427bac1..8a31249b4 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -64,8 +64,12 @@ interface CourseApplication extends Application { ======= securityStrategy: { shouldAllowPut: ( +<<<<<<< HEAD req: any, >>>>>>> enable put processing +======= + req: Application, +>>>>>>> chore: lint context: string, source: any, path: string @@ -196,7 +200,7 @@ export class CourseApi { this.server, 'vessels.self', null, - 'resources' + 'navigation.course' ) } @@ -234,6 +238,7 @@ export class CourseApi { return } // set previousPoint to vessel position +<<<<<<< HEAD <<<<<<< HEAD try { ======= @@ -264,6 +269,16 @@ export class CourseApi { this.emitCourseInfo() res.status(200) } else { +======= + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.emitCourseInfo() + res.status(200) + } + } catch (err) { +>>>>>>> chore: lint res.status(406).send(`Vessel position unavailable!`) } } @@ -329,7 +344,8 @@ export class CourseApi { res.status(403) return } - if (this.setArrivalCircle(req.body.value)) { + if (this.isValidArrivalCircle(req.body.value)) { + this.courseInfo.nextPoint.arrivalCircle = req.body.value this.emitCourseInfo() res.status(200) } else { @@ -517,8 +533,12 @@ export class CourseApi { res.status(403) return } +<<<<<<< HEAD // fetch route data >>>>>>> enable put processing +======= + // fetch active route data +>>>>>>> chore: lint if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return @@ -593,8 +613,13 @@ export class CourseApi { // set new destination this.courseInfo.nextPoint.position = this.getRoutePoint( rte, +<<<<<<< HEAD this.courseInfo.activeRoute.pointIndex >>>>>>> init courseApi +======= + this.courseInfo.activeRoute.pointIndex, + this.courseInfo.activeRoute.reverse +>>>>>>> chore: lint ) this.courseInfo.nextPoint.type = `RoutePoint` this.courseInfo.nextPoint.href = null @@ -602,12 +627,16 @@ export class CourseApi { // set previousPoint if (this.courseInfo.activeRoute.pointIndex === 0) { <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> chore: lint try { const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.courseInfo.previousPoint.type = `VesselPosition` } else { +<<<<<<< HEAD res.status(406).send(`Invalid Data`) return false } @@ -620,17 +649,27 @@ export class CourseApi { this.courseInfo.previousPoint.type = `VesselPosition` } else { >>>>>>> init courseApi +======= + return false + } + } catch (err) { +>>>>>>> chore: lint return false } } else { this.courseInfo.previousPoint.position = this.getRoutePoint( rte, +<<<<<<< HEAD <<<<<<< HEAD this.courseInfo.activeRoute.pointIndex - 1, this.courseInfo.activeRoute.reverse ======= this.courseInfo.activeRoute.pointIndex - 1 >>>>>>> init courseApi +======= + this.courseInfo.activeRoute.pointIndex - 1, + this.courseInfo.activeRoute.reverse +>>>>>>> chore: lint ) this.courseInfo.previousPoint.type = `RoutePoint` } @@ -664,6 +703,7 @@ export class CourseApi { return false } +<<<<<<< HEAD <<<<<<< HEAD const newCourse: any = {} Object.assign(newCourse, this.courseInfo) @@ -740,71 +780,91 @@ export class CourseApi { if (href) { // fetch waypoint resource details ======= +======= + const newDest: any = {} + Object.assign(newDest, this.courseInfo) + +>>>>>>> chore: lint // set activeroute - this.courseInfo.activeRoute.href = route.href + newDest.activeRoute.href = route.href - if (typeof route.arrivalCircle === 'number') { - this.setArrivalCircle(route.arrivalCircle) + if (this.isValidArrivalCircle(route.arrivalCircle as number)) { + newDest.nextPoint.arrivalCircle = route.arrivalCircle } - this.courseInfo.activeRoute.reverse = - typeof route.reverse === 'boolean' ? route.reverse : false + newDest.activeRoute.startTime = new Date().toISOString() - this.courseInfo.activeRoute.pointIndex = - typeof route.pointIndex === 'number' - ? this.parsePointIndex(route.pointIndex, rte) - : 0 + if (typeof route.reverse === 'boolean') { + newDest.activeRoute.reverse = route.reverse + } - this.courseInfo.activeRoute.startTime = new Date().toISOString() + newDest.activeRoute.pointIndex = this.parsePointIndex( + route.pointIndex as number, + rte + ) // set nextPoint - this.courseInfo.nextPoint.position = this.getRoutePoint( + newDest.nextPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex + newDest.activeRoute.pointIndex, + newDest.activeRoute.reverse ) - this.courseInfo.nextPoint.type = `RoutePoint` - this.courseInfo.nextPoint.href = null + newDest.nextPoint.type = `RoutePoint` + newDest.nextPoint.href = null // set previousPoint - if (this.courseInfo.activeRoute.pointIndex === 0) { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + if (newDest.activeRoute.pointIndex === 0) { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } catch (err) { return false } } else { - this.courseInfo.previousPoint.position = this.getRoutePoint( + newDest.previousPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex - 1 + newDest.activeRoute.pointIndex - 1, + newDest.activeRoute.reverse ) - this.courseInfo.previousPoint.type = `RoutePoint` + newDest.previousPoint.type = `RoutePoint` } - this.courseInfo.previousPoint.href = null + newDest.previousPoint.href = null + this.courseInfo = newDest return true } - private async setDestination(dest: Destination): Promise { + private async setDestination(dest: any): Promise { + const newDest: any = {} + Object.assign(newDest, this.courseInfo) + // set nextPoint - if (typeof dest.arrivalCircle === 'number') { - this.setArrivalCircle(dest.arrivalCircle) + if (this.isValidArrivalCircle(dest.arrivalCircle)) { + newDest.nextPoint.arrivalCircle = dest.arrivalCircle } - this.courseInfo.nextPoint.type = - typeof dest.type !== 'undefined' ? dest.type : null + newDest.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - this.courseInfo.nextPoint.href = dest.href + newDest.href = dest.href const href = this.parseHref(dest.href) if (href) { +<<<<<<< HEAD >>>>>>> init courseApi +======= + // fetch waypoint resource details +>>>>>>> chore: lint try { const r = await this.server.resourcesApi.getResource( href.type, href.id ) +<<<<<<< HEAD <<<<<<< HEAD if (r.position && typeof r.position?.latitude !== 'undefined') { newCourse.nextPoint.position = r.position @@ -814,12 +874,19 @@ export class CourseApi { if (r.position && typeof r.position.value?.latitude !== 'undefined') { this.courseInfo.nextPoint.position = r.position.value >>>>>>> init courseApi +======= + if (r.position && typeof r.position?.latitude !== 'undefined') { + newDest.nextPoint.position = r.position + } else { + return false +>>>>>>> chore: lint } } catch (err) { return false } } } else if (dest.position) { +<<<<<<< HEAD <<<<<<< HEAD newCourse.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { @@ -829,6 +896,11 @@ export class CourseApi { if (typeof dest.position.latitude !== 'undefined') { this.courseInfo.nextPoint.position = dest.position >>>>>>> init courseApi +======= + newDest.nextPoint.href = null + if (typeof dest.position.latitude !== 'undefined') { + newDest.nextPoint.position = dest.position +>>>>>>> chore: lint } else { return false } @@ -837,6 +909,7 @@ export class CourseApi { } // set previousPoint +<<<<<<< HEAD <<<<<<< HEAD try { const position: any = this.server.getSelfPath('navigation.position') @@ -858,11 +931,26 @@ export class CourseApi { this.courseInfo.previousPoint.position = position.value this.courseInfo.previousPoint.type = `VesselPosition` } else { +======= + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + newDest.previousPoint.position = position.value + newDest.previousPoint.type = `VesselPosition` + } else { + return false + } + newDest.previousPoint.href = null + } catch (err) { +>>>>>>> chore: lint return false } - this.courseInfo.previousPoint.href = null +<<<<<<< HEAD >>>>>>> init courseApi +======= + this.courseInfo = newDest +>>>>>>> chore: lint return true } @@ -879,6 +967,7 @@ export class CourseApi { this.courseInfo.previousPoint.position = null } +<<<<<<< HEAD <<<<<<< HEAD private isValidArrivalCircle(value: number): boolean { return typeof value === 'number' && value >= 0 @@ -899,6 +988,14 @@ export class CourseApi { private parsePointIndex(index: number, rte: any): number { if (!rte) { >>>>>>> init courseApi +======= + private isValidArrivalCircle(value: number): boolean { + return typeof value === 'number' && value >= 0 + } + + private parsePointIndex(index: number, rte: any): number { + if (typeof index !== 'number' || !rte) { +>>>>>>> chore: lint return 0 } if (!rte.feature?.geometry?.coordinates) { @@ -917,6 +1014,7 @@ export class CourseApi { } private parseHref(href: string): { type: string; id: string } | undefined { +<<<<<<< HEAD <<<<<<< HEAD if (!href) { return undefined @@ -932,6 +1030,13 @@ export class CourseApi { } const ref: string[] = href.split('/') >>>>>>> init courseApi +======= + if (!href) { + return undefined + } + + const ref: string[] = href.split('/').slice(-3) +>>>>>>> chore: lint if (ref.length < 3) { return undefined } @@ -944,6 +1049,7 @@ export class CourseApi { } } +<<<<<<< HEAD <<<<<<< HEAD private getRoutePoint(rte: any, index: number, reverse: boolean) { const pos = reverse @@ -951,6 +1057,10 @@ export class CourseApi { private getRoutePoint(rte: any, index: number) { const pos = this.courseInfo.activeRoute.reverse >>>>>>> init courseApi +======= + private getRoutePoint(rte: any, index: number, reverse: boolean) { + const pos = reverse +>>>>>>> chore: lint ? rte.feature.geometry.coordinates[ rte.feature.geometry.coordinates.length - (index + 1) ] @@ -958,11 +1068,15 @@ export class CourseApi { return { latitude: pos[1], longitude: pos[0], +<<<<<<< HEAD <<<<<<< HEAD altitude: pos.length === 3 ? pos[2] : 0 ======= altitude: pos.length == 3 ? pos[2] : 0 >>>>>>> init courseApi +======= + altitude: pos.length === 3 ? pos[2] : 0 +>>>>>>> chore: lint } } @@ -980,6 +1094,7 @@ export class CourseApi { } private buildDeltaMsg(): any { +<<<<<<< HEAD <<<<<<< HEAD const values: Array<{ path: string; value: any }> = [] const navPath = [ @@ -987,6 +1102,10 @@ export class CourseApi { let values: Array<{path:string, value:any}> = [] let root = [ >>>>>>> init courseApi +======= + const values: Array<{ path: string; value: any }> = [] + const navPath = [ +>>>>>>> chore: lint 'navigation.courseGreatCircle', 'navigation.courseRhumbline' ] @@ -1001,6 +1120,7 @@ export class CourseApi { }) values.push({ +<<<<<<< HEAD <<<<<<< HEAD path: `${navPath[0]}.activeRoute.href`, value: this.courseInfo.activeRoute.href @@ -1075,86 +1195,97 @@ export class CourseApi { ======= >>>>>>> update detlas path: `${root[0]}.activeRoute.href`, +======= + path: `${navPath[0]}.activeRoute.href`, +>>>>>>> chore: lint value: this.courseInfo.activeRoute.href }) values.push({ - path: `${root[1]}.activeRoute.href`, + path: `${navPath[1]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${root[0]}.activeRoute.startTime`, + path: `${navPath[0]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${root[1]}.activeRoute.startTime`, + path: `${navPath[1]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${root[0]}.nextPoint.href`, + path: `${navPath[0]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${root[1]}.nextPoint.href`, + path: `${navPath[1]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${root[0]}.nextPoint.position`, + path: `${navPath[0]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${root[1]}.nextPoint.position`, + path: `${navPath[1]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${root[0]}.nextPoint.type`, + path: `${navPath[0]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${root[1]}.nextPoint.type`, + path: `${navPath[1]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${root[0]}.nextPoint.arrivalCircle`, + path: `${navPath[0]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${root[1]}.nextPoint.arrivalCircle`, + path: `${navPath[1]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${root[0]}.previousPoint.href`, + path: `${navPath[0]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${root[1]}.previousPoint.href`, + path: `${navPath[1]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${root[0]}.previousPoint.position`, + path: `${navPath[0]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${root[1]}.previousPoint.position`, + path: `${navPath[1]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${root[0]}.previousPoint.type`, + path: `${navPath[0]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) values.push({ +<<<<<<< HEAD path: `${root[1]}.previousPoint.type`, >>>>>>> init courseApi +======= + path: `${navPath[1]}.previousPoint.type`, +>>>>>>> chore: lint value: this.courseInfo.previousPoint.type }) return { updates: [ { +<<<<<<< HEAD <<<<<<< HEAD values ======= values: values >>>>>>> init courseApi +======= + values +>>>>>>> chore: lint } ] } diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index 16b5a8d29..5db5fb426 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -108,6 +108,7 @@ "put": { "tags": ["course"], "summary": "Restart course calculations", +<<<<<<< HEAD <<<<<<< HEAD "description": "Sets previousPoint value to current vessel location" ======= @@ -135,6 +136,9 @@ } } >>>>>>> init courseApi +======= + "description": "Sets previousPoint value to current vessel location" +>>>>>>> chore: lint } }, @@ -204,6 +208,7 @@ "delete": { "tags": ["course/destination"], "summary": "Clear destination", +<<<<<<< HEAD <<<<<<< HEAD "description": "Sets activeRoute, nextPoint & previousPoint values to null" ======= @@ -231,6 +236,9 @@ } } >>>>>>> init courseApi +======= + "description": "Sets activeRoute, nextPoint & previousPoint values to null" +>>>>>>> chore: lint } }, @@ -321,6 +329,7 @@ "delete": { "tags": ["course/activeRoute"], "summary": "Clear active route", +<<<<<<< HEAD <<<<<<< HEAD "description": "Sets activeRoute, nextPoint & previousPoint values to null" ======= @@ -348,6 +357,9 @@ } } >>>>>>> init courseApi +======= + "description": "Sets activeRoute, nextPoint & previousPoint values to null" +>>>>>>> chore: lint } }, From f0877a6c101a17e137de3075193863c3d89aa5f4 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 8 Dec 2021 09:27:06 +1030 Subject: [PATCH 352/410] add 30sec delta interval --- src/api/course/index.ts | 127 +++++++++++++++++++++++++++++----------- 1 file changed, 93 insertions(+), 34 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 8a31249b4..1e81cac38 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -50,6 +50,8 @@ const API_METHODS: string[] = [] ======= >>>>>>> update detlas +const DELTA_INTERVAL: number = 30000 + interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any @@ -176,6 +178,7 @@ export class CourseApi { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app this.initResourceRoutes() +<<<<<<< HEAD <<<<<<< HEAD setInterval(() => { if (this.courseInfo.nextPoint.position) { @@ -193,6 +196,13 @@ export class CourseApi { ) ======= >>>>>>> init courseApi +======= + setInterval( ()=> { + if(this.courseInfo.nextPoint.position) { + this.emitCourseInfo() + } + }, DELTA_INTERVAL) +>>>>>>> add 30sec delta interval } private updateAllowed(): boolean { @@ -226,11 +236,15 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/restart`) if (!this.updateAllowed()) { +<<<<<<< HEAD <<<<<<< HEAD res.status(403).send('Unauthorised') ======= res.status(403) >>>>>>> enable put processing +======= + res.status(403).send('Unauthorised') +>>>>>>> add 30sec delta interval return } if (!this.courseInfo.nextPoint.position) { @@ -275,7 +289,7 @@ export class CourseApi { if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } } catch (err) { >>>>>>> chore: lint @@ -341,13 +355,13 @@ export class CourseApi { ======= debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (this.isValidArrivalCircle(req.body.value)) { this.courseInfo.nextPoint.arrivalCircle = req.body.value this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { res.status(406).send(`Invalid Data`) >>>>>>> enable put processing @@ -371,7 +385,7 @@ export class CourseApi { >>>>>>> init courseApi ======= if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } >>>>>>> enable put processing @@ -383,6 +397,7 @@ export class CourseApi { if (result) { this.emitCourseInfo() <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD res.status(200).send('OK') ======= @@ -391,6 +406,9 @@ export class CourseApi { ======= res.status(200) >>>>>>> enable put processing +======= + res.status(200).send('OK') +>>>>>>> add 30sec delta interval } else { this.clearDestination() this.emitCourseInfo() @@ -420,13 +438,13 @@ export class CourseApi { ======= ======= if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } >>>>>>> enable put processing this.clearDestination() this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } ) @@ -459,14 +477,18 @@ export class CourseApi { >>>>>>> init courseApi ======= if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } const result = await this.activateRoute(req.body.value) if (result) { this.emitCourseInfo() +<<<<<<< HEAD res.status(200) >>>>>>> enable put processing +======= + res.status(200).send('OK') +>>>>>>> add 30sec delta interval } else { this.clearDestination() this.emitCourseInfo() @@ -509,8 +531,12 @@ export class CourseApi { ======= this.clearDestination() this.emitCourseInfo() +<<<<<<< HEAD res.status(200) >>>>>>> enable put processing +======= + res.status(200).send('OK') +>>>>>>> add 30sec delta interval } ) @@ -530,7 +556,7 @@ export class CourseApi { >>>>>>> init courseApi ======= if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } <<<<<<< HEAD @@ -576,6 +602,9 @@ export class CourseApi { } else { res.status(406).send(`Invalid Data`) <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> add 30sec delta interval return } } @@ -597,6 +626,9 @@ export class CourseApi { } else { res.status(406).send(`Invalid Data`) <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> add 30sec delta interval return } } @@ -636,6 +668,7 @@ export class CourseApi { this.courseInfo.previousPoint.position = position.value this.courseInfo.previousPoint.type = `VesselPosition` } else { +<<<<<<< HEAD <<<<<<< HEAD res.status(406).send(`Invalid Data`) return false @@ -654,6 +687,13 @@ export class CourseApi { } } catch (err) { >>>>>>> chore: lint +======= + res.status(406).send(`Invalid Data`) + return false + } + } catch (err) { + res.status(406).send(`Invalid Data`) +>>>>>>> add 30sec delta interval return false } } else { @@ -674,6 +714,7 @@ export class CourseApi { this.courseInfo.previousPoint.type = `RoutePoint` } this.courseInfo.previousPoint.href = null +<<<<<<< HEAD <<<<<<< HEAD res.status(200).send('OK') ======= @@ -684,6 +725,9 @@ export class CourseApi { ======= res.status(200) >>>>>>> enable put processing +======= + res.status(200).send('OK') +>>>>>>> add 30sec delta interval } ) } @@ -703,6 +747,7 @@ export class CourseApi { return false } +<<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD const newCourse: any = {} @@ -783,37 +828,41 @@ export class CourseApi { ======= const newDest: any = {} Object.assign(newDest, this.courseInfo) +======= + const newCourse: any = {} + Object.assign(newCourse, this.courseInfo) +>>>>>>> add 30sec delta interval >>>>>>> chore: lint // set activeroute - newDest.activeRoute.href = route.href + newCourse.activeRoute.href = route.href if (this.isValidArrivalCircle(route.arrivalCircle as number)) { - newDest.nextPoint.arrivalCircle = route.arrivalCircle + newCourse.nextPoint.arrivalCircle = route.arrivalCircle } - newDest.activeRoute.startTime = new Date().toISOString() + newCourse.activeRoute.startTime = new Date().toISOString() if (typeof route.reverse === 'boolean') { - newDest.activeRoute.reverse = route.reverse + newCourse.activeRoute.reverse = route.reverse } - newDest.activeRoute.pointIndex = this.parsePointIndex( + newCourse.activeRoute.pointIndex = this.parsePointIndex( route.pointIndex as number, rte ) // set nextPoint - newDest.nextPoint.position = this.getRoutePoint( + newCourse.nextPoint.position = this.getRoutePoint( rte, - newDest.activeRoute.pointIndex, - newDest.activeRoute.reverse + newCourse.activeRoute.pointIndex, + newCourse.activeRoute.reverse ) - newDest.nextPoint.type = `RoutePoint` - newDest.nextPoint.href = null + newCourse.nextPoint.type = `RoutePoint` + newCourse.nextPoint.href = null // set previousPoint - if (newDest.activeRoute.pointIndex === 0) { + if (newCourse.activeRoute.pointIndex === 0) { try { const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -826,32 +875,32 @@ export class CourseApi { return false } } else { - newDest.previousPoint.position = this.getRoutePoint( + newCourse.previousPoint.position = this.getRoutePoint( rte, - newDest.activeRoute.pointIndex - 1, - newDest.activeRoute.reverse + newCourse.activeRoute.pointIndex - 1, + newCourse.activeRoute.reverse ) - newDest.previousPoint.type = `RoutePoint` + newCourse.previousPoint.type = `RoutePoint` } - newDest.previousPoint.href = null + newCourse.previousPoint.href = null - this.courseInfo = newDest + this.courseInfo = newCourse return true } private async setDestination(dest: any): Promise { - const newDest: any = {} - Object.assign(newDest, this.courseInfo) + const newCourse: any = {} + Object.assign(newCourse, this.courseInfo) // set nextPoint if (this.isValidArrivalCircle(dest.arrivalCircle)) { - newDest.nextPoint.arrivalCircle = dest.arrivalCircle + newCourse.nextPoint.arrivalCircle = dest.arrivalCircle } - newDest.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null + newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - newDest.href = dest.href + newCourse.href = dest.href const href = this.parseHref(dest.href) if (href) { <<<<<<< HEAD @@ -876,7 +925,7 @@ export class CourseApi { >>>>>>> init courseApi ======= if (r.position && typeof r.position?.latitude !== 'undefined') { - newDest.nextPoint.position = r.position + newCourse.nextPoint.position = r.position } else { return false >>>>>>> chore: lint @@ -887,6 +936,7 @@ export class CourseApi { } } else if (dest.position) { <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD newCourse.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { @@ -901,6 +951,11 @@ export class CourseApi { if (typeof dest.position.latitude !== 'undefined') { newDest.nextPoint.position = dest.position >>>>>>> chore: lint +======= + newCourse.nextPoint.href = null + if (typeof dest.position.latitude !== 'undefined') { + newCourse.nextPoint.position = dest.position +>>>>>>> add 30sec delta interval } else { return false } @@ -935,22 +990,26 @@ export class CourseApi { try { const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { - newDest.previousPoint.position = position.value - newDest.previousPoint.type = `VesselPosition` + newCourse.previousPoint.position = position.value + newCourse.previousPoint.type = `VesselPosition` } else { return false } - newDest.previousPoint.href = null + newCourse.previousPoint.href = null } catch (err) { >>>>>>> chore: lint return false } +<<<<<<< HEAD <<<<<<< HEAD >>>>>>> init courseApi ======= this.courseInfo = newDest >>>>>>> chore: lint +======= + this.courseInfo = newCourse +>>>>>>> add 30sec delta interval return true } From 33e5e91f73682c7d6fb6d10309123bf44aaa3166 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 12 Dec 2021 14:29:38 +1030 Subject: [PATCH 353/410] chore: lint --- src/api/course/index.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 1e81cac38..ea9eb4471 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -50,7 +50,7 @@ const API_METHODS: string[] = [] ======= >>>>>>> update detlas -const DELTA_INTERVAL: number = 30000 +const DELTA_INTERVAL: number = 30000 interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void @@ -179,6 +179,7 @@ export class CourseApi { this.server = app this.initResourceRoutes() <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD setInterval(() => { if (this.courseInfo.nextPoint.position) { @@ -199,6 +200,10 @@ export class CourseApi { ======= setInterval( ()=> { if(this.courseInfo.nextPoint.position) { +======= + setInterval(() => { + if (this.courseInfo.nextPoint.position) { +>>>>>>> chore: lint this.emitCourseInfo() } }, DELTA_INTERVAL) @@ -897,7 +902,8 @@ export class CourseApi { newCourse.nextPoint.arrivalCircle = dest.arrivalCircle } - newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null + newCourse.nextPoint.type = + typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { newCourse.href = dest.href From 2ec2dda33118c6824a3ffb01eaa0bfbfb093a370 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 4 Dec 2021 17:12:14 +1030 Subject: [PATCH 354/410] init courseApi --- src/api/course/index.ts | 304 +++++++++++++++++++++++++++--------- src/api/course/openApi.json | 18 +++ 2 files changed, 249 insertions(+), 73 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index ea9eb4471..ad46cb1c1 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -2,6 +2,7 @@ <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD import Debug from 'debug' import { Application, Request, Response } from 'express' ======= @@ -33,6 +34,16 @@ import { v4 as uuidv4 } from 'uuid' import Debug from 'debug' import { Application, Request, Response } from 'express' >>>>>>> update detlas +======= +import { + ResourceProvider, + ResourceProviderMethods, + SignalKResourceType +} from '@signalk/server-api' +import Debug from 'debug' +import { Application, Request, Response } from 'express' +import { v4 as uuidv4 } from 'uuid' +>>>>>>> init courseApi const debug = Debug('signalk:courseApi') @@ -40,6 +51,7 @@ const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD const DELTA_INTERVAL: number = 30000 ======= @@ -49,8 +61,11 @@ const API_METHODS: string[] = [] >>>>>>> init courseApi ======= >>>>>>> update detlas +======= +const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' +>>>>>>> init courseApi -const DELTA_INTERVAL: number = 30000 +const API_METHODS: string[] = [] interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void @@ -59,6 +74,7 @@ interface CourseApplication extends Application { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD securityStrategy: { shouldAllowPut: ( @@ -91,6 +107,8 @@ interface CourseApplication extends Application { >>>>>>> update detlas ======= >>>>>>> enable put processing +======= +>>>>>>> init courseApi resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -180,6 +198,7 @@ export class CourseApi { this.initResourceRoutes() <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD setInterval(() => { if (this.courseInfo.nextPoint.position) { @@ -217,6 +236,8 @@ export class CourseApi { null, 'navigation.course' ) +======= +>>>>>>> init courseApi } private initResourceRoutes() { @@ -234,11 +255,16 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> enable put processing +======= + // restart / arrivalCircle +>>>>>>> init courseApi this.server.put( - `${COURSE_API_PATH}/restart`, + `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { +<<<<<<< HEAD debug(`** PUT ${COURSE_API_PATH}/restart`) if (!this.updateAllowed()) { <<<<<<< HEAD @@ -315,28 +341,27 @@ export class CourseApi { return } // set previousPoint to vessel position +>>>>>>> init courseApi +======= + debug(`** PUT ${COURSE_API_PATH}/:action`) + if (req.params.restart) { + // set previousPoint to vessel position >>>>>>> init courseApi const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.emitCourseInfo() +<<<<<<< HEAD <<<<<<< HEAD res.status(200).send('OK') +======= + res.status(200).send(`Course restarted.`) + } else { + res.status(406).send(`Vessel position unavailable!`) +>>>>>>> init courseApi } - } catch (err) { - res.status(406).send(`Vessel position unavailable!`) - } - } - ) - - this.server.put( - `${COURSE_API_PATH}/arrivalCircle`, - async (req: Request, res: Response) => { - debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return } +<<<<<<< HEAD if (this.isValidArrivalCircle(req.body.value)) { this.courseInfo.nextPoint.arrivalCircle = req.body.value this.emitCourseInfo() @@ -349,6 +374,8 @@ export class CourseApi { res.status(406).send(`Vessel position unavailable!`) } } +======= +>>>>>>> init courseApi if (req.params.arrivalCircle) { if (this.setArrivalCircle(req.params.arrivalCircle)) { this.emitCourseInfo() @@ -356,6 +383,7 @@ export class CourseApi { } else { res.status(406).send(`Invalid Data`) } +<<<<<<< HEAD >>>>>>> init courseApi ======= debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) @@ -370,6 +398,8 @@ export class CourseApi { } else { res.status(406).send(`Invalid Data`) >>>>>>> enable put processing +======= +>>>>>>> init courseApi } } ) @@ -380,6 +410,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (!this.updateAllowed()) { res.status(403).send('Unauthorised') @@ -394,6 +425,8 @@ export class CourseApi { return } >>>>>>> enable put processing +======= +>>>>>>> init courseApi if (!req.body.value) { res.status(406).send(`Invalid Data`) return @@ -403,6 +436,7 @@ export class CourseApi { this.emitCourseInfo() <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD res.status(200).send('OK') ======= @@ -414,6 +448,9 @@ export class CourseApi { ======= res.status(200).send('OK') >>>>>>> add 30sec delta interval +======= + res.status(200).send(`Destination set successfully.`) +>>>>>>> init courseApi } else { this.clearDestination() this.emitCourseInfo() @@ -428,17 +465,21 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (!this.updateAllowed()) { res.status(403).send('Unauthorised') return } +======= +>>>>>>> init courseApi this.clearDestination() this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).send(`Destination cleared.`) } ) +<<<<<<< HEAD // set activeRoute ======= ======= @@ -459,11 +500,15 @@ export class CourseApi { ======= // set activeRoute >>>>>>> enable put processing +======= + // set / clear activeRoute +>>>>>>> init courseApi this.server.put( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (!this.updateAllowed()) { res.status(403).send('Unauthorised') @@ -494,6 +539,13 @@ export class CourseApi { ======= res.status(200).send('OK') >>>>>>> add 30sec delta interval +======= + + const result = await this.activateRoute(req.body.value) + if (result) { + this.emitCourseInfo() + res.status(200).send(`Active route set.`) +>>>>>>> init courseApi } else { this.clearDestination() this.emitCourseInfo() @@ -502,6 +554,7 @@ export class CourseApi { } ) <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD // clear activeRoute /destination @@ -511,12 +564,15 @@ export class CourseApi { // clear activeRoute /destination >>>>>>> enable put processing +======= +>>>>>>> init courseApi this.server.delete( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/activeRoute`) <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> enable put processing if (!this.updateAllowed()) { @@ -542,6 +598,12 @@ export class CourseApi { ======= res.status(200).send('OK') >>>>>>> add 30sec delta interval +======= + + this.clearDestination() + this.emitCourseInfo() + res.status(200).send(`Active route cleared.`) +>>>>>>> init courseApi } ) @@ -550,6 +612,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (!this.updateAllowed()) { res.status(403).send('Unauthorised') @@ -570,6 +633,8 @@ export class CourseApi { ======= // fetch active route data >>>>>>> chore: lint +======= +>>>>>>> init courseApi if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return @@ -587,6 +652,7 @@ export class CourseApi { return } +<<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD if (req.params.action === 'nextPoint') { @@ -596,6 +662,9 @@ export class CourseApi { ======= if (req.params.action === 'nextPoint') { >>>>>>> enable put processing +======= + if (req.params.nextPoint) { +>>>>>>> init courseApi if ( typeof req.body.value === 'number' && (req.body.value === 1 || req.body.value === -1) @@ -608,6 +677,7 @@ export class CourseApi { res.status(406).send(`Invalid Data`) <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> add 30sec delta interval return @@ -623,6 +693,11 @@ export class CourseApi { ======= if (req.params.action === 'pointIndex') { >>>>>>> enable put processing +======= + } + } + if (req.params.pointIndex) { +>>>>>>> init courseApi if (typeof req.body.value === 'number') { this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( req.body.value, @@ -632,15 +707,19 @@ export class CourseApi { res.status(406).send(`Invalid Data`) <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> add 30sec delta interval return +======= +>>>>>>> init courseApi } } - // set new destination + // set nextPoint this.courseInfo.nextPoint.position = this.getRoutePoint( rte, +<<<<<<< HEAD this.courseInfo.activeRoute.pointIndex, this.courseInfo.activeRoute.reverse ======= @@ -657,6 +736,9 @@ export class CourseApi { this.courseInfo.activeRoute.pointIndex, this.courseInfo.activeRoute.reverse >>>>>>> chore: lint +======= + this.courseInfo.activeRoute.pointIndex +>>>>>>> init courseApi ) this.courseInfo.nextPoint.type = `RoutePoint` this.courseInfo.nextPoint.href = null @@ -665,6 +747,7 @@ export class CourseApi { if (this.courseInfo.activeRoute.pointIndex === 0) { <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> chore: lint try { @@ -681,11 +764,14 @@ export class CourseApi { } catch (err) { res.status(406).send(`Invalid Data`) ======= +======= +>>>>>>> init courseApi const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.courseInfo.previousPoint.type = `VesselPosition` } else { +<<<<<<< HEAD >>>>>>> init courseApi ======= return false @@ -699,12 +785,15 @@ export class CourseApi { } catch (err) { res.status(406).send(`Invalid Data`) >>>>>>> add 30sec delta interval +======= +>>>>>>> init courseApi return false } } else { this.courseInfo.previousPoint.position = this.getRoutePoint( rte, <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD this.courseInfo.activeRoute.pointIndex - 1, this.courseInfo.activeRoute.reverse @@ -715,11 +804,15 @@ export class CourseApi { this.courseInfo.activeRoute.pointIndex - 1, this.courseInfo.activeRoute.reverse >>>>>>> chore: lint +======= + this.courseInfo.activeRoute.pointIndex - 1 +>>>>>>> init courseApi ) this.courseInfo.previousPoint.type = `RoutePoint` } this.courseInfo.previousPoint.href = null <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD res.status(200).send('OK') ======= @@ -733,6 +826,10 @@ export class CourseApi { ======= res.status(200).send('OK') >>>>>>> add 30sec delta interval +======= + + res.status(200).send(`OK`) +>>>>>>> init courseApi } ) } @@ -754,80 +851,73 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD const newCourse: any = {} Object.assign(newCourse, this.courseInfo) +======= +>>>>>>> init courseApi // set activeroute - newCourse.activeRoute.href = route.href + this.courseInfo.activeRoute.href = route.href - if (this.isValidArrivalCircle(route.arrivalCircle as number)) { - newCourse.nextPoint.arrivalCircle = route.arrivalCircle + if (typeof route.arrivalCircle === 'number') { + this.setArrivalCircle(route.arrivalCircle) } - newCourse.activeRoute.startTime = new Date().toISOString() + this.courseInfo.activeRoute.reverse = + typeof route.reverse === 'boolean' ? route.reverse : false - if (typeof route.reverse === 'boolean') { - newCourse.activeRoute.reverse = route.reverse - } + this.courseInfo.activeRoute.pointIndex = + typeof route.pointIndex === 'number' + ? this.parsePointIndex(route.pointIndex, rte) + : 0 - newCourse.activeRoute.pointIndex = this.parsePointIndex( - route.pointIndex as number, - rte - ) + this.courseInfo.activeRoute.startTime = new Date().toISOString() // set nextPoint - newCourse.nextPoint.position = this.getRoutePoint( + this.courseInfo.nextPoint.position = this.getRoutePoint( rte, - newCourse.activeRoute.pointIndex, - newCourse.activeRoute.reverse + this.courseInfo.activeRoute.pointIndex ) - newCourse.nextPoint.type = `RoutePoint` - newCourse.nextPoint.href = null + this.courseInfo.nextPoint.type = `RoutePoint` + this.courseInfo.nextPoint.href = null // set previousPoint - if (newCourse.activeRoute.pointIndex === 0) { - try { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { - return false - } - } catch (err) { + if (this.courseInfo.activeRoute.pointIndex === 0) { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { return false } } else { - newCourse.previousPoint.position = this.getRoutePoint( + this.courseInfo.previousPoint.position = this.getRoutePoint( rte, - newCourse.activeRoute.pointIndex - 1, - newCourse.activeRoute.reverse + this.courseInfo.activeRoute.pointIndex - 1 ) - newCourse.previousPoint.type = `RoutePoint` + this.courseInfo.previousPoint.type = `RoutePoint` } - newCourse.previousPoint.href = null + this.courseInfo.previousPoint.href = null - this.courseInfo = newCourse return true } - private async setDestination(dest: any): Promise { - const newCourse: any = {} - Object.assign(newCourse, this.courseInfo) - + private async setDestination(dest: Destination): Promise { // set nextPoint - if (this.isValidArrivalCircle(dest.arrivalCircle)) { - newCourse.nextPoint.arrivalCircle = dest.arrivalCircle + if (typeof dest.arrivalCircle === 'number') { + this.setArrivalCircle(dest.arrivalCircle) } - newCourse.nextPoint.type = + this.courseInfo.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - newCourse.href = dest.href + this.courseInfo.nextPoint.href = dest.href const href = this.parseHref(dest.href) if (href) { +<<<<<<< HEAD // fetch waypoint resource details ======= ======= @@ -914,12 +1004,15 @@ export class CourseApi { ======= // fetch waypoint resource details >>>>>>> chore: lint +======= +>>>>>>> init courseApi try { const r = await this.server.resourcesApi.getResource( href.type, href.id ) <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (r.position && typeof r.position?.latitude !== 'undefined') { newCourse.nextPoint.position = r.position @@ -935,6 +1028,10 @@ export class CourseApi { } else { return false >>>>>>> chore: lint +======= + if (r.position && typeof r.position.value?.latitude !== 'undefined') { + this.courseInfo.nextPoint.position = r.position.value +>>>>>>> init courseApi } } catch (err) { return false @@ -943,6 +1040,7 @@ export class CourseApi { } else if (dest.position) { <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD newCourse.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { @@ -962,6 +1060,11 @@ export class CourseApi { if (typeof dest.position.latitude !== 'undefined') { newCourse.nextPoint.position = dest.position >>>>>>> add 30sec delta interval +======= + this.courseInfo.nextPoint.href = null + if (typeof dest.position.latitude !== 'undefined') { + this.courseInfo.nextPoint.position = dest.position +>>>>>>> init courseApi } else { return false } @@ -971,6 +1074,7 @@ export class CourseApi { // set previousPoint <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD try { const position: any = this.server.getSelfPath('navigation.position') @@ -987,11 +1091,14 @@ export class CourseApi { this.courseInfo = newCourse ======= +======= +>>>>>>> init courseApi const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.courseInfo.previousPoint.type = `VesselPosition` } else { +<<<<<<< HEAD ======= try { const position: any = this.server.getSelfPath('navigation.position') @@ -1004,9 +1111,13 @@ export class CourseApi { newCourse.previousPoint.href = null } catch (err) { >>>>>>> chore: lint +======= +>>>>>>> init courseApi return false } + this.courseInfo.previousPoint.href = null +<<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD >>>>>>> init courseApi @@ -1016,6 +1127,8 @@ export class CourseApi { ======= this.courseInfo = newCourse >>>>>>> add 30sec delta interval +======= +>>>>>>> init courseApi return true } @@ -1032,6 +1145,7 @@ export class CourseApi { this.courseInfo.previousPoint.position = null } +<<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD private isValidArrivalCircle(value: number): boolean { @@ -1041,6 +1155,8 @@ export class CourseApi { private parsePointIndex(index: number, rte: any): number { if (typeof index !== 'number' || !rte) { ======= +======= +>>>>>>> init courseApi private setArrivalCircle(value: any): boolean { if (typeof value === 'number' && value >= 0) { this.courseInfo.nextPoint.arrivalCircle = value @@ -1048,6 +1164,7 @@ export class CourseApi { } else { return false } +<<<<<<< HEAD } private parsePointIndex(index: number, rte: any): number { @@ -1061,6 +1178,12 @@ export class CourseApi { private parsePointIndex(index: number, rte: any): number { if (typeof index !== 'number' || !rte) { >>>>>>> chore: lint +======= + } + + private parsePointIndex(index: number, rte: any): number { + if (!rte) { +>>>>>>> init courseApi return 0 } if (!rte.feature?.geometry?.coordinates) { @@ -1080,6 +1203,7 @@ export class CourseApi { private parseHref(href: string): { type: string; id: string } | undefined { <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (!href) { return undefined @@ -1090,10 +1214,16 @@ export class CourseApi { if (href.length === 0) { return undefined } +======= + if (href.length === 0) { + return undefined + } +>>>>>>> init courseApi if (href[0] === '/') { href = href.slice(1) } const ref: string[] = href.split('/') +<<<<<<< HEAD >>>>>>> init courseApi ======= if (!href) { @@ -1102,6 +1232,8 @@ export class CourseApi { const ref: string[] = href.split('/').slice(-3) >>>>>>> chore: lint +======= +>>>>>>> init courseApi if (ref.length < 3) { return undefined } @@ -1114,6 +1246,7 @@ export class CourseApi { } } +<<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD private getRoutePoint(rte: any, index: number, reverse: boolean) { @@ -1126,6 +1259,10 @@ export class CourseApi { private getRoutePoint(rte: any, index: number, reverse: boolean) { const pos = reverse >>>>>>> chore: lint +======= + private getRoutePoint(rte: any, index: number) { + const pos = this.courseInfo.activeRoute.reverse +>>>>>>> init courseApi ? rte.feature.geometry.coordinates[ rte.feature.geometry.coordinates.length - (index + 1) ] @@ -1134,6 +1271,7 @@ export class CourseApi { latitude: pos[1], longitude: pos[0], <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD altitude: pos.length === 3 ? pos[2] : 0 ======= @@ -1142,6 +1280,9 @@ export class CourseApi { ======= altitude: pos.length === 3 ? pos[2] : 0 >>>>>>> chore: lint +======= + altitude: pos.length == 3 ? pos[2] : 0 +>>>>>>> init courseApi } } @@ -1160,6 +1301,7 @@ export class CourseApi { private buildDeltaMsg(): any { <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD const values: Array<{ path: string; value: any }> = [] const navPath = [ @@ -1171,6 +1313,10 @@ export class CourseApi { const values: Array<{ path: string; value: any }> = [] const navPath = [ >>>>>>> chore: lint +======= + let values: Array<{path:string, value:any}> = [] + let root = [ +>>>>>>> init courseApi 'navigation.courseGreatCircle', 'navigation.courseRhumbline' ] @@ -1178,6 +1324,7 @@ export class CourseApi { values.push({ <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> update detlas path: `navigation.course`, @@ -1188,73 +1335,77 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD path: `${navPath[0]}.activeRoute.href`, +======= + path: `${root[0]}.activeRoute.href`, +>>>>>>> init courseApi value: this.courseInfo.activeRoute.href }) values.push({ - path: `${navPath[1]}.activeRoute.href`, + path: `${root[1]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${navPath[0]}.activeRoute.startTime`, + path: `${root[0]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${navPath[1]}.activeRoute.startTime`, + path: `${root[1]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${navPath[0]}.nextPoint.href`, + path: `${root[0]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${navPath[1]}.nextPoint.href`, + path: `${root[1]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${navPath[0]}.nextPoint.position`, + path: `${root[0]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${navPath[1]}.nextPoint.position`, + path: `${root[1]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${navPath[0]}.nextPoint.type`, + path: `${root[0]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${navPath[1]}.nextPoint.type`, + path: `${root[1]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${navPath[0]}.nextPoint.arrivalCircle`, + path: `${root[0]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${navPath[1]}.nextPoint.arrivalCircle`, + path: `${root[1]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${navPath[0]}.previousPoint.href`, + path: `${root[0]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${navPath[1]}.previousPoint.href`, + path: `${root[1]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${navPath[0]}.previousPoint.position`, + path: `${root[0]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${navPath[1]}.previousPoint.position`, + path: `${root[1]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${navPath[0]}.previousPoint.type`, + path: `${root[0]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) values.push({ +<<<<<<< HEAD path: `${navPath[1]}.previousPoint.type`, ======= ======= @@ -1336,6 +1487,9 @@ export class CourseApi { ======= path: `${navPath[1]}.previousPoint.type`, >>>>>>> chore: lint +======= + path: `${root[1]}.previousPoint.type`, +>>>>>>> init courseApi value: this.courseInfo.previousPoint.type }) @@ -1343,6 +1497,7 @@ export class CourseApi { updates: [ { <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD values ======= @@ -1351,6 +1506,9 @@ export class CourseApi { ======= values >>>>>>> chore: lint +======= + values: values +>>>>>>> init courseApi } ] } diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index 5db5fb426..ca25260a7 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -109,9 +109,12 @@ "tags": ["course"], "summary": "Restart course calculations", <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD "description": "Sets previousPoint value to current vessel location" ======= +======= +>>>>>>> init courseApi "description": "Sets previousPoint value to current vessel location", "requestBody": { "description": "Restart payload", @@ -135,10 +138,13 @@ } } } +<<<<<<< HEAD >>>>>>> init courseApi ======= "description": "Sets previousPoint value to current vessel location" >>>>>>> chore: lint +======= +>>>>>>> init courseApi } }, @@ -209,9 +215,12 @@ "tags": ["course/destination"], "summary": "Clear destination", <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD "description": "Sets activeRoute, nextPoint & previousPoint values to null" ======= +======= +>>>>>>> init courseApi "description": "Sets activeRoute, nextPoint & previousPoint values to null", "requestBody": { "description": "Delete payload", @@ -235,10 +244,13 @@ } } } +<<<<<<< HEAD >>>>>>> init courseApi ======= "description": "Sets activeRoute, nextPoint & previousPoint values to null" >>>>>>> chore: lint +======= +>>>>>>> init courseApi } }, @@ -330,9 +342,12 @@ "tags": ["course/activeRoute"], "summary": "Clear active route", <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD "description": "Sets activeRoute, nextPoint & previousPoint values to null" ======= +======= +>>>>>>> init courseApi "description": "Sets activeRoute, nextPoint & previousPoint values to null", "requestBody": { "description": "Delete payload", @@ -356,10 +371,13 @@ } } } +<<<<<<< HEAD >>>>>>> init courseApi ======= "description": "Sets activeRoute, nextPoint & previousPoint values to null" >>>>>>> chore: lint +======= +>>>>>>> init courseApi } }, From 643ed78dea9ff10e283c4c1397c3b39f68cd366e Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 5 Dec 2021 10:26:47 +1030 Subject: [PATCH 355/410] update detlas --- src/api/course/index.ts | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index ad46cb1c1..a82edaf4b 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -3,6 +3,7 @@ <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD import Debug from 'debug' import { Application, Request, Response } from 'express' ======= @@ -44,6 +45,10 @@ import Debug from 'debug' import { Application, Request, Response } from 'express' import { v4 as uuidv4 } from 'uuid' >>>>>>> init courseApi +======= +import Debug from 'debug' +import { Application, Request, Response } from 'express' +>>>>>>> update detlas const debug = Debug('signalk:courseApi') @@ -52,6 +57,7 @@ const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/cou <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD const DELTA_INTERVAL: number = 30000 ======= @@ -66,6 +72,8 @@ const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' >>>>>>> init courseApi const API_METHODS: string[] = [] +======= +>>>>>>> update detlas interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void @@ -75,6 +83,7 @@ interface CourseApplication extends Application { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD securityStrategy: { shouldAllowPut: ( @@ -109,6 +118,9 @@ interface CourseApplication extends Application { >>>>>>> enable put processing ======= >>>>>>> init courseApi +======= + registerPutHandler: (context:string, path:string, cb:any) => any +>>>>>>> update detlas resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -256,9 +268,22 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> enable put processing ======= +======= + // + if(this.server.registerPutHandler) { + debug('** Registering PUT Action Handler(s) **') + this.server.registerPutHandler( + 'vessels.self', + 'navigation.course.*', + this.handleCourseApiPut + ); + } + +>>>>>>> update detlas // restart / arrivalCircle >>>>>>> init courseApi this.server.put( @@ -345,6 +370,10 @@ export class CourseApi { ======= debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { + //test for active destination + if (!this.courseInfo.nextPoint.position) { + return + } // set previousPoint to vessel position >>>>>>> init courseApi const position: any = this.server.getSelfPath('navigation.position') @@ -834,6 +863,12 @@ export class CourseApi { ) } + private handleCourseApiPut(context:string, path:string, value:any, cb:any) { + + debug('** PUT handler **') + return undefined + } + private async activateRoute(route: ActiveRoute): Promise { let rte: any @@ -1325,6 +1360,9 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> update detlas ======= >>>>>>> update detlas path: `navigation.course`, @@ -1333,9 +1371,12 @@ export class CourseApi { values.push({ <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD path: `${navPath[0]}.activeRoute.href`, ======= +======= +>>>>>>> update detlas path: `${root[0]}.activeRoute.href`, >>>>>>> init courseApi value: this.courseInfo.activeRoute.href From 0b94dfc2cff77b56289992d2b8f152f0b1d58fcb Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 4 Dec 2021 17:12:14 +1030 Subject: [PATCH 356/410] init courseApi --- src/api/course/index.ts | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index a82edaf4b..49dcccca5 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -4,6 +4,7 @@ <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD import Debug from 'debug' import { Application, Request, Response } from 'express' ======= @@ -49,6 +50,17 @@ import { v4 as uuidv4 } from 'uuid' import Debug from 'debug' import { Application, Request, Response } from 'express' >>>>>>> update detlas +======= + +import { + ResourceProvider, + ResourceProviderMethods, + SignalKResourceType +} from '@signalk/server-api' +import Debug from 'debug' +import { Application, Request, Response } from 'express' +import { v4 as uuidv4 } from 'uuid' +>>>>>>> init courseApi const debug = Debug('signalk:courseApi') @@ -75,6 +87,10 @@ const API_METHODS: string[] = [] ======= >>>>>>> update detlas +const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' + +const API_METHODS: string[] = [] + interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any @@ -84,6 +100,7 @@ interface CourseApplication extends Application { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD securityStrategy: { shouldAllowPut: ( @@ -121,6 +138,9 @@ interface CourseApplication extends Application { ======= registerPutHandler: (context:string, path:string, cb:any) => any >>>>>>> update detlas +======= + +>>>>>>> init courseApi resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -269,6 +289,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> enable put processing ======= @@ -284,6 +305,8 @@ export class CourseApi { } >>>>>>> update detlas +======= +>>>>>>> init courseApi // restart / arrivalCircle >>>>>>> init courseApi this.server.put( @@ -370,10 +393,6 @@ export class CourseApi { ======= debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { - //test for active destination - if (!this.courseInfo.nextPoint.position) { - return - } // set previousPoint to vessel position >>>>>>> init courseApi const position: any = this.server.getSelfPath('navigation.position') @@ -863,12 +882,6 @@ export class CourseApi { ) } - private handleCourseApiPut(context:string, path:string, value:any, cb:any) { - - debug('** PUT handler **') - return undefined - } - private async activateRoute(route: ActiveRoute): Promise { let rte: any From 2bf52aa02ccdc5be0d195ca229fae904ae4d3ab6 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 5 Dec 2021 10:26:47 +1030 Subject: [PATCH 357/410] update detlas --- src/api/course/index.ts | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 49dcccca5..69105975a 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -5,6 +5,7 @@ <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD import Debug from 'debug' import { Application, Request, Response } from 'express' ======= @@ -61,6 +62,10 @@ import Debug from 'debug' import { Application, Request, Response } from 'express' import { v4 as uuidv4 } from 'uuid' >>>>>>> init courseApi +======= +import Debug from 'debug' +import { Application, Request, Response } from 'express' +>>>>>>> update detlas const debug = Debug('signalk:courseApi') @@ -87,10 +92,6 @@ const API_METHODS: string[] = [] ======= >>>>>>> update detlas -const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' - -const API_METHODS: string[] = [] - interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any @@ -101,6 +102,7 @@ interface CourseApplication extends Application { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD securityStrategy: { shouldAllowPut: ( @@ -141,6 +143,9 @@ interface CourseApplication extends Application { ======= >>>>>>> init courseApi +======= + registerPutHandler: (context:string, path:string, cb:any) => any +>>>>>>> update detlas resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -290,10 +295,13 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> enable put processing ======= ======= +======= +>>>>>>> update detlas // if(this.server.registerPutHandler) { debug('** Registering PUT Action Handler(s) **') @@ -304,9 +312,12 @@ export class CourseApi { ); } +<<<<<<< HEAD >>>>>>> update detlas ======= >>>>>>> init courseApi +======= +>>>>>>> update detlas // restart / arrivalCircle >>>>>>> init courseApi this.server.put( @@ -393,6 +404,10 @@ export class CourseApi { ======= debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { + //test for active destination + if (!this.courseInfo.nextPoint.position) { + return + } // set previousPoint to vessel position >>>>>>> init courseApi const position: any = this.server.getSelfPath('navigation.position') @@ -882,6 +897,12 @@ export class CourseApi { ) } + private handleCourseApiPut(context:string, path:string, value:any, cb:any) { + + debug('** PUT handler **') + return undefined + } + private async activateRoute(route: ActiveRoute): Promise { let rte: any From 8a39c4413d7a68360650c05f68c9f86fb248020c Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 6 Dec 2021 08:39:56 +1030 Subject: [PATCH 358/410] enable put processing --- src/api/course/index.ts | 137 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 128 insertions(+), 9 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 69105975a..09834c6eb 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -103,6 +103,7 @@ interface CourseApplication extends Application { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD securityStrategy: { shouldAllowPut: ( @@ -116,12 +117,18 @@ interface CourseApplication extends Application { ======= req: Application, >>>>>>> chore: lint +======= + securityStrategy: { + shouldAllowPut: ( + req: any, +>>>>>>> enable put processing context: string, source: any, path: string ) => boolean } <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> init courseApi ======= @@ -146,6 +153,8 @@ interface CourseApplication extends Application { ======= registerPutHandler: (context:string, path:string, cb:any) => any >>>>>>> update detlas +======= +>>>>>>> enable put processing resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -277,6 +286,15 @@ export class CourseApi { >>>>>>> init courseApi } + private updateAllowed(): boolean { + return this.server.securityStrategy.shouldAllowPut( + this.server, + 'vessels.self', + null, + 'resources' + ) + } + private initResourceRoutes() { // return current course information this.server.get( @@ -296,6 +314,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> enable put processing ======= @@ -320,9 +339,36 @@ export class CourseApi { >>>>>>> update detlas // restart / arrivalCircle >>>>>>> init courseApi +======= + this.server.put( + `${COURSE_API_PATH}/restart`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/restart`) + if (!this.updateAllowed()) { + res.status(403) + return + } + if (!this.courseInfo.nextPoint.position) { + res.status(406).send(`No active destination!`) + return + } + // set previousPoint to vessel position + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.emitCourseInfo() + res.status(200) + } else { + res.status(406).send(`Vessel position unavailable!`) + } + } + ) + +>>>>>>> enable put processing this.server.put( - `${COURSE_API_PATH}/:action`, + `${COURSE_API_PATH}/arrivalCircle`, async (req: Request, res: Response) => { +<<<<<<< HEAD <<<<<<< HEAD debug(`** PUT ${COURSE_API_PATH}/restart`) if (!this.updateAllowed()) { @@ -463,6 +509,18 @@ export class CourseApi { >>>>>>> enable put processing ======= >>>>>>> init courseApi +======= + debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) + if (!this.updateAllowed()) { + res.status(403) + return + } + if (this.setArrivalCircle(req.body.value)) { + this.emitCourseInfo() + res.status(200) + } else { + res.status(406).send(`Invalid Data`) +>>>>>>> enable put processing } } ) @@ -474,6 +532,7 @@ export class CourseApi { debug(`** PUT ${COURSE_API_PATH}/destination`) <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (!this.updateAllowed()) { res.status(403).send('Unauthorised') @@ -490,6 +549,12 @@ export class CourseApi { >>>>>>> enable put processing ======= >>>>>>> init courseApi +======= + if (!this.updateAllowed()) { + res.status(403) + return + } +>>>>>>> enable put processing if (!req.body.value) { res.status(406).send(`Invalid Data`) return @@ -500,6 +565,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD res.status(200).send('OK') ======= @@ -514,6 +580,9 @@ export class CourseApi { ======= res.status(200).send(`Destination set successfully.`) >>>>>>> init courseApi +======= + res.status(200) +>>>>>>> enable put processing } else { this.clearDestination() this.emitCourseInfo() @@ -529,6 +598,7 @@ export class CourseApi { debug(`** DELETE ${COURSE_API_PATH}/destination`) <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (!this.updateAllowed()) { res.status(403).send('Unauthorised') @@ -536,12 +606,19 @@ export class CourseApi { } ======= >>>>>>> init courseApi +======= + if (!this.updateAllowed()) { + res.status(403) + return + } +>>>>>>> enable put processing this.clearDestination() this.emitCourseInfo() - res.status(200).send(`Destination cleared.`) + res.status(200) } ) +<<<<<<< HEAD <<<<<<< HEAD // set activeRoute ======= @@ -566,12 +643,16 @@ export class CourseApi { ======= // set / clear activeRoute >>>>>>> init courseApi +======= + // set activeRoute +>>>>>>> enable put processing this.server.put( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (!this.updateAllowed()) { res.status(403).send('Unauthorised') @@ -609,6 +690,16 @@ export class CourseApi { this.emitCourseInfo() res.status(200).send(`Active route set.`) >>>>>>> init courseApi +======= + if (!this.updateAllowed()) { + res.status(403) + return + } + const result = await this.activateRoute(req.body.value) + if (result) { + this.emitCourseInfo() + res.status(200) +>>>>>>> enable put processing } else { this.clearDestination() this.emitCourseInfo() @@ -618,6 +709,7 @@ export class CourseApi { ) <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD // clear activeRoute /destination @@ -629,6 +721,10 @@ export class CourseApi { >>>>>>> enable put processing ======= >>>>>>> init courseApi +======= + + // clear activeRoute /destination +>>>>>>> enable put processing this.server.delete( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { @@ -636,12 +732,16 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> enable put processing ======= >>>>>>> enable put processing if (!this.updateAllowed()) { res.status(403) return } +<<<<<<< HEAD <<<<<<< HEAD this.clearDestination() this.emitCourseInfo() @@ -667,6 +767,11 @@ export class CourseApi { this.emitCourseInfo() res.status(200).send(`Active route cleared.`) >>>>>>> init courseApi +======= + this.clearDestination() + this.emitCourseInfo() + res.status(200) +>>>>>>> enable put processing } ) @@ -676,6 +781,7 @@ export class CourseApi { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (!this.updateAllowed()) { res.status(403).send('Unauthorised') @@ -698,6 +804,13 @@ export class CourseApi { >>>>>>> chore: lint ======= >>>>>>> init courseApi +======= + if (!this.updateAllowed()) { + res.status(403) + return + } + // fetch route data +>>>>>>> enable put processing if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return @@ -717,6 +830,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (req.params.action === 'nextPoint') { ======= @@ -728,6 +842,9 @@ export class CourseApi { ======= if (req.params.nextPoint) { >>>>>>> init courseApi +======= + if (req.params.action === 'nextPoint') { +>>>>>>> enable put processing if ( typeof req.body.value === 'number' && (req.body.value === 1 || req.body.value === -1) @@ -750,6 +867,7 @@ export class CourseApi { ======= } } +<<<<<<< HEAD <<<<<<< HEAD if (req.params.pointIndex) { >>>>>>> init courseApi @@ -761,6 +879,9 @@ export class CourseApi { } if (req.params.pointIndex) { >>>>>>> init courseApi +======= + if (req.params.action === 'pointIndex') { +>>>>>>> enable put processing if (typeof req.body.value === 'number') { this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( req.body.value, @@ -779,7 +900,7 @@ export class CourseApi { } } - // set nextPoint + // set new destination this.courseInfo.nextPoint.position = this.getRoutePoint( rte, <<<<<<< HEAD @@ -880,6 +1001,7 @@ export class CourseApi { res.status(200).send('OK') ======= +<<<<<<< HEAD <<<<<<< HEAD res.status(200).send(`OK`) >>>>>>> init courseApi @@ -893,16 +1015,13 @@ export class CourseApi { res.status(200).send(`OK`) >>>>>>> init courseApi +======= + res.status(200) +>>>>>>> enable put processing } ) } - private handleCourseApiPut(context:string, path:string, value:any, cb:any) { - - debug('** PUT handler **') - return undefined - } - private async activateRoute(route: ActiveRoute): Promise { let rte: any From f82b700b98c9fc289eadd6eb1bfba511f50bd43a Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 6 Dec 2021 13:34:31 +1030 Subject: [PATCH 359/410] chore: lint --- src/api/course/index.ts | 232 +++++++++++++++++++++++++++--------- src/api/course/openApi.json | 12 ++ 2 files changed, 189 insertions(+), 55 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 09834c6eb..9ecf48a6c 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -120,8 +120,12 @@ interface CourseApplication extends Application { ======= securityStrategy: { shouldAllowPut: ( +<<<<<<< HEAD req: any, >>>>>>> enable put processing +======= + req: Application, +>>>>>>> chore: lint context: string, source: any, path: string @@ -291,7 +295,7 @@ export class CourseApi { this.server, 'vessels.self', null, - 'resources' + 'navigation.course' ) } @@ -353,12 +357,14 @@ export class CourseApi { return } // set previousPoint to vessel position - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.emitCourseInfo() - res.status(200) - } else { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.emitCourseInfo() + res.status(200) + } + } catch (err) { res.status(406).send(`Vessel position unavailable!`) } } @@ -515,7 +521,8 @@ export class CourseApi { res.status(403) return } - if (this.setArrivalCircle(req.body.value)) { + if (this.isValidArrivalCircle(req.body.value)) { + this.courseInfo.nextPoint.arrivalCircle = req.body.value this.emitCourseInfo() res.status(200) } else { @@ -809,8 +816,12 @@ export class CourseApi { res.status(403) return } +<<<<<<< HEAD // fetch route data >>>>>>> enable put processing +======= + // fetch active route data +>>>>>>> chore: lint if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return @@ -903,6 +914,7 @@ export class CourseApi { // set new destination this.courseInfo.nextPoint.position = this.getRoutePoint( rte, +<<<<<<< HEAD <<<<<<< HEAD this.courseInfo.activeRoute.pointIndex, this.courseInfo.activeRoute.reverse @@ -923,6 +935,10 @@ export class CourseApi { ======= this.courseInfo.activeRoute.pointIndex >>>>>>> init courseApi +======= + this.courseInfo.activeRoute.pointIndex, + this.courseInfo.activeRoute.reverse +>>>>>>> chore: lint ) this.courseInfo.nextPoint.type = `RoutePoint` this.courseInfo.nextPoint.href = null @@ -932,6 +948,9 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> chore: lint ======= >>>>>>> chore: lint try { @@ -941,6 +960,7 @@ export class CourseApi { this.courseInfo.previousPoint.type = `VesselPosition` } else { <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD res.status(406).send(`Invalid Data`) return false @@ -971,6 +991,11 @@ export class CourseApi { >>>>>>> add 30sec delta interval ======= >>>>>>> init courseApi +======= + return false + } + } catch (err) { +>>>>>>> chore: lint return false } } else { @@ -978,6 +1003,7 @@ export class CourseApi { rte, <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD this.courseInfo.activeRoute.pointIndex - 1, this.courseInfo.activeRoute.reverse @@ -991,6 +1017,10 @@ export class CourseApi { ======= this.courseInfo.activeRoute.pointIndex - 1 >>>>>>> init courseApi +======= + this.courseInfo.activeRoute.pointIndex - 1, + this.courseInfo.activeRoute.reverse +>>>>>>> chore: lint ) this.courseInfo.previousPoint.type = `RoutePoint` } @@ -1040,71 +1070,88 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD const newCourse: any = {} Object.assign(newCourse, this.courseInfo) ======= >>>>>>> init courseApi +======= + const newDest: any = {} + Object.assign(newDest, this.courseInfo) + +>>>>>>> chore: lint // set activeroute - this.courseInfo.activeRoute.href = route.href + newDest.activeRoute.href = route.href - if (typeof route.arrivalCircle === 'number') { - this.setArrivalCircle(route.arrivalCircle) + if (this.isValidArrivalCircle(route.arrivalCircle as number)) { + newDest.nextPoint.arrivalCircle = route.arrivalCircle } - this.courseInfo.activeRoute.reverse = - typeof route.reverse === 'boolean' ? route.reverse : false + newDest.activeRoute.startTime = new Date().toISOString() - this.courseInfo.activeRoute.pointIndex = - typeof route.pointIndex === 'number' - ? this.parsePointIndex(route.pointIndex, rte) - : 0 + if (typeof route.reverse === 'boolean') { + newDest.activeRoute.reverse = route.reverse + } - this.courseInfo.activeRoute.startTime = new Date().toISOString() + newDest.activeRoute.pointIndex = this.parsePointIndex( + route.pointIndex as number, + rte + ) // set nextPoint - this.courseInfo.nextPoint.position = this.getRoutePoint( + newDest.nextPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex + newDest.activeRoute.pointIndex, + newDest.activeRoute.reverse ) - this.courseInfo.nextPoint.type = `RoutePoint` - this.courseInfo.nextPoint.href = null + newDest.nextPoint.type = `RoutePoint` + newDest.nextPoint.href = null // set previousPoint - if (this.courseInfo.activeRoute.pointIndex === 0) { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + if (newDest.activeRoute.pointIndex === 0) { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } catch (err) { return false } } else { - this.courseInfo.previousPoint.position = this.getRoutePoint( + newDest.previousPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex - 1 + newDest.activeRoute.pointIndex - 1, + newDest.activeRoute.reverse ) - this.courseInfo.previousPoint.type = `RoutePoint` + newDest.previousPoint.type = `RoutePoint` } - this.courseInfo.previousPoint.href = null + newDest.previousPoint.href = null + this.courseInfo = newDest return true } - private async setDestination(dest: Destination): Promise { + private async setDestination(dest: any): Promise { + const newDest: any = {} + Object.assign(newDest, this.courseInfo) + // set nextPoint - if (typeof dest.arrivalCircle === 'number') { - this.setArrivalCircle(dest.arrivalCircle) + if (this.isValidArrivalCircle(dest.arrivalCircle)) { + newDest.nextPoint.arrivalCircle = dest.arrivalCircle } - this.courseInfo.nextPoint.type = - typeof dest.type !== 'undefined' ? dest.type : null + newDest.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - this.courseInfo.nextPoint.href = dest.href + newDest.href = dest.href const href = this.parseHref(dest.href) if (href) { +<<<<<<< HEAD <<<<<<< HEAD // fetch waypoint resource details ======= @@ -1194,6 +1241,9 @@ export class CourseApi { >>>>>>> chore: lint ======= >>>>>>> init courseApi +======= + // fetch waypoint resource details +>>>>>>> chore: lint try { const r = await this.server.resourcesApi.getResource( href.type, @@ -1201,6 +1251,7 @@ export class CourseApi { ) <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (r.position && typeof r.position?.latitude !== 'undefined') { newCourse.nextPoint.position = r.position @@ -1220,6 +1271,12 @@ export class CourseApi { if (r.position && typeof r.position.value?.latitude !== 'undefined') { this.courseInfo.nextPoint.position = r.position.value >>>>>>> init courseApi +======= + if (r.position && typeof r.position?.latitude !== 'undefined') { + newDest.nextPoint.position = r.position + } else { + return false +>>>>>>> chore: lint } } catch (err) { return false @@ -1229,6 +1286,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD newCourse.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { @@ -1253,6 +1311,11 @@ export class CourseApi { if (typeof dest.position.latitude !== 'undefined') { this.courseInfo.nextPoint.position = dest.position >>>>>>> init courseApi +======= + newDest.nextPoint.href = null + if (typeof dest.position.latitude !== 'undefined') { + newDest.nextPoint.position = dest.position +>>>>>>> chore: lint } else { return false } @@ -1263,6 +1326,7 @@ export class CourseApi { // set previousPoint <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD try { const position: any = this.server.getSelfPath('navigation.position') @@ -1301,13 +1365,25 @@ export class CourseApi { >>>>>>> chore: lint ======= >>>>>>> init courseApi +======= + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + newDest.previousPoint.position = position.value + newDest.previousPoint.type = `VesselPosition` + } else { + return false + } + newDest.previousPoint.href = null + } catch (err) { +>>>>>>> chore: lint return false } - this.courseInfo.previousPoint.href = null <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD >>>>>>> init courseApi ======= this.courseInfo = newDest @@ -1317,6 +1393,9 @@ export class CourseApi { >>>>>>> add 30sec delta interval ======= >>>>>>> init courseApi +======= + this.courseInfo = newDest +>>>>>>> chore: lint return true } @@ -1335,6 +1414,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD private isValidArrivalCircle(value: number): boolean { return typeof value === 'number' && value >= 0 @@ -1372,6 +1452,14 @@ export class CourseApi { private parsePointIndex(index: number, rte: any): number { if (!rte) { >>>>>>> init courseApi +======= + private isValidArrivalCircle(value: number): boolean { + return typeof value === 'number' && value >= 0 + } + + private parsePointIndex(index: number, rte: any): number { + if (typeof index !== 'number' || !rte) { +>>>>>>> chore: lint return 0 } if (!rte.feature?.geometry?.coordinates) { @@ -1392,6 +1480,7 @@ export class CourseApi { private parseHref(href: string): { type: string; id: string } | undefined { <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (!href) { return undefined @@ -1422,6 +1511,13 @@ export class CourseApi { >>>>>>> chore: lint ======= >>>>>>> init courseApi +======= + if (!href) { + return undefined + } + + const ref: string[] = href.split('/').slice(-3) +>>>>>>> chore: lint if (ref.length < 3) { return undefined } @@ -1436,6 +1532,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD private getRoutePoint(rte: any, index: number, reverse: boolean) { const pos = reverse @@ -1451,6 +1548,10 @@ export class CourseApi { private getRoutePoint(rte: any, index: number) { const pos = this.courseInfo.activeRoute.reverse >>>>>>> init courseApi +======= + private getRoutePoint(rte: any, index: number, reverse: boolean) { + const pos = reverse +>>>>>>> chore: lint ? rte.feature.geometry.coordinates[ rte.feature.geometry.coordinates.length - (index + 1) ] @@ -1460,6 +1561,7 @@ export class CourseApi { longitude: pos[0], <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD altitude: pos.length === 3 ? pos[2] : 0 ======= @@ -1471,6 +1573,9 @@ export class CourseApi { ======= altitude: pos.length == 3 ? pos[2] : 0 >>>>>>> init courseApi +======= + altitude: pos.length === 3 ? pos[2] : 0 +>>>>>>> chore: lint } } @@ -1490,6 +1595,7 @@ export class CourseApi { private buildDeltaMsg(): any { <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD const values: Array<{ path: string; value: any }> = [] const navPath = [ @@ -1505,6 +1611,10 @@ export class CourseApi { let values: Array<{path:string, value:any}> = [] let root = [ >>>>>>> init courseApi +======= + const values: Array<{ path: string; value: any }> = [] + const navPath = [ +>>>>>>> chore: lint 'navigation.courseGreatCircle', 'navigation.courseRhumbline' ] @@ -1525,6 +1635,7 @@ export class CourseApi { values.push({ <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD path: `${navPath[0]}.activeRoute.href`, ======= @@ -1532,73 +1643,77 @@ export class CourseApi { >>>>>>> update detlas path: `${root[0]}.activeRoute.href`, >>>>>>> init courseApi +======= + path: `${navPath[0]}.activeRoute.href`, +>>>>>>> chore: lint value: this.courseInfo.activeRoute.href }) values.push({ - path: `${root[1]}.activeRoute.href`, + path: `${navPath[1]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${root[0]}.activeRoute.startTime`, + path: `${navPath[0]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${root[1]}.activeRoute.startTime`, + path: `${navPath[1]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${root[0]}.nextPoint.href`, + path: `${navPath[0]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${root[1]}.nextPoint.href`, + path: `${navPath[1]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${root[0]}.nextPoint.position`, + path: `${navPath[0]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${root[1]}.nextPoint.position`, + path: `${navPath[1]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${root[0]}.nextPoint.type`, + path: `${navPath[0]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${root[1]}.nextPoint.type`, + path: `${navPath[1]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${root[0]}.nextPoint.arrivalCircle`, + path: `${navPath[0]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${root[1]}.nextPoint.arrivalCircle`, + path: `${navPath[1]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${root[0]}.previousPoint.href`, + path: `${navPath[0]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${root[1]}.previousPoint.href`, + path: `${navPath[1]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${root[0]}.previousPoint.position`, + path: `${navPath[0]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${root[1]}.previousPoint.position`, + path: `${navPath[1]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${root[0]}.previousPoint.type`, + path: `${navPath[0]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) values.push({ +<<<<<<< HEAD <<<<<<< HEAD path: `${navPath[1]}.previousPoint.type`, ======= @@ -1684,6 +1799,9 @@ export class CourseApi { ======= path: `${root[1]}.previousPoint.type`, >>>>>>> init courseApi +======= + path: `${navPath[1]}.previousPoint.type`, +>>>>>>> chore: lint value: this.courseInfo.previousPoint.type }) @@ -1692,6 +1810,7 @@ export class CourseApi { { <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD values ======= @@ -1703,6 +1822,9 @@ export class CourseApi { ======= values: values >>>>>>> init courseApi +======= + values +>>>>>>> chore: lint } ] } diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index ca25260a7..a898b962d 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -110,6 +110,7 @@ "summary": "Restart course calculations", <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD "description": "Sets previousPoint value to current vessel location" ======= @@ -145,6 +146,9 @@ >>>>>>> chore: lint ======= >>>>>>> init courseApi +======= + "description": "Sets previousPoint value to current vessel location" +>>>>>>> chore: lint } }, @@ -216,6 +220,7 @@ "summary": "Clear destination", <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD "description": "Sets activeRoute, nextPoint & previousPoint values to null" ======= @@ -251,6 +256,9 @@ >>>>>>> chore: lint ======= >>>>>>> init courseApi +======= + "description": "Sets activeRoute, nextPoint & previousPoint values to null" +>>>>>>> chore: lint } }, @@ -343,6 +351,7 @@ "summary": "Clear active route", <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD "description": "Sets activeRoute, nextPoint & previousPoint values to null" ======= @@ -378,6 +387,9 @@ >>>>>>> chore: lint ======= >>>>>>> init courseApi +======= + "description": "Sets activeRoute, nextPoint & previousPoint values to null" +>>>>>>> chore: lint } }, From 719671f01a47022677d1b8c16a72fa3adaddde20 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 8 Dec 2021 09:27:06 +1030 Subject: [PATCH 360/410] add 30sec delta interval --- src/api/course/index.ts | 124 ++++++++++++++++++++++++++++------------ 1 file changed, 89 insertions(+), 35 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 9ecf48a6c..83880a24d 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -92,6 +92,8 @@ const API_METHODS: string[] = [] ======= >>>>>>> update detlas +const DELTA_INTERVAL: number = 30000 + interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any @@ -249,6 +251,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD setInterval(() => { if (this.courseInfo.nextPoint.position) { @@ -288,6 +291,13 @@ export class CourseApi { ) ======= >>>>>>> init courseApi +======= + setInterval( ()=> { + if(this.courseInfo.nextPoint.position) { + this.emitCourseInfo() + } + }, DELTA_INTERVAL) +>>>>>>> add 30sec delta interval } private updateAllowed(): boolean { @@ -349,7 +359,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/restart`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (!this.courseInfo.nextPoint.position) { @@ -362,7 +372,7 @@ export class CourseApi { if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } } catch (err) { res.status(406).send(`Vessel position unavailable!`) @@ -518,13 +528,13 @@ export class CourseApi { ======= debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (this.isValidArrivalCircle(req.body.value)) { this.courseInfo.nextPoint.arrivalCircle = req.body.value this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { res.status(406).send(`Invalid Data`) >>>>>>> enable put processing @@ -558,7 +568,7 @@ export class CourseApi { >>>>>>> init courseApi ======= if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } >>>>>>> enable put processing @@ -573,6 +583,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD res.status(200).send('OK') ======= @@ -590,6 +601,9 @@ export class CourseApi { ======= res.status(200) >>>>>>> enable put processing +======= + res.status(200).send('OK') +>>>>>>> add 30sec delta interval } else { this.clearDestination() this.emitCourseInfo() @@ -615,13 +629,13 @@ export class CourseApi { >>>>>>> init courseApi ======= if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } >>>>>>> enable put processing this.clearDestination() this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } ) @@ -699,14 +713,18 @@ export class CourseApi { >>>>>>> init courseApi ======= if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } const result = await this.activateRoute(req.body.value) if (result) { this.emitCourseInfo() +<<<<<<< HEAD res.status(200) >>>>>>> enable put processing +======= + res.status(200).send('OK') +>>>>>>> add 30sec delta interval } else { this.clearDestination() this.emitCourseInfo() @@ -777,8 +795,12 @@ export class CourseApi { ======= this.clearDestination() this.emitCourseInfo() +<<<<<<< HEAD res.status(200) >>>>>>> enable put processing +======= + res.status(200).send('OK') +>>>>>>> add 30sec delta interval } ) @@ -813,7 +835,7 @@ export class CourseApi { >>>>>>> init courseApi ======= if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } <<<<<<< HEAD @@ -869,6 +891,9 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> add 30sec delta interval ======= >>>>>>> add 30sec delta interval return @@ -903,6 +928,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> add 30sec delta interval return @@ -919,6 +945,9 @@ export class CourseApi { this.courseInfo.activeRoute.pointIndex, this.courseInfo.activeRoute.reverse ======= +======= + return +>>>>>>> add 30sec delta interval } } @@ -961,6 +990,7 @@ export class CourseApi { } else { <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD res.status(406).send(`Invalid Data`) return false @@ -983,11 +1013,14 @@ export class CourseApi { } catch (err) { >>>>>>> chore: lint ======= +======= +>>>>>>> add 30sec delta interval res.status(406).send(`Invalid Data`) return false } } catch (err) { res.status(406).send(`Invalid Data`) +<<<<<<< HEAD >>>>>>> add 30sec delta interval ======= >>>>>>> init courseApi @@ -996,6 +1029,8 @@ export class CourseApi { } } catch (err) { >>>>>>> chore: lint +======= +>>>>>>> add 30sec delta interval return false } } else { @@ -1027,6 +1062,7 @@ export class CourseApi { this.courseInfo.previousPoint.href = null <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD res.status(200).send('OK') ======= @@ -1048,6 +1084,9 @@ export class CourseApi { ======= res.status(200) >>>>>>> enable put processing +======= + res.status(200).send('OK') +>>>>>>> add 30sec delta interval } ) } @@ -1071,6 +1110,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD const newCourse: any = {} Object.assign(newCourse, this.courseInfo) @@ -1080,37 +1120,41 @@ export class CourseApi { ======= const newDest: any = {} Object.assign(newDest, this.courseInfo) +======= + const newCourse: any = {} + Object.assign(newCourse, this.courseInfo) +>>>>>>> add 30sec delta interval >>>>>>> chore: lint // set activeroute - newDest.activeRoute.href = route.href + newCourse.activeRoute.href = route.href if (this.isValidArrivalCircle(route.arrivalCircle as number)) { - newDest.nextPoint.arrivalCircle = route.arrivalCircle + newCourse.nextPoint.arrivalCircle = route.arrivalCircle } - newDest.activeRoute.startTime = new Date().toISOString() + newCourse.activeRoute.startTime = new Date().toISOString() if (typeof route.reverse === 'boolean') { - newDest.activeRoute.reverse = route.reverse + newCourse.activeRoute.reverse = route.reverse } - newDest.activeRoute.pointIndex = this.parsePointIndex( + newCourse.activeRoute.pointIndex = this.parsePointIndex( route.pointIndex as number, rte ) // set nextPoint - newDest.nextPoint.position = this.getRoutePoint( + newCourse.nextPoint.position = this.getRoutePoint( rte, - newDest.activeRoute.pointIndex, - newDest.activeRoute.reverse + newCourse.activeRoute.pointIndex, + newCourse.activeRoute.reverse ) - newDest.nextPoint.type = `RoutePoint` - newDest.nextPoint.href = null + newCourse.nextPoint.type = `RoutePoint` + newCourse.nextPoint.href = null // set previousPoint - if (newDest.activeRoute.pointIndex === 0) { + if (newCourse.activeRoute.pointIndex === 0) { try { const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -1123,32 +1167,32 @@ export class CourseApi { return false } } else { - newDest.previousPoint.position = this.getRoutePoint( + newCourse.previousPoint.position = this.getRoutePoint( rte, - newDest.activeRoute.pointIndex - 1, - newDest.activeRoute.reverse + newCourse.activeRoute.pointIndex - 1, + newCourse.activeRoute.reverse ) - newDest.previousPoint.type = `RoutePoint` + newCourse.previousPoint.type = `RoutePoint` } - newDest.previousPoint.href = null + newCourse.previousPoint.href = null - this.courseInfo = newDest + this.courseInfo = newCourse return true } private async setDestination(dest: any): Promise { - const newDest: any = {} - Object.assign(newDest, this.courseInfo) + const newCourse: any = {} + Object.assign(newCourse, this.courseInfo) // set nextPoint if (this.isValidArrivalCircle(dest.arrivalCircle)) { - newDest.nextPoint.arrivalCircle = dest.arrivalCircle + newCourse.nextPoint.arrivalCircle = dest.arrivalCircle } - newDest.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null + newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - newDest.href = dest.href + newCourse.href = dest.href const href = this.parseHref(dest.href) if (href) { <<<<<<< HEAD @@ -1273,7 +1317,7 @@ export class CourseApi { >>>>>>> init courseApi ======= if (r.position && typeof r.position?.latitude !== 'undefined') { - newDest.nextPoint.position = r.position + newCourse.nextPoint.position = r.position } else { return false >>>>>>> chore: lint @@ -1287,6 +1331,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD newCourse.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { @@ -1316,6 +1361,11 @@ export class CourseApi { if (typeof dest.position.latitude !== 'undefined') { newDest.nextPoint.position = dest.position >>>>>>> chore: lint +======= + newCourse.nextPoint.href = null + if (typeof dest.position.latitude !== 'undefined') { + newCourse.nextPoint.position = dest.position +>>>>>>> add 30sec delta interval } else { return false } @@ -1369,12 +1419,12 @@ export class CourseApi { try { const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { - newDest.previousPoint.position = position.value - newDest.previousPoint.type = `VesselPosition` + newCourse.previousPoint.position = position.value + newCourse.previousPoint.type = `VesselPosition` } else { return false } - newDest.previousPoint.href = null + newCourse.previousPoint.href = null } catch (err) { >>>>>>> chore: lint return false @@ -1384,6 +1434,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD >>>>>>> init courseApi ======= this.courseInfo = newDest @@ -1396,6 +1447,9 @@ export class CourseApi { ======= this.courseInfo = newDest >>>>>>> chore: lint +======= + this.courseInfo = newCourse +>>>>>>> add 30sec delta interval return true } From 2f8bb59a881d8b92e5afe1cd8371830215c171b1 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 12 Dec 2021 14:29:38 +1030 Subject: [PATCH 361/410] chore: lint --- src/api/course/index.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 83880a24d..01bfd3260 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -92,7 +92,7 @@ const API_METHODS: string[] = [] ======= >>>>>>> update detlas -const DELTA_INTERVAL: number = 30000 +const DELTA_INTERVAL: number = 30000 interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void @@ -252,6 +252,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD setInterval(() => { if (this.courseInfo.nextPoint.position) { @@ -272,6 +273,10 @@ export class CourseApi { ======= setInterval( ()=> { if(this.courseInfo.nextPoint.position) { +======= + setInterval(() => { + if (this.courseInfo.nextPoint.position) { +>>>>>>> chore: lint ======= setInterval(() => { if (this.courseInfo.nextPoint.position) { @@ -1189,7 +1194,8 @@ export class CourseApi { newCourse.nextPoint.arrivalCircle = dest.arrivalCircle } - newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null + newCourse.nextPoint.type = + typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { newCourse.href = dest.href From 5cb466396e3b85d98ee82631f65b9097c0c4d840 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 4 Dec 2021 17:12:14 +1030 Subject: [PATCH 362/410] init courseApi --- src/api/course/index.ts | 308 ++++++++++++++++++++++++++++++++++++ src/api/course/openApi.json | 18 +++ 2 files changed, 326 insertions(+) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 01bfd3260..f9877278b 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -6,6 +6,7 @@ <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD import Debug from 'debug' import { Application, Request, Response } from 'express' ======= @@ -53,6 +54,8 @@ import { Application, Request, Response } from 'express' >>>>>>> update detlas ======= +======= +>>>>>>> init courseApi import { ResourceProvider, ResourceProviderMethods, @@ -61,11 +64,14 @@ import { import Debug from 'debug' import { Application, Request, Response } from 'express' import { v4 as uuidv4 } from 'uuid' +<<<<<<< HEAD >>>>>>> init courseApi ======= import Debug from 'debug' import { Application, Request, Response } from 'express' >>>>>>> update detlas +======= +>>>>>>> init courseApi const debug = Debug('signalk:courseApi') @@ -75,6 +81,7 @@ const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/cou <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD const DELTA_INTERVAL: number = 30000 ======= @@ -93,6 +100,11 @@ const API_METHODS: string[] = [] >>>>>>> update detlas const DELTA_INTERVAL: number = 30000 +======= +const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' + +const API_METHODS: string[] = [] +>>>>>>> init courseApi interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void @@ -106,6 +118,7 @@ interface CourseApplication extends Application { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD securityStrategy: { shouldAllowPut: ( @@ -161,6 +174,8 @@ interface CourseApplication extends Application { >>>>>>> update detlas ======= >>>>>>> enable put processing +======= +>>>>>>> init courseApi resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -180,11 +195,15 @@ interface Destination extends DestinationBase { } <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> init courseApi ======= >>>>>>> update detlas +======= + +>>>>>>> init courseApi interface ActiveRoute extends DestinationBase { pointIndex?: number reverse?: boolean @@ -253,6 +272,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD setInterval(() => { if (this.courseInfo.nextPoint.position) { @@ -312,6 +332,8 @@ export class CourseApi { null, 'navigation.course' ) +======= +>>>>>>> init courseApi } private initResourceRoutes() { @@ -334,6 +356,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> enable put processing ======= @@ -476,12 +499,22 @@ export class CourseApi { return } // set previousPoint to vessel position +>>>>>>> init courseApi +======= + // restart / arrivalCircle + this.server.put( + `${COURSE_API_PATH}/:action`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/:action`) + if (req.params.restart) { + // set previousPoint to vessel position >>>>>>> init courseApi const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.emitCourseInfo() <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD res.status(200).send('OK') ======= @@ -499,11 +532,16 @@ export class CourseApi { } else { res.status(406).send(`Invalid Data`) ======= +======= +>>>>>>> init courseApi res.status(200).send(`Course restarted.`) } else { res.status(406).send(`Vessel position unavailable!`) } } +<<<<<<< HEAD +======= +>>>>>>> init courseApi ======= >>>>>>> init courseApi if (req.params.arrivalCircle) { @@ -514,6 +552,7 @@ export class CourseApi { res.status(406).send(`Invalid Data`) } <<<<<<< HEAD +<<<<<<< HEAD >>>>>>> init courseApi ======= debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) @@ -543,6 +582,8 @@ export class CourseApi { } else { res.status(406).send(`Invalid Data`) >>>>>>> enable put processing +======= +>>>>>>> init courseApi } } ) @@ -555,6 +596,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (!this.updateAllowed()) { res.status(403).send('Unauthorised') @@ -577,6 +619,9 @@ export class CourseApi { return } >>>>>>> enable put processing +======= + +>>>>>>> init courseApi if (!req.body.value) { res.status(406).send(`Invalid Data`) return @@ -589,6 +634,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD res.status(200).send('OK') ======= @@ -609,6 +655,9 @@ export class CourseApi { ======= res.status(200).send('OK') >>>>>>> add 30sec delta interval +======= + res.status(200).send(`Destination set successfully.`) +>>>>>>> init courseApi } else { this.clearDestination() this.emitCourseInfo() @@ -625,6 +674,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (!this.updateAllowed()) { res.status(403).send('Unauthorised') @@ -672,6 +722,15 @@ export class CourseApi { ======= // set activeRoute >>>>>>> enable put processing +======= + this.clearDestination() + this.emitCourseInfo() + res.status(200).send(`Destination cleared.`) + } + ) + + // set / clear activeRoute +>>>>>>> init courseApi this.server.put( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { @@ -679,6 +738,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (!this.updateAllowed()) { res.status(403).send('Unauthorised') @@ -710,11 +770,14 @@ export class CourseApi { res.status(200).send('OK') >>>>>>> add 30sec delta interval ======= +======= +>>>>>>> init courseApi const result = await this.activateRoute(req.body.value) if (result) { this.emitCourseInfo() res.status(200).send(`Active route set.`) +<<<<<<< HEAD >>>>>>> init courseApi ======= if (!this.updateAllowed()) { @@ -730,6 +793,8 @@ export class CourseApi { ======= res.status(200).send('OK') >>>>>>> add 30sec delta interval +======= +>>>>>>> init courseApi } else { this.clearDestination() this.emitCourseInfo() @@ -740,6 +805,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD // clear activeRoute /destination @@ -755,6 +821,8 @@ export class CourseApi { // clear activeRoute /destination >>>>>>> enable put processing +======= +>>>>>>> init courseApi this.server.delete( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { @@ -763,6 +831,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> enable put processing ======= @@ -792,10 +861,13 @@ export class CourseApi { res.status(200).send('OK') >>>>>>> add 30sec delta interval ======= +======= +>>>>>>> init courseApi this.clearDestination() this.emitCourseInfo() res.status(200).send(`Active route cleared.`) +<<<<<<< HEAD >>>>>>> init courseApi ======= this.clearDestination() @@ -806,6 +878,8 @@ export class CourseApi { ======= res.status(200).send('OK') >>>>>>> add 30sec delta interval +======= +>>>>>>> init courseApi } ) @@ -816,6 +890,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (!this.updateAllowed()) { res.status(403).send('Unauthorised') @@ -849,17 +924,24 @@ export class CourseApi { ======= // fetch active route data >>>>>>> chore: lint +======= + +>>>>>>> init courseApi if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return } <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> init courseApi ======= >>>>>>> enable put processing +======= + +>>>>>>> init courseApi const rte = await this.getRoute(this.courseInfo.activeRoute.href) if (!rte) { res.status(406).send(`Invalid Data`) @@ -869,6 +951,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (req.params.action === 'nextPoint') { ======= @@ -883,6 +966,9 @@ export class CourseApi { ======= if (req.params.action === 'nextPoint') { >>>>>>> enable put processing +======= + if (req.params.nextPoint) { +>>>>>>> init courseApi if ( typeof req.body.value === 'number' && (req.body.value === 1 || req.body.value === -1) @@ -897,6 +983,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> add 30sec delta interval ======= @@ -923,6 +1010,11 @@ export class CourseApi { ======= if (req.params.action === 'pointIndex') { >>>>>>> enable put processing +======= + } + } + if (req.params.pointIndex) { +>>>>>>> init courseApi if (typeof req.body.value === 'number') { this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( req.body.value, @@ -934,6 +1026,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> add 30sec delta interval return @@ -973,6 +1066,15 @@ export class CourseApi { this.courseInfo.activeRoute.pointIndex, this.courseInfo.activeRoute.reverse >>>>>>> chore: lint +======= + } + } + + // set nextPoint + this.courseInfo.nextPoint.position = this.getRoutePoint( + rte, + this.courseInfo.activeRoute.pointIndex +>>>>>>> init courseApi ) this.courseInfo.nextPoint.type = `RoutePoint` this.courseInfo.nextPoint.href = null @@ -983,6 +1085,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> chore: lint ======= @@ -1004,6 +1107,8 @@ export class CourseApi { res.status(406).send(`Invalid Data`) ======= ======= +>>>>>>> init courseApi +======= >>>>>>> init courseApi const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -1011,6 +1116,7 @@ export class CourseApi { this.courseInfo.previousPoint.type = `VesselPosition` } else { <<<<<<< HEAD +<<<<<<< HEAD >>>>>>> init courseApi ======= return false @@ -1036,6 +1142,8 @@ export class CourseApi { >>>>>>> chore: lint ======= >>>>>>> add 30sec delta interval +======= +>>>>>>> init courseApi return false } } else { @@ -1044,6 +1152,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD this.courseInfo.activeRoute.pointIndex - 1, this.courseInfo.activeRoute.reverse @@ -1061,6 +1170,9 @@ export class CourseApi { this.courseInfo.activeRoute.pointIndex - 1, this.courseInfo.activeRoute.reverse >>>>>>> chore: lint +======= + this.courseInfo.activeRoute.pointIndex - 1 +>>>>>>> init courseApi ) this.courseInfo.previousPoint.type = `RoutePoint` } @@ -1068,6 +1180,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD res.status(200).send('OK') ======= @@ -1092,6 +1205,10 @@ export class CourseApi { ======= res.status(200).send('OK') >>>>>>> add 30sec delta interval +======= + + res.status(200).send(`OK`) +>>>>>>> init courseApi } ) } @@ -1116,6 +1233,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD const newCourse: any = {} Object.assign(newCourse, this.courseInfo) @@ -1294,6 +1412,67 @@ export class CourseApi { ======= // fetch waypoint resource details >>>>>>> chore: lint +======= + // set activeroute + this.courseInfo.activeRoute.href = route.href + + if (typeof route.arrivalCircle === 'number') { + this.setArrivalCircle(route.arrivalCircle) + } + + this.courseInfo.activeRoute.reverse = + typeof route.reverse === 'boolean' ? route.reverse : false + + this.courseInfo.activeRoute.pointIndex = + typeof route.pointIndex === 'number' + ? this.parsePointIndex(route.pointIndex, rte) + : 0 + + this.courseInfo.activeRoute.startTime = new Date().toISOString() + + // set nextPoint + this.courseInfo.nextPoint.position = this.getRoutePoint( + rte, + this.courseInfo.activeRoute.pointIndex + ) + this.courseInfo.nextPoint.type = `RoutePoint` + this.courseInfo.nextPoint.href = null + + // set previousPoint + if (this.courseInfo.activeRoute.pointIndex === 0) { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } else { + this.courseInfo.previousPoint.position = this.getRoutePoint( + rte, + this.courseInfo.activeRoute.pointIndex - 1 + ) + this.courseInfo.previousPoint.type = `RoutePoint` + } + this.courseInfo.previousPoint.href = null + + return true + } + + private async setDestination(dest: Destination): Promise { + // set nextPoint + if (typeof dest.arrivalCircle === 'number') { + this.setArrivalCircle(dest.arrivalCircle) + } + + this.courseInfo.nextPoint.type = + typeof dest.type !== 'undefined' ? dest.type : null + + if (dest.href) { + this.courseInfo.nextPoint.href = dest.href + const href = this.parseHref(dest.href) + if (href) { +>>>>>>> init courseApi try { const r = await this.server.resourcesApi.getResource( href.type, @@ -1302,6 +1481,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (r.position && typeof r.position?.latitude !== 'undefined') { newCourse.nextPoint.position = r.position @@ -1327,6 +1507,10 @@ export class CourseApi { } else { return false >>>>>>> chore: lint +======= + if (r.position && typeof r.position.value?.latitude !== 'undefined') { + this.courseInfo.nextPoint.position = r.position.value +>>>>>>> init courseApi } } catch (err) { return false @@ -1338,6 +1522,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD newCourse.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { @@ -1372,6 +1557,11 @@ export class CourseApi { if (typeof dest.position.latitude !== 'undefined') { newCourse.nextPoint.position = dest.position >>>>>>> add 30sec delta interval +======= + this.courseInfo.nextPoint.href = null + if (typeof dest.position.latitude !== 'undefined') { + this.courseInfo.nextPoint.position = dest.position +>>>>>>> init courseApi } else { return false } @@ -1383,6 +1573,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD try { const position: any = this.server.getSelfPath('navigation.position') @@ -1400,6 +1591,8 @@ export class CourseApi { this.courseInfo = newCourse ======= ======= +>>>>>>> init courseApi +======= >>>>>>> init courseApi const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -1407,6 +1600,7 @@ export class CourseApi { this.courseInfo.previousPoint.type = `VesselPosition` } else { <<<<<<< HEAD +<<<<<<< HEAD ======= try { const position: any = this.server.getSelfPath('navigation.position') @@ -1456,6 +1650,12 @@ export class CourseApi { ======= this.courseInfo = newCourse >>>>>>> add 30sec delta interval +======= + return false + } + this.courseInfo.previousPoint.href = null + +>>>>>>> init courseApi return true } @@ -1475,6 +1675,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD private isValidArrivalCircle(value: number): boolean { return typeof value === 'number' && value >= 0 @@ -1484,6 +1685,8 @@ export class CourseApi { if (typeof index !== 'number' || !rte) { ======= ======= +>>>>>>> init courseApi +======= >>>>>>> init courseApi private setArrivalCircle(value: any): boolean { if (typeof value === 'number' && value >= 0) { @@ -1492,6 +1695,7 @@ export class CourseApi { } else { return false } +<<<<<<< HEAD <<<<<<< HEAD } @@ -1507,10 +1711,13 @@ export class CourseApi { if (typeof index !== 'number' || !rte) { >>>>>>> chore: lint ======= +======= +>>>>>>> init courseApi } private parsePointIndex(index: number, rte: any): number { if (!rte) { +<<<<<<< HEAD >>>>>>> init courseApi ======= private isValidArrivalCircle(value: number): boolean { @@ -1520,6 +1727,8 @@ export class CourseApi { private parsePointIndex(index: number, rte: any): number { if (typeof index !== 'number' || !rte) { >>>>>>> chore: lint +======= +>>>>>>> init courseApi return 0 } if (!rte.feature?.geometry?.coordinates) { @@ -1541,6 +1750,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (!href) { return undefined @@ -1551,6 +1761,11 @@ export class CourseApi { if (href.length === 0) { return undefined } +======= + if (href.length === 0) { + return undefined + } +>>>>>>> init courseApi ======= if (href.length === 0) { return undefined @@ -1561,6 +1776,7 @@ export class CourseApi { } const ref: string[] = href.split('/') <<<<<<< HEAD +<<<<<<< HEAD >>>>>>> init courseApi ======= if (!href) { @@ -1578,6 +1794,8 @@ export class CourseApi { const ref: string[] = href.split('/').slice(-3) >>>>>>> chore: lint +======= +>>>>>>> init courseApi if (ref.length < 3) { return undefined } @@ -1593,6 +1811,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD private getRoutePoint(rte: any, index: number, reverse: boolean) { const pos = reverse @@ -1612,6 +1831,10 @@ export class CourseApi { private getRoutePoint(rte: any, index: number, reverse: boolean) { const pos = reverse >>>>>>> chore: lint +======= + private getRoutePoint(rte: any, index: number) { + const pos = this.courseInfo.activeRoute.reverse +>>>>>>> init courseApi ? rte.feature.geometry.coordinates[ rte.feature.geometry.coordinates.length - (index + 1) ] @@ -1622,6 +1845,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD altitude: pos.length === 3 ? pos[2] : 0 ======= @@ -1636,6 +1860,9 @@ export class CourseApi { ======= altitude: pos.length === 3 ? pos[2] : 0 >>>>>>> chore: lint +======= + altitude: pos.length == 3 ? pos[2] : 0 +>>>>>>> init courseApi } } @@ -1656,6 +1883,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD const values: Array<{ path: string; value: any }> = [] const navPath = [ @@ -1675,6 +1903,10 @@ export class CourseApi { const values: Array<{ path: string; value: any }> = [] const navPath = [ >>>>>>> chore: lint +======= + let values: Array<{path:string, value:any}> = [] + let root = [ +>>>>>>> init courseApi 'navigation.courseGreatCircle', 'navigation.courseRhumbline' ] @@ -1684,6 +1916,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> update detlas ======= @@ -1862,6 +2095,77 @@ export class CourseApi { ======= path: `${navPath[1]}.previousPoint.type`, >>>>>>> chore: lint +======= + path: `${root[0]}.activeRoute.href`, + value: this.courseInfo.activeRoute.href + }) + values.push({ + path: `${root[1]}.activeRoute.href`, + value: this.courseInfo.activeRoute.href + }) + values.push({ + path: `${root[0]}.activeRoute.startTime`, + value: this.courseInfo.activeRoute.startTime + }) + values.push({ + path: `${root[1]}.activeRoute.startTime`, + value: this.courseInfo.activeRoute.startTime + }) + values.push({ + path: `${root[0]}.nextPoint.href`, + value: this.courseInfo.nextPoint.href + }) + values.push({ + path: `${root[1]}.nextPoint.href`, + value: this.courseInfo.nextPoint.href + }) + values.push({ + path: `${root[0]}.nextPoint.position`, + value: this.courseInfo.nextPoint.position + }) + values.push({ + path: `${root[1]}.nextPoint.position`, + value: this.courseInfo.nextPoint.position + }) + values.push({ + path: `${root[0]}.nextPoint.type`, + value: this.courseInfo.nextPoint.type + }) + values.push({ + path: `${root[1]}.nextPoint.type`, + value: this.courseInfo.nextPoint.type + }) + values.push({ + path: `${root[0]}.nextPoint.arrivalCircle`, + value: this.courseInfo.nextPoint.arrivalCircle + }) + values.push({ + path: `${root[1]}.nextPoint.arrivalCircle`, + value: this.courseInfo.nextPoint.arrivalCircle + }) + values.push({ + path: `${root[0]}.previousPoint.href`, + value: this.courseInfo.previousPoint.href + }) + values.push({ + path: `${root[1]}.previousPoint.href`, + value: this.courseInfo.previousPoint.href + }) + values.push({ + path: `${root[0]}.previousPoint.position`, + value: this.courseInfo.previousPoint.position + }) + values.push({ + path: `${root[1]}.previousPoint.position`, + value: this.courseInfo.previousPoint.position + }) + values.push({ + path: `${root[0]}.previousPoint.type`, + value: this.courseInfo.previousPoint.type + }) + values.push({ + path: `${root[1]}.previousPoint.type`, +>>>>>>> init courseApi value: this.courseInfo.previousPoint.type }) @@ -1871,6 +2175,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD values ======= @@ -1885,6 +2190,9 @@ export class CourseApi { ======= values >>>>>>> chore: lint +======= + values: values +>>>>>>> init courseApi } ] } diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index a898b962d..e14c45709 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -111,10 +111,13 @@ <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD "description": "Sets previousPoint value to current vessel location" ======= ======= +>>>>>>> init courseApi +======= >>>>>>> init courseApi "description": "Sets previousPoint value to current vessel location", "requestBody": { @@ -140,6 +143,7 @@ } } <<<<<<< HEAD +<<<<<<< HEAD >>>>>>> init courseApi ======= "description": "Sets previousPoint value to current vessel location" @@ -149,6 +153,8 @@ ======= "description": "Sets previousPoint value to current vessel location" >>>>>>> chore: lint +======= +>>>>>>> init courseApi } }, @@ -221,10 +227,13 @@ <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD "description": "Sets activeRoute, nextPoint & previousPoint values to null" ======= ======= +>>>>>>> init courseApi +======= >>>>>>> init courseApi "description": "Sets activeRoute, nextPoint & previousPoint values to null", "requestBody": { @@ -250,6 +259,7 @@ } } <<<<<<< HEAD +<<<<<<< HEAD >>>>>>> init courseApi ======= "description": "Sets activeRoute, nextPoint & previousPoint values to null" @@ -259,6 +269,8 @@ ======= "description": "Sets activeRoute, nextPoint & previousPoint values to null" >>>>>>> chore: lint +======= +>>>>>>> init courseApi } }, @@ -352,10 +364,13 @@ <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD "description": "Sets activeRoute, nextPoint & previousPoint values to null" ======= ======= +>>>>>>> init courseApi +======= >>>>>>> init courseApi "description": "Sets activeRoute, nextPoint & previousPoint values to null", "requestBody": { @@ -381,6 +396,7 @@ } } <<<<<<< HEAD +<<<<<<< HEAD >>>>>>> init courseApi ======= "description": "Sets activeRoute, nextPoint & previousPoint values to null" @@ -390,6 +406,8 @@ ======= "description": "Sets activeRoute, nextPoint & previousPoint values to null" >>>>>>> chore: lint +======= +>>>>>>> init courseApi } }, From 4ac7b0bb90b83265c5c54b7b6e5becf8570e590d Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 5 Dec 2021 10:26:47 +1030 Subject: [PATCH 363/410] update detlas --- src/api/course/index.ts | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index f9877278b..920fae42f 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -7,6 +7,7 @@ <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD import Debug from 'debug' import { Application, Request, Response } from 'express' ======= @@ -72,6 +73,10 @@ import { Application, Request, Response } from 'express' >>>>>>> update detlas ======= >>>>>>> init courseApi +======= +import Debug from 'debug' +import { Application, Request, Response } from 'express' +>>>>>>> update detlas const debug = Debug('signalk:courseApi') @@ -82,6 +87,7 @@ const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/cou <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD const DELTA_INTERVAL: number = 30000 ======= @@ -105,6 +111,8 @@ const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' const API_METHODS: string[] = [] >>>>>>> init courseApi +======= +>>>>>>> update detlas interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void @@ -119,6 +127,7 @@ interface CourseApplication extends Application { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD securityStrategy: { shouldAllowPut: ( @@ -176,6 +185,9 @@ interface CourseApplication extends Application { >>>>>>> enable put processing ======= >>>>>>> init courseApi +======= + registerPutHandler: (context:string, path:string, cb:any) => any +>>>>>>> update detlas resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -196,6 +208,7 @@ interface Destination extends DestinationBase { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> init courseApi @@ -204,6 +217,8 @@ interface Destination extends DestinationBase { ======= >>>>>>> init courseApi +======= +>>>>>>> update detlas interface ActiveRoute extends DestinationBase { pointIndex?: number reverse?: boolean @@ -357,11 +372,14 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> enable put processing ======= ======= ======= +>>>>>>> update detlas +======= >>>>>>> update detlas // if(this.server.registerPutHandler) { @@ -373,6 +391,7 @@ export class CourseApi { ); } +<<<<<<< HEAD <<<<<<< HEAD >>>>>>> update detlas ======= @@ -501,12 +520,18 @@ export class CourseApi { // set previousPoint to vessel position >>>>>>> init courseApi ======= +======= +>>>>>>> update detlas // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { + //test for active destination + if (!this.courseInfo.nextPoint.position) { + return + } // set previousPoint to vessel position >>>>>>> init courseApi const position: any = this.server.getSelfPath('navigation.position') @@ -1213,6 +1238,12 @@ export class CourseApi { ) } + private handleCourseApiPut(context:string, path:string, value:any, cb:any) { + + debug('** PUT handler **') + return undefined + } + private async activateRoute(route: ActiveRoute): Promise { let rte: any @@ -1917,6 +1948,9 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> update detlas ======= >>>>>>> update detlas ======= @@ -1929,6 +1963,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD path: `${navPath[0]}.activeRoute.href`, ======= @@ -2096,6 +2131,8 @@ export class CourseApi { path: `${navPath[1]}.previousPoint.type`, >>>>>>> chore: lint ======= +======= +>>>>>>> update detlas path: `${root[0]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) From 4dbfc86ec2700b8d198b29c16bf9c53a0d9dff6d Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 4 Dec 2021 17:12:14 +1030 Subject: [PATCH 364/410] init courseApi --- src/api/course/index.ts | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 920fae42f..b77aa473a 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -8,6 +8,7 @@ <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD import Debug from 'debug' import { Application, Request, Response } from 'express' ======= @@ -77,6 +78,17 @@ import { Application, Request, Response } from 'express' import Debug from 'debug' import { Application, Request, Response } from 'express' >>>>>>> update detlas +======= + +import { + ResourceProvider, + ResourceProviderMethods, + SignalKResourceType +} from '@signalk/server-api' +import Debug from 'debug' +import { Application, Request, Response } from 'express' +import { v4 as uuidv4 } from 'uuid' +>>>>>>> init courseApi const debug = Debug('signalk:courseApi') @@ -114,6 +126,10 @@ const API_METHODS: string[] = [] ======= >>>>>>> update detlas +const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' + +const API_METHODS: string[] = [] + interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any @@ -128,6 +144,7 @@ interface CourseApplication extends Application { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD securityStrategy: { shouldAllowPut: ( @@ -188,6 +205,9 @@ interface CourseApplication extends Application { ======= registerPutHandler: (context:string, path:string, cb:any) => any >>>>>>> update detlas +======= + +>>>>>>> init courseApi resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -373,6 +393,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> enable put processing ======= @@ -522,16 +543,14 @@ export class CourseApi { ======= ======= >>>>>>> update detlas +======= +>>>>>>> init courseApi // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { - //test for active destination - if (!this.courseInfo.nextPoint.position) { - return - } // set previousPoint to vessel position >>>>>>> init courseApi const position: any = this.server.getSelfPath('navigation.position') @@ -1238,12 +1257,6 @@ export class CourseApi { ) } - private handleCourseApiPut(context:string, path:string, value:any, cb:any) { - - debug('** PUT handler **') - return undefined - } - private async activateRoute(route: ActiveRoute): Promise { let rte: any From e7740fef641293b70b69416e78763653ad8e85bc Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 5 Dec 2021 10:26:47 +1030 Subject: [PATCH 365/410] update detlas --- src/api/course/index.ts | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index b77aa473a..2dd8f439e 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -9,6 +9,7 @@ <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD import Debug from 'debug' import { Application, Request, Response } from 'express' ======= @@ -89,6 +90,10 @@ import Debug from 'debug' import { Application, Request, Response } from 'express' import { v4 as uuidv4 } from 'uuid' >>>>>>> init courseApi +======= +import Debug from 'debug' +import { Application, Request, Response } from 'express' +>>>>>>> update detlas const debug = Debug('signalk:courseApi') @@ -126,10 +131,6 @@ const API_METHODS: string[] = [] ======= >>>>>>> update detlas -const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' - -const API_METHODS: string[] = [] - interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any @@ -145,6 +146,7 @@ interface CourseApplication extends Application { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD securityStrategy: { shouldAllowPut: ( @@ -208,6 +210,9 @@ interface CourseApplication extends Application { ======= >>>>>>> init courseApi +======= + registerPutHandler: (context:string, path:string, cb:any) => any +>>>>>>> update detlas resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -394,6 +399,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> enable put processing ======= @@ -401,6 +407,8 @@ export class CourseApi { ======= >>>>>>> update detlas ======= +>>>>>>> update detlas +======= >>>>>>> update detlas // if(this.server.registerPutHandler) { @@ -412,6 +420,7 @@ export class CourseApi { ); } +<<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD >>>>>>> update detlas @@ -545,12 +554,18 @@ export class CourseApi { >>>>>>> update detlas ======= >>>>>>> init courseApi +======= +>>>>>>> update detlas // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { + //test for active destination + if (!this.courseInfo.nextPoint.position) { + return + } // set previousPoint to vessel position >>>>>>> init courseApi const position: any = this.server.getSelfPath('navigation.position') @@ -1257,6 +1272,12 @@ export class CourseApi { ) } + private handleCourseApiPut(context:string, path:string, value:any, cb:any) { + + debug('** PUT handler **') + return undefined + } + private async activateRoute(route: ActiveRoute): Promise { let rte: any From 549595e226a952b01b801d08c87001d534913ed2 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 6 Dec 2021 08:39:56 +1030 Subject: [PATCH 366/410] enable put processing --- src/api/course/index.ts | 133 +++++++++++++++++++++++++++++++++++++--- src/put.js | 26 +------- 2 files changed, 125 insertions(+), 34 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 2dd8f439e..960e70430 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -147,6 +147,7 @@ interface CourseApplication extends Application { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD securityStrategy: { shouldAllowPut: ( @@ -169,6 +170,11 @@ interface CourseApplication extends Application { ======= req: Application, >>>>>>> chore: lint +======= + securityStrategy: { + shouldAllowPut: ( + req: any, +>>>>>>> enable put processing context: string, source: any, path: string @@ -176,6 +182,7 @@ interface CourseApplication extends Application { } <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> init courseApi ======= @@ -213,6 +220,8 @@ interface CourseApplication extends Application { ======= registerPutHandler: (context:string, path:string, cb:any) => any >>>>>>> update detlas +======= +>>>>>>> enable put processing resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -376,6 +385,15 @@ export class CourseApi { >>>>>>> init courseApi } + private updateAllowed(): boolean { + return this.server.securityStrategy.shouldAllowPut( + this.server, + 'vessels.self', + null, + 'resources' + ) + } + private initResourceRoutes() { // return current course information this.server.get( @@ -400,6 +418,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> enable put processing ======= @@ -431,12 +450,18 @@ export class CourseApi { // restart / arrivalCircle >>>>>>> init courseApi ======= +======= +>>>>>>> enable put processing this.server.put( `${COURSE_API_PATH}/restart`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/restart`) if (!this.updateAllowed()) { +<<<<<<< HEAD res.status(403).send('Unauthorised') +======= + res.status(403) +>>>>>>> enable put processing return } if (!this.courseInfo.nextPoint.position) { @@ -444,6 +469,7 @@ export class CourseApi { return } // set previousPoint to vessel position +<<<<<<< HEAD try { const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -452,10 +478,19 @@ export class CourseApi { res.status(200).send('OK') } } catch (err) { +======= + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.emitCourseInfo() + res.status(200) + } else { +>>>>>>> enable put processing res.status(406).send(`Vessel position unavailable!`) } } ) +<<<<<<< HEAD >>>>>>> enable put processing this.server.put( @@ -557,9 +592,13 @@ export class CourseApi { ======= >>>>>>> update detlas // restart / arrivalCircle +======= + +>>>>>>> enable put processing this.server.put( - `${COURSE_API_PATH}/:action`, + `${COURSE_API_PATH}/arrivalCircle`, async (req: Request, res: Response) => { +<<<<<<< HEAD debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { //test for active destination @@ -643,6 +682,18 @@ export class CourseApi { >>>>>>> enable put processing ======= >>>>>>> init courseApi +======= + debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) + if (!this.updateAllowed()) { + res.status(403) + return + } + if (this.setArrivalCircle(req.body.value)) { + this.emitCourseInfo() + res.status(200) + } else { + res.status(406).send(`Invalid Data`) +>>>>>>> enable put processing } } ) @@ -656,6 +707,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (!this.updateAllowed()) { res.status(403).send('Unauthorised') @@ -681,6 +733,12 @@ export class CourseApi { ======= >>>>>>> init courseApi +======= + if (!this.updateAllowed()) { + res.status(403) + return + } +>>>>>>> enable put processing if (!req.body.value) { res.status(406).send(`Invalid Data`) return @@ -694,6 +752,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD res.status(200).send('OK') ======= @@ -717,6 +776,9 @@ export class CourseApi { ======= res.status(200).send(`Destination set successfully.`) >>>>>>> init courseApi +======= + res.status(200) +>>>>>>> enable put processing } else { this.clearDestination() this.emitCourseInfo() @@ -734,6 +796,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (!this.updateAllowed()) { res.status(403).send('Unauthorised') @@ -782,14 +845,24 @@ export class CourseApi { // set activeRoute >>>>>>> enable put processing ======= +======= + if (!this.updateAllowed()) { + res.status(403) + return + } +>>>>>>> enable put processing this.clearDestination() this.emitCourseInfo() - res.status(200).send(`Destination cleared.`) + res.status(200) } ) +<<<<<<< HEAD // set / clear activeRoute >>>>>>> init courseApi +======= + // set activeRoute +>>>>>>> enable put processing this.server.put( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { @@ -798,6 +871,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (!this.updateAllowed()) { res.status(403).send('Unauthorised') @@ -854,6 +928,16 @@ export class CourseApi { >>>>>>> add 30sec delta interval ======= >>>>>>> init courseApi +======= + if (!this.updateAllowed()) { + res.status(403) + return + } + const result = await this.activateRoute(req.body.value) + if (result) { + this.emitCourseInfo() + res.status(200) +>>>>>>> enable put processing } else { this.clearDestination() this.emitCourseInfo() @@ -865,6 +949,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD // clear activeRoute /destination @@ -882,6 +967,10 @@ export class CourseApi { >>>>>>> enable put processing ======= >>>>>>> init courseApi +======= + + // clear activeRoute /destination +>>>>>>> enable put processing this.server.delete( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { @@ -891,6 +980,9 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> enable put processing ======= >>>>>>> enable put processing ======= @@ -900,6 +992,7 @@ export class CourseApi { return } <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD this.clearDestination() this.emitCourseInfo() @@ -939,6 +1032,11 @@ export class CourseApi { >>>>>>> add 30sec delta interval ======= >>>>>>> init courseApi +======= + this.clearDestination() + this.emitCourseInfo() + res.status(200) +>>>>>>> enable put processing } ) @@ -950,6 +1048,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (!this.updateAllowed()) { res.status(403).send('Unauthorised') @@ -986,6 +1085,13 @@ export class CourseApi { ======= >>>>>>> init courseApi +======= + if (!this.updateAllowed()) { + res.status(403) + return + } + // fetch route data +>>>>>>> enable put processing if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return @@ -993,6 +1099,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> init courseApi @@ -1001,6 +1108,8 @@ export class CourseApi { ======= >>>>>>> init courseApi +======= +>>>>>>> enable put processing const rte = await this.getRoute(this.courseInfo.activeRoute.href) if (!rte) { res.status(406).send(`Invalid Data`) @@ -1011,6 +1120,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (req.params.action === 'nextPoint') { ======= @@ -1028,6 +1138,9 @@ export class CourseApi { ======= if (req.params.nextPoint) { >>>>>>> init courseApi +======= + if (req.params.action === 'nextPoint') { +>>>>>>> enable put processing if ( typeof req.body.value === 'number' && (req.body.value === 1 || req.body.value === -1) @@ -1072,8 +1185,12 @@ export class CourseApi { ======= } } +<<<<<<< HEAD if (req.params.pointIndex) { >>>>>>> init courseApi +======= + if (req.params.action === 'pointIndex') { +>>>>>>> enable put processing if (typeof req.body.value === 'number') { this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( req.body.value, @@ -1129,7 +1246,7 @@ export class CourseApi { } } - // set nextPoint + // set new destination this.courseInfo.nextPoint.position = this.getRoutePoint( rte, this.courseInfo.activeRoute.pointIndex @@ -1256,6 +1373,7 @@ export class CourseApi { >>>>>>> add 30sec delta interval ======= +<<<<<<< HEAD res.status(200).send(`OK`) >>>>>>> init courseApi ======= @@ -1268,16 +1386,13 @@ export class CourseApi { res.status(200).send(`OK`) >>>>>>> init courseApi +======= + res.status(200) +>>>>>>> enable put processing } ) } - private handleCourseApiPut(context:string, path:string, value:any, cb:any) { - - debug('** PUT handler **') - return undefined - } - private async activateRoute(route: ActiveRoute): Promise { let rte: any diff --git a/src/put.js b/src/put.js index 5498f51b0..5b0fdd114 100644 --- a/src/put.js +++ b/src/put.js @@ -32,40 +32,16 @@ module.exports = { app.put(apiPathPrefix + '*', function(req, res, next) { // ** ignore resources paths ** -<<<<<<< HEAD -<<<<<<< HEAD if (req.path.split('/')[4] === 'resources') { next() return } -<<<<<<< HEAD -<<<<<<< HEAD -======= - if(req.path.split('/')[4]==='resources') { - next() - return - } ->>>>>>> Add Signal K standard resource path handling -======= - if (req.path.split('/')[4] === 'resources') { - next() - return - } ->>>>>>> chore: linted -======= -======= ->>>>>>> enable put processing - // ** ignore course paths ** if (req.path.indexOf('/navigation/course') !== -1) { next() return } - -<<<<<<< HEAD ->>>>>>> enable put processing -======= ->>>>>>> enable put processing + let path = String(req.path).replace(apiPathPrefix, '') const value = req.body From 57d47bb14d9d2a885c41c571dc2b9e43d448acd5 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 6 Dec 2021 13:34:31 +1030 Subject: [PATCH 367/410] chore: lint --- src/api/course/index.ts | 238 ++++++++++++++++++++++++++++-------- src/api/course/openApi.json | 12 ++ 2 files changed, 201 insertions(+), 49 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 960e70430..f15a4cea5 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -152,6 +152,7 @@ interface CourseApplication extends Application { securityStrategy: { shouldAllowPut: ( req: Application, +<<<<<<< HEAD ======= securityStrategy: { shouldAllowPut: ( @@ -175,6 +176,8 @@ interface CourseApplication extends Application { shouldAllowPut: ( req: any, >>>>>>> enable put processing +======= +>>>>>>> chore: lint context: string, source: any, path: string @@ -390,7 +393,7 @@ export class CourseApi { this.server, 'vessels.self', null, - 'resources' + 'navigation.course' ) } @@ -517,6 +520,7 @@ export class CourseApi { } // set previousPoint to vessel position <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD try { ======= @@ -548,14 +552,22 @@ export class CourseApi { res.status(200) } else { ======= +======= +>>>>>>> chore: lint try { const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.emitCourseInfo() +<<<<<<< HEAD res.status(200).send('OK') } } catch (err) { +>>>>>>> chore: lint +======= + res.status(200) + } + } catch (err) { >>>>>>> chore: lint res.status(406).send(`Vessel position unavailable!`) } @@ -688,7 +700,8 @@ export class CourseApi { res.status(403) return } - if (this.setArrivalCircle(req.body.value)) { + if (this.isValidArrivalCircle(req.body.value)) { + this.courseInfo.nextPoint.arrivalCircle = req.body.value this.emitCourseInfo() res.status(200) } else { @@ -1090,8 +1103,12 @@ export class CourseApi { res.status(403) return } +<<<<<<< HEAD // fetch route data >>>>>>> enable put processing +======= + // fetch active route data +>>>>>>> chore: lint if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return @@ -1228,6 +1245,7 @@ export class CourseApi { // set new destination this.courseInfo.nextPoint.position = this.getRoutePoint( rte, +<<<<<<< HEAD <<<<<<< HEAD this.courseInfo.activeRoute.pointIndex >>>>>>> init courseApi @@ -1518,6 +1536,36 @@ export class CourseApi { if (this.isValidArrivalCircle(route.arrivalCircle as number)) { newCourse.nextPoint.arrivalCircle = route.arrivalCircle } +======= + this.courseInfo.activeRoute.pointIndex, + this.courseInfo.activeRoute.reverse + ) + this.courseInfo.nextPoint.type = `RoutePoint` + this.courseInfo.nextPoint.href = null + + // set previousPoint + if (this.courseInfo.activeRoute.pointIndex === 0) { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } catch (err) { + return false + } + } else { + this.courseInfo.previousPoint.position = this.getRoutePoint( + rte, + this.courseInfo.activeRoute.pointIndex - 1, + this.courseInfo.activeRoute.reverse + ) + this.courseInfo.previousPoint.type = `RoutePoint` + } + this.courseInfo.previousPoint.href = null +>>>>>>> chore: lint newCourse.activeRoute.startTime = new Date().toISOString() @@ -1578,6 +1626,7 @@ export class CourseApi { newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null +<<<<<<< HEAD if (dest.href) { newCourse.href = dest.href const href = this.parseHref(dest.href) @@ -1593,66 +1642,85 @@ export class CourseApi { // fetch waypoint resource details >>>>>>> chore: lint ======= +======= + const newDest: any = {} + Object.assign(newDest, this.courseInfo) + +>>>>>>> chore: lint // set activeroute - this.courseInfo.activeRoute.href = route.href + newDest.activeRoute.href = route.href - if (typeof route.arrivalCircle === 'number') { - this.setArrivalCircle(route.arrivalCircle) + if (this.isValidArrivalCircle(route.arrivalCircle as number)) { + newDest.nextPoint.arrivalCircle = route.arrivalCircle } - this.courseInfo.activeRoute.reverse = - typeof route.reverse === 'boolean' ? route.reverse : false + newDest.activeRoute.startTime = new Date().toISOString() - this.courseInfo.activeRoute.pointIndex = - typeof route.pointIndex === 'number' - ? this.parsePointIndex(route.pointIndex, rte) - : 0 + if (typeof route.reverse === 'boolean') { + newDest.activeRoute.reverse = route.reverse + } - this.courseInfo.activeRoute.startTime = new Date().toISOString() + newDest.activeRoute.pointIndex = this.parsePointIndex( + route.pointIndex as number, + rte + ) // set nextPoint - this.courseInfo.nextPoint.position = this.getRoutePoint( + newDest.nextPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex + newDest.activeRoute.pointIndex, + newDest.activeRoute.reverse ) - this.courseInfo.nextPoint.type = `RoutePoint` - this.courseInfo.nextPoint.href = null + newDest.nextPoint.type = `RoutePoint` + newDest.nextPoint.href = null // set previousPoint - if (this.courseInfo.activeRoute.pointIndex === 0) { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + if (newDest.activeRoute.pointIndex === 0) { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } catch (err) { return false } } else { - this.courseInfo.previousPoint.position = this.getRoutePoint( + newDest.previousPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex - 1 + newDest.activeRoute.pointIndex - 1, + newDest.activeRoute.reverse ) - this.courseInfo.previousPoint.type = `RoutePoint` + newDest.previousPoint.type = `RoutePoint` } - this.courseInfo.previousPoint.href = null + newDest.previousPoint.href = null + this.courseInfo = newDest return true } - private async setDestination(dest: Destination): Promise { + private async setDestination(dest: any): Promise { + const newDest: any = {} + Object.assign(newDest, this.courseInfo) + // set nextPoint - if (typeof dest.arrivalCircle === 'number') { - this.setArrivalCircle(dest.arrivalCircle) + if (this.isValidArrivalCircle(dest.arrivalCircle)) { + newDest.nextPoint.arrivalCircle = dest.arrivalCircle } - this.courseInfo.nextPoint.type = - typeof dest.type !== 'undefined' ? dest.type : null + newDest.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - this.courseInfo.nextPoint.href = dest.href + newDest.href = dest.href const href = this.parseHref(dest.href) if (href) { +<<<<<<< HEAD >>>>>>> init courseApi +======= + // fetch waypoint resource details +>>>>>>> chore: lint try { const r = await this.server.resourcesApi.getResource( href.type, @@ -1662,6 +1730,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (r.position && typeof r.position?.latitude !== 'undefined') { newCourse.nextPoint.position = r.position @@ -1691,6 +1760,12 @@ export class CourseApi { if (r.position && typeof r.position.value?.latitude !== 'undefined') { this.courseInfo.nextPoint.position = r.position.value >>>>>>> init courseApi +======= + if (r.position && typeof r.position?.latitude !== 'undefined') { + newDest.nextPoint.position = r.position + } else { + return false +>>>>>>> chore: lint } } catch (err) { return false @@ -1703,6 +1778,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD newCourse.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { @@ -1742,6 +1818,11 @@ export class CourseApi { if (typeof dest.position.latitude !== 'undefined') { this.courseInfo.nextPoint.position = dest.position >>>>>>> init courseApi +======= + newDest.nextPoint.href = null + if (typeof dest.position.latitude !== 'undefined') { + newDest.nextPoint.position = dest.position +>>>>>>> chore: lint } else { return false } @@ -1754,6 +1835,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD try { const position: any = this.server.getSelfPath('navigation.position') @@ -1831,11 +1913,26 @@ export class CourseApi { this.courseInfo = newCourse >>>>>>> add 30sec delta interval ======= +======= + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + newDest.previousPoint.position = position.value + newDest.previousPoint.type = `VesselPosition` + } else { + return false + } + newDest.previousPoint.href = null + } catch (err) { +>>>>>>> chore: lint return false } - this.courseInfo.previousPoint.href = null +<<<<<<< HEAD >>>>>>> init courseApi +======= + this.courseInfo = newDest +>>>>>>> chore: lint return true } @@ -1856,6 +1953,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD private isValidArrivalCircle(value: number): boolean { return typeof value === 'number' && value >= 0 @@ -1909,6 +2007,14 @@ export class CourseApi { >>>>>>> chore: lint ======= >>>>>>> init courseApi +======= + private isValidArrivalCircle(value: number): boolean { + return typeof value === 'number' && value >= 0 + } + + private parsePointIndex(index: number, rte: any): number { + if (typeof index !== 'number' || !rte) { +>>>>>>> chore: lint return 0 } if (!rte.feature?.geometry?.coordinates) { @@ -1931,6 +2037,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (!href) { return undefined @@ -1976,6 +2083,13 @@ export class CourseApi { >>>>>>> chore: lint ======= >>>>>>> init courseApi +======= + if (!href) { + return undefined + } + + const ref: string[] = href.split('/').slice(-3) +>>>>>>> chore: lint if (ref.length < 3) { return undefined } @@ -1992,6 +2106,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD private getRoutePoint(rte: any, index: number, reverse: boolean) { const pos = reverse @@ -2015,6 +2130,10 @@ export class CourseApi { private getRoutePoint(rte: any, index: number) { const pos = this.courseInfo.activeRoute.reverse >>>>>>> init courseApi +======= + private getRoutePoint(rte: any, index: number, reverse: boolean) { + const pos = reverse +>>>>>>> chore: lint ? rte.feature.geometry.coordinates[ rte.feature.geometry.coordinates.length - (index + 1) ] @@ -2026,6 +2145,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD altitude: pos.length === 3 ? pos[2] : 0 ======= @@ -2043,6 +2163,9 @@ export class CourseApi { ======= altitude: pos.length == 3 ? pos[2] : 0 >>>>>>> init courseApi +======= + altitude: pos.length === 3 ? pos[2] : 0 +>>>>>>> chore: lint } } @@ -2064,6 +2187,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD const values: Array<{ path: string; value: any }> = [] const navPath = [ @@ -2087,6 +2211,10 @@ export class CourseApi { let values: Array<{path:string, value:any}> = [] let root = [ >>>>>>> init courseApi +======= + const values: Array<{ path: string; value: any }> = [] + const navPath = [ +>>>>>>> chore: lint 'navigation.courseGreatCircle', 'navigation.courseRhumbline' ] @@ -2113,6 +2241,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD path: `${navPath[0]}.activeRoute.href`, ======= @@ -2283,75 +2412,82 @@ export class CourseApi { ======= >>>>>>> update detlas path: `${root[0]}.activeRoute.href`, +======= + path: `${navPath[0]}.activeRoute.href`, +>>>>>>> chore: lint value: this.courseInfo.activeRoute.href }) values.push({ - path: `${root[1]}.activeRoute.href`, + path: `${navPath[1]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${root[0]}.activeRoute.startTime`, + path: `${navPath[0]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${root[1]}.activeRoute.startTime`, + path: `${navPath[1]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${root[0]}.nextPoint.href`, + path: `${navPath[0]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${root[1]}.nextPoint.href`, + path: `${navPath[1]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${root[0]}.nextPoint.position`, + path: `${navPath[0]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${root[1]}.nextPoint.position`, + path: `${navPath[1]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${root[0]}.nextPoint.type`, + path: `${navPath[0]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${root[1]}.nextPoint.type`, + path: `${navPath[1]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${root[0]}.nextPoint.arrivalCircle`, + path: `${navPath[0]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${root[1]}.nextPoint.arrivalCircle`, + path: `${navPath[1]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${root[0]}.previousPoint.href`, + path: `${navPath[0]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${root[1]}.previousPoint.href`, + path: `${navPath[1]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${root[0]}.previousPoint.position`, + path: `${navPath[0]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${root[1]}.previousPoint.position`, + path: `${navPath[1]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${root[0]}.previousPoint.type`, + path: `${navPath[0]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) values.push({ +<<<<<<< HEAD path: `${root[1]}.previousPoint.type`, >>>>>>> init courseApi +======= + path: `${navPath[1]}.previousPoint.type`, +>>>>>>> chore: lint value: this.courseInfo.previousPoint.type }) @@ -2362,6 +2498,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD values ======= @@ -2379,6 +2516,9 @@ export class CourseApi { ======= values: values >>>>>>> init courseApi +======= + values +>>>>>>> chore: lint } ] } diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index e14c45709..011ff2e15 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -112,6 +112,7 @@ <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD "description": "Sets previousPoint value to current vessel location" ======= @@ -155,6 +156,9 @@ >>>>>>> chore: lint ======= >>>>>>> init courseApi +======= + "description": "Sets previousPoint value to current vessel location" +>>>>>>> chore: lint } }, @@ -228,6 +232,7 @@ <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD "description": "Sets activeRoute, nextPoint & previousPoint values to null" ======= @@ -271,6 +276,9 @@ >>>>>>> chore: lint ======= >>>>>>> init courseApi +======= + "description": "Sets activeRoute, nextPoint & previousPoint values to null" +>>>>>>> chore: lint } }, @@ -365,6 +373,7 @@ <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD "description": "Sets activeRoute, nextPoint & previousPoint values to null" ======= @@ -408,6 +417,9 @@ >>>>>>> chore: lint ======= >>>>>>> init courseApi +======= + "description": "Sets activeRoute, nextPoint & previousPoint values to null" +>>>>>>> chore: lint } }, From 44d29c81f9f1f9f96ba0e041b8a4494507577c2d Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 8 Dec 2021 09:27:06 +1030 Subject: [PATCH 368/410] add 30sec delta interval --- src/api/course/index.ts | 127 ++++++++++++++++++++++++++++++---------- 1 file changed, 95 insertions(+), 32 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index f15a4cea5..2bda390e9 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -131,6 +131,8 @@ const API_METHODS: string[] = [] ======= >>>>>>> update detlas +const DELTA_INTERVAL: number = 30000 + interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any @@ -325,6 +327,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD setInterval(() => { if (this.courseInfo.nextPoint.position) { @@ -369,11 +372,14 @@ export class CourseApi { ======= >>>>>>> init courseApi ======= +======= +>>>>>>> add 30sec delta interval setInterval( ()=> { if(this.courseInfo.nextPoint.position) { this.emitCourseInfo() } }, DELTA_INTERVAL) +<<<<<<< HEAD >>>>>>> add 30sec delta interval } @@ -386,6 +392,8 @@ export class CourseApi { ) ======= >>>>>>> init courseApi +======= +>>>>>>> add 30sec delta interval } private updateAllowed(): boolean { @@ -460,6 +468,7 @@ export class CourseApi { async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/restart`) if (!this.updateAllowed()) { +<<<<<<< HEAD <<<<<<< HEAD res.status(403).send('Unauthorised') ======= @@ -509,6 +518,9 @@ export class CourseApi { ======= res.status(403) >>>>>>> enable put processing +======= + res.status(403).send('Unauthorised') +>>>>>>> add 30sec delta interval ======= res.status(403).send('Unauthorised') >>>>>>> add 30sec delta interval @@ -559,6 +571,7 @@ export class CourseApi { if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.emitCourseInfo() +<<<<<<< HEAD <<<<<<< HEAD res.status(200).send('OK') } @@ -566,6 +579,9 @@ export class CourseApi { >>>>>>> chore: lint ======= res.status(200) +======= + res.status(200).send('OK') +>>>>>>> add 30sec delta interval } } catch (err) { >>>>>>> chore: lint @@ -697,13 +713,13 @@ export class CourseApi { ======= debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (this.isValidArrivalCircle(req.body.value)) { this.courseInfo.nextPoint.arrivalCircle = req.body.value this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { res.status(406).send(`Invalid Data`) >>>>>>> enable put processing @@ -748,7 +764,7 @@ export class CourseApi { >>>>>>> init courseApi ======= if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } >>>>>>> enable put processing @@ -766,6 +782,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD res.status(200).send('OK') ======= @@ -792,6 +809,9 @@ export class CourseApi { ======= res.status(200) >>>>>>> enable put processing +======= + res.status(200).send('OK') +>>>>>>> add 30sec delta interval } else { this.clearDestination() this.emitCourseInfo() @@ -860,13 +880,13 @@ export class CourseApi { ======= ======= if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } >>>>>>> enable put processing this.clearDestination() this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } ) @@ -943,14 +963,18 @@ export class CourseApi { >>>>>>> init courseApi ======= if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } const result = await this.activateRoute(req.body.value) if (result) { this.emitCourseInfo() +<<<<<<< HEAD res.status(200) >>>>>>> enable put processing +======= + res.status(200).send('OK') +>>>>>>> add 30sec delta interval } else { this.clearDestination() this.emitCourseInfo() @@ -1037,6 +1061,7 @@ export class CourseApi { ======= this.clearDestination() this.emitCourseInfo() +<<<<<<< HEAD <<<<<<< HEAD res.status(200) >>>>>>> enable put processing @@ -1050,6 +1075,9 @@ export class CourseApi { this.emitCourseInfo() res.status(200) >>>>>>> enable put processing +======= + res.status(200).send('OK') +>>>>>>> add 30sec delta interval } ) @@ -1173,6 +1201,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> add 30sec delta interval ======= @@ -1200,6 +1229,9 @@ export class CourseApi { if (req.params.action === 'pointIndex') { >>>>>>> enable put processing ======= +======= + return +>>>>>>> add 30sec delta interval } } <<<<<<< HEAD @@ -1220,11 +1252,15 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> add 30sec delta interval return ======= >>>>>>> init courseApi +======= + return +>>>>>>> add 30sec delta interval } } @@ -1293,6 +1329,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD res.status(406).send(`Invalid Data`) return false @@ -1338,6 +1375,13 @@ export class CourseApi { >>>>>>> add 30sec delta interval ======= >>>>>>> init courseApi +======= + res.status(406).send(`Invalid Data`) + return false + } + } catch (err) { + res.status(406).send(`Invalid Data`) +>>>>>>> add 30sec delta interval return false } } else { @@ -1375,6 +1419,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD res.status(200).send('OK') ======= @@ -1407,6 +1452,9 @@ export class CourseApi { ======= res.status(200) >>>>>>> enable put processing +======= + res.status(200).send('OK') +>>>>>>> add 30sec delta interval } ) } @@ -1432,6 +1480,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD const newCourse: any = {} Object.assign(newCourse, this.courseInfo) @@ -1445,6 +1494,10 @@ export class CourseApi { const newCourse: any = {} Object.assign(newCourse, this.courseInfo) >>>>>>> add 30sec delta interval +======= + const newCourse: any = {} + Object.assign(newCourse, this.courseInfo) +>>>>>>> add 30sec delta interval >>>>>>> chore: lint // set activeroute @@ -1648,34 +1701,34 @@ export class CourseApi { >>>>>>> chore: lint // set activeroute - newDest.activeRoute.href = route.href + newCourse.activeRoute.href = route.href if (this.isValidArrivalCircle(route.arrivalCircle as number)) { - newDest.nextPoint.arrivalCircle = route.arrivalCircle + newCourse.nextPoint.arrivalCircle = route.arrivalCircle } - newDest.activeRoute.startTime = new Date().toISOString() + newCourse.activeRoute.startTime = new Date().toISOString() if (typeof route.reverse === 'boolean') { - newDest.activeRoute.reverse = route.reverse + newCourse.activeRoute.reverse = route.reverse } - newDest.activeRoute.pointIndex = this.parsePointIndex( + newCourse.activeRoute.pointIndex = this.parsePointIndex( route.pointIndex as number, rte ) // set nextPoint - newDest.nextPoint.position = this.getRoutePoint( + newCourse.nextPoint.position = this.getRoutePoint( rte, - newDest.activeRoute.pointIndex, - newDest.activeRoute.reverse + newCourse.activeRoute.pointIndex, + newCourse.activeRoute.reverse ) - newDest.nextPoint.type = `RoutePoint` - newDest.nextPoint.href = null + newCourse.nextPoint.type = `RoutePoint` + newCourse.nextPoint.href = null // set previousPoint - if (newDest.activeRoute.pointIndex === 0) { + if (newCourse.activeRoute.pointIndex === 0) { try { const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -1688,32 +1741,32 @@ export class CourseApi { return false } } else { - newDest.previousPoint.position = this.getRoutePoint( + newCourse.previousPoint.position = this.getRoutePoint( rte, - newDest.activeRoute.pointIndex - 1, - newDest.activeRoute.reverse + newCourse.activeRoute.pointIndex - 1, + newCourse.activeRoute.reverse ) - newDest.previousPoint.type = `RoutePoint` + newCourse.previousPoint.type = `RoutePoint` } - newDest.previousPoint.href = null + newCourse.previousPoint.href = null - this.courseInfo = newDest + this.courseInfo = newCourse return true } private async setDestination(dest: any): Promise { - const newDest: any = {} - Object.assign(newDest, this.courseInfo) + const newCourse: any = {} + Object.assign(newCourse, this.courseInfo) // set nextPoint if (this.isValidArrivalCircle(dest.arrivalCircle)) { - newDest.nextPoint.arrivalCircle = dest.arrivalCircle + newCourse.nextPoint.arrivalCircle = dest.arrivalCircle } - newDest.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null + newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - newDest.href = dest.href + newCourse.href = dest.href const href = this.parseHref(dest.href) if (href) { <<<<<<< HEAD @@ -1762,7 +1815,7 @@ export class CourseApi { >>>>>>> init courseApi ======= if (r.position && typeof r.position?.latitude !== 'undefined') { - newDest.nextPoint.position = r.position + newCourse.nextPoint.position = r.position } else { return false >>>>>>> chore: lint @@ -1779,6 +1832,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD newCourse.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { @@ -1823,6 +1877,11 @@ export class CourseApi { if (typeof dest.position.latitude !== 'undefined') { newDest.nextPoint.position = dest.position >>>>>>> chore: lint +======= + newCourse.nextPoint.href = null + if (typeof dest.position.latitude !== 'undefined') { + newCourse.nextPoint.position = dest.position +>>>>>>> add 30sec delta interval } else { return false } @@ -1917,22 +1976,26 @@ export class CourseApi { try { const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { - newDest.previousPoint.position = position.value - newDest.previousPoint.type = `VesselPosition` + newCourse.previousPoint.position = position.value + newCourse.previousPoint.type = `VesselPosition` } else { return false } - newDest.previousPoint.href = null + newCourse.previousPoint.href = null } catch (err) { >>>>>>> chore: lint return false } +<<<<<<< HEAD <<<<<<< HEAD >>>>>>> init courseApi ======= this.courseInfo = newDest >>>>>>> chore: lint +======= + this.courseInfo = newCourse +>>>>>>> add 30sec delta interval return true } From 3aa6a250b9be7a141e48d5dec33da76e7f768076 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 12 Dec 2021 14:29:38 +1030 Subject: [PATCH 369/410] chore: lint --- src/api/course/index.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 2bda390e9..05dea8146 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -131,7 +131,7 @@ const API_METHODS: string[] = [] ======= >>>>>>> update detlas -const DELTA_INTERVAL: number = 30000 +const DELTA_INTERVAL: number = 30000 interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void @@ -328,6 +328,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD setInterval(() => { if (this.courseInfo.nextPoint.position) { @@ -376,6 +377,10 @@ export class CourseApi { >>>>>>> add 30sec delta interval setInterval( ()=> { if(this.courseInfo.nextPoint.position) { +======= + setInterval(() => { + if (this.courseInfo.nextPoint.position) { +>>>>>>> chore: lint this.emitCourseInfo() } }, DELTA_INTERVAL) @@ -1763,7 +1768,8 @@ export class CourseApi { newCourse.nextPoint.arrivalCircle = dest.arrivalCircle } - newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null + newCourse.nextPoint.type = + typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { newCourse.href = dest.href From 7b461eaefaa367fce353b1c35b3e668cf8d0f315 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 4 Dec 2021 17:12:14 +1030 Subject: [PATCH 370/410] init courseApi --- src/api/course/index.ts | 308 ++++++++++++++++++++++++++++++++++++ src/api/course/openApi.json | 18 +++ 2 files changed, 326 insertions(+) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 05dea8146..ff71ee42b 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -10,6 +10,7 @@ <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD import Debug from 'debug' import { Application, Request, Response } from 'express' ======= @@ -81,6 +82,8 @@ import { Application, Request, Response } from 'express' >>>>>>> update detlas ======= +======= +>>>>>>> init courseApi import { ResourceProvider, ResourceProviderMethods, @@ -89,11 +92,14 @@ import { import Debug from 'debug' import { Application, Request, Response } from 'express' import { v4 as uuidv4 } from 'uuid' +<<<<<<< HEAD >>>>>>> init courseApi ======= import Debug from 'debug' import { Application, Request, Response } from 'express' >>>>>>> update detlas +======= +>>>>>>> init courseApi const debug = Debug('signalk:courseApi') @@ -105,6 +111,7 @@ const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/cou <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD const DELTA_INTERVAL: number = 30000 ======= @@ -132,6 +139,11 @@ const API_METHODS: string[] = [] >>>>>>> update detlas const DELTA_INTERVAL: number = 30000 +======= +const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' + +const API_METHODS: string[] = [] +>>>>>>> init courseApi interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void @@ -150,6 +162,7 @@ interface CourseApplication extends Application { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD securityStrategy: { shouldAllowPut: ( @@ -227,6 +240,8 @@ interface CourseApplication extends Application { >>>>>>> update detlas ======= >>>>>>> enable put processing +======= +>>>>>>> init courseApi resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -248,6 +263,7 @@ interface Destination extends DestinationBase { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> init courseApi @@ -258,6 +274,9 @@ interface Destination extends DestinationBase { >>>>>>> init courseApi ======= >>>>>>> update detlas +======= + +>>>>>>> init courseApi interface ActiveRoute extends DestinationBase { pointIndex?: number reverse?: boolean @@ -329,6 +348,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD setInterval(() => { if (this.courseInfo.nextPoint.position) { @@ -408,6 +428,8 @@ export class CourseApi { null, 'navigation.course' ) +======= +>>>>>>> init courseApi } private initResourceRoutes() { @@ -435,6 +457,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> enable put processing ======= @@ -639,6 +662,15 @@ export class CourseApi { return } // set previousPoint to vessel position +>>>>>>> init courseApi +======= + // restart / arrivalCircle + this.server.put( + `${COURSE_API_PATH}/:action`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/:action`) + if (req.params.restart) { + // set previousPoint to vessel position >>>>>>> init courseApi const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -646,6 +678,7 @@ export class CourseApi { this.emitCourseInfo() <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD res.status(200).send('OK') ======= @@ -664,6 +697,8 @@ export class CourseApi { res.status(406).send(`Invalid Data`) ======= ======= +>>>>>>> init courseApi +======= >>>>>>> init courseApi res.status(200).send(`Course restarted.`) } else { @@ -671,6 +706,9 @@ export class CourseApi { } } <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> init courseApi ======= >>>>>>> init courseApi ======= @@ -684,6 +722,7 @@ export class CourseApi { } <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD >>>>>>> init courseApi ======= debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) @@ -728,6 +767,8 @@ export class CourseApi { } else { res.status(406).send(`Invalid Data`) >>>>>>> enable put processing +======= +>>>>>>> init courseApi } } ) @@ -742,6 +783,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (!this.updateAllowed()) { res.status(403).send('Unauthorised') @@ -773,6 +815,9 @@ export class CourseApi { return } >>>>>>> enable put processing +======= + +>>>>>>> init courseApi if (!req.body.value) { res.status(406).send(`Invalid Data`) return @@ -788,6 +833,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD res.status(200).send('OK') ======= @@ -817,6 +863,9 @@ export class CourseApi { ======= res.status(200).send('OK') >>>>>>> add 30sec delta interval +======= + res.status(200).send(`Destination set successfully.`) +>>>>>>> init courseApi } else { this.clearDestination() this.emitCourseInfo() @@ -835,6 +884,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (!this.updateAllowed()) { res.status(403).send('Unauthorised') @@ -901,6 +951,15 @@ export class CourseApi { ======= // set activeRoute >>>>>>> enable put processing +======= + this.clearDestination() + this.emitCourseInfo() + res.status(200).send(`Destination cleared.`) + } + ) + + // set / clear activeRoute +>>>>>>> init courseApi this.server.put( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { @@ -910,6 +969,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (!this.updateAllowed()) { res.status(403).send('Unauthorised') @@ -942,6 +1002,8 @@ export class CourseApi { >>>>>>> add 30sec delta interval ======= ======= +>>>>>>> init courseApi +======= >>>>>>> init courseApi const result = await this.activateRoute(req.body.value) @@ -949,6 +1011,7 @@ export class CourseApi { this.emitCourseInfo() res.status(200).send(`Active route set.`) <<<<<<< HEAD +<<<<<<< HEAD >>>>>>> init courseApi ======= if (!this.updateAllowed()) { @@ -980,6 +1043,8 @@ export class CourseApi { ======= res.status(200).send('OK') >>>>>>> add 30sec delta interval +======= +>>>>>>> init courseApi } else { this.clearDestination() this.emitCourseInfo() @@ -992,6 +1057,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD // clear activeRoute /destination @@ -1013,6 +1079,8 @@ export class CourseApi { // clear activeRoute /destination >>>>>>> enable put processing +======= +>>>>>>> init courseApi this.server.delete( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { @@ -1023,6 +1091,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> enable put processing ======= @@ -1056,12 +1125,15 @@ export class CourseApi { >>>>>>> add 30sec delta interval ======= ======= +>>>>>>> init courseApi +======= >>>>>>> init courseApi this.clearDestination() this.emitCourseInfo() res.status(200).send(`Active route cleared.`) <<<<<<< HEAD +<<<<<<< HEAD >>>>>>> init courseApi ======= this.clearDestination() @@ -1083,6 +1155,8 @@ export class CourseApi { ======= res.status(200).send('OK') >>>>>>> add 30sec delta interval +======= +>>>>>>> init courseApi } ) @@ -1095,6 +1169,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (!this.updateAllowed()) { res.status(403).send('Unauthorised') @@ -1142,6 +1217,9 @@ export class CourseApi { ======= // fetch active route data >>>>>>> chore: lint +======= + +>>>>>>> init courseApi if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return @@ -1150,6 +1228,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> init courseApi @@ -1160,6 +1239,9 @@ export class CourseApi { >>>>>>> init courseApi ======= >>>>>>> enable put processing +======= + +>>>>>>> init courseApi const rte = await this.getRoute(this.courseInfo.activeRoute.href) if (!rte) { res.status(406).send(`Invalid Data`) @@ -1171,6 +1253,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (req.params.action === 'nextPoint') { ======= @@ -1191,6 +1274,9 @@ export class CourseApi { ======= if (req.params.action === 'nextPoint') { >>>>>>> enable put processing +======= + if (req.params.nextPoint) { +>>>>>>> init courseApi if ( typeof req.body.value === 'number' && (req.body.value === 1 || req.body.value === -1) @@ -1207,6 +1293,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> add 30sec delta interval ======= @@ -1245,6 +1332,11 @@ export class CourseApi { ======= if (req.params.action === 'pointIndex') { >>>>>>> enable put processing +======= + } + } + if (req.params.pointIndex) { +>>>>>>> init courseApi if (typeof req.body.value === 'number') { this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( req.body.value, @@ -1258,6 +1350,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> add 30sec delta interval return @@ -1309,6 +1402,15 @@ export class CourseApi { this.courseInfo.nextPoint.position = this.getRoutePoint( rte, this.courseInfo.activeRoute.pointIndex +>>>>>>> init courseApi +======= + } + } + + // set nextPoint + this.courseInfo.nextPoint.position = this.getRoutePoint( + rte, + this.courseInfo.activeRoute.pointIndex >>>>>>> init courseApi ) this.courseInfo.nextPoint.type = `RoutePoint` @@ -1321,6 +1423,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> chore: lint ======= @@ -1345,6 +1448,8 @@ export class CourseApi { ======= >>>>>>> init courseApi ======= +>>>>>>> init courseApi +======= >>>>>>> init courseApi const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -1353,6 +1458,7 @@ export class CourseApi { } else { <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD >>>>>>> init courseApi ======= return false @@ -1387,6 +1493,8 @@ export class CourseApi { } catch (err) { res.status(406).send(`Invalid Data`) >>>>>>> add 30sec delta interval +======= +>>>>>>> init courseApi return false } } else { @@ -1396,6 +1504,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD this.courseInfo.activeRoute.pointIndex - 1, this.courseInfo.activeRoute.reverse @@ -1413,6 +1522,9 @@ export class CourseApi { this.courseInfo.activeRoute.pointIndex - 1, this.courseInfo.activeRoute.reverse >>>>>>> chore: lint +======= + this.courseInfo.activeRoute.pointIndex - 1 +>>>>>>> init courseApi ======= this.courseInfo.activeRoute.pointIndex - 1 >>>>>>> init courseApi @@ -1425,6 +1537,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD res.status(200).send('OK') ======= @@ -1460,6 +1573,10 @@ export class CourseApi { ======= res.status(200).send('OK') >>>>>>> add 30sec delta interval +======= + + res.status(200).send(`OK`) +>>>>>>> init courseApi } ) } @@ -1486,6 +1603,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD const newCourse: any = {} Object.assign(newCourse, this.courseInfo) @@ -1780,6 +1898,67 @@ export class CourseApi { ======= // fetch waypoint resource details >>>>>>> chore: lint +======= + // set activeroute + this.courseInfo.activeRoute.href = route.href + + if (typeof route.arrivalCircle === 'number') { + this.setArrivalCircle(route.arrivalCircle) + } + + this.courseInfo.activeRoute.reverse = + typeof route.reverse === 'boolean' ? route.reverse : false + + this.courseInfo.activeRoute.pointIndex = + typeof route.pointIndex === 'number' + ? this.parsePointIndex(route.pointIndex, rte) + : 0 + + this.courseInfo.activeRoute.startTime = new Date().toISOString() + + // set nextPoint + this.courseInfo.nextPoint.position = this.getRoutePoint( + rte, + this.courseInfo.activeRoute.pointIndex + ) + this.courseInfo.nextPoint.type = `RoutePoint` + this.courseInfo.nextPoint.href = null + + // set previousPoint + if (this.courseInfo.activeRoute.pointIndex === 0) { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } else { + this.courseInfo.previousPoint.position = this.getRoutePoint( + rte, + this.courseInfo.activeRoute.pointIndex - 1 + ) + this.courseInfo.previousPoint.type = `RoutePoint` + } + this.courseInfo.previousPoint.href = null + + return true + } + + private async setDestination(dest: Destination): Promise { + // set nextPoint + if (typeof dest.arrivalCircle === 'number') { + this.setArrivalCircle(dest.arrivalCircle) + } + + this.courseInfo.nextPoint.type = + typeof dest.type !== 'undefined' ? dest.type : null + + if (dest.href) { + this.courseInfo.nextPoint.href = dest.href + const href = this.parseHref(dest.href) + if (href) { +>>>>>>> init courseApi try { const r = await this.server.resourcesApi.getResource( href.type, @@ -1790,6 +1969,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (r.position && typeof r.position?.latitude !== 'undefined') { newCourse.nextPoint.position = r.position @@ -1825,6 +2005,10 @@ export class CourseApi { } else { return false >>>>>>> chore: lint +======= + if (r.position && typeof r.position.value?.latitude !== 'undefined') { + this.courseInfo.nextPoint.position = r.position.value +>>>>>>> init courseApi } } catch (err) { return false @@ -1839,6 +2023,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD newCourse.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { @@ -1888,6 +2073,11 @@ export class CourseApi { if (typeof dest.position.latitude !== 'undefined') { newCourse.nextPoint.position = dest.position >>>>>>> add 30sec delta interval +======= + this.courseInfo.nextPoint.href = null + if (typeof dest.position.latitude !== 'undefined') { + this.courseInfo.nextPoint.position = dest.position +>>>>>>> init courseApi } else { return false } @@ -1901,6 +2091,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD try { const position: any = this.server.getSelfPath('navigation.position') @@ -1920,6 +2111,8 @@ export class CourseApi { ======= >>>>>>> init courseApi ======= +>>>>>>> init courseApi +======= >>>>>>> init courseApi const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { @@ -1928,6 +2121,7 @@ export class CourseApi { } else { <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= try { const position: any = this.server.getSelfPath('navigation.position') @@ -2002,6 +2196,12 @@ export class CourseApi { ======= this.courseInfo = newCourse >>>>>>> add 30sec delta interval +======= + return false + } + this.courseInfo.previousPoint.href = null + +>>>>>>> init courseApi return true } @@ -2023,6 +2223,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD private isValidArrivalCircle(value: number): boolean { return typeof value === 'number' && value >= 0 @@ -2034,6 +2235,8 @@ export class CourseApi { ======= >>>>>>> init courseApi ======= +>>>>>>> init courseApi +======= >>>>>>> init courseApi private setArrivalCircle(value: any): boolean { if (typeof value === 'number' && value >= 0) { @@ -2043,6 +2246,7 @@ export class CourseApi { return false } <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD } @@ -2059,12 +2263,15 @@ export class CourseApi { >>>>>>> chore: lint ======= ======= +>>>>>>> init courseApi +======= >>>>>>> init courseApi } private parsePointIndex(index: number, rte: any): number { if (!rte) { <<<<<<< HEAD +<<<<<<< HEAD >>>>>>> init courseApi ======= private isValidArrivalCircle(value: number): boolean { @@ -2084,6 +2291,8 @@ export class CourseApi { private parsePointIndex(index: number, rte: any): number { if (typeof index !== 'number' || !rte) { >>>>>>> chore: lint +======= +>>>>>>> init courseApi return 0 } if (!rte.feature?.geometry?.coordinates) { @@ -2107,6 +2316,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (!href) { return undefined @@ -2122,6 +2332,11 @@ export class CourseApi { return undefined } >>>>>>> init courseApi +======= + if (href.length === 0) { + return undefined + } +>>>>>>> init courseApi ======= if (href.length === 0) { return undefined @@ -2133,6 +2348,7 @@ export class CourseApi { const ref: string[] = href.split('/') <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD >>>>>>> init courseApi ======= if (!href) { @@ -2159,6 +2375,8 @@ export class CourseApi { const ref: string[] = href.split('/').slice(-3) >>>>>>> chore: lint +======= +>>>>>>> init courseApi if (ref.length < 3) { return undefined } @@ -2176,6 +2394,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD private getRoutePoint(rte: any, index: number, reverse: boolean) { const pos = reverse @@ -2203,6 +2422,10 @@ export class CourseApi { private getRoutePoint(rte: any, index: number, reverse: boolean) { const pos = reverse >>>>>>> chore: lint +======= + private getRoutePoint(rte: any, index: number) { + const pos = this.courseInfo.activeRoute.reverse +>>>>>>> init courseApi ? rte.feature.geometry.coordinates[ rte.feature.geometry.coordinates.length - (index + 1) ] @@ -2215,6 +2438,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD altitude: pos.length === 3 ? pos[2] : 0 ======= @@ -2235,6 +2459,9 @@ export class CourseApi { ======= altitude: pos.length === 3 ? pos[2] : 0 >>>>>>> chore: lint +======= + altitude: pos.length == 3 ? pos[2] : 0 +>>>>>>> init courseApi } } @@ -2257,6 +2484,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD const values: Array<{ path: string; value: any }> = [] const navPath = [ @@ -2284,6 +2512,10 @@ export class CourseApi { const values: Array<{ path: string; value: any }> = [] const navPath = [ >>>>>>> chore: lint +======= + let values: Array<{path:string, value:any}> = [] + let root = [ +>>>>>>> init courseApi 'navigation.courseGreatCircle', 'navigation.courseRhumbline' ] @@ -2295,6 +2527,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> update detlas ======= @@ -2557,6 +2790,77 @@ export class CourseApi { ======= path: `${navPath[1]}.previousPoint.type`, >>>>>>> chore: lint +======= + path: `${root[0]}.activeRoute.href`, + value: this.courseInfo.activeRoute.href + }) + values.push({ + path: `${root[1]}.activeRoute.href`, + value: this.courseInfo.activeRoute.href + }) + values.push({ + path: `${root[0]}.activeRoute.startTime`, + value: this.courseInfo.activeRoute.startTime + }) + values.push({ + path: `${root[1]}.activeRoute.startTime`, + value: this.courseInfo.activeRoute.startTime + }) + values.push({ + path: `${root[0]}.nextPoint.href`, + value: this.courseInfo.nextPoint.href + }) + values.push({ + path: `${root[1]}.nextPoint.href`, + value: this.courseInfo.nextPoint.href + }) + values.push({ + path: `${root[0]}.nextPoint.position`, + value: this.courseInfo.nextPoint.position + }) + values.push({ + path: `${root[1]}.nextPoint.position`, + value: this.courseInfo.nextPoint.position + }) + values.push({ + path: `${root[0]}.nextPoint.type`, + value: this.courseInfo.nextPoint.type + }) + values.push({ + path: `${root[1]}.nextPoint.type`, + value: this.courseInfo.nextPoint.type + }) + values.push({ + path: `${root[0]}.nextPoint.arrivalCircle`, + value: this.courseInfo.nextPoint.arrivalCircle + }) + values.push({ + path: `${root[1]}.nextPoint.arrivalCircle`, + value: this.courseInfo.nextPoint.arrivalCircle + }) + values.push({ + path: `${root[0]}.previousPoint.href`, + value: this.courseInfo.previousPoint.href + }) + values.push({ + path: `${root[1]}.previousPoint.href`, + value: this.courseInfo.previousPoint.href + }) + values.push({ + path: `${root[0]}.previousPoint.position`, + value: this.courseInfo.previousPoint.position + }) + values.push({ + path: `${root[1]}.previousPoint.position`, + value: this.courseInfo.previousPoint.position + }) + values.push({ + path: `${root[0]}.previousPoint.type`, + value: this.courseInfo.previousPoint.type + }) + values.push({ + path: `${root[1]}.previousPoint.type`, +>>>>>>> init courseApi value: this.courseInfo.previousPoint.type }) @@ -2568,6 +2872,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD values ======= @@ -2588,6 +2893,9 @@ export class CourseApi { ======= values >>>>>>> chore: lint +======= + values: values +>>>>>>> init courseApi } ] } diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index 011ff2e15..dca136e43 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -113,12 +113,15 @@ <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD "description": "Sets previousPoint value to current vessel location" ======= ======= >>>>>>> init courseApi ======= +>>>>>>> init courseApi +======= >>>>>>> init courseApi "description": "Sets previousPoint value to current vessel location", "requestBody": { @@ -145,6 +148,7 @@ } <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD >>>>>>> init courseApi ======= "description": "Sets previousPoint value to current vessel location" @@ -159,6 +163,8 @@ ======= "description": "Sets previousPoint value to current vessel location" >>>>>>> chore: lint +======= +>>>>>>> init courseApi } }, @@ -233,12 +239,15 @@ <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD "description": "Sets activeRoute, nextPoint & previousPoint values to null" ======= ======= >>>>>>> init courseApi ======= +>>>>>>> init courseApi +======= >>>>>>> init courseApi "description": "Sets activeRoute, nextPoint & previousPoint values to null", "requestBody": { @@ -265,6 +274,7 @@ } <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD >>>>>>> init courseApi ======= "description": "Sets activeRoute, nextPoint & previousPoint values to null" @@ -279,6 +289,8 @@ ======= "description": "Sets activeRoute, nextPoint & previousPoint values to null" >>>>>>> chore: lint +======= +>>>>>>> init courseApi } }, @@ -374,12 +386,15 @@ <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD "description": "Sets activeRoute, nextPoint & previousPoint values to null" ======= ======= >>>>>>> init courseApi ======= +>>>>>>> init courseApi +======= >>>>>>> init courseApi "description": "Sets activeRoute, nextPoint & previousPoint values to null", "requestBody": { @@ -406,6 +421,7 @@ } <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD >>>>>>> init courseApi ======= "description": "Sets activeRoute, nextPoint & previousPoint values to null" @@ -420,6 +436,8 @@ ======= "description": "Sets activeRoute, nextPoint & previousPoint values to null" >>>>>>> chore: lint +======= +>>>>>>> init courseApi } }, From dc841e19505cb11cdf1eb565e4787254b991cd45 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 5 Dec 2021 10:26:47 +1030 Subject: [PATCH 371/410] update detlas --- src/api/course/index.ts | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index ff71ee42b..4611831d6 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -11,6 +11,7 @@ <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD import Debug from 'debug' import { Application, Request, Response } from 'express' ======= @@ -100,6 +101,10 @@ import { Application, Request, Response } from 'express' >>>>>>> update detlas ======= >>>>>>> init courseApi +======= +import Debug from 'debug' +import { Application, Request, Response } from 'express' +>>>>>>> update detlas const debug = Debug('signalk:courseApi') @@ -112,6 +117,7 @@ const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/cou <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD const DELTA_INTERVAL: number = 30000 ======= @@ -144,6 +150,8 @@ const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' const API_METHODS: string[] = [] >>>>>>> init courseApi +======= +>>>>>>> update detlas interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void @@ -163,6 +171,7 @@ interface CourseApplication extends Application { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD securityStrategy: { shouldAllowPut: ( @@ -242,6 +251,9 @@ interface CourseApplication extends Application { >>>>>>> enable put processing ======= >>>>>>> init courseApi +======= + registerPutHandler: (context:string, path:string, cb:any) => any +>>>>>>> update detlas resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -264,6 +276,7 @@ interface Destination extends DestinationBase { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> init courseApi @@ -277,6 +290,8 @@ interface Destination extends DestinationBase { ======= >>>>>>> init courseApi +======= +>>>>>>> update detlas interface ActiveRoute extends DestinationBase { pointIndex?: number reverse?: boolean @@ -458,6 +473,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> enable put processing ======= @@ -467,6 +483,8 @@ export class CourseApi { ======= >>>>>>> update detlas ======= +>>>>>>> update detlas +======= >>>>>>> update detlas // if(this.server.registerPutHandler) { @@ -481,6 +499,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD >>>>>>> update detlas ======= >>>>>>> init courseApi @@ -664,12 +683,18 @@ export class CourseApi { // set previousPoint to vessel position >>>>>>> init courseApi ======= +======= +>>>>>>> update detlas // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { + //test for active destination + if (!this.courseInfo.nextPoint.position) { + return + } // set previousPoint to vessel position >>>>>>> init courseApi const position: any = this.server.getSelfPath('navigation.position') @@ -1581,6 +1606,12 @@ export class CourseApi { ) } + private handleCourseApiPut(context:string, path:string, value:any, cb:any) { + + debug('** PUT handler **') + return undefined + } + private async activateRoute(route: ActiveRoute): Promise { let rte: any @@ -2528,6 +2559,9 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> update detlas ======= >>>>>>> update detlas ======= @@ -2544,6 +2578,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD path: `${navPath[0]}.activeRoute.href`, ======= @@ -2791,6 +2826,8 @@ export class CourseApi { path: `${navPath[1]}.previousPoint.type`, >>>>>>> chore: lint ======= +======= +>>>>>>> update detlas path: `${root[0]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) From 511c7d527370f6513f66c002776cc119f978d8c5 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 4 Dec 2021 17:12:14 +1030 Subject: [PATCH 372/410] init courseApi --- src/api/course/index.ts | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 4611831d6..4d36b4d42 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -12,6 +12,7 @@ <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD import Debug from 'debug' import { Application, Request, Response } from 'express' ======= @@ -105,6 +106,17 @@ import { Application, Request, Response } from 'express' import Debug from 'debug' import { Application, Request, Response } from 'express' >>>>>>> update detlas +======= + +import { + ResourceProvider, + ResourceProviderMethods, + SignalKResourceType +} from '@signalk/server-api' +import Debug from 'debug' +import { Application, Request, Response } from 'express' +import { v4 as uuidv4 } from 'uuid' +>>>>>>> init courseApi const debug = Debug('signalk:courseApi') @@ -153,6 +165,10 @@ const API_METHODS: string[] = [] ======= >>>>>>> update detlas +const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' + +const API_METHODS: string[] = [] + interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any @@ -172,6 +188,7 @@ interface CourseApplication extends Application { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD securityStrategy: { shouldAllowPut: ( @@ -254,6 +271,9 @@ interface CourseApplication extends Application { ======= registerPutHandler: (context:string, path:string, cb:any) => any >>>>>>> update detlas +======= + +>>>>>>> init courseApi resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -474,6 +494,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> enable put processing ======= @@ -685,16 +706,14 @@ export class CourseApi { ======= ======= >>>>>>> update detlas +======= +>>>>>>> init courseApi // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { - //test for active destination - if (!this.courseInfo.nextPoint.position) { - return - } // set previousPoint to vessel position >>>>>>> init courseApi const position: any = this.server.getSelfPath('navigation.position') @@ -1606,12 +1625,6 @@ export class CourseApi { ) } - private handleCourseApiPut(context:string, path:string, value:any, cb:any) { - - debug('** PUT handler **') - return undefined - } - private async activateRoute(route: ActiveRoute): Promise { let rte: any From 4e264d41445e9ef9e6285fddbba5171ffd9f98d5 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 5 Dec 2021 10:26:47 +1030 Subject: [PATCH 373/410] update detlas --- src/api/course/index.ts | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 4d36b4d42..39233870b 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -13,6 +13,7 @@ <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD import Debug from 'debug' import { Application, Request, Response } from 'express' ======= @@ -117,6 +118,10 @@ import Debug from 'debug' import { Application, Request, Response } from 'express' import { v4 as uuidv4 } from 'uuid' >>>>>>> init courseApi +======= +import Debug from 'debug' +import { Application, Request, Response } from 'express' +>>>>>>> update detlas const debug = Debug('signalk:courseApi') @@ -165,10 +170,6 @@ const API_METHODS: string[] = [] ======= >>>>>>> update detlas -const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' - -const API_METHODS: string[] = [] - interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any @@ -189,6 +190,7 @@ interface CourseApplication extends Application { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD securityStrategy: { shouldAllowPut: ( @@ -274,6 +276,9 @@ interface CourseApplication extends Application { ======= >>>>>>> init courseApi +======= + registerPutHandler: (context:string, path:string, cb:any) => any +>>>>>>> update detlas resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -495,6 +500,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> enable put processing ======= @@ -506,6 +512,8 @@ export class CourseApi { ======= >>>>>>> update detlas ======= +>>>>>>> update detlas +======= >>>>>>> update detlas // if(this.server.registerPutHandler) { @@ -521,6 +529,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD >>>>>>> update detlas ======= >>>>>>> init courseApi @@ -708,12 +717,18 @@ export class CourseApi { >>>>>>> update detlas ======= >>>>>>> init courseApi +======= +>>>>>>> update detlas // restart / arrivalCircle this.server.put( `${COURSE_API_PATH}/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { + //test for active destination + if (!this.courseInfo.nextPoint.position) { + return + } // set previousPoint to vessel position >>>>>>> init courseApi const position: any = this.server.getSelfPath('navigation.position') @@ -1625,6 +1640,12 @@ export class CourseApi { ) } + private handleCourseApiPut(context:string, path:string, value:any, cb:any) { + + debug('** PUT handler **') + return undefined + } + private async activateRoute(route: ActiveRoute): Promise { let rte: any From 1e54d3a6e16f0cd027351efdbe49d379e5a29985 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 6 Dec 2021 08:39:56 +1030 Subject: [PATCH 374/410] enable put processing --- src/api/course/index.ts | 129 +++++++++++++++++++++++++++++++++++++--- src/put.js | 1 - 2 files changed, 120 insertions(+), 10 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 39233870b..bae7479d4 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -191,6 +191,7 @@ interface CourseApplication extends Application { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD securityStrategy: { shouldAllowPut: ( @@ -221,6 +222,11 @@ interface CourseApplication extends Application { >>>>>>> enable put processing ======= >>>>>>> chore: lint +======= + securityStrategy: { + shouldAllowPut: ( + req: any, +>>>>>>> enable put processing context: string, source: any, path: string @@ -229,6 +235,7 @@ interface CourseApplication extends Application { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> init courseApi ======= @@ -279,6 +286,8 @@ interface CourseApplication extends Application { ======= registerPutHandler: (context:string, path:string, cb:any) => any >>>>>>> update detlas +======= +>>>>>>> enable put processing resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -472,6 +481,15 @@ export class CourseApi { >>>>>>> init courseApi } + private updateAllowed(): boolean { + return this.server.securityStrategy.shouldAllowPut( + this.server, + 'vessels.self', + null, + 'resources' + ) + } + private initResourceRoutes() { // return current course information this.server.get( @@ -501,6 +519,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> enable put processing ======= @@ -539,6 +558,8 @@ export class CourseApi { >>>>>>> init courseApi ======= ======= +>>>>>>> enable put processing +======= >>>>>>> enable put processing this.server.put( `${COURSE_API_PATH}/restart`, @@ -546,8 +567,12 @@ export class CourseApi { debug(`** PUT ${COURSE_API_PATH}/restart`) if (!this.updateAllowed()) { <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD res.status(403).send('Unauthorised') +======= + res.status(403) +>>>>>>> enable put processing ======= res.status(403) >>>>>>> enable put processing @@ -558,6 +583,7 @@ export class CourseApi { return } // set previousPoint to vessel position +<<<<<<< HEAD <<<<<<< HEAD try { const position: any = this.server.getSelfPath('navigation.position') @@ -568,18 +594,24 @@ export class CourseApi { } } catch (err) { ======= +======= +>>>>>>> enable put processing const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.emitCourseInfo() res.status(200) } else { +<<<<<<< HEAD +>>>>>>> enable put processing +======= >>>>>>> enable put processing res.status(406).send(`Vessel position unavailable!`) } } ) <<<<<<< HEAD +<<<<<<< HEAD >>>>>>> enable put processing this.server.put( @@ -720,9 +752,13 @@ export class CourseApi { ======= >>>>>>> update detlas // restart / arrivalCircle +======= + +>>>>>>> enable put processing this.server.put( - `${COURSE_API_PATH}/:action`, + `${COURSE_API_PATH}/arrivalCircle`, async (req: Request, res: Response) => { +<<<<<<< HEAD debug(`** PUT ${COURSE_API_PATH}/:action`) if (req.params.restart) { //test for active destination @@ -828,6 +864,18 @@ export class CourseApi { >>>>>>> enable put processing ======= >>>>>>> init courseApi +======= + debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) + if (!this.updateAllowed()) { + res.status(403) + return + } + if (this.setArrivalCircle(req.body.value)) { + this.emitCourseInfo() + res.status(200) + } else { + res.status(406).send(`Invalid Data`) +>>>>>>> enable put processing } } ) @@ -843,6 +891,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (!this.updateAllowed()) { res.status(403).send('Unauthorised') @@ -877,6 +926,12 @@ export class CourseApi { ======= >>>>>>> init courseApi +======= + if (!this.updateAllowed()) { + res.status(403) + return + } +>>>>>>> enable put processing if (!req.body.value) { res.status(406).send(`Invalid Data`) return @@ -893,6 +948,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD res.status(200).send('OK') ======= @@ -925,6 +981,9 @@ export class CourseApi { ======= res.status(200).send(`Destination set successfully.`) >>>>>>> init courseApi +======= + res.status(200) +>>>>>>> enable put processing } else { this.clearDestination() this.emitCourseInfo() @@ -944,6 +1003,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (!this.updateAllowed()) { res.status(403).send('Unauthorised') @@ -1011,14 +1071,24 @@ export class CourseApi { // set activeRoute >>>>>>> enable put processing ======= +======= + if (!this.updateAllowed()) { + res.status(403) + return + } +>>>>>>> enable put processing this.clearDestination() this.emitCourseInfo() - res.status(200).send(`Destination cleared.`) + res.status(200) } ) +<<<<<<< HEAD // set / clear activeRoute >>>>>>> init courseApi +======= + // set activeRoute +>>>>>>> enable put processing this.server.put( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { @@ -1029,6 +1099,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (!this.updateAllowed()) { res.status(403).send('Unauthorised') @@ -1104,6 +1175,16 @@ export class CourseApi { >>>>>>> add 30sec delta interval ======= >>>>>>> init courseApi +======= + if (!this.updateAllowed()) { + res.status(403) + return + } + const result = await this.activateRoute(req.body.value) + if (result) { + this.emitCourseInfo() + res.status(200) +>>>>>>> enable put processing } else { this.clearDestination() this.emitCourseInfo() @@ -1117,6 +1198,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD // clear activeRoute /destination @@ -1140,6 +1222,10 @@ export class CourseApi { >>>>>>> enable put processing ======= >>>>>>> init courseApi +======= + + // clear activeRoute /destination +>>>>>>> enable put processing this.server.delete( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { @@ -1151,6 +1237,9 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> enable put processing ======= >>>>>>> enable put processing ======= @@ -1163,6 +1252,7 @@ export class CourseApi { } <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD this.clearDestination() this.emitCourseInfo() @@ -1216,6 +1306,11 @@ export class CourseApi { >>>>>>> add 30sec delta interval ======= >>>>>>> init courseApi +======= + this.clearDestination() + this.emitCourseInfo() + res.status(200) +>>>>>>> enable put processing } ) @@ -1229,6 +1324,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (!this.updateAllowed()) { res.status(403).send('Unauthorised') @@ -1266,10 +1362,13 @@ export class CourseApi { >>>>>>> init courseApi ======= +======= +>>>>>>> enable put processing if (!this.updateAllowed()) { res.status(403) return } +<<<<<<< HEAD <<<<<<< HEAD // fetch route data >>>>>>> enable put processing @@ -1279,6 +1378,9 @@ export class CourseApi { ======= >>>>>>> init courseApi +======= + // fetch route data +>>>>>>> enable put processing if (!this.courseInfo.activeRoute.href) { res.status(406).send(`Invalid Data`) return @@ -1288,6 +1390,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> init courseApi @@ -1301,6 +1404,8 @@ export class CourseApi { ======= >>>>>>> init courseApi +======= +>>>>>>> enable put processing const rte = await this.getRoute(this.courseInfo.activeRoute.href) if (!rte) { res.status(406).send(`Invalid Data`) @@ -1313,6 +1418,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (req.params.action === 'nextPoint') { ======= @@ -1336,6 +1442,9 @@ export class CourseApi { ======= if (req.params.nextPoint) { >>>>>>> init courseApi +======= + if (req.params.action === 'nextPoint') { +>>>>>>> enable put processing if ( typeof req.body.value === 'number' && (req.body.value === 1 || req.body.value === -1) @@ -1374,6 +1483,7 @@ export class CourseApi { ======= } } +<<<<<<< HEAD if (req.params.pointIndex) { >>>>>>> init courseApi ======= @@ -1396,6 +1506,9 @@ export class CourseApi { } if (req.params.pointIndex) { >>>>>>> init courseApi +======= + if (req.params.action === 'pointIndex') { +>>>>>>> enable put processing if (typeof req.body.value === 'number') { this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( req.body.value, @@ -1466,7 +1579,7 @@ export class CourseApi { } } - // set nextPoint + // set new destination this.courseInfo.nextPoint.position = this.getRoutePoint( rte, this.courseInfo.activeRoute.pointIndex @@ -1634,18 +1747,16 @@ export class CourseApi { >>>>>>> add 30sec delta interval ======= +<<<<<<< HEAD res.status(200).send(`OK`) >>>>>>> init courseApi +======= + res.status(200) +>>>>>>> enable put processing } ) } - private handleCourseApiPut(context:string, path:string, value:any, cb:any) { - - debug('** PUT handler **') - return undefined - } - private async activateRoute(route: ActiveRoute): Promise { let rte: any diff --git a/src/put.js b/src/put.js index 5b0fdd114..01f6f5584 100644 --- a/src/put.js +++ b/src/put.js @@ -41,7 +41,6 @@ module.exports = { next() return } - let path = String(req.path).replace(apiPathPrefix, '') const value = req.body From 88166ed28c3f8afbfc80722aaf388a1c0744003b Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 6 Dec 2021 13:34:31 +1030 Subject: [PATCH 375/410] chore: lint --- src/api/course/index.ts | 229 ++++++++++++++++++++++++++++-------- src/api/course/openApi.json | 12 ++ 2 files changed, 192 insertions(+), 49 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index bae7479d4..ccdbe0593 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -225,8 +225,12 @@ interface CourseApplication extends Application { ======= securityStrategy: { shouldAllowPut: ( +<<<<<<< HEAD req: any, >>>>>>> enable put processing +======= + req: Application, +>>>>>>> chore: lint context: string, source: any, path: string @@ -486,7 +490,7 @@ export class CourseApi { this.server, 'vessels.self', null, - 'resources' + 'navigation.course' ) } @@ -642,6 +646,7 @@ export class CourseApi { // set previousPoint to vessel position <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD try { ======= @@ -674,6 +679,8 @@ export class CourseApi { } else { ======= ======= +>>>>>>> chore: lint +======= >>>>>>> chore: lint try { const position: any = this.server.getSelfPath('navigation.position') @@ -681,6 +688,7 @@ export class CourseApi { this.courseInfo.previousPoint.position = position.value this.emitCourseInfo() <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD res.status(200).send('OK') } @@ -693,6 +701,11 @@ export class CourseApi { >>>>>>> add 30sec delta interval } } catch (err) { +>>>>>>> chore: lint +======= + res.status(200) + } + } catch (err) { >>>>>>> chore: lint res.status(406).send(`Vessel position unavailable!`) } @@ -870,7 +883,8 @@ export class CourseApi { res.status(403) return } - if (this.setArrivalCircle(req.body.value)) { + if (this.isValidArrivalCircle(req.body.value)) { + this.courseInfo.nextPoint.arrivalCircle = req.body.value this.emitCourseInfo() res.status(200) } else { @@ -1105,6 +1119,7 @@ export class CourseApi { res.status(403).send('Unauthorised') return } +<<<<<<< HEAD const result = await this.activateRoute(req.body.value) if (result) { this.emitCourseInfo() @@ -1119,6 +1134,11 @@ export class CourseApi { ======= if (!this.updateAllowed()) { res.status(403).send('Unauthorised') +======= + // fetch active route data + if (!this.courseInfo.activeRoute.href) { + res.status(406).send(`Invalid Data`) +>>>>>>> chore: lint return } const result = await this.activateRoute(req.body.value) @@ -1573,6 +1593,7 @@ export class CourseApi { // set new destination this.courseInfo.nextPoint.position = this.getRoutePoint( rte, +<<<<<<< HEAD this.courseInfo.activeRoute.pointIndex >>>>>>> init courseApi ======= @@ -1584,6 +1605,10 @@ export class CourseApi { rte, this.courseInfo.activeRoute.pointIndex >>>>>>> init courseApi +======= + this.courseInfo.activeRoute.pointIndex, + this.courseInfo.activeRoute.reverse +>>>>>>> chore: lint ) this.courseInfo.nextPoint.type = `RoutePoint` this.courseInfo.nextPoint.href = null @@ -1596,6 +1621,9 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> chore: lint ======= >>>>>>> chore: lint ======= @@ -1610,6 +1638,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD res.status(406).send(`Invalid Data`) return false @@ -1667,6 +1696,11 @@ export class CourseApi { >>>>>>> add 30sec delta interval ======= >>>>>>> init courseApi +======= + return false + } + } catch (err) { +>>>>>>> chore: lint return false } } else { @@ -1677,6 +1711,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD this.courseInfo.activeRoute.pointIndex - 1, this.courseInfo.activeRoute.reverse @@ -1700,6 +1735,10 @@ export class CourseApi { ======= this.courseInfo.activeRoute.pointIndex - 1 >>>>>>> init courseApi +======= + this.courseInfo.activeRoute.pointIndex - 1, + this.courseInfo.activeRoute.reverse +>>>>>>> chore: lint ) this.courseInfo.previousPoint.type = `RoutePoint` } @@ -1780,6 +1819,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD const newCourse: any = {} Object.assign(newCourse, this.courseInfo) @@ -2075,66 +2115,85 @@ export class CourseApi { // fetch waypoint resource details >>>>>>> chore: lint ======= +======= + const newDest: any = {} + Object.assign(newDest, this.courseInfo) + +>>>>>>> chore: lint // set activeroute - this.courseInfo.activeRoute.href = route.href + newDest.activeRoute.href = route.href - if (typeof route.arrivalCircle === 'number') { - this.setArrivalCircle(route.arrivalCircle) + if (this.isValidArrivalCircle(route.arrivalCircle as number)) { + newDest.nextPoint.arrivalCircle = route.arrivalCircle } - this.courseInfo.activeRoute.reverse = - typeof route.reverse === 'boolean' ? route.reverse : false + newDest.activeRoute.startTime = new Date().toISOString() - this.courseInfo.activeRoute.pointIndex = - typeof route.pointIndex === 'number' - ? this.parsePointIndex(route.pointIndex, rte) - : 0 + if (typeof route.reverse === 'boolean') { + newDest.activeRoute.reverse = route.reverse + } - this.courseInfo.activeRoute.startTime = new Date().toISOString() + newDest.activeRoute.pointIndex = this.parsePointIndex( + route.pointIndex as number, + rte + ) // set nextPoint - this.courseInfo.nextPoint.position = this.getRoutePoint( + newDest.nextPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex + newDest.activeRoute.pointIndex, + newDest.activeRoute.reverse ) - this.courseInfo.nextPoint.type = `RoutePoint` - this.courseInfo.nextPoint.href = null + newDest.nextPoint.type = `RoutePoint` + newDest.nextPoint.href = null // set previousPoint - if (this.courseInfo.activeRoute.pointIndex === 0) { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { + if (newDest.activeRoute.pointIndex === 0) { + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + this.courseInfo.previousPoint.position = position.value + this.courseInfo.previousPoint.type = `VesselPosition` + } else { + return false + } + } catch (err) { return false } } else { - this.courseInfo.previousPoint.position = this.getRoutePoint( + newDest.previousPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex - 1 + newDest.activeRoute.pointIndex - 1, + newDest.activeRoute.reverse ) - this.courseInfo.previousPoint.type = `RoutePoint` + newDest.previousPoint.type = `RoutePoint` } - this.courseInfo.previousPoint.href = null + newDest.previousPoint.href = null + this.courseInfo = newDest return true } - private async setDestination(dest: Destination): Promise { + private async setDestination(dest: any): Promise { + const newDest: any = {} + Object.assign(newDest, this.courseInfo) + // set nextPoint - if (typeof dest.arrivalCircle === 'number') { - this.setArrivalCircle(dest.arrivalCircle) + if (this.isValidArrivalCircle(dest.arrivalCircle)) { + newDest.nextPoint.arrivalCircle = dest.arrivalCircle } - this.courseInfo.nextPoint.type = - typeof dest.type !== 'undefined' ? dest.type : null + newDest.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null if (dest.href) { - this.courseInfo.nextPoint.href = dest.href + newDest.href = dest.href const href = this.parseHref(dest.href) if (href) { +<<<<<<< HEAD >>>>>>> init courseApi +======= + // fetch waypoint resource details +>>>>>>> chore: lint try { const r = await this.server.resourcesApi.getResource( href.type, @@ -2146,6 +2205,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (r.position && typeof r.position?.latitude !== 'undefined') { newCourse.nextPoint.position = r.position @@ -2185,6 +2245,12 @@ export class CourseApi { if (r.position && typeof r.position.value?.latitude !== 'undefined') { this.courseInfo.nextPoint.position = r.position.value >>>>>>> init courseApi +======= + if (r.position && typeof r.position?.latitude !== 'undefined') { + newDest.nextPoint.position = r.position + } else { + return false +>>>>>>> chore: lint } } catch (err) { return false @@ -2200,6 +2266,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD newCourse.nextPoint.href = null if (typeof dest.position.latitude !== 'undefined') { @@ -2254,6 +2321,11 @@ export class CourseApi { if (typeof dest.position.latitude !== 'undefined') { this.courseInfo.nextPoint.position = dest.position >>>>>>> init courseApi +======= + newDest.nextPoint.href = null + if (typeof dest.position.latitude !== 'undefined') { + newDest.nextPoint.position = dest.position +>>>>>>> chore: lint } else { return false } @@ -2268,6 +2340,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD try { const position: any = this.server.getSelfPath('navigation.position') @@ -2373,11 +2446,26 @@ export class CourseApi { this.courseInfo = newCourse >>>>>>> add 30sec delta interval ======= +======= + try { + const position: any = this.server.getSelfPath('navigation.position') + if (position && position.value) { + newDest.previousPoint.position = position.value + newDest.previousPoint.type = `VesselPosition` + } else { + return false + } + newDest.previousPoint.href = null + } catch (err) { +>>>>>>> chore: lint return false } - this.courseInfo.previousPoint.href = null +<<<<<<< HEAD >>>>>>> init courseApi +======= + this.courseInfo = newDest +>>>>>>> chore: lint return true } @@ -2400,6 +2488,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD private isValidArrivalCircle(value: number): boolean { return typeof value === 'number' && value >= 0 @@ -2469,6 +2558,14 @@ export class CourseApi { >>>>>>> chore: lint ======= >>>>>>> init courseApi +======= + private isValidArrivalCircle(value: number): boolean { + return typeof value === 'number' && value >= 0 + } + + private parsePointIndex(index: number, rte: any): number { + if (typeof index !== 'number' || !rte) { +>>>>>>> chore: lint return 0 } if (!rte.feature?.geometry?.coordinates) { @@ -2493,6 +2590,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (!href) { return undefined @@ -2553,6 +2651,13 @@ export class CourseApi { >>>>>>> chore: lint ======= >>>>>>> init courseApi +======= + if (!href) { + return undefined + } + + const ref: string[] = href.split('/').slice(-3) +>>>>>>> chore: lint if (ref.length < 3) { return undefined } @@ -2571,6 +2676,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD private getRoutePoint(rte: any, index: number, reverse: boolean) { const pos = reverse @@ -2602,6 +2708,10 @@ export class CourseApi { private getRoutePoint(rte: any, index: number) { const pos = this.courseInfo.activeRoute.reverse >>>>>>> init courseApi +======= + private getRoutePoint(rte: any, index: number, reverse: boolean) { + const pos = reverse +>>>>>>> chore: lint ? rte.feature.geometry.coordinates[ rte.feature.geometry.coordinates.length - (index + 1) ] @@ -2615,6 +2725,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD altitude: pos.length === 3 ? pos[2] : 0 ======= @@ -2638,6 +2749,9 @@ export class CourseApi { ======= altitude: pos.length == 3 ? pos[2] : 0 >>>>>>> init courseApi +======= + altitude: pos.length === 3 ? pos[2] : 0 +>>>>>>> chore: lint } } @@ -2661,6 +2775,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD const values: Array<{ path: string; value: any }> = [] const navPath = [ @@ -2692,6 +2807,10 @@ export class CourseApi { let values: Array<{path:string, value:any}> = [] let root = [ >>>>>>> init courseApi +======= + const values: Array<{ path: string; value: any }> = [] + const navPath = [ +>>>>>>> chore: lint 'navigation.courseGreatCircle', 'navigation.courseRhumbline' ] @@ -2724,6 +2843,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD path: `${navPath[0]}.activeRoute.href`, ======= @@ -2974,75 +3094,82 @@ export class CourseApi { ======= >>>>>>> update detlas path: `${root[0]}.activeRoute.href`, +======= + path: `${navPath[0]}.activeRoute.href`, +>>>>>>> chore: lint value: this.courseInfo.activeRoute.href }) values.push({ - path: `${root[1]}.activeRoute.href`, + path: `${navPath[1]}.activeRoute.href`, value: this.courseInfo.activeRoute.href }) values.push({ - path: `${root[0]}.activeRoute.startTime`, + path: `${navPath[0]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${root[1]}.activeRoute.startTime`, + path: `${navPath[1]}.activeRoute.startTime`, value: this.courseInfo.activeRoute.startTime }) values.push({ - path: `${root[0]}.nextPoint.href`, + path: `${navPath[0]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${root[1]}.nextPoint.href`, + path: `${navPath[1]}.nextPoint.href`, value: this.courseInfo.nextPoint.href }) values.push({ - path: `${root[0]}.nextPoint.position`, + path: `${navPath[0]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${root[1]}.nextPoint.position`, + path: `${navPath[1]}.nextPoint.position`, value: this.courseInfo.nextPoint.position }) values.push({ - path: `${root[0]}.nextPoint.type`, + path: `${navPath[0]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${root[1]}.nextPoint.type`, + path: `${navPath[1]}.nextPoint.type`, value: this.courseInfo.nextPoint.type }) values.push({ - path: `${root[0]}.nextPoint.arrivalCircle`, + path: `${navPath[0]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${root[1]}.nextPoint.arrivalCircle`, + path: `${navPath[1]}.nextPoint.arrivalCircle`, value: this.courseInfo.nextPoint.arrivalCircle }) values.push({ - path: `${root[0]}.previousPoint.href`, + path: `${navPath[0]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${root[1]}.previousPoint.href`, + path: `${navPath[1]}.previousPoint.href`, value: this.courseInfo.previousPoint.href }) values.push({ - path: `${root[0]}.previousPoint.position`, + path: `${navPath[0]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${root[1]}.previousPoint.position`, + path: `${navPath[1]}.previousPoint.position`, value: this.courseInfo.previousPoint.position }) values.push({ - path: `${root[0]}.previousPoint.type`, + path: `${navPath[0]}.previousPoint.type`, value: this.courseInfo.previousPoint.type }) values.push({ +<<<<<<< HEAD path: `${root[1]}.previousPoint.type`, >>>>>>> init courseApi +======= + path: `${navPath[1]}.previousPoint.type`, +>>>>>>> chore: lint value: this.courseInfo.previousPoint.type }) @@ -3055,6 +3182,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD values ======= @@ -3078,6 +3206,9 @@ export class CourseApi { ======= values: values >>>>>>> init courseApi +======= + values +>>>>>>> chore: lint } ] } diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index dca136e43..13543bb20 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -114,6 +114,7 @@ <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD "description": "Sets previousPoint value to current vessel location" ======= @@ -165,6 +166,9 @@ >>>>>>> chore: lint ======= >>>>>>> init courseApi +======= + "description": "Sets previousPoint value to current vessel location" +>>>>>>> chore: lint } }, @@ -240,6 +244,7 @@ <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD "description": "Sets activeRoute, nextPoint & previousPoint values to null" ======= @@ -291,6 +296,9 @@ >>>>>>> chore: lint ======= >>>>>>> init courseApi +======= + "description": "Sets activeRoute, nextPoint & previousPoint values to null" +>>>>>>> chore: lint } }, @@ -387,6 +395,7 @@ <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD "description": "Sets activeRoute, nextPoint & previousPoint values to null" ======= @@ -438,6 +447,9 @@ >>>>>>> chore: lint ======= >>>>>>> init courseApi +======= + "description": "Sets activeRoute, nextPoint & previousPoint values to null" +>>>>>>> chore: lint } }, From f10f7f381e9da3e55db8a1541c433cbea3b5b3ac Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 8 Dec 2021 09:27:06 +1030 Subject: [PATCH 376/410] add 30sec delta interval --- src/api/course/index.ts | 108 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 101 insertions(+), 7 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index ccdbe0593..d3538f878 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -170,6 +170,8 @@ const API_METHODS: string[] = [] ======= >>>>>>> update detlas +const DELTA_INTERVAL: number = 30000 + interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any @@ -402,6 +404,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD setInterval(() => { if (this.courseInfo.nextPoint.position) { @@ -483,6 +486,13 @@ export class CourseApi { ) ======= >>>>>>> init courseApi +======= + setInterval( ()=> { + if(this.courseInfo.nextPoint.position) { + this.emitCourseInfo() + } + }, DELTA_INTERVAL) +>>>>>>> add 30sec delta interval } private updateAllowed(): boolean { @@ -572,6 +582,7 @@ export class CourseApi { if (!this.updateAllowed()) { <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD res.status(403).send('Unauthorised') ======= @@ -634,6 +645,9 @@ export class CourseApi { ======= res.status(403).send('Unauthorised') >>>>>>> add 30sec delta interval +======= + res.status(403).send('Unauthorised') +>>>>>>> add 30sec delta interval ======= res.status(403).send('Unauthorised') >>>>>>> add 30sec delta interval @@ -689,6 +703,7 @@ export class CourseApi { this.emitCourseInfo() <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD res.status(200).send('OK') } @@ -696,6 +711,9 @@ export class CourseApi { >>>>>>> chore: lint ======= res.status(200) +======= + res.status(200).send('OK') +>>>>>>> add 30sec delta interval ======= res.status(200).send('OK') >>>>>>> add 30sec delta interval @@ -880,13 +898,13 @@ export class CourseApi { ======= debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } if (this.isValidArrivalCircle(req.body.value)) { this.courseInfo.nextPoint.arrivalCircle = req.body.value this.emitCourseInfo() - res.status(200) + res.status(200).send('OK') } else { res.status(406).send(`Invalid Data`) >>>>>>> enable put processing @@ -942,7 +960,7 @@ export class CourseApi { >>>>>>> init courseApi ======= if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } >>>>>>> enable put processing @@ -963,6 +981,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD res.status(200).send('OK') ======= @@ -998,6 +1017,9 @@ export class CourseApi { ======= res.status(200) >>>>>>> enable put processing +======= + res.status(200).send('OK') +>>>>>>> add 30sec delta interval } else { this.clearDestination() this.emitCourseInfo() @@ -1046,10 +1068,22 @@ export class CourseApi { res.status(403).send('Unauthorised') return } +<<<<<<< HEAD >>>>>>> enable put processing this.clearDestination() this.emitCourseInfo() res.status(200).send('OK') +======= + const result = await this.activateRoute(req.body.value) + if (result) { + this.emitCourseInfo() + res.status(200).send('OK') + } else { + this.clearDestination() + this.emitCourseInfo() + res.status(406).send(`Invalid Data`) + } +>>>>>>> add 30sec delta interval } ) @@ -1385,7 +1419,7 @@ export class CourseApi { ======= >>>>>>> enable put processing if (!this.updateAllowed()) { - res.status(403) + res.status(403).send('Unauthorised') return } <<<<<<< HEAD @@ -1482,6 +1516,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> add 30sec delta interval ======= @@ -1510,6 +1545,9 @@ export class CourseApi { if (req.params.action === 'pointIndex') { >>>>>>> enable put processing ======= +======= + return +>>>>>>> add 30sec delta interval ======= return >>>>>>> add 30sec delta interval @@ -1543,6 +1581,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> add 30sec delta interval return @@ -1597,6 +1636,9 @@ export class CourseApi { this.courseInfo.activeRoute.pointIndex >>>>>>> init courseApi ======= +======= + return +>>>>>>> add 30sec delta interval } } @@ -1639,6 +1681,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD res.status(406).send(`Invalid Data`) return false @@ -1701,6 +1744,13 @@ export class CourseApi { } } catch (err) { >>>>>>> chore: lint +======= + res.status(406).send(`Invalid Data`) + return false + } + } catch (err) { + res.status(406).send(`Invalid Data`) +>>>>>>> add 30sec delta interval return false } } else { @@ -1749,6 +1799,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD res.status(200).send('OK') ======= @@ -1792,6 +1843,9 @@ export class CourseApi { ======= res.status(200) >>>>>>> enable put processing +======= + res.status(200).send('OK') +>>>>>>> add 30sec delta interval } ) } @@ -1820,6 +1874,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD const newCourse: any = {} Object.assign(newCourse, this.courseInfo) @@ -1837,6 +1892,10 @@ export class CourseApi { const newCourse: any = {} Object.assign(newCourse, this.courseInfo) >>>>>>> add 30sec delta interval +======= + const newCourse: any = {} + Object.assign(newCourse, this.courseInfo) +>>>>>>> add 30sec delta interval >>>>>>> chore: lint // set activeroute @@ -1902,8 +1961,12 @@ export class CourseApi { newCourse.nextPoint.arrivalCircle = dest.arrivalCircle } +<<<<<<< HEAD newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null +======= + newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null +>>>>>>> add 30sec delta interval if (dest.href) { newCourse.href = dest.href @@ -1912,6 +1975,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD // fetch waypoint resource details +<<<<<<< HEAD ======= ======= const newDest: any = {} @@ -1920,6 +1984,32 @@ export class CourseApi { const newCourse: any = {} Object.assign(newCourse, this.courseInfo) >>>>>>> add 30sec delta interval +======= + try { + const r = await this.server.resourcesApi.getResource( + href.type, + href.id + ) + if (r.position && typeof r.position?.latitude !== 'undefined') { + newCourse.nextPoint.position = r.position + } else { + return false + } + } catch (err) { + return false + } + } + } else if (dest.position) { + newCourse.nextPoint.href = null + if (typeof dest.position.latitude !== 'undefined') { + newCourse.nextPoint.position = dest.position + } else { + return false + } + } else { + return false + } +>>>>>>> add 30sec delta interval >>>>>>> chore: lint // set activeroute @@ -2450,22 +2540,26 @@ export class CourseApi { try { const position: any = this.server.getSelfPath('navigation.position') if (position && position.value) { - newDest.previousPoint.position = position.value - newDest.previousPoint.type = `VesselPosition` + newCourse.previousPoint.position = position.value + newCourse.previousPoint.type = `VesselPosition` } else { return false } - newDest.previousPoint.href = null + newCourse.previousPoint.href = null } catch (err) { >>>>>>> chore: lint return false } +<<<<<<< HEAD <<<<<<< HEAD >>>>>>> init courseApi ======= this.courseInfo = newDest >>>>>>> chore: lint +======= + this.courseInfo = newCourse +>>>>>>> add 30sec delta interval return true } From 75683ca6606644cc472e93bce92927942d97f69b Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 12 Dec 2021 14:29:38 +1030 Subject: [PATCH 377/410] chore: lint --- src/api/course/index.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index d3538f878..e9e6d07fb 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -170,7 +170,7 @@ const API_METHODS: string[] = [] ======= >>>>>>> update detlas -const DELTA_INTERVAL: number = 30000 +const DELTA_INTERVAL: number = 30000 interface CourseApplication extends Application { handleMessage: (id: string, data: any) => void @@ -405,6 +405,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD setInterval(() => { if (this.courseInfo.nextPoint.position) { @@ -489,6 +490,10 @@ export class CourseApi { ======= setInterval( ()=> { if(this.courseInfo.nextPoint.position) { +======= + setInterval(() => { + if (this.courseInfo.nextPoint.position) { +>>>>>>> chore: lint this.emitCourseInfo() } }, DELTA_INTERVAL) @@ -1961,12 +1966,17 @@ export class CourseApi { newCourse.nextPoint.arrivalCircle = dest.arrivalCircle } +<<<<<<< HEAD <<<<<<< HEAD newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null ======= newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null >>>>>>> add 30sec delta interval +======= + newCourse.nextPoint.type = + typeof dest.type !== 'undefined' ? dest.type : null +>>>>>>> chore: lint if (dest.href) { newCourse.href = dest.href From f5f255d9f15f1cf64d6a615f40e507186594b4e3 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Fri, 31 Dec 2021 17:06:01 +1030 Subject: [PATCH 378/410] persist courseInfo to settings file --- src/api/course/index.ts | 60 ++++++++++++++++++++++++++++++++++++++--- src/api/store.ts | 42 +++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 4 deletions(-) create mode 100644 src/api/store.ts diff --git a/src/api/course/index.ts b/src/api/course/index.ts index e9e6d07fb..8d49bf36c 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -16,6 +16,7 @@ <<<<<<< HEAD import Debug from 'debug' import { Application, Request, Response } from 'express' +<<<<<<< HEAD ======= import { ResourceProvider, @@ -122,6 +123,11 @@ import { v4 as uuidv4 } from 'uuid' import Debug from 'debug' import { Application, Request, Response } from 'express' >>>>>>> update detlas +======= +import path from 'path' +import { WithConfig, WithSecurityStrategy, WithSignalK } from '../../app' +import { Store } from '../store' +>>>>>>> persist courseInfo to settings file const debug = Debug('signalk:courseApi') @@ -172,8 +178,14 @@ const API_METHODS: string[] = [] const DELTA_INTERVAL: number = 30000 +interface CourseApplication + extends Application, + WithConfig, + WithSignalK, + WithSecurityStrategy {} + interface CourseApplication extends Application { - handleMessage: (id: string, data: any) => void + // handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any <<<<<<< HEAD <<<<<<< HEAD @@ -194,6 +206,7 @@ interface CourseApplication extends Application { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD securityStrategy: { shouldAllowPut: ( @@ -294,6 +307,8 @@ interface CourseApplication extends Application { >>>>>>> update detlas ======= >>>>>>> enable put processing +======= +>>>>>>> persist courseInfo to settings file resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -386,12 +401,17 @@ export class CourseApi { } } + private store: Store + constructor(app: CourseApplication) { this.server = app - this.start(app) + this.store = new Store(path.join(app.config.configPath, 'api/course')) + this.start(app).catch(error => { + console.log(error) + }) } - private start(app: any) { + private async start(app: any) { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app this.initResourceRoutes() @@ -406,6 +426,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD setInterval(() => { if (this.courseInfo.nextPoint.position) { @@ -495,11 +516,37 @@ export class CourseApi { if (this.courseInfo.nextPoint.position) { >>>>>>> chore: lint this.emitCourseInfo() +======= + + try { + const storeData = await this.store.read() + this.courseInfo = this.validateCourseInfo(storeData) + } catch (error) { + debug('** No persisted course data (using default) **') + this.store.write(this.courseInfo).catch(error => { + console.log(error) + }) + } + debug(this.courseInfo) + + setInterval(() => { + if (this.courseInfo.nextPoint.position) { + this.emitCourseInfo(true) +>>>>>>> persist courseInfo to settings file } }, DELTA_INTERVAL) >>>>>>> add 30sec delta interval } + private validateCourseInfo(info: CourseInfo) { + if (info.activeRoute && info.nextPoint && info.previousPoint) { + return info + } else { + debug(`** Error: Loaded course data is invalid!! (using default) **`) + return this.courseInfo + } + } + private updateAllowed(): boolean { return this.server.securityStrategy.shouldAllowPut( this.server, @@ -3318,7 +3365,12 @@ export class CourseApi { } } - private emitCourseInfo() { + private emitCourseInfo(noSave: boolean = false) { this.server.handleMessage('courseApi', this.buildDeltaMsg()) + if (!noSave) { + this.store.write(this.courseInfo).catch(error => { + console.log(error) + }) + } } } diff --git a/src/api/store.ts b/src/api/store.ts new file mode 100644 index 000000000..5e979d13b --- /dev/null +++ b/src/api/store.ts @@ -0,0 +1,42 @@ +import { constants } from 'fs' +import { access, mkdir, readFile, writeFile } from 'fs/promises' +import path from 'path' + +export class Store { + private filePath: string = '' + private fileName: string = '' + + constructor(filePath: string, fileName: string = 'settings.json') { + this.filePath = filePath + this.fileName = fileName + this.init().catch(error => { + console.log(error) + }) + } + + async read(): Promise { + const data = await readFile(path.join(this.filePath, this.fileName), 'utf8') + return JSON.parse(data) + } + + write(data: any): Promise { + return writeFile( + path.join(this.filePath, this.fileName), + JSON.stringify(data) + ) + } + + private async init() { + try { + /* tslint:disable:no-bitwise */ + await access(this.filePath, constants.R_OK | constants.W_OK) + /* tslint:enable:no-bitwise */ + } catch (error) { + try { + await mkdir(this.filePath, { recursive: true }) + } catch (error) { + console.log(`Error: Unable to create ${this.filePath}`) + } + } + } +} From 5b21c33625babb56751241d7ddb04eedd2cdb668 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 1 Jan 2022 16:16:59 +1030 Subject: [PATCH 379/410] add getVesselPosition --- src/api/course/index.ts | 39 +++++++++++++++++++++++++++++++++---- src/api/course/openApi.json | 24 ++--------------------- src/put.js | 7 ++++--- 3 files changed, 41 insertions(+), 29 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 8d49bf36c..2922707ea 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -126,6 +126,7 @@ import { Application, Request, Response } from 'express' ======= import path from 'path' import { WithConfig, WithSecurityStrategy, WithSignalK } from '../../app' +import _ from 'lodash' import { Store } from '../store' >>>>>>> persist courseInfo to settings file @@ -185,6 +186,7 @@ interface CourseApplication WithSecurityStrategy {} interface CourseApplication extends Application { +<<<<<<< HEAD // handleMessage: (id: string, data: any) => void getSelfPath: (path: string) => any <<<<<<< HEAD @@ -309,6 +311,8 @@ interface CourseApplication extends Application { >>>>>>> enable put processing ======= >>>>>>> persist courseInfo to settings file +======= +>>>>>>> add getVesselPosition resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -403,6 +407,10 @@ export class CourseApi { private store: Store + private getVesselPosition() { + return _.get( (this.server.signalk as any).self, 'navigation.position') + } + constructor(app: CourseApplication) { this.server = app this.store = new Store(path.join(app.config.configPath, 'api/course')) @@ -528,6 +536,7 @@ export class CourseApi { }) } debug(this.courseInfo) + this.emitCourseInfo(true) setInterval(() => { if (this.courseInfo.nextPoint.position) { @@ -749,7 +758,7 @@ export class CourseApi { ======= >>>>>>> chore: lint try { - const position: any = this.server.getSelfPath('navigation.position') + const position: any = this.getVesselPosition() if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.emitCourseInfo() @@ -1017,6 +1026,7 @@ export class CourseApi { } >>>>>>> enable put processing if (!req.body.value) { + debug(`** Error: req.body.value is null || undefined!`) res.status(406).send(`Invalid Data`) return } @@ -1723,7 +1733,7 @@ export class CourseApi { ======= >>>>>>> chore: lint try { - const position: any = this.server.getSelfPath('navigation.position') + const position: any = this.getVesselPosition() if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.courseInfo.previousPoint.type = `VesselPosition` @@ -1980,7 +1990,7 @@ export class CourseApi { // set previousPoint if (newCourse.activeRoute.pointIndex === 0) { try { - const position: any = this.server.getSelfPath('navigation.position') + const position: any = this.getVesselPosition() if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.courseInfo.previousPoint.type = `VesselPosition` @@ -2026,7 +2036,6 @@ export class CourseApi { >>>>>>> chore: lint if (dest.href) { - newCourse.href = dest.href const href = this.parseHref(dest.href) if (href) { <<<<<<< HEAD @@ -2049,6 +2058,8 @@ export class CourseApi { ) if (r.position && typeof r.position?.latitude !== 'undefined') { newCourse.nextPoint.position = r.position + newCourse.nextPoint.href = dest.href + newCourse.nextPoint.type = 'Waypoint' } else { return false } @@ -2058,9 +2069,11 @@ export class CourseApi { } } else if (dest.position) { newCourse.nextPoint.href = null + newCourse.nextPoint.type = 'Location' if (typeof dest.position.latitude !== 'undefined') { newCourse.nextPoint.position = dest.position } else { + debug(`** Error: position.latitude is undefined!`) return false } } else { @@ -2068,9 +2081,27 @@ export class CourseApi { } >>>>>>> add 30sec delta interval +<<<<<<< HEAD >>>>>>> chore: lint // set activeroute newCourse.activeRoute.href = route.href +======= + // set previousPoint + try { + const position: any = this.getVesselPosition() + if (position && position.value) { + newCourse.previousPoint.position = position.value + newCourse.previousPoint.type = `VesselPosition` + newCourse.previousPoint.href = null + } else { + debug(`** Error: navigation.position.value is undefined! (${position})`) + return false + } + } catch (err) { + debug(`** Error: unable to retrieve navigation.position! (${err})`) + return false + } +>>>>>>> add getVesselPosition if (this.isValidArrivalCircle(route.arrivalCircle as number)) { newCourse.nextPoint.arrivalCircle = route.arrivalCircle diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index 13543bb20..7aa22d56e 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -224,10 +224,6 @@ "example": 500 } } - }, - "source": { - "type": "string", - "example": "freeboard-sk" } } } @@ -320,11 +316,7 @@ "type": "number", "format": "float", "example": 500 - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } + } } } } @@ -375,11 +367,7 @@ "example": 500 } } - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } + } } } } @@ -472,10 +460,6 @@ "description": "increment (1) / decrement (-1) index of point in route to use as destination", "enum": [-1,1], "example": -1 - }, - "source": { - "type": "string", - "example": "freeboard-sk" } } } @@ -504,10 +488,6 @@ "description": "Index of point in route to use as destination", "minimum": 0, "example": 2 - }, - "source": { - "type": "string", - "example": "freeboard-sk" } } } diff --git a/src/put.js b/src/put.js index 01f6f5584..13120ba95 100644 --- a/src/put.js +++ b/src/put.js @@ -31,13 +31,14 @@ module.exports = { app.deRegisterActionHandler = deRegisterActionHandler app.put(apiPathPrefix + '*', function(req, res, next) { - // ** ignore resources paths ** + // ** ignore resources API paths ** if (req.path.split('/')[4] === 'resources') { next() return } - // ** ignore course paths ** - if (req.path.indexOf('/navigation/course') !== -1) { + + // ** ignore course API paths ** + if (req.path.indexOf('/navigation/course/') !== -1) { next() return } From 55806bc8444b4492aa59f06462757692dad90b06 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 1 Jan 2022 16:36:11 +1030 Subject: [PATCH 380/410] align openApi.json --- src/api/course/index.ts | 9 ++- src/api/course/openApi.json | 114 ++++++++++++++++-------------------- 2 files changed, 58 insertions(+), 65 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 2922707ea..c1d1721f2 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1024,13 +1024,18 @@ export class CourseApi { res.status(403).send('Unauthorised') return } +<<<<<<< HEAD >>>>>>> enable put processing if (!req.body.value) { debug(`** Error: req.body.value is null || undefined!`) +======= + if (!req.body) { + debug(`** Error: req.body is null || undefined!`) +>>>>>>> align openApi.json res.status(406).send(`Invalid Data`) return } - const result = await this.setDestination(req.body.value) + const result = await this.setDestination(req.body) if (result) { this.emitCourseInfo() <<<<<<< HEAD @@ -1280,7 +1285,7 @@ export class CourseApi { res.status(403).send('Unauthorised') return } - const result = await this.activateRoute(req.body.value) + const result = await this.activateRoute(req.body) if (result) { this.emitCourseInfo() <<<<<<< HEAD diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index 7aa22d56e..65ff7003b 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -184,46 +184,40 @@ "application/json": { "schema": { "type": "object", - "required": ["value"], "properties": { - "value": { + "position": { "type": "object", + "description": "Destination position", + "required": [ + "latitude", + "longitude" + ], "properties": { - "position": { - "type": "object", - "description": "Destination position", - "required": [ - "latitude", - "longitude" - ], - "properties": { - "latitude": { - "type": "number", - "format": "float" - }, - "longitude": { - "type": "number", - "format": "float" - } - }, - "example": {"latitude":-29.5,"longitude":137.5} - }, - "href": { - "type": "string", - "description": "A reference (URL) to an object (under /resources) this point is related to", - "example": "/resources/waypoints/urn:mrn:signalk:uuid:3dd34dcc-36bf-4d61-ba80-233799b25672" - }, - "type": { - "type": "string", - "description": "Type of point", - "example": "POI" + "latitude": { + "type": "number", + "format": "float" }, - "arrivalCircle": { + "longitude": { "type": "number", - "format": "float", - "example": 500 + "format": "float" } - } + }, + "example": {"latitude":-29.5,"longitude":137.5} + }, + "href": { + "type": "string", + "description": "A reference (URL) to an object (under /resources) this point is related to", + "example": "/resources/waypoints/urn:mrn:signalk:uuid:3dd34dcc-36bf-4d61-ba80-233799b25672" + }, + "type": { + "type": "string", + "description": "Type of point", + "example": "POI" + }, + "arrivalCircle": { + "type": "number", + "format": "float", + "example": 500 } } } @@ -337,36 +331,30 @@ "application/json": { "schema": { "type": "object", - "required": ["value"], "properties": { - "value": { - "type": "object", - "properties": { - "href": { - "type": "string", - "description": "Path to route resource", - "example": "/resources/routes/urn:mrn:signalk:uuid:3dd34dcc-36bf-4d61-ba80-233799b25672" - }, - "pointIndex": { - "type": "number", - "format": "int64", - "description": "Zero based index of route point to use as destination", - "default": 0, - "minimum": 0, - "example": 2 - }, - "reverse": { - "type": "boolean", - "default": false, - "description": "If true performs operations on route points in reverse order", - "example": 2 - }, - "arrivalCircle": { - "type": "number", - "format": "float", - "example": 500 - } - } + "href": { + "type": "string", + "description": "Path to route resource", + "example": "/resources/routes/urn:mrn:signalk:uuid:3dd34dcc-36bf-4d61-ba80-233799b25672" + }, + "pointIndex": { + "type": "number", + "format": "int64", + "description": "Zero based index of route point to use as destination", + "default": 0, + "minimum": 0, + "example": 2 + }, + "reverse": { + "type": "boolean", + "default": false, + "description": "If true performs operations on route points in reverse order", + "example": 2 + }, + "arrivalCircle": { + "type": "number", + "format": "float", + "example": 500 } } } From 0127a53c5abc56d325645d93cc35faed5e1ab27a Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 2 Jan 2022 14:33:38 +1030 Subject: [PATCH 381/410] move `store.ts` to serverstate` folder --- src/api/course/index.ts | 9 +++++++++ src/{api => serverstate}/store.ts | 0 2 files changed, 9 insertions(+) rename src/{api => serverstate}/store.ts (100%) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index c1d1721f2..b3ae5f60f 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -127,8 +127,12 @@ import { Application, Request, Response } from 'express' import path from 'path' import { WithConfig, WithSecurityStrategy, WithSignalK } from '../../app' import _ from 'lodash' +<<<<<<< HEAD import { Store } from '../store' >>>>>>> persist courseInfo to settings file +======= +import { Store } from '../../serverstate/store' +>>>>>>> move `store.ts` to serverstate` folder const debug = Debug('signalk:courseApi') @@ -777,12 +781,17 @@ export class CourseApi { >>>>>>> add 30sec delta interval ======= res.status(200).send('OK') +<<<<<<< HEAD >>>>>>> add 30sec delta interval } } catch (err) { >>>>>>> chore: lint ======= res.status(200) +======= + } else { + res.status(406).send(`Vessel position unavailable!`) +>>>>>>> move `store.ts` to serverstate` folder } } catch (err) { >>>>>>> chore: lint diff --git a/src/api/store.ts b/src/serverstate/store.ts similarity index 100% rename from src/api/store.ts rename to src/serverstate/store.ts From 2ba8a688f1e758b9644df0ddffbcca24050740a7 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 3 Jan 2022 13:10:10 +1030 Subject: [PATCH 382/410] change saved couorse path to serverstate --- src/api/course/index.ts | 5 +---- src/serverstate/store.ts | 9 +++++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index b3ae5f60f..01beeab5e 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -417,7 +417,7 @@ export class CourseApi { constructor(app: CourseApplication) { this.server = app - this.store = new Store(path.join(app.config.configPath, 'api/course')) + this.store = new Store(path.join(app.config.configPath, 'serverstate/course')) this.start(app).catch(error => { console.log(error) }) @@ -535,9 +535,6 @@ export class CourseApi { this.courseInfo = this.validateCourseInfo(storeData) } catch (error) { debug('** No persisted course data (using default) **') - this.store.write(this.courseInfo).catch(error => { - console.log(error) - }) } debug(this.courseInfo) this.emitCourseInfo(true) diff --git a/src/serverstate/store.ts b/src/serverstate/store.ts index 5e979d13b..6839806ea 100644 --- a/src/serverstate/store.ts +++ b/src/serverstate/store.ts @@ -10,13 +10,18 @@ export class Store { this.filePath = filePath this.fileName = fileName this.init().catch(error => { + console.log(`Could not initialise ${path.join(this.filePath, this.fileName)}`) console.log(error) }) } async read(): Promise { - const data = await readFile(path.join(this.filePath, this.fileName), 'utf8') - return JSON.parse(data) + try { + const data = await readFile(path.join(this.filePath, this.fileName), 'utf8') + return JSON.parse(data) + } catch (error) { + throw(error) + } } write(data: any): Promise { From f0920d61f36c69b9f251f5441f86033d369cea86 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 8 Jan 2022 13:13:40 +1030 Subject: [PATCH 383/410] regresstion testing fixes --- src/api/course/index.ts | 187 ++++++++++++++++++++++++++++++++---- src/api/course/openApi.json | 13 +++ src/api/resources/index.ts | 165 +++++++++++++++++++++---------- src/api/responses.ts | 31 ++++++ src/serverstate/store.ts | 11 ++- 5 files changed, 335 insertions(+), 72 deletions(-) create mode 100644 src/api/responses.ts diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 01beeab5e..009e66ed9 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -17,6 +17,7 @@ import Debug from 'debug' import { Application, Request, Response } from 'express' <<<<<<< HEAD +<<<<<<< HEAD ======= import { ResourceProvider, @@ -133,6 +134,13 @@ import { Store } from '../store' ======= import { Store } from '../../serverstate/store' >>>>>>> move `store.ts` to serverstate` folder +======= +import _ from 'lodash' +import path from 'path' +import { WithConfig, WithSecurityStrategy, WithSignalK } from '../../app' +import { Store } from '../../serverstate/store' +import { Responses } from '../responses' +>>>>>>> regresstion testing fixes const debug = Debug('signalk:courseApi') @@ -371,6 +379,7 @@ interface CourseInfo { href: string | null startTime: string | null pointIndex: number + pointTotal: number reverse: boolean } nextPoint: { @@ -394,6 +403,7 @@ export class CourseApi { href: null, startTime: null, pointIndex: 0, + pointTotal: 0, reverse: false }, nextPoint: { @@ -411,21 +421,24 @@ export class CourseApi { private store: Store - private getVesselPosition() { - return _.get( (this.server.signalk as any).self, 'navigation.position') - } - constructor(app: CourseApplication) { this.server = app - this.store = new Store(path.join(app.config.configPath, 'serverstate/course')) + this.store = new Store( + path.join(app.config.configPath, 'serverstate/course') + ) this.start(app).catch(error => { console.log(error) }) } + private getVesselPosition() { + return _.get((this.server.signalk as any).self, 'navigation.position') + } + private async start(app: any) { debug(`** Initialise ${COURSE_API_PATH} path handler **`) this.server = app +<<<<<<< HEAD this.initResourceRoutes() <<<<<<< HEAD <<<<<<< HEAD @@ -529,6 +542,9 @@ export class CourseApi { >>>>>>> chore: lint this.emitCourseInfo() ======= +======= + this.initCourseRoutes() +>>>>>>> regresstion testing fixes try { const storeData = await this.store.read() @@ -566,7 +582,7 @@ export class CourseApi { ) } - private initResourceRoutes() { + private initCourseRoutes() { // return current course information this.server.get( `${COURSE_API_PATH}`, @@ -645,6 +661,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD res.status(403).send('Unauthorised') ======= @@ -713,10 +730,17 @@ export class CourseApi { ======= res.status(403).send('Unauthorised') >>>>>>> add 30sec delta interval +======= + res.status(403).json(Responses.unauthorised) +>>>>>>> regresstion testing fixes return } if (!this.courseInfo.nextPoint.position) { - res.status(406).send(`No active destination!`) + res.status(406).json({ + state: 'FAILED', + statusCode: 406, + message: `No active destination!` + }) return } // set previousPoint to vessel position @@ -766,6 +790,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD res.status(200).send('OK') } @@ -793,6 +818,22 @@ export class CourseApi { } catch (err) { >>>>>>> chore: lint res.status(406).send(`Vessel position unavailable!`) +======= + res.status(200).json(Responses.ok) + } else { + res.status(406).json({ + state: 'FAILED', + statusCode: 406, + message: `Vessel position unavailable!` + }) + } + } catch (err) { + res.status(406).json({ + state: 'FAILED', + statusCode: 406, + message: `Vessel position unavailable!` + }) +>>>>>>> regresstion testing fixes } } ) @@ -965,16 +1006,20 @@ export class CourseApi { ======= debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') + res.status(403).json(Responses.unauthorised) return } if (this.isValidArrivalCircle(req.body.value)) { this.courseInfo.nextPoint.arrivalCircle = req.body.value this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).json(Responses.ok) } else { +<<<<<<< HEAD res.status(406).send(`Invalid Data`) >>>>>>> enable put processing +======= + res.status(406).json(Responses.invalid) +>>>>>>> regresstion testing fixes } } ) @@ -1027,7 +1072,7 @@ export class CourseApi { >>>>>>> init courseApi ======= if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') + res.status(403).json(Responses.unauthorised) return } <<<<<<< HEAD @@ -1037,8 +1082,12 @@ export class CourseApi { ======= if (!req.body) { debug(`** Error: req.body is null || undefined!`) +<<<<<<< HEAD >>>>>>> align openApi.json res.status(406).send(`Invalid Data`) +======= + res.status(406).json(Responses.invalid) +>>>>>>> regresstion testing fixes return } const result = await this.setDestination(req.body) @@ -1055,6 +1104,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD res.status(200).send('OK') ======= @@ -1093,10 +1143,13 @@ export class CourseApi { ======= res.status(200).send('OK') >>>>>>> add 30sec delta interval +======= + res.status(200).json(Responses.ok) +>>>>>>> regresstion testing fixes } else { this.clearDestination() this.emitCourseInfo() - res.status(406).send(`Invalid Data`) + res.status(406).json(Responses.invalid) } } ) @@ -1122,13 +1175,13 @@ export class CourseApi { >>>>>>> init courseApi ======= if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') + res.status(403).json(Responses.unauthorised) return } >>>>>>> enable put processing this.clearDestination() this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).json(Responses.ok) } ) @@ -1288,12 +1341,13 @@ export class CourseApi { >>>>>>> init courseApi ======= if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') + res.status(403).json(Responses.unauthorised) return } const result = await this.activateRoute(req.body) if (result) { this.emitCourseInfo() +<<<<<<< HEAD <<<<<<< HEAD res.status(200) >>>>>>> enable put processing @@ -1312,10 +1366,13 @@ export class CourseApi { this.emitCourseInfo() res.status(200) >>>>>>> enable put processing +======= + res.status(200).json(Responses.ok) +>>>>>>> regresstion testing fixes } else { this.clearDestination() this.emitCourseInfo() - res.status(406).send(`Invalid Data`) + res.status(406).json(Responses.invalid) } } ) @@ -1374,7 +1431,7 @@ export class CourseApi { ======= >>>>>>> enable put processing if (!this.updateAllowed()) { - res.status(403) + res.status(403).json(Responses.unauthorised) return } <<<<<<< HEAD @@ -1426,6 +1483,7 @@ export class CourseApi { ======= this.clearDestination() this.emitCourseInfo() +<<<<<<< HEAD res.status(200) >>>>>>> enable put processing ======= @@ -1438,12 +1496,16 @@ export class CourseApi { this.emitCourseInfo() res.status(200) >>>>>>> enable put processing +======= + res.status(200).json(Responses.ok) +>>>>>>> regresstion testing fixes } ) this.server.put( `${COURSE_API_PATH}/activeRoute/:action`, async (req: Request, res: Response) => { +<<<<<<< HEAD debug(`** PUT ${COURSE_API_PATH}/activeRoute`) <<<<<<< HEAD <<<<<<< HEAD @@ -1491,8 +1553,11 @@ export class CourseApi { ======= ======= >>>>>>> enable put processing +======= + debug(`** PUT ${COURSE_API_PATH}/activeRoute/${req.params.action}`) +>>>>>>> regresstion testing fixes if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') + res.status(403).json(Responses.unauthorised) return } <<<<<<< HEAD @@ -1509,7 +1574,7 @@ export class CourseApi { // fetch route data >>>>>>> enable put processing if (!this.courseInfo.activeRoute.href) { - res.status(406).send(`Invalid Data`) + res.status(406).json(Responses.invalid) return } <<<<<<< HEAD @@ -1535,7 +1600,7 @@ export class CourseApi { >>>>>>> enable put processing const rte = await this.getRoute(this.courseInfo.activeRoute.href) if (!rte) { - res.status(406).send(`Invalid Data`) + res.status(406).json(Responses.invalid) return } @@ -1581,6 +1646,7 @@ export class CourseApi { rte ) } else { +<<<<<<< HEAD res.status(406).send(`Invalid Data`) <<<<<<< HEAD <<<<<<< HEAD @@ -1622,14 +1688,21 @@ export class CourseApi { return >>>>>>> add 30sec delta interval ======= +======= + res.status(406).json(Responses.invalid) +>>>>>>> regresstion testing fixes return >>>>>>> add 30sec delta interval } } +<<<<<<< HEAD <<<<<<< HEAD if (req.params.pointIndex) { >>>>>>> init courseApi ======= +======= + +>>>>>>> regresstion testing fixes if (req.params.action === 'pointIndex') { >>>>>>> enable put processing ======= @@ -1646,6 +1719,7 @@ export class CourseApi { rte ) } else { +<<<<<<< HEAD res.status(406).send(`Invalid Data`) <<<<<<< HEAD <<<<<<< HEAD @@ -1710,11 +1784,36 @@ export class CourseApi { >>>>>>> init courseApi ======= ======= +======= + res.status(406).json(Responses.invalid) +>>>>>>> regresstion testing fixes return >>>>>>> add 30sec delta interval } } + if (req.params.action === 'refresh') { + this.courseInfo.activeRoute.pointTotal = + rte.feature.geometry.coordinates.length + let idx: number = -1 + for (let i = 0; i < rte.feature.geometry.coordinates.length; i++) { + if ( + rte.feature.geometry.coordinates[i][0] === + this.courseInfo.nextPoint.position?.longitude && + rte.feature.geometry.coordinates[i][1] === + this.courseInfo.nextPoint.position?.latitude + ) { + idx = i + } + } + if (idx !== -1) { + this.courseInfo.activeRoute.pointIndex = idx + } + this.emitCourseInfo() + res.status(200).json(Responses.ok) + return + } + // set new destination this.courseInfo.nextPoint.position = this.getRoutePoint( rte, @@ -1755,6 +1854,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD res.status(406).send(`Invalid Data`) return false @@ -1824,6 +1924,14 @@ export class CourseApi { } catch (err) { res.status(406).send(`Invalid Data`) >>>>>>> add 30sec delta interval +======= + res.status(406).json(Responses.invalid) + return false + } + } catch (err) { + console.log(`** Error: unable to retrieve vessel position!`) + res.status(406).json(Responses.invalid) +>>>>>>> regresstion testing fixes return false } } else { @@ -1873,6 +1981,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD res.status(200).send('OK') ======= @@ -1919,6 +2028,10 @@ export class CourseApi { ======= res.status(200).send('OK') >>>>>>> add 30sec delta interval +======= + this.emitCourseInfo() + res.status(200).json(Responses.ok) +>>>>>>> regresstion testing fixes } ) } @@ -1929,9 +2042,11 @@ export class CourseApi { if (route.href) { rte = await this.getRoute(route.href) if (!rte) { + console.log(`** Could not retrieve route information for ${route.href}`) return false } if (!Array.isArray(rte.feature?.geometry?.coordinates)) { + debug(`** Invalid route coordinate data! (${route.href})`) return false } } else { @@ -1988,6 +2103,7 @@ export class CourseApi { route.pointIndex as number, rte ) + newCourse.activeRoute.pointTotal = rte.feature.geometry.coordinates.length // set nextPoint newCourse.nextPoint.position = this.getRoutePoint( @@ -2006,6 +2122,7 @@ export class CourseApi { this.courseInfo.previousPoint.position = position.value this.courseInfo.previousPoint.type = `VesselPosition` } else { + console.log(`** Error: unable to retrieve vessel position!`) return false } } catch (err) { @@ -2072,9 +2189,13 @@ export class CourseApi { newCourse.nextPoint.href = dest.href newCourse.nextPoint.type = 'Waypoint' } else { + debug(`** Invalid waypoint coordinate data! (${dest.href})`) return false } } catch (err) { + console.log( + `** Could not retrieve waypoint information for ${dest.href}` + ) return false } } @@ -2092,11 +2213,21 @@ export class CourseApi { } >>>>>>> add 30sec delta interval +<<<<<<< HEAD <<<<<<< HEAD >>>>>>> chore: lint // set activeroute newCourse.activeRoute.href = route.href ======= +======= + // clear activeRoute values + newCourse.activeRoute.href = null + newCourse.activeRoute.startTime = null + newCourse.activeRoute.pointindex = 0 + newCourse.activeRoute.pointTotal = 0 + newCourse.activeRoute.reverse = false + +>>>>>>> regresstion testing fixes // set previousPoint try { const position: any = this.getVesselPosition() @@ -2109,11 +2240,12 @@ export class CourseApi { return false } } catch (err) { - debug(`** Error: unable to retrieve navigation.position! (${err})`) + console.log(`** Error: unable to retrieve vessel position!`) return false } >>>>>>> add getVesselPosition +<<<<<<< HEAD if (this.isValidArrivalCircle(route.arrivalCircle as number)) { newCourse.nextPoint.arrivalCircle = route.arrivalCircle } @@ -2659,6 +2791,9 @@ export class CourseApi { ======= this.courseInfo = newCourse >>>>>>> add 30sec delta interval +======= + this.courseInfo = newCourse +>>>>>>> regresstion testing fixes return true } @@ -2666,6 +2801,10 @@ export class CourseApi { this.courseInfo.activeRoute.href = null this.courseInfo.activeRoute.startTime = null this.courseInfo.activeRoute.pointIndex = 0 +<<<<<<< HEAD +======= + this.courseInfo.activeRoute.pointTotal = 0 +>>>>>>> regresstion testing fixes this.courseInfo.activeRoute.reverse = false this.courseInfo.nextPoint.href = null this.courseInfo.nextPoint.type = null @@ -2682,6 +2821,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD private isValidArrivalCircle(value: number): boolean { return typeof value === 'number' && value >= 0 @@ -2752,13 +2892,18 @@ export class CourseApi { ======= >>>>>>> init courseApi ======= +======= +>>>>>>> regresstion testing fixes private isValidArrivalCircle(value: number): boolean { return typeof value === 'number' && value >= 0 } private parsePointIndex(index: number, rte: any): number { if (typeof index !== 'number' || !rte) { +<<<<<<< HEAD >>>>>>> chore: lint +======= +>>>>>>> regresstion testing fixes return 0 } if (!rte.feature?.geometry?.coordinates) { @@ -2954,9 +3099,11 @@ export class CourseApi { try { return await this.server.resourcesApi.getResource(h.type, h.id) } catch (err) { + debug(`** Unable to fetch resource: ${h.type}, ${h.id}`) return undefined } } else { + debug(`** Unable to parse href: ${href}`) return undefined } } diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index 65ff7003b..ec4fcbb31 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -35,6 +35,11 @@ "format": "int64", "example": 2 }, + "pointTotal": { + "type": "number", + "format": "int64", + "example": 9 + }, "reverse": { "type": "boolean", "default": false @@ -483,6 +488,14 @@ } } } + }, + + "/vessels/self/navigation/course/activeRoute/refresh": { + "put": { + "tags": ["course/activeRoute"], + "summary": "Refresh route details.", + "description": "Use after active route is modified to refresh pointIndex and pointsTotal values." + } } } diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 7338c6bd7..8942652ab 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -22,6 +22,7 @@ import { Application, NextFunction, Request, Response } from 'express' import { v4 as uuidv4 } from 'uuid' import { WithSecurityStrategy, WithSignalK } from '../../app' +import { Responses } from '../responses' import { buildResource } from './resources' import { validate } from './validate' @@ -72,7 +73,9 @@ export class Resources { typeof provider.methods.setResource !== 'function' || typeof provider.methods.deleteResource !== 'function' ) { - console.error(`Error: Could not register Resource Provider for ${i.toUpperCase()} due to missing provider methods!`) + console.error( + `Error: Could not register Resource Provider for ${i.toUpperCase()} due to missing provider methods!` + ) return } else { provider.methods.pluginId = pluginId @@ -147,7 +150,11 @@ export class Resources { ]?.getResource(req.params.resourceType, req.params.resourceId) res.json(retVal) } catch (err) { - res.status(404).send(`Resource not found! (${req.params.resourceId})`) + res.status(404).json({ + state: 'FAILED', + statusCode: 404, + message: `Resource not found! (${req.params.resourceId})` + }) } } ) @@ -170,7 +177,11 @@ export class Resources { ]?.listResources(req.params.resourceType, req.query) res.json(retVal) } catch (err) { - res.status(404).send(`Error retrieving resources!`) + res.status(404).json({ + state: 'FAILED', + statusCode: 404, + message: `Error retrieving resources!` + }) } } ) @@ -190,7 +201,7 @@ export class Resources { } if (!this.updateAllowed()) { - res.status(403) + res.status(403).json(Responses.unauthorised) return } if ( @@ -200,7 +211,7 @@ export class Resources { ) { <<<<<<< HEAD if (!validate.resource(req.params.resourceType, req.body)) { - res.status(406).send(`Invalid resource data supplied!`) + res.status(406).json(Responses.invalid) return } } @@ -225,13 +236,17 @@ export class Resources { req.body ) ) - res - .status(200) - .send(`New ${req.params.resourceType} resource (${id}) saved.`) + res.status(200).json({ + state: 'COMPLETED', + statusCode: 200, + message: `New ${req.params.resourceType} resource (${id}) saved.` + }) } catch (err) { - res - .status(404) - .send(`Error saving ${req.params.resourceType} resource (${id})!`) + res.status(404).json({ + state: 'FAILED', + statusCode: 404, + message: `Error saving ${req.params.resourceType} resource (${id})!` + }) } } ) @@ -255,7 +270,7 @@ export class Resources { } if (!this.updateAllowed()) { - res.status(403) + res.status(403).json(Responses.unauthorised) return } if ( @@ -270,14 +285,18 @@ export class Resources { isValidId = validate.uuid(req.params.resourceId) } if (!isValidId) { - res - .status(406) - .send(`Invalid resource id provided (${req.params.resourceId})`) + res.status(406).json({ + state: 'FAILED', + statusCode: 406, + message: `Invalid resource id provided (${req.params.resourceId})` + }) return } + debug('req.body') + debug(req.body) if (!validate.resource(req.params.resourceType, req.body)) { - res.status(406).send(`Invalid resource data supplied!`) + res.status(406).json(Responses.invalid) return } } @@ -299,17 +318,17 @@ export class Resources { req.body ) ) - res - .status(200) - .send( - `${req.params.resourceType} resource (${req.params.resourceId}) saved.` - ) + res.status(200).json({ + state: 'COMPLETED', + statusCode: 200, + message: `${req.params.resourceType} resource (${req.params.resourceId}) saved.` + }) } catch (err) { - res - .status(404) - .send( - `Error saving ${req.params.resourceType} resource (${req.params.resourceId})!` - ) + res.status(404).json({ + state: 'FAILED', + statusCode: 404, + message: `Error saving ${req.params.resourceType} resource (${req.params.resourceId})!` + }) } } ) @@ -330,7 +349,7 @@ export class Resources { } if (!this.updateAllowed()) { - res.status(403) + res.status(403).json(Responses.unauthorised) return } try { @@ -346,11 +365,17 @@ export class Resources { null ) ) - res.status(200).send(`Resource (${req.params.resourceId}) deleted.`) + res.status(200).json({ + state: 'COMPLETED', + statusCode: 200, + message: `Resource (${req.params.resourceId}) deleted.` + }) } catch (err) { - res - .status(400) - .send(`Error deleting resource (${req.params.resourceId})!`) + res.status(400).json({ + state: 'FAILED', + statusCode: 400, + message: `Error deleting resource (${req.params.resourceId})!` + }) } } ) @@ -378,29 +403,41 @@ export class Resources { debug(`** POST ${SIGNALK_API_PATH}/resources/set/:resourceType`) if (!this.updateAllowed()) { - res.status(403) + res.status(403).json(Responses.unauthorised) return } - let apiData = this.processApiRequest(req) + const apiData = this.processApiRequest(req) debug(apiData) if (!this.checkForProvider(apiData.type)) { - res.status(501).send(`No provider for ${apiData.type}!`) + res.status(501).json({ + state: 'FAILED', + statusCode: 501, + message: `No provider for ${apiData.type}!` + }) return } if (!apiData.value) { - res.status(406).send(`Invalid resource data supplied!`) + res.status(406).json(Responses.invalid) return } if (apiData.type === 'charts') { if (!validate.chartId(apiData.id)) { - res.status(406).send(`Invalid chart resource id supplied!`) + res.status(406).json({ + state: 'FAILED', + statusCode: 406, + message: `Invalid chart resource id supplied!` + }) return } } else { if (!validate.uuid(apiData.id)) { - res.status(406).send(`Invalid resource id supplied!`) + res.status(406).json({ + state: 'FAILED', + statusCode: 406, + message: `Invalid resource id supplied!` + }) return } } @@ -415,9 +452,17 @@ export class Resources { this.resProvider[apiData.type]?.pluginId as string, this.buildDeltaMsg(apiData.type, apiData.id, apiData.value) ) - res.status(200).send(`SUCCESS: New ${req.params.resourceType} resource created.`) + res.status(200).json({ + state: 'COMPLETED', + statusCode: 200, + message: `SUCCESS: New ${req.params.resourceType} resource created.` + }) } catch (err) { - res.status(404).send(`ERROR: Could not create ${req.params.resourceType} resource!`) + res.status(404).json({ + state: 'FAILED', + statusCode: 404, + message: `ERROR: Could not create ${req.params.resourceType} resource!` + }) } } ) @@ -429,7 +474,7 @@ export class Resources { ) if (!this.updateAllowed()) { - res.status(403) + res.status(403).json(Responses.unauthorised) return } @@ -984,22 +1029,34 @@ export class Resources { const apiData = this.processApiRequest(req) if (!this.checkForProvider(apiData.type)) { - res.status(501).send(`No provider for ${apiData.type}!`) + res.status(501).json({ + state: 'FAILED', + statusCode: 501, + message: `No provider for ${apiData.type}!` + }) return } if (!apiData.value) { - res.status(406).send(`Invalid resource data supplied!`) + res.status(406).json(Responses.invalid) return >>>>>>> add securityStrategy test } if (apiData.type === 'charts') { if (!validate.chartId(apiData.id)) { - res.status(406).send(`Invalid chart resource id supplied!`) + res.status(406).json({ + state: 'FAILED', + statusCode: 406, + message: `Invalid chart resource id supplied!` + }) return } } else { if (!validate.uuid(apiData.id)) { - res.status(406).send(`Invalid resource id supplied!`) + res.status(406).json({ + state: 'FAILED', + statusCode: 406, + message: `Invalid resource id supplied!` + }) return } } @@ -1014,16 +1071,24 @@ export class Resources { this.resProvider[apiData.type]?.pluginId as string, this.buildDeltaMsg(apiData.type, apiData.id, apiData.value) ) - res.status(200).send(`SUCCESS: ${req.params.resourceType} resource updated.`) + res.status(200).json({ + state: 'COMPLETED', + statusCode: 200, + message: `SUCCESS: ${req.params.resourceType} resource updated.` + }) } catch (err) { - res.status(404).send(`ERROR: ${req.params.resourceType} resource could not be updated!`) + res.status(404).json({ + state: 'FAILED', + statusCode: 404, + message: `ERROR: ${req.params.resourceType} resource could not be updated!` + }) } } ) } private processApiRequest(req: Request) { - let apiReq: any = { + const apiReq: any = { type: undefined, id: undefined, value: undefined @@ -1049,7 +1114,9 @@ export class Resources { apiReq.id = req.params.resourceId ? req.params.resourceId - : (apiReq.type === 'charts' ? apiReq.value.identifier : UUID_PREFIX + uuidv4()) + : apiReq.type === 'charts' + ? apiReq.value.identifier + : UUID_PREFIX + uuidv4() return apiReq } @@ -1072,7 +1139,7 @@ export class Resources { private checkForProvider(resType: SignalKResourceType): boolean { debug(`** checkForProvider(${resType})`) debug(this.resProvider[resType]) - return (this.resProvider[resType]) ? true : false + return this.resProvider[resType] ? true : false } <<<<<<< HEAD diff --git a/src/api/responses.ts b/src/api/responses.ts new file mode 100644 index 000000000..82c009c27 --- /dev/null +++ b/src/api/responses.ts @@ -0,0 +1,31 @@ +export interface ApiResponse { + state: 'FAILED' | 'COMPLETED' | 'PENDING' + statusCode: number + message: string + requestId?: string + href?: string + token?: string +} + +export const Responses = { + ok: { + state: 'COMPLETED', + statusCode: 200, + message: 'OK' + }, + invalid: { + state: 'FAILED', + statusCode: 406, + message: `Invalid Data supplied.` + }, + unauthorised: { + state: 'FAILED', + statusCode: 403, + message: 'Unauthorised' + }, + notFound: { + state: 'FAILED', + statusCode: 404, + message: 'Resource not found.' + } +} diff --git a/src/serverstate/store.ts b/src/serverstate/store.ts index 6839806ea..99a2d74de 100644 --- a/src/serverstate/store.ts +++ b/src/serverstate/store.ts @@ -10,17 +10,22 @@ export class Store { this.filePath = filePath this.fileName = fileName this.init().catch(error => { - console.log(`Could not initialise ${path.join(this.filePath, this.fileName)}`) + console.log( + `Could not initialise ${path.join(this.filePath, this.fileName)}` + ) console.log(error) }) } async read(): Promise { try { - const data = await readFile(path.join(this.filePath, this.fileName), 'utf8') + const data = await readFile( + path.join(this.filePath, this.fileName), + 'utf8' + ) return JSON.parse(data) } catch (error) { - throw(error) + throw error } } From cabf92bd07c45c66d41e98adbfe1d4c234e628be Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 8 Jan 2022 15:43:20 +1030 Subject: [PATCH 384/410] add activeRouote/reverse path --- src/api/course/index.ts | 18 ++++++++++++++++++ src/api/course/openApi.json | 27 +++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 009e66ed9..a8727f05e 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1791,6 +1791,16 @@ export class CourseApi { >>>>>>> add 30sec delta interval } } + // reverse direction from current point + if (req.params.action === 'reverse') { + if (typeof req.body.pointIndex === 'number') { + this.courseInfo.activeRoute.pointIndex = req.body.pointIndex + } else { + this.courseInfo.activeRoute.pointIndex = this.calcReversedIndex() + } + this.courseInfo.activeRoute.reverse = !this.courseInfo.activeRoute + .reverse + } if (req.params.action === 'refresh') { this.courseInfo.activeRoute.pointTotal = @@ -2036,6 +2046,14 @@ export class CourseApi { ) } + private calcReversedIndex(): number { + return ( + this.courseInfo.activeRoute.pointTotal - + 1 - + this.courseInfo.activeRoute.pointIndex + ) + } + private async activateRoute(route: ActiveRoute): Promise { let rte: any diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index ec4fcbb31..5e1e1f776 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -496,6 +496,33 @@ "summary": "Refresh route details.", "description": "Use after active route is modified to refresh pointIndex and pointsTotal values." } + }, + + "/vessels/self/navigation/course/activeRoute/reverse": { + "put": { + "tags": ["course/activeRoute"], + "summary": "Reverse direction of route", + "description": "Reverse direction of route from current point or from supplied pointIndex.", + "requestBody": { + "description": "Reverse route", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "pointIndex": { + "type": "number", + "description": "Index of point in route to use as destination", + "minimum": 0, + "example": 2 + } + } + } + } + } + } + } } } From 76efbb0a2e0145c21c3013733d8856d0bed441e6 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 9 Jan 2022 18:06:25 +1030 Subject: [PATCH 385/410] chore: update OpenApi responses --- src/api/course/index.ts | 40 +++ src/api/course/openApi.json | 278 +++++++++++++++- src/api/resources/openApi.json | 579 +++++++++++++++++++++++++++------ 3 files changed, 798 insertions(+), 99 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index a8727f05e..a1dd5bc43 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -2263,9 +2263,49 @@ export class CourseApi { } >>>>>>> add getVesselPosition +<<<<<<< HEAD <<<<<<< HEAD if (this.isValidArrivalCircle(route.arrivalCircle as number)) { newCourse.nextPoint.arrivalCircle = route.arrivalCircle +======= + this.courseInfo = newCourse + return true + } + + private clearDestination() { + this.courseInfo.activeRoute.href = null + this.courseInfo.activeRoute.startTime = null + this.courseInfo.activeRoute.pointIndex = 0 + this.courseInfo.activeRoute.pointTotal = 0 + this.courseInfo.activeRoute.reverse = false + this.courseInfo.nextPoint.href = null + this.courseInfo.nextPoint.type = null + this.courseInfo.nextPoint.position = null + this.courseInfo.previousPoint.href = null + this.courseInfo.previousPoint.type = null + this.courseInfo.previousPoint.position = null + } + + private isValidArrivalCircle(value: number): boolean { + return typeof value === 'number' && value >= 0 + } + + private parsePointIndex(index: number, rte: any): number { + if (typeof index !== 'number' || !rte) { + return 0 + } + if (!rte.feature?.geometry?.coordinates) { + return 0 + } + if (!Array.isArray(rte.feature?.geometry?.coordinates)) { + return 0 + } + if (index < 0) { + return 0 + } + if (index > rte.feature?.geometry?.coordinates.length - 1) { + return rte.feature?.geometry?.coordinates.length -1 +>>>>>>> chore: update OpenApi responses } ======= this.courseInfo.activeRoute.pointIndex, diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index 5e1e1f776..549c625ef 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -120,6 +120,7 @@ <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD "description": "Sets previousPoint value to current vessel location" ======= @@ -147,6 +148,31 @@ "type": "string", "example": "freeboard-sk" } +======= + "description": "Sets previousPoint value to current vessel location", + "responses": { + "default": { + "description": "Default response format", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } +>>>>>>> chore: update OpenApi responses } } } @@ -155,6 +181,7 @@ <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD >>>>>>> init courseApi ======= "description": "Sets previousPoint value to current vessel location" @@ -174,6 +201,8 @@ ======= "description": "Sets previousPoint value to current vessel location" >>>>>>> chore: lint +======= +>>>>>>> chore: update OpenApi responses } }, @@ -228,6 +257,33 @@ } } } + }, + "responses": { + "default": { + "description": "Default response format", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } + } + } + } + } } }, "delete": { @@ -240,6 +296,7 @@ <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD "description": "Sets activeRoute, nextPoint & previousPoint values to null" ======= @@ -267,6 +324,31 @@ "type": "string", "example": "freeboard-sk" } +======= + "description": "Sets activeRoute, nextPoint & previousPoint values to null", + "responses": { + "default": { + "description": "Default response format", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } +>>>>>>> chore: update OpenApi responses } } } @@ -275,6 +357,7 @@ <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD >>>>>>> init courseApi ======= "description": "Sets activeRoute, nextPoint & previousPoint values to null" @@ -294,6 +377,8 @@ ======= "description": "Sets activeRoute, nextPoint & previousPoint values to null" >>>>>>> chore: lint +======= +>>>>>>> chore: update OpenApi responses } }, @@ -320,6 +405,33 @@ } } } + }, + "responses": { + "default": { + "description": "Default response format", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } + } + } + } + } } } }, @@ -365,6 +477,33 @@ } } } + }, + "responses": { + "default": { + "description": "Default response format", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } + } + } + } + } } }, "delete": { @@ -377,6 +516,7 @@ <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD "description": "Sets activeRoute, nextPoint & previousPoint values to null" ======= @@ -404,6 +544,31 @@ "type": "string", "example": "freeboard-sk" } +======= + "description": "Sets activeRoute, nextPoint & previousPoint values to null", + "responses": { + "default": { + "description": "Default response format", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } +>>>>>>> chore: update OpenApi responses } } } @@ -412,6 +577,7 @@ <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD >>>>>>> init courseApi ======= "description": "Sets activeRoute, nextPoint & previousPoint values to null" @@ -431,6 +597,8 @@ ======= "description": "Sets activeRoute, nextPoint & previousPoint values to null" >>>>>>> chore: lint +======= +>>>>>>> chore: update OpenApi responses } }, @@ -458,6 +626,33 @@ } } } + }, + "responses": { + "default": { + "description": "Default response format", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } + } + } + } + } } } }, @@ -486,6 +681,33 @@ } } } + }, + "responses": { + "default": { + "description": "Default response format", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } + } + } + } + } } } }, @@ -494,7 +716,34 @@ "put": { "tags": ["course/activeRoute"], "summary": "Refresh route details.", - "description": "Use after active route is modified to refresh pointIndex and pointsTotal values." + "description": "Use after active route is modified to refresh pointIndex and pointsTotal values.", + "responses": { + "default": { + "description": "Default response format", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } + } + } + } + } + } } }, @@ -521,6 +770,33 @@ } } } + }, + "responses": { + "default": { + "description": "Default response format", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } + } + } + } + } } } } diff --git a/src/api/resources/openApi.json b/src/api/resources/openApi.json index 114c35f38..cc494d86e 100644 --- a/src/api/resources/openApi.json +++ b/src/api/resources/openApi.json @@ -295,12 +295,27 @@ >>>>>>> addressed comments re parameters ======= "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -498,12 +513,27 @@ >>>>>>> addressed comments re parameters ======= "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -539,12 +569,27 @@ >>>>>>> addressed comments re parameters ======= "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -690,12 +735,27 @@ >>>>>>> addressed comments re parameters ======= "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -900,12 +960,27 @@ >>>>>>> addressed comments re parameters ======= "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -941,12 +1016,27 @@ >>>>>>> addressed comments re parameters ======= "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -1028,12 +1118,27 @@ >>>>>>> add API definitions }, "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -1196,12 +1301,27 @@ >>>>>>> addressed comments re parameters ======= "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -1216,12 +1336,27 @@ <<<<<<< HEAD "summary": "Remove Note with supplied id", "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -1464,12 +1599,27 @@ >>>>>>> addressed comments re parameters ======= "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -1726,12 +1876,27 @@ >>>>>>> addressed comments re parameters ======= "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -1746,12 +1911,27 @@ <<<<<<< HEAD "summary": "Remove Region with supplied id", "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -1875,12 +2055,27 @@ } }, "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -2033,12 +2228,27 @@ } }, "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -2050,12 +2260,27 @@ "tags": ["resources/charts"], "summary": "Remove Chart with supplied id", "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -2117,12 +2342,27 @@ } }, "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -2269,12 +2509,27 @@ } }, "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -2366,12 +2621,27 @@ } }, "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -2440,12 +2710,27 @@ ======= }, "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -2522,12 +2807,27 @@ } }, "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -2575,6 +2875,7 @@ "description": "A url to the tilemap of the chart for use in TMS chartplotting apps", "example":"http://{server}:8080/mapcache/NZ615" }, +<<<<<<< HEAD "region": { "type": "string", "description": "Region related to note. A pointer to a region UUID. Alternative to geohash" @@ -3421,6 +3722,8 @@ "type": "string", "description": "MIME type of the note" }, +======= +>>>>>>> chore: update OpenApi responses "url": { "type": "string", "description": "Location of the note contents" @@ -3431,12 +3734,32 @@ } }, "responses": { +<<<<<<< HEAD "200": { "description": "OK", +======= + "default": { + "description": "Default response format", +>>>>>>> chore: update OpenApi responses "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -3500,12 +3823,27 @@ } }, "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -3569,12 +3907,27 @@ } }, "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -3691,12 +4044,27 @@ } }, "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } @@ -3805,12 +4173,27 @@ } }, "responses": { - "200": { - "description": "OK", + "default": { + "description": "Default response format", "content": { - "text/plain": { + "application/json": { "schema": { - "type": "string" + "type": "object", + "description": "Signal K response", + "required": ["statusCode", "status", "message"], + "properties": { + "statusCode": { + "type": "number", + "format": "int64" + }, + "status": { + "type": "string", + "enum": ["COMPLETED", "FAILED"] + }, + "message": { + "type": "string" + } + } } } } From 37a4966ee1fc80c9736adc4791911add7c23b247 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sun, 9 Jan 2022 18:08:43 +1030 Subject: [PATCH 386/410] regression testing fixes 2 --- src/api/course/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index a1dd5bc43..b354b3b38 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -2241,7 +2241,7 @@ export class CourseApi { // clear activeRoute values newCourse.activeRoute.href = null newCourse.activeRoute.startTime = null - newCourse.activeRoute.pointindex = 0 + newCourse.activeRoute.pointIndex = 0 newCourse.activeRoute.pointTotal = 0 newCourse.activeRoute.reverse = false From 90addc78b624686c3898db2e0f6f0b4dd8c3cb8e Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 9 Jan 2022 20:14:07 +0200 Subject: [PATCH 387/410] style: linter fix --- src/api/course/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index b354b3b38..f9b8db7c7 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -2304,8 +2304,12 @@ export class CourseApi { return 0 } if (index > rte.feature?.geometry?.coordinates.length - 1) { +<<<<<<< HEAD return rte.feature?.geometry?.coordinates.length -1 >>>>>>> chore: update OpenApi responses +======= + return rte.feature?.geometry?.coordinates.length - 1 +>>>>>>> style: linter fix } ======= this.courseInfo.activeRoute.pointIndex, From 652f093eef3866167ded6796f6d4d6e96d61a78f Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 9 Jan 2022 20:15:21 +0200 Subject: [PATCH 388/410] style: no-inferrable-types --- src/api/course/index.ts | 11 ++++++++--- src/api/resources/resources.ts | 4 ++-- src/deltaPriority.ts | 2 +- src/serverstate/store.ts | 6 +++--- tslint.js | 3 ++- 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index f9b8db7c7..8276a0c72 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -144,6 +144,7 @@ import { Responses } from '../responses' const debug = Debug('signalk:courseApi') +<<<<<<< HEAD const SIGNALK_API_PATH: string = `/signalk/v1/api` const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` <<<<<<< HEAD @@ -188,8 +189,12 @@ const API_METHODS: string[] = [] >>>>>>> init courseApi ======= >>>>>>> update detlas +======= +const SIGNALK_API_PATH = `/signalk/v1/api` +const COURSE_API_PATH = `${SIGNALK_API_PATH}/vessels/self/navigation/course` +>>>>>>> style: no-inferrable-types -const DELTA_INTERVAL: number = 30000 +const DELTA_INTERVAL = 30000 interface CourseApplication extends Application, @@ -1805,7 +1810,7 @@ export class CourseApi { if (req.params.action === 'refresh') { this.courseInfo.activeRoute.pointTotal = rte.feature.geometry.coordinates.length - let idx: number = -1 + let idx = -1 for (let i = 0; i < rte.feature.geometry.coordinates.length; i++) { if ( rte.feature.geometry.coordinates[i][0] === @@ -3616,7 +3621,7 @@ export class CourseApi { } } - private emitCourseInfo(noSave: boolean = false) { + private emitCourseInfo(noSave = false) { this.server.handleMessage('courseApi', this.buildDeltaMsg()) if (!noSave) { this.store.write(this.courseInfo).catch(error => { diff --git a/src/api/resources/resources.ts b/src/api/resources/resources.ts index 808371a5e..43d578f7f 100644 --- a/src/api/resources/resources.ts +++ b/src/api/resources/resources.ts @@ -56,7 +56,7 @@ const buildRoute = (rData: any): any => { if (!Array.isArray(rData.points)) { return null } - let isValid: boolean = true + let isValid = true rData.points.forEach((p: any) => { if (!isValidCoordinate(p)) { isValid = false @@ -185,7 +185,7 @@ const buildRegion = (rData: any): any => { if (!Array.isArray(rData.points)) { return null } - let isValid: boolean = true + let isValid = true rData.points.forEach((p: any) => { if (!isValidCoordinate(p)) { isValid = false diff --git a/src/deltaPriority.ts b/src/deltaPriority.ts index 38086bc63..0f70bb870 100644 --- a/src/deltaPriority.ts +++ b/src/deltaPriority.ts @@ -63,7 +63,7 @@ export type ToPreferredDelta = ( export const getToPreferredDelta = ( sourcePrioritiesData: SourcePrioritiesData, - unknownSourceTimeout: number = 10000 + unknownSourceTimeout = 10000 ): ToPreferredDelta => { if (!sourcePrioritiesData) { debug('No priorities data') diff --git a/src/serverstate/store.ts b/src/serverstate/store.ts index 99a2d74de..d6db82f7b 100644 --- a/src/serverstate/store.ts +++ b/src/serverstate/store.ts @@ -3,10 +3,10 @@ import { access, mkdir, readFile, writeFile } from 'fs/promises' import path from 'path' export class Store { - private filePath: string = '' - private fileName: string = '' + private filePath = '' + private fileName = '' - constructor(filePath: string, fileName: string = 'settings.json') { + constructor(filePath: string, fileName = 'settings.json') { this.filePath = filePath this.fileName = fileName this.init().catch(error => { diff --git a/tslint.js b/tslint.js index 36884a0e2..140f322bd 100644 --- a/tslint.js +++ b/tslint.js @@ -3,7 +3,8 @@ const TSRULES = { 'member-access': [true, 'no-public'], 'interface-name': false, 'max-classes-per-file': false, - 'no-any': false + 'no-any': false, + 'no-inferrable-types': true } const UNIVERSAL_RULES = { From 49c27ec693ff6556c94d5cffac3f8ab603e1c866 Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 9 Jan 2022 20:20:24 +0200 Subject: [PATCH 389/410] fix: double definition of CourseApplication --- src/api/course/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 8276a0c72..904fd9809 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -200,6 +200,7 @@ interface CourseApplication extends Application, WithConfig, WithSignalK, +<<<<<<< HEAD WithSecurityStrategy {} interface CourseApplication extends Application { @@ -330,6 +331,9 @@ interface CourseApplication extends Application { >>>>>>> persist courseInfo to settings file ======= >>>>>>> add getVesselPosition +======= + WithSecurityStrategy { +>>>>>>> fix: double definition of CourseApplication resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } From 3cf2c61bb2b81d2a10ea59f56a7eab69b17e499b Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 9 Jan 2022 20:25:18 +0200 Subject: [PATCH 390/410] chore: use global Position type --- src/api/course/index.ts | 13 ++----------- src/types.ts | 1 + 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 904fd9809..090bc9d9a 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -139,6 +139,7 @@ import _ from 'lodash' import path from 'path' import { WithConfig, WithSecurityStrategy, WithSignalK } from '../../app' import { Store } from '../../serverstate/store' +import { Position } from '../../types' import { Responses } from '../responses' >>>>>>> regresstion testing fixes @@ -344,11 +345,7 @@ interface DestinationBase { arrivalCircle?: number } interface Destination extends DestinationBase { - position?: { - latitude: number - longitude: number - altitude?: number - } + position?: Position type?: string } <<<<<<< HEAD @@ -377,12 +374,6 @@ interface ActiveRoute extends DestinationBase { reverse?: boolean } -interface Position { - latitude: number - longitude: number - altitude?: number -} - interface CourseInfo { activeRoute: { href: string | null diff --git a/src/types.ts b/src/types.ts index cc475b9d9..66f9e411c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -91,4 +91,5 @@ export type Value = object | number | string | null export interface Position { latitude: number longitude: number + altitude?: number } From 97785a4b9ead1cc50d0645d8ac63ef5d743395c7 Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 9 Jan 2022 20:50:14 +0200 Subject: [PATCH 391/410] refactor: prefer object spread over Object.assign Allows typechecking. --- src/api/course/index.ts | 9 ++++++--- tslint.js | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 090bc9d9a..afce59d0e 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -2081,6 +2081,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD const newCourse: any = {} Object.assign(newCourse, this.courseInfo) @@ -2102,13 +2103,16 @@ export class CourseApi { const newCourse: any = {} Object.assign(newCourse, this.courseInfo) >>>>>>> add 30sec delta interval +======= + const newCourse: CourseInfo = {...this.courseInfo} +>>>>>>> refactor: prefer object spread over Object.assign >>>>>>> chore: lint // set activeroute newCourse.activeRoute.href = route.href if (this.isValidArrivalCircle(route.arrivalCircle as number)) { - newCourse.nextPoint.arrivalCircle = route.arrivalCircle + newCourse.nextPoint.arrivalCircle = route.arrivalCircle as number } newCourse.activeRoute.startTime = new Date().toISOString() @@ -2161,8 +2165,7 @@ export class CourseApi { } private async setDestination(dest: any): Promise { - const newCourse: any = {} - Object.assign(newCourse, this.courseInfo) + const newCourse: CourseInfo = {...this.courseInfo} // set nextPoint if (this.isValidArrivalCircle(dest.arrivalCircle)) { diff --git a/tslint.js b/tslint.js index 140f322bd..dc1a72635 100644 --- a/tslint.js +++ b/tslint.js @@ -4,7 +4,8 @@ const TSRULES = { 'interface-name': false, 'max-classes-per-file': false, 'no-any': false, - 'no-inferrable-types': true + 'no-inferrable-types': true, + 'prefer-object-spread': true } const UNIVERSAL_RULES = { From 269c8969d6eea6f2d07e9cb308f05dc2a29f372d Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 9 Jan 2022 21:08:42 +0200 Subject: [PATCH 392/410] fix: SecurityStrategy.shouldAllowPut must have Request shouldAllowPut first Parameter must be the request that is being checked for access. --- src/api/resources/index.ts | 14 +++++++------- src/types.ts | 6 +++++- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 8942652ab..061bb8b23 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -114,9 +114,9 @@ export class Resources { this.initResourceRoutes() } - private updateAllowed(): boolean { + private updateAllowed(req: Request): boolean { return this.server.securityStrategy.shouldAllowPut( - this.server, + req, 'vessels.self', null, 'resources' @@ -200,7 +200,7 @@ export class Resources { return } - if (!this.updateAllowed()) { + if (!this.updateAllowed(req)) { res.status(403).json(Responses.unauthorised) return } @@ -269,7 +269,7 @@ export class Resources { return } - if (!this.updateAllowed()) { + if (!this.updateAllowed(req)) { res.status(403).json(Responses.unauthorised) return } @@ -348,7 +348,7 @@ export class Resources { return } - if (!this.updateAllowed()) { + if (!this.updateAllowed(req)) { res.status(403).json(Responses.unauthorised) return } @@ -402,7 +402,7 @@ export class Resources { async (req: Request, res: Response) => { debug(`** POST ${SIGNALK_API_PATH}/resources/set/:resourceType`) - if (!this.updateAllowed()) { + if (!this.updateAllowed(req)) { res.status(403).json(Responses.unauthorised) return } @@ -473,7 +473,7 @@ export class Resources { `** PUT ${SIGNALK_API_PATH}/resources/set/:resourceType/:resourceId` ) - if (!this.updateAllowed()) { + if (!this.updateAllowed(req)) { res.status(403).json(Responses.unauthorised) return } diff --git a/src/types.ts b/src/types.ts index 66f9e411c..459343550 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,9 @@ import { FullSignalK } from '@signalk/signalk-schema' +<<<<<<< HEAD import { SecurityStrategy } from './security' +======= +import { Request } from 'express' +>>>>>>> fix: SecurityStrategy.shouldAllowPut must have Request import SubscriptionManager from './subscriptionmanager' export interface HelloMessage { @@ -18,7 +22,7 @@ export interface SecurityStrategy { shouldFilterDeltas: () => boolean filterReadDelta: (user: any, delta: any) => any shouldAllowPut: ( - req: any, + req: Request, context: string, source: any, path: string From 348e331bdb3e3ef9f145358540b24cb4cbbb8fb4 Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 9 Jan 2022 21:11:00 +0200 Subject: [PATCH 393/410] fix: updateAllowed(request) is required --- src/api/course/index.ts | 46 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index afce59d0e..3a8079799 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -573,9 +573,9 @@ export class CourseApi { } } - private updateAllowed(): boolean { + private updateAllowed(request: Request): boolean { return this.server.securityStrategy.shouldAllowPut( - this.server, + request, 'vessels.self', null, 'navigation.course' @@ -657,6 +657,7 @@ export class CourseApi { `${COURSE_API_PATH}/restart`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/restart`) +<<<<<<< HEAD if (!this.updateAllowed()) { <<<<<<< HEAD <<<<<<< HEAD @@ -731,6 +732,9 @@ export class CourseApi { res.status(403).send('Unauthorised') >>>>>>> add 30sec delta interval ======= +======= + if (!this.updateAllowed(req)) { +>>>>>>> fix: updateAllowed(request) is required res.status(403).json(Responses.unauthorised) >>>>>>> regresstion testing fixes return @@ -1005,7 +1009,7 @@ export class CourseApi { >>>>>>> init courseApi ======= debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) - if (!this.updateAllowed()) { + if (!this.updateAllowed(req)) { res.status(403).json(Responses.unauthorised) return } @@ -1036,6 +1040,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (!this.updateAllowed()) { res.status(403).send('Unauthorised') @@ -1072,6 +1077,9 @@ export class CourseApi { >>>>>>> init courseApi ======= if (!this.updateAllowed()) { +======= + if (!this.updateAllowed(req)) { +>>>>>>> fix: updateAllowed(request) is required res.status(403).json(Responses.unauthorised) return } @@ -1166,6 +1174,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD if (!this.updateAllowed()) { res.status(403).send('Unauthorised') @@ -1175,6 +1184,9 @@ export class CourseApi { >>>>>>> init courseApi ======= if (!this.updateAllowed()) { +======= + if (!this.updateAllowed(req)) { +>>>>>>> fix: updateAllowed(request) is required res.status(403).json(Responses.unauthorised) return } @@ -1188,10 +1200,19 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD // set activeRoute +<<<<<<< HEAD ======= ======= if (!this.updateAllowed()) { res.status(403).send('Unauthorised') +======= + this.server.put( + `${COURSE_API_PATH}/activeRoute`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/activeRoute`) + if (!this.updateAllowed(req)) { + res.status(403).json(Responses.unauthorised) +>>>>>>> fix: updateAllowed(request) is required return } <<<<<<< HEAD @@ -1213,6 +1234,7 @@ export class CourseApi { } ) +<<<<<<< HEAD <<<<<<< HEAD // set / clear activeRoute >>>>>>> init courseApi @@ -1229,6 +1251,15 @@ export class CourseApi { ======= if (!this.updateAllowed()) { res.status(403).send('Unauthorised') +======= + // clear activeRoute /destination + this.server.delete( + `${COURSE_API_PATH}/activeRoute`, + async (req: Request, res: Response) => { + debug(`** DELETE ${COURSE_API_PATH}/activeRoute`) + if (!this.updateAllowed(req)) { + res.status(403).json(Responses.unauthorised) +>>>>>>> fix: updateAllowed(request) is required return } >>>>>>> enable put processing @@ -1238,6 +1269,7 @@ export class CourseApi { } ) +<<<<<<< HEAD <<<<<<< HEAD // set / clear activeRoute >>>>>>> init courseApi @@ -1248,6 +1280,14 @@ export class CourseApi { ======= if (!this.updateAllowed()) { res.status(403) +======= + this.server.put( + `${COURSE_API_PATH}/activeRoute/:action`, + async (req: Request, res: Response) => { + debug(`** PUT ${COURSE_API_PATH}/activeRoute/${req.params.action}`) + if (!this.updateAllowed(req)) { + res.status(403).json(Responses.unauthorised) +>>>>>>> fix: updateAllowed(request) is required return } >>>>>>> enable put processing From 9b1e0a84a3e0bf7991ab0f15c8bf1e42110ca024 Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 9 Jan 2022 21:11:31 +0200 Subject: [PATCH 394/410] chore: this.server is initialised in constructor --- src/api/course/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 3a8079799..755b3512d 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -437,6 +437,7 @@ export class CourseApi { private async start(app: any) { debug(`** Initialise ${COURSE_API_PATH} path handler **`) +<<<<<<< HEAD this.server = app <<<<<<< HEAD this.initResourceRoutes() @@ -543,6 +544,8 @@ export class CourseApi { this.emitCourseInfo() ======= ======= +======= +>>>>>>> chore: this.server is initialised in constructor this.initCourseRoutes() >>>>>>> regresstion testing fixes From c29b35580a5f8333052b2c91df36bd3f384f8657 Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Sun, 9 Jan 2022 21:24:57 +0200 Subject: [PATCH 395/410] refactor: use Destination type --- src/api/course/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 755b3512d..952bd6a0a 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -2207,12 +2207,12 @@ export class CourseApi { return true } - private async setDestination(dest: any): Promise { + private async setDestination(dest: Destination): Promise { const newCourse: CourseInfo = {...this.courseInfo} // set nextPoint if (this.isValidArrivalCircle(dest.arrivalCircle)) { - newCourse.nextPoint.arrivalCircle = dest.arrivalCircle + newCourse.nextPoint.arrivalCircle = dest.arrivalCircle as number } <<<<<<< HEAD @@ -2332,7 +2332,7 @@ export class CourseApi { this.courseInfo.previousPoint.position = null } - private isValidArrivalCircle(value: number): boolean { + private isValidArrivalCircle(value: number | undefined): boolean { return typeof value === 'number' && value >= 0 } From 23322dd002d976403c99714bb010fc2c83097991 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 10 Jan 2022 16:43:37 +1030 Subject: [PATCH 396/410] review updates 1 --- src/api/course/index.ts | 33 ++++++++++++-- src/api/resources/types.ts | 85 +++++++++++++++++++++++++++++++++++ src/api/resources/validate.ts | 11 ++--- 3 files changed, 121 insertions(+), 8 deletions(-) create mode 100644 src/api/resources/types.ts diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 952bd6a0a..ba8ba8c0e 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -140,6 +140,7 @@ import path from 'path' import { WithConfig, WithSecurityStrategy, WithSignalK } from '../../app' import { Store } from '../../serverstate/store' import { Position } from '../../types' +import { Route } from '../resources/types' import { Responses } from '../responses' >>>>>>> regresstion testing fixes @@ -2125,6 +2126,7 @@ export class CourseApi { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD const newCourse: any = {} Object.assign(newCourse, this.courseInfo) @@ -2149,6 +2151,9 @@ export class CourseApi { ======= const newCourse: CourseInfo = {...this.courseInfo} >>>>>>> refactor: prefer object spread over Object.assign +======= + const newCourse: CourseInfo = { ...this.courseInfo } +>>>>>>> review updates 1 >>>>>>> chore: lint // set activeroute @@ -2208,7 +2213,7 @@ export class CourseApi { } private async setDestination(dest: Destination): Promise { - const newCourse: CourseInfo = {...this.courseInfo} + const newCourse: CourseInfo = { ...this.courseInfo } // set nextPoint if (this.isValidArrivalCircle(dest.arrivalCircle)) { @@ -2248,7 +2253,10 @@ export class CourseApi { href.type, href.id ) - if (r.position && typeof r.position?.latitude !== 'undefined') { + if ( + typeof r.position?.latitude !== 'undefined' && + typeof r.position?.longitude !== 'undefined' + ) { newCourse.nextPoint.position = r.position newCourse.nextPoint.href = dest.href newCourse.nextPoint.type = 'Waypoint' @@ -2262,6 +2270,9 @@ export class CourseApi { ) return false } + } else { + debug(`** Invalid href! (${dest.href})`) + return false } } else if (dest.position) { newCourse.nextPoint.href = null @@ -2394,6 +2405,7 @@ export class CourseApi { newCourse.activeRoute.reverse = route.reverse } +<<<<<<< HEAD newCourse.activeRoute.pointIndex = this.parsePointIndex( route.pointIndex as number, rte @@ -3202,6 +3214,9 @@ export class CourseApi { } private async getRoute(href: string): Promise { +======= + private async getRoute(href: string): Promise { +>>>>>>> review updates 1 const h = this.parseHref(href) if (h) { try { @@ -3263,6 +3278,18 @@ export class CourseApi { 'navigation.courseRhumbline' ] + let course = null + if (this.courseInfo.activeRoute.href) { + course = this.courseInfo + } else if (this.courseInfo.nextPoint.position) { + course = { + nextPoint: this.courseInfo.nextPoint, + previousPoint: this.courseInfo.previousPoint + } + } + + debug(course) + values.push({ <<<<<<< HEAD <<<<<<< HEAD @@ -3281,7 +3308,7 @@ export class CourseApi { ======= >>>>>>> update detlas path: `navigation.course`, - value: this.courseInfo + value: course }) values.push({ diff --git a/src/api/resources/types.ts b/src/api/resources/types.ts new file mode 100644 index 000000000..b995b0adb --- /dev/null +++ b/src/api/resources/types.ts @@ -0,0 +1,85 @@ +import { Position } from '../../types' + +export interface Route { + name?: string + description?: string + distance?: number + start?: string + end?: string + feature: { + type: 'Feature' + geometry: { + type: 'LineString' + coordinates: GeoJsonLinestring + } + properties?: object + id?: string + } +} + +export interface Waypoint { + position?: Position + feature: { + type: 'Feature' + geometry: { + type: 'Point' + coords: GeoJsonPoint + } + properties?: object + id?: string + } +} + +export interface Note { + title?: string + description?: string + region?: string + position?: Position + geohash?: string + mimeType?: string + url?: string +} + +export interface Region { + geohash?: string + feature: Polygon | MultiPolygon +} + +export interface Chart { + name: string + identifier: string + description?: string + tilemapUrl?: string + chartUrl?: string + geohash?: string + region?: string + scale?: number + chartLayers?: string[] + bounds?: [[number, number], [number, number]] + chartFormat: string +} + +type GeoJsonPoint = [number, number, number?] +type GeoJsonLinestring = GeoJsonPoint[] +type GeoJsonPolygon = GeoJsonLinestring[] +type GeoJsonMultiPolygon = GeoJsonPolygon[] + +interface Polygon { + type: 'Feature' + geometry: { + type: 'Polygon' + coords: GeoJsonPolygon + } + properties?: object + id?: string +} + +interface MultiPolygon { + type: 'Feature' + geometry: { + type: 'MultiPolygon' + coords: GeoJsonMultiPolygon + } + properties?: object + id?: string +} diff --git a/src/api/resources/validate.ts b/src/api/resources/validate.ts index a16096fd7..a156d011d 100644 --- a/src/api/resources/validate.ts +++ b/src/api/resources/validate.ts @@ -3,6 +3,7 @@ <<<<<<< HEAD import geoJSON from 'geojson-validation' import { isValidCoordinate } from 'geolib' +import { Chart, Note, Region, Route, Waypoint } from '../resources/types' export const validate = { resource: (type: string, value: any): boolean => { @@ -46,7 +47,7 @@ export const validate = { } } -const validateRoute = (r: any): boolean => { +const validateRoute = (r: Route): boolean => { if (r.start) { const l = r.start.split('/') if (!validate.uuid(l[l.length - 1])) { @@ -72,7 +73,7 @@ const validateRoute = (r: any): boolean => { return true } -const validateWaypoint = (r: any): boolean => { +const validateWaypoint = (r: Waypoint): boolean => { if (typeof r.position === 'undefined') { return false } @@ -93,7 +94,7 @@ const validateWaypoint = (r: any): boolean => { } // validate note data -const validateNote = (r: any): boolean => { +const validateNote = (r: Note): boolean => { if (!r.region && !r.position && !r.geohash) { return false } @@ -111,7 +112,7 @@ const validateNote = (r: any): boolean => { return true } -const validateRegion = (r: any): boolean => { +const validateRegion = (r: Region): boolean => { if (!r.geohash && !r.feature) { return false } @@ -133,7 +134,7 @@ const validateRegion = (r: any): boolean => { return true } -const validateChart = (r: any): boolean => { +const validateChart = (r: Chart): boolean => { if (!r.name || !r.identifier || !r.chartFormat) { return false } From 1dd44261a8bc90d76ebd40e8932281e3c8c6d542 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Tue, 11 Jan 2022 09:14:40 +1030 Subject: [PATCH 397/410] removed periodic delta transmission --- src/api/course/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index ba8ba8c0e..5a846ca58 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -196,8 +196,6 @@ const SIGNALK_API_PATH = `/signalk/v1/api` const COURSE_API_PATH = `${SIGNALK_API_PATH}/vessels/self/navigation/course` >>>>>>> style: no-inferrable-types -const DELTA_INTERVAL = 30000 - interface CourseApplication extends Application, WithConfig, @@ -558,6 +556,7 @@ export class CourseApi { } debug(this.courseInfo) this.emitCourseInfo(true) +<<<<<<< HEAD setInterval(() => { if (this.courseInfo.nextPoint.position) { @@ -566,6 +565,8 @@ export class CourseApi { } }, DELTA_INTERVAL) >>>>>>> add 30sec delta interval +======= +>>>>>>> removed periodic delta transmission } private validateCourseInfo(info: CourseInfo) { From be7dfb4212a0d6b346cb9dbea5806d357f39d1fd Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 12 Jan 2022 16:58:52 +1030 Subject: [PATCH 398/410] update types --- packages/server-api/src/index.ts | 35 +++++++++----------------------- 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/packages/server-api/src/index.ts b/packages/server-api/src/index.ts index bc5ac6b7a..57b321985 100644 --- a/packages/server-api/src/index.ts +++ b/packages/server-api/src/index.ts @@ -5,23 +5,21 @@ export { PropertyValue, PropertyValues, PropertyValuesCallback } from './propert export type SignalKResourceType= 'routes' | 'waypoints' |'notes' |'regions' |'charts' -<<<<<<< HEAD -<<<<<<< HEAD export type ResourceTypes= SignalKResourceType[] | string[] -export interface ResourceProviderMethods { - pluginId?: string -======= +export interface ResourcesApi { + register: (pluginId: string, provider: ResourceProvider) => void; + unRegister: (pluginId: string) => void; + getResource: (resType: SignalKResourceType, resId: string) => any; +} -export interface ResourceProviderMethods { - pluginId: string ->>>>>>> add ResourceProvider types to server-api -======= -export type ResourceTypes= SignalKResourceType[] | string[] +export interface ResourceProvider { + types: ResourceTypes + methods: ResourceProviderMethods +} export interface ResourceProviderMethods { pluginId?: string ->>>>>>> update types listResources: (type: string, query: { [key: string]: any }) => Promise getResource: (type: string, id: string) => Promise setResource: ( @@ -32,19 +30,6 @@ export interface ResourceProviderMethods { deleteResource: (type: string, id: string) => Promise } -export interface ResourceProvider { -<<<<<<< HEAD -<<<<<<< HEAD - types: ResourceTypes -======= - types: SignalKResourceType[] ->>>>>>> add ResourceProvider types to server-api -======= - types: ResourceTypes ->>>>>>> update types - methods: ResourceProviderMethods -} - type Unsubscribe = () => {} export interface PropertyValuesEmitter { emitPropertyValue: (name: string, value: any) => void @@ -96,5 +81,5 @@ export interface Plugin { registerWithRouter?: (router: IRouter) => void signalKApiRoutes?: (router: IRouter) => IRouter enabledByDefault?: boolean - resourceProvider: ResourceProvider + resourceProvider?: ResourceProvider } From f0f3648cea8c6f3e50ab7837ef4c1db87a1a83ec Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Wed, 19 Jan 2022 09:52:56 +1030 Subject: [PATCH 399/410] add test for ApiRequst path --- src/api/course/index.ts | 3044 +------------------------------- src/api/index.ts | 43 + src/api/resources/index.ts | 4 +- src/api/resources/resources.ts | 2 +- src/api/responses.ts | 31 - src/put.js | 11 +- 6 files changed, 101 insertions(+), 3034 deletions(-) create mode 100644 src/api/index.ts delete mode 100644 src/api/responses.ts diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 5a846ca58..7958bfb78 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,339 +1,26 @@ -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD import Debug from 'debug' -import { Application, Request, Response } from 'express' -<<<<<<< HEAD -<<<<<<< HEAD -======= -import { - ResourceProvider, - ResourceProviderMethods, - SignalKResourceType -} from '@signalk/server-api' -import Debug from 'debug' -import { Application, Request, Response } from 'express' -import { v4 as uuidv4 } from 'uuid' ->>>>>>> init courseApi -======= -import Debug from 'debug' -import { Application, Request, Response } from 'express' ->>>>>>> update detlas -======= - -import { - ResourceProvider, - ResourceProviderMethods, - SignalKResourceType -} from '@signalk/server-api' -import Debug from 'debug' -import { Application, Request, Response } from 'express' -import { v4 as uuidv4 } from 'uuid' ->>>>>>> init courseApi -======= -import Debug from 'debug' -import { Application, Request, Response } from 'express' ->>>>>>> update detlas -======= -import { - ResourceProvider, - ResourceProviderMethods, - SignalKResourceType -} from '@signalk/server-api' -import Debug from 'debug' -import { Application, Request, Response } from 'express' -import { v4 as uuidv4 } from 'uuid' ->>>>>>> init courseApi -======= -import Debug from 'debug' -import { Application, Request, Response } from 'express' ->>>>>>> update detlas -======= - -======= ->>>>>>> init courseApi -import { - ResourceProvider, - ResourceProviderMethods, - SignalKResourceType -} from '@signalk/server-api' -import Debug from 'debug' -import { Application, Request, Response } from 'express' -import { v4 as uuidv4 } from 'uuid' -<<<<<<< HEAD ->>>>>>> init courseApi -======= -import Debug from 'debug' -import { Application, Request, Response } from 'express' ->>>>>>> update detlas -======= ->>>>>>> init courseApi -======= -import Debug from 'debug' -import { Application, Request, Response } from 'express' ->>>>>>> update detlas -======= - -======= ->>>>>>> init courseApi -import { - ResourceProvider, - ResourceProviderMethods, - SignalKResourceType -} from '@signalk/server-api' -import Debug from 'debug' -import { Application, Request, Response } from 'express' -import { v4 as uuidv4 } from 'uuid' -<<<<<<< HEAD ->>>>>>> init courseApi -======= -import Debug from 'debug' -import { Application, Request, Response } from 'express' ->>>>>>> update detlas -======= ->>>>>>> init courseApi -======= -import Debug from 'debug' -import { Application, Request, Response } from 'express' ->>>>>>> update detlas -======= +const debug = Debug('signalk:courseApi') +// import { createDebug } from './debug' +// const debug = createDebug('signalk:courseApi') -import { - ResourceProvider, - ResourceProviderMethods, - SignalKResourceType -} from '@signalk/server-api' -import Debug from 'debug' -import { Application, Request, Response } from 'express' -import { v4 as uuidv4 } from 'uuid' ->>>>>>> init courseApi -======= -import Debug from 'debug' import { Application, Request, Response } from 'express' ->>>>>>> update detlas -======= -import path from 'path' -import { WithConfig, WithSecurityStrategy, WithSignalK } from '../../app' -import _ from 'lodash' -<<<<<<< HEAD -import { Store } from '../store' ->>>>>>> persist courseInfo to settings file -======= -import { Store } from '../../serverstate/store' ->>>>>>> move `store.ts` to serverstate` folder -======= import _ from 'lodash' import path from 'path' import { WithConfig, WithSecurityStrategy, WithSignalK } from '../../app' -import { Store } from '../../serverstate/store' import { Position } from '../../types' -import { Route } from '../resources/types' -import { Responses } from '../responses' ->>>>>>> regresstion testing fixes - -const debug = Debug('signalk:courseApi') -<<<<<<< HEAD -const SIGNALK_API_PATH: string = `/signalk/v1/api` -const COURSE_API_PATH: string = `${SIGNALK_API_PATH}/vessels/self/navigation/course` -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - -const DELTA_INTERVAL: number = 30000 -======= -const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' - -const API_METHODS: string[] = [] ->>>>>>> init courseApi -======= ->>>>>>> update detlas -======= -const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' ->>>>>>> init courseApi - -const API_METHODS: string[] = [] -======= ->>>>>>> update detlas - -const DELTA_INTERVAL: number = 30000 -======= -const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' - -const API_METHODS: string[] = [] ->>>>>>> init courseApi -======= ->>>>>>> update detlas - -const DELTA_INTERVAL: number = 30000 -======= -const UUID_PREFIX: string = 'urn:mrn:signalk:uuid:' +import { Store } from '../../serverstate/store' +import { Route } from '../resources/types' +import { Responses } from '../' -const API_METHODS: string[] = [] ->>>>>>> init courseApi -======= ->>>>>>> update detlas -======= const SIGNALK_API_PATH = `/signalk/v1/api` const COURSE_API_PATH = `${SIGNALK_API_PATH}/vessels/self/navigation/course` ->>>>>>> style: no-inferrable-types interface CourseApplication extends Application, WithConfig, WithSignalK, -<<<<<<< HEAD - WithSecurityStrategy {} - -interface CourseApplication extends Application { -<<<<<<< HEAD - // handleMessage: (id: string, data: any) => void - getSelfPath: (path: string) => any -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - securityStrategy: { - shouldAllowPut: ( - req: Application, -<<<<<<< HEAD -======= - securityStrategy: { - shouldAllowPut: ( -<<<<<<< HEAD - req: any, ->>>>>>> enable put processing -======= - req: Application, ->>>>>>> chore: lint -======= - securityStrategy: { - shouldAllowPut: ( -<<<<<<< HEAD - req: any, ->>>>>>> enable put processing -======= - req: Application, ->>>>>>> chore: lint -======= - securityStrategy: { - shouldAllowPut: ( - req: any, ->>>>>>> enable put processing -======= ->>>>>>> chore: lint -======= - securityStrategy: { - shouldAllowPut: ( -<<<<<<< HEAD - req: any, ->>>>>>> enable put processing -======= - req: Application, ->>>>>>> chore: lint - context: string, - source: any, - path: string - ) => boolean - } -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> init courseApi -======= - registerPutHandler: (context:string, path:string, cb:any) => any ->>>>>>> update detlas -======= - ->>>>>>> init courseApi -======= - registerPutHandler: (context:string, path:string, cb:any) => any ->>>>>>> update detlas -======= ->>>>>>> enable put processing -======= ->>>>>>> init courseApi -======= - registerPutHandler: (context:string, path:string, cb:any) => any ->>>>>>> update detlas -======= - ->>>>>>> init courseApi -======= - registerPutHandler: (context:string, path:string, cb:any) => any ->>>>>>> update detlas -======= ->>>>>>> enable put processing -======= ->>>>>>> init courseApi -======= - registerPutHandler: (context:string, path:string, cb:any) => any ->>>>>>> update detlas -======= - ->>>>>>> init courseApi -======= - registerPutHandler: (context:string, path:string, cb:any) => any ->>>>>>> update detlas -======= ->>>>>>> enable put processing -======= ->>>>>>> init courseApi -======= - registerPutHandler: (context:string, path:string, cb:any) => any ->>>>>>> update detlas -======= - ->>>>>>> init courseApi -======= - registerPutHandler: (context:string, path:string, cb:any) => any ->>>>>>> update detlas -======= ->>>>>>> enable put processing -======= ->>>>>>> persist courseInfo to settings file -======= ->>>>>>> add getVesselPosition -======= WithSecurityStrategy { ->>>>>>> fix: double definition of CourseApplication resourcesApi: { getResource: (resourceType: string, resourceId: string) => any } @@ -347,27 +34,6 @@ interface Destination extends DestinationBase { position?: Position type?: string } -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -======= - ->>>>>>> init courseApi -======= ->>>>>>> update detlas -======= - ->>>>>>> init courseApi -======= ->>>>>>> update detlas -======= - ->>>>>>> init courseApi -======= ->>>>>>> update detlas interface ActiveRoute extends DestinationBase { pointIndex?: number reverse?: boolean @@ -436,117 +102,7 @@ export class CourseApi { private async start(app: any) { debug(`** Initialise ${COURSE_API_PATH} path handler **`) -<<<<<<< HEAD - this.server = app -<<<<<<< HEAD - this.initResourceRoutes() -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - setInterval(() => { - if (this.courseInfo.nextPoint.position) { - this.emitCourseInfo() - } - }, DELTA_INTERVAL) - } - - private updateAllowed(): boolean { - return this.server.securityStrategy.shouldAllowPut( - this.server, - 'vessels.self', - null, - 'navigation.course' - ) -======= ->>>>>>> init courseApi -======= - setInterval( ()=> { - if(this.courseInfo.nextPoint.position) { -======= - setInterval(() => { - if (this.courseInfo.nextPoint.position) { ->>>>>>> chore: lint -======= - setInterval(() => { - if (this.courseInfo.nextPoint.position) { ->>>>>>> chore: lint - this.emitCourseInfo() - } - }, DELTA_INTERVAL) ->>>>>>> add 30sec delta interval - } - - private updateAllowed(): boolean { - return this.server.securityStrategy.shouldAllowPut( - this.server, - 'vessels.self', - null, - 'navigation.course' - ) -======= ->>>>>>> init courseApi -======= -======= ->>>>>>> add 30sec delta interval - setInterval( ()=> { - if(this.courseInfo.nextPoint.position) { -======= - setInterval(() => { - if (this.courseInfo.nextPoint.position) { ->>>>>>> chore: lint - this.emitCourseInfo() - } - }, DELTA_INTERVAL) -<<<<<<< HEAD ->>>>>>> add 30sec delta interval - } - - private updateAllowed(): boolean { - return this.server.securityStrategy.shouldAllowPut( - this.server, - 'vessels.self', - null, - 'navigation.course' - ) -======= ->>>>>>> init courseApi -======= ->>>>>>> add 30sec delta interval - } - - private updateAllowed(): boolean { - return this.server.securityStrategy.shouldAllowPut( - this.server, - 'vessels.self', - null, - 'navigation.course' - ) -======= ->>>>>>> init courseApi -======= - setInterval( ()=> { - if(this.courseInfo.nextPoint.position) { -======= - setInterval(() => { - if (this.courseInfo.nextPoint.position) { ->>>>>>> chore: lint - this.emitCourseInfo() -======= -======= -======= ->>>>>>> chore: this.server is initialised in constructor this.initCourseRoutes() ->>>>>>> regresstion testing fixes try { const storeData = await this.store.read() @@ -556,17 +112,6 @@ export class CourseApi { } debug(this.courseInfo) this.emitCourseInfo(true) -<<<<<<< HEAD - - setInterval(() => { - if (this.courseInfo.nextPoint.position) { - this.emitCourseInfo(true) ->>>>>>> persist courseInfo to settings file - } - }, DELTA_INTERVAL) ->>>>>>> add 30sec delta interval -======= ->>>>>>> removed periodic delta transmission } private validateCourseInfo(info: CourseInfo) { @@ -597,151 +142,12 @@ export class CourseApi { } ) -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> enable put processing -======= -======= -======= ->>>>>>> update detlas -======= ->>>>>>> update detlas -======= ->>>>>>> update detlas -======= ->>>>>>> update detlas -======= ->>>>>>> update detlas - // - if(this.server.registerPutHandler) { - debug('** Registering PUT Action Handler(s) **') - this.server.registerPutHandler( - 'vessels.self', - 'navigation.course.*', - this.handleCourseApiPut - ); - } - -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD ->>>>>>> update detlas -======= ->>>>>>> init courseApi -======= ->>>>>>> update detlas - // restart / arrivalCircle ->>>>>>> init courseApi -======= -======= ->>>>>>> enable put processing -======= ->>>>>>> enable put processing this.server.put( `${COURSE_API_PATH}/restart`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/restart`) -<<<<<<< HEAD - if (!this.updateAllowed()) { -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - res.status(403).send('Unauthorised') -======= - res.status(403) ->>>>>>> enable put processing -======= - res.status(403) ->>>>>>> enable put processing - return - } - if (!this.courseInfo.nextPoint.position) { - res.status(406).send(`No active destination!`) - return - } - // set previousPoint to vessel position -<<<<<<< HEAD -<<<<<<< HEAD - try { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.emitCourseInfo() - res.status(200).send('OK') - } - } catch (err) { -======= -======= ->>>>>>> enable put processing - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.emitCourseInfo() - res.status(200) - } else { -<<<<<<< HEAD ->>>>>>> enable put processing -======= ->>>>>>> enable put processing - res.status(406).send(`Vessel position unavailable!`) - } - } - ) -<<<<<<< HEAD -<<<<<<< HEAD - ->>>>>>> enable put processing - this.server.put( - `${COURSE_API_PATH}/arrivalCircle`, - async (req: Request, res: Response) => { -<<<<<<< HEAD -<<<<<<< HEAD - debug(`** PUT ${COURSE_API_PATH}/restart`) - if (!this.updateAllowed()) { -<<<<<<< HEAD -<<<<<<< HEAD - res.status(403).send('Unauthorised') -======= - res.status(403) ->>>>>>> enable put processing -======= - res.status(403).send('Unauthorised') ->>>>>>> add 30sec delta interval -======= - res.status(403).send('Unauthorised') ->>>>>>> add 30sec delta interval -======= - res.status(403).send('Unauthorised') ->>>>>>> add 30sec delta interval -======= -======= if (!this.updateAllowed(req)) { ->>>>>>> fix: updateAllowed(request) is required res.status(403).json(Responses.unauthorised) ->>>>>>> regresstion testing fixes return } if (!this.courseInfo.nextPoint.position) { @@ -753,81 +159,11 @@ export class CourseApi { return } // set previousPoint to vessel position -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - try { -======= -======= -======= ->>>>>>> update detlas - // - if(this.server.registerPutHandler) { - debug('** Registering PUT Action Handler(s) **') - this.server.registerPutHandler( - 'vessels.self', - 'navigation.course.*', - this.handleCourseApiPut - ); - } - -<<<<<<< HEAD ->>>>>>> update detlas -======= ->>>>>>> init courseApi -======= ->>>>>>> update detlas - // restart / arrivalCircle -======= - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.emitCourseInfo() - res.status(200) - } else { -======= -======= ->>>>>>> chore: lint -======= ->>>>>>> chore: lint try { const position: any = this.getVesselPosition() if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.emitCourseInfo() -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - res.status(200).send('OK') - } - } catch (err) { ->>>>>>> chore: lint -======= - res.status(200) -======= - res.status(200).send('OK') ->>>>>>> add 30sec delta interval -======= - res.status(200).send('OK') -<<<<<<< HEAD ->>>>>>> add 30sec delta interval - } - } catch (err) { ->>>>>>> chore: lint -======= - res.status(200) -======= - } else { - res.status(406).send(`Vessel position unavailable!`) ->>>>>>> move `store.ts` to serverstate` folder - } - } catch (err) { ->>>>>>> chore: lint - res.status(406).send(`Vessel position unavailable!`) -======= res.status(200).json(Responses.ok) } else { res.status(406).json({ @@ -842,177 +178,13 @@ export class CourseApi { statusCode: 406, message: `Vessel position unavailable!` }) ->>>>>>> regresstion testing fixes } } ) ->>>>>>> enable put processing - this.server.put( - `${COURSE_API_PATH}/arrivalCircle`, - async (req: Request, res: Response) => { -<<<<<<< HEAD - debug(`** PUT ${COURSE_API_PATH}/:action`) - if (req.params.restart) { - //test for active destination - if (!this.courseInfo.nextPoint.position) { - return - } - // set previousPoint to vessel position ->>>>>>> init courseApi -======= - debug(`** PUT ${COURSE_API_PATH}/:action`) - if (req.params.restart) { - //test for active destination - if (!this.courseInfo.nextPoint.position) { - return - } - // set previousPoint to vessel position ->>>>>>> init courseApi -======= -======= ->>>>>>> update detlas -======= ->>>>>>> init courseApi -======= ->>>>>>> update detlas - // restart / arrivalCircle -======= - ->>>>>>> enable put processing - this.server.put( - `${COURSE_API_PATH}/arrivalCircle`, - async (req: Request, res: Response) => { -<<<<<<< HEAD - debug(`** PUT ${COURSE_API_PATH}/:action`) - if (req.params.restart) { - //test for active destination - if (!this.courseInfo.nextPoint.position) { - return - } - // set previousPoint to vessel position ->>>>>>> init courseApi -======= -======= ->>>>>>> update detlas -======= ->>>>>>> init courseApi -======= ->>>>>>> update detlas - // restart / arrivalCircle -======= - ->>>>>>> enable put processing this.server.put( `${COURSE_API_PATH}/arrivalCircle`, async (req: Request, res: Response) => { -<<<<<<< HEAD - debug(`** PUT ${COURSE_API_PATH}/:action`) - if (req.params.restart) { - //test for active destination - if (!this.courseInfo.nextPoint.position) { - return - } - // set previousPoint to vessel position ->>>>>>> init courseApi - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.emitCourseInfo() -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - res.status(200).send('OK') -======= - res.status(200).send(`Course restarted.`) - } else { - res.status(406).send(`Vessel position unavailable!`) ->>>>>>> init courseApi - } - } -<<<<<<< HEAD - if (this.isValidArrivalCircle(req.body.value)) { - this.courseInfo.nextPoint.arrivalCircle = req.body.value - this.emitCourseInfo() - res.status(200).send('OK') - } else { - res.status(406).send(`Invalid Data`) -======= -======= ->>>>>>> init courseApi -======= ->>>>>>> init courseApi - res.status(200).send(`Course restarted.`) - } else { - res.status(406).send(`Vessel position unavailable!`) - } - } -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> init courseApi -======= ->>>>>>> init courseApi -======= ->>>>>>> init courseApi - if (req.params.arrivalCircle) { - if (this.setArrivalCircle(req.params.arrivalCircle)) { - this.emitCourseInfo() - res.status(200).send(`Destination set successfully.`) - } else { - res.status(406).send(`Invalid Data`) - } -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD ->>>>>>> init courseApi -======= - debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } - if (this.isValidArrivalCircle(req.body.value)) { - this.courseInfo.nextPoint.arrivalCircle = req.body.value - this.emitCourseInfo() - res.status(200).send('OK') - } else { - res.status(406).send(`Invalid Data`) ->>>>>>> enable put processing -======= ->>>>>>> init courseApi -======= - debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } - if (this.isValidArrivalCircle(req.body.value)) { - this.courseInfo.nextPoint.arrivalCircle = req.body.value - this.emitCourseInfo() - res.status(200).send('OK') - } else { - res.status(406).send(`Invalid Data`) ->>>>>>> enable put processing -======= ->>>>>>> init courseApi -======= - debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } - if (this.isValidArrivalCircle(req.body.value)) { - this.courseInfo.nextPoint.arrivalCircle = req.body.value - this.emitCourseInfo() - res.status(200).send('OK') - } else { - res.status(406).send(`Invalid Data`) ->>>>>>> enable put processing -======= ->>>>>>> init courseApi -======= debug(`** PUT ${COURSE_API_PATH}/arrivalCircle`) if (!this.updateAllowed(req)) { res.status(403).json(Responses.unauthorised) @@ -1023,12 +195,7 @@ export class CourseApi { this.emitCourseInfo() res.status(200).json(Responses.ok) } else { -<<<<<<< HEAD - res.status(406).send(`Invalid Data`) ->>>>>>> enable put processing -======= res.status(406).json(Responses.invalid) ->>>>>>> regresstion testing fixes } } ) @@ -1038,127 +205,19 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/destination`) -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } -======= - ->>>>>>> init courseApi -======= - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } ->>>>>>> enable put processing -======= ->>>>>>> init courseApi -======= - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } ->>>>>>> enable put processing -======= - ->>>>>>> init courseApi -======= - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } ->>>>>>> enable put processing -======= - ->>>>>>> init courseApi -======= - if (!this.updateAllowed()) { -======= if (!this.updateAllowed(req)) { ->>>>>>> fix: updateAllowed(request) is required res.status(403).json(Responses.unauthorised) return } -<<<<<<< HEAD ->>>>>>> enable put processing - if (!req.body.value) { - debug(`** Error: req.body.value is null || undefined!`) -======= if (!req.body) { debug(`** Error: req.body is null || undefined!`) -<<<<<<< HEAD ->>>>>>> align openApi.json - res.status(406).send(`Invalid Data`) -======= res.status(406).json(Responses.invalid) ->>>>>>> regresstion testing fixes return } const result = await this.setDestination(req.body) if (result) { this.emitCourseInfo() -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - res.status(200).send('OK') -======= - res.status(200).send(`Destination set successfully.`) ->>>>>>> init courseApi -======= - res.status(200) ->>>>>>> enable put processing -======= - res.status(200).send('OK') ->>>>>>> add 30sec delta interval -======= - res.status(200).send(`Destination set successfully.`) ->>>>>>> init courseApi -======= - res.status(200) ->>>>>>> enable put processing -======= - res.status(200).send('OK') ->>>>>>> add 30sec delta interval -======= - res.status(200).send(`Destination set successfully.`) ->>>>>>> init courseApi -======= - res.status(200) ->>>>>>> enable put processing -======= - res.status(200).send('OK') ->>>>>>> add 30sec delta interval -======= - res.status(200).send(`Destination set successfully.`) ->>>>>>> init courseApi -======= - res.status(200) ->>>>>>> enable put processing -======= - res.status(200).send('OK') ->>>>>>> add 30sec delta interval -======= res.status(200).json(Responses.ok) ->>>>>>> regresstion testing fixes } else { this.clearDestination() this.emitCourseInfo() @@ -1172,91 +231,37 @@ export class CourseApi { `${COURSE_API_PATH}/destination`, async (req: Request, res: Response) => { debug(`** DELETE ${COURSE_API_PATH}/destination`) -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } -======= ->>>>>>> init courseApi -======= - if (!this.updateAllowed()) { -======= if (!this.updateAllowed(req)) { ->>>>>>> fix: updateAllowed(request) is required res.status(403).json(Responses.unauthorised) return } ->>>>>>> enable put processing this.clearDestination() this.emitCourseInfo() res.status(200).json(Responses.ok) } ) -<<<<<<< HEAD -<<<<<<< HEAD // set activeRoute -<<<<<<< HEAD -======= -======= - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') -======= this.server.put( `${COURSE_API_PATH}/activeRoute`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute`) if (!this.updateAllowed(req)) { res.status(403).json(Responses.unauthorised) ->>>>>>> fix: updateAllowed(request) is required return } -<<<<<<< HEAD ->>>>>>> enable put processing - this.clearDestination() - this.emitCourseInfo() - res.status(200).send('OK') -======= - const result = await this.activateRoute(req.body.value) + const result = await this.activateRoute(req.body) if (result) { this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).json(Responses.ok) } else { this.clearDestination() this.emitCourseInfo() - res.status(406).send(`Invalid Data`) + res.status(406).json(Responses.invalid) } ->>>>>>> add 30sec delta interval } ) -<<<<<<< HEAD -<<<<<<< HEAD - // set / clear activeRoute ->>>>>>> init courseApi -======= - // set activeRoute ->>>>>>> enable put processing -======= - // set / clear activeRoute ->>>>>>> init courseApi -======= - // set activeRoute ->>>>>>> enable put processing -======= -======= - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') -======= // clear activeRoute /destination this.server.delete( `${COURSE_API_PATH}/activeRoute`, @@ -1264,424 +269,34 @@ export class CourseApi { debug(`** DELETE ${COURSE_API_PATH}/activeRoute`) if (!this.updateAllowed(req)) { res.status(403).json(Responses.unauthorised) ->>>>>>> fix: updateAllowed(request) is required return } ->>>>>>> enable put processing this.clearDestination() this.emitCourseInfo() - res.status(200).send('OK') + res.status(200).json(Responses.ok) } ) -<<<<<<< HEAD -<<<<<<< HEAD - // set / clear activeRoute ->>>>>>> init courseApi -======= - // set activeRoute ->>>>>>> enable put processing -======= -======= - if (!this.updateAllowed()) { - res.status(403) -======= this.server.put( `${COURSE_API_PATH}/activeRoute/:action`, async (req: Request, res: Response) => { debug(`** PUT ${COURSE_API_PATH}/activeRoute/${req.params.action}`) if (!this.updateAllowed(req)) { res.status(403).json(Responses.unauthorised) ->>>>>>> fix: updateAllowed(request) is required - return - } ->>>>>>> enable put processing - this.clearDestination() - this.emitCourseInfo() - res.status(200) - } - ) - -<<<<<<< HEAD - // set / clear activeRoute ->>>>>>> init courseApi -======= - // set activeRoute ->>>>>>> enable put processing - this.server.put( - `${COURSE_API_PATH}/activeRoute`, - async (req: Request, res: Response) => { - debug(`** PUT ${COURSE_API_PATH}/activeRoute`) -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') return } -<<<<<<< HEAD - const result = await this.activateRoute(req.body.value) - if (result) { - this.emitCourseInfo() - res.status(200).send('OK') -======= - - const result = await this.activateRoute(req.body.value) - if (result) { - this.emitCourseInfo() - res.status(200).send(`Active route set.`) ->>>>>>> init courseApi -======= - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') -======= // fetch active route data if (!this.courseInfo.activeRoute.href) { - res.status(406).send(`Invalid Data`) ->>>>>>> chore: lint - return - } - const result = await this.activateRoute(req.body.value) - if (result) { - this.emitCourseInfo() -<<<<<<< HEAD - res.status(200) ->>>>>>> enable put processing -======= - res.status(200).send('OK') ->>>>>>> add 30sec delta interval -======= -======= ->>>>>>> init courseApi -======= ->>>>>>> init courseApi - - const result = await this.activateRoute(req.body.value) - if (result) { - this.emitCourseInfo() - res.status(200).send(`Active route set.`) -<<<<<<< HEAD -<<<<<<< HEAD ->>>>>>> init courseApi -======= - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } - const result = await this.activateRoute(req.body.value) - if (result) { - this.emitCourseInfo() -<<<<<<< HEAD - res.status(200) ->>>>>>> enable put processing -======= - res.status(200).send('OK') ->>>>>>> add 30sec delta interval -======= ->>>>>>> init courseApi -======= - if (!this.updateAllowed()) { - res.status(403).json(Responses.unauthorised) - return - } - const result = await this.activateRoute(req.body) - if (result) { - this.emitCourseInfo() -<<<<<<< HEAD -<<<<<<< HEAD - res.status(200) ->>>>>>> enable put processing -======= - res.status(200).send('OK') ->>>>>>> add 30sec delta interval -======= ->>>>>>> init courseApi -======= - if (!this.updateAllowed()) { - res.status(403) + res.status(406).json(Responses.invalid) return } - const result = await this.activateRoute(req.body.value) - if (result) { - this.emitCourseInfo() - res.status(200) ->>>>>>> enable put processing -======= - res.status(200).json(Responses.ok) ->>>>>>> regresstion testing fixes - } else { - this.clearDestination() - this.emitCourseInfo() + const rte = await this.getRoute(this.courseInfo.activeRoute.href) + if (!rte) { res.status(406).json(Responses.invalid) + return } - } - ) -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - // clear activeRoute /destination -======= ->>>>>>> init courseApi -======= - - // clear activeRoute /destination ->>>>>>> enable put processing -======= ->>>>>>> init courseApi -======= - - // clear activeRoute /destination ->>>>>>> enable put processing -======= ->>>>>>> init courseApi -======= - - // clear activeRoute /destination ->>>>>>> enable put processing -======= ->>>>>>> init courseApi -======= - - // clear activeRoute /destination ->>>>>>> enable put processing - this.server.delete( - `${COURSE_API_PATH}/activeRoute`, - async (req: Request, res: Response) => { - debug(`** DELETE ${COURSE_API_PATH}/activeRoute`) -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> enable put processing -======= ->>>>>>> enable put processing -======= ->>>>>>> enable put processing -======= ->>>>>>> enable put processing - if (!this.updateAllowed()) { - res.status(403).json(Responses.unauthorised) - return - } -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - this.clearDestination() - this.emitCourseInfo() - res.status(200).send('OK') -======= - - this.clearDestination() - this.emitCourseInfo() - res.status(200).send(`Active route cleared.`) ->>>>>>> init courseApi -======= - this.clearDestination() - this.emitCourseInfo() -<<<<<<< HEAD - res.status(200) ->>>>>>> enable put processing -======= - res.status(200).send('OK') ->>>>>>> add 30sec delta interval -======= -======= ->>>>>>> init courseApi -======= ->>>>>>> init courseApi - - this.clearDestination() - this.emitCourseInfo() - res.status(200).send(`Active route cleared.`) -<<<<<<< HEAD -<<<<<<< HEAD ->>>>>>> init courseApi -======= - this.clearDestination() - this.emitCourseInfo() -<<<<<<< HEAD -<<<<<<< HEAD - res.status(200) ->>>>>>> enable put processing -======= - res.status(200).send('OK') ->>>>>>> add 30sec delta interval -======= ->>>>>>> init courseApi -======= - this.clearDestination() - this.emitCourseInfo() -<<<<<<< HEAD - res.status(200) ->>>>>>> enable put processing -======= - res.status(200).send('OK') ->>>>>>> add 30sec delta interval -======= ->>>>>>> init courseApi -======= - this.clearDestination() - this.emitCourseInfo() - res.status(200) ->>>>>>> enable put processing -======= - res.status(200).json(Responses.ok) ->>>>>>> regresstion testing fixes - } - ) - - this.server.put( - `${COURSE_API_PATH}/activeRoute/:action`, - async (req: Request, res: Response) => { -<<<<<<< HEAD - debug(`** PUT ${COURSE_API_PATH}/activeRoute`) -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } - // fetch active route data -======= - ->>>>>>> init courseApi -======= - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } -<<<<<<< HEAD - // fetch route data ->>>>>>> enable put processing -======= - // fetch active route data ->>>>>>> chore: lint -======= ->>>>>>> init courseApi -======= - if (!this.updateAllowed()) { - res.status(403).send('Unauthorised') - return - } -<<<<<<< HEAD - // fetch route data ->>>>>>> enable put processing -======= - // fetch active route data ->>>>>>> chore: lint -======= - ->>>>>>> init courseApi -======= -======= ->>>>>>> enable put processing -======= - debug(`** PUT ${COURSE_API_PATH}/activeRoute/${req.params.action}`) ->>>>>>> regresstion testing fixes - if (!this.updateAllowed()) { - res.status(403).json(Responses.unauthorised) - return - } -<<<<<<< HEAD -<<<<<<< HEAD - // fetch route data ->>>>>>> enable put processing -======= - // fetch active route data ->>>>>>> chore: lint -======= - ->>>>>>> init courseApi -======= - // fetch route data ->>>>>>> enable put processing - if (!this.courseInfo.activeRoute.href) { - res.status(406).json(Responses.invalid) - return - } -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -======= - ->>>>>>> init courseApi -======= ->>>>>>> enable put processing -======= - ->>>>>>> init courseApi -======= ->>>>>>> enable put processing -======= - ->>>>>>> init courseApi -======= ->>>>>>> enable put processing - const rte = await this.getRoute(this.courseInfo.activeRoute.href) - if (!rte) { - res.status(406).json(Responses.invalid) - return - } - -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - if (req.params.action === 'nextPoint') { -======= - if (req.params.nextPoint) { ->>>>>>> init courseApi -======= - if (req.params.action === 'nextPoint') { ->>>>>>> enable put processing -======= - if (req.params.nextPoint) { ->>>>>>> init courseApi -======= if (req.params.action === 'nextPoint') { ->>>>>>> enable put processing -======= - if (req.params.nextPoint) { ->>>>>>> init courseApi -======= - if (req.params.action === 'nextPoint') { ->>>>>>> enable put processing -======= - if (req.params.nextPoint) { ->>>>>>> init courseApi -======= - if (req.params.action === 'nextPoint') { ->>>>>>> enable put processing if ( typeof req.body.value === 'number' && (req.body.value === 1 || req.body.value === -1) @@ -1691,149 +306,20 @@ export class CourseApi { rte ) } else { -<<<<<<< HEAD - res.status(406).send(`Invalid Data`) -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> add 30sec delta interval -======= ->>>>>>> add 30sec delta interval - return - } - } - if (req.params.action === 'pointIndex') { -======= - } - } -<<<<<<< HEAD -<<<<<<< HEAD - if (req.params.pointIndex) { ->>>>>>> init courseApi -======= - if (req.params.action === 'pointIndex') { ->>>>>>> enable put processing -======= - } - } -<<<<<<< HEAD - if (req.params.pointIndex) { ->>>>>>> init courseApi -======= - if (req.params.action === 'pointIndex') { ->>>>>>> enable put processing -======= -======= - return ->>>>>>> add 30sec delta interval -======= -======= res.status(406).json(Responses.invalid) ->>>>>>> regresstion testing fixes return ->>>>>>> add 30sec delta interval } } -<<<<<<< HEAD -<<<<<<< HEAD - if (req.params.pointIndex) { ->>>>>>> init courseApi -======= -======= ->>>>>>> regresstion testing fixes - if (req.params.action === 'pointIndex') { ->>>>>>> enable put processing -======= - } - } - if (req.params.pointIndex) { ->>>>>>> init courseApi -======= if (req.params.action === 'pointIndex') { ->>>>>>> enable put processing if (typeof req.body.value === 'number') { this.courseInfo.activeRoute.pointIndex = this.parsePointIndex( req.body.value, rte ) } else { -<<<<<<< HEAD - res.status(406).send(`Invalid Data`) -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> add 30sec delta interval - return -======= ->>>>>>> init courseApi -======= - return ->>>>>>> add 30sec delta interval - } - } - - // set new destination - this.courseInfo.nextPoint.position = this.getRoutePoint( - rte, -<<<<<<< HEAD -<<<<<<< HEAD - this.courseInfo.activeRoute.pointIndex, - this.courseInfo.activeRoute.reverse -======= -======= - return ->>>>>>> add 30sec delta interval - } - } - - // set new destination - this.courseInfo.nextPoint.position = this.getRoutePoint( - rte, -<<<<<<< HEAD -<<<<<<< HEAD - this.courseInfo.activeRoute.pointIndex ->>>>>>> init courseApi -======= - this.courseInfo.activeRoute.pointIndex, - this.courseInfo.activeRoute.reverse ->>>>>>> chore: lint -======= - this.courseInfo.activeRoute.pointIndex ->>>>>>> init courseApi -======= - this.courseInfo.activeRoute.pointIndex, - this.courseInfo.activeRoute.reverse ->>>>>>> chore: lint -======= - } - } - - // set new destination - this.courseInfo.nextPoint.position = this.getRoutePoint( - rte, -<<<<<<< HEAD - this.courseInfo.activeRoute.pointIndex ->>>>>>> init courseApi -======= -======= -======= res.status(406).json(Responses.invalid) ->>>>>>> regresstion testing fixes return ->>>>>>> add 30sec delta interval } } // reverse direction from current point @@ -1872,221 +358,39 @@ export class CourseApi { // set new destination this.courseInfo.nextPoint.position = this.getRoutePoint( rte, - this.courseInfo.activeRoute.pointIndex ->>>>>>> init courseApi -======= this.courseInfo.activeRoute.pointIndex, this.courseInfo.activeRoute.reverse ->>>>>>> chore: lint ) this.courseInfo.nextPoint.type = `RoutePoint` this.courseInfo.nextPoint.href = null // set previousPoint if (this.courseInfo.activeRoute.pointIndex === 0) { -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> chore: lint -======= ->>>>>>> chore: lint -======= ->>>>>>> chore: lint try { const position: any = this.getVesselPosition() if (position && position.value) { this.courseInfo.previousPoint.position = position.value this.courseInfo.previousPoint.type = `VesselPosition` } else { -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - res.status(406).send(`Invalid Data`) - return false - } - } catch (err) { - res.status(406).send(`Invalid Data`) -======= -======= ->>>>>>> init courseApi -======= ->>>>>>> init courseApi -======= ->>>>>>> init courseApi - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD ->>>>>>> init courseApi -======= - return false - } - } catch (err) { ->>>>>>> chore: lint -======= -======= ->>>>>>> add 30sec delta interval - res.status(406).send(`Invalid Data`) - return false - } - } catch (err) { - res.status(406).send(`Invalid Data`) -<<<<<<< HEAD ->>>>>>> add 30sec delta interval -======= ->>>>>>> init courseApi -======= - return false - } - } catch (err) { ->>>>>>> chore: lint -======= ->>>>>>> add 30sec delta interval -======= ->>>>>>> init courseApi -======= - res.status(406).send(`Invalid Data`) - return false - } - } catch (err) { - res.status(406).send(`Invalid Data`) ->>>>>>> add 30sec delta interval -======= ->>>>>>> init courseApi -======= - return false - } - } catch (err) { ->>>>>>> chore: lint -======= - res.status(406).send(`Invalid Data`) - return false - } - } catch (err) { - res.status(406).send(`Invalid Data`) ->>>>>>> add 30sec delta interval -======= res.status(406).json(Responses.invalid) return false } } catch (err) { console.log(`** Error: unable to retrieve vessel position!`) res.status(406).json(Responses.invalid) ->>>>>>> regresstion testing fixes return false } } else { this.courseInfo.previousPoint.position = this.getRoutePoint( rte, -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - this.courseInfo.activeRoute.pointIndex - 1, - this.courseInfo.activeRoute.reverse -======= - this.courseInfo.activeRoute.pointIndex - 1 ->>>>>>> init courseApi -======= - this.courseInfo.activeRoute.pointIndex - 1, - this.courseInfo.activeRoute.reverse ->>>>>>> chore: lint -======= - this.courseInfo.activeRoute.pointIndex - 1 ->>>>>>> init courseApi -======= - this.courseInfo.activeRoute.pointIndex - 1, - this.courseInfo.activeRoute.reverse ->>>>>>> chore: lint -======= - this.courseInfo.activeRoute.pointIndex - 1 ->>>>>>> init courseApi -======= - this.courseInfo.activeRoute.pointIndex - 1 ->>>>>>> init courseApi -======= this.courseInfo.activeRoute.pointIndex - 1, this.courseInfo.activeRoute.reverse ->>>>>>> chore: lint ) this.courseInfo.previousPoint.type = `RoutePoint` } this.courseInfo.previousPoint.href = null -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - res.status(200).send('OK') -======= - -<<<<<<< HEAD -<<<<<<< HEAD - res.status(200).send(`OK`) ->>>>>>> init courseApi -======= - res.status(200) ->>>>>>> enable put processing -======= - res.status(200).send('OK') ->>>>>>> add 30sec delta interval -======= - -<<<<<<< HEAD - res.status(200).send(`OK`) ->>>>>>> init courseApi -======= - res.status(200) ->>>>>>> enable put processing -======= - res.status(200).send('OK') ->>>>>>> add 30sec delta interval -======= - - res.status(200).send(`OK`) ->>>>>>> init courseApi -======= - res.status(200) ->>>>>>> enable put processing -======= - res.status(200).send('OK') ->>>>>>> add 30sec delta interval -======= - -<<<<<<< HEAD - res.status(200).send(`OK`) ->>>>>>> init courseApi -======= - res.status(200) ->>>>>>> enable put processing -======= - res.status(200).send('OK') ->>>>>>> add 30sec delta interval -======= this.emitCourseInfo() res.status(200).json(Responses.ok) ->>>>>>> regresstion testing fixes } ) } @@ -2116,47 +420,8 @@ export class CourseApi { return false } -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - const newCourse: any = {} - Object.assign(newCourse, this.courseInfo) - -======= ->>>>>>> init courseApi -======= - const newDest: any = {} - Object.assign(newDest, this.courseInfo) -======= - const newCourse: any = {} - Object.assign(newCourse, this.courseInfo) ->>>>>>> add 30sec delta interval -======= - const newCourse: any = {} - Object.assign(newCourse, this.courseInfo) ->>>>>>> add 30sec delta interval -======= - const newCourse: any = {} - Object.assign(newCourse, this.courseInfo) ->>>>>>> add 30sec delta interval -======= - const newCourse: CourseInfo = {...this.courseInfo} ->>>>>>> refactor: prefer object spread over Object.assign -======= const newCourse: CourseInfo = { ...this.courseInfo } ->>>>>>> review updates 1 ->>>>>>> chore: lint // set activeroute newCourse.activeRoute.href = route.href @@ -2221,34 +486,13 @@ export class CourseApi { newCourse.nextPoint.arrivalCircle = dest.arrivalCircle as number } -<<<<<<< HEAD -<<<<<<< HEAD - newCourse.nextPoint.type = - typeof dest.type !== 'undefined' ? dest.type : null -======= - newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null ->>>>>>> add 30sec delta interval -======= newCourse.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null ->>>>>>> chore: lint if (dest.href) { const href = this.parseHref(dest.href) if (href) { -<<<<<<< HEAD -<<<<<<< HEAD // fetch waypoint resource details -<<<<<<< HEAD -======= -======= - const newDest: any = {} - Object.assign(newDest, this.courseInfo) -======= - const newCourse: any = {} - Object.assign(newCourse, this.courseInfo) ->>>>>>> add 30sec delta interval -======= try { const r = await this.server.resourcesApi.getResource( href.type, @@ -2287,15 +531,7 @@ export class CourseApi { } else { return false } ->>>>>>> add 30sec delta interval -<<<<<<< HEAD -<<<<<<< HEAD ->>>>>>> chore: lint - // set activeroute - newCourse.activeRoute.href = route.href -======= -======= // clear activeRoute values newCourse.activeRoute.href = null newCourse.activeRoute.startTime = null @@ -2303,7 +539,6 @@ export class CourseApi { newCourse.activeRoute.pointTotal = 0 newCourse.activeRoute.reverse = false ->>>>>>> regresstion testing fixes // set previousPoint try { const position: any = this.getVesselPosition() @@ -2319,13 +554,7 @@ export class CourseApi { console.log(`** Error: unable to retrieve vessel position!`) return false } ->>>>>>> add getVesselPosition -<<<<<<< HEAD -<<<<<<< HEAD - if (this.isValidArrivalCircle(route.arrivalCircle as number)) { - newCourse.nextPoint.arrivalCircle = route.arrivalCircle -======= this.courseInfo = newCourse return true } @@ -2362,919 +591,60 @@ export class CourseApi { return 0 } if (index > rte.feature?.geometry?.coordinates.length - 1) { -<<<<<<< HEAD - return rte.feature?.geometry?.coordinates.length -1 ->>>>>>> chore: update OpenApi responses -======= return rte.feature?.geometry?.coordinates.length - 1 ->>>>>>> style: linter fix } -======= - this.courseInfo.activeRoute.pointIndex, - this.courseInfo.activeRoute.reverse - ) - this.courseInfo.nextPoint.type = `RoutePoint` - this.courseInfo.nextPoint.href = null - - // set previousPoint - if (this.courseInfo.activeRoute.pointIndex === 0) { - try { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { - return false - } - } catch (err) { - return false - } - } else { - this.courseInfo.previousPoint.position = this.getRoutePoint( - rte, - this.courseInfo.activeRoute.pointIndex - 1, - this.courseInfo.activeRoute.reverse - ) - this.courseInfo.previousPoint.type = `RoutePoint` - } - this.courseInfo.previousPoint.href = null ->>>>>>> chore: lint - - newCourse.activeRoute.startTime = new Date().toISOString() + return index + } - if (typeof route.reverse === 'boolean') { - newCourse.activeRoute.reverse = route.reverse + private parseHref(href: string): { type: string; id: string } | undefined { + if (!href) { + return undefined } -<<<<<<< HEAD - newCourse.activeRoute.pointIndex = this.parsePointIndex( - route.pointIndex as number, - rte - ) + const ref: string[] = href.split('/').slice(-3) + if (ref.length < 3) { + return undefined + } + if (ref[0] !== 'resources') { + return undefined + } + return { + type: ref[1], + id: ref[2] + } + } - // set nextPoint - newCourse.nextPoint.position = this.getRoutePoint( - rte, - newCourse.activeRoute.pointIndex, - newCourse.activeRoute.reverse - ) - newCourse.nextPoint.type = `RoutePoint` - newCourse.nextPoint.href = null + private getRoutePoint(rte: any, index: number, reverse: boolean) { + const pos = reverse + ? rte.feature.geometry.coordinates[ + rte.feature.geometry.coordinates.length - (index + 1) + ] + : rte.feature.geometry.coordinates[index] + return { + latitude: pos[1], + longitude: pos[0], + altitude: pos.length === 3 ? pos[2] : 0 + } + } - // set previousPoint - if (newCourse.activeRoute.pointIndex === 0) { + private async getRoute(href: string): Promise { + const h = this.parseHref(href) + if (h) { try { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { - return false - } + return await this.server.resourcesApi.getResource(h.type, h.id) } catch (err) { - return false + debug(`** Unable to fetch resource: ${h.type}, ${h.id}`) + return undefined } } else { - newCourse.previousPoint.position = this.getRoutePoint( - rte, - newCourse.activeRoute.pointIndex - 1, - newCourse.activeRoute.reverse - ) - newCourse.previousPoint.type = `RoutePoint` - } - newCourse.previousPoint.href = null - - this.courseInfo = newCourse - return true - } - - private async setDestination(dest: any): Promise { - const newCourse: any = {} - Object.assign(newCourse, this.courseInfo) - - // set nextPoint - if (this.isValidArrivalCircle(dest.arrivalCircle)) { - newCourse.nextPoint.arrivalCircle = dest.arrivalCircle - } - - newCourse.nextPoint.type = - typeof dest.type !== 'undefined' ? dest.type : null - -<<<<<<< HEAD - if (dest.href) { - newCourse.href = dest.href - const href = this.parseHref(dest.href) - if (href) { -<<<<<<< HEAD ->>>>>>> init courseApi -======= - // fetch waypoint resource details ->>>>>>> chore: lint -======= ->>>>>>> init courseApi -======= - // fetch waypoint resource details ->>>>>>> chore: lint -======= -======= - const newDest: any = {} - Object.assign(newDest, this.courseInfo) - ->>>>>>> chore: lint - // set activeroute - newCourse.activeRoute.href = route.href - - if (this.isValidArrivalCircle(route.arrivalCircle as number)) { - newCourse.nextPoint.arrivalCircle = route.arrivalCircle - } - - newCourse.activeRoute.startTime = new Date().toISOString() - - if (typeof route.reverse === 'boolean') { - newCourse.activeRoute.reverse = route.reverse - } - - newCourse.activeRoute.pointIndex = this.parsePointIndex( - route.pointIndex as number, - rte - ) - - // set nextPoint - newCourse.nextPoint.position = this.getRoutePoint( - rte, - newCourse.activeRoute.pointIndex, - newCourse.activeRoute.reverse - ) - newCourse.nextPoint.type = `RoutePoint` - newCourse.nextPoint.href = null - - // set previousPoint - if (newCourse.activeRoute.pointIndex === 0) { - try { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { - return false - } - } catch (err) { - return false - } - } else { - newCourse.previousPoint.position = this.getRoutePoint( - rte, - newCourse.activeRoute.pointIndex - 1, - newCourse.activeRoute.reverse - ) - newCourse.previousPoint.type = `RoutePoint` - } - newCourse.previousPoint.href = null - - this.courseInfo = newCourse - return true - } - - private async setDestination(dest: any): Promise { - const newCourse: any = {} - Object.assign(newCourse, this.courseInfo) - - // set nextPoint - if (this.isValidArrivalCircle(dest.arrivalCircle)) { - newCourse.nextPoint.arrivalCircle = dest.arrivalCircle - } - - newCourse.nextPoint.type = - typeof dest.type !== 'undefined' ? dest.type : null - - if (dest.href) { - newCourse.href = dest.href - const href = this.parseHref(dest.href) - if (href) { -<<<<<<< HEAD ->>>>>>> init courseApi -======= - // fetch waypoint resource details ->>>>>>> chore: lint -======= -======= - const newDest: any = {} - Object.assign(newDest, this.courseInfo) - ->>>>>>> chore: lint - // set activeroute - newDest.activeRoute.href = route.href - - if (this.isValidArrivalCircle(route.arrivalCircle as number)) { - newDest.nextPoint.arrivalCircle = route.arrivalCircle - } - - newDest.activeRoute.startTime = new Date().toISOString() - - if (typeof route.reverse === 'boolean') { - newDest.activeRoute.reverse = route.reverse - } - - newDest.activeRoute.pointIndex = this.parsePointIndex( - route.pointIndex as number, - rte - ) - - // set nextPoint - newDest.nextPoint.position = this.getRoutePoint( - rte, - newDest.activeRoute.pointIndex, - newDest.activeRoute.reverse - ) - newDest.nextPoint.type = `RoutePoint` - newDest.nextPoint.href = null - - // set previousPoint - if (newDest.activeRoute.pointIndex === 0) { - try { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { - return false - } - } catch (err) { - return false - } - } else { - newDest.previousPoint.position = this.getRoutePoint( - rte, - newDest.activeRoute.pointIndex - 1, - newDest.activeRoute.reverse - ) - newDest.previousPoint.type = `RoutePoint` - } - newDest.previousPoint.href = null - - this.courseInfo = newDest - return true - } - - private async setDestination(dest: any): Promise { - const newDest: any = {} - Object.assign(newDest, this.courseInfo) - - // set nextPoint - if (this.isValidArrivalCircle(dest.arrivalCircle)) { - newDest.nextPoint.arrivalCircle = dest.arrivalCircle - } - - newDest.nextPoint.type = typeof dest.type !== 'undefined' ? dest.type : null - - if (dest.href) { - newDest.href = dest.href - const href = this.parseHref(dest.href) - if (href) { -<<<<<<< HEAD ->>>>>>> init courseApi -======= - // fetch waypoint resource details ->>>>>>> chore: lint - try { - const r = await this.server.resourcesApi.getResource( - href.type, - href.id - ) -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - if (r.position && typeof r.position?.latitude !== 'undefined') { - newCourse.nextPoint.position = r.position - } else { - return false -======= - if (r.position && typeof r.position.value?.latitude !== 'undefined') { - this.courseInfo.nextPoint.position = r.position.value ->>>>>>> init courseApi -======= - if (r.position && typeof r.position?.latitude !== 'undefined') { - newCourse.nextPoint.position = r.position - } else { - return false ->>>>>>> chore: lint -======= - if (r.position && typeof r.position.value?.latitude !== 'undefined') { - this.courseInfo.nextPoint.position = r.position.value ->>>>>>> init courseApi -======= - if (r.position && typeof r.position?.latitude !== 'undefined') { - newCourse.nextPoint.position = r.position - } else { - return false ->>>>>>> chore: lint -======= - if (r.position && typeof r.position.value?.latitude !== 'undefined') { - this.courseInfo.nextPoint.position = r.position.value ->>>>>>> init courseApi -======= - if (r.position && typeof r.position?.latitude !== 'undefined') { - newCourse.nextPoint.position = r.position - } else { - return false ->>>>>>> chore: lint -======= - if (r.position && typeof r.position.value?.latitude !== 'undefined') { - this.courseInfo.nextPoint.position = r.position.value ->>>>>>> init courseApi -======= - if (r.position && typeof r.position?.latitude !== 'undefined') { - newDest.nextPoint.position = r.position - } else { - return false ->>>>>>> chore: lint - } - } catch (err) { - return false - } - } - } else if (dest.position) { -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - newCourse.nextPoint.href = null - if (typeof dest.position.latitude !== 'undefined') { - newCourse.nextPoint.position = dest.position -======= - this.courseInfo.nextPoint.href = null - if (typeof dest.position.latitude !== 'undefined') { - this.courseInfo.nextPoint.position = dest.position ->>>>>>> init courseApi -======= - newDest.nextPoint.href = null - if (typeof dest.position.latitude !== 'undefined') { - newDest.nextPoint.position = dest.position ->>>>>>> chore: lint -======= - newCourse.nextPoint.href = null - if (typeof dest.position.latitude !== 'undefined') { - newCourse.nextPoint.position = dest.position ->>>>>>> add 30sec delta interval -======= - this.courseInfo.nextPoint.href = null - if (typeof dest.position.latitude !== 'undefined') { - this.courseInfo.nextPoint.position = dest.position ->>>>>>> init courseApi -======= - newDest.nextPoint.href = null - if (typeof dest.position.latitude !== 'undefined') { - newDest.nextPoint.position = dest.position ->>>>>>> chore: lint -======= - newCourse.nextPoint.href = null - if (typeof dest.position.latitude !== 'undefined') { - newCourse.nextPoint.position = dest.position ->>>>>>> add 30sec delta interval -======= - this.courseInfo.nextPoint.href = null - if (typeof dest.position.latitude !== 'undefined') { - this.courseInfo.nextPoint.position = dest.position ->>>>>>> init courseApi -======= - newDest.nextPoint.href = null - if (typeof dest.position.latitude !== 'undefined') { - newDest.nextPoint.position = dest.position ->>>>>>> chore: lint -======= - newCourse.nextPoint.href = null - if (typeof dest.position.latitude !== 'undefined') { - newCourse.nextPoint.position = dest.position ->>>>>>> add 30sec delta interval -======= - this.courseInfo.nextPoint.href = null - if (typeof dest.position.latitude !== 'undefined') { - this.courseInfo.nextPoint.position = dest.position ->>>>>>> init courseApi -======= - newDest.nextPoint.href = null - if (typeof dest.position.latitude !== 'undefined') { - newDest.nextPoint.position = dest.position ->>>>>>> chore: lint - } else { - return false - } - } else { - return false - } - - // set previousPoint -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - try { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - newCourse.previousPoint.position = position.value - newCourse.previousPoint.type = `VesselPosition` - } else { - return false - } - newCourse.previousPoint.href = null - } catch (err) { - return false - } - - this.courseInfo = newCourse -======= -======= ->>>>>>> init courseApi -======= ->>>>>>> init courseApi -======= ->>>>>>> init courseApi - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - this.courseInfo.previousPoint.position = position.value - this.courseInfo.previousPoint.type = `VesselPosition` - } else { -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -======= - try { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - newCourse.previousPoint.position = position.value - newCourse.previousPoint.type = `VesselPosition` - } else { - return false - } - newCourse.previousPoint.href = null - } catch (err) { ->>>>>>> chore: lint -======= ->>>>>>> init courseApi -======= - try { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - newCourse.previousPoint.position = position.value - newCourse.previousPoint.type = `VesselPosition` - } else { - return false - } - newCourse.previousPoint.href = null - } catch (err) { ->>>>>>> chore: lint - return false - } - -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD ->>>>>>> init courseApi -======= - this.courseInfo = newDest ->>>>>>> chore: lint -======= - this.courseInfo = newCourse ->>>>>>> add 30sec delta interval -======= ->>>>>>> init courseApi -======= - this.courseInfo = newDest ->>>>>>> chore: lint -======= - this.courseInfo = newCourse ->>>>>>> add 30sec delta interval -======= -======= - try { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - newCourse.previousPoint.position = position.value - newCourse.previousPoint.type = `VesselPosition` - } else { - return false - } - newCourse.previousPoint.href = null - } catch (err) { ->>>>>>> chore: lint - return false - } - -<<<<<<< HEAD -<<<<<<< HEAD ->>>>>>> init courseApi -======= - this.courseInfo = newDest ->>>>>>> chore: lint -======= - this.courseInfo = newCourse ->>>>>>> add 30sec delta interval -======= -======= - try { - const position: any = this.server.getSelfPath('navigation.position') - if (position && position.value) { - newCourse.previousPoint.position = position.value - newCourse.previousPoint.type = `VesselPosition` - } else { - return false - } - newCourse.previousPoint.href = null - } catch (err) { ->>>>>>> chore: lint - return false - } - -<<<<<<< HEAD -<<<<<<< HEAD ->>>>>>> init courseApi -======= - this.courseInfo = newDest ->>>>>>> chore: lint -======= - this.courseInfo = newCourse ->>>>>>> add 30sec delta interval -======= - this.courseInfo = newCourse ->>>>>>> regresstion testing fixes - return true - } - - private clearDestination() { - this.courseInfo.activeRoute.href = null - this.courseInfo.activeRoute.startTime = null - this.courseInfo.activeRoute.pointIndex = 0 -<<<<<<< HEAD -======= - this.courseInfo.activeRoute.pointTotal = 0 ->>>>>>> regresstion testing fixes - this.courseInfo.activeRoute.reverse = false - this.courseInfo.nextPoint.href = null - this.courseInfo.nextPoint.type = null - this.courseInfo.nextPoint.position = null - this.courseInfo.previousPoint.href = null - this.courseInfo.previousPoint.type = null - this.courseInfo.previousPoint.position = null - } - -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - private isValidArrivalCircle(value: number): boolean { - return typeof value === 'number' && value >= 0 - } - - private parsePointIndex(index: number, rte: any): number { - if (typeof index !== 'number' || !rte) { -======= -======= ->>>>>>> init courseApi -======= ->>>>>>> init courseApi -======= ->>>>>>> init courseApi - private setArrivalCircle(value: any): boolean { - if (typeof value === 'number' && value >= 0) { - this.courseInfo.nextPoint.arrivalCircle = value - return true - } else { - return false - } -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - } - - private parsePointIndex(index: number, rte: any): number { - if (!rte) { ->>>>>>> init courseApi -======= - private isValidArrivalCircle(value: number): boolean { - return typeof value === 'number' && value >= 0 - } - - private parsePointIndex(index: number, rte: any): number { - if (typeof index !== 'number' || !rte) { ->>>>>>> chore: lint -======= -======= ->>>>>>> init courseApi -======= ->>>>>>> init courseApi - } - - private parsePointIndex(index: number, rte: any): number { - if (!rte) { -<<<<<<< HEAD -<<<<<<< HEAD ->>>>>>> init courseApi -======= - private isValidArrivalCircle(value: number): boolean { - return typeof value === 'number' && value >= 0 - } - - private parsePointIndex(index: number, rte: any): number { - if (typeof index !== 'number' || !rte) { ->>>>>>> chore: lint -======= ->>>>>>> init courseApi -======= - private isValidArrivalCircle(value: number): boolean { - return typeof value === 'number' && value >= 0 - } - - private parsePointIndex(index: number, rte: any): number { - if (typeof index !== 'number' || !rte) { ->>>>>>> chore: lint -======= ->>>>>>> init courseApi -======= -======= ->>>>>>> regresstion testing fixes - private isValidArrivalCircle(value: number): boolean { - return typeof value === 'number' && value >= 0 - } - - private parsePointIndex(index: number, rte: any): number { - if (typeof index !== 'number' || !rte) { -<<<<<<< HEAD ->>>>>>> chore: lint -======= ->>>>>>> regresstion testing fixes - return 0 - } - if (!rte.feature?.geometry?.coordinates) { - return 0 - } - if (!Array.isArray(rte.feature?.geometry?.coordinates)) { - return 0 - } - if (index < 0) { - return 0 - } - if (index > rte.feature?.geometry?.coordinates.length - 1) { - return rte.feature?.geometry?.coordinates.length - } - return index - } - - private parseHref(href: string): { type: string; id: string } | undefined { -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - if (!href) { - return undefined - } - - const ref: string[] = href.split('/').slice(-3) -======= - if (href.length === 0) { - return undefined - } -======= - if (href.length === 0) { - return undefined - } ->>>>>>> init courseApi -======= - if (href.length === 0) { - return undefined - } ->>>>>>> init courseApi -======= - if (href.length === 0) { - return undefined - } ->>>>>>> init courseApi - if (href[0] === '/') { - href = href.slice(1) - } - const ref: string[] = href.split('/') -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD ->>>>>>> init courseApi -======= - if (!href) { - return undefined - } - - const ref: string[] = href.split('/').slice(-3) ->>>>>>> chore: lint -======= ->>>>>>> init courseApi -======= - if (!href) { - return undefined - } - - const ref: string[] = href.split('/').slice(-3) ->>>>>>> chore: lint -======= ->>>>>>> init courseApi -======= - if (!href) { - return undefined - } - - const ref: string[] = href.split('/').slice(-3) ->>>>>>> chore: lint -======= ->>>>>>> init courseApi -======= - if (!href) { - return undefined - } - - const ref: string[] = href.split('/').slice(-3) ->>>>>>> chore: lint - if (ref.length < 3) { - return undefined - } - if (ref[0] !== 'resources') { - return undefined - } - return { - type: ref[1], - id: ref[2] - } - } - -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - private getRoutePoint(rte: any, index: number, reverse: boolean) { - const pos = reverse -======= - private getRoutePoint(rte: any, index: number) { - const pos = this.courseInfo.activeRoute.reverse ->>>>>>> init courseApi -======= - private getRoutePoint(rte: any, index: number, reverse: boolean) { - const pos = reverse ->>>>>>> chore: lint -======= - private getRoutePoint(rte: any, index: number) { - const pos = this.courseInfo.activeRoute.reverse ->>>>>>> init courseApi -======= - private getRoutePoint(rte: any, index: number, reverse: boolean) { - const pos = reverse ->>>>>>> chore: lint -======= - private getRoutePoint(rte: any, index: number) { - const pos = this.courseInfo.activeRoute.reverse ->>>>>>> init courseApi -======= - private getRoutePoint(rte: any, index: number, reverse: boolean) { - const pos = reverse ->>>>>>> chore: lint -======= - private getRoutePoint(rte: any, index: number) { - const pos = this.courseInfo.activeRoute.reverse ->>>>>>> init courseApi -======= - private getRoutePoint(rte: any, index: number, reverse: boolean) { - const pos = reverse ->>>>>>> chore: lint - ? rte.feature.geometry.coordinates[ - rte.feature.geometry.coordinates.length - (index + 1) - ] - : rte.feature.geometry.coordinates[index] - return { - latitude: pos[1], - longitude: pos[0], -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - altitude: pos.length === 3 ? pos[2] : 0 -======= - altitude: pos.length == 3 ? pos[2] : 0 ->>>>>>> init courseApi -======= - altitude: pos.length === 3 ? pos[2] : 0 ->>>>>>> chore: lint -======= - altitude: pos.length == 3 ? pos[2] : 0 ->>>>>>> init courseApi -======= - altitude: pos.length === 3 ? pos[2] : 0 ->>>>>>> chore: lint -======= - altitude: pos.length == 3 ? pos[2] : 0 ->>>>>>> init courseApi -======= - altitude: pos.length === 3 ? pos[2] : 0 ->>>>>>> chore: lint -======= - altitude: pos.length == 3 ? pos[2] : 0 ->>>>>>> init courseApi -======= - altitude: pos.length === 3 ? pos[2] : 0 ->>>>>>> chore: lint - } - } - - private async getRoute(href: string): Promise { -======= - private async getRoute(href: string): Promise { ->>>>>>> review updates 1 - const h = this.parseHref(href) - if (h) { - try { - return await this.server.resourcesApi.getResource(h.type, h.id) - } catch (err) { - debug(`** Unable to fetch resource: ${h.type}, ${h.id}`) - return undefined - } - } else { - debug(`** Unable to parse href: ${href}`) - return undefined + debug(`** Unable to parse href: ${href}`) + return undefined } } private buildDeltaMsg(): any { -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - const values: Array<{ path: string; value: any }> = [] - const navPath = [ -======= - let values: Array<{path:string, value:any}> = [] - let root = [ ->>>>>>> init courseApi -======= - const values: Array<{ path: string; value: any }> = [] - const navPath = [ ->>>>>>> chore: lint -======= - let values: Array<{path:string, value:any}> = [] - let root = [ ->>>>>>> init courseApi -======= - const values: Array<{ path: string; value: any }> = [] - const navPath = [ ->>>>>>> chore: lint -======= - let values: Array<{path:string, value:any}> = [] - let root = [ ->>>>>>> init courseApi -======= - const values: Array<{ path: string; value: any }> = [] - const navPath = [ ->>>>>>> chore: lint -======= - let values: Array<{path:string, value:any}> = [] - let root = [ ->>>>>>> init courseApi -======= const values: Array<{ path: string; value: any }> = [] const navPath = [ ->>>>>>> chore: lint 'navigation.courseGreatCircle', 'navigation.courseRhumbline' ] @@ -3292,287 +662,12 @@ export class CourseApi { debug(course) values.push({ -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> update detlas -======= ->>>>>>> update detlas -======= ->>>>>>> update detlas -======= ->>>>>>> update detlas path: `navigation.course`, value: course }) values.push({ -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - path: `${navPath[0]}.activeRoute.href`, -======= -======= ->>>>>>> update detlas - path: `${root[0]}.activeRoute.href`, ->>>>>>> init courseApi -======= - path: `${navPath[0]}.activeRoute.href`, ->>>>>>> chore: lint - value: this.courseInfo.activeRoute.href - }) - values.push({ - path: `${navPath[1]}.activeRoute.href`, - value: this.courseInfo.activeRoute.href - }) - values.push({ - path: `${navPath[0]}.activeRoute.startTime`, - value: this.courseInfo.activeRoute.startTime - }) - values.push({ - path: `${navPath[1]}.activeRoute.startTime`, - value: this.courseInfo.activeRoute.startTime - }) - values.push({ - path: `${navPath[0]}.nextPoint.href`, - value: this.courseInfo.nextPoint.href - }) - values.push({ - path: `${navPath[1]}.nextPoint.href`, - value: this.courseInfo.nextPoint.href - }) - values.push({ - path: `${navPath[0]}.nextPoint.position`, - value: this.courseInfo.nextPoint.position - }) - values.push({ - path: `${navPath[1]}.nextPoint.position`, - value: this.courseInfo.nextPoint.position - }) - values.push({ - path: `${navPath[0]}.nextPoint.type`, - value: this.courseInfo.nextPoint.type - }) - values.push({ - path: `${navPath[1]}.nextPoint.type`, - value: this.courseInfo.nextPoint.type - }) - values.push({ - path: `${navPath[0]}.nextPoint.arrivalCircle`, - value: this.courseInfo.nextPoint.arrivalCircle - }) - values.push({ - path: `${navPath[1]}.nextPoint.arrivalCircle`, - value: this.courseInfo.nextPoint.arrivalCircle - }) - values.push({ - path: `${navPath[0]}.previousPoint.href`, - value: this.courseInfo.previousPoint.href - }) - values.push({ - path: `${navPath[1]}.previousPoint.href`, - value: this.courseInfo.previousPoint.href - }) - values.push({ - path: `${navPath[0]}.previousPoint.position`, - value: this.courseInfo.previousPoint.position - }) - values.push({ - path: `${navPath[1]}.previousPoint.position`, - value: this.courseInfo.previousPoint.position - }) - values.push({ - path: `${navPath[0]}.previousPoint.type`, - value: this.courseInfo.previousPoint.type - }) - values.push({ -<<<<<<< HEAD -<<<<<<< HEAD - path: `${navPath[1]}.previousPoint.type`, -======= -======= ->>>>>>> update detlas - path: `${root[0]}.activeRoute.href`, -======= - path: `${navPath[0]}.activeRoute.href`, ->>>>>>> chore: lint - value: this.courseInfo.activeRoute.href - }) - values.push({ - path: `${navPath[1]}.activeRoute.href`, - value: this.courseInfo.activeRoute.href - }) - values.push({ - path: `${navPath[0]}.activeRoute.startTime`, - value: this.courseInfo.activeRoute.startTime - }) - values.push({ - path: `${navPath[1]}.activeRoute.startTime`, - value: this.courseInfo.activeRoute.startTime - }) - values.push({ - path: `${navPath[0]}.nextPoint.href`, - value: this.courseInfo.nextPoint.href - }) - values.push({ - path: `${navPath[1]}.nextPoint.href`, - value: this.courseInfo.nextPoint.href - }) - values.push({ - path: `${navPath[0]}.nextPoint.position`, - value: this.courseInfo.nextPoint.position - }) - values.push({ - path: `${navPath[1]}.nextPoint.position`, - value: this.courseInfo.nextPoint.position - }) - values.push({ - path: `${navPath[0]}.nextPoint.type`, - value: this.courseInfo.nextPoint.type - }) - values.push({ - path: `${navPath[1]}.nextPoint.type`, - value: this.courseInfo.nextPoint.type - }) - values.push({ - path: `${navPath[0]}.nextPoint.arrivalCircle`, - value: this.courseInfo.nextPoint.arrivalCircle - }) - values.push({ - path: `${navPath[1]}.nextPoint.arrivalCircle`, - value: this.courseInfo.nextPoint.arrivalCircle - }) - values.push({ - path: `${navPath[0]}.previousPoint.href`, - value: this.courseInfo.previousPoint.href - }) - values.push({ - path: `${navPath[1]}.previousPoint.href`, - value: this.courseInfo.previousPoint.href - }) - values.push({ - path: `${navPath[0]}.previousPoint.position`, - value: this.courseInfo.previousPoint.position - }) - values.push({ - path: `${navPath[1]}.previousPoint.position`, - value: this.courseInfo.previousPoint.position - }) - values.push({ - path: `${navPath[0]}.previousPoint.type`, - value: this.courseInfo.previousPoint.type - }) - values.push({ -<<<<<<< HEAD - path: `${root[1]}.previousPoint.type`, ->>>>>>> init courseApi -======= - path: `${navPath[1]}.previousPoint.type`, ->>>>>>> chore: lint -======= - path: `${root[1]}.previousPoint.type`, ->>>>>>> init courseApi -======= - path: `${navPath[1]}.previousPoint.type`, ->>>>>>> chore: lint -======= -======= ->>>>>>> update detlas - path: `${root[0]}.activeRoute.href`, -======= - path: `${navPath[0]}.activeRoute.href`, ->>>>>>> chore: lint - value: this.courseInfo.activeRoute.href - }) - values.push({ - path: `${navPath[1]}.activeRoute.href`, - value: this.courseInfo.activeRoute.href - }) - values.push({ - path: `${navPath[0]}.activeRoute.startTime`, - value: this.courseInfo.activeRoute.startTime - }) - values.push({ - path: `${navPath[1]}.activeRoute.startTime`, - value: this.courseInfo.activeRoute.startTime - }) - values.push({ - path: `${navPath[0]}.nextPoint.href`, - value: this.courseInfo.nextPoint.href - }) - values.push({ - path: `${navPath[1]}.nextPoint.href`, - value: this.courseInfo.nextPoint.href - }) - values.push({ - path: `${navPath[0]}.nextPoint.position`, - value: this.courseInfo.nextPoint.position - }) - values.push({ - path: `${navPath[1]}.nextPoint.position`, - value: this.courseInfo.nextPoint.position - }) - values.push({ - path: `${navPath[0]}.nextPoint.type`, - value: this.courseInfo.nextPoint.type - }) - values.push({ - path: `${navPath[1]}.nextPoint.type`, - value: this.courseInfo.nextPoint.type - }) - values.push({ - path: `${navPath[0]}.nextPoint.arrivalCircle`, - value: this.courseInfo.nextPoint.arrivalCircle - }) - values.push({ - path: `${navPath[1]}.nextPoint.arrivalCircle`, - value: this.courseInfo.nextPoint.arrivalCircle - }) - values.push({ - path: `${navPath[0]}.previousPoint.href`, - value: this.courseInfo.previousPoint.href - }) - values.push({ - path: `${navPath[1]}.previousPoint.href`, - value: this.courseInfo.previousPoint.href - }) - values.push({ - path: `${navPath[0]}.previousPoint.position`, - value: this.courseInfo.previousPoint.position - }) - values.push({ - path: `${navPath[1]}.previousPoint.position`, - value: this.courseInfo.previousPoint.position - }) - values.push({ - path: `${navPath[0]}.previousPoint.type`, - value: this.courseInfo.previousPoint.type - }) - values.push({ -<<<<<<< HEAD - path: `${root[1]}.previousPoint.type`, ->>>>>>> init courseApi -======= - path: `${navPath[1]}.previousPoint.type`, ->>>>>>> chore: lint -======= -======= ->>>>>>> update detlas - path: `${root[0]}.activeRoute.href`, -======= path: `${navPath[0]}.activeRoute.href`, ->>>>>>> chore: lint value: this.courseInfo.activeRoute.href }) values.push({ @@ -3640,51 +735,14 @@ export class CourseApi { value: this.courseInfo.previousPoint.type }) values.push({ -<<<<<<< HEAD - path: `${root[1]}.previousPoint.type`, ->>>>>>> init courseApi -======= path: `${navPath[1]}.previousPoint.type`, ->>>>>>> chore: lint value: this.courseInfo.previousPoint.type }) return { updates: [ { -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - values -======= - values: values ->>>>>>> init courseApi -======= - values ->>>>>>> chore: lint -======= - values: values ->>>>>>> init courseApi -======= - values ->>>>>>> chore: lint -======= - values: values ->>>>>>> init courseApi -======= - values ->>>>>>> chore: lint -======= - values: values ->>>>>>> init courseApi -======= values ->>>>>>> chore: lint } ] } diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 000000000..02232b2cc --- /dev/null +++ b/src/api/index.ts @@ -0,0 +1,43 @@ +export interface ApiResponse { + state: 'FAILED' | 'COMPLETED' | 'PENDING' + statusCode: number + message: string + requestId?: string + href?: string + token?: string +} + +export const Responses = { + ok: { + state: 'COMPLETED', + statusCode: 200, + message: 'OK' + }, + invalid: { + state: 'FAILED', + statusCode: 406, + message: `Invalid Data supplied.` + }, + unauthorised: { + state: 'FAILED', + statusCode: 403, + message: 'Unauthorised' + }, + notFound: { + state: 'FAILED', + statusCode: 404, + message: 'Resource not found.' + } +} + +// returns true if target path is an API request +export function isApiRequest(path:string): boolean { + if ( + path.split('/')[4] === 'resources' || // resources API + path.indexOf('/navigation/course/') !== -1 // course API + ) { + return true + } else { + return false + } +} \ No newline at end of file diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 061bb8b23..2a56aaa36 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -22,11 +22,11 @@ import { Application, NextFunction, Request, Response } from 'express' import { v4 as uuidv4 } from 'uuid' import { WithSecurityStrategy, WithSignalK } from '../../app' -import { Responses } from '../responses' +import { Responses } from '../' import { buildResource } from './resources' import { validate } from './validate' -const debug = Debug('signalk:resources') +const debug = Debug('signalk:resourcesApi') const SIGNALK_API_PATH = `/signalk/v1/api` const UUID_PREFIX = 'urn:mrn:signalk:uuid:' diff --git a/src/api/resources/resources.ts b/src/api/resources/resources.ts index 43d578f7f..a40a7b2fe 100644 --- a/src/api/resources/resources.ts +++ b/src/api/resources/resources.ts @@ -4,7 +4,7 @@ ======= >>>>>>> fix type import { SignalKResourceType } from '@signalk/server-api' -import { getDistance, getLatitude, isValidCoordinate } from 'geolib' +import { getDistance, isValidCoordinate } from 'geolib' <<<<<<< HEAD <<<<<<< HEAD diff --git a/src/api/responses.ts b/src/api/responses.ts deleted file mode 100644 index 82c009c27..000000000 --- a/src/api/responses.ts +++ /dev/null @@ -1,31 +0,0 @@ -export interface ApiResponse { - state: 'FAILED' | 'COMPLETED' | 'PENDING' - statusCode: number - message: string - requestId?: string - href?: string - token?: string -} - -export const Responses = { - ok: { - state: 'COMPLETED', - statusCode: 200, - message: 'OK' - }, - invalid: { - state: 'FAILED', - statusCode: 406, - message: `Invalid Data supplied.` - }, - unauthorised: { - state: 'FAILED', - statusCode: 403, - message: 'Unauthorised' - }, - notFound: { - state: 'FAILED', - statusCode: 404, - message: 'Resource not found.' - } -} diff --git a/src/put.js b/src/put.js index 13120ba95..335914546 100644 --- a/src/put.js +++ b/src/put.js @@ -5,6 +5,8 @@ const { v4: uuidv4 } = require('uuid') const { createRequest, updateRequest } = require('./requestResponse') const skConfig = require('./config/config') +const {isApiRequest} = require('./api') + const pathPrefix = '/signalk' const versionPrefix = '/v1' const apiPathPrefix = pathPrefix + versionPrefix + '/api/' @@ -31,14 +33,9 @@ module.exports = { app.deRegisterActionHandler = deRegisterActionHandler app.put(apiPathPrefix + '*', function(req, res, next) { - // ** ignore resources API paths ** - if (req.path.split('/')[4] === 'resources') { - next() - return - } - // ** ignore course API paths ** - if (req.path.indexOf('/navigation/course/') !== -1) { + // check for resources API, course API, etc request + if (isApiRequest(req.path)) { next() return } From eab4c550a3b3e1f89444ab4be135088db6e64f99 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Fri, 21 Jan 2022 13:34:27 +1030 Subject: [PATCH 400/410] chore: update docs --- RESOURCE_PROVIDER_PLUGINS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index f563fc9b5..0815a9c1b 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -6,6 +6,8 @@ >>>>>>> chore: Updated documentation _This document should be read in conjunction with [SERVERPLUGINS.md](./SERVERPLUGINS.md) as it contains additional information regarding the development of plugins that facilitate the storage and retrieval of resource data._ +To see an example of a resource provider plugin see [resources-provider-plugin](https://github.com/SignalK/resources-provider-plugin/) + --- <<<<<<< HEAD From 5afc8b7e1941973fd62796cda520eb3fb5c91208 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Sat, 22 Jan 2022 10:04:39 +1030 Subject: [PATCH 401/410] POST, PUT & DELETE response.message = resource id --- src/api/resources/index.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 2a56aaa36..68dee8737 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -239,7 +239,7 @@ export class Resources { res.status(200).json({ state: 'COMPLETED', statusCode: 200, - message: `New ${req.params.resourceType} resource (${id}) saved.` + message: id }) } catch (err) { res.status(404).json({ @@ -321,7 +321,7 @@ export class Resources { res.status(200).json({ state: 'COMPLETED', statusCode: 200, - message: `${req.params.resourceType} resource (${req.params.resourceId}) saved.` + message: req.params.resourceId }) } catch (err) { res.status(404).json({ @@ -368,7 +368,7 @@ export class Resources { res.status(200).json({ state: 'COMPLETED', statusCode: 200, - message: `Resource (${req.params.resourceId}) deleted.` + message: req.params.resourceId }) } catch (err) { res.status(400).json({ @@ -455,7 +455,7 @@ export class Resources { res.status(200).json({ state: 'COMPLETED', statusCode: 200, - message: `SUCCESS: New ${req.params.resourceType} resource created.` + message: apiData.id }) } catch (err) { res.status(404).json({ @@ -1074,13 +1074,13 @@ export class Resources { res.status(200).json({ state: 'COMPLETED', statusCode: 200, - message: `SUCCESS: ${req.params.resourceType} resource updated.` + message: apiData.id }) } catch (err) { res.status(404).json({ state: 'FAILED', statusCode: 404, - message: `ERROR: ${req.params.resourceType} resource could not be updated!` + message: `ERROR: ${req.params.resourceType}/${apiData.id} could not be updated!` }) } } From 16212a7df442a2ad623cdf9c15ee1f5c52c1a9bd Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Thu, 27 Jan 2022 12:03:42 +1030 Subject: [PATCH 402/410] fix type definitions --- src/api/resources/types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/resources/types.ts b/src/api/resources/types.ts index b995b0adb..2a6b0d1d7 100644 --- a/src/api/resources/types.ts +++ b/src/api/resources/types.ts @@ -68,7 +68,7 @@ interface Polygon { type: 'Feature' geometry: { type: 'Polygon' - coords: GeoJsonPolygon + coordinates: GeoJsonPolygon } properties?: object id?: string @@ -78,7 +78,7 @@ interface MultiPolygon { type: 'Feature' geometry: { type: 'MultiPolygon' - coords: GeoJsonMultiPolygon + coordinates: GeoJsonMultiPolygon } properties?: object id?: string From 685cffc9a4f86034a831c45f7c6e8dbb6fa06f50 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Thu, 27 Jan 2022 12:04:20 +1030 Subject: [PATCH 403/410] update debug use --- src/api/resources/index.ts | 631 +------------------------------------ 1 file changed, 4 insertions(+), 627 deletions(-) diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 68dee8737..464c949f0 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -1,23 +1,14 @@ -<<<<<<< HEAD import Debug from 'debug' -import { Application, NextFunction, Request, Response } from 'express' -import { v4 as uuidv4 } from 'uuid' -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -import { buildResource } from './resources' -import { validate } from './validate' +const debug = Debug('signalk:resourcesApi') +// import { createDebug } from './debug' +// const debug = createDebug('signalk:resourcesApi') -======= ->>>>>>> add charts API methods -======= ->>>>>>> add securityStrategy test import { ResourceProvider, ResourceProviderMethods, SignalKResourceType } from '@signalk/server-api' -import Debug from 'debug' + import { Application, NextFunction, Request, Response } from 'express' import { v4 as uuidv4 } from 'uuid' import { WithSecurityStrategy, WithSignalK } from '../../app' @@ -26,8 +17,6 @@ import { Responses } from '../' import { buildResource } from './resources' import { validate } from './validate' -const debug = Debug('signalk:resourcesApi') - const SIGNALK_API_PATH = `/signalk/v1/api` const UUID_PREFIX = 'urn:mrn:signalk:uuid:' @@ -209,7 +198,6 @@ export class Resources { req.params.resourceType as SignalKResourceType ) ) { -<<<<<<< HEAD if (!validate.resource(req.params.resourceType, req.body)) { res.status(406).json(Responses.invalid) return @@ -251,13 +239,8 @@ export class Resources { } ) -<<<<<<< HEAD - this.server.use( -<<<<<<< HEAD -======= // facilitate creation / update of resource entry at supplied id this.server.put( ->>>>>>> Use Express routing params for processing requests `${SIGNALK_API_PATH}/resources/:resourceType/:resourceId`, async (req: Request, res: Response, next: NextFunction) => { debug(`** PUT ${SIGNALK_API_PATH}/resources/:resourceType/:resourceId`) @@ -380,22 +363,6 @@ export class Resources { } ) -<<<<<<< HEAD - this.server.use( - `${SIGNALK_API_PATH}/resources/:resourceType`, - async (req: any, res: any, next: any) => { -======= - `${SIGNALK_API_PATH}/resources/*`, - async (req: Request, res: Response, next: NextFunction) => { ->>>>>>> refactor: use Express types - const result = this.parseResourceRequest(req) - if (result) { - const ar = await this.actionResourceRequest(result) - if (typeof ar.statusCode !== 'undefined') { - debug(`${JSON.stringify(ar)}`) - res.status = ar.statusCode - res.send(ar.message) -======= // facilitate API requests this.server.post( `${SIGNALK_API_PATH}/resources/set/:resourceType`, @@ -478,554 +445,6 @@ export class Resources { return } -<<<<<<< HEAD - if (req.params.apiFunction.toLowerCase().indexOf('set') !== -1) { - resValue = buildResource(resType, req.body) - if (!resValue) { - res.status(406).send(`Invalid resource data supplied!`) - return - } - if (!req.body.id) { - resId = UUID_PREFIX + uuidv4() ->>>>>>> Use Express routing params for processing requests - } else { - if (!validate.uuid(req.body.id)) { - res.status(406).send(`Invalid resource id supplied!`) - return - } - resId = req.body.id - } - } - if (req.params.apiFunction.toLowerCase().indexOf('delete') !== -1) { - resValue = null - if (!req.body.id) { - res.status(406).send(`No resource id supplied!`) - return - } - if (!validate.uuid(req.body.id)) { - res.status(406).send(`Invalid resource id supplied!`) - return - } - resId = req.body.id - } - - try { - const retVal = await this.resProvider[resType]?.setResource( - resType, - resId, - resValue - ) - this.server.handleMessage( - this.resProvider[resType]?.pluginId as string, - this.buildDeltaMsg(resType, resId, resValue) - ) - res.status(200).send(`SUCCESS: ${req.params.apiFunction} complete.`) - } catch (err) { - res.status(404).send(`ERROR: ${req.params.apiFunction} incomplete!`) - } - } - ) - } - - private getResourcePaths(): { [key: string]: any } { - const resPaths: { [key: string]: any } = {} - for (const i in this.resProvider) { - resPaths[i] = { - description: `Path containing ${ - i.slice(-1) === 's' ? i.slice(0, i.length - 1) : i - } resources`, - $source: this.resProvider[i]?.pluginId - } - } - return resPaths - } - -<<<<<<< HEAD -<<<<<<< HEAD - private parseResourceRequest(req: any): ResourceRequest | undefined { - debug('********* parse request *************') -======= - // parse api path request and return ResourceRequest object - private parseResourceRequest(req: Request): ResourceRequest | undefined { - debug('** req.originalUrl:', req.originalUrl) ->>>>>>> refactor: use Express types - debug('** req.method:', req.method) - debug('** req.body:', req.body) - debug('** req.query:', req.query) - debug('** req.params:', req.params) - - const resReq:any = { - method: req.method, - body: req.body, - query: req.query, - resourceType: req.params.resourceType ?? null, - resourceId: req.params.resourceId ?? null, - apiMethod: API_METHODS.includes(req.params.resourceType) ? req.params.resourceType : null - } - - if (resReq.apiMethod) { - if (resReq.apiMethod.toLowerCase().indexOf('waypoint') !== -1) { - resReq.resourceType = 'waypoints' - } - if (resReq.apiMethod.toLowerCase().indexOf('route') !== -1) { - resReq.resourceType = 'routes' - } - if (resReq.apiMethod.toLowerCase().indexOf('note') !== -1) { - resReq.resourceType = 'notes' - } - if (resReq.apiMethod.toLowerCase().indexOf('region') !== -1) { - resReq.resourceType = 'regions' - } - } else { - const resAttrib = req.params['0'] ? req.params['0'].split('/') : [] - req.query.attrib = resAttrib - } - - debug('** resReq:', resReq) - - if ( - this.resourceTypes.includes(resReq.resourceType) && - this.resProvider[resReq.resourceType] - ) { - return resReq - } else { - debug('Invalid resource type or no provider for this type!') - return undefined - } -======= - private checkForProvider(resType: SignalKResourceType): boolean { -<<<<<<< HEAD - return this.resourceTypes.includes(resType) && this.resProvider[resType] - ? true - : false ->>>>>>> Use Express routing params for processing requests -======= - debug(`** checkForProvider(${resType})`) - debug(this.resProvider[resType]) - - if (this.resProvider[resType]) { - if ( - !this.resProvider[resType]?.listResources || - !this.resProvider[resType]?.getResource || - !this.resProvider[resType]?.setResource || - !this.resProvider[resType]?.deleteResource || - typeof this.resProvider[resType]?.listResources !== 'function' || - typeof this.resProvider[resType]?.getResource !== 'function' || - typeof this.resProvider[resType]?.setResource !== 'function' || - typeof this.resProvider[resType]?.deleteResource !== 'function' - ) { - return false - } else { - return true - } - } else { - return false - } ->>>>>>> allow registering custom resource types - } - - private buildDeltaMsg( - resType: SignalKResourceType, - resid: string, - resValue: any - ): any { - return { - updates: [ - { - values: [ - { - path: `resources.${resType}.${resid}`, - value: resValue - } - ] - } - ] - } - } -======= -import { validate } from './validate' -======= ->>>>>>> chore: linted -import { buildResource } from './resources' -import { validate } from './validate' - -import { - ResourceProvider, - ResourceProviderMethods, - SignalKResourceType -} from '@signalk/server-api' -import { Application, Handler, NextFunction, Request, Response } from 'express' - -const debug = Debug('signalk:resources') - -const SIGNALK_API_PATH = `/signalk/v1/api` -const UUID_PREFIX = 'urn:mrn:signalk:uuid:' - -const API_METHODS = [ - 'setWaypoint', - 'deleteWaypoint', - 'setRoute', - 'deleteRoute', - 'setNote', - 'deleteNote', - 'setRegion', - 'deleteRegion' -] - -interface ResourceApplication extends Application { - handleMessage: (id: string, data: any) => void -} - -export class Resources { - private resProvider: { [key: string]: ResourceProviderMethods | null } = {} - private server: ResourceApplication - - private signalkResTypes: SignalKResourceType[] = [ - 'routes', - 'waypoints', - 'notes', - 'regions', - 'charts' - ] - - constructor(app: ResourceApplication) { - this.server = app - this.start(app) - } - - register(pluginId: string, provider: ResourceProvider) { - debug(`** Registering provider(s)....${provider?.types}`) - if (!provider) { - return - } - if (provider.types && !Array.isArray(provider.types)) { - return - } - provider.types.forEach((i: string) => { - if (!this.resProvider[i]) { - provider.methods.pluginId = pluginId - this.resProvider[i] = provider.methods - } - }) - debug(this.resProvider) - } - - unRegister(pluginId: string) { - if (!pluginId) { - return - } - debug(`** Un-registering ${pluginId} resource provider(s)....`) - for (const resourceType in this.resProvider) { - if (this.resProvider[resourceType]?.pluginId === pluginId) { - debug(`** Un-registering ${resourceType}....`) - delete this.resProvider[resourceType] - } - } - debug(JSON.stringify(this.resProvider)) - } - - getResource(resType: SignalKResourceType, resId: string) { - debug(`** getResource(${resType}, ${resId})`) - if (!this.checkForProvider(resType)) { - return Promise.reject(new Error(`No provider for ${resType}`)) - } - return this.resProvider[resType]?.getResource(resType, resId) - } - - private start(app: any) { - debug(`** Initialise ${SIGNALK_API_PATH}/resources path handler **`) - this.server = app - this.initResourceRoutes() - } - - private initResourceRoutes() { - // list all serviced paths under resources - this.server.get( - `${SIGNALK_API_PATH}/resources`, - (req: Request, res: Response) => { - res.json(this.getResourcePaths()) - } - ) - - // facilitate retrieval of a specific resource - this.server.get( - `${SIGNALK_API_PATH}/resources/:resourceType/:resourceId`, - async (req: Request, res: Response, next: NextFunction) => { - debug(`** GET ${SIGNALK_API_PATH}/resources/:resourceType/:resourceId`) - if ( - !this.checkForProvider(req.params.resourceType as SignalKResourceType) - ) { - debug('** No provider found... calling next()...') - next() - return - } - try { - const retVal = await this.resProvider[ - req.params.resourceType - ]?.getResource(req.params.resourceType, req.params.resourceId) - res.json(retVal) - } catch (err) { - res.status(404).send(`Resource not found! (${req.params.resourceId})`) - } - } - ) - - // facilitate retrieval of a collection of resource entries - this.server.get( - `${SIGNALK_API_PATH}/resources/:resourceType`, - async (req: Request, res: Response, next: NextFunction) => { - debug(`** GET ${SIGNALK_API_PATH}/resources/:resourceType`) - if ( - !this.checkForProvider(req.params.resourceType as SignalKResourceType) - ) { - debug('** No provider found... calling next()...') - next() - return - } - try { - const retVal = await this.resProvider[ - req.params.resourceType - ]?.listResources(req.params.resourceType, req.query) - res.json(retVal) - } catch (err) { - res.status(404).send(`Error retrieving resources!`) - } - } - ) - - // facilitate creation of new resource entry of supplied type - this.server.post( - `${SIGNALK_API_PATH}/resources/:resourceType`, - async (req: Request, res: Response, next: NextFunction) => { - debug(`** POST ${SIGNALK_API_PATH}/resources/:resourceType`) - if ( - !this.checkForProvider(req.params.resourceType as SignalKResourceType) - ) { - debug('** No provider found... calling next()...') - next() - return - } -<<<<<<< HEAD - if (req.params.resourceType !== 'charts') { - if(!validate.uuid(req.params.resourceId)) { - res - .status(406) - .send(`Invalid resource id provided (${req.params.resourceId})`) - return - } - } - if (!validate.resource(req.params.resourceType, req.body)) { - res.status(406).send(`Invalid resource data supplied!`) - return - } - if (!validate.resource(req.params.resourceType, req.body)) { - res.status(406).send(`Invalid resource data supplied!`) - return -======= - - if (this.signalkResTypes.includes(req.params.resourceType as SignalKResourceType)) { -======= ->>>>>>> chore: lint - if (!validate.resource(req.params.resourceType, req.body)) { - res.status(406).send(`Invalid resource data supplied!`) - return - } ->>>>>>> allow registering custom resource types - } - - let id: string - if (req.params.resourceType === 'charts') { - id = req.body.identifier - } else { - id = UUID_PREFIX + uuidv4() - } - - try { - const retVal = await this.resProvider[ - req.params.resourceType - ]?.setResource(req.params.resourceType, id, req.body) - - this.server.handleMessage( - this.resProvider[req.params.resourceType]?.pluginId as string, - this.buildDeltaMsg( - req.params.resourceType as SignalKResourceType, - id, - req.body - ) - ) - res - .status(200) - .send(`New ${req.params.resourceType} resource (${id}) saved.`) - } catch (err) { - res - .status(404) - .send(`Error saving ${req.params.resourceType} resource (${id})!`) - } - } - ) - - // facilitate creation / update of resource entry at supplied id - this.server.put( - `${SIGNALK_API_PATH}/resources/:resourceType/:resourceId`, - async (req: Request, res: Response, next: NextFunction) => { - debug(`** PUT ${SIGNALK_API_PATH}/resources/:resourceType/:resourceId`) - if ( - !this.checkForProvider(req.params.resourceType as SignalKResourceType) - ) { - debug('** No provider found... calling next()...') - next() - return - } -<<<<<<< HEAD -======= - - if ( - this.signalkResTypes.includes( - req.params.resourceType as SignalKResourceType - ) - ) { - let isValidId: boolean - if (req.params.resourceType === 'charts') { - isValidId = validate.chartId(req.params.resourceId) - } else { - isValidId = validate.uuid(req.params.resourceId) - } - if (isValidId) { - res - .status(406) - .send(`Invalid resource id provided (${req.params.resourceId})`) - return - } - -<<<<<<< HEAD ->>>>>>> add chartId test & require alignment with spec. - if (!validate.resource(req.params.resourceType, req.body)) { - res.status(406).send(`Invalid resource data supplied!`) - return -======= - if (!validate.resource(req.params.resourceType, req.body)) { - res.status(406).send(`Invalid resource data supplied!`) - return - } ->>>>>>> allow registering custom resource types - } - - try { - const retVal = await this.resProvider[ - req.params.resourceType - ]?.setResource( - req.params.resourceType, - req.params.resourceId, - req.body - ) - - this.server.handleMessage( - this.resProvider[req.params.resourceType]?.pluginId as string, - this.buildDeltaMsg( - req.params.resourceType as SignalKResourceType, - req.params.resourceId, - req.body - ) - ) - res - .status(200) - .send( - `${req.params.resourceType} resource (${req.params.resourceId}) saved.` - ) - } catch (err) { - res - .status(404) - .send( - `Error saving ${req.params.resourceType} resource (${req.params.resourceId})!` - ) - } - } - ) - - // facilitate deletion of specific of resource entry at supplied id - this.server.delete( - `${SIGNALK_API_PATH}/resources/:resourceType/:resourceId`, - async (req: Request, res: Response, next: NextFunction) => { - debug( - `** DELETE ${SIGNALK_API_PATH}/resources/:resourceType/:resourceId` - ) - if ( - !this.checkForProvider(req.params.resourceType as SignalKResourceType) - ) { - debug('** No provider found... calling next()...') - next() - return - } - - try { - const retVal = await this.resProvider[ - req.params.resourceType - ]?.deleteResource(req.params.resourceType, req.params.resourceId) - - this.server.handleMessage( - this.resProvider[req.params.resourceType]?.pluginId as string, - this.buildDeltaMsg( - req.params.resourceType as SignalKResourceType, - req.params.resourceId, - null - ) - ) - res.status(200).send(`Resource (${req.params.resourceId}) deleted.`) - } catch (err) { - res - .status(400) - .send(`Error deleting resource (${req.params.resourceId})!`) - } - } - ) - - // facilitate API requests - this.server.put( - `${SIGNALK_API_PATH}/resources/:apiFunction`, - async (req: Request, res: Response, next: NextFunction) => { - debug(`** PUT ${SIGNALK_API_PATH}/resources/:apiFunction`) - - // check for valid API method request - if (!API_METHODS.includes(req.params.apiFunction)) { - res.status(501).send(`Invalid API method ${req.params.apiFunction}!`) - return - } - let resType: SignalKResourceType = 'waypoints' - if (req.params.apiFunction.toLowerCase().indexOf('waypoint') !== -1) { - resType = 'waypoints' - } - if (req.params.apiFunction.toLowerCase().indexOf('route') !== -1) { - resType = 'routes' - } - if (req.params.apiFunction.toLowerCase().indexOf('note') !== -1) { - resType = 'notes' - } - if (req.params.apiFunction.toLowerCase().indexOf('region') !== -1) { - resType = 'regions' - } - if (!this.checkForProvider(resType)) { - res.status(501).send(`No provider for ${resType}!`) - return - } - let resId: string = '' - let resValue: any = null - - if (req.params.apiFunction.toLowerCase().indexOf('set') !== -1) { - resValue = buildResource(resType, req.body) - if (!resValue) { - res.status(406).send(`Invalid resource data supplied!`) - return - } - if (!req.body.id) { - resId = UUID_PREFIX + uuidv4() - } else { - if (!validate.uuid(req.body.id)) { - res.status(406).send(`Invalid resource id supplied!`) - return - } - resId = req.body.id - } -======= const apiData = this.processApiRequest(req) if (!this.checkForProvider(apiData.type)) { @@ -1039,7 +458,6 @@ export class Resources { if (!apiData.value) { res.status(406).json(Responses.invalid) return ->>>>>>> add securityStrategy test } if (apiData.type === 'charts') { if (!validate.chartId(apiData.id)) { @@ -1142,52 +560,12 @@ export class Resources { return this.resProvider[resType] ? true : false } -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - // ** Get provider methods for supplied resource type. Returns null if none found ** - private getResourceProviderFor(resType:string): ResourceProviderMethods | null { - if(!this.server.plugins) { return null} - let pSource: ResourceProviderMethods | null= null - this.server.plugins.forEach((plugin:any)=> { - if(typeof plugin.resourceProvider !== 'undefined') { - pSource= plugin.resourceProvider.types.includes(resType) ? - plugin.resourceProvider.methods : - null - } - }) - debug(`** Checking for ${resType} provider.... ${pSource ? 'Found' : 'Not found'}`) - return pSource - } - ->>>>>>> Add Signal K standard resource path handling -======= ->>>>>>> add pluginId to unRegister function -======= - // ** send delta message with resource PUT, POST, DELETE action result -======= - // Send delta message. Used by resource PUT, POST, DELETE actions ->>>>>>> specify SignalKResourceType -======= ->>>>>>> cleanup express route handling - private sendDelta( - providerId: string, - type: string, - id: string, - value: any - ): void { - debug(`** Sending Delta: resources.${type}.${id}`) - this.server.handleMessage(providerId, { -======= private buildDeltaMsg( resType: SignalKResourceType, resid: string, resValue: any ): any { return { ->>>>>>> Use Express routing params for processing requests updates: [ { values: [ @@ -1200,5 +578,4 @@ export class Resources { ] } } ->>>>>>> chore: linted } From e1838b6006a7cb911c03931f0de4f1de81308d5c Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Thu, 27 Jan 2022 13:49:17 +1030 Subject: [PATCH 404/410] update after v1.41 rebase --- RESOURCE_PROVIDER_PLUGINS.md | 393 ------- SERVERPLUGINS.md | 108 -- WORKING_WITH_RESOURCES_API.md | 1 - src/api/course/index.ts | 6 +- src/api/course/openApi.json | 186 ---- src/api/resources/index.ts | 6 +- src/api/resources/openApi.json | 1815 +------------------------------- src/api/resources/resources.ts | 248 ----- src/api/resources/validate.ts | 160 --- 9 files changed, 37 insertions(+), 2886 deletions(-) diff --git a/RESOURCE_PROVIDER_PLUGINS.md b/RESOURCE_PROVIDER_PLUGINS.md index 0815a9c1b..fc8efa00b 100644 --- a/RESOURCE_PROVIDER_PLUGINS.md +++ b/RESOURCE_PROVIDER_PLUGINS.md @@ -1,16 +1,11 @@ # Resource Provider plugins -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> chore: Updated documentation _This document should be read in conjunction with [SERVERPLUGINS.md](./SERVERPLUGINS.md) as it contains additional information regarding the development of plugins that facilitate the storage and retrieval of resource data._ To see an example of a resource provider plugin see [resources-provider-plugin](https://github.com/SignalK/resources-provider-plugin/) --- -<<<<<<< HEAD ## Overview The SignalK specification defines the path `/signalk/v1/api/resources` for accessing resources to aid in navigation and operation of the vessel. @@ -69,84 +64,6 @@ The `ResourceProvider` interface defines the contract between the the Resource P ```typescript interface ResourceProvider: { types: string[], -======= -======= ->>>>>>> chore: Updated documentation -## Overview - -The SignalK specification defines the path `/signalk/v1/api/resources` for accessing resources to aid in navigation and operation of the vessel. - -It also defines the schema for the following __Common__ resource types: -- routes -- waypoints -- notes -- regions -- charts - -each of with its own path under the root `resources` path _(e.g. `/signalk/v1/api/resources/routes`)_. - -It should also be noted that the `/signalk/v1/api/resources` path can also host other types of resource data which can be grouped within a __Custom__ path name _(e.g. `/signalk/v1/api/resources/fishingZones`)_. - -The SignalK server does not natively provide the ability to store or retrieve resource data for either __Common__ and __Custom__ resource types. -This functionality needs to be provided by one or more server plugins that handle the data for specific resource types. - -These plugins are called __Resource Providers__. - -This de-coupling of resource request handling and storage / retrieval provides great flexibility to ensure that an appropriate resource storage solution can be configured for your SignalK implementation. - -SignalK server handles requests for both __Common__ and __Custom__ resource types in a similar manner, the only difference being that it does not perform any validation on __Custom__ resource data, so a plugin can act a s a provider for both types. - ---- -## Server Operation: - -The Signal K server handles all requests to `/signalk/v1/api/resources` and all sub-paths, before passing on the request to the registered resource provider plugin. - -<<<<<<< HEAD -<<<<<<< HEAD -_Definition: `resourceProvider` interface._ -```javascript -resourceProvider: { - types: [], ->>>>>>> Added Resource_Provider documentation -======= -As detailed earlier in this document, the __Common__ resource types are: -`routes`, `waypoints`, `notes`, `regions` & `charts`. -======= -The following operations are performed by the server when a request is received: -- Checks for a registered provider for the resource type -- Checks that ResourceProvider methods are defined -- For __Common__ resource types, checks the validity of the: - - Resource id - - Submitted resource data. ->>>>>>> chore: update docs - -Upon successful completion of these operations the request will then be passed to the registered resource provider plugin. - ---- -## Resource Provider plugin: - -For a plugin to be considered a Resource Provider it needs to implement the `ResourceProvider` interface. - -By implementing this interface the plugin is able to register with the SignalK server the: -- Resource types provided for by the plugin -- Methods to used to action requests. - -It is these methods that perform the retrival, saving and deletion of resources from storage. - - -### Resource Provider Interface - ---- -The `ResourceProvider` interface defines the contract between the the Resource Provider plugin and the SignalK server and has the following definition _(which it and other related types can be imported from `@signalk/server-api`)_: - -```typescript -interface ResourceProvider: { -<<<<<<< HEAD - types: SignalKResourceType[], ->>>>>>> chore: Updated documentation -======= - types: string[], ->>>>>>> chore: update docs methods: { listResources: (type:string, query: {[key:string]:any})=> Promise getResource: (type:string, id:string)=> Promise @@ -155,20 +72,9 @@ interface ResourceProvider: { } } ``` -<<<<<<< HEAD -<<<<<<< HEAD where: - `types`: An array containing a list of resource types provided for by the plugin. These can be a mixture of both __Common__ and __Custom__ resource types. -======= -where: - -<<<<<<< HEAD -- `types`: An array containing a list of __Common__ resource types provided for by the plugin ->>>>>>> chore: Updated documentation -======= -- `types`: An array containing a list of resource types provided for by the plugin. These can be a mixture of both __Common__ and __Custom__ resource types. ->>>>>>> chore: update docs - `methods`: An object containing the methods resource requests are passed to by the SignalK server. The plugin __MUST__ implement each method, even if that operation is not supported by the plugin! #### __Method Details:__ @@ -178,21 +84,9 @@ __`listResources(type, query)`__: This method is called when a request is made f _Note: It is the responsibility of the resource provider plugin to filter the resources returned as per the supplied query parameters._ -<<<<<<< HEAD -<<<<<<< HEAD `type:` String containing the type of resource to retrieve. `query:` Object contining `key | value` pairs repesenting the parameters by which to filter the returned entries. _e.g. {distance,'50000}_ -======= -`type:` Array of __Common__ resource types provied for by the plugin _e.g. ['routes','waypoints']_ - -`query:` Object contining `key | value` pairs repesenting the parameters by which to filter the returned entries _e.g. {distance,'50000}_ ->>>>>>> chore: Updated documentation -======= -`type:` String containing the type of resource to retrieve. - -`query:` Object contining `key | value` pairs repesenting the parameters by which to filter the returned entries. _e.g. {distance,'50000}_ ->>>>>>> chore: update docs `returns:` - Resolved Promise containing a list of resource entries on completion. @@ -218,21 +112,9 @@ listResources( --- __`getResource(type, id)`__: This method is called when a request is made for a specific resource entry of the supplied resource type and id. -<<<<<<< HEAD -<<<<<<< HEAD -`type:` String containing the type of resource to retrieve. - -`id:` String containing the target resource entry id. _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ -======= -`type:` String containing one of the __Common__ resource types _e.g. 'waypoints'_ - -`id:` String containing the target resource entry id _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ ->>>>>>> chore: Updated documentation -======= `type:` String containing the type of resource to retrieve. `id:` String containing the target resource entry id. _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ ->>>>>>> chore: update docs `returns:` - Resolved Promise containing the resource entry on completion. @@ -243,7 +125,6 @@ _Example resource request:_ GET /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99 ``` _ResourceProvider method invocation:_ -<<<<<<< HEAD ```javascript getResource( @@ -388,280 +269,10 @@ module.exports = function (app) { plugin.stop = function(options) { app.resourcesApi.unRegister(plugin.id); ... -======= -======= ->>>>>>> chore: Updated documentation - -```javascript -getResource( - 'routes', - 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99' -); -``` - ---- -__`setResource(type, id, value)`__: This method is called when a request is made to save / update a resource entry of the specified resource type, with the supplied id and data. - -`type:` String containing the type of resource to store. - -`id:` String containing the target resource entry id. _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ - -`value:` Resource data to be stored. - -`returns:` -- Resolved Promise containing a list of resource entries on completion. -- Rejected Promise containing an Error if incomplete or not implemented. - -_Example PUT resource request:_ -``` -PUT /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99 {resource_data} -``` -_ResourceProvider method invocation:_ - -```javascript -setResource( - 'routes', - 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99', - {} -); -``` - -_Example POST resource request:_ -``` -POST /signalk/v1/api/resources/routes {resource_data} -``` -_ResourceProvider method invocation:_ - -```javascript -setResource( - 'routes', - '', - {} -); -``` - ---- -__`deleteResource(type, id)`__: This method is called when a request is made to remove the specific resource entry of the supplied resource type and id. - -`type:` String containing the type of resource to delete. - -`id:` String containing the target resource entry id. _e.g. 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99'_ - -`returns:` -- Resolved Promise on completion. -- Rejected Promise containing an Error if incomplete or not implemented. - -_Example resource request:_ -``` -DELETE /signalk/v1/api/resources/routes/urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99 -``` -_ResourceProvider method invocation:_ - -```javascript -deleteResource( - 'routes', - 'urn:mrn:signalk:uuid:07894aba-f151-4099-aa4f-5e5773734b99' -); -``` - -### Registering a Resource Provider: ---- - -To register the resource provider plugin with the SignalK server, the server's `resourcesApi.register()` function should be called during plugin startup. - -The server `resourcesApi.register()` function has the following signature: - -```typescript -app.resourcesApi.register(pluginId: string, resourceProvider: ResourceProvider) -``` -where: -- `pluginId`: is the plugin's id -- `resourceProvider`: is a reference to the plugins ResourceProvider interface. - -_Note: A resource type can only have one registered plugin, so if more than one plugin attempts to register as a provider for the same resource type, the first plugin to call the `register()` function will be registered by the server for the resource types defined in the ResourceProvider interface!_ - -_Example:_ -```javascript -module.exports = function (app) { - - let plugin= { - id: 'mypluginid', - name: 'My Resource Providerplugin', - resourceProvider: { - types: ['routes','waypoints'], - methods: { - listResources: (type, params)=> { ... }, - getResource: (type:string, id:string)=> { ... } , - setResource: (type:string, id:string, value:any)=> { ... }, - deleteResource: (type:string, id:string)=> { ... } - } - } - } - - plugin.start = function(options) { - ... - app.resourcesApi.register(plugin.id, plugin.resourceProvider); - } -} -``` - -### Un-registering the Resource Provider: ---- - -When a resource provider plugin is disabled, it should un-register itself to ensure resource requests are no longer directed to it by calling the SignalK server. This should be done by calling the server's `resourcesApi.unRegister()` function during shutdown. - -The server `resourcesApi.unRegister()` function has the following signature: - -```typescript -app.resourcesApi.unRegister(pluginId: string) -``` -where: -- `pluginId`: is the plugin's id - - -_Example:_ -```javascript -module.exports = function (app) { - - let plugin= { - id: 'mypluginid', - name: 'My Resource Providerplugin', - resourceProvider: { - types: [ ... ], - methods: { ... } - } - } - - plugin.stop = function(options) { - app.resourcesApi.unRegister(plugin.id); - ... - } -} -``` - ---- - -### __Example:__ - -Resource Provider plugin providing for the retrieval of routes & waypoints. - -```javascript -// SignalK server plugin -module.exports = function (app) { - - let plugin= { - id: 'mypluginid', - name: 'My Resource Providerplugin', - // ResourceProvider interface - resourceProvider: { - types: ['routes','waypoints'], - methods: { - listResources: (type, params)=> { - return new Promise( (resolve, reject) => { - // fetch resource entries from storage - .... - if(ok) { // success - resolve({ - 'id1': { ... }, - 'id2': { ... }, - }); - } else { // error - reject(new Error('Error encountered!') - } - } - }, - getResource: (type, id)=> { - // fetch resource entries from storage - .... - if(ok) { // success - return Promise.resolve({ - ... - }); - } else { // error - reject(new Error('Error encountered!') - } - }, - setResource: (type, id, value)=> { - // not implemented - return Promise.reject(new Error('NOT IMPLEMENTED!')); - }, - deleteResource: (type, id)=> { - // not implemented - return Promise.reject(new Error('NOT IMPLEMENTED!')); - } - } - }, - - start: (options)=> { - ... - app.resourceApi.register(this.id, this.resourceProvider); - }, - - stop: ()=> { - app.resourceApi.unRegister(this.id); - ... - } - } -} -``` -<<<<<<< HEAD ---- - -<<<<<<< HEAD -`POST` requests to a resource path that do not contina the resource id will be dispatched to the `setResource` method passing the resource type, an id (generated by the server) and resource data as parameters. - -`setResource()` returns `true` on success and `null` on failure. - -_Example: New route record._ -```typescript -POST /signalk/v1/api/resources/routes {} - -setResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a', ) - -returns Promise -``` - -### __Deleting Resources:__ - -`DELETE` requests to a path containing the resource id will be dispatched to the `deleteResource` method passing the resource type and id as parameters. - -`deleteResource()` returns `true` on success and `null` on failure. - -_Example: Delete region with supplied id._ -```typescript -DELETE /signalk/v1/api/resources/regions/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a - -<<<<<<< HEAD -_Example:_ -```JAVASCRIPT -module.exports = function (app) { - let plugin= { - id: 'mypluginid', - name: 'My Resource Providerplugin', - start: (options, restart)=> { - ... - setTimeout( ()=> { app.resourcesApi.checkForProviders(true) }, 1000) - ... - }, - stop: ()=> { ... }, - ... ->>>>>>> Added Resource_Provider documentation } } -======= -deleteResource('regions', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a') - -<<<<<<< HEAD -returns true | null ->>>>>>> Add register / unregister -======= -returns Promise ->>>>>>> chore: return value descriptions to show a Promise ``` -======= ->>>>>>> chore: Updated documentation -<<<<<<< HEAD --- ### __Example:__ @@ -727,7 +338,3 @@ module.exports = function (app) { } } ``` -======= ->>>>>>> Added Resource_Provider documentation -======= ->>>>>>> chore: update docs diff --git a/SERVERPLUGINS.md b/SERVERPLUGINS.md index 4de5a2bba..1d8a53797 100644 --- a/SERVERPLUGINS.md +++ b/SERVERPLUGINS.md @@ -23,11 +23,6 @@ The plugin module must export a single `function(app)` that must return an objec ## Getting Started with Plugin Development To get started with SignalK plugin development, you can follow this guide. -<<<<<<< HEAD - -_Note: For plugins acting as a provider for one or more of the SignalK resource types listed in the specification (`routes`, `waypoints`, `notes`, `regions` or `charts`) please refer to __[RESOURCE_PROVIDER_PLUGINS.md](./RESOURCE_PROVIDER_PLUGINS.md)__ for additional details._ -======= ->>>>>>> chore: Updated documentation _Note: For plugins acting as a provider for one or more of the SignalK resource types listed in the specification (`routes`, `waypoints`, `notes`, `regions` or `charts`) please refer to __[RESOURCE_PROVIDER_PLUGINS.md](./RESOURCE_PROVIDER_PLUGINS.md)__ for additional details._ @@ -707,64 +702,6 @@ app.registerDeltaInputHandler((delta, next) => { }) ``` -<<<<<<< HEAD -<<<<<<< HEAD -### `app.resourcesApi.getResource(resource_type, resource_id)` - -Retrieve resource data for the supplied SignalK resource type and resource id. - -_Valid resource types are `routes`, `waypoints`, `notes`, `regions` & `charts`._ - - -This method invokes the `registered Resource Provider` for the supplied `resource_type` and returns a `resovled` __Promise__ containing the resource data if successful or -a `rejected` __Promise__ containing an __Error__ object if unsuccessful. - -_Example:_ -```javascript -let resource= app.resourcesApi.getResource('routes', 'urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a'); - -resource.then ( (data)=> { - // route data - console.log(data); - ... -}).catch (error) { - // handle error - console.log(error.message); - ... -} -``` - - -### `app.resourcesApi.register(pluginId, resourceProvider)` - -If a plugin wants to act as a resource provider, it will need to register its provider methods during startup using this function. - -See [`RESOURCE_PROVIDER_PLUGINS.md`](./RESOURCE_PROVIDER_PLUGINS.md) for details. - - -```javascript -module.exports = function (app) { - let plugin= { - id: 'mypluginid', - name: 'My Resource Providerplugin', - resourceProvider: { - types: ['routes','waypoints'], - methods: { ... } - } - start: function(options) { - // do plugin start up - app.resourcesApi.register(this.id, this.resourceProvider); - } - ... - } -} -``` - -### `app.resourcesApi.unRegister(pluginId)` - -When a resource provider plugin is disabled it will need to un-register its provider methods for all of the resource types it manages. This should be done in the plugin's `stop()` function. -======= -======= ### `app.resourcesApi.getResource(resource_type, resource_id)` Retrieve resource data for the supplied SignalK resource type and resource id. @@ -790,20 +727,8 @@ resource.then ( (data)=> { } ``` -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD ->>>>>>> add getResource function -### `app.resourcesApi.register(provider)` -======= -### `app.resourcesApi.register(pluginId, provider)` ->>>>>>> add pluginId to register() function -======= -======= ->>>>>>> chore: Updated documentation ### `app.resourcesApi.register(pluginId, resourceProvider)` ->>>>>>> update docs If a plugin wants to act as a resource provider, it will need to register its provider methods during startup using this function. @@ -830,21 +755,12 @@ module.exports = function (app) { ### `app.resourcesApi.unRegister(pluginId)` -<<<<<<< HEAD -When a resource provider plugin is disabled it will need to un-register its provider methods for the resource types it manages. This should be done in the plugin's `stop()` function. ->>>>>>> Add register / unregister -======= When a resource provider plugin is disabled it will need to un-register its provider methods for all of the resource types it manages. This should be done in the plugin's `stop()` function. ->>>>>>> add pluginId to unRegister function See [`RESOURCE_PROVIDER_PLUGINS.md`](./RESOURCE_PROVIDER_PLUGINS.md) for details. ```javascript -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> add pluginId to unRegister function module.exports = function (app) { let plugin= { id: 'mypluginid', @@ -855,35 +771,11 @@ module.exports = function (app) { } ... stop: function(options) { -<<<<<<< HEAD -<<<<<<< HEAD - app.resourcesApi.unRegister(this.id); - // do plugin shutdown - } - } -} -======= -plugin.stop = function(options) { - // resource_types example: ['routes',waypoints'] - app.resourcesApi.unRegister(resource_types); - ... -} - ->>>>>>> Add register / unregister -======= - app.resourcesApi.unRegister(this.id, this.resourceProvider.types); -======= app.resourcesApi.unRegister(this.id); -<<<<<<< HEAD ->>>>>>> update docs - ... -======= // do plugin shutdown ->>>>>>> chore: Updated documentation } } } ->>>>>>> add pluginId to unRegister function ``` diff --git a/WORKING_WITH_RESOURCES_API.md b/WORKING_WITH_RESOURCES_API.md index f972ea2c4..5656391cf 100644 --- a/WORKING_WITH_RESOURCES_API.md +++ b/WORKING_WITH_RESOURCES_API.md @@ -317,4 +317,3 @@ HTTP PUT 'http://hostname:3000/signalk/v1/api/resources/set/note/urn:mrn:signalk href: '/resources/regions/urn:mrn:signalk:uuid:35052456-65fa-48ce-a85d-41b78a9d2a61' } ``` - diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 7958bfb78..f69491c64 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,7 +1,5 @@ -import Debug from 'debug' -const debug = Debug('signalk:courseApi') -// import { createDebug } from './debug' -// const debug = createDebug('signalk:courseApi') +import { createDebug } from './debug' +const debug = createDebug('signalk:courseApi') import { Application, Request, Response } from 'express' import _ from 'lodash' diff --git a/src/api/course/openApi.json b/src/api/course/openApi.json index 549c625ef..6fde0b6cd 100644 --- a/src/api/course/openApi.json +++ b/src/api/course/openApi.json @@ -113,42 +113,6 @@ "put": { "tags": ["course"], "summary": "Restart course calculations", -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - "description": "Sets previousPoint value to current vessel location" -======= -======= ->>>>>>> init courseApi -======= ->>>>>>> init courseApi -======= ->>>>>>> init courseApi - "description": "Sets previousPoint value to current vessel location", - "requestBody": { - "description": "Restart payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } -======= "description": "Sets previousPoint value to current vessel location", "responses": { "default": { @@ -172,37 +136,11 @@ "type": "string" } } ->>>>>>> chore: update OpenApi responses } } } } } -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD ->>>>>>> init courseApi -======= - "description": "Sets previousPoint value to current vessel location" ->>>>>>> chore: lint -======= ->>>>>>> init courseApi -======= - "description": "Sets previousPoint value to current vessel location" ->>>>>>> chore: lint -======= ->>>>>>> init courseApi -======= - "description": "Sets previousPoint value to current vessel location" ->>>>>>> chore: lint -======= ->>>>>>> init courseApi -======= - "description": "Sets previousPoint value to current vessel location" ->>>>>>> chore: lint -======= ->>>>>>> chore: update OpenApi responses } }, @@ -289,42 +227,6 @@ "delete": { "tags": ["course/destination"], "summary": "Clear destination", -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - "description": "Sets activeRoute, nextPoint & previousPoint values to null" -======= -======= ->>>>>>> init courseApi -======= ->>>>>>> init courseApi -======= ->>>>>>> init courseApi - "description": "Sets activeRoute, nextPoint & previousPoint values to null", - "requestBody": { - "description": "Delete payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } -======= "description": "Sets activeRoute, nextPoint & previousPoint values to null", "responses": { "default": { @@ -348,37 +250,11 @@ "type": "string" } } ->>>>>>> chore: update OpenApi responses } } } } } -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD ->>>>>>> init courseApi -======= - "description": "Sets activeRoute, nextPoint & previousPoint values to null" ->>>>>>> chore: lint -======= ->>>>>>> init courseApi -======= - "description": "Sets activeRoute, nextPoint & previousPoint values to null" ->>>>>>> chore: lint -======= ->>>>>>> init courseApi -======= - "description": "Sets activeRoute, nextPoint & previousPoint values to null" ->>>>>>> chore: lint -======= ->>>>>>> init courseApi -======= - "description": "Sets activeRoute, nextPoint & previousPoint values to null" ->>>>>>> chore: lint -======= ->>>>>>> chore: update OpenApi responses } }, @@ -509,42 +385,6 @@ "delete": { "tags": ["course/activeRoute"], "summary": "Clear active route", -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - "description": "Sets activeRoute, nextPoint & previousPoint values to null" -======= -======= ->>>>>>> init courseApi -======= ->>>>>>> init courseApi -======= ->>>>>>> init courseApi - "description": "Sets activeRoute, nextPoint & previousPoint values to null", - "requestBody": { - "description": "Delete payload", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "object", - "default": null - }, - "source": { - "type": "string", - "example": "freeboard-sk" - } -======= "description": "Sets activeRoute, nextPoint & previousPoint values to null", "responses": { "default": { @@ -568,37 +408,11 @@ "type": "string" } } ->>>>>>> chore: update OpenApi responses } } } } } -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD ->>>>>>> init courseApi -======= - "description": "Sets activeRoute, nextPoint & previousPoint values to null" ->>>>>>> chore: lint -======= ->>>>>>> init courseApi -======= - "description": "Sets activeRoute, nextPoint & previousPoint values to null" ->>>>>>> chore: lint -======= ->>>>>>> init courseApi -======= - "description": "Sets activeRoute, nextPoint & previousPoint values to null" ->>>>>>> chore: lint -======= ->>>>>>> init courseApi -======= - "description": "Sets activeRoute, nextPoint & previousPoint values to null" ->>>>>>> chore: lint -======= ->>>>>>> chore: update OpenApi responses } }, diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 464c949f0..ced1ad08d 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -1,7 +1,5 @@ -import Debug from 'debug' -const debug = Debug('signalk:resourcesApi') -// import { createDebug } from './debug' -// const debug = createDebug('signalk:resourcesApi') +import { createDebug } from './debug' +const debug = createDebug('signalk:resourcesApi') import { ResourceProvider, diff --git a/src/api/resources/openApi.json b/src/api/resources/openApi.json index cc494d86e..e70e60d87 100644 --- a/src/api/resources/openApi.json +++ b/src/api/resources/openApi.json @@ -10,20 +10,9 @@ "/resources": { "get": { "tags": ["resources"], -<<<<<<< HEAD -<<<<<<< HEAD "summary": "List available resource types", "responses": { "200": { -======= - "summary": "List available resource types", - "responses": { -<<<<<<< HEAD - "default": { ->>>>>>> addressed comments re parameters -======= - "200": { ->>>>>>> add API definitions "description": "List of available resource types", "content": { "application/json": { @@ -37,45 +26,18 @@ } } } -<<<<<<< HEAD - } - }, - - "/resources/{resourceType}": { -======= - "summary": "List available resource types" - -======= ->>>>>>> addressed comments re parameters } }, -<<<<<<< HEAD - "/resources/{resourceClass}": { ->>>>>>> add OpenApi definition file -======= "/resources/{resourceType}": { ->>>>>>> OpenApi descriptions "get": { "tags": ["resources"], "summary": "Retrieve resources", "parameters": [ { -<<<<<<< HEAD -<<<<<<< HEAD - "name": "resourceType", - "in": "path", - "description": "Type of resources to retrieve. Valid values are: routes, waypoints, notes, regions, charts", -======= - "name": "resourceClass", - "in": "path", - "description": "resource class", ->>>>>>> add OpenApi definition file -======= "name": "resourceType", "in": "path", "description": "Type of resources to retrieve. Valid values are: routes, waypoints, notes, regions, charts", ->>>>>>> OpenApi descriptions "required": true, "schema": { "type": "string", @@ -95,18 +57,8 @@ } }, { -<<<<<<< HEAD -<<<<<<< HEAD - "name": "distance", - "in": "query", -======= - "in": "query", - "name": "radius", ->>>>>>> add OpenApi definition file -======= "name": "distance", "in": "query", ->>>>>>> addressed comments re parameters "description": "Limit results to resources that fall within a square area, centered around the vessel's position, the edges of which are the sepecified distance in meters from the vessel.", "schema": { "type": "integer", @@ -116,24 +68,9 @@ } }, { -<<<<<<< HEAD -<<<<<<< HEAD "name": "bbox", "in": "query", "description": "Limit results to resources that fall within the bounded area defined as lower left (south west) and upper right (north east) coordinates [swlon,swlat,nelon,nelat]", -======= - "in": "query", - "name": "bbox", -<<<<<<< HEAD - "description": "Limit results to resources that fall within the bounded area defined as lower left and upper right x,y coordinates x1,y1,x2,y2", ->>>>>>> add OpenApi definition file -======= -======= - "name": "bbox", - "in": "query", ->>>>>>> OpenApi descriptions - "description": "Limit results to resources that fall within the bounded area defined as lower left (south west) and upper right (north east) coordinates [swlon,swlat,nelon,nelat]", ->>>>>>> addressed comments re parameters "style": "form", "explode": false, "schema": { @@ -209,10 +146,6 @@ "description": "A GeoJSON feature object which describes a route", "properties": { "geometry": { -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> addressed comments re parameters "type": "object", "properties": { "type": { @@ -220,38 +153,14 @@ "enum": ["LineString"] }, "coordinates": { -<<<<<<< HEAD - "type": "array", - "items": { - "type": "array", - "maxItems": 3, - "minItems": 2, - "items": { - "type": "number" - } -======= - "type": { - "type": "string", - "enum": ["LineString"] - }, - "coordinates": { - "type": "array", - "items": { -======= ->>>>>>> addressed comments re parameters "type": "array", "items": { -<<<<<<< HEAD - "type": "number" ->>>>>>> add OpenApi definition file -======= "type": "array", "maxItems": 3, "minItems": 2, "items": { "type": "number" } ->>>>>>> addressed comments re parameters } } } @@ -271,29 +180,7 @@ } } } -<<<<<<< HEAD -<<<<<<< HEAD - }, - "responses": { - "200": { - "description": "OK", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - } -======= ->>>>>>> add OpenApi definition file - } -======= }, -<<<<<<< HEAD - "responses": {} ->>>>>>> addressed comments re parameters -======= "responses": { "default": { "description": "Default response format", @@ -321,7 +208,6 @@ } } } ->>>>>>> add API definitions } }, @@ -339,34 +225,7 @@ "get": { "tags": ["resources/routes"], -<<<<<<< HEAD -<<<<<<< HEAD - "summary": "Retrieve route with supplied id", - "responses": { - "200": { - "description": "List of resources identified by their UUID", - "content": { - "application/json": { - "schema": { - "description": "List of Signal K resources", - "type": "object", - "additionalProperties": { - "type": "string" - } - } - } - } - } - } -======= - "summary": "Retrieve route with supplied id" ->>>>>>> add OpenApi definition file -======= "summary": "Retrieve route with supplied id", -<<<<<<< HEAD - "responses": {} ->>>>>>> addressed comments re parameters -======= "responses": { "200": { "description": "List of resources identified by their UUID", @@ -383,7 +242,6 @@ } } } ->>>>>>> add API definitions }, "put": { @@ -427,10 +285,6 @@ "description": "A GeoJSON feature object which describes a route", "properties": { "geometry": { -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> addressed comments re parameters "type": "object", "properties": { "type": { @@ -438,38 +292,14 @@ "enum": ["LineString"] }, "coordinates": { -<<<<<<< HEAD - "type": "array", - "items": { - "type": "array", - "maxItems": 3, - "minItems": 2, - "items": { - "type": "number" - } -======= - "type": { - "type": "string", - "enum": ["LineString"] - }, - "coordinates": { - "type": "array", - "items": { -======= ->>>>>>> addressed comments re parameters "type": "array", "items": { -<<<<<<< HEAD - "type": "number" ->>>>>>> add OpenApi definition file -======= "type": "array", "maxItems": 3, "minItems": 2, "items": { "type": "number" } ->>>>>>> addressed comments re parameters } } } @@ -489,29 +319,7 @@ } } } -<<<<<<< HEAD -<<<<<<< HEAD - }, - "responses": { - "200": { - "description": "OK", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - } -======= ->>>>>>> add OpenApi definition file - } -======= }, -<<<<<<< HEAD - "responses": {} ->>>>>>> addressed comments re parameters -======= "responses": { "default": { "description": "Default response format", @@ -539,35 +347,11 @@ } } } ->>>>>>> add API definitions }, "delete": { "tags": ["resources/routes"], -<<<<<<< HEAD -<<<<<<< HEAD - "summary": "Remove Route with supplied id", - "responses": { - "200": { - "description": "OK", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - } - } -======= - "summary": "Remove Route with supplied id" ->>>>>>> add OpenApi definition file -======= "summary": "Remove Route with supplied id", -<<<<<<< HEAD - "responses": {} ->>>>>>> addressed comments re parameters -======= "responses": { "default": { "description": "Default response format", @@ -595,7 +379,6 @@ } } } ->>>>>>> add API definitions } }, @@ -643,40 +426,6 @@ "properties": { "geometry": { "type": "object", -<<<<<<< HEAD -<<<<<<< HEAD - "properties": { - "type": "object", - "description": "GeoJSon geometry", - "properties": { - "type": { - "type": "string", - "enum": ["Point"] - } - }, - "coordinates": { - "type": "array", - "maxItems": 3, - "minItems": 2, - "items": { - "type": "number" - } -======= - "description": "GeoJSon geometry", - "properties": { - "type": { - "type": "string", - "enum": ["Point"] - } - }, - "coordinates": { - "type": "array", - "maxItems": 3, - "minItems": 2, - "items": { - "type": "number" ->>>>>>> add OpenApi definition file -======= "properties": { "type": "object", "description": "GeoJSon geometry", @@ -693,7 +442,6 @@ "items": { "type": "number" } ->>>>>>> addressed comments re parameters } } }, @@ -711,29 +459,7 @@ } } } -<<<<<<< HEAD -<<<<<<< HEAD - }, - "responses": { - "200": { - "description": "OK", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - } -======= ->>>>>>> add OpenApi definition file - } -======= }, -<<<<<<< HEAD - "responses": {} ->>>>>>> addressed comments re parameters -======= "responses": { "default": { "description": "Default response format", @@ -761,7 +487,6 @@ } } } ->>>>>>> add API definitions } }, @@ -779,34 +504,7 @@ "get": { "tags": ["resources/waypoints"], -<<<<<<< HEAD -<<<<<<< HEAD - "summary": "Retrieve waypoint with supplied id", - "responses": { - "200": { - "description": "List of resources identified by their UUID", - "content": { - "application/json": { - "schema": { - "description": "List of Signal K resources", - "type": "object", - "additionalProperties": { - "type": "string" - } - } - } - } - } - } -======= - "summary": "Retrieve waypoint with supplied id" ->>>>>>> add OpenApi definition file -======= "summary": "Retrieve waypoint with supplied id", -<<<<<<< HEAD - "responses": {} ->>>>>>> addressed comments re parameters -======= "responses": { "200": { "description": "List of resources identified by their UUID", @@ -823,7 +521,6 @@ } } } ->>>>>>> add API definitions }, "put": { @@ -868,40 +565,6 @@ "properties": { "geometry": { "type": "object", -<<<<<<< HEAD -<<<<<<< HEAD - "properties": { - "type": "object", - "description": "GeoJSon geometry", - "properties": { - "type": { - "type": "string", - "enum": ["Point"] - } - }, - "coordinates": { - "type": "array", - "maxItems": 3, - "minItems": 2, - "items": { - "type": "number" - } -======= - "description": "GeoJSon geometry", - "properties": { - "type": { - "type": "string", - "enum": ["Point"] - } - }, - "coordinates": { - "type": "array", - "maxItems": 3, - "minItems": 2, - "items": { - "type": "number" ->>>>>>> add OpenApi definition file -======= "properties": { "type": "object", "description": "GeoJSon geometry", @@ -918,7 +581,6 @@ "items": { "type": "number" } ->>>>>>> addressed comments re parameters } } }, @@ -936,29 +598,7 @@ } } } -<<<<<<< HEAD -<<<<<<< HEAD - }, - "responses": { - "200": { - "description": "OK", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - } -======= ->>>>>>> add OpenApi definition file - } -======= }, -<<<<<<< HEAD - "responses": {} ->>>>>>> addressed comments re parameters -======= "responses": { "default": { "description": "Default response format", @@ -986,35 +626,11 @@ } } } ->>>>>>> add API definitions }, "delete": { "tags": ["resources/waypoints"], -<<<<<<< HEAD -<<<<<<< HEAD - "summary": "Remove Waypoint with supplied id", - "responses": { - "200": { - "description": "OK", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - } - } -======= - "summary": "Remove Waypoint with supplied id" ->>>>>>> add OpenApi definition file -======= "summary": "Remove Waypoint with supplied id", -<<<<<<< HEAD - "responses": {} ->>>>>>> addressed comments re parameters -======= "responses": { "default": { "description": "Default response format", @@ -1042,7 +658,6 @@ } } } ->>>>>>> add API definitions } }, @@ -1112,10 +727,6 @@ } } } -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> add API definitions }, "responses": { "default": { @@ -1143,11 +754,6 @@ } } } -<<<<<<< HEAD -======= ->>>>>>> add OpenApi definition file -======= ->>>>>>> add API definitions } } }, @@ -1166,34 +772,7 @@ "get": { "tags": ["resources/notes"], -<<<<<<< HEAD -<<<<<<< HEAD - "summary": "Retrieve Note with supplied id", - "responses": { - "200": { - "description": "List of resources identified by their UUID", - "content": { - "application/json": { - "schema": { - "description": "List of Signal K resources", - "type": "object", - "additionalProperties": { - "type": "string" - } - } - } - } - } - } -======= - "summary": "Retrieve Note with supplied id" ->>>>>>> add OpenApi definition file -======= "summary": "Retrieve Note with supplied id", -<<<<<<< HEAD - "responses": {} ->>>>>>> addressed comments re parameters -======= "responses": { "200": { "description": "List of resources identified by their UUID", @@ -1210,7 +789,6 @@ } } } ->>>>>>> add API definitions }, "put": { @@ -1277,29 +855,7 @@ } } } -<<<<<<< HEAD -<<<<<<< HEAD - }, - "responses": { - "200": { - "description": "OK", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - } -======= ->>>>>>> add OpenApi definition file - } -======= }, -<<<<<<< HEAD - "responses": {} ->>>>>>> addressed comments re parameters -======= "responses": { "default": { "description": "Default response format", @@ -1327,13 +883,10 @@ } } } ->>>>>>> add API definitions }, "delete": { "tags": ["resources/notes"], -<<<<<<< HEAD -<<<<<<< HEAD "summary": "Remove Note with supplied id", "responses": { "default": { @@ -1362,28 +915,6 @@ } } } -======= - "summary": "Remove Note with supplied id" ->>>>>>> add OpenApi definition file -======= - "summary": "Remove Note with supplied id", -<<<<<<< HEAD - "responses": {} ->>>>>>> addressed comments re parameters -======= - "responses": { - "200": { - "description": "OK", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - } - } ->>>>>>> add API definitions } }, @@ -1391,15 +922,7 @@ "/resources/regions/": { "post": { "tags": ["resources/regions"], -<<<<<<< HEAD -<<<<<<< HEAD "summary": "Add a new Region", -======= - "summary": "Add a new Regkion", ->>>>>>> add OpenApi definition file -======= - "summary": "Add a new Region", ->>>>>>> add charts API methods "requestBody": { "description": "Region details", "required": true, @@ -1420,10 +943,6 @@ "description": "A Geo JSON feature object which describes the regions boundary", "properties": { "geometry": { -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> addressed comments re parameters "type": "object", "properties": { "oneOf": [ @@ -1436,55 +955,20 @@ "enum": ["Polygon"] }, "coordinates": { -<<<<<<< HEAD - "type": "array", - "items": { - "type": "array", - "items": { - "type": "array", - "maxItems": 3, - "minItems": 2, - "items": { - "type": "number" - } -======= - "oneOf": [ - { - "type": "object", - "description": "GeoJSon geometry", - "properties": { - "type": { - "type": "string", - "enum": ["Polygon"] - }, - "coordinates": { - "type": "array", - "items": { -======= ->>>>>>> addressed comments re parameters "type": "array", "items": { "type": "array", "items": { -<<<<<<< HEAD - "type": "number" ->>>>>>> add OpenApi definition file -======= "type": "array", "maxItems": 3, "minItems": 2, "items": { "type": "number" } ->>>>>>> addressed comments re parameters } } } } -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> addressed comments re parameters }, { "type": "object", @@ -1495,45 +979,11 @@ "enum": ["MultiPolygon"] }, "coordinates": { -<<<<<<< HEAD -======= - } - }, - { - "type": "object", - "description": "GeoJSon geometry", - "properties": { - "type": { - "type": "string", - "enum": ["MultiPolygon"] - }, - "coordinates": { - "type": "array", - "items": { ->>>>>>> add OpenApi definition file -======= ->>>>>>> addressed comments re parameters "type": "array", "items": { "type": "array", "items": { "type": "array", -<<<<<<< HEAD -<<<<<<< HEAD - "items": { - "type": "array", - "maxItems": 3, - "minItems": 2, - "items": { - "type": "number" - } -======= - "maxItems": 3, - "minItems": 2, - "items": { - "type": "number" ->>>>>>> add OpenApi definition file -======= "items": { "type": "array", "maxItems": 3, @@ -1541,25 +991,14 @@ "items": { "type": "number" } ->>>>>>> addressed comments re parameters } } } } } } -<<<<<<< HEAD -<<<<<<< HEAD - ] - } -======= - } - ] ->>>>>>> add OpenApi definition file -======= ] } ->>>>>>> addressed comments re parameters }, "properties": { "description": "Additional feature properties", @@ -1575,29 +1014,7 @@ } } } -<<<<<<< HEAD -<<<<<<< HEAD - }, - "responses": { - "200": { - "description": "OK", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - } -======= ->>>>>>> add OpenApi definition file - } -======= }, -<<<<<<< HEAD - "responses": {} ->>>>>>> addressed comments re parameters -======= "responses": { "default": { "description": "Default response format", @@ -1625,7 +1042,6 @@ } } } ->>>>>>> add API definitions } }, @@ -1643,8 +1059,6 @@ "get": { "tags": ["resources/regions"], -<<<<<<< HEAD -<<<<<<< HEAD "summary": "Retrieve Region with supplied id", "responses": { "200": { @@ -1662,16 +1076,6 @@ } } } -<<<<<<< HEAD -======= - "summary": "Retrieve Region with supplied id" ->>>>>>> add OpenApi definition file -======= - "summary": "Retrieve Region with supplied id", - "responses": {} ->>>>>>> addressed comments re parameters -======= ->>>>>>> add API definitions }, "put": { @@ -1697,10 +1101,6 @@ "description": "A Geo JSON feature object which describes the regions boundary", "properties": { "geometry": { -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> addressed comments re parameters "type": "object", "properties": { "oneOf": [ @@ -1713,7 +1113,6 @@ "enum": ["Polygon"] }, "coordinates": { -<<<<<<< HEAD "type": "array", "items": { "type": "array", @@ -1724,44 +1123,10 @@ "items": { "type": "number" } -======= - "oneOf": [ - { - "type": "object", - "description": "GeoJSon geometry", - "properties": { - "type": { - "type": "string", - "enum": ["Polygon"] - }, - "coordinates": { - "type": "array", - "items": { -======= ->>>>>>> addressed comments re parameters - "type": "array", - "items": { - "type": "array", - "items": { -<<<<<<< HEAD - "type": "number" ->>>>>>> add OpenApi definition file -======= - "type": "array", - "maxItems": 3, - "minItems": 2, - "items": { - "type": "number" - } ->>>>>>> addressed comments re parameters } } } } -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> addressed comments re parameters }, { "type": "object", @@ -1772,45 +1137,11 @@ "enum": ["MultiPolygon"] }, "coordinates": { -<<<<<<< HEAD -======= - } - }, - { - "type": "object", - "description": "GeoJSon geometry", - "properties": { - "type": { - "type": "string", - "enum": ["MultiPolygon"] - }, - "coordinates": { - "type": "array", - "items": { ->>>>>>> add OpenApi definition file -======= ->>>>>>> addressed comments re parameters "type": "array", "items": { "type": "array", "items": { "type": "array", -<<<<<<< HEAD -<<<<<<< HEAD - "items": { - "type": "array", - "maxItems": 3, - "minItems": 2, - "items": { - "type": "number" - } -======= - "maxItems": 3, - "minItems": 2, - "items": { - "type": "number" ->>>>>>> add OpenApi definition file -======= "items": { "type": "array", "maxItems": 3, @@ -1818,25 +1149,14 @@ "items": { "type": "number" } ->>>>>>> addressed comments re parameters } } } } } } -<<<<<<< HEAD -<<<<<<< HEAD - ] - } -======= - } - ] ->>>>>>> add OpenApi definition file -======= ] } ->>>>>>> addressed comments re parameters }, "properties": { "description": "Additional feature properties", @@ -1852,29 +1172,7 @@ } } } -<<<<<<< HEAD -<<<<<<< HEAD - }, - "responses": { - "200": { - "description": "OK", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - } -======= ->>>>>>> add OpenApi definition file - } -======= }, -<<<<<<< HEAD - "responses": {} ->>>>>>> addressed comments re parameters -======= "responses": { "default": { "description": "Default response format", @@ -1902,13 +1200,10 @@ } } } ->>>>>>> add API definitions }, "delete": { "tags": ["resources/regions"], -<<<<<<< HEAD -<<<<<<< HEAD "summary": "Remove Region with supplied id", "responses": { "default": { @@ -1945,8 +1240,6 @@ "post": { "tags": ["resources/charts"], "summary": "Add a new Chart", -<<<<<<< HEAD -======= "requestBody": { "description": "Chart details", "required": true, @@ -2375,86 +1668,23 @@ "put": { "tags": ["resources/api"], "summary": "Add / update a Waypoint", ->>>>>>> add charts API methods "requestBody": { - "description": "Chart details", + "description": "Waypoint attributes", "required": true, "content": { "application/json": { "schema": { "type": "object", - "description": "Signal K Chart resource", - "required": ["feature"], + "required": ["position"], "properties": { "name": { - "description": "Chart common name", - "example":"NZ615 Marlborough Sounds", - "type": "string" - }, - "identifier": { "type": "string", - "description": "Chart number", - "example":"NZ615" + "description": "Waypoint name" }, "description": { "type": "string", - "description": "A description of the chart" - }, -<<<<<<< HEAD - "tilemapUrl": { - "type": "string", - "description": "A url to the tilemap of the chart for use in TMS chartplotting apps", - "example":"http://{server}:8080/mapcache/NZ615" - }, - "region": { - "type": "string", - "description": "Region related to note. A pointer to a region UUID. Alternative to geohash" - }, - "geohash": { - "description": "Position related to chart. Alternative to region", - "type": "string" - }, - "chartUrl": { - "type": "string", - "description": "A url to the chart file's storage location", - "example":"file:///home/pi/freeboard/mapcache/NZ615" - }, - "scale": { - "type": "integer", - "description": "The scale of the chart, the larger number from 1:200000" - }, - "chartLayers": { - "type": "array", - "description": "If the chart format is WMS, the layers enabled for the chart.", - "items": { - "type": "string", - "description": "Identifier for the layer." - } + "description": "Textual description of the waypoint" }, - "bounds": { - "type": "array", - "description": "The bounds of the chart. An array containing the position of the upper left corner, and the lower right corner. Useful when the chart isn't inherently geo-referenced.", - "items": { - "description": "Position of a corner of the chart", - "type": "object", - "required": [ - "latitude", - "longitude" - ], - "properties": { - "latitude": { - "type": "number", - "format": "float" - }, - "longitude": { - "type": "number", - "format": "float" - }, - "altitude": { - "type": "number", - "format": "float" - } -======= "attributes": { "type": "object", "description": "Additional attributes as name:value pairs.", @@ -2477,31 +1707,8 @@ "longitude": { "type": "number", "format": "float" ->>>>>>> add API definitions } } - }, - "chartFormat": { - "type": "string", - "description": "The format of the chart", - "enum": [ - "gif", - "geotiff", - "kap", - "png", - "jpg", - "kml", - "wkt", - "topojson", - "geojson", - "gpx", - "tms", - "wms", - "S-57", - "S-63", - "svg", - "other" - ] } } } @@ -2538,34 +1745,6 @@ } }, -<<<<<<< HEAD -<<<<<<< HEAD - "/resources/charts/{id}": { - "parameters": { - "name": "id", - "in": "path", - "description": "Chart id", - "required": true, - "schema": { - "type": "string", - "pattern": "(^[A-Za-z0-9_-]{8,}$)" - } - }, - - "get": { - "tags": ["resources/charts"], - "summary": "Retrieve Chart with supplied id", - "responses": { - "200": { - "description": "List of resources identified by their UUID", - "content": { - "application/json": { - "schema": { - "description": "List of Signal K resources", - "type": "object", - "additionalProperties": { - "type": "string" -======= "/resources/set/route": { "post": { "tags": ["resources/api"], @@ -2698,16 +1877,11 @@ } } } ->>>>>>> Use POST for paths that DO NOT specify an id } } } } } -<<<<<<< HEAD - } - }, -======= }, "responses": { "default": { @@ -2738,13 +1912,7 @@ } } }, ->>>>>>> add API definitions -<<<<<<< HEAD -======= - "/resources/set/route/{id}": { ->>>>>>> chore: update docs -======= "/resources/set/note": { "post": { "tags": ["resources/api"], @@ -2837,884 +2005,50 @@ }, "/resources/set/note/{id}": { ->>>>>>> Use POST for paths that DO NOT specify an id "put": { - "tags": ["resources/charts"], - "summary": "Add / update a new Chart with supplied id", + "tags": ["resources/api"], + "summary": "Add / update a Note", "requestBody": { - "description": "Chart details", + "description": "Note attributes", "required": true, "content": { "application/json": { "schema": { "type": "object", -<<<<<<< HEAD - "description": "Signal K Chart resource", - "required": ["feature"], -======= "required": ["title"], ->>>>>>> chore: update docs "properties": { - "name": { - "description": "Chart common name", - "example":"NZ615 Marlborough Sounds", - "type": "string" - }, - "identifier": { + "title": { "type": "string", - "description": "Chart number", - "example":"NZ615" + "description": "Note's common name" }, "description": { "type": "string", - "description": "A description of the chart" - }, -<<<<<<< HEAD - "tilemapUrl": { - "type": "string", - "description": "A url to the tilemap of the chart for use in TMS chartplotting apps", - "example":"http://{server}:8080/mapcache/NZ615" - }, -<<<<<<< HEAD - "region": { - "type": "string", - "description": "Region related to note. A pointer to a region UUID. Alternative to geohash" - }, - "geohash": { - "description": "Position related to chart. Alternative to region", - "type": "string" - }, - "chartUrl": { - "type": "string", - "description": "A url to the chart file's storage location", - "example":"file:///home/pi/freeboard/mapcache/NZ615" - }, - "scale": { - "type": "integer", - "description": "The scale of the chart, the larger number from 1:200000" - }, - "chartLayers": { - "type": "array", - "description": "If the chart format is WMS, the layers enabled for the chart.", - "items": { - "type": "string", - "description": "Identifier for the layer." - } - }, - "bounds": { -======= - "attributes": { - "type": "object", - "description": "Additional attributes as name:value pairs.", - "additionalProperties": { - "type": "string" - } + "description": " Textual description of the note" }, - "points": { - "description": "Route points", ->>>>>>> add API definitions - "type": "array", - "description": "The bounds of the chart. An array containing the position of the upper left corner, and the lower right corner. Useful when the chart isn't inherently geo-referenced.", - "items": { - "description": "Position of a corner of the chart", - "type": "object", - "required": [ - "latitude", - "longitude" - ], - "properties": { - "latitude": { - "type": "number", - "format": "float" - }, - "longitude": { - "type": "number", - "format": "float" - }, - "altitude": { - "type": "number", - "format": "float" + "oneOf":[ + { + "position": { + "description": "Position related to note. Alternative to region or geohash", + "type": "object", + "required": [ + "latitude", + "longitude" + ], + "properties": { + "latitude": { + "type": "number", + "format": "float" + }, + "longitude": { + "type": "number", + "format": "float" + } } - } - } - }, - "chartFormat": { - "type": "string", - "description": "The format of the chart", - "enum": [ - "gif", - "geotiff", - "kap", - "png", - "jpg", - "kml", - "wkt", - "topojson", - "geojson", - "gpx", - "tms", - "wms", - "S-57", - "S-63", - "svg", - "other" - ] - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - } - } -<<<<<<< HEAD - }, - - "delete": { - "tags": ["resources/charts"], - "summary": "Remove Chart with supplied id", - "responses": { - "200": { - "description": "OK", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - } - } -======= - "summary": "Remove Region with supplied id", - "responses": {} ->>>>>>> addressed comments re parameters -======= ->>>>>>> add API definitions - } - - }, - -<<<<<<< HEAD - "/resources/setWaypoint": { - "put": { - "tags": ["resources/api"], - "summary": "Add / update a Waypoint", - "requestBody": { - "description": "Waypoint attributes", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["position"], - "properties": { - "id": { - "type": "string", - "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", - "description": "Waypoint identifier" - }, - "name": { - "type": "string", - "description": "Waypoint name" - }, - "description": { - "type": "string", - "description": "Textual description of the waypoint" - }, -<<<<<<< HEAD - "attributes": { - "type": "object", - "description": "Additional attributes as name:value pairs.", - "additionalProperties": { - "type": "string" - } - }, -======= ->>>>>>> addressed comments re parameters - "position": { - "description": "The waypoint position", - "type": "object", - "required": [ - "latitude", - "longitude" - ], - "properties": { - "latitude": { - "type": "number", - "format": "float" - }, - "longitude": { - "type": "number", - "format": "float" - } - } - } - } - } - } - } - }, -<<<<<<< HEAD - "responses": { - "200": { - "description": "OK", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - } - } -======= - "responses": {} ->>>>>>> addressed comments re parameters - } - }, - - "/resources/deleteWaypoint": { - "put": { - "tags": ["resources/api"], - "summary": "Remove a Waypoint", - "requestBody": { - "description": "Waypoint identifier", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["id"], - "properties": { - "id": { - "type": "string", - "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", - "description": "Waypoint identifier" - } - } - } - } - } - }, -<<<<<<< HEAD - "responses": { - "200": { - "description": "OK", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - } - } -======= - "responses": {} ->>>>>>> addressed comments re parameters - } - }, - - "/resources/setRoute": { - "put": { - "tags": ["resources/api"], - "summary": "Add / update a Route", - "requestBody": { - "description": "Note attributes", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["position"], - "properties": { - "id": { - "type": "string", - "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", - "description": "Route identifier" - }, - "name": { - "type": "string", - "description": "Route name" - }, - "description": { - "type": "string", - "description": "Textual description of the route" - }, -<<<<<<< HEAD - "attributes": { - "type": "object", - "description": "Additional attributes as name:value pairs.", - "additionalProperties": { - "type": "string" - } - }, -======= ->>>>>>> addressed comments re parameters - "points": { - "description": "Route points", - "type": "array", - "items": { - "type": "object", - "required": [ - "latitude", - "longitude" - ], - "properties": { - "latitude": { - "type": "number", - "format": "float" - }, - "longitude": { - "type": "number", - "format": "float" - } - } - } - } - } - } - } - } - }, -<<<<<<< HEAD - "responses": { - "200": { - "description": "OK", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - } - } -======= - "responses": {} ->>>>>>> addressed comments re parameters - } - }, - - "/resources/deleteRoute": { - "put": { - "tags": ["resources/api"], - "summary": "Remove a Route", - "requestBody": { - "description": "Route identifier", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["id"], - "properties": { - "id": { - "type": "string", - "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", - "description": "Route identifier" - } - } - } - } - } - }, -<<<<<<< HEAD - "responses": { - "200": { - "description": "OK", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - } - } - } - }, - - "/resources/setNote": { - "put": { - "tags": ["resources/api"], - "summary": "Add / update a Note", - "requestBody": { - "description": "Note attributes", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["position"], - "properties": { - "id": { - "type": "string", - "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", - "description": "Note identifier" - }, - "title": { - "type": "string", - "description": "Note's common name" - }, - "description": { - "type": "string", - "description": " Textual description of the note" - }, - "oneOf":[ - { - "position": { - "description": "Position related to note. Alternative to region or geohash", - "type": "object", - "required": [ - "latitude", - "longitude" - ], - "properties": { - "latitude": { - "type": "number", - "format": "float" - }, - "longitude": { - "type": "number", - "format": "float" - } - } - }, - "geohash": { - "type": "string", - "description": "Position related to note. Alternative to region or position" - }, - "region": { - "type": "string", - "description": "Region related to note. A pointer to a region UUID. Alternative to position or geohash", - "example": "/resources/regions/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a" - } - } - ], - "mimeType": { - "type": "string", - "description": "MIME type of the note" - }, - "url": { - "type": "string", - "description": "Location of the note contents" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - } - } - } - }, - - "/resources/deleteNote": { - "put": { - "tags": ["resources/api"], - "summary": "Remove a Note", - "requestBody": { - "description": "Note identifier", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["id"], - "properties": { - "id": { - "type": "string", - "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", - "description": "Note identifier" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - } - } - } - }, - - "/resources/setRegion": { - "put": { - "tags": ["resources/api"], - "summary": "Add / update a Region", - "requestBody": { - "description": "Region attributes", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["position"], - "properties": { - "id": { - "type": "string", - "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", - "description": "Region identifier" - }, - "name": { - "type": "string", - "description": "Region name" - }, - "description": { - "type": "string", - "description": "Textual description of region" - }, - "attributes": { - "type": "object", - "description": "Additional attributes as name:value pairs.", - "additionalProperties": { - "type": "string" - } - }, - "geohash": { - "type": "string", - "description": "Area related to region. Alternative to points." - }, - "points": { - "description": "Region boundary points", - "type": "array", - "items": { - "type": "object", - "required": [ - "latitude", - "longitude" - ], - "properties": { - "latitude": { - "type": "number", - "format": "float" - }, - "longitude": { - "type": "number", - "format": "float" - } - } - } - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - } - } -<<<<<<< HEAD - } - }, - - "/resources/deleteRegion": { - "put": { - "tags": ["resources/api"], - "summary": "Remove a Region", - "requestBody": { - "description": "Region identifier", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["id"], - "properties": { - "id": { - "type": "string", - "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", - "description": "Region identifier" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - } - } - } - }, - - "/resources/setChart": { - "put": { - "tags": ["resources/api"], - "summary": "Add / update a Chart", - "requestBody": { - "description": "Chart attributes", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "description": "Signal K Chart resource", - "required": ["identifier","tilemapUrl","chartUrl","chartFormat"], - "properties": { - "name": { - "description": "Chart common name", - "example":"NZ615 Marlborough Sounds", - "type": "string" - }, - "identifier": { - "type": "string", - "description": "Chart number", - "example":"NZ615" - }, - "description": { - "type": "string", - "description": "A description of the chart" - }, - "oneOf": [ - { - "tilemapUrl": { - "type": "string", - "description": "A url to the tilemap of the chart for use in TMS chartplotting apps", - "example":"http://{server}:8080/mapcache/NZ615" - } - }, - { - "chartUrl": { - "type": "string", - "description": "A url to the chart file's storage location", - "example":"file:///home/pi/freeboard/mapcache/NZ615" - } - } - ], - "region": { - "type": "string", - "description": "Region related to note. A pointer to a region UUID. Alternative to geohash" - }, - "geohash": { - "description": "Position related to chart. Alternative to region", - "type": "string" - }, - "scale": { - "type": "integer", - "description": "The scale of the chart, the larger number from 1:200000" - }, - "chartLayers": { - "type": "array", - "description": "If the chart format is WMS, the layers enabled for the chart.", - "items": { - "type": "string", - "description": "Identifier for the layer." - } - }, - "bounds": { - "type": "array", - "description": "The bounds of the chart. An array containing the position of the upper left corner, and the lower right corner. Useful when the chart isn't inherently geo-referenced.", - "items": { - "description": "Position of a corner of the chart", - "type": "object", - "required": [ - "latitude", - "longitude" - ], - "properties": { - "latitude": { - "type": "number", - "format": "float" - }, - "longitude": { - "type": "number", - "format": "float" - }, - "altitude": { - "type": "number", - "format": "float" - } - } - } - }, - "chartFormat": { - "type": "string", - "description": "The format of the chart", - "enum": [ - "gif", - "geotiff", - "kap", - "png", - "jpg", - "kml", - "wkt", - "topojson", - "geojson", - "gpx", - "tms", - "wms", - "S-57", - "S-63", - "svg", - "other" - ] - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - } - } - } - }, - - "/resources/deleteChart": { - "put": { - "tags": ["resources/api"], - "summary": "Remove a Chart", - "requestBody": { - "description": "Chart identifier", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["id"], - "properties": { - "id": { - "type": "string", - "pattern": "(^[A-Za-z0-9_-]{8,}$)", - "description": "Chart identifier" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - } - } - } -======= - "summary": "Remove Region with supplied id" -======= - "responses": {} ->>>>>>> addressed comments re parameters -======= ->>>>>>> add API definitions - } - }, - -<<<<<<< HEAD ->>>>>>> add OpenApi definition file -======= - "/resources/setNote": { -======= - "/resources/set/note/{id}": { ->>>>>>> chore: update docs - "put": { - "tags": ["resources/api"], - "summary": "Add / update a Note", - "requestBody": { - "description": "Note attributes", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["position"], - "properties": { - "title": { - "type": "string", - "description": "Note's common name" - }, - "description": { - "type": "string", - "description": " Textual description of the note" - }, - "oneOf":[ - { - "position": { - "description": "Position related to note. Alternative to region or geohash", - "type": "object", - "required": [ - "latitude", - "longitude" - ], - "properties": { - "latitude": { - "type": "number", - "format": "float" - }, - "longitude": { - "type": "number", - "format": "float" - } - } - }, - "region": { - "type": "string", - "description": "Region related to note. A pointer to a region UUID. Alternative to position or geohash", - "example": "/resources/regions/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a" + }, + "region": { + "type": "string", + "description": "Region related to note. A pointer to a region UUID. Alternative to position or geohash", + "example": "/resources/regions/urn:mrn:signalk:uuid:ac3a3b2d-07e8-4f25-92bc-98e7c92f7f1a" } } ], @@ -3722,8 +2056,6 @@ "type": "string", "description": "MIME type of the note" }, -======= ->>>>>>> chore: update OpenApi responses "url": { "type": "string", "description": "Location of the note contents" @@ -3734,13 +2066,8 @@ } }, "responses": { -<<<<<<< HEAD - "200": { - "description": "OK", -======= "default": { "description": "Default response format", ->>>>>>> chore: update OpenApi responses "content": { "application/json": { "schema": { @@ -3936,35 +2263,17 @@ } }, -<<<<<<< HEAD -<<<<<<< HEAD - "/resources/deleteRegion": { - "put": { - "tags": ["resources/api"], - "summary": "Remove a Region", - "requestBody": { - "description": "Region identifier", -======= "/resources/set/chart": { "post": { "tags": ["resources/api"], "summary": "Add / update a Chart", "requestBody": { "description": "Chart attributes", ->>>>>>> Use POST for paths that DO NOT specify an id "required": true, "content": { "application/json": { "schema": { "type": "object", -<<<<<<< HEAD - "required": ["id"], - "properties": { - "id": { - "type": "string", - "pattern": "urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$", - "description": "Region identifier" -======= "description": "Signal K Chart resource", "required": ["identifier", "tilemapUrl"], "properties": { @@ -4036,7 +2345,6 @@ "tilelayer", "WMS" ] ->>>>>>> Use POST for paths that DO NOT specify an id } } } @@ -4071,20 +2379,9 @@ } } } -<<<<<<< HEAD -<<<<<<< HEAD ->>>>>>> addressed comments re parameters -======= - }, - - "/resources/setChart": { -======= -======= }, ->>>>>>> Use POST for paths that DO NOT specify an id "/resources/set/chart/{id}": { ->>>>>>> chore: update docs "put": { "tags": ["resources/api"], "summary": "Add / update a Chart", @@ -4200,53 +2497,7 @@ } } } -<<<<<<< HEAD -<<<<<<< HEAD - }, - - "/resources/deleteChart": { - "put": { - "tags": ["resources/api"], - "summary": "Remove a Chart", - "requestBody": { - "description": "Chart identifier", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["id"], - "properties": { - "id": { - "type": "string", - "pattern": "(^[A-Za-z0-9_-]{8,}$)", - "description": "Chart identifier" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - } - } - } ->>>>>>> add charts API methods -======= ->>>>>>> chore: update docs - } -======= } ->>>>>>> Use POST for paths that DO NOT specify an id } diff --git a/src/api/resources/resources.ts b/src/api/resources/resources.ts index a40a7b2fe..709d964f3 100644 --- a/src/api/resources/resources.ts +++ b/src/api/resources/resources.ts @@ -1,13 +1,6 @@ -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> fix type import { SignalKResourceType } from '@signalk/server-api' import { getDistance, isValidCoordinate } from 'geolib' -<<<<<<< HEAD -<<<<<<< HEAD export const buildResource = (resType: SignalKResourceType, data: any): any => { if (resType === 'routes') { return buildRoute(data) @@ -208,247 +201,6 @@ const buildRegion = (rData: any): any => { reg.feature.geometry.coordinates.push(coords) return reg -======= -import { getDistance } from 'geolib' -======= -import { getDistance, isValidCoordinate } from 'geolib' ->>>>>>> align with openapi definitions -import ngeohash from 'ngeohash' - -// ** build resource item ** -======= - -<<<<<<< HEAD ->>>>>>> chore: return value descriptions to show a Promise -export const buildResource = (resType: string, data: any): any => { -======= -======= ->>>>>>> chore: lint -export const buildResource = (resType: SignalKResourceType, data: any): any => { ->>>>>>> fix type - if (resType === 'routes') { - return buildRoute(data) - } - if (resType === 'waypoints') { - return buildWaypoint(data) - } - if (resType === 'notes') { - return buildNote(data) - } - if (resType === 'regions') { - return buildRegion(data) - } -} - -const buildRoute = (rData: any): any => { - const rte: any = { - feature: { - type: 'Feature', - geometry: { - type: 'LineString', - coordinates: [] - }, - properties: {} - } - } - if (typeof rData.name !== 'undefined') { - rte.name = rData.name - rte.feature.properties.name = rData.name - } - if (typeof rData.description !== 'undefined') { - rte.description = rData.description - rte.feature.properties.description = rData.description - } - if (typeof rData.attributes !== 'undefined') { - Object.assign(rte.feature.properties, rData.attributes) - } - - if (typeof rData.points === 'undefined') { - return null - } - if (!Array.isArray(rData.points)) { - return null - } - let isValid: boolean = true - rData.points.forEach((p: any) => { - if (!isValidCoordinate(p)) { - isValid = false - } - }) - if (!isValid) { - return null - } - rte.feature.geometry.coordinates = rData.points.map((p: any) => { - return [p.longitude, p.latitude] - }) - - rte.distance = 0 - for (let i = 0; i < rData.points.length; i++) { - if (i !== 0) { - rte.distance = - rte.distance + getDistance(rData.points[i - 1], rData.points[i]) - } - } - return rte -} - -const buildWaypoint = (rData: any): any => { - const wpt: any = { - position: { - latitude: 0, - longitude: 0 - }, - feature: { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [] - }, - properties: {} - } - } - if (typeof rData.name !== 'undefined') { - wpt.feature.properties.name = rData.name - } - if (typeof rData.description !== 'undefined') { - wpt.feature.properties.description = rData.description - } - if (typeof rData.attributes !== 'undefined') { - Object.assign(wpt.feature.properties, rData.attributes) - } - - if (typeof rData.position === 'undefined') { - return null - } - if (!isValidCoordinate(rData.position)) { - return null - } - - wpt.position = rData.position - wpt.feature.geometry.coordinates = [ - rData.position.longitude, - rData.position.latitude - ] - - return wpt -} - -const buildNote = (rData: any): any => { - const note: any = {} - if (typeof rData.title !== 'undefined') { - note.title = rData.title - note.feature.properties.title = rData.title - } - if (typeof rData.description !== 'undefined') { - note.description = rData.description - note.feature.properties.description = rData.description - } - if ( - typeof rData.position === 'undefined' && - typeof rData.region === 'undefined' && - typeof rData.geohash === 'undefined' - ) { - return null - } - - if (typeof rData.position !== 'undefined') { - if (!isValidCoordinate(rData.position)) { - return null - } - note.position = rData.position - } - if (typeof rData.region !== 'undefined') { - note.region = rData.region - } - if (typeof rData.geohash !== 'undefined') { - note.geohash = rData.geohash - } - if (typeof rData.url !== 'undefined') { - note.url = rData.url - } - if (typeof rData.mimeType !== 'undefined') { - note.mimeType = rData.mimeType - } - - return note -} - -const buildRegion = (rData: any): any => { - const reg: any = { - feature: { - type: 'Feature', - geometry: { - type: 'Polygon', - coordinates: [] - }, - properties: {} - } - } - let coords: Array<[number, number]> = [] - - if (typeof rData.name !== 'undefined') { - reg.feature.properties.name = rData.name - } - if (typeof rData.description !== 'undefined') { - reg.feature.properties.description = rData.description - } - if (typeof rData.attributes !== 'undefined') { - Object.assign(reg.feature.properties, rData.attributes) - } - - if (typeof rData.points === 'undefined' && rData.geohash === 'undefined') { - return null - } - if (typeof rData.geohash !== 'undefined') { - reg.geohash = rData.geohash - - const bounds = ngeohash.decode_bbox(rData.geohash) - coords = [ - [bounds[1], bounds[0]], - [bounds[3], bounds[0]], - [bounds[3], bounds[2]], - [bounds[1], bounds[2]], - [bounds[1], bounds[0]] - ] - reg.feature.geometry.coordinates.push(coords) - } - if (typeof rData.points !== 'undefined' && coords.length === 0) { - if (!Array.isArray(rData.points)) { - return null - } - let isValid: boolean = true - rData.points.forEach((p: any) => { - if (!isValidCoordinate(p)) { - isValid = false - } - }) - if (!isValid) { - return null - } -<<<<<<< HEAD -<<<<<<< HEAD - - return reg ->>>>>>> add API endpoint processing -======= -======= - if ( - rData.points[0].latitude !== - rData.points[rData.points.length - 1].latitude && - rData.points[0].longitude !== - rData.points[rData.points.length - 1].longitude - ) { - rData.points.push(rData.points[0]) - } ->>>>>>> chore: return value descriptions to show a Promise - coords = rData.points.map((p: any) => { - return [p.longitude, p.latitude] - }) - reg.feature.geometry.coordinates.push(coords) - } - - return reg ->>>>>>> chore: linted } const buildChart = (rData: any): any => { diff --git a/src/api/resources/validate.ts b/src/api/resources/validate.ts index a156d011d..305c885be 100644 --- a/src/api/resources/validate.ts +++ b/src/api/resources/validate.ts @@ -1,6 +1,3 @@ -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD import geoJSON from 'geojson-validation' import { isValidCoordinate } from 'geolib' import { Chart, Note, Region, Route, Waypoint } from '../resources/types' @@ -26,7 +23,6 @@ export const validate = { case 'charts': return validateChart(value) break -<<<<<<< HEAD default: return true } @@ -145,159 +141,3 @@ const validateChart = (r: Chart): boolean => { return true } -======= -//import { GeoHash, GeoBounds } from './geo'; -======= ->>>>>>> add API endpoint processing -import geoJSON from 'geojson-validation'; -======= -import geoJSON from 'geojson-validation' ->>>>>>> chore: linted -import { isValidCoordinate } from 'geolib' - -export const validate = { - resource: (type: string, value: any): boolean => { - if (!type) { - return false - } - switch (type) { - case 'routes': - return validateRoute(value) - break - case 'waypoints': - return validateWaypoint(value) - break - case 'notes': - return validateNote(value) - break - case 'regions': - return validateRegion(value) - break -======= ->>>>>>> add chartId test & require alignment with spec. - default: - return true - } - }, - - // returns true if id is a valid Signal K UUID - uuid: (id: string): boolean => { - const uuid = RegExp( - '^urn:mrn:signalk:uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$' - ) - return uuid.test(id) - }, - - // returns true if id is a valid Signal K Chart resource id - chartId: (id: string): boolean => { - const uuid = RegExp('(^[A-Za-z0-9_-]{8,}$)') - return uuid.test(id) - } -} - -const validateRoute = (r: any): boolean => { - if (r.start) { - const l = r.start.split('/') - if (!validate.uuid(l[l.length - 1])) { - return false - } - } - if (r.end) { - const l = r.end.split('/') - if (!validate.uuid(l[l.length - 1])) { - return false - } - } - try { - if (!r.feature || !geoJSON.valid(r.feature)) { - return false - } - if (r.feature.geometry.type !== 'LineString') { - return false - } - } catch (err) { - return false - } - return true -} - -const validateWaypoint = (r: any): boolean => { - if (typeof r.position === 'undefined') { - return false - } - if (!isValidCoordinate(r.position)) { - return false - } - try { - if (!r.feature || !geoJSON.valid(r.feature)) { - return false - } - if (r.feature.geometry.type !== 'Point') { - return false - } - } catch (e) { - return false - } - return true -} - -// validate note data -const validateNote = (r: any): boolean => { - if (!r.region && !r.position && !r.geohash) { - return false - } - if (typeof r.position !== 'undefined') { - if (!isValidCoordinate(r.position)) { - return false - } - } - if (r.region) { - const l = r.region.split('/') - if (!validate.uuid(l[l.length - 1])) { - return false - } - } - return true -} - -const validateRegion = (r: any): boolean => { - if (!r.geohash && !r.feature) { - return false - } - if (r.feature) { - try { - if (!geoJSON.valid(r.feature)) { - return false - } - if ( - r.feature.geometry.type !== 'Polygon' && - r.feature.geometry.type !== 'MultiPolygon' - ) { - return false - } - } catch (e) { - return false - } - } - return true -} -<<<<<<< HEAD -<<<<<<< HEAD - ->>>>>>> Add Signal K standard resource path handling -======= ->>>>>>> chore: linted -======= - -const validateChart = (r: any): boolean => { - if (!r.name || !r.identifier || !r.chartFormat) { - return false - } - - if (!r.tilemapUrl && !r.chartUrl) { - return false - } - - return true -} ->>>>>>> add chartId test & require alignment with spec. From 1576d557fa49f6ff987c710675b2963fc7bc878c Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Thu, 27 Jan 2022 14:37:28 +1030 Subject: [PATCH 405/410] fix debug post v1.41 --- src/api/course/index.ts | 2 +- src/api/resources/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index f69491c64..475582653 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,4 +1,4 @@ -import { createDebug } from './debug' +import { createDebug } from '../../debug' const debug = createDebug('signalk:courseApi') import { Application, Request, Response } from 'express' diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index ced1ad08d..80f15e95e 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -1,4 +1,4 @@ -import { createDebug } from './debug' +import { createDebug } from '../../debug' const debug = createDebug('signalk:resourcesApi') import { From 709ec3e9fe482816974d46dc7b9af8adbd81d3c4 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Thu, 27 Jan 2022 14:39:02 +1030 Subject: [PATCH 406/410] add shouldAllowPut() to Security Strategy --- src/security.ts | 7 +++++++ src/types.ts | 20 -------------------- 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/src/security.ts b/src/security.ts index ccffd8692..b75c87446 100644 --- a/src/security.ts +++ b/src/security.ts @@ -30,6 +30,7 @@ import { Mode } from 'stat-mode' import { WithConfig } from './app' import { createDebug } from './debug' import dummysecurity from './dummysecurity' +import { Request } from 'express' const debug = createDebug('signalk-server:security') export interface WithSecurityStrategy { @@ -44,6 +45,12 @@ export interface SecurityStrategy { configFromArguments: boolean securityConfig: any requestAccess: (config: any, request: any, ip: any, updateCb: any) => any + shouldAllowPut: ( + req: Request, + context: string, + source: any, + path: string + ) => boolean } export class InvalidTokenError extends Error { diff --git a/src/types.ts b/src/types.ts index 459343550..b16cd6ba7 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,9 +1,5 @@ import { FullSignalK } from '@signalk/signalk-schema' -<<<<<<< HEAD import { SecurityStrategy } from './security' -======= -import { Request } from 'express' ->>>>>>> fix: SecurityStrategy.shouldAllowPut must have Request import SubscriptionManager from './subscriptionmanager' export interface HelloMessage { @@ -14,22 +10,6 @@ export interface HelloMessage { timestamp: Date } -<<<<<<< HEAD -======= -export interface SecurityStrategy { - isDummy: () => boolean - allowReadOnly: () => boolean - shouldFilterDeltas: () => boolean - filterReadDelta: (user: any, delta: any) => any - shouldAllowPut: ( - req: Request, - context: string, - source: any, - path: string - ) => boolean -} - ->>>>>>> refactor: tweak types export interface Bus { onValue: (callback: (value: any) => any) => () => void push: (v: any) => void From 230e68ffd60e1d457da133984fc1ef7cba7bf2ea Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Thu, 27 Jan 2022 14:39:30 +1030 Subject: [PATCH 407/410] fix SecurityStrategy import path --- src/app.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app.ts b/src/app.ts index f00e821f3..d00a80c23 100644 --- a/src/app.ts +++ b/src/app.ts @@ -2,7 +2,7 @@ import { FullSignalK } from '@signalk/signalk-schema' import { EventEmitter } from 'events' import { Config } from './config/config' import DeltaCache from './deltacache' -import { SecurityStrategy } from './types' +import { SecurityStrategy } from './security' export interface ServerApp { started: boolean From aca6dfb876c267009fbca81b87a69dfc880e9a84 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Thu, 27 Jan 2022 14:40:29 +1030 Subject: [PATCH 408/410] add any return type to app.emit to allow compilation --- src/index.ts | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/index.ts b/src/index.ts index fda6204aa..e932ac21e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -257,14 +257,9 @@ class Server { const self = this const app = this.app -<<<<<<< HEAD const eventDebugs: { [key: string]: Debugger } = {} const expressAppEmit = app.emit.bind(app) -======= - const eventDebugs: { [key: string]: Debug.Debugger } = {} - const emit = app.emit ->>>>>>> refactor: tweak types - app.emit = (eventName: string, ...args: any[]) => { + app.emit = (eventName: string, ...args: any[]): any => { if (eventName !== 'serverlog') { let eventDebug = eventDebugs[eventName] if (!eventDebug) { @@ -273,17 +268,10 @@ class Server { ) } if (eventDebug.enabled) { -<<<<<<< HEAD eventDebug(args) } } expressAppEmit(eventName, ...args) -======= - eventDebug([...args].slice(1)) - } - } - return emit.apply(app, [eventName, ...args]) ->>>>>>> refactor: tweak types } this.app.intervals = [] From d851a9fe84c36b2a56cf0f387fa358e6b9482af1 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Thu, 27 Jan 2022 14:58:56 +1030 Subject: [PATCH 409/410] chore: lint streams --- packages/streams/nmea0183-signalk.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/streams/nmea0183-signalk.js b/packages/streams/nmea0183-signalk.js index 1afcc5db8..531f4945a 100644 --- a/packages/streams/nmea0183-signalk.js +++ b/packages/streams/nmea0183-signalk.js @@ -39,8 +39,9 @@ function Nmea0183ToSignalK(options) { Transform.call(this, { objectMode: true, }) - this.debug = (options.createDebug || require('debug'))('signalk:streams:nmea0183-signalk') - + this.debug = (options.createDebug || require('debug'))( + 'signalk:streams:nmea0183-signalk' + ) this.parser = new Parser(options) this.n2kParser = new FromPgn(options) From f447545dc0489c920d37fcd513f1cb99e6570c72 Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Thu, 27 Jan 2022 14:59:51 +1030 Subject: [PATCH 410/410] chore: lint courseApi --- src/api/course/index.ts | 2 +- src/api/index.ts | 72 ++++++++++++++++++++--------------------- src/put.js | 3 +- src/security.ts | 2 +- src/types.ts | 2 +- 5 files changed, 40 insertions(+), 41 deletions(-) diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 475582653..3d6d570c1 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -7,9 +7,9 @@ import path from 'path' import { WithConfig, WithSecurityStrategy, WithSignalK } from '../../app' import { Position } from '../../types' +import { Responses } from '../' import { Store } from '../../serverstate/store' import { Route } from '../resources/types' -import { Responses } from '../' const SIGNALK_API_PATH = `/signalk/v1/api` const COURSE_API_PATH = `${SIGNALK_API_PATH}/vessels/self/navigation/course` diff --git a/src/api/index.ts b/src/api/index.ts index 02232b2cc..584c3645a 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,43 +1,43 @@ export interface ApiResponse { - state: 'FAILED' | 'COMPLETED' | 'PENDING' - statusCode: number - message: string - requestId?: string - href?: string - token?: string + state: 'FAILED' | 'COMPLETED' | 'PENDING' + statusCode: number + message: string + requestId?: string + href?: string + token?: string } export const Responses = { - ok: { - state: 'COMPLETED', - statusCode: 200, - message: 'OK' - }, - invalid: { - state: 'FAILED', - statusCode: 406, - message: `Invalid Data supplied.` - }, - unauthorised: { - state: 'FAILED', - statusCode: 403, - message: 'Unauthorised' - }, - notFound: { - state: 'FAILED', - statusCode: 404, - message: 'Resource not found.' - } + ok: { + state: 'COMPLETED', + statusCode: 200, + message: 'OK' + }, + invalid: { + state: 'FAILED', + statusCode: 406, + message: `Invalid Data supplied.` + }, + unauthorised: { + state: 'FAILED', + statusCode: 403, + message: 'Unauthorised' + }, + notFound: { + state: 'FAILED', + statusCode: 404, + message: 'Resource not found.' + } } // returns true if target path is an API request -export function isApiRequest(path:string): boolean { - if ( - path.split('/')[4] === 'resources' || // resources API - path.indexOf('/navigation/course/') !== -1 // course API - ) { - return true - } else { - return false - } -} \ No newline at end of file +export function isApiRequest(path: string): boolean { + if ( + path.split('/')[4] === 'resources' || // resources API + path.indexOf('/navigation/course/') !== -1 // course API + ) { + return true + } else { + return false + } +} diff --git a/src/put.js b/src/put.js index 335914546..ce1bd8af1 100644 --- a/src/put.js +++ b/src/put.js @@ -5,7 +5,7 @@ const { v4: uuidv4 } = require('uuid') const { createRequest, updateRequest } = require('./requestResponse') const skConfig = require('./config/config') -const {isApiRequest} = require('./api') +const { isApiRequest } = require('./api') const pathPrefix = '/signalk' const versionPrefix = '/v1' @@ -33,7 +33,6 @@ module.exports = { app.deRegisterActionHandler = deRegisterActionHandler app.put(apiPathPrefix + '*', function(req, res, next) { - // check for resources API, course API, etc request if (isApiRequest(req.path)) { next() diff --git a/src/security.ts b/src/security.ts index b75c87446..75a60fbd8 100644 --- a/src/security.ts +++ b/src/security.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { Request } from 'express' import { chmodSync, existsSync, @@ -30,7 +31,6 @@ import { Mode } from 'stat-mode' import { WithConfig } from './app' import { createDebug } from './debug' import dummysecurity from './dummysecurity' -import { Request } from 'express' const debug = createDebug('signalk-server:security') export interface WithSecurityStrategy { diff --git a/src/types.ts b/src/types.ts index 81407b7cb..19e0a30f5 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,6 @@ import { FullSignalK } from '@signalk/signalk-schema' -import { SecurityStrategy } from './security' import { Request } from 'express' +import { SecurityStrategy } from './security' import SubscriptionManager from './subscriptionmanager' export interface HelloMessage {