Compare commits

..

No commits in common. "2fe2b116594d806ab6f040afb4629afd54c68b34" and "ce5508fe7f5472b62b1f8bcbfc9d8eb8901e2c89" have entirely different histories.

8 changed files with 229 additions and 396 deletions

367
package-lock.json generated
View File

@ -8,17 +8,17 @@
"name": "frontend",
"version": "1.0.0-b0",
"dependencies": {
"@angular/animations": "^18.0.5",
"@angular/cdk": "~18.0.5",
"@angular/cdk-experimental": "^18.0.5",
"@angular/common": "^18.0.5",
"@angular/compiler": "^18.0.5",
"@angular/core": "^18.0.5",
"@angular/forms": "^18.0.5",
"@angular/material": "~18.0.5",
"@angular/platform-browser": "^18.0.5",
"@angular/platform-browser-dynamic": "^18.0.5",
"@angular/router": "^18.0.5",
"@angular/animations": "^18.0.4",
"@angular/cdk": "~18.0.4",
"@angular/cdk-experimental": "^18.0.4",
"@angular/common": "^18.0.4",
"@angular/compiler": "^18.0.4",
"@angular/core": "^18.0.4",
"@angular/forms": "^18.0.4",
"@angular/material": "~18.0.4",
"@angular/platform-browser": "^18.0.4",
"@angular/platform-browser-dynamic": "^18.0.4",
"@angular/router": "^18.0.4",
"@progress/kendo-date-math": "^1.5.13",
"rxjs": "~7.8.1",
"tslib": "^2.6.3",
@ -27,7 +27,7 @@
"devDependencies": {
"@angular-devkit/build-angular": "^18.0.6",
"@angular/cli": "^18.0.6",
"@angular/compiler-cli": "^18.0.5",
"@angular/compiler-cli": "^18.0.4",
"@types/jasmine": "~5.1.4",
"jasmine-core": "~5.1.2",
"karma": "~6.4.3",
@ -198,75 +198,6 @@
}
}
},
"node_modules/@angular-devkit/build-angular/node_modules/@angular/build": {
"version": "18.0.6",
"resolved": "https://registry.npmjs.org/@angular/build/-/build-18.0.6.tgz",
"integrity": "sha512-W6S1sx00D4pd7qDIyzPMNFmw8d783+/Aknl+2cUrYlJqw0Oan1Bt6mXVg48Jwxr0hVsovoNZXSRFXXI5hvW8ZA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "2.3.0",
"@angular-devkit/architect": "0.1800.6",
"@babel/core": "7.24.5",
"@babel/helper-annotate-as-pure": "7.22.5",
"@babel/helper-split-export-declaration": "7.24.5",
"@vitejs/plugin-basic-ssl": "1.1.0",
"ansi-colors": "4.1.3",
"browserslist": "^4.23.0",
"critters": "0.0.22",
"esbuild": "0.21.3",
"fast-glob": "3.3.2",
"https-proxy-agent": "7.0.4",
"inquirer": "9.2.22",
"lmdb": "3.0.8",
"magic-string": "0.30.10",
"mrmime": "2.0.0",
"ora": "5.4.1",
"parse5-html-rewriting-stream": "7.0.0",
"picomatch": "4.0.2",
"piscina": "4.5.0",
"sass": "1.77.2",
"semver": "7.6.2",
"undici": "6.18.0",
"vite": "5.2.11",
"watchpack": "2.4.1"
},
"engines": {
"node": "^18.19.1 || ^20.11.1 || >=22.0.0",
"npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
"yarn": ">= 1.13.0"
},
"peerDependencies": {
"@angular/compiler-cli": "^18.0.0",
"@angular/localize": "^18.0.0",
"@angular/platform-server": "^18.0.0",
"@angular/service-worker": "^18.0.0",
"less": "^4.2.0",
"postcss": "^8.4.0",
"tailwindcss": "^2.0.0 || ^3.0.0",
"typescript": ">=5.4 <5.5"
},
"peerDependenciesMeta": {
"@angular/localize": {
"optional": true
},
"@angular/platform-server": {
"optional": true
},
"@angular/service-worker": {
"optional": true
},
"less": {
"optional": true
},
"postcss": {
"optional": true
},
"tailwindcss": {
"optional": true
}
}
},
"node_modules/@angular-devkit/build-angular/node_modules/@babel/core": {
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz",
@ -306,23 +237,6 @@
"semver": "bin/semver.js"
}
},
"node_modules/@angular-devkit/build-angular/node_modules/@ngtools/webpack": {
"version": "18.0.6",
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.0.6.tgz",
"integrity": "sha512-chSRbPpnqTThURQqUvWAgEGkLcn5TQnUQPD1HBf4WcoO/OkaK4Q1Sa8FrEllkC6/Dlyj7myi8rskQz+V8K7GSg==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.19.1 || ^20.11.1 || >=22.0.0",
"npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
"yarn": ">= 1.13.0"
},
"peerDependencies": {
"@angular/compiler-cli": "^18.0.0",
"typescript": ">=5.4 <5.5",
"webpack": "^5.54.0"
}
},
"node_modules/@angular-devkit/build-angular/node_modules/convert-source-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
@ -451,9 +365,9 @@
}
},
"node_modules/@angular/animations": {
"version": "18.0.5",
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-18.0.5.tgz",
"integrity": "sha512-RYwlS+4I33beAWdzFFmaDPqXZN+r66qPzzMOk9LQguwF76eBJbykHniODalSLvjrY6Iz7CULavByYNpzq2TT7A==",
"version": "18.0.4",
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-18.0.4.tgz",
"integrity": "sha512-xbdtBUvpTGEmVQkCoOad26LBMRy9ddM9pvCidMZBWXiM7NEuc3dfVT99a1cU4MZFiJeiQEvOWQn03iXskbBMGQ==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
@ -462,13 +376,143 @@
"node": "^18.19.1 || ^20.11.1 || >=22.0.0"
},
"peerDependencies": {
"@angular/core": "18.0.5"
"@angular/core": "18.0.4"
}
},
"node_modules/@angular/build": {
"version": "18.0.6",
"resolved": "https://registry.npmjs.org/@angular/build/-/build-18.0.6.tgz",
"integrity": "sha512-W6S1sx00D4pd7qDIyzPMNFmw8d783+/Aknl+2cUrYlJqw0Oan1Bt6mXVg48Jwxr0hVsovoNZXSRFXXI5hvW8ZA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "2.3.0",
"@angular-devkit/architect": "0.1800.6",
"@babel/core": "7.24.5",
"@babel/helper-annotate-as-pure": "7.22.5",
"@babel/helper-split-export-declaration": "7.24.5",
"@vitejs/plugin-basic-ssl": "1.1.0",
"ansi-colors": "4.1.3",
"browserslist": "^4.23.0",
"critters": "0.0.22",
"esbuild": "0.21.3",
"fast-glob": "3.3.2",
"https-proxy-agent": "7.0.4",
"inquirer": "9.2.22",
"lmdb": "3.0.8",
"magic-string": "0.30.10",
"mrmime": "2.0.0",
"ora": "5.4.1",
"parse5-html-rewriting-stream": "7.0.0",
"picomatch": "4.0.2",
"piscina": "4.5.0",
"sass": "1.77.2",
"semver": "7.6.2",
"undici": "6.18.0",
"vite": "5.2.11",
"watchpack": "2.4.1"
},
"engines": {
"node": "^18.19.1 || ^20.11.1 || >=22.0.0",
"npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
"yarn": ">= 1.13.0"
},
"peerDependencies": {
"@angular/compiler-cli": "^18.0.0",
"@angular/localize": "^18.0.0",
"@angular/platform-server": "^18.0.0",
"@angular/service-worker": "^18.0.0",
"less": "^4.2.0",
"postcss": "^8.4.0",
"tailwindcss": "^2.0.0 || ^3.0.0",
"typescript": ">=5.4 <5.5"
},
"peerDependenciesMeta": {
"@angular/localize": {
"optional": true
},
"@angular/platform-server": {
"optional": true
},
"@angular/service-worker": {
"optional": true
},
"less": {
"optional": true
},
"postcss": {
"optional": true
},
"tailwindcss": {
"optional": true
}
}
},
"node_modules/@angular/build/node_modules/@babel/core": {
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz",
"integrity": "sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.24.2",
"@babel/generator": "^7.24.5",
"@babel/helper-compilation-targets": "^7.23.6",
"@babel/helper-module-transforms": "^7.24.5",
"@babel/helpers": "^7.24.5",
"@babel/parser": "^7.24.5",
"@babel/template": "^7.24.0",
"@babel/traverse": "^7.24.5",
"@babel/types": "^7.24.5",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
"json5": "^2.2.3",
"semver": "^6.3.1"
},
"engines": {
"node": ">=6.9.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/babel"
}
},
"node_modules/@angular/build/node_modules/@babel/core/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/@angular/build/node_modules/convert-source-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
"dev": true,
"license": "MIT"
},
"node_modules/@angular/build/node_modules/semver": {
"version": "7.6.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
"integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@angular/cdk": {
"version": "18.0.5",
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.0.5.tgz",
"integrity": "sha512-Yf94Udxip8xjVIJlxwh80h6fUpX5JFcBv3FCFer7DU/YzWdoTL+BTIYF8og+NjlDRt1nSbTxdyU2LVI0rTVkpg==",
"version": "18.0.4",
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.0.4.tgz",
"integrity": "sha512-OCG1EGv/nyZYGcSu7y6IAuarC5gZcZYhhvEQsgMUDrf1TGRSa+0dBN5W2HxRWKs6NsGgDjW1VcK+AC85PYLXPA==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
@ -483,15 +527,15 @@
}
},
"node_modules/@angular/cdk-experimental": {
"version": "18.0.5",
"resolved": "https://registry.npmjs.org/@angular/cdk-experimental/-/cdk-experimental-18.0.5.tgz",
"integrity": "sha512-vrEDGbQ6afdrausjP9F9fYWszT/hUe+OWlZczFUqVwd/2TveRuPoF419OIGHwJKzFkZF08wBP2qqUsHj4AZevQ==",
"version": "18.0.4",
"resolved": "https://registry.npmjs.org/@angular/cdk-experimental/-/cdk-experimental-18.0.4.tgz",
"integrity": "sha512-e1MXfNFUPJmg34dlg6mXvs/wn2U66QyKWhdha5DdZ5QECozBtdBY7WwQcjGOb7KnQm+lk38WiWG5REwqJjnvzg==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
},
"peerDependencies": {
"@angular/cdk": "18.0.5",
"@angular/cdk": "18.0.4",
"@angular/core": "^18.0.0 || ^19.0.0"
}
},
@ -542,9 +586,9 @@
}
},
"node_modules/@angular/common": {
"version": "18.0.5",
"resolved": "https://registry.npmjs.org/@angular/common/-/common-18.0.5.tgz",
"integrity": "sha512-yItVQSu+Rx8gthWJDTOHwbzItY8/lqmmmYA1RMex0u3GkJoX3/3TZSGXbbBXl8GH8vmQOfp9yj3C02JmlwldRg==",
"version": "18.0.4",
"resolved": "https://registry.npmjs.org/@angular/common/-/common-18.0.4.tgz",
"integrity": "sha512-7WxZKLzSu5QtyLGrtlZrtUQlP3WfDR++yHr5jF9DJZ3IY35UutwiPCegCcq4Qh5X2xWqnRKGm20TLlKVoj0t5Q==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
@ -553,14 +597,14 @@
"node": "^18.19.1 || ^20.11.1 || >=22.0.0"
},
"peerDependencies": {
"@angular/core": "18.0.5",
"@angular/core": "18.0.4",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@angular/compiler": {
"version": "18.0.5",
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-18.0.5.tgz",
"integrity": "sha512-U1/qjNDjxMukXwQrJZjmr87KVxQmHbD7fxVlg0+qafHLe+YDuCtyOfQSGEZrWhwktxvAYZbl3FK+m3Hnk/D3Nw==",
"version": "18.0.4",
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-18.0.4.tgz",
"integrity": "sha512-OVPXtJo5SkGQUCioCVxKcRfEw48tz8xCtJGDXjVKWtyOkXnmWl8Y/e54mteiJd1KybXHvPLW0LPtWZYB06Qy7g==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
@ -569,7 +613,7 @@
"node": "^18.19.1 || ^20.11.1 || >=22.0.0"
},
"peerDependencies": {
"@angular/core": "18.0.5"
"@angular/core": "18.0.4"
},
"peerDependenciesMeta": {
"@angular/core": {
@ -578,9 +622,9 @@
}
},
"node_modules/@angular/compiler-cli": {
"version": "18.0.5",
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-18.0.5.tgz",
"integrity": "sha512-aFKDDTsRmc691EkNRj9OkrKNXDOaHdXB42MyUrj3WwJIJFMnSY/UDf6h+CRVF0U+CITszFyWhmeHQRA/3mJWNg==",
"version": "18.0.4",
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-18.0.4.tgz",
"integrity": "sha512-pUv664JCZHKHsLDvO8iNjWXVHOB2ggKxVoxiowOMNpR4dqxrK/oOLGkPGltYUW/xF6Eajc7Zs0lK/R5uljoYQg==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -602,14 +646,14 @@
"node": "^18.19.1 || ^20.11.1 || >=22.0.0"
},
"peerDependencies": {
"@angular/compiler": "18.0.5",
"@angular/compiler": "18.0.4",
"typescript": ">=5.4 <5.5"
}
},
"node_modules/@angular/core": {
"version": "18.0.5",
"resolved": "https://registry.npmjs.org/@angular/core/-/core-18.0.5.tgz",
"integrity": "sha512-0UuL+aMMWGYksz09YBsiHq1li7GmL8obB3IC3T5MwDqnn7FGRUBfBUOZEkM6B+pwgg+RAtNdJkbCfbh1z74bFQ==",
"version": "18.0.4",
"resolved": "https://registry.npmjs.org/@angular/core/-/core-18.0.4.tgz",
"integrity": "sha512-k0AUZbJc0eyzRexvKlR1sR0qNhe54Om9ln6lRn7y1+gAsg+OwFDyF427fFuzqpZVe/MmpvX3CXWdl0twZAYEiA==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
@ -623,9 +667,9 @@
}
},
"node_modules/@angular/forms": {
"version": "18.0.5",
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-18.0.5.tgz",
"integrity": "sha512-nO7bN+nO2/czgKSvPx6ewqpfb8xXOyns06uovWpAXSH4jYoiZ6CHTHhOKrOL/3SRkhUV9u+EUXTTAOSBkS+OBA==",
"version": "18.0.4",
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-18.0.4.tgz",
"integrity": "sha512-LM2rVIuJa2fGxP0oCy0uFSGY6h9tyL64gtGp02QqKaVszG4oJ8wue0/VSbBtKyH0xEN4eOXDzOXbiahbtFhRZA==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
@ -634,16 +678,16 @@
"node": "^18.19.1 || ^20.11.1 || >=22.0.0"
},
"peerDependencies": {
"@angular/common": "18.0.5",
"@angular/core": "18.0.5",
"@angular/platform-browser": "18.0.5",
"@angular/common": "18.0.4",
"@angular/core": "18.0.4",
"@angular/platform-browser": "18.0.4",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@angular/material": {
"version": "18.0.5",
"resolved": "https://registry.npmjs.org/@angular/material/-/material-18.0.5.tgz",
"integrity": "sha512-8xCyEs9yT7tp7vSBcVww+Adt2ue0oFG2yfISYRNweg0RXC9qk64DDhcIyYhn92xLSnFH1q6mPcjrNw8RmhA10A==",
"version": "18.0.4",
"resolved": "https://registry.npmjs.org/@angular/material/-/material-18.0.4.tgz",
"integrity": "sha512-ES4peq3+tMEPKe9RgdQ3pp3CcjM0Cr+vi4f+0ruH2wu1NTBk522/1/ABHncg3A/eCurKS96JJdihqOAjMek4Ow==",
"license": "MIT",
"dependencies": {
"@material/animation": "15.0.0-canary.7f224ddd4.0",
@ -698,7 +742,7 @@
},
"peerDependencies": {
"@angular/animations": "^18.0.0 || ^19.0.0",
"@angular/cdk": "18.0.5",
"@angular/cdk": "18.0.4",
"@angular/common": "^18.0.0 || ^19.0.0",
"@angular/core": "^18.0.0 || ^19.0.0",
"@angular/forms": "^18.0.0 || ^19.0.0",
@ -707,9 +751,9 @@
}
},
"node_modules/@angular/platform-browser": {
"version": "18.0.5",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-18.0.5.tgz",
"integrity": "sha512-hBKaGz7dhsjNhD0aWB8G2/YZQ/MaBhzFIQSAZMPs2ccAqH1Jx772/Y11k57seA3VaPpnL8WZ1apOSJgALUJ//w==",
"version": "18.0.4",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-18.0.4.tgz",
"integrity": "sha512-8TJEPzIRV89s1ZP9T+7g9K7PFNfec+4Xyw5BLaTRBOqjXHmMzk+miRx0L18Lr66rp5r2vbNEE9vojMVHQRwhVA==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
@ -718,9 +762,9 @@
"node": "^18.19.1 || ^20.11.1 || >=22.0.0"
},
"peerDependencies": {
"@angular/animations": "18.0.5",
"@angular/common": "18.0.5",
"@angular/core": "18.0.5"
"@angular/animations": "18.0.4",
"@angular/common": "18.0.4",
"@angular/core": "18.0.4"
},
"peerDependenciesMeta": {
"@angular/animations": {
@ -729,9 +773,9 @@
}
},
"node_modules/@angular/platform-browser-dynamic": {
"version": "18.0.5",
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-18.0.5.tgz",
"integrity": "sha512-i8CXojKcjsKzD2JR2clIisqavlHCW1jw+F2hJVrf/JR9iu6kVpGpZOqb3yYHoQCsPa7hUzQnn0ewYwBvlWsDmw==",
"version": "18.0.4",
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-18.0.4.tgz",
"integrity": "sha512-K36/gamqs8etGlmWew7IwZ/bDJdI5ZeUqvOUmkKjJ9F2I/g5P/zZrB1qExwN/zsxzxd9idkvEhwY+YDeiZEEJg==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
@ -740,16 +784,16 @@
"node": "^18.19.1 || ^20.11.1 || >=22.0.0"
},
"peerDependencies": {
"@angular/common": "18.0.5",
"@angular/compiler": "18.0.5",
"@angular/core": "18.0.5",
"@angular/platform-browser": "18.0.5"
"@angular/common": "18.0.4",
"@angular/compiler": "18.0.4",
"@angular/core": "18.0.4",
"@angular/platform-browser": "18.0.4"
}
},
"node_modules/@angular/router": {
"version": "18.0.5",
"resolved": "https://registry.npmjs.org/@angular/router/-/router-18.0.5.tgz",
"integrity": "sha512-GmdzD5FZYPKCGP6mV3AZraAU6czfGcjjCym6mIsdJr3DyMwnQSwaaHAu8qlQbPDVfsP+gKVSPh1JxI1lzzarLA==",
"version": "18.0.4",
"resolved": "https://registry.npmjs.org/@angular/router/-/router-18.0.4.tgz",
"integrity": "sha512-nr1ZI3lynKBtr3a75APuVkIaiXRG5mEnW/RIyxwzxbKBB14901mby46o0jm9Y/CPb2rH5UpuwZhTKRE6QS/xLw==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
@ -758,9 +802,9 @@
"node": "^18.19.1 || ^20.11.1 || >=22.0.0"
},
"peerDependencies": {
"@angular/common": "18.0.5",
"@angular/core": "18.0.5",
"@angular/platform-browser": "18.0.5",
"@angular/common": "18.0.4",
"@angular/core": "18.0.4",
"@angular/platform-browser": "18.0.4",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
@ -4173,6 +4217,23 @@
"win32"
]
},
"node_modules/@ngtools/webpack": {
"version": "18.0.6",
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.0.6.tgz",
"integrity": "sha512-chSRbPpnqTThURQqUvWAgEGkLcn5TQnUQPD1HBf4WcoO/OkaK4Q1Sa8FrEllkC6/Dlyj7myi8rskQz+V8K7GSg==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.19.1 || ^20.11.1 || >=22.0.0",
"npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
"yarn": ">= 1.13.0"
},
"peerDependencies": {
"@angular/compiler-cli": "^18.0.0",
"typescript": ">=5.4 <5.5",
"webpack": "^5.54.0"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",

View File

@ -10,17 +10,17 @@
},
"private": true,
"dependencies": {
"@angular/animations": "^18.0.5",
"@angular/cdk": "~18.0.5",
"@angular/cdk-experimental": "^18.0.5",
"@angular/common": "^18.0.5",
"@angular/compiler": "^18.0.5",
"@angular/core": "^18.0.5",
"@angular/forms": "^18.0.5",
"@angular/material": "~18.0.5",
"@angular/platform-browser": "^18.0.5",
"@angular/platform-browser-dynamic": "^18.0.5",
"@angular/router": "^18.0.5",
"@angular/animations": "^18.0.4",
"@angular/cdk": "~18.0.4",
"@angular/cdk-experimental": "^18.0.4",
"@angular/common": "^18.0.4",
"@angular/compiler": "^18.0.4",
"@angular/core": "^18.0.4",
"@angular/forms": "^18.0.4",
"@angular/material": "~18.0.4",
"@angular/platform-browser": "^18.0.4",
"@angular/platform-browser-dynamic": "^18.0.4",
"@angular/router": "^18.0.4",
"@progress/kendo-date-math": "^1.5.13",
"rxjs": "~7.8.1",
"tslib": "^2.6.3",
@ -29,7 +29,7 @@
"devDependencies": {
"@angular-devkit/build-angular": "^18.0.6",
"@angular/cli": "^18.0.6",
"@angular/compiler-cli": "^18.0.5",
"@angular/compiler-cli": "^18.0.4",
"@types/jasmine": "~5.1.4",
"jasmine-core": "~5.1.2",
"karma": "~6.4.3",

View File

@ -1,12 +1,10 @@
import {catchError, filter, mergeMap, Observable, retryWhen, switchMap, take, tap, timer} from "rxjs";
import {catchError, mergeMap, Observable, retryWhen, tap, timer} from "rxjs";
import {HttpClient, HttpErrorResponse} from "@angular/common/http";
import {NotifyColor, OpenNotifyService} from "@service/open-notify.service";
import {environment} from "@environment";
import {Router} from "@angular/router";
import {Injectable} from "@angular/core";
import {RequestBuilder, RequestData, SetRequestBuilderAfterBuild} from "@api/RequestBuilder";
import {TokenRefreshService} from "@service/token-refresh.service";
import {AuthToken} from "@service/auth.service";
export function retryWithInterval<T>(): (source: Observable<T>) => Observable<T> {
return (source: Observable<T>) =>
@ -37,14 +35,13 @@ export enum AvailableVersion {
@Injectable()
export default abstract class ApiService implements SetRequestBuilderAfterBuild {
constructor(private http: HttpClient, private notify: OpenNotifyService, private router: Router, protected tokenRefreshService: TokenRefreshService) {
constructor(private http: HttpClient, private notify: OpenNotifyService, private router: Router) {
}
private apiUrl = environment.apiUrl;
protected abstract basePath: string;
protected abstract version: AvailableVersion;
private request: RequestData = RequestBuilder.getStandardRequestData();
public static readonly tokenKey = 'auth_token';
public setRequestBuilder(request: RequestData): void {
this.request = request;
@ -131,18 +128,6 @@ export default abstract class ApiService implements SetRequestBuilderAfterBuild
return this.makeHttpRequest<Type>('delete');
}
public addAuth() {
const token = localStorage.getItem(ApiService.tokenKey);
if (!token)
return this;
const authToken = AuthToken.httpHeader((JSON.parse(token) as AuthToken));
authToken.keys().forEach(key => this.request.httpHeaders = this.request.httpHeaders.append(key, authToken.get(key) ?? ''))
return this;
}
private handleError(error: HttpErrorResponse): void {
// todo: change to Retry-After condition
if (error.error.toString().includes("setup")) {
@ -162,7 +147,6 @@ export default abstract class ApiService implements SetRequestBuilderAfterBuild
message = 'Ошибка запроса. Пожалуйста, проверьте отправленные данные.';
break;
case 401:
this.router.navigate(['/login/']).then();
message = 'Ошибка авторизации. Пожалуйста, выполните вход с правильными учетными данными.';
break;
case 403:

View File

@ -4,7 +4,6 @@ import {FooterComponent} from "@component/common/footer/footer.component";
import localeRu from '@angular/common/locales/ru';
import { registerLocaleData } from '@angular/common';
import {FocusNextDirective} from "@/directives/focus-next.directive";
import {TokenRefreshService} from "@service/token-refresh.service";
@Component({
selector: 'app-root',
@ -15,8 +14,7 @@ import {TokenRefreshService} from "@service/token-refresh.service";
<app-footer/>`
})
export class AppComponent {
constructor(tokenRefreshService: TokenRefreshService) {
constructor() {
registerLocaleData(localeRu);
tokenRefreshService.startTokenRefresh();
}
}

View File

@ -1,88 +0,0 @@
import {EventEmitter, Injectable} from '@angular/core';
import {HttpClient, HttpHeaders} from "@angular/common/http";
import {catchError, Observable, of, tap} from "rxjs";
import {TokenResponse} from "@api/v1/tokenResponse";
import ApiService from "@api/api.service";
export enum AvailableAuthenticationProvider {
Bearer
}
export class AuthToken {
accessToken: string;
expiresIn: Date;
authProvider: AvailableAuthenticationProvider;
endpoint: string;
constructor(accessToken: string, expiresIn: Date, authProvider: AvailableAuthenticationProvider, refreshEndpoint: string) {
this.accessToken = accessToken;
this.expiresIn = expiresIn;
this.authProvider = authProvider;
this.endpoint = refreshEndpoint;
}
static httpHeader(token: AuthToken): HttpHeaders {
let header = new HttpHeaders();
if (token.authProvider === AvailableAuthenticationProvider.Bearer)
header = header.set('Authorization', `Bearer ${token.accessToken}`);
return header;
}
}
@Injectable({
providedIn: 'root',
})
export class AuthService {
public expireTokenChange = new EventEmitter<Date>();
public tokenChangeError = new EventEmitter();
constructor(private http: HttpClient) {
}
public static setToken(token: TokenResponse, provider: AvailableAuthenticationProvider, refreshEndpoint: string) {
localStorage.setItem(ApiService.tokenKey, JSON.stringify(
new AuthToken(token.accessToken, token.expiresIn, provider, refreshEndpoint)
));
}
public static get tokenExpiresIn(): Date {
const token = localStorage.getItem(ApiService.tokenKey);
if (!token)
return new Date();
const result = new Date((JSON.parse(token) as AuthToken).expiresIn);
return result <= new Date() ? new Date() : result;
}
public refreshToken(): Observable<TokenResponse> {
const token = localStorage.getItem(ApiService.tokenKey);
if (!token)
return of();
const authToken = JSON.parse(token) as AuthToken;
switch (authToken.authProvider) {
case AvailableAuthenticationProvider.Bearer:
return this.http.get<TokenResponse>(authToken.endpoint, {withCredentials: true})
.pipe(
catchError(error => {
this.tokenChangeError.emit();
throw error;
}),
tap(response => {
const newExpireDate = new Date(response.expiresIn);
const oldExpireDate = new Date(authToken.expiresIn);
if (newExpireDate.getTime() !== oldExpireDate.getTime()) {
AuthService.setToken(response, AvailableAuthenticationProvider.Bearer, authToken.endpoint);
this.expireTokenChange.emit(newExpireDate);
}
})
);
}
}
}

View File

@ -1,72 +0,0 @@
import {BehaviorSubject, interval, Subscription, switchMap} from "rxjs";
import {Injectable} from "@angular/core";
import {AuthService} from "@service/auth.service";
import {environment} from "@environment";
@Injectable({
providedIn: 'root',
})
export class TokenRefreshService {
private tokenRefreshSubscription: Subscription | undefined;
private tokenRefreshing$ = new BehaviorSubject<boolean>(false);
private refreshTokenExpireMs: number = environment.retryDelay;
constructor(private authService: AuthService) {
this.setRefreshTokenExpireMs(AuthService.tokenExpiresIn.getTime() - 1000 - Date.now());
authService.tokenChangeError.subscribe(_ => {
console.log('Token change error event received');
this.tokenRefreshing$.next(false);
this.stopTokenRefresh();
});
authService.expireTokenChange.subscribe(date => {
console.log('Expire token change event received:', date);
this.setRefreshTokenExpireMs(date.getTime() - 1000 - Date.now());
});
}
public startTokenRefresh(date: Date | null = null): void {
if (date)
this.refreshTokenExpireMs = new Date(date).getTime() - 1000 - Date.now();
if (!this.tokenRefreshSubscription || this.tokenRefreshSubscription.closed) {
this.tokenRefreshSubscription = interval(this.refreshTokenExpireMs).pipe(
switchMap(() => {
this.tokenRefreshing$.next(true);
return this.authService.refreshToken();
})
).subscribe({
next: (_) => {
this.tokenRefreshing$.next(false);
},
error: error => {
console.error('Token refresh error:', error);
this.tokenRefreshing$.next(false);
}
});
}
}
public getTokenRefreshing$(): BehaviorSubject<boolean> {
return this.tokenRefreshing$;
}
public stopTokenRefresh(): void {
if (this.tokenRefreshSubscription && !this.tokenRefreshSubscription.closed) {
this.tokenRefreshSubscription.unsubscribe();
this.tokenRefreshSubscription = undefined;
}
}
public setRefreshTokenExpireMs(expireMs: number): void {
if (expireMs < environment.retryDelay)
expireMs = 3000;
console.log(expireMs);
this.refreshTokenExpireMs = expireMs;
this.stopTokenRefresh();
this.startTokenRefresh();
}
}

View File

@ -1,25 +0,0 @@
/**
* MIREA Schedule Web API
* This API provides a convenient interface for retrieving data stored in the database. Special attention was paid to the lightweight and easy transfer of all necessary data. Made by the Winsomnia team.
*
* OpenAPI spec version: 1.0
* Contact: support@winsomnia.net
*
* NOTE: This class is auto generated by the swagger code generator program.
* https://github.com/swagger-api/swagger-codegen.git
* Do not edit the class manually.
*/
/**
*
*/
export interface LoginRequest {
/**
*
*/
username: string;
/**
*
*/
password: number;
}

View File

@ -1,25 +0,0 @@
/**
* MIREA Schedule Web API
* This API provides a convenient interface for retrieving data stored in the database. Special attention was paid to the lightweight and easy transfer of all necessary data. Made by the Winsomnia team.
*
* OpenAPI spec version: 1.0
* Contact: support@winsomnia.net
*
* NOTE: This class is auto generated by the swagger code generator program.
* https://github.com/swagger-api/swagger-codegen.git
* Do not edit the class manually.
*/
/**
*
*/
export interface TokenResponse {
/**
*
*/
accessToken : string;
/**
*
*/
expiresIn: Date;
}