diff --git a/.yarn/cache/@angular-devkit-architect-npm-0.1602.14-725d7bd035-88405d68dc.zip b/.yarn/cache/@angular-devkit-architect-npm-0.1602.15-cddc67ba7d-dbd247e7be.zip similarity index 99% rename from .yarn/cache/@angular-devkit-architect-npm-0.1602.14-725d7bd035-88405d68dc.zip rename to .yarn/cache/@angular-devkit-architect-npm-0.1602.15-cddc67ba7d-dbd247e7be.zip index 7deb5b79e..ee11877e8 100644 Binary files a/.yarn/cache/@angular-devkit-architect-npm-0.1602.14-725d7bd035-88405d68dc.zip and b/.yarn/cache/@angular-devkit-architect-npm-0.1602.15-cddc67ba7d-dbd247e7be.zip differ diff --git a/.yarn/cache/@angular-devkit-core-npm-16.2.14-e715699b60-995a242bd1.zip b/.yarn/cache/@angular-devkit-core-npm-16.2.15-9cdd79fbc9-30506e50c6.zip similarity index 98% rename from .yarn/cache/@angular-devkit-core-npm-16.2.14-e715699b60-995a242bd1.zip rename to .yarn/cache/@angular-devkit-core-npm-16.2.15-9cdd79fbc9-30506e50c6.zip index 68751b8ae..8beb4a1ef 100644 Binary files a/.yarn/cache/@angular-devkit-core-npm-16.2.14-e715699b60-995a242bd1.zip and b/.yarn/cache/@angular-devkit-core-npm-16.2.15-9cdd79fbc9-30506e50c6.zip differ diff --git a/.yarn/cache/@babel-compat-data-npm-7.25.2-119057710e-b61bc9da7c.zip b/.yarn/cache/@babel-compat-data-npm-7.25.2-119057710e-b61bc9da7c.zip deleted file mode 100644 index 4bef9a733..000000000 Binary files a/.yarn/cache/@babel-compat-data-npm-7.25.2-119057710e-b61bc9da7c.zip and /dev/null differ diff --git a/.yarn/cache/@babel-compat-data-npm-7.25.4-213b9c835f-b12a91d27c.zip b/.yarn/cache/@babel-compat-data-npm-7.25.4-213b9c835f-b12a91d27c.zip new file mode 100644 index 000000000..1dc463cf6 Binary files /dev/null and b/.yarn/cache/@babel-compat-data-npm-7.25.4-213b9c835f-b12a91d27c.zip differ diff --git a/.yarn/cache/@babel-generator-npm-7.25.0-4bba208756-bf25649dde.zip b/.yarn/cache/@babel-generator-npm-7.25.0-4bba208756-bf25649dde.zip deleted file mode 100644 index d1a7e948c..000000000 Binary files a/.yarn/cache/@babel-generator-npm-7.25.0-4bba208756-bf25649dde.zip and /dev/null differ diff --git a/.yarn/cache/@babel-generator-npm-7.25.6-3bdca6c59f-b55975cd66.zip b/.yarn/cache/@babel-generator-npm-7.25.6-3bdca6c59f-b55975cd66.zip new file mode 100644 index 000000000..1ea1d983c Binary files /dev/null and b/.yarn/cache/@babel-generator-npm-7.25.6-3bdca6c59f-b55975cd66.zip differ diff --git a/.yarn/cache/@babel-helper-create-class-features-plugin-npm-7.25.0-8c1a9bf7ca-e986c1187e.zip b/.yarn/cache/@babel-helper-create-class-features-plugin-npm-7.25.4-125644448f-4544ebda45.zip similarity index 71% rename from .yarn/cache/@babel-helper-create-class-features-plugin-npm-7.25.0-8c1a9bf7ca-e986c1187e.zip rename to .yarn/cache/@babel-helper-create-class-features-plugin-npm-7.25.4-125644448f-4544ebda45.zip index 9d98d86c2..d486118ea 100644 Binary files a/.yarn/cache/@babel-helper-create-class-features-plugin-npm-7.25.0-8c1a9bf7ca-e986c1187e.zip and b/.yarn/cache/@babel-helper-create-class-features-plugin-npm-7.25.4-125644448f-4544ebda45.zip differ diff --git a/.yarn/cache/@babel-helpers-npm-7.25.0-f552d9aaf3-739e3704ff.zip b/.yarn/cache/@babel-helpers-npm-7.25.6-6722375514-5a548999db.zip similarity index 79% rename from .yarn/cache/@babel-helpers-npm-7.25.0-f552d9aaf3-739e3704ff.zip rename to .yarn/cache/@babel-helpers-npm-7.25.6-6722375514-5a548999db.zip index 52135bdd4..d081ee69d 100644 Binary files a/.yarn/cache/@babel-helpers-npm-7.25.0-f552d9aaf3-739e3704ff.zip and b/.yarn/cache/@babel-helpers-npm-7.25.6-6722375514-5a548999db.zip differ diff --git a/.yarn/cache/@babel-parser-npm-7.25.3-e33bb4a0e6-b55aba6421.zip b/.yarn/cache/@babel-parser-npm-7.25.3-e33bb4a0e6-b55aba6421.zip deleted file mode 100644 index 5ec509e09..000000000 Binary files a/.yarn/cache/@babel-parser-npm-7.25.3-e33bb4a0e6-b55aba6421.zip and /dev/null differ diff --git a/.yarn/cache/@babel-parser-npm-7.25.6-3cb198940b-85b237ded0.zip b/.yarn/cache/@babel-parser-npm-7.25.6-3cb198940b-85b237ded0.zip new file mode 100644 index 000000000..cfd68a6ef Binary files /dev/null and b/.yarn/cache/@babel-parser-npm-7.25.6-3cb198940b-85b237ded0.zip differ diff --git a/.yarn/cache/@babel-plugin-syntax-import-assertions-npm-7.24.7-06924e77aa-c4d67be4eb.zip b/.yarn/cache/@babel-plugin-syntax-import-assertions-npm-7.24.7-06924e77aa-c4d67be4eb.zip deleted file mode 100644 index 0f8703eb9..000000000 Binary files a/.yarn/cache/@babel-plugin-syntax-import-assertions-npm-7.24.7-06924e77aa-c4d67be4eb.zip and /dev/null differ diff --git a/.yarn/cache/@babel-plugin-syntax-import-assertions-npm-7.25.6-f61334bd30-b3b251ace9.zip b/.yarn/cache/@babel-plugin-syntax-import-assertions-npm-7.25.6-f61334bd30-b3b251ace9.zip new file mode 100644 index 000000000..131985bbb Binary files /dev/null and b/.yarn/cache/@babel-plugin-syntax-import-assertions-npm-7.25.6-f61334bd30-b3b251ace9.zip differ diff --git a/.yarn/cache/@babel-plugin-syntax-import-attributes-npm-7.24.7-6101aa2bfb-590dbb5d1a.zip b/.yarn/cache/@babel-plugin-syntax-import-attributes-npm-7.24.7-6101aa2bfb-590dbb5d1a.zip deleted file mode 100644 index 29847ebe0..000000000 Binary files a/.yarn/cache/@babel-plugin-syntax-import-attributes-npm-7.24.7-6101aa2bfb-590dbb5d1a.zip and /dev/null differ diff --git a/.yarn/cache/@babel-plugin-syntax-import-attributes-npm-7.25.6-21fbaebb12-3b0928e73e.zip b/.yarn/cache/@babel-plugin-syntax-import-attributes-npm-7.25.6-21fbaebb12-3b0928e73e.zip new file mode 100644 index 000000000..05ea6eee5 Binary files /dev/null and b/.yarn/cache/@babel-plugin-syntax-import-attributes-npm-7.25.6-21fbaebb12-3b0928e73e.zip differ diff --git a/.yarn/cache/@babel-plugin-transform-async-generator-functions-npm-7.25.0-f63107cabf-cce2bab70a.zip b/.yarn/cache/@babel-plugin-transform-async-generator-functions-npm-7.25.0-f63107cabf-cce2bab70a.zip deleted file mode 100644 index 681b4de3c..000000000 Binary files a/.yarn/cache/@babel-plugin-transform-async-generator-functions-npm-7.25.0-f63107cabf-cce2bab70a.zip and /dev/null differ diff --git a/.yarn/cache/@babel-plugin-transform-async-generator-functions-npm-7.25.4-5ba1962e15-4235444735.zip b/.yarn/cache/@babel-plugin-transform-async-generator-functions-npm-7.25.4-5ba1962e15-4235444735.zip new file mode 100644 index 000000000..9f328890c Binary files /dev/null and b/.yarn/cache/@babel-plugin-transform-async-generator-functions-npm-7.25.4-5ba1962e15-4235444735.zip differ diff --git a/.yarn/cache/@babel-plugin-transform-class-properties-npm-7.24.7-42a5aafd3c-1348d7ce74.zip b/.yarn/cache/@babel-plugin-transform-class-properties-npm-7.24.7-42a5aafd3c-1348d7ce74.zip deleted file mode 100644 index 619dc50fe..000000000 Binary files a/.yarn/cache/@babel-plugin-transform-class-properties-npm-7.24.7-42a5aafd3c-1348d7ce74.zip and /dev/null differ diff --git a/.yarn/cache/@babel-plugin-transform-class-properties-npm-7.25.4-d9eb8b633d-b73f7d9686.zip b/.yarn/cache/@babel-plugin-transform-class-properties-npm-7.25.4-d9eb8b633d-b73f7d9686.zip new file mode 100644 index 000000000..0b5b1f5be Binary files /dev/null and b/.yarn/cache/@babel-plugin-transform-class-properties-npm-7.25.4-d9eb8b633d-b73f7d9686.zip differ diff --git a/.yarn/cache/@babel-plugin-transform-classes-npm-7.25.0-82eaa19fb4-ff97f168e6.zip b/.yarn/cache/@babel-plugin-transform-classes-npm-7.25.4-4c8c2f428f-0bf20e46ee.zip similarity index 50% rename from .yarn/cache/@babel-plugin-transform-classes-npm-7.25.0-82eaa19fb4-ff97f168e6.zip rename to .yarn/cache/@babel-plugin-transform-classes-npm-7.25.4-4c8c2f428f-0bf20e46ee.zip index 2f2aef3c7..fc9dea40d 100644 Binary files a/.yarn/cache/@babel-plugin-transform-classes-npm-7.25.0-82eaa19fb4-ff97f168e6.zip and b/.yarn/cache/@babel-plugin-transform-classes-npm-7.25.4-4c8c2f428f-0bf20e46ee.zip differ diff --git a/.yarn/cache/@babel-plugin-transform-private-methods-npm-7.24.7-a20cce0583-c151548e34.zip b/.yarn/cache/@babel-plugin-transform-private-methods-npm-7.24.7-a20cce0583-c151548e34.zip deleted file mode 100644 index 83db1b8d4..000000000 Binary files a/.yarn/cache/@babel-plugin-transform-private-methods-npm-7.24.7-a20cce0583-c151548e34.zip and /dev/null differ diff --git a/.yarn/cache/@babel-plugin-transform-private-methods-npm-7.25.4-0bfe911738-cb1dabfc03.zip b/.yarn/cache/@babel-plugin-transform-private-methods-npm-7.25.4-0bfe911738-cb1dabfc03.zip new file mode 100644 index 000000000..117fd6eb2 Binary files /dev/null and b/.yarn/cache/@babel-plugin-transform-private-methods-npm-7.25.4-0bfe911738-cb1dabfc03.zip differ diff --git a/.yarn/cache/@babel-plugin-transform-unicode-sets-regex-npm-7.24.7-f4501a8afb-08a2844914.zip b/.yarn/cache/@babel-plugin-transform-unicode-sets-regex-npm-7.24.7-f4501a8afb-08a2844914.zip deleted file mode 100644 index 9014a713a..000000000 Binary files a/.yarn/cache/@babel-plugin-transform-unicode-sets-regex-npm-7.24.7-f4501a8afb-08a2844914.zip and /dev/null differ diff --git a/.yarn/cache/@babel-plugin-transform-unicode-sets-regex-npm-7.25.4-ce2960540d-6d1a7e9fdd.zip b/.yarn/cache/@babel-plugin-transform-unicode-sets-regex-npm-7.25.4-ce2960540d-6d1a7e9fdd.zip new file mode 100644 index 000000000..28b1d707f Binary files /dev/null and b/.yarn/cache/@babel-plugin-transform-unicode-sets-regex-npm-7.25.4-ce2960540d-6d1a7e9fdd.zip differ diff --git a/.yarn/cache/@babel-runtime-npm-7.25.0-a7bca33687-4a2a374a58.zip b/.yarn/cache/@babel-runtime-npm-7.25.6-6725f0979a-ee1a69d3ac.zip similarity index 89% rename from .yarn/cache/@babel-runtime-npm-7.25.0-a7bca33687-4a2a374a58.zip rename to .yarn/cache/@babel-runtime-npm-7.25.6-6725f0979a-ee1a69d3ac.zip index 58adb5ac0..f4cda3fdc 100644 Binary files a/.yarn/cache/@babel-runtime-npm-7.25.0-a7bca33687-4a2a374a58.zip and b/.yarn/cache/@babel-runtime-npm-7.25.6-6725f0979a-ee1a69d3ac.zip differ diff --git a/.yarn/cache/@babel-traverse-npm-7.25.3-69c3455e97-5661308b13.zip b/.yarn/cache/@babel-traverse-npm-7.25.3-69c3455e97-5661308b13.zip deleted file mode 100644 index 78b9f6954..000000000 Binary files a/.yarn/cache/@babel-traverse-npm-7.25.3-69c3455e97-5661308b13.zip and /dev/null differ diff --git a/.yarn/cache/@babel-traverse-npm-7.25.6-1b9e2a314c-11ee47269a.zip b/.yarn/cache/@babel-traverse-npm-7.25.6-1b9e2a314c-11ee47269a.zip new file mode 100644 index 000000000..43210c1d1 Binary files /dev/null and b/.yarn/cache/@babel-traverse-npm-7.25.6-1b9e2a314c-11ee47269a.zip differ diff --git a/.yarn/cache/@babel-types-npm-7.25.2-7d3fc0ed1e-f73f66ba90.zip b/.yarn/cache/@babel-types-npm-7.25.6-98df73a2ca-9b2f84ff3f.zip similarity index 72% rename from .yarn/cache/@babel-types-npm-7.25.2-7d3fc0ed1e-f73f66ba90.zip rename to .yarn/cache/@babel-types-npm-7.25.6-98df73a2ca-9b2f84ff3f.zip index 2ea5f08d9..dd2a3d09d 100644 Binary files a/.yarn/cache/@babel-types-npm-7.25.2-7d3fc0ed1e-f73f66ba90.zip and b/.yarn/cache/@babel-types-npm-7.25.6-98df73a2ca-9b2f84ff3f.zip differ diff --git a/.yarn/cache/@pnpm-npm-conf-npm-2.3.0-30bc640839-df071050ba.zip b/.yarn/cache/@pnpm-npm-conf-npm-2.3.1-6c7cfac5b4-9e1e1ce5fa.zip similarity index 65% rename from .yarn/cache/@pnpm-npm-conf-npm-2.3.0-30bc640839-df071050ba.zip rename to .yarn/cache/@pnpm-npm-conf-npm-2.3.1-6c7cfac5b4-9e1e1ce5fa.zip index 757edbe15..be8bd5ecf 100644 Binary files a/.yarn/cache/@pnpm-npm-conf-npm-2.3.0-30bc640839-df071050ba.zip and b/.yarn/cache/@pnpm-npm-conf-npm-2.3.1-6c7cfac5b4-9e1e1ce5fa.zip differ diff --git a/.yarn/cache/@stomp-stompjs-npm-7.0.0-c66e011389-94618b5a39.zip b/.yarn/cache/@stomp-stompjs-npm-7.0.0-c66e011389-94618b5a39.zip deleted file mode 100644 index c4a02b085..000000000 Binary files a/.yarn/cache/@stomp-stompjs-npm-7.0.0-c66e011389-94618b5a39.zip and /dev/null differ diff --git a/.yarn/cache/@types-eslint-npm-9.6.0-85c3542082-7be4b1d24f.zip b/.yarn/cache/@types-eslint-npm-9.6.1-31a8cf74e4-c286e79707.zip similarity index 72% rename from .yarn/cache/@types-eslint-npm-9.6.0-85c3542082-7be4b1d24f.zip rename to .yarn/cache/@types-eslint-npm-9.6.1-31a8cf74e4-c286e79707.zip index 94d697984..1b91cabff 100644 Binary files a/.yarn/cache/@types-eslint-npm-9.6.0-85c3542082-7be4b1d24f.zip and b/.yarn/cache/@types-eslint-npm-9.6.1-31a8cf74e4-c286e79707.zip differ diff --git a/.yarn/cache/@types-node-npm-22.1.0-3ca0e36ca8-3544c35da0.zip b/.yarn/cache/@types-node-npm-22.5.2-430cf418f9-5e47839dd1.zip similarity index 55% rename from .yarn/cache/@types-node-npm-22.1.0-3ca0e36ca8-3544c35da0.zip rename to .yarn/cache/@types-node-npm-22.5.2-430cf418f9-5e47839dd1.zip index 8d9280b69..bb62cbd90 100644 Binary files a/.yarn/cache/@types-node-npm-22.1.0-3ca0e36ca8-3544c35da0.zip and b/.yarn/cache/@types-node-npm-22.5.2-430cf418f9-5e47839dd1.zip differ diff --git a/.yarn/cache/async-npm-3.2.5-f5dbdabdfc-5ec77f1312.zip b/.yarn/cache/async-npm-3.2.6-aa4f5aa081-ee6eb8cd8a.zip similarity index 53% rename from .yarn/cache/async-npm-3.2.5-f5dbdabdfc-5ec77f1312.zip rename to .yarn/cache/async-npm-3.2.6-aa4f5aa081-ee6eb8cd8a.zip index a0ee3e0fb..041303275 100644 Binary files a/.yarn/cache/async-npm-3.2.5-f5dbdabdfc-5ec77f1312.zip and b/.yarn/cache/async-npm-3.2.6-aa4f5aa081-ee6eb8cd8a.zip differ diff --git a/.yarn/cache/aws4-npm-1.13.1-11deb16979-78fc37f00f.zip b/.yarn/cache/aws4-npm-1.13.1-11deb16979-78fc37f00f.zip deleted file mode 100644 index 26847b9e9..000000000 Binary files a/.yarn/cache/aws4-npm-1.13.1-11deb16979-78fc37f00f.zip and /dev/null differ diff --git a/.yarn/cache/aws4-npm-1.13.2-b493d08ff0-9ac924e4a9.zip b/.yarn/cache/aws4-npm-1.13.2-b493d08ff0-9ac924e4a9.zip new file mode 100644 index 000000000..1220969e6 Binary files /dev/null and b/.yarn/cache/aws4-npm-1.13.2-b493d08ff0-9ac924e4a9.zip differ diff --git a/.yarn/cache/axios-npm-1.7.3-a63743a582-bc304d6da9.zip b/.yarn/cache/axios-npm-1.7.3-a63743a582-bc304d6da9.zip deleted file mode 100644 index b565a820f..000000000 Binary files a/.yarn/cache/axios-npm-1.7.3-a63743a582-bc304d6da9.zip and /dev/null differ diff --git a/.yarn/cache/axios-npm-1.7.7-cfbedc233d-882d4fe0ec.zip b/.yarn/cache/axios-npm-1.7.7-cfbedc233d-882d4fe0ec.zip new file mode 100644 index 000000000..93353ced6 Binary files /dev/null and b/.yarn/cache/axios-npm-1.7.7-cfbedc233d-882d4fe0ec.zip differ diff --git a/.yarn/cache/caniuse-lite-npm-1.0.30001650-c867884d42-4892c25200.zip b/.yarn/cache/caniuse-lite-npm-1.0.30001650-c867884d42-4892c25200.zip deleted file mode 100644 index 8b435f2ae..000000000 Binary files a/.yarn/cache/caniuse-lite-npm-1.0.30001650-c867884d42-4892c25200.zip and /dev/null differ diff --git a/.yarn/cache/caniuse-lite-npm-1.0.30001655-a0517610a4-3739c8f6d0.zip b/.yarn/cache/caniuse-lite-npm-1.0.30001655-a0517610a4-3739c8f6d0.zip new file mode 100644 index 000000000..2351669c4 Binary files /dev/null and b/.yarn/cache/caniuse-lite-npm-1.0.30001655-a0517610a4-3739c8f6d0.zip differ diff --git a/.yarn/cache/core-js-compat-npm-3.38.0-cd0aa312bc-bd410be723.zip b/.yarn/cache/core-js-compat-npm-3.38.1-4114633af1-a0a5673bcd.zip similarity index 82% rename from .yarn/cache/core-js-compat-npm-3.38.0-cd0aa312bc-bd410be723.zip rename to .yarn/cache/core-js-compat-npm-3.38.1-4114633af1-a0a5673bcd.zip index b602c0bbc..088ca16ac 100644 Binary files a/.yarn/cache/core-js-compat-npm-3.38.0-cd0aa312bc-bd410be723.zip and b/.yarn/cache/core-js-compat-npm-3.38.1-4114633af1-a0a5673bcd.zip differ diff --git a/.yarn/cache/electron-to-chromium-npm-1.5.13-be9902b49c-f18ac84dd3.zip b/.yarn/cache/electron-to-chromium-npm-1.5.13-be9902b49c-f18ac84dd3.zip new file mode 100644 index 000000000..a31ffffa7 Binary files /dev/null and b/.yarn/cache/electron-to-chromium-npm-1.5.13-be9902b49c-f18ac84dd3.zip differ diff --git a/.yarn/cache/electron-to-chromium-npm-1.5.5-ce59e1f3c6-fcdd2797ec.zip b/.yarn/cache/electron-to-chromium-npm-1.5.5-ce59e1f3c6-fcdd2797ec.zip deleted file mode 100644 index d0d964575..000000000 Binary files a/.yarn/cache/electron-to-chromium-npm-1.5.5-ce59e1f3c6-fcdd2797ec.zip and /dev/null differ diff --git a/.yarn/cache/escalade-npm-3.1.2-5826d31cf8-1ec0977aa2.zip b/.yarn/cache/escalade-npm-3.2.0-19b50dd48f-47b029c83d.zip similarity index 64% rename from .yarn/cache/escalade-npm-3.1.2-5826d31cf8-1ec0977aa2.zip rename to .yarn/cache/escalade-npm-3.2.0-19b50dd48f-47b029c83d.zip index 3a466d189..8212e54c6 100644 Binary files a/.yarn/cache/escalade-npm-3.1.2-5826d31cf8-1ec0977aa2.zip and b/.yarn/cache/escalade-npm-3.2.0-19b50dd48f-47b029c83d.zip differ diff --git a/.yarn/cache/follow-redirects-npm-1.15.6-50635fe51d-a62c378dfc.zip b/.yarn/cache/follow-redirects-npm-1.15.6-50635fe51d-a62c378dfc.zip deleted file mode 100644 index 4a44b00e0..000000000 Binary files a/.yarn/cache/follow-redirects-npm-1.15.6-50635fe51d-a62c378dfc.zip and /dev/null differ diff --git a/.yarn/cache/follow-redirects-npm-1.15.7-8ae1d6a161-dee9a8324d.zip b/.yarn/cache/follow-redirects-npm-1.15.7-8ae1d6a161-dee9a8324d.zip new file mode 100644 index 000000000..0e6d0ab2e Binary files /dev/null and b/.yarn/cache/follow-redirects-npm-1.15.7-8ae1d6a161-dee9a8324d.zip differ diff --git a/.yarn/cache/foreground-child-npm-3.2.1-788dc2267e-3e2e844d60.zip b/.yarn/cache/foreground-child-npm-3.2.1-788dc2267e-3e2e844d60.zip deleted file mode 100644 index dd963aa31..000000000 Binary files a/.yarn/cache/foreground-child-npm-3.2.1-788dc2267e-3e2e844d60.zip and /dev/null differ diff --git a/.yarn/cache/foreground-child-npm-3.3.0-b8be745271-1989698488.zip b/.yarn/cache/foreground-child-npm-3.3.0-b8be745271-1989698488.zip new file mode 100644 index 000000000..71bcc520f Binary files /dev/null and b/.yarn/cache/foreground-child-npm-3.3.0-b8be745271-1989698488.zip differ diff --git a/.yarn/cache/ignore-npm-5.3.1-f6947c5df7-71d7bb4c1d.zip b/.yarn/cache/ignore-npm-5.3.1-f6947c5df7-71d7bb4c1d.zip deleted file mode 100644 index 75ba53a27..000000000 Binary files a/.yarn/cache/ignore-npm-5.3.1-f6947c5df7-71d7bb4c1d.zip and /dev/null differ diff --git a/.yarn/cache/ignore-npm-5.3.2-346d3ba017-2acfd32a57.zip b/.yarn/cache/ignore-npm-5.3.2-346d3ba017-2acfd32a57.zip new file mode 100644 index 000000000..75b9d00a2 Binary files /dev/null and b/.yarn/cache/ignore-npm-5.3.2-346d3ba017-2acfd32a57.zip differ diff --git a/.yarn/cache/is-core-module-npm-2.15.0-c262aaf790-a9f7a52707.zip b/.yarn/cache/is-core-module-npm-2.15.0-c262aaf790-a9f7a52707.zip deleted file mode 100644 index 9359c84d3..000000000 Binary files a/.yarn/cache/is-core-module-npm-2.15.0-c262aaf790-a9f7a52707.zip and /dev/null differ diff --git a/.yarn/cache/is-core-module-npm-2.15.1-34c73a6cbd-df134c1681.zip b/.yarn/cache/is-core-module-npm-2.15.1-34c73a6cbd-df134c1681.zip new file mode 100644 index 000000000..15a0f6f1d Binary files /dev/null and b/.yarn/cache/is-core-module-npm-2.15.1-34c73a6cbd-df134c1681.zip differ diff --git a/.yarn/cache/launch-editor-npm-2.8.1-6d18da04cb-69adfc913c.zip b/.yarn/cache/launch-editor-npm-2.8.2-477570ed22-4b0f38db0e.zip similarity index 57% rename from .yarn/cache/launch-editor-npm-2.8.1-6d18da04cb-69adfc913c.zip rename to .yarn/cache/launch-editor-npm-2.8.2-477570ed22-4b0f38db0e.zip index afb6f76df..6791849cb 100644 Binary files a/.yarn/cache/launch-editor-npm-2.8.1-6d18da04cb-69adfc913c.zip and b/.yarn/cache/launch-editor-npm-2.8.2-477570ed22-4b0f38db0e.zip differ diff --git a/.yarn/cache/micromatch-npm-4.0.7-28fb7387ee-3cde047d70.zip b/.yarn/cache/micromatch-npm-4.0.7-28fb7387ee-3cde047d70.zip deleted file mode 100644 index c950c2612..000000000 Binary files a/.yarn/cache/micromatch-npm-4.0.7-28fb7387ee-3cde047d70.zip and /dev/null differ diff --git a/.yarn/cache/micromatch-npm-4.0.8-c9570e4aca-79920eb634.zip b/.yarn/cache/micromatch-npm-4.0.8-c9570e4aca-79920eb634.zip new file mode 100644 index 000000000..00406c860 Binary files /dev/null and b/.yarn/cache/micromatch-npm-4.0.8-c9570e4aca-79920eb634.zip differ diff --git a/.yarn/cache/node-gyp-build-npm-4.8.1-828de76ddb-fe6e95da6f.zip b/.yarn/cache/node-gyp-build-npm-4.8.1-828de76ddb-fe6e95da6f.zip deleted file mode 100644 index 128a68b3d..000000000 Binary files a/.yarn/cache/node-gyp-build-npm-4.8.1-828de76ddb-fe6e95da6f.zip and /dev/null differ diff --git a/.yarn/cache/node-gyp-build-npm-4.8.2-892437a2f1-1a57bba8c4.zip b/.yarn/cache/node-gyp-build-npm-4.8.2-892437a2f1-1a57bba8c4.zip new file mode 100644 index 000000000..14cf350df Binary files /dev/null and b/.yarn/cache/node-gyp-build-npm-4.8.2-892437a2f1-1a57bba8c4.zip differ diff --git a/.yarn/cache/picocolors-npm-1.0.1-39442f3da8-fa68166d1f.zip b/.yarn/cache/picocolors-npm-1.0.1-39442f3da8-fa68166d1f.zip deleted file mode 100644 index 21041b39e..000000000 Binary files a/.yarn/cache/picocolors-npm-1.0.1-39442f3da8-fa68166d1f.zip and /dev/null differ diff --git a/.yarn/cache/picocolors-npm-1.1.0-ea12a640bd-a64d653d3a.zip b/.yarn/cache/picocolors-npm-1.1.0-ea12a640bd-a64d653d3a.zip new file mode 100644 index 000000000..e22ec9f18 Binary files /dev/null and b/.yarn/cache/picocolors-npm-1.1.0-ea12a640bd-a64d653d3a.zip differ diff --git a/.yarn/cache/postcss-npm-8.4.41-1607021b28-f865894929.zip b/.yarn/cache/postcss-npm-8.4.41-1607021b28-f865894929.zip deleted file mode 100644 index 7da89509b..000000000 Binary files a/.yarn/cache/postcss-npm-8.4.41-1607021b28-f865894929.zip and /dev/null differ diff --git a/.yarn/cache/postcss-npm-8.4.44-2d4d96a81a-64d9ce7825.zip b/.yarn/cache/postcss-npm-8.4.44-2d4d96a81a-64d9ce7825.zip new file mode 100644 index 000000000..fd6106beb Binary files /dev/null and b/.yarn/cache/postcss-npm-8.4.44-2d4d96a81a-64d9ce7825.zip differ diff --git a/.yarn/cache/postcss-selector-parser-npm-6.1.1-ba452aaaa9-1c6a5adfc3.zip b/.yarn/cache/postcss-selector-parser-npm-6.1.2-46a8e03b00-ce9440fc42.zip similarity index 70% rename from .yarn/cache/postcss-selector-parser-npm-6.1.1-ba452aaaa9-1c6a5adfc3.zip rename to .yarn/cache/postcss-selector-parser-npm-6.1.2-46a8e03b00-ce9440fc42.zip index 27d9f6cd4..c4db5e617 100644 Binary files a/.yarn/cache/postcss-selector-parser-npm-6.1.1-ba452aaaa9-1c6a5adfc3.zip and b/.yarn/cache/postcss-selector-parser-npm-6.1.2-46a8e03b00-ce9440fc42.zip differ diff --git a/.yarn/cache/spdx-license-ids-npm-3.0.18-08d695a78d-457825df5d.zip b/.yarn/cache/spdx-license-ids-npm-3.0.18-08d695a78d-457825df5d.zip deleted file mode 100644 index fc94e3adb..000000000 Binary files a/.yarn/cache/spdx-license-ids-npm-3.0.18-08d695a78d-457825df5d.zip and /dev/null differ diff --git a/.yarn/cache/spdx-license-ids-npm-3.0.20-f08e1eabc0-0c57750bed.zip b/.yarn/cache/spdx-license-ids-npm-3.0.20-f08e1eabc0-0c57750bed.zip new file mode 100644 index 000000000..03fea96d4 Binary files /dev/null and b/.yarn/cache/spdx-license-ids-npm-3.0.20-f08e1eabc0-0c57750bed.zip differ diff --git a/.yarn/cache/terser-npm-5.31.4-0a3da0a7b6-c134d2e964.zip b/.yarn/cache/terser-npm-5.31.4-0a3da0a7b6-c134d2e964.zip deleted file mode 100644 index 047e95705..000000000 Binary files a/.yarn/cache/terser-npm-5.31.4-0a3da0a7b6-c134d2e964.zip and /dev/null differ diff --git a/.yarn/cache/terser-npm-5.31.6-535b99d333-60d3faf39c.zip b/.yarn/cache/terser-npm-5.31.6-535b99d333-60d3faf39c.zip new file mode 100644 index 000000000..ed0c8f44f Binary files /dev/null and b/.yarn/cache/terser-npm-5.31.6-535b99d333-60d3faf39c.zip differ diff --git a/.yarn/cache/tslib-npm-2.6.3-0fd136b3be-74fce0e100.zip b/.yarn/cache/tslib-npm-2.6.3-0fd136b3be-74fce0e100.zip deleted file mode 100644 index 78a637270..000000000 Binary files a/.yarn/cache/tslib-npm-2.6.3-0fd136b3be-74fce0e100.zip and /dev/null differ diff --git a/.yarn/cache/tslib-npm-2.7.0-21668f5c21-1606d5c89f.zip b/.yarn/cache/tslib-npm-2.7.0-21668f5c21-1606d5c89f.zip new file mode 100644 index 000000000..aa76e5e15 Binary files /dev/null and b/.yarn/cache/tslib-npm-2.7.0-21668f5c21-1606d5c89f.zip differ diff --git a/.yarn/cache/uglify-js-npm-3.19.1-43e61650b6-c71e455b0a.zip b/.yarn/cache/uglify-js-npm-3.19.3-d73835bac2-7ed6272fba.zip similarity index 50% rename from .yarn/cache/uglify-js-npm-3.19.1-43e61650b6-c71e455b0a.zip rename to .yarn/cache/uglify-js-npm-3.19.3-d73835bac2-7ed6272fba.zip index 0a1947e7e..ca34414c3 100644 Binary files a/.yarn/cache/uglify-js-npm-3.19.1-43e61650b6-c71e455b0a.zip and b/.yarn/cache/uglify-js-npm-3.19.3-d73835bac2-7ed6272fba.zip differ diff --git a/.yarn/cache/undici-types-npm-6.13.0-3ce6ceead9-9d0ef6bf58.zip b/.yarn/cache/undici-types-npm-6.13.0-3ce6ceead9-9d0ef6bf58.zip deleted file mode 100644 index 264e951f8..000000000 Binary files a/.yarn/cache/undici-types-npm-6.13.0-3ce6ceead9-9d0ef6bf58.zip and /dev/null differ diff --git a/.yarn/cache/undici-types-npm-6.19.8-9f12285b7a-de51f1b447.zip b/.yarn/cache/undici-types-npm-6.19.8-9f12285b7a-de51f1b447.zip new file mode 100644 index 000000000..432bfb3b6 Binary files /dev/null and b/.yarn/cache/undici-types-npm-6.19.8-9f12285b7a-de51f1b447.zip differ diff --git a/.yarn/cache/watchpack-npm-2.4.1-23f13203b4-5b01793486.zip b/.yarn/cache/watchpack-npm-2.4.2-3e587d5d5b-92d9d52ce3.zip similarity index 76% rename from .yarn/cache/watchpack-npm-2.4.1-23f13203b4-5b01793486.zip rename to .yarn/cache/watchpack-npm-2.4.2-3e587d5d5b-92d9d52ce3.zip index ab51c127c..94b18154c 100644 Binary files a/.yarn/cache/watchpack-npm-2.4.1-23f13203b4-5b01793486.zip and b/.yarn/cache/watchpack-npm-2.4.2-3e587d5d5b-92d9d52ce3.zip differ diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz index d7435ab14..1f8c0b75a 100644 Binary files a/.yarn/install-state.gz and b/.yarn/install-state.gz differ diff --git a/package.json b/package.json index 2427e6ebc..5df55a75f 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,6 @@ "@nguniversal/express-engine": "16.2.0", "@ngx-translate/core": "15.0.0", "@ngx-translate/http-loader": "8.0.0", - "@stomp/stompjs": "7.0.0", "bootstrap": "~4.6.2", "d3": "7.8.5", "express": "4.18.2", diff --git a/server.ts b/server.ts index 50ce45379..c0c9e47d8 100644 --- a/server.ts +++ b/server.ts @@ -25,7 +25,6 @@ const BASE_HREF = process.env.BASE_HREF || '/'; const BROKER_URL = process.env.BROKER_URL || 'ws://localhost:9000'; const SERVICE_URL = process.env.SERVICE_URL || 'http://localhost:9000'; -const SSR_SERVICE_URL = process.env.SSR_SERVICE_URL || 'http://127.0.0.1:9000'; const UI_URL = process.env.UI_URL || 'http://localhost:4200'; const EMBED_URL = process.env.EMBED_URL || 'http://localhost:4201'; @@ -37,7 +36,6 @@ const COLLECT_SEARCH_STATS = process.env.COLLECT_SEARCH_STATS === 'true'; const serverAppConfig = (appConfig: AppConfig): AppConfig => { return { ...appConfig, - serviceUrl: SSR_SERVICE_URL }; } diff --git a/src/app/+data-and-analytics/profile-summaries-export/profile-summaries-export.component.spec.ts b/src/app/+data-and-analytics/profile-summaries-export/profile-summaries-export.component.spec.ts index c88ba84c7..87812e1ac 100644 --- a/src/app/+data-and-analytics/profile-summaries-export/profile-summaries-export.component.spec.ts +++ b/src/app/+data-and-analytics/profile-summaries-export/profile-summaries-export.component.spec.ts @@ -7,6 +7,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { testAppConfig } from '../../../test.config'; import { getRequest } from '../../app.browser.module'; +import { APP_CONFIG } from '../../app.config'; import { Layout } from '../../core/model/view'; import { ContainerType } from '../../core/model/view/data-and-analytics-view'; import { Side } from '../../core/model/view/display-view'; @@ -39,6 +40,7 @@ describe('ProfileSummariesExportComponent', () => { ], providers: [ { provide: REQUEST, useFactory: getRequest }, + { provide: APP_CONFIG, useValue: testAppConfig }, RestService ] }).compileComponents(); diff --git a/src/app/+directory/directory.component.ts b/src/app/+directory/directory.component.ts index 44c04119e..25eb6062a 100644 --- a/src/app/+directory/directory.component.ts +++ b/src/app/+directory/directory.component.ts @@ -82,7 +82,7 @@ export class DirectoryComponent implements OnDestroy, OnInit { } public isActive(directoryView: DirectoryView, params: Params, option: string): boolean { - const queryParams: Params = Object.assign({}, params); + const queryParams: Params = { ...params }; if (hasFilter(queryParams.filters, directoryView.index.field)) { return queryParams[`${directoryView.index.field}.filter`] === option; } @@ -114,7 +114,7 @@ export class DirectoryComponent implements OnDestroy, OnInit { } public getDirectoryExportUrl(params: Params, directoryView: DirectoryView): string { - const queryParams: Params = Object.assign({}, params); + const queryParams: Params = { ...params }; queryParams.facets = null; queryParams.collection = null; addExportToQueryParams(queryParams, directoryView); @@ -124,13 +124,13 @@ export class DirectoryComponent implements OnDestroy, OnInit { } public getDirectoryQueryParamsRemovingFilter(params: Params, filterToRemove: Filter): Params { - const queryParams: Params = Object.assign({}, params); + const queryParams: Params = { ...params }; removeFilterFromQueryParams(queryParams, filterToRemove); return queryParams; } public getDirectoryQueryParamsResetting(params: Params, directoryView: DirectoryView): Params { - const queryParams: Params = Object.assign({}, params); + const queryParams: Params = { ...params }; if (hasFilter(queryParams.filters, directoryView.index.field)) { const filters = queryParams.filters.split(',') .map((field) => field.trim()) @@ -147,13 +147,13 @@ export class DirectoryComponent implements OnDestroy, OnInit { } public getDirectoryQueryParamsClearingFilters(params: Params, discoveryView: DiscoveryView): Params { - const queryParams: Params = Object.assign({}, params); + const queryParams: Params = { ...params }; resetFiltersInQueryParams(queryParams, discoveryView); return queryParams; } public getDirectoryQueryParamsWithOption(params: Params, directoryView: DirectoryView, option: string): Params { - const queryParams: Params = Object.assign({}, params); + const queryParams: Params = { ...params }; queryParams.page = 1; if (option) { queryParams[`${directoryView.index.field}.filter`] = option; diff --git a/src/app/+discovery/discovery.component.ts b/src/app/+discovery/discovery.component.ts index 620b1411b..46c16cac1 100644 --- a/src/app/+discovery/discovery.component.ts +++ b/src/app/+discovery/discovery.component.ts @@ -118,7 +118,7 @@ export class DiscoveryComponent implements OnDestroy, OnInit { } public getDiscoveryExportUrl(params: Params, discoveryView: DiscoveryView): string { - const queryParams: Params = Object.assign({}, params); + const queryParams: Params = { ...params }; queryParams.facets = null; queryParams.collection = null; addExportToQueryParams(queryParams, discoveryView); @@ -128,13 +128,13 @@ export class DiscoveryComponent implements OnDestroy, OnInit { } public getDiscoveryQueryParamsRemovingFilter(params: Params, filterToRemove: Filter): Params { - const queryParams: Params = Object.assign({}, params); + const queryParams: Params = { ...params }; removeFilterFromQueryParams(queryParams, filterToRemove); return queryParams; } public getDiscoveryQueryParamsClearingFilters(params: Params, discoveryView: DiscoveryView): Params { - const queryParams: Params = Object.assign({}, params); + const queryParams: Params = { ...params }; resetFiltersInQueryParams(queryParams, discoveryView); return queryParams; } diff --git a/src/app/app.component.ts b/src/app/app.component.ts index ebf99049c..2a3f1639f 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -29,7 +29,10 @@ export class AppComponent implements OnInit { private isPlatformBrowser: boolean; - constructor(@Inject(PLATFORM_ID) platformId: string, private store: Store) { + constructor( + @Inject(PLATFORM_ID) platformId: string, + private store: Store + ) { initializeTemplateHelpers(environment.formalize); this.isPlatformBrowser = isPlatformBrowser(platformId); } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index dd28747d2..756065fe5 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -24,6 +24,7 @@ export class I18nTranslateState { } export const I18N_TRANSLATE_STATE = makeStateKey('I18N_TRANSLATE_STATE'); +export const STORE_TRANSLATE_STATE = makeStateKey('STORE_TRANSLATE_STATE'); const getBaseHref = (document: Document, appConfig: AppConfig): string => { const baseTag = document.querySelector('head > base'); @@ -47,10 +48,10 @@ const createWithCredentialsInterceptor = (): HttpInterceptor => { ], imports: [ BrowserModule, - TransferHttpCacheModule, BrowserAnimationsModule, AppRoutingModule, HttpClientModule, + TransferHttpCacheModule, TranslateModule.forRoot(), CoreModule.forRoot(), NgbModule, diff --git a/src/app/app.server.module.ts b/src/app/app.server.module.ts index adcda5464..bb65f93cb 100644 --- a/src/app/app.server.module.ts +++ b/src/app/app.server.module.ts @@ -1,13 +1,14 @@ import { APP_BASE_HREF, DOCUMENT } from '@angular/common'; -import { NgModule, TransferState } from '@angular/core'; +import { APP_BOOTSTRAP_LISTENER, NgModule, TransferState } from '@angular/core'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { ServerModule } from '@angular/platform-server'; import { MissingTranslationHandler, TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { readFileSync } from 'fs'; -import { Observable, of } from 'rxjs'; +import { Observable, of, take } from 'rxjs'; +import { Store } from '@ngrx/store'; import { AppComponent } from './app.component'; -import { AppModule, I18N_TRANSLATE_STATE, I18nTranslateState } from './app.module'; +import { AppModule, I18N_TRANSLATE_STATE, I18nTranslateState, STORE_TRANSLATE_STATE } from './app.module'; import { ComputedStyleLoader } from './core/computed-style-loader'; import { CustomMissingTranslationHandler } from './core/handler/custom-missing-translation.handler'; @@ -72,6 +73,18 @@ const createUniversalStyleLoader = (document: Document, baseHref: string): Compu useFactory: createUniversalStyleLoader, deps: [DOCUMENT, APP_BASE_HREF], }, + { + provide: APP_BOOTSTRAP_LISTENER, + useFactory: (store: Store, transferState: TransferState) => { + return () => { + store.pipe(take(1)).subscribe(state => { + transferState.set(STORE_TRANSLATE_STATE, JSON.stringify(state)); + }); + }; + }, + deps: [Store, TransferState], + multi: true, + } ], }) export class AppServerModule { } diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 1122c0a78..8b35cb955 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -10,7 +10,6 @@ import { DialogService } from './service/dialog.service'; import { MetadataService } from './service/metadata.service'; import { RestService } from './service/rest.service'; import { StatsService } from './service/stats.service'; -import { StompService } from './service/stomp.service'; import { ThemeService } from './service/theme.service'; const MODULES = [ @@ -27,7 +26,6 @@ const PROVIDERS = [ MetadataService, RestService, StatsService, - StompService, ThemeService, ThemeRepo, UserRepo diff --git a/src/app/core/guard/auth.guard.ts b/src/app/core/guard/auth.guard.ts index 926785842..d404239ff 100644 --- a/src/app/core/guard/auth.guard.ts +++ b/src/app/core/guard/auth.guard.ts @@ -30,9 +30,9 @@ export class AuthGuard { public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { const roles = route.data.roles; return this.requiresAuthorization(roles).pipe( - switchMap((authorize: boolean) => { - return authorize ? this.isAuthorized(state.url, roles) : this.isAuthenticated(state.url); - }) + switchMap((authorize: boolean) => authorize + ? this.isAuthorized(state.url, roles) + : this.isAuthenticated(state.url)) ); } @@ -48,10 +48,7 @@ export class AuthGuard { map((user: User) => { const authorized = user ? roles.indexOf(Role[user.role]) >= 0 : false; if (!authorized) { - this.store.dispatch(new fromRouter.Go({ path: ['/'] })); - if (isPlatformBrowser(this.platformId)) { - this.store.dispatch(this.alert.unsubscribeFailureAlert()); - } + this.store.dispatch(new fromRouter.Link({ url: '/' })); } return authorized; })) : scheduled([false], asapScheduler))); @@ -62,11 +59,9 @@ export class AuthGuard { select(selectIsAuthenticated), map((authenticated: boolean) => { if (!authenticated) { - this.store.dispatch(new fromRouter.Go({ path: ['/'] })); + this.store.dispatch(new fromRouter.Link({ url: '/' })); + this.store.dispatch(new fromAuth.SetLoginRedirectAction({ url })); if (isPlatformBrowser(this.platformId)) { - this.store.dispatch(new fromAuth.SetLoginRedirectAction({ - navigation: { path: [url] }, - })); this.store.dispatch(this.dialog.loginDialog()); this.store.dispatch(this.alert.forbiddenAlert()); } diff --git a/src/app/core/service/alert.service.ts b/src/app/core/service/alert.service.ts index 531f4401c..e3aacdc9c 100644 --- a/src/app/core/service/alert.service.ts +++ b/src/app/core/service/alert.service.ts @@ -5,6 +5,7 @@ import { TranslateService } from '@ngx-translate/core'; import { AlertLocation, AlertType } from '../model/alert'; import * as fromAlert from '../store/alert/alert.actions'; +import * as fromAuth from '../store/auth/auth.actions'; import * as fromSdr from '../store/sdr/sdr.actions'; @Injectable({ @@ -77,7 +78,7 @@ export class AlertService { } public unauthorizedAlert(): fromAlert.AlertActions { - return this.alert(AlertLocation.MAIN, AlertType.DANGER, this.translate.instant('SHARED.ALERT.UNAUTHORIZED'), true, 15000); + return this.alert(AlertLocation.DIALOG, AlertType.WARNING, this.translate.instant('SHARED.ALERT.UNAUTHORIZED'), false); } public forbiddenAlert(): fromAlert.AlertActions { @@ -85,107 +86,115 @@ export class AlertService { } public connectFailureAlert(): fromAlert.AlertActions { - return this.alert(AlertLocation.MAIN, AlertType.DANGER, this.translate.instant('SHARED.ALERT.FAILED'), true, 15000); + return this.alert(AlertLocation.MAIN, AlertType.DANGER, this.translate.instant('SHARED.ALERT.CONNECT_FAILED'), true, 15000); } public disconnectFailureAlert(): fromAlert.AlertActions { - return this.alert(AlertLocation.MAIN, AlertType.DANGER, this.translate.instant('SHARED.ALERT.FAILED'), true, 15000); + return this.alert(AlertLocation.MAIN, AlertType.DANGER, this.translate.instant('SHARED.ALERT.DISCONNECT_FAILED'), true, 15000); } public unsubscribeFailureAlert(): fromAlert.AlertActions { - return this.alert(AlertLocation.MAIN, AlertType.DANGER, this.translate.instant('SHARED.ALERT.FAILED'), true, 15000); + return this.alert(AlertLocation.MAIN, AlertType.DANGER, this.translate.instant('SHARED.ALERT.UNSUBSCRIBE_FAILED'), true, 15000); } - public loadActiveThemeFailureAlert(payload: { response: any }): fromAlert.AlertActions { - return this.alert(AlertLocation.MAIN, AlertType.DANGER, `(${payload.response.status}) ${payload.response.message}`, true, 15000); + public loadActiveThemeFailureAlert(payload: { response: any }): fromAuth.AuthActions | fromAlert.AlertActions { + return this.handleUnauthorized(AlertLocation.MAIN, AlertType.DANGER, payload.response); } public applyActiveThemeFailureAlert(payload: { error: string }): fromAlert.AlertActions { return this.alert(AlertLocation.MAIN, AlertType.DANGER, payload.error, true, 15000); } - public getAllFailureAlert(payload: { response: any }): fromAlert.AlertActions { - return this.alert(AlertLocation.MAIN, AlertType.DANGER, `(${payload.response.status}) ${payload.response.message}`, true, 15000); + public getAllFailureAlert(payload: { response: any }): fromAuth.AuthActions | fromAlert.AlertActions { + return this.handleUnauthorized(AlertLocation.MAIN, AlertType.DANGER, payload.response); } - public getOneFailureAlert(payload: { response: any }): fromAlert.AlertActions { - return this.alert(AlertLocation.MAIN, AlertType.DANGER, `(${payload.response.status}) ${payload.response.message}`, true, 15000); + public getOneFailureAlert(payload: { response: any }): fromAuth.AuthActions | fromAlert.AlertActions { + return this.handleUnauthorized(AlertLocation.MAIN, AlertType.DANGER, payload.response); } - public getNetworkFailureAlert(payload: { response: any }): fromAlert.AlertActions { - return this.alert(AlertLocation.MAIN, AlertType.DANGER, `(${payload.response.status}) ${payload.response.message}`, true, 15000); + public getNetworkFailureAlert(payload: { response: any }): fromAuth.AuthActions | fromAlert.AlertActions { + return this.handleUnauthorized(AlertLocation.MAIN, AlertType.DANGER, payload.response); } - public getAcademicAgeFailureAlert(payload: { response: any }): fromAlert.AlertActions { - return this.alert(AlertLocation.MAIN, AlertType.DANGER, `(${payload.response.status}) ${payload.response.message}`, true, 15000); + public getAcademicAgeFailureAlert(payload: { response: any }): fromAuth.AuthActions | fromAlert.AlertActions { + return this.handleUnauthorized(AlertLocation.MAIN, AlertType.DANGER, payload.response); } - public getQuantityDistributionFailureAlert(payload: { response: any }): fromAlert.AlertActions { - return this.alert(AlertLocation.MAIN, AlertType.DANGER, `(${payload.response.status}) ${payload.response.message}`, true, 15000); + public getQuantityDistributionFailureAlert(payload: { response: any }): fromAuth.AuthActions | fromAlert.AlertActions { + return this.handleUnauthorized(AlertLocation.MAIN, AlertType.DANGER, payload.response); } - public getCoInvestigatorNetworkFailureAlert(payload: { response: any }): fromAlert.AlertActions { - return this.alert(AlertLocation.MAIN, AlertType.DANGER, `(${payload.response.status}) ${payload.response.message}`, true, 15000); + public getCoInvestigatorNetworkFailureAlert(payload: { response: any }): fromAuth.AuthActions | fromAlert.AlertActions { + return this.handleUnauthorized(AlertLocation.MAIN, AlertType.DANGER, payload.response); } - public findByIdInFailureAlert(payload: { response: any }): fromAlert.AlertActions { - return this.alert(AlertLocation.MAIN, AlertType.DANGER, `(${payload.response.status}) ${payload.response.message}`, true, 15000); + public findByIdInFailureAlert(payload: { response: any }): fromAuth.AuthActions | fromAlert.AlertActions { + return this.handleUnauthorized(AlertLocation.MAIN, AlertType.DANGER, payload.response); } - public findByTypesInFailureAlert(payload: { response: any }): fromAlert.AlertActions { - return this.alert(AlertLocation.MAIN, AlertType.DANGER, `(${payload.response.status}) ${payload.response.message}`, true, 15000); + public findByTypesInFailureAlert(payload: { response: any }): fromAuth.AuthActions | fromAlert.AlertActions { + return this.handleUnauthorized(AlertLocation.MAIN, AlertType.DANGER, payload.response); } - public fetchLazyRefernceFailureAlert(payload: { response: any }): fromAlert.AlertActions { - return this.alert(AlertLocation.MAIN, AlertType.DANGER, `(${payload.response.status}) ${payload.response.message}`, true, 15000); + public fetchLazyRefernceFailureAlert(payload: { response: any }): fromAuth.AuthActions | fromAlert.AlertActions { + return this.handleUnauthorized(AlertLocation.MAIN, AlertType.DANGER, payload.response); } - public pageFailureAlert(payload: { response: any }): fromAlert.AlertActions { - return this.alert(AlertLocation.MAIN, AlertType.DANGER, `(${payload.response.status}) ${payload.response.message}`, true, 15000); + public pageFailureAlert(payload: { response: any }): fromAuth.AuthActions | fromAlert.AlertActions { + return this.handleUnauthorized(AlertLocation.MAIN, AlertType.DANGER, payload.response); } - public countFailureAlert(payload: { response: any }): fromAlert.AlertActions { - return this.alert(AlertLocation.MAIN, AlertType.DANGER, `(${payload.response.status}) ${payload.response.message}`, true, 15000); + public countFailureAlert(payload: { response: any }): fromAuth.AuthActions | fromAlert.AlertActions { + return this.handleUnauthorized(AlertLocation.MAIN, AlertType.DANGER, payload.response); } - public recentlyUpdatedFailureAlert(payload: { response: any }): fromAlert.AlertActions { - return this.alert(AlertLocation.MAIN, AlertType.DANGER, `(${payload.response.status}) ${payload.response.message}`, true, 15000); + public recentlyUpdatedFailureAlert(payload: { response: any }): fromAuth.AuthActions | fromAlert.AlertActions { + return this.handleUnauthorized(AlertLocation.MAIN, AlertType.DANGER, payload.response); } - public searchFailureAlert(payload: { response: any }): fromAlert.AlertActions { - return this.alert(AlertLocation.MAIN, AlertType.DANGER, `(${payload.response.status}) ${payload.response.message}`, true, 15000); + public searchFailureAlert(payload: { response: any }): fromAuth.AuthActions | fromAlert.AlertActions { + return this.handleUnauthorized(AlertLocation.MAIN, AlertType.DANGER, payload.response); } public postSuccessAlert(action: fromSdr.PostResourceSuccessAction): fromAlert.AlertActions { return this.alert(AlertLocation.MAIN, AlertType.SUCCESS, this.translate.instant('SHARED.ALERT.POST_SUCCESS'), true, 10000); } - public postFailureAlert(payload: { response: any }): fromAlert.AlertActions { - return this.alert(AlertLocation.MAIN, AlertType.DANGER, `(${payload.response.status}) ${payload.response.message}`, true, 15000); + public postFailureAlert(payload: { response: any }): fromAuth.AuthActions | fromAlert.AlertActions { + return this.handleUnauthorized(AlertLocation.MAIN, AlertType.DANGER, payload.response); } public putSuccessAlert(action: fromSdr.PutResourceSuccessAction): fromAlert.AlertActions { return this.alert(AlertLocation.MAIN, AlertType.SUCCESS, this.translate.instant('SHARED.ALERT.PUT_SUCCESS'), true, 10000); } - public putFailureAlert(payload: { response: any }): fromAlert.AlertActions { - return this.alert(AlertLocation.MAIN, AlertType.DANGER, `(${payload.response.status}) ${payload.response.message}`, true, 15000); + public putFailureAlert(payload: { response: any }): fromAuth.AuthActions | fromAlert.AlertActions { + return this.handleUnauthorized(AlertLocation.MAIN, AlertType.DANGER, payload.response); } public patchSuccessAlert(action: fromSdr.PatchResourceSuccessAction): fromAlert.AlertActions { return this.alert(AlertLocation.MAIN, AlertType.SUCCESS, this.translate.instant('SHARED.ALERT.PATCH_SUCCESS'), true, 10000); } - public patchFailureAlert(payload: { response: any }): fromAlert.AlertActions { - return this.alert(AlertLocation.MAIN, AlertType.DANGER, `(${payload.response.status}) ${payload.response.message}`, true, 15000); + public patchFailureAlert(payload: { response: any }): fromAuth.AuthActions | fromAlert.AlertActions { + return this.handleUnauthorized(AlertLocation.MAIN, AlertType.DANGER, payload.response); } public deleteSuccessAlert(action: fromSdr.DeleteResourceSuccessAction): fromAlert.AlertActions { return this.alert(AlertLocation.MAIN, AlertType.SUCCESS, this.translate.instant('SHARED.ALERT.DELETE_SUCCESS'), true, 10000); } - public deleteFailureAlert(payload: { response: any }): fromAlert.AlertActions { - return this.alert(AlertLocation.MAIN, AlertType.DANGER, `(${payload.response.status}) ${payload.response.message}`, true, 15000); + public deleteFailureAlert(payload: { response: any }): fromAuth.AuthActions | fromAlert.AlertActions { + return this.handleUnauthorized(AlertLocation.MAIN, AlertType.DANGER, payload.response); + } + + public handleUnauthorized(location: AlertLocation, type: AlertType, response: any): fromAuth.AuthActions | fromAlert.AlertActions { + if (response.status === 401) { + return new fromAuth.LogoutAction({ reauthenticate: true }); + } + + return this.alert(location, type, `(${response.status}) ${response.message}`, true, 15000); } public alert(location: AlertLocation, type: AlertType, message: string, dismissible: boolean, timer?: number): fromAlert.AlertActions { diff --git a/src/app/core/service/auth.service.ts b/src/app/core/service/auth.service.ts index 04b9db78c..7ba02fd41 100644 --- a/src/app/core/service/auth.service.ts +++ b/src/app/core/service/auth.service.ts @@ -12,7 +12,10 @@ import { RestService } from './rest.service'; }) export class AuthService { - constructor(@Inject(APP_CONFIG) private appConfig: AppConfig, private restService: RestService) {} + constructor( + @Inject(APP_CONFIG) private appConfig: AppConfig, + private restService: RestService + ) { } public hasSession(): boolean { return this.restService.hasSession(); diff --git a/src/app/core/service/rest.service.spec.ts b/src/app/core/service/rest.service.spec.ts index 4b5a01125..e21fc13cc 100644 --- a/src/app/core/service/rest.service.spec.ts +++ b/src/app/core/service/rest.service.spec.ts @@ -2,7 +2,9 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'; import { inject, TestBed } from '@angular/core/testing'; import { REQUEST } from '@nguniversal/express-engine/tokens'; +import { testAppConfig } from '../../../test.config'; import { getRequest } from '../../app.browser.module'; +import { APP_CONFIG } from '../../app.config'; import { RestService } from './rest.service'; describe('RestService', () => { @@ -11,6 +13,7 @@ describe('RestService', () => { imports: [HttpClientTestingModule], providers: [ { provide: REQUEST, useFactory: getRequest }, + { provide: APP_CONFIG, useValue: testAppConfig }, RestService ], }); diff --git a/src/app/core/service/rest.service.ts b/src/app/core/service/rest.service.ts index 96e439db4..98072aa2a 100644 --- a/src/app/core/service/rest.service.ts +++ b/src/app/core/service/rest.service.ts @@ -1,19 +1,21 @@ -import { isPlatformBrowser, isPlatformServer } from '@angular/common'; -import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { isPlatformBrowser } from '@angular/common'; +import { HttpClient } from '@angular/common/http'; import { Inject, Injectable, PLATFORM_ID } from '@angular/core'; import { REQUEST } from '@nguniversal/express-engine/tokens'; -import { Observable, of } from 'rxjs'; -import { map, tap } from 'rxjs/operators'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; @Injectable({ providedIn: 'root', }) export class RestService { - private cache: Map; + constructor( + @Inject(PLATFORM_ID) private platformId: string, + @Inject(REQUEST) private request: any, + private http: HttpClient, + ) { - constructor(private http: HttpClient, @Inject(PLATFORM_ID) private platformId: string, @Inject(REQUEST) private request: any) { - this.cache = new Map(); } public hasSession(): boolean { @@ -29,20 +31,10 @@ export class RestService { } public get(url: string, options: any = {}, cache = true): Observable { - const request = JSON.stringify({ url, options }); - if (this.cache.has(request)) { - return of(this.cache.get(request)); - } // tslint:disable-next-line:no-shadowed-variable return this.processRequest(url, options, (url: string, options: any): any => { return this.http.get(url, options); - }).pipe( - tap((response: T) => { - if (cache) { - this.cache.set(request, response); - } - }) - ); + }); } public post(url: string, body: any = {}, options: any = {}): Observable { @@ -74,7 +66,6 @@ export class RestService { } private processRequest(url: string, options: any, callback: (url: string, options: any) => Observable) { - this.preProcessOptions(options); return callback(url, options).pipe( map((response: T) => { return response; @@ -83,7 +74,6 @@ export class RestService { } private processRequestWithData(url: string, body: any, options: any, callback: (url: string, body: any, options: any) => Observable) { - this.preProcessOptions(options); return callback(url, body, options).pipe( map((response: T) => { return response; @@ -91,22 +81,4 @@ export class RestService { ); } - private preProcessOptions(options: any): void { - if (this.useSession()) { - if (!options.headers) { - options.headers = new HttpHeaders({ - // tslint:disable-next-line: no-string-literal - cookie: this.request.headers['cookie'], - }); - } else { - // tslint:disable-next-line: no-string-literal - options.headers = (options.headers as HttpHeaders).set('cookie', this.request.headers['cookie']); - } - } - } - - private useSession(): boolean { - return isPlatformServer(this.platformId) && this.hasSession(); - } - } diff --git a/src/app/core/service/stomp.service.spec.ts b/src/app/core/service/stomp.service.spec.ts deleted file mode 100644 index feff6f8de..000000000 --- a/src/app/core/service/stomp.service.spec.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { inject, TestBed } from '@angular/core/testing'; - -import { testAppConfig } from '../../../test.config'; -import { APP_CONFIG } from '../../app.config'; -import { StompService } from './stomp.service'; - -describe('StompService', () => { - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [StompService, { provide: APP_CONFIG, useValue: testAppConfig }], - }); - }); - - it('should be created', inject([StompService], (service: StompService) => { - expect(service).toBeTruthy(); - })); -}); diff --git a/src/app/core/service/stomp.service.ts b/src/app/core/service/stomp.service.ts deleted file mode 100644 index 1c0f31339..000000000 --- a/src/app/core/service/stomp.service.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { isPlatformServer } from '@angular/common'; -import { Inject, Injectable, PLATFORM_ID } from '@angular/core'; -import { Client, IFrame, StompConfig, StompSubscription } from '@stomp/stompjs'; -import { Observable, Observer, asapScheduler, from, of, scheduled } from 'rxjs'; - -import { environment } from '../../../environments/environment'; -import { APP_CONFIG, AppConfig } from '../../app.config'; - -@Injectable({ - providedIn: 'root', -}) -export class StompService { - - private client: Client; - - constructor(@Inject(APP_CONFIG) private appConfig: AppConfig, @Inject(PLATFORM_ID) private platformId: string) { - - } - - public connect(): Observable { - if (isPlatformServer(this.platformId)) { - return scheduled([false], asapScheduler); - } - - this.client = new Client({ - brokerURL: `${this.appConfig.brokerUrl}/connect`, - onWebSocketError: (event: any) => { - console.error('onWebSocketError', event); - }, - onStompError: (frame: IFrame) => { - console.error('onStompError', frame); - }, - debug: (message: string) => { - if (environment.stompDebug) { - console.debug('debug', message); - } - }, - }); - - this.client.activate(); - - return new Observable((subscriber) => { - this.client.onConnect = (frame: IFrame) => { - subscriber.next(frame); - }; - }); - } - - public disconnect(): Observable { - if (isPlatformServer(this.platformId)) { - return scheduled([false], asapScheduler); - } - - return from(this.client.deactivate()); - } - - public subscribe(channel: string, callback: () => {}): Observable { - if (isPlatformServer(this.platformId)) { - return scheduled([false], asapScheduler); - } - - return of(this.client.subscribe(channel, callback)); - } - - public unsubscribe(id: string): Observable { - return scheduled([this.client.unsubscribe(id)], asapScheduler); - } - -} diff --git a/src/app/core/store/alert/alert.reducer.ts b/src/app/core/store/alert/alert.reducer.ts index f7e0f824f..8e361efa5 100644 --- a/src/app/core/store/alert/alert.reducer.ts +++ b/src/app/core/store/alert/alert.reducer.ts @@ -36,9 +36,6 @@ export function reducer(state = initialState, action: AlertActions): AlertState ...state, }; } - case AlertActionTypes.ADD_ALERT: - console.log(action.payload); - break; default: return state; } diff --git a/src/app/core/store/auth/auth.actions.ts b/src/app/core/store/auth/auth.actions.ts index cc3ebf825..db5a4a655 100644 --- a/src/app/core/store/auth/auth.actions.ts +++ b/src/app/core/store/auth/auth.actions.ts @@ -2,7 +2,6 @@ import { Action } from '@ngrx/store'; import { LoginRequest, RegistrationRequest } from '../../model/request'; import { User } from '../../model/user'; -import { RouterNavigation } from '../router/router.actions'; export enum AuthActionTypes { LOGIN = '[Auth] login', @@ -24,6 +23,7 @@ export enum AuthActionTypes { GET_USER_SUCCESS = '[Auth] success getting user', GET_USER_FAILURE = '[Auth] failed getting user', CHECK_SESSION = '[Auth] check session', + CLEAR_SESSION = '[Auth] clear session', SESSION_STATUS = '[Auth] session status', SET_LOGIN_REDIRECT = '[Auth] set login redirect', UNSET_LOGIN_REDIRECT = '[Auth] unset login redirect', @@ -91,11 +91,12 @@ export class CompleteRegistrationFailureAction implements Action { export class LogoutAction implements Action { readonly type = AuthActionTypes.LOGOUT; + constructor(public payload?: { reauthenticate: boolean }) { } } export class LogoutSuccessAction implements Action { readonly type = AuthActionTypes.LOGOUT_SUCCESS; - constructor(public payload: { message: string }) { } + constructor(public payload: { message: string, reauthenticate?: boolean }) { } } export class LogoutFailureAction implements Action { @@ -121,6 +122,10 @@ export class CheckSessionAction implements Action { readonly type = AuthActionTypes.CHECK_SESSION; } +export class ClearSessionAction implements Action { + readonly type = AuthActionTypes.CLEAR_SESSION; +} + export class SessionStatusAction implements Action { readonly type = AuthActionTypes.SESSION_STATUS; constructor(public payload: { authenticated: boolean }) { } @@ -128,7 +133,7 @@ export class SessionStatusAction implements Action { export class SetLoginRedirectAction implements Action { readonly type = AuthActionTypes.SET_LOGIN_REDIRECT; - constructor(public payload: { navigation: RouterNavigation }) { } + constructor(public payload: { url: string }) { } } export class UnsetLoginRedirectAction implements Action { @@ -155,6 +160,7 @@ export type AuthActions = | GetUserSuccessAction | GetUserFailureAction | CheckSessionAction + | ClearSessionAction | SessionStatusAction | SetLoginRedirectAction | UnsetLoginRedirectAction; diff --git a/src/app/core/store/auth/auth.effects.ts b/src/app/core/store/auth/auth.effects.ts index 0805bbc6b..5254f8c96 100644 --- a/src/app/core/store/auth/auth.effects.ts +++ b/src/app/core/store/auth/auth.effects.ts @@ -1,37 +1,39 @@ -import { Injectable } from '@angular/core'; +import { isPlatformBrowser } from '@angular/common'; +import { Inject, Injectable, PLATFORM_ID } from '@angular/core'; import { Actions, createEffect, ofType, OnInitEffects } from '@ngrx/effects'; -import { Action, select, Store } from '@ngrx/store'; -import { asapScheduler, combineLatest, scheduled } from 'rxjs'; -import { catchError, map, skipWhile, switchMap, take, withLatestFrom } from 'rxjs/operators'; +import { Action, Store } from '@ngrx/store'; +import { asapScheduler, scheduled } from 'rxjs'; +import { catchError, map, switchMap, withLatestFrom } from 'rxjs/operators'; import { AppState } from '../'; import { RegistrationStep } from '../../../shared/dialog/registration/registration.component'; import { LoginRequest, RegistrationRequest } from '../../model/request'; -import { Role, User } from '../../model/user'; +import { User } from '../../model/user'; import { AlertService } from '../../service/alert.service'; import { AuthService } from '../../service/auth.service'; import { DialogService } from '../../service/dialog.service'; -import { selectIsStompConnected } from '../stomp'; -import { selectLoginRedirect, selectUser } from './'; +import { selectRouterUrl } from '../router'; +import { selectLoginRedirect } from './'; import * as fromDialog from '../dialog/dialog.actions'; import * as fromRouter from '../router/router.actions'; import * as fromSdr from '../sdr/sdr.actions'; -import * as fromStomp from '../stomp/stomp.actions'; import * as fromAuth from './auth.actions'; @Injectable() export class AuthEffects implements OnInitEffects { - constructor(private actions: Actions, private store: Store, private alert: AlertService, private authService: AuthService, private dialog: DialogService) { + constructor( + @Inject(PLATFORM_ID) private platformId: string, + private actions: Actions, + private store: Store, + private alert: AlertService, + private authService: AuthService, + private dialog: DialogService + ) { } - reconnect = createEffect(() => this.actions.pipe( - ofType(fromAuth.AuthActionTypes.LOGIN_SUCCESS, fromAuth.AuthActionTypes.LOGOUT_SUCCESS), - map(() => new fromStomp.DisconnectAction({ reconnect: true })) - )); - login = createEffect(() => this.actions.pipe( ofType(fromAuth.AuthActionTypes.LOGIN), map((action: fromAuth.LoginAction) => action.payload), @@ -47,14 +49,15 @@ export class AuthEffects implements OnInitEffects { ofType(fromAuth.AuthActionTypes.LOGIN_SUCCESS), map((action: fromAuth.LoginSuccessAction) => action.payload), withLatestFrom(this.store.select(selectLoginRedirect)), - switchMap(([payload, redirect]) => { + switchMap(([payload, url]) => { const actions: Action[] = [ new fromAuth.GetUserSuccessAction({ user: payload.user }), new fromDialog.CloseDialogAction(), this.alert.loginSuccessAlert() ]; - if (redirect !== undefined) { - actions.push(new fromRouter.Go(redirect)); + if (url !== undefined) { + actions.push(new fromAuth.UnsetLoginRedirectAction()); + actions.push(new fromRouter.Link({ url })); } return actions; @@ -112,7 +115,7 @@ export class AuthEffects implements OnInitEffects { confirmRegistrationFailure = createEffect(() => this.actions.pipe( ofType(fromAuth.AuthActionTypes.CONFIRM_REGISTRATION_FAILURE), map((action: fromAuth.ConfirmRegistrationFailureAction) => action.payload), - switchMap((payload: { response: any }) => [new fromRouter.Go({ path: ['/'] }), this.alert.confirmRegistrationFailureAlert(payload)]) + switchMap((payload: { response: any }) => [new fromRouter.Link({ url: '/' }), this.alert.confirmRegistrationFailureAlert(payload)]) )); completeRegistration = createEffect(() => this.actions.pipe( @@ -131,7 +134,7 @@ export class AuthEffects implements OnInitEffects { map((action: fromAuth.CompleteRegistrationSuccessAction) => action.payload), switchMap(() => [ new fromDialog.CloseDialogAction(), - new fromRouter.Go({ path: ['/'] }), + new fromRouter.Link({ url: '/' }), this.alert.completeRegistrationSuccessAlert() ]) )); @@ -143,9 +146,10 @@ export class AuthEffects implements OnInitEffects { logout = createEffect(() => this.actions.pipe( ofType(fromAuth.AuthActionTypes.LOGOUT), - switchMap(() => + map((action: fromAuth.LoginAction) => action.payload), + switchMap((payload: any) => this.authService.logout().pipe( - map((response: any) => new fromAuth.LogoutSuccessAction({ message: response.message })), + map((response: any) => new fromAuth.LogoutSuccessAction({ message: response.message, ...payload })), catchError((response) => scheduled([new fromAuth.LogoutFailureAction({ response })], asapScheduler)) ) ) @@ -153,7 +157,24 @@ export class AuthEffects implements OnInitEffects { logoutSuccess = createEffect(() => this.actions.pipe( ofType(fromAuth.AuthActionTypes.LOGOUT_SUCCESS), - switchMap(() => [new fromSdr.ClearResourcesAction('Theme'), new fromSdr.ClearResourcesAction('User'), new fromRouter.Go({ path: ['/'] })]) + map((action: fromAuth.LogoutSuccessAction) => action.payload), + withLatestFrom(this.store.select(selectRouterUrl)), + switchMap(([payload, url]: any) => { + + const logoutActions: Action[] = [ + new fromSdr.ClearResourcesAction('Theme'), + new fromSdr.ClearResourcesAction('User'), + new fromRouter.Link({ url: '/' }) + ]; + + if (payload.reauthenticate) { + logoutActions.push(this.dialog.loginDialog()); + logoutActions.push(new fromAuth.SetLoginRedirectAction({ url })); + logoutActions.push(this.alert.unauthorizedAlert()); + } + + return logoutActions; + }) )); getUser = createEffect(() => this.actions.pipe( @@ -166,71 +187,30 @@ export class AuthEffects implements OnInitEffects { ) )); - getUserSuccess = createEffect(() => this.actions.pipe( - ofType(fromAuth.AuthActionTypes.GET_USER_SUCCESS), - switchMap(() => - combineLatest([ - this.store.pipe(select(selectUser), take(1)), - this.store.pipe( - select(selectIsStompConnected), - skipWhile((connected: boolean) => !connected), - take(1) - ), - ]) - ), - map( - ([user]) => - new fromStomp.SubscribeAction({ - channel: '/user/queue/users', - handle: (frame: any) => { - if (frame.command === 'MESSAGE') { - const body = JSON.parse(frame.body); - switch (body.action) { - case 'DELETE': - this.store.dispatch(new fromAuth.LogoutAction()); - this.store.dispatch(this.dialog.notificationDialog('Your account has been deleted!')); - break; - case 'UPDATE': - if (body.entity.enabled) { - this.store.dispatch(new fromAuth.GetUserSuccessAction({ user: body.entity })); - const roles = Object.keys(Role); - if (roles.indexOf(body.entity.role) < roles.indexOf(user.role)) { - // TODO: request new session to avoid logging out - this.store.dispatch(new fromAuth.LogoutAction()); - this.store.dispatch(this.dialog.notificationDialog('Your permissions have been reduced! Unfortunately, you must log in again.')); - } else if (roles.indexOf(body.entity.role) > roles.indexOf(user.role)) { - // TODO: request new session to avoid logging out - this.store.dispatch(new fromAuth.LogoutAction()); - this.store.dispatch(this.dialog.notificationDialog('Your permissions have been elevated! Unfortunately, you must log in again.')); - } - } else { - this.store.dispatch(new fromAuth.LogoutAction()); - this.store.dispatch(this.dialog.notificationDialog('Your account has been disabled!')); - } - break; - default: - } - } - }, - }) - ) - )); - getUserFailure = createEffect(() => this.actions.pipe( ofType(fromAuth.AuthActionTypes.GET_USER_FAILURE), - map(() => this.authService.clearSession()) + withLatestFrom(this.store.select(selectRouterUrl)), + map(([action, url]) => { + if (isPlatformBrowser(this.platformId)) { + this.store.dispatch(this.dialog.loginDialog()); + this.store.dispatch(new fromAuth.SetLoginRedirectAction({ url })); + this.store.dispatch(this.alert.unauthorizedAlert()); + } + }) ), { dispatch: false }); checkSession = createEffect(() => this.actions.pipe( ofType(fromAuth.AuthActionTypes.CHECK_SESSION), - map( - () => - new fromAuth.SessionStatusAction({ - authenticated: this.authService.hasSession(), - }) - ) + map(() => new fromAuth.SessionStatusAction({ + authenticated: this.authService.hasSession(), + })) )); + clearSession = createEffect(() => this.actions.pipe( + ofType(fromAuth.AuthActionTypes.CLEAR_SESSION), + map(() => this.authService.clearSession()) + ), { dispatch: false }); + sessionStatus = createEffect(() => this.actions.pipe( ofType(fromAuth.AuthActionTypes.SESSION_STATUS), map((action: fromAuth.SessionStatusAction) => action.payload), diff --git a/src/app/core/store/auth/auth.reducer.ts b/src/app/core/store/auth/auth.reducer.ts index e33d2d1bd..ebbc9f516 100644 --- a/src/app/core/store/auth/auth.reducer.ts +++ b/src/app/core/store/auth/auth.reducer.ts @@ -1,6 +1,5 @@ import { RegistrationRequest } from '../../model/request'; import { User } from '../../model/user'; -import { RouterNavigation } from '../router/router.actions'; import { AuthActions, AuthActionTypes } from './auth.actions'; export interface AuthState { @@ -12,7 +11,7 @@ export interface AuthState { completingRegistration: boolean; gettingUser: boolean; authenticated: boolean; - redirect: RouterNavigation; + redirect: string; user: User; registration: RegistrationRequest; error: any; @@ -131,7 +130,6 @@ export function reducer(state = initialState, action: AuthActions): AuthState { user: action.payload.user, }; case AuthActionTypes.GET_USER_FAILURE: - console.error(action); return { ...state, gettingUser: false, @@ -160,7 +158,7 @@ export function reducer(state = initialState, action: AuthActions): AuthState { case AuthActionTypes.SET_LOGIN_REDIRECT: return { ...state, - redirect: action.payload.navigation, + redirect: action.payload.url, }; case AuthActionTypes.UNSET_LOGIN_REDIRECT: return { diff --git a/src/app/core/store/dialog/dialog.effects.ts b/src/app/core/store/dialog/dialog.effects.ts index 81c0b87d6..c2697658f 100644 --- a/src/app/core/store/dialog/dialog.effects.ts +++ b/src/app/core/store/dialog/dialog.effects.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { Inject, Injectable, PLATFORM_ID } from '@angular/core'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Store } from '@ngrx/store'; @@ -11,11 +11,17 @@ import { selectAlertsByLocation } from '../alert'; import * as fromAlert from '../alert/alert.actions'; import * as fromDialog from './dialog.actions'; +import { isPlatformBrowser } from '@angular/common'; @Injectable() export class DialogEffects { - constructor(private actions: Actions, private store: Store, private modalService: NgbModal) { + constructor( + @Inject(PLATFORM_ID) private platformId: string, + private actions: Actions, + private store: Store, + private modalService: NgbModal + ) { } @@ -24,23 +30,27 @@ export class DialogEffects { map((action: fromDialog.OpenDialogAction) => action.payload), map((payload: { dialog: Dialog }) => payload.dialog), map((dialog: Dialog) => { - const modal = this.modalService.open(dialog.ref.component, dialog.options); - for (const key in dialog.ref.inputs) { - if (dialog.ref.inputs.hasOwnProperty(key)) { - modal.componentInstance[key] = dialog.ref.inputs[key]; + if (isPlatformBrowser(this.platformId)) { + const modal = this.modalService.open(dialog.ref.component, dialog.options); + for (const key in dialog.ref.inputs) { + if (dialog.ref.inputs.hasOwnProperty(key)) { + modal.componentInstance[key] = dialog.ref.inputs[key]; + } } + this.store.dispatch(new fromDialog.DialogOpenedAction()); } - return new fromDialog.DialogOpenedAction(); }) - )); + ), { dispatch: false }); closeDialog = createEffect(() => this.actions.pipe( ofType(fromDialog.DialogActionTypes.CLOSE_DIALOG), map(() => { - this.modalService.dismissAll(); - return new fromDialog.DialogClosedAction(); + if (isPlatformBrowser(this.platformId)) { + this.modalService.dismissAll(); + this.store.dispatch(new fromDialog.DialogClosedAction()); + } }) - )); + ), { dispatch: false }); dismissDialogAlerts = createEffect(() => this.actions.pipe( ofType(fromDialog.DialogActionTypes.DIALOG_CLOSED), diff --git a/src/app/core/store/index.ts b/src/app/core/store/index.ts index 280a91ed5..885ec54a4 100644 --- a/src/app/core/store/index.ts +++ b/src/app/core/store/index.ts @@ -18,7 +18,6 @@ import * as fromMetadata from './metadata/metadata.reducer'; import * as fromRootStore from './root-store.reducer'; import * as fromSdr from './sdr/sdr.reducer'; import * as fromSidebar from './sidebar/sidebar.reducer'; -import * as fromStomp from './stomp/stomp.reducer'; import * as fromTheme from './theme/theme.reducer'; export interface AppState { @@ -29,7 +28,6 @@ export interface AppState { layout: fromLayout.LayoutState; metadata: fromMetadata.MetadataState; sidebar: fromSidebar.SidebarState; - stomp: fromStomp.StompState; theme: fromTheme.ThemeState; individual: fromSdr.SdrState; themes: fromSdr.SdrState; @@ -55,7 +53,6 @@ export const reducers = (appConfig: AppConfig): ActionReducerMap => { layout: fromLayout.reducer, metadata: fromMetadata.reducer, sidebar: fromSidebar.reducer, - stomp: fromStomp.reducer, theme: fromTheme.reducer, individual: fromSdr.getSdrReducer('individual', additionalContext), themes: fromSdr.getSdrReducer('themes', additionalContext), diff --git a/src/app/core/store/metadata/metadata.effects.ts b/src/app/core/store/metadata/metadata.effects.ts index 23f39d3e1..74e5f0bc4 100644 --- a/src/app/core/store/metadata/metadata.effects.ts +++ b/src/app/core/store/metadata/metadata.effects.ts @@ -14,7 +14,12 @@ import * as fromMetadata from './metadata.actions'; @Injectable() export class MetadataEffects { - constructor(private actions: Actions, private router: Router, private store: Store, private metadataService: MetadataService) { + constructor( + private actions: Actions, + private router: Router, + private store: Store, + private metadataService: MetadataService + ) { this.listenForRouteDataTags(); } diff --git a/src/app/core/store/root-store.actions.ts b/src/app/core/store/root-store.actions.ts index a2d851687..14f5e00ba 100644 --- a/src/app/core/store/root-store.actions.ts +++ b/src/app/core/store/root-store.actions.ts @@ -8,7 +8,7 @@ export const StoreActionTypes = { export class RehydrateAction implements Action { readonly type = StoreActionTypes.REHYDRATE; - constructor(public payload: AppState) {} + constructor(public payload: { state: AppState }) {} } export type RootStoreActions = RehydrateAction; diff --git a/src/app/core/store/root-store.effects.ts b/src/app/core/store/root-store.effects.ts index 159f5d618..ff5c3b858 100644 --- a/src/app/core/store/root-store.effects.ts +++ b/src/app/core/store/root-store.effects.ts @@ -1,4 +1,33 @@ -import { Injectable } from '@angular/core'; +import { isPlatformBrowser } from '@angular/common'; +import { Inject, Injectable, PLATFORM_ID, TransferState } from '@angular/core'; +import { OnInitEffects } from '@ngrx/effects'; +import { Action, Store } from '@ngrx/store'; + +import { AppState } from '.'; +import { STORE_TRANSLATE_STATE } from '../../app.module'; + +import * as fromStore from './root-store.actions'; @Injectable() -export class RootStoreEffects {} +export class RootStoreEffects implements OnInitEffects { + + constructor( + @Inject(PLATFORM_ID) private platformId: string, + private store: Store, + private transferState: TransferState, + ) { } + + ngrxOnInitEffects(): Action { + if (isPlatformBrowser(this.platformId)) { + const serializedState = this.transferState.get(STORE_TRANSLATE_STATE, undefined); + + if (serializedState) { + const state = JSON.parse(serializedState); + this.store.dispatch(new fromStore.RehydrateAction({ state })); + this.transferState.remove(STORE_TRANSLATE_STATE); + } + } + return { type: '' }; + } + +} diff --git a/src/app/core/store/root-store.module.ts b/src/app/core/store/root-store.module.ts index 8810d3464..6a63a04f7 100644 --- a/src/app/core/store/root-store.module.ts +++ b/src/app/core/store/root-store.module.ts @@ -17,7 +17,6 @@ import { RouterEffects } from './router/router.effects'; import { CustomRouterStateSerializer } from './router/router.reducer'; import { SdrEffects } from './sdr/sdr.effects'; import { SidebarEffects } from './sidebar/sidebar.effects'; -import { StompEffects } from './stomp/stomp.effects'; import { ThemeEffects } from './theme/theme.effects'; @NgModule({ @@ -39,7 +38,6 @@ import { ThemeEffects } from './theme/theme.effects'; RootStoreEffects, RouterEffects, ThemeEffects, - StompEffects, SdrEffects, SidebarEffects, MetadataEffects, diff --git a/src/app/core/store/root-store.reducer.ts b/src/app/core/store/root-store.reducer.ts index 8bab5f9f2..0f59a0e4b 100644 --- a/src/app/core/store/root-store.reducer.ts +++ b/src/app/core/store/root-store.reducer.ts @@ -1,14 +1,21 @@ import * as fromStore from './root-store.actions'; +function merge(from: any, into: any): any { + return Object.entries(from).reduce( + (prev, [key, value]) => ({ + ...prev, + [key]: value, + }), + into + ); +} + export function universalMetaReducer(reducer) { return (state, action) => { - switch (action.type) { - case fromStore.StoreActionTypes.REHYDRATE: - state = Object.assign({}, state, action.payload); - break; - default: - break; + if (action.type === fromStore.StoreActionTypes.REHYDRATE) { + return reducer(merge(state, action.payload.state), action); } + return reducer(state, action); }; } diff --git a/src/app/core/store/router/router.actions.ts b/src/app/core/store/router/router.actions.ts index a67a371f5..0bab55f0c 100644 --- a/src/app/core/store/router/router.actions.ts +++ b/src/app/core/store/router/router.actions.ts @@ -1,16 +1,8 @@ -import { NavigationExtras } from '@angular/router'; import { Action } from '@ngrx/store'; import { Filter } from '../../model/view'; -export type RouterNavigation = Readonly<{ - path: any[]; - query?: object; - extras?: NavigationExtras; -}>; - export enum RouterActionTypes { - GO = '[Router] go', LINK = '[Router] link', BACK = '[Router] back', FORWARD = '[Router] forward', @@ -18,11 +10,6 @@ export enum RouterActionTypes { REMOVE_FILTER = '[Router] remove filter', } -export class Go implements Action { - readonly type = RouterActionTypes.GO; - constructor(public payload: RouterNavigation) { } -} - export class Link implements Action { readonly type = RouterActionTypes.LINK; constructor(public payload: { url: string }) { } @@ -46,7 +33,6 @@ export class RemoveFilter implements Action { } export type RouterActions = - Go | Link | Back | Forward | diff --git a/src/app/core/store/router/router.effects.ts b/src/app/core/store/router/router.effects.ts index 338020dd2..9b49c9f56 100644 --- a/src/app/core/store/router/router.effects.ts +++ b/src/app/core/store/router/router.effects.ts @@ -3,14 +3,12 @@ import { Injectable } from '@angular/core'; import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Store, select } from '@ngrx/store'; -import { filter, map, skipWhile, withLatestFrom } from 'rxjs/operators'; +import { filter, map, withLatestFrom } from 'rxjs/operators'; import { selectRouterQueryParams } from '.'; import { AppState } from '../'; import { FILTER_VALUE_DELIMITER } from '../../../shared/utilities/discovery.utility'; -import { selectLoginRedirect } from '../auth'; -import * as fromAuth from '../auth/auth.actions'; import * as fromRouter from './router.actions'; @Injectable() @@ -26,18 +24,6 @@ export class RouterEffects { this.listenForRouteChange(); } - navigate = createEffect(() => this.actions.pipe( - ofType(fromRouter.RouterActionTypes.GO), - map((action: fromRouter.Go) => action.payload), - map(({ path, query: queryParams, extras }) => - this.router.navigate(path, { - relativeTo: this.route, - queryParams, - ...extras, - }) - ) - ), { dispatch: false }); - navigateByUrl = createEffect(() => this.actions.pipe( ofType(fromRouter.RouterActionTypes.LINK), map((action: fromRouter.Link) => action.payload), @@ -54,14 +40,6 @@ export class RouterEffects { map(() => this.location.forward()) ), { dispatch: false }); - redirect = createEffect(() => this.actions.pipe( - ofType(fromRouter.RouterActionTypes.CHANGED), - withLatestFrom(this.store.pipe(select(selectLoginRedirect))), - map(([action, redirect]) => redirect), - skipWhile((redirect: fromRouter.RouterNavigation) => redirect === undefined), - map(() => new fromAuth.UnsetLoginRedirectAction()) - )); - removeFilter = createEffect(() => this.actions.pipe( ofType(fromRouter.RouterActionTypes.REMOVE_FILTER), map((action: fromRouter.RemoveFilter) => action.payload), diff --git a/src/app/core/store/sdr/sdr.effects.ts b/src/app/core/store/sdr/sdr.effects.ts index 9af74247d..cd565f09a 100644 --- a/src/app/core/store/sdr/sdr.effects.ts +++ b/src/app/core/store/sdr/sdr.effects.ts @@ -3,8 +3,8 @@ import { Params } from '@angular/router'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Store, select } from '@ngrx/store'; import { TranslateService } from '@ngx-translate/core'; -import { Observable, asapScheduler, combineLatest, defer, of, scheduled } from 'rxjs'; -import { catchError, filter, map, mergeMap, skipWhile, switchMap, take, withLatestFrom } from 'rxjs/operators'; +import { asapScheduler, combineLatest, defer, lastValueFrom, of, scheduled } from 'rxjs'; +import { catchError, filter, map, mergeMap, switchMap, take, withLatestFrom } from 'rxjs/operators'; import { AppState } from '../'; @@ -21,15 +21,12 @@ import { DialogService } from '../../service/dialog.service'; import { StatsService } from '../../service/stats.service'; import { selectRouterState } from '../router'; import { CustomRouterState } from '../router/router.reducer'; -import { selectIsStompConnected, selectStompState } from '../stomp'; -import { StompState } from '../stomp/stomp.reducer'; import { selectResourceById, selectSdrState } from './'; import { AcademicAge, DataNetwork, QuantityDistribution, SdrState } from './sdr.reducer'; import * as fromDialog from '../dialog/dialog.actions'; import * as fromRouter from '../router/router.actions'; import * as fromSidebar from '../sidebar/sidebar.actions'; -import * as fromStomp from '../stomp/stomp.actions'; import * as fromSdr from './sdr.actions'; @Injectable() @@ -50,8 +47,6 @@ export class SdrEffects { this.injectRepos(); } - // TODO: alerts should be in dialog location if a dialog is opened - getAll = createEffect(() => this.actions.pipe( ofType(...this.buildActions(fromSdr.SdrActionTypes.GET_ALL)), mergeMap((action: fromSdr.GetAllResourcesAction) => @@ -79,13 +74,6 @@ export class SdrEffects { ) )); - getAllSuccess = createEffect(() => this.actions.pipe( - ofType(...this.buildActions(fromSdr.SdrActionTypes.GET_ALL_SUCCESS)), - switchMap((action: fromSdr.GetAllResourcesSuccessAction) => this.waitForStompConnection(action.name)), - withLatestFrom(this.store.pipe(select(selectStompState))), - map(([combination, stomp]) => this.subscribeToResourceQueue(combination[0], stomp)) - ), { dispatch: false }); - getAllFailure = createEffect(() => this.actions.pipe( ofType(...this.buildActions(fromSdr.SdrActionTypes.GET_ALL_FAILURE)), map((action: fromSdr.GetAllResourcesFailureAction) => this.alert.getAllFailureAlert(action.payload)) @@ -132,17 +120,14 @@ export class SdrEffects { getOneSuccess = createEffect(() => this.actions.pipe( ofType(...this.buildActions(fromSdr.SdrActionTypes.GET_ONE_SUCCESS)), - switchMap((action: fromSdr.GetOneResourceSuccessAction) => { + map((action: fromSdr.GetOneResourceSuccessAction) => { if (action.payload.select) { this.store.dispatch(new fromSdr.SelectResourceSuccessAction(action.name, { individual: action.payload.individual })); } if (!!action.payload.queue && action.payload.queue.length > 0) { this.store.dispatch(action.payload.queue.pop()); } - return this.waitForStompConnection(action.name); - }), - withLatestFrom(this.store.pipe(select(selectStompState))), - map(([combination, stomp]) => this.subscribeToResourceQueue(combination[0], stomp)) + }) ), { dispatch: false }); getOneFailure = createEffect(() => this.actions.pipe( @@ -172,13 +157,6 @@ export class SdrEffects { ) )); - getNetworkSuccess = createEffect(() => this.actions.pipe( - ofType(...this.buildActions(fromSdr.SdrActionTypes.GET_NETWORK_SUCCESS)), - switchMap((action: fromSdr.GetNetworkSuccessAction) => this.waitForStompConnection(action.name)), - withLatestFrom(this.store.pipe(select(selectStompState))), - map(([combination, stomp]) => this.subscribeToResourceQueue(combination[0], stomp)) - ), { dispatch: false }); - getNetworkFailure = createEffect(() => this.actions.pipe( ofType(...this.buildActions(fromSdr.SdrActionTypes.GET_NETWORK_FAILURE)), map((action: fromSdr.GetNetworkFailureAction) => this.alert.getNetworkFailureAlert(action.payload)) @@ -208,9 +186,6 @@ export class SdrEffects { getAcademicAgeSuccess = createEffect(() => this.actions.pipe( ofType(...this.buildActions(fromSdr.SdrActionTypes.GET_ACADEMIC_AGE_SUCCESS)), - // TODO: determine utility and use of stomp connection for each success action dispatched (only applicable to asynchronous REST actions in which we want to switch to full duplex) - // switchMap((action: fromSdr.GetAcademicAgeSuccessAction) => this.waitForStompConnection(action.name)), - // withLatestFrom(this.store.pipe(select(selectStompState))), map((action: fromSdr.GetAcademicAgeSuccessAction) => { if (action.payload.queue.length > 0) { this.store.dispatch(action.payload.queue.pop()); @@ -247,9 +222,6 @@ export class SdrEffects { getQuantityDistributionSuccess = createEffect(() => this.actions.pipe( ofType(...this.buildActions(fromSdr.SdrActionTypes.GET_QUANTITY_DISTRIBUTION_SUCCESS)), - // TODO: determine utility and use of stomp connection for each success action dispatched (only applicable to asynchronous REST actions in which we want to switch to full duplex) - // switchMap((action: fromSdr.GetQuantityDistributionSuccessAction) => this.waitForStompConnection(action.name)), - // withLatestFrom(this.store.pipe(select(selectStompState))), map((action: fromSdr.GetQuantityDistributionSuccessAction) => { if (action.payload.queue.length > 0) { this.store.dispatch(action.payload.queue.pop()); @@ -289,13 +261,6 @@ export class SdrEffects { ) )); - findByIdInSuccess = createEffect(() => this.actions.pipe( - ofType(...this.buildActions(fromSdr.SdrActionTypes.FIND_BY_ID_IN_SUCCESS)), - switchMap((action: fromSdr.FindByIdInResourceSuccessAction) => this.waitForStompConnection(action.name)), - withLatestFrom(this.store.pipe(select(selectStompState))), - map(([combination, stomp]) => this.subscribeToResourceQueue(combination[0], stomp)) - ), { dispatch: false }); - findByIdInFailure = createEffect(() => this.actions.pipe( ofType(...this.buildActions(fromSdr.SdrActionTypes.FIND_BY_ID_IN_FAILURE)), map((action: fromSdr.FindByIdInResourceFailureAction) => this.alert.findByIdInFailureAlert(action.payload)) @@ -328,13 +293,6 @@ export class SdrEffects { ) )); - findByTypesInSuccess = createEffect(() => this.actions.pipe( - ofType(...this.buildActions(fromSdr.SdrActionTypes.FIND_BY_TYPES_IN_SUCCESS)), - switchMap((action: fromSdr.FindByTypesInResourceSuccessAction) => this.waitForStompConnection(action.name)), - withLatestFrom(this.store.pipe(select(selectStompState))), - map(([combination, stomp]) => this.subscribeToResourceQueue(combination[0], stomp)) - ), { dispatch: false }); - findByTypesInFailure = createEffect(() => this.actions.pipe( ofType(...this.buildActions(fromSdr.SdrActionTypes.FIND_BY_TYPES_IN_FAILURE)), map((action: fromSdr.FindByTypesInResourceFailureAction) => this.alert.findByTypesInFailureAlert(action.payload)) @@ -404,13 +362,6 @@ export class SdrEffects { ) )); - pageSuccess = createEffect(() => this.actions.pipe( - ofType(...this.buildActions(fromSdr.SdrActionTypes.PAGE_SUCCESS)), - switchMap((action: fromSdr.PageResourcesSuccessAction) => this.waitForStompConnection(action.name)), - withLatestFrom(this.store.pipe(select(selectStompState))), - map(([combination, stomp]) => this.subscribeToResourceQueue(combination[0], stomp)) - ), { dispatch: false }); - pageFailure = createEffect(() => this.actions.pipe( ofType(...this.buildActions(fromSdr.SdrActionTypes.PAGE_FAILURE)), map((action: fromSdr.PageResourcesFailureAction) => this.alert.pageFailureAlert(action.payload)) @@ -454,11 +405,11 @@ export class SdrEffects { ), this.store.pipe( select(selectSdrState('directoryViews')), - filter((directory: SdrState) => directory !== undefined) + filter((directory: SdrState) => directory !== undefined && !directory.loading) ), this.store.pipe( select(selectSdrState('discoveryViews')), - filter((discovery: SdrState) => discovery !== undefined) + filter((discovery: SdrState) => discovery !== undefined && !discovery.loading) ) ]) ), @@ -466,8 +417,8 @@ export class SdrEffects { return this.searchSuccessHandler({ action: latest[0], route: latest[1].state, - directory: latest[2] as SdrState, - discovery: latest[3] as SdrState + directory: latest[2], + discovery: latest[3] }); }) ), { dispatch: false }); @@ -543,11 +494,6 @@ export class SdrEffects { map((action: fromSdr.RecentlyUpdatedResourcesFailureAction) => this.alert.recentlyUpdatedFailureAlert(action.payload)) )); - clearResourceSubscription = createEffect(() => this.actions.pipe( - ofType(...this.buildActions(fromSdr.SdrActionTypes.CLEAR)), - map((action: fromSdr.ClearResourcesAction) => new fromStomp.UnsubscribeAction({ channel: `/queue/${action.name}` })) - )); - post = createEffect(() => this.actions.pipe( ofType(...this.buildActions(fromSdr.SdrActionTypes.POST)), switchMap((action: fromSdr.PostResourceAction) => @@ -714,33 +660,6 @@ export class SdrEffects { return loadActions; } - private waitForStompConnection(name: string): Observable<[string, boolean]> { - return combineLatest([ - scheduled([name], asapScheduler), - this.store.pipe( - select(selectIsStompConnected), - skipWhile((connected: boolean) => !connected), - take(1) - ), - ]); - } - - private subscribeToResourceQueue(name: string, stomp: StompState): void { - if (!stomp.subscriptions.has(`/queue/${name}`)) { - this.store.dispatch( - new fromStomp.SubscribeAction({ - channel: `/queue/${name}`, - handle: (frame: any) => { - // TODO: conditionally reload all - if (frame.command === 'MESSAGE') { - console.log(frame); - } - }, - }) - ); - } - } - private searchSuccessHandler(results: { action: fromSdr.SearchResourcesSuccessAction, route: CustomRouterState, @@ -749,7 +668,7 @@ export class SdrEffects { }): void { const { action, route, directory, discovery } = results; if (route.queryParams.collection) { - this.stats.collect(route.queryParams).toPromise().then((data: any) => { + lastValueFrom(this.stats.collect(route.queryParams)).then((data: any) => { if (data) { // do nothing } @@ -814,7 +733,7 @@ export class SdrEffects { parenthetical: facetEntry.count, selected, route: [], - queryParams: Object.assign({}, route.queryParams) + queryParams: { ...route.queryParams } }; sidebarItem.queryParams.page = 1; @@ -822,7 +741,7 @@ export class SdrEffects { if (selected) { sidebarSection.collapsed = false; if (hasFilter(sidebarItem.queryParams.filters, sdrFacet.field)) { - const queryParams: Params = Object.assign({}, sidebarItem.queryParams); + const queryParams: Params = { ...sidebarItem.queryParams }; removeFilterFromQueryParams(queryParams, { field: sdrFacet.field, value: filterValue, diff --git a/src/app/core/store/stomp/index.ts b/src/app/core/store/stomp/index.ts deleted file mode 100644 index 979f41299..000000000 --- a/src/app/core/store/stomp/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { createFeatureSelector, createSelector } from '@ngrx/store'; - -import * as fromStomp from './stomp.reducer'; - -export const selectStompState = createFeatureSelector('stomp'); - -export const selectIsStompConnected = createSelector(selectStompState, fromStomp.isConnected); -export const selectIsStompDisconnected = createSelector(selectStompState, fromStomp.isDisconnected); -export const selectStompSubscriptions = createSelector(selectStompState, fromStomp.getSubscriptions); diff --git a/src/app/core/store/stomp/stomp.actions.ts b/src/app/core/store/stomp/stomp.actions.ts deleted file mode 100644 index 8b377bdac..000000000 --- a/src/app/core/store/stomp/stomp.actions.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { Action } from '@ngrx/store'; -import { StompSubscription } from '@stomp/stompjs'; - -export enum StompActionTypes { - CONNECT = '[Stomp] connect', - CONNECT_SUCCESS = '[Stomp] connected successfully', - CONNECT_FAILURE = '[Stomp] failed to connect', - DISCONNECT = '[Stomp] disconnect', - DISCONNECT_SUCCESS = '[Stomp] disconnected successfully', - DISCONNECT_FAILURE = '[Stomp] failed to disconnect', - SUBSCRIBE = '[Stomp] subscribe', - SUBSCRIBE_SUCCESS = '[Stomp] subscribed successfully', - SUBSCRIBE_FAILURE = '[Stomp] failed to subscribe', - UNSUBSCRIBE = '[Stomp] unsubscribe', - UNSUBSCRIBE_SUCCESS = '[Stomp] unsubscribed successfully', - UNSUBSCRIBE_FAILURE = '[Stomp] failed to unsubscribe', -} - -export class ConnectAction implements Action { - readonly type = StompActionTypes.CONNECT; -} - -export class ConnectSuccessAction implements Action { - readonly type = StompActionTypes.CONNECT_SUCCESS; -} - -export class ConnectFailureAction implements Action { - readonly type = StompActionTypes.CONNECT_FAILURE; - constructor(public payload: { response: any }) { } -} - -export class DisconnectAction implements Action { - readonly type = StompActionTypes.DISCONNECT; - constructor(public payload: { reconnect: boolean } = { reconnect: false }) { } -} - -export class DisconnectSuccessAction implements Action { - readonly type = StompActionTypes.DISCONNECT_SUCCESS; - constructor(public payload: { reconnect: boolean } = { reconnect: false }) { } -} - -export class DisconnectFailureAction implements Action { - readonly type = StompActionTypes.DISCONNECT_FAILURE; - constructor(public payload: { response: any }) { } -} - -export class SubscribeAction implements Action { - readonly type = StompActionTypes.SUBSCRIBE; - constructor(public payload: { channel: string; handle: (message: any) => void }) { } -} - -export class SubscribeSuccessAction implements Action { - readonly type = StompActionTypes.SUBSCRIBE_SUCCESS; - constructor(public payload: { channel: string; subscription: StompSubscription }) { } -} - -export class SubscribeFailureAction implements Action { - readonly type = StompActionTypes.SUBSCRIBE_FAILURE; - constructor(public payload: { channel: string; response: any }) { } -} - -export class UnsubscribeAction implements Action { - readonly type = StompActionTypes.UNSUBSCRIBE; - constructor(public payload: { channel: string }) { } -} - -export class UnsubscribeSuccessAction implements Action { - readonly type = StompActionTypes.UNSUBSCRIBE_SUCCESS; - constructor(public payload: { channel: string }) { } -} - -export class UnsubscribeFailureAction implements Action { - readonly type = StompActionTypes.UNSUBSCRIBE_FAILURE; - constructor(public payload: { response: any }) { } -} - -export type StompActions = - ConnectAction | - ConnectSuccessAction | - ConnectFailureAction | - DisconnectAction | - DisconnectSuccessAction | - DisconnectFailureAction | - SubscribeAction | - SubscribeSuccessAction | - SubscribeFailureAction | - UnsubscribeAction | - UnsubscribeSuccessAction | - UnsubscribeFailureAction; diff --git a/src/app/core/store/stomp/stomp.effects.ts b/src/app/core/store/stomp/stomp.effects.ts deleted file mode 100644 index 3b0062b67..000000000 --- a/src/app/core/store/stomp/stomp.effects.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { Injectable } from '@angular/core'; -import { Actions, createEffect, ofType, OnInitEffects } from '@ngrx/effects'; -import { Action, Store } from '@ngrx/store'; -import { StompSubscription } from '@stomp/stompjs'; -import { asapScheduler, Observable, scheduled } from 'rxjs'; -import { catchError, filter, map, mergeMap, skipWhile, switchMap, withLatestFrom } from 'rxjs/operators'; - -import { AppState } from '../'; -import { AlertService } from '../../service/alert.service'; -import { StompService } from '../../service/stomp.service'; - -import * as fromStomp from './stomp.actions'; - -@Injectable() -export class StompEffects implements OnInitEffects { - - constructor(private actions: Actions, private store: Store, private stomp: StompService, private alert: AlertService) { - - } - - connect = createEffect(() => this.actions.pipe( - ofType(fromStomp.StompActionTypes.CONNECT), - switchMap(() => - this.stomp.connect().pipe( - map(() => new fromStomp.ConnectSuccessAction()), - catchError((response) => scheduled([new fromStomp.ConnectFailureAction({ response })], asapScheduler)) - ) - ) - )); - - connectFailure = createEffect(() => this.actions.pipe( - ofType(fromStomp.StompActionTypes.CONNECT_FAILURE), - map(() => this.alert.connectFailureAlert()) - )); - - disconnect = createEffect(() => this.actions.pipe( - ofType(fromStomp.StompActionTypes.DISCONNECT), - map((action: fromStomp.DisconnectAction) => action.payload), - withLatestFrom(this.store), - switchMap(([payload, state]) => { - const reconnect = payload.reconnect; - state.stomp.subscriptions.forEach((subscription: StompSubscription, channel: string) => { - subscription.unsubscribe(); - }); - return this.stomp.disconnect().pipe( - map(() => new fromStomp.DisconnectSuccessAction({ reconnect })), - catchError((response) => scheduled([new fromStomp.DisconnectFailureAction({ response })], asapScheduler)) - ); - }) - )); - - disconnectSuccess = createEffect(() => this.actions.pipe( - ofType(fromStomp.StompActionTypes.DISCONNECT_SUCCESS), - map((action: fromStomp.DisconnectSuccessAction) => action.payload), - skipWhile((payload: { reconnect: boolean }) => !payload.reconnect), - map(() => new fromStomp.ConnectAction()) - )); - - disconnectFailure = createEffect(() => this.actions.pipe( - ofType(fromStomp.StompActionTypes.DISCONNECT_FAILURE), - map(() => this.alert.disconnectFailureAlert()) - )); - - subscribe = createEffect(() => this.actions.pipe( - ofType(fromStomp.StompActionTypes.SUBSCRIBE), - map((action: fromStomp.SubscribeAction) => action.payload), - mergeMap((payload: { channel: string; handle: () => Observable }) => - this.stomp.subscribe(payload.channel, payload.handle).pipe( - map( - (subscription: StompSubscription) => - new fromStomp.SubscribeSuccessAction({ - channel: payload.channel, - subscription, - }) - ), - catchError((response) => - scheduled( - [ - new fromStomp.SubscribeFailureAction({ - channel: payload.channel, - response, - }), - ], - asapScheduler - ) - ) - ) - ) - )); - - unsubscribe = createEffect(() => this.actions.pipe( - ofType(fromStomp.StompActionTypes.UNSUBSCRIBE), - map((action: fromStomp.UnsubscribeAction) => action), - withLatestFrom(this.store), - filter(([action, store]) => !!store.stomp.subscriptions.get(action.payload.channel)), - switchMap(([action, store]) => - scheduled([store.stomp.subscriptions.get(action.payload.channel).unsubscribe()], asapScheduler).pipe( - map( - () => - new fromStomp.UnsubscribeSuccessAction({ - channel: action.payload.channel, - }) - ), - catchError((response) => scheduled([new fromStomp.UnsubscribeFailureAction({ response })], asapScheduler)) - ) - ) - )); - - unsubscribeFailure = createEffect(() => this.actions.pipe( - ofType(fromStomp.StompActionTypes.UNSUBSCRIBE_FAILURE), - map(() => this.alert.unsubscribeFailureAlert()) - )); - - ngrxOnInitEffects(): Action { - return new fromStomp.ConnectAction(); - } - -} diff --git a/src/app/core/store/stomp/stomp.reducer.ts b/src/app/core/store/stomp/stomp.reducer.ts deleted file mode 100644 index a60d63a6f..000000000 --- a/src/app/core/store/stomp/stomp.reducer.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { StompSubscription } from '@stomp/stompjs'; - -import { StompActions, StompActionTypes } from './stomp.actions'; - -export type StompState = Readonly<{ - connecting: boolean; - connected: boolean; - handles: Map void>; - subscriptions: Map; -}>; - -export const initialState: StompState = { - connecting: false, - connected: false, - handles: new Map void>(), - subscriptions: new Map(), -}; - -export function reducer(state = initialState, action: StompActions): StompState { - switch (action.type) { - case StompActionTypes.CONNECT: - return { - ...state, - connecting: true, - }; - case StompActionTypes.CONNECT_SUCCESS: - return { - ...state, - connecting: false, - connected: true, - }; - case StompActionTypes.CONNECT_FAILURE: - console.log(action); - return { - ...state, - connecting: false, - }; - case StompActionTypes.DISCONNECT_SUCCESS: - return { - ...state, - connecting: action.payload.reconnect ? true : false, - connected: false, - }; - case StompActionTypes.SUBSCRIBE: - state.handles.set(action.payload.channel, action.payload.handle); - return state; - case StompActionTypes.SUBSCRIBE_SUCCESS: - state.subscriptions.set(action.payload.channel, action.payload.subscription); - return state; - case StompActionTypes.SUBSCRIBE_FAILURE: - console.log(action); - state.handles.delete(action.payload.channel); - return state; - case StompActionTypes.UNSUBSCRIBE_SUCCESS: - state.handles.delete(action.payload.channel); - state.subscriptions.delete(action.payload.channel); - return state; - default: - return state; - } -} - -export const isConnected = (state: StompState) => state.connected; -export const isDisconnected = (state: StompState) => !state.connected; -export const getSubscriptions = (state: StompState) => state.subscriptions; diff --git a/src/app/footer/footer.component.ts b/src/app/footer/footer.component.ts index be980aeac..abe78cdfd 100644 --- a/src/app/footer/footer.component.ts +++ b/src/app/footer/footer.component.ts @@ -29,7 +29,12 @@ export class FooterComponent implements OnInit { public footer: Observable