Compare commits

...

16 Commits

Author SHA1 Message Date
Douglas Barone
4225d51140 Use middleware 2023-06-20 15:48:25 -04:00
Douglas Barone
dff6905050 Get status 2023-06-20 15:45:35 -04:00
Douglas Barone
d02c580d1b Basic functionality done 2023-06-20 15:21:28 -04:00
Douglas Barone
568332e502 Use proper env 2023-06-20 10:53:17 -04:00
Douglas Barone
4e373b1005 Update name 2023-06-20 10:49:27 -04:00
Douglas Barone
840686bfff Using tsx now 2023-06-20 10:48:27 -04:00
Douglas Barone
2d7600eb6f Basic jobs setup 2023-06-20 10:28:31 -04:00
Douglas Barone
c35d9e942f Rename folder 2023-06-20 09:44:25 -04:00
Douglas Barone
5aea67214a Create method 2023-06-20 09:35:32 -04:00
Douglas Barone
31204d4327 Printer CRUD OK 2023-06-20 09:13:28 -04:00
Douglas Barone
220fc48b35 Move router to controller 2023-06-19 15:57:18 -04:00
Douglas Barone
aa0305d1cb Refactor 2023-06-19 15:13:36 -04:00
Douglas Barone
36ef372726 Cleanup 2023-06-19 14:32:23 -04:00
Douglas Barone
c05c0d8f75 Group based roles 2023-06-19 13:44:21 -04:00
Douglas Barone
431700a888 Use res.locals 2023-06-19 08:26:54 -04:00
Douglas Barone
b8d73e3a94 Handle expired tokens better 2023-06-19 08:03:08 -04:00
28 changed files with 1524 additions and 238 deletions

1
globals.d.ts vendored Normal file
View File

@ -0,0 +1 @@
declare module 'net-snmp'

790
package-lock.json generated
View File

@ -1,20 +1,22 @@
{ {
"name": "ifms-healthtracker", "name": "ifms-printer-manager",
"version": "1.0.0", "version": "1.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "ifms-healthtracker", "name": "ifms-printer-manager",
"version": "1.0.0", "version": "1.0.0",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@prisma/client": "^4.15.0", "@prisma/client": "^4.15.0",
"body-parser": "^1.20.2", "body-parser": "^1.20.2",
"bree": "^9.1.3",
"dotenv": "^16.1.4", "dotenv": "^16.1.4",
"express": "^4.18.2", "express": "^4.18.2",
"jsonwebtoken": "^9.0.0", "jsonwebtoken": "^9.0.0",
"ldapts": "^4.2.6" "ldapts": "^4.2.6",
"net-snmp": "^3.9.6"
}, },
"devDependencies": { "devDependencies": {
"@types/express": "^4.17.17", "@types/express": "^4.17.17",
@ -24,9 +26,29 @@
"prisma": "^4.15.0", "prisma": "^4.15.0",
"rimraf": "^5.0.1", "rimraf": "^5.0.1",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"tsx": "^3.12.7",
"typescript": "^5.1.3" "typescript": "^5.1.3"
} }
}, },
"node_modules/@babel/runtime": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz",
"integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==",
"dependencies": {
"regenerator-runtime": "^0.13.11"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@breejs/later": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@breejs/later/-/later-4.1.0.tgz",
"integrity": "sha512-QgGnZ9b7o4k0Ai1ZbTJWwZpZcFK9d+Gb+DyNt4UT9x6IEIs5HVu0iIlmgzGqN+t9MoJSpSPo9S/Mm51UtHr3JA==",
"engines": {
"node": ">= 10"
}
},
"node_modules/@cspotcode/source-map-support": { "node_modules/@cspotcode/source-map-support": {
"version": "0.8.1", "version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
@ -39,6 +61,388 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/@esbuild-kit/cjs-loader": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/@esbuild-kit/cjs-loader/-/cjs-loader-2.4.2.tgz",
"integrity": "sha512-BDXFbYOJzT/NBEtp71cvsrGPwGAMGRB/349rwKuoxNSiKjPraNNnlK6MIIabViCjqZugu6j+xeMDlEkWdHHJSg==",
"dev": true,
"dependencies": {
"@esbuild-kit/core-utils": "^3.0.0",
"get-tsconfig": "^4.4.0"
}
},
"node_modules/@esbuild-kit/core-utils": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.1.0.tgz",
"integrity": "sha512-Uuk8RpCg/7fdHSceR1M6XbSZFSuMrxcePFuGgyvsBn+u339dk5OeL4jv2EojwTN2st/unJGsVm4qHWjWNmJ/tw==",
"dev": true,
"dependencies": {
"esbuild": "~0.17.6",
"source-map-support": "^0.5.21"
}
},
"node_modules/@esbuild-kit/esm-loader": {
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/@esbuild-kit/esm-loader/-/esm-loader-2.5.5.tgz",
"integrity": "sha512-Qwfvj/qoPbClxCRNuac1Du01r9gvNOT+pMYtJDapfB1eoGN1YlJ1BixLyL9WVENRx5RXgNLdfYdx/CuswlGhMw==",
"dev": true,
"dependencies": {
"@esbuild-kit/core-utils": "^3.0.0",
"get-tsconfig": "^4.4.0"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz",
"integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz",
"integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz",
"integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz",
"integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz",
"integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz",
"integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz",
"integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz",
"integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz",
"integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz",
"integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz",
"integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==",
"cpu": [
"loong64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz",
"integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==",
"cpu": [
"mips64el"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz",
"integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz",
"integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==",
"cpu": [
"riscv64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz",
"integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==",
"cpu": [
"s390x"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz",
"integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz",
"integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz",
"integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz",
"integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz",
"integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz",
"integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz",
"integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@isaacs/cliui": { "node_modules/@isaacs/cliui": {
"version": "8.0.2", "version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
@ -207,6 +611,11 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/lodash": {
"version": "4.14.195",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.195.tgz",
"integrity": "sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg=="
},
"node_modules/@types/mime": { "node_modules/@types/mime": {
"version": "1.3.2", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
@ -274,9 +683,9 @@
} }
}, },
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.8.2", "version": "8.9.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz",
"integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==",
"dev": true, "dev": true,
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
@ -350,6 +759,11 @@
"safer-buffer": "~2.1.0" "safer-buffer": "~2.1.0"
} }
}, },
"node_modules/asn1-ber": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/asn1-ber/-/asn1-ber-1.2.2.tgz",
"integrity": "sha512-CbNem/7hxrjSiOAOOTX4iZxu+0m3jiLqlsERQwwPM1IDR/22M8IPpA1VVndCLw5KtjRYyRODbvAEIfuTogNDng=="
},
"node_modules/balanced-match": { "node_modules/balanced-match": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@ -401,6 +815,11 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
}, },
"node_modules/boolean": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz",
"integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw=="
},
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -423,11 +842,37 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/bree": {
"version": "9.1.3",
"resolved": "https://registry.npmjs.org/bree/-/bree-9.1.3.tgz",
"integrity": "sha512-oqto4iG7MG2xdRKU0MhFNPTq7ZSztKvalohO3nyu4EIyy3SKpLDX92LkptcGTl6BE2RpQLrzgBP3HoPtWlWBaA==",
"dependencies": {
"@breejs/later": "^4.1.0",
"boolean": "^3.2.0",
"combine-errors": "^3.0.3",
"cron-validate": "^1.4.3",
"human-interval": "^2.0.1",
"is-string-and-not-blank": "^0.0.2",
"is-valid-path": "^0.1.1",
"ms": "^2.1.3",
"p-wait-for": "3",
"safe-timers": "^1.1.0"
},
"engines": {
"node": ">=12.17.0 <13.0.0-0||>=13.2.0"
}
},
"node_modules/buffer-equal-constant-time": { "node_modules/buffer-equal-constant-time": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
}, },
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"dev": true
},
"node_modules/bytes": { "node_modules/bytes": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@ -493,6 +938,15 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true "dev": true
}, },
"node_modules/combine-errors": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/combine-errors/-/combine-errors-3.0.3.tgz",
"integrity": "sha512-C8ikRNRMygCwaTx+Ek3Yr+OuZzgZjduCOfSQBjbM8V3MfgcjSTeto/GXP6PAwKvJz/v15b7GHZvx5rOlczFw/Q==",
"dependencies": {
"custom-error-instance": "2.1.1",
"lodash.uniqby": "4.5.0"
}
},
"node_modules/concat-map": { "node_modules/concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@ -537,6 +991,14 @@
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"dev": true "dev": true
}, },
"node_modules/cron-validate": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/cron-validate/-/cron-validate-1.4.5.tgz",
"integrity": "sha512-nKlOJEnYKudMn/aNyNH8xxWczlfpaazfWV32Pcx/2St51r2bxWbGhZD7uwzMcRhunA/ZNL+Htm/i0792Z59UMQ==",
"dependencies": {
"yup": "0.32.9"
}
},
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"version": "7.0.3", "version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@ -551,6 +1013,11 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/custom-error-instance": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/custom-error-instance/-/custom-error-instance-2.1.1.tgz",
"integrity": "sha512-p6JFxJc3M4OTD2li2qaHkDCw9SfMw82Ldr6OC9Je1aXiGfhx2W8p3GaoeaGrPJTUN9NirTM/KTxHWMUdR1rsUg=="
},
"node_modules/debug": { "node_modules/debug": {
"version": "3.2.7", "version": "3.2.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
@ -630,6 +1097,43 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/esbuild": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz",
"integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==",
"dev": true,
"hasInstallScript": true,
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=12"
},
"optionalDependencies": {
"@esbuild/android-arm": "0.17.19",
"@esbuild/android-arm64": "0.17.19",
"@esbuild/android-x64": "0.17.19",
"@esbuild/darwin-arm64": "0.17.19",
"@esbuild/darwin-x64": "0.17.19",
"@esbuild/freebsd-arm64": "0.17.19",
"@esbuild/freebsd-x64": "0.17.19",
"@esbuild/linux-arm": "0.17.19",
"@esbuild/linux-arm64": "0.17.19",
"@esbuild/linux-ia32": "0.17.19",
"@esbuild/linux-loong64": "0.17.19",
"@esbuild/linux-mips64el": "0.17.19",
"@esbuild/linux-ppc64": "0.17.19",
"@esbuild/linux-riscv64": "0.17.19",
"@esbuild/linux-s390x": "0.17.19",
"@esbuild/linux-x64": "0.17.19",
"@esbuild/netbsd-x64": "0.17.19",
"@esbuild/openbsd-x64": "0.17.19",
"@esbuild/sunos-x64": "0.17.19",
"@esbuild/win32-arm64": "0.17.19",
"@esbuild/win32-ia32": "0.17.19",
"@esbuild/win32-x64": "0.17.19"
}
},
"node_modules/escape-html": { "node_modules/escape-html": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
@ -841,6 +1345,18 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/get-tsconfig": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.6.0.tgz",
"integrity": "sha512-lgbo68hHTQnFddybKbbs/RDRJnJT5YyGy2kQzVwbq+g67X73i+5MVTval34QxGkOe9X5Ujf1UYpCaphLyltjEg==",
"dev": true,
"dependencies": {
"resolve-pkg-maps": "^1.0.0"
},
"funding": {
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
},
"node_modules/glob": { "node_modules/glob": {
"version": "10.2.7", "version": "10.2.7",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.2.7.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.7.tgz",
@ -956,6 +1472,14 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/human-interval": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/human-interval/-/human-interval-2.0.1.tgz",
"integrity": "sha512-r4Aotzf+OtKIGQCB3odUowy4GfUDTy3aTWTfLd7ZF2gBCy3XW3v/dJLRefZnOFFnjqs5B1TypvS8WarpBkYUNQ==",
"dependencies": {
"numbered": "^1.1.0"
}
},
"node_modules/iconv-lite": { "node_modules/iconv-lite": {
"version": "0.4.24", "version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@ -1028,6 +1552,36 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/is-invalid-path": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/is-invalid-path/-/is-invalid-path-0.1.0.tgz",
"integrity": "sha512-aZMG0T3F34mTg4eTdszcGXx54oiZ4NtHSft3hWNJMGJXUUqdIj3cOZuHcU0nCWWcY3jd7yRe/3AEm3vSNTpBGQ==",
"dependencies": {
"is-glob": "^2.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-invalid-path/node_modules/is-extglob": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
"integrity": "sha512-7Q+VbVafe6x2T+Tu6NcOf6sRklazEPmBoB3IWk3WdGZM2iGUwU/Oe3Wtq5lSEkDTTlpp8yx+5t4pzO/i9Ty1ww==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-invalid-path/node_modules/is-glob": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
"integrity": "sha512-a1dBeB19NXsf/E0+FHqkagizel/LQw2DjSQpvQrj3zT+jYPpaUCryPnrQajXKFLCMuf4I6FhRpaGtw4lPrG6Eg==",
"dependencies": {
"is-extglob": "^1.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-number": { "node_modules/is-number": {
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
@ -1037,6 +1591,33 @@
"node": ">=0.12.0" "node": ">=0.12.0"
} }
}, },
"node_modules/is-string-and-not-blank": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/is-string-and-not-blank/-/is-string-and-not-blank-0.0.2.tgz",
"integrity": "sha512-FyPGAbNVyZpTeDCTXnzuwbu9/WpNXbCfbHXLpCRpN4GANhS00eEIP5Ef+k5HYSNIzIhdN9zRDoBj6unscECvtQ==",
"dependencies": {
"is-string-blank": "^1.0.1"
},
"engines": {
"node": ">=6.4.0"
}
},
"node_modules/is-string-blank": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-string-blank/-/is-string-blank-1.0.1.tgz",
"integrity": "sha512-9H+ZBCVs3L9OYqv8nuUAzpcT9OTgMD1yAWrG7ihlnibdkbtB850heAmYWxHuXc4CHy4lKeK69tN+ny1K7gBIrw=="
},
"node_modules/is-valid-path": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-valid-path/-/is-valid-path-0.1.1.tgz",
"integrity": "sha512-+kwPrVDu9Ms03L90Qaml+79+6DZHqHyRoANI6IsZJ/g8frhnfchDOBCa0RbQ6/kdHt5CS5OeIEyrYznNuVN+8A==",
"dependencies": {
"is-invalid-path": "^0.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/isexe": { "node_modules/isexe": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@ -1163,6 +1744,60 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
}, },
"node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
},
"node_modules/lodash._baseiteratee": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/lodash._baseiteratee/-/lodash._baseiteratee-4.7.0.tgz",
"integrity": "sha512-nqB9M+wITz0BX/Q2xg6fQ8mLkyfF7MU7eE+MNBNjTHFKeKaZAPEzEg+E8LWxKWf1DQVflNEn9N49yAuqKh2mWQ==",
"dependencies": {
"lodash._stringtopath": "~4.8.0"
}
},
"node_modules/lodash._basetostring": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-4.12.0.tgz",
"integrity": "sha512-SwcRIbyxnN6CFEEK4K1y+zuApvWdpQdBHM/swxP962s8HIxPO3alBH5t3m/dl+f4CMUug6sJb7Pww8d13/9WSw=="
},
"node_modules/lodash._baseuniq": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz",
"integrity": "sha512-Ja1YevpHZctlI5beLA7oc5KNDhGcPixFhcqSiORHNsp/1QTv7amAXzw+gu4YOvErqVlMVyIJGgtzeepCnnur0A==",
"dependencies": {
"lodash._createset": "~4.0.0",
"lodash._root": "~3.0.0"
}
},
"node_modules/lodash._createset": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/lodash._createset/-/lodash._createset-4.0.3.tgz",
"integrity": "sha512-GTkC6YMprrJZCYU3zcqZj+jkXkrXzq3IPBcF/fIPpNEAB4hZEtXU8zp/RwKOvZl43NUmwDbyRk3+ZTbeRdEBXA=="
},
"node_modules/lodash._root": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz",
"integrity": "sha512-O0pWuFSK6x4EXhM1dhZ8gchNtG7JMqBtrHdoUFUWXD7dJnNSUze1GuyQr5sOs0aCvgGeI3o/OJW8f4ca7FDxmQ=="
},
"node_modules/lodash._stringtopath": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/lodash._stringtopath/-/lodash._stringtopath-4.8.0.tgz",
"integrity": "sha512-SXL66C731p0xPDC5LZg4wI5H+dJo/EO4KTqOMwLYCH3+FmmfAKJEZCm6ohGpI+T1xwsDsJCfL4OnhorllvlTPQ==",
"dependencies": {
"lodash._basetostring": "~4.12.0"
}
},
"node_modules/lodash.uniqby": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.5.0.tgz",
"integrity": "sha512-IRt7cfTtHy6f1aRVA5n7kT8rgN3N1nH6MOWLcHfpWG2SH19E3JksLK38MktLxZDhlAjCP9jpIXkOnRXlu6oByQ==",
"dependencies": {
"lodash._baseiteratee": "~4.7.0",
"lodash._baseuniq": "~4.6.0"
}
},
"node_modules/lru-cache": { "node_modules/lru-cache": {
"version": "9.1.2", "version": "9.1.2",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.2.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.2.tgz",
@ -1255,6 +1890,11 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
}, },
"node_modules/nanoclone": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz",
"integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA=="
},
"node_modules/negotiator": { "node_modules/negotiator": {
"version": "0.6.3", "version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
@ -1263,6 +1903,15 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/net-snmp": {
"version": "3.9.6",
"resolved": "https://registry.npmjs.org/net-snmp/-/net-snmp-3.9.6.tgz",
"integrity": "sha512-57MeEWjzp+1SEwRtE/uHowi9K5g5rxCQ0rX1Kc0ZtfjAauhx2mdk7hcE6Rdtw86eEoisuuPY3X79xxSRKbvQiA==",
"dependencies": {
"asn1-ber": "^1.2.1",
"smart-buffer": "^4.1.0"
}
},
"node_modules/nodemon": { "node_modules/nodemon": {
"version": "2.0.22", "version": "2.0.22",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz",
@ -1315,6 +1964,11 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/numbered": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/numbered/-/numbered-1.1.0.tgz",
"integrity": "sha512-pv/ue2Odr7IfYOO0byC1KgBI10wo5YDauLhxY6/saNzAdAs0r1SotGCPzzCLNPL0xtrAwWRialLu23AAu9xO1g=="
},
"node_modules/object-inspect": { "node_modules/object-inspect": {
"version": "1.12.3", "version": "1.12.3",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
@ -1334,6 +1988,39 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/p-finally": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
"integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==",
"engines": {
"node": ">=4"
}
},
"node_modules/p-timeout": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz",
"integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==",
"dependencies": {
"p-finally": "^1.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/p-wait-for": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/p-wait-for/-/p-wait-for-3.2.0.tgz",
"integrity": "sha512-wpgERjNkLrBiFmkMEjuZJEWKKDrNfHCKA1OhyN1wg1FrLkULbviEy6py1AyJUgZ72YWFbZ38FIpnqvVqAlDUwA==",
"dependencies": {
"p-timeout": "^3.0.0"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/parseurl": { "node_modules/parseurl": {
"version": "1.3.3", "version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@ -1401,6 +2088,11 @@
"node": ">=14.17" "node": ">=14.17"
} }
}, },
"node_modules/property-expr": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.5.tgz",
"integrity": "sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA=="
},
"node_modules/proxy-addr": { "node_modules/proxy-addr": {
"version": "2.0.7", "version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@ -1467,6 +2159,20 @@
"node": ">=8.10.0" "node": ">=8.10.0"
} }
}, },
"node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
},
"node_modules/resolve-pkg-maps": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
"dev": true,
"funding": {
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
}
},
"node_modules/rimraf": { "node_modules/rimraf": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.1.tgz", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.1.tgz",
@ -1504,6 +2210,11 @@
} }
] ]
}, },
"node_modules/safe-timers": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/safe-timers/-/safe-timers-1.1.0.tgz",
"integrity": "sha512-9aqY+v5eMvmRaluUEtdRThV1EjlSElzO7HuCj0sTW9xvp++8iJ9t/RWGNWV6/WHcUJLHpyT2SNf/apoKTU2EpA=="
},
"node_modules/safer-buffer": { "node_modules/safer-buffer": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@ -1640,6 +2351,34 @@
"semver": "bin/semver.js" "semver": "bin/semver.js"
} }
}, },
"node_modules/smart-buffer": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
"engines": {
"node": ">= 6.0.0",
"npm": ">= 3.0.0"
}
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map-support": {
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
"dev": true,
"dependencies": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
}
},
"node_modules/statuses": { "node_modules/statuses": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
@ -1781,6 +2520,11 @@
"node": ">=0.6" "node": ">=0.6"
} }
}, },
"node_modules/toposort": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
"integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg=="
},
"node_modules/touch": { "node_modules/touch": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
@ -1836,6 +2580,23 @@
} }
} }
}, },
"node_modules/tsx": {
"version": "3.12.7",
"resolved": "https://registry.npmjs.org/tsx/-/tsx-3.12.7.tgz",
"integrity": "sha512-C2Ip+jPmqKd1GWVQDvz/Eyc6QJbGfE7NrR3fx5BpEHMZsEHoIxHL1j+lKdGobr8ovEyqeNkPLSKp6SCSOt7gmw==",
"dev": true,
"dependencies": {
"@esbuild-kit/cjs-loader": "^2.4.2",
"@esbuild-kit/core-utils": "^3.0.0",
"@esbuild-kit/esm-loader": "^2.5.5"
},
"bin": {
"tsx": "dist/cli.js"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/type-is": { "node_modules/type-is": {
"version": "1.6.18", "version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
@ -2024,6 +2785,23 @@
"engines": { "engines": {
"node": ">=6" "node": ">=6"
} }
},
"node_modules/yup": {
"version": "0.32.9",
"resolved": "https://registry.npmjs.org/yup/-/yup-0.32.9.tgz",
"integrity": "sha512-Ci1qN+i2H0XpY7syDQ0k5zKQ/DoxO0LzPg8PAR/X4Mpj6DqaeCoIYEEjDJwhArh3Fa7GWbQQVDZKeXYlSH4JMg==",
"dependencies": {
"@babel/runtime": "^7.10.5",
"@types/lodash": "^4.14.165",
"lodash": "^4.17.20",
"lodash-es": "^4.17.15",
"nanoclone": "^0.2.1",
"property-expr": "^2.0.4",
"toposort": "^2.0.2"
},
"engines": {
"node": ">=10"
}
} }
} }
} }

View File

@ -1,17 +1,20 @@
{ {
"name": "ifms-healthtracker", "name": "ifms-printer-manager",
"version": "1.0.0", "version": "1.0.0",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"type": "module", "type": "module",
"scripts": { "scripts": {
"clean": "rimraf ./dist", "clean": "rimraf ./dist ./public",
"build": "npm run clean && tsc", "build:server": "npm run clean && tsc",
"start": "node dist", "build:web": "cd web && npm run build",
"dev": "nodemon --ext js,ts,mts,mjs,json,prisma ./src/index.ts --exec ts-node-esm" "build": "npm run build:server && npm run build:web",
"start": "NODE_ENV=production node dist",
"dev": "nodemon --ext js,ts,mts,mjs,json,prisma --exec \"tsx src/index.ts\"",
"devLegacy": "NODE_OPTIONS=\"--loader ts-node/esm\" node ./src/index.ts"
}, },
"prisma": { "prisma": {
"seed": "ts-node --esm prisma/seed.ts" "seed": "tsx prisma/seed.ts"
}, },
"keywords": [], "keywords": [],
"author": "Douglas Barone", "author": "Douglas Barone",
@ -24,14 +27,17 @@
"prisma": "^4.15.0", "prisma": "^4.15.0",
"rimraf": "^5.0.1", "rimraf": "^5.0.1",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"tsx": "^3.12.7",
"typescript": "^5.1.3" "typescript": "^5.1.3"
}, },
"dependencies": { "dependencies": {
"@prisma/client": "^4.15.0", "@prisma/client": "^4.15.0",
"body-parser": "^1.20.2", "body-parser": "^1.20.2",
"bree": "^9.1.3",
"dotenv": "^16.1.4", "dotenv": "^16.1.4",
"express": "^4.18.2", "express": "^4.18.2",
"jsonwebtoken": "^9.0.0", "jsonwebtoken": "^9.0.0",
"ldapts": "^4.2.6" "ldapts": "^4.2.6",
"net-snmp": "^3.9.6"
} }
} }

View File

@ -1,35 +0,0 @@
-- CreateEnum
CREATE TYPE "Role" AS ENUM ('ADMIN', 'INSPECTOR', 'USER');
-- CreateEnum
CREATE TYPE "PrinterModel" AS ENUM ('m3655idn', 'm2040dn', 'p6235cdn');
-- CreateTable
CREATE TABLE "User" (
"id" SERIAL NOT NULL,
"username" TEXT,
"mail" TEXT,
"displayName" TEXT,
"thumbnailPhoto" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"roles" "Role"[] DEFAULT ARRAY['USER']::"Role"[],
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Printer" (
"id" SERIAL NOT NULL,
"hostname" TEXT,
"friendlyName" TEXT,
"ip" TEXT NOT NULL,
"model" "PrinterModel" NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Printer_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");

View File

@ -1,28 +0,0 @@
/*
Warnings:
- A unique constraint covering the columns `[serialNumber]` on the table `Printer` will be added. If there are existing duplicate values, this will fail.
*/
-- AlterTable
ALTER TABLE "Printer" ADD COLUMN "serialNumber" TEXT;
-- CreateTable
CREATE TABLE "PrinterStatus" (
"id" SERIAL NOT NULL,
"tonerBlackLevel" INTEGER NOT NULL,
"tonerCyanLevel" INTEGER,
"tonerMagentaLevel" INTEGER,
"tonerYellowLevel" INTEGER,
"counter" INTEGER NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"printerId" INTEGER NOT NULL,
CONSTRAINT "PrinterStatus_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "Printer_serialNumber_key" ON "Printer"("serialNumber");
-- AddForeignKey
ALTER TABLE "PrinterStatus" ADD CONSTRAINT "PrinterStatus_printerId_fkey" FOREIGN KEY ("printerId") REFERENCES "Printer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -0,0 +1,61 @@
-- CreateEnum
CREATE TYPE "Role" AS ENUM ('ADMIN', 'INSPECTOR', 'USER');
-- CreateTable
CREATE TABLE "User" (
"id" SERIAL NOT NULL,
"username" TEXT,
"mail" TEXT,
"displayName" TEXT,
"thumbnailPhoto" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"roles" "Role"[] DEFAULT ARRAY['USER']::"Role"[],
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Printer" (
"id" SERIAL NOT NULL,
"friendlyName" TEXT,
"location" TEXT,
"serialNumber" TEXT,
"hostname" TEXT,
"ip" TEXT NOT NULL,
"model" TEXT NOT NULL,
"blackTonerModel" TEXT,
"cyanTonerModel" TEXT,
"magentaTonerModel" TEXT,
"yellowTonerModel" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Printer_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PrinterStatus" (
"id" SERIAL NOT NULL,
"tonerBlackLevel" INTEGER NOT NULL,
"tonerCyanLevel" INTEGER,
"tonerMagentaLevel" INTEGER,
"tonerYellowLevel" INTEGER,
"counter" INTEGER NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"printerId" INTEGER NOT NULL,
CONSTRAINT "PrinterStatus_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
-- CreateIndex
CREATE UNIQUE INDEX "Printer_serialNumber_key" ON "Printer"("serialNumber");
-- CreateIndex
CREATE UNIQUE INDEX "Printer_ip_key" ON "Printer"("ip");
-- AddForeignKey
ALTER TABLE "PrinterStatus" ADD CONSTRAINT "PrinterStatus_printerId_fkey" FOREIGN KEY ("printerId") REFERENCES "Printer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -0,0 +1,5 @@
-- DropForeignKey
ALTER TABLE "PrinterStatus" DROP CONSTRAINT "PrinterStatus_printerId_fkey";
-- AddForeignKey
ALTER TABLE "PrinterStatus" ADD CONSTRAINT "PrinterStatus_printerId_fkey" FOREIGN KEY ("printerId") REFERENCES "Printer"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -34,11 +34,12 @@ model Printer {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
friendlyName String? friendlyName String?
location String?
serialNumber String? @unique serialNumber String? @unique
hostname String? hostname String?
ip String ip String @unique
model PrinterModel model String
blackTonerModel String? blackTonerModel String?
cyanTonerModel String? cyanTonerModel String?
@ -48,13 +49,7 @@ model Printer {
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt updatedAt DateTime @default(now()) @updatedAt
PrinterStatus PrinterStatus[] status PrinterStatus[]
}
enum PrinterModel {
m3655idn
m2040dn
p6235cdn
} }
model PrinterStatus { model PrinterStatus {
@ -70,5 +65,5 @@ model PrinterStatus {
createdAt DateTime @default(now()) createdAt DateTime @default(now())
printerId Int printerId Int
printer Printer @relation(fields: [printerId], references: [id]) printer Printer @relation(fields: [printerId], references: [id], onDelete: Cascade)
} }

View File

@ -6,33 +6,33 @@ async function main() {
await prisma.printer.createMany({ await prisma.printer.createMany({
data: [ data: [
{ {
friendlyName: 'p04', friendlyName: 'P04',
ip: '10.7.0.134', ip: '10.7.0.134',
model: 'm3655idn', model: 'ECOSYS M3655idn',
serialNumber: 'R4P1478461' serialNumber: 'R4P1478461'
}, },
{ {
friendlyName: 'p05', friendlyName: 'P05',
ip: '10.7.0.135', ip: '10.7.0.135',
model: 'm2040dn', model: 'ECOSYS M2040dn',
serialNumber: 'VR91483974' serialNumber: 'VR91483974'
}, },
{ {
friendlyName: 'p06', friendlyName: 'P06',
ip: '10.7.0.136', ip: '10.7.0.136',
model: 'm2040dn', model: 'ECOSYS M2040dn',
serialNumber: 'VR91586433' serialNumber: 'VR91586433'
}, },
{ {
friendlyName: 'p07', friendlyName: 'P07',
ip: '10.7.0.137', ip: '10.7.0.137',
model: 'm2040dn', model: 'ECOSYS M2040dn',
serialNumber: 'VR91586432' serialNumber: 'VR91586432'
}, },
{ {
friendlyName: 'p08', friendlyName: 'P08',
ip: '10.7.0.138', ip: '10.7.0.138',
model: 'p6235cdn', model: 'ECOSYS P6235cdn',
serialNumber: 'RCG0304510' serialNumber: 'RCG0304510'
} }
] ]

View File

@ -0,0 +1,26 @@
import { Request, Response, Router } from 'express'
import { AuthenticationService } from '../services/AuthenticationService.js'
const router = Router()
class LoginController {
static async login(req: Request, res: Response) {
const { username, password } = req.body
if (!username || !password) {
res.status(400).json({ error: 'Missing username or password' })
return
}
try {
const token = await AuthenticationService.login(username, password)
res.json({ token })
} catch (error: any) {
res.status(401).json({ error: error.message })
}
}
}
router.post('/', LoginController.login)
export default router

View File

@ -0,0 +1,106 @@
import { Request, Response, Router } from 'express'
import { hasRolesMiddleware } from '../middlewares/hasRolesMiddleware.js'
import { prisma } from '../prisma.js'
import { PrinterStatusService } from '../services/PrinterStatusService.js'
import { distributedCopy } from '../utils/distributedCopy.js'
const router = Router()
class PrinterController {
static async index(req: Request, res: Response) {
const printers = await prisma.printer.findMany()
res.json(printers)
}
static async show(req: Request, res: Response) {
const { id } = req.params
const { take, minutes = 43200 } = req.query
const gte = new Date(Date.now() - 1000 * 60 * Number(minutes))
const printer = await prisma.printer.findUnique({
where: { id: Number(id) },
include: {
status: {
where: {
createdAt: {
gte
}
},
orderBy: {
createdAt: 'desc'
}
}
}
})
if (printer)
res.json({
...printer,
status: distributedCopy(printer.status, Number(take))
})
else res.status(400).json({ error: 'Printer not found' })
}
static async create(req: Request, res: Response) {
const { friendlyName, ip } = req.body
try {
const model = await PrinterStatusService.getPrinterModel(ip)
const printer = await prisma.printer.create({
data: { friendlyName, ip, model }
})
new PrinterStatusService(printer)
res.json(printer)
} catch (e) {
res
.status(400)
.json({ error: 'Este endereço não é de uma impressora suportada.' })
return
}
}
static async edit(req: Request, res: Response) {
const { id } = req.params
const { friendlyName } = req.body
// Verify if printer exists
const printerExists = await prisma.printer.findUnique({
where: { id: Number(id) }
})
if (printerExists) {
const printer = await prisma.printer.update({
where: { id: Number(id) },
data: { friendlyName }
})
res.json(printer)
} else {
res.status(400).json({ error: 'Printer not found' })
}
}
static async delete(req: Request, res: Response) {
const { id } = req.params
await prisma.printer.delete({ where: { id: Number(id) } })
res.json({ message: 'Printer deleted' })
}
}
router.use(hasRolesMiddleware(['ADMIN', 'INSPECTOR']))
router.get('/', PrinterController.index)
router.post('/', PrinterController.create)
router.get('/:id', PrinterController.show)
router.put('/:id', PrinterController.edit)
router.delete('/:id', PrinterController.delete)
export default router

View File

@ -0,0 +1,24 @@
import { Router, Request, Response } from 'express'
import { prisma } from '../prisma.js'
import { PrinterStatusService } from '../services/PrinterStatusService.js'
import { hasRolesMiddleware } from '../middlewares/hasRolesMiddleware.js'
const router = Router()
class PrinterStatusController {
static async update(req: Request, res: Response) {
const printers = await prisma.printer.findMany()
printers.forEach(async printer => {
new PrinterStatusService(printer)
})
res.json({ message: 'Updating printer status' })
}
}
router.use(hasRolesMiddleware(['ADMIN', 'INSPECTOR']))
router.post('/update', PrinterStatusController.update)
export default router

View File

@ -1,18 +0,0 @@
import { LdapController } from '../controllers/LdapController.js'
import { prisma } from '../prisma.js'
export class UserController {
static async importUser(username: string) {
const ldap = new LdapController()
const user = await ldap.getUser(username)
if (!user) throw new Error('User not found!')
return await prisma.user.upsert({
where: { username: user.username },
update: user,
create: user
})
}
}

View File

@ -1,24 +0,0 @@
import { UserController } from '../UserController.js'
import { prisma } from '../../prisma.js'
import { Request, Response } from 'express'
export class UserRouteController {
static async getOne(req: Request, res: Response) {
const { username } = req.params
if (!username) return res.status(400).json({ error: 'Missing username' })
try {
const user = await prisma.user.findUnique({
where: { username }
})
if (!user) return await UserController.importUser(username)
else UserController.importUser(username)
res.json(user)
} catch (error: any) {
res.status(500).json({ error: error.message })
}
}
}

View File

@ -1,9 +1,33 @@
import 'dotenv/config' import 'dotenv/config'
import * as path from 'node:path'
import { fileURLToPath } from 'node:url'
import { app } from './server.js' import { app } from './server.js'
import Bree from 'bree'
const PORT = process.env.PORT || 3000 const PORT = process.env.PORT || 3000
// Start server // Start server
app.listen(PORT, () => { app.listen(PORT, () => {
console.log(`Server listening on port ${PORT}`) console.log(
`Running in ${
process.env.NODE_ENV == 'production' ? 'PRODUCTION' : 'DEVELOPMENT'
} mode. \nServer listening http://127.0.0.1:${PORT}`
)
}) })
// Jobs
const bree = new Bree({
root: path.join(path.dirname(fileURLToPath(import.meta.url)), 'jobs'),
defaultExtension: process.env.NODE_ENV == 'production' ? 'js' : 'ts',
logger: false,
jobs: [
{
name: 'updatePrinterStatus',
interval: process.env.UPDATE_INTERVAL || '10m',
timeout: 0
}
]
})
bree.start()

View File

@ -0,0 +1,14 @@
import { prisma } from '../prisma.js'
import { PrinterStatusService } from '../services/PrinterStatusService.js'
function updatePrinterStatus() {
console.log(`Updating printer status ${new Date().toISOString()}`)
prisma.printer.findMany().then(printers => {
printers.forEach(async printer => {
new PrinterStatusService(printer)
})
})
}
updatePrinterStatus()

View File

@ -1,13 +1,12 @@
import { Response, NextFunction } from 'express' import { Response, NextFunction, Request } from 'express'
import { RequestWithUser } from '../types/RequestWithUser.js'
export async function authMiddleware( export async function authMiddleware(
req: RequestWithUser, req: Request,
res: Response, res: Response,
next: NextFunction next: NextFunction
) { ) {
try { try {
if (!req.user) { if (!res.locals.user) {
res.status(401).json({ error: 'Must be logged in' }) res.status(401).json({ error: 'Must be logged in' })
return return
} }

View File

@ -1,11 +1,11 @@
import { Response, NextFunction } from 'express' import { Response, NextFunction, Request } from 'express'
import { RequestWithUser } from '../types/RequestWithUser.js'
import { Role } from '@prisma/client' import { Role } from '@prisma/client'
export function hasRolesMiddleware(roles: Role[]) { export function hasRolesMiddleware(roles: Role[]) {
return function (req: RequestWithUser, res: Response, next: NextFunction) { return function (req: Request, res: Response, next: NextFunction) {
try { try {
const userRoles = req.user?.roles const userRoles = res.locals.user?.roles
if (roles.some(role => userRoles?.includes(role))) next() if (roles.some(role => userRoles?.includes(role))) next()
else res.status(401).json({ error: 'Not authorized!' }) else res.status(401).json({ error: 'Not authorized!' })

View File

@ -1,6 +1,5 @@
import { NextFunction, Request, Response } from 'express' import { NextFunction, Request, Response } from 'express'
import { RequestWithUser } from '../types/RequestWithUser.js' import { AuthenticationService } from '../services/AuthenticationService.js'
import { AuthenticationController } from '../controllers/AuthenticationController.js'
function getToken(req: Request) { function getToken(req: Request) {
const authHeader = req.headers.authorization as string const authHeader = req.headers.authorization as string
@ -13,16 +12,20 @@ function getToken(req: Request) {
return token return token
} }
export async function injectUserMiddleware( export async function populateUserMiddleware(
req: RequestWithUser, req: Request,
res: Response, res: Response,
next: NextFunction next: NextFunction
) { ) {
const token = getToken(req) const token = getToken(req)
if (token) { if (token) {
const user = await AuthenticationController.authenticate(token) try {
req.user = user const user = await AuthenticationService.jwtAuth(token)
res.locals.user = user
} catch (error: any) {
return res.status(401).json({ error: error.message })
}
} }
next() next()

View File

@ -0,0 +1,109 @@
export type PrinterObjectIds = {
model: string
objectIds: {
model: string
serialNumber: string
counter: string
location: string
toners: {
black: {
current: string
max: string
model: string
}
cyan?: {
current: string
max: string
model: string
}
magenta?: {
current: string
max: string
model: string
}
yellow?: {
current: string
max: string
model: string
}
}
}
}
const objectIds: PrinterObjectIds[] = [
{
model: 'ECOSYS M3655idn',
objectIds: {
model: '1.3.6.1.2.1.25.3.2.1.3.1',
serialNumber: '1.3.6.1.2.1.43.5.1.1.17.1',
counter: '1.3.6.1.4.1.1347.43.10.1.1.12.1.1',
location: '1.3.6.1.2.1.1.6.0',
toners: {
black: {
current: '1.3.6.1.2.1.43.11.1.1.9.1.1',
max: '1.3.6.1.2.1.43.11.1.1.8.1.1',
model: '1.3.6.1.2.1.43.11.1.1.6.1.1'
}
}
}
},
{
model: 'ECOSYS P6235cdn',
objectIds: {
model: '1.3.6.1.2.1.25.3.2.1.3.1',
serialNumber: '1.3.6.1.2.1.43.5.1.1.17.1',
counter: '1.3.6.1.4.1.1347.43.10.1.1.12.1.1',
location: '1.3.6.1.2.1.1.6.0',
toners: {
black: {
current: '1.3.6.1.2.1.43.11.1.1.9.1.4',
max: '1.3.6.1.2.1.43.11.1.1.8.1.4',
model: '1.3.6.1.2.1.43.11.1.1.6.1.4'
},
cyan: {
current: '1.3.6.1.2.1.43.11.1.1.9.1.1',
max: '1.3.6.1.2.1.43.11.1.1.8.1.1',
model: '1.3.6.1.2.1.43.11.1.1.6.1.1'
},
magenta: {
current: '1.3.6.1.2.1.43.11.1.1.9.1.2',
max: '1.3.6.1.2.1.43.11.1.1.8.1.2',
model: '1.3.6.1.2.1.43.11.1.1.6.1.2'
},
yellow: {
current: '1.3.6.1.2.1.43.11.1.1.9.1.3',
max: '1.3.6.1.2.1.43.11.1.1.8.1.3',
model: '1.3.6.1.2.1.43.11.1.1.6.1.3'
}
}
}
},
{
model: 'ECOSYS M2040dn',
objectIds: {
model: '1.3.6.1.2.1.25.3.2.1.3.1',
serialNumber: '1.3.6.1.2.1.43.5.1.1.17.1',
counter: '1.3.6.1.4.1.1347.43.10.1.1.12.1.1',
location: '1.3.6.1.2.1.1.6.0',
toners: {
black: {
current: '1.3.6.1.2.1.43.11.1.1.9.1.1',
max: '1.3.6.1.2.1.43.11.1.1.8.1.1',
model: '1.3.6.1.2.1.43.11.1.1.6.1.1'
}
}
}
}
]
export class objectIdsRepository {
private constructor() {}
static getPrinterObjectIds(model: string): PrinterObjectIds {
const printerObjectIds = objectIds.find(printer => printer.model === model)
if (!printerObjectIds) throw new Error('Model not found')
return printerObjectIds
}
}

View File

@ -1,67 +1,24 @@
import express, { Request, Response } from 'express' import express, { Request, Response } from 'express'
import bodyParser from 'body-parser' import bodyParser from 'body-parser'
import { injectUserMiddleware } from './middleware/injectUserMiddleware.js' import { populateUserMiddleware } from './middlewares/populateUserMiddleware.js'
import { authMiddleware } from './middleware/authMiddleware.js' import { authMiddleware } from './middlewares/authMiddleware.js'
import { hasRolesMiddleware } from './middleware/hasRolesMiddleware.js'
import { RequestWithUser } from './types/RequestWithUser.js' import LoginRouter from './controllers/LoginController.js'
import PrinterRouter from './controllers/PrinterController.js'
import { UserRouteController } from './controllers/routes/UserRouteController.js' import PrinterStatusRouter from './controllers/PrinterStatusController.js'
import { AuthenticationController } from './controllers/AuthenticationController.js'
export const app = express() export const app = express()
app.use('/', express.static('public')) app.use('/', express.static('public'))
app.use(injectUserMiddleware)
app.use(bodyParser.json()) app.use(bodyParser.json())
app.use(populateUserMiddleware)
// Test route app.use('/api/login', LoginRouter)
app.get('/api/', async (req: Request, res: Response) => { app.use('/api/printer', PrinterRouter)
res.json({ message: 'Hello!' }) app.use('/api/printer-status', PrinterStatusRouter)
})
// Login route app.get('/api/me', authMiddleware, async (req: Request, res: Response) =>
app.post('/api/login', async (req: Request, res: Response) => { res.json(res.locals.user)
const { username, password } = req.body
if (!username || !password)
return res.status(400).json({ error: 'Missing username or password' })
try {
const token = await AuthenticationController.login(username, password)
res.json({ token })
} catch (error: any) {
res.status(401).json({ error: error.message })
}
})
app.get(
'/api/me',
authMiddleware,
async (req: RequestWithUser, res: Response) => res.json(req.user)
)
app.get(
'/api/protected',
authMiddleware,
async (req: RequestWithUser, res: Response) => {
res.json('Hello protected world! ' + req.user?.displayName)
}
)
app.get(
'/api/admin',
hasRolesMiddleware(['ADMIN']),
async (req: RequestWithUser, res: Response) => {
res.json('Hello Admin!' + req.user?.username)
}
)
app.get(
'/api/user/:username',
hasRolesMiddleware(['ADMIN']),
UserRouteController.getOne
) )

View File

@ -1,19 +1,19 @@
import jwt from 'jsonwebtoken' import jwt from 'jsonwebtoken'
import { prisma } from '../prisma.js' import { prisma } from '../prisma.js'
import { LdapController } from '../controllers/LdapController.js' import { LdapService } from './LdapService.js'
import { UserController } from '../controllers/UserController.js' import { UserService } from './UserService.js'
const JWT_SECRET = process.env.JWT_SECRET || 'secret' const JWT_SECRET = process.env.JWT_SECRET || 'secret'
export class AuthenticationController { export class AuthenticationService {
private constructor() {} private constructor() {}
static async login(username: string, password: string) { static async login(username: string, password: string) {
const ldap = new LdapController() const ldap = new LdapService()
await ldap.authenticate(username, password) await ldap.authenticate(username, password)
await UserController.importUser(username) await UserService.importUser(username)
const token = jwt.sign({ username }, JWT_SECRET, { const token = jwt.sign({ username }, JWT_SECRET, {
expiresIn: '2 days' expiresIn: '2 days'
@ -22,7 +22,7 @@ export class AuthenticationController {
return `Bearer ${token}` return `Bearer ${token}`
} }
static async authenticate(token: string) { static async jwtAuth(token: string) {
try { try {
const { username } = jwt.verify(token, JWT_SECRET) as { username: string } const { username } = jwt.verify(token, JWT_SECRET) as { username: string }
@ -30,11 +30,11 @@ export class AuthenticationController {
where: { username } where: { username }
}) })
if (!user) return null if (!user) return await UserService.importUser(username)
return user return user
} catch (error: any) { } catch (error: any) {
throw new Error('Invalid token') throw new Error(`Invalid token. ${error.message}`)
} }
} }
} }

View File

@ -6,7 +6,7 @@ const BIND_USER = process.env.AD_BIND_USER || ''
const BIND_PASSWD = process.env.AD_BIND_PASSWORD || '' const BIND_PASSWD = process.env.AD_BIND_PASSWORD || ''
interface LdapClientInterface extends Client { interface LdapClientInterface extends Client {
authenticate(username: string, password: string): Promise<LdapUser | null> authenticate(username: string, password: string): Promise<void>
getUser(username: string): Promise<any> getUser(username: string): Promise<any>
} }
@ -15,19 +15,20 @@ type LdapUser = {
mail: string | null mail: string | null
displayName: string displayName: string
thumbnailPhoto: string | null thumbnailPhoto: string | null
groups?: string[]
} }
export class LdapController extends Client implements LdapClientInterface { export class LdapService extends Client implements LdapClientInterface {
private static instance: LdapController private static instance: LdapService
constructor() { constructor() {
if (LdapController.instance) return LdapController.instance if (LdapService.instance) return LdapService.instance
super({ super({
url: `ldap://${process.env.AD_HOST}` url: `ldap://${process.env.AD_HOST}`
}) })
LdapController.instance = this LdapService.instance = this
} }
/** /**
@ -45,19 +46,25 @@ export class LdapController extends Client implements LdapClientInterface {
} }
} }
async getUser(username: string) { async getUser(username: string): Promise<LdapUser> {
return await this.adminBondOperation(async () => { return await this.adminBondOperation(async () => {
const { searchEntries } = await this.search(DN, { const { searchEntries } = await this.search(DN, {
scope: 'sub', scope: 'sub',
filter: `(sAMAccountName=${username})`, filter: `(sAMAccountName=${username})`,
attributes: ['mail', 'sAMAccountName', 'displayName', 'thumbnailPhoto'], attributes: [
'mail',
'sAMAccountName',
'displayName',
'thumbnailPhoto',
'dn'
],
explicitBufferAttributes: ['thumbnailPhoto'] explicitBufferAttributes: ['thumbnailPhoto']
}) })
if (!searchEntries.length) if (!searchEntries.length)
throw new Error('User not found on LDAP server.') throw new Error('User not found on LDAP server.')
const { sAMAccountName, displayName, mail, thumbnailPhoto } = const { sAMAccountName, displayName, mail, thumbnailPhoto, dn } =
searchEntries[0] searchEntries[0]
const ldapUser: LdapUser = { const ldapUser: LdapUser = {
@ -66,20 +73,29 @@ export class LdapController extends Client implements LdapClientInterface {
mail: mail.toString(), mail: mail.toString(),
thumbnailPhoto: `data:image/png;base64,${Buffer.from( thumbnailPhoto: `data:image/png;base64,${Buffer.from(
thumbnailPhoto as Buffer thumbnailPhoto as Buffer
).toString('base64')}` ).toString('base64')}`,
groups: await this.getGroupsForUser(dn.toString())
} }
return ldapUser return ldapUser
}) })
} }
async getGroupsForUser(dn: string) {
const { searchEntries } = await this.search(DN, {
scope: 'sub',
filter: `(member:1.2.840.113556.1.4.1941:=${dn})`,
attributes: ['cn']
})
if (!searchEntries.length) throw new Error('User not found on LDAP server.')
return searchEntries.map(entry => entry.cn.toString())
}
async authenticate(username: string, password: string) { async authenticate(username: string, password: string) {
await this.bind(`${DOMAIN}\\${username}`, password) await this.bind(`${DOMAIN}\\${username}`, password)
const user = await this.getUser(username)
await this.unbind() await this.unbind()
return user
} }
} }

View File

@ -0,0 +1,213 @@
import snmp from 'net-snmp'
import { Printer, PrinterStatus } from '@prisma/client'
import { prisma } from '../prisma.js'
import {
objectIdsRepository,
PrinterObjectIds
} from '../repositories/ObjectIDRepository.js'
type VarbindString = {
oid: string
type: number
value: string
}
export type Varbind = {
oid: string
type: number
value: string | Buffer
}
export type PrinterInfo = {
serialNumber: string
counter: number
location: string
toners: {
black: {
level: number
model: string
}
cyan?: {
level: number
model: string
}
magenta?: {
level: number
model: string
}
yellow?: {
level: number
model: string
}
}
}
export class PrinterStatusService {
constructor(private printer: Printer) {
this.getPrinterInfo().then(async printerStatus => {
await prisma.printer.update({
where: { id: this.printer.id },
data: {
serialNumber: printerStatus.serialNumber,
location: printerStatus.location,
blackTonerModel: printerStatus.toners.black.model,
cyanTonerModel: printerStatus.toners.cyan?.model,
magentaTonerModel: printerStatus.toners.magenta?.model,
yellowTonerModel: printerStatus.toners.yellow?.model,
status: {
create: {
counter: printerStatus.counter,
tonerBlackLevel: printerStatus.toners.black.level,
tonerCyanLevel: printerStatus.toners.cyan?.level,
tonerMagentaLevel: printerStatus.toners.magenta?.level,
tonerYellowLevel: printerStatus.toners.yellow?.level
}
}
}
})
})
}
private objectIdsArray(): string[] {
const oIDsArray: string[] = []
function extractObjValues(obj: any) {
for (let key in obj) {
if (typeof obj[key] === 'object') {
extractObjValues(obj[key])
} else {
const oID = obj[key]
oIDsArray.push(oID)
}
}
}
extractObjValues(
objectIdsRepository.getPrinterObjectIds(this.printer.model).objectIds
)
return oIDsArray
}
private deBufferizeVarbinds(varbinds: Varbind[]) {
const varbindsString: VarbindString[] = []
varbinds.forEach((varbind: Varbind) => {
if (varbind.value instanceof Buffer)
varbindsString.push({ ...varbind, value: varbind.value.toString() })
else varbindsString.push({ ...varbind, value: varbind.value })
})
return varbindsString
}
static getPrinterModel(ip: string): Promise<string> {
return new Promise((resolve, reject) => {
const snmpSession = snmp.createSession(ip, 'public')
snmpSession.get(
['1.3.6.1.2.1.25.3.2.1.3.1'],
(error: any, varbinds: any) => {
if (error) {
reject(error)
} else {
resolve(varbinds[0].value.toString())
}
snmpSession.close()
}
)
})
}
async getPrinterInfo(): Promise<PrinterInfo> {
return new Promise((resolve, reject) => {
const session = snmp.createSession(this.printer.ip, 'public')
const oIDsArray = this.objectIdsArray()
session.get(oIDsArray, (error: any, varbinds: Varbind[]) => {
if (error) reject(error)
const varbindsString = this.deBufferizeVarbinds(varbinds)
const info = this.objectIDsToPrinterInfo(varbindsString)
resolve(info)
session.close()
})
})
}
private calcTonerLevelPercentage(
current: string | undefined,
max: string | undefined
) {
if (typeof current === 'undefined' || typeof max === 'undefined')
throw new Error('current or max is undefined')
return (+current! / +max!) * 100
}
private objectIDsToPrinterInfo(varbinds: Varbind[]): PrinterInfo {
const snmpInfo = this.deBufferizeVarbinds(varbinds)
const { objectIds }: PrinterObjectIds =
objectIdsRepository.getPrinterObjectIds(this.printer.model)
const printerInfo: PrinterInfo = {
serialNumber: snmpInfo.find(x => x.oid === objectIds.serialNumber)
?.value as string,
counter: Number(snmpInfo.find(x => x.oid === objectIds.counter)?.value),
location: snmpInfo.find(x => x.oid === objectIds.location)
?.value as string,
toners: {
black: {
level: this.calcTonerLevelPercentage(
snmpInfo.find(x => x.oid === objectIds.toners.black.current)?.value,
snmpInfo.find(x => x.oid === objectIds.toners.black.max)?.value
),
model: snmpInfo.find(x => x.oid === objectIds.toners.black.model)
?.value as string
},
cyan: objectIds.toners.cyan
? {
level: this.calcTonerLevelPercentage(
snmpInfo.find(x => x.oid === objectIds.toners.cyan?.current)
?.value,
snmpInfo.find(x => x.oid === objectIds.toners.cyan?.max)?.value
),
model: snmpInfo.find(x => x.oid === objectIds.toners.cyan?.model)
?.value as string
}
: undefined,
magenta: objectIds.toners.magenta
? {
level: this.calcTonerLevelPercentage(
snmpInfo.find(x => x.oid === objectIds.toners.magenta?.current)
?.value,
snmpInfo.find(x => x.oid === objectIds.toners.magenta?.max)
?.value
),
model: snmpInfo.find(
x => x.oid === objectIds.toners.magenta?.model
)?.value as string
}
: undefined,
yellow: objectIds.toners.yellow
? {
level: this.calcTonerLevelPercentage(
snmpInfo.find(x => x.oid === objectIds.toners.yellow?.current)
?.value,
snmpInfo.find(x => x.oid === objectIds.toners.yellow?.max)
?.value
),
model: snmpInfo.find(
x => x.oid === objectIds.toners.yellow?.model
)?.value as string
}
: undefined
}
}
return printerInfo
}
}

View File

@ -0,0 +1,37 @@
import { User } from '@prisma/client'
import { LdapService } from './LdapService.js'
import { prisma } from '../prisma.js'
const ADMIN_GROUP = process.env.ADMIN_GROUP || 'PP-SERTI'
const INSPECTOR_GROUP = process.env.INSPECTOR_GROUP || 'Inspectors'
const USER_GROUP = process.env.USER_GROUP || 'G_SERVIDORES'
export class UserService {
static async importUser(username: string) {
const ldap = new LdapService()
const ldapUser = await ldap.getUser(username)
if (!ldapUser) throw new Error('User not found!')
const user: Omit<User, 'id' | 'createdAt' | 'updatedAt'> = {
username: ldapUser.username,
displayName: ldapUser.displayName,
mail: ldapUser.mail,
thumbnailPhoto: ldapUser.thumbnailPhoto,
roles: []
}
ldapUser.groups?.forEach(group => {
if (group === USER_GROUP) user.roles?.push('USER')
if (group === ADMIN_GROUP) user.roles?.push('ADMIN')
if (group === INSPECTOR_GROUP) user.roles?.push('INSPECTOR')
})
return await prisma.user.upsert({
where: { username: ldapUser.username },
update: user,
create: user
})
}
}

View File

@ -1,6 +0,0 @@
import { Request } from 'express'
import { User } from '@prisma/client'
export interface RequestWithUser extends Request {
user?: User | null
}

View File

@ -0,0 +1,23 @@
/**
* Retrieve a fixed number of elements from an array, evenly distributed but
* always including the first and last elements.
*
* @param {Array} originalArray - The array to operate on.
* @param {number} take - The number of elements to extract.
* @returns {Array}
*/
export function distributedCopy(originalArray: Array<any>, take: number = 10) {
if (originalArray.length <= take) return [...originalArray]
const newArray = [originalArray[0]]
const interval = (originalArray.length - 2) / (take - 2)
for (let i = 1; i < take - 1; i++)
newArray.push(originalArray[Math.floor(interval * i)])
newArray.push(originalArray[originalArray.length - 1])
return newArray
}

View File

@ -14,8 +14,8 @@
"target": "ES2020" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, "target": "ES2020" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */ // "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ "experimentalDecorators": true /* Enable experimental support for legacy experimental decorators. */,
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ "emitDecoratorMetadata": true /* Emit design-type metadata for decorated declarations in source files. */,
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
@ -106,5 +106,5 @@
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */ "skipLibCheck": true /* Skip type checking all .d.ts files. */
}, },
"include": ["src/**/*"] "include": ["src/**/*", "jobs/**/*", "globals.d.ts"]
} }