Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[superceded by #1361] Add Resources path handling for those detailed in Signal K spec #1352

Closed
wants to merge 99 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
9de117b
Add Signal K standard resource path handling
panaaj Nov 7, 2021
73c1004
add OpenApi definition file
panaaj Nov 7, 2021
9612002
chore: fix lint errors
panaaj Nov 7, 2021
18e1877
addressed comments re parameters
panaaj Nov 9, 2021
85c7adb
add API definitions
panaaj Nov 9, 2021
c4eaec3
add geohash library
panaaj Nov 9, 2021
5e54a91
add API endpoint processing
panaaj Nov 9, 2021
447ac55
align with openapi definitions
panaaj Nov 9, 2021
6450ae3
Added Resource_Provider documentation
panaaj Nov 11, 2021
91f2e24
Add register / unregister
panaaj Nov 14, 2021
b8eefce
add constructor
panaaj Nov 14, 2021
aa9c223
add getResource function
panaaj Nov 14, 2021
f4429c0
chore: fix formatting
panaaj Nov 15, 2021
4624e4b
OpenApi descriptions
panaaj Nov 15, 2021
8c17182
add pluginId to register() function
panaaj Nov 20, 2021
a871517
add pluginId to unRegister function
panaaj Nov 20, 2021
0a54d2a
set plugin id as delta source
panaaj Nov 20, 2021
0a26d02
unregister only requires plugin id
panaaj Nov 20, 2021
0a2f5da
update docs
panaaj Nov 20, 2021
3ffa3b1
add resource attribute req to query object
panaaj Nov 21, 2021
008ca93
chore: update docs with query object examples.
panaaj Nov 21, 2021
19c3dff
chore: linted
panaaj Nov 21, 2021
11cd210
chore: return value descriptions to show a Promise
panaaj Nov 21, 2021
311582d
specify SignalKResourceType
panaaj Nov 21, 2021
176645b
move interfaces to server-api
panaaj Nov 21, 2021
1f10471
refactor: use Express types
tkurki Nov 21, 2021
d59c83c
cleanup express route handling
panaaj Nov 21, 2021
51d93f4
add ResourceProvider types to server-api
panaaj Nov 22, 2021
1b39f96
Merge branch 'resource-handler' of https://github.com/SignalK/signalk…
panaaj Nov 22, 2021
e5556d7
fix type
panaaj Nov 22, 2021
2fdb990
chore: lint
panaaj Nov 22, 2021
c8dceaf
chore: update return type
panaaj Nov 22, 2021
10323b2
Use Express routing params for processing requests
panaaj Nov 22, 2021
404db11
throw on error
panaaj Nov 23, 2021
a5f6d63
PUT/POST payloads directly in `body`..not `value:`
panaaj Nov 23, 2021
4d0c227
remove resourceId validity check on GET
panaaj Nov 24, 2021
63c35e7
chore: Updated documentation
panaaj Nov 24, 2021
e1c3a45
add chartId test & require alignment with spec.
panaaj Nov 24, 2021
69335f8
add charts API methods
panaaj Nov 24, 2021
8fa3820
allow registering custom resource types
panaaj Nov 25, 2021
6adb589
chore: update docs
panaaj Nov 25, 2021
345fbf4
chore: lint
panaaj Nov 25, 2021
4267f27
update types
panaaj Nov 27, 2021
6a10317
align response formatting forGET ./resources/
panaaj Nov 27, 2021
79e6f0f
Add Signal K standard resource path handling
panaaj Nov 7, 2021
8764399
add OpenApi definition file
panaaj Nov 7, 2021
4fea4c2
chore: fix lint errors
panaaj Nov 7, 2021
a497615
addressed comments re parameters
panaaj Nov 9, 2021
c29d462
add API definitions
panaaj Nov 9, 2021
317cd20
add geohash library
panaaj Nov 9, 2021
4761918
add API endpoint processing
panaaj Nov 9, 2021
68c8f2d
align with openapi definitions
panaaj Nov 9, 2021
699d0a8
Added Resource_Provider documentation
panaaj Nov 11, 2021
aacbc62
Add register / unregister
panaaj Nov 14, 2021
dff9219
add constructor
panaaj Nov 14, 2021
27fdd18
add getResource function
panaaj Nov 14, 2021
959b147
chore: fix formatting
panaaj Nov 15, 2021
ee7560d
OpenApi descriptions
panaaj Nov 15, 2021
c79327c
add pluginId to register() function
panaaj Nov 20, 2021
1c53058
add pluginId to unRegister function
panaaj Nov 20, 2021
cd94f5b
set plugin id as delta source
panaaj Nov 20, 2021
83b0181
unregister only requires plugin id
panaaj Nov 20, 2021
28e0454
update docs
panaaj Nov 20, 2021
4c640e1
add resource attribute req to query object
panaaj Nov 21, 2021
7577a83
chore: update docs with query object examples.
panaaj Nov 21, 2021
b0c29e0
chore: linted
panaaj Nov 21, 2021
446e0ba
chore: return value descriptions to show a Promise
panaaj Nov 21, 2021
3d70fd1
specify SignalKResourceType
panaaj Nov 21, 2021
8d2cb84
move interfaces to server-api
panaaj Nov 21, 2021
f14e974
cleanup express route handling
panaaj Nov 21, 2021
091e9c1
add ResourceProvider types to server-api
panaaj Nov 22, 2021
76fa928
refactor: use Express types
tkurki Nov 21, 2021
875e155
fix type
panaaj Nov 22, 2021
41f47ba
chore: lint
panaaj Nov 22, 2021
d746752
chore: update return type
panaaj Nov 22, 2021
73ee9a5
Use Express routing params for processing requests
panaaj Nov 22, 2021
b3d5922
throw on error
panaaj Nov 23, 2021
9b0bdda
PUT/POST payloads directly in `body`..not `value:`
panaaj Nov 23, 2021
559426f
remove resourceId validity check on GET
panaaj Nov 24, 2021
e5fab43
chore: Updated documentation
panaaj Nov 24, 2021
ac12f21
add chartId test & require alignment with spec.
panaaj Nov 24, 2021
39d49eb
add charts API methods
panaaj Nov 24, 2021
81907fe
allow registering custom resource types
panaaj Nov 25, 2021
cbb2bac
chore: update docs
panaaj Nov 25, 2021
115686c
chore: lint
panaaj Nov 25, 2021
f96e526
update types
panaaj Nov 27, 2021
5ac7eea
align response formatting forGET ./resources/
panaaj Nov 27, 2021
43176eb
add resourceApi to index.ts after rebase
panaaj Nov 30, 2021
8f2f7de
Merge branch 'resource-handler-rebased' into resource-handler
panaaj Nov 30, 2021
ba29964
add charts API methods
panaaj Dec 3, 2021
8545715
add securityStrategy test
panaaj Dec 5, 2021
fbade98
chore: update docs
panaaj Dec 5, 2021
bf62346
chore: update docs
panaaj Dec 6, 2021
7449207
fix chart identifier
panaaj Dec 6, 2021
0f209a3
chore: fix lint errors
panaaj Dec 7, 2021
5547682
refactor: tweak types
tkurki Dec 12, 2021
c442147
move provider.methods check to `register()`
panaaj Dec 12, 2021
f95a994
Use POST for paths that DO NOT specify an id
panaaj Dec 18, 2021
2d5aa1f
fix api request id validation
panaaj Dec 20, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/@types/geojson-validation.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare module 'geojson-validation'
265 changes: 265 additions & 0 deletions src/api/resources/index.ts
Original file line number Diff line number Diff line change
@@ -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<any>
getResource: (type:string, id:string)=> Promise<any>
setResource: (type:string, id:string, value:{[key:string]:any})=> Promise<any>
deleteResource: (type:string, id:string)=> Promise<any>
}

panaaj marked this conversation as resolved.
Show resolved Hide resolved
const SIGNALK_API_PATH= `/signalk/v1/api`
const UUID_PREFIX= 'urn:mrn:signalk:uuid:'

export class Resources {

// ** in-scope resource types **
private resourceTypes:Array<string>= [
'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) => {
panaaj marked this conversation as resolved.
Show resolved Hide resolved
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<any> {
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
}

}
Loading