- intermediate commit
This commit is contained in:
parent
7eaba06e6a
commit
79dea999ad
67 changed files with 1997 additions and 1170 deletions
1
pom.xml
1
pom.xml
|
|
@ -32,6 +32,7 @@
|
|||
<mockito.version>5.18.0</mockito.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-batch</artifactId>
|
||||
|
|
|
|||
165
src/frontend/assets/map.json
Normal file
165
src/frontend/assets/map.json
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
{
|
||||
"version": 8,
|
||||
"name": "Custom Style",
|
||||
"sources": {
|
||||
"raster-tiles": {
|
||||
"type": "raster",
|
||||
"tiles": ["https://atlas.microsoft.com/map/tile?api-version=2.0&tilesetId=microsoft.base.road&zoom={z}&x={x}&y={y}"],
|
||||
"tileSize": 256,
|
||||
"attribution": "© Microsoft"
|
||||
}
|
||||
},
|
||||
"sprite": "https://atlas.microsoft.com/map/sprites/atlas?api-version=2.0",
|
||||
"glyphs": "https://atlas.microsoft.com/map/fonts/{fontstack}/{range}.pbf?api-version=2.0",
|
||||
"layers": [
|
||||
{
|
||||
"id": "background",
|
||||
"type": "background",
|
||||
"paint": {
|
||||
"background-color": "#f8fafc"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "landcover",
|
||||
"type": "fill",
|
||||
"source": "vectorTiles",
|
||||
"source-layer": "Landcover",
|
||||
"paint": {
|
||||
"fill-color": "#ffffff",
|
||||
"fill-opacity": 0.8
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "water",
|
||||
"type": "fill",
|
||||
"source": "vectorTiles",
|
||||
"source-layer": "Water",
|
||||
"paint": {
|
||||
"fill-color": "#8DB3FE"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "water-outline",
|
||||
"type": "line",
|
||||
"source": "vectorTiles",
|
||||
"source-layer": "Water",
|
||||
"paint": {
|
||||
"line-color": "#6B869C",
|
||||
"line-width": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "park",
|
||||
"type": "fill",
|
||||
"source": "vectorTiles",
|
||||
"source-layer": "Landuse",
|
||||
"filter": ["==", ["get", "class"], "park"],
|
||||
"paint": {
|
||||
"fill-color": "#5AF0B4",
|
||||
"fill-opacity": 0.3
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "building",
|
||||
"type": "fill",
|
||||
"source": "vectorTiles",
|
||||
"source-layer": "Building",
|
||||
"paint": {
|
||||
"fill-color": "#6b7280",
|
||||
"fill-opacity": 0.6
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "building-outline",
|
||||
"type": "line",
|
||||
"source": "vectorTiles",
|
||||
"source-layer": "Building",
|
||||
"paint": {
|
||||
"line-color": "#002F54",
|
||||
"line-width": 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "road-minor",
|
||||
"type": "line",
|
||||
"source": "vectorTiles",
|
||||
"source-layer": "Road",
|
||||
"filter": ["in", ["get", "class"], ["literal", ["service", "track"]]],
|
||||
"paint": {
|
||||
"line-color": "#ffffff",
|
||||
"line-width": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "road-major",
|
||||
"type": "line",
|
||||
"source": "vectorTiles",
|
||||
"source-layer": "Road",
|
||||
"filter": ["in", ["get", "class"], ["literal", ["primary", "secondary", "tertiary"]]],
|
||||
"paint": {
|
||||
"line-color": "#6B869C",
|
||||
"line-width": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "road-highway",
|
||||
"type": "line",
|
||||
"source": "vectorTiles",
|
||||
"source-layer": "Road",
|
||||
"filter": ["in", ["get", "class"], ["literal", ["motorway", "trunk"]]],
|
||||
"paint": {
|
||||
"line-color": "#BC2B72",
|
||||
"line-width": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "road-label",
|
||||
"type": "symbol",
|
||||
"source": "vectorTiles",
|
||||
"source-layer": "Road",
|
||||
"layout": {
|
||||
"text-field": ["get", "name"],
|
||||
"text-font": ["Poppins"],
|
||||
"text-size": 12,
|
||||
"symbol-placement": "line"
|
||||
},
|
||||
"paint": {
|
||||
"text-color": "#002F54",
|
||||
"text-halo-color": "#ffffff",
|
||||
"text-halo-width": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "place-label",
|
||||
"type": "symbol",
|
||||
"source": "vectorTiles",
|
||||
"source-layer": "Place",
|
||||
"layout": {
|
||||
"text-field": ["get", "name"],
|
||||
"text-font": ["Poppins"],
|
||||
"text-size": 14
|
||||
},
|
||||
"paint": {
|
||||
"text-color": "#002F54",
|
||||
"text-halo-color": "#ffffff",
|
||||
"text-halo-width": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "poi-label",
|
||||
"type": "symbol",
|
||||
"source": "vectorTiles",
|
||||
"source-layer": "POI",
|
||||
"layout": {
|
||||
"text-field": ["get", "name"],
|
||||
"text-font": ["Poppins"],
|
||||
"text-size": 11
|
||||
},
|
||||
"paint": {
|
||||
"text-color": "#5AF0B4",
|
||||
"text-halo-color": "#002F54",
|
||||
"text-halo-width": 1.5
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>LCC</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
120
src/frontend/package-lock.json
generated
120
src/frontend/package-lock.json
generated
|
|
@ -10,7 +10,9 @@
|
|||
"dependencies": {
|
||||
"@phosphor-icons/vue": "^2.2.1",
|
||||
"@vueuse/core": "^13.6.0",
|
||||
"azure-maps-control": "^3.6.1",
|
||||
"chart.js": "^4.5.0",
|
||||
"leaflet": "^1.9.4",
|
||||
"loglevel": "^1.9.2",
|
||||
"pinia": "^3.0.3",
|
||||
"vite-plugin-static-copy": "^3.1.3",
|
||||
|
|
@ -41,6 +43,27 @@
|
|||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/msal-browser": {
|
||||
"version": "2.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-2.39.0.tgz",
|
||||
"integrity": "sha512-kks/n2AJzKUk+DBqZhiD+7zeQGBl+WpSOQYzWy6hff3bU0ZrYFqr4keFLlzB5VKuKZog0X59/FGHb1RPBDZLVg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@azure/msal-common": "13.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/msal-common": {
|
||||
"version": "13.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-13.3.3.tgz",
|
||||
"integrity": "sha512-n278DdCXKeiWhLwhEL7/u9HRMyzhUXLefeajiknf6AmEedoiOiv2r5aRJ7LXdT3NGPyubkdIbthaJlVtmuEqvA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
|
||||
|
|
@ -957,6 +980,46 @@
|
|||
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@mapbox/jsonlint-lines-primitives": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz",
|
||||
"integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/mapbox-gl-supported": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-2.0.1.tgz",
|
||||
"integrity": "sha512-HP6XvfNIzfoMVfyGjBckjiAOQK9WfX0ywdLubuPMPv+Vqf5fj0uCbgBQYpiqcWZT6cbyyRnTSXDheT1ugvF6UQ==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@mapbox/unitbezier": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz",
|
||||
"integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==",
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/@maplibre/maplibre-gl-style-spec": {
|
||||
"version": "20.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-20.4.0.tgz",
|
||||
"integrity": "sha512-AzBy3095fTFPjDjmWpR2w6HVRAZJ6hQZUCwk5Plz6EyfnfuQW1odeW5i2Ai47Y6TBA2hQnC+azscjBSALpaWgw==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@mapbox/jsonlint-lines-primitives": "~2.0.2",
|
||||
"@mapbox/unitbezier": "^0.0.1",
|
||||
"json-stringify-pretty-compact": "^4.0.0",
|
||||
"minimist": "^1.2.8",
|
||||
"quickselect": "^2.0.0",
|
||||
"rw": "^1.3.3",
|
||||
"tinyqueue": "^3.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"gl-style-format": "dist/gl-style-format.mjs",
|
||||
"gl-style-migrate": "dist/gl-style-migrate.mjs",
|
||||
"gl-style-validate": "dist/gl-style-validate.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@phosphor-icons/vue": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@phosphor-icons/vue/-/vue-2.2.1.tgz",
|
||||
|
|
@ -1282,6 +1345,12 @@
|
|||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/geojson": {
|
||||
"version": "7946.0.16",
|
||||
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
|
||||
"integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/web-bluetooth": {
|
||||
"version": "0.0.21",
|
||||
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz",
|
||||
|
|
@ -1627,6 +1696,18 @@
|
|||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/azure-maps-control": {
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/azure-maps-control/-/azure-maps-control-3.6.1.tgz",
|
||||
"integrity": "sha512-EqJ96GOjUcCG9XizUbyqDu92x3KKT9C9AwRL3hmPicQjn00ql7em6RbBqJYO4nvIoH53DG6MOITj9t/zv1mQYg==",
|
||||
"license": "SEE LICENSE.TXT",
|
||||
"dependencies": {
|
||||
"@azure/msal-browser": "^2.32.1",
|
||||
"@mapbox/mapbox-gl-supported": "^2.0.1",
|
||||
"@maplibre/maplibre-gl-style-spec": "^20.0.0",
|
||||
"@types/geojson": "^7946.0.14"
|
||||
}
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||
|
|
@ -2290,6 +2371,12 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/json-stringify-pretty-compact": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-4.0.0.tgz",
|
||||
"integrity": "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json5": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||
|
|
@ -2322,6 +2409,12 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/leaflet": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
|
||||
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==",
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/loglevel": {
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz",
|
||||
|
|
@ -2354,6 +2447,15 @@
|
|||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/mitt": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
|
||||
|
|
@ -2598,6 +2700,12 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/quickselect": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz",
|
||||
"integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
|
|
@ -2681,6 +2789,12 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/rw": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
|
||||
"integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
|
|
@ -2801,6 +2915,12 @@
|
|||
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyqueue": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz",
|
||||
"integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
|
|
|
|||
|
|
@ -14,7 +14,9 @@
|
|||
"dependencies": {
|
||||
"@phosphor-icons/vue": "^2.2.1",
|
||||
"@vueuse/core": "^13.6.0",
|
||||
"azure-maps-control": "^3.6.1",
|
||||
"chart.js": "^4.5.0",
|
||||
"leaflet": "^1.9.4",
|
||||
"loglevel": "^1.9.2",
|
||||
"pinia": "^3.0.3",
|
||||
"vite-plugin-static-copy": "^3.1.3",
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import ErrorNotification from "@/components/UI/ErrorNotifcation.vue";
|
|||
|
||||
export default {
|
||||
components: {ErrorNotification, TheHeader},
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -123,7 +123,6 @@ const executeRequest = async (requestingStore, request) => {
|
|||
throw e;
|
||||
});
|
||||
|
||||
|
||||
let data = null;
|
||||
if (request.expectResponse) {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -1,346 +0,0 @@
|
|||
<template>
|
||||
<div ref="mapContainer" class="map-container"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { config } from '@/config';
|
||||
// Annahme: Sie haben bereits MSAL oder ähnliche Entra ID Integration
|
||||
// import { msalInstance } from '@/auth/msal'; // Ihr MSAL Setup
|
||||
|
||||
export default {
|
||||
name: 'AzureMapsComponent',
|
||||
|
||||
props: {
|
||||
coordinates: {
|
||||
type: Object,
|
||||
required: true,
|
||||
validator(value) {
|
||||
return value &&
|
||||
typeof value.latitude === 'number' &&
|
||||
typeof value.longitude === 'number';
|
||||
}
|
||||
},
|
||||
draggable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
map: null,
|
||||
marker: null,
|
||||
datasource: null,
|
||||
azureMapsClientId: null // Wird vom Backend geholt
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.initializeMap();
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
if (this.map) {
|
||||
this.map.dispose();
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
coordinates: {
|
||||
handler(newCoords) {
|
||||
if (this.marker && this.map) {
|
||||
this.updateMarkerPosition(newCoords);
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
|
||||
draggable(newValue) {
|
||||
if (this.marker) {
|
||||
this.updateMarkerDraggable(newValue);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async initializeMap() {
|
||||
try {
|
||||
// Azure Maps SDK laden
|
||||
if (!window.atlas) {
|
||||
await this.loadAzureMapsSDK();
|
||||
}
|
||||
|
||||
// Azure Maps Client ID vom Backend abrufen
|
||||
await this.fetchAzureMapsConfig();
|
||||
|
||||
// Karte mit Entra ID-Authentifizierung initialisieren
|
||||
this.map = new window.atlas.Map(this.$refs.mapContainer, {
|
||||
center: [this.coordinates.longitude, this.coordinates.latitude],
|
||||
zoom: 15,
|
||||
authOptions: {
|
||||
authType: 'aad',
|
||||
clientId: this.azureMapsClientId,
|
||||
aadAppId: this.azureMapsClientId,
|
||||
aadTenant: 'common', // oder Ihre spezifische Tenant ID
|
||||
getToken: this.getEntraIdTokenForMaps
|
||||
}
|
||||
});
|
||||
|
||||
// Warten bis die Karte geladen ist
|
||||
this.map.events.add('ready', () => {
|
||||
this.setupMap();
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Initialisieren der Karte:', error);
|
||||
this.$emit('error', error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Azure Maps Konfiguration vom Backend abrufen
|
||||
*/
|
||||
async fetchAzureMapsConfig() {
|
||||
try {
|
||||
const response = await fetch(`${config.backendUrl}/maps/config`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${await this.getCurrentUserToken()}`
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const configData = await response.json();
|
||||
this.azureMapsClientId = configData.clientId;
|
||||
|
||||
console.debug('Azure Maps Konfiguration erfolgreich abgerufen');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Abrufen der Azure Maps Konfiguration:', error);
|
||||
throw new Error('Konnte Azure Maps Konfiguration nicht abrufen');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Entra ID Token für Azure Maps abrufen
|
||||
*/
|
||||
async getEntraIdTokenForMaps() {
|
||||
try {
|
||||
// Azure Maps spezifischen Scope verwenden
|
||||
const tokenRequest = {
|
||||
scopes: ['https://atlas.microsoft.com/user_impersonation'],
|
||||
account: msalInstance.getActiveAccount()
|
||||
};
|
||||
|
||||
const response = await msalInstance.acquireTokenSilent(tokenRequest);
|
||||
return response.accessToken;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Abrufen des Azure Maps Tokens:', error);
|
||||
|
||||
// Fallback: Interactive Token Acquisition
|
||||
try {
|
||||
const response = await msalInstance.acquireTokenPopup(tokenRequest);
|
||||
return response.accessToken;
|
||||
} catch (popupError) {
|
||||
console.error('Interactive Token-Abruf fehlgeschlagen:', popupError);
|
||||
throw new Error('Konnte kein Token für Azure Maps abrufen');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Aktuelles Benutzer-Token für Backend-Aufrufe
|
||||
*/
|
||||
async getCurrentUserToken() {
|
||||
try {
|
||||
const account = msalInstance.getActiveAccount();
|
||||
if (!account) {
|
||||
throw new Error('Kein aktiver Benutzer gefunden');
|
||||
}
|
||||
|
||||
const tokenRequest = {
|
||||
scopes: [`${config.backendUrl}/.default`], // Backend API Scope
|
||||
account: account
|
||||
};
|
||||
|
||||
const response = await msalInstance.acquireTokenSilent(tokenRequest);
|
||||
return response.accessToken;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Abrufen des Backend-Tokens:', error);
|
||||
throw new Error('Konnte Backend-Token nicht abrufen');
|
||||
}
|
||||
},
|
||||
|
||||
loadAzureMapsSDK() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (window.atlas) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
// CSS laden
|
||||
const linkElement = document.createElement('link');
|
||||
linkElement.rel = 'stylesheet';
|
||||
linkElement.href = 'https://atlas.microsoft.com/sdk/javascript/mapcontrol/2/atlas.min.css';
|
||||
document.head.appendChild(linkElement);
|
||||
|
||||
// JavaScript SDK laden
|
||||
const scriptElement = document.createElement('script');
|
||||
scriptElement.src = 'https://atlas.microsoft.com/sdk/javascript/mapcontrol/2/atlas.min.js';
|
||||
scriptElement.onload = resolve;
|
||||
scriptElement.onerror = reject;
|
||||
document.head.appendChild(scriptElement);
|
||||
});
|
||||
},
|
||||
|
||||
setupMap() {
|
||||
// Datenquelle für den Marker erstellen
|
||||
this.datasource = new window.atlas.source.DataSource();
|
||||
this.map.sources.add(this.datasource);
|
||||
|
||||
// Symbol-Layer für den Marker hinzufügen
|
||||
this.map.layers.add(new window.atlas.layer.SymbolLayer(this.datasource, null, {
|
||||
iconOptions: {
|
||||
allowOverlap: true,
|
||||
ignorePlacement: true
|
||||
}
|
||||
}));
|
||||
|
||||
// Marker erstellen
|
||||
this.createMarker();
|
||||
|
||||
// Event-Listener für draggable Funktionalität
|
||||
if (this.draggable) {
|
||||
this.enableMarkerDrag();
|
||||
}
|
||||
},
|
||||
|
||||
createMarker() {
|
||||
const point = new window.atlas.data.Point([
|
||||
this.coordinates.longitude,
|
||||
this.coordinates.latitude
|
||||
]);
|
||||
|
||||
this.marker = new window.atlas.data.Feature(point, {
|
||||
id: 'marker',
|
||||
draggable: this.draggable
|
||||
});
|
||||
|
||||
this.datasource.add(this.marker);
|
||||
},
|
||||
|
||||
updateMarkerPosition(coords) {
|
||||
if (this.marker && this.datasource) {
|
||||
this.marker.geometry.coordinates = [coords.longitude, coords.latitude];
|
||||
this.datasource.setShapes([this.marker]);
|
||||
this.map.setCamera({
|
||||
center: [coords.longitude, coords.latitude]
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
updateMarkerDraggable(isDraggable) {
|
||||
if (isDraggable) {
|
||||
this.enableMarkerDrag();
|
||||
} else {
|
||||
this.disableMarkerDrag();
|
||||
}
|
||||
},
|
||||
|
||||
enableMarkerDrag() {
|
||||
this.map.events.add('mousedown', this.datasource, this.onMarkerMouseDown);
|
||||
this.map.events.add('mousemove', this.onMarkerMouseMove);
|
||||
this.map.events.add('mouseup', this.onMarkerMouseUp);
|
||||
this.map.events.add('touchstart', this.datasource, this.onMarkerTouchStart);
|
||||
this.map.events.add('touchmove', this.onMarkerTouchMove);
|
||||
this.map.events.add('touchend', this.onMarkerTouchEnd);
|
||||
this.map.getCanvasContainer().style.cursor = 'pointer';
|
||||
},
|
||||
|
||||
disableMarkerDrag() {
|
||||
this.map.events.remove('mousedown', this.datasource, this.onMarkerMouseDown);
|
||||
this.map.events.remove('mousemove', this.onMarkerMouseMove);
|
||||
this.map.events.remove('mouseup', this.onMarkerMouseUp);
|
||||
this.map.events.remove('touchstart', this.datasource, this.onMarkerTouchStart);
|
||||
this.map.events.remove('touchmove', this.onMarkerTouchMove);
|
||||
this.map.events.remove('touchend', this.onMarkerTouchEnd);
|
||||
this.map.getCanvasContainer().style.cursor = 'default';
|
||||
},
|
||||
|
||||
onMarkerMouseDown(e) {
|
||||
if (e.shapes && e.shapes.length > 0) {
|
||||
this.isDragging = true;
|
||||
this.map.getCanvasContainer().style.cursor = 'grabbing';
|
||||
}
|
||||
},
|
||||
|
||||
onMarkerMouseMove(e) {
|
||||
if (this.isDragging) {
|
||||
this.marker.geometry.coordinates = [e.position[0], e.position[1]];
|
||||
this.datasource.setShapes([this.marker]);
|
||||
this.$emit('coordinates-changed', {
|
||||
latitude: e.position[1],
|
||||
longitude: e.position[0]
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
onMarkerMouseUp() {
|
||||
if (this.isDragging) {
|
||||
this.isDragging = false;
|
||||
this.map.getCanvasContainer().style.cursor = 'pointer';
|
||||
}
|
||||
},
|
||||
|
||||
onMarkerTouchStart(e) {
|
||||
if (e.shapes && e.shapes.length > 0) {
|
||||
this.isDragging = true;
|
||||
}
|
||||
},
|
||||
|
||||
onMarkerTouchMove(e) {
|
||||
if (this.isDragging && e.position) {
|
||||
this.marker.geometry.coordinates = [e.position[0], e.position[1]];
|
||||
this.datasource.setShapes([this.marker]);
|
||||
this.$emit('coordinates-changed', {
|
||||
latitude: e.position[1],
|
||||
longitude: e.position[0]
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
onMarkerTouchEnd() {
|
||||
this.isDragging = false;
|
||||
},
|
||||
|
||||
// Öffentliche Methoden
|
||||
setCenter(coords) {
|
||||
if (this.map) {
|
||||
this.map.setCamera({
|
||||
center: [coords.longitude, coords.latitude]
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
setZoom(zoom) {
|
||||
if (this.map) {
|
||||
this.map.setCamera({ zoom });
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.map-container {
|
||||
width: 100%;
|
||||
height: 250px;
|
||||
min-height: 150px;
|
||||
}
|
||||
</style>
|
||||
191
src/frontend/src/components/UI/OpenStreetMapEmbed.vue
Normal file
191
src/frontend/src/components/UI/OpenStreetMapEmbed.vue
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
<template>
|
||||
<div ref="mapContainer" class="map-container"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'OpenStreetMapEmbed',
|
||||
props: {
|
||||
coordinates: {
|
||||
type: Object,
|
||||
default: () => ({ latitude: 51.1657, longitude: 10.4515 })
|
||||
},
|
||||
zoom: {
|
||||
type: Number,
|
||||
default: 13,
|
||||
validator: (value) => value >= 1 && value <= 19
|
||||
},
|
||||
showMarker: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
markerColor: {
|
||||
type: String,
|
||||
default: '#002F54'
|
||||
},
|
||||
tileStyle: {
|
||||
type: String,
|
||||
default: 'standard',
|
||||
validator: (value) => ['standard', 'dark', 'humanitarian', 'topo'].includes(value)
|
||||
},
|
||||
customFilter: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '450px'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
map: null,
|
||||
marker: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
tileLayerUrl() {
|
||||
const styles = {
|
||||
standard: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||
dark: 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png',
|
||||
humanitarian: 'https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png',
|
||||
topo: 'https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png'
|
||||
};
|
||||
return styles[this.tileStyle] || styles.standard;
|
||||
},
|
||||
tileAttribution() {
|
||||
return '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors';
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initMap();
|
||||
},
|
||||
beforeUnmount() {
|
||||
if (this.map) {
|
||||
this.map.remove();
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
coordinates: {
|
||||
handler(newCoords) {
|
||||
if (this.map) {
|
||||
const { latitude, longitude } = newCoords;
|
||||
this.map.setView([latitude, longitude], this.zoom);
|
||||
|
||||
if (this.marker) {
|
||||
this.marker.setLatLng([latitude, longitude]);
|
||||
}
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
zoom(newZoom) {
|
||||
if (this.map) {
|
||||
this.map.setZoom(newZoom);
|
||||
}
|
||||
},
|
||||
showMarker(show) {
|
||||
if (this.map) {
|
||||
if (show && !this.marker) {
|
||||
this.addMarker();
|
||||
} else if (!show && this.marker) {
|
||||
this.map.removeLayer(this.marker);
|
||||
this.marker = null;
|
||||
}
|
||||
}
|
||||
},
|
||||
tileStyle() {
|
||||
if (this.map) {
|
||||
this.updateTileLayer();
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async initMap() {
|
||||
// Dynamically import Leaflet to avoid SSR issues
|
||||
const L = await import('leaflet');
|
||||
await import('leaflet/dist/leaflet.css');
|
||||
|
||||
const { latitude, longitude } = this.coordinates;
|
||||
|
||||
this.map = L.map(this.$refs.mapContainer, {
|
||||
center: [latitude, longitude],
|
||||
zoom: this.zoom,
|
||||
zoomControl: true,
|
||||
scrollWheelZoom: true
|
||||
});
|
||||
|
||||
this.updateTileLayer();
|
||||
|
||||
if (this.showMarker) {
|
||||
this.addMarker();
|
||||
}
|
||||
},
|
||||
async updateTileLayer() {
|
||||
const L = await import('leaflet');
|
||||
|
||||
// Remove existing tile layers
|
||||
this.map.eachLayer((layer) => {
|
||||
if (layer instanceof L.TileLayer) {
|
||||
this.map.removeLayer(layer);
|
||||
}
|
||||
});
|
||||
|
||||
// Add new tile layer
|
||||
L.tileLayer(this.tileLayerUrl, {
|
||||
attribution: this.tileAttribution,
|
||||
maxZoom: 19
|
||||
}).addTo(this.map);
|
||||
},
|
||||
async addMarker() {
|
||||
const L = await import('leaflet');
|
||||
const { latitude, longitude } = this.coordinates;
|
||||
|
||||
// Custom marker icon with color
|
||||
const markerHtml = `
|
||||
<svg width="25" height="41" viewBox="0 0 25 41" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.5 0C5.596 0 0 5.596 0 12.5c0 9.375 12.5 28.125 12.5 28.125S25 21.875 25 12.5C25 5.596 19.404 0 12.5 0z"
|
||||
fill="${this.markerColor}" stroke="#fff" stroke-width="2"/>
|
||||
<circle cx="12.5" cy="12.5" r="4" fill="#fff"/>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
const customIcon = L.divIcon({
|
||||
html: markerHtml,
|
||||
className: 'custom-marker',
|
||||
iconSize: [25, 41],
|
||||
iconAnchor: [12.5, 41]
|
||||
});
|
||||
|
||||
this.marker = L.marker([latitude, longitude], { icon: customIcon }).addTo(this.map);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.map-container {
|
||||
width: v-bind(width);
|
||||
height: v-bind(height);
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.map-container :deep(.leaflet-tile-pane) {
|
||||
filter: v-bind(customFilter);
|
||||
}
|
||||
|
||||
:deep(.custom-marker) {
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
:deep(.leaflet-control-attribution) {
|
||||
font-size: 10px;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
<div class="input-field">{{ coordinatesDMS }}</div>
|
||||
</div>
|
||||
<div class="supplier-map">
|
||||
<azure-maps-component :draggable="true" :coordinates="supplierCoordinates"></azure-maps-component>
|
||||
<open-street-map-embed :coordinates="supplierCoordinates" zoom="15" width="600px" height="300px" custom-filter="grayscale(0.8) sepia(0.5) hue-rotate(180deg) saturate(0.5) brightness(1.0)"></open-street-map-embed>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<modal :state="selectSupplierModalState" @close="closeEditModal">
|
||||
|
|
@ -37,11 +37,11 @@ import {PhUser} from "@phosphor-icons/vue";
|
|||
import ModalDialog from "@/components/UI/ModalDialog.vue";
|
||||
import SelectNode from "@/components/layout/node/SelectNode.vue";
|
||||
import Modal from "@/components/UI/Modal.vue";
|
||||
import AzureMapsComponent from "@/components/UI/AzureMapsComponent.vue";
|
||||
import OpenStreetMapEmbed from "@/components/UI/OpenStreetMapEmbed.vue";
|
||||
|
||||
export default {
|
||||
name: "SupplierView",
|
||||
components: {AzureMapsComponent, Modal, SelectNode, ModalDialog, PhUser, Flag, InputField, IconButton},
|
||||
components: { OpenStreetMapEmbed, Modal, SelectNode, ModalDialog, PhUser, Flag, InputField, IconButton},
|
||||
emits: ['updateSupplier'],
|
||||
props: {
|
||||
supplierName: {
|
||||
|
|
@ -140,6 +140,8 @@ export default {
|
|||
.supplier-map {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
padding: 1.6rem;
|
||||
}
|
||||
|
||||
.user-icon {
|
||||
|
|
@ -177,7 +179,9 @@ export default {
|
|||
.container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1.6rem;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
gap: 3.6rem;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import log from 'loglevel'
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
log.setLevel('silent')
|
||||
log.setLevel('debug') //TODO change back to 'silent'
|
||||
} else {
|
||||
log.setLevel('debug')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import Config from "@/pages/Config.vue";
|
|||
import CalculationSingleEdit from "@/pages/CalculationSingleEdit.vue";
|
||||
import CalculationMassEdit from "@/pages/CalculationMassEdit.vue";
|
||||
import CalculationAssistant from "@/pages/CalculationAssistant.vue";
|
||||
import ErrorLog from "@/pages/ErrorLog.vue";
|
||||
import CalculationDump from "@/components/layout/dev/CalculationDump.vue";
|
||||
import DevPage from "@/pages/DevPage.vue";
|
||||
import {useActiveUserStore} from "@/store/activeuser.js";
|
||||
|
|
@ -139,5 +138,4 @@ const router = createRouter({
|
|||
]
|
||||
})
|
||||
|
||||
|
||||
export default router;
|
||||
|
|
@ -21,8 +21,9 @@ export const useActiveUserStore = defineStore('activeUser', {
|
|||
return state.user.groups?.includes("super") || state.user.groups?.includes("freight") || state.user.groups?.includes("packaging");
|
||||
},
|
||||
allowReporting(state) {
|
||||
return state.user !== null;
|
||||
|
||||
if (state.user === null)
|
||||
return false;
|
||||
return state.user.groups?.includes("super") || state.user.groups?.includes("freight") || state.user.groups?.includes("packaging") || state.user.groups?.includes("basic") || state.user.groups?.includes("calculation");
|
||||
},
|
||||
isSuper(state) {
|
||||
if (state.user === null)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import {defineStore} from 'pinia'
|
||||
import {config} from '@/config'
|
||||
import {useErrorStore} from "@/store/error.js";
|
||||
import performRequest from "@/backend.js";
|
||||
|
||||
|
||||
export const useAssistantStore = defineStore('assistant', {
|
||||
|
|
@ -27,71 +28,16 @@ export const useAssistantStore = defineStore('assistant', {
|
|||
const materialIds = this.materials.map((material) => material.id);
|
||||
const supplierIds = this.suppliers.filter(s => s.id.startsWith('s')).map((supplier) => supplier.origId);
|
||||
const userSupplierIds = this.suppliers.filter(s => s.id.startsWith('u')).map((supplier) => supplier.origId);
|
||||
const jsonBody = JSON.stringify({
|
||||
const body = {
|
||||
material: materialIds,
|
||||
supplier: supplierIds,
|
||||
user_supplier: userSupplierIds,
|
||||
from_scratch: this.createEmpty
|
||||
});
|
||||
|
||||
console.log(`Creation body: ${jsonBody}`);
|
||||
|
||||
const params = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: jsonBody
|
||||
};
|
||||
|
||||
const request = {url: url, params: params};
|
||||
const resp = await performRequest(this, 'POST', `${config.backendUrl}/calculation/create/`, body, true);
|
||||
|
||||
const response = await fetch(url, params).catch(e => {
|
||||
this.error = {code: 'Network error.', message: "Please check your internet connection.", trace: null}
|
||||
console.error(this.error);
|
||||
this.loading = false;
|
||||
|
||||
console.error(this.error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(this.error, {store: this, request: request});
|
||||
|
||||
throw e;
|
||||
});
|
||||
|
||||
const data = await response.json().catch(e => {
|
||||
this.error = {
|
||||
code: 'Malformed response',
|
||||
message: "Malformed server response. Please contact support.",
|
||||
trace: null
|
||||
}
|
||||
console.error(this.error);
|
||||
this.loading = false;
|
||||
|
||||
console.error(this.error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(this.error, {store: this, request: request});
|
||||
|
||||
throw e;
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
this.error = {
|
||||
code: data.error.code,
|
||||
title: data.error.title,
|
||||
message: data.error.message,
|
||||
trace: data.error.trace
|
||||
};
|
||||
this.loading = false;
|
||||
|
||||
console.error(this.error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(this.error, {store: this, request: request});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
return data.map(p => p.id);
|
||||
return resp.data.map(p => p.id);
|
||||
|
||||
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {config} from '@/config'
|
|||
import {useErrorStore} from "@/store/error.js";
|
||||
import {useStageStore} from "@/store/stage.js";
|
||||
import {usePropertySetsStore} from "@/store/propertySets.js";
|
||||
import performRequest from "@/backend.js";
|
||||
|
||||
export const useCountryStore = defineStore('country', {
|
||||
state() {
|
||||
|
|
@ -35,7 +36,7 @@ export const useCountryStore = defineStore('country', {
|
|||
const url = `${config.backendUrl}/properties/country/${this.getSelectedCountry.iso_code}/${property.id}`;
|
||||
const body = { value: String(property.value)};
|
||||
|
||||
await this.performRequest('PUT', url, body, false);
|
||||
await performRequest(this,'PUT', url, body, false);
|
||||
|
||||
prop.draft_value = property.reset ? null : property.value;
|
||||
|
||||
|
|
@ -62,8 +63,8 @@ export const useCountryStore = defineStore('country', {
|
|||
params.append('property_set', this.selectedPeriodId);
|
||||
|
||||
const url = `${config.backendUrl}/countries/${this.selectedCountryId}/${params.size === 0 ? '' : '?'}${params.toString()}`;
|
||||
this.countryDetail = await this.performRequest('GET', url, null);
|
||||
|
||||
const resp = await performRequest(this,'GET', url, null);
|
||||
this.countryDetail = resp.data;
|
||||
this.loading = false;
|
||||
},
|
||||
async setQuery(query) {
|
||||
|
|
@ -79,101 +80,8 @@ export const useCountryStore = defineStore('country', {
|
|||
if (this.query !== null)
|
||||
params.append('filter', this.query);
|
||||
const url = `${config.backendUrl}/countries/all/${params.size === 0 ? '' : '?'}${params.toString()}`;
|
||||
this.countries = await this.performRequest('GET', url, null);
|
||||
},
|
||||
async performRequest(method, url, body, expectResponse = true) {
|
||||
|
||||
const params = {
|
||||
method: method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
if ((body ?? null) !== null) {
|
||||
params.body = JSON.stringify(body);
|
||||
}
|
||||
|
||||
const request = {url: url, params: params};
|
||||
console.log("Request:", request);
|
||||
|
||||
const response = await fetch(url, params
|
||||
).catch(e => {
|
||||
const error = {
|
||||
code: 'Network error.',
|
||||
message: "Please check your internet connection.",
|
||||
trace: null
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
|
||||
throw e;
|
||||
});
|
||||
|
||||
let data = null;
|
||||
if (expectResponse) {
|
||||
data = await response.json().catch(e => {
|
||||
const error = {
|
||||
code: 'Malformed response',
|
||||
message: "Malformed server response. Please contact support.",
|
||||
trace: null
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw e;
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = {
|
||||
code: data.error.code,
|
||||
title: data.error.title,
|
||||
message: data.error.message,
|
||||
trace: data.error.trace
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw new Error('Internal backend error');
|
||||
}
|
||||
} else {
|
||||
if (!response.ok) {
|
||||
|
||||
const data = await response.json().catch(e => {
|
||||
const error = {
|
||||
code: "Return code error " + response.status,
|
||||
message: "Server returned wrong response code",
|
||||
trace: null
|
||||
}
|
||||
console.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw new Error('Internal backend error');
|
||||
|
||||
});
|
||||
|
||||
const error = {
|
||||
code: data.error.code,
|
||||
title: data.error.title,
|
||||
message: data.error.message,
|
||||
trace: data.error.trace
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw new Error('Internal backend error');
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Response:", data);
|
||||
return data;
|
||||
const resp = await performRequest(this, 'GET', url, null);
|
||||
this.countries = resp.data;
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -60,50 +60,8 @@ export const usePremiseStore = defineStore('premise', {
|
|||
|
||||
const url = `${config.backendUrl}/calculation/view/${params.size === 0 ? '' : '?'}${params.toString()}`;
|
||||
|
||||
const request = {url: url, params: {method: 'GET'}};
|
||||
|
||||
const response = await fetch(url).catch(e => {
|
||||
this.error = {title: 'Network error.', message: "Please check your internet connection.", trace: null}
|
||||
this.loading = false;
|
||||
|
||||
console.error(this.error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(this.error, {store: this, request: request});
|
||||
|
||||
throw e;
|
||||
});
|
||||
|
||||
const data = await response.json().catch(e => {
|
||||
this.error = {
|
||||
title: 'Malformed response',
|
||||
message: "Malformed server response. Please contact support.",
|
||||
trace: null
|
||||
}
|
||||
this.loading = false;
|
||||
|
||||
console.error(this.error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(this.error, {store: this, request: request});
|
||||
|
||||
throw e;
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
this.error = {
|
||||
code: data.error.code,
|
||||
title: data.error.title,
|
||||
message: data.error.message,
|
||||
trace: data.error.trace
|
||||
}
|
||||
this.loading = false;
|
||||
|
||||
console.error(this.error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(this.error, {store: this, request: request});
|
||||
|
||||
console.log(data);
|
||||
return;
|
||||
}
|
||||
const resp = await performRequest(this, 'GET', url, null, true);
|
||||
const data = resp.data;
|
||||
|
||||
this.loading = false;
|
||||
this.empty = data.length === 0;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {config} from '@/config'
|
|||
import {useErrorStore} from "@/store/error.js";
|
||||
import { useStageStore } from './stage.js'
|
||||
import {usePropertySetsStore} from "@/store/propertySets.js";
|
||||
import performRequest from "@/backend.js";
|
||||
|
||||
export const usePropertiesStore = defineStore('properties', {
|
||||
state() {
|
||||
|
|
@ -44,7 +45,7 @@ export const usePropertiesStore = defineStore('properties', {
|
|||
const url = `${config.backendUrl}/properties/system/${property.id}`;
|
||||
const body = { value: String(property.value)};
|
||||
|
||||
await this.performRequest('PUT', url, body, false);
|
||||
await performRequest(this,'PUT', url, body, false);
|
||||
|
||||
prop.draft_value = property.reset ? null : property.value;
|
||||
|
||||
|
|
@ -59,104 +60,11 @@ export const usePropertiesStore = defineStore('properties', {
|
|||
if (period !== null)
|
||||
params.append('property_set', period);
|
||||
const url = `${config.backendUrl}/properties/${params.size === 0 ? '' : '?'}${params.toString()}`;
|
||||
this.properties = await this.performRequest('GET', url, null);
|
||||
const resp = await performRequest(this,'GET', url, null);
|
||||
this.properties = resp.data;
|
||||
this.loadedPeriod = period;
|
||||
|
||||
this.loading = false;
|
||||
},
|
||||
async performRequest(method, url, body, expectResponse = true) {
|
||||
|
||||
const params = {
|
||||
method: method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
if ((body ?? null) !== null) {
|
||||
params.body = JSON.stringify(body);
|
||||
}
|
||||
|
||||
const request = {url: url, params: params};
|
||||
console.log("Request:", request);
|
||||
|
||||
const response = await fetch(url, params
|
||||
).catch(e => {
|
||||
const error = {
|
||||
code: 'Network error.',
|
||||
message: "Please check your internet connection.",
|
||||
trace: null
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
|
||||
throw e;
|
||||
});
|
||||
|
||||
let data = null;
|
||||
if (expectResponse) {
|
||||
data = await response.json().catch(e => {
|
||||
const error = {
|
||||
code: 'Malformed response',
|
||||
message: "Malformed server response. Please contact support.",
|
||||
trace: null
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw e;
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = {
|
||||
code: data.error.code,
|
||||
title: data.error.title,
|
||||
message: data.error.message,
|
||||
trace: data.error.trace
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw new Error('Internal backend error');
|
||||
}
|
||||
} else {
|
||||
if (!response.ok) {
|
||||
|
||||
const data = await response.json().catch(e => {
|
||||
const error = {
|
||||
code: "Return code error " + response.status,
|
||||
message: "Server returned wrong response code",
|
||||
trace: null
|
||||
}
|
||||
console.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw new Error('Internal backend error');
|
||||
|
||||
});
|
||||
|
||||
const error = {
|
||||
code: data.error.code,
|
||||
title: data.error.title,
|
||||
message: data.error.message,
|
||||
trace: data.error.trace
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw new Error('Internal backend error');
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Response:", data);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -1,9 +1,6 @@
|
|||
import {defineStore} from 'pinia'
|
||||
import {config} from '@/config'
|
||||
import {useErrorStore} from "@/store/error.js";
|
||||
import {useStageStore} from "@/store/stage.js";
|
||||
import {usePropertySetsStore} from "@/store/propertySets.js";
|
||||
import logger from "@/logger.js";
|
||||
import performRequest from "@/backend.js";
|
||||
|
||||
export const useReportSearchStore = defineStore('reportSearch', {
|
||||
state() {
|
||||
|
|
@ -84,107 +81,14 @@ export const useReportSearchStore = defineStore('reportSearch', {
|
|||
params.append('material', this.getMaterialId);
|
||||
const url = `${config.backendUrl}/reports/search/${params.size === 0 ? '' : '?'}${params.toString()}`;
|
||||
|
||||
this.suppliers = await this.performRequest('GET', url, null).catch(e => {
|
||||
const resp = await performRequest(this,'GET', url, null).catch(e => {
|
||||
this.loading = false;
|
||||
});
|
||||
this.suppliers = resp.data;
|
||||
|
||||
this.updateShownSuppliers();
|
||||
|
||||
this.loading = false;
|
||||
},
|
||||
async performRequest(method, url, body, expectResponse = true) {
|
||||
|
||||
const params = {
|
||||
method: method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
if (body) {
|
||||
params.body = JSON.stringify(body);
|
||||
}
|
||||
|
||||
const request = {url: url, params: params};
|
||||
logger.info("Request:", request);
|
||||
|
||||
const response = await fetch(url, params
|
||||
).catch(e => {
|
||||
const error = {
|
||||
code: 'Network error.',
|
||||
message: "Please check your internet connection.",
|
||||
trace: null
|
||||
}
|
||||
|
||||
logger.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
|
||||
throw e;
|
||||
});
|
||||
|
||||
let data = null;
|
||||
if (expectResponse) {
|
||||
data = await response.json().catch(e => {
|
||||
const error = {
|
||||
code: 'Malformed response',
|
||||
message: "Malformed server response. Please contact support.",
|
||||
trace: null
|
||||
}
|
||||
|
||||
logger.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw e;
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = {
|
||||
code: data.error.code,
|
||||
title: data.error.title,
|
||||
message: data.error.message,
|
||||
trace: data.error.trace
|
||||
}
|
||||
|
||||
logger.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw new Error('Internal backend error');
|
||||
}
|
||||
} else {
|
||||
if (!response.ok) {
|
||||
|
||||
const data = await response.json().catch(e => {
|
||||
const error = {
|
||||
code: "Return code error " + response.status,
|
||||
message: "Server returned wrong response code",
|
||||
trace: null
|
||||
}
|
||||
logger.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw new Error('Internal backend error');
|
||||
|
||||
});
|
||||
|
||||
const error = {
|
||||
code: data.error.code,
|
||||
title: data.error.title,
|
||||
message: data.error.message,
|
||||
trace: data.error.trace
|
||||
}
|
||||
|
||||
logger.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw new Error('Internal backend error');
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Response:", data);
|
||||
return data;
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import {defineStore} from 'pinia'
|
||||
import {config} from '@/config'
|
||||
import {useErrorStore} from "@/store/error.js";
|
||||
import performRequest from "@/backend.js";
|
||||
|
||||
export const useStageStore = defineStore('stage', {
|
||||
state() {
|
||||
|
|
@ -16,106 +17,13 @@ export const useStageStore = defineStore('stage', {
|
|||
actions: {
|
||||
async checkStagedChanges() {
|
||||
const url = `${config.backendUrl}/properties/staged_changes`;
|
||||
this.stagedChanges = await this.performRequest('GET', url, null);
|
||||
const resp = await performRequest(this,'GET', url, null);
|
||||
this.stagedChanges = resp.data;
|
||||
},
|
||||
async applyChanges() {
|
||||
const url = `${config.backendUrl}/properties/staged_changes`;
|
||||
await this.performRequest('PUT', url, null, false);
|
||||
await performRequest(this, 'PUT', url, null, false);
|
||||
this.stagedChanges = false;
|
||||
},
|
||||
async performRequest(method, url, body, expectResponse = true) {
|
||||
|
||||
const params = {
|
||||
method: method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
if ((body ?? null) !== null) {
|
||||
params.body = JSON.stringify(body);
|
||||
}
|
||||
|
||||
const request = {url: url, params: params};
|
||||
console.log("Request:", request);
|
||||
|
||||
const response = await fetch(url, params
|
||||
).catch(e => {
|
||||
const error = {
|
||||
code: 'Network error.',
|
||||
message: "Please check your internet connection.",
|
||||
trace: null
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
|
||||
throw e;
|
||||
});
|
||||
|
||||
let data = null;
|
||||
if (expectResponse) {
|
||||
data = await response.json().catch(e => {
|
||||
const error = {
|
||||
code: 'Malformed response',
|
||||
message: "Malformed server response. Please contact support.",
|
||||
trace: null
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw e;
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = {
|
||||
code: data.error.code,
|
||||
title: data.error.title,
|
||||
message: data.error.message,
|
||||
trace: data.error.trace
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw new Error('Internal backend error');
|
||||
}
|
||||
} else {
|
||||
if (!response.ok) {
|
||||
|
||||
const data = await response.json().catch(e => {
|
||||
const error = {
|
||||
code: "Return code error " + response.status,
|
||||
message: "Server returned wrong response code",
|
||||
trace: null
|
||||
}
|
||||
console.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw new Error('Internal backend error');
|
||||
|
||||
});
|
||||
|
||||
const error = {
|
||||
code: data.error.code,
|
||||
title: data.error.title,
|
||||
message: data.error.message,
|
||||
trace: data.error.trace
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
const errorStore = useErrorStore();
|
||||
void errorStore.addError(error, {store: this, request: request});
|
||||
throw new Error('Internal backend error');
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Response:", data);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,12 @@ export default defineConfig({
|
|||
{
|
||||
src: 'assets/flags/*',
|
||||
dest: 'assets/flags'
|
||||
},
|
||||
{
|
||||
src: 'assets/map.json',
|
||||
dest: 'assets/'
|
||||
}
|
||||
|
||||
]
|
||||
})
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
package de.avatic.lcc.config;
|
||||
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* Konfiguration für Azure Maps Integration
|
||||
*/
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
public class AzureMapsConfig {
|
||||
|
||||
/**
|
||||
* RestTemplate Bean für HTTP-Requests
|
||||
*/
|
||||
@Bean
|
||||
public RestTemplate restTemplate(RestTemplateBuilder builder) {
|
||||
return builder
|
||||
.connectTimeout(Duration.ofSeconds(10))
|
||||
.readTimeout(Duration.ofSeconds(30))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache Manager für Token-Caching
|
||||
* Tokens werden für 50 Minuten gecacht (Azure Maps Tokens sind 60 Min gültig)
|
||||
*/
|
||||
@Bean
|
||||
public CacheManager cacheManager() {
|
||||
return new ConcurrentMapCacheManager("azureMapsTokens");
|
||||
}
|
||||
}
|
||||
|
|
@ -45,6 +45,9 @@ public class CorsConfig implements WebMvcConfigurer {
|
|||
.exposedHeaders("X-Total-Count", "X-Page-Count", "X-Current-Page")
|
||||
.allowCredentials(true);
|
||||
} else {
|
||||
|
||||
System.out.println("Applying PROD CORS configuration");
|
||||
|
||||
// Production CORS configuration
|
||||
registry.addMapping("/api/**")
|
||||
.allowedOrigins(allowedCors)
|
||||
|
|
|
|||
|
|
@ -46,13 +46,17 @@ public class DevUserEmulationFilter extends OncePerRequestFilter {
|
|||
HttpSession session = request.getSession(true);
|
||||
Integer emulatedUserId = (Integer) session.getAttribute(DEV_USER_ID_SESSION_KEY);
|
||||
|
||||
// Add logging to debug
|
||||
System.out.println("DevUserEmulationFilter - Session ID: " + session.getId());
|
||||
System.out.println("DevUserEmulationFilter - Emulated User ID: " + emulatedUserId);
|
||||
|
||||
// if(emulatedUserId != null) {
|
||||
User user = userRepository.getById(emulatedUserId == null ? 1 : emulatedUserId);
|
||||
if(emulatedUserId != null) {
|
||||
User user = userRepository.getById(emulatedUserId);
|
||||
if (user != null) {
|
||||
setEmulatedUser(user);
|
||||
System.out.println("DevUserEmulationFilter - Set user: " + user.getEmail());
|
||||
}
|
||||
}
|
||||
// }
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
package de.avatic.lcc.config;
|
||||
|
||||
import de.avatic.lcc.model.users.Group;
|
||||
import de.avatic.lcc.model.users.User;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
|
||||
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public class LccOidcUser extends DefaultOidcUser {
|
||||
|
||||
|
|
@ -20,6 +23,22 @@ public class LccOidcUser extends DefaultOidcUser {
|
|||
this.userId = userId;
|
||||
}
|
||||
|
||||
public static User createDatabaseUser(String email, String firstName, String lastName, String workdayId) {
|
||||
User user = new User();
|
||||
|
||||
Group group = new Group();
|
||||
group.setName("none");
|
||||
|
||||
user.setEmail(email);
|
||||
user.setFirstName(firstName == null ? "" : firstName);
|
||||
user.setLastName(lastName == null ? "" : lastName);
|
||||
user.setGroups(List.of(group));
|
||||
user.setWorkdayId(workdayId == null ? "" : workdayId);
|
||||
user.setActive(false);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
public Integer getSqlUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
|
|
|||
14
src/main/java/de/avatic/lcc/config/RestConfig.java
Normal file
14
src/main/java/de/avatic/lcc/config/RestConfig.java
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
package de.avatic.lcc.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@Configuration
|
||||
public class RestConfig {
|
||||
|
||||
@Bean
|
||||
public RestTemplate restTemplate() {
|
||||
return new RestTemplate();
|
||||
}
|
||||
}
|
||||
|
|
@ -33,16 +33,18 @@ import java.util.HashSet;
|
|||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
|
||||
@Configuration
|
||||
@EnableMethodSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
@Profile("!dev & !test") // Only active when NOT in dev profile
|
||||
@Profile("!dev & !test & !dev_id") // Only active when NOT in dev profile
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers("/api/**").authenticated()
|
||||
.requestMatchers("/api/dev/**").denyAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.oauth2Login(oauth2 -> oauth2
|
||||
|
|
@ -93,37 +95,43 @@ public class SecurityConfig {
|
|||
|
||||
Set<GrantedAuthority> mappedAuthorities = new HashSet<>(oidcUser.getAuthorities());
|
||||
|
||||
User user = null;
|
||||
|
||||
String workdayId = oidcUser.getAttribute("workday_id");
|
||||
if (workdayId != null) {
|
||||
User user = userRepository.getByWorkdayId(workdayId);
|
||||
if (user != null) {
|
||||
user.getGroups().forEach(group -> mappedAuthorities.add(new SimpleGrantedAuthority("ROLE_" + group.getName())));
|
||||
userId = user.getId();
|
||||
}
|
||||
}
|
||||
|
||||
// Try different ways to get email
|
||||
String email = oidcUser.getEmail();
|
||||
if (email == null) {
|
||||
email = oidcUser.getAttribute("email");
|
||||
}
|
||||
if (email == null) {
|
||||
email = oidcUser.getAttribute("preferred_username");
|
||||
}
|
||||
if (email == null) {
|
||||
email = oidcUser.getAttribute("upn");
|
||||
}
|
||||
if (email == null) {
|
||||
email = oidcUser.getAttribute("preferred_username");
|
||||
}
|
||||
|
||||
if (email != null) {
|
||||
User user = userRepository.getByEmail(email);
|
||||
|
||||
if (workdayId != null) {
|
||||
user = userRepository.getByWorkdayId(workdayId);
|
||||
if (user != null) {
|
||||
user.getGroups().forEach(group -> mappedAuthorities.add(new SimpleGrantedAuthority("ROLE_" + group.getName())));
|
||||
userId = user.getId();
|
||||
}
|
||||
} else if (email != null) {
|
||||
user = userRepository.getByEmail(email);
|
||||
if (user != null) {
|
||||
user.getGroups().forEach(group -> mappedAuthorities.add(new SimpleGrantedAuthority("ROLE_" + group.getName().toUpperCase())));
|
||||
userId = user.getId();
|
||||
} else {
|
||||
mappedAuthorities.add(new SimpleGrantedAuthority("ROLE_BASIC"));
|
||||
}
|
||||
}
|
||||
|
||||
if (user == null) {
|
||||
userRepository.update(LccOidcUser.createDatabaseUser(email, oidcUser.getGivenName(), oidcUser.getFamilyName(), workdayId));
|
||||
mappedAuthorities.add(new SimpleGrantedAuthority("ROLE_NONE"));
|
||||
}
|
||||
|
||||
|
||||
return new LccOidcUser(
|
||||
mappedAuthorities,
|
||||
oidcUser.getIdToken(),
|
||||
|
|
@ -132,9 +140,10 @@ public class SecurityConfig {
|
|||
userId
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static final class LccCsrfTokenRequestHandler extends CsrfTokenRequestAttributeHandler {
|
||||
private final CsrfTokenRequestHandler delegate = new CsrfTokenRequestAttributeHandler();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,16 @@
|
|||
package de.avatic.lcc.controller.configuration;
|
||||
|
||||
import de.avatic.lcc.dto.configuration.nodes.userNodes.LocateNodeDTO;
|
||||
import de.avatic.lcc.dto.generic.NodeDTO;
|
||||
import de.avatic.lcc.dto.configuration.nodes.view.NodeDetailDTO;
|
||||
import de.avatic.lcc.dto.configuration.nodes.update.NodeUpdateDTO;
|
||||
import de.avatic.lcc.dto.generic.NodeType;
|
||||
import de.avatic.lcc.dto.configuration.nodes.userNodes.AddUserNodeDTO;
|
||||
import de.avatic.lcc.model.nodes.Location;
|
||||
import de.avatic.lcc.repositories.pagination.SearchQueryResult;
|
||||
import de.avatic.lcc.service.GeoApiService;
|
||||
import de.avatic.lcc.service.api.GeoApiService;
|
||||
import de.avatic.lcc.service.access.NodeService;
|
||||
import de.avatic.lcc.service.access.UserNodeService;
|
||||
import de.avatic.lcc.util.Check;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
|
|
@ -77,7 +76,7 @@ public class NodeController {
|
|||
|
||||
@GetMapping("/locate")
|
||||
@PreAuthorize("hasAnyRole('SUPER', 'FREIGHT', 'PACKAGING')")
|
||||
public ResponseEntity<LocateNodeDTO> locateNode(@RequestParam String address) {
|
||||
public ResponseEntity<Location> locateNode(@RequestParam String address) {
|
||||
return ResponseEntity.ok(geoApiService.locate(address));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package de.avatic.lcc.controller.custom;
|
||||
|
||||
import de.avatic.lcc.service.CustomApiService;
|
||||
import de.avatic.lcc.service.api.CustomApiService;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
|
|
|||
|
|
@ -1,221 +1,48 @@
|
|||
package de.avatic.lcc.controller.maps;
|
||||
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import com.azure.core.credential.AccessToken;
|
||||
import com.azure.identity.DefaultAzureCredentialBuilder;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* REST Controller für Azure Maps mit Entra ID Integration
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/maps")
|
||||
@CrossOrigin(origins = {"http://localhost:3000", "http://localhost:8080", "https://yourdomain.com"})
|
||||
@RolesAllowed({"ROLE_SUPER", "ROLE_CALCULATION"})
|
||||
public class AzureMapsController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(AzureMapsController.class);
|
||||
|
||||
@Value("${azure.maps.client.id}")
|
||||
private String azureMapsClientId;
|
||||
private String mapsClientId;
|
||||
|
||||
@Value("${azure.maps.resource.id:}")
|
||||
private String azureMapsResourceId;
|
||||
@Value("${azure.maps.subscription.key}")
|
||||
private String mapsSubscriptionKey;
|
||||
|
||||
/**
|
||||
* Endpoint zum Abrufen der Azure Maps Konfiguration
|
||||
* Nur authentifizierte Benutzer können diese Konfiguration abrufen
|
||||
*
|
||||
* @return ConfigResponse mit Client ID und anderen Konfigurationsdaten
|
||||
*/
|
||||
@GetMapping("/config")
|
||||
@PreAuthorize("hasAuthority('SCOPE_https://yourdomain.com/api.read')") // Anpassen an Ihre API-Scopes
|
||||
public ResponseEntity<?> getConfig() {
|
||||
@GetMapping("/token")
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
public ResponseEntity<Map<String, Object>> getAzureMapsToken() {
|
||||
try {
|
||||
logger.debug("Providing Azure Maps configuration for authenticated user");
|
||||
// Verwende die DefaultAzureCredential für die Authentifizierung
|
||||
var credential = new DefaultAzureCredentialBuilder().build();
|
||||
|
||||
if (azureMapsClientId == null || azureMapsClientId.trim().isEmpty()) {
|
||||
logger.error("Azure Maps Client ID is null or empty - check application.properties");
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(new ErrorResponse("Azure Maps Client ID not configured"));
|
||||
}
|
||||
// Fordere ein Token für Azure Maps an
|
||||
AccessToken token = credential.getToken(
|
||||
new com.azure.core.credential.TokenRequestContext()
|
||||
.addScopes("https://atlas.microsoft.com/.default")
|
||||
).block();
|
||||
|
||||
ConfigResponse response = new ConfigResponse(
|
||||
azureMapsClientId.trim(),
|
||||
azureMapsResourceId != null ? azureMapsResourceId.trim() : null
|
||||
);
|
||||
|
||||
logger.debug("Successfully providing Azure Maps configuration");
|
||||
return ResponseEntity.ok(response);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error providing Azure Maps configuration: {}", e.getMessage(), e);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(new ErrorResponse("Internal server error: " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Health Check Endpoint für Azure Maps Integration
|
||||
* Öffentlich zugänglich für Monitoring
|
||||
*
|
||||
* @return Status der Azure Maps Konfiguration
|
||||
*/
|
||||
@GetMapping("/health")
|
||||
public ResponseEntity<?> healthCheck() {
|
||||
try {
|
||||
boolean isConfigured = azureMapsClientId != null && !azureMapsClientId.trim().isEmpty();
|
||||
|
||||
if (isConfigured) {
|
||||
return ResponseEntity.ok(new HealthResponse("Azure Maps Entra ID configuration is valid", true));
|
||||
} else {
|
||||
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
|
||||
.body(new HealthResponse("Azure Maps Client ID is not configured", false));
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Health check failed", e);
|
||||
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
|
||||
.body(new HealthResponse("Azure Maps service is unavailable", false));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint für rollenbasierte Zugriffskontrolle
|
||||
* Prüft ob der Benutzer Azure Maps verwenden darf
|
||||
*/
|
||||
@GetMapping("/permissions")
|
||||
@PreAuthorize("hasAuthority('SCOPE_https://yourdomain.com/api.read')")
|
||||
public ResponseEntity<?> checkPermissions() {
|
||||
try {
|
||||
// Hier können Sie zusätzliche Berechtigungsprüfungen implementieren
|
||||
// z.B. basierend auf Benutzerrolle, Gruppe, etc.
|
||||
|
||||
PermissionResponse response = new PermissionResponse(
|
||||
true, // canUseAzureMaps
|
||||
true, // canCreateMarkers
|
||||
true // canModifyMarkers
|
||||
);
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("token", token.getToken());
|
||||
response.put("expiresOn", token.getExpiresAt().toEpochSecond());
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error checking permissions: {}", e.getMessage(), e);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(new ErrorResponse("Error checking permissions: " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Response-Klasse für Konfiguration
|
||||
*/
|
||||
public static class ConfigResponse {
|
||||
private final String clientId;
|
||||
private final String resourceId;
|
||||
private final long timestamp;
|
||||
|
||||
public ConfigResponse(String clientId, String resourceId) {
|
||||
this.clientId = clientId;
|
||||
this.resourceId = resourceId;
|
||||
this.timestamp = Instant.now().toEpochMilli();
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public String getResourceId() {
|
||||
return resourceId;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Response-Klasse für Berechtigungen
|
||||
*/
|
||||
public static class PermissionResponse {
|
||||
private final boolean canUseAzureMaps;
|
||||
private final boolean canCreateMarkers;
|
||||
private final boolean canModifyMarkers;
|
||||
private final long timestamp;
|
||||
|
||||
public PermissionResponse(boolean canUseAzureMaps, boolean canCreateMarkers, boolean canModifyMarkers) {
|
||||
this.canUseAzureMaps = canUseAzureMaps;
|
||||
this.canCreateMarkers = canCreateMarkers;
|
||||
this.canModifyMarkers = canModifyMarkers;
|
||||
this.timestamp = Instant.now().toEpochMilli();
|
||||
}
|
||||
|
||||
public boolean isCanUseAzureMaps() {
|
||||
return canUseAzureMaps;
|
||||
}
|
||||
|
||||
public boolean isCanCreateMarkers() {
|
||||
return canCreateMarkers;
|
||||
}
|
||||
|
||||
public boolean isCanModifyMarkers() {
|
||||
return canModifyMarkers;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Response-Klasse für Fehler-Antworten
|
||||
*/
|
||||
public static class ErrorResponse {
|
||||
private final String error;
|
||||
private final long timestamp;
|
||||
|
||||
public ErrorResponse(String error) {
|
||||
this.error = error;
|
||||
this.timestamp = Instant.now().toEpochMilli();
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Response-Klasse für Health Check
|
||||
*/
|
||||
public static class HealthResponse {
|
||||
private final String message;
|
||||
private final boolean healthy;
|
||||
private final long timestamp;
|
||||
|
||||
public HealthResponse(String message, boolean healthy) {
|
||||
this.message = message;
|
||||
this.healthy = healthy;
|
||||
this.timestamp = Instant.now().toEpochMilli();
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public boolean isHealthy() {
|
||||
return healthy;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
return ResponseEntity.internalServerError().build();
|
||||
}
|
||||
}
|
||||
}
|
||||
87
src/main/java/de/avatic/lcc/model/azuremaps/Address.java
Normal file
87
src/main/java/de/avatic/lcc/model/azuremaps/Address.java
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
package de.avatic.lcc.model.azuremaps;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Address {
|
||||
|
||||
@JsonProperty("addressLine")
|
||||
private String addressLine;
|
||||
|
||||
@JsonProperty("locality")
|
||||
private String locality;
|
||||
|
||||
@JsonProperty("neighborhood")
|
||||
private String neighborhood;
|
||||
|
||||
@JsonProperty("adminDistricts")
|
||||
private List<AdminDistrict> adminDistricts;
|
||||
|
||||
@JsonProperty("postalCode")
|
||||
private String postalCode;
|
||||
|
||||
@JsonProperty("countryRegion")
|
||||
private CountryRegion countryRegion;
|
||||
|
||||
@JsonProperty("formattedAddress")
|
||||
private String formattedAddress;
|
||||
|
||||
public String getAddressLine() {
|
||||
return addressLine;
|
||||
}
|
||||
|
||||
public void setAddressLine(String addressLine) {
|
||||
this.addressLine = addressLine;
|
||||
}
|
||||
|
||||
public String getLocality() {
|
||||
return locality;
|
||||
}
|
||||
|
||||
public void setLocality(String locality) {
|
||||
this.locality = locality;
|
||||
}
|
||||
|
||||
public String getNeighborhood() {
|
||||
return neighborhood;
|
||||
}
|
||||
|
||||
public void setNeighborhood(String neighborhood) {
|
||||
this.neighborhood = neighborhood;
|
||||
}
|
||||
|
||||
public List<AdminDistrict> getAdminDistricts() {
|
||||
return adminDistricts;
|
||||
}
|
||||
|
||||
public void setAdminDistricts(List<AdminDistrict> adminDistricts) {
|
||||
this.adminDistricts = adminDistricts;
|
||||
}
|
||||
|
||||
public String getPostalCode() {
|
||||
return postalCode;
|
||||
}
|
||||
|
||||
public void setPostalCode(String postalCode) {
|
||||
this.postalCode = postalCode;
|
||||
}
|
||||
|
||||
public CountryRegion getCountryRegion() {
|
||||
return countryRegion;
|
||||
}
|
||||
|
||||
public void setCountryRegion(CountryRegion countryRegion) {
|
||||
this.countryRegion = countryRegion;
|
||||
}
|
||||
|
||||
public String getFormattedAddress() {
|
||||
return formattedAddress;
|
||||
}
|
||||
|
||||
public void setFormattedAddress(String formattedAddress) {
|
||||
this.formattedAddress = formattedAddress;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package de.avatic.lcc.model.azuremaps;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class AdminDistrict {
|
||||
|
||||
@JsonProperty("shortName")
|
||||
private String shortName;
|
||||
|
||||
@JsonProperty("name")
|
||||
private String name;
|
||||
|
||||
public String getShortName() {
|
||||
return shortName;
|
||||
}
|
||||
|
||||
public void setShortName(String shortName) {
|
||||
this.shortName = shortName;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package de.avatic.lcc.model.azuremaps;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class BatchGeocodingRequest {
|
||||
|
||||
@JsonProperty("batchItems")
|
||||
private List<BatchItem> batchItems;
|
||||
|
||||
public BatchGeocodingRequest() {
|
||||
}
|
||||
|
||||
public BatchGeocodingRequest(List<BatchItem> batchItems) {
|
||||
this.batchItems = batchItems;
|
||||
}
|
||||
|
||||
public List<BatchItem> getBatchItems() {
|
||||
return batchItems;
|
||||
}
|
||||
|
||||
public void setBatchItems(List<BatchItem> batchItems) {
|
||||
this.batchItems = batchItems;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package de.avatic.lcc.model.azuremaps;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class BatchGeocodingResponse {
|
||||
|
||||
@JsonProperty("summary")
|
||||
private Summary summary;
|
||||
|
||||
@JsonProperty("batchItems")
|
||||
private List<BatchResponseItem> batchItems;
|
||||
|
||||
public Summary getSummary() {
|
||||
return summary;
|
||||
}
|
||||
|
||||
public void setSummary(Summary summary) {
|
||||
this.summary = summary;
|
||||
}
|
||||
|
||||
public List<BatchResponseItem> getBatchItems() {
|
||||
return batchItems;
|
||||
}
|
||||
|
||||
public void setBatchItems(List<BatchResponseItem> batchItems) {
|
||||
this.batchItems = batchItems;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package de.avatic.lcc.model.azuremaps;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class BatchGeocodingResult {
|
||||
|
||||
private final List<GeocodingResult> results;
|
||||
private final int successfulRequests;
|
||||
private final int totalRequests;
|
||||
|
||||
public BatchGeocodingResult(List<GeocodingResult> results, int successfulRequests, int totalRequests) {
|
||||
this.results = results;
|
||||
this.successfulRequests = successfulRequests;
|
||||
this.totalRequests = totalRequests;
|
||||
}
|
||||
|
||||
public List<GeocodingResult> getResults() {
|
||||
return results;
|
||||
}
|
||||
|
||||
public int getSuccessfulRequests() {
|
||||
return successfulRequests;
|
||||
}
|
||||
|
||||
public int getTotalRequests() {
|
||||
return totalRequests;
|
||||
}
|
||||
|
||||
public int getFailedRequests() {
|
||||
return totalRequests - successfulRequests;
|
||||
}
|
||||
|
||||
public boolean isFullySuccessful() {
|
||||
return successfulRequests == totalRequests;
|
||||
}
|
||||
|
||||
public GeocodingResult getResult(int index) {
|
||||
if (index >= 0 && index < results.size()) {
|
||||
return results.get(index);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
56
src/main/java/de/avatic/lcc/model/azuremaps/BatchItem.java
Normal file
56
src/main/java/de/avatic/lcc/model/azuremaps/BatchItem.java
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
package de.avatic.lcc.model.azuremaps;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class BatchItem {
|
||||
@JsonProperty("addressLine")
|
||||
private String addressLine;
|
||||
|
||||
@JsonProperty("adminDistrict")
|
||||
private String adminDistrict;
|
||||
|
||||
@JsonProperty("locality")
|
||||
private String locality;
|
||||
|
||||
@JsonProperty("postalCode")
|
||||
private String postalCode;
|
||||
|
||||
public BatchItem() {
|
||||
}
|
||||
|
||||
public BatchItem(String addressLine) {
|
||||
this.addressLine = addressLine;
|
||||
}
|
||||
|
||||
public String getAddressLine() {
|
||||
return addressLine;
|
||||
}
|
||||
|
||||
public void setAddressLine(String addressLine) {
|
||||
this.addressLine = addressLine;
|
||||
}
|
||||
|
||||
public String getAdminDistrict() {
|
||||
return adminDistrict;
|
||||
}
|
||||
|
||||
public void setAdminDistrict(String adminDistrict) {
|
||||
this.adminDistrict = adminDistrict;
|
||||
}
|
||||
|
||||
public String getLocality() {
|
||||
return locality;
|
||||
}
|
||||
|
||||
public void setLocality(String locality) {
|
||||
this.locality = locality;
|
||||
}
|
||||
|
||||
public String getPostalCode() {
|
||||
return postalCode;
|
||||
}
|
||||
|
||||
public void setPostalCode(String postalCode) {
|
||||
this.postalCode = postalCode;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package de.avatic.lcc.model.azuremaps;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class BatchResponseItem {
|
||||
@JsonProperty("statusCode")
|
||||
private Integer statusCode;
|
||||
|
||||
@JsonProperty("response")
|
||||
private GeocodingResponse response;
|
||||
|
||||
public Integer getStatusCode() {
|
||||
return statusCode;
|
||||
}
|
||||
|
||||
public void setStatusCode(Integer statusCode) {
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
public GeocodingResponse getResponse() {
|
||||
return response;
|
||||
}
|
||||
|
||||
public void setResponse(GeocodingResponse response) {
|
||||
this.response = response;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package de.avatic.lcc.model.azuremaps;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class CountryRegion {
|
||||
|
||||
@JsonProperty("ISO")
|
||||
private String iso;
|
||||
|
||||
@JsonProperty("name")
|
||||
private String name;
|
||||
|
||||
public String getIso() {
|
||||
return iso;
|
||||
}
|
||||
|
||||
public void setIso(String iso) {
|
||||
this.iso = iso;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
54
src/main/java/de/avatic/lcc/model/azuremaps/Feature.java
Normal file
54
src/main/java/de/avatic/lcc/model/azuremaps/Feature.java
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
package de.avatic.lcc.model.azuremaps;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Feature {
|
||||
|
||||
@JsonProperty("type")
|
||||
private String type;
|
||||
|
||||
@JsonProperty("properties")
|
||||
private Properties properties;
|
||||
|
||||
@JsonProperty("geometry")
|
||||
private Geometry geometry;
|
||||
|
||||
@JsonProperty("bbox")
|
||||
private List<Double> bbox;
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Properties getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
public void setProperties(Properties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
public Geometry getGeometry() {
|
||||
return geometry;
|
||||
}
|
||||
|
||||
public void setGeometry(Geometry geometry) {
|
||||
this.geometry = geometry;
|
||||
}
|
||||
|
||||
public List<Double> getBbox() {
|
||||
return bbox;
|
||||
}
|
||||
|
||||
public void setBbox(List<Double> bbox) {
|
||||
this.bbox = bbox;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package de.avatic.lcc.model.azuremaps;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class GeocodePoint {
|
||||
|
||||
@JsonProperty("geometry")
|
||||
private Geometry geometry;
|
||||
|
||||
@JsonProperty("calculationMethod")
|
||||
private String calculationMethod;
|
||||
|
||||
@JsonProperty("usageTypes")
|
||||
private List<String> usageTypes;
|
||||
|
||||
public Geometry getGeometry() {
|
||||
return geometry;
|
||||
}
|
||||
|
||||
public void setGeometry(Geometry geometry) {
|
||||
this.geometry = geometry;
|
||||
}
|
||||
|
||||
public String getCalculationMethod() {
|
||||
return calculationMethod;
|
||||
}
|
||||
|
||||
public void setCalculationMethod(String calculationMethod) {
|
||||
this.calculationMethod = calculationMethod;
|
||||
}
|
||||
|
||||
public List<String> getUsageTypes() {
|
||||
return usageTypes;
|
||||
}
|
||||
|
||||
public void setUsageTypes(List<String> usageTypes) {
|
||||
this.usageTypes = usageTypes;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package de.avatic.lcc.model.azuremaps;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class GeocodingResponse {
|
||||
|
||||
@JsonProperty("type")
|
||||
private String type;
|
||||
|
||||
@JsonProperty("features")
|
||||
private List<Feature> features;
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public List<Feature> getFeatures() {
|
||||
return features;
|
||||
}
|
||||
|
||||
public void setFeatures(List<Feature> features) {
|
||||
this.features = features;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package de.avatic.lcc.model.azuremaps;
|
||||
|
||||
import de.avatic.lcc.model.nodes.Location;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GeocodingResult {
|
||||
|
||||
private final Feature feature;
|
||||
|
||||
public GeocodingResult(Feature feature) {
|
||||
this.feature = feature;
|
||||
}
|
||||
|
||||
public Location getLocation() {
|
||||
if (feature.getGeometry() != null &&
|
||||
feature.getGeometry().getCoordinates() != null &&
|
||||
feature.getGeometry().getCoordinates().size() >= 2) {
|
||||
// Azure Maps returns [longitude, latitude]
|
||||
return new Location(
|
||||
feature.getGeometry().getCoordinates().get(0),
|
||||
feature.getGeometry().getCoordinates().get(1)
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Address getAddress() {
|
||||
return feature.getProperties() != null ? feature.getProperties().getAddress() : null;
|
||||
}
|
||||
|
||||
public String getConfidence() {
|
||||
return feature.getProperties() != null ? feature.getProperties().getConfidence() : null;
|
||||
}
|
||||
|
||||
public List<String> getMatchCodes() {
|
||||
return feature.getProperties() != null ? feature.getProperties().getMatchCodes() : null;
|
||||
}
|
||||
|
||||
public String getGeocodeLevel() {
|
||||
return feature.getProperties() != null &&
|
||||
feature.getProperties().getGeocodePoints() != null &&
|
||||
!feature.getProperties().getGeocodePoints().isEmpty()
|
||||
? feature.getProperties().getGeocodePoints().getFirst().getCalculationMethod() : null;
|
||||
}
|
||||
|
||||
public List<GeocodePoint> getGeocodePoints() {
|
||||
return feature.getProperties() != null ? feature.getProperties().getGeocodePoints() : null;
|
||||
}
|
||||
|
||||
public Feature getRawFeature() {
|
||||
return feature;
|
||||
}
|
||||
}
|
||||
32
src/main/java/de/avatic/lcc/model/azuremaps/Geometry.java
Normal file
32
src/main/java/de/avatic/lcc/model/azuremaps/Geometry.java
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
package de.avatic.lcc.model.azuremaps;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Geometry {
|
||||
|
||||
@JsonProperty("type")
|
||||
private String type;
|
||||
|
||||
@JsonProperty("coordinates")
|
||||
private List<Double> coordinates;
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public List<Double> getCoordinates() {
|
||||
return coordinates;
|
||||
}
|
||||
|
||||
public void setCoordinates(List<Double> coordinates) {
|
||||
this.coordinates = coordinates;
|
||||
}
|
||||
}
|
||||
54
src/main/java/de/avatic/lcc/model/azuremaps/Properties.java
Normal file
54
src/main/java/de/avatic/lcc/model/azuremaps/Properties.java
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
package de.avatic.lcc.model.azuremaps;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Properties {
|
||||
|
||||
@JsonProperty("confidence")
|
||||
private String confidence;
|
||||
|
||||
@JsonProperty("matchCodes")
|
||||
private List<String> matchCodes;
|
||||
|
||||
@JsonProperty("geocodePoints")
|
||||
private List<GeocodePoint> geocodePoints;
|
||||
|
||||
@JsonProperty("address")
|
||||
private Address address;
|
||||
|
||||
public String getConfidence() {
|
||||
return confidence;
|
||||
}
|
||||
|
||||
public void setConfidence(String confidence) {
|
||||
this.confidence = confidence;
|
||||
}
|
||||
|
||||
public List<String> getMatchCodes() {
|
||||
return matchCodes;
|
||||
}
|
||||
|
||||
public void setMatchCodes(List<String> matchCodes) {
|
||||
this.matchCodes = matchCodes;
|
||||
}
|
||||
|
||||
public List<GeocodePoint> getGeocodePoints() {
|
||||
return geocodePoints;
|
||||
}
|
||||
|
||||
public void setGeocodePoints(List<GeocodePoint> geocodePoints) {
|
||||
this.geocodePoints = geocodePoints;
|
||||
}
|
||||
|
||||
public Address getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public void setAddress(Address address) {
|
||||
this.address = address;
|
||||
}
|
||||
}
|
||||
36
src/main/java/de/avatic/lcc/model/azuremaps/Route.java
Normal file
36
src/main/java/de/avatic/lcc/model/azuremaps/Route.java
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
package de.avatic.lcc.model.azuremaps;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import java.util.List;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Route {
|
||||
|
||||
private RouteSummary summary;
|
||||
private List<RouteLeg> legs;
|
||||
private List<RouteSection> sections;
|
||||
|
||||
public RouteSummary getSummary() {
|
||||
return summary;
|
||||
}
|
||||
|
||||
public void setSummary(RouteSummary summary) {
|
||||
this.summary = summary;
|
||||
}
|
||||
|
||||
public List<RouteLeg> getLegs() {
|
||||
return legs;
|
||||
}
|
||||
|
||||
public void setLegs(List<RouteLeg> legs) {
|
||||
this.legs = legs;
|
||||
}
|
||||
|
||||
public List<RouteSection> getSections() {
|
||||
return sections;
|
||||
}
|
||||
|
||||
public void setSections(List<RouteSection> sections) {
|
||||
this.sections = sections;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package de.avatic.lcc.model.azuremaps;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import java.util.List;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class RouteDirectionsResponse {
|
||||
|
||||
private String formatVersion;
|
||||
private List<Route> routes;
|
||||
|
||||
public String getFormatVersion() {
|
||||
return formatVersion;
|
||||
}
|
||||
|
||||
public void setFormatVersion(String formatVersion) {
|
||||
this.formatVersion = formatVersion;
|
||||
}
|
||||
|
||||
public List<Route> getRoutes() {
|
||||
return routes;
|
||||
}
|
||||
|
||||
public void setRoutes(List<Route> routes) {
|
||||
this.routes = routes;
|
||||
}
|
||||
}
|
||||
27
src/main/java/de/avatic/lcc/model/azuremaps/RouteLeg.java
Normal file
27
src/main/java/de/avatic/lcc/model/azuremaps/RouteLeg.java
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
package de.avatic.lcc.model.azuremaps;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import java.util.List;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class RouteLeg {
|
||||
|
||||
private RouteSummary summary;
|
||||
private List<RoutePoint> points;
|
||||
|
||||
public RouteSummary getSummary() {
|
||||
return summary;
|
||||
}
|
||||
|
||||
public void setSummary(RouteSummary summary) {
|
||||
this.summary = summary;
|
||||
}
|
||||
|
||||
public List<RoutePoint> getPoints() {
|
||||
return points;
|
||||
}
|
||||
|
||||
public void setPoints(List<RoutePoint> points) {
|
||||
this.points = points;
|
||||
}
|
||||
}
|
||||
26
src/main/java/de/avatic/lcc/model/azuremaps/RoutePoint.java
Normal file
26
src/main/java/de/avatic/lcc/model/azuremaps/RoutePoint.java
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
package de.avatic.lcc.model.azuremaps;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class RoutePoint {
|
||||
|
||||
private Double latitude;
|
||||
private Double longitude;
|
||||
|
||||
public Double getLatitude() {
|
||||
return latitude;
|
||||
}
|
||||
|
||||
public void setLatitude(Double latitude) {
|
||||
this.latitude = latitude;
|
||||
}
|
||||
|
||||
public Double getLongitude() {
|
||||
return longitude;
|
||||
}
|
||||
|
||||
public void setLongitude(Double longitude) {
|
||||
this.longitude = longitude;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package de.avatic.lcc.model.azuremaps;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class RouteSection {
|
||||
|
||||
private Integer startPointIndex;
|
||||
private Integer endPointIndex;
|
||||
private String sectionType;
|
||||
private String travelMode;
|
||||
|
||||
public Integer getStartPointIndex() {
|
||||
return startPointIndex;
|
||||
}
|
||||
|
||||
public void setStartPointIndex(Integer startPointIndex) {
|
||||
this.startPointIndex = startPointIndex;
|
||||
}
|
||||
|
||||
public Integer getEndPointIndex() {
|
||||
return endPointIndex;
|
||||
}
|
||||
|
||||
public void setEndPointIndex(Integer endPointIndex) {
|
||||
this.endPointIndex = endPointIndex;
|
||||
}
|
||||
|
||||
public String getSectionType() {
|
||||
return sectionType;
|
||||
}
|
||||
|
||||
public void setSectionType(String sectionType) {
|
||||
this.sectionType = sectionType;
|
||||
}
|
||||
|
||||
public String getTravelMode() {
|
||||
return travelMode;
|
||||
}
|
||||
|
||||
public void setTravelMode(String travelMode) {
|
||||
this.travelMode = travelMode;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package de.avatic.lcc.model.azuremaps;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class RouteSummary {
|
||||
|
||||
private Integer lengthInMeters;
|
||||
private Integer travelTimeInSeconds;
|
||||
private Integer trafficDelayInSeconds;
|
||||
private Integer trafficLengthInMeters;
|
||||
private String departureTime;
|
||||
private String arrivalTime;
|
||||
|
||||
public Integer getLengthInMeters() {
|
||||
return lengthInMeters;
|
||||
}
|
||||
|
||||
public void setLengthInMeters(Integer lengthInMeters) {
|
||||
this.lengthInMeters = lengthInMeters;
|
||||
}
|
||||
|
||||
public Integer getTravelTimeInSeconds() {
|
||||
return travelTimeInSeconds;
|
||||
}
|
||||
|
||||
public void setTravelTimeInSeconds(Integer travelTimeInSeconds) {
|
||||
this.travelTimeInSeconds = travelTimeInSeconds;
|
||||
}
|
||||
|
||||
public Integer getTrafficDelayInSeconds() {
|
||||
return trafficDelayInSeconds;
|
||||
}
|
||||
|
||||
public void setTrafficDelayInSeconds(Integer trafficDelayInSeconds) {
|
||||
this.trafficDelayInSeconds = trafficDelayInSeconds;
|
||||
}
|
||||
|
||||
public Integer getTrafficLengthInMeters() {
|
||||
return trafficLengthInMeters;
|
||||
}
|
||||
|
||||
public void setTrafficLengthInMeters(Integer trafficLengthInMeters) {
|
||||
this.trafficLengthInMeters = trafficLengthInMeters;
|
||||
}
|
||||
|
||||
public String getDepartureTime() {
|
||||
return departureTime;
|
||||
}
|
||||
|
||||
public void setDepartureTime(String departureTime) {
|
||||
this.departureTime = departureTime;
|
||||
}
|
||||
|
||||
public String getArrivalTime() {
|
||||
return arrivalTime;
|
||||
}
|
||||
|
||||
public void setArrivalTime(String arrivalTime) {
|
||||
this.arrivalTime = arrivalTime;
|
||||
}
|
||||
}
|
||||
29
src/main/java/de/avatic/lcc/model/azuremaps/Summary.java
Normal file
29
src/main/java/de/avatic/lcc/model/azuremaps/Summary.java
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
package de.avatic.lcc.model.azuremaps;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Summary {
|
||||
@JsonProperty("successfulRequests")
|
||||
private Integer successfulRequests;
|
||||
|
||||
@JsonProperty("totalRequests")
|
||||
private Integer totalRequests;
|
||||
|
||||
public Integer getSuccessfulRequests() {
|
||||
return successfulRequests;
|
||||
}
|
||||
|
||||
public void setSuccessfulRequests(Integer successfulRequests) {
|
||||
this.successfulRequests = successfulRequests;
|
||||
}
|
||||
|
||||
public Integer getTotalRequests() {
|
||||
return totalRequests;
|
||||
}
|
||||
|
||||
public void setTotalRequests(Integer totalRequests) {
|
||||
this.totalRequests = totalRequests;
|
||||
}
|
||||
}
|
||||
|
|
@ -4,8 +4,9 @@ import de.avatic.lcc.model.nodes.Distance;
|
|||
import de.avatic.lcc.model.nodes.DistanceMatrixState;
|
||||
import de.avatic.lcc.model.nodes.Node;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowCallbackHandler;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
|
|
@ -15,6 +16,8 @@ import java.util.Optional;
|
|||
|
||||
@Repository
|
||||
public class DistanceMatrixRepository {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DistanceMatrixRepository.class);
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
public DistanceMatrixRepository(JdbcTemplate jdbcTemplate) {
|
||||
|
|
@ -22,10 +25,9 @@ public class DistanceMatrixRepository {
|
|||
}
|
||||
|
||||
public Optional<Distance> getDistance(Node src, Node dest) {
|
||||
|
||||
String query = "SELECT * FROM distance_matrix WHERE from_node_id = ? AND to_node_id = ? AND state = ?";
|
||||
|
||||
var distance = jdbcTemplate.query(query, new DistanceMapper(), src.getId(), dest.getId(), DistanceMatrixState.VALID);
|
||||
var distance = jdbcTemplate.query(query, new DistanceMapper(), src.getId(), dest.getId(), DistanceMatrixState.VALID.name());
|
||||
|
||||
if(distance.isEmpty())
|
||||
return Optional.empty();
|
||||
|
|
@ -33,6 +35,71 @@ public class DistanceMatrixRepository {
|
|||
return Optional.of(distance.getFirst());
|
||||
}
|
||||
|
||||
public void saveDistance(Distance distance) {
|
||||
try {
|
||||
// First, check if an entry already exists
|
||||
String checkQuery = "SELECT id FROM distance_matrix WHERE from_node_id = ? AND to_node_id = ?";
|
||||
var existingIds = jdbcTemplate.query(checkQuery,
|
||||
(rs, rowNum) -> rs.getInt("id"),
|
||||
distance.getFromNodeId(),
|
||||
distance.getToNodeId());
|
||||
|
||||
if (!existingIds.isEmpty()) {
|
||||
// Update existing entry
|
||||
String updateQuery = """
|
||||
UPDATE distance_matrix
|
||||
SET from_geo_lat = ?,
|
||||
from_geo_lng = ?,
|
||||
to_geo_lat = ?,
|
||||
to_geo_lng = ?,
|
||||
distance = ?,
|
||||
state = ?,
|
||||
updated_at = ?
|
||||
WHERE from_node_id = ? AND to_node_id = ?
|
||||
""";
|
||||
|
||||
jdbcTemplate.update(updateQuery,
|
||||
distance.getFromGeoLat(),
|
||||
distance.getFromGeoLng(),
|
||||
distance.getToGeoLat(),
|
||||
distance.getToGeoLng(),
|
||||
distance.getDistance(),
|
||||
distance.getState().name(),
|
||||
distance.getUpdatedAt(),
|
||||
distance.getFromNodeId(),
|
||||
distance.getToNodeId());
|
||||
|
||||
logger.info("Updated existing distance entry for nodes {} -> {}",
|
||||
distance.getFromNodeId(), distance.getToNodeId());
|
||||
} else {
|
||||
// Insert new entry
|
||||
String insertQuery = """
|
||||
INSERT INTO distance_matrix
|
||||
(from_node_id, to_node_id, from_geo_lat, from_geo_lng, to_geo_lat, to_geo_lng, distance, state, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""";
|
||||
|
||||
jdbcTemplate.update(insertQuery,
|
||||
distance.getFromNodeId(),
|
||||
distance.getToNodeId(),
|
||||
distance.getFromGeoLat(),
|
||||
distance.getFromGeoLng(),
|
||||
distance.getToGeoLat(),
|
||||
distance.getToGeoLng(),
|
||||
distance.getDistance(),
|
||||
distance.getState().name(),
|
||||
distance.getUpdatedAt());
|
||||
|
||||
logger.info("Inserted new distance entry for nodes {} -> {}",
|
||||
distance.getFromNodeId(), distance.getToNodeId());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error saving distance to database for nodes {} -> {}",
|
||||
distance.getFromNodeId(), distance.getToNodeId(), e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private static class DistanceMapper implements RowMapper<Distance> {
|
||||
@Override
|
||||
public Distance mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||
|
|
@ -48,7 +115,6 @@ public class DistanceMatrixRepository {
|
|||
entity.setState(DistanceMatrixState.valueOf(rs.getString("state")));
|
||||
entity.setUpdatedAt(rs.getTimestamp("updated_at").toLocalDateTime());
|
||||
|
||||
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,8 +108,8 @@ public class UserRepository {
|
|||
return jdbcTemplate.query(
|
||||
query,
|
||||
ps -> {
|
||||
for (int parameterIndex = 1; parameterIndex <= groups.size(); parameterIndex++) {
|
||||
ps.setString(parameterIndex, groups.get(parameterIndex));
|
||||
for (int parameterIndex = 0; parameterIndex < groups.size(); parameterIndex++) {
|
||||
ps.setString(parameterIndex + 1, groups.get(parameterIndex));
|
||||
}
|
||||
},
|
||||
(rs, rowNum) -> rs.getInt("id")
|
||||
|
|
@ -117,26 +117,35 @@ public class UserRepository {
|
|||
|
||||
}
|
||||
|
||||
|
||||
private void updateUserGroupMappings(Integer userId, List<Integer> groups) {
|
||||
|
||||
// Handle empty groups list case
|
||||
if (groups.isEmpty()) {
|
||||
// Delete all mappings for this user
|
||||
jdbcTemplate.update("DELETE FROM sys_user_group_mapping WHERE user_id = ?", userId);
|
||||
return;
|
||||
} else
|
||||
{
|
||||
for (Integer groupId : groups) {
|
||||
jdbcTemplate.update(
|
||||
"INSERT IGNORE INTO sys_user_group_mapping (user_id, group_id) VALUES (?, ?)",
|
||||
userId, groupId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String placeholders = String.join(",", Collections.nCopies(groups.size(), "?"));
|
||||
String query = "DELETE FROM sys_user_group_mapping WHERE user_id = ? AND group_id NOT IN (" + placeholders + ")";
|
||||
|
||||
jdbcTemplate.query(
|
||||
jdbcTemplate.update(
|
||||
query,
|
||||
ps -> {
|
||||
for (int parameterIndex = 0; parameterIndex < groups.size(); parameterIndex++) {
|
||||
ps.setInt(parameterIndex + 1, groups.get(parameterIndex));
|
||||
ps.setInt(1, userId);
|
||||
for (int index = 0; index < groups.size(); index++) {
|
||||
ps.setInt(index + 2, groups.get(index)); // Parameters start at index 2
|
||||
}
|
||||
}
|
||||
},
|
||||
(rs, rowNum) -> rs.getInt("id")
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -152,7 +161,7 @@ public class UserRepository {
|
|||
|
||||
@Transactional
|
||||
public User getByWorkdayId(String workdayId) {
|
||||
List<User> results = jdbcTemplate.query("SELECT id FROM sys_user WHERE workday_id = ?",
|
||||
List<User> results = jdbcTemplate.query("SELECT * FROM sys_user WHERE workday_id = ?",
|
||||
new UserMapper(),
|
||||
workdayId);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
package de.avatic.lcc.service;
|
||||
|
||||
import de.avatic.lcc.dto.configuration.nodes.userNodes.LocateNodeDTO;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class GeoApiService {
|
||||
|
||||
|
||||
public LocateNodeDTO locate(String address) {
|
||||
//TODO implementation missing
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
200
src/main/java/de/avatic/lcc/service/api/BatchGeoApiService.java
Normal file
200
src/main/java/de/avatic/lcc/service/api/BatchGeoApiService.java
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
package de.avatic.lcc.service.api;
|
||||
|
||||
import de.avatic.lcc.excelmodel.ExcelNode;
|
||||
import de.avatic.lcc.model.azuremaps.*;
|
||||
import de.avatic.lcc.model.bulk.BulkInstruction;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class BatchGeoApiService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(BatchGeoApiService.class);
|
||||
private static final String AZURE_MAPS_BATCH_GEOCODING_URL = "https://atlas.microsoft.com/geocode:batch";
|
||||
private static final int MAX_BATCH_SIZE = 100; // Azure Maps batch limit
|
||||
|
||||
private final RestTemplate restTemplate;
|
||||
private final String subscriptionKey;
|
||||
|
||||
public BatchGeoApiService(RestTemplate restTemplate,
|
||||
@Value("${azure.maps.subscription.key}") String subscriptionKey) {
|
||||
this.restTemplate = restTemplate;
|
||||
this.subscriptionKey = subscriptionKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Geocode multiple addresses in a single batch request
|
||||
* @param addresses List of address strings to geocode
|
||||
* @return BatchGeocodingResult containing all results
|
||||
*/
|
||||
public BatchGeocodingResult geocodeBatch(List<BulkInstruction<ExcelNode>> nodes) {
|
||||
if (nodes == null || nodes.isEmpty()) {
|
||||
logger.warn("Address list is null or empty");
|
||||
return new BatchGeocodingResult(new ArrayList<>(), 0, 0);
|
||||
}
|
||||
|
||||
ArrayList<BulkInstruction<ExcelNode>> noGeo = new ArrayList<>();
|
||||
|
||||
for(var node : nodes) {
|
||||
if(node.getEntity().getGeoLat() == null || node.getEntity().getGeoLng() == null) {
|
||||
noGeo.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
// Split into chunks if exceeds max batch size
|
||||
if (noGeo.size() > MAX_BATCH_SIZE) {
|
||||
return geocodeLargeBatch(noGeo);
|
||||
}
|
||||
|
||||
List<BatchItem> batchItems = noGeo.stream()
|
||||
.map(BulkInstruction::getEntity).map(ExcelNode::getAddress).map(BatchItem::new)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return executeBatchRequest(batchItems);
|
||||
}
|
||||
|
||||
/**
|
||||
* Geocode multiple addresses with detailed address components
|
||||
* @param batchItems List of BatchItem with detailed address information
|
||||
* @return BatchGeocodingResult containing all results
|
||||
*/
|
||||
public BatchGeocodingResult geocodeBatchDetailed(List<BatchItem> batchItems) {
|
||||
if (batchItems == null || batchItems.isEmpty()) {
|
||||
logger.warn("Batch items list is null or empty");
|
||||
return new BatchGeocodingResult(new ArrayList<>(), 0, 0);
|
||||
}
|
||||
|
||||
// Split into chunks if exceeds max batch size
|
||||
if (batchItems.size() > MAX_BATCH_SIZE) {
|
||||
return geocodeLargeBatchDetailed(batchItems);
|
||||
}
|
||||
|
||||
return executeBatchRequest(batchItems);
|
||||
}
|
||||
|
||||
private BatchGeocodingResult executeBatchRequest(List<BatchItem> batchItems) {
|
||||
try {
|
||||
URI uri = UriComponentsBuilder.fromUriString(AZURE_MAPS_BATCH_GEOCODING_URL)
|
||||
.queryParam("api-version", "2023-06-01")
|
||||
.queryParam("subscription-key", subscriptionKey)
|
||||
.build()
|
||||
.toUri();
|
||||
|
||||
BatchGeocodingRequest request = new BatchGeocodingRequest(batchItems);
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
|
||||
HttpEntity<BatchGeocodingRequest> entity = new HttpEntity<>(request, headers);
|
||||
|
||||
logger.debug("Calling Azure Maps Batch API for {} addresses", batchItems.size());
|
||||
|
||||
ResponseEntity<BatchGeocodingResponse> responseEntity = restTemplate.exchange(
|
||||
uri,
|
||||
HttpMethod.POST,
|
||||
entity,
|
||||
BatchGeocodingResponse.class
|
||||
);
|
||||
|
||||
BatchGeocodingResponse response = responseEntity.getBody();
|
||||
|
||||
if (response != null) {
|
||||
List<GeocodingResult> results = processResponse(response);
|
||||
int successful = response.getSummary() != null ?
|
||||
response.getSummary().getSuccessfulRequests() : 0;
|
||||
int total = response.getSummary() != null ?
|
||||
response.getSummary().getTotalRequests() : batchItems.size();
|
||||
|
||||
logger.info("Batch geocoding completed: {}/{} successful", successful, total);
|
||||
return new BatchGeocodingResult(results, successful, total);
|
||||
}
|
||||
|
||||
logger.warn("Received null response from batch geocoding");
|
||||
return new BatchGeocodingResult(new ArrayList<>(), 0, batchItems.size());
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to execute batch geocoding request", e);
|
||||
throw new RuntimeException("Batch geocoding failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
private List<GeocodingResult> processResponse(BatchGeocodingResponse response) {
|
||||
List<GeocodingResult> results = new ArrayList<>();
|
||||
|
||||
if (response.getBatchItems() != null) {
|
||||
for (BatchResponseItem item : response.getBatchItems()) {
|
||||
if (item.getStatusCode() == 200 &&
|
||||
item.getResponse() != null &&
|
||||
item.getResponse().getFeatures() != null &&
|
||||
!item.getResponse().getFeatures().isEmpty()) {
|
||||
|
||||
Feature feature = item.getResponse().getFeatures().get(0);
|
||||
results.add(new GeocodingResult(feature));
|
||||
} else {
|
||||
// Add null for failed requests to maintain index alignment
|
||||
results.add(null);
|
||||
logger.debug("Failed to geocode item at index {}: status code {}",
|
||||
results.size() - 1, item.getStatusCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle batches larger than MAX_BATCH_SIZE by splitting into multiple requests
|
||||
*/
|
||||
private BatchGeocodingResult geocodeLargeBatch(List<BulkInstruction<ExcelNode>> addresses) {
|
||||
logger.info("Processing large batch of {} addresses in chunks of {}",
|
||||
addresses.size(), MAX_BATCH_SIZE);
|
||||
|
||||
List<GeocodingResult> allResults = new ArrayList<>();
|
||||
int totalSuccessful = 0;
|
||||
int totalRequests = addresses.size();
|
||||
//
|
||||
// for (int i = 0; i < addresses.size(); i += MAX_BATCH_SIZE) {
|
||||
// int end = Math.min(i + MAX_BATCH_SIZE, addresses.size());
|
||||
// List<String> chunk = addresses.subList(i, end);
|
||||
//
|
||||
// BatchGeocodingResult chunkResult = geocodeBatch(chunk);
|
||||
// allResults.addAll(chunkResult.getResults());
|
||||
// totalSuccessful += chunkResult.getSuccessfulRequests();
|
||||
// }
|
||||
|
||||
return new BatchGeocodingResult(allResults, totalSuccessful, totalRequests);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle detailed batches larger than MAX_BATCH_SIZE
|
||||
*/
|
||||
private BatchGeocodingResult geocodeLargeBatchDetailed(List<BatchItem> batchItems) {
|
||||
logger.info("Processing large detailed batch of {} items in chunks of {}",
|
||||
batchItems.size(), MAX_BATCH_SIZE);
|
||||
|
||||
List<GeocodingResult> allResults = new ArrayList<>();
|
||||
int totalSuccessful = 0;
|
||||
int totalRequests = batchItems.size();
|
||||
|
||||
for (int i = 0; i < batchItems.size(); i += MAX_BATCH_SIZE) {
|
||||
int end = Math.min(i + MAX_BATCH_SIZE, batchItems.size());
|
||||
List<BatchItem> chunk = batchItems.subList(i, end);
|
||||
|
||||
BatchGeocodingResult chunkResult = geocodeBatchDetailed(chunk);
|
||||
allResults.addAll(chunkResult.getResults());
|
||||
totalSuccessful += chunkResult.getSuccessfulRequests();
|
||||
}
|
||||
|
||||
return new BatchGeocodingResult(allResults, totalSuccessful, totalRequests);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package de.avatic.lcc.service;
|
||||
package de.avatic.lcc.service.api;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
109
src/main/java/de/avatic/lcc/service/api/DistanceApiService.java
Normal file
109
src/main/java/de/avatic/lcc/service/api/DistanceApiService.java
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
package de.avatic.lcc.service.api;
|
||||
|
||||
import de.avatic.lcc.model.azuremaps.RouteDirectionsResponse;
|
||||
import de.avatic.lcc.model.nodes.Distance;
|
||||
import de.avatic.lcc.model.nodes.DistanceMatrixState;
|
||||
import de.avatic.lcc.model.nodes.Node;
|
||||
import de.avatic.lcc.repositories.DistanceMatrixRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
public class DistanceApiService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DistanceApiService.class);
|
||||
private static final String AZURE_MAPS_ROUTE_API = "https://atlas.microsoft.com/route/directions/json";
|
||||
|
||||
private final DistanceMatrixRepository distanceMatrixRepository;
|
||||
private final RestTemplate restTemplate;
|
||||
|
||||
@Value("${azure.maps.subscription.key}")
|
||||
private String subscriptionKey;
|
||||
|
||||
public DistanceApiService(DistanceMatrixRepository distanceMatrixRepository,
|
||||
RestTemplate restTemplate) {
|
||||
this.distanceMatrixRepository = distanceMatrixRepository;
|
||||
this.restTemplate = restTemplate;
|
||||
}
|
||||
|
||||
public Optional<Distance> getDistance(Node from, Node to) {
|
||||
if (from == null || to == null) {
|
||||
logger.warn("Source or destination node is null");
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
if (from.getGeoLat() == null || from.getGeoLng() == null ||
|
||||
to.getGeoLat() == null || to.getGeoLng() == null) {
|
||||
logger.warn("Missing geo coordinates for nodes: from={}, to={}", from.getId(), to.getId());
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
// Check if distance exists in database and is valid
|
||||
Optional<Distance> cachedDistance = distanceMatrixRepository.getDistance(from, to);
|
||||
|
||||
if (cachedDistance.isPresent()) {
|
||||
logger.debug("Found cached distance from node {} to node {}", from.getId(), to.getId());
|
||||
return cachedDistance;
|
||||
}
|
||||
|
||||
// Distance not found or stale, fetch from Azure Maps
|
||||
logger.info("Fetching distance from Azure Maps for nodes {} to {}", from.getId(), to.getId());
|
||||
Optional<Distance> fetchedDistance = fetchDistanceFromAzureMaps(from, to);
|
||||
|
||||
if (fetchedDistance.isPresent()) {
|
||||
// Store in database
|
||||
distanceMatrixRepository.saveDistance(fetchedDistance.get());
|
||||
return fetchedDistance;
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private Optional<Distance> fetchDistanceFromAzureMaps(Node from, Node to) {
|
||||
try {
|
||||
String url = UriComponentsBuilder.fromHttpUrl(AZURE_MAPS_ROUTE_API)
|
||||
.queryParam("api-version", "1.0")
|
||||
.queryParam("subscription-key", subscriptionKey)
|
||||
.queryParam("query", String.format("%s,%s:%s,%s",
|
||||
from.getGeoLat(), from.getGeoLng(),
|
||||
to.getGeoLat(), to.getGeoLng()))
|
||||
.toUriString();
|
||||
|
||||
RouteDirectionsResponse response = restTemplate.getForObject(url, RouteDirectionsResponse.class);
|
||||
|
||||
if (response != null && response.getRoutes() != null && !response.getRoutes().isEmpty()) {
|
||||
Integer distanceInMeters = response.getRoutes().get(0).getSummary().getLengthInMeters();
|
||||
|
||||
Distance distance = new Distance();
|
||||
distance.setFromNodeId(from.getId());
|
||||
distance.setToNodeId(to.getId());
|
||||
distance.setFromGeoLat(from.getGeoLat());
|
||||
distance.setFromGeoLng(from.getGeoLng());
|
||||
distance.setToGeoLat(to.getGeoLat());
|
||||
distance.setToGeoLng(to.getGeoLng());
|
||||
distance.setDistance(BigDecimal.valueOf(distanceInMeters));
|
||||
distance.setState(DistanceMatrixState.VALID);
|
||||
distance.setUpdatedAt(LocalDateTime.now());
|
||||
|
||||
logger.info("Successfully fetched distance: {} meters", distanceInMeters);
|
||||
return Optional.of(distance);
|
||||
} else {
|
||||
logger.warn("No routes found in Azure Maps response");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error fetching distance from Azure Maps", e);
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
68
src/main/java/de/avatic/lcc/service/api/GeoApiService.java
Normal file
68
src/main/java/de/avatic/lcc/service/api/GeoApiService.java
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
package de.avatic.lcc.service.api;
|
||||
|
||||
import de.avatic.lcc.model.azuremaps.GeocodingResponse;
|
||||
import de.avatic.lcc.model.azuremaps.GeocodingResult;
|
||||
import de.avatic.lcc.model.azuremaps.Feature;
|
||||
import de.avatic.lcc.model.nodes.Location;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
@Service
|
||||
public class GeoApiService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(GeoApiService.class);
|
||||
private static final String AZURE_MAPS_GEOCODING_URL = "https://atlas.microsoft.com/geocode";
|
||||
|
||||
private final RestTemplate restTemplate;
|
||||
private final String subscriptionKey;
|
||||
|
||||
public GeoApiService(RestTemplate restTemplate,
|
||||
@Value("${azure.maps.subscription.key}") String subscriptionKey) {
|
||||
this.restTemplate = restTemplate;
|
||||
this.subscriptionKey = subscriptionKey;
|
||||
}
|
||||
|
||||
public GeocodingResult geocode(String address) {
|
||||
if (address == null || address.trim().isEmpty()) {
|
||||
logger.warn("Address is null or empty");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
URI uri = UriComponentsBuilder.fromUriString(AZURE_MAPS_GEOCODING_URL)
|
||||
.queryParam("api-version", "2023-06-01")
|
||||
.queryParam("query", address)
|
||||
.queryParam("subscription-key", subscriptionKey)
|
||||
.build()
|
||||
.toUri();
|
||||
|
||||
logger.info("Calling Azure Maps API for address: {}", address);
|
||||
|
||||
GeocodingResponse response = restTemplate.getForObject(uri, GeocodingResponse.class);
|
||||
|
||||
if (response != null && response.getFeatures() != null && !response.getFeatures().isEmpty()) {
|
||||
Feature feature = response.getFeatures().getFirst();
|
||||
logger.debug("Successfully geocoded address: {}", address);
|
||||
return new GeocodingResult(feature);
|
||||
}
|
||||
|
||||
logger.warn("No results found for address: {}", address);
|
||||
return null;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to geocode address: {}", address, e);
|
||||
throw new RuntimeException("Geocoding failed for: " + address, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Location locate(String address) {
|
||||
GeocodingResult result = geocode(address);
|
||||
return result != null ? result.getLocation() : null;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package de.avatic.lcc.service.bulk;
|
|||
import de.avatic.lcc.model.bulk.BulkFileTypes;
|
||||
import de.avatic.lcc.model.bulk.BulkOperation;
|
||||
import de.avatic.lcc.repositories.NodeRepository;
|
||||
import de.avatic.lcc.service.api.BatchGeoApiService;
|
||||
import de.avatic.lcc.service.bulk.bulkImport.*;
|
||||
import de.avatic.lcc.service.excelMapper.*;
|
||||
import de.avatic.lcc.service.transformer.generic.NodeTransformer;
|
||||
|
|
@ -32,8 +33,9 @@ public class BulkImportService {
|
|||
private final MaterialBulkImportService materialBulkImportService;
|
||||
private final MatrixRateImportService matrixRateImportService;
|
||||
private final ContainerRateImportService containerRateImportService;
|
||||
private final BatchGeoApiService batchGeoApiService;
|
||||
|
||||
public BulkImportService(MatrixRateExcelMapper matrixRateExcelMapper, ContainerRateExcelMapper containerRateExcelMapper, MaterialExcelMapper materialExcelMapper, PackagingExcelMapper packagingExcelMapper, NodeExcelMapper nodeExcelMapper, NodeRepository nodeRepository, NodeTransformer nodeTransformer, NodeBulkImportService nodeBulkImportService, PackagingBulkImportService packagingBulkImportService, MaterialBulkImportService materialBulkImportService, MatrixRateImportService matrixRateImportService, ContainerRateImportService containerRateImportService) {
|
||||
public BulkImportService(MatrixRateExcelMapper matrixRateExcelMapper, ContainerRateExcelMapper containerRateExcelMapper, MaterialExcelMapper materialExcelMapper, PackagingExcelMapper packagingExcelMapper, NodeExcelMapper nodeExcelMapper, NodeRepository nodeRepository, NodeTransformer nodeTransformer, NodeBulkImportService nodeBulkImportService, PackagingBulkImportService packagingBulkImportService, MaterialBulkImportService materialBulkImportService, MatrixRateImportService matrixRateImportService, ContainerRateImportService containerRateImportService, BatchGeoApiService batchGeoApiService) {
|
||||
this.matrixRateExcelMapper = matrixRateExcelMapper;
|
||||
this.containerRateExcelMapper = containerRateExcelMapper;
|
||||
this.materialExcelMapper = materialExcelMapper;
|
||||
|
|
@ -46,6 +48,7 @@ public class BulkImportService {
|
|||
this.materialBulkImportService = materialBulkImportService;
|
||||
this.matrixRateImportService = matrixRateImportService;
|
||||
this.containerRateImportService = containerRateImportService;
|
||||
this.batchGeoApiService = batchGeoApiService;
|
||||
}
|
||||
|
||||
public void processOperation(BulkOperation op) throws IOException {
|
||||
|
|
@ -88,6 +91,7 @@ public class BulkImportService {
|
|||
break;
|
||||
case NODE:
|
||||
var nodeInstructions = nodeExcelMapper.extractSheet(sheet);
|
||||
// batchGeoApiService.geocodeBatch(nodeInstructions);
|
||||
nodeInstructions.forEach(nodeBulkImportService::processNodeInstructions);
|
||||
break;
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -154,9 +154,9 @@ public class ConstraintGenerator {
|
|||
return result.toString();
|
||||
}
|
||||
|
||||
public void validateDecimalConstraint(Row row, int columnIdx, double min, double max) {
|
||||
public void validateDecimalConstraint(Row row, int columnIdx, double min, double max, boolean allowEmpty) {
|
||||
|
||||
checkEmptyCell(row, columnIdx);
|
||||
checkEmptyCell(row, columnIdx, allowEmpty);
|
||||
|
||||
CellType cellType = row.getCell(columnIdx).getCellType();
|
||||
|
||||
|
|
@ -173,6 +173,10 @@ public class ConstraintGenerator {
|
|||
|
||||
}
|
||||
|
||||
public void validateDecimalConstraint(Row row, int columnIdx, double min, double max) {
|
||||
validateDecimalConstraint(row, columnIdx, min, max, false);
|
||||
}
|
||||
|
||||
public void validateBooleanConstraint(Row row, int columnIdx) {
|
||||
|
||||
checkEmptyCell(row, columnIdx);
|
||||
|
|
@ -234,9 +238,9 @@ public class ConstraintGenerator {
|
|||
|
||||
}
|
||||
|
||||
public void validateNumericCell(Row row, int columnIdx) {
|
||||
public void validateNumericCell(Row row, int columnIdx, boolean allowEmpty) {
|
||||
|
||||
checkEmptyCell(row, columnIdx);
|
||||
checkEmptyCell(row, columnIdx, allowEmpty);
|
||||
|
||||
CellType cellType = row.getCell(columnIdx).getCellType();
|
||||
|
||||
|
|
@ -250,6 +254,10 @@ public class ConstraintGenerator {
|
|||
|
||||
}
|
||||
|
||||
public void validateNumericCell(Row row, int columnIdx) {
|
||||
validateNumericCell(row, columnIdx, false);
|
||||
}
|
||||
|
||||
public void validateIntegerCell(Row row, int columnIdx) {
|
||||
|
||||
checkEmptyCell(row, columnIdx);
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import de.avatic.lcc.repositories.packaging.PackagingPropertiesRepository;
|
|||
import de.avatic.lcc.repositories.packaging.PackagingRepository;
|
||||
import de.avatic.lcc.repositories.premise.PremiseRepository;
|
||||
import de.avatic.lcc.repositories.users.UserNodeRepository;
|
||||
import de.avatic.lcc.service.CustomApiService;
|
||||
import de.avatic.lcc.service.api.CustomApiService;
|
||||
import de.avatic.lcc.service.access.PremisesService;
|
||||
import de.avatic.lcc.service.users.AuthorizationService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import de.avatic.lcc.repositories.packaging.PackagingPropertiesRepository;
|
|||
import de.avatic.lcc.repositories.packaging.PackagingRepository;
|
||||
import de.avatic.lcc.repositories.premise.*;
|
||||
import de.avatic.lcc.repositories.users.UserNodeRepository;
|
||||
import de.avatic.lcc.service.CustomApiService;
|
||||
import de.avatic.lcc.service.api.CustomApiService;
|
||||
import de.avatic.lcc.service.access.DestinationService;
|
||||
import de.avatic.lcc.service.access.PremisesService;
|
||||
import de.avatic.lcc.service.users.AuthorizationService;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import de.avatic.lcc.repositories.packaging.PackagingPropertiesRepository;
|
|||
import de.avatic.lcc.repositories.packaging.PackagingRepository;
|
||||
import de.avatic.lcc.repositories.premise.PremiseRepository;
|
||||
import de.avatic.lcc.repositories.users.UserNodeRepository;
|
||||
import de.avatic.lcc.service.CustomApiService;
|
||||
import de.avatic.lcc.service.api.CustomApiService;
|
||||
import de.avatic.lcc.service.access.DestinationService;
|
||||
import de.avatic.lcc.service.transformer.generic.DimensionTransformer;
|
||||
import de.avatic.lcc.service.transformer.premise.PremiseTransformer;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import de.avatic.lcc.model.properties.SystemPropertyMappingId;
|
|||
import de.avatic.lcc.repositories.country.CountryPropertyRepository;
|
||||
import de.avatic.lcc.repositories.premise.RouteNodeRepository;
|
||||
import de.avatic.lcc.repositories.properties.PropertyRepository;
|
||||
import de.avatic.lcc.service.CustomApiService;
|
||||
import de.avatic.lcc.service.api.CustomApiService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
|
|
|||
|
|
@ -144,8 +144,10 @@ public class NodeExcelMapper {
|
|||
entity.setName(row.getCell(NodeHeader.NAME.ordinal()).getStringCellValue());
|
||||
entity.setAddress(row.getCell(NodeHeader.ADDRESS.ordinal()).getStringCellValue());
|
||||
entity.setCountryId(IsoCode.valueOf(row.getCell(NodeHeader.COUNTRY.ordinal()).getStringCellValue()));
|
||||
entity.setGeoLat(BigDecimal.valueOf(row.getCell(NodeHeader.GEO_LATITUDE.ordinal()).getNumericCellValue()));
|
||||
entity.setGeoLng(BigDecimal.valueOf(row.getCell(NodeHeader.GEO_LONGITUDE.ordinal()).getNumericCellValue()));
|
||||
|
||||
entity.setGeoLat(mapGeoCoordinate(CellUtil.getCell(row, NodeHeader.GEO_LATITUDE.ordinal())));
|
||||
entity.setGeoLng(mapGeoCoordinate(CellUtil.getCell(row, NodeHeader.GEO_LONGITUDE.ordinal())));
|
||||
|
||||
entity.setSource(Boolean.valueOf(row.getCell(NodeHeader.IS_SOURCE.ordinal()).getStringCellValue()));
|
||||
entity.setIntermediate(Boolean.valueOf(row.getCell(NodeHeader.IS_INTERMEDIATE.ordinal()).getStringCellValue()));
|
||||
entity.setDestination(Boolean.valueOf(row.getCell(NodeHeader.IS_DESTINATION.ordinal()).getStringCellValue()));
|
||||
|
|
@ -165,8 +167,9 @@ public class NodeExcelMapper {
|
|||
constraintGenerator.validateStringCell(row, NodeHeader.NAME.ordinal());
|
||||
constraintGenerator.validateStringCell(row, NodeHeader.ADDRESS.ordinal());
|
||||
|
||||
constraintGenerator.validateDecimalConstraint(row, NodeHeader.GEO_LATITUDE.ordinal(), -90.0, 90.0);
|
||||
constraintGenerator.validateDecimalConstraint(row, NodeHeader.GEO_LONGITUDE.ordinal(), -180.0, 180.0);
|
||||
constraintGenerator.validateDecimalConstraint(row, NodeHeader.GEO_LATITUDE.ordinal(), -90.0, 90.0, true);
|
||||
constraintGenerator.validateDecimalConstraint(row, NodeHeader.GEO_LONGITUDE.ordinal(), -180.0, 180.0, true);
|
||||
|
||||
constraintGenerator.validateBooleanConstraint(row, NodeHeader.IS_SOURCE.ordinal());
|
||||
constraintGenerator.validateBooleanConstraint(row, NodeHeader.IS_INTERMEDIATE.ordinal());
|
||||
constraintGenerator.validateBooleanConstraint(row, NodeHeader.IS_DESTINATION.ordinal());
|
||||
|
|
@ -177,6 +180,12 @@ public class NodeExcelMapper {
|
|||
|
||||
}
|
||||
|
||||
private BigDecimal mapGeoCoordinate(Cell geoCoordinate) {
|
||||
if (geoCoordinate == null) return null;
|
||||
if(geoCoordinate.getCellType() == CellType.BLANK) return null;
|
||||
return BigDecimal.valueOf(geoCoordinate.getNumericCellValue());
|
||||
}
|
||||
|
||||
private List<String> mapOutboundCountriesFromCell(String outboundCountryIds) {
|
||||
if (outboundCountryIds == null || outboundCountryIds.isBlank()) return Collections.emptyList();
|
||||
return Arrays.stream(outboundCountryIds.split(",")).map(String::trim).toList();
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import de.avatic.lcc.repositories.premise.DestinationRepository;
|
|||
import de.avatic.lcc.repositories.premise.PremiseRepository;
|
||||
import de.avatic.lcc.repositories.premise.RouteRepository;
|
||||
import de.avatic.lcc.repositories.properties.PropertyRepository;
|
||||
import de.avatic.lcc.service.CustomApiService;
|
||||
import de.avatic.lcc.service.api.CustomApiService;
|
||||
import de.avatic.lcc.service.access.PropertyService;
|
||||
import de.avatic.lcc.service.transformer.generic.DimensionTransformer;
|
||||
import de.avatic.lcc.util.exception.internalerror.PremiseValidationError;
|
||||
|
|
|
|||
|
|
@ -7,9 +7,8 @@ spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
|
|||
spring.sql.init.mode=never
|
||||
lcc.bulk.sheet_password=secretSheet?!
|
||||
lcc.allowed_cors=${ALLOWED_CORS_DOMAIN}
|
||||
azure.maps.client.id=${AZURE_MAPS_CLIENT_ID}
|
||||
azure.maps.subscription.key=${AZURE_MAPS_SUBSCRIPTION_KEY}
|
||||
azure.maps.client.id=your-app-registration-client-id
|
||||
azure.maps.resource.id=/subscriptions/sub-id/resourceGroups/rg-name/providers/Microsoft.Maps/accounts/account-name
|
||||
spring.servlet.multipart.max-file-size=30MB
|
||||
spring.servlet.multipart.max-request-size=50MB
|
||||
spring.cloud.azure.active-directory.enabled=true
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ VALUES ('USR001', 'john.doe@company.com', 'John', 'Doe', TRUE),
|
|||
('USR005', 'david.chen@company.com', 'David', 'Chen', TRUE)
|
||||
ON DUPLICATE KEY UPDATE email = VALUES(email);
|
||||
|
||||
INSERT INTO sys_group(group_name, group_description)
|
||||
VALUES ('none', 'no rights');
|
||||
INSERT INTO sys_group(group_name, group_description)
|
||||
VALUES ('basic', 'Login, generate reports');
|
||||
INSERT INTO sys_group(group_name, group_description)
|
||||
|
|
@ -20,23 +22,23 @@ VALUES ('super',
|
|||
'Login, generate reports, do calculations, edit freight rates, edit packaging data');
|
||||
|
||||
INSERT INTO sys_user_group_mapping (user_id, group_id)
|
||||
VALUES ((SELECT id FROM sys_group WHERE group_name = 'LCE'),
|
||||
VALUES ((SELECT id FROM sys_group WHERE group_name = 'super'),
|
||||
(SELECT id FROM sys_user WHERE email = 'john.doe@company.com'));
|
||||
|
||||
INSERT INTO sys_user_group_mapping (user_id, group_id)
|
||||
VALUES ((SELECT id FROM sys_group WHERE group_name = 'LCE'),
|
||||
VALUES ((SELECT id FROM sys_group WHERE group_name = 'basic'),
|
||||
(SELECT id FROM sys_user WHERE email = 'sarah.smith@company.com'));
|
||||
|
||||
INSERT INTO sys_user_group_mapping (user_id, group_id)
|
||||
VALUES ((SELECT id FROM sys_group WHERE group_name = 'LCE'),
|
||||
VALUES ((SELECT id FROM sys_group WHERE group_name = 'calculation'),
|
||||
(SELECT id FROM sys_user WHERE email = 'mike.johnson@company.com'));
|
||||
|
||||
INSERT INTO sys_user_group_mapping (user_id, group_id)
|
||||
VALUES ((SELECT id FROM sys_group WHERE group_name = 'LCE'),
|
||||
VALUES ((SELECT id FROM sys_group WHERE group_name = 'freight'),
|
||||
(SELECT id FROM sys_user WHERE email = 'anna.mueller@company.com'));
|
||||
|
||||
INSERT INTO sys_user_group_mapping (user_id, group_id)
|
||||
VALUES ((SELECT id FROM sys_group WHERE group_name = 'LCE'),
|
||||
VALUES ((SELECT id FROM sys_group WHERE group_name = 'packaging'),
|
||||
(SELECT id FROM sys_user WHERE email = 'david.chen@company.com'));
|
||||
|
||||
INSERT INTO sys_user_node (user_id, country_id, name, address, geo_lat, geo_lng, is_deprecated)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue