diff --git a/webapp/angular.json b/webapp/angular.json
index 57a14b27..ece95c1f 100644
--- a/webapp/angular.json
+++ b/webapp/angular.json
@@ -56,7 +56,12 @@
"styles": [
"src/styles.css"
],
- "scripts": []
+ "scripts": [],
+ "server": "src/main.server.ts",
+ "prerender": true,
+ "ssr": {
+ "entry": "server.ts"
+ }
},
"configurations": {
"production": {
diff --git a/webapp/package-lock.json b/webapp/package-lock.json
index 33d417cf..e306b3f2 100644
--- a/webapp/package-lock.json
+++ b/webapp/package-lock.json
@@ -16,7 +16,9 @@
"@angular/forms": "18.2.1",
"@angular/platform-browser": "18.2.1",
"@angular/platform-browser-dynamic": "18.2.1",
+ "@angular/platform-server": "18.2.1",
"@angular/router": "18.2.1",
+ "@angular/ssr": "^18.2.1",
"@ng-icons/core": "29.5.0",
"@ng-icons/lucide": "^26.3.0",
"@ng-icons/octicons": "29.5.0",
@@ -51,6 +53,7 @@
"clsx": "2.1.1",
"dayjs": "1.11.13",
"embla-carousel-angular": "^14.0.0",
+ "express": "^4.18.2",
"keycloak-js": "^26.0.0",
"lucide-angular": "0.429.0",
"ngx-scrollbar": "^13.0.1",
@@ -78,7 +81,9 @@
"@storybook/angular": "8.3.4",
"@storybook/blocks": "8.3.4",
"@storybook/test": "8.3.4",
+ "@types/express": "^4.17.17",
"@types/jasmine": "5.1.4",
+ "@types/node": "^18.18.0",
"@typescript-eslint/eslint-plugin": "8.2.0",
"@typescript-eslint/parser": "8.2.0",
"chromatic": "11.7.1",
@@ -993,6 +998,26 @@
"@angular/platform-browser": "18.2.1"
}
},
+ "node_modules/@angular/platform-server": {
+ "version": "18.2.1",
+ "resolved": "https://registry.npmjs.org/@angular/platform-server/-/platform-server-18.2.1.tgz",
+ "integrity": "sha512-xU/7EGYk/HXAY2V7VEzBx4YcVQe3rPuojXPubdgKJ8ueQ7XVtwumv/LHM72/Yn8ChvYYaoGLtM7nI2rG1MVAag==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.3.0",
+ "xhr2": "^0.2.0"
+ },
+ "engines": {
+ "node": "^18.19.1 || ^20.11.1 || >=22.0.0"
+ },
+ "peerDependencies": {
+ "@angular/animations": "18.2.1",
+ "@angular/common": "18.2.1",
+ "@angular/compiler": "18.2.1",
+ "@angular/core": "18.2.1",
+ "@angular/platform-browser": "18.2.1"
+ }
+ },
"node_modules/@angular/router": {
"version": "18.2.1",
"resolved": "https://registry.npmjs.org/@angular/router/-/router-18.2.1.tgz",
@@ -1010,6 +1035,20 @@
"rxjs": "^6.5.3 || ^7.4.0"
}
},
+ "node_modules/@angular/ssr": {
+ "version": "18.2.10",
+ "resolved": "https://registry.npmjs.org/@angular/ssr/-/ssr-18.2.10.tgz",
+ "integrity": "sha512-iCplUCLpOnEjMpcStNsH3LovYdGwR7qbTB5FnYwCUGk0VY8nwukB2oUO3h3jFRHtbA6BSbr5NIxdORKGhGhhoQ==",
+ "license": "MIT",
+ "dependencies": {
+ "critters": "0.0.24",
+ "tslib": "^2.3.0"
+ },
+ "peerDependencies": {
+ "@angular/common": "^18.0.0",
+ "@angular/core": "^18.0.0"
+ }
+ },
"node_modules/@babel/code-frame": {
"version": "7.25.7",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.7.tgz",
@@ -3668,6 +3707,16 @@
"node": ">=18"
}
},
+ "node_modules/@inquirer/core/node_modules/@types/node": {
+ "version": "22.8.4",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.4.tgz",
+ "integrity": "sha512-SpNNxkftTJOPk0oN+y2bIqurEXHTA2AOZ3EJDDKeJ5VzkvvORSvmQXGQarcOzWV1ac7DCaPBEdMDxBsM+d8jWw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.19.8"
+ }
+ },
"node_modules/@inquirer/core/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
@@ -3680,6 +3729,13 @@
"node": ">=8"
}
},
+ "node_modules/@inquirer/core/node_modules/undici-types": {
+ "version": "6.19.8",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
+ "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@inquirer/editor": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-2.2.0.tgz",
@@ -7276,6 +7332,16 @@
"storybook": "^8.3.4"
}
},
+ "node_modules/@storybook/angular/node_modules/@types/node": {
+ "version": "22.8.4",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.4.tgz",
+ "integrity": "sha512-SpNNxkftTJOPk0oN+y2bIqurEXHTA2AOZ3EJDDKeJ5VzkvvORSvmQXGQarcOzWV1ac7DCaPBEdMDxBsM+d8jWw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.19.8"
+ }
+ },
"node_modules/@storybook/angular/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -7342,6 +7408,13 @@
"node": ">=10.13.0"
}
},
+ "node_modules/@storybook/angular/node_modules/undici-types": {
+ "version": "6.19.8",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
+ "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@storybook/blocks": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/@storybook/blocks/-/blocks-8.3.4.tgz",
@@ -7430,6 +7503,16 @@
}
}
},
+ "node_modules/@storybook/builder-webpack5/node_modules/@types/node": {
+ "version": "22.8.4",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.4.tgz",
+ "integrity": "sha512-SpNNxkftTJOPk0oN+y2bIqurEXHTA2AOZ3EJDDKeJ5VzkvvORSvmQXGQarcOzWV1ac7DCaPBEdMDxBsM+d8jWw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.19.8"
+ }
+ },
"node_modules/@storybook/builder-webpack5/node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@@ -7675,6 +7758,13 @@
"node": ">=8"
}
},
+ "node_modules/@storybook/builder-webpack5/node_modules/undici-types": {
+ "version": "6.19.8",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
+ "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@storybook/builder-webpack5/node_modules/webpack-dev-middleware": {
"version": "6.1.3",
"resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-6.1.3.tgz",
@@ -7748,6 +7838,23 @@
"storybook": "^8.3.4"
}
},
+ "node_modules/@storybook/core-webpack/node_modules/@types/node": {
+ "version": "22.8.4",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.4.tgz",
+ "integrity": "sha512-SpNNxkftTJOPk0oN+y2bIqurEXHTA2AOZ3EJDDKeJ5VzkvvORSvmQXGQarcOzWV1ac7DCaPBEdMDxBsM+d8jWw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.19.8"
+ }
+ },
+ "node_modules/@storybook/core-webpack/node_modules/undici-types": {
+ "version": "6.19.8",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
+ "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@storybook/csf": {
"version": "0.1.11",
"resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.1.11.tgz",
@@ -8344,12 +8451,13 @@
}
},
"node_modules/@types/node": {
- "version": "22.7.4",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.4.tgz",
- "integrity": "sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==",
+ "version": "18.19.61",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.61.tgz",
+ "integrity": "sha512-z8fH66NcVkDzBItOao+Nyh0fiy7CYdxIyxnNCcZ60aY0I+EA/y4TSi/S/W9i8DIQvwVo7a0pgzAxmDeNnqrpkw==",
"devOptional": true,
+ "license": "MIT",
"dependencies": {
- "undici-types": "~6.19.2"
+ "undici-types": "~5.26.4"
}
},
"node_modules/@types/node-forge": {
@@ -9246,7 +9354,6 @@
"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==",
- "dev": true,
"dependencies": {
"mime-types": "~2.1.34",
"negotiator": "0.6.3"
@@ -9547,8 +9654,7 @@
"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==",
- "dev": true
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
},
"node_modules/array-union": {
"version": "2.1.0",
@@ -10041,7 +10147,6 @@
"version": "1.20.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
"integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
- "dev": true,
"dependencies": {
"bytes": "3.1.2",
"content-type": "~1.0.5",
@@ -10065,7 +10170,6 @@
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "dev": true,
"dependencies": {
"ms": "2.0.0"
}
@@ -10073,8 +10177,7 @@
"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==",
- "dev": true
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/bonjour-service": {
"version": "1.2.1",
@@ -10089,8 +10192,7 @@
"node_modules/boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
- "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
- "dev": true
+ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
},
"node_modules/brace-expansion": {
"version": "2.0.1",
@@ -10209,7 +10311,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
- "dev": true,
"engines": {
"node": ">= 0.8"
}
@@ -10295,7 +10396,6 @@
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
"integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
- "dev": true,
"dependencies": {
"es-define-property": "^1.0.0",
"es-errors": "^1.3.0",
@@ -10992,7 +11092,6 @@
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
- "dev": true,
"dependencies": {
"safe-buffer": "5.2.1"
},
@@ -11004,7 +11103,6 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
- "dev": true,
"engines": {
"node": ">= 0.6"
}
@@ -11019,7 +11117,6 @@
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
- "dev": true,
"engines": {
"node": ">= 0.6"
}
@@ -11027,8 +11124,7 @@
"node_modules/cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
- "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
- "dev": true
+ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
},
"node_modules/cookies": {
"version": "0.9.1",
@@ -11202,7 +11298,6 @@
"version": "0.0.24",
"resolved": "https://registry.npmjs.org/critters/-/critters-0.0.24.tgz",
"integrity": "sha512-Oyqew0FGM0wYUSNqR0L6AteO5MpMoUU0rhKRieXeiKs+PmRTxiJMyaunYB2KF6fQ3dzChXKCpbFOEJx3OQ1v/Q==",
- "dev": true,
"dependencies": {
"chalk": "^4.1.0",
"css-select": "^5.1.0",
@@ -11217,7 +11312,6 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
"dependencies": {
"color-convert": "^2.0.1"
},
@@ -11232,7 +11326,6 @@
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "dev": true,
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
@@ -11248,7 +11341,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true,
"engines": {
"node": ">=8"
}
@@ -11257,7 +11349,6 @@
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
"dependencies": {
"has-flag": "^4.0.0"
},
@@ -11385,7 +11476,6 @@
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
"integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
- "dev": true,
"dependencies": {
"boolbase": "^1.0.0",
"css-what": "^6.1.0",
@@ -11414,7 +11504,6 @@
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
"integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
- "dev": true,
"engines": {
"node": ">= 6"
},
@@ -11679,7 +11768,6 @@
"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==",
- "dev": true,
"dependencies": {
"es-define-property": "^1.0.0",
"es-errors": "^1.3.0",
@@ -11723,7 +11811,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
- "dev": true,
"engines": {
"node": ">= 0.8"
}
@@ -11742,7 +11829,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
- "dev": true,
"engines": {
"node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
@@ -11870,7 +11956,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
- "dev": true,
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.2",
@@ -11884,7 +11969,6 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -11896,7 +11980,6 @@
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
- "dev": true,
"dependencies": {
"domelementtype": "^2.3.0"
},
@@ -11911,7 +11994,6 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
"integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
- "dev": true,
"dependencies": {
"dom-serializer": "^2.0.0",
"domelementtype": "^2.3.0",
@@ -11972,8 +12054,7 @@
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
- "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
- "dev": true
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
},
"node_modules/ejs": {
"version": "3.1.10",
@@ -12041,7 +12122,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
- "dev": true,
"engines": {
"node": ">= 0.8"
}
@@ -12158,7 +12238,6 @@
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
- "devOptional": true,
"engines": {
"node": ">=0.12"
},
@@ -12219,7 +12298,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
- "dev": true,
"dependencies": {
"get-intrinsic": "^1.2.4"
},
@@ -12231,7 +12309,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
- "dev": true,
"engines": {
"node": ">= 0.4"
}
@@ -12317,8 +12394,7 @@
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
- "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
- "dev": true
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
},
"node_modules/escape-string-regexp": {
"version": "1.0.5",
@@ -12688,7 +12764,6 @@
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
- "dev": true,
"engines": {
"node": ">= 0.6"
}
@@ -12774,7 +12849,6 @@
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz",
"integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==",
- "dev": true,
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
@@ -12816,7 +12890,6 @@
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "dev": true,
"dependencies": {
"ms": "2.0.0"
}
@@ -12824,8 +12897,7 @@
"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==",
- "dev": true
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/extend": {
"version": "3.0.2",
@@ -13005,7 +13077,6 @@
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
"integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
- "dev": true,
"dependencies": {
"debug": "2.6.9",
"encodeurl": "~2.0.0",
@@ -13023,7 +13094,6 @@
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "dev": true,
"dependencies": {
"ms": "2.0.0"
}
@@ -13031,8 +13101,7 @@
"node_modules/finalhandler/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
- "dev": true
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/find-cache-dir": {
"version": "3.3.2",
@@ -13369,7 +13438,6 @@
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
- "dev": true,
"engines": {
"node": ">= 0.6"
}
@@ -13390,7 +13458,6 @@
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
- "dev": true,
"engines": {
"node": ">= 0.6"
}
@@ -13542,7 +13609,6 @@
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
- "dev": true,
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2",
@@ -13717,7 +13783,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
- "dev": true,
"dependencies": {
"get-intrinsic": "^1.1.3"
},
@@ -13756,7 +13821,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
- "dev": true,
"dependencies": {
"es-define-property": "^1.0.0"
},
@@ -13768,7 +13832,6 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
"integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
- "dev": true,
"engines": {
"node": ">= 0.4"
},
@@ -13780,7 +13843,6 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
- "dev": true,
"engines": {
"node": ">= 0.4"
},
@@ -14028,7 +14090,6 @@
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
"integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
- "dev": true,
"funding": [
"https://github.com/fb55/htmlparser2?sponsor=1",
{
@@ -14106,7 +14167,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
- "dev": true,
"dependencies": {
"depd": "2.0.0",
"inherits": "2.0.4",
@@ -14282,7 +14342,6 @@
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
- "dev": true,
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3"
},
@@ -14410,8 +14469,7 @@
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "dev": true
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"node_modules/ini": {
"version": "4.1.3",
@@ -14439,7 +14497,6 @@
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
- "dev": true,
"engines": {
"node": ">= 0.10"
}
@@ -16467,7 +16524,6 @@
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
- "dev": true,
"engines": {
"node": ">= 0.6"
}
@@ -16497,7 +16553,6 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
- "dev": true,
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
@@ -16520,7 +16575,6 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
- "dev": true,
"engines": {
"node": ">= 0.6"
}
@@ -16552,7 +16606,6 @@
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
- "dev": true,
"bin": {
"mime": "cli.js"
},
@@ -16564,7 +16617,6 @@
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
- "dev": true,
"engines": {
"node": ">= 0.6"
}
@@ -16573,7 +16625,6 @@
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
- "dev": true,
"dependencies": {
"mime-db": "1.52.0"
},
@@ -16844,8 +16895,7 @@
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "dev": true
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/msgpackr": {
"version": "1.11.0",
@@ -16967,7 +17017,6 @@
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
- "dev": true,
"engines": {
"node": ">= 0.6"
}
@@ -17340,7 +17389,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
- "dev": true,
"dependencies": {
"boolbase": "^1.0.0"
},
@@ -17659,7 +17707,6 @@
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz",
"integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==",
- "dev": true,
"engines": {
"node": ">= 0.4"
},
@@ -17677,7 +17724,6 @@
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
- "dev": true,
"dependencies": {
"ee-first": "1.1.1"
},
@@ -18144,7 +18190,6 @@
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
- "dev": true,
"engines": {
"node": ">= 0.8"
}
@@ -18220,8 +18265,7 @@
"node_modules/path-to-regexp": {
"version": "0.1.10",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
- "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==",
- "dev": true
+ "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="
},
"node_modules/path-type": {
"version": "4.0.0",
@@ -18667,8 +18711,7 @@
"node_modules/postcss-media-query-parser": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz",
- "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==",
- "dev": true
+ "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig=="
},
"node_modules/postcss-merge-longhand": {
"version": "6.0.5",
@@ -19200,7 +19243,6 @@
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
- "dev": true,
"dependencies": {
"forwarded": "0.2.0",
"ipaddr.js": "1.9.1"
@@ -19241,7 +19283,6 @@
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
- "dev": true,
"dependencies": {
"side-channel": "^1.0.6"
},
@@ -19290,7 +19331,6 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
- "dev": true,
"engines": {
"node": ">= 0.6"
}
@@ -19299,7 +19339,6 @@
"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==",
- "dev": true,
"dependencies": {
"bytes": "3.1.2",
"http-errors": "2.0.0",
@@ -19941,7 +19980,6 @@
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -19960,8 +19998,7 @@
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
- "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
- "dev": true
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"node_modules/sass": {
"version": "1.77.6",
@@ -20113,7 +20150,6 @@
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
"integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
- "dev": true,
"dependencies": {
"debug": "2.6.9",
"depd": "2.0.0",
@@ -20137,7 +20173,6 @@
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "dev": true,
"dependencies": {
"ms": "2.0.0"
}
@@ -20145,14 +20180,12 @@
"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==",
- "dev": true
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/send/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==",
- "dev": true,
"engines": {
"node": ">= 0.8"
}
@@ -20248,7 +20281,6 @@
"version": "1.16.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
"integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
- "dev": true,
"dependencies": {
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
@@ -20263,7 +20295,6 @@
"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==",
- "dev": true,
"dependencies": {
"define-data-property": "^1.1.4",
"es-errors": "^1.3.0",
@@ -20279,8 +20310,7 @@
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
- "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
- "dev": true
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
},
"node_modules/shallow-clone": {
"version": "3.0.1",
@@ -20326,7 +20356,6 @@
"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==",
- "dev": true,
"dependencies": {
"call-bind": "^1.0.7",
"es-errors": "^1.3.0",
@@ -20673,7 +20702,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
- "dev": true,
"engines": {
"node": ">= 0.8"
}
@@ -21578,7 +21606,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
- "dev": true,
"engines": {
"node": ">=0.6"
}
@@ -21897,7 +21924,6 @@
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
- "dev": true,
"dependencies": {
"media-typer": "0.3.0",
"mime-types": "~2.1.24"
@@ -21952,10 +21978,11 @@
}
},
"node_modules/undici-types": {
- "version": "6.19.8",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
- "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
- "devOptional": true
+ "version": "5.26.5",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
+ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
+ "devOptional": true,
+ "license": "MIT"
},
"node_modules/unicode-canonical-property-names-ecmascript": {
"version": "2.0.1",
@@ -22103,7 +22130,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
- "dev": true,
"engines": {
"node": ">= 0.8"
}
@@ -22236,7 +22262,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
- "dev": true,
"engines": {
"node": ">= 0.4.0"
}
@@ -22284,7 +22309,6 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
- "dev": true,
"engines": {
"node": ">= 0.8"
}
@@ -23493,6 +23517,15 @@
}
}
},
+ "node_modules/xhr2": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/xhr2/-/xhr2-0.2.1.tgz",
+ "integrity": "sha512-sID0rrVCqkVNUn8t6xuv9+6FViXjUVXq8H5rWOH2rz9fDNQEd4g0EA2XlcEdJXRz5BMEn4O1pJFdT+z4YHhoWw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
diff --git a/webapp/package.json b/webapp/package.json
index 79b83662..349d81a9 100644
--- a/webapp/package.json
+++ b/webapp/package.json
@@ -13,7 +13,8 @@
"prettier:check": "prettier --check src/",
"prettier:write": "prettier --write src/",
"lint": "eslint",
- "lint:fix": "eslint --fix"
+ "lint:fix": "eslint --fix",
+ "serve:ssr:webapp": "node dist/webapp/server/server.mjs"
},
"private": true,
"engines": {
@@ -28,7 +29,9 @@
"@angular/forms": "18.2.1",
"@angular/platform-browser": "18.2.1",
"@angular/platform-browser-dynamic": "18.2.1",
+ "@angular/platform-server": "18.2.1",
"@angular/router": "18.2.1",
+ "@angular/ssr": "^18.2.1",
"@ng-icons/core": "29.5.0",
"@ng-icons/lucide": "^26.3.0",
"@ng-icons/octicons": "29.5.0",
@@ -63,6 +66,7 @@
"clsx": "2.1.1",
"dayjs": "1.11.13",
"embla-carousel-angular": "^14.0.0",
+ "express": "^4.18.2",
"keycloak-js": "^26.0.0",
"lucide-angular": "0.429.0",
"ngx-scrollbar": "^13.0.1",
@@ -90,7 +94,9 @@
"@storybook/angular": "8.3.4",
"@storybook/blocks": "8.3.4",
"@storybook/test": "8.3.4",
+ "@types/express": "^4.17.17",
"@types/jasmine": "5.1.4",
+ "@types/node": "^18.18.0",
"@typescript-eslint/eslint-plugin": "8.2.0",
"@typescript-eslint/parser": "8.2.0",
"chromatic": "11.7.1",
@@ -106,4 +112,4 @@
"storybook": "8.3.4",
"typescript": "5.5.4"
}
-}
+}
\ No newline at end of file
diff --git a/webapp/server.ts b/webapp/server.ts
new file mode 100644
index 00000000..75b8a08c
--- /dev/null
+++ b/webapp/server.ts
@@ -0,0 +1,60 @@
+import { APP_BASE_HREF } from '@angular/common';
+import { CommonEngine } from '@angular/ssr';
+import express from 'express';
+import { fileURLToPath } from 'node:url';
+import { dirname, join, resolve } from 'node:path';
+import bootstrap from './src/main.server';
+
+// The Express app is exported so that it can be used by serverless Functions.
+export function app(): express.Express {
+ const server = express();
+ const serverDistFolder = dirname(fileURLToPath(import.meta.url));
+ const browserDistFolder = resolve(serverDistFolder, '../browser');
+ const indexHtml = join(serverDistFolder, 'index.server.html');
+
+ const commonEngine = new CommonEngine();
+
+ server.set('view engine', 'html');
+ server.set('views', browserDistFolder);
+
+ // Example Express Rest API endpoints
+ // server.get('/api/**', (req, res) => { });
+ // Serve static files from /browser
+ server.get(
+ '**',
+ express.static(browserDistFolder, {
+ maxAge: '1y',
+ index: 'index.html'
+ })
+ );
+
+ // All regular routes use the Angular engine
+ server.get('**', (req, res, next) => {
+ const { protocol, originalUrl, baseUrl, headers } = req;
+
+ commonEngine
+ .render({
+ bootstrap,
+ documentFilePath: indexHtml,
+ url: `${protocol}://${headers.host}${originalUrl}`,
+ publicPath: browserDistFolder,
+ providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }]
+ })
+ .then((html) => res.send(html))
+ .catch((err) => next(err));
+ });
+
+ return server;
+}
+
+function run(): void {
+ const port = process.env['PORT'] || 4200;
+
+ // Start up the Node server
+ const server = app();
+ server.listen(port, () => {
+ console.log(`Node Express server listening on http://localhost:${port}`);
+ });
+}
+
+run();
diff --git a/webapp/src/app/app.config.server.ts b/webapp/src/app/app.config.server.ts
new file mode 100644
index 00000000..b4d57c94
--- /dev/null
+++ b/webapp/src/app/app.config.server.ts
@@ -0,0 +1,11 @@
+import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
+import { provideServerRendering } from '@angular/platform-server';
+import { appConfig } from './app.config';
+
+const serverConfig: ApplicationConfig = {
+ providers: [
+ provideServerRendering()
+ ]
+};
+
+export const config = mergeApplicationConfig(appConfig, serverConfig);
diff --git a/webapp/src/app/app.config.ts b/webapp/src/app/app.config.ts
index 4e64ab39..f4788bf7 100644
--- a/webapp/src/app/app.config.ts
+++ b/webapp/src/app/app.config.ts
@@ -1,13 +1,14 @@
import { APP_INITIALIZER, ApplicationConfig, provideExperimentalZonelessChangeDetection } from '@angular/core';
-import { provideRouter } from '@angular/router';
+import { provideRouter, TitleStrategy } from '@angular/router';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
-import { provideHttpClient, withInterceptors } from '@angular/common/http';
+import { provideHttpClient, withFetch, withInterceptors } from '@angular/common/http';
import { provideAngularQuery, QueryClient } from '@tanstack/angular-query-experimental';
import { environment } from 'environments/environment';
import { BASE_PATH } from 'app/core/modules/openapi';
import { routes } from 'app/app.routes';
import { AnalyticsService } from './analytics.service';
import { securityInterceptor } from './core/security/security-interceptor';
+import { TemplatePageTitleStrategy } from './core/TemplatePageTitleStrategy';
function initializeAnalytics(analyticsService: AnalyticsService): () => void {
return () => {
@@ -20,9 +21,10 @@ export const appConfig: ApplicationConfig = {
provideExperimentalZonelessChangeDetection(),
provideRouter(routes),
provideAngularQuery(new QueryClient()),
- provideHttpClient(withInterceptors([securityInterceptor])),
+ provideHttpClient(withInterceptors([securityInterceptor]), withFetch()),
provideAnimationsAsync(),
{ provide: BASE_PATH, useValue: environment.serverUrl },
- { provide: APP_INITIALIZER, useFactory: initializeAnalytics, multi: true, deps: [AnalyticsService] }
+ { provide: APP_INITIALIZER, useFactory: initializeAnalytics, multi: true, deps: [AnalyticsService] },
+ { provide: TitleStrategy, useClass: TemplatePageTitleStrategy }
]
};
diff --git a/webapp/src/app/app.routes.ts b/webapp/src/app/app.routes.ts
index 905e9a4f..c2b4e0bb 100644
--- a/webapp/src/app/app.routes.ts
+++ b/webapp/src/app/app.routes.ts
@@ -6,12 +6,13 @@ import { AdminGuard } from '@app/core/security/admin.guard';
import { UserProfileComponent } from '@app/user/user-profile.component';
export const routes: Routes = [
- { path: '', component: HomeComponent },
- { path: 'about', component: AboutComponent },
+ { path: '', component: HomeComponent, title: 'Home', data: { meta: { description: 'Artemis Leaderboard - Hephaestus' } } },
+ { path: 'about', component: AboutComponent, title: 'About', data: { meta: { description: 'About Hephaestus' } } },
{
path: 'admin',
component: AdminComponent,
- canActivate: [AdminGuard]
+ canActivate: [AdminGuard],
+ title: 'Admin'
},
- { path: 'user/:id', component: UserProfileComponent }
+ { path: 'user/:id', component: UserProfileComponent, data: { meta: { description: 'User Profile - Hephaestus' } } }
];
diff --git a/webapp/src/app/core/TemplatePageTitleStrategy.ts b/webapp/src/app/core/TemplatePageTitleStrategy.ts
new file mode 100644
index 00000000..de12a0f6
--- /dev/null
+++ b/webapp/src/app/core/TemplatePageTitleStrategy.ts
@@ -0,0 +1,41 @@
+import { Injectable } from '@angular/core';
+import { Meta, Title } from '@angular/platform-browser';
+import { TitleStrategy, RouterStateSnapshot } from '@angular/router';
+
+@Injectable({ providedIn: 'root' })
+export class TemplatePageTitleStrategy extends TitleStrategy {
+ constructor(
+ private readonly title: Title,
+ private metaService: Meta
+ ) {
+ super();
+ }
+ override updateTitle(routerState: RouterStateSnapshot) {
+ // Update document title
+ const id = routerState.root.firstChild?.paramMap.get('id');
+ const titleParam = id ?? this.buildTitle(routerState);
+ const title = titleParam ? `${titleParam} | Hephaestus` : 'Hephaestus';
+ this.title.setTitle(title);
+
+ // Update meta tags
+ const metaTags = routerState.root.firstChild?.data['meta'];
+ if (metaTags) {
+ const iconURL = 'https://github.com/ls1intum/Hephaestus/raw/refs/heads/develop/docs/images/hammer.svg';
+ this.metaService.updateTag({ name: 'description', content: metaTags.description });
+ this.metaService.updateTag({ property: 'og:type', content: 'website' });
+ this.metaService.updateTag({ property: 'og:title', content: title });
+ this.metaService.updateTag({ property: 'og:description', content: metaTags.description });
+ this.metaService.updateTag({ property: 'og:image', content: iconURL });
+ this.metaService.updateTag({ property: 'og:image:width', content: '67' });
+ this.metaService.updateTag({ property: 'og:image:height', content: '60' });
+ this.metaService.updateTag({ property: 'og:url', content: 'https://hephaestus.ase.cit.tum.de' + routerState.url });
+
+ // this.metaService.updateTag({ name: 'twitter:card', content: 'summary_large_image' });
+ this.metaService.updateTag({ name: 'twitter:title', content: title });
+ this.metaService.updateTag({ name: 'twitter:description', content: metaTags.description });
+ this.metaService.updateTag({ name: 'twitter:image', content: iconURL });
+ this.metaService.updateTag({ name: 'twitter:image:width', content: '67' });
+ this.metaService.updateTag({ name: 'twitter:image:height', content: '60' });
+ }
+ }
+}
diff --git a/webapp/src/app/core/header/header.component.html b/webapp/src/app/core/header/header.component.html
index 8be8e399..f5bd9b61 100644
--- a/webapp/src/app/core/header/header.component.html
+++ b/webapp/src/app/core/header/header.component.html
@@ -5,7 +5,7 @@
-
+
@if (signedIn()) {