From 2e8672481051b0033438d73ccf777549a8224cd8 Mon Sep 17 00:00:00 2001 From: Dennis Bauszus Date: Fri, 1 Nov 2024 11:06:11 +0000 Subject: [PATCH] layer query and templates (#1588) * layer query and templates * check whether layer template is instance of error * parse layer param as string * Parse error message as string on return * remove string parser * update layer queries * add test --------- Co-authored-by: Rob Hurst <43169158+RobAndrewHurst@users.noreply.github.com> Co-authored-by: Robert Hurst --- mod/query.js | 14 ++- mod/workspace/getLayer.js | 22 +++- mod/workspace/templates/_queries.js | 15 +++ mod/workspace/templates/cluster.js | 34 ++--- mod/workspace/templates/cluster_hex.js | 123 ++++++++++--------- mod/workspace/templates/geojson.js | 43 ++++--- mod/workspace/templates/get_last_location.js | 21 ++-- mod/workspace/templates/get_nnearest.js | 3 +- mod/workspace/templates/infotip.js | 9 +- mod/workspace/templates/layer_extent.js | 9 +- mod/workspace/templates/location_count.js | 13 +- mod/workspace/templates/location_delete.js | 9 +- mod/workspace/templates/location_get.js | 9 +- mod/workspace/templates/location_new.js | 9 +- mod/workspace/templates/location_update.js | 9 +- mod/workspace/templates/locations_delete.js | 9 +- mod/workspace/templates/mvt.js | 9 +- mod/workspace/templates/mvt_geom.js | 9 +- mod/workspace/templates/wkt.js | 7 ++ tests/mod/query.test.mjs | 9 ++ 20 files changed, 265 insertions(+), 120 deletions(-) diff --git a/mod/query.js b/mod/query.js index 7fbcb78092..e2451d29f4 100644 --- a/mod/query.js +++ b/mod/query.js @@ -82,7 +82,7 @@ module.exports = async function query(req, res) { req.params.SQL = []; // Assign role filter and viewport params from layer object. - await layerQuery(req, res) + await layerQuery(req, res, template) if (res.finished) return; @@ -106,6 +106,8 @@ module.exports = async function query(req, res) { @description Queries which reference a layer must be checked against the layer JSON in the workspace. +Layer query templates must have a layer request property. + Layer queries have restricted viewport and filter params. These params can not be substituted in the database but must be replaced in the SQL query string. Any query which references a layer and locale will be passed through the layer query method. The getLayer method will fail return an error if the locale is not defined as param or the layer is not a member of the locale. @@ -116,16 +118,24 @@ Any query which references a layer and locale will be passed through the layer q @param {req} req HTTP request. @param {res} res HTTP response. +@param {Object} template The query template. +@property {Boolean} template.layer A layer query template. @property {Object} req.params Request params. @property {Object} params.filter JSON filter which must be turned into a SQL filter string for substitution. @property {Array} params.SQL Substitute parameter for SQL query. @property {Object} [params.user] Requesting user. @property {Array} [user.roles] User roles. */ -async function layerQuery(req, res) { +async function layerQuery(req, res, template) { if (!req.params.layer) { + if (template.layer) { + + // Layer query templates must have a layer request property. + return res.status(400).send(`${req.params.template} query requires a valid layer request parameter.`) + } + // Reserved params will be deleted to prevent DDL injection. delete req.params.filter delete req.params.viewport diff --git a/mod/workspace/getLayer.js b/mod/workspace/getLayer.js index 4c56f3de14..5ff375ad69 100644 --- a/mod/workspace/getLayer.js +++ b/mod/workspace/getLayer.js @@ -5,6 +5,7 @@ The getLayer module exports the getLayer method which is required by the query a @requires /utils/roles @requires /workspace/mergeTemplates @requires /workspace/getLocale +@requires /workspace/getTemplate @module /workspace/getLayer */ @@ -15,6 +16,8 @@ const mergeTemplates = require('./mergeTemplates') const getLocale = require('./getLocale') +const getTemplate = require('./getTemplate') + /** @function getLayer @async @@ -22,6 +25,8 @@ const getLocale = require('./getLocale') @description The layer locale is requested from the getLocale module. +A layer template lookup will be attempted if a layer is not found in locale.layers. + The mergeTemplate module will be called to merge templates into the locale object and substitute SRC_* environment variables. A role check is performed to check whether the requesting user has access to the locale. @@ -45,11 +50,22 @@ module.exports = async function getLayer(params) { // getLocale will return err if role.check fails. if (locale instanceof Error) return locale + let layer; + if (!Object.hasOwn(locale.layers, params.layer)) { - return new Error('Unable to validate layer param.') - } - let layer = locale.layers[params.layer] + // A layer maybe defined as a template only. + layer = await getTemplate(params.layer) + + if (!layer || layer instanceof Error) { + + return new Error('Unable to validate layer param.') + } + + } else { + + layer = locale.layers[params.layer] + } // layer maybe null. if (!layer) return; diff --git a/mod/workspace/templates/_queries.js b/mod/workspace/templates/_queries.js index dccb060d3c..6e0359e9b1 100644 --- a/mod/workspace/templates/_queries.js +++ b/mod/workspace/templates/_queries.js @@ -7,6 +7,7 @@ module.exports = { template: require('./gaz_query'), }, get_last_location: { + layer: true, render: require('./get_last_location'), }, distinct_values: { @@ -31,24 +32,30 @@ module.exports = { render: require('./get_nnearest'), }, geojson: { + layer: true, render: require('./geojson'), }, cluster: { + layer: true, render: require('./cluster'), reduce: true }, cluster_hex: { + layer: true, render: require('./cluster_hex'), reduce: true }, wkt: { + layer: true, render: require('./wkt'), reduce: true }, infotip: { + layer: true, render: require('./infotip'), }, layer_extent: { + layer: true, template: require('./layer_extent'), }, st_intersects_ab: { @@ -64,30 +71,38 @@ module.exports = { template: require('./st_distance_ab_multiple'), }, location_get: { + layer: true, render: require('./location_get'), }, location_new: { + layer: true, render: require('./location_new'), value_only: true }, location_delete: { + layer: true, render: require('./location_delete'), }, locations_delete: { + layer: true, render: require('./locations_delete'), }, location_update: { + layer: true, render: require('./location_update'), }, location_count: { + layer: true, template: require('./location_count'), value_only: true }, mvt: { + layer: true, render: require('./mvt'), value_only: true }, mvt_geom: { + layer: true, render: require('./mvt_geom'), value_only: true } diff --git a/mod/workspace/templates/cluster.js b/mod/workspace/templates/cluster.js index c78682eca2..850abebbf7 100644 --- a/mod/workspace/templates/cluster.js +++ b/mod/workspace/templates/cluster.js @@ -1,23 +1,28 @@ -module.exports = _ => { +/** +### /workspace/templates/cluster + +The cluster layer query template returns aggregated cluster features. - _.qID ??= _.layer.qID || null - _.geom ??= _.layer.geom +@module /workspace/templates/cluster +*/ +module.exports = _ => { - // Get fields array from query params. - const fields = _.fields?.split(',') - .map(field => `${_.workspace.templates[field]?.template || field} as ${field}`) + _.qID ??= _.layer.qID || null + _.geom ??= _.layer.geom - const aggFields = _.fields?.split(',') - .map(field => `CASE WHEN count(*)::int = 1 THEN (array_agg(${field}))[1] END as ${field}`) + // Get fields array from query params. + const fields = _.fields?.split(',') + .map(field => `${_.workspace.templates[field]?.template || field} as ${field}`) - const where = _.viewport || `AND ${_.geom} IS NOT NULL` + const aggFields = _.fields?.split(',') + .map(field => `CASE WHEN count(*)::int = 1 THEN (array_agg(${field}))[1] END as ${field}`) - // Calculate grid resolution (r) based on zoom level and resolution parameter. - const r = parseInt(40075016.68 / Math.pow(2, _.z) * _.resolution); + const where = _.viewport || `AND ${_.geom} IS NOT NULL` - // ${params.cat && `${params.aggregate || 'array_agg'}(cat) cat,` || ''} + // Calculate grid resolution (r) based on zoom level and resolution parameter. + const r = parseInt(40075016.68 / Math.pow(2, _.z) * _.resolution); - return ` + return ` SELECT ARRAY[x_round, y_round], count(*)::int, @@ -38,5 +43,4 @@ module.exports = _ => { WHERE TRUE ${where} \${filter}) grid GROUP BY x_round, y_round;` - -} \ No newline at end of file +} diff --git a/mod/workspace/templates/cluster_hex.js b/mod/workspace/templates/cluster_hex.js index eb89853070..c02980a1fd 100644 --- a/mod/workspace/templates/cluster_hex.js +++ b/mod/workspace/templates/cluster_hex.js @@ -1,102 +1,105 @@ -module.exports = _ => { +/** +### /workspace/templates/cluster_hex - _.qID ??= _.layer.qID || null - _.geom ??= _.layer.geom +The cluster_hex layer query template returns aggregated cluster features. - // Get fields array from query params. - const fields = _.fields?.split(',') - .map(field => `${_.workspace.templates[field]?.template || field} as ${field}`) +@module /workspace/templates/cluster_hex +*/ +module.exports = _ => { - const aggFields = _.fields?.split(',') - .map(field => `CASE WHEN count(*)::int = 1 THEN (array_agg(${field}))[1] END as ${field}`) + _.qID ??= _.layer.qID || null + _.geom ??= _.layer.geom - const where = _.viewport || `AND ${_.geom} IS NOT NULL` + // Get fields array from query params. + const fields = _.fields?.split(',') + .map(field => `${_.workspace.templates[field]?.template || field} as ${field}`) - // Calculate grid resolution (r) based on zoom level and resolution parameter. - const r = parseInt(40075016.68 / Math.pow(2, _.z) * _.resolution); + const aggFields = _.fields?.split(',') + .map(field => `CASE WHEN count(*)::int = 1 THEN (array_agg(${field}))[1] END as ${field}`) - // ${params.cat && `${params.aggregate || 'array_agg'}(cat) cat,` || ''} + const where = _.viewport || `AND ${_.geom} IS NOT NULL` - const _width = r; - const _height = r - ((r * 2 / Math.sqrt(3)) - r) / 2; + // Calculate grid resolution (r) based on zoom level and resolution parameter. + const r = parseInt(40075016.68 / Math.pow(2, _.z) * _.resolution); - return ` + const _width = r; + const _height = r - ((r * 2 / Math.sqrt(3)) - r) / 2; - WITH first as ( + return ` + WITH first as ( SELECT - ${_.qID} AS id, - ${_.fields ? fields.join() + ',' : ''} - ${_.geom} AS geom, - ST_X(${_.geom}) AS x, - ST_Y(${_.geom}) AS y, - - ((ST_Y(${_.geom}) / ${_height})::integer % 2) odds, - - CASE WHEN ((ST_Y(${_.geom}) / ${_height})::integer % 2) = 0 THEN - ST_Point( - round(ST_X(${_.geom}) / ${_width}) * ${_width}, - round(ST_Y(${_.geom}) / ${_height}) * ${_height}) + ${_.qID} AS id, + ${_.fields ? fields.join() + ',' : ''} + ${_.geom} AS geom, + ST_X(${_.geom}) AS x, + ST_Y(${_.geom}) AS y, + + ((ST_Y(${_.geom}) / ${_height})::integer % 2) odds, + + CASE WHEN ((ST_Y(${_.geom}) / ${_height})::integer % 2) = 0 THEN + ST_Point( + round(ST_X(${_.geom}) / ${_width}) * ${_width}, + round(ST_Y(${_.geom}) / ${_height}) * ${_height}) - ELSE ST_Point( - round(ST_X(${_.geom}) / ${_width}) * ${_width} + ${_width / 2}, - round(ST_Y(${_.geom}) / ${_height}) * ${_height}) + ELSE ST_Point( + round(ST_X(${_.geom}) / ${_width}) * ${_width} + ${_width / 2}, + round(ST_Y(${_.geom}) / ${_height}) * ${_height}) - END p0 + END p0 FROM ${_.table} WHERE TRUE ${where} \${filter}) SELECT - ARRAY[ST_X(point), ST_Y(point)], - count(*)::int, - CASE - WHEN count(*)::int = 1 THEN (array_agg(id))[1]::varchar - ELSE CONCAT('!',(array_agg(id))[1]::varchar) - END AS id + ARRAY[ST_X(point), ST_Y(point)], + count(*)::int, + CASE + WHEN count(*)::int = 1 THEN (array_agg(id))[1]::varchar + ELSE CONCAT('!',(array_agg(id))[1]::varchar) + END AS id - ${_.fields ? ',' + aggFields.join() : ''} + ${_.fields ? ',' + aggFields.join() : ''} FROM ( SELECT - ${_.qID} as id, - ${_.fields ? fields.join() + ',' : ''} + ${_.qID} as id, + ${_.fields ? fields.join() + ',' : ''} - CASE WHEN odds = 0 THEN CASE + CASE WHEN odds = 0 THEN CASE - WHEN x < ST_X(p0) THEN CASE + WHEN x < ST_X(p0) THEN CASE - WHEN y < ST_Y(p0) THEN CASE - WHEN (geom <#> ST_Translate(p0, -${_width / 2}, -${_height})) < (geom <#> p0) - THEN ST_SnapToGrid(ST_Translate(p0, -${_width / 2}, -${_height}), 1) + WHEN y < ST_Y(p0) THEN CASE + WHEN (geom <#> ST_Translate(p0, -${_width / 2}, -${_height})) < (geom <#> p0) + THEN ST_SnapToGrid(ST_Translate(p0, -${_width / 2}, -${_height}), 1) ELSE ST_SnapToGrid(p0, 1) END - ELSE CASE - WHEN (geom <#> ST_Translate(p0, -${_width / 2}, ${_height})) < (geom <#> p0) - THEN ST_SnapToGrid(ST_Translate(p0, -${_width / 2}, ${_height}), 1) + ELSE CASE + WHEN (geom <#> ST_Translate(p0, -${_width / 2}, ${_height})) < (geom <#> p0) + THEN ST_SnapToGrid(ST_Translate(p0, -${_width / 2}, ${_height}), 1) ELSE ST_SnapToGrid(p0, 1) END END ELSE CASE - WHEN y < ST_Y(p0) THEN CASE WHEN (geom <#> ST_Translate(p0, ${_width / 2}, -${_height})) < (geom <#> p0) THEN ST_SnapToGrid(ST_Translate(p0, ${_width / 2}, -${_height}), 1) - ELSE ST_SnapToGrid(p0, 1) - END + ELSE ST_SnapToGrid(p0, 1) + END ELSE CASE WHEN (geom <#> ST_Translate(p0, ${_width / 2}, ${_height})) < (geom <#> p0) THEN ST_SnapToGrid(ST_Translate(p0, ${_width / 2}, ${_height}), 1) - ELSE ST_SnapToGrid(p0, 1) - END + ELSE ST_SnapToGrid(p0, 1) + END - END + END - END + END ELSE CASE WHEN x < (ST_X(p0) - ${_width / 2}) THEN CASE @@ -132,9 +135,7 @@ module.exports = _ => { END END as point - FROM first - ) AS grid - - GROUP BY point;` + FROM first) AS grid -} \ No newline at end of file + GROUP BY point;` +} diff --git a/mod/workspace/templates/geojson.js b/mod/workspace/templates/geojson.js index d9a9509faf..2508872d4d 100644 --- a/mod/workspace/templates/geojson.js +++ b/mod/workspace/templates/geojson.js @@ -1,23 +1,30 @@ +/** +### /workspace/templates/geojson + +The geojson layer query template returns an array of records including a geojson geometry. + +@module /workspace/templates/geojson +*/ module.exports = _ => { - let properties = ''; + let properties = ''; - if (_.fields) { - const propertyKeyValuePairs = _.fields?.split(',').map(field => { - const value = _.workspace.templates[field]?.template || field; - return `'${field}',${value}`; - }); - properties = ', json_build_object(' + propertyKeyValuePairs.join(', ') + ') as properties'; - } + if (_.fields) { + const propertyKeyValuePairs = _.fields?.split(',').map(field => { + const value = _.workspace.templates[field]?.template || field; + return `'${field}',${value}`; + }); + properties = ', json_build_object(' + propertyKeyValuePairs.join(', ') + ') as properties'; + } - const where = _.viewport || `AND ${_.geom || _.layer.geom} IS NOT NULL` + const where = _.viewport || `AND ${_.geom || _.layer.geom} IS NOT NULL` - return ` - SELECT - 'Feature' AS type, - \${qID} AS id, - ST_asGeoJson(${_.geom || _.layer.geom})::json AS geometry - ${properties} - FROM \${table} - WHERE TRUE ${where} \${filter};` -} \ No newline at end of file + return ` + SELECT + 'Feature' AS type, + \${qID} AS id, + ST_asGeoJson(${_.geom || _.layer.geom})::json AS geometry + ${properties} + FROM \${table} + WHERE TRUE ${where} \${filter};` +} diff --git a/mod/workspace/templates/get_last_location.js b/mod/workspace/templates/get_last_location.js index 9b2ba37813..66de9ff6f9 100644 --- a/mod/workspace/templates/get_last_location.js +++ b/mod/workspace/templates/get_last_location.js @@ -1,3 +1,10 @@ +/** +### /workspace/templates/get_last_location + +The get_last_location layer query template returns the last id from layer table in a descending order. + +@module /workspace/templates/get_last_location +*/ module.exports = _ => { const table = _.layer.table || Object.values(_.layer.tables).find(tab => !!tab); @@ -5,10 +12,10 @@ module.exports = _ => { const geom = _.layer.geom || Object.values(_.layer.geoms).find(tab => !!tab); return ` - SELECT - ${_.layer.qID} as id - FROM ${table} - WHERE ${geom} IS NOT NULL AND ${_.layer.qID} IS NOT NULL \${filter} - ORDER BY ${_.layer.qID} DESC - LIMIT 1` -} \ No newline at end of file + SELECT + ${_.layer.qID} as id + FROM ${table} + WHERE ${geom} IS NOT NULL AND ${_.layer.qID} IS NOT NULL \${filter} + ORDER BY ${_.layer.qID} DESC + LIMIT 1` +} diff --git a/mod/workspace/templates/get_nnearest.js b/mod/workspace/templates/get_nnearest.js index 01def2ef08..c651b617cb 100644 --- a/mod/workspace/templates/get_nnearest.js +++ b/mod/workspace/templates/get_nnearest.js @@ -1,9 +1,8 @@ module.exports = _ => ` - SELECT \${qID} AS ID, \${label} AS label, array[st_x(st_centroid(\${geom})), st_y(st_centroid(\${geom}))] AS coords FROM \${table} WHERE true \${filter} - ORDER BY ST_Point(%{x},%{y}) <#> \${geom} LIMIT ${parseInt(_.n) || 99};` \ No newline at end of file + ORDER BY ST_Point(%{x},%{y}) <#> \${geom} LIMIT ${parseInt(_.n) || 99};` diff --git a/mod/workspace/templates/infotip.js b/mod/workspace/templates/infotip.js index 2f8b4262a1..c63c160af1 100644 --- a/mod/workspace/templates/infotip.js +++ b/mod/workspace/templates/infotip.js @@ -1,3 +1,10 @@ +/** +### /workspace/templates/infotip + +The infotip layer query returns a field property value from the location nearest to the provided coordinate. + +@module /workspace/templates/infotip +*/ module.exports = _ => { if (!_.coords) return ` @@ -12,4 +19,4 @@ module.exports = _ => { FROM \${table} WHERE true \${filter} ORDER BY ST_Point(${coords[0]},${coords[1]}) <#> \${geom} LIMIT 1` -} \ No newline at end of file +} diff --git a/mod/workspace/templates/layer_extent.js b/mod/workspace/templates/layer_extent.js index 0546d2380a..9c3f49c2e7 100644 --- a/mod/workspace/templates/layer_extent.js +++ b/mod/workspace/templates/layer_extent.js @@ -1,3 +1,10 @@ +/** +### /workspace/templates/layer_extent + +The layer_extent layer query returns the bbox coordinates of feature [record] geometries which which pass the provided layer filter. + +@module /workspace/templates/layer_extent +*/ module.exports = ` SELECT Box2D( @@ -7,4 +14,4 @@ module.exports = ` \${proj}), \${srid})) FROM \${table} - WHERE true \${filter};` \ No newline at end of file + WHERE true \${filter};` diff --git a/mod/workspace/templates/location_count.js b/mod/workspace/templates/location_count.js index f00c773957..230b4395d9 100644 --- a/mod/workspace/templates/location_count.js +++ b/mod/workspace/templates/location_count.js @@ -1,4 +1,11 @@ +/** +### /workspace/templates/location_count + +The location_count layer query returns the count of table records which pass the provided layer filter and viewport. + +@module /workspace/templates/location_count +*/ module.exports = ` - SELECT count(*) as location_count - FROM \${table} - WHERE true \${filter} \${viewport}` + SELECT count(*) as location_count + FROM \${table} + WHERE true \${filter} \${viewport}` diff --git a/mod/workspace/templates/location_delete.js b/mod/workspace/templates/location_delete.js index 9a8ec3d679..2b5be58494 100644 --- a/mod/workspace/templates/location_delete.js +++ b/mod/workspace/templates/location_delete.js @@ -1,4 +1,11 @@ +/** +### /workspace/templates/location_delete + +The location_delete layer query removes a record from a layer table where the layer qID matches the provided id property. + +@module /workspace/templates/location_delete +*/ module.exports = _ => { return `DELETE FROM ${_.table} WHERE ${_.layer.qID} = %{id};` -} \ No newline at end of file +} diff --git a/mod/workspace/templates/location_get.js b/mod/workspace/templates/location_get.js index 431acf72d2..9b7091d255 100644 --- a/mod/workspace/templates/location_get.js +++ b/mod/workspace/templates/location_get.js @@ -1,3 +1,10 @@ +/** +### /workspace/templates/location_get + +The location_get layer query returns the field values from a location record in the layer table where the location qID matches the provided id param. + +@module /workspace/templates/location_get +*/ module.exports = _ => { // The SQL array may be populated by a default filter which is not required for this query template. @@ -18,4 +25,4 @@ module.exports = _ => { SELECT ${fields.join()} FROM ${_.table} WHERE ${_.layer.qID} = %{id}` -} \ No newline at end of file +} diff --git a/mod/workspace/templates/location_new.js b/mod/workspace/templates/location_new.js index 3e5c8606c9..4bdd482d17 100644 --- a/mod/workspace/templates/location_new.js +++ b/mod/workspace/templates/location_new.js @@ -1,3 +1,10 @@ +/** +### /workspace/templates/location_new + +The location_new layer query returns the serial id for a new location record inserted into the table property. + +@module /workspace/templates/location_new +*/ module.exports = _ => { // select array for insert statement @@ -36,4 +43,4 @@ module.exports = _ => { INSERT INTO ${_.table} (${fields.join(',')}) SELECT ${selects.join(',')} RETURNING ${_.layer.qID}::varchar AS id;` -} \ No newline at end of file +} diff --git a/mod/workspace/templates/location_update.js b/mod/workspace/templates/location_update.js index 5b56f4a1ba..67a0314334 100644 --- a/mod/workspace/templates/location_update.js +++ b/mod/workspace/templates/location_update.js @@ -1,3 +1,10 @@ +/** +### /workspace/templates/location_update + +The location_update layer query updates a record in the layer table identified where layer qID matches the provided id property. + +@module /workspace/templates/location_update +*/ module.exports = _ => { // The location ID must not be altered. @@ -69,4 +76,4 @@ module.exports = _ => { UPDATE ${_.table} SET ${fields.join()} WHERE ${_.layer.qID} = %{id};` -} \ No newline at end of file +} diff --git a/mod/workspace/templates/locations_delete.js b/mod/workspace/templates/locations_delete.js index d5700d97c6..a2f19fc261 100644 --- a/mod/workspace/templates/locations_delete.js +++ b/mod/workspace/templates/locations_delete.js @@ -1,3 +1,10 @@ +/** +### /workspace/templates/locations_delete + +The locations_delete layer query deletes multiple records in a layer table which pass the provided viewport and/or SQL filter. + +@module /workspace/templates/locations_delete +*/ module.exports = _ => { // If no layer parameter, return @@ -8,4 +15,4 @@ module.exports = _ => { return ` DELETE FROM ${_.table || _.layer.table} WHERE TRUE ${_.viewport || ''} \${filter};` -} \ No newline at end of file +} diff --git a/mod/workspace/templates/mvt.js b/mod/workspace/templates/mvt.js index cc116819c9..6d5f8dc1d3 100644 --- a/mod/workspace/templates/mvt.js +++ b/mod/workspace/templates/mvt.js @@ -1,3 +1,10 @@ +/** +### /workspace/templates/mvt + +The mvt layer query template returns a vector tile (st_asmvt) with mvt geometries and their associated field properties. + +@module /workspace/templates/mvt +*/ module.exports = _ => { // Get fields array from query params. @@ -37,4 +44,4 @@ module.exports = _ => { ) \${filter} ) tile` -} \ No newline at end of file +} diff --git a/mod/workspace/templates/mvt_geom.js b/mod/workspace/templates/mvt_geom.js index 0467aa3936..bf81a9ead8 100644 --- a/mod/workspace/templates/mvt_geom.js +++ b/mod/workspace/templates/mvt_geom.js @@ -1,3 +1,10 @@ +/** +### /workspace/templates/mvt_geom + +The mvt layer query template returns a vector tile (st_asmvt) with mvt geometries but without field properties. + +@module /workspace/templates/mvt_geom +*/ module.exports = _ => { const @@ -25,4 +32,4 @@ module.exports = _ => { ${_.geom} ) ) tile` -} \ No newline at end of file +} diff --git a/mod/workspace/templates/wkt.js b/mod/workspace/templates/wkt.js index d238510838..c86ccea6be 100644 --- a/mod/workspace/templates/wkt.js +++ b/mod/workspace/templates/wkt.js @@ -1,3 +1,10 @@ +/** +### /workspace/templates/wkt + +The wkt layer query template returns feature records from a layer table as an ordered array. + +@module /workspace/templates/wkt +*/ module.exports = _ => { // Get fields array from query params. diff --git a/tests/mod/query.test.mjs b/tests/mod/query.test.mjs index 81bd604612..f2b4b3f1fc 100644 --- a/tests/mod/query.test.mjs +++ b/tests/mod/query.test.mjs @@ -54,5 +54,14 @@ export async function queryTest() { const results = await mapp.utils.xhr(`/test/api/query?template=bogus_data_array`); codi.assertTrue(results instanceof Error, 'We should return an error for a bogus DBS connection'); }); + + /** + * @description Query: Testing a query with a bogus dbs on the template + * @function it + */ + await codi.it('Query: Testing a query with a bogus dbs on the template', async () => { + const results = await mapp.utils.xhr(`/test/api/query?template=cluster`); + codi.assertTrue(results instanceof Error, 'We should get an error because we didnt provide a layer param'); + }); }); } \ No newline at end of file