From d27b87fa4cbe66db58ab206b96dbd21de35c9959 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 6 Sep 2026 07:16:02 +0200 Subject: [PATCH 01/15] chore(deps): Bump @angular/common in /eform-client (#7993) Bumps [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) from 20.3.16 to 20.3.27. - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v20.3.27/packages/common) --- updated-dependencies: - dependency-name: "@angular/common" dependency-version: 20.3.27 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- eform-client/package.json | 2 +- eform-client/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/eform-client/package.json b/eform-client/package.json index 36b82767f0..6a5024e3fb 100644 --- a/eform-client/package.json +++ b/eform-client/package.json @@ -36,7 +36,7 @@ "@angular-material-extensions/password-strength": "^16.0.0", "@angular/animations": "20.3.16", "@angular/cdk": "20.2.14", - "@angular/common": "20.3.16", + "@angular/common": "20.3.27", "@angular/compiler": "20.3.16", "@angular/core": "20.3.17", "@angular/forms": "20.3.16", diff --git a/eform-client/yarn.lock b/eform-client/yarn.lock index 5d82600704..b973fd85c6 100644 --- a/eform-client/yarn.lock +++ b/eform-client/yarn.lock @@ -532,10 +532,10 @@ yargs "18.0.0" zod "4.1.13" -"@angular/common@20.3.16": - version "20.3.16" - resolved "https://registry.yarnpkg.com/@angular/common/-/common-20.3.16.tgz#d2f5f1bbc8d65b81989b16b02c74b2529f4541e0" - integrity sha512-GRAziNlntwdnJy3F+8zCOvDdy7id0gITjDnM6P9+n2lXvtDuBLGJKU3DWBbvxcCjtD6JK/g/rEX5fbCxbUHkQQ== +"@angular/common@20.3.27": + version "20.3.27" + resolved "https://registry.yarnpkg.com/@angular/common/-/common-20.3.27.tgz#52405b538e1e662b4b93a4176d6ff86fbb81f7e7" + integrity sha512-4ectYP60XatB9zZ40WlfmaTzjmEhaz8SSqLsbZI4VZ8gDb5qNmxWtwwt8UxS3NmDHEgqdNL8UPO4E94+yKCICg== dependencies: tslib "^2.3.0" From a744367934193ae2f1157fa5c5b60f29987b9b52 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 6 Sep 2026 07:16:18 +0200 Subject: [PATCH 02/15] chore(deps): Bump @angular/core from 20.3.17 to 20.3.27 in /eform-client (#7995) Bumps [@angular/core](https://github.com/angular/angular/tree/HEAD/packages/core) from 20.3.17 to 20.3.27. - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v20.3.27/packages/core) --- updated-dependencies: - dependency-name: "@angular/core" dependency-version: 20.3.27 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- eform-client/package.json | 2 +- eform-client/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/eform-client/package.json b/eform-client/package.json index 6a5024e3fb..c63716580c 100644 --- a/eform-client/package.json +++ b/eform-client/package.json @@ -38,7 +38,7 @@ "@angular/cdk": "20.2.14", "@angular/common": "20.3.27", "@angular/compiler": "20.3.16", - "@angular/core": "20.3.17", + "@angular/core": "20.3.27", "@angular/forms": "20.3.16", "@angular/localize": "20.3.16", "@angular/material": "20.2.14", diff --git a/eform-client/yarn.lock b/eform-client/yarn.lock index b973fd85c6..0f51b96ec5 100644 --- a/eform-client/yarn.lock +++ b/eform-client/yarn.lock @@ -560,10 +560,10 @@ dependencies: tslib "^2.3.0" -"@angular/core@20.3.17": - version "20.3.17" - resolved "https://registry.yarnpkg.com/@angular/core/-/core-20.3.17.tgz#e396f0f2a6f47a85524e8d313082f65e35001059" - integrity sha512-YlQqxMeHI9XJw7I7oM3hYFQd4lQbK37IdlD9ztROIw5FjX6i6lmLU7+X1MQGSRi2r+X9l3IZtl33hRTNvkoUBw== +"@angular/core@20.3.27": + version "20.3.27" + resolved "https://registry.yarnpkg.com/@angular/core/-/core-20.3.27.tgz#97ce97a90c3e06564df7d7d8abbb2eeac3afdb07" + integrity sha512-8EfYIUST5CKldOF4MAYWTFFRB7EtwqUoQBZdar6US39/EEzWm/wm/iRNkH2jmKe4YuPa2GoeHS1WQ8VRuOk7Dg== dependencies: tslib "^2.3.0" From 86021f1fa9141c3955576b44648388efdef6b5e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 6 Sep 2026 07:17:08 +0200 Subject: [PATCH 03/15] chore(deps): Bump immutable from 5.1.5 to 5.1.9 in /eform-client (#7985) Bumps [immutable](https://github.com/immutable-js/immutable-js) from 5.1.5 to 5.1.9. - [Release notes](https://github.com/immutable-js/immutable-js/releases) - [Changelog](https://github.com/immutable-js/immutable-js/blob/main/CHANGELOG.md) - [Commits](https://github.com/immutable-js/immutable-js/compare/v5.1.5...v5.1.9) --- updated-dependencies: - dependency-name: immutable dependency-version: 5.1.9 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- eform-client/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/eform-client/yarn.lock b/eform-client/yarn.lock index 0f51b96ec5..de7b58886a 100644 --- a/eform-client/yarn.lock +++ b/eform-client/yarn.lock @@ -8269,9 +8269,9 @@ immediate@~3.0.5: integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== immutable@^5.0.2: - version "5.1.5" - resolved "https://registry.yarnpkg.com/immutable/-/immutable-5.1.5.tgz#93ee4db5c2a9ab42a4a783069f3c5d8847d40165" - integrity sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A== + version "5.1.9" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-5.1.9.tgz#ac23c3a01992ab665e14ac9ffff298f28cd74a0c" + integrity sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg== import-fresh@^3.2.1, import-fresh@^3.3.0: version "3.3.1" From 721cdaa6a94a2983232fcd39c7baf6ad22e6ca5d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 6 Sep 2026 07:17:20 +0200 Subject: [PATCH 04/15] chore(deps): Bump sigstore from 4.1.0 to 4.1.1 in /eform-client (#7964) Bumps [sigstore](https://github.com/sigstore/sigstore-js) from 4.1.0 to 4.1.1. - [Release notes](https://github.com/sigstore/sigstore-js/releases) - [Commits](https://github.com/sigstore/sigstore-js/compare/sigstore@4.1.0...sigstore@4.1.1) --- updated-dependencies: - dependency-name: sigstore dependency-version: 4.1.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- eform-client/yarn.lock | 77 +++++++++++++++++++++++++++++------------- 1 file changed, 54 insertions(+), 23 deletions(-) diff --git a/eform-client/yarn.lock b/eform-client/yarn.lock index de7b58886a..db4f1af289 100644 --- a/eform-client/yarn.lock +++ b/eform-client/yarn.lock @@ -2168,6 +2168,11 @@ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.10.tgz#a2a1e3812d14525f725d011a73eceb41fef5bc1c" integrity sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ== +"@gar/promise-retry@^1.0.0", "@gar/promise-retry@^1.0.2": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@gar/promise-retry/-/promise-retry-1.0.3.tgz#65e726428e794bc4453948e0a41e6de4215ce8b0" + integrity sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA== + "@hono/node-server@^1.19.7": version "1.19.13" resolved "https://registry.yarnpkg.com/@hono/node-server/-/node-server-1.19.13.tgz#4838c766a1237253d4dde3281cf7d5c65186fd32" @@ -3828,27 +3833,27 @@ dependencies: "@sigstore/protobuf-specs" "^0.5.0" -"@sigstore/core@^3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@sigstore/core/-/core-3.1.0.tgz#b418de73f56333ad9e369b915173d8c98e9b96d5" - integrity sha512-o5cw1QYhNQ9IroioJxpzexmPjfCe7gzafd2RY3qnMpxr4ZEja+Jad/U8sgFpaue6bOaF+z7RVkyKVV44FN+N8A== +"@sigstore/core@^3.2.0", "@sigstore/core@^3.2.1": + version "3.2.1" + resolved "https://registry.yarnpkg.com/@sigstore/core/-/core-3.2.1.tgz#4efd4ab0f59e768b6daf65612024ba925787de72" + integrity sha512-qRsxPnCrbC/puegGxKuynfnxgLiHqWStrSjxkoB4YKqq3Z3s4cyZyj42ZdWFAEblNP65C+rBH8EuREHIXoi83g== "@sigstore/protobuf-specs@^0.5.0": version "0.5.0" resolved "https://registry.yarnpkg.com/@sigstore/protobuf-specs/-/protobuf-specs-0.5.0.tgz#e5f029edcb3a4329853a09b603011e61043eb005" integrity sha512-MM8XIwUjN2bwvCg1QvrMtbBmpcSHrkhFSCu1D11NyPvDQ25HEc4oG5/OcQfd/Tlf/OxmKWERDj0zGE23jQaMwA== -"@sigstore/sign@^4.1.0": - version "4.1.0" - resolved "https://registry.yarnpkg.com/@sigstore/sign/-/sign-4.1.0.tgz#63df15a137337b29f463a1d1c51e1f7d4c1db2f1" - integrity sha512-Vx1RmLxLGnSUqx/o5/VsCjkuN5L7y+vxEEwawvc7u+6WtX2W4GNa7b9HEjmcRWohw/d6BpATXmvOwc78m+Swdg== +"@sigstore/sign@^4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@sigstore/sign/-/sign-4.1.1.tgz#34765fe4a190d693340c0771a3d150a397bcfc55" + integrity sha512-Hf4xglukg0XXQ2RiD5vSoLjdPe8OBUPA8XeVjUObheuDcWdYWrnH/BNmxZCzkAy68MzmNCxXLeurJvs6hcP2OQ== dependencies: + "@gar/promise-retry" "^1.0.2" "@sigstore/bundle" "^4.0.0" - "@sigstore/core" "^3.1.0" + "@sigstore/core" "^3.2.0" "@sigstore/protobuf-specs" "^0.5.0" - make-fetch-happen "^15.0.3" + make-fetch-happen "^15.0.4" proc-log "^6.1.0" - promise-retry "^2.0.1" "@sigstore/tuf@^4.0.1": version "4.0.1" @@ -3858,13 +3863,21 @@ "@sigstore/protobuf-specs" "^0.5.0" tuf-js "^4.1.0" -"@sigstore/verify@^3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@sigstore/verify/-/verify-3.1.0.tgz#4046d4186421db779501fe87fa5acaa5d4d21b08" - integrity sha512-mNe0Iigql08YupSOGv197YdHpPPr+EzDZmfCgMc7RPNaZTw5aLN01nBl6CHJOh3BGtnMIj83EeN4butBchc8Ag== +"@sigstore/tuf@^4.0.2": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@sigstore/tuf/-/tuf-4.0.2.tgz#7d2fa2abcd5afa5baf752671d14a1c6ed0ed3196" + integrity sha512-TCAzTy0xzdP79EnxSjq9KQ3eaR7+FmudLC6eRKknVKZbV7ZNlGLClAAQb/HMNJ5n2OBNk2GT1tEmU0xuPr+SLQ== + dependencies: + "@sigstore/protobuf-specs" "^0.5.0" + tuf-js "^4.1.0" + +"@sigstore/verify@^3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@sigstore/verify/-/verify-3.1.1.tgz#13c1c1cff28fe81f662de29d85ba5ae048fcbd8d" + integrity sha512-qv7+G3J2cc6wwFj3yKvXOamzqhMwSk1ogPGmhpS8iXllcPrJaIIBA+4HbttlHVu1pqWTdmaCH/WE7UOC51kdoA== dependencies: "@sigstore/bundle" "^4.0.0" - "@sigstore/core" "^3.1.0" + "@sigstore/core" "^3.2.1" "@sigstore/protobuf-specs" "^0.5.0" "@sinclair/typebox@^0.27.8": @@ -9686,6 +9699,24 @@ make-fetch-happen@^15.0.0, make-fetch-happen@^15.0.1, make-fetch-happen@^15.0.3: promise-retry "^2.0.1" ssri "^13.0.0" +make-fetch-happen@^15.0.4: + version "15.0.6" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-15.0.6.tgz#079dee708c88a04a1f79735bb02789a63597840e" + integrity sha512-Je0fLJ0F5atA7F+eIlLzk+Wkcl57JDf4kf+EW8xiP5E31xOQxkIxTbgf1Oi1Lw9tRI9UEMRdI5Vz2xTzoNU1Jw== + dependencies: + "@gar/promise-retry" "^1.0.0" + "@npmcli/agent" "^4.0.0" + "@npmcli/redact" "^4.0.0" + cacache "^20.0.1" + http-cache-semantics "^4.1.1" + minipass "^7.0.2" + minipass-fetch "^5.0.0" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + negotiator "^1.0.0" + proc-log "^6.0.0" + ssri "^13.0.0" + makeerror@1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" @@ -11857,16 +11888,16 @@ signal-exit@^4.0.1, signal-exit@^4.1.0: integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== sigstore@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/sigstore/-/sigstore-4.1.0.tgz#d34b92a544a05e003a2430209d26d8dfafd805a0" - integrity sha512-/fUgUhYghuLzVT/gaJoeVehLCgZiUxPCPMcyVNY0lIf/cTCz58K/WTI7PefDarXxp9nUKpEwg1yyz3eSBMTtgA== + version "4.1.1" + resolved "https://registry.yarnpkg.com/sigstore/-/sigstore-4.1.1.tgz#2959993dbf978c759a5d23d854cbd3729b6dcd97" + integrity sha512-endqECJkfhozrXMK5ngu/UAA0xVcVEFdnHJCElGaExypjW+HK5i6zu3NteLoaX/iFbRUbC3+DjttQs0GARr+5w== dependencies: "@sigstore/bundle" "^4.0.0" - "@sigstore/core" "^3.1.0" + "@sigstore/core" "^3.2.1" "@sigstore/protobuf-specs" "^0.5.0" - "@sigstore/sign" "^4.1.0" - "@sigstore/tuf" "^4.0.1" - "@sigstore/verify" "^3.1.0" + "@sigstore/sign" "^4.1.1" + "@sigstore/tuf" "^4.0.2" + "@sigstore/verify" "^3.1.1" simple-concat@^1.0.0: version "1.0.1" From 38c1f274daead9b914f21b7cbee931772c69f465 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 6 Sep 2026 07:18:50 +0200 Subject: [PATCH 05/15] chore(deps): Bump form-data from 4.0.5 to 4.0.6 in /eform-client (#7955) Bumps [form-data](https://github.com/form-data/form-data) from 4.0.5 to 4.0.6. - [Changelog](https://github.com/form-data/form-data/blob/master/CHANGELOG.md) - [Commits](https://github.com/form-data/form-data/compare/v4.0.5...v4.0.6) --- updated-dependencies: - dependency-name: form-data dependency-version: 4.0.6 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- eform-client/yarn.lock | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/eform-client/yarn.lock b/eform-client/yarn.lock index db4f1af289..5f4bcde047 100644 --- a/eform-client/yarn.lock +++ b/eform-client/yarn.lock @@ -7717,15 +7717,15 @@ forever-agent@~0.6.1: integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== form-data@^4.0.0, form-data@~4.0.4: - version "4.0.5" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.5.tgz#b49e48858045ff4cbf6b03e1805cebcad3679053" - integrity sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w== + version "4.0.6" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.6.tgz#28e864e1b786dbebb68db1f452f9635278665827" + integrity sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ== dependencies: asynckit "^0.4.0" combined-stream "^1.0.8" es-set-tostringtag "^2.1.0" - hasown "^2.0.2" - mime-types "^2.1.12" + hasown "^2.0.4" + mime-types "^2.1.35" forwarded@0.2.0: version "0.2.0" @@ -8051,6 +8051,13 @@ hasown@^2.0.2: dependencies: function-bind "^1.1.2" +hasown@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.4.tgz#8c62d8cb90beb2aad5d0a5b67581ad9854c3f003" + integrity sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A== + dependencies: + function-bind "^1.1.2" + hosted-git-info@^9.0.0, hosted-git-info@^9.0.2: version "9.0.2" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-9.0.2.tgz#b38c8a802b274e275eeeccf9f4a1b1a0a8557ada" @@ -9823,7 +9830,7 @@ mime-db@1.52.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== -mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.19, mime-types@~2.1.24, mime-types@~2.1.34, mime-types@~2.1.35: +mime-types@^2.1.27, mime-types@^2.1.31, mime-types@^2.1.35, mime-types@~2.1.19, mime-types@~2.1.24, mime-types@~2.1.34, mime-types@~2.1.35: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== From d554d3140d2b3aef7cf713f3b0026fb3db904a99 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 6 Sep 2026 07:19:08 +0200 Subject: [PATCH 06/15] chore(deps): Bump launch-editor from 2.13.0 to 2.14.1 in /eform-client (#7954) Bumps [launch-editor](https://github.com/vitejs/launch-editor) from 2.13.0 to 2.14.1. - [Commits](https://github.com/vitejs/launch-editor/compare/v2.13.0...v2.14.1) --- updated-dependencies: - dependency-name: launch-editor dependency-version: 2.14.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- eform-client/yarn.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/eform-client/yarn.lock b/eform-client/yarn.lock index 5f4bcde047..68a846bd5f 100644 --- a/eform-client/yarn.lock +++ b/eform-client/yarn.lock @@ -9310,12 +9310,12 @@ kind-of@^6.0.2: integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== launch-editor@^2.6.1: - version "2.13.0" - resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.13.0.tgz#6ff3d88d53ef3148d2145350b54854f9f2551ce1" - integrity sha512-u+9asUHMJ99lA15VRMXw5XKfySFR9dGXwgsgS14YTbUq3GITP58mIM32At90P5fZ+MUId5Yw+IwI/yKub7jnCQ== + version "2.14.1" + resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.14.1.tgz#f7e0da3f58aaea03fea01074d840b5f739ed7ddc" + integrity sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA== dependencies: picocolors "^1.1.1" - shell-quote "^1.8.3" + shell-quote "^1.8.4" less-loader@12.3.0: version "12.3.0" @@ -11839,7 +11839,7 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shell-quote@^1.8.3: +shell-quote@^1.8.4: version "1.8.4" resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.4.tgz#2edd9a4dcefc96649e2e2cb12f637b1f1d92a190" integrity sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ== From 105009d535e34367a2ee860e1b3c48be5f00b8a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 6 Sep 2026 07:20:15 +0200 Subject: [PATCH 07/15] chore(deps): Bump ip-address from 10.2.0 to 10.4.0 in /eform-client (#7996) Bumps [ip-address](https://github.com/beaugunderson/ip-address) from 10.2.0 to 10.4.0. - [Release notes](https://github.com/beaugunderson/ip-address/releases) - [Commits](https://github.com/beaugunderson/ip-address/compare/v10.2.0...v10.4.0) --- updated-dependencies: - dependency-name: ip-address dependency-version: 10.4.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- eform-client/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/eform-client/yarn.lock b/eform-client/yarn.lock index 68a846bd5f..425d2f9773 100644 --- a/eform-client/yarn.lock +++ b/eform-client/yarn.lock @@ -8381,9 +8381,9 @@ internmap@^1.0.0: integrity sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw== ip-address@^10.0.1: - version "10.2.0" - resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-10.2.0.tgz#805fc178b20c518bd4c8548b24fe30892d7f3206" - integrity sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA== + version "10.4.0" + resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-10.4.0.tgz#c5910bc541b6eae287765d1e4846be0308a05d93" + integrity sha512-oSK96Grm3aP6OrS263xVxbNDGVL7rzBtYdpGqlDG8iQdoenDoTs/nkki+DflYbAEE8Xl6o5YxhxlrKvI3nqKXQ== ipaddr.js@1.9.1: version "1.9.1" From f7916eea1851a111435e0f19874e14a2ac0ebe77 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 6 Sep 2026 07:20:29 +0200 Subject: [PATCH 08/15] chore(deps): Bump tar from 7.5.11 to 7.5.22 in /eform-client (#7988) Bumps [tar](https://github.com/isaacs/node-tar) from 7.5.11 to 7.5.22. - [Release notes](https://github.com/isaacs/node-tar/releases) - [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md) - [Commits](https://github.com/isaacs/node-tar/compare/v7.5.11...v7.5.22) --- updated-dependencies: - dependency-name: tar dependency-version: 7.5.22 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- eform-client/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/eform-client/yarn.lock b/eform-client/yarn.lock index 425d2f9773..686a58d2ee 100644 --- a/eform-client/yarn.lock +++ b/eform-client/yarn.lock @@ -12334,9 +12334,9 @@ tar-stream@^2.1.4: readable-stream "^3.1.1" tar@^7.4.3, tar@^7.5.1, tar@^7.5.4, tar@^7.5.7: - version "7.5.11" - resolved "https://registry.yarnpkg.com/tar/-/tar-7.5.11.tgz#1250fae45d98806b36d703b30973fa8e0a6d8868" - integrity sha512-ChjMH33/KetonMTAtpYdgUFr0tbz69Fp2v7zWxQfYZX4g5ZN2nOBXm1R2xyA+lMIKrLKIoKAwFj93jE/avX9cQ== + version "7.5.22" + resolved "https://registry.yarnpkg.com/tar/-/tar-7.5.22.tgz#a696f998136e71487dc3f869a85bba2c67971ba9" + integrity sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA== dependencies: "@isaacs/fs-minipass" "^4.0.0" chownr "^3.0.0" From 7488e7c1fb4ad5c53f43eacde89523c76f1d77e0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 6 Sep 2026 07:21:03 +0200 Subject: [PATCH 09/15] chore(deps): Bump tmp from 0.2.5 to 0.2.7 in /eform-client (#7913) Bumps [tmp](https://github.com/raszi/node-tmp) from 0.2.5 to 0.2.7. - [Changelog](https://github.com/raszi/node-tmp/blob/master/CHANGELOG.md) - [Commits](https://github.com/raszi/node-tmp/compare/v0.2.5...v0.2.7) --- updated-dependencies: - dependency-name: tmp dependency-version: 0.2.7 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- eform-client/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/eform-client/yarn.lock b/eform-client/yarn.lock index 686a58d2ee..41d30fbd60 100644 --- a/eform-client/yarn.lock +++ b/eform-client/yarn.lock @@ -12478,9 +12478,9 @@ tldts@^7.0.5: tldts-core "^7.0.23" tmp@~0.2.4: - version "0.2.5" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.5.tgz#b06bcd23f0f3c8357b426891726d16015abfd8f8" - integrity sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow== + version "0.2.7" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.7.tgz#26f4db11d1601ce8012dcb8a798ece1c06a99059" + integrity sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw== tmpl@1.0.5: version "1.0.5" From 871d77b47a3d0db183947d5fee4d0878ef6666a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 6 Sep 2026 07:22:02 +0200 Subject: [PATCH 10/15] Bump Sentry from 6.9.0 to 6.10.0 (#8034) --- updated-dependencies: - dependency-name: Sentry dependency-version: 6.10.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- eFormAPI/eFormAPI.Web/eFormAPI.Web.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eFormAPI/eFormAPI.Web/eFormAPI.Web.csproj b/eFormAPI/eFormAPI.Web/eFormAPI.Web.csproj index 9b91e30608..3783b9362e 100644 --- a/eFormAPI/eFormAPI.Web/eFormAPI.Web.csproj +++ b/eFormAPI/eFormAPI.Web/eFormAPI.Web.csproj @@ -59,7 +59,7 @@ - + From b13c4b5ffb7b482f69fd6dc3e08ff638df9e3a94 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 6 Sep 2026 07:22:28 +0200 Subject: [PATCH 11/15] chore(deps): Bump browserslist from 4.28.1 to 4.28.8 in /eform-client (#8029) Bumps [browserslist](https://github.com/browserslist/browserslist) from 4.28.1 to 4.28.8. - [Release notes](https://github.com/browserslist/browserslist/releases) - [Changelog](https://github.com/browserslist/browserslist/blob/main/CHANGELOG.md) - [Commits](https://github.com/browserslist/browserslist/compare/4.28.1...4.28.8) --- updated-dependencies: - dependency-name: browserslist dependency-version: 4.28.8 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- eform-client/yarn.lock | 55 +++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/eform-client/yarn.lock b/eform-client/yarn.lock index 41d30fbd60..92c926e241 100644 --- a/eform-client/yarn.lock +++ b/eform-client/yarn.lock @@ -5480,10 +5480,10 @@ base64-js@^1.3.1, base64-js@^1.5.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== -baseline-browser-mapping@^2.9.0: - version "2.10.0" - resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz#5b09935025bf8a80e29130251e337c6a7fc8cbb9" - integrity sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA== +baseline-browser-mapping@^2.11.12: + version "2.11.20" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.11.20.tgz#26078c7a4b08299656ea7ddceaebec955dc44303" + integrity sha512-H0ulySigv6icDJ1F7SjtdCD6PrhTpdYCmP0CactWy1+ekh0AFd0o1Wn5T8b+hnTmdBx19u9yhL6wvCylXMY7zw== batch@0.6.1: version "0.6.1" @@ -5644,15 +5644,15 @@ braces@^3.0.3, braces@~3.0.2: fill-range "^7.1.1" browserslist@^4.21.5, browserslist@^4.23.0, browserslist@^4.24.0, browserslist@^4.24.4, browserslist@^4.28.1: - version "4.28.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.1.tgz#7f534594628c53c63101079e27e40de490456a95" - integrity sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA== + version "4.28.8" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.8.tgz#a3c79ceb70028527e5da7dafc887f3200b5168c0" + integrity sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA== dependencies: - baseline-browser-mapping "^2.9.0" - caniuse-lite "^1.0.30001759" - electron-to-chromium "^1.5.263" - node-releases "^2.0.27" - update-browserslist-db "^1.2.0" + baseline-browser-mapping "^2.11.12" + caniuse-lite "^1.0.30001809" + electron-to-chromium "^1.5.402" + node-releases "^2.0.53" + update-browserslist-db "^1.3.0" bs-logger@^0.2.6: version "0.2.6" @@ -5751,11 +5751,16 @@ camelcase@^6.3.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001702, caniuse-lite@^1.0.30001759: +caniuse-lite@^1.0.30001702: version "1.0.30001770" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz#4dc47d3b263a50fbb243448034921e0a88591a84" integrity sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw== +caniuse-lite@^1.0.30001809: + version "1.0.30001810" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz#4970b477dea3278374de9bc43aa8f5d39fc3cda2" + integrity sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg== + canvas@^3.0.0-rc2: version "3.2.1" resolved "https://registry.yarnpkg.com/canvas/-/canvas-3.2.1.tgz#8f0390569f36b94bffba9c0e7aed6948875aec7b" @@ -6901,10 +6906,10 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -electron-to-chromium@^1.5.263: - version "1.5.286" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz#142be1ab5e1cd5044954db0e5898f60a4960384e" - integrity sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A== +electron-to-chromium@^1.5.402: + version "1.5.420" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.420.tgz#fc66d26a722d6f227e2092acdf38dd55b198cb44" + integrity sha512-2yD6XreGusOfNV+dUcvipJEXc3n/n7fgr7996aszTG+YY5E4mqM4tOq/3uhP129cazL9YHbVWSpc79ePotWtPA== emittery@^0.13.1: version "0.13.1" @@ -10225,10 +10230,10 @@ node-int64@^0.4.0: resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== -node-releases@^2.0.27: - version "2.0.27" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.27.tgz#eedca519205cf20f650f61d56b070db111231e4e" - integrity sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA== +node-releases@^2.0.53: + version "2.0.54" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.54.tgz#09af17d5647aa9f221ec5cf2becb95b68a981afe" + integrity sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ== nopt@^9.0.0: version "9.0.0" @@ -12816,10 +12821,10 @@ untildify@^4.0.0: resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== -update-browserslist-db@^1.2.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d" - integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w== +update-browserslist-db@^1.3.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz#9d99fbff56c50bb11ba5fd35cece5916da595836" + integrity sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw== dependencies: escalade "^3.2.0" picocolors "^1.1.1" From 3cdc9be3215a37327fae67b746010b77cf449c06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Schultz=20Madsen?= Date: Sun, 6 Sep 2026 12:50:15 +0200 Subject: [PATCH 12/15] fix(eform-files): make picture upload atomic and idempotent; localize its error (#8036) POST /api/template-files/image persisted UploadedData and FieldValue before the step that can fail (thumbnail generation), returned an untranslated resource key on failure, and the client let the user re-submit the same file on every further click. One transient failure became a raw-key toast and N duplicate picture cards after N retries. The endpoint also stored Extension without the leading dot, so every web-uploaded picture was unreachable through get-image/{fileName}.{ext} and invisible in the compliance image gallery. Backend (EFormFilesController.AddNewImage) - Thumbnails are derived to temp names before any row is written; the UploadedData/FieldValue creation and the storage puts run in one transaction. The SDK context uses EnableRetryOnFailure, so the unit runs through Database.CreateExecutionStrategy().ExecuteAsync with the entities built inside the delegate and the change tracker cleared on entry, so a transient retry cannot re-insert a failed attempt. - Idempotency guard: a non-removed FieldValue on (CaseId, FieldId) whose non-removed UploadedData has the same checksum returns success without creating anything; delete-then-reupload still creates a new row. - Extension stored with the leading dot (".png"); FileName keeps the {id}_{md5}.{ext} shape. - Temp files (GUID stem) removed in finally on every path; the exception is logged with its stack trace and structured case/field ids. - SharedResource.resx / .da.resx: ErrorWhileUpdateImage and ImageNotFound (neutral English + real Danish); no placeholders in other locales. Frontend (element-picture) - Host buttons and the dialog's Save are locked while the upload is in flight; released on success:false and on error so the user can retry. - "Add new image" label translated; AddPictureDialog renders Cancel before Save. Verified against the local dev stack: dotted extension, idempotent re-POST, thumbnails served under the dotted names, delete-then-reupload creates a new row, corrupt file yields the translated error with no rows and no temp leftovers. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_018qJL2WhHwhZ5CGZehZF2ro --- .../Eforms/EFormFilesController.cs | 185 +++++++++++++----- .../Resources/SharedResource.da.resx | 6 + .../Resources/SharedResource.resx | 6 + .../element-picture.component.html | 2 +- .../element-picture.component.ts | 64 ++++-- 5 files changed, 195 insertions(+), 68 deletions(-) diff --git a/eFormAPI/eFormAPI.Web/Controllers/Eforms/EFormFilesController.cs b/eFormAPI/eFormAPI.Web/Controllers/Eforms/EFormFilesController.cs index 4aadc8923d..243e3879d7 100644 --- a/eFormAPI/eFormAPI.Web/Controllers/Eforms/EFormFilesController.cs +++ b/eFormAPI/eFormAPI.Web/Controllers/Eforms/EFormFilesController.cs @@ -260,6 +260,11 @@ public async Task UpdateImage([FromForm]int uploadedObjId) [Authorize(Policy = AuthConsts.EformPolicies.Cases.CaseUpdate)] public async Task AddNewImage([FromForm] int fieldId, [FromForm] int caseId) { + // Temp files are tracked outside the try so they are always removed in + // finally, on success and on failure alike. + string filePath = null; + string smallTempPath = null; + string bigTempPath = null; try { var newFile = HttpContext.Request.Form.Files.Last(); @@ -279,7 +284,12 @@ public async Task AddNewImage([FromForm] int fieldId, [FromForm var folder = Path.Combine(Path.GetTempPath(), "cases-temp-files"); Directory.CreateDirectory(folder); - var filePath = Path.Combine(folder, $"{DateTime.Now.Ticks}.{newFile.FileName.Split(".").Last()}"); + // Collision-free temp stem (concurrent uploads within the same tick + // would otherwise share names). Temp names never reach storage keys + // or the DB. + var stem = Guid.NewGuid().ToString("N"); + var ext = newFile.FileName.Split(".").Last(); + filePath = Path.Combine(folder, $"{stem}.{ext}"); string hash; using (var md5 = MD5.Create()) { @@ -297,72 +307,139 @@ public async Task AddNewImage([FromForm] int fieldId, [FromForm } } - var newUploadData = new Microting.eForm.Infrastructure.Data.Entities.UploadedData - { - Checksum = hash, - FileName = $"{hash}.{newFile.FileName.Split(".").Last()}", - FileLocation = filePath, - Extension = newFile.FileName.Split(".").Last() - }; - await newUploadData.Create(sdkDbContext); - newUploadData.FileName = $"{newUploadData.Id}_{newUploadData.FileName}"; - await newUploadData.Update(sdkDbContext); - - await core.PutFileToStorageSystem(filePath, newUploadData.FileName); - var fieldValue = new Microting.eForm.Infrastructure.Data.Entities.FieldValue + // Idempotency guard: the same file already attached to this + // (case, field) is a no-op success. Both "not removed" conditions + // are load-bearing — DeleteImage soft-deletes only the + // UploadedData, so a delete-then-reupload must create a new row. + var alreadyAttached = await sdkDbContext.FieldValues + .Where(x => x.WorkflowState != Constants.WorkflowStates.Removed) + .Where(x => x.CaseId == caseDb.Id) + .Where(x => x.FieldId == field.Id) + .Where(x => x.UploadedDataId != null) + .AnyAsync(x => x.UploadedData.WorkflowState != Constants.WorkflowStates.Removed + && x.UploadedData.Checksum == hash); + if (alreadyAttached) { - FieldId = field.Id, - CaseId = caseDb.Id, - CheckListId = field.CheckListId, - WorkerId = caseDb.WorkerId, - DoneAt = DateTime.UtcNow, - UploadedDataId = newUploadData.Id - }; + return new OperationResult(true, localizationService.GetString("ImageUpdatedSuccessfully")); + } - await fieldValue.Create(sdkDbContext); - string smallFilename = $"{newUploadData.Id}_300_{newUploadData.Checksum}{newUploadData.Extension}"; //uploadedDataObj.Id + "_300_" + uploadedDataObj.Checksum; - string bigFilename = $"{newUploadData.Id}_700_{newUploadData.Checksum}{newUploadData.Extension}";//uploadedDataObj.Id + "_700_" + urlStr.Remove(0, index); - System.IO.File.Copy(filePath, Path.Combine(Path.GetTempPath(), smallFilename)); - System.IO.File.Copy(filePath, Path.Combine(Path.GetTempPath(), bigFilename)); - string filePathResized = Path.Combine(Path.GetTempPath(), smallFilename); - using (var image = new MagickImage(filePathResized)) + // Derive the 300/700 thumbnails to temp names BEFORE opening the + // transaction: this is the failure-prone step (MagickImage), and + // failing here leaves no rows behind. The id-based storage keys + // are applied at upload time below. + smallTempPath = Path.Combine(folder, $"{stem}_300.{ext}"); + bigTempPath = Path.Combine(folder, $"{stem}_700.{ext}"); + await ResizeToWidth(filePath, smallTempPath, 300); + await ResizeToWidth(filePath, bigTempPath, 700); + + // Everything that persists rows runs inside one transaction on the + // same sdkDbContext (PnBase.Create/Update call SaveChangesAsync on + // the passed context, so they join it). The storage keys embed the + // row id, so the rows cannot be created after the uploads. + // + // The SDK's MicrotingDbContextFactory configures EnableRetryOnFailure, + // i.e. a retrying execution strategy. With such a strategy EF Core + // rejects a user-initiated BeginTransactionAsync at the first + // SaveChangesAsync ("does not support user-initiated transactions"), + // so the whole unit must run through CreateExecutionStrategy(). + // The delegate may be re-executed on a transient failure, so it + // must be re-runnable: the entities are constructed inside it + // (re-adding an already-tracked instance would misbehave), the + // change tracker is cleared on entry so a previous attempt's + // entity left tracked as Added by a failed SaveChangesAsync is not + // re-inserted, and the storage uploads are overwrite-idempotent + // (an orphaned object from an aborted attempt is harmless). + // `await using` on the transaction rolls back on dispose when + // CommitAsync was not reached. + var strategy = sdkDbContext.Database.CreateExecutionStrategy(); + await strategy.ExecuteAsync(async () => { - decimal currentRation = image.Height / (decimal) image.Width; - int newWidth = 300; - int newHeight = (int) Math.Round((currentRation * newWidth)); + // A retried attempt must not re-insert the previous attempt's entity: a + // failed SaveChangesAsync leaves it tracked as Added. caseDb/field become + // detached, which is fine — only their scalar values are used below. + sdkDbContext.ChangeTracker.Clear(); - image.Resize((uint)newWidth, (uint)newHeight); - image.Crop((uint)newWidth, (uint)newHeight); - await image.WriteAsync(filePathResized); - image.Dispose(); - await core.PutFileToStorageSystem(Path.Combine(Path.GetTempPath(), filePathResized), smallFilename); + await using var tx = await sdkDbContext.Database.BeginTransactionAsync(); - } - System.IO.File.Delete(filePathResized); - filePathResized = Path.Combine(Path.GetTempPath(), bigFilename); - using (var image = new MagickImage(filePathResized)) - { - decimal currentRation = image.Height / (decimal) image.Width; - int newWidth = 700; - int newHeight = (int) Math.Round((currentRation * newWidth)); - - image.Resize((uint)newWidth, (uint)newHeight); - image.Crop((uint)newWidth, (uint)newHeight); - await image.WriteAsync(filePathResized); - image.Dispose(); - await core.PutFileToStorageSystem(Path.Combine(Path.GetTempPath(), filePathResized), bigFilename); - } - System.IO.File.Delete(filePathResized); + var newUploadData = new Microting.eForm.Infrastructure.Data.Entities.UploadedData + { + Checksum = hash, + FileName = $"{hash}.{ext}", + FileLocation = filePath, + // Stored WITH the leading dot, matching the SDK device path and + // the plugin upload path, so the derived thumbnail names + // {id}_300_{md5}{Extension} / {id}_700_{md5}{Extension} match + // the get-image/{fileName}.{ext} route. + Extension = $".{ext}" + }; + await newUploadData.Create(sdkDbContext); + newUploadData.FileName = $"{newUploadData.Id}_{newUploadData.FileName}"; + await newUploadData.Update(sdkDbContext); + + var fieldValue = new Microting.eForm.Infrastructure.Data.Entities.FieldValue + { + FieldId = field.Id, + CaseId = caseDb.Id, + CheckListId = field.CheckListId, + WorkerId = caseDb.WorkerId, + DoneAt = DateTime.UtcNow, + UploadedDataId = newUploadData.Id + }; + await fieldValue.Create(sdkDbContext); + + string smallFilename = $"{newUploadData.Id}_300_{newUploadData.Checksum}{newUploadData.Extension}"; + string bigFilename = $"{newUploadData.Id}_700_{newUploadData.Checksum}{newUploadData.Extension}"; + await core.PutFileToStorageSystem(filePath, newUploadData.FileName); + await core.PutFileToStorageSystem(smallTempPath, smallFilename); + await core.PutFileToStorageSystem(bigTempPath, bigFilename); + + await tx.CommitAsync(); + }); return new OperationResult(true, localizationService.GetString("ImageUpdatedSuccessfully")); } catch (Exception e) { SentrySdk.CaptureException(e); - logger.LogError(e.Message); - logger.LogTrace(e.StackTrace); + logger.LogError(e, "AddNewImage failed for case {CaseId} field {FieldId}", caseId, fieldId); return new OperationResult(false, localizationService.GetString("ErrorWhileUpdateImage")); } + finally + { + TryDeleteTempFile(filePath); + TryDeleteTempFile(smallTempPath); + TryDeleteTempFile(bigTempPath); + } + } + + private static async Task ResizeToWidth(string sourcePath, string targetPath, int newWidth) + { + using var image = new MagickImage(sourcePath); + decimal currentRation = image.Height / (decimal) image.Width; + int newHeight = (int) Math.Round((currentRation * newWidth)); + + image.Resize((uint)newWidth, (uint)newHeight); + image.Crop((uint)newWidth, (uint)newHeight); + await image.WriteAsync(targetPath); + } + + private void TryDeleteTempFile(string path) + { + if (string.IsNullOrEmpty(path)) + { + return; + } + try + { + if (System.IO.File.Exists(path)) + { + System.IO.File.Delete(path); + } + } + catch (Exception e) + { + logger.LogWarning(e, "Could not delete temp file {Path}", path); + } } [HttpGet] diff --git a/eFormAPI/eFormAPI.Web/Resources/SharedResource.da.resx b/eFormAPI/eFormAPI.Web/Resources/SharedResource.da.resx index e84f276346..2eb32cda6d 100644 --- a/eFormAPI/eFormAPI.Web/Resources/SharedResource.da.resx +++ b/eFormAPI/eFormAPI.Web/Resources/SharedResource.da.resx @@ -689,6 +689,12 @@ Nyt billede tilføjet + + Fejl under upload af billede + + + Billedet blev ikke fundet + Mapper diff --git a/eFormAPI/eFormAPI.Web/Resources/SharedResource.resx b/eFormAPI/eFormAPI.Web/Resources/SharedResource.resx index 80835030d3..e1256b6f91 100644 --- a/eFormAPI/eFormAPI.Web/Resources/SharedResource.resx +++ b/eFormAPI/eFormAPI.Web/Resources/SharedResource.resx @@ -747,6 +747,12 @@ Image updated successfully + + Error while uploading image + + + Image not found + Folders diff --git a/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-elements/element-picture/element-picture.component.html b/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-elements/element-picture/element-picture.component.html index 8c589c935a..742ca5b475 100644 --- a/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-elements/element-picture/element-picture.component.html +++ b/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-elements/element-picture/element-picture.component.html @@ -6,7 +6,7 @@ (click)="openAddImage()" > add - Add new image + {{ 'Add new image' | translate }}
diff --git a/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-elements/element-picture/element-picture.component.ts b/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-elements/element-picture/element-picture.component.ts index eac5818954..bdab8fff53 100644 --- a/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-elements/element-picture/element-picture.component.ts +++ b/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-elements/element-picture/element-picture.component.ts @@ -168,15 +168,43 @@ export class ElementPictureComponent implements OnChanges, OnDestroy { } } + /** + * Uploads the picked file. The host's buttons and the dialog's Save are + * locked for the whole request so a fast double-click, or a click after an + * error, can never re-POST the same File while one is in flight; the lock is + * released on failure so the user can retry, and on success the dialog + * closes and the host reloads its pictures. + */ addPicture(newImage: File, modalId: string) { + if (this.buttonsLocked) { + return; + } const fieldId = this.fieldId; + const dialogRef = this.dialog.getDialogById(modalId) as MatDialogRef | undefined; + const dialogComponent = dialogRef?.componentInstance; + const release = () => { + this.buttonsLocked = false; + if (dialogComponent) { + dialogComponent.saving = false; + } + }; + this.buttonsLocked = true; + if (dialogComponent) { + dialogComponent.saving = true; + } this.addImageSub$ = this.imageService .addNewImage(fieldId, this.resolvedCaseId, newImage) - .subscribe(data => { - if (data && data.success) { - this.dialog.getDialogById(modalId).close(); - this.pictureUpdated.emit();// fetch the new image - } + .subscribe({ + next: data => { + if (data && data.success) { + this.buttonsLocked = false; + dialogRef?.close(); + this.pictureUpdated.emit();// fetch the new image + } else { + release(); + } + }, + error: () => release(), }); } @@ -229,19 +257,19 @@ export class ElementPictureComponent implements OnChanges, OnDestroy {
- +
`, standalone: false }) @@ -250,9 +278,19 @@ export class AddPictureDialogComponent { addedPicture: EventEmitter = new EventEmitter(); image: File; + /** + * True while the host is uploading the picked file. Set and cleared by the + * host (ElementPictureComponent.addPicture); disables Save so the same File + * cannot be submitted twice, and is cleared again on failure so the user can + * retry. + */ + saving = false; onAddPicture() { - this.addedPicture.emit(this.image) + if (!this.image || this.saving) { + return; + } + this.addedPicture.emit(this.image); } From c7aeca3f6973ca5ad1bebe40f7d9b424e54145da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Schultz=20Madsen?= Date: Wed, 9 Sep 2026 11:35:14 +0200 Subject: [PATCH 13/15] ci: point the Jest job at test:ci, and correct TESTING.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `eform-client/TESTING.md` already documented `npm run test:ci` as what CI runs. The workflows were the outlier, still calling `test:unit`. This aligns them. Behaviourally this is close to neutral, and the doc now says so rather than implying a benefit: - `--ci` is redundant on GitHub Actions. Jest auto-detects CI via `ci-info`, which lists `GITHUB_ACTIONS`; `jest --showConfig` resolves `ci: true` on a runner without the flag. It is kept so the script stays correct anywhere detection does not fire. - `--maxWorkers=2` is dropped from `test:ci`. It was an unexamined number, and it buys nothing measurable: benchmarked on a 4-CPU cgroup with a cold cache, capped 239.7s vs default 240.1s over two runs each, with the two modes trading places. Peak RSS ~2.65 GB against 16 GB either way. Removing it lets Jest scale to whatever runner the repo gets rather than pinning a constant nobody re-measured. TESTING.md's CI section now names the job and both workflow files, states the worker behaviour accurately, and records that these jobs are not required status checks — so a failing Jest run does not by itself block a merge. Not fixed here, but worth knowing: the rest of TESTING.md is stale in ways a consistency touch-up should not absorb. Its entire "Testing Patterns" section teaches Jasmine (`jasmine.createSpyObj`), which no spec in the repo uses and `src/setup-jest.ts` explicitly says was removed; "Common Issues" and "Debugging Tests" reference `karma.conf.js` and `src/test.ts`, neither of which exists. That is a doc rewrite, not this change. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_018qJL2WhHwhZ5CGZehZF2ro --- .github/workflows/dotnet-core-master.yml | 2 +- .github/workflows/dotnet-core-pr.yml | 2 +- eform-client/TESTING.md | 14 +++++++++++--- eform-client/package.json | 2 +- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/.github/workflows/dotnet-core-master.yml b/.github/workflows/dotnet-core-master.yml index ff0cf4d0b8..47e70f5d87 100644 --- a/.github/workflows/dotnet-core-master.yml +++ b/.github/workflows/dotnet-core-master.yml @@ -138,7 +138,7 @@ jobs: - name: Install dependencies run: cd eform-client && yarn install --frozen-lockfile - name: Run Jest unit tests - run: cd eform-client && npm run test:unit + run: cd eform-client && npm run test:ci test-dotnet: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/dotnet-core-pr.yml b/.github/workflows/dotnet-core-pr.yml index 2c7dc44668..75f8f06a50 100644 --- a/.github/workflows/dotnet-core-pr.yml +++ b/.github/workflows/dotnet-core-pr.yml @@ -138,7 +138,7 @@ jobs: - name: Install dependencies run: cd eform-client && yarn install --frozen-lockfile - name: Run Jest unit tests - run: cd eform-client && npm run test:unit + run: cd eform-client && npm run test:ci test-dotnet: runs-on: ubuntu-latest steps: diff --git a/eform-client/TESTING.md b/eform-client/TESTING.md index 9665892281..f3eaee0503 100644 --- a/eform-client/TESTING.md +++ b/eform-client/TESTING.md @@ -318,7 +318,8 @@ ng test --include='**/component-name.component.spec.ts' ## Continuous Integration -Tests are automatically run in GitHub Actions workflows using Jest: +Tests are automatically run in GitHub Actions workflows using Jest. The `test-angular-unit` job in +`.github/workflows/dotnet-core-pr.yml` and `.github/workflows/dotnet-core-master.yml` runs: ```bash # Run tests in CI/CD mode @@ -327,8 +328,15 @@ npm run test:ci Jest is configured with: - Code coverage reporting -- CI-specific optimizations (--ci flag) -- Maximum of 2 workers for parallel execution +- CI-specific optimizations (the `--ci` flag). Jest auto-detects GitHub Actions, so the flag is + redundant there, but it keeps the script correct if it is ever run somewhere without CI + detection. +- Jest's default worker count. There is no explicit `--maxWorkers` cap, so Jest scales to + whatever runner it gets (3 workers on the current 4-vCPU `ubuntu-latest` runner). Capping at 2 + measured no faster on this suite, so there is no cap left to maintain. + +These jobs are not configured as required status checks, so a failing Jest run does not by itself +block a merge. ## Contributing diff --git a/eform-client/package.json b/eform-client/package.json index c63716580c..bd11d3fa83 100644 --- a/eform-client/package.json +++ b/eform-client/package.json @@ -13,7 +13,7 @@ "start-port": "ng serve --port 4300 --proxy-config proxy.config.json", "build": "ng build", "sentrysourcemap": "yarn sentry:sourcemaps", - "test:ci": "jest --ci --coverage --maxWorkers=2", + "test:ci": "jest --ci --coverage", "test:unit": "jest --coverage", "test:local_unit": "jest --watch", "test:watch": "jest --watch", From 7f4c34ab6b0117cd21fd065cf55e90c9a53285e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Schultz=20Madsen?= Date: Fri, 11 Sep 2026 19:28:58 +0200 Subject: [PATCH 14/15] fix(i18n): restore the {{value}} token in 'Generate report' for da, fiFI, huHU (#8058) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit enUS is 'Generate {{value}} report'. Three locales lost the token, so ngx-translate finds no matching parameter and the report name never renders. Two are the translated-placeholder-name bug: fiFI had '{{arvo}}' and huHU '{{érték}}' — an earlier translateTsFiles.py run sent the whole string to translation including the {{...}} tokens, so the placeholder NAMES were translated and ngx-translate renders the braces literally. Same defect as microting/eform-backendconfiguration-plugin#1181, which fixed 28 such entries in that plugin's i18n. Danish is the worse shape: {{value}} had been dropped ENTIRELY, translated away into plain prose ('Generer rapport'). There was nothing to substitute, so the token had to be reinserted. Placed as a compound modifier, 'Generer {{value}}-rapport', matching noNO for the same key — Danish and Norwegian take the same compound-noun hyphen here. A dropped token has no textual signature; only a token-multiset comparison against enUS finds it. Hungarian keeps its own word order — the token legitimately leads there, so only the name was substituted. All 26 shipped locales now carry the token. The only files without it are translates.ts and template.ts, the latter being an unimported scaffold whose values are all empty strings. Found by running the i18n guard added in eform-backendconfiguration-plugin across every i18n directory in the workspace. Note that guard hardcodes 'enUS.ts' as its reference and has no JSON support, so 24 of 29 directories abort before checking anything — it needs widening before it can be reused outside the plugin it was written for. Claude-Session: https://claude.ai/code/session_018qJL2WhHwhZ5CGZehZF2ro Co-authored-by: Claude Opus 5 --- eform-client/src/assets/i18n/da.ts | 2 +- eform-client/src/assets/i18n/fiFI.ts | 2 +- eform-client/src/assets/i18n/huHU.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/eform-client/src/assets/i18n/da.ts b/eform-client/src/assets/i18n/da.ts index ba4dc1f175..9c43cea4c7 100644 --- a/eform-client/src/assets/i18n/da.ts +++ b/eform-client/src/assets/i18n/da.ts @@ -362,7 +362,7 @@ export const da = { 'Security groups': 'Sikkerhedsgrupper', Custom: 'Brugerdefinerede', Add: 'Tilføj', - 'Generate report': 'Generer rapport', + 'Generate report': 'Generer {{value}}-rapport', 'Select date range': 'Vælg periode', 'Edit headers': 'Redigér overskrifter', Generate: 'Vis rapport', diff --git a/eform-client/src/assets/i18n/fiFI.ts b/eform-client/src/assets/i18n/fiFI.ts index 57832be463..d0ab7ab211 100644 --- a/eform-client/src/assets/i18n/fiFI.ts +++ b/eform-client/src/assets/i18n/fiFI.ts @@ -356,7 +356,7 @@ export const fiFI = { 'Security groups': 'Turvaryhmät', Custom: 'Mukautettu', Add: 'Lisätä', - 'Generate report': 'Luo {{arvo}} raportti', + 'Generate report': 'Luo {{value}} raportti', 'Select date range': 'Valitse ajanjakso', 'Edit headers': 'Muokkaa otsikoita', 'Text (TextSingleLine)': 'Teksti yksirivinen', diff --git a/eform-client/src/assets/i18n/huHU.ts b/eform-client/src/assets/i18n/huHU.ts index 8bba82fb62..ba4cbc8d97 100644 --- a/eform-client/src/assets/i18n/huHU.ts +++ b/eform-client/src/assets/i18n/huHU.ts @@ -356,7 +356,7 @@ export const huHU = { 'Security groups': 'Biztonsági csoportok', Custom: 'Egyedi', Add: 'Hozzáadás', - 'Generate report': '{{érték}} jelentés létrehozása', + 'Generate report': '{{value}} jelentés létrehozása', 'Select date range': 'Válassza ki a dátumtartományt', 'Edit headers': 'Szerkessze a fejléceket', 'Text (TextSingleLine)': 'Szöveg egysoros', From 5b7dac9900ee20ec3e76632947125d455c1d7c0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Schultz=20Madsen?= Date: Sat, 12 Sep 2026 07:58:57 +0200 Subject: [PATCH 15/15] feat(device-users): only the first user can delete a device user (#8059) Only the first user (lowest id), who also holds the delete claim, can delete a device user or a site. DeviceUsersService.Delete and SitesService.Delete share one check (FirstUserHelper) and refuse anyone else before any SDK call. The /device-users and Sites delete items show only for that user. RefreshToken now returns IsFirstUser like login does. Edit and New OTP on /device-users follow the update claim. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_014Wuv2gtqY9BLMLWBZiBM2Y --- .../Services/AuthServiceTests.cs | 54 +++++++- .../Services/DeviceUsersServiceDeleteTests.cs | 49 +++++++ .../Services/DeviceUsersServiceTests.cs | 2 + .../Services/FirstUserOnlyDeleteTestsBase.cs | 128 ++++++++++++++++++ .../Services/SitesServiceDeleteTests.cs | 49 +++++++ .../Services/SitesServiceTests.cs | 2 + .../Infrastructure/Helpers/FirstUserHelper.cs | 41 ++++++ .../Resources/SharedResource.bg.resx | 3 + .../Resources/SharedResource.cs.resx | 3 + .../Resources/SharedResource.da.resx | 3 + .../Resources/SharedResource.de.resx | 3 + .../Resources/SharedResource.el.resx | 3 + .../Resources/SharedResource.es.resx | 3 + .../Resources/SharedResource.et.resx | 3 + .../Resources/SharedResource.fi.resx | 3 + .../Resources/SharedResource.fr.resx | 3 + .../Resources/SharedResource.hr.resx | 3 + .../Resources/SharedResource.hu.resx | 3 + .../Resources/SharedResource.is.resx | 3 + .../Resources/SharedResource.it.resx | 3 + .../Resources/SharedResource.lt.resx | 3 + .../Resources/SharedResource.lv.resx | 3 + .../Resources/SharedResource.nl.resx | 3 + .../Resources/SharedResource.no.resx | 3 + .../Resources/SharedResource.pl.resx | 3 + .../Resources/SharedResource.pt.resx | 3 + .../Resources/SharedResource.resx | 3 + .../Resources/SharedResource.ro.resx | 3 + .../Resources/SharedResource.sk.resx | 3 + .../Resources/SharedResource.sl.resx | 3 + .../Resources/SharedResource.sv.resx | 3 + .../Resources/SharedResource.uk.resx | 3 + eFormAPI/eFormAPI.Web/Services/AuthService.cs | 3 + .../Services/DeviceUsersService.cs | 9 ++ .../eFormAPI.Web/Services/SitesService.cs | 9 ++ .../sites/sites/sites.component.html | 6 +- .../sites/sites/sites.component.spec.ts | 95 +++++++++++++ .../components/sites/sites/sites.component.ts | 21 ++- .../device-users-page.component.html | 6 +- .../device-users-page.component.spec.ts | 104 ++++++++++++++ .../device-users-page.component.ts | 35 ++--- eform-client/src/test-helpers.ts | 57 +++++++- 42 files changed, 712 insertions(+), 33 deletions(-) create mode 100644 eFormAPI/eFormAPI.Web.Integration.Tests/Services/DeviceUsersServiceDeleteTests.cs create mode 100644 eFormAPI/eFormAPI.Web.Integration.Tests/Services/FirstUserOnlyDeleteTestsBase.cs create mode 100644 eFormAPI/eFormAPI.Web.Integration.Tests/Services/SitesServiceDeleteTests.cs create mode 100644 eFormAPI/eFormAPI.Web/Infrastructure/Helpers/FirstUserHelper.cs create mode 100644 eform-client/src/app/modules/advanced/components/sites/sites/sites.component.spec.ts create mode 100644 eform-client/src/app/modules/device-users/components/device-users-page/device-users-page.component.spec.ts diff --git a/eFormAPI/eFormAPI.Web.Integration.Tests/Services/AuthServiceTests.cs b/eFormAPI/eFormAPI.Web.Integration.Tests/Services/AuthServiceTests.cs index 74ca3c864c..6d26fb5b2f 100644 --- a/eFormAPI/eFormAPI.Web.Integration.Tests/Services/AuthServiceTests.cs +++ b/eFormAPI/eFormAPI.Web.Integration.Tests/Services/AuthServiceTests.cs @@ -22,6 +22,11 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +using System; +using System.Collections.Generic; +using System.Security.Claims; +using System.Security.Cryptography; +using System.Threading.Tasks; using NUnit.Framework; using eFormAPI.Web.Services; using Microsoft.Extensions.Logging; @@ -48,6 +53,10 @@ public class AuthServiceTests : DbTestFixture private ILocalizationService _localizationService; private IClaimsService _claimsService; private IAuthCacheService _authCacheService; +#pragma warning disable NUnit1032 + private UserManager _userManager; + private RoleManager _roleManager; +#pragma warning restore NUnit1032 private AuthService _authService; public override void DoSetup() @@ -60,7 +69,7 @@ public override void DoSetup() _claimsService = Substitute.For(); _authCacheService = Substitute.For(); - var roleManager = Substitute.For>( + _roleManager = Substitute.For>( Substitute.For>(), null, null, null, null); var signInManager = Substitute.For>( Substitute.For>( @@ -68,7 +77,7 @@ public override void DoSetup() Substitute.For(), Substitute.For>(), null, null, null, null); - var userManager = Substitute.For>( + _userManager = Substitute.For>( Substitute.For>(), null, null, null, null, null, null, null, null); _localizationService.GetString(Arg.Any()) @@ -81,9 +90,9 @@ public override void DoSetup() _tokenOptions, _logger, _appSettings, - roleManager, + _roleManager, signInManager, - userManager, + _userManager, _userService, _localizationService, _claimsService, @@ -96,5 +105,42 @@ public void AuthService_InitializesCorrectly() // Assert Assert.That(_authService, Is.Not.Null); } + + // A refreshed token must say whether the caller is the first user, exactly as Login + // does: the client replaces its stored user with the refresh result, so a missing + // flag would drop the first user's first-user-only actions after every refresh. + [TestCase(1, true)] + [TestCase(2, false)] + public async Task RefreshToken_ReportsWhetherTheCallerIsTheFirstUser(int userId, bool expectedIsFirstUser) + { + // Arrange + const int firstUserId = 1; + var user = new EformUser + { + Id = userId, + UserName = $"user{userId}@example.com", + Email = $"user{userId}@example.com" + }; + _userService.UserId.Returns(userId); + _userService.GetByIdAsync(userId).Returns(user); + _userService.GetFirstUserIdInDb().Returns(firstUserId); + _userManager.GetRolesAsync(user).Returns(new List { EformRole.Admin }); + _userManager.GetClaimsAsync(user).Returns(new List()); + _roleManager.FindByNameAsync(Arg.Any()).Returns((EformRole)null); + _claimsService.GetUserPermissions(userId, Arg.Any()).Returns(new List()); + // HMAC-SHA256 needs a key of at least 256 bits; generate one per run. + _tokenOptions.Value.Returns(new EformTokenOptions + { + Issuer = "eform-tests", + SigningKey = Convert.ToHexString(RandomNumberGenerator.GetBytes(32)) + }); + + // Act + var result = await _authService.RefreshToken(); + + // Assert + Assert.That(result.Success, Is.True, result.Message); + Assert.That(result.Model.IsFirstUser, Is.EqualTo(expectedIsFirstUser)); + } } } diff --git a/eFormAPI/eFormAPI.Web.Integration.Tests/Services/DeviceUsersServiceDeleteTests.cs b/eFormAPI/eFormAPI.Web.Integration.Tests/Services/DeviceUsersServiceDeleteTests.cs new file mode 100644 index 0000000000..cff7a2b17c --- /dev/null +++ b/eFormAPI/eFormAPI.Web.Integration.Tests/Services/DeviceUsersServiceDeleteTests.cs @@ -0,0 +1,49 @@ +/* +The MIT License (MIT) + +Copyright (c) 2007 - 2021 Microting A/S + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +using System.Threading.Tasks; +using NUnit.Framework; +using eFormAPI.Web.Services; +using Microsoft.Extensions.Logging; +using NSubstitute; +using Microting.eFormApi.BasePn.Infrastructure.Models.API; + +namespace eFormAPI.Web.Integration.Tests.Services +{ + /// + /// The first-user delete rule on DELETE api/device-users/delete/{id}. + /// + [TestFixture] + public class DeviceUsersServiceDeleteTests : FirstUserOnlyDeleteTestsBase + { + protected override Task DeleteAsync(int id) => + new DeviceUsersService( + LocalizationService, + CoreHelper, + UserService, + Substitute.For>()).Delete(id); + + protected override string SdkFailureMessageKey => "DeviceUserParamCouldNotBeDeleted"; + } +} diff --git a/eFormAPI/eFormAPI.Web.Integration.Tests/Services/DeviceUsersServiceTests.cs b/eFormAPI/eFormAPI.Web.Integration.Tests/Services/DeviceUsersServiceTests.cs index 06bcb92d67..dea45c8f34 100644 --- a/eFormAPI/eFormAPI.Web.Integration.Tests/Services/DeviceUsersServiceTests.cs +++ b/eFormAPI/eFormAPI.Web.Integration.Tests/Services/DeviceUsersServiceTests.cs @@ -44,6 +44,7 @@ public override void DoSetup() { _localizationService = Substitute.For(); _coreHelper = Substitute.For(); + var userService = Substitute.For(); _logger = Substitute.For>(); _localizationService.GetString(Arg.Any()) @@ -52,6 +53,7 @@ public override void DoSetup() _deviceUsersService = new DeviceUsersService( _localizationService, _coreHelper, + userService, _logger); } diff --git a/eFormAPI/eFormAPI.Web.Integration.Tests/Services/FirstUserOnlyDeleteTestsBase.cs b/eFormAPI/eFormAPI.Web.Integration.Tests/Services/FirstUserOnlyDeleteTestsBase.cs new file mode 100644 index 0000000000..f5da231827 --- /dev/null +++ b/eFormAPI/eFormAPI.Web.Integration.Tests/Services/FirstUserOnlyDeleteTestsBase.cs @@ -0,0 +1,128 @@ +/* +The MIT License (MIT) + +Copyright (c) 2007 - 2021 Microting A/S + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +using System; +using System.Threading.Tasks; +using NUnit.Framework; +using NSubstitute; +using Microting.eFormApi.BasePn.Abstractions; +using Microting.eFormApi.BasePn.Infrastructure.Models.API; +using eFormAPI.Web.Abstractions; + +namespace eFormAPI.Web.Integration.Tests.Services +{ + /// + /// Only the first user (lowest AspNetUsers Id) may delete a device user, on every path + /// that removes its Site row; everyone else, admins included, is refused before the SDK + /// is touched. Each subclass points at one delete path and + /// inherits these tests. They never touch the database, so they avoid DbTestFixture. + /// + public abstract class FirstUserOnlyDeleteTestsBase + { + private const int FirstUserId = 1; + private const int OtherUserId = 2; + private const int SiteId = 42; + + protected ILocalizationService LocalizationService; + protected IEFormCoreService CoreHelper; + protected IUserService UserService; + + /// Calls the delete path under test with fresh service wiring. + protected abstract Task DeleteAsync(int id); + + /// The key the service reports when the (faked) SDK delete fails. + protected abstract string SdkFailureMessageKey { get; } + + [SetUp] + public void SetUpSubstitutes() + { + LocalizationService = Substitute.For(); + CoreHelper = Substitute.For(); + UserService = Substitute.For(); + + LocalizationService.GetString(Arg.Any()) + .Returns(args => args.Arg()); + LocalizationService.GetStringWithFormat(Arg.Any(), Arg.Any()) + .Returns(args => args.ArgAt(0)); + + UserService.GetFirstUserIdInDb().Returns(FirstUserId); + + // Every SDK call in Delete goes through the core instance, so a GetCore that + // throws marks the point where the SDK path is entered. + CoreHelper.GetCore() + .Returns(_ => throw new InvalidOperationException("SDK reached")); + } + + [Test] + public async Task Delete_ByAdminWhoIsNotTheFirstUser_IsRefusedWithoutReachingTheSdk() + { + // Arrange + UserService.UserId.Returns(OtherUserId); + // Admin status is deliberately irrelevant: an admin who is not the first user is refused. + UserService.IsAdmin().Returns(true); + + // Act + var result = await DeleteAsync(SiteId); + + // Assert + Assert.That(result.Success, Is.False); + Assert.That(result.Message, Is.EqualTo("OnlyTheFirstUserCanDeleteWorkers")); + // The SDK delete is only reachable through GetCore. + await CoreHelper.DidNotReceive().GetCore(); + } + + [Test] + public async Task Delete_WithoutAUserIdAgainstAnEmptyUsersTable_IsRefusedWithoutReachingTheSdk() + { + // Arrange: no signed-in user (UserId 0) and no users, so GetFirstUserIdInDb is 0 too. + UserService.UserId.Returns(0); + UserService.GetFirstUserIdInDb().Returns(0); + + // Act + var result = await DeleteAsync(SiteId); + + // Assert + Assert.That(result.Success, Is.False); + Assert.That(result.Message, Is.EqualTo("OnlyTheFirstUserCanDeleteWorkers")); + await CoreHelper.DidNotReceive().GetCore(); + } + + [Test] + public async Task Delete_ByTheFirstUser_PassesTheCheckAndReachesTheSdk() + { + // Arrange + UserService.UserId.Returns(FirstUserId); + + // Act + var result = await DeleteAsync(SiteId); + + // Assert + await UserService.Received(1).GetFirstUserIdInDb(); + await CoreHelper.Received(1).GetCore(); + // The faked SDK fails, so the result is the SDK-failure message, not the refusal. + Assert.That(result.Success, Is.False); + Assert.That(result.Message, Is.EqualTo(SdkFailureMessageKey)); + } + } +} diff --git a/eFormAPI/eFormAPI.Web.Integration.Tests/Services/SitesServiceDeleteTests.cs b/eFormAPI/eFormAPI.Web.Integration.Tests/Services/SitesServiceDeleteTests.cs new file mode 100644 index 0000000000..c6ba5b5ff6 --- /dev/null +++ b/eFormAPI/eFormAPI.Web.Integration.Tests/Services/SitesServiceDeleteTests.cs @@ -0,0 +1,49 @@ +/* +The MIT License (MIT) + +Copyright (c) 2007 - 2021 Microting A/S + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +using System.Threading.Tasks; +using NUnit.Framework; +using eFormAPI.Web.Services; +using Microsoft.Extensions.Logging; +using NSubstitute; +using Microting.eFormApi.BasePn.Infrastructure.Models.API; + +namespace eFormAPI.Web.Integration.Tests.Services +{ + /// + /// The first-user delete rule on Advanced > Sites, which removes the same Site row. + /// + [TestFixture] + public class SitesServiceDeleteTests : FirstUserOnlyDeleteTestsBase + { + protected override Task DeleteAsync(int id) => + new SitesService( + CoreHelper, + LocalizationService, + UserService, + Substitute.For>()).Delete(id); + + protected override string SdkFailureMessageKey => "SiteParamCouldNotBeDeleted"; + } +} diff --git a/eFormAPI/eFormAPI.Web.Integration.Tests/Services/SitesServiceTests.cs b/eFormAPI/eFormAPI.Web.Integration.Tests/Services/SitesServiceTests.cs index 8769a76bcc..37d7e3cec6 100644 --- a/eFormAPI/eFormAPI.Web.Integration.Tests/Services/SitesServiceTests.cs +++ b/eFormAPI/eFormAPI.Web.Integration.Tests/Services/SitesServiceTests.cs @@ -43,6 +43,7 @@ public override void DoSetup() { _coreHelper = Substitute.For(); _localizationService = Substitute.For(); + var userService = Substitute.For(); _logger = Substitute.For>(); _localizationService.GetString(Arg.Any()) @@ -51,6 +52,7 @@ public override void DoSetup() _sitesService = new SitesService( _coreHelper, _localizationService, + userService, _logger); } diff --git a/eFormAPI/eFormAPI.Web/Infrastructure/Helpers/FirstUserHelper.cs b/eFormAPI/eFormAPI.Web/Infrastructure/Helpers/FirstUserHelper.cs new file mode 100644 index 0000000000..a8991666a6 --- /dev/null +++ b/eFormAPI/eFormAPI.Web/Infrastructure/Helpers/FirstUserHelper.cs @@ -0,0 +1,41 @@ +/* +The MIT License (MIT) + +Copyright (c) 2007 - 2021 Microting A/S + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +namespace eFormAPI.Web.Infrastructure.Helpers; + +using System.Threading.Tasks; +using Microting.eFormApi.BasePn.Abstractions; + +internal static class FirstUserHelper +{ + /// + /// The first user is the account with the lowest AspNetUsers Id. A caller without a + /// user id is never the first user, even when the users table is empty. + /// + public static async Task IsFirstUserAsync(this IUserService userService) + { + var userId = userService.UserId; + return userId > 0 && userId == await userService.GetFirstUserIdInDb(); + } +} diff --git a/eFormAPI/eFormAPI.Web/Resources/SharedResource.bg.resx b/eFormAPI/eFormAPI.Web/Resources/SharedResource.bg.resx index 20a338cb15..9f67c4af02 100644 --- a/eFormAPI/eFormAPI.Web/Resources/SharedResource.bg.resx +++ b/eFormAPI/eFormAPI.Web/Resources/SharedResource.bg.resx @@ -78,6 +78,9 @@ Потребителят на устройството „{0}“ е изтрит успешно + + Само първият потребител може да изтрива потребители на устройства + Потребителят на устройството е актуализиран успешно diff --git a/eFormAPI/eFormAPI.Web/Resources/SharedResource.cs.resx b/eFormAPI/eFormAPI.Web/Resources/SharedResource.cs.resx index 0eaf635506..a6c7188dc6 100644 --- a/eFormAPI/eFormAPI.Web/Resources/SharedResource.cs.resx +++ b/eFormAPI/eFormAPI.Web/Resources/SharedResource.cs.resx @@ -78,6 +78,9 @@ Uživatel zařízení „{0}“ byl úspěšně smazán + + Pouze první uživatel může mazat uživatele zařízení + Uživatel zařízení byl úspěšně aktualizován diff --git a/eFormAPI/eFormAPI.Web/Resources/SharedResource.da.resx b/eFormAPI/eFormAPI.Web/Resources/SharedResource.da.resx index 2eb32cda6d..1378149646 100644 --- a/eFormAPI/eFormAPI.Web/Resources/SharedResource.da.resx +++ b/eFormAPI/eFormAPI.Web/Resources/SharedResource.da.resx @@ -137,6 +137,9 @@ Medarbejder "{0}" slettet + + Kun den første bruger kan slette mobilbrugere + Medarbejder opdateret diff --git a/eFormAPI/eFormAPI.Web/Resources/SharedResource.de.resx b/eFormAPI/eFormAPI.Web/Resources/SharedResource.de.resx index 4bf3d07e14..236d41f4b5 100644 --- a/eFormAPI/eFormAPI.Web/Resources/SharedResource.de.resx +++ b/eFormAPI/eFormAPI.Web/Resources/SharedResource.de.resx @@ -137,6 +137,9 @@ Gerätebenutzer "{0}" wurde erfolgreich gelöscht + + Nur der erste Benutzer kann Gerätebenutzer löschen + Gerätebenutzer erfolgreich aktualisiert diff --git a/eFormAPI/eFormAPI.Web/Resources/SharedResource.el.resx b/eFormAPI/eFormAPI.Web/Resources/SharedResource.el.resx index eb8399b0f3..ad62bfd1d2 100644 --- a/eFormAPI/eFormAPI.Web/Resources/SharedResource.el.resx +++ b/eFormAPI/eFormAPI.Web/Resources/SharedResource.el.resx @@ -78,6 +78,9 @@ Ο χρήστης συσκευής "{0}" διαγράφηκε με επιτυχία + + Μόνο ο πρώτος χρήστης μπορεί να διαγράψει χρήστες συσκευής + Ο χρήστης συσκευής ενημερώθηκε με επιτυχία diff --git a/eFormAPI/eFormAPI.Web/Resources/SharedResource.es.resx b/eFormAPI/eFormAPI.Web/Resources/SharedResource.es.resx index 4ef4b9d6a8..5f9d68fbb0 100644 --- a/eFormAPI/eFormAPI.Web/Resources/SharedResource.es.resx +++ b/eFormAPI/eFormAPI.Web/Resources/SharedResource.es.resx @@ -78,6 +78,9 @@ Usuario del dispositivo "{0}" eliminado correctamente + + Solo el primer usuario puede eliminar usuarios del dispositivo + Usuario del dispositivo actualizado exitosamente diff --git a/eFormAPI/eFormAPI.Web/Resources/SharedResource.et.resx b/eFormAPI/eFormAPI.Web/Resources/SharedResource.et.resx index 7a862fd314..81061e5c10 100644 --- a/eFormAPI/eFormAPI.Web/Resources/SharedResource.et.resx +++ b/eFormAPI/eFormAPI.Web/Resources/SharedResource.et.resx @@ -78,6 +78,9 @@ Seadme kasutaja "{0}" kustutamine õnnestus + + Ainult esimene kasutaja saab seadme kasutajaid kustutada + Seadme kasutaja värskendamine õnnestus diff --git a/eFormAPI/eFormAPI.Web/Resources/SharedResource.fi.resx b/eFormAPI/eFormAPI.Web/Resources/SharedResource.fi.resx index 92b5e5e66f..8d6727b361 100644 --- a/eFormAPI/eFormAPI.Web/Resources/SharedResource.fi.resx +++ b/eFormAPI/eFormAPI.Web/Resources/SharedResource.fi.resx @@ -78,6 +78,9 @@ Laitteen käyttäjän "{0}" poistaminen onnistui + + Vain ensimmäinen käyttäjä voi poistaa laitteen käyttäjiä + Laitteen käyttäjän päivitys onnistui diff --git a/eFormAPI/eFormAPI.Web/Resources/SharedResource.fr.resx b/eFormAPI/eFormAPI.Web/Resources/SharedResource.fr.resx index ee914917cd..12602e9895 100644 --- a/eFormAPI/eFormAPI.Web/Resources/SharedResource.fr.resx +++ b/eFormAPI/eFormAPI.Web/Resources/SharedResource.fr.resx @@ -78,6 +78,9 @@ L'utilisateur de l'appareil "{0}" a été supprimé avec succès + + Seul le premier utilisateur peut supprimer des utilisateurs de l'appareil + L'utilisateur de l'appareil a été mis à jour avec succès diff --git a/eFormAPI/eFormAPI.Web/Resources/SharedResource.hr.resx b/eFormAPI/eFormAPI.Web/Resources/SharedResource.hr.resx index b1a1f6eaf0..288b675d28 100644 --- a/eFormAPI/eFormAPI.Web/Resources/SharedResource.hr.resx +++ b/eFormAPI/eFormAPI.Web/Resources/SharedResource.hr.resx @@ -78,6 +78,9 @@ Korisnik uređaja "{0}" uspješno je izbrisan + + Samo prvi korisnik može brisati korisnike uređaja + Korisnik uređaja je uspješno ažuriran diff --git a/eFormAPI/eFormAPI.Web/Resources/SharedResource.hu.resx b/eFormAPI/eFormAPI.Web/Resources/SharedResource.hu.resx index b27757181b..a2cfb1fbbc 100644 --- a/eFormAPI/eFormAPI.Web/Resources/SharedResource.hu.resx +++ b/eFormAPI/eFormAPI.Web/Resources/SharedResource.hu.resx @@ -78,6 +78,9 @@ „{0}” eszközfelhasználó sikeresen törölve + + Csak az első felhasználó törölhet eszközfelhasználókat + Az eszközfelhasználó sikeresen frissítve diff --git a/eFormAPI/eFormAPI.Web/Resources/SharedResource.is.resx b/eFormAPI/eFormAPI.Web/Resources/SharedResource.is.resx index 27e6ab88c3..9cc73cec3c 100644 --- a/eFormAPI/eFormAPI.Web/Resources/SharedResource.is.resx +++ b/eFormAPI/eFormAPI.Web/Resources/SharedResource.is.resx @@ -78,6 +78,9 @@ Tækjanotandanum "{0}" var eytt + + Aðeins fyrsti notandinn getur eytt tækjanotendum + Tækjanotandi uppfærður diff --git a/eFormAPI/eFormAPI.Web/Resources/SharedResource.it.resx b/eFormAPI/eFormAPI.Web/Resources/SharedResource.it.resx index 6c8e4a1188..5587bdb9f8 100644 --- a/eFormAPI/eFormAPI.Web/Resources/SharedResource.it.resx +++ b/eFormAPI/eFormAPI.Web/Resources/SharedResource.it.resx @@ -78,6 +78,9 @@ L'utente del dispositivo "{0}" è stato eliminato correttamente + + Solo il primo utente può eliminare gli utenti del dispositivo + Utente del dispositivo aggiornato correttamente diff --git a/eFormAPI/eFormAPI.Web/Resources/SharedResource.lt.resx b/eFormAPI/eFormAPI.Web/Resources/SharedResource.lt.resx index ab920c1fa4..16b8644805 100644 --- a/eFormAPI/eFormAPI.Web/Resources/SharedResource.lt.resx +++ b/eFormAPI/eFormAPI.Web/Resources/SharedResource.lt.resx @@ -78,6 +78,9 @@ Įrenginio naudotojas „{0}“ sėkmingai ištrintas + + Tik pirmasis naudotojas gali ištrinti įrenginio naudotojus + Įrenginio naudotojas sėkmingai atnaujintas diff --git a/eFormAPI/eFormAPI.Web/Resources/SharedResource.lv.resx b/eFormAPI/eFormAPI.Web/Resources/SharedResource.lv.resx index c18384514a..13a8456a0e 100644 --- a/eFormAPI/eFormAPI.Web/Resources/SharedResource.lv.resx +++ b/eFormAPI/eFormAPI.Web/Resources/SharedResource.lv.resx @@ -78,6 +78,9 @@ Ierīces lietotājs “{0}” ir veiksmīgi izdzēsts + + Tikai pirmais lietotājs var dzēst ierīces lietotājus + Ierīces lietotājs ir veiksmīgi atjaunināts diff --git a/eFormAPI/eFormAPI.Web/Resources/SharedResource.nl.resx b/eFormAPI/eFormAPI.Web/Resources/SharedResource.nl.resx index e102273c06..fc1ed06ad3 100644 --- a/eFormAPI/eFormAPI.Web/Resources/SharedResource.nl.resx +++ b/eFormAPI/eFormAPI.Web/Resources/SharedResource.nl.resx @@ -78,6 +78,9 @@ Apparaatgebruiker "{0}" is succesvol verwijderd + + Alleen de eerste gebruiker kan apparaatgebruikers verwijderen + Apparaatgebruiker succesvol bijgewerkt diff --git a/eFormAPI/eFormAPI.Web/Resources/SharedResource.no.resx b/eFormAPI/eFormAPI.Web/Resources/SharedResource.no.resx index 3ad77411d7..69d2e864c8 100644 --- a/eFormAPI/eFormAPI.Web/Resources/SharedResource.no.resx +++ b/eFormAPI/eFormAPI.Web/Resources/SharedResource.no.resx @@ -78,6 +78,9 @@ Enhetsbruker «{0}» ble slettet + + Bare den første brukeren kan slette enhetsbrukere + Enhetsbruker er oppdatert diff --git a/eFormAPI/eFormAPI.Web/Resources/SharedResource.pl.resx b/eFormAPI/eFormAPI.Web/Resources/SharedResource.pl.resx index 05b7f56a44..99dda9f26d 100644 --- a/eFormAPI/eFormAPI.Web/Resources/SharedResource.pl.resx +++ b/eFormAPI/eFormAPI.Web/Resources/SharedResource.pl.resx @@ -78,6 +78,9 @@ Użytkownik urządzenia „{0}” został pomyślnie usunięty + + Tylko pierwszy użytkownik może usuwać użytkowników urządzenia + Użytkownik urządzenia został pomyślnie zaktualizowany diff --git a/eFormAPI/eFormAPI.Web/Resources/SharedResource.pt.resx b/eFormAPI/eFormAPI.Web/Resources/SharedResource.pt.resx index e7d4919250..4f4c64a2e8 100644 --- a/eFormAPI/eFormAPI.Web/Resources/SharedResource.pt.resx +++ b/eFormAPI/eFormAPI.Web/Resources/SharedResource.pt.resx @@ -78,6 +78,9 @@ Usuário do dispositivo "{0}" excluído com sucesso + + Apenas o primeiro usuário pode excluir usuários do dispositivo + Usuário do dispositivo atualizado com sucesso diff --git a/eFormAPI/eFormAPI.Web/Resources/SharedResource.resx b/eFormAPI/eFormAPI.Web/Resources/SharedResource.resx index e1256b6f91..66d5dc1859 100644 --- a/eFormAPI/eFormAPI.Web/Resources/SharedResource.resx +++ b/eFormAPI/eFormAPI.Web/Resources/SharedResource.resx @@ -195,6 +195,9 @@ Device User "{0}" deleted successfully + + Only the first user can delete device users + Device User updated successfully diff --git a/eFormAPI/eFormAPI.Web/Resources/SharedResource.ro.resx b/eFormAPI/eFormAPI.Web/Resources/SharedResource.ro.resx index 66d877aa92..fea06f1fe7 100644 --- a/eFormAPI/eFormAPI.Web/Resources/SharedResource.ro.resx +++ b/eFormAPI/eFormAPI.Web/Resources/SharedResource.ro.resx @@ -78,6 +78,9 @@ Utilizatorul dispozitivului „{0}” a fost șters cu succes + + Doar primul utilizator poate șterge utilizatorii dispozitivului + Utilizatorul dispozitivului a fost actualizat cu succes diff --git a/eFormAPI/eFormAPI.Web/Resources/SharedResource.sk.resx b/eFormAPI/eFormAPI.Web/Resources/SharedResource.sk.resx index e6bd31eb17..7997c8be36 100644 --- a/eFormAPI/eFormAPI.Web/Resources/SharedResource.sk.resx +++ b/eFormAPI/eFormAPI.Web/Resources/SharedResource.sk.resx @@ -78,6 +78,9 @@ Používateľ zariadenia „{0}“ bol úspešne odstránený + + Iba prvý používateľ môže odstraňovať používateľov zariadenia + Používateľ zariadenia bol úspešne aktualizovaný diff --git a/eFormAPI/eFormAPI.Web/Resources/SharedResource.sl.resx b/eFormAPI/eFormAPI.Web/Resources/SharedResource.sl.resx index efb7e8f27e..55f3b0b134 100644 --- a/eFormAPI/eFormAPI.Web/Resources/SharedResource.sl.resx +++ b/eFormAPI/eFormAPI.Web/Resources/SharedResource.sl.resx @@ -78,6 +78,9 @@ Uporabnik naprave »{0}« je bil uspešno izbrisan + + Samo prvi uporabnik lahko briše uporabnike naprave + Uporabnik naprave je uspešno posodobljen diff --git a/eFormAPI/eFormAPI.Web/Resources/SharedResource.sv.resx b/eFormAPI/eFormAPI.Web/Resources/SharedResource.sv.resx index 3a013210d6..7f5e4b47ef 100644 --- a/eFormAPI/eFormAPI.Web/Resources/SharedResource.sv.resx +++ b/eFormAPI/eFormAPI.Web/Resources/SharedResource.sv.resx @@ -78,6 +78,9 @@ Enhetsanvändaren "{0}" har tagits bort + + Endast den första användaren kan ta bort enhetsanvändare + Enhetsanvändaren har uppdaterats diff --git a/eFormAPI/eFormAPI.Web/Resources/SharedResource.uk.resx b/eFormAPI/eFormAPI.Web/Resources/SharedResource.uk.resx index 96ad0ed272..01e19bb0a5 100644 --- a/eFormAPI/eFormAPI.Web/Resources/SharedResource.uk.resx +++ b/eFormAPI/eFormAPI.Web/Resources/SharedResource.uk.resx @@ -137,6 +137,9 @@ Пристрій користувача "{0}" успішно видалений + + Лише перший користувач може видаляти користувачів пристрою + Пристрій користувача успішно оновлено diff --git a/eFormAPI/eFormAPI.Web/Services/AuthService.cs b/eFormAPI/eFormAPI.Web/Services/AuthService.cs index 311ac5a12a..254e080443 100644 --- a/eFormAPI/eFormAPI.Web/Services/AuthService.cs +++ b/eFormAPI/eFormAPI.Web/Services/AuthService.cs @@ -185,6 +185,8 @@ public async Task> RefreshToken() $"Role for user {userService.UserId} not found"); } + var firstUserIdInDb = await userService.GetFirstUserIdInDb(); + return new OperationDataResult(true, new EformAuthorizeResult { Id = user.Id, @@ -194,6 +196,7 @@ public async Task> RefreshToken() ExpiresIn = token.expireIn, FirstName = user.FirstName, LastName = user.LastName, + IsFirstUser = user.Id == firstUserIdInDb, AvatarUrl = user.ProfilePictureSnapshot != null ? $"api/images/login-page-images?fileName={user.ProfilePictureSnapshot}" : $"https://www.gravatar.com/avatar/{user.EmailSha256}?s=32&d=identicon" diff --git a/eFormAPI/eFormAPI.Web/Services/DeviceUsersService.cs b/eFormAPI/eFormAPI.Web/Services/DeviceUsersService.cs index 9f163ee9d5..738e41509f 100644 --- a/eFormAPI/eFormAPI.Web/Services/DeviceUsersService.cs +++ b/eFormAPI/eFormAPI.Web/Services/DeviceUsersService.cs @@ -34,6 +34,7 @@ namespace eFormAPI.Web.Services; using System.Threading.Tasks; using Abstractions; using Abstractions.Advanced; +using Infrastructure.Helpers; using Infrastructure.Models; using Infrastructure.Models.DeviceUsers; using Microsoft.EntityFrameworkCore; @@ -45,6 +46,7 @@ namespace eFormAPI.Web.Services; public class DeviceUsersService( ILocalizationService localizationService, IEFormCoreService coreHelper, + IUserService userService, ILogger logger) : IDeviceUsersService { @@ -272,6 +274,13 @@ public async Task Update(DeviceUserModel deviceUserModel) public async Task Delete(int id) { + // Only the first user may delete a device user; everyone else is refused before the SDK. + if (!await userService.IsFirstUserAsync()) + { + return new OperationResult(false, + localizationService.GetString("OnlyTheFirstUserCanDeleteWorkers")); + } + try { var core = await coreHelper.GetCore(); diff --git a/eFormAPI/eFormAPI.Web/Services/SitesService.cs b/eFormAPI/eFormAPI.Web/Services/SitesService.cs index e5bca473c6..f6955016c6 100644 --- a/eFormAPI/eFormAPI.Web/Services/SitesService.cs +++ b/eFormAPI/eFormAPI.Web/Services/SitesService.cs @@ -34,6 +34,7 @@ namespace eFormAPI.Web.Services; using System.Threading.Tasks; using Abstractions; using Abstractions.Advanced; +using Infrastructure.Helpers; using Infrastructure.Models.Sites; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; @@ -45,6 +46,7 @@ namespace eFormAPI.Web.Services; public class SitesService( IEFormCoreService coreHelper, ILocalizationService localizationService, + IUserService userService, ILogger logger) : ISitesService { @@ -240,6 +242,13 @@ public async Task Update(SiteUpdateModel updateModel) public async Task Delete(int id) { + // Only the first user may delete a site (a device user); everyone else is refused before the SDK. + if (!await userService.IsFirstUserAsync()) + { + return new OperationResult(false, + localizationService.GetString("OnlyTheFirstUserCanDeleteWorkers")); + } + try { var core = await coreHelper.GetCore(); diff --git a/eform-client/src/app/modules/advanced/components/sites/sites/sites.component.html b/eform-client/src/app/modules/advanced/components/sites/sites/sites.component.html index 80bb7fc305..50f79319e0 100644 --- a/eform-client/src/app/modules/advanced/components/sites/sites/sites.component.html +++ b/eform-client/src/app/modules/advanced/components/sites/sites/sites.component.html @@ -43,7 +43,7 @@ -
+
@@ -52,7 +52,7 @@ @@ -100,7 +100,7 @@