From f223aabf54fc12e6c98d5bc877b6ac2fa226e5e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Suszy=C5=84ski?= Date: Thu, 9 Mar 2023 20:35:10 +0100 Subject: [PATCH] Express app uses the React frontend --- expressjs/.eslintignore | 2 + expressjs/.gitignore | 1 + expressjs/Makefile | 6 +- expressjs/README.md | 59 +-- expressjs/package-lock.json | 400 +++++++----------- expressjs/package.json | 42 +- expressjs/public/rhoss-icon.svg | 19 - expressjs/public/style.css | 127 ------ expressjs/{src => }/scripts/build-image.js | 0 .../{src => }/scripts/cache-git-describe.js | 6 +- expressjs/scripts/extract-webjar.js | 40 ++ expressjs/src/app.js | 43 +- expressjs/src/domain/entity/project.js | 61 --- expressjs/src/index.js | 14 +- expressjs/src/{services => lib}/config.js | 0 expressjs/src/lib/env.js | 6 + expressjs/src/{services => lib}/fs.js | 0 expressjs/src/lib/openapi.js | 93 ++++ expressjs/src/{services => lib}/project.js | 45 +- expressjs/src/loaders/express.js | 15 - .../src/{routes => middleware}/health.js | 4 +- expressjs/src/middleware/json.js | 8 + .../{middlewares => middleware}/logging.js | 0 expressjs/src/middleware/openapi.js | 16 + .../{middlewares => middleware}/prometeus.js | 0 expressjs/src/middleware/public.js | 8 + expressjs/src/middlewares/handlebars.js | 6 - expressjs/src/middlewares/openapi.js | 22 - expressjs/src/middlewares/public.js | 5 - expressjs/src/routes/events/devdata.js | 22 + expressjs/src/routes/events/endpoint.js | 98 +++++ expressjs/src/routes/events/store.js | 59 +++ .../routes/{hello.js => hello/endpoint.js} | 8 +- .../src/{services => routes/hello}/greeter.js | 2 +- .../{domain/entity => routes/hello}/hello.js | 0 .../{services => routes/hello}/notifier.js | 0 expressjs/src/routes/home/endpoint.js | 92 ++++ expressjs/src/routes/index.js | 126 ------ expressjs/src/routes/info/endpoint.js | 54 +++ expressjs/src/routes/info/info.js | 16 + expressjs/src/scripts/ensure-versions.js | 19 - expressjs/test/config/project.test.js | 13 + .../{routes => middleware}/health.test.js | 3 +- .../{routes => middleware}/metrics.test.js | 3 +- .../{routes => middleware}/openapi.test.js | 7 +- .../{hello.test.js => hello/endpoint.test.js} | 3 +- .../hello}/greeter.test.js | 2 +- .../{index.test.js => index/endpoint.test.js} | 10 +- expressjs/test/services/project.test.js | 39 -- expressjs/views/index.handlebars | 53 --- expressjs/views/layouts/main.handlebars | 1 - frontend/Makefile | 1 + frontend/src/config/backend.ts | 9 +- 53 files changed, 833 insertions(+), 855 deletions(-) create mode 100644 expressjs/.eslintignore delete mode 100644 expressjs/public/rhoss-icon.svg delete mode 100644 expressjs/public/style.css rename expressjs/{src => }/scripts/build-image.js (100%) rename expressjs/{src => }/scripts/cache-git-describe.js (72%) create mode 100644 expressjs/scripts/extract-webjar.js delete mode 100644 expressjs/src/domain/entity/project.js rename expressjs/src/{services => lib}/config.js (100%) create mode 100644 expressjs/src/lib/env.js rename expressjs/src/{services => lib}/fs.js (100%) create mode 100644 expressjs/src/lib/openapi.js rename expressjs/src/{services => lib}/project.js (67%) delete mode 100644 expressjs/src/loaders/express.js rename expressjs/src/{routes => middleware}/health.js (92%) create mode 100644 expressjs/src/middleware/json.js rename expressjs/src/{middlewares => middleware}/logging.js (100%) create mode 100644 expressjs/src/middleware/openapi.js rename expressjs/src/{middlewares => middleware}/prometeus.js (100%) create mode 100644 expressjs/src/middleware/public.js delete mode 100644 expressjs/src/middlewares/handlebars.js delete mode 100644 expressjs/src/middlewares/openapi.js delete mode 100644 expressjs/src/middlewares/public.js create mode 100644 expressjs/src/routes/events/devdata.js create mode 100644 expressjs/src/routes/events/endpoint.js create mode 100644 expressjs/src/routes/events/store.js rename expressjs/src/routes/{hello.js => hello/endpoint.js} (92%) rename expressjs/src/{services => routes/hello}/greeter.js (93%) rename expressjs/src/{domain/entity => routes/hello}/hello.js (100%) rename expressjs/src/{services => routes/hello}/notifier.js (100%) create mode 100644 expressjs/src/routes/home/endpoint.js delete mode 100644 expressjs/src/routes/index.js create mode 100644 expressjs/src/routes/info/endpoint.js create mode 100644 expressjs/src/routes/info/info.js delete mode 100644 expressjs/src/scripts/ensure-versions.js create mode 100644 expressjs/test/config/project.test.js rename expressjs/test/{routes => middleware}/health.test.js (87%) rename expressjs/test/{routes => middleware}/metrics.test.js (74%) rename expressjs/test/{routes => middleware}/openapi.test.js (83%) rename expressjs/test/routes/{hello.test.js => hello/endpoint.test.js} (92%) rename expressjs/test/{services => routes/hello}/greeter.test.js (94%) rename expressjs/test/routes/{index.test.js => index/endpoint.test.js} (71%) delete mode 100644 expressjs/test/services/project.test.js delete mode 100644 expressjs/views/index.handlebars delete mode 100644 expressjs/views/layouts/main.handlebars diff --git a/expressjs/.eslintignore b/expressjs/.eslintignore new file mode 100644 index 0000000..dac8212 --- /dev/null +++ b/expressjs/.eslintignore @@ -0,0 +1,2 @@ +build/ +public/ diff --git a/expressjs/.gitignore b/expressjs/.gitignore index 23b8d3d..d88e380 100644 --- a/expressjs/.gitignore +++ b/expressjs/.gitignore @@ -1,3 +1,4 @@ node_modules/ build/ +public/ npm-debug.log diff --git a/expressjs/Makefile b/expressjs/Makefile index f9bc5e2..2be397e 100644 --- a/expressjs/Makefile +++ b/expressjs/Makefile @@ -6,8 +6,12 @@ deps: build: npm run build +test: + npm test + clean: frontend.clean rm -rfv build + rm -rf node_modules frontend: frontend.build @@ -17,5 +21,5 @@ frontend.clean: frontend.build: +$(MAKE) -C ../frontend -.PHONY: default deps build clean \ +.PHONY: default deps build clean test \ frontend frontend.build frontend.clean diff --git a/expressjs/README.md b/expressjs/README.md index 18b76a6..b964615 100644 --- a/expressjs/README.md +++ b/expressjs/README.md @@ -1,52 +1,33 @@ # Knative Showcase for JS -## TODO +This project uses Express.JS framework to showcase the Knative features. -### Required +## Prerequisites -* [x] `/` +This application requires the React frontend application webjar to be built and +deployed to a local maven repository. To do it, run the following command from +the root of the project: - ```bash - http :8080 - http options :8080 - http :8080 user-agent:Mozilla/5.0 - ``` +```shell +make frontend +``` -* [x] `/hello` +## Running the application in dev mode - ```bash - http :8080/hello - ``` +You can run your application in dev mode that enables live coding using: -* [x] cloudevents +```shell +npm run dev +``` - ```bash - http :8080/hello - ``` +## Packaging and running the application -* [x] `DELAY` parameter (in msec) for `/hello` -* [x] Containerfile to build as Knative app +The application can be packaged as OCI image using: -### Nice to have +```shell +npm run build +``` -* [x] readyness and liveness probes +## Learning - ```bash - http :8080/health/ready - http :8080/health/live - ``` - -* [x] Prometeus metrics - - ```bash - http :8080/metrics - ``` - -* [x] OpenAPI & Swagger UI - - ```bash - http :8080/openapi.json - http :8080/swagger-ui - ``` -* [x] Input validation (validation by OpenAPI schema) -* [ ] OpenTracing & OpenTelemetry & Distributed Tracing +If you want to learn more about Express.JS, please visit its website: https://expressjs.com/. diff --git a/expressjs/package-lock.json b/expressjs/package-lock.json index aea476a..d9314d3 100644 --- a/expressjs/package-lock.json +++ b/expressjs/package-lock.json @@ -1,38 +1,37 @@ { "name": "@openshift/knative-showcase", - "version": "0.5.1-pre", + "version": "main", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@openshift/knative-showcase", - "version": "0.5.1-pre", + "version": "main", "license": "Apache-2.0", "dependencies": { - "@unleash/express-openapi": "^0.2.1", + "@unleash/express-openapi": "^0.2.2", "axios": "^0.25.0", - "cloudevents": "^6.0.3", + "cloudevents": "^6.0.4", "dotenv": "^14.3.2", "express": "^4.18.2", - "express-handlebars": "^6.0.6", "express-prom-bundle": "^6.6.0", "git-describe": "^4.1.1", "joi": "^17.8.3", "morgan": "^1.10.0", - "prom-client": "^14.1.1", - "semver": "^7.3.8" + "prom-client": "^14.2.0" }, "devDependencies": { "@types/jest": "^27.5.2", + "adm-zip": "^0.5.10", "chalk": "^4.1.2", - "eslint": "^8.31.0", - "eslint-config-piecioshka": "^2.0.3", + "eslint": "^8.35.0", + "eslint-config-piecioshka": "^2.2.4", "eslint-plugin-jest": "^26.9.0", "get-port": "^5.1.1", "invoke-script": "^1.3.0", "jest": "^27.5.1", "nock": "^13.3.0", - "nodemon": "^2.0.20", + "nodemon": "^2.0.21", "shelljs": "^0.8.5", "supertest": "^6.3.3" } @@ -654,9 +653,9 @@ "dev": true }, "node_modules/@eslint/eslintrc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", - "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.0.tgz", + "integrity": "sha512-fluIaaV+GyV24CCu/ggiHdV+j4RNh85yQnAYS/G2mZODZgGmmlrgCydjUcV3YvxCm9x8nMAfThsqTni4KiXT4A==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -676,6 +675,15 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/js": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.35.0.tgz", + "integrity": "sha512-JXdzbRiWclLVoD8sNUjR443VVlYqiYmDVT6rGUEIEHU5YJW0gaVZwV2xgM7D4arkvASqD0IlLUVjHiFuxaftRw==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", @@ -1472,9 +1480,9 @@ } }, "node_modules/@unleash/express-openapi": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@unleash/express-openapi/-/express-openapi-0.2.1.tgz", - "integrity": "sha512-wvwgSrzMGo3khB2E/xPLXlB/uT6FruszIsUDThJpttILtxBmj1SDWb0L6SG1CQ2tlnC786/6+0iSBwcz0fViOg==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@unleash/express-openapi/-/express-openapi-0.2.2.tgz", + "integrity": "sha512-Evn1gVB5v7QMAs/mGjTc3NihX9wZnMdyBPvpd/JqMI8NDH9z/q46cYnh2t7bFPQj7FBghWwZlcJNm7PU0bxe7A==", "dependencies": { "ajv": "^6.10.2", "http-errors": "^1.7.3", @@ -1562,6 +1570,15 @@ "node": ">=0.4.0" } }, + "node_modules/adm-zip": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz", + "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -1839,7 +1856,8 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, "node_modules/basic-auth": { "version": "2.0.1", @@ -2197,14 +2215,18 @@ } }, "node_modules/cloudevents": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/cloudevents/-/cloudevents-6.0.3.tgz", - "integrity": "sha512-ADEHAv2KShH/cDIy2GP+npFz3R6Fu/UCsUO/j4kYA9VqN4yhGdF+Zg6wmjeq6qlUvlaKdrVBwgZuH/w57IsyGQ==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/cloudevents/-/cloudevents-6.0.4.tgz", + "integrity": "sha512-Vay81bTsutFkZxHnM2K0rev95d0x7aTZ3G+Bmm8/GnIzsVtGfeBkLcXFD4czZ08RoOn6POKl+rIXaBS+Xn+jIA==", "dependencies": { "ajv": "^8.11.0", "ajv-formats": "^2.1.1", + "process": "^0.11.10", "util": "^0.12.4", "uuid": "^8.3.2" + }, + "engines": { + "node": ">=12 <20.0.0" } }, "node_modules/cloudevents/node_modules/ajv": { @@ -2684,12 +2706,13 @@ } }, "node_modules/eslint": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.31.0.tgz", - "integrity": "sha512-0tQQEVdmPZ1UtUKXjX7EMm9BlgJ08G90IhWh0PKDCb3ZLsgAOHI8fYSIzYVZej92zsgq+ft0FGsxhJ3xo2tbuA==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.35.0.tgz", + "integrity": "sha512-BxAf1fVL7w+JLRQhWl2pzGeSiGqbWumV4WNvc9Rhp6tiCtm4oHnyPBSEtMGZwrQgudFQ+otqzWoPB7x+hxoWsw==", "dev": true, "dependencies": { - "@eslint/eslintrc": "^1.4.1", + "@eslint/eslintrc": "^2.0.0", + "@eslint/js": "8.35.0", "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -2703,7 +2726,7 @@ "eslint-utils": "^3.0.0", "eslint-visitor-keys": "^3.3.0", "espree": "^9.4.0", - "esquery": "^1.4.0", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", @@ -2740,9 +2763,9 @@ } }, "node_modules/eslint-config-piecioshka": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/eslint-config-piecioshka/-/eslint-config-piecioshka-2.0.3.tgz", - "integrity": "sha512-tVn8EMplUMAUW0aj+Zt5gqtMPfk2SM51K6+WPvZJpu+warIYAOEkP3SKoun63c34wCMC8hIBPswvU8JBuSfiKQ==", + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/eslint-config-piecioshka/-/eslint-config-piecioshka-2.2.4.tgz", + "integrity": "sha512-dtMNg5XvcCf9sSDYDsk3MlkGzw2MzONOFlwrvjSq7vMZLYiGlKJR6indL3qDEMlYf7p8293sE/oSZa+9kBfGcA==", "dev": true }, "node_modules/eslint-plugin-jest": { @@ -2849,9 +2872,9 @@ } }, "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, "dependencies": { "estraverse": "^5.1.0" @@ -2986,19 +3009,6 @@ "node": ">= 0.10.0" } }, - "node_modules/express-handlebars": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/express-handlebars/-/express-handlebars-6.0.6.tgz", - "integrity": "sha512-E4QHYCh+9fyfdBEb8uKJ8p6HD4qq/sUSHBq83lRNlLJp2TQKEg2nFJYbVdC+M3QzaV19dODe43lgjQWVaIpbyQ==", - "dependencies": { - "glob": "^8.0.2", - "graceful-fs": "^4.2.10", - "handlebars": "^4.7.7" - }, - "engines": { - "node": ">=v12.22.9" - } - }, "node_modules/express-prom-bundle": { "version": "6.6.0", "resolved": "https://registry.npmjs.org/express-prom-bundle/-/express-prom-bundle-6.6.0.tgz", @@ -3298,7 +3308,8 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true }, "node_modules/fsevents": { "version": "2.3.2", @@ -3407,24 +3418,6 @@ "semver": "bin/semver" } }, - "node_modules/glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -3437,29 +3430,10 @@ "node": ">=10.13.0" } }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/globals": { - "version": "13.19.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", - "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -3505,7 +3479,8 @@ "node_modules/graceful-fs": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true }, "node_modules/grapheme-splitter": { "version": "1.0.4", @@ -3513,26 +3488,6 @@ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", "dev": true }, - "node_modules/handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -3746,6 +3701,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -5096,14 +5052,6 @@ "node": "*" } }, - "node_modules/minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/mixin-object": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", @@ -5183,11 +5131,6 @@ "node": ">= 0.6" } }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" - }, "node_modules/nock": { "version": "13.3.0", "resolved": "https://registry.npmjs.org/nock/-/nock-13.3.0.tgz", @@ -5216,9 +5159,9 @@ "dev": true }, "node_modules/nodemon": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz", - "integrity": "sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==", + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.21.tgz", + "integrity": "sha512-djN/n2549DUtY33S7o1djRCd7dEm0kBnj9c7S9XVXqRUbuggN1MZH/Nqa+5RFQr63Fbefq37nFXAE9VU86yL1A==", "dev": true, "dependencies": { "chokidar": "^3.5.2", @@ -5355,6 +5298,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, "dependencies": { "wrappy": "1" } @@ -5647,10 +5591,18 @@ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/prom-client": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-14.1.1.tgz", - "integrity": "sha512-hFU32q7UZQ59bVJQGUtm3I2PrJ3gWvoCkilX9sF165ks1qflhugVCeK+S1JjJYHvyt3o5kj68+q3bchormjnzw==", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-14.2.0.tgz", + "integrity": "sha512-sF308EhTenb/pDRPakm+WgiN+VdM/T1RaHj1x+MvAuT8UiQP8JmOEbxVqtkbfR4LrvOg5n7ic01kRBDGXjYikA==", "dependencies": { "tdigest": "^0.1.1" }, @@ -6052,6 +6004,7 @@ "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -6066,6 +6019,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -6076,7 +6030,8 @@ "node_modules/semver/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true }, "node_modules/send": { "version": "0.18.0", @@ -6303,6 +6258,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -6773,18 +6729,6 @@ "node": ">=4.2.0" } }, - "node_modules/uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", @@ -7039,11 +6983,6 @@ "node": ">=0.10.0" } }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==" - }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -7097,7 +7036,8 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true }, "node_modules/write-file-atomic": { "version": "3.0.3", @@ -7689,9 +7629,9 @@ "dev": true }, "@eslint/eslintrc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", - "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.0.tgz", + "integrity": "sha512-fluIaaV+GyV24CCu/ggiHdV+j4RNh85yQnAYS/G2mZODZgGmmlrgCydjUcV3YvxCm9x8nMAfThsqTni4KiXT4A==", "dev": true, "requires": { "ajv": "^6.12.4", @@ -7705,6 +7645,12 @@ "strip-json-comments": "^3.1.1" } }, + "@eslint/js": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.35.0.tgz", + "integrity": "sha512-JXdzbRiWclLVoD8sNUjR443VVlYqiYmDVT6rGUEIEHU5YJW0gaVZwV2xgM7D4arkvASqD0IlLUVjHiFuxaftRw==", + "dev": true + }, "@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", @@ -8345,9 +8291,9 @@ } }, "@unleash/express-openapi": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@unleash/express-openapi/-/express-openapi-0.2.1.tgz", - "integrity": "sha512-wvwgSrzMGo3khB2E/xPLXlB/uT6FruszIsUDThJpttILtxBmj1SDWb0L6SG1CQ2tlnC786/6+0iSBwcz0fViOg==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@unleash/express-openapi/-/express-openapi-0.2.2.tgz", + "integrity": "sha512-Evn1gVB5v7QMAs/mGjTc3NihX9wZnMdyBPvpd/JqMI8NDH9z/q46cYnh2t7bFPQj7FBghWwZlcJNm7PU0bxe7A==", "requires": { "ajv": "^6.10.2", "http-errors": "^1.7.3", @@ -8417,6 +8363,12 @@ "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", "dev": true }, + "adm-zip": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz", + "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==", + "dev": true + }, "agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -8622,7 +8574,8 @@ "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, "basic-auth": { "version": "2.0.1", @@ -8890,12 +8843,13 @@ } }, "cloudevents": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/cloudevents/-/cloudevents-6.0.3.tgz", - "integrity": "sha512-ADEHAv2KShH/cDIy2GP+npFz3R6Fu/UCsUO/j4kYA9VqN4yhGdF+Zg6wmjeq6qlUvlaKdrVBwgZuH/w57IsyGQ==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/cloudevents/-/cloudevents-6.0.4.tgz", + "integrity": "sha512-Vay81bTsutFkZxHnM2K0rev95d0x7aTZ3G+Bmm8/GnIzsVtGfeBkLcXFD4czZ08RoOn6POKl+rIXaBS+Xn+jIA==", "requires": { "ajv": "^8.11.0", "ajv-formats": "^2.1.1", + "process": "^0.11.10", "util": "^0.12.4", "uuid": "^8.3.2" }, @@ -9272,12 +9226,13 @@ } }, "eslint": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.31.0.tgz", - "integrity": "sha512-0tQQEVdmPZ1UtUKXjX7EMm9BlgJ08G90IhWh0PKDCb3ZLsgAOHI8fYSIzYVZej92zsgq+ft0FGsxhJ3xo2tbuA==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.35.0.tgz", + "integrity": "sha512-BxAf1fVL7w+JLRQhWl2pzGeSiGqbWumV4WNvc9Rhp6tiCtm4oHnyPBSEtMGZwrQgudFQ+otqzWoPB7x+hxoWsw==", "dev": true, "requires": { - "@eslint/eslintrc": "^1.4.1", + "@eslint/eslintrc": "^2.0.0", + "@eslint/js": "8.35.0", "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -9291,7 +9246,7 @@ "eslint-utils": "^3.0.0", "eslint-visitor-keys": "^3.3.0", "espree": "^9.4.0", - "esquery": "^1.4.0", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", @@ -9319,9 +9274,9 @@ } }, "eslint-config-piecioshka": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/eslint-config-piecioshka/-/eslint-config-piecioshka-2.0.3.tgz", - "integrity": "sha512-tVn8EMplUMAUW0aj+Zt5gqtMPfk2SM51K6+WPvZJpu+warIYAOEkP3SKoun63c34wCMC8hIBPswvU8JBuSfiKQ==", + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/eslint-config-piecioshka/-/eslint-config-piecioshka-2.2.4.tgz", + "integrity": "sha512-dtMNg5XvcCf9sSDYDsk3MlkGzw2MzONOFlwrvjSq7vMZLYiGlKJR6indL3qDEMlYf7p8293sE/oSZa+9kBfGcA==", "dev": true }, "eslint-plugin-jest": { @@ -9384,9 +9339,9 @@ "dev": true }, "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, "requires": { "estraverse": "^5.1.0" @@ -9523,16 +9478,6 @@ } } }, - "express-handlebars": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/express-handlebars/-/express-handlebars-6.0.6.tgz", - "integrity": "sha512-E4QHYCh+9fyfdBEb8uKJ8p6HD4qq/sUSHBq83lRNlLJp2TQKEg2nFJYbVdC+M3QzaV19dODe43lgjQWVaIpbyQ==", - "requires": { - "glob": "^8.0.2", - "graceful-fs": "^4.2.10", - "handlebars": "^4.7.7" - } - }, "express-prom-bundle": { "version": "6.6.0", "resolved": "https://registry.npmjs.org/express-prom-bundle/-/express-prom-bundle-6.6.0.tgz", @@ -9741,7 +9686,8 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true }, "fsevents": { "version": "2.3.2", @@ -9813,36 +9759,6 @@ } } }, - "glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", - "requires": { - "brace-expansion": "^2.0.1" - } - } - } - }, "glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -9853,9 +9769,9 @@ } }, "globals": { - "version": "13.19.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", - "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -9886,7 +9802,8 @@ "graceful-fs": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true }, "grapheme-splitter": { "version": "1.0.4", @@ -9894,18 +9811,6 @@ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", "dev": true }, - "handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", - "requires": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4", - "wordwrap": "^1.0.0" - } - }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -10055,6 +9960,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -11084,11 +10990,6 @@ "brace-expansion": "^1.1.7" } }, - "minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==" - }, "mixin-object": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", @@ -11157,11 +11058,6 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" - }, "nock": { "version": "13.3.0", "resolved": "https://registry.npmjs.org/nock/-/nock-13.3.0.tgz", @@ -11187,9 +11083,9 @@ "dev": true }, "nodemon": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz", - "integrity": "sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==", + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.21.tgz", + "integrity": "sha512-djN/n2549DUtY33S7o1djRCd7dEm0kBnj9c7S9XVXqRUbuggN1MZH/Nqa+5RFQr63Fbefq37nFXAE9VU86yL1A==", "dev": true, "requires": { "chokidar": "^3.5.2", @@ -11288,6 +11184,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, "requires": { "wrappy": "1" } @@ -11503,10 +11400,15 @@ } } }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" + }, "prom-client": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-14.1.1.tgz", - "integrity": "sha512-hFU32q7UZQ59bVJQGUtm3I2PrJ3gWvoCkilX9sF165ks1qflhugVCeK+S1JjJYHvyt3o5kj68+q3bchormjnzw==", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-14.2.0.tgz", + "integrity": "sha512-sF308EhTenb/pDRPakm+WgiN+VdM/T1RaHj1x+MvAuT8UiQP8JmOEbxVqtkbfR4LrvOg5n7ic01kRBDGXjYikA==", "requires": { "tdigest": "^0.1.1" } @@ -11786,6 +11688,7 @@ "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, "requires": { "lru-cache": "^6.0.0" }, @@ -11794,6 +11697,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, "requires": { "yallist": "^4.0.0" } @@ -11801,7 +11705,8 @@ "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true } } }, @@ -11991,7 +11896,8 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true }, "source-map-support": { "version": "0.5.21", @@ -12344,12 +12250,6 @@ "dev": true, "peer": true }, - "uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", - "optional": true - }, "undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", @@ -12538,11 +12438,6 @@ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==" - }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -12583,7 +12478,8 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true }, "write-file-atomic": { "version": "3.0.3", diff --git a/expressjs/package.json b/expressjs/package.json index 948a151..1d22e98 100644 --- a/expressjs/package.json +++ b/expressjs/package.json @@ -1,21 +1,22 @@ { "name": "@openshift/knative-showcase", - "version": "0.5.1-pre", + "version": "main", "description": "A showcase app for knative written in Node.js", - "main": "src/app.js", + "main": "src/index.js", "scripts": { - "start": "node src/index", - "dev": "nodemon src/index", - "dev:versions": "invoke-script src/scripts/ensure-versions", - "watch": "jest --watch test/**", - "test:jest": "jest test/**", - "lint:eslint": "eslint .", - "build:prepare": "npm run build:cache-git && npm run dev:versions", - "build:cache-git": "invoke-script src/scripts/cache-git-describe", - "build:image": "npm run build:prepare && invoke-script src/scripts/build-image showcase", - "test": "npm run lint && npm run test:jest", + "start": "npm run build:prepare && nodemon src/index", + "test": "npm run lint && npm run test:jest:watch", + "build": "npm run test && npm run build:image", + "lint": "npm run lint:eslint", - "build": "npm run test && npm run build:image" + "serve": "npm run build:prepare && node src/index", + "test:jest:watch": "npm run build:prepare && jest --watch test/**", + "test:jest": "npm run build:prepare && jest test/**", + "lint:eslint": "eslint .", + "build:prepare": "npm run build:cache-git && npm run build:extract-webjar", + "build:cache-git": "invoke-script scripts/cache-git-describe", + "build:extract-webjar": "invoke-script scripts/extract-webjar", + "build:image": "npm run build:prepare && invoke-script scripts/build-image showcase" }, "repository": { "type": "git", @@ -35,30 +36,29 @@ }, "homepage": "https://github.com/openshift-knative/showcase#readme", "dependencies": { - "@unleash/express-openapi": "^0.2.1", + "@unleash/express-openapi": "^0.2.2", "axios": "^0.25.0", - "cloudevents": "^6.0.3", + "cloudevents": "^6.0.4", "dotenv": "^14.3.2", "express": "^4.18.2", - "express-handlebars": "^6.0.6", "express-prom-bundle": "^6.6.0", "git-describe": "^4.1.1", "joi": "^17.8.3", "morgan": "^1.10.0", - "prom-client": "^14.1.1", - "semver": "^7.3.8" + "prom-client": "^14.2.0" }, "devDependencies": { "@types/jest": "^27.5.2", + "adm-zip": "^0.5.10", "chalk": "^4.1.2", - "eslint": "^8.31.0", - "eslint-config-piecioshka": "^2.0.3", + "eslint": "^8.35.0", + "eslint-config-piecioshka": "^2.2.4", "eslint-plugin-jest": "^26.9.0", "get-port": "^5.1.1", "invoke-script": "^1.3.0", "jest": "^27.5.1", "nock": "^13.3.0", - "nodemon": "^2.0.20", + "nodemon": "^2.0.21", "shelljs": "^0.8.5", "supertest": "^6.3.3" } diff --git a/expressjs/public/rhoss-icon.svg b/expressjs/public/rhoss-icon.svg deleted file mode 100644 index 483c73a..0000000 --- a/expressjs/public/rhoss-icon.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - diff --git a/expressjs/public/style.css b/expressjs/public/style.css deleted file mode 100644 index ee2c316..0000000 --- a/expressjs/public/style.css +++ /dev/null @@ -1,127 +0,0 @@ -h1, -h2, -h3, -h4, -h5, -h6 { - margin-bottom: 0.5rem; - font-weight: 400; - line-height: 1.5; -} - -h1 { - font-size: 2.5rem; -} - -h2 { - font-size: 2rem -} - -h3 { - font-size: 1.75rem -} - -h4 { - font-size: 1.5rem -} - -h5 { - font-size: 1.25rem -} - -h6 { - font-size: 1rem -} - -.lead { - font-weight: 300; - font-size: 2rem; -} - -.note { - background-color: #f4f4ffee; - border: 0.1rem solid #00e; - padding: 1rem; - margin: 1rem 0 0; - display: inline-block; - font-style: italic; - font-size: .9rem; -} - -.note code { - background-color: #eef; - color: #44e; -} - -.banner { - font-size: 2.7rem; - margin: 0; - padding: 2rem 1rem; - background-color: #000; - color: white; - display: flex; -} - -.banner .logo { - padding: 0 1rem 0 0; - height: 4rem; -} - -body { - margin: 0; - font-family: -apple-system, system-ui, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; -} - -code { - font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; - font-size: 87.5%; - color: #e00; - background-color: #fff4f4; - padding: 0.2rem 0.4rem; - word-break: break-word; -} - -.left-column { - padding: .75rem; - max-width: 70%; - min-width: 55%; -} - -.references { - display: flex; -} - -.references .thumbnail { - padding: .75rem; - max-width: 10rem; -} - -.right-column { - padding: .75rem; - max-width: 30%; -} - -.container { - display: flex; - width: 100%; -} - -li { - margin: 0.75rem; -} - -.right-section { - margin-left: 1rem; - padding-left: 0.5rem; -} - -.right-section h3 { - padding-top: 0; - font-weight: 200; -} - -.right-section ul { - border-left: 0.3rem solid #e00; - list-style-type: none; - padding-left: 0; -} diff --git a/expressjs/src/scripts/build-image.js b/expressjs/scripts/build-image.js similarity index 100% rename from expressjs/src/scripts/build-image.js rename to expressjs/scripts/build-image.js diff --git a/expressjs/src/scripts/cache-git-describe.js b/expressjs/scripts/cache-git-describe.js similarity index 72% rename from expressjs/src/scripts/cache-git-describe.js rename to expressjs/scripts/cache-git-describe.js index 3c8f9f9..5a8a4c5 100644 --- a/expressjs/src/scripts/cache-git-describe.js +++ b/expressjs/scripts/cache-git-describe.js @@ -1,5 +1,5 @@ -const { resolveGitDescribe } = require('../services/project') -const { isDirectory } = require('../services/fs') +const { resolveGitDescribe } = require('../src/lib/project') +const { isDirectory } = require('../src/lib/fs') const fs = require('fs').promises const path = require('path') @@ -11,7 +11,7 @@ function cacheGitDescribe() { async function doCacheGitDescribe() { const info = await resolveGitDescribe() - const buildPath = path.resolve(__dirname, '..', '..', 'build') + const buildPath = path.resolve(__dirname, '..', 'build') if (!await isDirectory(buildPath)) { await fs.mkdir(buildPath) } diff --git a/expressjs/scripts/extract-webjar.js b/expressjs/scripts/extract-webjar.js new file mode 100644 index 0000000..ec78e99 --- /dev/null +++ b/expressjs/scripts/extract-webjar.js @@ -0,0 +1,40 @@ +const AdmZip = require('adm-zip') +const fs = require('fs').promises +const chalk = require('chalk') +const path = require('path') + +const coords = { + group: 'com.redhat.openshift.knative.showcase', + artifact: 'frontend', + version: 'main', +} + +const jarDir = `${process.env.HOME}/.m2/repository/${coords.group.replace(/\./g, '/')}/${coords.artifact}/${coords.version}` +const jarPath = `${jarDir}/${coords.artifact}-${coords.version}.jar` + +const extractWebjar = async () => { + const zip = new AdmZip(jarPath) + const zipEntries = zip.getEntries() + + console.log(`Extracting ${chalk.cyan(jarPath)} to ${chalk.green('public')}\n`) + + console.log(`Deleting ${chalk.red('public')} directory\n`) + await fs.rm('public', { recursive: true, force: true }) + + zipEntries.forEach(async zipEntry => { + // outputs zip entries information + if (zipEntry.entryName.startsWith('META-INF/resources') && !zipEntry.isDirectory) { + const targetPath = zipEntry.entryName.replace('META-INF/resources', 'public') + console.log(`${chalk.yellow(zipEntry.entryName)} -> ${chalk.green(targetPath)}`) + + const targetDir = path.dirname(targetPath) + await fs.mkdir(targetDir, { recursive: true }) + await fs.writeFile(targetPath, zipEntry.getData()) + } + }) + console.log(`\nExtracting the webjar is ${chalk.green('Done')}.\n`) +} + +module.exports = () => { + extractWebjar() +} diff --git a/expressjs/src/app.js b/expressjs/src/app.js index 1ffc319..7f5602b 100644 --- a/expressjs/src/app.js +++ b/expressjs/src/app.js @@ -1,16 +1,47 @@ const express = require('express') +const dotenv = require('dotenv') -async function createApp() { - require('dotenv').config() +const middleware = { + logging: require('./middleware/logging'), + public: require('./middleware/public'), + prometeus: require('./middleware/prometeus'), + openapi: require('./middleware/openapi'), + health: require('./middleware/health'), + json: require('./middleware/json'), +} + +const routes = { + home: require('./routes/home/endpoint'), + info: require('./routes/info/endpoint'), + hello: require('./routes/hello/endpoint'), + events: require('./routes/events/endpoint'), +} + +const loaders = app => { + // Middleware Functions + middleware.logging(app) + middleware.public(app) + middleware.prometeus(app) + middleware.health(app) + middleware.json(app) + middleware.openapi(app) + + // Routing + routes.home(app) + routes.info(app) + routes.hello(app) + routes.events(app) +} + +const createApp = () => { + dotenv.config() const ex = express() // Start initializing - await require('./loaders/express')(ex) + loaders(ex) return ex } -module.exports = { - createApp -} +module.exports = createApp diff --git a/expressjs/src/domain/entity/project.js b/expressjs/src/domain/entity/project.js deleted file mode 100644 index 20e366c..0000000 --- a/expressjs/src/domain/entity/project.js +++ /dev/null @@ -1,61 +0,0 @@ -const semver = require('semver') - -class Platform { - constructor({ node, express }) { - - /** - * @type {string} - */ - this.node = node - - /** - * @type {string} - */ - this.express = express - } - - toString() { - return `Express/${this.express} Node/${this.node}` - } -} - -class Project { - constructor({ group, artifact, version, platform }) { - - /** - * @type {string} - */ - this.group = group - - /** - * @type {string} - */ - this.artifact = artifact - - /** - * @type {string} - */ - this.version = version - - /** - * @type {Platform} - */ - this.platform = platform - } - - versionForNpm() { - let v = new semver.SemVer(this.version) - if (v.prerelease.length > 0) { - v = semver.coerce(v.toString()).inc('patch') - v += '-pre' - } else { - v = v.toString() - } - return v - } -} - -module.exports = { - Project, - Platform -} diff --git a/expressjs/src/index.js b/expressjs/src/index.js index 875612d..209d993 100644 --- a/expressjs/src/index.js +++ b/expressjs/src/index.js @@ -1,9 +1,15 @@ -(async () => { - const app = await require('./app').createApp() +const createApp = require('./app') +const config = require('./lib/config') - const port = require('./services/config').port() +const main = async () => { + + const app = await createApp() + + const port = config.port() app.listen(port, () => { console.log(`Listening at http://localhost:${port}/`) }) -})() +} + +main() diff --git a/expressjs/src/services/config.js b/expressjs/src/lib/config.js similarity index 100% rename from expressjs/src/services/config.js rename to expressjs/src/lib/config.js diff --git a/expressjs/src/lib/env.js b/expressjs/src/lib/env.js new file mode 100644 index 0000000..3787a4d --- /dev/null +++ b/expressjs/src/lib/env.js @@ -0,0 +1,6 @@ +const nodeenv = process.env.NODE_ENV || 'development' +const isProduction = nodeenv === 'production' + +module.exports = { + isProduction, +} diff --git a/expressjs/src/services/fs.js b/expressjs/src/lib/fs.js similarity index 100% rename from expressjs/src/services/fs.js rename to expressjs/src/lib/fs.js diff --git a/expressjs/src/lib/openapi.js b/expressjs/src/lib/openapi.js new file mode 100644 index 0000000..b55cc59 --- /dev/null +++ b/expressjs/src/lib/openapi.js @@ -0,0 +1,93 @@ +const openapi = require('@unleash/express-openapi') + +const oapi = openapi({ + openapi: '3.0.0', + info: { + description: 'Knative Showcase for JS', + }, + components: { + schemas: { + CloudEvent: { + type: 'object', + properties: { + specVersion: { + $ref: '#/components/schemas/SpecVersion' + }, + id: { + type: 'string' + }, + type: { + type: 'string' + }, + source: { + format: 'uri', + type: 'string' + }, + dataContentType: { + type: 'string', + example: 'application/json' + }, + dataSchema: { + format: 'uri', + type: 'string' + }, + subject: { + type: 'string' + }, + time: { + $ref: '#/components/schemas/OffsetDateTime' + }, + attributeNames: { + uniqueItems: true, + type: 'array', + items: { + type: 'string' + } + }, + extensionNames: { + uniqueItems: true, + type: 'array', + items: { + type: 'string' + } + }, + data: { + type: 'object' + } + } + }, + Hello: { + type: 'object', + properties: { + greeting: { + pattern: '^[A-Z][a-z]+$', + type: 'string' + }, + who: { + pattern: '^[A-Z][a-z]+$', + type: 'string' + }, + number: { + format: 'int32', + minimum: 1, + type: 'integer' + } + } + }, + OffsetDateTime: { + format: 'date-time', + type: 'string', + example: '2022-03-10T16:15:50.000Z' + }, + SpecVersion: { + enum: [ + '1.0', + '0.3' + ], + type: 'string' + } + } + } +}) + +module.exports = oapi diff --git a/expressjs/src/services/project.js b/expressjs/src/lib/project.js similarity index 67% rename from expressjs/src/services/project.js rename to expressjs/src/lib/project.js index d578609..dfe8203 100644 --- a/expressjs/src/services/project.js +++ b/expressjs/src/lib/project.js @@ -1,19 +1,40 @@ -const { Project, Platform } = require('../domain/entity/project') -const { isDirectory } = require('./fs') +const { isDirectory } = require('./fs.js') const packageJson = require('../../package.json') +const { gitDescribe } = require('git-describe') + +class Project { + constructor({ group, artifact, version, platform }) { + + /** + * @type {string} + */ + this.group = group + + /** + * @type {string} + */ + this.artifact = artifact + + /** + * @type {string} + */ + this.version = version + + /** + * @type {string} + */ + this.platform = platform + } +} async function resolveGitDescribe() { return new Promise((resolve, reject) => { - const { gitDescribe } = require('git-describe') gitDescribe((err, desc) => { if (err) { - reject() - return - } - if (!desc.dirty && desc.distance === 0) { - resolve(desc.tag) + reject(err) return } + resolve(desc.raw) }) }) @@ -36,13 +57,14 @@ function resolveExpressVersion() { } /** - * @returns {Platform} + * @returns {string} */ function resolvePlatform() { - return new Platform({ + const platform = { node: process.version.replace(/^v/, ''), express: resolveExpressVersion() - }) + } + return `Express/${platform.express} Node/${platform.node}` } /** @@ -65,6 +87,7 @@ async function resolveProject() { } module.exports = { + Project, resolveProject, resolveGitDescribe } diff --git a/expressjs/src/loaders/express.js b/expressjs/src/loaders/express.js deleted file mode 100644 index 4f7c44d..0000000 --- a/expressjs/src/loaders/express.js +++ /dev/null @@ -1,15 +0,0 @@ -module.exports = async app => { - - // Middleware Functions - require('../middlewares/logging')(app) - require('../middlewares/public')(app) - require('../middlewares/handlebars')(app) - require('../middlewares/prometeus')(app) - await require('../middlewares/openapi').middleware(app) - - // Routing - require('../routes/index')(app) - require('../routes/hello')(app) - require('../routes/health')(app) - -} diff --git a/expressjs/src/routes/health.js b/expressjs/src/middleware/health.js similarity index 92% rename from expressjs/src/routes/health.js rename to expressjs/src/middleware/health.js index ee6b5a3..94b2d88 100644 --- a/expressjs/src/routes/health.js +++ b/expressjs/src/middleware/health.js @@ -1,4 +1,4 @@ -const { oapi } = require('../middlewares/openapi') +const openapi = require('../lib/openapi') const Status = { UP: 'UP', @@ -24,7 +24,7 @@ module.exports = app => { } function doc(type) { - return oapi.path({ + return openapi.path({ summary: `${type}ness probe`, description: `A K8s style ${type} probe`, responses: { diff --git a/expressjs/src/middleware/json.js b/expressjs/src/middleware/json.js new file mode 100644 index 0000000..3992ddd --- /dev/null +++ b/expressjs/src/middleware/json.js @@ -0,0 +1,8 @@ +const express = require('express') + +/** + * @param {express.Express} app the app + */ +module.exports = app => { + app.use(express.json()) +} diff --git a/expressjs/src/middlewares/logging.js b/expressjs/src/middleware/logging.js similarity index 100% rename from expressjs/src/middlewares/logging.js rename to expressjs/src/middleware/logging.js diff --git a/expressjs/src/middleware/openapi.js b/expressjs/src/middleware/openapi.js new file mode 100644 index 0000000..bb56f79 --- /dev/null +++ b/expressjs/src/middleware/openapi.js @@ -0,0 +1,16 @@ +const oapi = require('../lib/openapi') +const { resolveProject } = require('../lib/project') +const { isProduction } = require('../lib/env') + + +module.exports = app => { + if (isProduction) { + return + } + const project = resolveProject() + oapi.document.info.title = project.artifact + oapi.document.info.version = project.version + + app.use(oapi) + app.use('/swagger-ui', oapi.swaggerui) +} diff --git a/expressjs/src/middlewares/prometeus.js b/expressjs/src/middleware/prometeus.js similarity index 100% rename from expressjs/src/middlewares/prometeus.js rename to expressjs/src/middleware/prometeus.js diff --git a/expressjs/src/middleware/public.js b/expressjs/src/middleware/public.js new file mode 100644 index 0000000..ce9aeb9 --- /dev/null +++ b/expressjs/src/middleware/public.js @@ -0,0 +1,8 @@ +const express = require('express') + +/** + * @param {express.Express} app the app + */ +module.exports = app => { + app.use(express.static('public', { index: false })) +} diff --git a/expressjs/src/middlewares/handlebars.js b/expressjs/src/middlewares/handlebars.js deleted file mode 100644 index 7646973..0000000 --- a/expressjs/src/middlewares/handlebars.js +++ /dev/null @@ -1,6 +0,0 @@ -const { engine } = require('express-handlebars') - -module.exports = app => { - app.engine('handlebars', engine()) - app.set('view engine', 'handlebars') -} diff --git a/expressjs/src/middlewares/openapi.js b/expressjs/src/middlewares/openapi.js deleted file mode 100644 index 92ff703..0000000 --- a/expressjs/src/middlewares/openapi.js +++ /dev/null @@ -1,22 +0,0 @@ -const openapi = require('@unleash/express-openapi') -const { resolveProject } = require('../services/project') - -const oapi = openapi({ - openapi: '3.0.0', - info: { - description: 'Knative Showcase for JS', - } -}) - -module.exports = { - middleware: async app => { - const project = await resolveProject() - oapi.document.info.title = project.artifact - oapi.document.info.version = project.version - app.use(oapi) - if (process.env.NODE_ENV !== 'production') { - app.use('/swagger-ui', oapi.swaggerui) - } - }, - oapi -} diff --git a/expressjs/src/middlewares/public.js b/expressjs/src/middlewares/public.js deleted file mode 100644 index 9973a14..0000000 --- a/expressjs/src/middlewares/public.js +++ /dev/null @@ -1,5 +0,0 @@ -const express = require('express') - -module.exports = app => { - app.use(express.static('public')) -} diff --git a/expressjs/src/routes/events/devdata.js b/expressjs/src/routes/events/devdata.js new file mode 100644 index 0000000..9dd3787 --- /dev/null +++ b/expressjs/src/routes/events/devdata.js @@ -0,0 +1,22 @@ +const { CloudEvent } = require('cloudevents') +const { isProduction } = require('../../lib/env') + +const random = (min, max) => Math.floor(Math.random() * (max - min + 1) + min) + +function newEvent() { + return new CloudEvent({ + type: 'com.redhat.openshift.knative.showcase.Hello', + source: '//localhost/dev', + datacontenttype: 'application/json', + time: new Date(random(1640991600000, 1678446163750)).toISOString(), + data: { + greeting: 'Welcome', + who: `Developer${random(0, 100)}`, + number: random(0, 100), + } + }) +} + +const devdata = [newEvent(), newEvent()].filter(() => !isProduction) + +module.exports = devdata diff --git a/expressjs/src/routes/events/endpoint.js b/expressjs/src/routes/events/endpoint.js new file mode 100644 index 0000000..0d23ef8 --- /dev/null +++ b/expressjs/src/routes/events/endpoint.js @@ -0,0 +1,98 @@ +const { HTTP } = require('cloudevents') +const openapi = require('../../lib/openapi') +const EventStore = require('./store') +const devdata = require('./devdata') + +const store = new EventStore() +devdata.forEach(event => store.add(event)) + +module.exports = async app => { + app.get('/events', streamDoc, async (_, res) => { + res.set('Content-Type', 'text/event-stream') + res.set('Cache-Control', 'no-cache') + res.set('Connection', 'keep-alive') + res.set('X-SSE-Content-Type', 'application/cloudevents+json') + res.set('transfer-encoding', 'chunked') + res.flushHeaders() + + const stream = store.createStream(res) + stream.stream() + }) + + app.post('/events', eventDoc, async (req, res) => { + try { + const ce = HTTP.toEvent({ headers: req.headers, body: req.body }) + store.add(ce) + res.status(201).end() + } catch (err) { + console.error(err) + res.status(500) + res.json(err) + } + }) +} + +const streamDoc = openapi.path({ + summary: 'Retrieves all registered events as a JSON stream', + responses: { + 200: { + headers: { + 'X-SSE-Content-Type': { + description: 'for example: application/json', + schema: { + type: 'string', + } + }, + 'transfer-encoding': { + description: 'value: chunked', + schema: { + type: 'string', + } + }, + 'content-type': { + description: 'value: text/event-stream', + schema: { + type: 'string', + } + } + }, + content: { + 'text/event-stream': { + example: `data:{ + "data": { + "score": 140 + }, + "datacontenttype": "application/json", + "id": "2", + "source": "//localhost/dev", + "specversion": "1.0", + "type": "com.redhat.openshift.knative.showcase.Score" +}` + } + } + } + } +}) + +const eventDoc = openapi.validPath({ + summary: 'Receives a CloudEvent and stores it', + requestBody: { + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/CloudEvent' + } + }, + 'application/cloudevents+json': { + schema: { + $ref: '#/components/schemas/CloudEvent' + } + } + } + }, + responses: { + 201: { + description: 'Created' + } + } +}) diff --git a/expressjs/src/routes/events/store.js b/expressjs/src/routes/events/store.js new file mode 100644 index 0000000..e72d786 --- /dev/null +++ b/expressjs/src/routes/events/store.js @@ -0,0 +1,59 @@ +const { HTTP } = require('cloudevents') + +class EventStore { + constructor() { + this.events = [] + } + + add(event) { + this.events.push(event) + } + + createStream(response) { + return new Stream(response, this.events) + } +} + +class Stream { + constructor(response, events) { + this.idx = 0 + this.active = true + this.events = events + this.res = response + } + + close() { + this.active = false + this.res.end() + } + + stream() { + this.send() + if (this.active) { + setTimeout(() => this.stream(), 100) + } + } + + send() { + while (this.hasNext()) { + if (!this.active) { + return + } + const curr = this.next() + const msg = HTTP.structured(curr) + this.res.write(`data:${msg.body}\n\n`) + } + } + + next() { + if (this.hasNext()) { + return this.events[this.idx++] + } + } + + hasNext() { + return this.idx < this.events.length + } +} + +module.exports = EventStore diff --git a/expressjs/src/routes/hello.js b/expressjs/src/routes/hello/endpoint.js similarity index 92% rename from expressjs/src/routes/hello.js rename to expressjs/src/routes/hello/endpoint.js index 02638a4..cce1e84 100644 --- a/expressjs/src/routes/hello.js +++ b/expressjs/src/routes/hello/endpoint.js @@ -1,11 +1,11 @@ -const { Greeter } = require('../services/greeter') -const { oapi } = require('../middlewares/openapi') +const openapi = require('../../lib/openapi') +const { Greeter } = require('./greeter') module.exports = app => { app.get('/hello', doc(), async (req, res) => { // Success - const greeter = new Greeter(require('../services/config')) + const greeter = new Greeter(require('../../lib/config')) const who = req.query.who || 'Person' const hello = await greeter.hello(who) @@ -24,7 +24,7 @@ module.exports = app => { } function doc() { - return oapi.validPath({ + return openapi.validPath({ summary: 'Say hello with a Greeter', description: 'Greeting can be changed by setting environment variable GREET', parameters: [ diff --git a/expressjs/src/services/greeter.js b/expressjs/src/routes/hello/greeter.js similarity index 93% rename from expressjs/src/services/greeter.js rename to expressjs/src/routes/hello/greeter.js index e52168c..37d3485 100644 --- a/expressjs/src/services/greeter.js +++ b/expressjs/src/routes/hello/greeter.js @@ -1,4 +1,4 @@ -const { Hello } = require('../domain/entity/hello') +const { Hello } = require('./hello') const { Notifier } = require('./notifier') let number = 0 diff --git a/expressjs/src/domain/entity/hello.js b/expressjs/src/routes/hello/hello.js similarity index 100% rename from expressjs/src/domain/entity/hello.js rename to expressjs/src/routes/hello/hello.js diff --git a/expressjs/src/services/notifier.js b/expressjs/src/routes/hello/notifier.js similarity index 100% rename from expressjs/src/services/notifier.js rename to expressjs/src/routes/hello/notifier.js diff --git a/expressjs/src/routes/home/endpoint.js b/expressjs/src/routes/home/endpoint.js new file mode 100644 index 0000000..e2c12f2 --- /dev/null +++ b/expressjs/src/routes/home/endpoint.js @@ -0,0 +1,92 @@ +const config = require('../../lib/config') +const oapi = require('../../lib/openapi') +const { resolveProject } = require('../../lib/project') + +/** + * @param {express.Express} app + */ +const home = app => { + app.get('/', doc, async (req, res) => { + try { + const project = await resolveProject() + const idx = new Index({ + artifact: project.artifact, + greeting: config.greet(), + }) + addResponseHeaders(res, project) + if (shouldRenderJson(req)) { + return res.json(idx) + } + res.status(200) + .sendFile('home.html', { root: 'public' }) + } catch (err) { + console.error(err) + res.status(500) + res.json(err) + } + }) +} + +class Index { + constructor({ artifact, greeting }) { + + /** + * @type {string} + */ + this.artifact = artifact + + /** + * @type {string} + */ + this.greeting = greeting + } +} + +/** + * Adds response headers + * + * @param {*} res the response object + * @param {Project} project holds project info + */ +function addResponseHeaders(res, project) { + const { sink, greet, delay } = config + res.header('Server', project.platform) + .header('X-Version', project.version) + .header('X-Config', JSON.stringify({ + sink: sink(), + greet: greet(), + delay: delay(), + })) +} + +function shouldRenderJson(req) { + const ua = req.header('user-agent') + if (ua && ua.startsWith('Mozilla')) { + return false + } + return req.accepts('application/json') +} + +const doc = oapi.path({ + summary: 'Displays a home page, or the short project info', + description: 'Displays a index HTML page, or the project info in ' + + 'JSON format if the Accept header is set to application/json or called ' + + 'not from a browser', + responses: { + 200: { + content: { + 'text/html': { + example: '\n\n[...]' + }, + 'application/json': { + example: { + artifact: 'knative-showcase', + greeting: 'Welcome', + } + } + } + } + } +}) + +module.exports = home diff --git a/expressjs/src/routes/index.js b/expressjs/src/routes/index.js deleted file mode 100644 index 38b5b49..0000000 --- a/expressjs/src/routes/index.js +++ /dev/null @@ -1,126 +0,0 @@ -const { oapi } = require('../middlewares/openapi') -const { resolveProject } = require('../services/project') -const config = require('../services/config') - -/** - * @param {express.Express} app - */ -module.exports = app => { - app.get('/', getDoc(), async (req, res) => { - try { - const project = await resolveProject() - const idx = new Index({ - artifact: project.artifact, - greeting: config.greet(), - }) - addResponseHeaders(res, project) - if (shouldRenderJson(req)) { - return res.json(idx) - } - const data = { project, config } - res.render('index', data) - } catch (err) { - console.error(err) - res.status(500) - res.json(err) - } - }) - - app.options('/', optionsDoc(), async (_, res) => { - try { - const project = await resolveProject() - const idx = new Index({ - artifact: project.artifact, - greeting: config.greet(), - }) - addResponseHeaders(res, project) - res.json(idx) - } catch (err) { - console.error(err) - res.status(500) - res.json(err) - } - }) -} - -class Index { - constructor({ artifact, greeting }) { - - /** - * @type {string} - */ - this.artifact = artifact - - /** - * @type {string} - */ - this.greeting = greeting - } -} - -/** - * Adds response headers - * - * @param {*} res the response object - * @param {Project} project holds project info - */ -function addResponseHeaders(res, project) { - const { sink, greet, delay } = config - res.header('Server', project.platform) - .header('X-Version', project.version) - .header('X-Config', JSON.stringify({ - sink: sink(), - greet: greet(), - delay: delay(), - })) -} - -function shouldRenderJson(req) { - const ua = req.header('user-agent') - if (ua && ua.startsWith('Mozilla')) { - return false - } - return req.accepts('application/json') -} - -function getDoc() { - return oapi.path({ - summary: 'Main index page', - description: 'A human readable welcome page with project info', - responses: { - 200: { - content: { - 'text/html': { - example: '\n\n[...]' - } - } - } - } - }) -} - -function optionsDoc() { - return oapi.path({ - summary: 'Project info', - description: 'Machine readable (JSON) information about project', - responses: { - 200: { - content: { - 'application/json': { - schema: { - type: 'object', - properties: { - greeting: { type: 'string' }, - artifact: { type: 'string' } - }, - example: { - greeting: 'Welcome', - artifact: 'knative-showcase' - } - } - } - } - } - } - }) -} diff --git a/expressjs/src/routes/info/endpoint.js b/expressjs/src/routes/info/endpoint.js new file mode 100644 index 0000000..9e960d9 --- /dev/null +++ b/expressjs/src/routes/info/endpoint.js @@ -0,0 +1,54 @@ +const config = require('../../lib/config') +const openapi = require('../../lib/openapi') +const { resolveProject } = require('../../lib/project') +const Info = require('./info') + +/** + * @param {express.Express} app + */ +module.exports = app => { + app.get('/info', doc, async (_, res) => { + try { + const project = await resolveProject() + const info = new Info({ + project, config: { + sink: config.sink(), + greet: config.greet(), + delay: config.delay() + } + }) + res.status(200) + res.json(info) + } catch (err) { + console.error(err) + res.status(500) + res.json(err) + } + }) +} + +const doc = openapi.path({ + summary: 'Retrives info about project', + description: 'Information about project like coordinates and versions', + responses: { + 200: { + content: { + 'application/json': { + example: { + config: { + delay: 0, + greet: 'Welcome', + sink: 'http://localhost:31111' + }, + project: { + artifact: 'knative-showcase', + group: 'openshift', + platform: 'Express/4.18.2 Node/18.14.2', + version: 'v0.5.0-50-g730a719-dirty' + } + } + } + } + } + } +}) diff --git a/expressjs/src/routes/info/info.js b/expressjs/src/routes/info/info.js new file mode 100644 index 0000000..0796f3e --- /dev/null +++ b/expressjs/src/routes/info/info.js @@ -0,0 +1,16 @@ +class Info { + constructor({ project, config }) { + + /** + * @type {Project} + */ + this.project = project + + /** + * @type {Config} + */ + this.config = config + } +} + +module.exports = Info diff --git a/expressjs/src/scripts/ensure-versions.js b/expressjs/src/scripts/ensure-versions.js deleted file mode 100644 index e65a9c6..0000000 --- a/expressjs/src/scripts/ensure-versions.js +++ /dev/null @@ -1,19 +0,0 @@ -const { resolveProject } = require('../services/project') -const fs = require('fs').promises -const path = require('path') - -function ensureVersions() { - doEnsureVersions() - .catch(console.error) -} - -async function doEnsureVersions() { - const project = await resolveProject() - const packageJson = require('../../package.json') - packageJson.version = project.versionForNpm() - - const content = JSON.stringify(packageJson, null, 2) - fs.writeFile(path.resolve(__dirname, '..', '..', 'package.json'), `${content}\n`) -} - -module.exports = ensureVersions diff --git a/expressjs/test/config/project.test.js b/expressjs/test/config/project.test.js new file mode 100644 index 0000000..b216168 --- /dev/null +++ b/expressjs/test/config/project.test.js @@ -0,0 +1,13 @@ +const { resolveProject } = require('../../src/lib/project') + +describe('project', () => { + it('resolve', async () => { + const project = await resolveProject() + expect(project.group).toEqual('openshift') + expect(project.artifact).toEqual('knative-showcase') + // ref: https://regex101.com/r/SqE4A0/1 + expect(project.version).toMatch(/^v\d+\.\d+\.\d+(?:-\d+-g[0-9a-f]{7}(?:-dirty)?)?$/) + expect(project.platform).toMatch(/^Express\/\d+\.\d+\.\d+ Node\/\d+\.\d+\.\d+$/) + }) +}) + diff --git a/expressjs/test/routes/health.test.js b/expressjs/test/middleware/health.test.js similarity index 87% rename from expressjs/test/routes/health.test.js rename to expressjs/test/middleware/health.test.js index 3d612da..8a8f98b 100644 --- a/expressjs/test/routes/health.test.js +++ b/expressjs/test/middleware/health.test.js @@ -1,7 +1,8 @@ const request = require('supertest') +const createApp = require('../../src/app') describe('Route', () => { - const app = require('../../src/app').createApp() + const app = createApp() it('GET /health/ready', async () => { await request(await app) .get('/health/ready') diff --git a/expressjs/test/routes/metrics.test.js b/expressjs/test/middleware/metrics.test.js similarity index 74% rename from expressjs/test/routes/metrics.test.js rename to expressjs/test/middleware/metrics.test.js index 038d172..0c15f31 100644 --- a/expressjs/test/routes/metrics.test.js +++ b/expressjs/test/middleware/metrics.test.js @@ -1,8 +1,9 @@ const request = require('supertest') +const createApp = require('../../src/app') describe('Route', () => { it('GET /metrics', async () => { - const app = await require('../../src/app').createApp() + const app = await createApp() await request(app) .get('/metrics') .expect('Content-Type', /text\/plain/) diff --git a/expressjs/test/routes/openapi.test.js b/expressjs/test/middleware/openapi.test.js similarity index 83% rename from expressjs/test/routes/openapi.test.js rename to expressjs/test/middleware/openapi.test.js index b14861d..225f20b 100644 --- a/expressjs/test/routes/openapi.test.js +++ b/expressjs/test/middleware/openapi.test.js @@ -1,7 +1,8 @@ const request = require('supertest') +const createApp = require('../../src/app') describe('Route', () => { - const app = require('../../src/app').createApp() + const app = createApp() it('GET /swagger-ui/', async () => { await request(await app) .get('/swagger-ui/') @@ -19,9 +20,11 @@ describe('Route', () => { expect(res.body.info.description).toEqual('Knative Showcase for JS') expect(Object.keys(res.body.paths).sort()).toEqual([ '/', + '/events', '/hello', '/health/live', - '/health/ready' + '/health/ready', + '/info' ].sort()) }) }) diff --git a/expressjs/test/routes/hello.test.js b/expressjs/test/routes/hello/endpoint.test.js similarity index 92% rename from expressjs/test/routes/hello.test.js rename to expressjs/test/routes/hello/endpoint.test.js index 848bf99..9ae4328 100644 --- a/expressjs/test/routes/hello.test.js +++ b/expressjs/test/routes/hello/endpoint.test.js @@ -1,8 +1,9 @@ const request = require('supertest') const nock = require('nock') +const createApp = require('../../../src/app') describe('Route', () => { - const app = require('../../src/app').createApp() + const app = createApp() it('GET /hello?who=James', async () => { let counter = 0 jest.spyOn(global.console, 'log').mockImplementation(() => jest.fn()) diff --git a/expressjs/test/services/greeter.test.js b/expressjs/test/routes/hello/greeter.test.js similarity index 94% rename from expressjs/test/services/greeter.test.js rename to expressjs/test/routes/hello/greeter.test.js index 51f30ab..f5f3306 100644 --- a/expressjs/test/services/greeter.test.js +++ b/expressjs/test/routes/hello/greeter.test.js @@ -2,7 +2,7 @@ const nock = require('nock') const freePort = require('get-port') const { HTTP } = require('cloudevents') -const { Greeter } = require('../../src/services/greeter') +const { Greeter } = require('../../../src/routes/hello/greeter') const mockUrl = port => `http://localhost:${port}/` let counter = 0 diff --git a/expressjs/test/routes/index.test.js b/expressjs/test/routes/index/endpoint.test.js similarity index 71% rename from expressjs/test/routes/index.test.js rename to expressjs/test/routes/index/endpoint.test.js index 43edc89..2a86d3b 100644 --- a/expressjs/test/routes/index.test.js +++ b/expressjs/test/routes/index/endpoint.test.js @@ -1,14 +1,8 @@ const request = require('supertest') +const createApp = require('../../../src/app') describe('Route', () => { - const app = require('../../src/app').createApp() - it('OPTIONS /', async () => { - await request(await app) - .options('/') - .expect('Content-Type', /application\/json/) - .expect(200) - }) - + const app = createApp() it('GET /', async () => { await request(await app) .get('/') diff --git a/expressjs/test/services/project.test.js b/expressjs/test/services/project.test.js deleted file mode 100644 index 0c750b9..0000000 --- a/expressjs/test/services/project.test.js +++ /dev/null @@ -1,39 +0,0 @@ -const { resolveProject } = require('../../src/services/project') -const semver = require('semver') - -expect.extend({ - toMatchGitDescribeVersion(v1, v2) { - let projectVersion = new semver.SemVer(v1) - let gitDescribe = new semver.SemVer(v2) - if (projectVersion.prerelease.length === 1 && projectVersion.prerelease[0] === 'pre') { - projectVersion = semver.coerce(projectVersion.toString()) - } - if (gitDescribe.prerelease.length > 0) { - gitDescribe = semver.coerce(gitDescribe.toString()).inc('patch') - } - const pass = semver.eq(projectVersion, gitDescribe) - const message = () => - `expected ${v1} to match GIT describe version of ${v2}` - return { message, pass } - }, -}) - -describe('project', () => { - it('resolve', async () => { - const project = await resolveProject() - expect(project.group).toEqual('openshift') - expect(project.artifact).toEqual('knative-showcase') - // ref: https://regex101.com/r/SqE4A0/1 - expect(project.version).toMatch(/^v\d+\.\d+\.\d+(?:-\d+-g[0-9a-f]{7}(?:-dirty)?)?$/) - expect(project.platform.node).toMatch(/^\d+\.\d+\.\d+$/) - expect(project.platform.express).toMatch(/^\d+\.\d+\.\d+$/) - }) - - it('ensure Npm version matches git describe', async () => { - const projectJson = require('../../package.json') - const project = await resolveProject() - // Run `npm run dev:version` to update the version in package.json - expect(projectJson.version).toEqual(project.versionForNpm()) - }) -}) - diff --git a/expressjs/views/index.handlebars b/expressjs/views/index.handlebars deleted file mode 100644 index 2e742d3..0000000 --- a/expressjs/views/index.handlebars +++ /dev/null @@ -1,53 +0,0 @@ - - - - - {{ project.artifact }} - {{ project.version }} - - - - - - - -
-
-

What can I do from here?

- -

- Invoke a hello endpoint: /hello.
- 💡 It will send CloudEvent to K_SINK = {{ config.sink }} -

-

Powered by:

-
- - - -
-

This application has been written with Node.js to showcase Knative.

-
-
-
-

Application

-
    -
  • Group: {{ project.group }}
  • -
  • Artifact: {{ project.artifact }}
  • -
  • Version: {{ project.version }}
  • -
  • Platform: {{ project.platform }}
  • -
-
-
-
- - - - diff --git a/expressjs/views/layouts/main.handlebars b/expressjs/views/layouts/main.handlebars deleted file mode 100644 index 38e5499..0000000 --- a/expressjs/views/layouts/main.handlebars +++ /dev/null @@ -1 +0,0 @@ -{{{body}}} diff --git a/frontend/Makefile b/frontend/Makefile index 5358a41..ba0ae30 100644 --- a/frontend/Makefile +++ b/frontend/Makefile @@ -14,5 +14,6 @@ webjar: clean: rm -rfv build + rm -rf node_modules .PHONY: default test build clean deps diff --git a/frontend/src/config/backend.ts b/frontend/src/config/backend.ts index 732d743..91255e1 100644 --- a/frontend/src/config/backend.ts +++ b/frontend/src/config/backend.ts @@ -1,10 +1,11 @@ const nodeenv = process.env.NODE_ENV ?? 'development' +const isProduction = nodeenv === 'production' -const shouldStubBackend = process.env.REACT_APP_BACKEND === undefined && ( - nodeenv === 'development' || nodeenv === 'test' -) +const shouldStubBackend = !isProduction && + process.env.REACT_APP_BACKEND === undefined -const baseAddress = process.env.BACKEND ?? 'http://localhost:8080' +const defaultBackend = isProduction ? '' : 'http://localhost:8080' +const baseAddress = process.env.BACKEND ?? defaultBackend export { shouldStubBackend,