feat: add schichtplanung sync
This commit is contained in:
parent
56d31dfc1a
commit
41472ba17b
|
|
@ -41,6 +41,13 @@ services:
|
||||||
networks:
|
networks:
|
||||||
- avic
|
- avic
|
||||||
|
|
||||||
|
avicenna-cache:
|
||||||
|
image: valkey/valkey:8.1.2-alpine
|
||||||
|
ports:
|
||||||
|
- 6379:6379
|
||||||
|
networks:
|
||||||
|
- avic
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
pgdata:
|
pgdata:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
"@faker-js/faker": "^9.6.0",
|
"@faker-js/faker": "^9.6.0",
|
||||||
"@nestjs/apollo": "^13.0.4",
|
"@nestjs/apollo": "^13.0.4",
|
||||||
"@nestjs/axios": "^4.0.0",
|
"@nestjs/axios": "^4.0.0",
|
||||||
|
"@nestjs/bullmq": "^11.0.2",
|
||||||
"@nestjs/common": "^11.0.1",
|
"@nestjs/common": "^11.0.1",
|
||||||
"@nestjs/config": "^4.0.2",
|
"@nestjs/config": "^4.0.2",
|
||||||
"@nestjs/core": "^11.0.15",
|
"@nestjs/core": "^11.0.15",
|
||||||
|
|
@ -25,6 +26,7 @@
|
||||||
"@opentelemetry/exporter-trace-otlp-http": "^0.202.0",
|
"@opentelemetry/exporter-trace-otlp-http": "^0.202.0",
|
||||||
"@opentelemetry/sdk-node": "^0.202.0",
|
"@opentelemetry/sdk-node": "^0.202.0",
|
||||||
"@prisma/client": "^6.6.0",
|
"@prisma/client": "^6.6.0",
|
||||||
|
"bullmq": "^5.56.0",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"date-fns-tz": "^3.2.0",
|
"date-fns-tz": "^3.2.0",
|
||||||
"graphql": "^16.10.0",
|
"graphql": "^16.10.0",
|
||||||
|
|
@ -311,6 +313,25 @@
|
||||||
"@types/serve-static": "*"
|
"@types/serve-static": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@apollo/server/node_modules/node-fetch": {
|
||||||
|
"version": "2.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||||
|
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||||
|
"dependencies": {
|
||||||
|
"whatwg-url": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "4.x || >=6.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"encoding": "^0.1.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"encoding": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@apollo/usage-reporting-protobuf": {
|
"node_modules/@apollo/usage-reporting-protobuf": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@apollo/usage-reporting-protobuf/-/usage-reporting-protobuf-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@apollo/usage-reporting-protobuf/-/usage-reporting-protobuf-4.1.1.tgz",
|
||||||
|
|
@ -2061,6 +2082,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@ioredis/commands": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg=="
|
||||||
|
},
|
||||||
"node_modules/@isaacs/cliui": {
|
"node_modules/@isaacs/cliui": {
|
||||||
"version": "8.0.2",
|
"version": "8.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||||
|
|
@ -2688,6 +2714,78 @@
|
||||||
"node": ">=16"
|
"node": ">=16"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
]
|
||||||
|
},
|
||||||
"node_modules/@nestjs/apollo": {
|
"node_modules/@nestjs/apollo": {
|
||||||
"version": "13.0.4",
|
"version": "13.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/apollo/-/apollo-13.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/apollo/-/apollo-13.0.4.tgz",
|
||||||
|
|
@ -2730,6 +2828,32 @@
|
||||||
"rxjs": "^7.0.0"
|
"rxjs": "^7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@nestjs/bull-shared": {
|
||||||
|
"version": "11.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nestjs/bull-shared/-/bull-shared-11.0.2.tgz",
|
||||||
|
"integrity": "sha512-dFlttJvBqIFD6M8JVFbkrR4Feb39OTAJPJpFVILU50NOJCM4qziRw3dSNG84Q3v+7/M6xUGMFdZRRGvBBKxoSA==",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "2.8.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@nestjs/common": "^10.0.0 || ^11.0.0",
|
||||||
|
"@nestjs/core": "^10.0.0 || ^11.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@nestjs/bullmq": {
|
||||||
|
"version": "11.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nestjs/bullmq/-/bullmq-11.0.2.tgz",
|
||||||
|
"integrity": "sha512-Lq6lGpKkETsm0RDcUktlzsthFoE3A5QTMp2FwPi1eztKqKD6/90KS1TcnC9CJFzjpUaYnQzIMrlNs55e+/wsHA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@nestjs/bull-shared": "^11.0.2",
|
||||||
|
"tslib": "2.8.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@nestjs/common": "^10.0.0 || ^11.0.0",
|
||||||
|
"@nestjs/core": "^10.0.0 || ^11.0.0",
|
||||||
|
"bullmq": "^3.0.0 || ^4.0.0 || ^5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nestjs/cli": {
|
"node_modules/@nestjs/cli": {
|
||||||
"version": "11.0.6",
|
"version": "11.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-11.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-11.0.6.tgz",
|
||||||
|
|
@ -5085,6 +5209,25 @@
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@prisma/internals/node_modules/node-fetch": {
|
||||||
|
"version": "2.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||||
|
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||||
|
"dependencies": {
|
||||||
|
"whatwg-url": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "4.x || >=6.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"encoding": "^0.1.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"encoding": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@prisma/internals/node_modules/resolve": {
|
"node_modules/@prisma/internals/node_modules/resolve": {
|
||||||
"version": "1.22.4",
|
"version": "1.22.4",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz",
|
||||||
|
|
@ -6716,6 +6859,20 @@
|
||||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
|
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/bullmq": {
|
||||||
|
"version": "5.56.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.56.0.tgz",
|
||||||
|
"integrity": "sha512-j5ct2tdc9M8PKcjhJw+euO24BsO1wXBAkNGXYI1R1qvh7FvRldZ5wtLixLWqQ4/crafj0Vrwi+y1kXFXMwBJFA==",
|
||||||
|
"dependencies": {
|
||||||
|
"cron-parser": "^4.9.0",
|
||||||
|
"ioredis": "^5.4.1",
|
||||||
|
"msgpackr": "^1.11.2",
|
||||||
|
"node-abort-controller": "^3.1.1",
|
||||||
|
"semver": "^7.5.4",
|
||||||
|
"tslib": "^2.0.0",
|
||||||
|
"uuid": "^9.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/busboy": {
|
"node_modules/busboy": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
||||||
|
|
@ -7069,6 +7226,14 @@
|
||||||
"node": ">=0.8"
|
"node": ">=0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cluster-key-slot": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/co": {
|
"node_modules/co": {
|
||||||
"version": "4.6.0",
|
"version": "4.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
||||||
|
|
@ -7364,6 +7529,17 @@
|
||||||
"luxon": "~3.5.0"
|
"luxon": "~3.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cron-parser": {
|
||||||
|
"version": "4.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz",
|
||||||
|
"integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"luxon": "^3.2.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cross-inspect": {
|
"node_modules/cross-inspect": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/cross-inspect/-/cross-inspect-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/cross-inspect/-/cross-inspect-1.0.1.tgz",
|
||||||
|
|
@ -7504,6 +7680,14 @@
|
||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/denque": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/depd": {
|
"node_modules/depd": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||||
|
|
@ -7521,6 +7705,15 @@
|
||||||
"npm": "1.2.8000 || >= 1.4.16"
|
"npm": "1.2.8000 || >= 1.4.16"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/detect-libc": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
|
||||||
|
"optional": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/detect-newline": {
|
"node_modules/detect-newline": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
|
||||||
|
|
@ -8722,6 +8915,25 @@
|
||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/gaxios/node_modules/node-fetch": {
|
||||||
|
"version": "2.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||||
|
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||||
|
"dependencies": {
|
||||||
|
"whatwg-url": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "4.x || >=6.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"encoding": "^0.1.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"encoding": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/gcp-metadata": {
|
"node_modules/gcp-metadata": {
|
||||||
"version": "6.1.1",
|
"version": "6.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz",
|
||||||
|
|
@ -9282,6 +9494,29 @@
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ioredis": {
|
||||||
|
"version": "5.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.6.1.tgz",
|
||||||
|
"integrity": "sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@ioredis/commands": "^1.1.1",
|
||||||
|
"cluster-key-slot": "^1.1.0",
|
||||||
|
"debug": "^4.3.4",
|
||||||
|
"denque": "^2.1.0",
|
||||||
|
"lodash.defaults": "^4.2.0",
|
||||||
|
"lodash.isarguments": "^3.1.0",
|
||||||
|
"redis-errors": "^1.2.0",
|
||||||
|
"redis-parser": "^3.0.0",
|
||||||
|
"standard-as-callback": "^2.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.22.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/ioredis"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ipaddr.js": {
|
"node_modules/ipaddr.js": {
|
||||||
"version": "1.9.1",
|
"version": "1.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||||
|
|
@ -10472,6 +10707,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
|
||||||
"integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g=="
|
"integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g=="
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.isarguments": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg=="
|
||||||
|
},
|
||||||
"node_modules/lodash.isplainobject": {
|
"node_modules/lodash.isplainobject": {
|
||||||
"version": "4.0.6",
|
"version": "4.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||||
|
|
@ -10771,6 +11011,35 @@
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/msgpackr": {
|
||||||
|
"version": "1.11.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.4.tgz",
|
||||||
|
"integrity": "sha512-uaff7RG9VIC4jacFW9xzL3jc0iM32DNHe4jYVycBcjUePT/Klnfj7pqtWJt9khvDFizmjN2TlYniYmSS2LIaZg==",
|
||||||
|
"optionalDependencies": {
|
||||||
|
"msgpackr-extract": "^3.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/msgpackr-extract": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"node-gyp-build-optional-packages": "5.2.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"download-msgpackr-prebuilds": "bin/download-prebuilds.js"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3",
|
||||||
|
"@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3",
|
||||||
|
"@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3",
|
||||||
|
"@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3",
|
||||||
|
"@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3",
|
||||||
|
"@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/multer": {
|
"node_modules/multer": {
|
||||||
"version": "1.4.5-lts.2",
|
"version": "1.4.5-lts.2",
|
||||||
"resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz",
|
"resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz",
|
||||||
|
|
@ -10839,23 +11108,18 @@
|
||||||
"lodash": "^4.17.21"
|
"lodash": "^4.17.21"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/node-fetch": {
|
"node_modules/node-gyp-build-optional-packages": {
|
||||||
"version": "2.7.0",
|
"version": "5.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz",
|
||||||
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
"integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==",
|
||||||
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"whatwg-url": "^5.0.0"
|
"detect-libc": "^2.0.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"bin": {
|
||||||
"node": "4.x || >=6.0.0"
|
"node-gyp-build-optional-packages": "bin.js",
|
||||||
},
|
"node-gyp-build-optional-packages-optional": "optional.js",
|
||||||
"peerDependencies": {
|
"node-gyp-build-optional-packages-test": "build-test.js"
|
||||||
"encoding": "^0.1.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"encoding": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/node-int64": {
|
"node_modules/node-int64": {
|
||||||
|
|
@ -11956,6 +12220,25 @@
|
||||||
"url": "https://paulmillr.com/funding/"
|
"url": "https://paulmillr.com/funding/"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/redis-errors": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/redis-parser": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==",
|
||||||
|
"dependencies": {
|
||||||
|
"redis-errors": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/reflect-metadata": {
|
"node_modules/reflect-metadata": {
|
||||||
"version": "0.2.2",
|
"version": "0.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
|
||||||
|
|
@ -12594,6 +12877,11 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/standard-as-callback": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="
|
||||||
|
},
|
||||||
"node_modules/statuses": {
|
"node_modules/statuses": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@
|
||||||
"@faker-js/faker": "^9.6.0",
|
"@faker-js/faker": "^9.6.0",
|
||||||
"@nestjs/apollo": "^13.0.4",
|
"@nestjs/apollo": "^13.0.4",
|
||||||
"@nestjs/axios": "^4.0.0",
|
"@nestjs/axios": "^4.0.0",
|
||||||
|
"@nestjs/bullmq": "^11.0.2",
|
||||||
"@nestjs/common": "^11.0.1",
|
"@nestjs/common": "^11.0.1",
|
||||||
"@nestjs/config": "^4.0.2",
|
"@nestjs/config": "^4.0.2",
|
||||||
"@nestjs/core": "^11.0.15",
|
"@nestjs/core": "^11.0.15",
|
||||||
|
|
@ -39,6 +40,7 @@
|
||||||
"@opentelemetry/exporter-trace-otlp-http": "^0.202.0",
|
"@opentelemetry/exporter-trace-otlp-http": "^0.202.0",
|
||||||
"@opentelemetry/sdk-node": "^0.202.0",
|
"@opentelemetry/sdk-node": "^0.202.0",
|
||||||
"@prisma/client": "^6.6.0",
|
"@prisma/client": "^6.6.0",
|
||||||
|
"bullmq": "^5.56.0",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"date-fns-tz": "^3.2.0",
|
"date-fns-tz": "^3.2.0",
|
||||||
"graphql": "^16.10.0",
|
"graphql": "^16.10.0",
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ export class AppController {
|
||||||
start: new Date(from),
|
start: new Date(from),
|
||||||
end: new Date(to),
|
end: new Date(to),
|
||||||
};
|
};
|
||||||
return this.importer.runImport(interval);
|
return this.importer.runImportForRange(interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('tickets')
|
@Get('tickets')
|
||||||
|
|
@ -63,6 +63,11 @@ export class AppController {
|
||||||
.then(() => this.anomaliesService.validateAnomalies());
|
.then(() => this.anomaliesService.validateAnomalies());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get('schichtplanung')
|
||||||
|
async syncSchichtplanung(@Query('day') day: string) {
|
||||||
|
return await this.importer.runSchichtplanungSync(new Date(day));
|
||||||
|
}
|
||||||
|
|
||||||
//! Used to create check 2 report
|
//! Used to create check 2 report
|
||||||
@Get('tour-calculation')
|
@Get('tour-calculation')
|
||||||
async tourCalculation(@Query('from') from: string, @Query('to') to: string) {
|
async tourCalculation(@Query('from') from: string, @Query('to') to: string) {
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,12 @@ import { AppHomeModule } from './modules/app-home/app-home.module';
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
BullModule.forRoot({
|
||||||
|
connection: {
|
||||||
|
host: 'localhost',
|
||||||
|
port: 6379,
|
||||||
|
},
|
||||||
|
}),
|
||||||
// DatabaseModule.forRoot({
|
// DatabaseModule.forRoot({
|
||||||
// dialect: new PostgresDialect({
|
// dialect: new PostgresDialect({
|
||||||
// pool: new Pool({
|
// pool: new Pool({
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ export class AppService {
|
||||||
end: endOfDay(addDays(now, 7)),
|
end: endOfDay(addDays(now, 7)),
|
||||||
};
|
};
|
||||||
this.importer
|
this.importer
|
||||||
.runImport(fetchInterval)
|
.runImportForRange(fetchInterval)
|
||||||
.pipe(
|
.pipe(
|
||||||
take(1),
|
take(1),
|
||||||
tap(() =>
|
tap(() =>
|
||||||
|
|
@ -84,7 +84,7 @@ export class AppService {
|
||||||
this.logger.log(`Timeout "${timeoutName}"`);
|
this.logger.log(`Timeout "${timeoutName}"`);
|
||||||
from([interval])
|
from([interval])
|
||||||
.pipe(
|
.pipe(
|
||||||
concatMap((interval) => this.importer.runImport(interval)),
|
concatMap((interval) => this.importer.runImportForRange(interval)),
|
||||||
switchMap(() => from(this.ticketService.createTickets(interval))),
|
switchMap(() => from(this.ticketService.createTickets(interval))),
|
||||||
)
|
)
|
||||||
.subscribe();
|
.subscribe();
|
||||||
|
|
@ -107,7 +107,7 @@ export class AppService {
|
||||||
from(intervals).pipe(
|
from(intervals).pipe(
|
||||||
concatMap((interval, index) =>
|
concatMap((interval, index) =>
|
||||||
this.importer
|
this.importer
|
||||||
.runImport(interval)
|
.runImportForRange(interval)
|
||||||
.pipe(
|
.pipe(
|
||||||
tap(() =>
|
tap(() =>
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
|
|
@ -141,7 +141,7 @@ export class AppService {
|
||||||
.pipe(
|
.pipe(
|
||||||
concatMap((interval, index) =>
|
concatMap((interval, index) =>
|
||||||
this.importer
|
this.importer
|
||||||
.runImport(interval)
|
.runImportForRange(interval)
|
||||||
.pipe(
|
.pipe(
|
||||||
tap(() =>
|
tap(() =>
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,12 @@ export class ConfigService extends NestConfigService {
|
||||||
dispoliveBaseUrl(): string {
|
dispoliveBaseUrl(): string {
|
||||||
return this.get('DISPO_LIVE_BASE_URL');
|
return this.get('DISPO_LIVE_BASE_URL');
|
||||||
}
|
}
|
||||||
|
dispoliveApiKey(): string {
|
||||||
|
return this.get('DISPO_LIVE_API_KEY');
|
||||||
|
}
|
||||||
|
dispoliveApiVersion(): string {
|
||||||
|
return this.get('DISPO_LIVE_API_VERSION') ?? 'v1.0.1';
|
||||||
|
}
|
||||||
dispoliveUsername(): string {
|
dispoliveUsername(): string {
|
||||||
return this.get('DISPO_LIVE_USERNAME');
|
return this.get('DISPO_LIVE_USERNAME');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,22 @@ import { DispoLiveExtractorsService } from './dispo-live-extractors.service';
|
||||||
import { DyflexisExtractorsService } from './dyflexis-extractors.service';
|
import { DyflexisExtractorsService } from './dyflexis-extractors.service';
|
||||||
import { ExtractorsService } from './extractors.service';
|
import { ExtractorsService } from './extractors.service';
|
||||||
import { SourceRepository } from './source.repository';
|
import { SourceRepository } from './source.repository';
|
||||||
|
import { DispoLiveApiExtractorsService } from './dispo-live-api-extractors.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [PrismaModule, HttpModule],
|
imports: [PrismaModule, HttpModule],
|
||||||
providers: [
|
providers: [
|
||||||
ExtractorsService,
|
ExtractorsService,
|
||||||
DispoLiveExtractorsService,
|
DispoLiveExtractorsService,
|
||||||
|
DispoLiveApiExtractorsService,
|
||||||
DyflexisExtractorsService,
|
DyflexisExtractorsService,
|
||||||
SourceRepository,
|
SourceRepository,
|
||||||
],
|
],
|
||||||
exports: [ExtractorsService],
|
exports: [
|
||||||
|
ExtractorsService,
|
||||||
|
DispoLiveApiExtractorsService,
|
||||||
|
DispoLiveExtractorsService,
|
||||||
|
DyflexisExtractorsService
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class DataAccessExtractorsModule {}
|
export class DataAccessExtractorsModule {}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
import { HttpService } from '@nestjs/axios';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { format, Interval } from 'date-fns';
|
||||||
|
import { ConfigService } from 'src/core/config/config.service';
|
||||||
|
import {
|
||||||
|
FahrzeugItemDto,
|
||||||
|
FahrzeugplanungItemDto,
|
||||||
|
TourSummaryDto,
|
||||||
|
} from './source.interface';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class DispoLiveApiExtractorsService {
|
||||||
|
constructor(
|
||||||
|
private readonly http: HttpService,
|
||||||
|
private readonly config: ConfigService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
toursFindByRange(interval: Interval) {
|
||||||
|
const endpoint = 'custom/open-api/fahrberichte/findByRange';
|
||||||
|
const url = `${this.config.dispoliveBaseUrl()}/${endpoint}/${format(new Date(interval.start), 'yyyy-MM-dd')}_${format(new Date(interval.end), 'yyyy-MM-dd')}`;
|
||||||
|
|
||||||
|
return this.http.get<TourSummaryDto[]>(url, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${this.config.dispoliveApiKey()}`,
|
||||||
|
'dispolive-api-version': this.config.dispoliveApiVersion(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fahrzeuge() {
|
||||||
|
const endpoint = 'custom/open-api/fahrzeuge/all';
|
||||||
|
const url = `${this.config.dispoliveBaseUrl()}/${endpoint}`;
|
||||||
|
|
||||||
|
return this.http.get<FahrzeugItemDto[]>(url, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${this.config.dispoliveApiKey()}`,
|
||||||
|
'dispolive-api-version': this.config.dispoliveApiVersion(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fahrzeugplanungFindByRange(interval: Interval) {
|
||||||
|
const endpoint = 'custom/open-api/fahrzeugplanung/findByRange';
|
||||||
|
const url = `${this.config.dispoliveBaseUrl()}/${endpoint}/${format(new Date(interval.start), 'yyyy-MM-dd')}_${format(new Date(interval.end), 'yyyy-MM-dd')}`;
|
||||||
|
|
||||||
|
return this.http.get<FahrzeugplanungItemDto[]>(url, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${this.config.dispoliveApiKey()}`,
|
||||||
|
'dispolive-api-version': this.config.dispoliveApiVersion(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fahrzeugplanungAdd(item: FahrzeugplanungItemDto) {
|
||||||
|
const endpoint = 'custom/open-api/fahrzeugplanung/add';
|
||||||
|
const url = `${this.config.dispoliveBaseUrl()}/${endpoint}`;
|
||||||
|
const authToken = `Bearer ${this.config.dispoliveApiKey()}`;
|
||||||
|
|
||||||
|
return this.http.post<FahrzeugplanungItemDto[]>(url, item, {
|
||||||
|
headers: {
|
||||||
|
Authorization: authToken,
|
||||||
|
'dispolive-api-version': this.config.dispoliveApiVersion(),
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fahrzeugplanungUpdate(item: FahrzeugplanungItemDto) {
|
||||||
|
const endpoint = 'custom/open-api/fahrzeugplanung/update';
|
||||||
|
const url = `${this.config.dispoliveBaseUrl()}/${endpoint}`;
|
||||||
|
|
||||||
|
return this.http.post<FahrzeugplanungItemDto[]>(url, item, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${this.config.dispoliveApiKey()}`,
|
||||||
|
'dispolive-api-version': this.config.dispoliveApiVersion(),
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fahrzeugplanungDelete(id: string) {
|
||||||
|
const endpoint = 'custom/open-api/fahrzeugplanung/delete';
|
||||||
|
const url = `${this.config.dispoliveBaseUrl()}/${endpoint}/${id}`;
|
||||||
|
|
||||||
|
return this.http.get<FahrzeugplanungItemDto[]>(url, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${this.config.dispoliveApiKey()}`,
|
||||||
|
'dispolive-api-version': this.config.dispoliveApiVersion(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -271,7 +271,7 @@ export class DyflexisExtractorsService {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private fetchPlannedTimeForMonth(
|
fetchPlannedTimeForMonth(
|
||||||
month: number,
|
month: number,
|
||||||
year: number,
|
year: number,
|
||||||
): Observable<PlannedTimeRaw[]> {
|
): Observable<PlannedTimeRaw[]> {
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,27 @@
|
||||||
|
import { BullModule } from '@nestjs/bullmq';
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
import { FeatBusinessObjectsModule } from 'src/modules/feat-business-objects/feat-business-objects.module';
|
||||||
import { DataAccessExtractorsModule } from '../feat-extractors/data-access-extractors.module';
|
import { DataAccessExtractorsModule } from '../feat-extractors/data-access-extractors.module';
|
||||||
import { DataAccessLoadersModule } from '../feat-loaders/data-access-loaders.module';
|
import { DataAccessLoadersModule } from '../feat-loaders/data-access-loaders.module';
|
||||||
import { DataAccessTransformersModule } from '../feat-transformers/data-access-transformers.module';
|
import { DataAccessTransformersModule } from '../feat-transformers/data-access-transformers.module';
|
||||||
import { ImporterService } from './importer.service';
|
import { ImporterService } from './importer.service';
|
||||||
|
import { SchichtplanungConsumer } from './processors/schichtplanung.consumer';
|
||||||
|
import { SchichtplanungSynchronizer } from './producer/schichtplanung';
|
||||||
|
import { SCHICHTPLANUNG_QUEUE } from './tokens';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
FeatBusinessObjectsModule,
|
||||||
DataAccessExtractorsModule,
|
DataAccessExtractorsModule,
|
||||||
DataAccessTransformersModule,
|
DataAccessTransformersModule,
|
||||||
DataAccessLoadersModule,
|
DataAccessLoadersModule,
|
||||||
|
BullModule.registerQueue({ name: SCHICHTPLANUNG_QUEUE }),
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
ImporterService,
|
||||||
|
SchichtplanungSynchronizer,
|
||||||
|
SchichtplanungConsumer,
|
||||||
],
|
],
|
||||||
providers: [ImporterService],
|
|
||||||
exports: [ImporterService],
|
exports: [ImporterService],
|
||||||
})
|
})
|
||||||
export class FeatureImporterModule {}
|
export class FeatureImporterModule {}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import { ETLEventTypes } from './events/event-types.enum';
|
||||||
import { ExtractionDoneEvent } from './events/extraction-done.event';
|
import { ExtractionDoneEvent } from './events/extraction-done.event';
|
||||||
import { LoadingDoneEvent } from './events/loading-done.event';
|
import { LoadingDoneEvent } from './events/loading-done.event';
|
||||||
import { TransformationDoneEvent } from './events/transformation-done.event';
|
import { TransformationDoneEvent } from './events/transformation-done.event';
|
||||||
|
import { SchichtplanungSynchronizer } from './producer/schichtplanung';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ImporterService {
|
export class ImporterService {
|
||||||
|
|
@ -20,13 +21,19 @@ export class ImporterService {
|
||||||
private readonly transformersService: TransformersService,
|
private readonly transformersService: TransformersService,
|
||||||
private readonly loadersService: LoadersService,
|
private readonly loadersService: LoadersService,
|
||||||
private readonly eventEmitter: EventEmitter2,
|
private readonly eventEmitter: EventEmitter2,
|
||||||
|
private readonly schichtplanungSynchronizer: SchichtplanungSynchronizer,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
runImport(interval: Interval) {
|
|
||||||
|
runSchichtplanungSync(day: Date) {
|
||||||
|
return this.schichtplanungSynchronizer.syncSchichtplanung(day);
|
||||||
|
}
|
||||||
|
|
||||||
|
runImportForRange(interval: Interval) {
|
||||||
const startTS = new Date().getTime();
|
const startTS = new Date().getTime();
|
||||||
const jobId = randomUUID();
|
const jobId = randomUUID();
|
||||||
return from(this.extractorsService.clearSource()).pipe(
|
return from(this.extractorsService.clearSource()).pipe(
|
||||||
switchMap(() => this.runExtractors(interval, jobId)),
|
switchMap(() => this.runExtractorsForRange(interval, jobId)),
|
||||||
tap(() =>
|
tap(() =>
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
`Extraction of ${jobId} took ${new Date().getTime() - startTS}ms`,
|
`Extraction of ${jobId} took ${new Date().getTime() - startTS}ms`,
|
||||||
|
|
@ -34,7 +41,7 @@ export class ImporterService {
|
||||||
),
|
),
|
||||||
switchMap(() =>
|
switchMap(() =>
|
||||||
from(this.transformersService.clearStaging()).pipe(
|
from(this.transformersService.clearStaging()).pipe(
|
||||||
switchMap(() => this.runTransformers(jobId)),
|
switchMap(() => this.runTransformersForRange(jobId)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
tap(() =>
|
tap(() =>
|
||||||
|
|
@ -42,7 +49,7 @@ export class ImporterService {
|
||||||
`Transformation of ${jobId} took ${new Date().getTime() - startTS}ms`,
|
`Transformation of ${jobId} took ${new Date().getTime() - startTS}ms`,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
switchMap(() => this.runLoaders(jobId)),
|
switchMap(() => this.runLoadersForRange(jobId)),
|
||||||
tap(() =>
|
tap(() =>
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
`Loading of ${jobId} took ${new Date().getTime() - startTS}ms`,
|
`Loading of ${jobId} took ${new Date().getTime() - startTS}ms`,
|
||||||
|
|
@ -51,7 +58,10 @@ export class ImporterService {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private runExtractors(interval: Interval, jobId: string): Observable<void> {
|
private runExtractorsForRange(
|
||||||
|
interval: Interval,
|
||||||
|
jobId: string,
|
||||||
|
): Observable<void> {
|
||||||
return combineLatest([
|
return combineLatest([
|
||||||
this.extractorsService.extractAttendanceRegistrations(interval, jobId),
|
this.extractorsService.extractAttendanceRegistrations(interval, jobId),
|
||||||
this.extractorsService.extractEmployees(jobId),
|
this.extractorsService.extractEmployees(jobId),
|
||||||
|
|
@ -72,7 +82,7 @@ export class ImporterService {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private runTransformers(jobId: string): Observable<void> {
|
private runTransformersForRange(jobId: string): Observable<void> {
|
||||||
return combineLatest([
|
return combineLatest([
|
||||||
this.transformersService.transformAttendanceRegistrations(jobId),
|
this.transformersService.transformAttendanceRegistrations(jobId),
|
||||||
this.transformersService.transformDyflexisEmployees(jobId),
|
this.transformersService.transformDyflexisEmployees(jobId),
|
||||||
|
|
@ -94,7 +104,7 @@ export class ImporterService {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private runLoaders(jobId: string): Observable<void> {
|
private runLoadersForRange(jobId: string): Observable<void> {
|
||||||
return this.loadersService.loadTours().pipe(
|
return this.loadersService.loadTours().pipe(
|
||||||
switchMap(() => this.loadersService.loadPatients()),
|
switchMap(() => this.loadersService.loadPatients()),
|
||||||
switchMap(() => this.loadersService.loadDyflexisEmployees()),
|
switchMap(() => this.loadersService.loadDyflexisEmployees()),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { Processor, WorkerHost } from '@nestjs/bullmq';
|
||||||
|
import { Logger } from '@nestjs/common';
|
||||||
|
import { Job } from 'bullmq';
|
||||||
|
import { catchError, lastValueFrom } from 'rxjs';
|
||||||
|
import { DispoLiveApiExtractorsService } from '../../feat-extractors/dispo-live-api-extractors.service';
|
||||||
|
import { SCHICHTPLANUNG_QUEUE } from '../tokens';
|
||||||
|
|
||||||
|
@Processor(SCHICHTPLANUNG_QUEUE, {
|
||||||
|
limiter: {
|
||||||
|
duration: 300,
|
||||||
|
max: 1,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export class SchichtplanungConsumer extends WorkerHost {
|
||||||
|
private readonly logger = new Logger(SchichtplanungConsumer.name);
|
||||||
|
|
||||||
|
constructor(private readonly dlApiService: DispoLiveApiExtractorsService) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
async process(job: Job, token?: string): Promise<any> {
|
||||||
|
try {
|
||||||
|
switch (job.name) {
|
||||||
|
case 'Create':
|
||||||
|
return await lastValueFrom(
|
||||||
|
this.dlApiService.fahrzeugplanungAdd(job.data),
|
||||||
|
);
|
||||||
|
case 'Update':
|
||||||
|
return await lastValueFrom(
|
||||||
|
this.dlApiService.fahrzeugplanungUpdate(job.data).pipe(
|
||||||
|
catchError((err) => {
|
||||||
|
throw err?.response?.data ?? err;
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
case 'Delete':
|
||||||
|
return await lastValueFrom(
|
||||||
|
this.dlApiService.fahrzeugplanungDelete(job.data),
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
throw new Error(
|
||||||
|
`[${SchichtplanungConsumer.name}]: job name not implemented ${job.name}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(
|
||||||
|
`job processing failed, ${typeof error === 'object' ? JSON.stringify(error) : error}, ${JSON.stringify(job.data)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,174 @@
|
||||||
|
import { InjectQueue } from '@nestjs/bullmq';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Queue } from 'bullmq';
|
||||||
|
import { addMinutes, endOfDay, getMonth, getYear, isSameDay, startOfDay } from 'date-fns';
|
||||||
|
import {
|
||||||
|
lastValueFrom
|
||||||
|
} from 'rxjs';
|
||||||
|
import { EmployeeService } from 'src/modules/feat-business-objects/entities/employee/employee.service';
|
||||||
|
import { DispoLiveApiExtractorsService } from '../../feat-extractors/dispo-live-api-extractors.service';
|
||||||
|
import { DyflexisExtractorsService } from '../../feat-extractors/dyflexis-extractors.service';
|
||||||
|
import {
|
||||||
|
parseCETDateTime,
|
||||||
|
pick,
|
||||||
|
transformProperties,
|
||||||
|
} from '../../feat-transformers/transformer.functions';
|
||||||
|
import { SCHICHTPLANUNG_QUEUE } from '../tokens';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SchichtplanungSynchronizer {
|
||||||
|
constructor(
|
||||||
|
private readonly dlApiService: DispoLiveApiExtractorsService,
|
||||||
|
private readonly dyflexisService: DyflexisExtractorsService,
|
||||||
|
private readonly employeeService: EmployeeService,
|
||||||
|
@InjectQueue(SCHICHTPLANUNG_QUEUE)
|
||||||
|
private readonly schichtplanungQueue: Queue,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
// TODO: Do we really want to load everything fresh? Anwer: For now it's fine, by this we circumvent problems arising from our application code
|
||||||
|
async syncSchichtplanung(day: Date) {
|
||||||
|
// Fetch and prepare data
|
||||||
|
const plannedTimes = await lastValueFrom(
|
||||||
|
this.dyflexisService.fetchPlannedTimeForMonth(
|
||||||
|
getMonth(day) + 1,
|
||||||
|
getYear(day),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const plannedTimeForDay = plannedTimes
|
||||||
|
.filter(({ mark }) => mark !== 'sick')
|
||||||
|
.filter(({ deleted }) => deleted != '1')
|
||||||
|
.filter(({ user_id }) => user_id != '0')
|
||||||
|
.filter(
|
||||||
|
({ department_name }) =>
|
||||||
|
department_name !== 'Büro' && department_name !== 'Leitstelle',
|
||||||
|
)
|
||||||
|
.filter(({ start_date }) => isSameDay(new Date(start_date), day));
|
||||||
|
|
||||||
|
const cars = await lastValueFrom(this.dlApiService.fahrzeuge());
|
||||||
|
|
||||||
|
// Create Schichtplanung
|
||||||
|
const schichtplanung = [];
|
||||||
|
for (const plannedTime of plannedTimeForDay) {
|
||||||
|
const { start_date, end_date } = transformProperties(
|
||||||
|
parseCETDateTime('yyyy-MM-dd HH:mm:ss'),
|
||||||
|
)(
|
||||||
|
pick(['start_date', 'end_date'])(
|
||||||
|
plannedTime as Record<'start_date' | 'end_date', string>,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const employee = await this.employeeService.findByPersonnelNumber(
|
||||||
|
plannedTime.user_id,
|
||||||
|
);
|
||||||
|
if (!employee) {
|
||||||
|
console.error(`couldn\'t find employee with id ${plannedTime.user_id}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const car = cars.data.find(({ kfzNr }) =>
|
||||||
|
this.isCarNameForDepartment(plannedTime.department_name, kfzNr),
|
||||||
|
);
|
||||||
|
if (!car) {
|
||||||
|
console.error(
|
||||||
|
`couldn\'t find car with department ${plannedTime.department_name}`,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let planning = {
|
||||||
|
className: 'greenItem',
|
||||||
|
start: start_date.toISOString(),
|
||||||
|
end: end_date.toISOString(),
|
||||||
|
group: car._id,
|
||||||
|
mitarbeiterId: employee.id,
|
||||||
|
}
|
||||||
|
|
||||||
|
if(car.kfzNr.indexOf("KTW") > -1) {
|
||||||
|
planning = {
|
||||||
|
...planning,
|
||||||
|
start: addMinutes(new Date(planning.start), 15).toISOString(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
schichtplanung.push(planning);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine update set
|
||||||
|
const currentPlanung = await lastValueFrom(
|
||||||
|
this.dlApiService.fahrzeugplanungFindByRange({
|
||||||
|
start: startOfDay(day),
|
||||||
|
end: endOfDay(day),
|
||||||
|
}),
|
||||||
|
).then(({ data }) =>
|
||||||
|
data.filter(({ className }) => className === 'greenItem'),
|
||||||
|
);
|
||||||
|
|
||||||
|
const toCreate = [];
|
||||||
|
const toUpdate = [];
|
||||||
|
const toDelete = currentPlanung
|
||||||
|
.filter(
|
||||||
|
({ mitarbeiterId }) =>
|
||||||
|
schichtplanung.findIndex(
|
||||||
|
({ mitarbeiterId: id }) => id === mitarbeiterId,
|
||||||
|
) === -1,
|
||||||
|
)
|
||||||
|
.map(({ _id }) => ({ _id }));
|
||||||
|
|
||||||
|
for (const plan of schichtplanung) {
|
||||||
|
const plannedTime = currentPlanung.find(
|
||||||
|
({ mitarbeiterId }) => mitarbeiterId === plan.mitarbeiterId,
|
||||||
|
);
|
||||||
|
if (!plannedTime) {
|
||||||
|
toCreate.push(plan);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!!plannedTime &&
|
||||||
|
(plannedTime?.start !== plan.start || plannedTime?.end !== plan.end || plannedTime?.group !== plan.group)
|
||||||
|
) {
|
||||||
|
toUpdate.push({ ...plan, _id: plannedTime._id });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
this.schichtplanungQueue.addBulk(
|
||||||
|
toCreate.map((item) => ({
|
||||||
|
name: 'Create',
|
||||||
|
data: item,
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
this.schichtplanungQueue.addBulk(
|
||||||
|
toUpdate.map((item) => ({
|
||||||
|
name: 'Update',
|
||||||
|
data: item,
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
this.schichtplanungQueue.addBulk(
|
||||||
|
toDelete.map((item) => ({
|
||||||
|
name: 'Delete',
|
||||||
|
data: item._id,
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
create: toCreate,
|
||||||
|
update: toUpdate,
|
||||||
|
delete: toDelete,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private isCarNameForDepartment(department: string, carName: string): boolean {
|
||||||
|
if (!department || !carName) return false;
|
||||||
|
const [ordinanceType, carNumber] = department
|
||||||
|
.replace('1/', '')
|
||||||
|
.replace('E', '')
|
||||||
|
.split('-')
|
||||||
|
.map((part) => part.trim());
|
||||||
|
return (
|
||||||
|
(carName.indexOf(ordinanceType) > -1 &&
|
||||||
|
carName.indexOf(carNumber) > -1) ||
|
||||||
|
carName === department
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export const SCHICHTPLANUNG_QUEUE = 'schichtplanung'
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
import {
|
||||||
|
format,
|
||||||
|
} from 'npm:date-fns@4.1.0';
|
||||||
|
|
||||||
|
export interface FahrzeugplanungItemDto {
|
||||||
|
_id?: string;
|
||||||
|
className: string; // 'greenItem' OR 'blocked'
|
||||||
|
group: string; // Fahrzeug ID
|
||||||
|
mitarbeiterId: string; // Mitarbeiter ID
|
||||||
|
start: string; // ISO 8601 TS (UTC)
|
||||||
|
end: string; // ISO 8601 TS (UTC)
|
||||||
|
}
|
||||||
|
|
||||||
|
function chunk<T>(input: T[], size: number): T[][] {
|
||||||
|
return input.reduce(
|
||||||
|
(array, item, index) =>
|
||||||
|
index % size === 0
|
||||||
|
? [...array, [item]]
|
||||||
|
: [...array.slice(0, -1), [...array.slice(-1)[0], item]],
|
||||||
|
[] as T[][]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sleep(ms: number) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteSchichtplanungItems(ids: string[]) {
|
||||||
|
const endpoint = 'custom/open-api/fahrzeugplanung/delete';
|
||||||
|
const url = `https://avicenna.dispolive.de/${endpoint}`;
|
||||||
|
|
||||||
|
console.log(`Deleting ids: ${ids.join(', ')}`)
|
||||||
|
return await Promise.all(
|
||||||
|
ids.map(id => fetch(`${url}/${id}`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ba397e8a-29f2-4c99-9141-75a71fd55830`,
|
||||||
|
'dispolive-api-version': 'v1.0.1',
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getSchichtplanungForDay(day: Date) {
|
||||||
|
const endpoint = 'custom/open-api/fahrzeugplanung/findByRange';
|
||||||
|
const url = `https://avicenna.dispolive.de/${endpoint}/${format(new Date(day), 'yyyy-MM-dd')}_${format(new Date(day), 'yyyy-MM-dd')}`;
|
||||||
|
|
||||||
|
return await fetch(url, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ba397e8a-29f2-4c99-9141-75a71fd55830`,
|
||||||
|
'dispolive-api-version': 'v1.0.1',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const schichtplanung = await getSchichtplanungForDay(new Date('2025-08-11')).then(response => response.json());
|
||||||
|
const chunks = chunk(schichtplanung.map(({id}) => id), 1)
|
||||||
|
for(const chunk of chunks) {
|
||||||
|
const res = await deleteSchichtplanungItems(chunk as string[])
|
||||||
|
console.log(res)
|
||||||
|
await sleep(200)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await main()
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"dev": "deno run --watch main.ts"
|
||||||
|
},
|
||||||
|
"imports": {
|
||||||
|
"@std/assert": "jsr:@std/assert@1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"version": "5",
|
||||||
|
"specifiers": {
|
||||||
|
"jsr:@std/assert@1": "1.0.9",
|
||||||
|
"jsr:@std/internal@^1.0.5": "1.0.5",
|
||||||
|
"npm:date-fns@4.1.0": "4.1.0"
|
||||||
|
},
|
||||||
|
"jsr": {
|
||||||
|
"@std/assert@1.0.9": {
|
||||||
|
"integrity": "a9f0c611a869cc791b26f523eec54c7e187aab7932c2c8e8bea0622d13680dcd",
|
||||||
|
"dependencies": [
|
||||||
|
"jsr:@std/internal"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@std/internal@1.0.5": {
|
||||||
|
"integrity": "54a546004f769c1ac9e025abd15a76b6671ddc9687e2313b67376125650dc7ba"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"npm": {
|
||||||
|
"date-fns@4.1.0": {
|
||||||
|
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"workspace": {
|
||||||
|
"dependencies": [
|
||||||
|
"jsr:@std/assert@1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -77,3 +77,8 @@ services:
|
||||||
- pgdata:/var/lib/postgresql/data
|
- pgdata:/var/lib/postgresql/data
|
||||||
networks:
|
networks:
|
||||||
- data-hub_net
|
- data-hub_net
|
||||||
|
|
||||||
|
cache:
|
||||||
|
image: valkey/valkey:8.1.2-alpine
|
||||||
|
networks:
|
||||||
|
- data-hub_net
|
||||||
Loading…
Reference in New Issue