mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
Compare commits
455 Commits
1.4
...
feature/sc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a27a0c8b9c | ||
|
|
acb76c1384 | ||
|
|
5882655862 | ||
|
|
5b440da557 | ||
|
|
be91a0f1b4 | ||
|
|
65e3d5d4f1 | ||
|
|
a0e7614fa0 | ||
|
|
094c881e7f | ||
|
|
d3cd6f415b | ||
|
|
e733396c63 | ||
|
|
e88795f96b | ||
|
|
fd276b6553 | ||
|
|
22c3b057d7 | ||
|
|
b7feea46f7 | ||
|
|
9052fe25db | ||
|
|
776115fbed | ||
|
|
962f0bc2c6 | ||
|
|
c184df717d | ||
|
|
26a7348d25 | ||
|
|
cf5052cbe5 | ||
|
|
be848123a1 | ||
|
|
04232b85a7 | ||
|
|
09bb84c34e | ||
|
|
4449992442 | ||
|
|
39c40f2e13 | ||
|
|
9813575584 | ||
|
|
b0af718cbc | ||
|
|
550544cbe4 | ||
|
|
3ec3cf0e5b | ||
|
|
0b79464e52 | ||
|
|
a1642c5749 | ||
|
|
148aab1495 | ||
|
|
2f78f2685f | ||
|
|
6dde5682a9 | ||
|
|
af8a3d89af | ||
|
|
ec97d1e84a | ||
|
|
eea412abd3 | ||
|
|
cf65ba3e6b | ||
|
|
3d4445bb46 | ||
|
|
94753ceac4 | ||
|
|
af16542ce5 | ||
|
|
e4c82441b8 | ||
|
|
8783802483 | ||
|
|
8ad28a16b0 | ||
|
|
f9403649e6 | ||
|
|
698af2ecc3 | ||
|
|
c92649a8d4 | ||
|
|
dfd1847a2d | ||
|
|
eeaafec80a | ||
|
|
896e91d4d9 | ||
|
|
d6507b428f | ||
|
|
6ff4d204c2 | ||
|
|
8145436d1d | ||
|
|
25f22b46c5 | ||
|
|
558d846812 | ||
|
|
05916031ad | ||
|
|
ffad6aa939 | ||
|
|
cb22a39ffc | ||
|
|
c6130dcffb | ||
|
|
c9f4143204 | ||
|
|
fa0e1d7d60 | ||
|
|
621b545e34 | ||
|
|
53e3d90064 | ||
|
|
c175826fd3 | ||
|
|
c6b584d637 | ||
|
|
d992ce87a9 | ||
|
|
a87072e542 | ||
|
|
eff0388b8f | ||
|
|
9c517810ba | ||
|
|
882cbddec0 | ||
|
|
d188272cbf | ||
|
|
72b3688365 | ||
|
|
0f81914875 | ||
|
|
e8e895d7b1 | ||
|
|
f0cc76f180 | ||
|
|
91b3f44c1e | ||
|
|
43859599f0 | ||
|
|
4dff0b1e6a | ||
|
|
f5fcee4e4a | ||
|
|
9ffb0b9a97 | ||
|
|
34698aca5e | ||
|
|
4fcf9fabbf | ||
|
|
fd907cf0cc | ||
|
|
cb543c7c98 | ||
|
|
02ee730080 | ||
|
|
5cccc5fedd | ||
|
|
9b6f4d1ecf | ||
|
|
022e8e9e73 | ||
|
|
a81581e67f | ||
|
|
5823f57e03 | ||
|
|
96f2233421 | ||
|
|
ac1a772b21 | ||
|
|
04851dbe39 | ||
|
|
29181469db | ||
|
|
cb447e13f9 | ||
|
|
04a3b1767a | ||
|
|
20f1a5c77e | ||
|
|
c979b48592 | ||
|
|
88ebc39d65 | ||
|
|
9aa3820e95 | ||
|
|
722ed6ade6 | ||
|
|
8f7448a095 | ||
|
|
5e9b3b56d4 | ||
|
|
95656b20d7 | ||
|
|
12fe8b46c3 | ||
|
|
92958f4b22 | ||
|
|
05a3bbef7a | ||
|
|
03467fcb83 | ||
|
|
e96c98e344 | ||
|
|
c660c5626d | ||
|
|
b443c7a5de | ||
|
|
578f3fee7a | ||
|
|
caddcd0e2b | ||
|
|
cce810d4e3 | ||
|
|
2f060e6209 | ||
|
|
df94c1ab59 | ||
|
|
9c2ed96331 | ||
|
|
b462e39a51 | ||
|
|
8340649292 | ||
|
|
1d2df695d3 | ||
|
|
f46ef394d9 | ||
|
|
fcf016ea85 | ||
|
|
e2ebba9f9f | ||
|
|
d9460df0ca | ||
|
|
9d988e18be | ||
|
|
949ee7da6e | ||
|
|
736f402179 | ||
|
|
e5c4eb6a8e | ||
|
|
c93233674b | ||
|
|
02abf0852e | ||
|
|
cf3e5ce9a3 | ||
|
|
da71454070 | ||
|
|
08a8575025 | ||
|
|
a99494b6ea | ||
|
|
52ab4fccbd | ||
|
|
2ab1599fa3 | ||
|
|
f220dde3b2 | ||
|
|
d027df8856 | ||
|
|
1dc979baaf | ||
|
|
f9908eaa57 | ||
|
|
8e8150e246 | ||
|
|
19fe83ed25 | ||
|
|
3c033a1f0d | ||
|
|
be031fb702 | ||
|
|
34a6c6d997 | ||
|
|
fa2c0d101f | ||
|
|
c3f6cef14e | ||
|
|
67bcb25106 | ||
|
|
6cfbce4f16 | ||
|
|
cc65a29b05 | ||
|
|
79d1cb7e87 | ||
|
|
925b0e75db | ||
|
|
85b6439d5f | ||
|
|
8d75d2e9c9 | ||
|
|
af7e2298b5 | ||
|
|
11fd851f21 | ||
|
|
2360a63fcb | ||
|
|
20e8433963 | ||
|
|
c655c1b90b | ||
|
|
215e542516 | ||
|
|
680ab2d92a | ||
|
|
c162c00c8d | ||
|
|
e5dfc2484b | ||
|
|
1962f47f81 | ||
|
|
6b9ea288d1 | ||
|
|
baeaec54c5 | ||
|
|
7272415ed4 | ||
|
|
c75d956c52 | ||
|
|
99d8084fe3 | ||
|
|
e61601fca2 | ||
|
|
0cfeabb0f2 | ||
|
|
d059646ebe | ||
|
|
553c5cd1c1 | ||
|
|
ba0a4f1bf9 | ||
|
|
614e8f0ccc | ||
|
|
428a905caa | ||
|
|
6193306255 | ||
|
|
b2913f9fa0 | ||
|
|
ce5ec1d961 | ||
|
|
96725d1730 | ||
|
|
eec018de3c | ||
|
|
f753d33731 | ||
|
|
ffd69fbc75 | ||
|
|
5997e67322 | ||
|
|
fe60370c0a | ||
|
|
cfee8e571e | ||
|
|
b4a7d3c879 | ||
|
|
9c1565dfcc | ||
|
|
a8b8573734 | ||
|
|
c09e559430 | ||
|
|
cbc1b85d92 | ||
|
|
d6f3ae7179 | ||
|
|
12c87aaa25 | ||
|
|
67a0230b7b | ||
|
|
539a6d420e | ||
|
|
f1baa7c0d2 | ||
|
|
36ac3776b7 | ||
|
|
a6b50631dc | ||
|
|
71fdedb2d9 | ||
|
|
f4be47a0d7 | ||
|
|
295400892a | ||
|
|
5c3d28033f | ||
|
|
229cb55b46 | ||
|
|
99fea8c2df | ||
|
|
1d7fd4fab1 | ||
|
|
76e1b7d320 | ||
|
|
0d4ca1b6a6 | ||
|
|
9050f33fcc | ||
|
|
8a1b151740 | ||
|
|
f088098159 | ||
|
|
d9462a2d17 | ||
|
|
eec72499e7 | ||
|
|
93016af766 | ||
|
|
5d699dff8f | ||
|
|
12e4f48460 | ||
|
|
68ad3cd407 | ||
|
|
c19c4d471d | ||
|
|
9cf5f23b5a | ||
|
|
b71d383c74 | ||
|
|
5086c3c82d | ||
|
|
d98c30506e | ||
|
|
6a4e5a121f | ||
|
|
15a8d2151e | ||
|
|
025672bd91 | ||
|
|
a925050912 | ||
|
|
a4a4fc344a | ||
|
|
bc1fc27a5d | ||
|
|
acfd88ec55 | ||
|
|
3745583e1c | ||
|
|
60f1a89984 | ||
|
|
cbfa2fc98c | ||
|
|
7c37a0bb05 | ||
|
|
699745526e | ||
|
|
ef9573a74d | ||
|
|
7efe8196a5 | ||
|
|
f1ed3f7438 | ||
|
|
30bdc0ee54 | ||
|
|
8c7217ad56 | ||
|
|
4f15bc257d | ||
|
|
4c25c5769a | ||
|
|
30a9225227 | ||
|
|
4a0d79b138 | ||
|
|
8856c984dc | ||
|
|
0198e635b3 | ||
|
|
f477411667 | ||
|
|
963aaa974e | ||
|
|
49bb23805e | ||
|
|
a1dea02498 | ||
|
|
e7eef7f169 | ||
|
|
072045ed59 | ||
|
|
9078c57909 | ||
|
|
abb15a9e28 | ||
|
|
2766751c79 | ||
|
|
d9df337ff0 | ||
|
|
5d2cfaf269 | ||
|
|
4a9b5ace01 | ||
|
|
e475ab6047 | ||
|
|
e2d78ff89e | ||
|
|
1260d29324 | ||
|
|
bed12304c0 | ||
|
|
c2ede383dd | ||
|
|
0e6f56230a | ||
|
|
ac7705442f | ||
|
|
b5a3ee19a4 | ||
|
|
29c8cc2062 | ||
|
|
098f314b83 | ||
|
|
5885aa759d | ||
|
|
c0dec7729c | ||
|
|
8d26d94b9f | ||
|
|
a207fc90dc | ||
|
|
13e0e4b5fa | ||
|
|
4c95bb8354 | ||
|
|
a4a39f643c | ||
|
|
744ee451cf | ||
|
|
c44af0d61e | ||
|
|
6cf385a6f0 | ||
|
|
c30d81dfa5 | ||
|
|
77a645bfe9 | ||
|
|
c6a0e87fdd | ||
|
|
990927d9d5 | ||
|
|
342db106fe | ||
|
|
43b56351ad | ||
|
|
a212a4fa90 | ||
|
|
1c7c79451d | ||
|
|
27a9d446ef | ||
|
|
0115e235c2 | ||
|
|
e838964860 | ||
|
|
8c2862cc80 | ||
|
|
445ba6ba20 | ||
|
|
5793094187 | ||
|
|
861333d748 | ||
|
|
75a45de060 | ||
|
|
b87485a56f | ||
|
|
8612f335ac | ||
|
|
cd56bc1fc0 | ||
|
|
bf768b970f | ||
|
|
1e083d0e64 | ||
|
|
2dc8141bbc | ||
|
|
a9bbc672c9 | ||
|
|
b51590e374 | ||
|
|
21f609053a | ||
|
|
0fe3e324cb | ||
|
|
921144c1f9 | ||
|
|
af2ab8f104 | ||
|
|
5c3539c820 | ||
|
|
6bdb758bc6 | ||
|
|
1d36c1b55d | ||
|
|
a4daa8b36d | ||
|
|
dd91b7c023 | ||
|
|
472716d06d | ||
|
|
5f34fa811c | ||
|
|
9538c364d4 | ||
|
|
b3f0c08e2c | ||
|
|
406ec89625 | ||
|
|
d0c930c080 | ||
|
|
85b3636cee | ||
|
|
d3a477425e | ||
|
|
62439f9e7d | ||
|
|
b0581c2f2b | ||
|
|
9dd7da7630 | ||
|
|
0e9edf0aba | ||
|
|
42583d306b | ||
|
|
11593091fd | ||
|
|
947da3d647 | ||
|
|
a6502a23af | ||
|
|
abeb6ea5a2 | ||
|
|
ff749f8892 | ||
|
|
2c3115dc47 | ||
|
|
d96729fb89 | ||
|
|
7b49fbc85c | ||
|
|
436936ff41 | ||
|
|
0f4c2c84d2 | ||
|
|
02f8f747e1 | ||
|
|
26d538adf2 | ||
|
|
ba3618d58a | ||
|
|
72568c3ae2 | ||
|
|
3f6b0447b9 | ||
|
|
be81bdf0b4 | ||
|
|
19340905db | ||
|
|
1f5cbd8579 | ||
|
|
bd0535fdc4 | ||
|
|
343bed6a6b | ||
|
|
816232d993 | ||
|
|
2ab8faa9eb | ||
|
|
144d105a13 | ||
|
|
f35b83368d | ||
|
|
00fb992f23 | ||
|
|
fec1f1ef43 | ||
|
|
83c36aff02 | ||
|
|
5f477f2665 | ||
|
|
ca74eea880 | ||
|
|
5808cbd0d9 | ||
|
|
50f9cb7405 | ||
|
|
b9e28dfdcd | ||
|
|
288f2fc920 | ||
|
|
1d42a2cf6c | ||
|
|
4fda2a4770 | ||
|
|
39169b338a | ||
|
|
d67a05b71d | ||
|
|
cdcf90c4c3 | ||
|
|
9fef39924e | ||
|
|
2f1584436a | ||
|
|
5041be7831 | ||
|
|
df19fc5ace | ||
|
|
9ce527aa3b | ||
|
|
ac27bb1fde | ||
|
|
d311dab698 | ||
|
|
5ab8c2b4f5 | ||
|
|
3729e81005 | ||
|
|
c3d0ead801 | ||
|
|
2658ef19a5 | ||
|
|
34e66e6a49 | ||
|
|
e9881fa9c5 | ||
|
|
da92de1990 | ||
|
|
1e2b527a80 | ||
|
|
7b71c0e343 | ||
|
|
658e64cbbc | ||
|
|
eabf8838c7 | ||
|
|
d3d4ef5e4d | ||
|
|
8239e553e7 | ||
|
|
83a24045be | ||
|
|
54b87db9c6 | ||
|
|
4961e580c5 | ||
|
|
a07be1ffd4 | ||
|
|
7c10dde089 | ||
|
|
04f74b6628 | ||
|
|
898374cdeb | ||
|
|
0db8688f19 | ||
|
|
286024739f | ||
|
|
771fb802ec | ||
|
|
9e34b35888 | ||
|
|
08a2abb773 | ||
|
|
dd904a863e | ||
|
|
15f5aa7d10 | ||
|
|
647f5a06be | ||
|
|
f501bab5d2 | ||
|
|
19539da8aa | ||
|
|
33173f88f3 | ||
|
|
a75febfeed | ||
|
|
d786adb604 | ||
|
|
96e147d26c | ||
|
|
1e66a624ef | ||
|
|
9bc93396d4 | ||
|
|
af307b7691 | ||
|
|
65c9539e80 | ||
|
|
16ca761e3d | ||
|
|
6e9c5a4300 | ||
|
|
7c2b556b0b | ||
|
|
7920c8acd1 | ||
|
|
4e8b5557fc | ||
|
|
f6da92ee92 | ||
|
|
3482b68f29 | ||
|
|
e907784528 | ||
|
|
26dc615a8c | ||
|
|
aef54b9f83 | ||
|
|
f0a4006ef0 | ||
|
|
ab93bccb00 | ||
|
|
20c95a8a26 | ||
|
|
4adb5f074e | ||
|
|
6aa98b8de6 | ||
|
|
17214b41d5 | ||
|
|
80a75df458 | ||
|
|
d3ac8d3ab5 | ||
|
|
9e76cfecfd | ||
|
|
f26becc505 | ||
|
|
09ac9bee7a | ||
|
|
e728860dd4 | ||
|
|
6c52fcd555 | ||
|
|
7ad7177f89 | ||
|
|
870b60320f | ||
|
|
df3340afef | ||
|
|
b8e409e92b | ||
|
|
0fa8f36144 | ||
|
|
3cdec75079 | ||
|
|
6d4373ae14 | ||
|
|
0883012a67 | ||
|
|
c4a5f21869 | ||
|
|
da8bcffd0b | ||
|
|
bf53c30d24 | ||
|
|
fe1c59607f | ||
|
|
5d0915ffd6 | ||
|
|
061b962359 | ||
|
|
d627a263c3 | ||
|
|
590407d04a | ||
|
|
236d81de15 | ||
|
|
8c3e3ed309 | ||
|
|
7cf8201b48 | ||
|
|
b34f7ad5f0 | ||
|
|
850743e2ee | ||
|
|
e46c6f258a | ||
|
|
89cc11d9c5 | ||
|
|
355be3bddb | ||
|
|
2b500784c4 | ||
|
|
79a62b4db9 | ||
|
|
f19df87ba0 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -34,6 +34,7 @@ speed-measure-plugin.json
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage
|
||||
/testresults
|
||||
/libpeerconnection.log
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
@@ -45,4 +46,4 @@ testem.log
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
libs/swagger/src/lib/*
|
||||
libs/swagger/src/lib/*
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
/helmvalues
|
||||
/apps/swagger
|
||||
/ng-swagger-gen
|
||||
/apps/isa-app/src/assets
|
||||
|
||||
*.json
|
||||
*.yml
|
||||
|
||||
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
@@ -3,7 +3,6 @@
|
||||
"johnpapa.angular2",
|
||||
"esbenp.prettier-vscode",
|
||||
"angular.ng-template",
|
||||
"ms-vscode.vscode-typescript-tslint-plugin",
|
||||
"eg2.vscode-npm-script"
|
||||
]
|
||||
}
|
||||
}
|
||||
20
Dockerfile
20
Dockerfile
@@ -1,5 +1,5 @@
|
||||
#stage 1
|
||||
FROM node:14 as node
|
||||
FROM node:14 as base
|
||||
ARG IS_PRODUCTION=false
|
||||
ARG SEMVERSION=1.0.0
|
||||
ARG BuildUniqueID
|
||||
@@ -11,9 +11,19 @@ RUN npm version ${SEMVERSION}
|
||||
RUN npm install --always-auth=false
|
||||
RUN if [ "${IS_PRODUCTION}" = "true" ] ; then npm run-script build-prod ; else npm run-script build ; fi
|
||||
|
||||
# stage 2
|
||||
FROM nginx:alpine
|
||||
# stage final
|
||||
FROM nginx:alpine as publish
|
||||
ARG BuildUniqueID
|
||||
LABEL build.uniqueid="${BuildUniqueID:-1}"
|
||||
COPY --from=node /app/dist/sales /usr/share/nginx/html
|
||||
COPY --from=node /app/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY --from=base /app/dist/isa-app /usr/share/nginx/html
|
||||
COPY --from=base /app/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
# stage npm test
|
||||
FROM base as test
|
||||
ARG BuildUniqueID
|
||||
LABEL build.uniqueid="${BuildUniqueID:-1}"
|
||||
RUN wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb -q -O /tmp/chrome.deb && apt update && apt install -y /tmp/chrome.deb
|
||||
# ignore exitcode, sonst gibts keinen container
|
||||
RUN npm test || true
|
||||
ENTRYPOINT [ "/bin/sleep", "60000" ]
|
||||
|
||||
|
||||
1170
angular.json
1170
angular.json
File diff suppressed because it is too large
Load Diff
25
apps/adapter/scan/README.md
Normal file
25
apps/adapter/scan/README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Scan
|
||||
|
||||
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 12.2.0.
|
||||
|
||||
## Code scaffolding
|
||||
|
||||
Run `ng generate component component-name --project scan` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project scan`.
|
||||
|
||||
> Note: Don't forget to add `--project scan` or else it will be added to the default project in your `angular.json` file.
|
||||
|
||||
## Build
|
||||
|
||||
Run `ng build scan` to build the project. The build artifacts will be stored in the `dist/` directory.
|
||||
|
||||
## Publishing
|
||||
|
||||
After building your library with `ng build scan`, go to the dist folder `cd dist/scan` and run `npm publish`.
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `ng test scan` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||
|
||||
## Further help
|
||||
|
||||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
|
||||
41
apps/adapter/scan/karma.conf.js
Normal file
41
apps/adapter/scan/karma.conf.js
Normal file
@@ -0,0 +1,41 @@
|
||||
// Karma configuration file, see link for more information
|
||||
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage'),
|
||||
require('@angular-devkit/build-angular/plugins/karma'),
|
||||
],
|
||||
client: {
|
||||
jasmine: {
|
||||
// you can add configuration options for Jasmine here
|
||||
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
|
||||
// for example, you can disable the random execution with `random: false`
|
||||
// or set a specific seed with `seed: 4321`
|
||||
},
|
||||
clearContext: false, // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
jasmineHtmlReporter: {
|
||||
suppressAll: true, // removes the duplicated traces
|
||||
},
|
||||
coverageReporter: {
|
||||
dir: require('path').join(__dirname, '../../../coverage/adapter/scan'),
|
||||
subdir: '.',
|
||||
reporters: [{ type: 'html' }, { type: 'text-summary' }],
|
||||
},
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
singleRun: false,
|
||||
restartOnFileChange: true,
|
||||
});
|
||||
};
|
||||
7
apps/adapter/scan/ng-package.json
Normal file
7
apps/adapter/scan/ng-package.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
|
||||
"dest": "../../../dist/adapter/scan",
|
||||
"lib": {
|
||||
"entryFile": "src/public-api.ts"
|
||||
}
|
||||
}
|
||||
11
apps/adapter/scan/package.json
Normal file
11
apps/adapter/scan/package.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "@adapter/scan",
|
||||
"version": "0.0.1",
|
||||
"peerDependencies": {
|
||||
"@angular/common": "^12.2.0",
|
||||
"@angular/core": "^12.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
}
|
||||
}
|
||||
46
apps/adapter/scan/src/lib/dev.scan-adapter.ts
Normal file
46
apps/adapter/scan/src/lib/dev.scan-adapter.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { PromptModalData, UiModalService, UiPromptModalComponent } from '@ui/modal';
|
||||
import { Observable } from 'rxjs';
|
||||
import { ScanAdapter } from './scan-adapter';
|
||||
|
||||
@Injectable()
|
||||
export class DevScanAdapter implements ScanAdapter {
|
||||
constructor(private _modal: UiModalService) {}
|
||||
|
||||
getName(): string {
|
||||
return 'Dev Scanner';
|
||||
}
|
||||
|
||||
isPrimary(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
isReady(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
scan(): Observable<string> {
|
||||
return new Observable((observer) => {
|
||||
const modalRef = this._modal.open({
|
||||
content: UiPromptModalComponent,
|
||||
title: 'Scannen',
|
||||
data: {
|
||||
message: 'Diese Eingabemaske dient nur zu Entwicklungs und Testzwecken.',
|
||||
placeholder: 'Scan Code',
|
||||
confirmText: 'weiter',
|
||||
cancelText: 'abbrechen',
|
||||
} as PromptModalData,
|
||||
});
|
||||
|
||||
const sub = modalRef.afterClosed$.subscribe((result) => {
|
||||
observer.next(result.data);
|
||||
observer.complete();
|
||||
});
|
||||
|
||||
return () => {
|
||||
modalRef.close();
|
||||
sub.unsubscribe();
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
32
apps/adapter/scan/src/lib/native.scan-adapter.ts
Normal file
32
apps/adapter/scan/src/lib/native.scan-adapter.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { NativeContainerService } from 'native-container';
|
||||
import { Observable } from 'rxjs';
|
||||
import { filter, map, take } from 'rxjs/operators';
|
||||
import { ScanAdapter } from './scan-adapter';
|
||||
|
||||
@Injectable()
|
||||
export class NativeScanAdapter implements ScanAdapter {
|
||||
constructor(private readonly nativeContainerService: NativeContainerService) {}
|
||||
|
||||
getName(): string {
|
||||
return 'Native Scanner';
|
||||
}
|
||||
|
||||
isPrimary(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
isReady(): boolean {
|
||||
// TODO: Fix Login Keycard Dauerschleife
|
||||
return this.nativeContainerService.isUiWebview().isNative;
|
||||
// return false;
|
||||
}
|
||||
|
||||
scan(): Observable<string> {
|
||||
return this.nativeContainerService.openScanner('scanBook').pipe(
|
||||
filter((result) => result.status === 'SUCCESS'),
|
||||
map((result) => result.data),
|
||||
take(1)
|
||||
);
|
||||
}
|
||||
}
|
||||
11
apps/adapter/scan/src/lib/scan-adapter.ts
Normal file
11
apps/adapter/scan/src/lib/scan-adapter.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
export interface ScanAdapter {
|
||||
getName(): string;
|
||||
|
||||
isPrimary(): boolean;
|
||||
|
||||
isReady(): boolean;
|
||||
|
||||
scan(): Observable<string>;
|
||||
}
|
||||
26
apps/adapter/scan/src/lib/scan.module.ts
Normal file
26
apps/adapter/scan/src/lib/scan.module.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { DevScanAdapter } from './dev.scan-adapter';
|
||||
import { NativeScanAdapter } from './native.scan-adapter';
|
||||
import { ScanditModalModule } from './scandit-modal';
|
||||
import { ScanditScanAdapter } from './scandit.scan-adapter';
|
||||
import { SCAN_ADAPTER } from './tokens';
|
||||
import { ZxingModalModule } from './zxing-modal';
|
||||
import { ZxingScanAdapter } from './zxing.scan-adapter';
|
||||
|
||||
@NgModule({
|
||||
imports: [ScanditModalModule, ZxingModalModule],
|
||||
})
|
||||
export class ScanAdapterModule {
|
||||
static forRoot(dev?: boolean) {
|
||||
return {
|
||||
ngModule: ScanAdapterModule,
|
||||
providers: [
|
||||
{ provide: SCAN_ADAPTER, useClass: NativeScanAdapter, multi: true },
|
||||
// { provide: SCAN_ADAPTER, useClass: ScanditScanAdapter, multi: true },
|
||||
{ provide: SCAN_ADAPTER, useClass: ZxingScanAdapter, multi: true },
|
||||
],
|
||||
// Use for testing:
|
||||
// providers: [{ provide: SCAN_ADAPTER, useClass: dev ? DevScanAdapter : NativeScanAdapter, multi: true }],
|
||||
};
|
||||
}
|
||||
}
|
||||
30
apps/adapter/scan/src/lib/scan.service.ts
Normal file
30
apps/adapter/scan/src/lib/scan.service.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
import { ScanAdapter } from './scan-adapter';
|
||||
import { SCAN_ADAPTER } from './tokens';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class ScanAdapterService {
|
||||
constructor(@Inject(SCAN_ADAPTER) private readonly scanAdapters: ScanAdapter[]) {}
|
||||
|
||||
scanners() {
|
||||
return this.scanAdapters.filter((adapter) => adapter.isReady());
|
||||
}
|
||||
|
||||
scanner() {
|
||||
return this.scanners().find((scanner) => scanner.isPrimary()) || this.scanners().find(() => true);
|
||||
}
|
||||
|
||||
isReady() {
|
||||
return this.scanAdapters.some((adapter) => adapter.isReady());
|
||||
}
|
||||
|
||||
scan() {
|
||||
const primaryScanner = this.scanner();
|
||||
if (primaryScanner) {
|
||||
return primaryScanner.scan();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
2
apps/adapter/scan/src/lib/scandit-modal/index.ts
Normal file
2
apps/adapter/scan/src/lib/scandit-modal/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './scandit-modal.component';
|
||||
export * from './scandit-modal.module';
|
||||
@@ -0,0 +1 @@
|
||||
<div class="scanner-container" #scanContainer></div>
|
||||
@@ -0,0 +1,4 @@
|
||||
.scanner-container {
|
||||
@apply mt-8;
|
||||
max-height: calc(100vh - 10rem);
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import { Component, ChangeDetectionStrategy, ViewChild, ElementRef, AfterViewInit, NgZone, Inject, OnDestroy } from '@angular/core';
|
||||
import { UiMessageModalComponent, UiModalRef, UiModalService } from '@ui/modal';
|
||||
import { Barcode, BarcodePicker, ScanSettings } from 'scandit-sdk';
|
||||
|
||||
@Component({
|
||||
selector: 'scandit-modal',
|
||||
templateUrl: 'scandit-modal.component.html',
|
||||
styleUrls: ['scandit-modal.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ScanditModalComponent implements AfterViewInit, OnDestroy {
|
||||
private _barcodePicker: BarcodePicker;
|
||||
|
||||
@ViewChild('scanContainer', { read: ElementRef, static: true }) scanContainer: ElementRef;
|
||||
|
||||
constructor(private _modalRef: UiModalRef, private readonly _zone: NgZone, private readonly _modal: UiModalService) {}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this._zone.runOutsideAngular(() => {
|
||||
BarcodePicker.create(this.scanContainer.nativeElement, {
|
||||
playSoundOnScan: true,
|
||||
vibrateOnScan: true,
|
||||
})
|
||||
.then(async (picker) => {
|
||||
this._barcodePicker = picker;
|
||||
|
||||
var scanSettings = new ScanSettings({
|
||||
blurryRecognition: false,
|
||||
|
||||
enabledSymbologies: [
|
||||
Barcode.Symbology.EAN8,
|
||||
Barcode.Symbology.EAN13,
|
||||
Barcode.Symbology.UPCA,
|
||||
Barcode.Symbology.UPCE,
|
||||
Barcode.Symbology.CODE128,
|
||||
Barcode.Symbology.CODE39,
|
||||
Barcode.Symbology.CODE93,
|
||||
Barcode.Symbology.INTERLEAVED_2_OF_5,
|
||||
Barcode.Symbology.QR,
|
||||
],
|
||||
codeDuplicateFilter: 1000,
|
||||
});
|
||||
picker.applyScanSettings(scanSettings);
|
||||
|
||||
picker.on('scan', (barcode) => {
|
||||
this._zone.run(() => {
|
||||
if (barcode.barcodes.length) {
|
||||
this._modalRef.close(barcode.barcodes[0].data);
|
||||
} else if (barcode.texts.length) {
|
||||
this._modalRef.close(barcode.texts[0].value);
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
this._zone.run(() => {
|
||||
this.cancel();
|
||||
|
||||
this._modal
|
||||
.open({
|
||||
content: UiMessageModalComponent,
|
||||
data: {
|
||||
message: `
|
||||
Scanner kann nicht aktiviert werden.
|
||||
Bitte stellen Sie sicher, dass der zugriff auf die Kamera erlaubt ist.
|
||||
`,
|
||||
},
|
||||
})
|
||||
.afterClosed$.subscribe(() => {
|
||||
this.cancel();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this._modalRef.close();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this._zone.runOutsideAngular(() => {
|
||||
this._barcodePicker?.destroy(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { ScanditModalComponent } from './scandit-modal.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule],
|
||||
exports: [ScanditModalComponent],
|
||||
declarations: [ScanditModalComponent],
|
||||
})
|
||||
export class ScanditModalModule {}
|
||||
49
apps/adapter/scan/src/lib/scandit.scan-adapter.ts
Normal file
49
apps/adapter/scan/src/lib/scandit.scan-adapter.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { UiModalService } from '@ui/modal';
|
||||
import { Observable } from 'rxjs';
|
||||
import { ScanAdapter } from './scan-adapter';
|
||||
|
||||
import { configure } from 'scandit-sdk';
|
||||
import { ScanditModalComponent } from './scandit-modal';
|
||||
import { filter, map } from 'rxjs/operators';
|
||||
import { Config } from '@core/config';
|
||||
|
||||
@Injectable()
|
||||
export class ScanditScanAdapter implements ScanAdapter {
|
||||
private _isReady = false;
|
||||
|
||||
constructor(private readonly _modal: UiModalService, private readonly _config: Config) {
|
||||
this.configure();
|
||||
}
|
||||
|
||||
private async configure() {
|
||||
await configure(this._config.get('licence.scandit'), {
|
||||
engineLocation: '/assets/scandit/',
|
||||
});
|
||||
|
||||
this._isReady = true;
|
||||
}
|
||||
|
||||
getName(): string {
|
||||
return 'Scandit';
|
||||
}
|
||||
|
||||
isPrimary(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
isReady(): boolean {
|
||||
return this._isReady;
|
||||
}
|
||||
|
||||
scan(): Observable<string> {
|
||||
return this._modal
|
||||
.open({
|
||||
content: ScanditModalComponent,
|
||||
})
|
||||
.afterClosed$.pipe(
|
||||
map((result) => result.data),
|
||||
filter((result) => !!result)
|
||||
);
|
||||
}
|
||||
}
|
||||
4
apps/adapter/scan/src/lib/tokens.ts
Normal file
4
apps/adapter/scan/src/lib/tokens.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { InjectionToken } from '@angular/core';
|
||||
import { ScanAdapter } from './scan-adapter';
|
||||
|
||||
export const SCAN_ADAPTER = new InjectionToken<ScanAdapter>('SCAN_ADAPTER');
|
||||
2
apps/adapter/scan/src/lib/zxing-modal/index.ts
Normal file
2
apps/adapter/scan/src/lib/zxing-modal/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './zxing-modal.component';
|
||||
export * from './zxing-modal.module';
|
||||
@@ -0,0 +1,17 @@
|
||||
<!-- <select>
|
||||
<options [value]="dev.deviceId" *ngFor="let dev in devices$ | async">
|
||||
{{ dev.label }}
|
||||
</options>
|
||||
</select> -->
|
||||
|
||||
<div class="device-container">
|
||||
<button
|
||||
[class.selected]="(selectedDevice$ | async) === dev.deviceId"
|
||||
*ngFor="let dev of devices$ | async"
|
||||
(click)="setSelectedDevice(dev.deviceId)"
|
||||
>
|
||||
{{ dev.label }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<video class="scanner-container" #scanContainer></video>
|
||||
@@ -0,0 +1,17 @@
|
||||
.scanner-container {
|
||||
// @apply mt-8;
|
||||
max-height: calc(100vh - 10rem);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
button {
|
||||
@apply bg-white text-brand px-3 py-2;
|
||||
|
||||
&.selected {
|
||||
@apply bg-brand text-white;
|
||||
}
|
||||
}
|
||||
|
||||
.device-container {
|
||||
@apply flex flex-row items-center justify-center my-4;
|
||||
}
|
||||
104
apps/adapter/scan/src/lib/zxing-modal/zxing-modal.component.ts
Normal file
104
apps/adapter/scan/src/lib/zxing-modal/zxing-modal.component.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { Component, ChangeDetectionStrategy, ViewChild, ElementRef, AfterViewInit, NgZone, Inject, OnDestroy, OnInit } from '@angular/core';
|
||||
import { UiMessageModalComponent, UiModalRef, UiModalService } from '@ui/modal';
|
||||
import { BarcodeFormat, DecodeHintType, BrowserMultiFormatReader } from '@zxing/library';
|
||||
import { BehaviorSubject, from, ReplaySubject, Subject } from 'rxjs';
|
||||
import { filter, takeUntil, tap } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'zxing-modal',
|
||||
templateUrl: 'zxing-modal.component.html',
|
||||
styleUrls: ['zxing-modal.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ZxingModalComponent implements OnInit, OnDestroy {
|
||||
private _reader: BrowserMultiFormatReader;
|
||||
|
||||
@ViewChild('scanContainer', { read: ElementRef, static: true }) scanContainer: ElementRef;
|
||||
|
||||
devices$ = new ReplaySubject<MediaDeviceInfo[]>();
|
||||
|
||||
selectedDevice$ = new BehaviorSubject<string>(null);
|
||||
|
||||
get selectedDevice() {
|
||||
return this.selectedDevice$.value;
|
||||
}
|
||||
|
||||
private _onDestroy$ = new Subject<void>();
|
||||
|
||||
constructor(private _modalRef: UiModalRef, private readonly _zone: NgZone, private readonly _modal: UiModalService) {}
|
||||
|
||||
async ngOnInit() {
|
||||
try {
|
||||
const mediaStream: MediaStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: false });
|
||||
mediaStream.getVideoTracks().forEach((track) => track.stop());
|
||||
|
||||
const hints = new Map<DecodeHintType, any>();
|
||||
|
||||
const formats = [
|
||||
BarcodeFormat.EAN_8,
|
||||
BarcodeFormat.EAN_13,
|
||||
BarcodeFormat.UPC_A,
|
||||
BarcodeFormat.UPC_E,
|
||||
BarcodeFormat.CODE_128,
|
||||
BarcodeFormat.CODE_39,
|
||||
BarcodeFormat.CODE_93,
|
||||
BarcodeFormat.ITF,
|
||||
BarcodeFormat.QR_CODE,
|
||||
];
|
||||
|
||||
hints.set(DecodeHintType.POSSIBLE_FORMATS, formats);
|
||||
|
||||
this._reader = new BrowserMultiFormatReader(hints, 1000);
|
||||
|
||||
from(this._reader.listVideoInputDevices()).subscribe((devices) => {
|
||||
this.devices$.next(devices);
|
||||
this.selectedDevice$.next(devices[0].deviceId);
|
||||
});
|
||||
|
||||
this.selectedDevice$
|
||||
.pipe(
|
||||
takeUntil(this._onDestroy$),
|
||||
filter((v) => !!v),
|
||||
tap(() => {
|
||||
this._reader?.reset();
|
||||
})
|
||||
)
|
||||
.subscribe((device) => {
|
||||
this._reader.decodeFromVideoDevice(device, this.scanContainer.nativeElement, (result) => {
|
||||
if (result) {
|
||||
this._modalRef.close(result.getText());
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
this.cancel();
|
||||
this._modal
|
||||
.open({
|
||||
content: UiMessageModalComponent,
|
||||
data: {
|
||||
message: `
|
||||
Scanner kann nicht aktiviert werden.
|
||||
Bitte stellen Sie sicher, dass der zugriff auf die Kamera erlaubt ist.
|
||||
`,
|
||||
},
|
||||
})
|
||||
.afterClosed$.subscribe(() => {
|
||||
this.cancel();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this._modalRef.close();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this._onDestroy$.next();
|
||||
|
||||
this._reader?.reset();
|
||||
}
|
||||
|
||||
setSelectedDevice(id: string) {
|
||||
this.selectedDevice$.next(id);
|
||||
}
|
||||
}
|
||||
11
apps/adapter/scan/src/lib/zxing-modal/zxing-modal.module.ts
Normal file
11
apps/adapter/scan/src/lib/zxing-modal/zxing-modal.module.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { ZxingModalComponent } from './zxing-modal.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule],
|
||||
exports: [ZxingModalComponent],
|
||||
declarations: [ZxingModalComponent],
|
||||
})
|
||||
export class ZxingModalModule {}
|
||||
38
apps/adapter/scan/src/lib/zxing.scan-adapter.ts
Normal file
38
apps/adapter/scan/src/lib/zxing.scan-adapter.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { UiModalService } from '@ui/modal';
|
||||
import { Observable } from 'rxjs';
|
||||
import { ScanAdapter } from './scan-adapter';
|
||||
|
||||
import { filter, map } from 'rxjs/operators';
|
||||
import { Config } from '@core/config';
|
||||
import { ZxingModalComponent } from './zxing-modal/zxing-modal.component';
|
||||
|
||||
@Injectable()
|
||||
export class ZxingScanAdapter implements ScanAdapter {
|
||||
private _isReady = true;
|
||||
|
||||
constructor(private readonly _modal: UiModalService, private readonly _config: Config) {}
|
||||
|
||||
getName(): string {
|
||||
return 'Zxing';
|
||||
}
|
||||
|
||||
isPrimary(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
isReady(): boolean {
|
||||
return this._isReady;
|
||||
}
|
||||
|
||||
scan(): Observable<string> {
|
||||
return this._modal
|
||||
.open({
|
||||
content: ZxingModalComponent,
|
||||
})
|
||||
.afterClosed$.pipe(
|
||||
map((result) => result.data),
|
||||
filter((result) => !!result)
|
||||
);
|
||||
}
|
||||
}
|
||||
6
apps/adapter/scan/src/public-api.ts
Normal file
6
apps/adapter/scan/src/public-api.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
/*
|
||||
* Public API Surface of scan
|
||||
*/
|
||||
|
||||
export * from './lib/scan.service';
|
||||
export * from './lib/scan.module';
|
||||
25
apps/adapter/scan/src/test.ts
Normal file
25
apps/adapter/scan/src/test.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
||||
|
||||
import 'zone.js';
|
||||
import 'zone.js/testing';
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
declare const require: {
|
||||
context(
|
||||
path: string,
|
||||
deep?: boolean,
|
||||
filter?: RegExp
|
||||
): {
|
||||
keys(): string[];
|
||||
<T>(id: string): T;
|
||||
};
|
||||
};
|
||||
|
||||
// First, initialize the Angular testing environment.
|
||||
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), { teardown: { destroyAfterEach: true } });
|
||||
|
||||
// Then we find all the tests.
|
||||
const context = require.context('./', true, /\.spec\.ts$/);
|
||||
// And load the modules.
|
||||
context.keys().map(context);
|
||||
20
apps/adapter/scan/tsconfig.lib.json
Normal file
20
apps/adapter/scan/tsconfig.lib.json
Normal file
@@ -0,0 +1,20 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../out-tsc/lib",
|
||||
"target": "es2015",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"inlineSources": true,
|
||||
"types": [],
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2018"
|
||||
]
|
||||
},
|
||||
"exclude": [
|
||||
"src/test.ts",
|
||||
"**/*.spec.ts"
|
||||
]
|
||||
}
|
||||
10
apps/adapter/scan/tsconfig.lib.prod.json
Normal file
10
apps/adapter/scan/tsconfig.lib.prod.json
Normal file
@@ -0,0 +1,10 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "./tsconfig.lib.json",
|
||||
"compilerOptions": {
|
||||
"declarationMap": false
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"compilationMode": "partial"
|
||||
}
|
||||
}
|
||||
17
apps/adapter/scan/tsconfig.spec.json
Normal file
17
apps/adapter/scan/tsconfig.spec.json
Normal file
@@ -0,0 +1,17 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../out-tsc/spec",
|
||||
"types": [
|
||||
"jasmine"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"src/test.ts"
|
||||
],
|
||||
"include": [
|
||||
"**/*.spec.ts",
|
||||
"**/*.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
import { Config } from '@core/config';
|
||||
import { CDN_PRODUCT_IMAGE } from './tokens';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class ProductImageService {
|
||||
constructor(@Inject(CDN_PRODUCT_IMAGE) private imageUrl: string) {}
|
||||
constructor(private readonly _config: Config) {}
|
||||
|
||||
getImageUrl({
|
||||
imageId,
|
||||
@@ -18,6 +19,6 @@ export class ProductImageService {
|
||||
height?: number;
|
||||
showDummy?: boolean;
|
||||
}): string {
|
||||
return `${this.imageUrl}/${imageId}_${width}x${height}.jpg?showDummy=${showDummy}`;
|
||||
return `${this._config.get('@cdn/product-image.url')}/${imageId}_${width}x${height}.jpg?showDummy=${showDummy}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,4 +4,5 @@
|
||||
|
||||
export * from './lib/product-image.service';
|
||||
export * from './lib/product-image.module';
|
||||
export * from './lib/product-image.pipe';
|
||||
export * from './lib/tokens';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
||||
|
||||
import 'zone.js/dist/zone';
|
||||
import 'zone.js/dist/zone-testing';
|
||||
import 'zone.js';
|
||||
import 'zone.js/testing';
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
// Karma configuration file, see link for more information
|
||||
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||
const customLaunchers = require('../../../karma/custom-launchers');
|
||||
const junitReporter = require('../../../karma/junit-reporter')('core-application');
|
||||
const coverageReporter = require('../../../karma/coverage-reporter')('core-application');
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
@@ -9,23 +12,31 @@ module.exports = function (config) {
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage-istanbul-reporter'),
|
||||
require('karma-coverage'),
|
||||
require('karma-junit-reporter'),
|
||||
require('@angular-devkit/build-angular/plugins/karma'),
|
||||
],
|
||||
client: {
|
||||
jasmine: {
|
||||
// you can add configuration options for Jasmine here
|
||||
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
|
||||
// for example, you can disable the random execution with `random: false`
|
||||
// or set a specific seed with `seed: 4321`
|
||||
},
|
||||
clearContext: false, // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
coverageIstanbulReporter: {
|
||||
dir: require('path').join(__dirname, '../../../coverage/core/application'),
|
||||
reports: ['html', 'lcovonly', 'text-summary'],
|
||||
fixWebpackSourcePaths: true,
|
||||
jasmineHtmlReporter: {
|
||||
suppressAll: true, // removes the duplicated traces
|
||||
},
|
||||
coverageReporter,
|
||||
junitReporter,
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
customLaunchers,
|
||||
singleRun: false,
|
||||
restartOnFileChange: true,
|
||||
});
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { NgModule, ModuleWithProviders } from '@angular/core';
|
||||
import { StoreModule } from '@ngrx/store';
|
||||
import { applicationReducer } from './store';
|
||||
import { ApplicationService } from './application.service';
|
||||
|
||||
@NgModule({
|
||||
@@ -9,8 +11,13 @@ import { ApplicationService } from './application.service';
|
||||
export class CoreApplicationModule {
|
||||
static forRoot(): ModuleWithProviders<CoreApplicationModule> {
|
||||
return {
|
||||
ngModule: CoreApplicationModule,
|
||||
providers: [ApplicationService],
|
||||
ngModule: RootCoreApplicationModule,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [StoreModule.forFeature('core-application', applicationReducer)],
|
||||
providers: [ApplicationService],
|
||||
})
|
||||
export class RootCoreApplicationModule {}
|
||||
|
||||
@@ -1,32 +1,232 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { isObservable } from 'rxjs';
|
||||
import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { ApplicationProcess } from './defs';
|
||||
|
||||
import { ApplicationService } from './application.service';
|
||||
import * as actions from './store/application.actions';
|
||||
|
||||
describe('ApplicationService', () => {
|
||||
let service: ApplicationService;
|
||||
let spectator: SpectatorService<ApplicationService>;
|
||||
let store: SpyObject<Store>;
|
||||
const createService = createServiceFactory({
|
||||
service: ApplicationService,
|
||||
mocks: [Store],
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [ApplicationService],
|
||||
});
|
||||
service = TestBed.inject(ApplicationService);
|
||||
spectator = createService({});
|
||||
store = spectator.inject(Store);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('activatedProcessId', () => {
|
||||
it('should return the processId', () => {
|
||||
service.setActivatedProcessId(100);
|
||||
expect(service.activatedProcessId).toEqual(100);
|
||||
});
|
||||
expect(spectator.service).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('activatedProcessId$', () => {
|
||||
it('should return an observable', () => {
|
||||
expect(isObservable(service.activatedProcessId$)).toBeTruthy();
|
||||
expect(spectator.service.activatedProcessId$).toBeInstanceOf(Observable);
|
||||
});
|
||||
});
|
||||
|
||||
describe('activatedProcessId', () => {
|
||||
it('should return the process id as a number', () => {
|
||||
spyOnProperty(spectator.service['activatedProcessIdSubject'] as any, 'value').and.returnValue(2);
|
||||
expect(spectator.service.activatedProcessId).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getProcesses$()', () => {
|
||||
it('should call select on store and return all selected processes', async () => {
|
||||
const processes: ApplicationProcess[] = [
|
||||
{ id: 1, name: 'Vorgang', type: 'cart', section: 'customer', data: { count: 1 } },
|
||||
{ id: 2, name: 'Vorgang', type: 'task-calendar', section: 'branch' },
|
||||
];
|
||||
store.select.and.returnValue(of(processes));
|
||||
const result = await spectator.service.getProcesses$().pipe(first()).toPromise();
|
||||
expect(result).toEqual(processes);
|
||||
expect(store.select).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call select on store and return all section customer processes', async () => {
|
||||
const processes: ApplicationProcess[] = [
|
||||
{ id: 1, name: 'Vorgang', type: 'cart', section: 'customer', data: { count: 1 } },
|
||||
{ id: 2, name: 'Vorgang', type: 'task-calendar', section: 'branch' },
|
||||
];
|
||||
store.select.and.returnValue(of(processes));
|
||||
const result = await spectator.service.getProcesses$('customer').pipe(first()).toPromise();
|
||||
expect(result).toEqual([processes[0]]);
|
||||
expect(store.select).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call select on store and return all section branch processes', async () => {
|
||||
const processes: ApplicationProcess[] = [
|
||||
{ id: 1, name: 'Vorgang', type: 'cart', section: 'customer', data: { count: 1 } },
|
||||
{ id: 2, name: 'Vorgang', type: 'task-calendar', section: 'branch' },
|
||||
];
|
||||
store.select.and.returnValue(of(processes));
|
||||
const result = await spectator.service.getProcesses$('branch').pipe(first()).toPromise();
|
||||
expect(result).toEqual([processes[1]]);
|
||||
expect(store.select).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getProcessById$()', () => {
|
||||
it('should return the process by id', async () => {
|
||||
const processes: ApplicationProcess[] = [
|
||||
{ id: 1, name: 'Vorgang 1', section: 'customer' },
|
||||
{ id: 2, name: 'Vorgang 2', section: 'customer' },
|
||||
];
|
||||
spyOn(spectator.service, 'getProcesses$').and.returnValue(of(processes));
|
||||
|
||||
const process = await spectator.service.getProcessById$(1).toPromise();
|
||||
expect(process.id).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSection$()', () => {
|
||||
it('should return the selected section branch', async () => {
|
||||
const section = 'branch';
|
||||
store.select.and.returnValue(of(section));
|
||||
const result = await spectator.service.getSection$().pipe(first()).toPromise();
|
||||
expect(result).toEqual(section);
|
||||
expect(store.select).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getActivatedProcessId$', () => {
|
||||
it('should return the current selected activated process id', async () => {
|
||||
const activatedProcessId = 2;
|
||||
store.select.and.returnValue(of({ id: activatedProcessId }));
|
||||
const result = await spectator.service.getActivatedProcessId$().pipe(first()).toPromise();
|
||||
expect(result).toEqual(activatedProcessId);
|
||||
expect(store.select).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('activateProcess()', () => {
|
||||
it('should dispatch action setActivatedProcess with argument activatedProcessId and action type', () => {
|
||||
const activatedProcessId = 2;
|
||||
spectator.service.activateProcess(activatedProcessId);
|
||||
expect(store.dispatch).toHaveBeenCalledWith({ activatedProcessId, type: actions.setActivatedProcess.type });
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeProcess()', () => {
|
||||
it('should dispatch action removeProcess with argument processId and action type', () => {
|
||||
const processId = 2;
|
||||
spectator.service.removeProcess(processId);
|
||||
expect(store.dispatch).toHaveBeenCalledWith({ processId, type: actions.removeProcess.type });
|
||||
});
|
||||
});
|
||||
|
||||
describe('createProcess()', () => {
|
||||
it('should dispatch action addProcess with process', async () => {
|
||||
const process: ApplicationProcess = {
|
||||
id: 1,
|
||||
name: 'Vorgang 1',
|
||||
section: 'customer',
|
||||
type: 'cart',
|
||||
};
|
||||
|
||||
const timestamp = 100;
|
||||
spyOn(spectator.service as any, '_createTimestamp').and.returnValue(timestamp);
|
||||
spyOn(spectator.service, 'getProcessById$').and.returnValue(of(undefined));
|
||||
await spectator.service.createProcess(process);
|
||||
|
||||
expect(store.dispatch).toHaveBeenCalledWith({
|
||||
type: actions.addProcess.type,
|
||||
process: {
|
||||
...process,
|
||||
activated: 0,
|
||||
created: timestamp,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if the process id is already existing', async () => {
|
||||
const process: ApplicationProcess = {
|
||||
id: 1,
|
||||
name: 'Vorgang 1',
|
||||
section: 'customer',
|
||||
type: 'cart',
|
||||
};
|
||||
spyOn(spectator.service, 'getProcessById$').and.returnValue(of(process));
|
||||
await expectAsync(spectator.service.createProcess(process)).toBeRejectedWithError('Process Id existiert bereits');
|
||||
});
|
||||
|
||||
it('should throw an error if the process id is not a number', async () => {
|
||||
const process: ApplicationProcess = {
|
||||
id: undefined,
|
||||
name: 'Vorgang 1',
|
||||
section: 'customer',
|
||||
type: 'cart',
|
||||
};
|
||||
spyOn(spectator.service, 'getProcessById$').and.returnValue(of({ id: 5, name: 'Vorgang 2', section: 'customer' }));
|
||||
await expectAsync(spectator.service.createProcess(process)).toBeRejectedWithError('Process Id nicht gesetzt');
|
||||
});
|
||||
});
|
||||
|
||||
describe('patchProcess', () => {
|
||||
it('should dispatch action patchProcess with changes', async () => {
|
||||
const process: ApplicationProcess = {
|
||||
id: 1,
|
||||
name: 'Vorgang 1',
|
||||
section: 'customer',
|
||||
type: 'cart',
|
||||
};
|
||||
|
||||
await spectator.service.patchProcess(process.id, process);
|
||||
|
||||
expect(store.dispatch).toHaveBeenCalledWith({
|
||||
type: actions.patchProcess.type,
|
||||
process: {
|
||||
...process,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setSection()', () => {
|
||||
it('should dispatch action setSection with argument section and action type', () => {
|
||||
const section = 'customer';
|
||||
spectator.service.setSection(section);
|
||||
expect(store.dispatch).toHaveBeenCalledWith({ section, type: actions.setSection.type });
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLastActivatedProcessWithSectionAndType()', () => {
|
||||
it('should return the last activated process by section and type', async () => {
|
||||
const processes: ApplicationProcess[] = [
|
||||
{ id: 1, name: 'Vorgang 1', section: 'customer', type: 'cart', activated: 100 },
|
||||
{ id: 2, name: 'Vorgang 2', section: 'customer', type: 'cart', activated: 200 },
|
||||
{ id: 3, name: 'Vorgang 3', section: 'customer', type: 'goodsOut', activated: 300 },
|
||||
];
|
||||
spyOn(spectator.service, 'getProcesses$').and.returnValue(of(processes));
|
||||
|
||||
expect(await spectator.service.getLastActivatedProcessWithSectionAndType$('customer', 'cart').pipe(first()).toPromise()).toBe(
|
||||
processes[1]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLastActivatedProcessWithSection()', () => {
|
||||
it('should return the last activated process by section', async () => {
|
||||
const processes: ApplicationProcess[] = [
|
||||
{ id: 1, name: 'Vorgang 1', section: 'customer', activated: 100 },
|
||||
{ id: 2, name: 'Vorgang 2', section: 'customer', activated: 200 },
|
||||
{ id: 3, name: 'Vorgang 3', section: 'customer', activated: 300 },
|
||||
];
|
||||
spyOn(spectator.service, 'getProcesses$').and.returnValue(of(processes));
|
||||
|
||||
expect(await spectator.service.getLastActivatedProcessWithSection$('customer').pipe(first()).toPromise()).toBe(processes[2]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('_createTimestamp', () => {
|
||||
it('should return the current timestamp in ms', () => {
|
||||
expect(spectator.service['_createTimestamp']()).toBeCloseTo(Date.now());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,31 +1,129 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { processRemoved } from './store';
|
||||
import { isBoolean, isNumber } from '@utils/common';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { first, map, switchMap } from 'rxjs/operators';
|
||||
import { ApplicationProcess } from './defs';
|
||||
import {
|
||||
removeProcess,
|
||||
selectSection,
|
||||
selectProcesses,
|
||||
setSection,
|
||||
addProcess,
|
||||
setActivatedProcess,
|
||||
selectActivatedProcess,
|
||||
patchProcess,
|
||||
patchProcessData,
|
||||
} from './store';
|
||||
|
||||
@Injectable()
|
||||
export class ApplicationService {
|
||||
/** @deprecated */
|
||||
private activatedProcessIdSubject = new BehaviorSubject<number>(undefined);
|
||||
|
||||
/** @deprecated */
|
||||
get activatedProcessId() {
|
||||
return this.activatedProcessIdSubject.value;
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
get activatedProcessId$() {
|
||||
return this.activatedProcessIdSubject.asObservable();
|
||||
}
|
||||
|
||||
constructor(private store: Store) {}
|
||||
|
||||
setActivatedProcessId(processId: number) {
|
||||
if (this.activatedProcessId !== processId) {
|
||||
this.activatedProcessIdSubject.next(processId);
|
||||
}
|
||||
getProcesses$(section?: 'customer' | 'branch') {
|
||||
const processes$ = this.store.select(selectProcesses);
|
||||
return processes$.pipe(map((processes) => processes.filter((process) => (section ? process.section === section : true))));
|
||||
}
|
||||
|
||||
getProcessById$(processId: number): Observable<ApplicationProcess> {
|
||||
return this.getProcesses$().pipe(map((processes) => processes.find((process) => process.id === processId)));
|
||||
}
|
||||
|
||||
getSection$() {
|
||||
return this.store.select(selectSection);
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
getActivatedProcessId$() {
|
||||
return this.store.select(selectActivatedProcess).pipe(map((process) => process?.id));
|
||||
}
|
||||
|
||||
activateProcess(activatedProcessId: number) {
|
||||
this.store.dispatch(setActivatedProcess({ activatedProcessId }));
|
||||
this.activatedProcessIdSubject.next(activatedProcessId);
|
||||
}
|
||||
|
||||
removeProcess(processId: number) {
|
||||
this.store.dispatch(processRemoved({ processId }));
|
||||
this.store.dispatch(removeProcess({ processId }));
|
||||
}
|
||||
|
||||
createProcess() {}
|
||||
patchProcess(processId: number, changes: Partial<ApplicationProcess>) {
|
||||
this.store.dispatch(patchProcess({ processId, changes }));
|
||||
}
|
||||
|
||||
patchProcessData(processId: number, data: Record<string, any>) {
|
||||
this.store.dispatch(patchProcessData({ processId, data }));
|
||||
}
|
||||
|
||||
async createProcess(process: ApplicationProcess) {
|
||||
const existingProcess = await this.getProcessById$(process?.id).pipe(first()).toPromise();
|
||||
if (existingProcess?.id === process?.id) {
|
||||
throw new Error('Process Id existiert bereits');
|
||||
}
|
||||
|
||||
if (!isNumber(process.id)) {
|
||||
throw new Error('Process Id nicht gesetzt');
|
||||
}
|
||||
|
||||
if (!isBoolean(process.closeable)) {
|
||||
process.closeable = true;
|
||||
}
|
||||
|
||||
if (!isBoolean(process.confirmClosing)) {
|
||||
process.confirmClosing = true;
|
||||
}
|
||||
|
||||
process.created = this._createTimestamp();
|
||||
process.activated = 0;
|
||||
this.store.dispatch(addProcess({ process }));
|
||||
}
|
||||
|
||||
setSection(section: 'customer' | 'branch') {
|
||||
this.store.dispatch(setSection({ section }));
|
||||
}
|
||||
|
||||
getLastActivatedProcessWithSectionAndType$(section: 'customer' | 'branch', type: string): Observable<ApplicationProcess> {
|
||||
return this.getProcesses$(section).pipe(
|
||||
map((processes) =>
|
||||
processes
|
||||
?.filter((process) => process.type === type)
|
||||
?.reduce((latest, current) => {
|
||||
if (!latest) {
|
||||
return current;
|
||||
}
|
||||
return latest?.activated > current?.activated ? latest : current;
|
||||
}, undefined)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
getLastActivatedProcessWithSection$(section: 'customer' | 'branch'): Observable<ApplicationProcess> {
|
||||
return this.getProcesses$(section).pipe(
|
||||
map((processes) =>
|
||||
processes?.reduce((latest, current) => {
|
||||
if (!latest) {
|
||||
return current;
|
||||
}
|
||||
return latest?.activated > current?.activated ? latest : current;
|
||||
}, undefined)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private _createTimestamp() {
|
||||
return Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
export interface ApplicationProcess {
|
||||
id: number;
|
||||
created?: number;
|
||||
activated?: number;
|
||||
name: string;
|
||||
data: { [key: string]: any };
|
||||
section: 'customer' | 'branch';
|
||||
type?: string;
|
||||
data?: { [key: string]: any };
|
||||
closeable?: boolean;
|
||||
confirmClosing?: boolean;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// start:ng42.barrel
|
||||
export * from './application.module';
|
||||
export * from './application.service';
|
||||
export * from './process.service';
|
||||
export * from './defs';
|
||||
export * from './store';
|
||||
// end:ng42.barrel
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ProcessService {
|
||||
constructor() {}
|
||||
|
||||
updateName(processId: number, name: string) {}
|
||||
}
|
||||
@@ -1,8 +1,16 @@
|
||||
import { createAction, props } from '@ngrx/store';
|
||||
import { ApplicationProcess } from '..';
|
||||
|
||||
const prefix = '[CORE-APPLICATION]';
|
||||
|
||||
/**
|
||||
* Action nach Entfernung eines Prozesses
|
||||
*/
|
||||
export const processRemoved = createAction(`${prefix} Process Removed`, props<{ processId: number }>());
|
||||
export const setSection = createAction(`${prefix} Set Section`, props<{ section: 'customer' | 'branch' }>());
|
||||
|
||||
export const addProcess = createAction(`${prefix} Add Process`, props<{ process: ApplicationProcess }>());
|
||||
|
||||
export const removeProcess = createAction(`${prefix} Remove Process`, props<{ processId: number }>());
|
||||
|
||||
export const setActivatedProcess = createAction(`${prefix} Set Activated Process`, props<{ activatedProcessId: number }>());
|
||||
|
||||
export const patchProcess = createAction(`${prefix} Patch Process`, props<{ processId: number; changes: Partial<ApplicationProcess> }>());
|
||||
|
||||
export const patchProcessData = createAction(`${prefix} Patch Process Data`, props<{ processId: number; data: Record<string, any> }>());
|
||||
|
||||
200
apps/core/application/src/lib/store/application.reducer.spec.ts
Normal file
200
apps/core/application/src/lib/store/application.reducer.spec.ts
Normal file
@@ -0,0 +1,200 @@
|
||||
import { INITIAL_APPLICATION_STATE } from './application.state';
|
||||
import * as actions from './application.actions';
|
||||
import { applicationReducer } from './application.reducer';
|
||||
import { ApplicationProcess } from '../defs';
|
||||
import { ApplicationState } from './application.state';
|
||||
|
||||
describe('applicationReducer', () => {
|
||||
describe('setSection()', () => {
|
||||
it('should return modified state with section customer', () => {
|
||||
const initialState = INITIAL_APPLICATION_STATE;
|
||||
|
||||
const action = actions.setSection({ section: 'customer' });
|
||||
const state = applicationReducer(initialState, action);
|
||||
|
||||
expect(state).toEqual({
|
||||
...initialState,
|
||||
section: 'customer',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return modified state with section branch', () => {
|
||||
const initialState = INITIAL_APPLICATION_STATE;
|
||||
|
||||
const action = actions.setSection({ section: 'branch' });
|
||||
const state = applicationReducer(initialState, action);
|
||||
|
||||
expect(state).toEqual({
|
||||
...initialState,
|
||||
section: 'branch',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('addProcess()', () => {
|
||||
it('should return modified state with new process if no processes are registered in the state', () => {
|
||||
const initialState = INITIAL_APPLICATION_STATE;
|
||||
|
||||
const process: ApplicationProcess = {
|
||||
id: 1,
|
||||
name: 'Vorgang',
|
||||
section: 'customer',
|
||||
type: 'cart',
|
||||
data: {},
|
||||
};
|
||||
|
||||
const action = actions.addProcess({ process });
|
||||
const state = applicationReducer(initialState, action);
|
||||
expect(state.processes[0]).toEqual(process);
|
||||
});
|
||||
});
|
||||
|
||||
describe('patchProcess()', () => {
|
||||
it('should return modified state with updated process when id is found', () => {
|
||||
const initialState = INITIAL_APPLICATION_STATE;
|
||||
|
||||
const process: ApplicationProcess = {
|
||||
id: 1,
|
||||
name: 'Vorgang',
|
||||
section: 'customer',
|
||||
type: 'cart',
|
||||
};
|
||||
|
||||
const action = actions.patchProcess({ process: { ...process, name: 'Test' } });
|
||||
const state = applicationReducer(
|
||||
{
|
||||
...initialState,
|
||||
processes: [process],
|
||||
},
|
||||
action
|
||||
);
|
||||
expect(state.processes[0].name).toEqual('Test');
|
||||
});
|
||||
|
||||
it('should return unmodified state when id is not existing', () => {
|
||||
const initialState = INITIAL_APPLICATION_STATE;
|
||||
|
||||
const process: ApplicationProcess = {
|
||||
id: 1,
|
||||
name: 'Vorgang',
|
||||
section: 'customer',
|
||||
type: 'cart',
|
||||
};
|
||||
|
||||
const action = actions.patchProcess({ process: { ...process, id: 2 } });
|
||||
const state = applicationReducer(
|
||||
{
|
||||
...initialState,
|
||||
processes: [process],
|
||||
},
|
||||
action
|
||||
);
|
||||
expect(state.processes).toEqual([process]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeProcess()', () => {
|
||||
it('should return initial state if no processes are registered in the state', () => {
|
||||
const initialState = INITIAL_APPLICATION_STATE;
|
||||
|
||||
const action = actions.removeProcess({ processId: 2 });
|
||||
const state = applicationReducer(initialState, action);
|
||||
expect(state).toEqual(initialState);
|
||||
});
|
||||
|
||||
it('should return the unmodified state if processId not found', () => {
|
||||
const initialState = INITIAL_APPLICATION_STATE;
|
||||
const modifiedState: ApplicationState = {
|
||||
...initialState,
|
||||
section: 'customer',
|
||||
processes: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Vorgang',
|
||||
section: 'customer',
|
||||
type: 'cart',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'Vorgang',
|
||||
section: 'customer',
|
||||
type: 'goods-out',
|
||||
},
|
||||
] as ApplicationProcess[],
|
||||
};
|
||||
|
||||
const action = actions.removeProcess({ processId: 2 });
|
||||
const state = applicationReducer(modifiedState, action);
|
||||
expect(state).toEqual(modifiedState);
|
||||
});
|
||||
|
||||
it('should return modified state, after process gets removed', () => {
|
||||
const initialState = INITIAL_APPLICATION_STATE;
|
||||
const modifiedState: ApplicationState = {
|
||||
...initialState,
|
||||
section: 'customer',
|
||||
processes: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Vorgang',
|
||||
section: 'customer',
|
||||
type: 'cart',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Vorgang',
|
||||
section: 'customer',
|
||||
type: 'goods-out',
|
||||
},
|
||||
] as ApplicationProcess[],
|
||||
};
|
||||
|
||||
const action = actions.removeProcess({ processId: 2 });
|
||||
const state = applicationReducer(modifiedState, action);
|
||||
expect(state.processes).toEqual([
|
||||
{
|
||||
id: 1,
|
||||
name: 'Vorgang',
|
||||
section: 'customer',
|
||||
type: 'cart',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setActivatedProcess()', () => {
|
||||
it('should return modified state with process.activated value', () => {
|
||||
const process: ApplicationProcess = {
|
||||
id: 3,
|
||||
name: 'Vorgang 3',
|
||||
section: 'customer',
|
||||
};
|
||||
const initialState: ApplicationState = {
|
||||
...INITIAL_APPLICATION_STATE,
|
||||
processes: [process],
|
||||
};
|
||||
|
||||
const action = actions.setActivatedProcess({ activatedProcessId: 3 });
|
||||
const state = applicationReducer(initialState, action);
|
||||
|
||||
expect(state.processes[0].activated).toBeDefined();
|
||||
});
|
||||
|
||||
it('should return modified state without process.activated value when activatedProcessId doesnt exist', () => {
|
||||
const process: ApplicationProcess = {
|
||||
id: 1,
|
||||
name: 'Vorgang 3',
|
||||
section: 'customer',
|
||||
};
|
||||
const initialState: ApplicationState = {
|
||||
...INITIAL_APPLICATION_STATE,
|
||||
processes: [process],
|
||||
};
|
||||
|
||||
const action = actions.setActivatedProcess({ activatedProcessId: 3 });
|
||||
const state = applicationReducer(initialState, action);
|
||||
|
||||
expect(state.processes[0].activated).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
47
apps/core/application/src/lib/store/application.reducer.ts
Normal file
47
apps/core/application/src/lib/store/application.reducer.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { Action, createReducer, on } from '@ngrx/store';
|
||||
import { setSection, addProcess, removeProcess, setActivatedProcess, patchProcess, patchProcessData } from './application.actions';
|
||||
import { ApplicationState, INITIAL_APPLICATION_STATE } from './application.state';
|
||||
|
||||
const _applicationReducer = createReducer(
|
||||
INITIAL_APPLICATION_STATE,
|
||||
on(setSection, (state, { section }) => ({ ...state, section })),
|
||||
on(addProcess, (state, { process }) => ({ ...state, processes: [...state.processes, { data: {}, ...process }] })),
|
||||
on(removeProcess, (state, { processId }) => {
|
||||
const processes = state?.processes?.filter((process) => process.id !== processId) || [];
|
||||
return { ...state, processes };
|
||||
}),
|
||||
on(setActivatedProcess, (state, { activatedProcessId }) => {
|
||||
const processes = state.processes.map((process) => {
|
||||
if (process.id === activatedProcessId) {
|
||||
return { ...process, activated: Date.now() };
|
||||
}
|
||||
return process;
|
||||
});
|
||||
|
||||
return { ...state, processes };
|
||||
}),
|
||||
on(patchProcess, (state, { processId, changes }) => {
|
||||
const processes = state.processes.map((process) => {
|
||||
if (process.id === processId) {
|
||||
return { ...process, ...changes };
|
||||
}
|
||||
return process;
|
||||
});
|
||||
|
||||
return { ...state, processes };
|
||||
}),
|
||||
on(patchProcessData, (state, { processId, data }) => {
|
||||
const processes = state.processes.map((process) => {
|
||||
if (process.id === processId) {
|
||||
return { ...process, data: { ...(process.data || {}), ...data } };
|
||||
}
|
||||
return process;
|
||||
});
|
||||
|
||||
return { ...state, processes };
|
||||
})
|
||||
);
|
||||
|
||||
export function applicationReducer(state: ApplicationState, action: Action) {
|
||||
return _applicationReducer(state, action);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { ApplicationState } from './application.state';
|
||||
import { ApplicationProcess } from '../defs';
|
||||
import * as selectors from './application.selectors';
|
||||
|
||||
describe('applicationSelectors', () => {
|
||||
it('should select the processes', () => {
|
||||
const processes: ApplicationProcess[] = [{ id: 1, name: 'Vorgang 1', section: 'customer' }];
|
||||
const state: Partial<ApplicationState> = {
|
||||
processes,
|
||||
};
|
||||
expect(selectors.selectProcesses.projector(state)).toEqual(processes);
|
||||
});
|
||||
|
||||
it('should select the section', () => {
|
||||
const state: Partial<ApplicationState> = {
|
||||
section: 'customer',
|
||||
};
|
||||
expect(selectors.selectSection.projector(state)).toEqual('customer');
|
||||
});
|
||||
|
||||
it('should select the activatedProcess', () => {
|
||||
const processes: ApplicationProcess[] = [
|
||||
{ id: 1, name: 'Vorgang 1', section: 'customer', activated: 100 },
|
||||
{ id: 2, name: 'Vorgang 2', section: 'customer', activated: 300 },
|
||||
{ id: 3, name: 'Vorgang 3', section: 'customer', activated: 200 },
|
||||
];
|
||||
const state: Partial<ApplicationState> = {
|
||||
processes,
|
||||
};
|
||||
expect(selectors.selectActivatedProcess.projector(state)).toEqual(processes[1]);
|
||||
});
|
||||
});
|
||||
16
apps/core/application/src/lib/store/application.selectors.ts
Normal file
16
apps/core/application/src/lib/store/application.selectors.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { createFeatureSelector, createSelector } from '@ngrx/store';
|
||||
import { ApplicationState } from './application.state';
|
||||
export const selectApplicationState = createFeatureSelector<ApplicationState>('core-application');
|
||||
|
||||
export const selectSection = createSelector(selectApplicationState, (s) => s.section);
|
||||
|
||||
export const selectProcesses = createSelector(selectApplicationState, (s) => s.processes);
|
||||
|
||||
export const selectActivatedProcess = createSelector(selectApplicationState, (s) =>
|
||||
s?.processes?.reduce((process, current) => {
|
||||
if (!process) {
|
||||
return current;
|
||||
}
|
||||
return process.activated > current.activated ? process : current;
|
||||
}, undefined)
|
||||
);
|
||||
11
apps/core/application/src/lib/store/application.state.ts
Normal file
11
apps/core/application/src/lib/store/application.state.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { ApplicationProcess } from '../defs';
|
||||
|
||||
export interface ApplicationState {
|
||||
processes: ApplicationProcess[];
|
||||
section: 'customer' | 'branch';
|
||||
}
|
||||
|
||||
export const INITIAL_APPLICATION_STATE: ApplicationState = {
|
||||
processes: [],
|
||||
section: 'customer',
|
||||
};
|
||||
@@ -1,3 +1,6 @@
|
||||
// start:ng42.barrel
|
||||
export * from './application.actions';
|
||||
export * from './application.reducer';
|
||||
export * from './application.selectors';
|
||||
export * from './application.state';
|
||||
// end:ng42.barrel
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
||||
|
||||
import 'zone.js/dist/zone';
|
||||
import 'zone.js/dist/zone-testing';
|
||||
import 'zone.js';
|
||||
import 'zone.js/testing';
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
|
||||
25
apps/core/auth/README.md
Normal file
25
apps/core/auth/README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Auth
|
||||
|
||||
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 12.2.0.
|
||||
|
||||
## Code scaffolding
|
||||
|
||||
Run `ng generate component component-name --project auth` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project auth`.
|
||||
|
||||
> Note: Don't forget to add `--project auth` or else it will be added to the default project in your `angular.json` file.
|
||||
|
||||
## Build
|
||||
|
||||
Run `ng build auth` to build the project. The build artifacts will be stored in the `dist/` directory.
|
||||
|
||||
## Publishing
|
||||
|
||||
After building your library with `ng build auth`, go to the dist folder `cd dist/auth` and run `npm publish`.
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `ng test auth` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||
|
||||
## Further help
|
||||
|
||||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
|
||||
43
apps/core/auth/karma.conf.js
Normal file
43
apps/core/auth/karma.conf.js
Normal file
@@ -0,0 +1,43 @@
|
||||
// Karma configuration file, see link for more information
|
||||
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||
const customLaunchers = require('../../../karma/custom-launchers');
|
||||
const junitReporter = require('../../../karma/junit-reporter')('core-auth');
|
||||
const coverageReporter = require('../../../karma/coverage-reporter')('core-auth');
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage'),
|
||||
require('karma-junit-reporter'),
|
||||
require('@angular-devkit/build-angular/plugins/karma'),
|
||||
],
|
||||
client: {
|
||||
jasmine: {
|
||||
// you can add configuration options for Jasmine here
|
||||
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
|
||||
// for example, you can disable the random execution with `random: false`
|
||||
// or set a specific seed with `seed: 4321`
|
||||
},
|
||||
clearContext: false, // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
jasmineHtmlReporter: {
|
||||
suppressAll: true, // removes the duplicated traces
|
||||
},
|
||||
coverageReporter,
|
||||
junitReporter,
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
customLaunchers,
|
||||
singleRun: false,
|
||||
restartOnFileChange: true,
|
||||
});
|
||||
};
|
||||
7
apps/core/auth/ng-package.json
Normal file
7
apps/core/auth/ng-package.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
|
||||
"dest": "../../../dist/core/auth",
|
||||
"lib": {
|
||||
"entryFile": "src/public-api.ts"
|
||||
}
|
||||
}
|
||||
11
apps/core/auth/package.json
Normal file
11
apps/core/auth/package.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "@core/auth",
|
||||
"version": "0.0.1",
|
||||
"peerDependencies": {
|
||||
"@angular/common": "^12.2.0",
|
||||
"@angular/core": "^12.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
}
|
||||
}
|
||||
17
apps/core/auth/src/lib/auth.module.ts
Normal file
17
apps/core/auth/src/lib/auth.module.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { ModuleWithProviders, NgModule } from '@angular/core';
|
||||
import { AuthService } from './auth.service';
|
||||
import { OAuthModule } from 'angular-oauth2-oidc';
|
||||
@NgModule({})
|
||||
export class AuthModule {
|
||||
static forRoot(): ModuleWithProviders<AuthModule> {
|
||||
return {
|
||||
ngModule: AuthModule,
|
||||
providers: [
|
||||
AuthService,
|
||||
OAuthModule.forRoot({
|
||||
resourceServer: { sendAccessToken: true },
|
||||
}).providers,
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
151
apps/core/auth/src/lib/auth.service.spec.ts
Normal file
151
apps/core/auth/src/lib/auth.service.spec.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import { Config } from '@core/config';
|
||||
import { SpectatorService, createServiceFactory, SpyObject } from '@ngneat/spectator';
|
||||
import { OAuthService } from 'angular-oauth2-oidc';
|
||||
import { JwksValidationHandler } from 'angular-oauth2-oidc-jwks';
|
||||
import { Observable } from 'rxjs';
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
describe('AuthService', () => {
|
||||
let spectator: SpectatorService<AuthService>;
|
||||
const createService = createServiceFactory({
|
||||
service: AuthService,
|
||||
mocks: [Config, OAuthService],
|
||||
});
|
||||
let config: SpyObject<Config>;
|
||||
let oAuthService: SpyObject<OAuthService>;
|
||||
|
||||
beforeEach(() => {
|
||||
spectator = createService();
|
||||
config = spectator.inject(Config);
|
||||
oAuthService = spectator.inject(OAuthService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(spectator.service).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('init()', () => {
|
||||
it('should configure the oAuthService', () => {
|
||||
config.get.and.returnValue({});
|
||||
spectator.service.init();
|
||||
expect(oAuthService.configure).toHaveBeenCalledWith({
|
||||
redirectUri: window.location.origin,
|
||||
silentRefreshRedirectUri: window.location.origin + '/silent-refresh.html',
|
||||
useSilentRefresh: true,
|
||||
});
|
||||
expect(oAuthService.tokenValidationHandler).toBeInstanceOf(JwksValidationHandler);
|
||||
expect(oAuthService.setupAutomaticSilentRefresh).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should load the discovery document', async () => {
|
||||
config.get.and.returnValue({});
|
||||
oAuthService.loadDiscoveryDocumentAndTryLogin.and.returnValue(Promise.resolve(true));
|
||||
|
||||
spyOn(spectator.service['_initialized'], 'next');
|
||||
await spectator.service.init();
|
||||
|
||||
expect(oAuthService.loadDiscoveryDocumentAndTryLogin).toHaveBeenCalled();
|
||||
expect(spectator.service['_initialized'].next).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it('should throw an error if its already initialized', async () => {
|
||||
spyOn(spectator.service['_initialized'], 'getValue').and.returnValue(true);
|
||||
await expectAsync(spectator.service.init()).toBeRejectedWithError('AuthService is already initialized');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isAuthenticated()', () => {
|
||||
it('should call hasValidIdToken() and return its value', () => {
|
||||
oAuthService.hasValidIdToken.and.returnValue(true);
|
||||
expect(spectator.service.isAuthenticated()).toBeTrue();
|
||||
expect(oAuthService.hasValidIdToken).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getToken()', () => {
|
||||
it('should call getAccessToken() and return its value', () => {
|
||||
oAuthService.getAccessToken.and.returnValue('token');
|
||||
expect(spectator.service.getToken()).toEqual('token');
|
||||
expect(oAuthService.getAccessToken).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getClaims()', () => {
|
||||
it('should call getAccessToken() and return its value', () => {
|
||||
oAuthService.getAccessToken.and.returnValue('token');
|
||||
const claims = {
|
||||
claim1: 'value',
|
||||
claim2: 'value2',
|
||||
};
|
||||
spyOn(spectator.service, 'parseJwt').and.returnValue(claims);
|
||||
expect(spectator.service.getClaims()).toEqual(claims);
|
||||
expect(spectator.service.parseJwt).toHaveBeenCalledWith('token');
|
||||
expect(oAuthService.getAccessToken).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getClaimByKey()', () => {
|
||||
it('should call getClaims() and return its key value', () => {
|
||||
spyOn(spectator.service, 'getClaims').and.returnValue({
|
||||
claim1: 'value',
|
||||
claim2: 'value2',
|
||||
});
|
||||
|
||||
expect(spectator.service.getClaimByKey('claim1')).toEqual('value');
|
||||
expect(spectator.service.getClaims).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return null if getClaims() returns null or undefined', () => {
|
||||
spyOn(spectator.service, 'getClaims').and.returnValue(null);
|
||||
|
||||
expect(spectator.service.getClaimByKey('claim1')).toBeNull();
|
||||
expect(spectator.service.getClaims).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseJwt()', () => {
|
||||
it('should return null if the token is null or undefined', () => {
|
||||
expect(spectator.service.parseJwt(null)).toBeNull();
|
||||
expect(spectator.service.parseJwt(undefined)).toBeNull();
|
||||
});
|
||||
|
||||
it('should return the value of the key', () => {
|
||||
const token =
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ';
|
||||
expect(spectator.service.parseJwt(token)).toEqual({
|
||||
sub: '1234567890',
|
||||
name: 'John Doe',
|
||||
admin: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('login()', () => {
|
||||
it('should call initLoginFlow()', () => {
|
||||
spectator.service.login();
|
||||
expect(oAuthService.initLoginFlow).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('logout()', () => {
|
||||
it('should call revokeTokenAndLogout()', async () => {
|
||||
await spectator.service.logout();
|
||||
expect(oAuthService.revokeTokenAndLogout).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getToken()', () => {
|
||||
it('should return getAccessToken()', () => {
|
||||
spectator.service.getToken();
|
||||
expect(oAuthService.getAccessToken).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('initialized', () => {
|
||||
it('should return _initialized as Observable', () => {
|
||||
spyOn(spectator.service['_initialized'], 'asObservable').and.callThrough();
|
||||
expect(spectator.service.initialized$).toBeInstanceOf(Observable);
|
||||
expect(spectator.service['_initialized'].asObservable).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
84
apps/core/auth/src/lib/auth.service.ts
Normal file
84
apps/core/auth/src/lib/auth.service.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Config } from '@core/config';
|
||||
import { isNullOrUndefined } from '@utils/common';
|
||||
import { AuthConfig, OAuthService } from 'angular-oauth2-oidc';
|
||||
import { JwksValidationHandler } from 'angular-oauth2-oidc-jwks';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class AuthService {
|
||||
private readonly _initialized = new BehaviorSubject<boolean>(false);
|
||||
get initialized$() {
|
||||
return this._initialized.asObservable();
|
||||
}
|
||||
|
||||
constructor(private _config: Config, private readonly _oAuthService: OAuthService) {}
|
||||
|
||||
async init() {
|
||||
if (this._initialized.getValue()) {
|
||||
throw new Error('AuthService is already initialized');
|
||||
}
|
||||
|
||||
const authConfig: AuthConfig = this._config.get('@core/auth');
|
||||
|
||||
authConfig.redirectUri = window.location.origin;
|
||||
authConfig.silentRefreshRedirectUri = window.location.origin + '/silent-refresh.html';
|
||||
authConfig.useSilentRefresh = true;
|
||||
|
||||
this._oAuthService.configure(authConfig);
|
||||
this._oAuthService.tokenValidationHandler = new JwksValidationHandler();
|
||||
|
||||
this._oAuthService.setupAutomaticSilentRefresh();
|
||||
await this._oAuthService.loadDiscoveryDocumentAndTryLogin();
|
||||
|
||||
this._initialized.next(true);
|
||||
}
|
||||
|
||||
isAuthenticated() {
|
||||
return this._oAuthService.hasValidIdToken();
|
||||
}
|
||||
|
||||
getToken() {
|
||||
return this._oAuthService.getAccessToken();
|
||||
}
|
||||
|
||||
getClaims() {
|
||||
const token = this._oAuthService.getAccessToken();
|
||||
return this.parseJwt(token);
|
||||
}
|
||||
|
||||
getClaimByKey(key: string) {
|
||||
const claims = this.getClaims();
|
||||
if (isNullOrUndefined(claims)) {
|
||||
return null;
|
||||
}
|
||||
return claims[key];
|
||||
}
|
||||
|
||||
parseJwt(token: string) {
|
||||
if (isNullOrUndefined(token)) {
|
||||
return null;
|
||||
}
|
||||
const base64Url = token.split('.')[1];
|
||||
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
|
||||
|
||||
const encoded = window.atob(base64);
|
||||
return JSON.parse(encoded);
|
||||
}
|
||||
|
||||
login() {
|
||||
this._oAuthService.initLoginFlow();
|
||||
}
|
||||
|
||||
setKeyCardToken(token: string) {
|
||||
this._oAuthService.customQueryParams = {
|
||||
temp_token: token,
|
||||
};
|
||||
}
|
||||
|
||||
async logout() {
|
||||
await this._oAuthService.revokeTokenAndLogout();
|
||||
}
|
||||
}
|
||||
4
apps/core/auth/src/lib/index.ts
Normal file
4
apps/core/auth/src/lib/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// start:ng42.barrel
|
||||
export * from './auth.module';
|
||||
export * from './auth.service';
|
||||
// end:ng42.barrel
|
||||
5
apps/core/auth/src/public-api.ts
Normal file
5
apps/core/auth/src/public-api.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
/*
|
||||
* Public API Surface of auth
|
||||
*/
|
||||
|
||||
export * from './lib';
|
||||
25
apps/core/auth/src/test.ts
Normal file
25
apps/core/auth/src/test.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
||||
|
||||
import 'zone.js';
|
||||
import 'zone.js/testing';
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
declare const require: {
|
||||
context(
|
||||
path: string,
|
||||
deep?: boolean,
|
||||
filter?: RegExp
|
||||
): {
|
||||
keys(): string[];
|
||||
<T>(id: string): T;
|
||||
};
|
||||
};
|
||||
|
||||
// First, initialize the Angular testing environment.
|
||||
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), { teardown: { destroyAfterEach: true } });
|
||||
|
||||
// Then we find all the tests.
|
||||
const context = require.context('./', true, /\.spec\.ts$/);
|
||||
// And load the modules.
|
||||
context.keys().map(context);
|
||||
20
apps/core/auth/tsconfig.lib.json
Normal file
20
apps/core/auth/tsconfig.lib.json
Normal file
@@ -0,0 +1,20 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../out-tsc/lib",
|
||||
"target": "es2015",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"inlineSources": true,
|
||||
"types": [],
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2018"
|
||||
]
|
||||
},
|
||||
"exclude": [
|
||||
"src/test.ts",
|
||||
"**/*.spec.ts"
|
||||
]
|
||||
}
|
||||
10
apps/core/auth/tsconfig.lib.prod.json
Normal file
10
apps/core/auth/tsconfig.lib.prod.json
Normal file
@@ -0,0 +1,10 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "./tsconfig.lib.json",
|
||||
"compilerOptions": {
|
||||
"declarationMap": false
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"compilationMode": "partial"
|
||||
}
|
||||
}
|
||||
17
apps/core/auth/tsconfig.spec.json
Normal file
17
apps/core/auth/tsconfig.spec.json
Normal file
@@ -0,0 +1,17 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../out-tsc/spec",
|
||||
"types": [
|
||||
"jasmine"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"src/test.ts"
|
||||
],
|
||||
"include": [
|
||||
"**/*.spec.ts",
|
||||
"**/*.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { Store } from '@ngrx/store';
|
||||
import { getNumberId } from '@utils/id';
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
import { take } from 'rxjs/operators';
|
||||
import { first, map, take } from 'rxjs/operators';
|
||||
import { Breadcrumb } from './defs';
|
||||
|
||||
import * as actions from './store/breadcrumb.actions';
|
||||
@@ -13,6 +13,10 @@ import * as selectors from './store/breadcrumb.selectors';
|
||||
export class BreadcrumbService {
|
||||
constructor(private store: Store<any>) {}
|
||||
|
||||
getAll$() {
|
||||
return this.store.select(selectors.selectBreadcrumbs);
|
||||
}
|
||||
|
||||
getByKey$(key: string): Observable<Breadcrumb[]> {
|
||||
return this.store.select(selectors.selectBreadcrumbsByKey, key);
|
||||
}
|
||||
@@ -25,14 +29,32 @@ export class BreadcrumbService {
|
||||
return this.store.select(selectors.selectBreadcrumbsByKey, key);
|
||||
}
|
||||
|
||||
getLastActivatedBreadcrumbByKey$(key: string | number): Observable<Breadcrumb> {
|
||||
return this.getBreadcrumbByKey$(key).pipe(
|
||||
map((crumbs) =>
|
||||
crumbs.reduce((latest, current) => {
|
||||
if (!latest) {
|
||||
return current;
|
||||
}
|
||||
return latest.timestamp > current.timestamp ? latest : current;
|
||||
}, undefined)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
addBreadcrumb(breadcrumb: Breadcrumb): Breadcrumb {
|
||||
const newBreadcrumb: Breadcrumb = { ...breadcrumb, id: getNumberId(), timestamp: Date.now() };
|
||||
const newBreadcrumb: Breadcrumb = { ...breadcrumb, id: getNumberId(), timestamp: Date.now(), changed: Date.now() };
|
||||
this.store.dispatch(actions.addBreadcrumb({ breadcrumb: newBreadcrumb }));
|
||||
return newBreadcrumb;
|
||||
}
|
||||
|
||||
patchBreadcrumb(breadcrumbId: number, changes: Partial<Breadcrumb>) {
|
||||
this.store.dispatch(actions.updateBreadcrumb({ id: breadcrumbId, changes }));
|
||||
this.store.dispatch(actions.updateBreadcrumb({ id: breadcrumbId, changes: { ...changes, changed: Date.now() } }));
|
||||
}
|
||||
|
||||
async patchBreadcrumbByKeyAndTags(key: string | number, tags: string[], changes: Partial<Breadcrumb>) {
|
||||
const crumbs = await this.getBreadcrumbsByKeyAndTags$(key, tags).pipe(first()).toPromise();
|
||||
crumbs.forEach((crumb) => this.patchBreadcrumb(crumb.id, changes));
|
||||
}
|
||||
|
||||
async addBreadcrumbIfNotExists(breadcrumb: Breadcrumb) {
|
||||
@@ -107,4 +129,15 @@ export class BreadcrumbService {
|
||||
|
||||
this.store.dispatch(actions.removeManyBreadcrumb({ ids: breadcrumbsToRemove.map((crumb) => crumb.id) }));
|
||||
}
|
||||
|
||||
async removeBreadcrumbsByKeyAndTags(key: number | string, tags: string[]) {
|
||||
const crumbs = await this.getBreadcrumbsByKeyAndTags$(key, tags).pipe(first()).toPromise();
|
||||
crumbs.forEach((crumb) => this.removeBreadcrumb(crumb.id));
|
||||
}
|
||||
|
||||
getLatestBreadcrumbForSection(section: 'customer' | 'branch') {
|
||||
return this.store
|
||||
.select(selectors.selectBreadcrumbsBySection, { section })
|
||||
.pipe(map((crumbs) => crumbs.sort((a, b) => b.changed - a.changed).find((f) => true)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,4 +33,14 @@ export interface Breadcrumb {
|
||||
* Timestamp
|
||||
*/
|
||||
timestamp?: number;
|
||||
|
||||
/**
|
||||
* Cahnged
|
||||
*/
|
||||
changed?: number;
|
||||
|
||||
/**
|
||||
* Applicatiuon Section
|
||||
*/
|
||||
section: string;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { processRemoved } from '@core/application';
|
||||
import { removeProcess } from '@core/application';
|
||||
import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||
import { NEVER } from 'rxjs';
|
||||
import { mergeMap, tap, first, map } from 'rxjs/operators';
|
||||
@@ -10,7 +10,7 @@ export class BreadcrumbEffects {
|
||||
removeProcess$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType(processRemoved),
|
||||
ofType(removeProcess),
|
||||
mergeMap((action) =>
|
||||
this.breadcrumb.getBreadcrumbByKey$(action.processId).pipe(
|
||||
first(),
|
||||
|
||||
@@ -39,3 +39,10 @@ export const selectBreadcrumbsByKeyAndTags = createSelector(
|
||||
(entities: Breadcrumb[], { key, tags }: { key: string; tags: string[] }) =>
|
||||
entities.filter((crumb) => crumb.key === key && isArray(crumb.tags) && tags.every((tag) => crumb.tags.includes(tag)))
|
||||
);
|
||||
|
||||
/**
|
||||
* Gibt alle Breadcrumb Entities als Array zurück die die tags enthalten
|
||||
*/
|
||||
export const selectBreadcrumbsBySection = createSelector(selectAll, (entities: Breadcrumb[], { section }: { section: string }) =>
|
||||
entities.filter((crumb) => crumb.section === section)
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
||||
|
||||
import 'zone.js/dist/zone';
|
||||
import 'zone.js/dist/zone-testing';
|
||||
import 'zone.js';
|
||||
import 'zone.js/testing';
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
|
||||
4
apps/core/cache/src/test.ts
vendored
4
apps/core/cache/src/test.ts
vendored
@@ -1,7 +1,7 @@
|
||||
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
||||
|
||||
import 'zone.js/dist/zone';
|
||||
import 'zone.js/dist/zone-testing';
|
||||
import 'zone.js';
|
||||
import 'zone.js/testing';
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
|
||||
25
apps/core/command/README.md
Normal file
25
apps/core/command/README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Command
|
||||
|
||||
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 10.1.2.
|
||||
|
||||
## Code scaffolding
|
||||
|
||||
Run `ng generate component component-name --project command` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project command`.
|
||||
|
||||
> Note: Don't forget to add `--project command` or else it will be added to the default project in your `angular.json` file.
|
||||
|
||||
## Build
|
||||
|
||||
Run `ng build command` to build the project. The build artifacts will be stored in the `dist/` directory.
|
||||
|
||||
## Publishing
|
||||
|
||||
After building your library with `ng build command`, go to the dist folder `cd dist/command` and run `npm publish`.
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `ng test command` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||
|
||||
## Further help
|
||||
|
||||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
|
||||
32
apps/core/command/karma.conf.js
Normal file
32
apps/core/command/karma.conf.js
Normal file
@@ -0,0 +1,32 @@
|
||||
// Karma configuration file, see link for more information
|
||||
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage-istanbul-reporter'),
|
||||
require('@angular-devkit/build-angular/plugins/karma'),
|
||||
],
|
||||
client: {
|
||||
clearContext: false, // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
coverageIstanbulReporter: {
|
||||
dir: require('path').join(__dirname, '../../../coverage/core/command'),
|
||||
reports: ['html', 'lcovonly', 'text-summary'],
|
||||
fixWebpackSourcePaths: true,
|
||||
},
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
singleRun: false,
|
||||
restartOnFileChange: true,
|
||||
});
|
||||
};
|
||||
7
apps/core/command/ng-package.json
Normal file
7
apps/core/command/ng-package.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
|
||||
"dest": "../../../dist/core/command",
|
||||
"lib": {
|
||||
"entryFile": "src/public-api.ts"
|
||||
}
|
||||
}
|
||||
11
apps/core/command/package.json
Normal file
11
apps/core/command/package.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "@core/command",
|
||||
"version": "0.0.1",
|
||||
"peerDependencies": {
|
||||
"@angular/common": "^10.1.2",
|
||||
"@angular/core": "^10.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": "^2.0.0"
|
||||
}
|
||||
}
|
||||
4
apps/core/command/src/lib/action-handler.interface.ts
Normal file
4
apps/core/command/src/lib/action-handler.interface.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export abstract class ActionHandler<T = any> {
|
||||
constructor(readonly action: string) {}
|
||||
abstract handler(data: T): Promise<T>;
|
||||
}
|
||||
21
apps/core/command/src/lib/command.module.ts
Normal file
21
apps/core/command/src/lib/command.module.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { ModuleWithProviders, NgModule, Type } from '@angular/core';
|
||||
import { ActionHandler } from './action-handler.interface';
|
||||
import { CommandService } from './command.service';
|
||||
import { FEATURE_ACTION_HANDLERS, ROOT_ACTION_HANDLERS } from './tokens';
|
||||
|
||||
@NgModule({})
|
||||
export class CoreCommandModule {
|
||||
static forRoot(actionHandlers: Type<ActionHandler>[]): ModuleWithProviders<CoreCommandModule> {
|
||||
return {
|
||||
ngModule: CoreCommandModule,
|
||||
providers: [CommandService, actionHandlers.map((handler) => ({ provide: ROOT_ACTION_HANDLERS, useClass: handler, multi: true }))],
|
||||
};
|
||||
}
|
||||
|
||||
static forChild(actionHandlers: Type<ActionHandler>[]): ModuleWithProviders<CoreCommandModule> {
|
||||
return {
|
||||
ngModule: CoreCommandModule,
|
||||
providers: [CommandService, actionHandlers.map((handler) => ({ provide: FEATURE_ACTION_HANDLERS, useClass: handler, multi: true }))],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { OmsService } from './oms.service';
|
||||
import { CommandService } from './command.service';
|
||||
|
||||
describe('OmsService', () => {
|
||||
let service: OmsService;
|
||||
describe('CommandService', () => {
|
||||
let service: CommandService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(OmsService);
|
||||
service = TestBed.inject(CommandService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
34
apps/core/command/src/lib/command.service.ts
Normal file
34
apps/core/command/src/lib/command.service.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Injectable, Injector } from '@angular/core';
|
||||
import { ActionHandler } from './action-handler.interface';
|
||||
import { FEATURE_ACTION_HANDLERS, ROOT_ACTION_HANDLERS } from './tokens';
|
||||
|
||||
@Injectable()
|
||||
export class CommandService {
|
||||
constructor(private injector: Injector) {}
|
||||
|
||||
async handleCommand<T>(command: string, data?: T): Promise<T> {
|
||||
const actions = this.getActions(command);
|
||||
|
||||
for (const action of actions) {
|
||||
const handler = this.getActionHandler(action);
|
||||
if (!handler) {
|
||||
console.error('CommandService.handleCommand', 'Action Handler does not exist', { action });
|
||||
throw new Error('Action Handler does not exist');
|
||||
}
|
||||
console.log('handle command', handler, data);
|
||||
data = await handler.handler(data);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
getActions(command: string) {
|
||||
return command?.split('|') || [];
|
||||
}
|
||||
|
||||
getActionHandler(action: string): ActionHandler {
|
||||
const featureActionHandlers: ActionHandler[] = this.injector.get(FEATURE_ACTION_HANDLERS, []);
|
||||
const rootActionHandlers: ActionHandler[] = this.injector.get(ROOT_ACTION_HANDLERS, []);
|
||||
|
||||
return [...featureActionHandlers, ...rootActionHandlers].find((handler) => handler.action === action);
|
||||
}
|
||||
}
|
||||
6
apps/core/command/src/lib/index.ts
Normal file
6
apps/core/command/src/lib/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
// start:ng42.barrel
|
||||
export * from './action-handler.interface';
|
||||
export * from './command.module';
|
||||
export * from './command.service';
|
||||
export * from './tokens';
|
||||
// end:ng42.barrel
|
||||
6
apps/core/command/src/lib/tokens.ts
Normal file
6
apps/core/command/src/lib/tokens.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { InjectionToken } from '@angular/core';
|
||||
import { ActionHandler } from './action-handler.interface';
|
||||
|
||||
export const ROOT_ACTION_HANDLERS = new InjectionToken<ActionHandler[]>('@core/domain ROOT_ACTION_HANDLER');
|
||||
|
||||
export const FEATURE_ACTION_HANDLERS = new InjectionToken<ActionHandler[]>('@core/domain FEATURE_ACTION_HANDLER');
|
||||
5
apps/core/command/src/public-api.ts
Normal file
5
apps/core/command/src/public-api.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
/*
|
||||
* Public API Surface of command
|
||||
*/
|
||||
|
||||
export * from './lib';
|
||||
24
apps/core/command/src/test.ts
Normal file
24
apps/core/command/src/test.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
||||
|
||||
import 'zone.js';
|
||||
import 'zone.js/testing';
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
declare const require: {
|
||||
context(
|
||||
path: string,
|
||||
deep?: boolean,
|
||||
filter?: RegExp
|
||||
): {
|
||||
keys(): string[];
|
||||
<T>(id: string): T;
|
||||
};
|
||||
};
|
||||
|
||||
// First, initialize the Angular testing environment.
|
||||
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
|
||||
// Then we find all the tests.
|
||||
const context = require.context('./', true, /\.spec\.ts$/);
|
||||
// And load the modules.
|
||||
context.keys().map(context);
|
||||
25
apps/core/command/tsconfig.lib.json
Normal file
25
apps/core/command/tsconfig.lib.json
Normal file
@@ -0,0 +1,25 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../out-tsc/lib",
|
||||
"target": "es2015",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"inlineSources": true,
|
||||
"types": [],
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2018"
|
||||
]
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"skipTemplateCodegen": true,
|
||||
"strictMetadataEmit": true,
|
||||
"enableResourceInlining": true
|
||||
},
|
||||
"exclude": [
|
||||
"src/test.ts",
|
||||
"**/*.spec.ts"
|
||||
]
|
||||
}
|
||||
10
apps/core/command/tsconfig.lib.prod.json
Normal file
10
apps/core/command/tsconfig.lib.prod.json
Normal file
@@ -0,0 +1,10 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "./tsconfig.lib.json",
|
||||
"compilerOptions": {
|
||||
"declarationMap": false
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableIvy": false
|
||||
}
|
||||
}
|
||||
17
apps/core/command/tsconfig.spec.json
Normal file
17
apps/core/command/tsconfig.spec.json
Normal file
@@ -0,0 +1,17 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../out-tsc/spec",
|
||||
"types": [
|
||||
"jasmine"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"src/test.ts"
|
||||
],
|
||||
"include": [
|
||||
"**/*.spec.ts",
|
||||
"**/*.d.ts"
|
||||
]
|
||||
}
|
||||
17
apps/core/command/tslint.json
Normal file
17
apps/core/command/tslint.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"extends": "../../../tslint.json",
|
||||
"rules": {
|
||||
"directive-selector": [
|
||||
true,
|
||||
"attribute",
|
||||
"lib",
|
||||
"camelCase"
|
||||
],
|
||||
"component-selector": [
|
||||
true,
|
||||
"element",
|
||||
"lib",
|
||||
"kebab-case"
|
||||
]
|
||||
}
|
||||
}
|
||||
25
apps/core/config/README.md
Normal file
25
apps/core/config/README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Config
|
||||
|
||||
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 12.2.0.
|
||||
|
||||
## Code scaffolding
|
||||
|
||||
Run `ng generate component component-name --project config` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project config`.
|
||||
|
||||
> Note: Don't forget to add `--project config` or else it will be added to the default project in your `angular.json` file.
|
||||
|
||||
## Build
|
||||
|
||||
Run `ng build config` to build the project. The build artifacts will be stored in the `dist/` directory.
|
||||
|
||||
## Publishing
|
||||
|
||||
After building your library with `ng build config`, go to the dist folder `cd dist/config` and run `npm publish`.
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `ng test config` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||
|
||||
## Further help
|
||||
|
||||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
|
||||
43
apps/core/config/karma.conf.js
Normal file
43
apps/core/config/karma.conf.js
Normal file
@@ -0,0 +1,43 @@
|
||||
// Karma configuration file, see link for more information
|
||||
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||
const customLaunchers = require('../../../karma/custom-launchers');
|
||||
const junitReporter = require('../../../karma/junit-reporter')('core-config');
|
||||
const coverageReporter = require('../../../karma/coverage-reporter')('core-config');
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage'),
|
||||
require('karma-junit-reporter'),
|
||||
require('@angular-devkit/build-angular/plugins/karma'),
|
||||
],
|
||||
client: {
|
||||
jasmine: {
|
||||
// you can add configuration options for Jasmine here
|
||||
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
|
||||
// for example, you can disable the random execution with `random: false`
|
||||
// or set a specific seed with `seed: 4321`
|
||||
},
|
||||
clearContext: false, // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
jasmineHtmlReporter: {
|
||||
suppressAll: true, // removes the duplicated traces
|
||||
},
|
||||
coverageReporter,
|
||||
junitReporter,
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
customLaunchers,
|
||||
singleRun: false,
|
||||
restartOnFileChange: true,
|
||||
});
|
||||
};
|
||||
7
apps/core/config/ng-package.json
Normal file
7
apps/core/config/ng-package.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
|
||||
"dest": "../../../dist/core/config",
|
||||
"lib": {
|
||||
"entryFile": "src/public-api.ts"
|
||||
}
|
||||
}
|
||||
11
apps/core/config/package.json
Normal file
11
apps/core/config/package.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "@core/config",
|
||||
"version": "0.0.1",
|
||||
"peerDependencies": {
|
||||
"@angular/common": "^12.2.0",
|
||||
"@angular/core": "^12.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
}
|
||||
}
|
||||
8
apps/core/config/src/lib/config-loaders/config-loader.ts
Normal file
8
apps/core/config/src/lib/config-loaders/config-loader.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
/**
|
||||
* Config loader interface for loading configurations
|
||||
*/
|
||||
export interface ConfigLoader {
|
||||
load(): Promise<any>;
|
||||
}
|
||||
4
apps/core/config/src/lib/config-loaders/index.ts
Normal file
4
apps/core/config/src/lib/config-loaders/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// start:ng42.barrel
|
||||
export * from './config-loader';
|
||||
export * from './json.config-loader';
|
||||
// end:ng42.barrel
|
||||
@@ -0,0 +1,36 @@
|
||||
// unit test JsonConfigLoader
|
||||
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
|
||||
import { createServiceFactory, SpectatorService } from '@ngneat/spectator';
|
||||
import { CORE_JSON_CONFIG_LOADER_URL } from '../tokens';
|
||||
import { JsonConfigLoader } from './json.config-loader';
|
||||
|
||||
describe('JsonConfigLoader', () => {
|
||||
let spectator: SpectatorService<JsonConfigLoader>;
|
||||
const createService = createServiceFactory({
|
||||
imports: [HttpClientTestingModule],
|
||||
service: JsonConfigLoader,
|
||||
mocks: [],
|
||||
providers: [{ provide: CORE_JSON_CONFIG_LOADER_URL, useValue: '/assets/config.json' }],
|
||||
});
|
||||
let httpTestingController: HttpTestingController;
|
||||
|
||||
beforeEach(() => {
|
||||
spectator = createService();
|
||||
httpTestingController = spectator.inject(HttpTestingController);
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(spectator.service).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('load', () => {
|
||||
it('should call the provided url', async () => {
|
||||
const reqPromise = spectator.service.load();
|
||||
const req = httpTestingController.expectOne('/assets/config.json');
|
||||
req.flush({ unit: 'test' });
|
||||
const result = await reqPromise;
|
||||
httpTestingController.verify();
|
||||
expect(result).toEqual({ unit: 'test' });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
import { ConfigLoader } from './config-loader';
|
||||
import { CORE_JSON_CONFIG_LOADER_URL } from '../tokens';
|
||||
|
||||
@Injectable()
|
||||
export class JsonConfigLoader implements ConfigLoader {
|
||||
constructor(@Inject(CORE_JSON_CONFIG_LOADER_URL) private url: string, private http: HttpClient) {}
|
||||
|
||||
load(): Promise<any> {
|
||||
return this.http.get(this.url).toPromise();
|
||||
}
|
||||
}
|
||||
7
apps/core/config/src/lib/config-module-options.ts
Normal file
7
apps/core/config/src/lib/config-module-options.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Type } from '@angular/core';
|
||||
import { ConfigLoader } from './config-loaders';
|
||||
|
||||
export interface ConfigModuleOptions {
|
||||
useConfigLoader: Type<ConfigLoader>;
|
||||
jsonConfigLoaderUrl?: string;
|
||||
}
|
||||
28
apps/core/config/src/lib/config.module.ts
Normal file
28
apps/core/config/src/lib/config.module.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { APP_INITIALIZER, ModuleWithProviders, NgModule } from '@angular/core';
|
||||
import { CORE_CONFIG_LOADER } from '@core/config';
|
||||
import { Config } from './config';
|
||||
import { ConfigModuleOptions } from './config-module-options';
|
||||
import { CORE_JSON_CONFIG_LOADER_URL } from './tokens';
|
||||
|
||||
export function _initializeConfigFactory(config: Config) {
|
||||
return () => config.init();
|
||||
}
|
||||
|
||||
@NgModule({})
|
||||
export class ConfigModule {
|
||||
static forRoot(options: ConfigModuleOptions): ModuleWithProviders<ConfigModule> {
|
||||
const configLoaderProvider = {
|
||||
provide: CORE_CONFIG_LOADER,
|
||||
useClass: options.useConfigLoader,
|
||||
};
|
||||
|
||||
return {
|
||||
ngModule: ConfigModule,
|
||||
providers: [
|
||||
Config,
|
||||
configLoaderProvider,
|
||||
options.jsonConfigLoaderUrl ? { provide: CORE_JSON_CONFIG_LOADER_URL, useValue: options.jsonConfigLoaderUrl } : null,
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
45
apps/core/config/src/lib/config.spec.ts
Normal file
45
apps/core/config/src/lib/config.spec.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { createServiceFactory, SpectatorService } from '@ngneat/spectator';
|
||||
import { Config } from './config';
|
||||
import { ConfigLoader } from './config-loaders';
|
||||
import { CORE_CONFIG_LOADER } from './tokens';
|
||||
|
||||
class TestConfigLoader implements ConfigLoader {
|
||||
load() {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
}
|
||||
|
||||
// Unit test Config
|
||||
describe('Config', () => {
|
||||
let spectator: SpectatorService<Config>;
|
||||
const createService = createServiceFactory({
|
||||
service: Config,
|
||||
providers: [{ provide: CORE_CONFIG_LOADER, useClass: TestConfigLoader }],
|
||||
});
|
||||
let configLoader: ConfigLoader;
|
||||
|
||||
beforeEach(() => {
|
||||
spectator = createService();
|
||||
configLoader = spectator.inject(CORE_CONFIG_LOADER);
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(spectator.service).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('init()', () => {
|
||||
it('should load config and assigns it to _config', async () => {
|
||||
const config = { unit: 'test' };
|
||||
spyOn(configLoader, 'load').and.returnValue(Promise.resolve(config));
|
||||
await spectator.service.init();
|
||||
expect(spectator.service['_config']).toEqual(config);
|
||||
});
|
||||
});
|
||||
|
||||
describe('get()', () => {
|
||||
it('should return config value', () => {
|
||||
spectator.service['_config'] = { test: 'test' };
|
||||
expect(spectator.service.get('test')).toEqual('test');
|
||||
});
|
||||
});
|
||||
});
|
||||
27
apps/core/config/src/lib/config.ts
Normal file
27
apps/core/config/src/lib/config.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
import { ReplaySubject } from 'rxjs';
|
||||
import { ConfigLoader } from './config-loaders';
|
||||
import { CORE_CONFIG_LOADER } from './tokens';
|
||||
import { pick } from './utils';
|
||||
|
||||
@Injectable()
|
||||
export class Config {
|
||||
private _config: any;
|
||||
|
||||
private readonly _initilized = new ReplaySubject<void>(1);
|
||||
get initialized() {
|
||||
return this._initilized.asObservable();
|
||||
}
|
||||
|
||||
constructor(@Inject(CORE_CONFIG_LOADER) private readonly _configLoader: ConfigLoader) {}
|
||||
|
||||
// load config and assign it to this._config
|
||||
async init() {
|
||||
this._config = await this._configLoader.load();
|
||||
this._initilized.next();
|
||||
}
|
||||
|
||||
get(path: string) {
|
||||
return pick(path, this._config);
|
||||
}
|
||||
}
|
||||
8
apps/core/config/src/lib/index.ts
Normal file
8
apps/core/config/src/lib/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
// start:ng42.barrel
|
||||
export * from './config-module-options';
|
||||
export * from './config.module';
|
||||
export * from './config';
|
||||
export * from './tokens';
|
||||
export * from './config-loaders';
|
||||
export * from './utils';
|
||||
// end:ng42.barrel
|
||||
6
apps/core/config/src/lib/tokens.ts
Normal file
6
apps/core/config/src/lib/tokens.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { InjectionToken } from '@angular/core';
|
||||
import { ConfigLoader } from './config-loaders';
|
||||
|
||||
export const CORE_CONFIG_LOADER = new InjectionToken<ConfigLoader>('core.config.loader');
|
||||
|
||||
export const CORE_JSON_CONFIG_LOADER_URL = new InjectionToken<ConfigLoader>('core.json.config.loader.url');
|
||||
3
apps/core/config/src/lib/utils/index.ts
Normal file
3
apps/core/config/src/lib/utils/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// start:ng42.barrel
|
||||
export * from './pick';
|
||||
// end:ng42.barrel
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user