diff --git a/package-lock.json b/package-lock.json index 408b652..d8d6a3d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,7 @@ "color": "4.2.3", "commander": "12.1.0", "cors": "2.8.5", - "express": "4.19.2", + "express": "5.0.1", "handlebars": "4.7.8", "http-shutdown": "1.2.2", "morgan": "1.10.0", @@ -1721,17 +1721,44 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" }, "engines": { "node": ">= 0.6" } }, + "node_modules/accepts/node_modules/mime-db": { + "version": "1.53.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz", + "integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-types": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.0.tgz", + "integrity": "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==", + "dependencies": { + "mime-db": "^1.53.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.12.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", @@ -1924,9 +1951,9 @@ } }, "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-3.0.0.tgz", + "integrity": "sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==" }, "node_modules/array-ify": { "version": "1.0.0", @@ -2041,41 +2068,58 @@ } }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.0.2.tgz", + "integrity": "sha512-SNMk0OONlQ01uk8EPeiBvTW7W4ovpL5b1O3t1sjpPgfxOQ6BqQJ6XjxinDPR79Z6HdcD5zBBwr5ssiTlgdNztQ==", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", + "debug": "3.1.0", "destroy": "1.2.0", "http-errors": "2.0.0", - "iconv-lite": "0.4.24", + "iconv-lite": "0.5.2", "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" + "qs": "6.13.0", + "raw-body": "^3.0.0", + "type-is": "~1.6.18" }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">=18" } }, "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dependencies": { "ms": "2.0.0" } }, + "node_modules/body-parser/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/body-parser/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -2156,12 +2200,19 @@ } }, "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "license": "MIT", "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2492,9 +2543,9 @@ "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" }, "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", "dependencies": { "safe-buffer": "5.2.1" }, @@ -2565,17 +2616,21 @@ } }, "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "engines": { + "node": ">=6.6.0" + } }, "node_modules/cookiejar": { "version": "2.1.4", @@ -2742,6 +2797,23 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/define-properties": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", @@ -2867,9 +2939,9 @@ "dev": true }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "engines": { "node": ">= 0.8" } @@ -2976,6 +3048,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-module-lexer": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", @@ -3445,59 +3538,66 @@ } }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.0.1.tgz", + "integrity": "sha512-ORF7g6qGnD+YtUG9yx4DFoqCShNMmUKiXuT5oWMHiOvt/4WFbHC6yCwQMTSBMno7AqntNCAzzcnnjowRkTL9eQ==", "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.2", - "content-disposition": "0.5.4", + "accepts": "^2.0.0", + "body-parser": "^2.0.1", + "content-disposition": "^1.0.0", "content-type": "~1.0.4", - "cookie": "0.6.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", + "cookie": "0.7.1", + "cookie-signature": "^1.2.1", + "debug": "4.3.6", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", + "finalhandler": "^2.0.0", + "fresh": "2.0.0", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "^2.0.0", "methods": "~1.1.2", + "mime-types": "^3.0.0", "on-finished": "2.4.1", + "once": "1.4.0", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", + "router": "^2.0.0", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "^1.1.0", + "serve-static": "^2.1.0", "setprototypeof": "1.2.0", "statuses": "2.0.1", - "type-is": "~1.6.18", + "type-is": "^2.0.0", "utils-merge": "1.0.1", "vary": "~1.1.2" }, "engines": { - "node": ">= 0.10.0" + "node": ">= 18" } }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/express/node_modules/mime-db": { + "version": "1.53.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz", + "integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/mime-types": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.0.tgz", + "integrity": "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==", "dependencies": { - "ms": "2.0.0" + "mime-db": "^1.53.0" + }, + "engines": { + "node": ">= 0.6" } }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "node_modules/extend-shallow": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", @@ -3599,9 +3699,9 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.0.0.tgz", + "integrity": "sha512-MX6Zo2adDViYh+GcxxB1dpO43eypOGUOL12rLCOTMQv/DfIbpSJUy4oQIIZhVZkH9e+bZWKMon0XHFEju16tkQ==", "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", @@ -3623,6 +3723,14 @@ "ms": "2.0.0" } }, + "node_modules/finalhandler/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -3735,11 +3843,11 @@ } }, "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/fs-minipass": { @@ -3772,9 +3880,13 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/function.prototype.name": { "version": "1.1.5", @@ -3877,13 +3989,19 @@ } }, "node_modules/get-intrinsic": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", - "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "license": "MIT", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4146,11 +4264,12 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", "dependencies": { - "get-intrinsic": "^1.1.1" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4197,6 +4316,18 @@ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -4295,9 +4426,9 @@ } }, "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz", + "integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -4632,6 +4763,11 @@ "node": ">=0.10.0" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" + }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -5305,11 +5441,11 @@ } }, "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/memorystream": { @@ -5382,9 +5518,15 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-stream": { "version": "2.0.0", @@ -5427,17 +5569,6 @@ "node": ">=8.6" } }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -5908,6 +6039,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "optional": true, "engines": { "node": ">= 0.6" } @@ -6318,9 +6450,13 @@ } }, "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -6543,9 +6679,12 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "engines": { + "node": ">=16" + } }, "node_modules/path-type": { "version": "4.0.0", @@ -6712,11 +6851,12 @@ } }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -6776,19 +6916,30 @@ } }, "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", - "iconv-lite": "0.4.24", + "iconv-lite": "0.6.3", "unpipe": "1.0.0" }, "engines": { "node": ">= 0.8" } }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -7086,6 +7237,23 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/router": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.0.0.tgz", + "integrity": "sha512-dIM5zVoG8xhC6rnSN8uoAgFARwTE7BQs8YwHEvK0VCmfxQXMaOuA1uiR1IPwsW7JyK5iTt7Od/TC9StasS2NPQ==", + "dependencies": { + "array-flatten": "3.0.0", + "is-promise": "4.0.0", + "methods": "~1.1.2", + "parseurl": "~1.3.3", + "path-to-regexp": "^8.0.0", + "setprototypeof": "1.2.0", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -7186,41 +7354,35 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.1.0.tgz", + "integrity": "sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==", "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" + "debug": "^4.3.5", + "destroy": "^1.2.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^0.5.2", + "http-errors": "^2.0.0", + "mime-types": "^2.1.35", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 18" } }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" + "node_modules/send/node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" } }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -7236,17 +7398,17 @@ } }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.1.0.tgz", + "integrity": "sha512-A3We5UfEjG8Z7VkDv6uItWw6HY2bBSBJT1KtVESn6EOoOr2jAxNhxWCLY3jDE2WcuHXByWju74ck3ZgLwL8xmA==", "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.0.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 18" } }, "node_modules/set-blocking": { @@ -7254,6 +7416,23 @@ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", @@ -7406,13 +7585,18 @@ "dev": true }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8110,12 +8294,32 @@ } }, "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.0.tgz", + "integrity": "sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw==", "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-db": { + "version": "1.53.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz", + "integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-types": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.0.tgz", + "integrity": "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==", + "dependencies": { + "mime-db": "^1.53.0" }, "engines": { "node": ">= 0.6" diff --git a/package.json b/package.json index 14f94ae..0c8dcf3 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "color": "4.2.3", "commander": "12.1.0", "cors": "2.8.5", - "express": "4.19.2", + "express": "5.0.1", "handlebars": "4.7.8", "http-shutdown": "1.2.2", "morgan": "1.10.0", diff --git a/src/serve_data.js b/src/serve_data.js index 1936da6..fd45328 100644 --- a/src/serve_data.js +++ b/src/serve_data.js @@ -24,149 +24,109 @@ export const serve_data = { init: (options, repo) => { const app = express().disable('x-powered-by'); - app.get( - '/:id/:z(\\d+)/:x(\\d+)/:y(\\d+).:format([\\w.]+)', - async (req, res, next) => { - const item = repo[req.params.id]; - if (!item) { - return res.sendStatus(404); - } - const tileJSONFormat = item.tileJSON.format; - const z = req.params.z | 0; - const x = req.params.x | 0; - const y = req.params.y | 0; - let format = req.params.format; - if (format === options.pbfAlias) { - format = 'pbf'; - } - if ( - format !== tileJSONFormat && - !(format === 'geojson' && tileJSONFormat === 'pbf') - ) { - return res.status(404).send('Invalid format'); - } - if ( - z < item.tileJSON.minzoom || - 0 || - x < 0 || - y < 0 || - z > item.tileJSON.maxzoom || - x >= Math.pow(2, z) || - y >= Math.pow(2, z) - ) { - return res.status(404).send('Out of bounds'); - } - if (item.sourceType === 'pmtiles') { - let tileinfo = await getPMtilesTile(item.source, z, x, y); - if (tileinfo == undefined || tileinfo.data == undefined) { - return res.status(404).send('Not found'); - } else { - let data = tileinfo.data; - let headers = tileinfo.header; - if (tileJSONFormat === 'pbf') { - if (options.dataDecoratorFunc) { - data = options.dataDecoratorFunc(id, 'data', data, z, x, y); - } + app.get('/:id/:z/:x/:y.:format', async (req, res) => { + const item = repo[req.params.id]; + if (!item) { + return res.sendStatus(404); + } + const tileJSONFormat = item.tileJSON.format; + const z = req.params.z | 0; + const x = req.params.x | 0; + const y = req.params.y | 0; + let format = req.params.format; + if (format === options.pbfAlias) { + format = 'pbf'; + } + if ( + format !== tileJSONFormat && + !(format === 'geojson' && tileJSONFormat === 'pbf') + ) { + return res.status(404).send('Invalid format'); + } + if ( + z < item.tileJSON.minzoom || + 0 || + x < 0 || + y < 0 || + z > item.tileJSON.maxzoom || + x >= Math.pow(2, z) || + y >= Math.pow(2, z) + ) { + return res.status(404).send('Out of bounds'); + } + if (item.sourceType === 'pmtiles') { + let tileinfo = await getPMtilesTile(item.source, z, x, y); + if (tileinfo == undefined || tileinfo.data == undefined) { + return res.status(404).send('Not found'); + } else { + let data = tileinfo.data; + let headers = tileinfo.header; + if (tileJSONFormat === 'pbf') { + if (options.dataDecoratorFunc) { + data = options.dataDecoratorFunc(id, 'data', data, z, x, y); } - if (format === 'pbf') { - headers['Content-Type'] = 'application/x-protobuf'; - } else if (format === 'geojson') { - headers['Content-Type'] = 'application/json'; - const tile = new VectorTile(new Pbf(data)); - const geojson = { - type: 'FeatureCollection', - features: [], - }; - for (const layerName in tile.layers) { - const layer = tile.layers[layerName]; - for (let i = 0; i < layer.length; i++) { - const feature = layer.feature(i); - const featureGeoJSON = feature.toGeoJSON(x, y, z); - featureGeoJSON.properties.layer = layerName; - geojson.features.push(featureGeoJSON); - } - } - data = JSON.stringify(geojson); - } - delete headers['ETag']; // do not trust the tile ETag -- regenerate - headers['Content-Encoding'] = 'gzip'; - res.set(headers); - - data = await gzipP(data); - - return res.status(200).send(data); } - } else if (item.sourceType === 'mbtiles') { - item.source.getTile(z, x, y, async (err, data, headers) => { - let isGzipped; - if (err) { - if (/does not exist/.test(err.message)) { - return res.status(204).send(); - } else { - return res - .status(500) - .header('Content-Type', 'text/plain') - .send(err.message); + if (format === 'pbf') { + headers['Content-Type'] = 'application/x-protobuf'; + } else if (format === 'geojson') { + headers['Content-Type'] = 'application/json'; + const tile = new VectorTile(new Pbf(data)); + const geojson = { + type: 'FeatureCollection', + features: [], + }; + for (const layerName in tile.layers) { + const layer = tile.layers[layerName]; + for (let i = 0; i < layer.length; i++) { + const feature = layer.feature(i); + const featureGeoJSON = feature.toGeoJSON(x, y, z); + featureGeoJSON.properties.layer = layerName; + geojson.features.push(featureGeoJSON); } - } else { - if (data == null) { - return res.status(404).send('Not found'); - } else { - if (tileJSONFormat === 'pbf') { - isGzipped = - data.slice(0, 2).indexOf(Buffer.from([0x1f, 0x8b])) === 0; - if (options.dataDecoratorFunc) { - if (isGzipped) { - data = await gunzipP(data); - isGzipped = false; - } - data = options.dataDecoratorFunc(id, 'data', data, z, x, y); - } - } - if (format === 'pbf') { - headers['Content-Type'] = 'application/x-protobuf'; - } else if (format === 'geojson') { - headers['Content-Type'] = 'application/json'; + } + data = JSON.stringify(geojson); + } + delete headers['ETag']; // do not trust the tile ETag -- regenerate + headers['Content-Encoding'] = 'gzip'; + res.set(headers); + data = await gzipP(data); + + return res.status(200).send(data); + } + } else if (item.sourceType === 'mbtiles') { + item.source.getTile(z, x, y, async (err, data, headers) => { + let isGzipped; + if (err) { + if (/does not exist/.test(err.message)) { + return res.status(204).send(); + } else { + return res + .status(500) + .header('Content-Type', 'text/plain') + .send(err.message); + } + } else { + if (data == null) { + return res.status(404).send('Not found'); + } else { + if (tileJSONFormat === 'pbf') { + isGzipped = + data.slice(0, 2).indexOf(Buffer.from([0x1f, 0x8b])) === 0; + if (options.dataDecoratorFunc) { if (isGzipped) { data = await gunzipP(data); isGzipped = false; } - - const tile = new VectorTile(new Pbf(data)); - const geojson = { - type: 'FeatureCollection', - features: [], - }; - for (const layerName in tile.layers) { - const layer = tile.layers[layerName]; - for (let i = 0; i < layer.length; i++) { - const feature = layer.feature(i); - const featureGeoJSON = feature.toGeoJSON(x, y, z); - featureGeoJSON.properties.layer = layerName; - geojson.features.push(featureGeoJSON); - } - } - data = JSON.stringify(geojson); + data = options.dataDecoratorFunc(id, 'data', data, z, x, y); } - delete headers['ETag']; // do not trust the tile ETag -- regenerate - headers['Content-Encoding'] = 'gzip'; - res.set(headers); - - if (!isGzipped) { - data = await gzipP(data); - } - - return res.status(200).send(data); } - } - }); - } - }, - ); + if (format === 'pbf') { + headers['Content-Type'] = 'application/x-protobuf'; + } else if (format === 'geojson') { + headers['Content-Type'] = 'application/json'; - app.get( - '^/:id/elevation/:z([0-9]+)/:x([-.0-9]+)/:y([-.0-9]+)', + app.get('/:id/elevation/:z/:x/:y', async (req, res, next) => { try { const item = repo?.[req.params.id]; diff --git a/src/serve_font.js b/src/serve_font.js index 02f46dc..d753905 100644 --- a/src/serve_font.js +++ b/src/serve_font.js @@ -13,31 +13,29 @@ export const serve_font = async (options, allowedFonts) => { const existingFonts = {}; - app.get( - '/fonts/:fontstack/:range([\\d]+-[\\d]+).pbf', - async (req, res, next) => { - const fontstack = decodeURI(req.params.fontstack); - const range = req.params.range; + app.get('/fonts/:fontstack/:range.pbf', async (req, res) => { + const fontstack = decodeURI(req.params.fontstack); + const range = req.params.range; - try { - const concatenated = await getFontsPbf( - options.serveAllFonts ? null : allowedFonts, - fontPath, - fontstack, - range, - existingFonts, - ); + try { + const concatenated = await getFontsPbf( + options.serveAllFonts ? null : allowedFonts, + fontPath, + fontstack, + range, + existingFonts, + ); - res.header('Content-type', 'application/x-protobuf'); - res.header('Last-Modified', lastModified); - return res.send(concatenated); - } catch (err) { - res.status(400).header('Content-Type', 'text/plain').send(err); - } - }, - ); + res.header('Content-type', 'application/x-protobuf'); + res.header('Last-Modified', lastModified); + return res.send(concatenated); + } catch (err) { + console.error('Error serving font:', err); + return res.status(400).header('Content-Type', 'text/plain').send(err); + } + }); - app.get('/fonts.json', (req, res, next) => { + app.get('/fonts.json', (req, res) => { res.header('Content-type', 'application/json'); return res.send( Object.keys(options.serveAllFonts ? existingFonts : allowedFonts).sort(), diff --git a/src/serve_rendered.js b/src/serve_rendered.js index 3e5c94e..65246f1 100644 --- a/src/serve_rendered.js +++ b/src/serve_rendered.js @@ -44,13 +44,43 @@ import fsp from 'node:fs/promises'; import { existsP, gunzipP } from './promises.js'; import { openMbTilesWrapper } from './mbtiles_wrapper.js'; -const FLOAT_PATTERN = '[+-]?(?:\\d+|\\d+.?\\d+)'; +const FLOAT_PATTERN = '[+-]?(?:\\d+|\\d*\\.\\d+)'; + +const staticTypeRegex = new RegExp( + `^` + + `(?:` + + // Format 1: {lon},{lat},{zoom}[@{bearing}[,{pitch}]] + `(?${FLOAT_PATTERN}),(?${FLOAT_PATTERN}),(?${FLOAT_PATTERN})` + + `(?:@(?${FLOAT_PATTERN})(?:,(?${FLOAT_PATTERN}))?)?` + + `|` + + // Format 2: {minx},{miny},{maxx},{maxy} + `(?${FLOAT_PATTERN}),(?${FLOAT_PATTERN}),(?${FLOAT_PATTERN}),(?${FLOAT_PATTERN})` + + `|` + + // Format 3: auto + `(?auto)` + + `)` + + `$`, +); + const PATH_PATTERN = /^((fill|stroke|width)\:[^\|]+\|)*(enc:.+|-?\d+(\.\d*)?,-?\d+(\.\d*)?(\|-?\d+(\.\d*)?,-?\d+(\.\d*)?)+)/; const httpTester = /^https?:\/\//i; const mercator = new SphericalMercator(); -const getScale = (scale) => (scale || '@1x').slice(1, 2) | 0; + +const parseScale = (scale, maxScaleDigit = 9) => { + if (scale === undefined) { + return 1; + } + + // eslint-disable-next-line security/detect-non-literal-regexp + const regex = new RegExp(`^[2-${maxScaleDigit}]x$`); + if (!regex.test(scale)) { + return null; + } + + return parseInt(scale.slice(0, -1), 10); +}; mlgl.on('message', (e) => { if (e.severity === 'WARNING' || e.severity === 'ERROR') { @@ -555,307 +585,256 @@ let maxScaleFactor = 2; export const serve_rendered = { init: async (options, repo) => { maxScaleFactor = Math.min(Math.floor(options.maxScaleFactor || 3), 9); - let scalePattern = ''; - for (let i = 2; i <= maxScaleFactor; i++) { - scalePattern += i.toFixed(); - } - scalePattern = `@[${scalePattern}]x`; - const app = express().disable('x-powered-by'); app.get( - `/:id/(:tileSize(256|512)/)?:z(\\d+)/:x(\\d+)/:y(\\d+):scale(${scalePattern})?.:format([\\w]+)`, - (req, res, next) => { - const item = repo[req.params.id]; - if (!item) { - return res.sendStatus(404); - } + `/:id{/:tileSize}/:z/:x/:y{@:scale}{.:format}`, + async (req, res, next) => { + try { + console.log(req.params); + if ( + req.params.z === 'static' || + (req.params.tileSize && + req.params.tileSize != 256 && + req.params.tileSize != 512) + ) { + //workaroud for /:id/static{/:raw}{/:type}/:width{x:height}{@:scale}{.:format} + next('route'); + } else { + const item = repo[req.params.id]; + if (!item) { + return res.sendStatus(404); + } - const modifiedSince = req.get('if-modified-since'); - const cc = req.get('cache-control'); - if (modifiedSince && (!cc || cc.indexOf('no-cache') === -1)) { - if (new Date(item.lastModified) <= new Date(modifiedSince)) { - return res.sendStatus(304); + const modifiedSince = req.get('if-modified-since'); + const cc = req.get('cache-control'); + if (modifiedSince && (!cc || cc.indexOf('no-cache') === -1)) { + if (new Date(item.lastModified) <= new Date(modifiedSince)) { + return res.sendStatus(304); + } + } + + const z = req.params.z | 0; + const x = req.params.x | 0; + const y = req.params.y | 0; + const scale = parseScale(req.params.scale, maxScaleFactor); + const format = req.params.format; + const tileSize = parseInt(req.params.tileSize, 10) || 256; + if ( + scale == null || + z < 0 || + x < 0 || + y < 0 || + z > 22 || + x >= Math.pow(2, z) || + y >= Math.pow(2, z) + ) { + return res.status(404).send('Out of bounds'); + } + + const tileCenter = mercator.ll( + [ + ((x + 0.5) / (1 << z)) * (256 << z), + ((y + 0.5) / (1 << z)) * (256 << z), + ], + z, + ); + + // prettier-ignore + return await respondImage( + options, item, z, tileCenter[0], tileCenter[1], 0, 0, tileSize, tileSize, scale, format, res, + ); } + } catch (e) { + console.log(e); + next('route'); } - - const z = req.params.z | 0; - const x = req.params.x | 0; - const y = req.params.y | 0; - const scale = getScale(req.params.scale); - const format = req.params.format; - const tileSize = parseInt(req.params.tileSize, 10) || 256; - - if ( - z < 0 || - x < 0 || - y < 0 || - z > 22 || - x >= Math.pow(2, z) || - y >= Math.pow(2, z) - ) { - return res.status(404).send('Out of bounds'); - } - - const tileCenter = mercator.ll( - [ - ((x + 0.5) / (1 << z)) * (256 << z), - ((y + 0.5) / (1 << z)) * (256 << z), - ], - z, - ); - - // prettier-ignore - return respondImage( - options, item, z, tileCenter[0], tileCenter[1], 0, 0, tileSize, tileSize, scale, format, res, - ); }, ); if (options.serveStaticMaps !== false) { - const staticPattern = `/:id/static/:raw(raw)?/%s/:width(\\d+)x:height(\\d+):scale(${scalePattern})?.:format([\\w]+)`; - - const centerPattern = util.format( - ':x(%s),:y(%s),:z(%s)(@:bearing(%s)(,:pitch(%s))?)?', - FLOAT_PATTERN, - FLOAT_PATTERN, - FLOAT_PATTERN, - FLOAT_PATTERN, - FLOAT_PATTERN, - ); - app.get( - util.format(staticPattern, centerPattern), + `/:id/static{/:raw}{/:type}/:width{x:height}{@:scale}{.:format}`, async (req, res, next) => { try { const item = repo[req.params.id]; - if (!item) { + console.log(req.params); + const format = req.params.format; + const w = parseInt(req.params.width) || 512; + const h = parseInt(req.params.height) || 512; + const scale = parseScale(req.params.scale, maxScaleFactor); + let raw = req.params.raw !== undefined; + let type = req.params.type; + if (!type) { + //workaround for type when raw is not set + type = req.params.raw; + raw = false; + } + + if (!item || !type || !format || !scale) { return res.sendStatus(404); } - const raw = req.params.raw; - const z = +req.params.z; - let x = +req.params.x; - let y = +req.params.y; - const bearing = +(req.params.bearing || '0'); - const pitch = +(req.params.pitch || '0'); - const w = req.params.width | 0; - const h = req.params.height | 0; - const scale = getScale(req.params.scale); - const format = req.params.format; - if (z < 0) { - return res.status(404).send('Invalid zoom'); - } + const staticTypeMatch = type.match(staticTypeRegex); + console.log(staticTypeMatch.groups); + if (staticTypeMatch.groups.lon) { + // Center Based Static Image + const z = staticTypeMatch.groups.zoom; + let x = staticTypeMatch.groups.lon; + let y = staticTypeMatch.groups.lat; + const bearing = staticTypeMatch.groups.bearing; + const pitch = staticTypeMatch.groups.pitch; - const transformer = raw - ? mercator.inverse.bind(mercator) - : item.dataProjWGStoInternalWGS; + if (z < 0) { + return res.status(404).send('Invalid zoom'); + } - if (transformer) { - const ll = transformer([x, y]); - x = ll[0]; - y = ll[1]; - } + const transformer = raw + ? mercator.inverse.bind(mercator) + : item.dataProjWGStoInternalWGS; - const paths = extractPathsFromQuery(req.query, transformer); - const markers = extractMarkersFromQuery( - req.query, - options, - transformer, - ); + if (transformer) { + const ll = transformer([x, y]); + x = ll[0]; + y = ll[1]; + } - // prettier-ignore - const overlay = await renderOverlay( - z, x, y, bearing, pitch, w, h, scale, paths, markers, req.query, - ); + const paths = extractPathsFromQuery(req.query, transformer); + const markers = extractMarkersFromQuery( + req.query, + options, + transformer, + ); - // prettier-ignore - return respondImage( - options, item, z, x, y, bearing, pitch, w, h, scale, format, res, overlay, 'static', - ); - } catch (e) { - next(e); - } - }, - ); + // prettier-ignore + const overlay = await renderOverlay( + z, x, y, bearing, pitch, w, h, scale, paths, markers, req.query, + ); - const serveBounds = async (req, res, next) => { - try { - const item = repo[req.params.id]; - if (!item) { - return res.sendStatus(404); - } - const raw = req.params.raw; - const bbox = [ - +req.params.minx, - +req.params.miny, - +req.params.maxx, - +req.params.maxy, - ]; - let center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2]; + // prettier-ignore + return await respondImage( + options, item, z, x, y, bearing, pitch, w, h, scale, format, res, overlay, 'static', + ); + } else if (staticTypeMatch.groups.minx) { + // Area Based Static Image + const bbox = [ + +staticTypeMatch.groups.minx, + +staticTypeMatch.groups.miny, + +staticTypeMatch.groups.maxx, + +staticTypeMatch.groups.maxx, + ]; + let center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2]; - const transformer = raw - ? mercator.inverse.bind(mercator) - : item.dataProjWGStoInternalWGS; + const transformer = raw + ? mercator.inverse.bind(mercator) + : item.dataProjWGStoInternalWGS; - if (transformer) { - const minCorner = transformer(bbox.slice(0, 2)); - const maxCorner = transformer(bbox.slice(2)); - bbox[0] = minCorner[0]; - bbox[1] = minCorner[1]; - bbox[2] = maxCorner[0]; - bbox[3] = maxCorner[1]; - center = transformer(center); - } + if (transformer) { + const minCorner = transformer(bbox.slice(0, 2)); + const maxCorner = transformer(bbox.slice(2)); + bbox[0] = minCorner[0]; + bbox[1] = minCorner[1]; + bbox[2] = maxCorner[0]; + bbox[3] = maxCorner[1]; + center = transformer(center); + } - const w = req.params.width | 0; - const h = req.params.height | 0; - const scale = getScale(req.params.scale); - const format = req.params.format; + const z = calcZForBBox(bbox, w, h, req.query); + const x = center[0]; + const y = center[1]; + const bearing = 0; + const pitch = 0; - const z = calcZForBBox(bbox, w, h, req.query); - const x = center[0]; - const y = center[1]; - const bearing = 0; - const pitch = 0; + const paths = extractPathsFromQuery(req.query, transformer); + const markers = extractMarkersFromQuery( + req.query, + options, + transformer, + ); - const paths = extractPathsFromQuery(req.query, transformer); - const markers = extractMarkersFromQuery( - req.query, - options, - transformer, - ); + // prettier-ignore + const overlay = await renderOverlay( + z, x, y, bearing, pitch, w, h, scale, paths, markers, req.query, + ); - // prettier-ignore - const overlay = await renderOverlay( - z, x, y, bearing, pitch, w, h, scale, paths, markers, req.query, - ); + // prettier-ignore + return await respondImage( + options, item, z, x, y, bearing, pitch, w, h, scale, format, res, overlay, 'static', + ); + } else if (staticTypeMatch.groups.auto) { + // Area Static Image + const bearing = 0; + const pitch = 0; - // prettier-ignore - return respondImage( - options, item, z, x, y, bearing, pitch, w, h, scale, format, res, overlay, 'static', - ); - } catch (e) { - next(e); - } - }; + const transformer = raw + ? mercator.inverse.bind(mercator) + : item.dataProjWGStoInternalWGS; - const boundsPattern = util.format( - ':minx(%s),:miny(%s),:maxx(%s),:maxy(%s)', - FLOAT_PATTERN, - FLOAT_PATTERN, - FLOAT_PATTERN, - FLOAT_PATTERN, - ); + const paths = extractPathsFromQuery(req.query, transformer); + const markers = extractMarkersFromQuery( + req.query, + options, + transformer, + ); - app.get(util.format(staticPattern, boundsPattern), serveBounds); + // Extract coordinates from markers + const markerCoordinates = []; + for (const marker of markers) { + markerCoordinates.push(marker.location); + } - app.get('/:id/static/', (req, res, next) => { - for (const key in req.query) { - req.query[key.toLowerCase()] = req.query[key]; - } - req.params.raw = true; - req.params.format = (req.query.format || 'image/png').split('/').pop(); - const bbox = (req.query.bbox || '').split(','); - req.params.minx = bbox[0]; - req.params.miny = bbox[1]; - req.params.maxx = bbox[2]; - req.params.maxy = bbox[3]; - req.params.width = req.query.width || '256'; - req.params.height = req.query.height || '256'; - if (req.query.scale) { - req.params.width /= req.query.scale; - req.params.height /= req.query.scale; - req.params.scale = `@${req.query.scale}`; - } + // Create array with coordinates from markers and path + const coords = [].concat(paths.flat()).concat(markerCoordinates); - return serveBounds(req, res, next); - }); + // Check if we have at least one coordinate to calculate a bounding box + if (coords.length < 1) { + return res.status(400).send('No coordinates provided'); + } - const autoPattern = 'auto'; + const bbox = [Infinity, Infinity, -Infinity, -Infinity]; + for (const pair of coords) { + bbox[0] = Math.min(bbox[0], pair[0]); + bbox[1] = Math.min(bbox[1], pair[1]); + bbox[2] = Math.max(bbox[2], pair[0]); + bbox[3] = Math.max(bbox[3], pair[1]); + } - app.get( - util.format(staticPattern, autoPattern), - async (req, res, next) => { - try { - const item = repo[req.params.id]; - if (!item) { + const bbox_ = mercator.convert(bbox, '900913'); + const center = mercator.inverse([ + (bbox_[0] + bbox_[2]) / 2, + (bbox_[1] + bbox_[3]) / 2, + ]); + + // Calculate zoom level + const maxZoom = parseFloat(req.query.maxzoom); + let z = calcZForBBox(bbox, w, h, req.query); + if (maxZoom > 0) { + z = Math.min(z, maxZoom); + } + + const x = center[0]; + const y = center[1]; + + // prettier-ignore + const overlay = await renderOverlay( + z, x, y, bearing, pitch, w, h, scale, paths, markers, req.query, + ); + + // prettier-ignore + return await respondImage( + options, item, z, x, y, bearing, pitch, w, h, scale, format, res, overlay, 'static', + ); + } else { return res.sendStatus(404); } - const raw = req.params.raw; - const w = req.params.width | 0; - const h = req.params.height | 0; - const bearing = 0; - const pitch = 0; - const scale = getScale(req.params.scale); - const format = req.params.format; - - const transformer = raw - ? mercator.inverse.bind(mercator) - : item.dataProjWGStoInternalWGS; - - const paths = extractPathsFromQuery(req.query, transformer); - const markers = extractMarkersFromQuery( - req.query, - options, - transformer, - ); - - // Extract coordinates from markers - const markerCoordinates = []; - for (const marker of markers) { - markerCoordinates.push(marker.location); - } - - // Create array with coordinates from markers and path - const coords = [].concat(paths.flat()).concat(markerCoordinates); - - // Check if we have at least one coordinate to calculate a bounding box - if (coords.length < 1) { - return res.status(400).send('No coordinates provided'); - } - - const bbox = [Infinity, Infinity, -Infinity, -Infinity]; - for (const pair of coords) { - bbox[0] = Math.min(bbox[0], pair[0]); - bbox[1] = Math.min(bbox[1], pair[1]); - bbox[2] = Math.max(bbox[2], pair[0]); - bbox[3] = Math.max(bbox[3], pair[1]); - } - - const bbox_ = mercator.convert(bbox, '900913'); - const center = mercator.inverse([ - (bbox_[0] + bbox_[2]) / 2, - (bbox_[1] + bbox_[3]) / 2, - ]); - - // Calculate zoom level - const maxZoom = parseFloat(req.query.maxzoom); - let z = calcZForBBox(bbox, w, h, req.query); - if (maxZoom > 0) { - z = Math.min(z, maxZoom); - } - - const x = center[0]; - const y = center[1]; - - // prettier-ignore - const overlay = await renderOverlay( - z, x, y, bearing, pitch, w, h, scale, paths, markers, req.query, - ); - - // prettier-ignore - return respondImage( - options, item, z, x, y, bearing, pitch, w, h, scale, format, res, overlay, 'static', - ); } catch (e) { - next(e); + next('route'); } }, ); } - app.get('/(:tileSize(256|512)/)?:id.json', (req, res, next) => { + app.get('{/:tileSize}/:id.json', (req, res, next) => { const item = repo[req.params.id]; if (!item) { return res.sendStatus(404); diff --git a/src/serve_style.js b/src/serve_style.js index 5d3b469..15f9250 100644 --- a/src/serve_style.js +++ b/src/serve_style.js @@ -10,9 +10,15 @@ import { validateStyleMin } from '@maplibre/maplibre-gl-style-spec'; import { fixUrl, allowedOptions } from './utils.js'; const httpTester = /^https?:\/\//i; -const allowedSpriteScales = allowedOptions(['', '@2x', '@3x']); const allowedSpriteFormats = allowedOptions(['png', 'json']); +const allowedSpriteScales = (scale) => { + if (!scale) return ''; // Default to 1 if no scale provided + const match = scale.match(/(\d+)x/); // Match one or more digits before 'x' + const parsedScale = match ? parseInt(match[1], 10) : 1; // Parse the number, or default to 1 if no match + return '@' + Math.min(parsedScale, 3) + 'x'; +}; + export const serve_style = { init: (options, repo) => { const app = express().disable('x-powered-by'); @@ -46,14 +52,18 @@ export const serve_style = { return res.send(styleJSON_); }); - app.get( - '/:id/sprite(/:spriteID)?:scale(@[23]x)?.:format([\\w]+)', - (req, res, next) => { - const { spriteID = 'default', id } = req.params; - const scale = allowedSpriteScales(req.params.scale) || ''; - const format = allowedSpriteFormats(req.params.format); - - if (format) { + app.get(`/:id/:sprite{/:spriteID}{@:scale}{.:format}`, (req, res, next) => { + console.log(req.params); + const { spriteID = 'default', id, format } = req.params; + const scale = allowedSpriteScales(req.params.scale); + try { + if ( + !allowedSpriteFormats(format) || + ((id == 256 || id == 512) && format === 'json') + ) { + //Workaround for {/:tileSize}/:id.json' and /styles/:id/wmts.xml + next('route'); + } else { const item = repo[id]; const sprite = item.spritePaths.find( (sprite) => sprite.id === spriteID, @@ -74,11 +84,12 @@ export const serve_style = { } else { return res.status(400).send('Bad Sprite ID or Scale'); } - } else { - return res.status(400).send('Bad Sprite Format'); } - }, - ); + } catch (e) { + console.log(e); + next('route'); + } + }); return app; }, diff --git a/src/server.js b/src/server.js index 39808e3..c5ddf36 100644 --- a/src/server.js +++ b/src/server.js @@ -37,7 +37,7 @@ const serve_rendered = ( * * @param opts */ -function start(opts) { +async function start(opts) { console.log('Starting server'); const app = express().disable('x-powered-by'); @@ -73,7 +73,7 @@ function start(opts) { config = JSON.parse(fs.readFileSync(configPath, 'utf8')); } catch (e) { console.log('ERROR: Config file not found or invalid!'); - console.log(' See README.md for instructions and sample data.'); + console.log(' See README.md for instructions and sample data.'); process.exit(1); } } @@ -379,14 +379,14 @@ function start(opts) { return arr; }; - app.get('/(:tileSize(256|512)/)?rendered.json', (req, res, next) => { + app.get('{/:tileSize}/rendered.json', (req, res, next) => { const tileSize = parseInt(req.params.tileSize, 10) || undefined; res.send(addTileJSONs([], req, 'rendered', tileSize)); }); - app.get('/data.json', (req, res, next) => { + app.get('/data.json', (req, res) => { res.send(addTileJSONs([], req, 'data', undefined)); }); - app.get('/(:tileSize(256|512)/)?index.json', (req, res, next) => { + app.get('{/:tileSize}/index.json', (req, res, next) => { const tileSize = parseInt(req.params.tileSize, 10) || undefined; res.send( addTileJSONs( @@ -415,44 +415,38 @@ function start(opts) { templateFile = path.resolve(paths.root, options.frontPage); } } - startupPromises.push( - new Promise((resolve, reject) => { - fs.readFile(templateFile, (err, content) => { - if (err) { - err = new Error(`Template not found: ${err.message}`); - reject(err); - return; + try { + const content = fs.readFileSync(templateFile, 'utf-8'); + const compiled = handlebars.compile(content.toString()); + app.get(urlPath, (req, res) => { + console.log(`Serving template at path: ${urlPath}`); + let data = {}; + if (dataGetter) { + data = dataGetter(req); + if (!data) { + console.error(`Data getter for ${template} returned null`); + return res.status(404).send('Not found'); } - const compiled = handlebars.compile(content.toString()); - - app.use(urlPath, (req, res, next) => { - let data = {}; - if (dataGetter) { - data = dataGetter(req); - if (!data) { - return res.status(404).send('Not found'); - } - } - data['server_version'] = - `${packageJson.name} v${packageJson.version}`; - data['public_url'] = opts.publicUrl || '/'; - data['is_light'] = isLight; - data['key_query_part'] = req.query.key - ? `key=${encodeURIComponent(req.query.key)}&` - : ''; - data['key_query'] = req.query.key - ? `?key=${encodeURIComponent(req.query.key)}` - : ''; - if (template === 'wmts') res.set('Content-Type', 'text/xml'); - return res.status(200).send(compiled(data)); - }); - resolve(); - }); - }), - ); + } + data['server_version'] = `${packageJson.name} v${packageJson.version}`; + data['public_url'] = opts.publicUrl || '/'; + data['is_light'] = isLight; + data['key_query_part'] = req.query.key + ? `key=${encodeURIComponent(req.query.key)}&` + : ''; + data['key_query'] = req.query.key + ? `?key=${encodeURIComponent(req.query.key)}` + : ''; + if (template === 'wmts') res.set('Content-Type', 'text/xml'); + return res.status(200).send(compiled(data)); + }); + } catch (err) { + console.error(`Error reading template file: ${templateFile}`, err); + throw new Error(`Template not found: ${err.message}`); //throw an error so that the server doesnt start + } }; - serveTemplate('/$', 'index', (req) => { + serveTemplate('/', 'index', (req) => { let styles = {}; for (const id of Object.keys(serving.styles || {})) { let style = { @@ -552,7 +546,7 @@ function start(opts) { }; }); - serveTemplate('/styles/:id/$', 'viewer', (req) => { + serveTemplate('/styles/:id/', 'viewer', (req) => { const { id } = req.params; const style = clone(((serving.styles || {})[id] || {}).styleJSON); @@ -569,11 +563,6 @@ function start(opts) { }; }); - /* - app.use('/rendered/:id/$', function(req, res, next) { - return res.redirect(301, '/styles/' + req.params.id + '/'); - }); - */ serveTemplate('/styles/:id/wmts.xml', 'wmts', (req) => { const { id } = req.params; const wmts = clone((serving.styles || {})[id]); @@ -605,9 +594,8 @@ function start(opts) { }; }); - serveTemplate('^/data/(:preview(preview)/)?:id/$', 'data', (req) => { - const id = req.params.id; - const preview = req.params.preview || undefined; + serveTemplate('^/data{/:view}/:id/', 'data', (req) => { + const { id, view } = req.params; const data = serving.data[id]; if (!data) { @@ -616,7 +604,7 @@ function start(opts) { const is_terrain = (data.tileJSON.encoding === 'terrarium' || data.tileJSON.encoding === 'mapbox') && - preview === 'preview'; + view === 'preview'; return { ...data, id, @@ -633,7 +621,7 @@ function start(opts) { startupComplete = true; }); - app.get('/health', (req, res, next) => { + app.get('/health', (req, res) => { if (startupComplete) { return res.status(200).send('OK'); } else { @@ -676,8 +664,8 @@ function stopGracefully(signal) { * * @param opts */ -export function server(opts) { - const running = start(opts); +export async function server(opts) { + const running = await start(opts); running.startupPromise.catch((err) => { console.error(err.message); diff --git a/test/setup.js b/test/setup.js index 34fba67..1852a19 100644 --- a/test/setup.js +++ b/test/setup.js @@ -7,10 +7,10 @@ import { server } from '../src/server.js'; global.expect = expect; global.supertest = supertest; -before(function () { +before(async function () { console.log('global setup'); process.chdir('test_data'); - const running = server({ + const running = await server({ configPath: 'config.json', port: 8888, publicUrl: '/test/',