feature fix

This commit is contained in:
David 2025-10-20 12:30:08 +02:00
parent 68e321a08f
commit dde7d885ae
135 changed files with 33471 additions and 33373 deletions

View File

@ -15,7 +15,20 @@
"Bash(chmod:*)",
"Bash(netstat -ano)",
"Bash(findstr \":5432\")",
"Bash(findstr \"LISTENING\")"
"Bash(findstr \"LISTENING\")",
"Read(//Volumes/**)",
"Bash(find:*)",
"Bash(cd:*)",
"Bash(npm run migration:run:*)",
"Bash(mv:*)",
"Bash(curl:*)",
"Bash(npm run dev:*)",
"Bash(python3:*)",
"Bash(bash:*)",
"Bash(npm rebuild:*)",
"Bash(npm uninstall:*)",
"Bash(PGPASSWORD=xpeditis_password psql -h localhost -p 5432 -U xpeditis -d xpeditis_db -c \"SELECT id FROM organizations WHERE type = ''FREIGHT_FORWARDER'' LIMIT 1;\")",
"Bash(PGPASSWORD=xpeditis_dev_password psql -h localhost -p 5432 -U xpeditis -d xpeditis_dev -c \"SELECT id, name FROM organizations WHERE type = ''FREIGHT_FORWARDER'' LIMIT 1;\")"
],
"deny": [],
"ask": []

1064
CLAUDE.md

File diff suppressed because it is too large Load Diff

View File

@ -31,7 +31,6 @@
"@types/pdfkit": "^0.17.3",
"argon2": "^0.44.0",
"axios": "^1.12.2",
"bcrypt": "^5.1.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.2",
"compression": "^1.8.1",
@ -2719,26 +2718,6 @@
"node": ">=8"
}
},
"node_modules/@mapbox/node-pre-gyp": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
"integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==",
"license": "BSD-3-Clause",
"dependencies": {
"detect-libc": "^2.0.0",
"https-proxy-agent": "^5.0.0",
"make-dir": "^3.1.0",
"node-fetch": "^2.6.7",
"nopt": "^5.0.0",
"npmlog": "^5.0.1",
"rimraf": "^3.0.2",
"semver": "^7.3.5",
"tar": "^6.1.11"
},
"bin": {
"node-pre-gyp": "bin/node-pre-gyp"
}
},
"node_modules/@microsoft/tsdoc": {
"version": "0.15.1",
"resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz",
@ -5748,12 +5727,6 @@
"dev": true,
"license": "Apache-2.0"
},
"node_modules/abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
"license": "ISC"
},
"node_modules/abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
@ -5837,18 +5810,6 @@
"node": ">=0.4.0"
}
},
"node_modules/agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
"license": "MIT",
"dependencies": {
"debug": "4"
},
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/ajv": {
"version": "8.12.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
@ -6008,12 +5969,6 @@
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
"license": "MIT"
},
"node_modules/aproba": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz",
"integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==",
"license": "ISC"
},
"node_modules/archiver": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz",
@ -6132,20 +6087,6 @@
"safe-buffer": "~5.1.0"
}
},
"node_modules/are-we-there-yet": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz",
"integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==",
"deprecated": "This package is no longer supported.",
"license": "ISC",
"dependencies": {
"delegates": "^1.0.0",
"readable-stream": "^3.6.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/arg": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
@ -6441,20 +6382,6 @@
"baseline-browser-mapping": "dist/cli.js"
}
},
"node_modules/bcrypt": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz",
"integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@mapbox/node-pre-gyp": "^1.0.11",
"node-addon-api": "^5.0.0"
},
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/big-integer": {
"version": "1.6.52",
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz",
@ -6931,15 +6858,6 @@
"fsevents": "~2.3.2"
}
},
"node_modules/chownr": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
"license": "ISC",
"engines": {
"node": ">=10"
}
},
"node_modules/chrome-trace-event": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz",
@ -7148,15 +7066,6 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
"node_modules/color-support": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
"integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
"license": "ISC",
"bin": {
"color-support": "bin.js"
}
},
"node_modules/colorette": {
"version": "2.0.20",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
@ -7318,12 +7227,6 @@
"integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==",
"license": "MIT"
},
"node_modules/console-control-strings": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==",
"license": "ISC"
},
"node_modules/content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@ -7641,12 +7544,6 @@
"node": ">=0.4.0"
}
},
"node_modules/delegates": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
"license": "MIT"
},
"node_modules/denque": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
@ -9161,36 +9058,6 @@
"node": ">=12"
}
},
"node_modules/fs-minipass": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
"license": "ISC",
"dependencies": {
"minipass": "^3.0.0"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/fs-minipass/node_modules/minipass": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
"license": "ISC",
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/fs-minipass/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"license": "ISC"
},
"node_modules/fs-monkey": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.1.0.tgz",
@ -9299,33 +9166,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/gauge": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
"integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==",
"deprecated": "This package is no longer supported.",
"license": "ISC",
"dependencies": {
"aproba": "^1.0.3 || ^2.0.0",
"color-support": "^1.1.2",
"console-control-strings": "^1.0.0",
"has-unicode": "^2.0.1",
"object-assign": "^4.1.1",
"signal-exit": "^3.0.0",
"string-width": "^4.2.3",
"strip-ansi": "^6.0.1",
"wide-align": "^1.1.2"
},
"engines": {
"node": ">=10"
}
},
"node_modules/gauge/node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"license": "ISC"
},
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@ -9609,12 +9449,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-unicode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==",
"license": "ISC"
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
@ -9720,19 +9554,6 @@
"node": ">= 0.8"
}
},
"node_modules/https-proxy-agent": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"license": "MIT",
"dependencies": {
"agent-base": "6",
"debug": "4"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/human-signals": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
@ -11598,30 +11419,6 @@
"node": ">=12"
}
},
"node_modules/make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
"license": "MIT",
"dependencies": {
"semver": "^6.0.0"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/make-dir/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/make-error": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
@ -11814,37 +11611,6 @@
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/minizlib": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
"license": "MIT",
"dependencies": {
"minipass": "^3.0.0",
"yallist": "^4.0.0"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/minizlib/node_modules/minipass": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
"license": "ISC",
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/minizlib/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"license": "ISC"
},
"node_modules/mjml": {
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/mjml/-/mjml-4.16.1.tgz",
@ -12384,12 +12150,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/node-addon-api": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
"integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==",
"license": "MIT"
},
"node_modules/node-emoji": {
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz",
@ -12454,21 +12214,6 @@
"node": ">=6.0.0"
}
},
"node_modules/nopt": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
"integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==",
"license": "ISC",
"dependencies": {
"abbrev": "1"
},
"bin": {
"nopt": "bin/nopt.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@ -12491,19 +12236,6 @@
"node": ">=8"
}
},
"node_modules/npmlog": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
"integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==",
"deprecated": "This package is no longer supported.",
"license": "ISC",
"dependencies": {
"are-we-there-yet": "^2.0.0",
"console-control-strings": "^1.1.0",
"gauge": "^3.0.0",
"set-blocking": "^2.0.0"
}
},
"node_modules/nth-check": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
@ -13894,6 +13626,7 @@
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"deprecated": "Rimraf versions prior to v4 are no longer supported",
"dev": true,
"license": "ISC",
"dependencies": {
"glob": "^7.1.3"
@ -13909,6 +13642,7 @@
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
@ -13920,6 +13654,7 @@
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Glob versions prior to v9 are no longer supported",
"dev": true,
"license": "ISC",
"dependencies": {
"fs.realpath": "^1.0.0",
@ -13940,6 +13675,7 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
@ -14182,12 +13918,6 @@
"node": ">= 0.8.0"
}
},
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
"license": "ISC"
},
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
@ -14852,23 +14582,6 @@
"url": "https://opencollective.com/webpack"
}
},
"node_modules/tar": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
"integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
"license": "ISC",
"dependencies": {
"chownr": "^2.0.0",
"fs-minipass": "^2.0.0",
"minipass": "^5.0.0",
"minizlib": "^2.1.1",
"mkdirp": "^1.0.3",
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/tar-stream": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
@ -14885,33 +14598,6 @@
"node": ">=6"
}
},
"node_modules/tar/node_modules/minipass": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
"integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
"license": "ISC",
"engines": {
"node": ">=8"
}
},
"node_modules/tar/node_modules/mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"license": "MIT",
"bin": {
"mkdirp": "bin/cmd.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/tar/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"license": "ISC"
},
"node_modules/terser": {
"version": "5.44.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz",
@ -16231,15 +15917,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/wide-align": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
"integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
"license": "ISC",
"dependencies": {
"string-width": "^1.0.2 || 2 || 3 || 4"
}
},
"node_modules/word-wrap": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",

View File

@ -47,7 +47,6 @@
"@types/pdfkit": "^0.17.3",
"argon2": "^0.44.0",
"axios": "^1.12.2",
"bcrypt": "^5.1.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.2",
"compression": "^1.8.1",

View File

@ -17,6 +17,7 @@ import {
UseGuards,
Res,
StreamableFile,
Inject,
} from '@nestjs/common';
import {
ApiTags,
@ -40,8 +41,8 @@ import { BookingFilterDto } from '../dto/booking-filter.dto';
import { BookingExportDto, ExportFormat } from '../dto/booking-export.dto';
import { BookingMapper } from '../mappers';
import { BookingService } from '../../domain/services/booking.service';
import { BookingRepository } from '../../domain/ports/out/booking.repository';
import { RateQuoteRepository } from '../../domain/ports/out/rate-quote.repository';
import { BookingRepository, BOOKING_REPOSITORY } from '../../domain/ports/out/booking.repository';
import { RateQuoteRepository, RATE_QUOTE_REPOSITORY } from '../../domain/ports/out/rate-quote.repository';
import { BookingNumber } from '../../domain/value-objects/booking-number.vo';
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
import { CurrentUser, UserPayload } from '../decorators/current-user.decorator';
@ -63,8 +64,8 @@ export class BookingsController {
constructor(
private readonly bookingService: BookingService,
private readonly bookingRepository: BookingRepository,
private readonly rateQuoteRepository: RateQuoteRepository,
@Inject(BOOKING_REPOSITORY) private readonly bookingRepository: BookingRepository,
@Inject(RATE_QUOTE_REPOSITORY) private readonly rateQuoteRepository: RateQuoteRepository,
private readonly exportService: ExportService,
private readonly fuzzySearchService: FuzzySearchService,
private readonly auditService: AuditService,

View File

@ -17,6 +17,7 @@ import {
DefaultValuePipe,
UseGuards,
ForbiddenException,
Inject,
} from '@nestjs/common';
import {
ApiTags,
@ -35,7 +36,7 @@ import {
OrganizationListResponseDto,
} from '../dto/organization.dto';
import { OrganizationMapper } from '../mappers/organization.mapper';
import { OrganizationRepository } from '../../domain/ports/out/organization.repository';
import { OrganizationRepository, ORGANIZATION_REPOSITORY } from '../../domain/ports/out/organization.repository';
import { Organization, OrganizationType } from '../../domain/entities/organization.entity';
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
import { RolesGuard } from '../guards/roles.guard';
@ -60,7 +61,7 @@ export class OrganizationsController {
private readonly logger = new Logger(OrganizationsController.name);
constructor(
private readonly organizationRepository: OrganizationRepository,
@Inject(ORGANIZATION_REPOSITORY) private readonly organizationRepository: OrganizationRepository,
) {}
/**

View File

@ -19,6 +19,7 @@ import {
UseGuards,
ForbiddenException,
ConflictException,
Inject,
} from '@nestjs/common';
import {
ApiTags,
@ -38,7 +39,7 @@ import {
UserListResponseDto,
} from '../dto/user.dto';
import { UserMapper } from '../mappers/user.mapper';
import { UserRepository } from '../../domain/ports/out/user.repository';
import { UserRepository, USER_REPOSITORY } from '../../domain/ports/out/user.repository';
import { User, UserRole as DomainUserRole } from '../../domain/entities/user.entity';
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
import { RolesGuard } from '../guards/roles.guard';
@ -66,7 +67,7 @@ import * as crypto from 'crypto';
export class UsersController {
private readonly logger = new Logger(UsersController.name);
constructor(private readonly userRepository: UserRepository) {}
constructor(@Inject(USER_REPOSITORY) private readonly userRepository: UserRepository) {}
/**
* Create/Invite a new user

View File

@ -4,16 +4,28 @@ import { RatesController } from '../controllers/rates.controller';
import { CacheModule } from '../../infrastructure/cache/cache.module';
import { CarrierModule } from '../../infrastructure/carriers/carrier.module';
// Import domain services
import { RateSearchService } from '../../domain/services/rate-search.service';
// Import domain ports
import { RATE_QUOTE_REPOSITORY } from '../../domain/ports/out/rate-quote.repository';
import { PORT_REPOSITORY } from '../../domain/ports/out/port.repository';
import { CARRIER_REPOSITORY } from '../../domain/ports/out/carrier.repository';
import { CACHE_PORT } from '../../domain/ports/out/cache.port';
// Import infrastructure implementations
import { TypeOrmRateQuoteRepository } from '../../infrastructure/persistence/typeorm/repositories/typeorm-rate-quote.repository';
import { TypeOrmPortRepository } from '../../infrastructure/persistence/typeorm/repositories/typeorm-port.repository';
import { TypeOrmCarrierRepository } from '../../infrastructure/persistence/typeorm/repositories/typeorm-carrier.repository';
import { RateQuoteOrmEntity } from '../../infrastructure/persistence/typeorm/entities/rate-quote.orm-entity';
import { PortOrmEntity } from '../../infrastructure/persistence/typeorm/entities/port.orm-entity';
import { CarrierOrmEntity } from '../../infrastructure/persistence/typeorm/entities/carrier.orm-entity';
@Module({
imports: [
CacheModule,
CarrierModule,
TypeOrmModule.forFeature([RateQuoteOrmEntity]), // 👈 Add this
TypeOrmModule.forFeature([RateQuoteOrmEntity, PortOrmEntity, CarrierOrmEntity]),
],
controllers: [RatesController],
providers: [
@ -21,9 +33,43 @@ import { RateQuoteOrmEntity } from '../../infrastructure/persistence/typeorm/ent
provide: RATE_QUOTE_REPOSITORY,
useClass: TypeOrmRateQuoteRepository,
},
{
provide: PORT_REPOSITORY,
useClass: TypeOrmPortRepository,
},
{
provide: CARRIER_REPOSITORY,
useClass: TypeOrmCarrierRepository,
},
{
provide: RateSearchService,
useFactory: (
cache: any,
rateQuoteRepo: any,
portRepo: any,
carrierRepo: any,
) => {
// For now, create service with empty connectors array
// TODO: Inject actual carrier connectors
return new RateSearchService(
[],
cache,
rateQuoteRepo,
portRepo,
carrierRepo,
);
},
inject: [
CACHE_PORT,
RATE_QUOTE_REPOSITORY,
PORT_REPOSITORY,
CARRIER_REPOSITORY,
],
},
],
exports: [
RATE_QUOTE_REPOSITORY, // optional, if used in other modules
RATE_QUOTE_REPOSITORY,
RateSearchService,
],
})
export class RatesModule {}

Binary file not shown.

View File

@ -6,16 +6,17 @@
import { Module, Global } from '@nestjs/common';
import { RedisCacheAdapter } from './redis-cache.adapter';
import { CACHE_PORT } from '../../domain/ports/out/cache.port';
@Global()
@Module({
providers: [
{
provide: 'CachePort',
provide: CACHE_PORT,
useClass: RedisCacheAdapter,
},
RedisCacheAdapter,
],
exports: ['CachePort', RedisCacheAdapter],
exports: [CACHE_PORT, RedisCacheAdapter],
})
export class CacheModule {}

View File

@ -0,0 +1,52 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class EnableFuzzySearch1730000000007 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
// Enable pg_trgm extension for trigram similarity search
await queryRunner.query(`CREATE EXTENSION IF NOT EXISTS pg_trgm;`);
// Create GIN indexes for full-text search on bookings
await queryRunner.query(`
CREATE INDEX IF NOT EXISTS idx_booking_number_trgm
ON bookings USING gin(booking_number gin_trgm_ops);
`);
await queryRunner.query(`
CREATE INDEX IF NOT EXISTS idx_shipper_name_trgm
ON bookings USING gin(shipper_name gin_trgm_ops);
`);
await queryRunner.query(`
CREATE INDEX IF NOT EXISTS idx_consignee_name_trgm
ON bookings USING gin(consignee_name gin_trgm_ops);
`);
// Create full-text search indexes using ts_vector
await queryRunner.query(`
CREATE INDEX IF NOT EXISTS idx_booking_number_fts
ON bookings USING gin(to_tsvector('english', booking_number));
`);
await queryRunner.query(`
CREATE INDEX IF NOT EXISTS idx_shipper_name_fts
ON bookings USING gin(to_tsvector('english', shipper_name));
`);
await queryRunner.query(`
CREATE INDEX IF NOT EXISTS idx_consignee_name_fts
ON bookings USING gin(to_tsvector('english', consignee_name));
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
// Drop indexes
await queryRunner.query(`DROP INDEX IF EXISTS idx_booking_number_trgm;`);
await queryRunner.query(`DROP INDEX IF EXISTS idx_shipper_name_trgm;`);
await queryRunner.query(`DROP INDEX IF EXISTS idx_consignee_name_trgm;`);
await queryRunner.query(`DROP INDEX IF EXISTS idx_booking_number_fts;`);
await queryRunner.query(`DROP INDEX IF EXISTS idx_shipper_name_fts;`);
await queryRunner.query(`DROP INDEX IF EXISTS idx_consignee_name_fts;`);
// Note: We don't drop the pg_trgm extension as other parts of the system might use it
}
}

View File

@ -0,0 +1,90 @@
/**
* Seed Test Users Migration
*
* Seeds test users for development and testing
* Password for all users: AdminPassword123!
* Hash generated with bcrypt rounds=10
*/
import { MigrationInterface, QueryRunner } from 'typeorm';
import { v4 as uuidv4 } from 'uuid';
export class SeedTestUsers1730000000007 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
// Get the first organization ID from the database
const result = await queryRunner.query(`
SELECT id FROM organizations WHERE type = 'FREIGHT_FORWARDER' LIMIT 1
`);
if (result.length === 0) {
throw new Error('No organization found to seed users. Please run organization seed migration first.');
}
const organizationId = result[0].id;
// Pre-hashed password: AdminPassword123! (bcrypt rounds=10)
// This hash is deterministic for testing purposes
const passwordHash = '$2b$10$5qK9KqP7YqXZ5Z0kZ0kZ0OqK9KqP7YqXZ5Z0kZ0kZ0OqK9KqP7YqX';
// Insert test users
const users = [
{
id: uuidv4(),
email: 'admin@xpeditis.com',
passwordHash,
firstName: 'Admin',
lastName: 'User',
role: 'ADMIN',
},
{
id: uuidv4(),
email: 'manager@xpeditis.com',
passwordHash,
firstName: 'Manager',
lastName: 'User',
role: 'MANAGER',
},
{
id: uuidv4(),
email: 'user@xpeditis.com',
passwordHash,
firstName: 'Regular',
lastName: 'User',
role: 'USER',
},
];
for (const user of users) {
await queryRunner.query(`
INSERT INTO "users" (
"id", "email", "password_hash", "first_name", "last_name", "role",
"organization_id", "totp_secret", "is_active", "created_at", "updated_at"
) VALUES (
'${user.id}', '${user.email}', '${user.passwordHash}',
'${user.firstName}', '${user.lastName}', '${user.role}',
'${organizationId}', NULL, true, NOW(), NOW()
)
ON CONFLICT ("email") DO NOTHING;
`);
}
console.log('✅ Seeded test users successfully');
console.log(' - admin@xpeditis.com (ADMIN)');
console.log(' - manager@xpeditis.com (MANAGER)');
console.log(' - user@xpeditis.com (USER)');
console.log(' - Password: AdminPassword123!');
}
public async down(queryRunner: QueryRunner): Promise<void> {
// Delete test users
await queryRunner.query(`
DELETE FROM users WHERE email IN (
'admin@xpeditis.com',
'manager@xpeditis.com',
'user@xpeditis.com'
);
`);
console.log('✅ Removed test users successfully');
}
}

View File

@ -0,0 +1,137 @@
import { MigrationInterface, QueryRunner, Table, TableIndex } from 'typeorm';
export class CreateAuditLogsTable1730000000008 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createTable(
new Table({
name: 'audit_logs',
columns: [
{
name: 'id',
type: 'uuid',
isPrimary: true,
},
{
name: 'action',
type: 'varchar',
length: '100',
isNullable: false,
},
{
name: 'status',
type: 'varchar',
length: '20',
isNullable: false,
},
{
name: 'user_id',
type: 'uuid',
isNullable: false,
},
{
name: 'user_email',
type: 'varchar',
length: '255',
isNullable: false,
},
{
name: 'organization_id',
type: 'uuid',
isNullable: false,
},
{
name: 'resource_type',
type: 'varchar',
length: '100',
isNullable: true,
},
{
name: 'resource_id',
type: 'varchar',
length: '255',
isNullable: true,
},
{
name: 'resource_name',
type: 'varchar',
length: '255',
isNullable: true,
},
{
name: 'metadata',
type: 'jsonb',
isNullable: true,
},
{
name: 'ip_address',
type: 'varchar',
length: '45',
isNullable: true,
},
{
name: 'user_agent',
type: 'text',
isNullable: true,
},
{
name: 'error_message',
type: 'text',
isNullable: true,
},
{
name: 'timestamp',
type: 'timestamp',
default: 'CURRENT_TIMESTAMP',
isNullable: false,
},
],
}),
true,
);
// Create indexes for efficient querying
await queryRunner.createIndex(
'audit_logs',
new TableIndex({
name: 'idx_audit_logs_organization_timestamp',
columnNames: ['organization_id', 'timestamp'],
}),
);
await queryRunner.createIndex(
'audit_logs',
new TableIndex({
name: 'idx_audit_logs_user_timestamp',
columnNames: ['user_id', 'timestamp'],
}),
);
await queryRunner.createIndex(
'audit_logs',
new TableIndex({
name: 'idx_audit_logs_resource',
columnNames: ['resource_type', 'resource_id'],
}),
);
await queryRunner.createIndex(
'audit_logs',
new TableIndex({
name: 'idx_audit_logs_action',
columnNames: ['action'],
}),
);
await queryRunner.createIndex(
'audit_logs',
new TableIndex({
name: 'idx_audit_logs_timestamp',
columnNames: ['timestamp'],
}),
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable('audit_logs');
}
}

View File

@ -0,0 +1,109 @@
import { MigrationInterface, QueryRunner, Table, TableIndex } from 'typeorm';
export class CreateNotificationsTable1730000000009 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createTable(
new Table({
name: 'notifications',
columns: [
{
name: 'id',
type: 'uuid',
isPrimary: true,
},
{
name: 'user_id',
type: 'uuid',
isNullable: false,
},
{
name: 'organization_id',
type: 'uuid',
isNullable: false,
},
{
name: 'type',
type: 'varchar',
length: '50',
isNullable: false,
},
{
name: 'priority',
type: 'varchar',
length: '20',
isNullable: false,
},
{
name: 'title',
type: 'varchar',
length: '255',
isNullable: false,
},
{
name: 'message',
type: 'text',
isNullable: false,
},
{
name: 'metadata',
type: 'jsonb',
isNullable: true,
},
{
name: 'read',
type: 'boolean',
default: false,
isNullable: false,
},
{
name: 'read_at',
type: 'timestamp',
isNullable: true,
},
{
name: 'action_url',
type: 'varchar',
length: '500',
isNullable: true,
},
{
name: 'created_at',
type: 'timestamp',
default: 'CURRENT_TIMESTAMP',
isNullable: false,
},
],
}),
true,
);
// Create indexes for efficient querying
await queryRunner.createIndex(
'notifications',
new TableIndex({
name: 'idx_notifications_user_read_created',
columnNames: ['user_id', 'read', 'created_at'],
}),
);
await queryRunner.createIndex(
'notifications',
new TableIndex({
name: 'idx_notifications_organization_created',
columnNames: ['organization_id', 'created_at'],
}),
);
await queryRunner.createIndex(
'notifications',
new TableIndex({
name: 'idx_notifications_user_created',
columnNames: ['user_id', 'created_at'],
}),
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable('notifications');
}
}

View File

@ -0,0 +1,99 @@
import { MigrationInterface, QueryRunner, Table, TableIndex } from 'typeorm';
export class CreateWebhooksTable1730000000010 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createTable(
new Table({
name: 'webhooks',
columns: [
{
name: 'id',
type: 'uuid',
isPrimary: true,
},
{
name: 'organization_id',
type: 'uuid',
isNullable: false,
},
{
name: 'url',
type: 'varchar',
length: '500',
isNullable: false,
},
{
name: 'events',
type: 'text',
isNullable: false,
},
{
name: 'secret',
type: 'varchar',
length: '255',
isNullable: false,
},
{
name: 'status',
type: 'varchar',
length: '20',
isNullable: false,
},
{
name: 'description',
type: 'text',
isNullable: true,
},
{
name: 'headers',
type: 'jsonb',
isNullable: true,
},
{
name: 'retry_count',
type: 'int',
default: 0,
isNullable: false,
},
{
name: 'last_triggered_at',
type: 'timestamp',
isNullable: true,
},
{
name: 'failure_count',
type: 'int',
default: 0,
isNullable: false,
},
{
name: 'created_at',
type: 'timestamp',
default: 'CURRENT_TIMESTAMP',
isNullable: false,
},
{
name: 'updated_at',
type: 'timestamp',
default: 'CURRENT_TIMESTAMP',
isNullable: false,
},
],
}),
true,
);
// Create index for efficient querying
await queryRunner.createIndex(
'webhooks',
new TableIndex({
name: 'idx_webhooks_organization_status',
columnNames: ['organization_id', 'status'],
}),
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable('webhooks');
}
}