Compare commits
108 Commits
ba3227545d
...
v0.1.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
| 3a0c729b9a | |||
| 057a570e43 | |||
| 52974aa0b4 | |||
| ecfbda9036 | |||
| 389211186f | |||
| 3a27fd8542 | |||
| 1f6637c512 | |||
| 1840ac5e58 | |||
| 636daaed0a | |||
| 71c83062cb | |||
| cd67c4de80 | |||
| 36ac1dccb4 | |||
| 514a44b6de | |||
| a7bb364a2f | |||
| 047cc7cdf0 | |||
| 8dc4d70e28 | |||
| c8931c7249 | |||
| 67f36c5499 | |||
| ebf1060475 | |||
| c64392f457 | |||
| e9e73c829c | |||
| bcb7773007 | |||
| eb950d2c29 | |||
| 2616acf106 | |||
| 30ff7b71d9 | |||
| e7af3d1182 | |||
| 3e66c3920d | |||
| eb9d77c3d4 | |||
| 342a97f6b1 | |||
| b0c7277a6c | |||
| dc95e50a84 | |||
| d2a9e1d110 | |||
| a9c69250bd | |||
| d61be61f44 | |||
| f5bae2c0c2 | |||
| 05758791be | |||
| 51026e3e2c | |||
| 9631736e26 | |||
| ce9d8eaaf5 | |||
| 1bbf5c2a49 | |||
| 13718fe702 | |||
| 0de2579942 | |||
| 7c31b43a4a | |||
| 85e96f5ed1 | |||
| 6b515c608f | |||
| d8869b103b | |||
| 1dba774abc | |||
| 505d7cea5d | |||
| 1ff5e5032f | |||
| 5fa70da90c | |||
| 0459cd788a | |||
| 7d7d991122 | |||
| 2721bb2a3b | |||
| 4424c742d2 | |||
| 6d8499bfb8 | |||
| 9edafc9d28 | |||
| e9b0101095 | |||
| ca885fb01a | |||
| edb3668548 | |||
| 87803eed43 | |||
| e61038e004 | |||
| d99449ddc4 | |||
| 3552ca31f9 | |||
| b578f05d64 | |||
| 4ca74de279 | |||
| 12412536d1 | |||
| a38e2e0339 | |||
| 8c253a90b6 | |||
| ba30281e59 | |||
| 2ad78e22f1 | |||
| 518c0a8c19 | |||
| cd13360cfb | |||
| 4e0cf8c54c | |||
| 36995e9fb4 | |||
| 30ffd843c7 | |||
| bb6155c969 | |||
| 7d2f048932 | |||
| 649ae1ee9f | |||
| 8446dbc955 | |||
| 0b7318f856 | |||
| bddc9aca0d | |||
| 77b4533dea | |||
| 83a542d1b7 | |||
| 4855412733 | |||
| 251970ec8f | |||
| f7ea5f709e | |||
| 3d3c2aa964 | |||
| 781025dca0 | |||
| a593bb2baa | |||
| 759f96b0b6 | |||
| de5df2b00b | |||
| 4d53af0338 | |||
| f7276ca2d7 | |||
| d6328ab764 | |||
| a6d53f0266 | |||
| 7962463927 | |||
| f716de1a58 | |||
| 88cef2a56c | |||
| cb00addee9 | |||
| b832d7aa1e | |||
| 32517d0c98 | |||
| 82f8369640 | |||
| 3734d9daac | |||
| a1eeadeec4 | |||
| 3639c1b77c | |||
| cfbc156517 | |||
| fb3cd85b41 | |||
| 5b1c88546f |
@@ -9,4 +9,4 @@ builds
|
|||||||
testFiles
|
testFiles
|
||||||
nssm.exe
|
nssm.exe
|
||||||
postgresql-17.9-2-windows-x64.exe
|
postgresql-17.9-2-windows-x64.exe
|
||||||
VSCodeUserSetup-x64-1.112.0.msi
|
VSCodeSetup-x64-1.120.0.msi
|
||||||
@@ -50,3 +50,11 @@ GP_PASSWORD=
|
|||||||
# how often to check for new/updated queries in min
|
# how often to check for new/updated queries in min
|
||||||
QUERY_TIME_TYPE=m #valid options are m, h
|
QUERY_TIME_TYPE=m #valid options are m, h
|
||||||
QUERY_CHECK=1
|
QUERY_CHECK=1
|
||||||
|
|
||||||
|
|
||||||
|
# Oauth setup
|
||||||
|
PROVIDER=""
|
||||||
|
CLIENT_ID=""
|
||||||
|
CLIENT_SECRET=""
|
||||||
|
CLIENT_SCOPES="openid profile email groups"
|
||||||
|
DISCOVERY_URL=""
|
||||||
|
|||||||
66
.gitea/ISSUE_TEMPLATE/bug_report.md
Normal file
66
.gitea/ISSUE_TEMPLATE/bug_report.md
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
---
|
||||||
|
name: Bug Report
|
||||||
|
about: Report something that is broken or not working correctly
|
||||||
|
title: "[BUG] "
|
||||||
|
ref: "main"
|
||||||
|
labels:
|
||||||
|
|
||||||
|
- bug
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Summary
|
||||||
|
|
||||||
|
Briefly explain the issue.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Steps To Reproduce
|
||||||
|
|
||||||
|
1. Go to ...
|
||||||
|
2. Click ...
|
||||||
|
3. Scan ...
|
||||||
|
4. Error occurs ...
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Expected Behavior
|
||||||
|
|
||||||
|
What should have happened?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Actual Behavior
|
||||||
|
|
||||||
|
What actually happened instead?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Severity
|
||||||
|
|
||||||
|
- [ ] Low
|
||||||
|
- [ ] Medium
|
||||||
|
- [ ] High
|
||||||
|
- [ ] Critical
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
- Production
|
||||||
|
- Development
|
||||||
|
- Zebra Scanner
|
||||||
|
- Mobile Device
|
||||||
|
- Windows Server
|
||||||
|
- Docker
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Logs / Screenshots
|
||||||
|
|
||||||
|
Paste logs or upload screenshots here.
|
||||||
|
|
||||||
|
```txt
|
||||||
|
Paste logs here
|
||||||
1
.gitea/ISSUE_TEMPLATE/config.yaml
Normal file
1
.gitea/ISSUE_TEMPLATE/config.yaml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
47
.gitea/ISSUE_TEMPLATE/enhancement.md
Normal file
47
.gitea/ISSUE_TEMPLATE/enhancement.md
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
---
|
||||||
|
name: Enhancement
|
||||||
|
about: Improve or refine an existing feature
|
||||||
|
title: "[ENHANCEMENT] "
|
||||||
|
ref: "main"
|
||||||
|
labels:
|
||||||
|
|
||||||
|
- enhancement
|
||||||
|
---
|
||||||
|
|
||||||
|
# Existing Feature
|
||||||
|
|
||||||
|
What current feature or workflow is being improved?
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
- Notifications
|
||||||
|
- Scanner Login
|
||||||
|
- Release Monitor
|
||||||
|
- Printing
|
||||||
|
- Auth
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Proposed Improvement
|
||||||
|
|
||||||
|
Describe the improvement.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Expected Benefit
|
||||||
|
|
||||||
|
Why would this improvement help?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Impact
|
||||||
|
|
||||||
|
- [ ] Small
|
||||||
|
- [ ] Medium
|
||||||
|
- [ ] Large
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Additional Notes
|
||||||
|
|
||||||
|
Anything else worth mentioning.
|
||||||
40
.gitea/ISSUE_TEMPLATE/feature_request.md
Normal file
40
.gitea/ISSUE_TEMPLATE/feature_request.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
name: Feature Request
|
||||||
|
about: Suggest a brand new feature or module
|
||||||
|
title: "[FEATURE] "
|
||||||
|
ref: "main"
|
||||||
|
labels:
|
||||||
|
|
||||||
|
- feature
|
||||||
|
---
|
||||||
|
|
||||||
|
# Problem Statement
|
||||||
|
|
||||||
|
What problem are you trying to solve?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Proposed Solution
|
||||||
|
|
||||||
|
Describe the feature you would like added.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Alternatives Considered
|
||||||
|
|
||||||
|
Any other ideas, workarounds, or approaches?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Priority
|
||||||
|
|
||||||
|
- [ ] Nice to Have
|
||||||
|
- [ ] Medium Priority
|
||||||
|
- [ ] High Priority
|
||||||
|
- [ ] Critical
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Additional Context
|
||||||
|
|
||||||
|
Add mockups, screenshots, examples, or notes here.
|
||||||
@@ -12,20 +12,20 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout (local)
|
- name: Checkout (local)
|
||||||
run: |
|
run: |
|
||||||
git clone https://git.tuffraid.net/cowch/lst_v3.git .
|
git clone http://10.75.9.150:3100/cowch/lst_v3.git .
|
||||||
git checkout ${{ gitea.sha }}
|
git checkout ${{ gitea.sha }}
|
||||||
|
|
||||||
- name: Login to registry
|
- name: Login to registry
|
||||||
run: echo "${{ secrets.PASSWORD }}" | docker login git.tuffraid.net -u "cowch" --password-stdin
|
run: echo "${{ secrets.PASSWORD }}" | docker login 10.75.9.150:3100 -u "cowch" --password-stdin
|
||||||
|
|
||||||
- name: Build image
|
- name: Build image
|
||||||
run: |
|
run: |
|
||||||
docker build \
|
docker build \
|
||||||
-t git.tuffraid.net/cowch/lst_v3:latest \
|
-t 10.75.9.150:3100/cowch/lst_v3:latest \
|
||||||
-t git.tuffraid.net/cowch/lst_v3:${{ gitea.sha }} \
|
-t 10.75.9.150:3100/cowch/lst_v3:${{ gitea.sha }} \
|
||||||
.
|
.
|
||||||
|
|
||||||
- name: Push
|
- name: Push
|
||||||
run: |
|
run: |
|
||||||
docker push git.tuffraid.net/cowch/lst_v3:latest
|
docker push 10.75.9.150:3100/cowch/lst_v3:latest
|
||||||
docker push git.tuffraid.net/cowch/lst_v3:${{ gitea.sha }}
|
docker push 10.75.9.150:3100/cowch/lst_v3:${{ gitea.sha }}
|
||||||
@@ -14,12 +14,12 @@ jobs:
|
|||||||
# Examples:
|
# Examples:
|
||||||
# http://gitea.internal.lan:3000
|
# http://gitea.internal.lan:3000
|
||||||
# https://gitea-origin.yourdomain.local
|
# https://gitea-origin.yourdomain.local
|
||||||
GITEA_INTERNAL_URL: "https://git.tuffraid.net"
|
GITEA_INTERNAL_URL: "http://10.75.9.150:3100" #"https://git.tuffraid.net"
|
||||||
|
|
||||||
# Internal/origin registry host. Usually same host as above, but without protocol.
|
# Internal/origin registry host. Usually same host as above, but without protocol.
|
||||||
# Example:
|
# Example:
|
||||||
# gitea.internal:3000
|
# gitea.internal:3000
|
||||||
REGISTRY_HOST: "git.tuffraid.net"
|
REGISTRY_HOST: "10.75.9.150:3100" #"git.tuffraid.net"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out repository
|
- name: Check out repository
|
||||||
|
|||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -5,11 +5,13 @@ builds
|
|||||||
.buildNumber
|
.buildNumber
|
||||||
temp
|
temp
|
||||||
brunoApi
|
brunoApi
|
||||||
|
downloads
|
||||||
.scriptCreds
|
.scriptCreds
|
||||||
node-v24.14.0-x64.msi
|
node-v24.14.0-x64.msi
|
||||||
postgresql-17.9-2-windows-x64.exe
|
postgresql-17.9-2-windows-x64.exe
|
||||||
VSCodeUserSetup-x64-1.112.0.exe
|
VSCodeSetup-x64-1.120.0.exe
|
||||||
nssm.exe
|
nssm.exe
|
||||||
|
frontend/.tanstack
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
@@ -148,3 +150,4 @@ dist
|
|||||||
.yarn/install-state.gz
|
.yarn/install-state.gz
|
||||||
.pnp.*
|
.pnp.*
|
||||||
|
|
||||||
|
frontend/.tanstack/tmp/2249110e-da91fb0b1b87b6c4cc3e2c2cd25037fd
|
||||||
|
|||||||
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"editor.defaultFormatter": "biomejs.biome",
|
"editor.defaultFormatter": "biomejs.biome",
|
||||||
"workbench.colorTheme": "Default Dark+",
|
"workbench.colorTheme": "Dark+",
|
||||||
"terminal.integrated.env.windows": {},
|
"terminal.integrated.env.windows": {},
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"typescript.preferences.importModuleSpecifier": "relative",
|
"typescript.preferences.importModuleSpecifier": "relative",
|
||||||
|
|||||||
265
CHANGELOG.md
265
CHANGELOG.md
@@ -1,5 +1,270 @@
|
|||||||
# All Changes to LST can be found below.
|
# All Changes to LST can be found below.
|
||||||
|
|
||||||
|
## [0.1.0-alpha.2](https://git.tuffraid.net/cowch/lst_v3/compare/v0.1.0-alpha.1...v0.1.0-alpha.2) (2026-05-23)
|
||||||
|
|
||||||
|
|
||||||
|
### ⚠ BREAKING CHANGES
|
||||||
|
|
||||||
|
* **build:** gives a rabbit hole error
|
||||||
|
|
||||||
|
### 🌟 Enhancements
|
||||||
|
|
||||||
|
* **opendock:** added in new article link setup for fine tuning how od works ([3892111](https://git.tuffraid.net/cowch/lst_v3/commits/389211186f00cb8a6fdd5de092a944fa7e5898aa))
|
||||||
|
* **opendock:** scheduing updates ([1840ac5](https://git.tuffraid.net/cowch/lst_v3/commits/1840ac5e580c726c452216480b6e14e7c52a0f35)), closes [#23](https://git.tuffraid.net/cowch/lst_v3/issues/23)
|
||||||
|
|
||||||
|
|
||||||
|
### 🐛 Bug fixes
|
||||||
|
|
||||||
|
* **build:** crashes when files changed :( ([1f6637c](https://git.tuffraid.net/cowch/lst_v3/commits/1f6637c512dcd465c5000f8d1baaa8e76766edc1)), closes [#24](https://git.tuffraid.net/cowch/lst_v3/issues/24)
|
||||||
|
* **docs:** wrong location for images ([057a570](https://git.tuffraid.net/cowch/lst_v3/commits/057a570e43a8e1763652d98244c90999c3fccd42))
|
||||||
|
* **mobile:** correction to axios helper ([ecfbda9](https://git.tuffraid.net/cowch/lst_v3/commits/ecfbda9036f3d68c93e9c1d81021efa8093f18e2))
|
||||||
|
* **sql queries:** disable job would error so now we will check if it exists before trying to kill it ([636daae](https://git.tuffraid.net/cowch/lst_v3/commits/636daaed0adeda908e7e850a4f5bb20d7bbef861))
|
||||||
|
|
||||||
|
|
||||||
|
### 📚 Documentation
|
||||||
|
|
||||||
|
* **mobile:** updated imgs to be a little smaller ([3a27fd8](https://git.tuffraid.net/cowch/lst_v3/commits/3a27fd8542c3fa4ad5520532c2f10c6e3eaa951c))
|
||||||
|
|
||||||
|
|
||||||
|
### 🛠️ Code Refactor
|
||||||
|
|
||||||
|
* **mobile:** added missing error to the scanner ([52974aa](https://git.tuffraid.net/cowch/lst_v3/commits/52974aa0b4f21431777b773200a57f185b4babd2))
|
||||||
|
* **opendock:** changes to how we do the intergration scheduling ([cd67c4d](https://git.tuffraid.net/cowch/lst_v3/commits/cd67c4de80b6f0244afc639a7360e9dc2ba97a21)), closes [#23](https://git.tuffraid.net/cowch/lst_v3/issues/23)
|
||||||
|
|
||||||
|
|
||||||
|
### 📈 Project changes
|
||||||
|
|
||||||
|
* **docker:** changes to the ignore file ([71c8306](https://git.tuffraid.net/cowch/lst_v3/commits/71c83062cb644796ebbfd845084ac6c019206faa))
|
||||||
|
|
||||||
|
## [0.1.0-alpha.1](https://git.tuffraid.net/cowch/lst_v3/compare/v0.1.0-alpha.0...v0.1.0-alpha.1) (2026-05-19)
|
||||||
|
|
||||||
|
|
||||||
|
### 🐛 Bug fixes
|
||||||
|
|
||||||
|
* **notifications:** reprinting ([c8931c7](https://git.tuffraid.net/cowch/lst_v3/commits/c8931c7249b8f532b5dd37df3271da98f14ee710)), closes [#20](https://git.tuffraid.net/cowch/lst_v3/issues/20)
|
||||||
|
* **settings:** failed build due it dormant import ([a7bb364](https://git.tuffraid.net/cowch/lst_v3/commits/a7bb364a2fd49d96b6195aca0cd58ba57c58f3a6))
|
||||||
|
|
||||||
|
|
||||||
|
### 🛠️ Code Refactor
|
||||||
|
|
||||||
|
* **servers:** changed activeity around and trying to make use of it ([514a44b](https://git.tuffraid.net/cowch/lst_v3/commits/514a44b6de3efe8dd8b308d98bdbc82e31ed8427))
|
||||||
|
* **users:** lots of auth stuff added to make it more easy to manage users ([047cc7c](https://git.tuffraid.net/cowch/lst_v3/commits/047cc7cdf036c39a89a0b87ab59dda8328efe0c0))
|
||||||
|
|
||||||
|
|
||||||
|
### 📈 Project changes
|
||||||
|
|
||||||
|
* **app:** added in chokidar to monitor folders ([8dc4d70](https://git.tuffraid.net/cowch/lst_v3/commits/8dc4d70e2827f0a40d2f54886fd757c8a2dc5ac4))
|
||||||
|
|
||||||
|
## [0.1.0-alpha.0](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.2-alpha.10...v0.1.0-alpha.0) (2026-05-14)
|
||||||
|
|
||||||
|
|
||||||
|
### ⚠ BREAKING CHANGES
|
||||||
|
|
||||||
|
* **app:** moved teh middleware to call the api hits to the main app and removed from
|
||||||
|
everywhere else
|
||||||
|
|
||||||
|
### 🌟 Enhancements
|
||||||
|
|
||||||
|
* **notification:** migrated sql cleanup ([3e66c39](https://git.tuffraid.net/cowch/lst_v3/commits/3e66c3920d65cee7a0a788f3910c1ddf09a07805))
|
||||||
|
* **scan users:** added in the place to add the new scanner users in ([ce9d8ea](https://git.tuffraid.net/cowch/lst_v3/commits/ce9d8eaaf5bcb8f53ea4bdc191347df8d589fdfa))
|
||||||
|
|
||||||
|
|
||||||
|
### 🐛 Bug fixes
|
||||||
|
|
||||||
|
* **anaylistics:** changes to the daily section so it populates correctly now ([f5bae2c](https://git.tuffraid.net/cowch/lst_v3/commits/f5bae2c0c24b85423c5c421164d94d58159ff70a))
|
||||||
|
* **anaylitics:** unique values were missing causing a weird crash ([13718fe](https://git.tuffraid.net/cowch/lst_v3/commits/13718fe70293c039bd1d9bf8cf395852e6ea6c21))
|
||||||
|
* **app:** emit.maxlistener issue ([7c31b43](https://git.tuffraid.net/cowch/lst_v3/commits/7c31b43a4a313237fa63c0c9bbc3690b74f63a6f)), closes [#18](https://git.tuffraid.net/cowch/lst_v3/issues/18)
|
||||||
|
* **app:** required auth was in wrong spot caused entire app to want you logged in ([d2a9e1d](https://git.tuffraid.net/cowch/lst_v3/commits/d2a9e1d1107ea05f13725e9528bc6ab1566c8efb))
|
||||||
|
* **notification subs:** made it so only acitve show ([2616acf](https://git.tuffraid.net/cowch/lst_v3/commits/2616acf106530f5c5ee04d1b79033795cf06b42d)), closes [#14](https://git.tuffraid.net/cowch/lst_v3/issues/14)
|
||||||
|
* **scanner:** changed to not crash on logging ([0de2579](https://git.tuffraid.net/cowch/lst_v3/commits/0de25799420f38a293ee9acc70eb36e3287145c4)), closes [#19](https://git.tuffraid.net/cowch/lst_v3/issues/19)
|
||||||
|
* **scanner:** fixes to be more clear that you need to scan a command to start ([0575879](https://git.tuffraid.net/cowch/lst_v3/commits/05758791be7a50e90b5da05d4977e618c311f654)), closes [#16](https://git.tuffraid.net/cowch/lst_v3/issues/16)
|
||||||
|
* **scanner:** logut out corrections ([85e96f5](https://git.tuffraid.net/cowch/lst_v3/commits/85e96f5ed13a81fd466c6bbff31c539244750838)), closes [#17](https://git.tuffraid.net/cowch/lst_v3/issues/17)
|
||||||
|
* **table:** skelly table causing hydration error ([1bbf5c2](https://git.tuffraid.net/cowch/lst_v3/commits/1bbf5c2a4955107a36ace05595886d19cc8e64f4))
|
||||||
|
|
||||||
|
|
||||||
|
### 📝 Chore
|
||||||
|
|
||||||
|
* **mobile:** removed console log that shouldnt be there ([9631736](https://git.tuffraid.net/cowch/lst_v3/commits/9631736e263ed00189f8118f686690cab25f09d3))
|
||||||
|
|
||||||
|
|
||||||
|
### 📚 Documentation
|
||||||
|
|
||||||
|
* **scanner:** added in instructions on how to update the scanner ([b0c7277](https://git.tuffraid.net/cowch/lst_v3/commits/b0c7277a6cdb5becec3a994ea1d5cc2d7b0326aa))
|
||||||
|
* **scanner:** added in westbend and dayton commands to scan for updates ([eb9d77c](https://git.tuffraid.net/cowch/lst_v3/commits/eb9d77c3d4767fd961759662ef44c3e09e00946b))
|
||||||
|
|
||||||
|
|
||||||
|
### 🛠️ Code Refactor
|
||||||
|
|
||||||
|
* **api:** changes to call a helper api to quit and redirect if needed ([c64392f](https://git.tuffraid.net/cowch/lst_v3/commits/c64392f45769108aa4134c7fd865f3d4bc664179))
|
||||||
|
* **app:** changed ways we get data so we can have better reasons why app no worky ([30ff7b7](https://git.tuffraid.net/cowch/lst_v3/commits/30ff7b71d9d159ced263a5330d70d53b97393157))
|
||||||
|
* **mobile:** scanner response ([a9c6925](https://git.tuffraid.net/cowch/lst_v3/commits/a9c69250bd3272ad682751e41b671c119cb678f1)), closes [#16](https://git.tuffraid.net/cowch/lst_v3/issues/16)
|
||||||
|
* **scanner:** logging - version of app ([d61be61](https://git.tuffraid.net/cowch/lst_v3/commits/d61be61f4433a2be2678d724f4724301931614c9))
|
||||||
|
* **scanner:** more scanner admin stuff ([eb950d2](https://git.tuffraid.net/cowch/lst_v3/commits/eb950d2c29f692b806d5cc4ab7014bd59a726a8d))
|
||||||
|
* **scanner:** removed 69 as an option lol ([e7af3d1](https://git.tuffraid.net/cowch/lst_v3/commits/e7af3d11824b42915cf6789f9c508a727511d678))
|
||||||
|
* **servers:** server name now links to the actual server:port ([ebf1060](https://git.tuffraid.net/cowch/lst_v3/commits/ebf1060475d37627b371bc6c79507cdde411600b))
|
||||||
|
* **users:** some user refactoring and configuring ([342a97f](https://git.tuffraid.net/cowch/lst_v3/commits/342a97f6b1054443b9126186d2c7872fbd8586da))
|
||||||
|
|
||||||
|
|
||||||
|
### 📈 Project changes
|
||||||
|
|
||||||
|
* **mobile:** added in ehs config to make it more easy for users to update the scanner app on the fly ([dc95e50](https://git.tuffraid.net/cowch/lst_v3/commits/dc95e50a8412b4fbc629fd44fcb5c77295583ca8))
|
||||||
|
* **notification:** removal of more console logs that shouldnt be here ([51026e3](https://git.tuffraid.net/cowch/lst_v3/commits/51026e3e2cce4d7f696d26aae305b3fd221f5bb1))
|
||||||
|
* **servives:** helpers moved around ([e9e73c8](https://git.tuffraid.net/cowch/lst_v3/commits/e9e73c829c2e5726650c0ac7ffa6a9055dbc982b))
|
||||||
|
* **updateserver:** changes to actually add the new env stuff ([bcb7773](https://git.tuffraid.net/cowch/lst_v3/commits/bcb7773007894ac2f85fe2a0b47faf14c7b474ad))
|
||||||
|
|
||||||
|
## [0.0.2-alpha.10](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.2-alpha.9...v0.0.2-alpha.10) (2026-05-08)
|
||||||
|
|
||||||
|
|
||||||
|
### 🌟 Enhancements
|
||||||
|
|
||||||
|
* **analytics:** added in backend anaylitics ([9edafc9](https://git.tuffraid.net/cowch/lst_v3/commits/9edafc9d2810f339d197c10dfc6a037b3352d81f))
|
||||||
|
* **api hits:** added in api hits for monitoring ([2721bb2](https://git.tuffraid.net/cowch/lst_v3/commits/2721bb2a3bf1f829591d26a0716f74c4f7fc0c79))
|
||||||
|
* **scanner:** added in lanechecks ([87803ee](https://git.tuffraid.net/cowch/lst_v3/commits/87803eed43069b73de3f66e6524bb45da9c46334))
|
||||||
|
|
||||||
|
|
||||||
|
### 🐛 Bug fixes
|
||||||
|
|
||||||
|
* **scan user:** typo ([d8869b1](https://git.tuffraid.net/cowch/lst_v3/commits/d8869b103b80e4208b3928a370a9524ef33d25cd))
|
||||||
|
* **schema:** typo in add_date ([7d7d991](https://git.tuffraid.net/cowch/lst_v3/commits/7d7d9911223905d6767b87d2471b6607a90f1ea7))
|
||||||
|
* **spelling:** corrected the spelling on the file ([0459cd7](https://git.tuffraid.net/cowch/lst_v3/commits/0459cd788aaad6ac54a67e23f798ce5e5a437394))
|
||||||
|
|
||||||
|
|
||||||
|
### 📝 Chore
|
||||||
|
|
||||||
|
* **file:** name changes.. spelled wrong ([5fa70da](https://git.tuffraid.net/cowch/lst_v3/commits/5fa70da90ca290ee45088e9c8eb06ba48a6677af))
|
||||||
|
* **server:** removed a console log that shouldnt be there ([1dba774](https://git.tuffraid.net/cowch/lst_v3/commits/1dba774abc54bf20850c3f26d49926e86d59712d))
|
||||||
|
|
||||||
|
|
||||||
|
### 🛠️ Code Refactor
|
||||||
|
|
||||||
|
* **analyitics:** finished analyitics as a base ([4424c74](https://git.tuffraid.net/cowch/lst_v3/commits/4424c742d24dc230b2bc1782e33535184c378cf0))
|
||||||
|
* **scan:** bump in build and style update ([505d7ce](https://git.tuffraid.net/cowch/lst_v3/commits/505d7cea5d2f52fc4a3ec1edff1878be703c4034))
|
||||||
|
* **scanner:** added toasts in to make it look better ([edb3668](https://git.tuffraid.net/cowch/lst_v3/commits/edb366854825f4c24ab5d77cf88759465d067f00))
|
||||||
|
|
||||||
|
|
||||||
|
### 📝 Testing Code
|
||||||
|
|
||||||
|
* **scanusers:** added in scan users as test ([1ff5e50](https://git.tuffraid.net/cowch/lst_v3/commits/1ff5e5032f9c8bf81f972dc99d6c86ba8d3936c6))
|
||||||
|
|
||||||
|
|
||||||
|
### 📈 Project changes
|
||||||
|
|
||||||
|
* **template:** bug in getting the template to work correctly ([e9b0101](https://git.tuffraid.net/cowch/lst_v3/commits/e9b01010954624aed738cd6e4b82fccbba195cc4))
|
||||||
|
* **templates:** added in templates for the repo to make it more easy to manage and add in new ideas ([ca885fb](https://git.tuffraid.net/cowch/lst_v3/commits/ca885fb01a3c8bc22694c2e05269c43fcd4de70e))
|
||||||
|
* **templates:** force useage ([6d8499b](https://git.tuffraid.net/cowch/lst_v3/commits/6d8499bfb85f7b9131b1ec7b31a17c4256d0f0cf))
|
||||||
|
|
||||||
|
## [0.0.2-alpha.9](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.2-alpha.8...v0.0.2-alpha.9) (2026-05-06)
|
||||||
|
|
||||||
|
|
||||||
|
### 🛠️ Code Refactor
|
||||||
|
|
||||||
|
* **mobile:** valildation of server after each scan ([4ca74de](https://git.tuffraid.net/cowch/lst_v3/commits/4ca74de2795cea7244e38697d16afe2822164ed6))
|
||||||
|
* **scanner:** added in running number ([a38e2e0](https://git.tuffraid.net/cowch/lst_v3/commits/a38e2e033977b725538e9a9046098d94194d549e))
|
||||||
|
* **scanner:** finished login stuff for current routes ([1241253](https://git.tuffraid.net/cowch/lst_v3/commits/12412536d10981013053c39d156c6c9cb0babd11))
|
||||||
|
|
||||||
|
|
||||||
|
### 📝 Testing Code
|
||||||
|
|
||||||
|
* **scanner:** lane check ([d99449d](https://git.tuffraid.net/cowch/lst_v3/commits/d99449ddc4e2777c1b0fe9189ba0a7c01fe1dd8f))
|
||||||
|
|
||||||
|
|
||||||
|
### 📈 Project Builds
|
||||||
|
|
||||||
|
* **builds:** changed to ip as its on the same server ([3552ca3](https://git.tuffraid.net/cowch/lst_v3/commits/3552ca31f9f7b3bcbe557a145e7eb154bfdae79c))
|
||||||
|
* **release:** bypass cloudflare upload limit ([b578f05](https://git.tuffraid.net/cowch/lst_v3/commits/b578f05d6482f9b6f30febeee6ab0b708a70f68b))
|
||||||
|
|
||||||
|
## [0.0.2-alpha.8](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.2-alpha.7...v0.0.2-alpha.8) (2026-05-06)
|
||||||
|
|
||||||
|
|
||||||
|
### 🌟 Enhancements
|
||||||
|
|
||||||
|
* **mobile:** auth added in ([ba30281](https://git.tuffraid.net/cowch/lst_v3/commits/ba30281e59040513a036fb7413e372457d04a7c8))
|
||||||
|
|
||||||
|
## [0.0.2-alpha.7](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.2-alpha.6...v0.0.2-alpha.7) (2026-05-06)
|
||||||
|
|
||||||
|
|
||||||
|
### 🌟 Enhancements
|
||||||
|
|
||||||
|
* **intial auth:** intial auth setup for the scanner ([cd13360](https://git.tuffraid.net/cowch/lst_v3/commits/cd13360cfb931daca50fd7b111e1c8f8ab09a909))
|
||||||
|
* **mobile:** new route for the ehs launcher ([649ae1e](https://git.tuffraid.net/cowch/lst_v3/commits/649ae1ee9f245a9b5d308ea8a636357bf72b1e34))
|
||||||
|
* **mobile:** shadcn like and tailwind added to make things look yummy ([7d2f048](https://git.tuffraid.net/cowch/lst_v3/commits/7d2f048932b77269568149de34351840b75486e2))
|
||||||
|
* **mobile:** update notifications and more error handling added ([30ffd84](https://git.tuffraid.net/cowch/lst_v3/commits/30ffd843c725da79ed035e2d9564f60a6babcda8))
|
||||||
|
* **scanner:** more work on the scanner and can now scan to prod no lst right now ([77b4533](https://git.tuffraid.net/cowch/lst_v3/commits/77b4533dea8314fd4fb81a597995cabd041fe188))
|
||||||
|
* **servers:** added iowa ebm ([8446dbc](https://git.tuffraid.net/cowch/lst_v3/commits/8446dbc955462235b9df35c501354761661e4f6a))
|
||||||
|
|
||||||
|
|
||||||
|
### 🐛 Bug fixes
|
||||||
|
|
||||||
|
* **mobile:** typo for version checking ([0b7318f](https://git.tuffraid.net/cowch/lst_v3/commits/0b7318f8566d15414edd3cd67c89fa5346058ab0))
|
||||||
|
|
||||||
|
|
||||||
|
### 🛠️ Code Refactor
|
||||||
|
|
||||||
|
* **docker compose:** changed to have the correct url that will be used as this is for auth ([4e0cf8c](https://git.tuffraid.net/cowch/lst_v3/commits/4e0cf8c54c4dfd68edba7e733518846a47c55064))
|
||||||
|
* **gp connection:** added in gp ip into env if not there use static name for dns ([36995e9](https://git.tuffraid.net/cowch/lst_v3/commits/36995e9fb42cfa1b72c096b8860866d70b86e70c))
|
||||||
|
* **mobile:** more look and feel work ([bb6155c](https://git.tuffraid.net/cowch/lst_v3/commits/bb6155c9692220542a52664848abf0b9eee91a43))
|
||||||
|
* **mobile:** moved the versioning lookup at at the mobile folder plus renamed ([bddc9ac](https://git.tuffraid.net/cowch/lst_v3/commits/bddc9aca0d2da2b2f53dec1250276d7a076a8601))
|
||||||
|
* **scanner:** format changes ([518c0a8](https://git.tuffraid.net/cowch/lst_v3/commits/518c0a8c19a4bff0b757bbd06ca5460d3565d8bd))
|
||||||
|
|
||||||
|
|
||||||
|
### 📈 Project Builds
|
||||||
|
|
||||||
|
* **scripts:** changing how the relase works so it purposly builds before it trys to release ([83a542d](https://git.tuffraid.net/cowch/lst_v3/commits/83a542d1b7beafe394949c001917f2b25056fac2))
|
||||||
|
|
||||||
|
## [0.0.2-alpha.6](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.2-alpha.1...v0.0.2-alpha.6) (2026-04-23)
|
||||||
|
|
||||||
|
## [0.0.2-alpha.1](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.2-alpha.0...v0.0.2-alpha.1) (2026-04-23)
|
||||||
|
|
||||||
|
## [0.0.2-alpha.0](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.1...v0.0.2-alpha.0) (2026-04-23)
|
||||||
|
|
||||||
|
## [0.0.1](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.1-alpha.5...v0.0.1) (2026-04-23)
|
||||||
|
|
||||||
|
|
||||||
|
### 🐛 Bug fixes
|
||||||
|
|
||||||
|
* **frontend:** lingering import crashed us ([781025d](https://git.tuffraid.net/cowch/lst_v3/commits/781025dca00e9dd4b2ad9b283be944ed91bbc1e5))
|
||||||
|
|
||||||
|
|
||||||
|
### 📝 Chore
|
||||||
|
|
||||||
|
* **doc remove:** removed a doc and put it in the real area for docs ([a593bb2](https://git.tuffraid.net/cowch/lst_v3/commits/a593bb2baafd0166a178b80cd76dd8862f240e11))
|
||||||
|
|
||||||
|
## [0.0.1-alpha.5](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.1-alpha.4...v0.0.1-alpha.5) (2026-04-23)
|
||||||
|
|
||||||
|
|
||||||
|
### 🌟 Enhancements
|
||||||
|
|
||||||
|
* **admin:** moved server build/update to full app ([cb00add](https://git.tuffraid.net/cowch/lst_v3/commits/cb00addee96b3ecccf2694f85cb7882cac9c7e3d))
|
||||||
|
* **lstmobile:** intial scanner setup kinda working ([3734d9d](https://git.tuffraid.net/cowch/lst_v3/commits/3734d9daac143ad8fb4404c59990bc4f546f365b))
|
||||||
|
* **oidc:** added in so we could use an oidc to login as well :D ([f7276ca](https://git.tuffraid.net/cowch/lst_v3/commits/f7276ca2d722e30da65bbead23dc9bd57df25aa7))
|
||||||
|
* **servers:** added marked tree in to the mix ([4d53af0](https://git.tuffraid.net/cowch/lst_v3/commits/4d53af033876d81e0d38c148c15cb0af6f3d5bf0))
|
||||||
|
|
||||||
|
|
||||||
|
### 🐛 Bug fixes
|
||||||
|
|
||||||
|
* **datamart:** fixes to correct how we handle activations of new features and legacy queries ([b832d7a](https://git.tuffraid.net/cowch/lst_v3/commits/b832d7aa1ecd063be1bbb7e969617fc7a6376ffa))
|
||||||
|
* **datamart:** if we do not have 2.0 warehousing activate we need to use legacy ([5b1c885](https://git.tuffraid.net/cowch/lst_v3/commits/5b1c88546ff9a42dc572450fe05ad68015edb627))
|
||||||
|
* **gp:** weird issue with db username and password ([d6328ab](https://git.tuffraid.net/cowch/lst_v3/commits/d6328ab764c3626aef99727b873003384951d299))
|
||||||
|
* **inventory:** changes to accruatly adjust the query and check the feature set ([32517d0](https://git.tuffraid.net/cowch/lst_v3/commits/32517d0c98c42a0f0f60135b4a9951c4090ccd58))
|
||||||
|
* **logistics:** historical issue where it was being really weird ([cfbc156](https://git.tuffraid.net/cowch/lst_v3/commits/cfbc1565172f7c2e27f0a1593fe8e99b00d91bb7))
|
||||||
|
* **logistics:** purchasing monitoring was going off every 5th min instead of every 5 min ([3639c1b](https://git.tuffraid.net/cowch/lst_v3/commits/3639c1b77c597a94816bfedd0892f0c8980c6403))
|
||||||
|
* **ocp:** fixes to make sure we always hav printer.data as an array or dont do anything ([fb3cd85](https://git.tuffraid.net/cowch/lst_v3/commits/fb3cd85b411315cac0abd22d050ee88929754833))
|
||||||
|
* **psi:** refactor psi queries ([a1eeade](https://git.tuffraid.net/cowch/lst_v3/commits/a1eeadeec438f7c5c6d31f190fee5c22f83dc6b0))
|
||||||
|
|
||||||
|
|
||||||
|
### 📝 Chore
|
||||||
|
|
||||||
|
* **clean:** removed bruno api a proper api doc will be added to lst later ([f716de1](https://git.tuffraid.net/cowch/lst_v3/commits/f716de1a58a4a4c02d9a0a375444ceecea4a018b))
|
||||||
|
* **scripts:** added in a helper to remove old stuff ([de5df2b](https://git.tuffraid.net/cowch/lst_v3/commits/de5df2b00b1c6fe7c53d6ea075b4cf7e0fb845f9))
|
||||||
|
|
||||||
|
|
||||||
|
### 🛠️ Code Refactor
|
||||||
|
|
||||||
|
* **scanner:** more basic work to get the scanner just running ([82f8369](https://git.tuffraid.net/cowch/lst_v3/commits/82f8369640b2b0ff63dd640dc0aa0609a42c7dda))
|
||||||
|
* **servers:** added mcd and stp1 ([88cef2a](https://git.tuffraid.net/cowch/lst_v3/commits/88cef2a56c390b692866658ce519e59ffeaf4c17))
|
||||||
|
* **server:** server updates can now only be done from a dev pc ([7962463](https://git.tuffraid.net/cowch/lst_v3/commits/7962463927c4c5d2e12db9a0dd536b0f29fc65b2))
|
||||||
|
* **sql:** changed sql connection to ip:port ([a6d53f0](https://git.tuffraid.net/cowch/lst_v3/commits/a6d53f0266f1edc3f3946cd1f07d893c8a98d9c7))
|
||||||
|
|
||||||
## [0.0.1-alpha.4](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.1-alpha.3...v0.0.1-alpha.4) (2026-04-15)
|
## [0.0.1-alpha.4](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.1-alpha.3...v0.0.1-alpha.4) (2026-04-15)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
38
backend/admin/admin.build.ts
Normal file
38
backend/admin/admin.build.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* To be able to run this we need to set our dev pc in the .env.
|
||||||
|
* if its empty just ignore it. this will just be the double catch
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Router } from "express";
|
||||||
|
import { build, building } from "../utils/build.utils.js";
|
||||||
|
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||||
|
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.post("/release", async (_, res) => {
|
||||||
|
if (!building) {
|
||||||
|
build();
|
||||||
|
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: true,
|
||||||
|
level: "info",
|
||||||
|
module: "admin",
|
||||||
|
subModule: "build",
|
||||||
|
message: `The build has been triggered see logs for progress of the current build.`,
|
||||||
|
data: [],
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "admin",
|
||||||
|
subModule: "build",
|
||||||
|
message: `There is a build in progress already please check the logs for on going progress.`,
|
||||||
|
data: [],
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
16
backend/admin/admin.routes.ts
Normal file
16
backend/admin/admin.routes.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import type { Express } from "express";
|
||||||
|
import { requireAuth } from "../middleware/auth.middleware.js";
|
||||||
|
|
||||||
|
import build from "./admin.build.js";
|
||||||
|
import update from "./admin.updateServer.js";
|
||||||
|
import users from "./admin.users.js";
|
||||||
|
|
||||||
|
export const setupAdminRoutes = (baseUrl: string, app: Express) => {
|
||||||
|
//stats will be like this as we dont need to change this
|
||||||
|
|
||||||
|
app.use(`${baseUrl}/api/admin/build`, requireAuth, build);
|
||||||
|
app.use(`${baseUrl}/api/admin/build`, requireAuth, update);
|
||||||
|
app.use(`${baseUrl}/api/admin/user`, requireAuth, users);
|
||||||
|
|
||||||
|
// all other system should be under /api/system/*
|
||||||
|
};
|
||||||
86
backend/admin/admin.updateServer.ts
Normal file
86
backend/admin/admin.updateServer.ts
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
/**
|
||||||
|
* To be able to run this we need to set our dev pc in the .env.
|
||||||
|
* if its empty just ignore it. this will just be the double catch
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Router } from "express";
|
||||||
|
import z from "zod";
|
||||||
|
import { building } from "../utils/build.utils.js";
|
||||||
|
import { runUpdate, updating } from "../utils/deployApp.js";
|
||||||
|
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||||
|
|
||||||
|
const updateServer = z.object({
|
||||||
|
server: z.string(),
|
||||||
|
destination: z.string(),
|
||||||
|
token: z.string().min(5, "Plant tokens should be at least 5 characters long"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
type Update = {
|
||||||
|
success: boolean;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
router.post("/updateServer", async (req, res) => {
|
||||||
|
try {
|
||||||
|
const validated = updateServer.parse(req.body);
|
||||||
|
|
||||||
|
if (!updating && !building) {
|
||||||
|
const update = (await runUpdate({
|
||||||
|
server: validated.server,
|
||||||
|
destination: validated.destination,
|
||||||
|
token: validated.token,
|
||||||
|
})) as Update;
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: update.success,
|
||||||
|
level: update.success ? "info" : "error",
|
||||||
|
module: "admin",
|
||||||
|
subModule: "update",
|
||||||
|
message: update.message,
|
||||||
|
data: [],
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "admin",
|
||||||
|
subModule: "update",
|
||||||
|
message: `${validated.server}: ${validated.token} is already being updated, or is currently building the app.`,
|
||||||
|
data: [],
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof z.ZodError) {
|
||||||
|
const flattened = z.flattenError(err);
|
||||||
|
// return res.status(400).json({
|
||||||
|
// error: "Validation failed",
|
||||||
|
// details: flattened,
|
||||||
|
// });
|
||||||
|
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: false,
|
||||||
|
level: "error", //connect.success ? "info" : "error",
|
||||||
|
module: "routes",
|
||||||
|
subModule: "auth",
|
||||||
|
message: "Validation failed",
|
||||||
|
data: [flattened.fieldErrors],
|
||||||
|
status: 400, //connect.success ? 200 : 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: false,
|
||||||
|
level: "error", //connect.success ? "info" : "error",
|
||||||
|
module: "routes",
|
||||||
|
subModule: "auth",
|
||||||
|
message: "Internal Server Error creating user",
|
||||||
|
data: [err],
|
||||||
|
status: 400, //connect.success ? 200 : 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
46
backend/admin/admin.users.ts
Normal file
46
backend/admin/admin.users.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* To be able to run this we need to set our dev pc in the .env.
|
||||||
|
* if its empty just ignore it. this will just be the double catch
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { fromNodeHeaders } from "better-auth/node";
|
||||||
|
import { Router } from "express";
|
||||||
|
import { auth } from "../utils/auth.utils.js";
|
||||||
|
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||||
|
|
||||||
|
const r = Router();
|
||||||
|
|
||||||
|
r.get("/", async (req, res) => {
|
||||||
|
const { users } = await auth.api.listUsers({
|
||||||
|
query: {
|
||||||
|
limit: 50,
|
||||||
|
},
|
||||||
|
headers: fromNodeHeaders(req.headers),
|
||||||
|
});
|
||||||
|
|
||||||
|
// console.log(error);
|
||||||
|
|
||||||
|
// if (error) {
|
||||||
|
// return apiReturn(res, {
|
||||||
|
// success: false,
|
||||||
|
// level: "info",
|
||||||
|
// module: "admin",
|
||||||
|
// subModule: "user",
|
||||||
|
// message: `There was an error getting the users.`,
|
||||||
|
// data: users,
|
||||||
|
// status: 400,
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: true,
|
||||||
|
level: "info",
|
||||||
|
module: "admin",
|
||||||
|
subModule: "users",
|
||||||
|
message: `Current active users.`,
|
||||||
|
data: users,
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default r;
|
||||||
@@ -3,7 +3,9 @@ import { fileURLToPath } from "node:url";
|
|||||||
import { toNodeHandler } from "better-auth/node";
|
import { toNodeHandler } from "better-auth/node";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import morgan from "morgan";
|
import morgan from "morgan";
|
||||||
|
import { umamiConfig } from "./configs/umami.config.js";
|
||||||
import { createLogger } from "./logger/logger.controller.js";
|
import { createLogger } from "./logger/logger.controller.js";
|
||||||
|
import { routeHitMiddleware } from "./middleware/routeHit.middleware.js";
|
||||||
import { setupRoutes } from "./routeHandler.routes.js";
|
import { setupRoutes } from "./routeHandler.routes.js";
|
||||||
import { auth } from "./utils/auth.utils.js";
|
import { auth } from "./utils/auth.utils.js";
|
||||||
import { lstCors } from "./utils/cors.utils.js";
|
import { lstCors } from "./utils/cors.utils.js";
|
||||||
@@ -29,8 +31,26 @@ const createApp = async () => {
|
|||||||
app.use(morgan("dev"));
|
app.use(morgan("dev"));
|
||||||
app.set("trust proxy", true);
|
app.set("trust proxy", true);
|
||||||
app.use(lstCors());
|
app.use(lstCors());
|
||||||
|
app.use(routeHitMiddleware);
|
||||||
app.all(`${baseUrl}/api/auth/*splat`, toNodeHandler(auth));
|
app.all(`${baseUrl}/api/auth/*splat`, toNodeHandler(auth));
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
|
||||||
|
app.get(`${baseUrl}/api/lst-config.js`, (_, res) => {
|
||||||
|
res.type("application/javascript");
|
||||||
|
res.setHeader("Cache-Control", "no-store");
|
||||||
|
|
||||||
|
res.send(`
|
||||||
|
window.LST_CONFIG = {
|
||||||
|
appName: ${JSON.stringify(umamiConfig.appName ?? "LST")},
|
||||||
|
site: ${JSON.stringify(umamiConfig.site ?? "unknown")},
|
||||||
|
server: ${JSON.stringify(umamiConfig.server ?? "unknown")},
|
||||||
|
appVersion: ${JSON.stringify(umamiConfig.appVersion ?? "dev")},
|
||||||
|
umamiHost: ${JSON.stringify(umamiConfig.umamiHost ?? "")},
|
||||||
|
umamiWebsiteId: ${JSON.stringify(umamiConfig.umamiWebsiteId ?? "")}
|
||||||
|
};
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
setupRoutes(baseUrl, app);
|
setupRoutes(baseUrl, app);
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import type { Express } from "express";
|
import type { Express } from "express";
|
||||||
|
|
||||||
import login from "./login.route.js";
|
import login from "./login.route.js";
|
||||||
import register from "./register.route.js";
|
import register from "./register.route.js";
|
||||||
|
|
||||||
export const setupAuthRoutes = (baseUrl: string, app: Express) => {
|
export const setupAuthRoutes = (baseUrl: string, app: Express) => {
|
||||||
//setup all the routes
|
//setup all the routes
|
||||||
|
|
||||||
app.use(`${baseUrl}/api/authentication/login`, login);
|
app.use(`${baseUrl}/api/authentication/login`, login);
|
||||||
app.use(`${baseUrl}/api/authentication/register`, register);
|
app.use(`${baseUrl}/api/authentication/register`, register);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
import type sql from "mssql";
|
import type sql from "mssql";
|
||||||
|
|
||||||
|
// TODO : Remove this later and get it onto the env
|
||||||
const username = "gpviewer";
|
const username = "gpviewer";
|
||||||
const password = "gp$$ViewOnly!";
|
const password = "gp$$ViewOnly!";
|
||||||
|
|
||||||
|
const port = process.env.SQL_PORT
|
||||||
|
? Number.parseInt(process.env.SQL_PORT, 10)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
export const gpSqlConfig: sql.config = {
|
export const gpSqlConfig: sql.config = {
|
||||||
server: `USMCD1VMS011`,
|
server: `${process.env.GP_SERVER ?? "USMCD1VMS011"}`,
|
||||||
|
port: port,
|
||||||
database: `ALPLA`,
|
database: `ALPLA`,
|
||||||
user: username,
|
user: username,
|
||||||
password: password,
|
password: password,
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
import type sql from "mssql";
|
import type sql from "mssql";
|
||||||
|
|
||||||
|
const port = process.env.SQL_PORT
|
||||||
|
? Number.parseInt(process.env.SQL_PORT, 10)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
export const prodSqlConfig: sql.config = {
|
export const prodSqlConfig: sql.config = {
|
||||||
server: `${process.env.PROD_SERVER}`,
|
server: `${process.env.PROD_SERVER}`,
|
||||||
database: `AlplaPROD_${process.env.PROD_PLANT_TOKEN}_cus`,
|
database: `AlplaPROD_${process.env.PROD_PLANT_TOKEN}_cus`,
|
||||||
|
port: port,
|
||||||
user: process.env.PROD_USER,
|
user: process.env.PROD_USER,
|
||||||
password: process.env.PROD_PASSWORD,
|
password: process.env.PROD_PASSWORD,
|
||||||
options: {
|
options: {
|
||||||
|
|||||||
21
backend/configs/umami.config.ts
Normal file
21
backend/configs/umami.config.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
export type UmamiRuntimeConfig = {
|
||||||
|
appName: string;
|
||||||
|
site: string;
|
||||||
|
server: string;
|
||||||
|
appVersion: string;
|
||||||
|
umamiHost: string;
|
||||||
|
umamiWebsiteId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const umamiConfig: UmamiRuntimeConfig = {
|
||||||
|
appName: process.env.APP_NAME ?? "LST",
|
||||||
|
site: process.env.URL ?? "unknown",
|
||||||
|
server: process.env.PROD_PLANT_TOKEN ?? "unknown", // could also be server name based on our setup.
|
||||||
|
appVersion: process.env.NODE_ENV ?? "dev",
|
||||||
|
umamiHost: process.env.UMAMI_HOST ?? "",
|
||||||
|
umamiWebsiteId: process.env.UMAMI_WEBSITE_ID ?? "",
|
||||||
|
};
|
||||||
|
|
||||||
|
export function isUmamiEnabled() {
|
||||||
|
return Boolean(umamiConfig.umamiHost && umamiConfig.umamiWebsiteId);
|
||||||
|
}
|
||||||
@@ -95,7 +95,39 @@ export const runDatamartQuery = async (data: Data) => {
|
|||||||
notify: false,
|
notify: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const sqlQuery = sqlQuerySelector(`datamart.${data.name}`) as SqlQuery;
|
|
||||||
|
const featureQ = sqlQuerySelector(`featureCheck`) as SqlQuery;
|
||||||
|
|
||||||
|
const { data: fd, error: fe } = await tryCatch(
|
||||||
|
prodQuery(featureQ.query, `Running feature check`),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (fe) {
|
||||||
|
return returnFunc({
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "datamart",
|
||||||
|
subModule: "query",
|
||||||
|
message: `feature check failed`,
|
||||||
|
data: fe as any,
|
||||||
|
notify: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// for queries that will need to be ran on legacy until we get the plant updated need to go in here
|
||||||
|
const doubleQueries = ["inventory"];
|
||||||
|
let queryFile = "";
|
||||||
|
|
||||||
|
if (doubleQueries.includes(data.name)) {
|
||||||
|
queryFile = `datamart.${
|
||||||
|
fd.data[0].activated > 0 ? data.name : `legacy.${data.name}`
|
||||||
|
}`;
|
||||||
|
} else {
|
||||||
|
queryFile = `datamart.${data.name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sqlQuery = sqlQuerySelector(queryFile) as SqlQuery;
|
||||||
|
// checking if warehousing is as it will start to effect a lot of queries for plants that are not on 2.
|
||||||
|
|
||||||
const getDataMartInfo = datamartData.filter((x) => x.endpoint === data.name);
|
const getDataMartInfo = datamartData.filter((x) => x.endpoint === data.name);
|
||||||
|
|
||||||
@@ -141,7 +173,19 @@ export const runDatamartQuery = async (data: Data) => {
|
|||||||
case "deliveryByDateRange":
|
case "deliveryByDateRange":
|
||||||
datamartQuery = datamartQuery
|
datamartQuery = datamartQuery
|
||||||
.replace("[startDate]", `${data.options.startDate}`)
|
.replace("[startDate]", `${data.options.startDate}`)
|
||||||
.replace("[endDate]", `${data.options.endDate}`);
|
.replace("[endDate]", `${data.options.endDate}`)
|
||||||
|
.replace(
|
||||||
|
"--and r.ArticleHumanReadableId in ([articles]) ",
|
||||||
|
data.options.articles
|
||||||
|
? `and r.ArticleHumanReadableId in (${data.options.articles})`
|
||||||
|
: "--and r.ArticleHumanReadableId in ([articles]) ",
|
||||||
|
)
|
||||||
|
.replace(
|
||||||
|
"and DeliveredQuantity > 0",
|
||||||
|
data.options.all
|
||||||
|
? "--and DeliveredQuantity > 0"
|
||||||
|
: "and DeliveredQuantity > 0",
|
||||||
|
);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case "customerInventory":
|
case "customerInventory":
|
||||||
@@ -170,23 +214,15 @@ export const runDatamartQuery = async (data: Data) => {
|
|||||||
"--,l.MachineLocation,l.MachineName,l.ProductionLotRunningNumber as lot",
|
"--,l.MachineLocation,l.MachineName,l.ProductionLotRunningNumber as lot",
|
||||||
`${data.options.lots ? `,l.MachineLocation,l.MachineName,l.ProductionLotRunningNumber as lot` : `--,l.MachineLocation,l.MachineName,l.ProductionLotRunningNumber as lot`}`,
|
`${data.options.lots ? `,l.MachineLocation,l.MachineName,l.ProductionLotRunningNumber as lot` : `--,l.MachineLocation,l.MachineName,l.ProductionLotRunningNumber as lot`}`,
|
||||||
)
|
)
|
||||||
|
.replaceAll(
|
||||||
|
"--,l.MachineLocation,l.MachineName,l.ProductionLotRunningNumber",
|
||||||
|
`${data.options.lots ? `,l.MachineLocation,l.MachineName,l.ProductionLotRunningNumber` : `--,l.MachineLocation,l.MachineName,l.ProductionLotRunningNumber`}`,
|
||||||
|
)
|
||||||
.replaceAll(
|
.replaceAll(
|
||||||
"--,l.WarehouseDescription,l.LaneDescription",
|
"--,l.WarehouseDescription,l.LaneDescription",
|
||||||
`${data.options.locations ? `,l.WarehouseDescription,l.LaneDescription` : `--,l.WarehouseDescription,l.LaneDescription`}`,
|
`${data.options.locations ? `,l.WarehouseDescription,l.LaneDescription` : `--,l.WarehouseDescription,l.LaneDescription`}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// adding in a test for historical check.
|
|
||||||
if (data.options.historical) {
|
|
||||||
datamartQuery = datamartQuery
|
|
||||||
.replace(
|
|
||||||
"--,l.ProductionLotRunningNumber as lot,l.warehousehumanreadableid as warehouseId,l.WarehouseDescription as warehouseDescription,l.lanehumanreadableid as locationId,l.lanedescription as laneDescription",
|
|
||||||
",l.ProductionLotRunningNumber as lot,l.warehousehumanreadableid as warehouseId,l.WarehouseDescription as warehouseDescription,l.lanehumanreadableid as locationId,l.lanedescription as laneDescription",
|
|
||||||
)
|
|
||||||
.replace(
|
|
||||||
"--,l.ProductionLotRunningNumber,l.warehousehumanreadableid,l.WarehouseDescription,l.lanehumanreadableid,l.lanedescription",
|
|
||||||
",l.ProductionLotRunningNumber,l.warehousehumanreadableid,l.WarehouseDescription,l.lanehumanreadableid,l.lanedescription",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case "fakeEDIUpdate":
|
case "fakeEDIUpdate":
|
||||||
datamartQuery = datamartQuery.replace(
|
datamartQuery = datamartQuery.replace(
|
||||||
@@ -213,16 +249,13 @@ export const runDatamartQuery = async (data: Data) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "psiDeliveryData":
|
case "psiDeliveryData":
|
||||||
datamartQuery = datamartQuery
|
datamartQuery = datamartQuery
|
||||||
.replace("[startDate]", `${data.options.startDate}`)
|
.replace("[startDate]", `${data.options.startDate}`)
|
||||||
.replace("[endDate]", `${data.options.endDate}`)
|
.replace("[endDate]", `${data.options.endDate}`)
|
||||||
.replace(
|
.replace(
|
||||||
"and IdArtikelVarianten in ([articles])",
|
"[articles]",
|
||||||
data.options.articles
|
data.options.articles ? `${data.options.articles}` : "[articles]",
|
||||||
? `and IdArtikelVarianten in (${data.options.articles})`
|
|
||||||
: "--and IdArtikelVarianten in ([articles])",
|
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "productionData":
|
case "productionData":
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { drizzle } from "drizzle-orm/postgres-js";
|
import { drizzle } from "drizzle-orm/postgres-js";
|
||||||
import postgres from "postgres";
|
import postgres from "postgres";
|
||||||
|
import * as opendockAVCheck from "./schema/opendock_articleSetup.js";
|
||||||
|
import * as scanUserSchema from "./schema/scanUsers.js";
|
||||||
|
import * as settingsSchema from "./schema/settings.schema.js";
|
||||||
|
|
||||||
const dbURL = `postgres://${process.env.DATABASE_USER}:${process.env.DATABASE_PASSWORD}@${process.env.DATABASE_HOST}:${process.env.DATABASE_PORT}/${process.env.DATABASE_DB}`;
|
const dbURL = `postgres://${process.env.DATABASE_USER}:${process.env.DATABASE_PASSWORD}@${process.env.DATABASE_HOST}:${process.env.DATABASE_PORT}/${process.env.DATABASE_DB}`;
|
||||||
|
|
||||||
@@ -13,4 +16,12 @@ const queryClient = postgres(dbURL, {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const db = drizzle({ client: queryClient });
|
//export const db = drizzle({ client: queryClient });
|
||||||
|
|
||||||
|
export const db = drizzle(queryClient, {
|
||||||
|
schema: {
|
||||||
|
...scanUserSchema,
|
||||||
|
...settingsSchema,
|
||||||
|
...opendockAVCheck,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
21
backend/db/schema/analytics.schema.ts
Normal file
21
backend/db/schema/analytics.schema.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { integer, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
|
||||||
|
|
||||||
|
export const analytics = pgTable("analytics", {
|
||||||
|
id: uuid("id").defaultRandom().primaryKey(),
|
||||||
|
|
||||||
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||||
|
|
||||||
|
method: text("method").notNull(),
|
||||||
|
routePattern: text("route_pattern").notNull(),
|
||||||
|
actualPath: text("actual_path").notNull(),
|
||||||
|
|
||||||
|
statusCode: integer("status_code").notNull(),
|
||||||
|
durationMs: integer("duration_ms").notNull(),
|
||||||
|
|
||||||
|
module: text("module"),
|
||||||
|
userId: text("user_id"),
|
||||||
|
userEmail: text("user_email"),
|
||||||
|
|
||||||
|
ipAddress: text("ip_address"),
|
||||||
|
userAgent: text("user_agent"),
|
||||||
|
});
|
||||||
10
backend/db/schema/buildHistory.schema.ts
Normal file
10
backend/db/schema/buildHistory.schema.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { integer, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
|
||||||
|
|
||||||
|
export const deploymentHistory = pgTable("deployment_history", {
|
||||||
|
id: uuid("id").defaultRandom().primaryKey(),
|
||||||
|
serverId: uuid("server_id"),
|
||||||
|
buildNumber: integer("build_number").notNull(),
|
||||||
|
status: text("status").notNull(), // started, success, failed
|
||||||
|
message: text("message"),
|
||||||
|
createdAt: timestamp("created_at").defaultNow(),
|
||||||
|
});
|
||||||
45
backend/db/schema/dailyAnalytics.schema.ts
Normal file
45
backend/db/schema/dailyAnalytics.schema.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import {
|
||||||
|
date,
|
||||||
|
integer,
|
||||||
|
pgTable,
|
||||||
|
text,
|
||||||
|
timestamp,
|
||||||
|
unique,
|
||||||
|
uuid,
|
||||||
|
} from "drizzle-orm/pg-core";
|
||||||
|
|
||||||
|
export const analyticsDaily = pgTable(
|
||||||
|
"analytics_daily",
|
||||||
|
{
|
||||||
|
id: uuid("id").defaultRandom().primaryKey(),
|
||||||
|
|
||||||
|
businessDate: date("business_date", { mode: "string" }).notNull(),
|
||||||
|
|
||||||
|
method: text("method").notNull(),
|
||||||
|
routePattern: text("route_pattern").notNull(),
|
||||||
|
module: text("module").notNull(),
|
||||||
|
|
||||||
|
totalHits: integer("total_hits").notNull(),
|
||||||
|
uniqueUsers: integer("unique_users").notNull(),
|
||||||
|
|
||||||
|
successCount: integer("success_count").notNull(),
|
||||||
|
errorCount: integer("error_count").notNull(),
|
||||||
|
|
||||||
|
avgDurationMs: integer("avg_duration_ms").notNull(),
|
||||||
|
maxDurationMs: integer("max_duration_ms").notNull(),
|
||||||
|
|
||||||
|
firstHitAt: timestamp("first_hit_at").notNull(),
|
||||||
|
lastHitAt: timestamp("last_hit_at").notNull(),
|
||||||
|
|
||||||
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||||
|
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
||||||
|
},
|
||||||
|
(table) => [
|
||||||
|
unique("analytics_daily_business_route_unique").on(
|
||||||
|
table.businessDate,
|
||||||
|
table.method,
|
||||||
|
table.routePattern,
|
||||||
|
table.module,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
@@ -14,14 +14,13 @@ export const opendockApt = pgTable(
|
|||||||
"opendock_apt",
|
"opendock_apt",
|
||||||
{
|
{
|
||||||
id: uuid("id").defaultRandom().primaryKey(),
|
id: uuid("id").defaultRandom().primaryKey(),
|
||||||
release: integer("release").notNull().unique(),
|
release: integer("release").notNull().unique("opendock_apt_release_unique"),
|
||||||
openDockAptId: text("open_dock_apt_id").notNull(),
|
openDockAptId: text("open_dock_apt_id").notNull(),
|
||||||
appointment: jsonb("appointment").notNull().default([]),
|
appointment: jsonb("appointment").notNull().default([]),
|
||||||
upd_date: timestamp("upd_date").notNull().defaultNow(),
|
upd_date: timestamp("upd_date").notNull().defaultNow(),
|
||||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||||
},
|
},
|
||||||
(table) => ({
|
(table) => ({
|
||||||
releaseIdx: index("opendock_apt_release_idx").on(table.release),
|
|
||||||
openDockAptIdIdx: index("opendock_apt_opendock_id_idx").on(
|
openDockAptIdIdx: index("opendock_apt_opendock_id_idx").on(
|
||||||
table.openDockAptId,
|
table.openDockAptId,
|
||||||
),
|
),
|
||||||
46
backend/db/schema/opendock_articleSetup.ts
Normal file
46
backend/db/schema/opendock_articleSetup.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import {
|
||||||
|
integer,
|
||||||
|
pgEnum,
|
||||||
|
pgTable,
|
||||||
|
text,
|
||||||
|
timestamp,
|
||||||
|
unique,
|
||||||
|
uuid,
|
||||||
|
} from "drizzle-orm/pg-core";
|
||||||
|
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
|
||||||
|
import type { z } from "zod";
|
||||||
|
|
||||||
|
export const loadTypeEnum = pgEnum("load_type", ["drop", "live"]);
|
||||||
|
|
||||||
|
export const opendockArticleSetup = pgTable(
|
||||||
|
"opendock_article_setup",
|
||||||
|
{
|
||||||
|
id: uuid("id").defaultRandom().primaryKey(),
|
||||||
|
av: integer("av").notNull(),
|
||||||
|
description: text("description").notNull(),
|
||||||
|
customer: text("customer").notNull(), // customer should be a concat of the ID - Desc
|
||||||
|
customerDescription: text("customer_description").notNull(),
|
||||||
|
loadType: loadTypeEnum("load_type").notNull().default("drop"),
|
||||||
|
dock: text("dock").notNull(),
|
||||||
|
upd_date: timestamp("upd_date").notNull().defaultNow(),
|
||||||
|
upd_user: text("upd_user").notNull().default("lst-system"),
|
||||||
|
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||||
|
add_user: text("add_user").notNull().default("lst-system"),
|
||||||
|
},
|
||||||
|
(table) => ({
|
||||||
|
uniqueAvCustomer: unique("uq_opendock_article_setup_av_customer").on(
|
||||||
|
table.av,
|
||||||
|
table.customer,
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const opendockArticleSetupSchema =
|
||||||
|
createSelectSchema(opendockArticleSetup);
|
||||||
|
export const newOpendockArticleSetupSchema =
|
||||||
|
createInsertSchema(opendockArticleSetup);
|
||||||
|
|
||||||
|
export type OpendockArticleSetup = z.infer<typeof opendockArticleSetupSchema>;
|
||||||
|
export type NewOpendockArticleSetup = z.infer<
|
||||||
|
typeof newOpendockArticleSetupSchema
|
||||||
|
>;
|
||||||
21
backend/db/schema/opendock_docks.ts
Normal file
21
backend/db/schema/opendock_docks.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
|
||||||
|
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
|
||||||
|
import type { z } from "zod";
|
||||||
|
|
||||||
|
export const opendockDockSetup = pgTable("opendock_dock_setup", {
|
||||||
|
id: uuid("id").defaultRandom().primaryKey(),
|
||||||
|
name: text("name").notNull(),
|
||||||
|
dockID: text("dock_id").notNull(),
|
||||||
|
upd_date: timestamp("upd_date").notNull().defaultNow(),
|
||||||
|
upd_user: text("upd_user").notNull().default("lst-system"),
|
||||||
|
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||||
|
add_user: text("add_user").notNull().default("lst-system"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const opendockDockSetupSchema = createSelectSchema(opendockDockSetup);
|
||||||
|
export const newOpendockDockSetupSchema = createInsertSchema(opendockDockSetup);
|
||||||
|
|
||||||
|
export type OpendockArticleSetup = z.infer<typeof opendockDockSetupSchema>;
|
||||||
|
export type NewOpendockArticleSetup = z.infer<
|
||||||
|
typeof newOpendockDockSetupSchema
|
||||||
|
>;
|
||||||
48
backend/db/schema/scanUsers.ts
Normal file
48
backend/db/schema/scanUsers.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import {
|
||||||
|
boolean,
|
||||||
|
jsonb,
|
||||||
|
pgEnum,
|
||||||
|
pgTable,
|
||||||
|
text,
|
||||||
|
timestamp,
|
||||||
|
unique,
|
||||||
|
uuid,
|
||||||
|
} from "drizzle-orm/pg-core";
|
||||||
|
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
|
||||||
|
import type z from "zod";
|
||||||
|
|
||||||
|
export const mobileRoleEnum = pgEnum("mobile_role", [
|
||||||
|
"user",
|
||||||
|
"lead",
|
||||||
|
"manager",
|
||||||
|
"admin",
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const scanUser = pgTable(
|
||||||
|
"scan_users",
|
||||||
|
{
|
||||||
|
id: uuid("id").defaultRandom().primaryKey(),
|
||||||
|
name: text("name").notNull(), // the user that will be using the scanner
|
||||||
|
scannerId: text("scanner_id").unique().notNull(),
|
||||||
|
pinNumber: text("pin_number").unique().notNull(),
|
||||||
|
pinHash: text("pin_hash").notNull(),
|
||||||
|
excludedCommand: jsonb("excluded_commands").default([]),
|
||||||
|
role: mobileRoleEnum("role").notNull().default("user"),
|
||||||
|
active: boolean("active").default(true),
|
||||||
|
lastScan: timestamp("last_scan").defaultNow(),
|
||||||
|
add_Date: timestamp("add_Date").defaultNow(),
|
||||||
|
upd_date: timestamp("upd_date").defaultNow(),
|
||||||
|
},
|
||||||
|
(table) => ({
|
||||||
|
userNotificationUnique: unique("scan_user_unique").on(
|
||||||
|
table.scannerId,
|
||||||
|
table.pinNumber,
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const scanUserSchema = createSelectSchema(scanUser);
|
||||||
|
export const newsSanUserSchema = createInsertSchema(scanUser);
|
||||||
|
|
||||||
|
export type ScanUser = z.infer<typeof scanUserSchema>;
|
||||||
|
export type NewScanUser = z.infer<typeof newsSanUserSchema>;
|
||||||
23
backend/db/schema/scanlog.schema.ts
Normal file
23
backend/db/schema/scanlog.schema.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { jsonb, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
|
||||||
|
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
|
||||||
|
import type z from "zod";
|
||||||
|
|
||||||
|
export const scanLog = pgTable("scan_log", {
|
||||||
|
id: uuid("id").defaultRandom().primaryKey(),
|
||||||
|
user: text("user"),
|
||||||
|
scannerId: text("scanner_id"),
|
||||||
|
message: text("message").notNull(),
|
||||||
|
prompt: text("prompt"),
|
||||||
|
commandDescription: text("command_description"),
|
||||||
|
runningNumber: text("running_number").default("0"),
|
||||||
|
status: text("status"),
|
||||||
|
scannerVersion: text("scanner_version").default("0"),
|
||||||
|
lines: jsonb("lines").default([]),
|
||||||
|
add_Date: timestamp("add_date").defaultNow(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const scanLogSchema = createSelectSchema(scanLog);
|
||||||
|
export const newScanLogSchema = createInsertSchema(scanLog);
|
||||||
|
|
||||||
|
export type Printer = z.infer<typeof scanLogSchema>;
|
||||||
|
export type NewPrinter = z.infer<typeof newScanLogSchema>;
|
||||||
40
backend/db/schema/serverData.schema.ts
Normal file
40
backend/db/schema/serverData.schema.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import {
|
||||||
|
boolean,
|
||||||
|
integer,
|
||||||
|
pgTable,
|
||||||
|
text,
|
||||||
|
timestamp,
|
||||||
|
uuid,
|
||||||
|
} from "drizzle-orm/pg-core";
|
||||||
|
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
|
||||||
|
import type z from "zod";
|
||||||
|
|
||||||
|
export const serverData = pgTable(
|
||||||
|
"server_data",
|
||||||
|
{
|
||||||
|
server_id: uuid("id").defaultRandom().primaryKey(),
|
||||||
|
name: text("name").notNull(),
|
||||||
|
server: text("server"),
|
||||||
|
plantToken: text("plant_token").notNull().unique(),
|
||||||
|
idAddress: text("id_address"),
|
||||||
|
greatPlainsPlantCode: text("great_plains_plant_code"),
|
||||||
|
contactEmail: text("contact_email"),
|
||||||
|
contactPhone: text("contact_phone"),
|
||||||
|
active: boolean("active").default(true),
|
||||||
|
serverLoc: text("server_loc"),
|
||||||
|
lastUpdated: timestamp("last_updated").defaultNow(),
|
||||||
|
buildNumber: integer("build_number"),
|
||||||
|
isUpgrading: boolean("is_upgrading").default(false),
|
||||||
|
},
|
||||||
|
|
||||||
|
// (table) => [
|
||||||
|
// // uniqueIndex('emailUniqueIndex').on(sql`lower(${table.email})`),
|
||||||
|
// uniqueIndex("plant_token").on(table.plantToken),
|
||||||
|
// ],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const serverDataSchema = createSelectSchema(serverData);
|
||||||
|
export const newServerDataSchema = createInsertSchema(serverData);
|
||||||
|
|
||||||
|
export type ServerDataSchema = z.infer<typeof serverDataSchema>;
|
||||||
|
export type NewServerData = z.infer<typeof newServerDataSchema>;
|
||||||
@@ -38,7 +38,7 @@ export const settings = pgTable(
|
|||||||
},
|
},
|
||||||
(table) => [
|
(table) => [
|
||||||
// uniqueIndex('emailUniqueIndex').on(sql`lower(${table.email})`),
|
// uniqueIndex('emailUniqueIndex').on(sql`lower(${table.email})`),
|
||||||
uniqueIndex("name").on(table.name),
|
uniqueIndex("settings_name_unique").on(table.name),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,27 @@
|
|||||||
import type { InferSelectModel } from "drizzle-orm";
|
import {
|
||||||
import { integer, pgTable, text, timestamp } from "drizzle-orm/pg-core";
|
boolean,
|
||||||
|
integer,
|
||||||
|
jsonb,
|
||||||
|
pgTable,
|
||||||
|
text,
|
||||||
|
timestamp,
|
||||||
|
} from "drizzle-orm/pg-core";
|
||||||
|
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
|
||||||
|
import type z from "zod";
|
||||||
|
|
||||||
export const serverStats = pgTable("stats", {
|
export const appStats = pgTable("app_stats", {
|
||||||
id: text("id").primaryKey().default("serverStats"),
|
id: text("id").primaryKey().default("primary"),
|
||||||
build: integer("build").notNull().default(1),
|
currentBuild: integer("current_build").notNull().default(1),
|
||||||
lastUpdate: timestamp("last_update").defaultNow(),
|
lastBuildAt: timestamp("last_build_at"),
|
||||||
|
lastDeployAt: timestamp("last_deploy_at"),
|
||||||
|
building: boolean("building").notNull().default(false),
|
||||||
|
updating: boolean("updating").notNull().default(false),
|
||||||
|
lastUpdated: timestamp("last_updated").defaultNow(),
|
||||||
|
meta: jsonb("meta").$type<Record<string, unknown>>().default({}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type ServerStats = InferSelectModel<typeof serverStats>;
|
export const appStatsSchema = createSelectSchema(appStats);
|
||||||
|
export const newAppStatsSchema = createInsertSchema(appStats, {});
|
||||||
|
|
||||||
|
export type AppStats = z.infer<typeof appStatsSchema>;
|
||||||
|
export type NewAppStats = z.infer<typeof newAppStatsSchema>;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { type Express, Router } from "express";
|
import { type Express, Router } from "express";
|
||||||
import { requireAuth } from "../middleware/auth.middleware.js";
|
import { requireAuth } from "../middleware/auth.middleware.js";
|
||||||
|
|
||||||
import restart from "./gpSqlRestart.route.js";
|
import restart from "./gpSqlRestart.route.js";
|
||||||
import start from "./gpSqlStart.route.js";
|
import start from "./gpSqlStart.route.js";
|
||||||
import stop from "./gpSqlStop.route.js";
|
import stop from "./gpSqlStop.route.js";
|
||||||
@@ -7,11 +8,10 @@ export const setupGPSqlRoutes = (baseUrl: string, app: Express) => {
|
|||||||
//setup all the routes
|
//setup all the routes
|
||||||
// Apply auth to entire router
|
// Apply auth to entire router
|
||||||
const router = Router();
|
const router = Router();
|
||||||
router.use(requireAuth);
|
|
||||||
|
|
||||||
router.use(start);
|
router.use(start);
|
||||||
router.use(stop);
|
router.use(stop);
|
||||||
router.use(restart);
|
router.use(restart);
|
||||||
|
|
||||||
app.use(`${baseUrl}/api/system/gpSql`, router);
|
app.use(`${baseUrl}/api/system/gpSql`, requireAuth, router);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ let attempt = 0;
|
|||||||
const maxAttempts = 10;
|
const maxAttempts = 10;
|
||||||
|
|
||||||
export const connectGPSql = async () => {
|
export const connectGPSql = async () => {
|
||||||
const serverUp = await checkHostnamePort(`USMCD1VMS011:1433`);
|
const serverUp = await checkHostnamePort(
|
||||||
|
`${process.env.GP_SERVER ?? "usmcd1vms011"}:1433`,
|
||||||
|
);
|
||||||
if (!serverUp) {
|
if (!serverUp) {
|
||||||
// we will try to reconnect
|
// we will try to reconnect
|
||||||
connected = false;
|
connected = false;
|
||||||
@@ -53,13 +55,14 @@ export const connectGPSql = async () => {
|
|||||||
notify: false,
|
notify: false,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
reconnectToSql;
|
reconnectToSql;
|
||||||
return returnFunc({
|
return returnFunc({
|
||||||
success: false,
|
success: false,
|
||||||
level: "error",
|
level: "error",
|
||||||
module: "system",
|
module: "system",
|
||||||
subModule: "db",
|
subModule: "db",
|
||||||
message: "Failed to connect to the prod sql server.",
|
message: "Failed to connect to the gp sql server.",
|
||||||
data: [error],
|
data: [error],
|
||||||
notify: false,
|
notify: false,
|
||||||
});
|
});
|
||||||
@@ -118,7 +121,9 @@ export const reconnectToSql = async () => {
|
|||||||
|
|
||||||
await new Promise((res) => setTimeout(res, delayStart));
|
await new Promise((res) => setTimeout(res, delayStart));
|
||||||
|
|
||||||
const serverUp = await checkHostnamePort(`${process.env.PROD_SERVER}:1433`);
|
const serverUp = await checkHostnamePort(
|
||||||
|
`${process.env.GP_SERVER ?? "usmcd1vms011"}:1433`,
|
||||||
|
);
|
||||||
|
|
||||||
if (!serverUp) {
|
if (!serverUp) {
|
||||||
delayStart = Math.min(delayStart * 2, 30000); // exponential backoff until up to 30000
|
delayStart = Math.min(delayStart * 2, 30000); // exponential backoff until up to 30000
|
||||||
|
|||||||
@@ -66,7 +66,10 @@ const historicalInvImport = async () => {
|
|||||||
|
|
||||||
const { data: inv, error: invError } = await tryCatch(
|
const { data: inv, error: invError } = await tryCatch(
|
||||||
//prodQuery(sqlQuery.query, "Inventory data"),
|
//prodQuery(sqlQuery.query, "Inventory data"),
|
||||||
runDatamartQuery({ name: "inventory", options: { historical: "x" } }),
|
runDatamartQuery({
|
||||||
|
name: "inventory",
|
||||||
|
options: { lots: "x", locations: "x" },
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data: av, error: avError } = (await tryCatch(
|
const { data: av, error: avError } = (await tryCatch(
|
||||||
|
|||||||
83
backend/middleware/routeHit.middleware.ts
Normal file
83
backend/middleware/routeHit.middleware.ts
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
// routeHit.middleware.ts
|
||||||
|
|
||||||
|
import type { NextFunction, Request, Response } from "express";
|
||||||
|
import {
|
||||||
|
createRouteHit,
|
||||||
|
shouldIgnoreRoute,
|
||||||
|
} from "../utils/analyticRouteHits.utils.js";
|
||||||
|
|
||||||
|
export function routeHitMiddleware(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction,
|
||||||
|
) {
|
||||||
|
const start = performance.now();
|
||||||
|
|
||||||
|
res.on("finish", () => {
|
||||||
|
const actualPath = getActualPath(req);
|
||||||
|
|
||||||
|
if (shouldIgnoreRoute(actualPath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const durationMs = Math.round(performance.now() - start);
|
||||||
|
|
||||||
|
const routePattern = getRoutePattern(req) as string;
|
||||||
|
const module = getModuleName(req);
|
||||||
|
|
||||||
|
void createRouteHit({
|
||||||
|
method: req.method,
|
||||||
|
routePattern,
|
||||||
|
actualPath,
|
||||||
|
statusCode: res.statusCode,
|
||||||
|
durationMs,
|
||||||
|
module,
|
||||||
|
|
||||||
|
// adjust these names to your Better Auth/session shape
|
||||||
|
userId: req.user?.id ?? null,
|
||||||
|
userEmail: req.user?.email ?? null,
|
||||||
|
|
||||||
|
ipAddress: req.ip ?? null,
|
||||||
|
userAgent: req.get("user-agent") ?? null,
|
||||||
|
}).catch((err) => {
|
||||||
|
console.error("Failed to save route hit", err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getActualPath(req: Request) {
|
||||||
|
return req.originalUrl.split("?")[0] ?? req.path ?? "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRoutePattern(req: Request) {
|
||||||
|
const baseUrl = req.baseUrl || "";
|
||||||
|
const routePath = req.route?.path;
|
||||||
|
|
||||||
|
if (typeof routePath === "string") {
|
||||||
|
return `${baseUrl}${routePath}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getActualPath(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getModuleName(req: Request) {
|
||||||
|
const path = req.originalUrl.split("?")[0];
|
||||||
|
|
||||||
|
if (path?.includes("/printers")) return "printers";
|
||||||
|
if (path?.includes("/releases")) return "releases";
|
||||||
|
if (path?.includes("/quality")) return "quality";
|
||||||
|
if (path?.includes("/scanner")) return "scanner";
|
||||||
|
if (path?.includes("/settings")) return "settings";
|
||||||
|
if (path?.includes("/users")) return "users";
|
||||||
|
if (path?.includes("/mobile")) return "mobile";
|
||||||
|
if (path?.includes("/servers")) return "servers";
|
||||||
|
if (path?.includes("/logistics")) return "servers";
|
||||||
|
if (path?.includes("/ocp")) return "ocp";
|
||||||
|
if (path?.includes("/auth")) return "auth";
|
||||||
|
if (path?.includes("/datamart")) return "datamart";
|
||||||
|
if (path?.includes("/opendock")) return "opendock";
|
||||||
|
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
54
backend/mobile/availableScanIds.route.ts
Normal file
54
backend/mobile/availableScanIds.route.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import { Router } from "express";
|
||||||
|
import { db } from "../db/db.controller.js";
|
||||||
|
import { scanUser } from "../db/schema/scanUsers.js";
|
||||||
|
import { settings } from "../db/schema/settings.schema.js";
|
||||||
|
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||||
|
|
||||||
|
const r = Router();
|
||||||
|
|
||||||
|
// scanners that are dedicated to specific users.
|
||||||
|
const SPECIAL_SCANNERS = [98];
|
||||||
|
|
||||||
|
const buildAllowedScannerIds = (scannerCount: number) => {
|
||||||
|
const generatedIds = Array.from({ length: scannerCount }, (_, i) => i + 1);
|
||||||
|
|
||||||
|
return Array.from(new Set([...generatedIds, ...SPECIAL_SCANNERS])).sort(
|
||||||
|
(a, b) => a - b,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
r.get("/", async (_, res) => {
|
||||||
|
// get the scan users and setting
|
||||||
|
const scanusers = await db.select().from(scanUser);
|
||||||
|
const scannerIdSetting = await db
|
||||||
|
.select()
|
||||||
|
.from(settings)
|
||||||
|
.where(eq(settings.name, "scannerIds"));
|
||||||
|
|
||||||
|
const usedScannerIds = scanusers.map((x) => Number(x.scannerId));
|
||||||
|
const allowedScannerIds = buildAllowedScannerIds(
|
||||||
|
Number(scannerIdSetting[0]?.value ?? 0),
|
||||||
|
);
|
||||||
|
|
||||||
|
const availableScannerIds = allowedScannerIds.filter(
|
||||||
|
(id) => !usedScannerIds.includes(id),
|
||||||
|
);
|
||||||
|
|
||||||
|
const data = availableScannerIds.map((id) => ({
|
||||||
|
label: `${id}`,
|
||||||
|
value: id,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: true,
|
||||||
|
level: "info",
|
||||||
|
module: "mobile",
|
||||||
|
subModule: "scanner",
|
||||||
|
message: `There are ${availableScannerIds.length} scanner id's`,
|
||||||
|
data,
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default r;
|
||||||
125
backend/mobile/downloadApps.route.ts
Normal file
125
backend/mobile/downloadApps.route.ts
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
import fs from "node:fs";
|
||||||
|
import { Router } from "express";
|
||||||
|
import path from "path";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
const downloadDir = path.resolve(__dirname, "../../downloads/mobile");
|
||||||
|
|
||||||
|
const currentApk = {
|
||||||
|
fileName: "lst-mobile.apk",
|
||||||
|
};
|
||||||
|
|
||||||
|
router.get("/latest", (_, res) => {
|
||||||
|
const apkPath = path.join(downloadDir, currentApk.fileName);
|
||||||
|
|
||||||
|
if (!fs.existsSync(apkPath)) {
|
||||||
|
return res.status(404).json({ success: false, message: "APK not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
res.setHeader("Content-Type", "application/vnd.android.package-archive");
|
||||||
|
res.setHeader(
|
||||||
|
"Content-Disposition",
|
||||||
|
`attachment; filename="${currentApk.fileName}"`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.sendFile(apkPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get("/ehs", (_, res) => {
|
||||||
|
const apkPath = path.join(downloadDir, "EHS.apk");
|
||||||
|
|
||||||
|
if (!fs.existsSync(apkPath)) {
|
||||||
|
return res.status(404).json({ success: false, message: "APK not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
res.setHeader("Content-Type", "application/vnd.android.package-archive");
|
||||||
|
res.setHeader("Content-Disposition", `attachment; filename="EHS.apk"`);
|
||||||
|
|
||||||
|
return res.sendFile(apkPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get("/ehs/xml", (_, res) => {
|
||||||
|
const xmlPath = path.join(downloadDir, "enterprisehomescreen.xml");
|
||||||
|
|
||||||
|
if (!fs.existsSync(xmlPath)) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
message: "EHS XML not found",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
res.setHeader("Content-Type", "application/xml");
|
||||||
|
res.setHeader(
|
||||||
|
"Content-Disposition",
|
||||||
|
`attachment; filename="enterprisehomescreen.xml"`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.sendFile(xmlPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get("/android/upgrade/11", (_, res) => {
|
||||||
|
const apkPath = path.join(
|
||||||
|
downloadDir,
|
||||||
|
"HE_FULL_UPDATE_11-70-20.00-RG-U00-STD-HEL-04.zip",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!fs.existsSync(apkPath)) {
|
||||||
|
return res.status(404).json({ success: false, message: "APK not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
//res.setHeader("Content-Type", "application/vnd.android.package-archive");
|
||||||
|
res.setHeader("Content-Type", "application/zip");
|
||||||
|
res.setHeader(
|
||||||
|
"Content-Disposition",
|
||||||
|
`attachment; filename="HE_FULL_UPDATE_11.zip"`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.sendFile(apkPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get("/android/upgrade/13", (_, res) => {
|
||||||
|
const apkPath = path.join(
|
||||||
|
downloadDir,
|
||||||
|
"HE_FULL_UPDATE_13-51-16.00-TG-U00-STD-HEL-04.zip",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!fs.existsSync(apkPath)) {
|
||||||
|
return res.status(404).json({ success: false, message: "APK not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
//res.setHeader("Content-Type", "application/vnd.android.package-archive");
|
||||||
|
res.setHeader("Content-Type", "application/zip");
|
||||||
|
res.setHeader(
|
||||||
|
"Content-Disposition",
|
||||||
|
`attachment; filename="HE_FULL_UPDATE_13.zip"`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.sendFile(apkPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get("/android/upgrade/14", (_, res) => {
|
||||||
|
const apkPath = path.join(
|
||||||
|
downloadDir,
|
||||||
|
"HE_FULL_UPDATE_14-38-04.00-UG-U15-STD-HEL-04.zip",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!fs.existsSync(apkPath)) {
|
||||||
|
return res.status(404).json({ success: false, message: "APK not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
//res.setHeader("Content-Type", "application/vnd.android.package-archive");
|
||||||
|
res.setHeader("Content-Type", "application/zip");
|
||||||
|
res.setHeader(
|
||||||
|
"Content-Disposition",
|
||||||
|
`attachment; filename="HE_FULL_UPDATE_14.zip"`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.sendFile(apkPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
60
backend/mobile/laneCheck.ts
Normal file
60
backend/mobile/laneCheck.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import { Router } from "express";
|
||||||
|
import { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
|
||||||
|
import {
|
||||||
|
type SqlQuery,
|
||||||
|
sqlQuerySelector,
|
||||||
|
} from "../prodSql/prodSqlQuerySelector.utils.js";
|
||||||
|
import { runProdApi } from "../utils/prodEndpoint.utils.js";
|
||||||
|
import { apiReturn, returnFunc } from "../utils/returnHelper.utils.js";
|
||||||
|
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||||
|
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.post("/", async (req, res) => {
|
||||||
|
const body = req.body;
|
||||||
|
|
||||||
|
const lane = body.lane.split("#");
|
||||||
|
|
||||||
|
// check if the plant has warehousing activated
|
||||||
|
const featureQ = sqlQuerySelector(`featureCheck`) as SqlQuery;
|
||||||
|
|
||||||
|
const { data: fd, error: fe } = await tryCatch(
|
||||||
|
prodQuery(featureQ.query, `Running feature check`),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (fe) {
|
||||||
|
return returnFunc({
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "datamart",
|
||||||
|
subModule: "query",
|
||||||
|
message: `feature check failed`,
|
||||||
|
data: fe as any,
|
||||||
|
notify: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(fd);
|
||||||
|
|
||||||
|
const laneData = await runProdApi({
|
||||||
|
method: "post",
|
||||||
|
endpoint: "/public/v1.1/Warehousing/GetWarehouseUnits",
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
laneIds: [lane[2]],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: true,
|
||||||
|
level: "info",
|
||||||
|
module: "mobile",
|
||||||
|
subModule: "lane check",
|
||||||
|
message: `all data for lane Id: ${lane}`,
|
||||||
|
data: laneData?.data ?? [],
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
23
backend/mobile/mobile.routes.ts
Normal file
23
backend/mobile/mobile.routes.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import type { Express } from "express";
|
||||||
|
import { featureCheck } from "../middleware/featureActive.middleware.js";
|
||||||
|
import available from "./availableScanIds.route.js";
|
||||||
|
import downloads from "./downloadApps.route.js";
|
||||||
|
import lanes from "./laneCheck.js";
|
||||||
|
import authPin from "./mobileAuth.route.js";
|
||||||
|
import newPin from "./mobilePin.route.js";
|
||||||
|
import logs from "./scanLogs.route.js";
|
||||||
|
import version from "./version.route.js";
|
||||||
|
|
||||||
|
export const setupMobileRoutes = (baseUrl: string, app: Express) => {
|
||||||
|
//stats will be like this as we dont need to change this
|
||||||
|
|
||||||
|
app.use(`${baseUrl}/api/mobile/version`, featureCheck("mobile"), version);
|
||||||
|
app.use(`${baseUrl}/api/mobile/apk`, featureCheck("mobile"), downloads);
|
||||||
|
app.use(`${baseUrl}/api/mobile/logs`, featureCheck("mobile"), logs);
|
||||||
|
app.use(`${baseUrl}/api/mobile/auth`, featureCheck("mobile"), authPin);
|
||||||
|
app.use(`${baseUrl}/api/mobile/pin`, featureCheck("mobile"), newPin);
|
||||||
|
app.use(`${baseUrl}/api/mobile/laneCheck`, featureCheck("mobile"), lanes);
|
||||||
|
app.use(`${baseUrl}/api/mobile/available`, featureCheck("mobile"), available);
|
||||||
|
|
||||||
|
// all other system should be under /api/system/*
|
||||||
|
};
|
||||||
343
backend/mobile/mobileAuth.route.ts
Normal file
343
backend/mobile/mobileAuth.route.ts
Normal file
@@ -0,0 +1,343 @@
|
|||||||
|
import bcrypt from "bcryptjs";
|
||||||
|
import { eq, sql } from "drizzle-orm";
|
||||||
|
import { Router } from "express";
|
||||||
|
import z from "zod";
|
||||||
|
import { db } from "../db/db.controller.js";
|
||||||
|
import {
|
||||||
|
type NewScanUser,
|
||||||
|
type ScanUser,
|
||||||
|
scanUser,
|
||||||
|
} from "../db/schema/scanUsers.js";
|
||||||
|
import { requireAuth } from "../middleware/auth.middleware.js";
|
||||||
|
import { apiReturn, returnFunc } from "../utils/returnHelper.utils.js";
|
||||||
|
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||||
|
|
||||||
|
const r = Router();
|
||||||
|
|
||||||
|
export async function hashPin(pin: string) {
|
||||||
|
// if (!/^\d{6}$/.test(pin)) {
|
||||||
|
// throw new Error("PIN must be exactly 6 digits");
|
||||||
|
// }
|
||||||
|
|
||||||
|
return bcrypt.hashSync(pin, 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
const registerSchema = z.object({
|
||||||
|
name: z.string().min(2).max(100),
|
||||||
|
pinNumber: z.string(),
|
||||||
|
scannerId: z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.max(500)
|
||||||
|
.optional()
|
||||||
|
.describe("if you leave blank it will be the same as your username"),
|
||||||
|
role: z
|
||||||
|
.enum(["user", "lead", "manager", "admin"])
|
||||||
|
.optional()
|
||||||
|
.describe("What roles are available to use."),
|
||||||
|
pinHash: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
r.post("/pin", async (req, res) => {
|
||||||
|
const { pin } = req.body;
|
||||||
|
|
||||||
|
if (!pin || pin.length !== 6) {
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "mobile",
|
||||||
|
subModule: "auth",
|
||||||
|
message: `Pin number must be a min of 6 digits`,
|
||||||
|
data: [],
|
||||||
|
status: 401,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// const user = await db
|
||||||
|
// .select()
|
||||||
|
// .from(scanUser)
|
||||||
|
// .where(eq(scanUser.pinNumber, parseInt(pin, 10)));
|
||||||
|
|
||||||
|
const user = await db.query.scanUser.findFirst({
|
||||||
|
where: (u, { eq }) => eq(u.pinNumber, pin),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "mobile",
|
||||||
|
subModule: "auth",
|
||||||
|
message: `Invalid login please try again.`,
|
||||||
|
data: [],
|
||||||
|
status: 401,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const validPin = bcrypt.compareSync(pin, user.pinHash);
|
||||||
|
|
||||||
|
if (!validPin) {
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "mobile",
|
||||||
|
subModule: "auth",
|
||||||
|
message: `Invalid pin please try again.`,
|
||||||
|
data: [],
|
||||||
|
status: 401,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: true,
|
||||||
|
level: "info",
|
||||||
|
module: "mobile",
|
||||||
|
subModule: "auth",
|
||||||
|
message: `Welcome back ${user.name}`,
|
||||||
|
data: user as ScanUser | any,
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
r.post("/user", async (req, res) => {
|
||||||
|
try {
|
||||||
|
// validate the body is correct before accepting it
|
||||||
|
let validated = registerSchema.parse(req.body);
|
||||||
|
|
||||||
|
validated = {
|
||||||
|
...validated,
|
||||||
|
pinHash: await hashPin(validated.pinNumber.toString()),
|
||||||
|
};
|
||||||
|
|
||||||
|
const values: NewScanUser = {
|
||||||
|
name: validated.name,
|
||||||
|
pinNumber: validated.pinNumber,
|
||||||
|
pinHash: validated.pinHash ?? "",
|
||||||
|
scannerId: validated.scannerId ?? "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const newUser = await db.insert(scanUser).values(values).returning();
|
||||||
|
|
||||||
|
apiReturn(res, {
|
||||||
|
success: true,
|
||||||
|
level: "info", //connect.success ? "info" : "error",
|
||||||
|
module: "mobile",
|
||||||
|
subModule: "auth",
|
||||||
|
message: `${validated.name} was just created`,
|
||||||
|
data: newUser as any,
|
||||||
|
status: 200, //connect.success ? 200 : 400,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof z.ZodError) {
|
||||||
|
const flattened = z.flattenError(err);
|
||||||
|
// return res.status(400).json({
|
||||||
|
// error: "Validation failed",
|
||||||
|
// details: flattened,
|
||||||
|
// });
|
||||||
|
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: false,
|
||||||
|
level: "error", //connect.success ? "info" : "error",
|
||||||
|
module: "mobile",
|
||||||
|
subModule: "auth",
|
||||||
|
message: "Validation failed",
|
||||||
|
data: [flattened.fieldErrors],
|
||||||
|
status: 400, //connect.success ? 200 : 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: false,
|
||||||
|
level: "error", //connect.success ? "info" : "error",
|
||||||
|
module: "mobile",
|
||||||
|
subModule: "auth",
|
||||||
|
message:
|
||||||
|
"This User already exist with this pin or scanner id please try again",
|
||||||
|
data: [err],
|
||||||
|
status: 400, //connect.success ? 200 : 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
r.get("/user", requireAuth, async (_, res) => {
|
||||||
|
const { data, error } = await tryCatch(db.select().from(scanUser));
|
||||||
|
|
||||||
|
// await trackLstEvent({
|
||||||
|
// eventName: "mobile_get_users",
|
||||||
|
// url: "/mobile/users",
|
||||||
|
// eventData: {
|
||||||
|
// module: "mobile",
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "mobile",
|
||||||
|
subModule: "auth",
|
||||||
|
message: `There was an error getting the user`,
|
||||||
|
data: error as any,
|
||||||
|
status: 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: true,
|
||||||
|
level: "info",
|
||||||
|
module: "mobile",
|
||||||
|
subModule: "auth",
|
||||||
|
message: `There are no users you should add one . `,
|
||||||
|
data: [],
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: true,
|
||||||
|
level: "info",
|
||||||
|
module: "mobile",
|
||||||
|
subModule: "auth",
|
||||||
|
message: `All users. `,
|
||||||
|
data,
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
r.patch("/user/:id", requireAuth, async (req, res) => {
|
||||||
|
const updates: Record<string, unknown | null> = {};
|
||||||
|
const { id } = req.params;
|
||||||
|
|
||||||
|
const { data, error } = await tryCatch(
|
||||||
|
db.query.scanUser.findFirst({
|
||||||
|
where: (u, { eq }) => eq(u.id, `${id}`),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "mobile",
|
||||||
|
subModule: "auth",
|
||||||
|
message: `There was an error getting the user`,
|
||||||
|
data: error as any,
|
||||||
|
status: 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "mobile",
|
||||||
|
subModule: "auth",
|
||||||
|
message: `Invalid user id was passed over. `,
|
||||||
|
data: [],
|
||||||
|
status: 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.body?.name !== undefined) {
|
||||||
|
updates.name = req.body.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.body?.pinNumber !== undefined) {
|
||||||
|
const existing = await db.query.scanUser.findFirst({
|
||||||
|
where: (u, { eq }) => eq(u.pinHash, req.body.pinNumber),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existing)
|
||||||
|
return returnFunc({
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "mobile",
|
||||||
|
subModule: "auth",
|
||||||
|
message: `${req.body.pinNumber} already exists please try again`,
|
||||||
|
data: [],
|
||||||
|
notify: false,
|
||||||
|
room: "",
|
||||||
|
});
|
||||||
|
updates.pinNumber = req.body.pinNumber;
|
||||||
|
updates.pinHash = await hashPin(req.body.pinNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.body?.scannerId !== undefined) {
|
||||||
|
updates.scannerId = req.body.scannerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.body?.active !== undefined) {
|
||||||
|
updates.active = req.body.active;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.body?.excludedCommand !== undefined) {
|
||||||
|
updates.excludedCommand = req.body.excludedCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.body?.role !== undefined) {
|
||||||
|
updates.role = req.body.role;
|
||||||
|
}
|
||||||
|
|
||||||
|
updates.upd_date = sql`NOW()`;
|
||||||
|
|
||||||
|
const updatedSetting = await db
|
||||||
|
.update(scanUser)
|
||||||
|
.set(updates)
|
||||||
|
.where(eq(scanUser.id, `${id}`))
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: true,
|
||||||
|
level: "info",
|
||||||
|
module: "mobile",
|
||||||
|
subModule: "user",
|
||||||
|
message: `User ${data.name} was updated. `,
|
||||||
|
data: updatedSetting,
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
r.delete("/user/:id", requireAuth, async (req, res) => {
|
||||||
|
const { id } = req.params;
|
||||||
|
|
||||||
|
const { data, error } = await tryCatch(
|
||||||
|
db.delete(scanUser).where(eq(scanUser.id, `${id}`)),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "mobile",
|
||||||
|
subModule: "auth",
|
||||||
|
message: `There was an error deleting the user`,
|
||||||
|
data: error as any,
|
||||||
|
status: 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "mobile",
|
||||||
|
subModule: "auth",
|
||||||
|
message: `There was no user to delete. `,
|
||||||
|
data: [],
|
||||||
|
status: 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: true,
|
||||||
|
level: "info",
|
||||||
|
module: "mobile",
|
||||||
|
subModule: "user",
|
||||||
|
message: `User was deleted. `,
|
||||||
|
data: data ?? [],
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default r;
|
||||||
21
backend/mobile/mobilePin.route.ts
Normal file
21
backend/mobile/mobilePin.route.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { Router } from "express";
|
||||||
|
import { generateUniquePin } from "../utils/generateScannerPin.utils.js";
|
||||||
|
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||||
|
|
||||||
|
const r = Router();
|
||||||
|
|
||||||
|
r.get("/new", async (_, res) => {
|
||||||
|
const getPin = await generateUniquePin();
|
||||||
|
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: getPin.success,
|
||||||
|
level: getPin.level,
|
||||||
|
module: "mobile",
|
||||||
|
subModule: "auth",
|
||||||
|
message: getPin.message,
|
||||||
|
data: getPin.data,
|
||||||
|
status: getPin.success ? 200 : 400,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default r;
|
||||||
46
backend/mobile/scanLogs.route.ts
Normal file
46
backend/mobile/scanLogs.route.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { eq, sql } from "drizzle-orm";
|
||||||
|
import { Router } from "express";
|
||||||
|
import { db } from "../db/db.controller.js";
|
||||||
|
import { scanLog } from "../db/schema/scanlog.schema.js";
|
||||||
|
import { scanUser } from "../db/schema/scanUsers.js";
|
||||||
|
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||||
|
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.post("/", async (req, res) => {
|
||||||
|
const body = req.body;
|
||||||
|
try {
|
||||||
|
await db
|
||||||
|
.update(scanUser)
|
||||||
|
.set({ lastScan: sql`NOW()` })
|
||||||
|
.where(eq(scanUser.name, body.name));
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
const newLog = await db
|
||||||
|
.insert(scanLog)
|
||||||
|
.values({
|
||||||
|
scannerId: body.scannerId ?? "",
|
||||||
|
message: body.message ?? "",
|
||||||
|
prompt: body.prompt ?? "",
|
||||||
|
commandDescription: body.commandDescription ?? "",
|
||||||
|
status: body.status ?? "",
|
||||||
|
lines: body.lines ?? "",
|
||||||
|
user: body.user ?? "",
|
||||||
|
runningNumber: body.runningNumber ?? "",
|
||||||
|
scannerVersion: body.scannerVersion ?? "0",
|
||||||
|
})
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: true,
|
||||||
|
level: "info",
|
||||||
|
module: "mobile",
|
||||||
|
subModule: "scan logs",
|
||||||
|
message: `New log from ${body.scannerId}`,
|
||||||
|
data: newLog,
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
40
backend/mobile/version.route.ts
Normal file
40
backend/mobile/version.route.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import fs from "node:fs";
|
||||||
|
import { and, eq } from "drizzle-orm";
|
||||||
|
import { Router } from "express";
|
||||||
|
import path from "path";
|
||||||
|
import { db } from "../db/db.controller.js";
|
||||||
|
import { settings } from "../db/schema/settings.schema.js";
|
||||||
|
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
const projectRoot = path.resolve("./lstMobile"); // adjust as needed
|
||||||
|
const appJsonPath = path.join(projectRoot, "app.json");
|
||||||
|
|
||||||
|
router.get("/", async (req, res) => {
|
||||||
|
const baseUrl = `${req.protocol}://${req.get("host")}`;
|
||||||
|
const mobileSettings = await db
|
||||||
|
.select()
|
||||||
|
.from(settings)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(settings.moduleName, "mobile"),
|
||||||
|
eq(settings.settingType, "standard"),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const raw = fs.readFileSync(appJsonPath, "utf-8");
|
||||||
|
const config = JSON.parse(raw);
|
||||||
|
|
||||||
|
const exp = config.expo;
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
packageName: exp.android?.package,
|
||||||
|
versionName: exp.version,
|
||||||
|
versionCode: exp.android?.versionCode,
|
||||||
|
minSupportedVersionCode: exp?.android?.minSupportedVersionCode ?? 0,
|
||||||
|
downloadUrl: `${baseUrl}/lst/api/mobile/apk/latest`,
|
||||||
|
settings: mobileSettings,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
80
backend/notification/notification.SqlJobCleanUp.ts
Normal file
80
backend/notification/notification.SqlJobCleanUp.ts
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
|
||||||
|
import {
|
||||||
|
type SqlQuery,
|
||||||
|
sqlQuerySelector,
|
||||||
|
} from "../prodSql/prodSqlQuerySelector.utils.js";
|
||||||
|
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||||
|
|
||||||
|
// disable the jobs
|
||||||
|
const jobNames: string[] = [
|
||||||
|
"monitor_$_lots",
|
||||||
|
"monitor_$_lots_2",
|
||||||
|
"monitor$lots",
|
||||||
|
"Monitor_APO", //listen for people to cry this is no longer a thing
|
||||||
|
"Monitor_APO2",
|
||||||
|
"Monitor_AutoConsumeMaterials", // TODO: migrate to lst
|
||||||
|
"Monitor_AutoConsumeMaterials_iow1",
|
||||||
|
"Monitor_AutoConsumeMaterials_iow2",
|
||||||
|
"Monitor_BlockedINV_Loc",
|
||||||
|
"monitor_inv_cycle",
|
||||||
|
"monitor_inv_cycle_1",
|
||||||
|
"monitor_inv_cycle_2",
|
||||||
|
"monitor_edi_import", // TODO: migrate to lst -- for the query select count(*) from AlplaPROD_test3.dbo.T_EDIDokumente (nolock) where /* IdLieferant > 1 and */ add_date > DATEADD(MINUTE, -30, getdate())
|
||||||
|
"Monitor_Lot_Progression",
|
||||||
|
"Monitor_Lots", // TODO: migrate to lst -- this should be the one where we monitor the when a lot is assigned if its missing some data.
|
||||||
|
"Monitor_MinMax", // TODO:Migrate to lst
|
||||||
|
"Monitor_MinMax_iow2",
|
||||||
|
"Monitor_PM",
|
||||||
|
"Monitor_Purity",
|
||||||
|
"monitor_wastebookings", // TODO: Migrate
|
||||||
|
"LastPriceUpdate", // not even sure what this is
|
||||||
|
"GETLabelsCount", // seems like an old jc job
|
||||||
|
"jobforpuritycount", // was not even working correctly
|
||||||
|
"Monitor_EmptyAutoConsumLocations", // not sure who uses this one
|
||||||
|
"monitor_labelreprint", // Migrated but need to find out who really wants this
|
||||||
|
"test", // not even sure why this is active
|
||||||
|
"UpdateLastMoldUsed", // old jc inserts data into a table but not sure what its used for not linked to any other alert
|
||||||
|
"UpdateWhsePositions3", // old jc inserts data into a table but not sure what its used for not linked to any other alert
|
||||||
|
"UpdateWhsePositions4",
|
||||||
|
"delete_print", // i think this was in here for when we was having lag prints in iowa1
|
||||||
|
"INV_WHSE_1", // something random i wrote long time ago looks like an inv thing to see aged stuff
|
||||||
|
"INV_WHSE_2",
|
||||||
|
"laneAgeCheck", // another strange one thats been since moved to lst
|
||||||
|
"monitor_blocking_2",
|
||||||
|
"monitor_blocking", // already in lst
|
||||||
|
"monitor_min_inv", // do we still want this one? it has a description of: this checks m-f the min inventory of materials based on the min level set in stock
|
||||||
|
"Monitor_MixedLocations",
|
||||||
|
"Monitor_PM",
|
||||||
|
"Monitor_PM2",
|
||||||
|
"wrong_lots_1",
|
||||||
|
"wrong_lots_2",
|
||||||
|
"invenotry check", // spelling error one of my stupids
|
||||||
|
"monitor_hold_monitor",
|
||||||
|
"Monitor_Silo_adjustments",
|
||||||
|
"monitor_qualityLocMonitor", // validating with lima this is still needed
|
||||||
|
"Monitor_Stock_Change",
|
||||||
|
];
|
||||||
|
|
||||||
|
export const sqlJobCleanUp = async () => {
|
||||||
|
// running a query to disable jobs that are moved to lst to be better maintained
|
||||||
|
const sqlQuery = sqlQuerySelector("disableJob") as SqlQuery;
|
||||||
|
|
||||||
|
if (!sqlQuery.success) {
|
||||||
|
console.error("Failed to load the query: ", sqlQuery.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const job of jobNames) {
|
||||||
|
const { error } = await tryCatch(
|
||||||
|
prodQuery(
|
||||||
|
sqlQuery.query.replace("[jobName]", `${job}`),
|
||||||
|
`Disabling job: ${job}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
113
backend/notification/notification.minLevel.ts
Normal file
113
backend/notification/notification.minLevel.ts
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import { db } from "../db/db.controller.js";
|
||||||
|
import { notifications } from "../db/schema/notifications.schema.js";
|
||||||
|
import { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
|
||||||
|
import {
|
||||||
|
type SqlQuery,
|
||||||
|
sqlQuerySelector,
|
||||||
|
} from "../prodSql/prodSqlQuerySelector.utils.js";
|
||||||
|
import { returnFunc } from "../utils/returnHelper.utils.js";
|
||||||
|
import { sendEmail } from "../utils/sendEmail.utils.js";
|
||||||
|
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const func = async (data: any, emails: string) => {
|
||||||
|
// get the actual notification as items will be updated between intervals if no one touches
|
||||||
|
const { data: l, error: le } = (await tryCatch(
|
||||||
|
db.select().from(notifications).where(eq(notifications.id, data.id)),
|
||||||
|
)) as any;
|
||||||
|
|
||||||
|
if (le) {
|
||||||
|
return returnFunc({
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "notification",
|
||||||
|
subModule: "query",
|
||||||
|
message: `${data.name} encountered an error while trying to get initial info`,
|
||||||
|
data: [le],
|
||||||
|
notify: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// search the query db for the query by name
|
||||||
|
const sqlQuery = sqlQuerySelector(`${data.name}`) as SqlQuery;
|
||||||
|
// create the ignore audit logs ids
|
||||||
|
const ignoreIds = l[0].options[0]?.auditId
|
||||||
|
? `${l[0].options[0]?.auditId}`
|
||||||
|
: "0";
|
||||||
|
|
||||||
|
// run the check
|
||||||
|
const { data: queryRun, error } = await tryCatch(
|
||||||
|
prodQuery(
|
||||||
|
sqlQuery.query
|
||||||
|
.replace("[intervalCheck]", l[0].interval)
|
||||||
|
.replace("[ignoreList]", ignoreIds),
|
||||||
|
`Running notification query: ${l[0].name}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return returnFunc({
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "notification",
|
||||||
|
subModule: "query",
|
||||||
|
message: `Data for: ${l[0].name} encountered an error while trying to get it`,
|
||||||
|
data: [error],
|
||||||
|
notify: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queryRun.data.length > 0) {
|
||||||
|
// update the latest audit id
|
||||||
|
const { error: dbe } = await tryCatch(
|
||||||
|
db
|
||||||
|
.update(notifications)
|
||||||
|
.set({ options: [{ auditId: `${queryRun.data[0].id}` }] })
|
||||||
|
.where(eq(notifications.id, data.id)),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (dbe) {
|
||||||
|
return returnFunc({
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "notification",
|
||||||
|
subModule: "query",
|
||||||
|
message: `Data for: ${l[0].name} encountered an error while trying to get it`,
|
||||||
|
data: [dbe],
|
||||||
|
notify: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// send the email
|
||||||
|
|
||||||
|
const sentEmail = await sendEmail({
|
||||||
|
email: emails,
|
||||||
|
subject: "Alert! Label Reprinted",
|
||||||
|
template: "reprintLabels",
|
||||||
|
context: {
|
||||||
|
items: queryRun.data,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!sentEmail?.success) {
|
||||||
|
return returnFunc({
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "email",
|
||||||
|
subModule: "notification",
|
||||||
|
message: `${l[0].name} failed to send the email`,
|
||||||
|
data: [sentEmail],
|
||||||
|
notify: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("doing nothing as there is nothing to do.");
|
||||||
|
}
|
||||||
|
// TODO send the error to systemAdmin users so they do not always need to be on the notifications.
|
||||||
|
// these errors are defined per notification.
|
||||||
|
};
|
||||||
|
|
||||||
|
export default func;
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { Express } from "express";
|
import type { Express } from "express";
|
||||||
import { requireAuth } from "../middleware/auth.middleware.js";
|
import { requireAuth } from "../middleware/auth.middleware.js";
|
||||||
|
|
||||||
import manual from "./notification.manualTrigger.js";
|
import manual from "./notification.manualTrigger.js";
|
||||||
import getNotifications from "./notification.route.js";
|
import getNotifications from "./notification.route.js";
|
||||||
import updateNote from "./notification.update.route.js";
|
import updateNote from "./notification.update.route.js";
|
||||||
@@ -10,13 +11,48 @@ import updateSub from "./notificationSub.update.route.js";
|
|||||||
|
|
||||||
export const setupNotificationRoutes = (baseUrl: string, app: Express) => {
|
export const setupNotificationRoutes = (baseUrl: string, app: Express) => {
|
||||||
//stats will be like this as we dont need to change this
|
//stats will be like this as we dont need to change this
|
||||||
app.use(`${baseUrl}/api/notification`, requireAuth, getNotifications);
|
app.use(
|
||||||
app.use(`${baseUrl}/api/notification`, requireAuth, updateNote);
|
`${baseUrl}/api/notification`,
|
||||||
app.use(`${baseUrl}/api/notification/manual`, requireAuth, manual);
|
requireAuth,
|
||||||
app.use(`${baseUrl}/api/notification/sub`, requireAuth, subs);
|
|
||||||
app.use(`${baseUrl}/api/notification/sub`, requireAuth, newSub);
|
getNotifications,
|
||||||
app.use(`${baseUrl}/api/notification/sub`, requireAuth, updateSub);
|
);
|
||||||
app.use(`${baseUrl}/api/notification/sub`, requireAuth, deleteSub);
|
app.use(
|
||||||
|
`${baseUrl}/api/notification`,
|
||||||
|
requireAuth,
|
||||||
|
|
||||||
|
updateNote,
|
||||||
|
);
|
||||||
|
app.use(
|
||||||
|
`${baseUrl}/api/notification/manual`,
|
||||||
|
requireAuth,
|
||||||
|
|
||||||
|
manual,
|
||||||
|
);
|
||||||
|
app.use(
|
||||||
|
`${baseUrl}/api/notification/sub`,
|
||||||
|
requireAuth,
|
||||||
|
|
||||||
|
subs,
|
||||||
|
);
|
||||||
|
app.use(
|
||||||
|
`${baseUrl}/api/notification/sub`,
|
||||||
|
requireAuth,
|
||||||
|
|
||||||
|
newSub,
|
||||||
|
);
|
||||||
|
app.use(
|
||||||
|
`${baseUrl}/api/notification/sub`,
|
||||||
|
requireAuth,
|
||||||
|
|
||||||
|
updateSub,
|
||||||
|
);
|
||||||
|
app.use(
|
||||||
|
`${baseUrl}/api/notification/sub`,
|
||||||
|
requireAuth,
|
||||||
|
|
||||||
|
deleteSub,
|
||||||
|
);
|
||||||
|
|
||||||
// all other system should be under /api/system/*
|
// all other system should be under /api/system/*
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ const parseZebraAlert = (body: any): PrinterEvent => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
r.post("/printer/listener/:printer", upload.any(), async (req, res) => {
|
r.post("/:printer", upload.any(), async (req, res) => {
|
||||||
const { printer: printerName } = req.params;
|
const { printer: printerName } = req.params;
|
||||||
const event: PrinterEvent = parseZebraAlert(req.body);
|
const event: PrinterEvent = parseZebraAlert(req.body);
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export const printerSync = async () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (printers?.success) {
|
if (printers?.success && Array.isArray(printers.data)) {
|
||||||
const ignorePrinters = ["pdf24", "standard"];
|
const ignorePrinters = ["pdf24", "standard"];
|
||||||
|
|
||||||
const validPrinters =
|
const validPrinters =
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import { printerSync } from "./ocp.printer.manage.js";
|
|||||||
|
|
||||||
const r = Router();
|
const r = Router();
|
||||||
|
|
||||||
r.post("/printer/update", async (_, res) => {
|
r.post("/update", async (_, res) => {
|
||||||
printerSync();
|
printerSync();
|
||||||
return apiReturn(res, {
|
return apiReturn(res, {
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -1,24 +1,16 @@
|
|||||||
import { type Express, Router } from "express";
|
import type { Express } from "express";
|
||||||
import { requireAuth } from "../middleware/auth.middleware.js";
|
import { requireAuth } from "../middleware/auth.middleware.js";
|
||||||
import { featureCheck } from "../middleware/featureActive.middleware.js";
|
import { featureCheck } from "../middleware/featureActive.middleware.js";
|
||||||
|
|
||||||
import listener from "./ocp.printer.listener.js";
|
import listener from "./ocp.printer.listener.js";
|
||||||
import update from "./ocp.printer.update.js";
|
import update from "./ocp.printer.update.js";
|
||||||
|
|
||||||
export const setupOCPRoutes = (baseUrl: string, app: Express) => {
|
export const setupOCPRoutes = (baseUrl: string, app: Express) => {
|
||||||
//setup all the routes
|
app.use(`${baseUrl}/api/ocp/printer/listener`, featureCheck("ocp"), listener);
|
||||||
const router = Router();
|
app.use(
|
||||||
|
`${baseUrl}/api/ocp/printer`,
|
||||||
// is the feature even on?
|
featureCheck("ocp"),
|
||||||
router.use(featureCheck("ocp"));
|
requireAuth,
|
||||||
|
update,
|
||||||
// non auth routes up here
|
);
|
||||||
router.use(listener);
|
|
||||||
|
|
||||||
// auth routes below here
|
|
||||||
router.use(requireAuth);
|
|
||||||
|
|
||||||
router.use(update);
|
|
||||||
//router.use("");
|
|
||||||
|
|
||||||
app.use(`${baseUrl}/api/ocp`, router);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { addHours } from "date-fns";
|
|||||||
import { formatInTimeZone } from "date-fns-tz";
|
import { formatInTimeZone } from "date-fns-tz";
|
||||||
import { eq, sql } from "drizzle-orm";
|
import { eq, sql } from "drizzle-orm";
|
||||||
import { db } from "../db/db.controller.js";
|
import { db } from "../db/db.controller.js";
|
||||||
import { opendockApt } from "../db/schema/opendock.schema.js";
|
import { opendockApt } from "../db/schema/opendock_apt.schema.js";
|
||||||
import { settings } from "../db/schema/settings.schema.js";
|
import { settings } from "../db/schema/settings.schema.js";
|
||||||
import { createLogger } from "../logger/logger.controller.js";
|
import { createLogger } from "../logger/logger.controller.js";
|
||||||
import { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
|
import { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
|
||||||
@@ -27,6 +27,9 @@ type Releases = {
|
|||||||
Quantity: number;
|
Quantity: number;
|
||||||
LineItemArticleWeight: number;
|
LineItemArticleWeight: number;
|
||||||
CustomerReleaseNumber: string;
|
CustomerReleaseNumber: string;
|
||||||
|
DeliveryAddressDescription: string;
|
||||||
|
DeliveryAddressHumanReadableId: string;
|
||||||
|
AdditionalInformation1: string;
|
||||||
};
|
};
|
||||||
const timeZone = process.env.TIMEZONE as string;
|
const timeZone = process.env.TIMEZONE as string;
|
||||||
const TWENTY_FOUR_HOURS = 24 * 60 * 60 * 1000;
|
const TWENTY_FOUR_HOURS = 24 * 60 * 60 * 1000;
|
||||||
@@ -73,6 +76,28 @@ const postRelease = async (release: Releases) => {
|
|||||||
log.info({}, "Refreshing Auth Token");
|
log.info({}, "Refreshing Auth Token");
|
||||||
await getToken();
|
await getToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// load validation checks
|
||||||
|
const defaultDock = await db.query.settings.findFirst({
|
||||||
|
where: (u, { eq }) => eq(u.name, "defaultLoadType"),
|
||||||
|
});
|
||||||
|
|
||||||
|
// check if the release has the data in it
|
||||||
|
const releaseLoadtypeCheck = (release.AdditionalInformation1 ?? "")
|
||||||
|
.toLowerCase()
|
||||||
|
.split(",")
|
||||||
|
.map((x) => x.trim())
|
||||||
|
.includes("drop");
|
||||||
|
|
||||||
|
const opendDockArticleCheck = await db.query.opendockArticleSetup.findFirst({
|
||||||
|
where: (table, { and, eq }) =>
|
||||||
|
and(
|
||||||
|
eq(table.av, release.LineItemArticleWeight),
|
||||||
|
eq(table.customer, release.DeliveryAddressHumanReadableId),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: add in docks from lst db here to make it more universal for the team
|
||||||
/**
|
/**
|
||||||
* ReleaseState
|
* ReleaseState
|
||||||
* 0 = open
|
* 0 = open
|
||||||
@@ -101,6 +126,7 @@ const postRelease = async (release: Releases) => {
|
|||||||
: release.DeliveryState === 4 && "Completed",
|
: release.DeliveryState === 4 && "Completed",
|
||||||
userId: process.env.DEFAULT_CARRIER, // this should be the carrierid
|
userId: process.env.DEFAULT_CARRIER, // this should be the carrierid
|
||||||
loadTypeId: process.env.DEFAULT_LOAD_TYPE, // well get this and make it a default one
|
loadTypeId: process.env.DEFAULT_LOAD_TYPE, // well get this and make it a default one
|
||||||
|
// TODO: look in the remarks in the release and if its says
|
||||||
dockId: process.env.DEFAULT_DOCK, // this the warehouse we want it in to start out
|
dockId: process.env.DEFAULT_DOCK, // this the warehouse we want it in to start out
|
||||||
refNumbers: [release.ReleaseNumber],
|
refNumbers: [release.ReleaseNumber],
|
||||||
//refNumber: release.ReleaseNumber,
|
//refNumber: release.ReleaseNumber,
|
||||||
@@ -115,6 +141,19 @@ const postRelease = async (release: Releases) => {
|
|||||||
},
|
},
|
||||||
units: null,
|
units: null,
|
||||||
customFields: [
|
customFields: [
|
||||||
|
{
|
||||||
|
name: "strCustomer",
|
||||||
|
type: "str",
|
||||||
|
label: "Customer",
|
||||||
|
value: `${release.DeliveryAddressDescription}`,
|
||||||
|
description: "Who is the customer ",
|
||||||
|
placeholder: "",
|
||||||
|
dropDownValues: [],
|
||||||
|
minLengthOrValue: 1,
|
||||||
|
hiddenFromCarrier: false,
|
||||||
|
requiredForCarrier: false,
|
||||||
|
requiredForWarehouse: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "strArticle",
|
name: "strArticle",
|
||||||
type: "str",
|
type: "str",
|
||||||
@@ -190,58 +229,179 @@ const postRelease = async (release: Releases) => {
|
|||||||
|
|
||||||
if (existing) {
|
if (existing) {
|
||||||
const id = existing.openDockAptId;
|
const id = existing.openDockAptId;
|
||||||
try {
|
|
||||||
const response = await axios.patch(
|
|
||||||
`${process.env.OPENDOCK_URL}/appointment/${id}`,
|
|
||||||
newDockApt,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
"content-type": "application/json; charset=utf-8",
|
|
||||||
Authorization: `Bearer ${odToken.odToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.status === 400) {
|
if (
|
||||||
log.error({}, response.data.data.message);
|
(releaseLoadtypeCheck ||
|
||||||
|
opendDockArticleCheck?.loadType === "drop" ||
|
||||||
|
defaultDock?.value === "drop") &&
|
||||||
|
(release.DeliveryState === 0 || release.DeliveryState === 1)
|
||||||
|
) {
|
||||||
|
const setArrival = { ...newDockApt, status: "Arrived" };
|
||||||
|
|
||||||
|
// set to arrived
|
||||||
|
try {
|
||||||
|
const response = await axios.patch(
|
||||||
|
`${process.env.OPENDOCK_URL}/appointment/${id}`,
|
||||||
|
setArrival,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json; charset=utf-8",
|
||||||
|
Authorization: `Bearer ${odToken.odToken}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.status === 400) {
|
||||||
|
log.error({}, response.data.data.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the release in the db leaving as insert just incase something weird happened
|
||||||
|
try {
|
||||||
|
await db
|
||||||
|
.insert(opendockApt)
|
||||||
|
.values({
|
||||||
|
release: release.ReleaseNumber,
|
||||||
|
openDockAptId: response.data.data.id,
|
||||||
|
appointment: response.data.data,
|
||||||
|
})
|
||||||
|
.onConflictDoUpdate({
|
||||||
|
target: opendockApt.release,
|
||||||
|
set: {
|
||||||
|
openDockAptId: response.data.data.id,
|
||||||
|
appointment: response.data.data,
|
||||||
|
upd_date: sql`NOW()`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
log.info({}, `${release.ReleaseNumber} was updated`);
|
||||||
|
} catch (e) {
|
||||||
|
log.error(
|
||||||
|
{ stack: e },
|
||||||
|
`Error updating the release: ${release.ReleaseNumber}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: to many possibilities
|
||||||
|
} catch (e: any) {
|
||||||
|
//console.info(newDockApt);
|
||||||
|
log.error(
|
||||||
|
{ stack: e.response.data },
|
||||||
|
`An error has occurred during patching of the release: ${release.ReleaseNumber}`,
|
||||||
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// update the release in the db leaving as insert just incase something weird happened
|
// set to inprogress
|
||||||
|
await delay(1500);
|
||||||
try {
|
try {
|
||||||
await db
|
const response = await axios.patch(
|
||||||
.insert(opendockApt)
|
`${process.env.OPENDOCK_URL}/appointment/${id}`,
|
||||||
.values({
|
newDockApt,
|
||||||
release: release.ReleaseNumber,
|
{
|
||||||
openDockAptId: response.data.data.id,
|
headers: {
|
||||||
appointment: response.data.data,
|
"content-type": "application/json; charset=utf-8",
|
||||||
})
|
Authorization: `Bearer ${odToken.odToken}`,
|
||||||
.onConflictDoUpdate({
|
},
|
||||||
target: opendockApt.release,
|
},
|
||||||
set: {
|
);
|
||||||
|
|
||||||
|
if (response.status === 400) {
|
||||||
|
log.error({}, response.data.data.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the release in the db leaving as insert just incase something weird happened
|
||||||
|
try {
|
||||||
|
await db
|
||||||
|
.insert(opendockApt)
|
||||||
|
.values({
|
||||||
|
release: release.ReleaseNumber,
|
||||||
openDockAptId: response.data.data.id,
|
openDockAptId: response.data.data.id,
|
||||||
appointment: response.data.data,
|
appointment: response.data.data,
|
||||||
upd_date: sql`NOW()`,
|
})
|
||||||
},
|
.onConflictDoUpdate({
|
||||||
})
|
target: opendockApt.release,
|
||||||
.returning();
|
set: {
|
||||||
|
openDockAptId: response.data.data.id,
|
||||||
|
appointment: response.data.data,
|
||||||
|
upd_date: sql`NOW()`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.returning();
|
||||||
|
|
||||||
log.info({}, `${release.ReleaseNumber} was updated`);
|
log.info({}, `${release.ReleaseNumber} was updated`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
log.error(
|
||||||
|
{ stack: e },
|
||||||
|
`Error updating the release: ${release.ReleaseNumber}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: to many possibilities
|
||||||
|
} catch (e: any) {
|
||||||
|
//console.info(newDockApt);
|
||||||
log.error(
|
log.error(
|
||||||
{ error: e },
|
{ stack: e.response.data },
|
||||||
`Error updating the release: ${release.ReleaseNumber}`,
|
`An error has occurred during patching of the release: ${release.ReleaseNumber}`,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: to many possibilities
|
|
||||||
} catch (e: any) {
|
|
||||||
//console.info(newDockApt);
|
|
||||||
log.error(
|
|
||||||
{ error: e.response.data },
|
|
||||||
`An error has occurred during patching of the release: ${release.ReleaseNumber}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const response = await axios.patch(
|
||||||
|
`${process.env.OPENDOCK_URL}/appointment/${id}`,
|
||||||
|
newDockApt,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json; charset=utf-8",
|
||||||
|
Authorization: `Bearer ${odToken.odToken}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.status === 400) {
|
||||||
|
log.error({}, response.data.data.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the release in the db leaving as insert just incase something weird happened
|
||||||
|
try {
|
||||||
|
await db
|
||||||
|
.insert(opendockApt)
|
||||||
|
.values({
|
||||||
|
release: release.ReleaseNumber,
|
||||||
|
openDockAptId: response.data.data.id,
|
||||||
|
appointment: response.data.data,
|
||||||
|
})
|
||||||
|
.onConflictDoUpdate({
|
||||||
|
target: opendockApt.release,
|
||||||
|
set: {
|
||||||
|
openDockAptId: response.data.data.id,
|
||||||
|
appointment: response.data.data,
|
||||||
|
upd_date: sql`NOW()`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
log.info({}, `${release.ReleaseNumber} was updated`);
|
||||||
|
} catch (e) {
|
||||||
|
log.error(
|
||||||
|
{ stack: e },
|
||||||
|
`Error updating the release: ${release.ReleaseNumber}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: to many possibilities
|
||||||
|
} catch (e: any) {
|
||||||
|
//console.info(newDockApt);
|
||||||
|
log.error(
|
||||||
|
{ stack: e.response.data },
|
||||||
|
`An error has occurred during patching of the release: ${release.ReleaseNumber}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
@@ -287,13 +447,13 @@ const postRelease = async (release: Releases) => {
|
|||||||
|
|
||||||
log.info({}, `${release.ReleaseNumber} was created`);
|
log.info({}, `${release.ReleaseNumber} was created`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error({ error: e }, "Error creating new release");
|
log.error({ stack: e }, "Error creating new release");
|
||||||
}
|
}
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: to many possibilities
|
// biome-ignore lint/suspicious/noExplicitAny: to many possibilities
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
log.error(
|
log.error(
|
||||||
{ error: e?.response?.data },
|
{ stack: e?.response?.data },
|
||||||
"Error posting new release to opendock",
|
`Error posting new release to opendock, ${release.ReleaseNumber}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|||||||
277
backend/opendock/opendock.articleCheck.route.ts
Normal file
277
backend/opendock/opendock.articleCheck.route.ts
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
import { desc, eq, sql } from "drizzle-orm";
|
||||||
|
import { Router } from "express";
|
||||||
|
import z from "zod";
|
||||||
|
import { db } from "../db/db.controller.js";
|
||||||
|
import {
|
||||||
|
type NewOpendockArticleSetup,
|
||||||
|
opendockArticleSetup,
|
||||||
|
} from "../db/schema/opendock_articleSetup.js";
|
||||||
|
import { opendockDockSetup } from "../db/schema/opendock_docks.js";
|
||||||
|
import { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
|
||||||
|
import {
|
||||||
|
type SqlQuery,
|
||||||
|
sqlQuerySelector,
|
||||||
|
} from "../prodSql/prodSqlQuerySelector.utils.js";
|
||||||
|
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||||
|
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||||
|
|
||||||
|
const r = Router();
|
||||||
|
|
||||||
|
const newArticleLink = z.object({
|
||||||
|
av: z.number().int(),
|
||||||
|
description: z.string(),
|
||||||
|
customer: z.string().min(1).max(32),
|
||||||
|
customerDescription: z.string().min(2).max(100),
|
||||||
|
loadType: z
|
||||||
|
.enum(["drop", "live"])
|
||||||
|
.optional()
|
||||||
|
.describe("What roles are available to use."),
|
||||||
|
dock: z
|
||||||
|
//.record(z.string(), z.unknown())
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.describe(
|
||||||
|
"This allows us to add extra fields to the data to parse against",
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
const newDockLink = z.object({
|
||||||
|
name: z.string(),
|
||||||
|
dockID: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
r.post("/", async (req, res) => {
|
||||||
|
try {
|
||||||
|
const validated = newArticleLink.parse(req.body) as NewOpendockArticleSetup;
|
||||||
|
|
||||||
|
const newLink = await db
|
||||||
|
.insert(opendockArticleSetup)
|
||||||
|
.values({
|
||||||
|
av: validated.av,
|
||||||
|
description: validated.description,
|
||||||
|
customer: validated.customer,
|
||||||
|
customerDescription: validated.customerDescription,
|
||||||
|
loadType: validated.loadType,
|
||||||
|
dock: validated.dock,
|
||||||
|
add_user: req.user?.username ?? "lst_user",
|
||||||
|
})
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: true,
|
||||||
|
level: "info",
|
||||||
|
module: "opendock",
|
||||||
|
subModule: "articleCheck",
|
||||||
|
message: `${validated.av} was just added `,
|
||||||
|
data: newLink as any,
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof z.ZodError) {
|
||||||
|
const flattened = z.flattenError(err);
|
||||||
|
// return res.status(400).json({
|
||||||
|
// error: "Validation failed",
|
||||||
|
// details: flattened,
|
||||||
|
// });
|
||||||
|
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: false,
|
||||||
|
level: "error", //connect.success ? "info" : "error",
|
||||||
|
module: "opendock",
|
||||||
|
subModule: "articleCheck",
|
||||||
|
message: "Validation failed",
|
||||||
|
data: [flattened.fieldErrors],
|
||||||
|
status: 400, //connect.success ? 200 : 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: false,
|
||||||
|
level: "error", //connect.success ? "info" : "error",
|
||||||
|
module: "opendock",
|
||||||
|
subModule: "articleCheck",
|
||||||
|
message: "Internal Server Error adding article link",
|
||||||
|
data: [err],
|
||||||
|
status: 400, //connect.success ? 200 : 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
r.patch("/:id", async (req, res) => {
|
||||||
|
const { id } = req.params;
|
||||||
|
const updates: Record<string, unknown | null> = {};
|
||||||
|
|
||||||
|
if (req.body?.loadType !== undefined) {
|
||||||
|
updates.loadType = req.body.loadType;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.body?.dock !== undefined) {
|
||||||
|
updates.dock = req.body.dock;
|
||||||
|
}
|
||||||
|
|
||||||
|
updates.upd_user = req.user?.username || "lst_user";
|
||||||
|
updates.upd_date = sql`NOW()`;
|
||||||
|
|
||||||
|
const updatedSetting = await db
|
||||||
|
.update(opendockArticleSetup)
|
||||||
|
.set(updates)
|
||||||
|
.where(eq(opendockArticleSetup.id, id))
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: true,
|
||||||
|
level: "info",
|
||||||
|
module: "opendock",
|
||||||
|
subModule: "articleCheck",
|
||||||
|
message: `${updatedSetting[0]?.av} was just updated. `,
|
||||||
|
data: updatedSetting,
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
r.delete("/:id", async (req, res) => {
|
||||||
|
const { id } = req.params;
|
||||||
|
|
||||||
|
const removeLink = await db
|
||||||
|
.delete(opendockArticleSetup)
|
||||||
|
.where(eq(opendockArticleSetup.id, id))
|
||||||
|
.returning();
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: false,
|
||||||
|
level: "info", //connect.success ? "info" : "error",
|
||||||
|
module: "opendock",
|
||||||
|
subModule: "articleCheck",
|
||||||
|
message: "Article link was deleted",
|
||||||
|
data: removeLink,
|
||||||
|
status: 200, //connect.success ? 200 : 400,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
r.get("/", async (_, res) => {
|
||||||
|
const { data } = await tryCatch(
|
||||||
|
db
|
||||||
|
.select()
|
||||||
|
.from(opendockArticleSetup)
|
||||||
|
.orderBy(desc(opendockArticleSetup.customer))
|
||||||
|
.limit(1500),
|
||||||
|
);
|
||||||
|
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: true,
|
||||||
|
level: "info",
|
||||||
|
module: "opendock",
|
||||||
|
subModule: "articleCheck",
|
||||||
|
message: `All links`,
|
||||||
|
data: data ?? [],
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
r.get("/customers/:av", async (req, res) => {
|
||||||
|
const { av } = req.params;
|
||||||
|
|
||||||
|
const avSQLQuery = sqlQuerySelector(`opendock.addressLink`) as SqlQuery;
|
||||||
|
|
||||||
|
if (!avSQLQuery.success) {
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: true,
|
||||||
|
level: "error",
|
||||||
|
module: "opendock",
|
||||||
|
subModule: "articleCheck",
|
||||||
|
message: avSQLQuery.message,
|
||||||
|
data: [],
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const { data } = await tryCatch(
|
||||||
|
prodQuery(
|
||||||
|
avSQLQuery.query.replace("[articleCheck]", av),
|
||||||
|
"openDock addressLink",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: true,
|
||||||
|
level: "info",
|
||||||
|
module: "opendock",
|
||||||
|
subModule: "articleCheck",
|
||||||
|
message: `All customers linked to av: ${av}`,
|
||||||
|
data: data?.data ?? ([] as any),
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
r.post("/dock", async (req, res) => {
|
||||||
|
try {
|
||||||
|
const validated = newDockLink.parse(req.body) as any;
|
||||||
|
|
||||||
|
const newLink = await db
|
||||||
|
.insert(opendockDockSetup)
|
||||||
|
.values({
|
||||||
|
name: validated.name,
|
||||||
|
dockID: validated.dockID,
|
||||||
|
add_user: req.user?.username ?? "lst_user",
|
||||||
|
})
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: true,
|
||||||
|
level: "info",
|
||||||
|
module: "opendock",
|
||||||
|
subModule: "articleCheck",
|
||||||
|
message: `${validated.name} was just added `,
|
||||||
|
data: newLink as any,
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof z.ZodError) {
|
||||||
|
const flattened = z.flattenError(err);
|
||||||
|
// return res.status(400).json({
|
||||||
|
// error: "Validation failed",
|
||||||
|
// details: flattened,
|
||||||
|
// });
|
||||||
|
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: false,
|
||||||
|
level: "error", //connect.success ? "info" : "error",
|
||||||
|
module: "opendock",
|
||||||
|
subModule: "articleCheck",
|
||||||
|
message: "Validation failed",
|
||||||
|
data: [flattened.fieldErrors],
|
||||||
|
status: 400, //connect.success ? 200 : 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: false,
|
||||||
|
level: "error", //connect.success ? "info" : "error",
|
||||||
|
module: "opendock",
|
||||||
|
subModule: "articleCheck",
|
||||||
|
message: "Internal Server Error adding dock link",
|
||||||
|
data: [err],
|
||||||
|
status: 400, //connect.success ? 200 : 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
r.get("/dock", async (_, res) => {
|
||||||
|
const { data } = await tryCatch(
|
||||||
|
db
|
||||||
|
.select()
|
||||||
|
.from(opendockDockSetup)
|
||||||
|
.orderBy(desc(opendockDockSetup.name))
|
||||||
|
.limit(1500),
|
||||||
|
);
|
||||||
|
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: true,
|
||||||
|
level: "info",
|
||||||
|
module: "opendock",
|
||||||
|
subModule: "articleCheck",
|
||||||
|
message: `All dock links`,
|
||||||
|
data: data ?? [],
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default r;
|
||||||
@@ -1,19 +1,24 @@
|
|||||||
import { type Express, Router } from "express";
|
import type { Express } from "express";
|
||||||
import { requireAuth } from "../middleware/auth.middleware.js";
|
import { requireAuth } from "../middleware/auth.middleware.js";
|
||||||
import { featureCheck } from "../middleware/featureActive.middleware.js";
|
import { featureCheck } from "../middleware/featureActive.middleware.js";
|
||||||
|
import articleCheck from "./opendock.articleCheck.route.js";
|
||||||
|
|
||||||
import getApt from "./opendockGetRelease.route.js";
|
import getApt from "./opendockGetRelease.route.js";
|
||||||
|
|
||||||
export const setupOpendockRoutes = (baseUrl: string, app: Express) => {
|
export const setupOpendockRoutes = (baseUrl: string, app: Express) => {
|
||||||
//setup all the routes
|
//setup all the routes
|
||||||
// Apply auth to entire router
|
|
||||||
const router = Router();
|
|
||||||
|
|
||||||
// is the feature even on?
|
app.use(
|
||||||
router.use(featureCheck("opendock_sync"));
|
`${baseUrl}/api/opendock`,
|
||||||
|
featureCheck("opendock_sync"),
|
||||||
|
requireAuth,
|
||||||
|
getApt,
|
||||||
|
);
|
||||||
|
|
||||||
// we need to make sure we are authenticated to see the releases
|
app.use(
|
||||||
router.use(requireAuth);
|
`${baseUrl}/api/opendock/articleCheck`,
|
||||||
|
featureCheck("opendock_sync"),
|
||||||
router.use(getApt);
|
requireAuth,
|
||||||
app.use(`${baseUrl}/api/opendock`, router);
|
articleCheck,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { desc, gte, sql } from "drizzle-orm";
|
import { desc, gte, sql } from "drizzle-orm";
|
||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
import { db } from "../db/db.controller.js";
|
import { db } from "../db/db.controller.js";
|
||||||
import { opendockApt } from "../db/schema/opendock.schema.js";
|
import { opendockApt } from "../db/schema/opendock_apt.schema.js";
|
||||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { type Express, Router } from "express";
|
import { type Express, Router } from "express";
|
||||||
import { requireAuth } from "../middleware/auth.middleware.js";
|
import { requireAuth } from "../middleware/auth.middleware.js";
|
||||||
|
|
||||||
import restart from "./prodSqlRestart.route.js";
|
import restart from "./prodSqlRestart.route.js";
|
||||||
import start from "./prodSqlStart.route.js";
|
import start from "./prodSqlStart.route.js";
|
||||||
import stop from "./prodSqlStop.route.js";
|
import stop from "./prodSqlStop.route.js";
|
||||||
@@ -9,9 +10,7 @@ export const setupProdSqlRoutes = (baseUrl: string, app: Express) => {
|
|||||||
const router = Router();
|
const router = Router();
|
||||||
router.use(requireAuth);
|
router.use(requireAuth);
|
||||||
|
|
||||||
router.use(start);
|
app.use(`${baseUrl}/api/system/prodSql/start`, requireAuth, start);
|
||||||
router.use(stop);
|
app.use(`${baseUrl}/api/system/prodSql/stop`, requireAuth, stop);
|
||||||
router.use(restart);
|
app.use(`${baseUrl}/api/system/prodSql/restart`, requireAuth, restart);
|
||||||
|
|
||||||
app.use(`${baseUrl}/api/system/prodSql`, router);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { closePool, connectProdSql } from "./prodSqlConnection.controller.js";
|
|||||||
|
|
||||||
const r = Router();
|
const r = Router();
|
||||||
|
|
||||||
r.post("/restart", async (_, res) => {
|
r.post("/", async (_, res) => {
|
||||||
await closePool();
|
await closePool();
|
||||||
|
|
||||||
await new Promise((r) => setTimeout(r, 2000));
|
await new Promise((r) => setTimeout(r, 2000));
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { connectProdSql } from "./prodSqlConnection.controller.js";
|
|||||||
|
|
||||||
const r = Router();
|
const r = Router();
|
||||||
|
|
||||||
r.post("/start", async (_, res) => {
|
r.post("/", async (_, res) => {
|
||||||
const connect = await connectProdSql();
|
const connect = await connectProdSql();
|
||||||
apiReturn(res, {
|
apiReturn(res, {
|
||||||
success: connect.success,
|
success: connect.success,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { closePool } from "./prodSqlConnection.controller.js";
|
|||||||
|
|
||||||
const r = Router();
|
const r = Router();
|
||||||
|
|
||||||
r.post("/stop", async (_, res) => {
|
r.post("/", async (_, res) => {
|
||||||
const connect = await closePool();
|
const connect = await closePool();
|
||||||
apiReturn(res, {
|
apiReturn(res, {
|
||||||
success: connect.success,
|
success: connect.success,
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ r.[ArticleHumanReadableId]
|
|||||||
,ea.JournalNummer as BOL_Number
|
,ea.JournalNummer as BOL_Number
|
||||||
,[ReleaseConfirmationState]
|
,[ReleaseConfirmationState]
|
||||||
,[PlanningState]
|
,[PlanningState]
|
||||||
--,format(r.[OrderDate], 'yyyy-MM-dd HH:mm') as OrderDate
|
,format(r.[OrderDate], 'yyyy-MM-dd HH:mm') as OrderDate
|
||||||
,r.[OrderDate]
|
--,r.[OrderDate]
|
||||||
--,FORMAT(r.[DeliveryDate], 'yyyy-MM-dd HH:mm') as DeliveryDate
|
,FORMAT(r.[DeliveryDate], 'yyyy-MM-dd HH:mm') as DeliveryDate
|
||||||
,r.[DeliveryDate]
|
--,r.[DeliveryDate]
|
||||||
--,FORMAT(r.[LoadingDate], 'yyyy-MM-dd HH:mm') as LoadingDate
|
,FORMAT(r.[LoadingDate], 'yyyy-MM-dd HH:mm') as LoadingDate
|
||||||
,r.[LoadingDate]
|
--,r.[LoadingDate]
|
||||||
,[Quantity]
|
,[Quantity]
|
||||||
,[DeliveredQuantity]
|
,[DeliveredQuantity]
|
||||||
,r.[AdditionalInformation1]
|
,r.[AdditionalInformation1]
|
||||||
@@ -66,9 +66,9 @@ ROW_NUMBER() OVER (PARTITION BY IdJournal ORDER BY add_date DESC) AS RowNum
|
|||||||
zz.IdLieferschein = ea.IdJournal
|
zz.IdLieferschein = ea.IdJournal
|
||||||
|
|
||||||
where
|
where
|
||||||
--r.ArticleHumanReadableId in ([articles])
|
|
||||||
--r.ReleaseNumber = 1452
|
--r.ReleaseNumber = 1452
|
||||||
|
|
||||||
r.DeliveryDate between @StartDate AND @EndDate
|
r.DeliveryDate between @StartDate AND @EndDate
|
||||||
and DeliveredQuantity > 0
|
and DeliveredQuantity > 0
|
||||||
|
--and r.ArticleHumanReadableId in ([articles])
|
||||||
--and Journalnummer = 169386
|
--and Journalnummer = 169386
|
||||||
@@ -21,9 +21,6 @@ ArticleHumanReadableId as article
|
|||||||
/** data mart include location data **/
|
/** data mart include location data **/
|
||||||
--,l.WarehouseDescription,l.LaneDescription
|
--,l.WarehouseDescription,l.LaneDescription
|
||||||
|
|
||||||
/** historical section **/
|
|
||||||
--,l.ProductionLotRunningNumber as lot,l.warehousehumanreadableid as warehouseId,l.WarehouseDescription as warehouseDescription,l.lanehumanreadableid as locationId,l.lanedescription as laneDescription
|
|
||||||
|
|
||||||
,articleTypeName
|
,articleTypeName
|
||||||
|
|
||||||
FROM [warehousing].[WarehouseUnit] as l (nolock)
|
FROM [warehousing].[WarehouseUnit] as l (nolock)
|
||||||
@@ -58,7 +55,4 @@ ArticleTypeName
|
|||||||
/** data mart include location data **/
|
/** data mart include location data **/
|
||||||
--,l.WarehouseDescription,l.LaneDescription
|
--,l.WarehouseDescription,l.LaneDescription
|
||||||
|
|
||||||
/** historical section **/
|
|
||||||
--,l.ProductionLotRunningNumber,l.warehousehumanreadableid,l.WarehouseDescription,l.lanehumanreadableid,l.lanedescription
|
|
||||||
|
|
||||||
order by ArticleHumanReadableId
|
order by ArticleHumanReadableId
|
||||||
48
backend/prodSql/queries/datamart.legacy.inventory.sql
Normal file
48
backend/prodSql/queries/datamart.legacy.inventory.sql
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
select
|
||||||
|
x.idartikelVarianten as article,
|
||||||
|
x.ArtikelVariantenAlias as alias
|
||||||
|
--x.Lfdnr as RunningNumber,
|
||||||
|
,round(sum(EinlagerungsMengeVPKSum),2) as total_pallets
|
||||||
|
,sum(EinlagerungsMengeSum) as total_palletQTY
|
||||||
|
,round(sum(VerfuegbareMengeVPKSum),0) as available_Pallets
|
||||||
|
,sum(VerfuegbareMengeSum) as available_QTY
|
||||||
|
,sum(case when c.Description LIKE '%COA%' then GesperrteMengeVPKSum else 0 end) as coa_Pallets
|
||||||
|
,sum(case when c.Description LIKE '%COA%' then GesperrteMengeSum else 0 end) as coa_QTY
|
||||||
|
,sum(case when c.Description NOT LIKE '%COA%' or x.IdMainDefect = -1 then GesperrteMengeVPKSum else 0 end) as held_Pallets
|
||||||
|
,sum(case when c.Description NOT LIKE '%COA%' or x.IdMainDefect = -1 then GesperrteMengeSum else 0 end) as held_QTY
|
||||||
|
,sum(case when x.WarenLagerLagerTyp = 8 then VerfuegbareMengeSum else 0 end) as consignment_qty
|
||||||
|
,IdProdPlanung as lot
|
||||||
|
----,IdAdressen,
|
||||||
|
,x.AdressBez
|
||||||
|
,x.IdLagerAbteilung as locationId
|
||||||
|
,x.LagerAbteilungKurzBez as laneDescription
|
||||||
|
,x.IdWarenlager as warehouseId
|
||||||
|
,x.WarenLagerKurzBez as warehouseDescription
|
||||||
|
--,*
|
||||||
|
from [AlplaPROD_test1].dbo.[V_LagerPositionenBarcodes] (nolock) x
|
||||||
|
|
||||||
|
left join
|
||||||
|
[AlplaPROD_test1].dbo.T_EtikettenGedruckt as l(nolock) on
|
||||||
|
x.Lfdnr = l.Lfdnr AND l.Lfdnr > 1
|
||||||
|
|
||||||
|
left join
|
||||||
|
|
||||||
|
(SELECT *
|
||||||
|
FROM [AlplaPROD_test1].[dbo].[T_BlockingDefects] where Active = 1) as c
|
||||||
|
on x.IdMainDefect = c.IdBlockingDefect
|
||||||
|
/*
|
||||||
|
The data below will be controlled by the user in excell by default everything will be passed over
|
||||||
|
IdAdressen = 3
|
||||||
|
*/
|
||||||
|
where /*IdArtikelTyp = 1 and */x.IdWarenlager not in (6, 1)
|
||||||
|
|
||||||
|
group by x.idartikelVarianten, ArtikelVariantenAlias, c.Description
|
||||||
|
--,IdAdressen
|
||||||
|
,x.AdressBez
|
||||||
|
,IdProdPlanung
|
||||||
|
,x.IdLagerAbteilung
|
||||||
|
,x.LagerAbteilungKurzBez
|
||||||
|
,x.IdWarenlager
|
||||||
|
,x.WarenLagerKurzBez
|
||||||
|
--, x.Lfdnr
|
||||||
|
order by x.IdArtikelVarianten
|
||||||
@@ -5,19 +5,75 @@ move this over to the delivery date range query once we have the shift data mapp
|
|||||||
|
|
||||||
update the psi stuff on this as well.
|
update the psi stuff on this as well.
|
||||||
**/
|
**/
|
||||||
declare @start_date nvarchar(30) = '[startDate]' --'2025-01-01'
|
DECLARE @StartDate DATE = '[startDate]' -- 2025-1-1
|
||||||
declare @end_date nvarchar(30) = '[endDate]' --'2025-08-09'
|
DECLARE @EndDate DATE = '[endDate]' -- 2025-1-31
|
||||||
|
SELECT
|
||||||
|
r.[ArticleHumanReadableId]
|
||||||
|
,[ReleaseNumber]
|
||||||
|
,h.CustomerOrderNumber
|
||||||
|
,x.CustomerLineItemNumber
|
||||||
|
,[CustomerReleaseNumber]
|
||||||
|
,[ReleaseState]
|
||||||
|
,[DeliveryState]
|
||||||
|
,ea.JournalNummer as BOL_Number
|
||||||
|
,[ReleaseConfirmationState]
|
||||||
|
,[PlanningState]
|
||||||
|
--,format(r.[OrderDate], 'yyyy-MM-dd HH:mm') as OrderDate
|
||||||
|
,r.[OrderDate]
|
||||||
|
--,FORMAT(r.[DeliveryDate], 'yyyy-MM-dd HH:mm') as DeliveryDate
|
||||||
|
,r.[DeliveryDate]
|
||||||
|
--,FORMAT(r.[LoadingDate], 'yyyy-MM-dd HH:mm') as LoadingDate
|
||||||
|
,r.[LoadingDate]
|
||||||
|
,[Quantity]
|
||||||
|
,[DeliveredQuantity]
|
||||||
|
,r.[AdditionalInformation1]
|
||||||
|
,r.[AdditionalInformation2]
|
||||||
|
,[TradeUnits]
|
||||||
|
,[LoadingUnits]
|
||||||
|
,[Trucks]
|
||||||
|
,[LoadingToleranceType]
|
||||||
|
,[SalesPrice]
|
||||||
|
,[Currency]
|
||||||
|
,[QuantityUnit]
|
||||||
|
,[SalesPriceRemark]
|
||||||
|
,r.[Remark]
|
||||||
|
,[Irradiated]
|
||||||
|
,r.[CreatedByEdi]
|
||||||
|
,[DeliveryAddressHumanReadableId]
|
||||||
|
,DeliveryAddressDescription
|
||||||
|
,[CustomerArtNo]
|
||||||
|
,[TotalPrice]
|
||||||
|
,r.[ArticleAlias]
|
||||||
|
|
||||||
|
FROM [order].[Release] (nolock) as r
|
||||||
|
|
||||||
select IdArtikelVarianten,
|
left join
|
||||||
ArtikelVariantenBez,
|
[order].LineItem as x on
|
||||||
sum(Menge) totalDelivered,
|
|
||||||
case when convert(time, upd_date) between '00:00' and '07:00' then convert(date, upd_date - 1) else convert(date, upd_date) end as ShippingDate
|
|
||||||
|
|
||||||
from dbo.V_LadePlanungenLadeAuftragAbruf (nolock)
|
r.LineItemId = x.id
|
||||||
|
|
||||||
where upd_date between CONVERT(datetime, @start_date + ' 7:00') and CONVERT(datetime, @end_date + ' 7:00')
|
left join
|
||||||
and IdArtikelVarianten in ([articles])
|
[order].Header as h on
|
||||||
|
x.HeaderId = h.id
|
||||||
|
|
||||||
group by IdArtikelVarianten, upd_date,
|
--bol stuff
|
||||||
ArtikelVariantenBez
|
left join
|
||||||
|
AlplaPROD_test1.dbo.V_LadePlanungenLadeAuftragAbruf (nolock) as zz
|
||||||
|
on zz.AbrufIdAuftragsAbruf = r.ReleaseNumber
|
||||||
|
|
||||||
|
left join
|
||||||
|
(select * from (SELECT
|
||||||
|
ROW_NUMBER() OVER (PARTITION BY IdJournal ORDER BY add_date DESC) AS RowNum
|
||||||
|
,*
|
||||||
|
FROM [AlplaPROD_test1].[dbo].[T_Lieferungen] (nolock)) x
|
||||||
|
|
||||||
|
where RowNum = 1) as ea on
|
||||||
|
zz.IdLieferschein = ea.IdJournal
|
||||||
|
|
||||||
|
where
|
||||||
|
r.ArticleHumanReadableId in ([articles])
|
||||||
|
--r.ReleaseNumber = 1452
|
||||||
|
|
||||||
|
and r.DeliveryDate between @StartDate AND @EndDate
|
||||||
|
--and DeliveredQuantity > 0
|
||||||
|
--and Journalnummer = 169386
|
||||||
|
|||||||
21
backend/prodSql/queries/disableJob.sql
Normal file
21
backend/prodSql/queries/disableJob.sql
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
disables sql jobs.
|
||||||
|
*/
|
||||||
|
--EXEC msdb.dbo.sp_update_job @job_name = N'[jobName]', @enabled = 0;
|
||||||
|
-- DECLARE @JobName varchar(max) = '[jobName]'
|
||||||
|
-- UPDATE msdb.dbo.sysjobs
|
||||||
|
-- SET enabled = 0
|
||||||
|
-- WHERE name = @JobName;
|
||||||
|
|
||||||
|
DECLARE @JobName NVARCHAR(128) = N'[jobName]';
|
||||||
|
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM msdb.dbo.sysjobs
|
||||||
|
WHERE name = @JobName
|
||||||
|
)
|
||||||
|
BEGIN
|
||||||
|
EXEC msdb.dbo.sp_update_job
|
||||||
|
@job_name = @JobName,
|
||||||
|
@enabled = 0;
|
||||||
|
END
|
||||||
11
backend/prodSql/queries/featureCheck.sql
Normal file
11
backend/prodSql/queries/featureCheck.sql
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
SELECT count(*) as activated
|
||||||
|
FROM [test1_AlplaPROD2.0_Read].[support].[FeatureActivation]
|
||||||
|
|
||||||
|
where feature in (108,7)
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
as more features get activated and need to have this checked to include the new endpoints add here so we can check this.
|
||||||
|
108 = waste
|
||||||
|
7 = warehousing
|
||||||
|
*/
|
||||||
34
backend/prodSql/queries/opendock.addressLink.sql
Normal file
34
backend/prodSql/queries/opendock.addressLink.sql
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
This will return all address with a sales price.
|
||||||
|
*/
|
||||||
|
WITH ranked AS (
|
||||||
|
SELECT
|
||||||
|
av.id,
|
||||||
|
av.humanReadableId as av,
|
||||||
|
av.Alias as description,
|
||||||
|
-- CONCAT(ad.HumanReadableId, ' - ',ad.Name) as customer ,
|
||||||
|
ad.HumanReadableId as customer,
|
||||||
|
ad.Name as customerDescription,
|
||||||
|
ROW_NUMBER() OVER (
|
||||||
|
PARTITION BY AddressId, sp.articleId
|
||||||
|
ORDER BY ValidAfter DESC
|
||||||
|
) AS rn
|
||||||
|
FROM [test1_AlplaPROD2.0_Read].[masterData].[SalesPrice] as sp (nolock)
|
||||||
|
|
||||||
|
/* av */
|
||||||
|
left join
|
||||||
|
[test1_AlplaPROD2.0_Read].[masterData].[Article] as av (nolock) on
|
||||||
|
av.id = sp.articleId
|
||||||
|
|
||||||
|
/* address */
|
||||||
|
left join
|
||||||
|
[test1_AlplaPROD2.0_Read].[masterData].[Address] as ad (nolock) on
|
||||||
|
ad.id = AddressId
|
||||||
|
|
||||||
|
)
|
||||||
|
SELECT *
|
||||||
|
FROM ranked
|
||||||
|
WHERE rn = 1
|
||||||
|
and ranked.av = '[articleCheck]'
|
||||||
|
|
||||||
|
order by customerDescription
|
||||||
@@ -21,7 +21,7 @@ SELECT
|
|||||||
,[MainMaterialId]
|
,[MainMaterialId]
|
||||||
,[MainMaterialHumanReadableId]
|
,[MainMaterialHumanReadableId]
|
||||||
,[MainMaterialDescription]
|
,[MainMaterialDescription]
|
||||||
,[AdditionalInformation1]
|
,[AdditionalInformation1] -- we will use this to reference as the first check
|
||||||
,[AdditionalInformation2]
|
,[AdditionalInformation2]
|
||||||
,[D365SupplierLot]
|
,[D365SupplierLot]
|
||||||
,[TradeUnits]
|
,[TradeUnits]
|
||||||
@@ -47,9 +47,9 @@ SELECT
|
|||||||
,[PaymentTermsId]
|
,[PaymentTermsId]
|
||||||
,[PaymentTermsHumanReadableId]
|
,[PaymentTermsHumanReadableId]
|
||||||
,[PaymentTermsDescription]
|
,[PaymentTermsDescription]
|
||||||
,[Remark]
|
,[Remark]
|
||||||
,[DeliveryAddressId]
|
,[DeliveryAddressId]
|
||||||
,[DeliveryAddressHumanReadableId]
|
,[DeliveryAddressHumanReadableId] --use this to validate with the new drop or live check
|
||||||
,[DeliveryAddressDescription]
|
,[DeliveryAddressDescription]
|
||||||
,[DeliveryStreetName]
|
,[DeliveryStreetName]
|
||||||
,[DeliveryAddressZip]
|
,[DeliveryAddressZip]
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
use [test1_AlplaPROD2.0_Read]
|
use [test1_AlplaPROD2.0_Read]
|
||||||
|
|
||||||
SELECT
|
SELECT
|
||||||
--JSON_VALUE(content, '$.EntityId') as labelId
|
JSON_VALUE(content, '$.EntityId') as labelId,
|
||||||
a.id
|
a.id
|
||||||
,ActorName
|
,ActorName
|
||||||
,FORMAT(PrintDate, 'yyyy-MM-dd HH:mm') as printDate
|
--,FORMAT(l.PrintDate, 'yyyy-MM-dd HH:mm') as printDate
|
||||||
|
,Format(COALESCE(l.PrintDate, e.ProductionDate), 'yyyy-MM-dd HH:mm') as printDate
|
||||||
,FORMAT(CreatedDateTime, 'yyyy-MM-dd HH:mm') createdDateTime
|
,FORMAT(CreatedDateTime, 'yyyy-MM-dd HH:mm') createdDateTime
|
||||||
,l.ArticleHumanReadableId as av
|
,COALESCE(l.ArticleHumanReadableId,e.ArticleHumanReadableId) as av
|
||||||
,l.ArticleDescription as alias
|
,COALESCE(l.ArticleDescription, av.Name) as alias
|
||||||
,PrintedCopies
|
,COALESCE(l.PrintedCopies, 0) as PrintedCopies
|
||||||
,p.name as printerName
|
,COALESCE(p.name,'External Label not tracked') as printerName
|
||||||
,RunningNumber
|
,COALESCE(l.RunningNumber, e.RunningNumber) as runningNumber
|
||||||
--,*
|
--,*
|
||||||
FROM [support].[AuditLog] (nolock) as a
|
FROM [support].[AuditLog] (nolock) as a
|
||||||
|
|
||||||
@@ -18,10 +19,20 @@ left join
|
|||||||
[labelling].[InternalLabel] (nolock) as l on
|
[labelling].[InternalLabel] (nolock) as l on
|
||||||
l.id = JSON_VALUE(content, '$.EntityId')
|
l.id = JSON_VALUE(content, '$.EntityId')
|
||||||
|
|
||||||
|
OUTER APPLY (
|
||||||
|
SELECT TOP 1 *
|
||||||
|
FROM labelling.ExternalLabel e
|
||||||
|
WHERE e.id = JSON_VALUE(a.content, '$.EntityId')
|
||||||
|
ORDER BY e.Id DESC
|
||||||
|
) e
|
||||||
|
|
||||||
left join
|
left join
|
||||||
[masterData].[printer] (nolock) as p on
|
[masterData].[printer] (nolock) as p on
|
||||||
p.id = l.PrinterId
|
p.id = l.PrinterId
|
||||||
|
|
||||||
|
left join
|
||||||
|
[masterData].[article] (nolock) as av on
|
||||||
|
av.HumanReadableId = e.ArticleHumanReadableId
|
||||||
where message like '%reprint%'
|
where message like '%reprint%'
|
||||||
and CreatedDateTime > DATEADD(minute, -[intervalCheck], SYSDATETIMEOFFSET())
|
and CreatedDateTime > DATEADD(minute, -[intervalCheck], SYSDATETIMEOFFSET())
|
||||||
and a.id > [ignoreList]
|
and a.id > [ignoreList]
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export const monitorAlplaPurchase = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (purchaseMonitor[0]?.active) {
|
if (purchaseMonitor[0]?.active) {
|
||||||
createCronJob("purchaseMonitor", "0 */5 * * * *", async () => {
|
createCronJob("purchaseMonitor", "0 5 * * * *", async () => {
|
||||||
try {
|
try {
|
||||||
const result = await prodQuery(
|
const result = await prodQuery(
|
||||||
sqlQuery.query.replace(
|
sqlQuery.query.replace(
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import type { Express } from "express";
|
import type { Express } from "express";
|
||||||
|
import { setupAdminRoutes } from "./admin/admin.routes.js";
|
||||||
import { setupAuthRoutes } from "./auth/auth.routes.js";
|
import { setupAuthRoutes } from "./auth/auth.routes.js";
|
||||||
// import the routes and route setups
|
// import the routes and route setups
|
||||||
import { setupApiDocsRoutes } from "./configs/scaler.config.js";
|
import { setupApiDocsRoutes } from "./configs/scaler.config.js";
|
||||||
import { setupDatamartRoutes } from "./datamart/datamart.routes.js";
|
import { setupDatamartRoutes } from "./datamart/datamart.routes.js";
|
||||||
import { setupGPSqlRoutes } from "./gpSql/gpSql.routes.js";
|
import { setupGPSqlRoutes } from "./gpSql/gpSql.routes.js";
|
||||||
|
import { setupMobileRoutes } from "./mobile/mobile.routes.js";
|
||||||
import { setupNotificationRoutes } from "./notification/notification.routes.js";
|
import { setupNotificationRoutes } from "./notification/notification.routes.js";
|
||||||
import { setupOCPRoutes } from "./ocp/ocp.routes.js";
|
import { setupOCPRoutes } from "./ocp/ocp.routes.js";
|
||||||
import { setupOpendockRoutes } from "./opendock/opendock.routes.js";
|
import { setupOpendockRoutes } from "./opendock/opendock.routes.js";
|
||||||
@@ -15,7 +16,9 @@ import { setupUtilsRoutes } from "./utils/utils.routes.js";
|
|||||||
|
|
||||||
export const setupRoutes = (baseUrl: string, app: Express) => {
|
export const setupRoutes = (baseUrl: string, app: Express) => {
|
||||||
//routes that are on by default
|
//routes that are on by default
|
||||||
|
setupMobileRoutes(baseUrl, app);
|
||||||
setupSystemRoutes(baseUrl, app);
|
setupSystemRoutes(baseUrl, app);
|
||||||
|
setupAdminRoutes(baseUrl, app);
|
||||||
setupApiDocsRoutes(baseUrl, app);
|
setupApiDocsRoutes(baseUrl, app);
|
||||||
setupProdSqlRoutes(baseUrl, app);
|
setupProdSqlRoutes(baseUrl, app);
|
||||||
setupGPSqlRoutes(baseUrl, app);
|
setupGPSqlRoutes(baseUrl, app);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { connectGPSql } from "./gpSql/gpSqlConnection.controller.js";
|
|||||||
import { createLogger } from "./logger/logger.controller.js";
|
import { createLogger } from "./logger/logger.controller.js";
|
||||||
import { historicalSchedule } from "./logistics/logistics.historicalInv.js";
|
import { historicalSchedule } from "./logistics/logistics.historicalInv.js";
|
||||||
import { startNotifications } from "./notification/notification.controller.js";
|
import { startNotifications } from "./notification/notification.controller.js";
|
||||||
|
import { sqlJobCleanUp } from "./notification/notification.SqlJobCleanUp.js";
|
||||||
import { createNotifications } from "./notification/notifications.master.js";
|
import { createNotifications } from "./notification/notifications.master.js";
|
||||||
import { printerSync } from "./ocp/ocp.printer.manage.js";
|
import { printerSync } from "./ocp/ocp.printer.manage.js";
|
||||||
import { monitorReleaseChanges } from "./opendock/openDockRreleaseMonitor.utils.js";
|
import { monitorReleaseChanges } from "./opendock/openDockRreleaseMonitor.utils.js";
|
||||||
@@ -15,8 +16,14 @@ import { opendockSocketMonitor } from "./opendock/opendockSocketMonitor.utils.js
|
|||||||
import { connectProdSql } from "./prodSql/prodSqlConnection.controller.js";
|
import { connectProdSql } from "./prodSql/prodSqlConnection.controller.js";
|
||||||
import { monitorAlplaPurchase } from "./purchase/purchase.controller.js";
|
import { monitorAlplaPurchase } from "./purchase/purchase.controller.js";
|
||||||
import { setupSocketIORoutes } from "./socket.io/serverSetup.js";
|
import { setupSocketIORoutes } from "./socket.io/serverSetup.js";
|
||||||
|
import { serversChecks } from "./system/serverData.controller.js";
|
||||||
import { baseSettingValidationCheck } from "./system/settingsBase.controller.js";
|
import { baseSettingValidationCheck } from "./system/settingsBase.controller.js";
|
||||||
import { startTCPServer } from "./tcpServer/tcp.server.js";
|
import { startTCPServer } from "./tcpServer/tcp.server.js";
|
||||||
|
import {
|
||||||
|
aggregateRouteHitsForBusinessDay,
|
||||||
|
cleanupOldRouteHits,
|
||||||
|
runRouteHitAnalyticsCron,
|
||||||
|
} from "./utils/analyticRouteHits.utils.js";
|
||||||
import { createCronJob } from "./utils/croner.utils.js";
|
import { createCronJob } from "./utils/croner.utils.js";
|
||||||
import { sendEmail } from "./utils/sendEmail.utils.js";
|
import { sendEmail } from "./utils/sendEmail.utils.js";
|
||||||
|
|
||||||
@@ -67,9 +74,19 @@ const start = async () => {
|
|||||||
createCronJob("logsCleanup", "0 15 5 * * *", () => dbCleanup("logs", 120));
|
createCronJob("logsCleanup", "0 15 5 * * *", () => dbCleanup("logs", 120));
|
||||||
historicalSchedule();
|
historicalSchedule();
|
||||||
|
|
||||||
|
createCronJob("aggregateHits", "0 0 7 * * *", async () =>
|
||||||
|
runRouteHitAnalyticsCron(),
|
||||||
|
);
|
||||||
|
|
||||||
|
createCronJob("cleanHitsUp", "0 0 7 * * *", () => cleanupOldRouteHits());
|
||||||
// one shots only needed to run on server startups
|
// one shots only needed to run on server startups
|
||||||
createNotifications();
|
createNotifications();
|
||||||
startNotifications();
|
startNotifications();
|
||||||
|
serversChecks();
|
||||||
|
aggregateRouteHitsForBusinessDay();
|
||||||
|
|
||||||
|
// can be removed at a later date
|
||||||
|
sqlJobCleanUp();
|
||||||
}, 5 * 1000);
|
}, 5 * 1000);
|
||||||
|
|
||||||
process.on("uncaughtException", async (err) => {
|
process.on("uncaughtException", async (err) => {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ type RoomDefinition<T = unknown> = {
|
|||||||
|
|
||||||
export const protectedRooms: any = {
|
export const protectedRooms: any = {
|
||||||
logs: { requiresAuth: true, role: ["admin", "systemAdmin"] },
|
logs: { requiresAuth: true, role: ["admin", "systemAdmin"] },
|
||||||
admin: { requiresAuth: true, role: ["admin", "systemAdmin"] },
|
//admin: { requiresAuth: false, role: ["admin", "systemAdmin"] },
|
||||||
};
|
};
|
||||||
|
|
||||||
export const roomDefinition: Record<RoomId, RoomDefinition> = {
|
export const roomDefinition: Record<RoomId, RoomDefinition> = {
|
||||||
@@ -36,4 +36,16 @@ export const roomDefinition: Record<RoomId, RoomDefinition> = {
|
|||||||
return [];
|
return [];
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
admin: {
|
||||||
|
seed: async (limit) => {
|
||||||
|
console.info(limit);
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"admin:build": {
|
||||||
|
seed: async (limit) => {
|
||||||
|
console.info(limit);
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -88,14 +88,12 @@ export const setupSocketIORoutes = (baseUrl: string, server: HttpServer) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const roles = Array.isArray(config.role) ? config.role : [config.role];
|
const roles = Array.isArray(config?.role) ? config?.role : [config?.role];
|
||||||
|
|
||||||
console.log(roles, s.user.role);
|
|
||||||
|
|
||||||
//if (config?.role && s.user?.role !== config.role) {
|
//if (config?.role && s.user?.role !== config.role) {
|
||||||
if (config?.role && !roles.includes(s.user?.role)) {
|
if (config?.role && !roles.includes(s.user?.role)) {
|
||||||
return s.emit("room-error", {
|
return s.emit("room-error", {
|
||||||
room: rn,
|
roomId: rn,
|
||||||
message: `Not authorized to be in room: ${rn}`,
|
message: `Not authorized to be in room: ${rn}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export type RoomId = "logs" | "labels"; //| "alerts" | "metrics";
|
export type RoomId = "logs" | "labels" | "admin" | "admin:build"; //| "alerts" | "metrics";
|
||||||
|
|||||||
205
backend/system/serverData.controller.ts
Normal file
205
backend/system/serverData.controller.ts
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
import { sql } from "drizzle-orm";
|
||||||
|
import { db } from "../db/db.controller.js";
|
||||||
|
import {
|
||||||
|
type NewServerData,
|
||||||
|
serverData,
|
||||||
|
} from "../db/schema/serverData.schema.js";
|
||||||
|
import { createLogger } from "../logger/logger.controller.js";
|
||||||
|
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||||
|
|
||||||
|
const servers: NewServerData[] = [
|
||||||
|
{
|
||||||
|
name: "Test server 1",
|
||||||
|
server: "USMCD1VMS036",
|
||||||
|
plantToken: "test3",
|
||||||
|
idAddress: "10.193.0.56",
|
||||||
|
greatPlainsPlantCode: "00",
|
||||||
|
contactEmail: "",
|
||||||
|
contactPhone: "",
|
||||||
|
serverLoc: "D$\\LST_V3",
|
||||||
|
buildNumber: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test server 2",
|
||||||
|
server: "USIOW1VMS036",
|
||||||
|
plantToken: "test2",
|
||||||
|
idAddress: "10.75.0.56",
|
||||||
|
greatPlainsPlantCode: "00",
|
||||||
|
contactEmail: "",
|
||||||
|
contactPhone: "",
|
||||||
|
serverLoc: "D$\\LST_V3",
|
||||||
|
buildNumber: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Lima",
|
||||||
|
server: "USLIM1VMS006",
|
||||||
|
plantToken: "uslim1",
|
||||||
|
idAddress: "10.53.0.26", // port opened 3000 2222
|
||||||
|
greatPlainsPlantCode: "50",
|
||||||
|
contactEmail: "",
|
||||||
|
contactPhone: "",
|
||||||
|
serverLoc: "D$\\LST_V3",
|
||||||
|
buildNumber: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Houston",
|
||||||
|
server: "ushou1VMS006",
|
||||||
|
plantToken: "ushou1",
|
||||||
|
idAddress: "10.195.0.26",
|
||||||
|
greatPlainsPlantCode: "20",
|
||||||
|
contactEmail: "",
|
||||||
|
contactPhone: "",
|
||||||
|
serverLoc: "D$\\LST_V3",
|
||||||
|
buildNumber: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Dayton",
|
||||||
|
server: "usday1VMS006",
|
||||||
|
plantToken: "usday1",
|
||||||
|
idAddress: "10.44.0.56", // ports opened 3000 and 2222
|
||||||
|
greatPlainsPlantCode: "80",
|
||||||
|
contactEmail: "",
|
||||||
|
contactPhone: "",
|
||||||
|
serverLoc: "D$\\LST_V3",
|
||||||
|
buildNumber: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "West Bend",
|
||||||
|
server: "usweb1VMS006",
|
||||||
|
plantToken: "usweb1",
|
||||||
|
idAddress: "10.80.0.26",
|
||||||
|
greatPlainsPlantCode: "65",
|
||||||
|
contactEmail: "",
|
||||||
|
contactPhone: "",
|
||||||
|
serverLoc: "D$\\LST_V3",
|
||||||
|
buildNumber: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Jeff City",
|
||||||
|
server: "usjci1VMS006",
|
||||||
|
plantToken: "usjci",
|
||||||
|
idAddress: "10.167.0.26",
|
||||||
|
greatPlainsPlantCode: "40",
|
||||||
|
contactEmail: "",
|
||||||
|
contactPhone: "",
|
||||||
|
serverLoc: "D$\\LST_V3",
|
||||||
|
buildNumber: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Sherman",
|
||||||
|
server: "usshe1vms006",
|
||||||
|
plantToken: "usshe1",
|
||||||
|
idAddress: "10.205.0.26",
|
||||||
|
greatPlainsPlantCode: "21",
|
||||||
|
contactEmail: "",
|
||||||
|
contactPhone: "",
|
||||||
|
serverLoc: "D$\\LST_V3",
|
||||||
|
buildNumber: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "McDonough",
|
||||||
|
server: "USMCD1VMS006",
|
||||||
|
plantToken: "usmcd1",
|
||||||
|
idAddress: "10.193.0.26",
|
||||||
|
greatPlainsPlantCode: "10",
|
||||||
|
contactEmail: "",
|
||||||
|
contactPhone: "",
|
||||||
|
serverLoc: "D$\\LST_V3",
|
||||||
|
buildNumber: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "St. Peters",
|
||||||
|
server: "USSTP1VMS006",
|
||||||
|
plantToken: "usstp1",
|
||||||
|
idAddress: "10.37.0.26",
|
||||||
|
greatPlainsPlantCode: "45",
|
||||||
|
contactEmail: "",
|
||||||
|
contactPhone: "",
|
||||||
|
serverLoc: "D$\\LST_V3",
|
||||||
|
buildNumber: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Marked Tree",
|
||||||
|
server: "USMAR1VMS006",
|
||||||
|
plantToken: "usmar1",
|
||||||
|
idAddress: "10.206.9.26", // 3000,2222 requested REQ0236838
|
||||||
|
greatPlainsPlantCode: "90",
|
||||||
|
contactEmail: "",
|
||||||
|
contactPhone: "",
|
||||||
|
serverLoc: "D$\\LST_V3",
|
||||||
|
buildNumber: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Iowa City EBM",
|
||||||
|
server: "USIOW1VMS006",
|
||||||
|
plantToken: "usiow1",
|
||||||
|
idAddress: "10.75.0.26",
|
||||||
|
greatPlainsPlantCode: "30",
|
||||||
|
contactEmail: "",
|
||||||
|
contactPhone: "",
|
||||||
|
serverLoc: "D$\\LST_V3",
|
||||||
|
buildNumber: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Bowling Green 1",
|
||||||
|
server: "USBOW1VMS006",
|
||||||
|
plantToken: "usbow1",
|
||||||
|
idAddress: "10.25.0.26", // 3000 is open REQ0236527 2222 already open
|
||||||
|
greatPlainsPlantCode: "55",
|
||||||
|
contactEmail: "",
|
||||||
|
contactPhone: "",
|
||||||
|
serverLoc: "D$\\LST_V3",
|
||||||
|
buildNumber: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Bethlehem",
|
||||||
|
server: "USBET1VMS006",
|
||||||
|
plantToken: "usbet1",
|
||||||
|
idAddress: "10.25.0.26",
|
||||||
|
greatPlainsPlantCode: "75",
|
||||||
|
contactEmail: "",
|
||||||
|
contactPhone: "",
|
||||||
|
serverLoc: "D$\\LST_V3",
|
||||||
|
buildNumber: 1,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// notes here for when it comes time to pull in all the server address info [test1_AlplaPROD2.0_Read].[masterData].[Plant] has everything from every server :D
|
||||||
|
export const serversChecks = async () => {
|
||||||
|
const log = createLogger({ module: "system", subModule: "serverData" });
|
||||||
|
const { data, error } = await tryCatch(
|
||||||
|
db
|
||||||
|
.insert(serverData)
|
||||||
|
.values(servers)
|
||||||
|
.onConflictDoUpdate({
|
||||||
|
target: serverData.plantToken,
|
||||||
|
set: {
|
||||||
|
server: sql`excluded.server`,
|
||||||
|
name: sql`excluded.name`,
|
||||||
|
idAddress: sql`excluded."id_address"`,
|
||||||
|
greatPlainsPlantCode: sql`excluded.great_plains_plant_code`,
|
||||||
|
contactEmail: sql`excluded."contact_email"`,
|
||||||
|
contactPhone: sql`excluded.contact_phone`,
|
||||||
|
serverLoc: sql`excluded.server_loc`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.returning(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
log.error(
|
||||||
|
{ error: error },
|
||||||
|
"There was an error when adding or updating the servers.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
log.info({}, "All Servers were added/updated");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Communication from logistic network to logisticsSupportTool (for printers and scanners)
|
||||||
|
|
||||||
|
// network justification
|
||||||
|
|
||||||
|
// scanners and printers are on dhcp
|
||||||
43
backend/system/serverData.route.ts
Normal file
43
backend/system/serverData.route.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { type Response, Router } from "express";
|
||||||
|
import { db } from "../db/db.controller.js";
|
||||||
|
import { serverData } from "../db/schema/serverData.schema.js";
|
||||||
|
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||||
|
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||||
|
|
||||||
|
// export const updateSetting = async (setting: Setting) => {
|
||||||
|
// // TODO: when the setting is a feature setting we will need to have it run each kill switch on the crons well just stop them and during a reset it just wont start them
|
||||||
|
// // TODO: when the setting is a system we will need to force an app restart
|
||||||
|
// // TODO: when the setting is standard we don't do anything.
|
||||||
|
// };
|
||||||
|
|
||||||
|
const r = Router();
|
||||||
|
|
||||||
|
r.get("/", async (_, res: Response) => {
|
||||||
|
const { data: sName, error: sError } = await tryCatch(
|
||||||
|
db.select().from(serverData).orderBy(serverData.name),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (sError) {
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "system",
|
||||||
|
subModule: "serverData",
|
||||||
|
message: `There was an error getting the servers `,
|
||||||
|
data: [sError],
|
||||||
|
status: 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: true,
|
||||||
|
level: "info",
|
||||||
|
module: "system",
|
||||||
|
subModule: "serverData",
|
||||||
|
message: `All current servers`,
|
||||||
|
data: sName ?? [],
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default r;
|
||||||
@@ -76,6 +76,16 @@ const newSettings: NewSetting[] = [
|
|||||||
roles: ["admin"],
|
roles: ["admin"],
|
||||||
seedVersion: 1,
|
seedVersion: 1,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "mobile",
|
||||||
|
value: "0",
|
||||||
|
active: false,
|
||||||
|
description: "LST Android Mobile app",
|
||||||
|
moduleName: "mobile",
|
||||||
|
settingType: "feature",
|
||||||
|
roles: ["admin"],
|
||||||
|
seedVersion: 1,
|
||||||
|
},
|
||||||
|
|
||||||
// standard settings
|
// standard settings
|
||||||
{
|
{
|
||||||
@@ -304,6 +314,60 @@ const newSettings: NewSetting[] = [
|
|||||||
roles: ["admin"],
|
roles: ["admin"],
|
||||||
seedVersion: 1,
|
seedVersion: 1,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "laneCheck",
|
||||||
|
value: "0",
|
||||||
|
active: false,
|
||||||
|
description:
|
||||||
|
"Allows the driver to scan a lane and see what is in the lane and details about each pallet.",
|
||||||
|
moduleName: "mobile",
|
||||||
|
settingType: "standard",
|
||||||
|
roles: ["admin"],
|
||||||
|
seedVersion: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dockScan",
|
||||||
|
value: "0",
|
||||||
|
active: false,
|
||||||
|
description:
|
||||||
|
"Enables dock door scanning, must have a dock scanner setup for this to work.",
|
||||||
|
moduleName: "mobile",
|
||||||
|
settingType: "standard",
|
||||||
|
roles: ["admin"],
|
||||||
|
seedVersion: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "cycleCounting",
|
||||||
|
value: "0",
|
||||||
|
active: false,
|
||||||
|
description: "Enables a cycle count to be triggered from the scanner.",
|
||||||
|
moduleName: "mobile",
|
||||||
|
settingType: "standard",
|
||||||
|
roles: ["admin"],
|
||||||
|
seedVersion: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "scannerIds",
|
||||||
|
value: "10",
|
||||||
|
active: false,
|
||||||
|
description:
|
||||||
|
"How many scanners ids are setup for this, there should be a lst_scanner instance created.",
|
||||||
|
moduleName: "mobile",
|
||||||
|
settingType: "standard",
|
||||||
|
roles: ["admin"],
|
||||||
|
seedVersion: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "defaultLoadType",
|
||||||
|
value: "drop",
|
||||||
|
active: false,
|
||||||
|
description:
|
||||||
|
"What is the default load type we will use for creating new apt: drop or live are the current options.",
|
||||||
|
moduleName: "opendock",
|
||||||
|
settingType: "standard",
|
||||||
|
roles: ["admin"],
|
||||||
|
seedVersion: 1,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const baseSettingValidationCheck = async () => {
|
export const baseSettingValidationCheck = async () => {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ router.get("/", async (_, res) => {
|
|||||||
? sqlServerStats?.data[0].UptimeSeconds
|
? sqlServerStats?.data[0].UptimeSeconds
|
||||||
: [],
|
: [],
|
||||||
eomFGPkgSheetVersion: 1, // this is the excel file version when we have a change to the macro we want to grab this
|
eomFGPkgSheetVersion: 1, // this is the excel file version when we have a change to the macro we want to grab this
|
||||||
masterMacroFile: 1,
|
masterMacroFile: 1.1,
|
||||||
tcpServerOnline: isServerRunning,
|
tcpServerOnline: isServerRunning,
|
||||||
sqlServerConnected: prodSql,
|
sqlServerConnected: prodSql,
|
||||||
gpServerConnected: gpSql,
|
gpServerConnected: gpSql,
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import type { Express } from "express";
|
import type { Express } from "express";
|
||||||
import { requireAuth } from "../middleware/auth.middleware.js";
|
import { requireAuth } from "../middleware/auth.middleware.js";
|
||||||
|
|
||||||
|
import getServers from "./serverData.route.js";
|
||||||
import getSettings from "./settings.route.js";
|
import getSettings from "./settings.route.js";
|
||||||
import updSetting from "./settingsUpdate.route.js";
|
import updSetting from "./settingsUpdate.route.js";
|
||||||
import stats from "./stats.route.js";
|
import stats from "./stats.route.js";
|
||||||
@@ -8,6 +10,7 @@ export const setupSystemRoutes = (baseUrl: string, app: Express) => {
|
|||||||
//stats will be like this as we dont need to change this
|
//stats will be like this as we dont need to change this
|
||||||
app.use(`${baseUrl}/api/stats`, stats);
|
app.use(`${baseUrl}/api/stats`, stats);
|
||||||
app.use(`${baseUrl}/api/settings`, getSettings);
|
app.use(`${baseUrl}/api/settings`, getSettings);
|
||||||
|
app.use(`${baseUrl}/api/servers`, getServers);
|
||||||
app.use(`${baseUrl}/api/settings`, requireAuth, updSetting);
|
app.use(`${baseUrl}/api/settings`, requireAuth, updSetting);
|
||||||
|
|
||||||
// all other system should be under /api/system/*
|
// all other system should be under /api/system/*
|
||||||
|
|||||||
@@ -1,14 +1,21 @@
|
|||||||
import type { Express } from "express";
|
import type { Express } from "express";
|
||||||
import { requireAuth } from "../middleware/auth.middleware.js";
|
import { requireAuth } from "../middleware/auth.middleware.js";
|
||||||
|
|
||||||
import restart from "./tcpRestart.route.js";
|
import restart from "./tcpRestart.route.js";
|
||||||
import start from "./tcpStart.route.js";
|
import start from "./tcpStart.route.js";
|
||||||
import stop from "./tcpStop.route.js";
|
import stop from "./tcpStop.route.js";
|
||||||
|
|
||||||
export const setupTCPRoutes = (baseUrl: string, app: Express) => {
|
export const setupTCPRoutes = (baseUrl: string, app: Express) => {
|
||||||
//stats will be like this as we dont need to change this
|
//stats will be like this as we dont need to change this
|
||||||
|
|
||||||
app.use(`${baseUrl}/api/tcp/start`, requireAuth, start);
|
app.use(`${baseUrl}/api/tcp/start`, requireAuth, start);
|
||||||
app.use(`${baseUrl}/api/tcp/stop`, requireAuth, stop);
|
app.use(`${baseUrl}/api/tcp/stop`, requireAuth, stop);
|
||||||
app.use(`${baseUrl}/api/tcp/restart`, requireAuth, restart);
|
app.use(
|
||||||
|
`${baseUrl}/api/tcp/restart`,
|
||||||
|
requireAuth,
|
||||||
|
|
||||||
|
restart,
|
||||||
|
);
|
||||||
|
|
||||||
// all other system should be under /api/system/*
|
// all other system should be under /api/system/*
|
||||||
};
|
};
|
||||||
|
|||||||
148
backend/utils/analyticRouteHits.utils.ts
Normal file
148
backend/utils/analyticRouteHits.utils.ts
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
import { and, count, countDistinct, gte, lt, sql } from "drizzle-orm";
|
||||||
|
import { db } from "../db/db.controller.js";
|
||||||
|
import { analytics } from "../db/schema/analytics.schema.js";
|
||||||
|
import { analyticsDaily } from "../db/schema/dailyAnalytics.schema.js";
|
||||||
|
|
||||||
|
export const ignoredRoutePrefixes = [
|
||||||
|
"/health",
|
||||||
|
"/favicon.ico",
|
||||||
|
"/socket.io",
|
||||||
|
"/lst/api/ws",
|
||||||
|
"/lst-config.js",
|
||||||
|
];
|
||||||
|
|
||||||
|
export function shouldIgnoreRoute(path: string) {
|
||||||
|
return ignoredRoutePrefixes.some((prefix) => path.startsWith(prefix));
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateRouteHitInput = {
|
||||||
|
method: string;
|
||||||
|
routePattern: string;
|
||||||
|
actualPath: string;
|
||||||
|
statusCode: number;
|
||||||
|
durationMs: number;
|
||||||
|
module?: string | null;
|
||||||
|
userId?: string | null;
|
||||||
|
userEmail?: string | null;
|
||||||
|
ipAddress?: string | null;
|
||||||
|
userAgent?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function createRouteHit(input: CreateRouteHitInput) {
|
||||||
|
await db.insert(analytics).values(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPreviousBusinessDayWindow(date = new Date()) {
|
||||||
|
const end = new Date(date);
|
||||||
|
end.setHours(7, 0, 0, 0);
|
||||||
|
|
||||||
|
const start = new Date(end);
|
||||||
|
start.setDate(start.getDate() - 1);
|
||||||
|
|
||||||
|
const businessDate = start.toISOString().slice(0, 10);
|
||||||
|
|
||||||
|
return {
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
businessDate,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function runRouteHitAnalyticsCron(): Promise<void> {
|
||||||
|
const result = await aggregateRouteHitsForBusinessDay();
|
||||||
|
|
||||||
|
await cleanupOldRouteHits();
|
||||||
|
|
||||||
|
console.log("Route hit analytics aggregated", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function aggregateRouteHitsForBusinessDay() {
|
||||||
|
const { start, end, businessDate } = getPreviousBusinessDayWindow();
|
||||||
|
|
||||||
|
const rows = await db
|
||||||
|
.select({
|
||||||
|
businessDate: sql<string>`CAST(${businessDate} AS date)`,
|
||||||
|
method: analytics.method,
|
||||||
|
routePattern: analytics.routePattern,
|
||||||
|
module: sql<string>`COALESCE(${analytics.module}, 'unknown')`,
|
||||||
|
|
||||||
|
totalHits: count(),
|
||||||
|
uniqueUsers: countDistinct(analytics.userId),
|
||||||
|
|
||||||
|
successCount: sql<number>`
|
||||||
|
COUNT(*) FILTER (WHERE ${analytics.statusCode} < 400)
|
||||||
|
`,
|
||||||
|
errorCount: sql<number>`
|
||||||
|
COUNT(*) FILTER (WHERE ${analytics.statusCode} >= 400)
|
||||||
|
`,
|
||||||
|
|
||||||
|
avgDurationMs: sql<number>`
|
||||||
|
COALESCE(ROUND(AVG(${analytics.durationMs})), 0)
|
||||||
|
`,
|
||||||
|
maxDurationMs: sql<number>`
|
||||||
|
COALESCE(MAX(${analytics.durationMs}), 0)
|
||||||
|
`,
|
||||||
|
|
||||||
|
firstHitAt: sql<Date>`
|
||||||
|
COALESCE(MIN(${analytics.createdAt}), NOW())
|
||||||
|
`,
|
||||||
|
lastHitAt: sql<Date>`
|
||||||
|
COALESCE(MAX(${analytics.createdAt}), NOW())
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
.from(analytics)
|
||||||
|
.where(and(gte(analytics.createdAt, start), lt(analytics.createdAt, end)))
|
||||||
|
.groupBy(
|
||||||
|
analytics.method,
|
||||||
|
analytics.routePattern,
|
||||||
|
sql`COALESCE(${analytics.module}, 'unknown')`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (rows.length === 0) {
|
||||||
|
return {
|
||||||
|
businessDate,
|
||||||
|
inserted: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const values = rows.map((row) => ({
|
||||||
|
...row,
|
||||||
|
businessDate: row.businessDate,
|
||||||
|
firstHitAt: new Date(row.firstHitAt),
|
||||||
|
lastHitAt: new Date(row.lastHitAt),
|
||||||
|
}));
|
||||||
|
|
||||||
|
await db
|
||||||
|
.insert(analyticsDaily)
|
||||||
|
.values(values)
|
||||||
|
.onConflictDoUpdate({
|
||||||
|
target: [
|
||||||
|
analyticsDaily.businessDate,
|
||||||
|
analyticsDaily.method,
|
||||||
|
analyticsDaily.routePattern,
|
||||||
|
analyticsDaily.module,
|
||||||
|
],
|
||||||
|
set: {
|
||||||
|
totalHits: sql`excluded.total_hits`,
|
||||||
|
uniqueUsers: sql`excluded.unique_users`,
|
||||||
|
successCount: sql`excluded.success_count`,
|
||||||
|
errorCount: sql`excluded.error_count`,
|
||||||
|
avgDurationMs: sql`excluded.avg_duration_ms`,
|
||||||
|
maxDurationMs: sql`excluded.max_duration_ms`,
|
||||||
|
firstHitAt: sql`excluded.first_hit_at`,
|
||||||
|
lastHitAt: sql`excluded.last_hit_at`,
|
||||||
|
updatedAt: sql`now()`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
businessDate,
|
||||||
|
inserted: rows.length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function cleanupOldRouteHits() {
|
||||||
|
await db
|
||||||
|
.delete(analytics)
|
||||||
|
.where(lt(analytics.createdAt, sql`now() - interval '4 days'`));
|
||||||
|
}
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
import { createAccessControl } from "better-auth/plugins/access";
|
import { createAccessControl } from "better-auth/plugins/access";
|
||||||
|
import { adminAc, defaultStatements } from "better-auth/plugins/admin/access";
|
||||||
|
|
||||||
export const statement = {
|
export const statement = {
|
||||||
app: ["read", "create", "share", "update", "delete", "readAll"],
|
...defaultStatements,
|
||||||
user: ["ban"],
|
app: ["read", "create", "update", "delete", "readAll"],
|
||||||
quality: ["read", "create", "share", "update", "delete", "readAll"],
|
quality: ["read", "create", "update", "delete", "readAll"],
|
||||||
notifications: ["read", "create", "share", "update", "delete", "readAll"],
|
logistics: ["read", "create", "update", "delete", "readAll"],
|
||||||
|
mobile: ["read", "create", "update", "delete", "readAll"],
|
||||||
|
openDock: ["read", "create", "update", "delete"],
|
||||||
|
notifications: ["read", "create", "update", "delete", "readAll"],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const ac = createAccessControl(statement);
|
export const ac = createAccessControl(statement);
|
||||||
@@ -12,15 +16,50 @@ export const ac = createAccessControl(statement);
|
|||||||
export const user = ac.newRole({
|
export const user = ac.newRole({
|
||||||
app: ["read", "create"],
|
app: ["read", "create"],
|
||||||
notifications: ["read", "create"],
|
notifications: ["read", "create"],
|
||||||
|
openDock: ["read"],
|
||||||
|
});
|
||||||
|
|
||||||
|
export const manager = ac.newRole({
|
||||||
|
app: ["read", "create", "update"],
|
||||||
|
mobile: ["read", "create", "update"],
|
||||||
|
openDock: ["read", "create", "update"],
|
||||||
|
});
|
||||||
|
|
||||||
|
export const transport = ac.newRole({
|
||||||
|
app: ["read", "create", "update"],
|
||||||
|
openDock: ["read", "create", "update"],
|
||||||
});
|
});
|
||||||
|
|
||||||
export const admin = ac.newRole({
|
export const admin = ac.newRole({
|
||||||
app: ["read", "create", "update"],
|
app: ["read", "create", "update"],
|
||||||
|
mobile: ["read", "create", "update"],
|
||||||
|
user: ["create", "update", "ban"],
|
||||||
|
openDock: ["read", "create", "update"],
|
||||||
});
|
});
|
||||||
|
|
||||||
export const systemAdmin = ac.newRole({
|
export const systemAdmin = ac.newRole({
|
||||||
app: ["read", "create", "share", "update", "delete", "readAll"],
|
...adminAc.statements,
|
||||||
user: ["ban"],
|
app: ["read", "create", "update", "delete", "readAll"],
|
||||||
quality: ["read", "create", "share", "update", "delete", "readAll"],
|
quality: ["read", "create", "update", "delete", "readAll"],
|
||||||
notifications: ["read", "create", "share", "update", "delete", "readAll"],
|
mobile: ["read", "create", "update", "delete", "readAll"],
|
||||||
|
logistics: ["read", "create", "update", "delete", "readAll"],
|
||||||
|
notifications: ["read", "create", "update", "delete", "readAll"],
|
||||||
|
openDock: ["read", "create", "update", "delete"],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* example usage
|
||||||
|
const canCreateProject = await authClient.admin.hasPermission({
|
||||||
|
permissions: {
|
||||||
|
project: ["create"],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// You can also check multiple resource permissions at the same time
|
||||||
|
const canCreateProjectAndCreateSale = await authClient.admin.hasPermission({
|
||||||
|
permissions: {
|
||||||
|
project: ["create"],
|
||||||
|
sale: ["create"]
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { betterAuth } from "better-auth";
|
|||||||
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
||||||
import {
|
import {
|
||||||
admin as adminPlugin,
|
admin as adminPlugin,
|
||||||
|
genericOAuth,
|
||||||
// apiKey,
|
// apiKey,
|
||||||
// createAuthMiddleware,
|
// createAuthMiddleware,
|
||||||
//customSession,
|
//customSession,
|
||||||
@@ -12,10 +13,50 @@ import {
|
|||||||
//import { eq } from "drizzle-orm";
|
//import { eq } from "drizzle-orm";
|
||||||
import { db } from "../db/db.controller.js";
|
import { db } from "../db/db.controller.js";
|
||||||
import * as rawSchema from "../db/schema/auth.schema.js";
|
import * as rawSchema from "../db/schema/auth.schema.js";
|
||||||
import { ac, admin, systemAdmin, user } from "./auth.permissions.js";
|
import { ac, admin, manager, systemAdmin, user } from "./auth.permissions.js";
|
||||||
import { allowedOrigins } from "./cors.utils.js";
|
import { allowedOrigins } from "./cors.utils.js";
|
||||||
import { sendEmail } from "./sendEmail.utils.js";
|
import { sendEmail } from "./sendEmail.utils.js";
|
||||||
|
|
||||||
|
function decodeJwtPayload<T = Record<string, unknown>>(jwt: string): T {
|
||||||
|
const parts = jwt.split(".");
|
||||||
|
if (parts.length < 2) {
|
||||||
|
throw new Error("Invalid JWT");
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = parts[1]?.replace(/-/g, "+").replace(/_/g, "/");
|
||||||
|
|
||||||
|
const padded = payload?.padEnd(
|
||||||
|
payload.length + ((4 - (payload.length % 4)) % 4),
|
||||||
|
"=",
|
||||||
|
);
|
||||||
|
|
||||||
|
const json = Buffer.from(padded ?? "", "base64").toString("utf8");
|
||||||
|
return JSON.parse(json) as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeGroups(groups?: unknown): string[] {
|
||||||
|
if (!Array.isArray(groups)) return [];
|
||||||
|
|
||||||
|
return groups
|
||||||
|
.filter((g): g is string => typeof g === "string")
|
||||||
|
.map((g) => g.trim().toLowerCase())
|
||||||
|
.filter((g) => g.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
type VoidAuthClaims = {
|
||||||
|
sub: string;
|
||||||
|
name?: string;
|
||||||
|
preferred_username?: string;
|
||||||
|
email?: string;
|
||||||
|
email_verified?: boolean;
|
||||||
|
groups?: string[];
|
||||||
|
picture?: string;
|
||||||
|
iss?: string;
|
||||||
|
aud?: string;
|
||||||
|
exp?: number;
|
||||||
|
iat?: number;
|
||||||
|
};
|
||||||
|
|
||||||
export const schema = {
|
export const schema = {
|
||||||
user: rawSchema.user,
|
user: rawSchema.user,
|
||||||
session: rawSchema.session,
|
session: rawSchema.session,
|
||||||
@@ -25,9 +66,73 @@ export const schema = {
|
|||||||
apiKey: rawSchema.apikey, // 🔑 rename to apiKey
|
apiKey: rawSchema.apikey, // 🔑 rename to apiKey
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const hasOAuth =
|
||||||
|
Boolean(process.env.PROVIDER) &&
|
||||||
|
Boolean(process.env.CLIENT_ID) &&
|
||||||
|
Boolean(process.env.CLIENT_SECRET) &&
|
||||||
|
Boolean(process.env.DISCOVERY_URL);
|
||||||
|
|
||||||
|
if (!hasOAuth) {
|
||||||
|
console.warn("Missing oauth data.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const oauthPlugins = hasOAuth
|
||||||
|
? [
|
||||||
|
genericOAuth({
|
||||||
|
config: [
|
||||||
|
{
|
||||||
|
providerId: process.env.PROVIDER!,
|
||||||
|
clientId: process.env.CLIENT_ID!,
|
||||||
|
clientSecret: process.env.CLIENT_SECRET!,
|
||||||
|
discoveryUrl: process.env.DISCOVERY_URL!,
|
||||||
|
scopes: (process.env.CLIENT_SCOPES ?? "")
|
||||||
|
.split(/[,\s]+/)
|
||||||
|
.filter(Boolean),
|
||||||
|
pkce: true,
|
||||||
|
requireIssuerValidation: true,
|
||||||
|
redirectURI: `${process.env.URL}/lst/api/auth/oauth2/callback/${process.env.PROVIDER!}`,
|
||||||
|
getUserInfo: async (tokens) => {
|
||||||
|
if (!tokens.idToken) {
|
||||||
|
throw new Error("VoidAuth did not return an idToken");
|
||||||
|
}
|
||||||
|
|
||||||
|
const claims = decodeJwtPayload<VoidAuthClaims>(tokens.idToken);
|
||||||
|
const groups = normalizeGroups(claims.groups);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: claims.sub,
|
||||||
|
email: claims.email ?? "",
|
||||||
|
name:
|
||||||
|
claims.name ??
|
||||||
|
claims.preferred_username ??
|
||||||
|
claims.email ??
|
||||||
|
"Unknown User",
|
||||||
|
image: claims.picture ?? null,
|
||||||
|
emailVerified: Boolean(claims.email_verified),
|
||||||
|
groups,
|
||||||
|
username: claims.preferred_username ?? null,
|
||||||
|
} as any;
|
||||||
|
},
|
||||||
|
|
||||||
|
mapProfileToUser: async (profile) => {
|
||||||
|
return {
|
||||||
|
name: profile.name,
|
||||||
|
role: profile.groups?.includes("lst_admins")
|
||||||
|
? "systemAdmin"
|
||||||
|
: profile.groups?.includes("admins")
|
||||||
|
? "admin"
|
||||||
|
: "user",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
appName: "lst",
|
appName: "lst",
|
||||||
baseURL: process.env.URL,
|
baseURL: `${process.env.URL}/lst/api/auth`,
|
||||||
database: drizzleAdapter(db, {
|
database: drizzleAdapter(db, {
|
||||||
provider: "pg",
|
provider: "pg",
|
||||||
schema,
|
schema,
|
||||||
@@ -42,6 +147,14 @@ export const auth = betterAuth({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
account: {
|
||||||
|
encryptOAuthTokens: true,
|
||||||
|
updateAccountOnSignIn: true,
|
||||||
|
accountLinking: {
|
||||||
|
enabled: true,
|
||||||
|
trustedProviders: ["voidauth"],
|
||||||
|
},
|
||||||
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
jwt({ jwt: { expirationTime: "1h" } }),
|
jwt({ jwt: { expirationTime: "1h" } }),
|
||||||
//apiKey(),
|
//apiKey(),
|
||||||
@@ -50,6 +163,7 @@ export const auth = betterAuth({
|
|||||||
roles: {
|
roles: {
|
||||||
admin,
|
admin,
|
||||||
user,
|
user,
|
||||||
|
manager,
|
||||||
systemAdmin,
|
systemAdmin,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@@ -63,6 +177,7 @@ export const auth = betterAuth({
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
...oauthPlugins,
|
||||||
|
|
||||||
// customSession(async ({ user, session }) => {
|
// customSession(async ({ user, session }) => {
|
||||||
// const roles = await db
|
// const roles = await db
|
||||||
@@ -121,7 +236,7 @@ export const auth = betterAuth({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
cookie: {
|
cookie: {
|
||||||
path: "/lst/app",
|
path: "/lst",
|
||||||
sameSite: "lax",
|
sameSite: "lax",
|
||||||
secure: false,
|
secure: false,
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
|
|||||||
91
backend/utils/build.utils.ts
Normal file
91
backend/utils/build.utils.ts
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import { spawn } from "node:child_process";
|
||||||
|
import { createLogger } from "../logger/logger.controller.js";
|
||||||
|
import { emitToRoom } from "../socket.io/roomEmitter.socket.js";
|
||||||
|
import { updateAppStats } from "./updateAppStats.utils.js";
|
||||||
|
import { zipBuild } from "./zipper.utils.js";
|
||||||
|
|
||||||
|
export const emitBuildLog = (message: string, level = "info") => {
|
||||||
|
const payload = {
|
||||||
|
type: "build",
|
||||||
|
level,
|
||||||
|
message,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
//console.log(`[BUILD][${level.toUpperCase()}] ${message}`);
|
||||||
|
|
||||||
|
emitToRoom("admin:build", payload as any);
|
||||||
|
if (payload.level === "info") {
|
||||||
|
log.info({ stack: payload }, payload.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (log) {
|
||||||
|
// log(payload);
|
||||||
|
// }
|
||||||
|
};
|
||||||
|
|
||||||
|
export let building = false;
|
||||||
|
const log = createLogger({ module: "utils", subModule: "builds" });
|
||||||
|
export const build = async () => {
|
||||||
|
const appDir = process.env.DEV_DIR ?? "";
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
building = true;
|
||||||
|
|
||||||
|
updateAppStats({
|
||||||
|
lastUpdated: new Date(),
|
||||||
|
building: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
emitBuildLog(`Starting build in: ${appDir}`);
|
||||||
|
|
||||||
|
const child = spawn("npm", ["run", "build"], {
|
||||||
|
cwd: appDir,
|
||||||
|
shell: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
child.stdout.on("data", (data) => {
|
||||||
|
const lines = data.toString().split(/\r?\n/);
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.trim() !== "") {
|
||||||
|
emitBuildLog(line, "info");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
child.stderr.on("data", (data) => {
|
||||||
|
const lines = data.toString().split(/\r?\n/);
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.trim() !== "") {
|
||||||
|
emitBuildLog(line, "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
child.on("close", (code) => {
|
||||||
|
if (code === 0) {
|
||||||
|
emitBuildLog("Build completed successfully.", "info");
|
||||||
|
building = false;
|
||||||
|
zipBuild();
|
||||||
|
resolve(true);
|
||||||
|
} else {
|
||||||
|
building = false;
|
||||||
|
updateAppStats({
|
||||||
|
lastUpdated: new Date(),
|
||||||
|
building: false,
|
||||||
|
});
|
||||||
|
emitBuildLog(`Build failed with code ${code}`, "error");
|
||||||
|
//reject(new Error(`Build failed with code ${code}`));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
child.on("error", (err) => {
|
||||||
|
building = false;
|
||||||
|
updateAppStats({
|
||||||
|
lastUpdated: new Date(),
|
||||||
|
building: false,
|
||||||
|
});
|
||||||
|
emitBuildLog(`Process error: ${err.message}`, "error");
|
||||||
|
// reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -9,6 +9,7 @@ export const allowedOrigins = [
|
|||||||
"http://localhost:4000",
|
"http://localhost:4000",
|
||||||
"http://localhost:4001",
|
"http://localhost:4001",
|
||||||
"http://localhost:5500",
|
"http://localhost:5500",
|
||||||
|
"http://localhost:8081",
|
||||||
"https://admin.socket.io",
|
"https://admin.socket.io",
|
||||||
"https://electron-socket-io-playground.vercel.app",
|
"https://electron-socket-io-playground.vercel.app",
|
||||||
`${process.env.URL}`,
|
`${process.env.URL}`,
|
||||||
@@ -16,6 +17,7 @@ export const allowedOrigins = [
|
|||||||
`http://${process.env.PROD_SERVER}:3100`, // temp
|
`http://${process.env.PROD_SERVER}:3100`, // temp
|
||||||
`http://usmcd1olp082:3000`,
|
`http://usmcd1olp082:3000`,
|
||||||
`${process.env.EXTERNAL_URL}`, // internal docker
|
`${process.env.EXTERNAL_URL}`, // internal docker
|
||||||
|
"chrome-extension://mddoackclclnbkmofficmmepfnadolfa",
|
||||||
];
|
];
|
||||||
export const lstCors = () => {
|
export const lstCors = () => {
|
||||||
return cors({
|
return cors({
|
||||||
|
|||||||
123
backend/utils/deployApp.ts
Normal file
123
backend/utils/deployApp.ts
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import { spawn } from "node:child_process";
|
||||||
|
import { eq, sql } from "drizzle-orm";
|
||||||
|
import { db } from "../db/db.controller.js";
|
||||||
|
import { serverData } from "../db/schema/serverData.schema.js";
|
||||||
|
import { appStats } from "../db/schema/stats.schema.js";
|
||||||
|
//import { createLogger } from "../logger/logger.controller.js";
|
||||||
|
import { emitBuildLog } from "./build.utils.js";
|
||||||
|
import { returnFunc } from "./returnHelper.utils.js";
|
||||||
|
|
||||||
|
// const log = createLogger({ module: "utils", subModule: "deploy" });
|
||||||
|
export let updating = false;
|
||||||
|
|
||||||
|
const updateServerBuildNumber = async (token: string) => {
|
||||||
|
// get the current build
|
||||||
|
const buildNum = await db.select().from(appStats);
|
||||||
|
|
||||||
|
// update the build now
|
||||||
|
|
||||||
|
await db
|
||||||
|
.update(serverData)
|
||||||
|
.set({ buildNumber: buildNum[0]?.currentBuild, lastUpdated: sql`NOW()` })
|
||||||
|
.where(eq(serverData.plantToken, token));
|
||||||
|
};
|
||||||
|
export const runUpdate = ({
|
||||||
|
server,
|
||||||
|
destination,
|
||||||
|
token,
|
||||||
|
}: {
|
||||||
|
server: string;
|
||||||
|
destination: string;
|
||||||
|
token: string;
|
||||||
|
}) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
updating = true;
|
||||||
|
const scriptPath = process.env.UPDATE_SCRIPT_PATH;
|
||||||
|
if (!scriptPath) {
|
||||||
|
return returnFunc({
|
||||||
|
success: true,
|
||||||
|
level: "error",
|
||||||
|
module: "utils",
|
||||||
|
subModule: "deploy",
|
||||||
|
message: "UPDATE_SCRIPT_PATH please make sure you have this set.",
|
||||||
|
data: [],
|
||||||
|
notify: true,
|
||||||
|
room: "admin",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const args = [
|
||||||
|
"-ExecutionPolicy",
|
||||||
|
"Bypass",
|
||||||
|
"-File",
|
||||||
|
scriptPath,
|
||||||
|
"-Server",
|
||||||
|
server,
|
||||||
|
"-Destination",
|
||||||
|
destination,
|
||||||
|
"-Token",
|
||||||
|
token,
|
||||||
|
"-ADM_USER",
|
||||||
|
process.env.DEV_USER ?? "",
|
||||||
|
"-ADM_PASSWORD",
|
||||||
|
process.env.DEV_PASSWORD ?? "",
|
||||||
|
"-AppDir",
|
||||||
|
process.env.DEV_DIR ?? "",
|
||||||
|
];
|
||||||
|
|
||||||
|
emitBuildLog(`Starting update for ${server}`);
|
||||||
|
|
||||||
|
const child = spawn("powershell.exe", args, {
|
||||||
|
shell: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
child.stdout.on("data", (data) => {
|
||||||
|
const lines = data.toString().split(/\r?\n/);
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.trim()) {
|
||||||
|
emitBuildLog(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
child.stderr.on("data", (data) => {
|
||||||
|
const lines = data.toString().split(/\r?\n/);
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.trim()) {
|
||||||
|
emitBuildLog(line, "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
child.on("close", (code) => {
|
||||||
|
if (code === 0) {
|
||||||
|
emitBuildLog(`Update completed for ${server}`);
|
||||||
|
updating = false;
|
||||||
|
updateServerBuildNumber(token);
|
||||||
|
resolve({
|
||||||
|
success: true,
|
||||||
|
message: `Update completed for ${server}`,
|
||||||
|
data: [],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
emitBuildLog(`Update failed for ${server} (code ${code})`, "error");
|
||||||
|
updating = false;
|
||||||
|
reject({
|
||||||
|
success: false,
|
||||||
|
message: `Update failed for ${server} (code ${code})`,
|
||||||
|
data: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
child.on("error", (err) => {
|
||||||
|
emitBuildLog(`Process error: ${err.message}`, "error");
|
||||||
|
updating = false;
|
||||||
|
reject({
|
||||||
|
success: false,
|
||||||
|
message: `${server}: Encountered an error while processing: ${err.message} `,
|
||||||
|
data: err,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
49
backend/utils/generateScannerPin.utils.ts
Normal file
49
backend/utils/generateScannerPin.utils.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { db } from "../db/db.controller.js";
|
||||||
|
import { returnFunc } from "./returnHelper.utils.js";
|
||||||
|
|
||||||
|
export function generateSixDigitPin() {
|
||||||
|
return Math.floor(100000 + Math.random() * 900000).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateUniquePin() {
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
const pin = generateSixDigitPin();
|
||||||
|
|
||||||
|
const existing = await db.query.scanUser.findFirst({
|
||||||
|
where: (u, { eq }) => eq(u.pinHash, pin),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existing)
|
||||||
|
return returnFunc({
|
||||||
|
success: true,
|
||||||
|
level: "info",
|
||||||
|
module: "utils",
|
||||||
|
subModule: "genPin",
|
||||||
|
message: "New pin generated",
|
||||||
|
data: [{ pin: pin }],
|
||||||
|
notify: false,
|
||||||
|
room: "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnFunc({
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "utils",
|
||||||
|
subModule: "genPin",
|
||||||
|
message: "Failed to generate unique PIN after 10 attempts",
|
||||||
|
data: [],
|
||||||
|
notify: true,
|
||||||
|
room: "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// export const pinExists = async (pin: string | number) => {
|
||||||
|
// const existing = await db.query.scanUser.findFirst({
|
||||||
|
// where: (u, { eq }) => eq(u.pinHash, pin),
|
||||||
|
// });
|
||||||
|
|
||||||
|
// if (!existing) return true;
|
||||||
|
|
||||||
|
// return false;
|
||||||
|
// };
|
||||||
@@ -14,7 +14,9 @@ export interface ReturnHelper<T = unknown[]> {
|
|||||||
| "email"
|
| "email"
|
||||||
| "purchase"
|
| "purchase"
|
||||||
| "tcp"
|
| "tcp"
|
||||||
| "logistics";
|
| "logistics"
|
||||||
|
| "admin"
|
||||||
|
| "mobile";
|
||||||
subModule: string;
|
subModule: string;
|
||||||
|
|
||||||
level: "info" | "error" | "debug" | "fatal" | "warn";
|
level: "info" | "error" | "debug" | "fatal" | "warn";
|
||||||
|
|||||||
61
backend/utils/umami.utils.ts
Normal file
61
backend/utils/umami.utils.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { isUmamiEnabled, umamiConfig } from "../configs/umami.config.js";
|
||||||
|
|
||||||
|
type TrackLstEventInput = {
|
||||||
|
eventName: string;
|
||||||
|
eventData?: Record<string, unknown>;
|
||||||
|
url?: string;
|
||||||
|
hostname?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function trackLstEvent({
|
||||||
|
eventName,
|
||||||
|
eventData,
|
||||||
|
url = "/backend",
|
||||||
|
hostname = umamiConfig.server,
|
||||||
|
}: TrackLstEventInput): Promise<void> {
|
||||||
|
if (!isUmamiEnabled()) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fetch(`${umamiConfig.umamiHost}/api/send`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"User-Agent": "LST-Backend",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
type: "event",
|
||||||
|
payload: {
|
||||||
|
website: umamiConfig.umamiWebsiteId,
|
||||||
|
name: eventName,
|
||||||
|
url,
|
||||||
|
hostname,
|
||||||
|
language: "en-US",
|
||||||
|
screen: "backend",
|
||||||
|
data: {
|
||||||
|
app: umamiConfig.appName,
|
||||||
|
site: umamiConfig.site,
|
||||||
|
server: umamiConfig.server,
|
||||||
|
appVersion: umamiConfig.appVersion,
|
||||||
|
source: "backend",
|
||||||
|
...eventData,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to send Umami backend event", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
await trackLstEvent({
|
||||||
|
eventName: "label_print_completed",
|
||||||
|
url: "/backend/printers",
|
||||||
|
eventData: {
|
||||||
|
module: "printers",
|
||||||
|
printerName,
|
||||||
|
labelType,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
*/
|
||||||
17
backend/utils/updateAppStats.utils.ts
Normal file
17
backend/utils/updateAppStats.utils.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { db } from "../db/db.controller.js";
|
||||||
|
import { appStats } from "../db/schema/stats.schema.js";
|
||||||
|
|
||||||
|
export const updateAppStats = async (
|
||||||
|
data: Partial<typeof appStats.$inferInsert>,
|
||||||
|
) => {
|
||||||
|
await db
|
||||||
|
.insert(appStats)
|
||||||
|
.values({
|
||||||
|
id: "primary",
|
||||||
|
...data,
|
||||||
|
})
|
||||||
|
.onConflictDoUpdate({
|
||||||
|
target: appStats.id,
|
||||||
|
set: data,
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { Express } from "express";
|
import type { Express } from "express";
|
||||||
|
|
||||||
import getActiveJobs from "./cronerActiveJobs.route.js";
|
import getActiveJobs from "./cronerActiveJobs.route.js";
|
||||||
import jobStatusChange from "./cronerStatusChange.route.js";
|
import jobStatusChange from "./cronerStatusChange.route.js";
|
||||||
export const setupUtilsRoutes = (baseUrl: string, app: Express) => {
|
export const setupUtilsRoutes = (baseUrl: string, app: Express) => {
|
||||||
|
|||||||
177
backend/utils/zipper.utils.ts
Normal file
177
backend/utils/zipper.utils.ts
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
import fs from "node:fs";
|
||||||
|
import fsp from "node:fs/promises";
|
||||||
|
import path from "node:path";
|
||||||
|
import archiver from "archiver";
|
||||||
|
import { createLogger } from "../logger/logger.controller.js";
|
||||||
|
import { emitBuildLog } from "./build.utils.js";
|
||||||
|
import { updateAppStats } from "./updateAppStats.utils.js";
|
||||||
|
|
||||||
|
const log = createLogger({ module: "utils", subModule: "zip" });
|
||||||
|
|
||||||
|
const exists = async (target: string) => {
|
||||||
|
try {
|
||||||
|
await fsp.access(target);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getNextBuildNumber = async (buildNumberFile: string) => {
|
||||||
|
if (!(await exists(buildNumberFile))) {
|
||||||
|
await fsp.writeFile(buildNumberFile, "1", "utf8");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const raw = await fsp.readFile(buildNumberFile, "utf8");
|
||||||
|
const current = Number.parseInt(raw.trim(), 10);
|
||||||
|
|
||||||
|
if (Number.isNaN(current) || current < 1) {
|
||||||
|
await fsp.writeFile(buildNumberFile, "1", "utf8");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const next = current + 1;
|
||||||
|
|
||||||
|
await fsp.writeFile(buildNumberFile, String(next), "utf8");
|
||||||
|
|
||||||
|
// update the server with the next build number
|
||||||
|
|
||||||
|
await updateAppStats({
|
||||||
|
currentBuild: next,
|
||||||
|
lastBuildAt: new Date(),
|
||||||
|
building: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
|
||||||
|
const cleanupOldBuilds = async (buildFolder: string, maxBuilds: number) => {
|
||||||
|
const entries = await fsp.readdir(buildFolder, { withFileTypes: true });
|
||||||
|
|
||||||
|
const zipFiles: { fullPath: string; name: string; mtimeMs: number }[] = [];
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (!entry.isFile()) continue;
|
||||||
|
if (!/^LSTV3-\d+\.zip$/i.test(entry.name)) continue;
|
||||||
|
|
||||||
|
const fullPath = path.join(buildFolder, entry.name);
|
||||||
|
const stat = await fsp.stat(fullPath);
|
||||||
|
|
||||||
|
zipFiles.push({
|
||||||
|
fullPath,
|
||||||
|
name: entry.name,
|
||||||
|
mtimeMs: stat.mtimeMs,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
zipFiles.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
||||||
|
|
||||||
|
const toRemove = zipFiles.slice(maxBuilds);
|
||||||
|
|
||||||
|
for (const file of toRemove) {
|
||||||
|
await fsp.rm(file.fullPath, { force: true });
|
||||||
|
emitBuildLog(`Removed old build: ${file.name}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const zipBuild = async () => {
|
||||||
|
const appDir = process.env.DEV_DIR ?? "";
|
||||||
|
const maxBuilds = Number(process.env.MAX_BUILDS ?? 5);
|
||||||
|
|
||||||
|
if (!appDir) {
|
||||||
|
log.error({ notify: true }, "Forgot to add in the dev dir into the env");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const includesFile = path.join(appDir, ".includes");
|
||||||
|
const buildNumberFile = path.join(appDir, ".buildNumber");
|
||||||
|
const buildFolder = path.join(appDir, "builds");
|
||||||
|
const tempFolder = path.join(appDir, "temp", "zip-temp");
|
||||||
|
if (!(await exists(includesFile))) {
|
||||||
|
log.error({ notify: true }, "Missing .includes file common");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await fsp.mkdir(buildFolder, { recursive: true });
|
||||||
|
|
||||||
|
const buildNumber = await getNextBuildNumber(buildNumberFile);
|
||||||
|
const zipFileName = `LSTV3-${buildNumber}.zip`;
|
||||||
|
const zipFile = path.join(buildFolder, zipFileName);
|
||||||
|
// make the folders in case they are not created already
|
||||||
|
emitBuildLog(`Using build number: ${buildNumber}`);
|
||||||
|
|
||||||
|
if (await exists(tempFolder)) {
|
||||||
|
await fsp.rm(tempFolder, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
await fsp.mkdir(tempFolder, { recursive: true });
|
||||||
|
|
||||||
|
const includes = (await fsp.readFile(includesFile, "utf8"))
|
||||||
|
.split(/\r?\n/)
|
||||||
|
.map((line) => line.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
emitBuildLog(`Preparing zip from ${includes.length} include entries`);
|
||||||
|
|
||||||
|
for (const relPath of includes) {
|
||||||
|
const source = path.join(appDir, relPath);
|
||||||
|
const dest = path.join(tempFolder, relPath);
|
||||||
|
|
||||||
|
if (!(await exists(source))) {
|
||||||
|
emitBuildLog(`Skipping missing path: ${relPath}`, "error");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stat = await fsp.stat(source);
|
||||||
|
await fsp.mkdir(path.dirname(dest), { recursive: true });
|
||||||
|
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
emitBuildLog(`Copying folder: ${relPath}`);
|
||||||
|
await fsp.cp(source, dest, { recursive: true });
|
||||||
|
} else {
|
||||||
|
emitBuildLog(`Copying file: ${relPath}`);
|
||||||
|
await fsp.copyFile(source, dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if something crazy happens and we get the same build lets just reuse it
|
||||||
|
// if (await exists(zipFile)) {
|
||||||
|
// await fsp.rm(zipFile, { force: true });
|
||||||
|
// }
|
||||||
|
|
||||||
|
emitBuildLog(`Creating zip: ${zipFile}`);
|
||||||
|
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
const output = fs.createWriteStream(zipFile);
|
||||||
|
const archive = archiver("zip", { zlib: { level: 9 } });
|
||||||
|
|
||||||
|
output.on("close", () => resolve());
|
||||||
|
output.on("error", reject);
|
||||||
|
archive.on("error", reject);
|
||||||
|
|
||||||
|
archive.pipe(output);
|
||||||
|
|
||||||
|
// zip contents of temp folder, not temp folder itself
|
||||||
|
archive.directory(tempFolder, false);
|
||||||
|
archive.finalize();
|
||||||
|
});
|
||||||
|
|
||||||
|
await fsp.rm(tempFolder, { recursive: true, force: true });
|
||||||
|
|
||||||
|
emitBuildLog(`Zip completed successfully: ${zipFile}`);
|
||||||
|
|
||||||
|
await cleanupOldBuilds(buildFolder, maxBuilds);
|
||||||
|
|
||||||
|
await updateAppStats({
|
||||||
|
lastUpdated: new Date(),
|
||||||
|
building: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
buildNumber,
|
||||||
|
zipFile,
|
||||||
|
zipFileName,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Login
|
|
||||||
type: http
|
|
||||||
seq: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
post {
|
|
||||||
url: {{url}}/api/auth/sign-in/email
|
|
||||||
body: json
|
|
||||||
auth: inherit
|
|
||||||
}
|
|
||||||
|
|
||||||
headers {
|
|
||||||
Origin: http://localhost:3000
|
|
||||||
}
|
|
||||||
|
|
||||||
body:json {
|
|
||||||
{
|
|
||||||
"email": "blake.matthes@alpla.com",
|
|
||||||
"password": "nova0511"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
script:post-response {
|
|
||||||
// // grab the raw Set-Cookie header
|
|
||||||
// const cookies = res.headers["set-cookie"];
|
|
||||||
|
|
||||||
// const sessionCookie = cookies[0].split(";")[0];
|
|
||||||
|
|
||||||
// // Save it as an environment variable
|
|
||||||
// bru.setEnvVar("session_cookie", sessionCookie);
|
|
||||||
}
|
|
||||||
|
|
||||||
settings {
|
|
||||||
encodeUrl: true
|
|
||||||
timeout: 0
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Register
|
|
||||||
type: http
|
|
||||||
seq: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
post {
|
|
||||||
url: {{url}}/api/authentication/register
|
|
||||||
body: json
|
|
||||||
auth: inherit
|
|
||||||
}
|
|
||||||
|
|
||||||
body:json {
|
|
||||||
{
|
|
||||||
"name":"Blake", // option when in the frontend as we will pass over as username if not added
|
|
||||||
"username": "matthes01",
|
|
||||||
"email": "blake.matthes@alpla.com",
|
|
||||||
"password": "nova0511"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
script:post-response {
|
|
||||||
// // grab the raw Set-Cookie header
|
|
||||||
// const cookies = res.headers["set-cookie"];
|
|
||||||
|
|
||||||
// const sessionCookie = cookies[0].split(";")[0];
|
|
||||||
|
|
||||||
// // Save it as an environment variable
|
|
||||||
// bru.setEnvVar("session_cookie", sessionCookie);
|
|
||||||
}
|
|
||||||
|
|
||||||
settings {
|
|
||||||
encodeUrl: true
|
|
||||||
timeout: 0
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: auth
|
|
||||||
seq: 5
|
|
||||||
}
|
|
||||||
|
|
||||||
auth {
|
|
||||||
mode: inherit
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: getSession
|
|
||||||
type: http
|
|
||||||
seq: 3
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url: {{url}}/api/auth/get-session
|
|
||||||
body: none
|
|
||||||
auth: inherit
|
|
||||||
}
|
|
||||||
|
|
||||||
settings {
|
|
||||||
encodeUrl: true
|
|
||||||
timeout: 0
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "1",
|
|
||||||
"name": "lst_v3",
|
|
||||||
"type": "collection",
|
|
||||||
"ignore": [
|
|
||||||
"node_modules",
|
|
||||||
".git"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
docs {
|
|
||||||
All Api endpoints to the logistics support tool
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Get queries
|
|
||||||
type: http
|
|
||||||
seq: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url: {{url}}/api/datamart
|
|
||||||
body: none
|
|
||||||
auth: inherit
|
|
||||||
}
|
|
||||||
|
|
||||||
settings {
|
|
||||||
encodeUrl: true
|
|
||||||
timeout: 0
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user